mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.31.0
* Added: Option for displaying larger embeds in chat for supported sources. This won't do anything until the link service is updated with support. * Added: Support for v6 rich content for embeds, tool-tips, and the rich content debugger. * Changed: Limit the width of rich content embeds in portrait mode. * Fixed: Clicking badges not working correctly. * Fixed: Rich embeds being rendered when an unsupported version is returned from the embed server. * Fixed: The month being off by one in the default filename when saving a settings backup. * Fixed: The Chat Identity entry not appearing in the chat settings menu when appropriate. * API Added: `Mutex()` class for limiting something to a certain number of accessors at once.
This commit is contained in:
parent
97c96be276
commit
e704677e84
14 changed files with 228 additions and 104 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.30.1",
|
||||
"version": "4.31.0",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
|
|
|
@ -412,50 +412,17 @@ export default class Badges extends Module {
|
|||
tip.add_class = 'ffz__tooltip--badges';
|
||||
|
||||
const show_previews = this.parent.context.get('tooltip.badge-images');
|
||||
let container = target.parentElement.parentElement;
|
||||
if ( ! container.dataset.roomId )
|
||||
container = target.closest('[data-room-id]');
|
||||
const ds = this.getBadgeData(target);
|
||||
|
||||
const room_id = container?.dataset?.roomId,
|
||||
room_login = container?.dataset?.room,
|
||||
out = [];
|
||||
const out = [];
|
||||
|
||||
let data;
|
||||
if ( target.dataset.badgeData )
|
||||
data = JSON.parse(target.dataset.badgeData);
|
||||
else {
|
||||
const badge_idx = target.dataset.badgeIdx;
|
||||
let message;
|
||||
|
||||
if ( container.message )
|
||||
message = container.message;
|
||||
else {
|
||||
const fine = this.resolve('site.fine');
|
||||
|
||||
if ( fine ) {
|
||||
message = container[fine.accessor]?.return?.stateNode?.props?.message;
|
||||
if ( ! message )
|
||||
message = fine.searchParent(container, n => n.props?.message)?.props?.message;
|
||||
if ( ! message )
|
||||
message = fine.searchParent(container, n => n.props?.node)?.props?.node?._ffz_message;
|
||||
if ( ! message )
|
||||
message = fine.searchParent(container, n => n.props?.messageContext)?.props?.messageContext?.comment?._ffz_message;
|
||||
}
|
||||
}
|
||||
|
||||
if ( message?._ffz_message)
|
||||
message = message._ffz_message;
|
||||
if ( message )
|
||||
data = message.ffz_badge_cache?.[badge_idx]?.[1]?.badges;
|
||||
}
|
||||
|
||||
if ( data == null )
|
||||
if ( ds.data == null )
|
||||
return out;
|
||||
|
||||
for(const d of data) {
|
||||
for(const d of ds.data) {
|
||||
const p = d.provider;
|
||||
if ( p === 'twitch' ) {
|
||||
const bd = this.getTwitchBadge(d.badge, d.version, room_id, room_login),
|
||||
const bd = this.getTwitchBadge(d.badge, d.version, ds.room_id, ds.room_login),
|
||||
global_badge = this.getTwitchBadge(d.badge, d.version, null, null, true) || {};
|
||||
if ( ! bd )
|
||||
continue;
|
||||
|
@ -489,14 +456,6 @@ export default class Badges extends Module {
|
|||
{title}
|
||||
</div>);
|
||||
|
||||
/*out.push(e('div', {className: 'ffz-badge-tip'}, [
|
||||
show_previews && e('img', {
|
||||
className: 'preview-image ffz-badge',
|
||||
src: bd.image4x
|
||||
}),
|
||||
bd.title
|
||||
]));*/
|
||||
|
||||
} else if ( p === 'ffz' ) {
|
||||
out.push(<div class="ffz-badge-tip">
|
||||
{show_previews && d.image && <div
|
||||
|
@ -508,17 +467,6 @@ export default class Badges extends Module {
|
|||
/>}
|
||||
{d.title}
|
||||
</div>);
|
||||
|
||||
/*out.push(e('div', {className: 'ffz-badge-tip'}, [
|
||||
show_previews && e('div', {
|
||||
className: 'preview-image ffz-badge',
|
||||
style: {
|
||||
backgroundColor: d.color,
|
||||
backgroundImage: `url("${d.image}")`
|
||||
}
|
||||
}),
|
||||
d.title
|
||||
]));*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -527,31 +475,74 @@ export default class Badges extends Module {
|
|||
}
|
||||
|
||||
|
||||
getBadgeData(target) {
|
||||
let container = target.parentElement?.parentElement;
|
||||
if ( ! container?.dataset?.roomId )
|
||||
container = target.closest('[data-room-id]');
|
||||
|
||||
const room_id = container?.dataset?.roomId,
|
||||
room_login = container?.dataset?.room,
|
||||
|
||||
user_id = container?.dataset?.userId,
|
||||
user_login = container?.dataset?.user;
|
||||
|
||||
let data;
|
||||
if (target.dataset.badgeData )
|
||||
data = JSON.parse(target.dataset.badgeData);
|
||||
else {
|
||||
const badge_idx = target.dataset.badgeIdx;
|
||||
let message;
|
||||
|
||||
if ( container.message )
|
||||
message = container.message;
|
||||
else {
|
||||
const fine = this.resolve('site.fine');
|
||||
|
||||
if ( fine ) {
|
||||
message = container[fine.accessor]?.return?.stateNode?.props?.message;
|
||||
if ( ! message )
|
||||
message = fine.searchParent(container, n => n.props?.message)?.props?.message;
|
||||
if ( ! message )
|
||||
message = fine.searchParent(container, n => n.props?.node)?.props?.node?._ffz_message;
|
||||
if ( ! message )
|
||||
message = fine.searchParent(container, n => n.props?.messageContext)?.props?.messageContext?.comment?._ffz_message;
|
||||
if ( ! message )
|
||||
message = fine.searchParent(container, n => n._ffzIdentityMsg, 50)?._ffzIdentityMsg;
|
||||
}
|
||||
}
|
||||
|
||||
if ( message?._ffz_message)
|
||||
message = message._ffz_message;
|
||||
if ( message )
|
||||
data = message.ffz_badge_cache?.[badge_idx]?.[1]?.badges;
|
||||
}
|
||||
|
||||
return {
|
||||
room_id: room_id,
|
||||
room_login: room_login,
|
||||
user_id: user_id,
|
||||
user_login: user_login,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
handleClick(event) {
|
||||
if ( ! this.parent.context.get('chat.badges.clickable') )
|
||||
return;
|
||||
|
||||
const target = event.target;
|
||||
let container = target.parentElement.parentElement;
|
||||
if ( ! container.dataset.roomId )
|
||||
container = target.closest('[data-room-id]');
|
||||
const ds = this.getBadgeData(target);
|
||||
|
||||
const ds = container?.dataset,
|
||||
room_id = ds?.roomId,
|
||||
room_login = ds?.room,
|
||||
user_id = ds?.userId,
|
||||
user_login = ds?.user,
|
||||
data = JSON.parse(target.dataset.badgeData);
|
||||
|
||||
if ( data == null )
|
||||
if ( ds.data == null )
|
||||
return;
|
||||
|
||||
let url = null;
|
||||
|
||||
for(const d of data) {
|
||||
for(const d of ds.data) {
|
||||
const p = d.provider;
|
||||
if ( p === 'twitch' ) {
|
||||
const bd = this.getTwitchBadge(d.badge, d.version, room_id, room_login),
|
||||
const bd = this.getTwitchBadge(d.badge, d.version, ds.room_id, ds.room_login),
|
||||
global_badge = this.getTwitchBadge(d.badge, d.version, null, null, true) || {};
|
||||
if ( ! bd )
|
||||
continue;
|
||||
|
@ -560,8 +551,8 @@ export default class Badges extends Module {
|
|||
url = bd.click_url;
|
||||
else if ( global_badge.click_url )
|
||||
url = global_badge.click_url;
|
||||
else if ( (bd.click_action === 'sub' || global_badge.click_action === 'sub') && room_login )
|
||||
url = `https://www.twitch.tv/subs/${room_login}`;
|
||||
else if ( (bd.click_action === 'sub' || global_badge.click_action === 'sub') && ds.room_login )
|
||||
url = `https://www.twitch.tv/subs/${ds.room_login}`;
|
||||
else
|
||||
continue;
|
||||
|
||||
|
@ -570,7 +561,7 @@ export default class Badges extends Module {
|
|||
} else if ( p === 'ffz' ) {
|
||||
const badge = this.badges[target.dataset.badge];
|
||||
if ( badge?.click_handler ) {
|
||||
url = badge.click_handler(user_id, user_login, room_id, room_login, data, event);
|
||||
url = badge.click_handler(ds.user_id, ds.user_login, ds.room_id, ds.room_login, ds.data, event);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,15 +8,18 @@ let tokenizer;
|
|||
|
||||
|
||||
export default {
|
||||
props: ['data', 'url', 'events', 'forceFull', 'forceUnsafe', 'forceMedia'],
|
||||
props: ['data', 'url', 'events', 'forceFull', 'forceUnsafe', 'forceMedia', 'forceMid'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
has_tokenizer: false,
|
||||
loaded: false,
|
||||
version: null,
|
||||
fragments: {},
|
||||
error: null,
|
||||
accent: null,
|
||||
short: null,
|
||||
mid: null,
|
||||
full: null,
|
||||
unsafe: false,
|
||||
urls: null,
|
||||
|
@ -103,9 +106,12 @@ export default {
|
|||
|
||||
this.loaded = false;
|
||||
this.error = null;
|
||||
this.version = null;
|
||||
this.accent = null;
|
||||
this.short = null;
|
||||
this.mid = null;
|
||||
this.full = null;
|
||||
this.fragments = {};
|
||||
this.unsafe = false;
|
||||
this.urls = null;
|
||||
this.allow_media = false;
|
||||
|
@ -164,10 +170,13 @@ export default {
|
|||
}
|
||||
|
||||
this.loaded = true;
|
||||
this.version = data.v;
|
||||
this.error = data.error;
|
||||
this.accent = data.accent;
|
||||
this.short = data.short;
|
||||
this.mid = data.mid;
|
||||
this.full = data.full;
|
||||
this.fragments = data.fragments ?? {};
|
||||
this.unsafe = data.unsafe;
|
||||
this.urls = data.urls;
|
||||
this.allow_media = data.allow_media;
|
||||
|
@ -214,14 +223,22 @@ export default {
|
|||
},
|
||||
|
||||
renderBody(h) {
|
||||
if ( this.has_tokenizer && this.loaded && (this.forceFull ? this.full : this.short) ) {
|
||||
let body = this.forceFull ? this.full :
|
||||
this.forceMid ? this.mid : this.short;
|
||||
|
||||
if ( this.has_tokenizer && this.version && this.version > tokenizer.VERSION )
|
||||
body = null;
|
||||
|
||||
if ( this.has_tokenizer && this.loaded && body ) {
|
||||
return h('div', {
|
||||
class: 'ffz--card-rich tw-full-width tw-overflow-hidden tw-flex tw-flex-column'
|
||||
}, tokenizer.renderTokens(this.forceFull ? this.full : this.short, h, {
|
||||
}, tokenizer.renderTokens(body, h, {
|
||||
vue: true,
|
||||
tList: (...args) => this.tList(...args),
|
||||
i18n: this.getI18n(),
|
||||
|
||||
fragments: this.fragments,
|
||||
|
||||
allow_media: this.forceMedia ?? this.allow_media,
|
||||
allow_unsafe: this.forceUnsafe ?? this.allow_unsafe
|
||||
}));
|
||||
|
@ -234,6 +251,9 @@ export default {
|
|||
if ( this.loaded && this.forceFull && ! this.full ) {
|
||||
description = 'null';
|
||||
|
||||
} else if ( this.loaded && this.forceMid && ! this.mid ) {
|
||||
description = 'null -- will use short instead';
|
||||
|
||||
} else if ( this.error ) {
|
||||
title = this.t('card.error', 'An error occurred.');
|
||||
description = this.error;
|
||||
|
|
|
@ -224,6 +224,16 @@ export default class Chat extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.rich.want-mid', {
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Chat > Appearance >> Rich Content',
|
||||
title: 'Display larger rich content in chat.',
|
||||
description: 'This enables the use of bigger rich content embeds in chat. This is **not** recommended for most users and/or chats.\n\n**Note:** Enabling this may cause chat to scroll at inopportune times due to content loading. Moderators should not use this feature.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.rich.hide-tokens', {
|
||||
default: false,
|
||||
ui: {
|
||||
|
@ -1879,11 +1889,13 @@ export default class Chat extends Module {
|
|||
|
||||
const providers = this.__rich_providers;
|
||||
|
||||
const want_mid = this.context.get('chat.rich.want-mid');
|
||||
|
||||
for(const token of tokens) {
|
||||
for(const provider of providers)
|
||||
if ( provider.test.call(this, token, msg) ) {
|
||||
token.hidden = provider.can_hide_token && (this.context.get('chat.rich.hide-tokens') || provider.hide_token);
|
||||
return provider.process.call(this, token);
|
||||
return provider.process.call(this, token, want_mid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,11 +38,12 @@ export const Links = {
|
|||
return token.type === 'link'
|
||||
},
|
||||
|
||||
process(token) {
|
||||
process(token, want_mid) {
|
||||
return {
|
||||
card_tooltip: true,
|
||||
url: token.url,
|
||||
timeout: 0,
|
||||
want_mid,
|
||||
|
||||
getData: async (refresh = false) => {
|
||||
let data;
|
||||
|
|
|
@ -56,8 +56,6 @@ function datasetBool(value) {
|
|||
return value == null ? null : value === 'true';
|
||||
}
|
||||
|
||||
const TOOLTIP_VERSION = 5;
|
||||
|
||||
export const Links = {
|
||||
type: 'link',
|
||||
priority: 50,
|
||||
|
@ -91,12 +89,15 @@ export const Links = {
|
|||
import(/* webpackChunkName: 'rich_tokens' */ 'utilities/rich_tokens'),
|
||||
this.get_link_info(url)
|
||||
]).then(([rich_tokens, data]) => {
|
||||
if ( ! data || (data.v || 1) > TOOLTIP_VERSION )
|
||||
if ( ! data || (data.v || 1) > rich_tokens.VERSION )
|
||||
return '';
|
||||
|
||||
const ctx = {
|
||||
tList: (...args) => this.i18n.tList(...args),
|
||||
i18n: this.i18n,
|
||||
|
||||
fragments: data.fragments,
|
||||
|
||||
allow_media: show_images,
|
||||
allow_unsafe: show_unsafe,
|
||||
onload: () => requestAnimationFrame(() => tip.update())
|
||||
|
@ -111,12 +112,14 @@ export const Links = {
|
|||
if ( data.full ) {
|
||||
content = rich_tokens.renderTokens(data.full, createElement, ctx);
|
||||
|
||||
} else {
|
||||
if ( data.short ) {
|
||||
content = rich_tokens.renderTokens(data.short, createElement, ctx);
|
||||
} else
|
||||
content = this.i18n.t('card.empty', 'No data was returned.');
|
||||
}
|
||||
} else if ( data.mid ) {
|
||||
content = rich_tokens.renderTokens(data.mid, createElement, ctx);
|
||||
|
||||
} else if ( data.short ) {
|
||||
content = rich_tokens.renderTokens(data.short, createElement, ctx);
|
||||
|
||||
} else
|
||||
content = this.i18n.t('card.empty', 'No data was returned.');
|
||||
|
||||
if ( ! data.urls )
|
||||
return content;
|
||||
|
@ -1038,7 +1041,7 @@ export const CheerEmotes = {
|
|||
|
||||
if ( length > 12 ) {
|
||||
out.push(<br />);
|
||||
out.push(this.i18n.t('tooltip.bits.more', '(and {count} more)', length-12));
|
||||
out.push(this.i18n.t('tooltip.bits.more', '(and {count, number} more)', length-12));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -156,6 +156,22 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-flex tw-mg-b-1 tw-full-width">
|
||||
<label>
|
||||
{{ t('debug.link-provider.mid-embed', 'Mid Embed') }}
|
||||
</label>
|
||||
<div class="tw-full-width tw-overflow-hidden">
|
||||
<chat-rich
|
||||
v-if="rich_data"
|
||||
:data="rich_data"
|
||||
:url="url"
|
||||
:force-mid="true"
|
||||
:force-media="force_media"
|
||||
:force-unsafe="force_unsafe"
|
||||
:events="events"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-flex tw-mg-b-1 tw-full-width">
|
||||
<label>
|
||||
{{ t('debug.link-provider.full-embed', 'Full Embed') }}
|
||||
|
@ -172,6 +188,14 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-flex tw-mg-b-1 tw-full-width">
|
||||
<label>
|
||||
{{ t('debug.link-provider.raw-length', 'Raw Length') }}
|
||||
</label>
|
||||
<div>
|
||||
{{ tNumber(length) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-flex tw-mg-b-1 tw-full-width">
|
||||
<label>
|
||||
{{ t('debug.link-provider.raw', 'Raw Data') }}
|
||||
|
@ -234,6 +258,7 @@ export default {
|
|||
rich_data: null,
|
||||
raw_loading: false,
|
||||
raw_data: null,
|
||||
length: 0,
|
||||
|
||||
force_media: state?.ffz_lt_media ?? true,
|
||||
force_unsafe: state?.ffz_lt_unsafe ?? false,
|
||||
|
@ -436,6 +461,7 @@ export default {
|
|||
|
||||
async refreshRaw() {
|
||||
this.raw_data = null;
|
||||
this.length = 0;
|
||||
if ( ! this.rich_data ) {
|
||||
this.raw_loading = false;
|
||||
return;
|
||||
|
@ -443,7 +469,9 @@ export default {
|
|||
|
||||
this.raw_loading = true;
|
||||
try {
|
||||
this.raw_data = JSON.stringify(await this.chat.get_link_info(this.url), null, '\t');
|
||||
const data = await this.chat.get_link_info(this.url);
|
||||
this.raw_data = JSON.stringify(data, null, '\t');
|
||||
this.length = JSON.stringify(data).length;
|
||||
} catch(err) {
|
||||
this.raw_data = `Error\n\n${err.toString()}`;
|
||||
}
|
||||
|
|
|
@ -389,7 +389,7 @@ export default class SettingsManager extends Module {
|
|||
|
||||
async generateBackupFile() {
|
||||
const now = new Date(),
|
||||
timestamp = `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}`;
|
||||
timestamp = `${now.getFullYear()}-${now.getMonth()+1}-${now.getDate()}`;
|
||||
|
||||
if ( await this._needsZipBackup() ) {
|
||||
const blob = await this._getZipBackup();
|
||||
|
|
|
@ -176,7 +176,14 @@ export default class RichContent extends Module {
|
|||
}
|
||||
|
||||
renderBody() {
|
||||
const doc = this.props.force_full ? this.state.full : this.state.short;
|
||||
let doc = this.props.force_full ? this.state.full :
|
||||
this.props.force_mid ? this.state.mid :
|
||||
((this.props.want_mid ? this.state.mid : null) ?? this.state.short);
|
||||
|
||||
if ( t.has_tokenizer && this.state.v && this.state.v > t.tokenizer.VERSION)
|
||||
doc = null;
|
||||
|
||||
//const doc = (this.props.force_full ? this.state.full : null) ?? (this.props.force_mid ? this.state.mid : null) ?? this.state.short;
|
||||
if ( t.has_tokenizer && this.state.loaded && doc ) {
|
||||
return (<div class="ffz-card-rich tw-full-width tw-overflow-hidden tw-flex tw-flex-column">
|
||||
{t.tokenizer.renderTokens(doc, createElement, {
|
||||
|
@ -184,6 +191,8 @@ export default class RichContent extends Module {
|
|||
tList: (...args) => t.i18n.tList(...args),
|
||||
i18n: t.i18n,
|
||||
|
||||
fragments: this.state.fragments,
|
||||
|
||||
allow_media: t.chat.context.get('tooltip.link-images'),
|
||||
allow_unsafe: t.chat.context.get('tooltip.link-nsfw-images')
|
||||
})}
|
||||
|
|
|
@ -145,6 +145,16 @@ export default class SettingsMenu extends Module {
|
|||
this.props.onCloseSettings();
|
||||
}
|
||||
|
||||
const msg = {
|
||||
user,
|
||||
badges,
|
||||
ffz_badges: t.badges.getBadges(user.id, user.login, this.props.channelID, this.props.channelLogin),
|
||||
roomID: this.props.channelID,
|
||||
roomLogin: this.props.channelLogin
|
||||
};
|
||||
|
||||
this._ffzIdentityMsg = msg;
|
||||
|
||||
return (<div class="ffz-identity">
|
||||
<div class="tw-mg-y-05 tw-pd-x-05">
|
||||
<p class="tw-c-text-alt-2 tw-font-size-6 tw-strong tw-upcase">
|
||||
|
@ -162,15 +172,9 @@ export default class SettingsMenu extends Module {
|
|||
<span
|
||||
class="ffz--editor-badges"
|
||||
data-room-id={this.props.channelID}
|
||||
data-room-login={this.props.channelLogin}
|
||||
data-room={this.props.channelLogin}
|
||||
>
|
||||
{t.badges.render({
|
||||
user,
|
||||
badges,
|
||||
ffz_badges: t.badges.getBadges(user.id, user.login, this.props.channelID, this.props.channelLogin),
|
||||
roomID: this.props.channelID,
|
||||
roomLogin: this.props.channelLogin
|
||||
}, createElement, true, true)}
|
||||
{t.badges.render(msg, createElement, true, true)}
|
||||
</span>
|
||||
|
||||
<span class="tw-strong notranslate" style={{color}}>
|
||||
|
@ -192,7 +196,8 @@ export default class SettingsMenu extends Module {
|
|||
const out = old_render.call(this);
|
||||
|
||||
try {
|
||||
const children = out?.props?.children?.props?.children?.[1]?.props?.children?.props?.children;
|
||||
const children = out?.props?.children?.props?.children?.props?.children?.[1]?.props?.children?.props?.children;
|
||||
//const children = out?.props?.children?.props?.children?.[1]?.props?.children?.props?.children;
|
||||
if ( Array.isArray(children) ) {
|
||||
const extra = this.ffzRenderIdentity();
|
||||
if ( extra )
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
--ffz-theater-height: calc(calc(100vw * 0.5625) + var(--ffz-portrait-extra-height));
|
||||
--ffz-chat-height: calc(100vh - var(--ffz-player-height));
|
||||
|
||||
.chat-shell .ffz--chat-card {
|
||||
--width: max(30rem, min(50%, calc(1.5 * var(--ffz-chat-width))));
|
||||
width: var(--width);
|
||||
margin-left: min(2rem, calc(100% - calc(4rem + var(--width))));
|
||||
}
|
||||
|
||||
& > div:first-child > div[class^="Layout-sc"] {
|
||||
.ffz--portrait-invert & {
|
||||
position: absolute;
|
||||
|
|
|
@ -61,6 +61,7 @@ export const UPDATE_TOKEN_SETTINGS = [
|
|||
'chat.emoji.style',
|
||||
'chat.bits.stack',
|
||||
'chat.rich.enabled',
|
||||
'chat.rich.want-mid',
|
||||
'chat.rich.hide-tokens',
|
||||
'chat.rich.all-links',
|
||||
'chat.rich.minimum-level',
|
||||
|
|
|
@ -94,6 +94,39 @@ export function timeout(promise, delay) {
|
|||
}
|
||||
|
||||
|
||||
export class Mutex {
|
||||
constructor(limit = 1) {
|
||||
this.limit = limit;
|
||||
this._active = 0;
|
||||
this._waiting = [];
|
||||
|
||||
this._done = this._done.bind(this);
|
||||
}
|
||||
|
||||
get available() { return this._active < this.limit }
|
||||
|
||||
_done() {
|
||||
this._active--;
|
||||
|
||||
while(this._active < this.limit && this._waiting.length > 0) {
|
||||
this._active++;
|
||||
const waiter = this._waiting.shift();
|
||||
waiter(this._done);
|
||||
}
|
||||
}
|
||||
|
||||
wait() {
|
||||
if ( this._active < this.limit) {
|
||||
this._active++;
|
||||
return Promise.resolve(this._done);
|
||||
}
|
||||
|
||||
return new Promise(s => this._waiting.push(s));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return a wrapper for a function that will only execute the function
|
||||
* a period of time after it has stopped being called.
|
||||
|
|
|
@ -8,6 +8,8 @@ import {has} from 'utilities/object';
|
|||
import Markdown from 'markdown-it';
|
||||
import MILA from 'markdown-it-link-attributes';
|
||||
|
||||
export const VERSION = 6;
|
||||
|
||||
export const TOKEN_TYPES = {};
|
||||
|
||||
const validate = (input, valid) => valid.includes(input) ? input : null;
|
||||
|
@ -243,6 +245,17 @@ export function renderTokens(tokens, createElement, ctx, markdown) {
|
|||
export default renderTokens;
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Token Type: Reference
|
||||
// ============================================================================
|
||||
|
||||
TOKEN_TYPES.ref = function(token, createElement, ctx) {
|
||||
const frag = ctx.fragments?.[token.name];
|
||||
if (frag )
|
||||
return renderTokens(frag, createElement, ctx);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Token Type: Box
|
||||
// ============================================================================
|
||||
|
@ -667,9 +680,11 @@ function header_normal(token, createElement, ctx) {
|
|||
content.appendChild(image);
|
||||
else
|
||||
content.insertBefore(image, content.firstChild);
|
||||
} else {
|
||||
console.warn('Add React support!');
|
||||
console.log(content);
|
||||
} else if ( Array.isArray(content?.props?.children) ) {
|
||||
if ( right )
|
||||
content.props.children.push(image);
|
||||
else
|
||||
content.props.children.unshift(image);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue