mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.40.0
* Added: Chat actions for modding and un-modding users. * Fixed: Settings not being removed when an add-on is unloaded. * Changed: Add a few new icons. * API Added: Add support for header backgrounds for rich token documents. * API Added: Methods for adding/updating emotes to and removing emotes from an emote set. * API Added: Context flag to disable FFZ's chat message processing. * API Changed: Add-ons can now be hot reloaded for development purposes. This feature may be somewhat unstable.
This commit is contained in:
parent
14400e16bc
commit
8e48021c43
35 changed files with 1285 additions and 214 deletions
|
@ -805,6 +805,68 @@
|
|||
"css": "volume-up",
|
||||
"code": 59464,
|
||||
"src": "elusive"
|
||||
},
|
||||
{
|
||||
"uid": "1fc437d46c5ef828375b6b3de577918d",
|
||||
"css": "unmod",
|
||||
"code": 59465,
|
||||
"src": "custom_icons",
|
||||
"selected": true,
|
||||
"svg": {
|
||||
"path": "M350 100A200 200 0 0 0 299.3 493.5C294.4 525.5 266.7 550 233.3 550A133.3 133.3 0 0 0 100 683.4V900H200V683.4C200 664.9 214.9 650 233.3 650 278.8 650 319.9 631.9 350 602.4 380.1 631.9 421.3 650 466.7 650 485.1 650 500 664.9 500 683.4V900H600V683.4A133.3 133.3 0 0 0 466.7 550C433.3 550 405.6 525.5 400.8 493.5A200.1 200.1 0 0 0 350 100ZM250 300A100 100 0 1 0 450 300 100 100 0 0 0 250 300ZM600 420.7L670.7 350 751.8 431.1 833 350 903.7 420.7 822.6 501.8 903.7 583 833 653.7 751.9 572.5 670.7 653.7 600 583 681.1 501.9 600 420.7Z",
|
||||
"width": 1000
|
||||
},
|
||||
"search": [
|
||||
"unmod"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "2c1f4d302aa8281c3ed4882568669043",
|
||||
"css": "mod",
|
||||
"code": 59466,
|
||||
"src": "custom_icons",
|
||||
"selected": true,
|
||||
"svg": {
|
||||
"path": "M350 100A200 200 0 0 0 299.3 493.5C294.4 525.5 266.7 550 233.3 550A133.3 133.3 0 0 0 100 683.4V900H200V683.4C200 664.9 214.9 650 233.3 650 278.8 650 319.9 631.9 350 602.4 380.1 631.9 421.3 650 466.7 650 485.1 650 500 664.9 500 683.4V900H600V683.4A133.3 133.3 0 0 0 466.7 550C433.3 550 405.6 525.5 400.8 493.5A200.1 200.1 0 0 0 350 100ZM250 300A100 100 0 1 0 450 300 100 100 0 0 0 250 300ZM750 350L900 500 750 650V550H600V450H750V350Z",
|
||||
"width": 1000
|
||||
},
|
||||
"search": [
|
||||
"mod"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "e436d990b8c910352dba1fe3e88d9ca3",
|
||||
"css": "flag",
|
||||
"code": 59467,
|
||||
"src": "custom_icons",
|
||||
"selected": true,
|
||||
"svg": {
|
||||
"path": "M900 100L700 400 900 700H200V900H100V100H900ZM200 600H713.1L579.8 400 713.1 200H200V600Z",
|
||||
"width": 1000
|
||||
},
|
||||
"search": [
|
||||
"flag"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "c56ae110cddeae77e2e904e33f9b9718",
|
||||
"css": "mange-suspicious",
|
||||
"code": 59468,
|
||||
"src": "custom_icons",
|
||||
"selected": true,
|
||||
"svg": {
|
||||
"path": "M450 550V650H550V550H450ZM250.2 197.3A500 500 0 0 0 475.9 116L500 100 524 116A500 500 0 0 0 801.5 200H850L825.3 482.1A450 450 0 0 1 623.1 819.6L500 900 376.9 819.6A450 450 0 0 1 174.7 482.1L150 200H198.6C215.9 200 233.1 199.1 250.2 197.3ZM258.9 296.9L274.3 473.4A350 350 0 0 0 431.6 735.9L500 780.6 568.4 735.9A350 350 0 0 0 725.7 473.4L741.2 296.9A600 600 0 0 1 550 244.8V450H450V244.8A600 600 0 0 1 258.8 296.9Z",
|
||||
"width": 1000
|
||||
},
|
||||
"search": [
|
||||
"mange-suspicious"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "5408be43f7c42bccee419c6be53fdef5",
|
||||
"css": "doc-text",
|
||||
"code": 61686,
|
||||
"src": "fontawesome"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.39.0",
|
||||
"version": "4.40.0",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
|
|
Binary file not shown.
|
@ -152,6 +152,14 @@
|
|||
|
||||
<glyph glyph-name="volume-up" unicode="" d="M0 169l0 360 203 0 307 250 0-858-307 248-203 0z m563 33q62 63 62 151t-62 152l60 65q90-90 92-219 0-125-92-213z m101-105q106 105 106 256t-106 258l66 62q131-133 131-319t-131-321z m100-98q146 147 146 354t-146 353l62 65q82-82 128-190t46-227-46-229-128-190z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="unmod" unicode="" d="M350 750a200 200 0 0 1-51-393c-5-32-32-57-66-57a133 133 0 0 1-133-133v-217h100v217c0 18 15 33 33 33 46 0 87 18 117 48 30-30 71-48 117-48 18 0 33-15 33-33v-217h100v217a133 133 0 0 1-133 133c-34 0-61 25-66 57a200 200 0 0 1-51 393z m-100-200a100 100 0 1 1 200 0 100 100 0 0 1-200 0z m350-121l71 71 81-81 81 81 71-71-81-81 81-81-71-71-81 82-81-82-71 71 81 81-81 81z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="mod" unicode="" d="M350 750a200 200 0 0 1-51-393c-5-32-32-57-66-57a133 133 0 0 1-133-133v-217h100v217c0 18 15 33 33 33 46 0 87 18 117 48 30-30 71-48 117-48 18 0 33-15 33-33v-217h100v217a133 133 0 0 1-133 133c-34 0-61 25-66 57a200 200 0 0 1-51 393z m-100-200a100 100 0 1 1 200 0 100 100 0 0 1-200 0z m500-50l150-150-150-150v100h-150v100h150v100z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="flag" unicode="" d="M900 750l-200-300 200-300h-700v-200h-100v800h800z m-700-500h513l-133 200 133 200h-513v-400z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="mange-suspicious" unicode="" d="M450 300v-100h100v100h-100z m-200 353a500 500 0 0 1 226 81l24 16 24-16a500 500 0 0 1 278-84h48l-25-282a450 450 0 0 0-202-338l-123-80-123 80a450 450 0 0 0-202 338l-25 282h49c17 0 34 1 51 3z m9-100l15-176a350 350 0 0 1 158-263l68-45 68 45a350 350 0 0 1 158 263l15 176a600 600 0 0 0-191 52v-205h-100v205a600 600 0 0 0-191-52z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="move" unicode="" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-215v-215h72q14 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-11 10-11 25t11 25 25 10h72v215h-215v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h215v215h-72q-14 0-25 10t-11 25 11 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26t-11-25-25-10h-72v-215h215v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="link-ext" unicode="" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
||||
|
@ -176,6 +184,8 @@
|
|||
|
||||
<glyph glyph-name="upload-cloud" unicode="" d="M714 368q0 8-5 13l-196 196q-5 5-13 5t-13-5l-196-196q-5-6-5-13 0-8 5-13t13-5h125v-196q0-8 5-13t12-5h108q7 0 12 5t5 13v196h125q8 0 13 5t5 13z m357-161q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 72 39 134t105 92q-1 17-1 24 0 118 84 202t202 84q87 0 159-49t105-129q40 35 93 35 59 0 101-42t42-101q0-43-23-77 72-17 119-76t46-133z" horiz-adv-x="1071.4" />
|
||||
|
||||
<glyph glyph-name="doc-text" unicode="" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-572 483q0 7 5 12t13 5h393q8 0 13-5t5-12v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36z m411-125q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z m0-143q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="reply" unicode="" d="M1000 225q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="smile" unicode="" d="M633 250q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
||||
|
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
146
src/addons.js
146
src/addons.js
|
@ -57,6 +57,8 @@ export default class AddonManager extends Module {
|
|||
isAddonExternal: id => this.isAddonExternal(id),
|
||||
enableAddon: id => this.enableAddon(id),
|
||||
disableAddon: id => this.disableAddon(id),
|
||||
reloadAddon: id => this.reloadAddon(id),
|
||||
canReloadAddon: id => this.canReloadAddon(id),
|
||||
isReloadRequired: () => this.reload_required,
|
||||
refresh: () => window.location.reload(),
|
||||
|
||||
|
@ -280,6 +282,150 @@ export default class AddonManager extends Module {
|
|||
return module.external || (module.constructor && module.constructor.external);
|
||||
}
|
||||
|
||||
canReloadAddon(id) {
|
||||
// Obviously we can't reload it if we don't have it.
|
||||
if ( ! this.hasAddon(id) )
|
||||
throw new Error(`Unknown add-on id: ${id}`);
|
||||
|
||||
// If the module isn't available, we can't reload it.
|
||||
let module = this.resolve(`addon.${id}`);
|
||||
if ( ! module )
|
||||
return false;
|
||||
|
||||
// If the module cannot be disabled, or it cannot be unloaded, then
|
||||
// we can't reload it.
|
||||
if ( ! module.canDisable() || ! module.canUnload() )
|
||||
return false;
|
||||
|
||||
// Check each child.
|
||||
if ( module.children )
|
||||
for(const child of Object.values(module.children))
|
||||
if ( ! child.canDisable() || ! child.canUnload() )
|
||||
return false;
|
||||
|
||||
// If we got here, we might be able to reload it.
|
||||
return true;
|
||||
}
|
||||
|
||||
async fullyUnloadModule(module) {
|
||||
if ( ! module )
|
||||
return;
|
||||
|
||||
if ( module.children )
|
||||
for(const child of Object.values(module.children))
|
||||
await this.fullyUnloadModule(child);
|
||||
|
||||
await module.disable();
|
||||
await module.unload();
|
||||
|
||||
// Clean up parent references.
|
||||
if ( module.parent && module.parent.children[module.name] === module )
|
||||
delete module.parent.children[module.name];
|
||||
|
||||
// Clean up all individual references.
|
||||
for(const entry of module.references) {
|
||||
const other = this.resolve(entry[0]),
|
||||
name = entry[1];
|
||||
if ( other && other[name] === module )
|
||||
other[name] = null;
|
||||
}
|
||||
|
||||
// Clean up the global reference.
|
||||
if ( this.__modules[module.__path] === module )
|
||||
delete this.__modules[module.__path]; /* = [
|
||||
module.dependents,
|
||||
module.load_dependents,
|
||||
module.references
|
||||
];*/
|
||||
|
||||
// Remove any events we didn't unregister.
|
||||
this.offContext(null, module);
|
||||
|
||||
// Do the same for settings.
|
||||
for(const ctx of this.settings.__contexts)
|
||||
ctx.offContext(null, module);
|
||||
|
||||
// Clean up all settings.
|
||||
for(const [key, def] of Array.from(this.settings.definitions.entries())) {
|
||||
if ( def && def.__source === module.__path ) {
|
||||
this.settings.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the logger too.
|
||||
module.__log = null;
|
||||
}
|
||||
|
||||
async reloadAddon(id) {
|
||||
const addon = this.getAddon(id),
|
||||
button = this.resolve('site.menu_button');
|
||||
if ( ! addon )
|
||||
throw new Error(`Unknown add-on id: ${id}`);
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
// Yeet the module into the abyss.
|
||||
// This will also yeet all children.
|
||||
let module = this.resolve(`addon.${id}`);
|
||||
if ( module )
|
||||
try {
|
||||
await this.fullyUnloadModule(module);
|
||||
} catch(err) {
|
||||
if ( button )
|
||||
button.addToast({
|
||||
title_i18n: 'addons.reload.toast-error',
|
||||
title: 'Error Reloading Add-On',
|
||||
text_i18n: 'addons.reload.toast-error.unload',
|
||||
text: 'Unable to unload existing modules for add-on "{addon_id}":\n\n{error}',
|
||||
icon: 'ffz-i-attention',
|
||||
addon_id: id,
|
||||
error: String(err)
|
||||
});
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Is there a script tab?
|
||||
let el = document.querySelector(`script#ffz-loaded-addon-${addon.id}`);
|
||||
if ( el )
|
||||
el.remove();
|
||||
|
||||
// Do unnatural things to webpack.
|
||||
if ( window.ffzAddonsWebpackJsonp )
|
||||
window.ffzAddonsWebpackJsonp = undefined;
|
||||
|
||||
// Now, reload it all~
|
||||
try {
|
||||
await this._enableAddon(id);
|
||||
} catch(err) {
|
||||
if ( button )
|
||||
button.addToast({
|
||||
title_i18n: 'addons.reload.toast-error',
|
||||
title: 'Error Reloading Add-On',
|
||||
text_i18n: 'addons.reload.toast-error.reload',
|
||||
text: 'Unable to load new module for add-on "{addon_id}":\n\n{error}',
|
||||
error: String(err),
|
||||
icon: 'ffz-i-attention',
|
||||
addon_id: id
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
|
||||
const end = performance.now();
|
||||
|
||||
if ( button )
|
||||
button.addToast({
|
||||
title_i18n: 'addons.reload.toast',
|
||||
title: 'Reloaded Add-On',
|
||||
text_i18n: 'addons.reload.toast.text',
|
||||
text: 'Successfully reloaded add-on "{addon_id}" in {duration}ms.',
|
||||
icon: 'ffz-i-info',
|
||||
addon_id: id,
|
||||
timeout: 5000,
|
||||
duration: Math.round(100 * (end - start)) / 100
|
||||
});
|
||||
}
|
||||
|
||||
async _enableAddon(id) {
|
||||
const addon = this.getAddon(id);
|
||||
if ( ! addon )
|
||||
|
|
|
@ -1124,6 +1124,8 @@ export default class Actions extends Module {
|
|||
if ( target._ffz_tooltip )
|
||||
target._ffz_tooltip.hide();
|
||||
|
||||
|
||||
|
||||
return data.definition.click.call(this, event, data);
|
||||
}
|
||||
|
||||
|
|
|
@ -499,6 +499,84 @@ export const untimeout = {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Mod and Unmod User
|
||||
// ============================================================================
|
||||
|
||||
export const mod = {
|
||||
presets: [{
|
||||
appearance: {
|
||||
type: 'icon',
|
||||
icon: 'ffz-i-mod'
|
||||
}
|
||||
}],
|
||||
|
||||
required_context: ['room', 'user'],
|
||||
|
||||
title: 'Mod User',
|
||||
|
||||
tooltip(data) {
|
||||
return this.i18n.t('chat.actions.mod.tooltip', 'Mod {user.login}', {user: data.user});
|
||||
},
|
||||
|
||||
hidden(data, message, current_room, current_user, mod_icons, instance) {
|
||||
// You cannot mod mods.
|
||||
if ( message.user.type === 'mod' )
|
||||
return true;
|
||||
|
||||
// You cannot mod the broadcaster.
|
||||
if ( message.user.id === current_room.id )
|
||||
return true;
|
||||
|
||||
// Only the broadcaster can mod, otherwise.
|
||||
return current_room.id !== current_user.id;
|
||||
},
|
||||
|
||||
click(event, data) {
|
||||
this.sendMessage(data.room.login, `/mod ${data.user.login}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const unmod = {
|
||||
presets: [{
|
||||
appearance: {
|
||||
type: 'icon',
|
||||
icon: 'ffz-i-unmod'
|
||||
}
|
||||
}],
|
||||
|
||||
required_context: ['room', 'user'],
|
||||
|
||||
title: 'Un-Mod User',
|
||||
|
||||
tooltip(data) {
|
||||
return this.i18n.t('chat.actions.unmod.tooltip', 'Un-Mod {user.login}', {user: data.user});
|
||||
},
|
||||
|
||||
hidden(data, message, current_room, current_user, mod_icons, instance) {
|
||||
// You can only un-mod mods.
|
||||
if ( message.user.type !== 'mod' )
|
||||
return true;
|
||||
|
||||
// You can unmod yourself.
|
||||
if ( message.user.id === current_user.id )
|
||||
return false;
|
||||
|
||||
// You cannot unmod the broadcaster.
|
||||
if ( message.user.id === current_room.id )
|
||||
return false;
|
||||
|
||||
// Only the broadcaster can unmod, otherwise.
|
||||
return current_room.id !== current_user.id;
|
||||
},
|
||||
|
||||
click(event, data) {
|
||||
this.sendMessage(data.room.login, `/unmod ${data.user.login}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Whisper
|
||||
// ============================================================================
|
||||
|
|
|
@ -23,6 +23,7 @@ export default {
|
|||
full: null,
|
||||
unsafe: false,
|
||||
urls: null,
|
||||
i18n_prefix: null,
|
||||
allow_media: false,
|
||||
allow_unsafe: false
|
||||
}
|
||||
|
@ -114,6 +115,7 @@ export default {
|
|||
this.fragments = {};
|
||||
this.unsafe = false;
|
||||
this.urls = null;
|
||||
this.i18n_prefix = null;
|
||||
this.allow_media = false;
|
||||
this.allow_unsafe = false;
|
||||
this.load(refresh);
|
||||
|
@ -179,6 +181,7 @@ export default {
|
|||
this.fragments = data.fragments ?? {};
|
||||
this.unsafe = data.unsafe;
|
||||
this.urls = data.urls;
|
||||
this.i18n_prefix = data.i18n_prefix;
|
||||
this.allow_media = data.allow_media;
|
||||
this.allow_unsafe = data.allow_unsafe;
|
||||
},
|
||||
|
@ -238,6 +241,7 @@ export default {
|
|||
i18n: this.getI18n(),
|
||||
|
||||
fragments: this.fragments,
|
||||
i18n_prefix: this.i18n_prefix,
|
||||
|
||||
allow_media: this.forceMedia ?? this.allow_media,
|
||||
allow_unsafe: this.forceUnsafe ?? this.allow_unsafe
|
||||
|
|
|
@ -197,8 +197,8 @@ export default class Emotes extends Module {
|
|||
for(const set_id in this.emote_sets)
|
||||
if ( has(this.emote_sets, set_id) ) {
|
||||
const emote_set = this.emote_sets[set_id];
|
||||
if ( emote_set && emote_set.pending_css ) {
|
||||
this.style.set(`es--${set_id}`, emote_set.pending_css + (emote_set.css || ''));
|
||||
if ( emote_set && (emote_set.pending_css || emote_set.css) ) {
|
||||
this.style.set(`es--${set_id}`, (emote_set.pending_css || '') + (emote_set.css || ''));
|
||||
emote_set.pending_css = null;
|
||||
}
|
||||
}
|
||||
|
@ -816,6 +816,166 @@ export default class Emotes extends Module {
|
|||
}
|
||||
|
||||
|
||||
processEmote(emote, set_id) {
|
||||
if ( ! emote.id || ! emote.name || ! emote.urls )
|
||||
return null;
|
||||
|
||||
emote.set_id = set_id;
|
||||
emote.src = emote.urls[1];
|
||||
emote.srcSet = `${emote.urls[1]} 1x`;
|
||||
if ( emote.urls[2] )
|
||||
emote.srcSet += `, ${emote.urls[2]} 2x`;
|
||||
if ( emote.urls[4] )
|
||||
emote.srcSet += `, ${emote.urls[4]} 4x`;
|
||||
|
||||
if ( emote.urls[2] ) {
|
||||
emote.can_big = true;
|
||||
emote.src2 = emote.urls[2];
|
||||
emote.srcSet2 = `${emote.urls[2]} 1x`;
|
||||
if ( emote.urls[4] )
|
||||
emote.srcSet2 += `, ${emote.urls[4]} 2x`;
|
||||
}
|
||||
|
||||
if ( emote.animated?.[1] ) {
|
||||
emote.animSrc = emote.animated[1];
|
||||
emote.animSrcSet = `${emote.animated[1]} 1x`;
|
||||
if ( emote.animated[2] ) {
|
||||
emote.animSrcSet += `, ${emote.animated[2]} 2x`;
|
||||
emote.animSrc2 = emote.animated[2];
|
||||
emote.animSrcSet2 = `${emote.animated[2]} 1x`;
|
||||
|
||||
if ( emote.animated[4] ) {
|
||||
emote.animSrcSet += `, ${emote.animated[4]} 4x`;
|
||||
emote.animSrcSet2 += `, ${emote.animated[4]} 2x`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emote.token = {
|
||||
type: 'emote',
|
||||
id: emote.id,
|
||||
set: set_id,
|
||||
provider: 'ffz',
|
||||
src: emote.src,
|
||||
srcSet: emote.srcSet,
|
||||
can_big: !! emote.urls[2],
|
||||
src2: emote.src2,
|
||||
srcSet2: emote.srcSet2,
|
||||
animSrc: emote.animSrc,
|
||||
animSrcSet: emote.animSrcSet,
|
||||
animSrc2: emote.animSrc2,
|
||||
animSrcSet2: emote.animSrcSet2,
|
||||
text: emote.hidden ? '???' : emote.name,
|
||||
length: emote.name.length,
|
||||
height: emote.height
|
||||
};
|
||||
|
||||
if ( has(MODIFIERS, emote.id) )
|
||||
Object.assign(emote, MODIFIERS[emote.id]);
|
||||
|
||||
return emote;
|
||||
}
|
||||
|
||||
|
||||
addEmoteToSet(set_id, emote) {
|
||||
const set = this.emote_sets[set_id];
|
||||
if ( ! set )
|
||||
throw new Error(`Invalid emote set "${set_id}"`);
|
||||
|
||||
let processed = this.processEmote(emote, set_id);
|
||||
if ( ! processed )
|
||||
throw new Error("Invalid emote data object.");
|
||||
|
||||
// Are we removing an existing emote?
|
||||
const old_emote = set.emotes[processed.id],
|
||||
old_css = old_emote && this.generateEmoteCSS(old_emote);
|
||||
|
||||
// Store the emote.
|
||||
set.emotes[processed.id] = processed;
|
||||
if ( ! old_emote )
|
||||
set.count++;
|
||||
|
||||
// Now we need to update the CSS. If we had old emote CSS, then we
|
||||
// will need to totally rebuild the CSS.
|
||||
const style_key = `es--${set_id}`;
|
||||
|
||||
if ( old_css && old_css.length ) {
|
||||
const css = [];
|
||||
for(const em of Object.values(set.emotes)) {
|
||||
const emote_css = this.generateEmoteCSS(em);
|
||||
if ( emote_css && emote_css.length )
|
||||
css.push(emote_css);
|
||||
}
|
||||
|
||||
if ( this.style && (css.length || set.css) )
|
||||
this.style.set(style_key, css.join('') + (set.css || ''));
|
||||
else if ( css.length )
|
||||
set.pending_css = css.join('');
|
||||
|
||||
} else {
|
||||
const emote_css = this.generateEmoteCSS(processed);
|
||||
if ( emote_css && emote_css.length ) {
|
||||
if ( this.style )
|
||||
this.style.set(style_key, (this.style.get(style_key) || '') + emote_css);
|
||||
else
|
||||
set.pending_css = (set.pending_css || '') + emote_css;
|
||||
}
|
||||
}
|
||||
|
||||
// Send a loaded event because this emote set changed.
|
||||
this.emit(':loaded', set_id, set);
|
||||
}
|
||||
|
||||
|
||||
removeEmoteFromSet(set_id, emote_id) {
|
||||
const set = this.emote_sets[set_id];
|
||||
if ( ! set )
|
||||
throw new Error(`Invalid emote set "${set_id}"`);
|
||||
|
||||
if ( emote_id && emote_id.id )
|
||||
emote_id = emote_id.id;
|
||||
|
||||
const emote = set.emotes[emote_id];
|
||||
if ( ! emote )
|
||||
return;
|
||||
|
||||
const emote_css = this.generateEmoteCSS(emote);
|
||||
const css = (emote_css && emote_css.length) ? [] : null;
|
||||
|
||||
// Rebuild the emotes object to avoid gaps.
|
||||
const new_emotes = {};
|
||||
let count = 0;
|
||||
|
||||
for(const em of Object.values(set.emotes)) {
|
||||
if ( em.id == emote_id )
|
||||
continue;
|
||||
|
||||
new_emotes[em.id] = em;
|
||||
count++;
|
||||
|
||||
if ( css != null) {
|
||||
const em_css = this.generateEmoteCSS(em);
|
||||
if ( em_css && em_css.length )
|
||||
css.push(em_css);
|
||||
}
|
||||
}
|
||||
|
||||
set.emotes = new_emotes;
|
||||
set.count = count;
|
||||
|
||||
if ( css != null ) {
|
||||
const style_key = `es--${set_id}`;
|
||||
if ( this.style && (css.length || set.css) )
|
||||
this.style.set(style_key, css.join('') + (set.css || ''));
|
||||
else if ( css.length )
|
||||
set.pending_css = css.join('');
|
||||
}
|
||||
|
||||
// Send a loaded event because this emote set changed.
|
||||
this.emit(':loaded', set_id, set);
|
||||
}
|
||||
|
||||
|
||||
loadSetData(set_id, data, suppress_log = false) {
|
||||
const old_set = this.emote_sets[set_id];
|
||||
if ( ! data ) {
|
||||
|
@ -838,70 +998,18 @@ export default class Emotes extends Module {
|
|||
const bad_emotes = [];
|
||||
|
||||
for(const emote of ems) {
|
||||
if ( ! emote.id || ! emote.name || ! emote.urls ) {
|
||||
let processed = this.processEmote(emote, set_id);
|
||||
if ( ! processed ) {
|
||||
bad_emotes.push(emote);
|
||||
continue;
|
||||
}
|
||||
|
||||
emote.set_id = set_id;
|
||||
emote.src = emote.urls[1];
|
||||
emote.srcSet = `${emote.urls[1]} 1x`;
|
||||
if ( emote.urls[2] )
|
||||
emote.srcSet += `, ${emote.urls[2]} 2x`;
|
||||
if ( emote.urls[4] )
|
||||
emote.srcSet += `, ${emote.urls[4]} 4x`;
|
||||
|
||||
if ( emote.urls[2] ) {
|
||||
emote.can_big = true;
|
||||
emote.src2 = emote.urls[2];
|
||||
emote.srcSet2 = `${emote.urls[2]} 1x`;
|
||||
if ( emote.urls[4] )
|
||||
emote.srcSet2 += `, ${emote.urls[4]} 2x`;
|
||||
}
|
||||
|
||||
if ( emote.animated?.[1] ) {
|
||||
emote.animSrc = emote.animated[1];
|
||||
emote.animSrcSet = `${emote.animated[1]} 1x`;
|
||||
if ( emote.animated[2] ) {
|
||||
emote.animSrcSet += `, ${emote.animated[2]} 2x`;
|
||||
emote.animSrc2 = emote.animated[2];
|
||||
emote.animSrcSet2 = `${emote.animated[2]} 1x`;
|
||||
|
||||
if ( emote.animated[4] ) {
|
||||
emote.animSrcSet += `, ${emote.animated[4]} 4x`;
|
||||
emote.animSrcSet2 += `, ${emote.animated[4]} 2x`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emote.token = {
|
||||
type: 'emote',
|
||||
id: emote.id,
|
||||
set: set_id,
|
||||
provider: 'ffz',
|
||||
src: emote.src,
|
||||
srcSet: emote.srcSet,
|
||||
can_big: !! emote.urls[2],
|
||||
src2: emote.src2,
|
||||
srcSet2: emote.srcSet2,
|
||||
animSrc: emote.animSrc,
|
||||
animSrcSet: emote.animSrcSet,
|
||||
animSrc2: emote.animSrc2,
|
||||
animSrcSet2: emote.animSrcSet2,
|
||||
text: emote.hidden ? '???' : emote.name,
|
||||
length: emote.name.length,
|
||||
height: emote.height
|
||||
};
|
||||
|
||||
if ( has(MODIFIERS, emote.id) )
|
||||
Object.assign(emote, MODIFIERS[emote.id]);
|
||||
|
||||
const emote_css = this.generateEmoteCSS(emote);
|
||||
const emote_css = this.generateEmoteCSS(processed);
|
||||
if ( emote_css )
|
||||
css.push(emote_css);
|
||||
|
||||
count++;
|
||||
new_ems[emote.id] = emote;
|
||||
new_ems[processed.id] = processed;
|
||||
}
|
||||
|
||||
if ( bad_emotes.length )
|
||||
|
|
|
@ -1668,6 +1668,10 @@ export default class Chat extends Module {
|
|||
b[item.setID] = item.version;
|
||||
}
|
||||
|
||||
// Validate User Type
|
||||
if ( user.type == null && msg.badges && msg.badges.moderator )
|
||||
user.type = 'mod';
|
||||
|
||||
// Standardize Timestamp
|
||||
if ( ! msg.timestamp && msg.sentAt )
|
||||
msg.timestamp = new Date(msg.sentAt).getTime();
|
||||
|
@ -2235,6 +2239,9 @@ export default class Chat extends Module {
|
|||
}
|
||||
|
||||
fixLinkInfo(data) {
|
||||
if ( ! data )
|
||||
return data;
|
||||
|
||||
if ( data.error && data.message )
|
||||
data.error = data.message;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ const USER_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/([^/]+)$/;
|
|||
|
||||
const BAD_USERS = [
|
||||
'directory', '_deck', 'p', 'downloads', 'jobs', 'turbo', 'settings', 'friends',
|
||||
'subscriptions', 'inventory', 'wallet'
|
||||
'subscriptions', 'inventory', 'wallet', 'store', 'drops', 'search', 'prime'
|
||||
];
|
||||
|
||||
import GET_CLIP from './clip_info.gql';
|
||||
|
|
|
@ -97,6 +97,7 @@ export const Links = {
|
|||
i18n: this.i18n,
|
||||
|
||||
fragments: data.fragments,
|
||||
i18n_prefix: data.i18n_prefix,
|
||||
|
||||
allow_media: show_images,
|
||||
allow_unsafe: show_unsafe,
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
<img :src="icon" class="tw-image">
|
||||
</div>
|
||||
|
||||
<div v-if="reloading" class="tw-mg-b-05 ffz-pill">
|
||||
{{ t('addon.reloading', 'Reloading') }}
|
||||
</div>
|
||||
|
||||
<div v-if="external" class="tw-mg-b-05 ffz-pill">
|
||||
{{ t('addon.external', 'External') }}
|
||||
</div>
|
||||
|
@ -96,6 +100,20 @@
|
|||
{{ t('addon.disable', 'Disable') }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="addon.dev && can_reload"
|
||||
class="tw-button ffz-button--hollow tw-mg-r-1"
|
||||
:class="{'tw-button--disabled': reloading}"
|
||||
:disabled="reloading"
|
||||
@click="reloadAddon()"
|
||||
>
|
||||
<span class="tw-button__icon tw-button__icon--left">
|
||||
<figure class="ffz-i-arrows-cw" />
|
||||
</span>
|
||||
<span class="tw-button__text">
|
||||
{{ t('addon.reload', 'Reload') }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="addon.settings"
|
||||
class="tw-button ffz-button--hollow tw-mg-r-1"
|
||||
|
@ -151,6 +169,8 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
enabled: this.item.isAddonEnabled(this.id),
|
||||
can_reload: this.addon.dev && this.item.canReloadAddon(this.id),
|
||||
reloading: false,
|
||||
external: this.item.isAddonExternal(this.id),
|
||||
version: this.item.getVersion(this.id),
|
||||
expanded: false
|
||||
|
@ -251,6 +271,19 @@ export default {
|
|||
list.push(`add_ons.${this.addon.name.toSnakeCase()}`);
|
||||
|
||||
this.$emit('navigate', ...list);
|
||||
},
|
||||
|
||||
reloadAddon() {
|
||||
this.reloading = true;
|
||||
this.item.reloadAddon(this.id)
|
||||
.then(() => {
|
||||
this.reloading = false;
|
||||
this.can_reload = this.item.canReloadAddon(this.id);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
this.reloading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,117 @@
|
|||
{{ t('setting.experiments.about', 'This feature allows you to override experiment values. Please note that, for most experiments, you may have to refresh the page for your changes to take effect.') }}
|
||||
</div>
|
||||
|
||||
<div class="tw-mg-b-2 tw-flex tw-align-items-center">
|
||||
<div class="tw-flex-grow-1">
|
||||
{{ t('setting.experiments.unique-id', 'Unique ID: {id}', {id: unique_id}) }}
|
||||
</div>
|
||||
<select
|
||||
ref="sort_select"
|
||||
class="tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
||||
@change="onSort"
|
||||
>
|
||||
<option :selected="sort_by === 0">
|
||||
{{ t('setting.experiments.sort-name', 'Sort By: Name') }}
|
||||
</option>
|
||||
<option :selected="sort_by === 1">
|
||||
{{ t('setting.experiments.sort-rarity', 'Sort By: Rarity') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="tw-mg-b-2 tw-flex tw-align-items-center">
|
||||
<div class="tw-flex-grow-1" />
|
||||
<div class="ffz-checkbox tw-relative">
|
||||
<input
|
||||
id="unused"
|
||||
ref="unused"
|
||||
v-model="unused"
|
||||
type="checkbox"
|
||||
class="ffz-checkbox__input"
|
||||
>
|
||||
|
||||
<label for="unused" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.experiments.show-unused', 'Display unused experiments.') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="tw-mg-b-1">
|
||||
<span>
|
||||
{{ t('setting.experiments.ffz', 'FrankerFaceZ Experiments') }}
|
||||
</span>
|
||||
<span v-if="filter" class="tw-mg-l-1 tw-font-size-base tw-regular tw-c-text-alt-2">
|
||||
{{ t('setting.experiments.visible', '(Showing {visible,number} of {total,number})', {
|
||||
visible: visible_ffz.length,
|
||||
total: sorted_ffz.length
|
||||
}) }}
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div class="ffz--experiment-list">
|
||||
<section
|
||||
v-for="({key, exp}) of visible_ffz"
|
||||
:key="key"
|
||||
:data-key="key"
|
||||
>
|
||||
<div class="tw-elevation-1 tw-c-background-base tw-border tw-pd-y-05 tw-pd-x-1 tw-mg-y-05 tw-flex tw-flex-nowrap">
|
||||
<div class="tw-flex-grow-1">
|
||||
<h4>{{ exp.name }}</h4>
|
||||
<div v-if="exp.description" class="description">
|
||||
{{ exp.description }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tw-flex tw-flex-shrink-0 tw-align-items-start">
|
||||
<select
|
||||
:data-key="key"
|
||||
class="tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="(i, idx) in exp.groups"
|
||||
:key="idx"
|
||||
:selected="i.value === exp.value"
|
||||
>
|
||||
{{ t('setting.experiments.entry', '{value,tostring} (weight: {weight,tostring})', i) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
:disabled="exp.default"
|
||||
:class="{'tw-button--disabled': exp.default}"
|
||||
class="tw-mg-t-05 tw-button tw-button--text ffz-il-tooltip__container"
|
||||
@click="reset(key)"
|
||||
>
|
||||
<span class="tw-button__text ffz-i-cancel" />
|
||||
<span class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-right">
|
||||
{{ t('setting.reset', 'Reset to Default') }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div v-if="! Object.keys(ffz_data).length">
|
||||
{{ t('setting.experiments.none', 'There are no current experiments.') }}
|
||||
</div>
|
||||
<div v-else-if="! visible_ffz.length">
|
||||
{{ t('setting.experiments.none-filter', 'There are no matching experiments.') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="tw-mg-t-5 tw-mg-b-1">
|
||||
<span>
|
||||
{{ t('setting.experiments.twitch', 'Twitch Experiments') }}
|
||||
</span>
|
||||
<span v-if="experiments_locked && filter" class="tw-mg-l-1 tw-font-size-base tw-regular tw-c-text-alt-2">
|
||||
{{ t('setting.experiments.visible', '(Showing {visible,number} of {total,number})', {
|
||||
visible: visible_twitch.length,
|
||||
total: sorted_twitch.length
|
||||
}) }}
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<section v-if="experiments_locked">
|
||||
<div class="tw-c-background-accent tw-c-text-overlay tw-pd-1 tw-mg-b-2">
|
||||
<h3 class="ffz-i-attention">
|
||||
|
@ -16,7 +127,7 @@
|
|||
<input
|
||||
ref="code"
|
||||
type="text"
|
||||
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05"
|
||||
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05 tw-mg-b-5"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
@keydown.enter="enterCode"
|
||||
|
@ -25,117 +136,6 @@
|
|||
</section>
|
||||
|
||||
<section v-else>
|
||||
<div class="tw-mg-b-2 tw-flex tw-align-items-center">
|
||||
<div class="tw-flex-grow-1">
|
||||
{{ t('setting.experiments.unique-id', 'Unique ID: {id}', {id: unique_id}) }}
|
||||
</div>
|
||||
<select
|
||||
ref="sort_select"
|
||||
class="tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
||||
@change="onSort"
|
||||
>
|
||||
<option :selected="sort_by === 0">
|
||||
{{ t('setting.experiments.sort-name', 'Sort By: Name') }}
|
||||
</option>
|
||||
<option :selected="sort_by === 1">
|
||||
{{ t('setting.experiments.sort-rarity', 'Sort By: Rarity') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="tw-mg-b-2 tw-flex tw-align-items-center">
|
||||
<div class="tw-flex-grow-1" />
|
||||
<div class="ffz-checkbox tw-relative">
|
||||
<input
|
||||
id="unused"
|
||||
ref="unused"
|
||||
v-model="unused"
|
||||
type="checkbox"
|
||||
class="ffz-checkbox__input"
|
||||
>
|
||||
|
||||
<label for="unused" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.experiments.show-unused', 'Display unused experiments.') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="tw-mg-b-1">
|
||||
<span>
|
||||
{{ t('setting.experiments.ffz', 'FrankerFaceZ Experiments') }}
|
||||
</span>
|
||||
<span v-if="filter" class="tw-mg-l-1 tw-font-size-base tw-regular tw-c-text-alt-2">
|
||||
{{ t('setting.experiments.visible', '(Showing {visible,number} of {total,number})', {
|
||||
visible: visible_ffz.length,
|
||||
total: sorted_ffz.length
|
||||
}) }}
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div class="ffz--experiment-list">
|
||||
<section
|
||||
v-for="({key, exp}) of visible_ffz"
|
||||
:key="key"
|
||||
:data-key="key"
|
||||
>
|
||||
<div class="tw-elevation-1 tw-c-background-base tw-border tw-pd-y-05 tw-pd-x-1 tw-mg-y-05 tw-flex tw-flex-nowrap">
|
||||
<div class="tw-flex-grow-1">
|
||||
<h4>{{ exp.name }}</h4>
|
||||
<div v-if="exp.description" class="description">
|
||||
{{ exp.description }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tw-flex tw-flex-shrink-0 tw-align-items-start">
|
||||
<select
|
||||
:data-key="key"
|
||||
class="tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="(i, idx) in exp.groups"
|
||||
:key="idx"
|
||||
:selected="i.value === exp.value"
|
||||
>
|
||||
{{ t('setting.experiments.entry', '{value,tostring} (weight: {weight,tostring})', i) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
:disabled="exp.default"
|
||||
:class="{'tw-button--disabled': exp.default}"
|
||||
class="tw-mg-t-05 tw-button tw-button--text ffz-il-tooltip__container"
|
||||
@click="reset(key)"
|
||||
>
|
||||
<span class="tw-button__text ffz-i-cancel" />
|
||||
<span class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-right">
|
||||
{{ t('setting.reset', 'Reset to Default') }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div v-if="! Object.keys(ffz_data).length">
|
||||
{{ t('setting.experiments.none', 'There are no current experiments.') }}
|
||||
</div>
|
||||
<div v-else-if="! visible_ffz.length">
|
||||
{{ t('setting.experiments.none-filter', 'There are no matching experiments.') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="tw-mg-t-5 tw-mg-b-1">
|
||||
<span>
|
||||
{{ t('setting.experiments.twitch', 'Twitch Experiments') }}
|
||||
</span>
|
||||
<span v-if="filter" class="tw-mg-l-1 tw-font-size-base tw-regular tw-c-text-alt-2">
|
||||
{{ t('setting.experiments.visible', '(Showing {visible,number} of {total,number})', {
|
||||
visible: visible_twitch.length,
|
||||
total: sorted_twitch.length
|
||||
}) }}
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div class="ffz--experiment-list">
|
||||
<section
|
||||
v-for="({key, exp}) of visible_twitch"
|
||||
|
|
|
@ -215,7 +215,12 @@ export default class MainMenu extends Module {
|
|||
this.on('settings:added-definition', (key, definition) => {
|
||||
this._addDefinitionToTree(key, definition);
|
||||
this.scheduleUpdate();
|
||||
})
|
||||
});
|
||||
|
||||
this.on('settings:removed-definition', key => {
|
||||
this._removeDefinitionFromTree(key);
|
||||
this.scheduleUpdate();
|
||||
});
|
||||
|
||||
this.on('socket:command:new_version', version => {
|
||||
if ( version === window.FrankerFaceZ.version_info.commit )
|
||||
|
@ -361,6 +366,7 @@ export default class MainMenu extends Module {
|
|||
this.log.info('Context proxy gone.');
|
||||
this.updateContext({proxied: false});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
try {
|
||||
|
@ -508,6 +514,58 @@ export default class MainMenu extends Module {
|
|||
}
|
||||
|
||||
|
||||
_removeDefinitionFromTree(key) {
|
||||
if ( ! this._settings_tree )
|
||||
return;
|
||||
|
||||
let page;
|
||||
for(const val of Object.values(this._settings_tree)) {
|
||||
if ( ! val || ! Array.isArray(val.settings) )
|
||||
continue;
|
||||
|
||||
for(let i = 0; i < val.settings.length; i++) {
|
||||
const entry = val.settings[i];
|
||||
if ( entry && entry[0] === key ) {
|
||||
val.settings.splice(i, 1);
|
||||
page = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( page )
|
||||
break;
|
||||
}
|
||||
|
||||
// Was it found?
|
||||
if ( ! page )
|
||||
return;
|
||||
|
||||
this._maybeDeleteSection(page);
|
||||
}
|
||||
|
||||
_maybeDeleteSection(page) {
|
||||
// Is the section empty?
|
||||
if ( page.settings && page.settings.length )
|
||||
return;
|
||||
|
||||
const id = page.full_key;
|
||||
|
||||
// Check for children.
|
||||
for(const val of Object.values(this._settings_tree)) {
|
||||
if ( val.parent === id )
|
||||
return;
|
||||
}
|
||||
|
||||
// Nope~
|
||||
delete this._settings_tree[id];
|
||||
|
||||
if ( page.parent ) {
|
||||
const parent = this._settings_tree[page.parent];
|
||||
if ( parent )
|
||||
this._maybeDeleteSection(parent);
|
||||
}
|
||||
}
|
||||
|
||||
_addDefinitionToTree(key, def) {
|
||||
if ( ! def.ui || ! this._settings_tree )
|
||||
return;
|
||||
|
|
|
@ -941,11 +941,45 @@ export default class SettingsManager extends Module {
|
|||
setContext(context) { return this.main_context.setContext(context) }
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Add-On Proxy
|
||||
// ========================================================================
|
||||
|
||||
getAddonProxy(module) {
|
||||
const path = module.__path;
|
||||
|
||||
const add = (key, definition) => {
|
||||
return this.add(key, definition, path);
|
||||
}
|
||||
|
||||
const addUI = (key, definition) => {
|
||||
return this.addUI(key, definition, path);
|
||||
}
|
||||
|
||||
const addClearable = (key, definition) => {
|
||||
return this.addClearable(key, definition, path);
|
||||
}
|
||||
|
||||
const handler = {
|
||||
get(obj, prop) {
|
||||
if ( prop === 'add' )
|
||||
return add;
|
||||
if ( prop === 'addUI' )
|
||||
return addUI;
|
||||
if ( prop === 'addClearable' )
|
||||
return addClearable;
|
||||
return Reflect.get(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
return new Proxy(this, handler);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Definitions
|
||||
// ========================================================================
|
||||
|
||||
add(key, definition) {
|
||||
add(key, definition, source) {
|
||||
if ( typeof key === 'object' ) {
|
||||
for(const k in key)
|
||||
if ( has(key, k) )
|
||||
|
@ -960,6 +994,8 @@ export default class SettingsManager extends Module {
|
|||
definition.required_by = required_by;
|
||||
definition.requires = definition.requires || [];
|
||||
|
||||
definition.__source = source;
|
||||
|
||||
for(const req_key of definition.requires) {
|
||||
const req = this.definitions.get(req_key);
|
||||
if ( ! req )
|
||||
|
@ -1007,7 +1043,42 @@ export default class SettingsManager extends Module {
|
|||
}
|
||||
|
||||
|
||||
addUI(key, definition) {
|
||||
remove(key) {
|
||||
const definition = this.definitions.get(key);
|
||||
if ( ! definition )
|
||||
return;
|
||||
|
||||
// If the definition is an array, we're already not defined.
|
||||
if ( Array.isArray(definition) )
|
||||
return;
|
||||
|
||||
// Remove this definition from the definitions list.
|
||||
if ( Array.isArray(definition.required_by) && definition.required_by.length > 0 )
|
||||
this.definitions.set(key, definition.required_by);
|
||||
else
|
||||
this.definitions.delete(key);
|
||||
|
||||
// Remove it from all the things it required.
|
||||
if ( Array.isArray(definition.requires) )
|
||||
for(const req_key of definition.requires) {
|
||||
let req = this.definitions.get(req_key);
|
||||
if ( req.required_by )
|
||||
req = req.required_by;
|
||||
if ( Array.isArray(req) ) {
|
||||
const idx = req.indexOf(key);
|
||||
if ( idx !== -1 )
|
||||
req.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if ( definition.changed )
|
||||
this.off(`:changed:${key}`, definition.changed);
|
||||
|
||||
this.emit(':removed-definition', key, definition);
|
||||
}
|
||||
|
||||
|
||||
addUI(key, definition, source) {
|
||||
if ( typeof key === 'object' ) {
|
||||
for(const k in key)
|
||||
if ( has(key, k) )
|
||||
|
@ -1018,6 +1089,8 @@ export default class SettingsManager extends Module {
|
|||
if ( ! definition.ui )
|
||||
definition = {ui: definition};
|
||||
|
||||
definition.__source = source;
|
||||
|
||||
const ui = definition.ui;
|
||||
ui.path_tokens = ui.path_tokens ?
|
||||
format_path_tokens(ui.path_tokens) :
|
||||
|
@ -1038,14 +1111,16 @@ export default class SettingsManager extends Module {
|
|||
}
|
||||
|
||||
|
||||
addClearable(key, definition) {
|
||||
addClearable(key, definition, source) {
|
||||
if ( typeof key === 'object' ) {
|
||||
for(const k in key)
|
||||
if ( has(key, k) )
|
||||
this.addClearable(k, key[k]);
|
||||
this.addClearable(k, key[k], source);
|
||||
return;
|
||||
}
|
||||
|
||||
definition.__source = source;
|
||||
|
||||
this.clearables[key] = definition;
|
||||
}
|
||||
|
||||
|
|
|
@ -276,6 +276,24 @@ export default class ChatHook extends Module {
|
|||
|
||||
// Settings
|
||||
|
||||
this.settings.add('chat.disable-handling', {
|
||||
default: null,
|
||||
requires: ['context.disable-chat-processing'],
|
||||
process(ctx, val) {
|
||||
if ( val != null )
|
||||
return ! val;
|
||||
if ( ctx.get('context.disable-chat-processing') )
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
ui: {
|
||||
path: 'Debugging > Chat >> Processing',
|
||||
title: 'Enable processing of chat messages.',
|
||||
component: 'setting-check-box',
|
||||
force_seen: true
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.addUI('debug.chat-test', {
|
||||
path: 'Debugging > Chat >> Chat',
|
||||
component: 'chat-tester',
|
||||
|
@ -887,6 +905,11 @@ export default class ChatHook extends Module {
|
|||
}
|
||||
|
||||
|
||||
updateDisableHandling() {
|
||||
this.disable_handling = this.chat.context.get('chat.disable-handling');
|
||||
}
|
||||
|
||||
|
||||
onEnable() {
|
||||
this.on('site.web_munch:loaded', this.grabTypes);
|
||||
this.on('site.web_munch:loaded', this.defineClasses);
|
||||
|
@ -909,6 +932,8 @@ export default class ChatHook extends Module {
|
|||
this.chat.context.on('changed:chat.banners.prediction', this.cleanHighlights, this);
|
||||
this.chat.context.on('changed:chat.banners.drops', this.cleanHighlights, this);
|
||||
|
||||
this.chat.context.on('changed:chat.disable-handling', this.updateDisableHandling, this);
|
||||
|
||||
this.chat.context.on('changed:chat.subs.gift-banner', () => this.GiftBanner.forceUpdate(), this);
|
||||
this.chat.context.on('changed:chat.effective-width', this.updateChatCSS, this);
|
||||
this.settings.main_context.on('changed:chat.use-width', this.updateChatCSS, this);
|
||||
|
@ -992,6 +1017,7 @@ export default class ChatHook extends Module {
|
|||
this.chat.context.getChanges('chat.input.show-elevate-your-message', val =>
|
||||
this.css_tweaks.toggleHide('elevate-your-message', ! val));
|
||||
|
||||
this.updateDisableHandling();
|
||||
this.updateChatCSS();
|
||||
this.updateColors();
|
||||
this.updateLineBorders();
|
||||
|
@ -1325,7 +1351,7 @@ export default class ChatHook extends Module {
|
|||
});
|
||||
|
||||
this.subpump.on(':pubsub-message', event => {
|
||||
if ( event.prefix !== 'community-points-channel-v1' )
|
||||
if ( event.prefix !== 'community-points-channel-v1' || this.disable_handling )
|
||||
return;
|
||||
|
||||
const service = this.ChatService.first,
|
||||
|
@ -2186,7 +2212,7 @@ export default class ChatHook extends Module {
|
|||
|
||||
const old_announce = this.onAnnouncementEvent;
|
||||
this.onAnnouncementEvent = function(e) {
|
||||
console.log('announcement', e);
|
||||
//console.log('announcement', e);
|
||||
return old_announce.call(this, e);
|
||||
}
|
||||
|
||||
|
@ -2197,6 +2223,9 @@ export default class ChatHook extends Module {
|
|||
if ( t.chat.context.get('chat.filtering.blocked-types').has('Subscription') )
|
||||
return;
|
||||
|
||||
if ( t.disable_handling )
|
||||
return old_sub.call(i, e);
|
||||
|
||||
if ( t.chat.context.get('chat.subs.show') < 3 )
|
||||
return;
|
||||
|
||||
|
@ -2236,6 +2265,9 @@ export default class ChatHook extends Module {
|
|||
if ( t.chat.context.get('chat.filtering.blocked-types').has('Resubscription') )
|
||||
return;
|
||||
|
||||
if ( t.disable_handling )
|
||||
return old_resub.call(i, e);
|
||||
|
||||
if ( t.chat.context.get('chat.subs.show') < 2 && ! e.body )
|
||||
return;
|
||||
|
||||
|
@ -2267,6 +2299,9 @@ export default class ChatHook extends Module {
|
|||
if ( t.chat.context.get('chat.filtering.blocked-types').has('SubGift') )
|
||||
return;
|
||||
|
||||
if ( t.disable_handling )
|
||||
return old_subgift.call(i, e);
|
||||
|
||||
const key = `${e.channel}:${e.user.userID}`,
|
||||
mystery = mysteries[key];
|
||||
|
||||
|
@ -2316,6 +2351,9 @@ export default class ChatHook extends Module {
|
|||
const old_communityintro = this.onCommunityIntroductionEvent;
|
||||
this.onCommunityIntroductionEvent = function(e) {
|
||||
try {
|
||||
if ( t.disable_handling )
|
||||
return old_communityintro.call(this, e);
|
||||
|
||||
if ( t.chat.context.get('chat.filtering.blocked-types').has('CommunityIntroduction') ) {
|
||||
const out = i.convertMessage(e);
|
||||
return i.postMessageToCurrentChannel(e, out);
|
||||
|
@ -2335,6 +2373,9 @@ export default class ChatHook extends Module {
|
|||
if ( t.chat.context.get('chat.filtering.blocked-types').has('AnonSubGift') )
|
||||
return;
|
||||
|
||||
if ( t.disable_handling )
|
||||
return old_anonsubgift.call(i, e);
|
||||
|
||||
const key = `${e.channel}:ANON`,
|
||||
mystery = mysteries[key];
|
||||
|
||||
|
@ -2388,6 +2429,9 @@ export default class ChatHook extends Module {
|
|||
if ( t.chat.context.get('chat.filtering.blocked-types').has('SubMysteryGift') )
|
||||
return;
|
||||
|
||||
if ( t.disable_handling )
|
||||
return old_submystery.call(i, e);
|
||||
|
||||
let mystery = null;
|
||||
if ( e.massGiftCount > t.chat.context.get('chat.subs.merge-gifts') ) {
|
||||
const key = `${e.channel}:${e.user.userID}`;
|
||||
|
@ -2423,6 +2467,9 @@ export default class ChatHook extends Module {
|
|||
if ( t.chat.context.get('chat.filtering.blocked-types').has('AnonSubMysteryGift') )
|
||||
return;
|
||||
|
||||
if ( t.disable_handling )
|
||||
return old_anonsubmystery.call(i, e);
|
||||
|
||||
let mystery = null;
|
||||
if ( e.massGiftCount > t.chat.context.get('chat.subs.merge-gifts') ) {
|
||||
const key = `${e.channel}:ANON`;
|
||||
|
@ -2457,6 +2504,9 @@ export default class ChatHook extends Module {
|
|||
if ( t.chat.context.get('chat.filtering.blocked-types').has('Ritual') )
|
||||
return;
|
||||
|
||||
if ( t.disable_handling )
|
||||
return old_ritual.call(i, e);
|
||||
|
||||
const out = i.convertMessage(e);
|
||||
out.ffz_type = 'ritual';
|
||||
out.ritual = e.type;
|
||||
|
@ -2475,6 +2525,9 @@ export default class ChatHook extends Module {
|
|||
if ( t.chat.context.get('chat.filtering.blocked-types').has('ChannelPointsReward') )
|
||||
return;
|
||||
|
||||
if ( t.disable_handling )
|
||||
return old_points.call(i, e);
|
||||
|
||||
const reward = e.rewardID && get(e.rewardID, i.props.rewardMap);
|
||||
if ( reward ) {
|
||||
const out = i.convertMessage(e);
|
||||
|
|
|
@ -39,6 +39,12 @@ export default class ChatLine extends Module {
|
|||
|
||||
this.line_types = {};
|
||||
|
||||
this.line_types.unknown = {
|
||||
renderNotice: (msg, current_user, room, inst, e) => {
|
||||
return `Unknown message type: ${msg.ffz_type}`
|
||||
}
|
||||
};
|
||||
|
||||
this.line_types.cheer = {
|
||||
renderNotice: (msg, current_user, room, inst, e) => {
|
||||
return this.i18n.tList(
|
||||
|
@ -741,6 +747,9 @@ other {# messages were deleted by a moderator.}
|
|||
if ( ! type && msg.bits > 0 && t.chat.context.get('chat.bits.cheer-notice') )
|
||||
type = t.line_types.cheer;
|
||||
|
||||
if ( ! type && msg.ffz_type )
|
||||
type = t.line_types.unknown;
|
||||
|
||||
if ( type ) {
|
||||
if ( type.render )
|
||||
return type.render(msg, current_user, current_room, this, e);
|
||||
|
|
|
@ -192,6 +192,7 @@ export default class RichContent extends Module {
|
|||
i18n: t.i18n,
|
||||
|
||||
fragments: this.state.fragments,
|
||||
i18n_prefix: this.state.i18n_prefix,
|
||||
|
||||
allow_media: t.chat.context.get('tooltip.link-images'),
|
||||
allow_unsafe: t.chat.context.get('tooltip.link-nsfw-images')
|
||||
|
|
|
@ -8,6 +8,13 @@ export class Addon extends Module {
|
|||
this.inject('settings');
|
||||
}
|
||||
|
||||
__processModule(module, name) {
|
||||
if ( module.getAddonProxy )
|
||||
return module.getAddonProxy(this);
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
static register(id, info) {
|
||||
if ( typeof id === 'object' ) {
|
||||
info = id;
|
||||
|
|
|
@ -271,6 +271,13 @@ export class ManagedStyle {
|
|||
this._style = null;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
const block = this._blocks[key];
|
||||
if ( block )
|
||||
return block.textContent;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
set(key, value, force) {
|
||||
const block = this._blocks[key];
|
||||
if ( block ) {
|
||||
|
|
|
@ -132,6 +132,36 @@ export class EventEmitter {
|
|||
this.__dead_events++;
|
||||
}
|
||||
|
||||
offContext(event, ctx) {
|
||||
if ( event == null ) {
|
||||
for(const evt in Object.keys(this.__listeners)) {
|
||||
if ( ! this.__running.has(evt) )
|
||||
this.offContext(evt, ctx);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.__running.has(event) )
|
||||
throw new Error(`concurrent modification: tried removing event listener while event is running`);
|
||||
|
||||
let list = this.__listeners[event];
|
||||
if ( ! list )
|
||||
return;
|
||||
|
||||
if ( ! fn )
|
||||
list = null;
|
||||
else {
|
||||
list = list.filter(x => x && x[1] !== ctx);
|
||||
if ( ! list.length )
|
||||
list = null;
|
||||
}
|
||||
|
||||
this.__listeners[event] = list;
|
||||
if ( ! list )
|
||||
this.__dead_events++;
|
||||
}
|
||||
|
||||
events() {
|
||||
this.__cleanListeners();
|
||||
return Object.keys(this.__listeners);
|
||||
|
|
|
@ -106,5 +106,10 @@ export default [
|
|||
"right-open",
|
||||
"list-bullet",
|
||||
"mastodon",
|
||||
"volume-up"
|
||||
"volume-up",
|
||||
"unmod",
|
||||
"mod",
|
||||
"flag",
|
||||
"mange-suspicious",
|
||||
"doc-text"
|
||||
];
|
|
@ -111,6 +111,13 @@ export class Module extends EventEmitter {
|
|||
return this.__disable(args, this.__path, []);
|
||||
}
|
||||
|
||||
canUnload() {
|
||||
return this.__canUnload(this.__path, []);
|
||||
}
|
||||
|
||||
canDisable() {
|
||||
return this.__canDisable(this.__path, []);
|
||||
}
|
||||
|
||||
__load(args, initial, chain) {
|
||||
const path = this.__path || this.name,
|
||||
|
@ -172,6 +179,43 @@ export class Module extends EventEmitter {
|
|||
}
|
||||
|
||||
|
||||
__canUnload(initial, chain) {
|
||||
const path = this.__path || this.name,
|
||||
state = this.__load_state;
|
||||
|
||||
if ( chain.includes(this) )
|
||||
throw new CyclicDependencyError(`cyclic load requirements when checking if can unload ${initial}`, [...chain, this]);
|
||||
else if ( this.load_dependents ) {
|
||||
chain.push(this);
|
||||
|
||||
for(const dep of this.load_dependents) {
|
||||
const module = this.resolve(dep);
|
||||
if ( module ) {
|
||||
if ( chain.includes(module) )
|
||||
throw new CyclicDependencyError(`cyclic load requirements when checking if can unload ${initial}`, [...chain, this, module]);
|
||||
|
||||
if ( ! module.__canUnload(initial, Array.from(chain)) )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( state === State.UNLOADING )
|
||||
return true;
|
||||
|
||||
else if ( state === State.UNLOADED )
|
||||
return true;
|
||||
|
||||
else if ( this.onLoad && ! this.onUnload )
|
||||
return false;
|
||||
|
||||
else if ( state === State.LOADING )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
__unload(args, initial, chain) {
|
||||
const path = this.__path || this.name,
|
||||
state = this.__load_state;
|
||||
|
@ -193,7 +237,7 @@ export class Module extends EventEmitter {
|
|||
else if ( state === State.UNLOADED )
|
||||
return Promise.resolve();
|
||||
|
||||
else if ( ! this.onUnload )
|
||||
else if ( this.onLoad && ! this.onUnload )
|
||||
return Promise.reject(new ModuleError(`attempted to unload module ${path} but module cannot be unloaded`));
|
||||
|
||||
else if ( state === State.LOADING )
|
||||
|
@ -220,7 +264,9 @@ export class Module extends EventEmitter {
|
|||
}
|
||||
|
||||
this.__time('unload-self');
|
||||
return this.onUnload(...args);
|
||||
if ( this.onUnload )
|
||||
return this.onUnload(...args);
|
||||
return null;
|
||||
|
||||
})().then(ret => {
|
||||
this.__load_state = State.UNLOADED;
|
||||
|
@ -307,6 +353,40 @@ export class Module extends EventEmitter {
|
|||
}
|
||||
|
||||
|
||||
__canDisable(initial, chain) {
|
||||
const path = this.__path || this.name,
|
||||
state = this.__state;
|
||||
|
||||
if ( chain.includes(this) )
|
||||
throw new CyclicDependencyError(`cyclic load requirements when checking if can disable ${initial}`, [...chain, this]);
|
||||
else if ( this.dependents ) {
|
||||
chain.push(this);
|
||||
|
||||
for(const dep of this.dependents) {
|
||||
const module = this.resolve(dep);
|
||||
if ( module ) {
|
||||
if ( chain.includes(module) )
|
||||
throw new CyclicDependencyError(`cyclic load requirements when checking if can disable ${initial}`, [...chain, this, module]);
|
||||
|
||||
if ( ! module.__canDisable(initial, Array.from(chain)) )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( state === State.DISABLING || state === State.DISABLED )
|
||||
return true;
|
||||
|
||||
else if ( ! this.onDisable )
|
||||
return false;
|
||||
|
||||
else if ( state === State.ENABLING )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
__disable(args, initial, chain) {
|
||||
const path = this.__path || this.name,
|
||||
state = this.__state;
|
||||
|
@ -516,6 +596,11 @@ export class Module extends EventEmitter {
|
|||
if ( this.enabled && ! module.enabled )
|
||||
module.enable();
|
||||
|
||||
module.references.push([this.__path, name]);
|
||||
|
||||
if ( this.__processModule )
|
||||
module = this.__processModule(module, name);
|
||||
|
||||
return this[name] = module;
|
||||
}
|
||||
|
||||
|
@ -569,9 +654,15 @@ export class Module extends EventEmitter {
|
|||
if ( require )
|
||||
requires.push(module.abs_path('.'));
|
||||
|
||||
|
||||
if ( this.enabled && ! module.enabled )
|
||||
module.enable();
|
||||
|
||||
module.references.push([this.__path, variable]);
|
||||
|
||||
if ( this.__processModule )
|
||||
module = this.__processModule(module, name);
|
||||
|
||||
return this[variable] = module;
|
||||
}
|
||||
|
||||
|
@ -600,6 +691,7 @@ export class Module extends EventEmitter {
|
|||
|
||||
inst.dependents = dependents[0];
|
||||
inst.load_dependents = dependents[1];
|
||||
inst.references = dependents[2];
|
||||
|
||||
if ( inst instanceof SiteModule && ! requires.includes('site') )
|
||||
requires.push('site');
|
||||
|
|
|
@ -8,7 +8,7 @@ import {has} from 'utilities/object';
|
|||
import Markdown from 'markdown-it';
|
||||
import MILA from 'markdown-it-link-attributes';
|
||||
|
||||
export const VERSION = 6;
|
||||
export const VERSION = 7;
|
||||
|
||||
export const TOKEN_TYPES = {};
|
||||
|
||||
|
@ -282,6 +282,34 @@ TOKEN_TYPES.box = function(token, createElement, ctx) {
|
|||
style['--ffz-lines'] = token.lines;
|
||||
}
|
||||
|
||||
if ( token.border )
|
||||
classes.push('tw-border');
|
||||
|
||||
if ( token.rounding ) {
|
||||
const round = getRoundClass(token.rounding);
|
||||
if ( round )
|
||||
classes.push(round);
|
||||
}
|
||||
|
||||
if ( token.background ) {
|
||||
if ( token.background === 'text' )
|
||||
style.backgroundColor = `var(--color-text-base)`;
|
||||
else if ( token.background === 'text-alt' )
|
||||
style.backgroundColor = `var(--color-text-alt)`;
|
||||
else if ( token.background === 'text-alt-2' )
|
||||
style.backgroundColor = `var(--color-text-alt-2)`;
|
||||
else if ( VALID_COLORS.includes(token.background) )
|
||||
classes.push(`tw-c-background-${token.background}`);
|
||||
else
|
||||
style.backgroundColor = token.background;
|
||||
}
|
||||
|
||||
if ( token.width )
|
||||
style.width = token.width;
|
||||
|
||||
if ( token.height )
|
||||
style.height = token.height;
|
||||
|
||||
applySpacing('pd', token, classes, style);
|
||||
applySpacing('mg', token, classes, style);
|
||||
|
||||
|
@ -338,7 +366,8 @@ TOKEN_TYPES.fieldset = function(token, createElement, ctx) {
|
|||
|
||||
|
||||
const name = renderTokens(field.name, createElement, ctx, token.markdown),
|
||||
value = renderTokens(field.value, createElement, ctx, token.markdown);
|
||||
value = renderTokens(field.value, createElement, ctx, token.markdown),
|
||||
icon = renderTokens(field.icon, createElement, ctx, token.markdown);
|
||||
|
||||
if ( name == null || value == null )
|
||||
continue;
|
||||
|
@ -347,20 +376,19 @@ TOKEN_TYPES.fieldset = function(token, createElement, ctx) {
|
|||
fields.push(createElement('div', {
|
||||
class: [
|
||||
'ffz--field',
|
||||
field.inline ? 'ffz--field-inline' : false
|
||||
field.inline ? 'ffz--field-inline' : false,
|
||||
icon ? 'ffz--field-icon' : false
|
||||
]
|
||||
}, [
|
||||
createElement('div', {
|
||||
class: 'ffz--field__name tw-semibold'
|
||||
}, name),
|
||||
createElement('div', {
|
||||
class: 'ffz--field__value tw-c-text-alt'
|
||||
}, value)
|
||||
createElement('div', {class: 'ffz--field__icon'}, icon),
|
||||
createElement('div', {class: 'ffz--field__name tw-semibold'}, name),
|
||||
createElement('div', {class: 'ffz--field__value tw-c-text-alt'}, value)
|
||||
]));
|
||||
else
|
||||
fields.push(createElement('div', {
|
||||
className: `ffz--field ${field.inline ? 'ffz--field-inline' : ''}`
|
||||
className: `ffz--field ${field.inline ? 'ffz--field-inline' : ''} ${icon ? 'ffz--field-icon' : ''}`
|
||||
}, [
|
||||
createElement('div', {className: 'ffz--field__icon'}, icon),
|
||||
createElement('div', {className: 'ffz--field__name tw-semibold'}, name),
|
||||
createElement('div', {className: 'ffz--field__value tw-c-text-alt'}, value)
|
||||
]));
|
||||
|
@ -531,6 +559,7 @@ TOKEN_TYPES.gallery = function(token, createElement, ctx) {
|
|||
|
||||
function header_vue(token, h, ctx) {
|
||||
let content = [];
|
||||
let background;
|
||||
|
||||
if ( token.title ) {
|
||||
const out = renderWithCapture(token.title, h, ctx, token.markdown);
|
||||
|
@ -569,6 +598,25 @@ function header_vue(token, h, ctx) {
|
|||
]
|
||||
}, content);
|
||||
|
||||
let bgtoken = resolveToken(token.sfw_background, ctx);
|
||||
const nsfw_bg_token = resolveToken(token.background, ctx);
|
||||
if ( nsfw_bg_token && canShowImage(nsfw_bg_token, ctx) )
|
||||
bgtoken = nsfw_bg_token;
|
||||
|
||||
if ( bgtoken ) {
|
||||
if ( bgtoken.type === 'image' )
|
||||
background = render_image({
|
||||
...bgtoken,
|
||||
aspect: undefined
|
||||
}, h, ctx);
|
||||
else if ( bgtoken.type === 'icon' )
|
||||
background = h('figure', {
|
||||
class: `ffz-i-${bgtoken.name}`
|
||||
});
|
||||
else
|
||||
background = renderWithCapture(token.background, h, ctx, token.markdown).content;
|
||||
}
|
||||
|
||||
let imtok = resolveToken(token.sfw_image, ctx);
|
||||
const nsfw_token = resolveToken(token.image, ctx);
|
||||
if ( nsfw_token && canShowImage(nsfw_token, ctx) )
|
||||
|
@ -576,11 +624,19 @@ function header_vue(token, h, ctx) {
|
|||
|
||||
if ( imtok ) {
|
||||
const aspect = imtok.aspect;
|
||||
let image;
|
||||
|
||||
if ( imtok.type === 'image' )
|
||||
image = render_image({
|
||||
...imtok,
|
||||
aspect: undefined
|
||||
}, h, ctx);
|
||||
|
||||
if ( imtok.type === 'icon' )
|
||||
image = h('figure', {
|
||||
class: `ffz-i-${imtok.name}`
|
||||
});
|
||||
|
||||
let image = render_image({
|
||||
...imtok,
|
||||
aspect: undefined
|
||||
}, h, ctx);
|
||||
const right = token.image_side === 'right';
|
||||
|
||||
if ( image ) {
|
||||
|
@ -626,11 +682,24 @@ function header_vue(token, h, ctx) {
|
|||
content
|
||||
]);
|
||||
|
||||
if ( background )
|
||||
content = h('div', {
|
||||
class: 'ffz--rich-header--background'
|
||||
}, [
|
||||
h('div', {
|
||||
class: 'ffz--rich-header__background'
|
||||
}, [
|
||||
background
|
||||
]),
|
||||
content
|
||||
]);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
function header_normal(token, createElement, ctx) {
|
||||
let content = [];
|
||||
let background;
|
||||
|
||||
if ( token.title ) {
|
||||
const out = renderWithCapture(token.title, createElement, ctx, token.markdown);
|
||||
|
@ -656,6 +725,25 @@ function header_normal(token, createElement, ctx) {
|
|||
}, out.content));
|
||||
}
|
||||
|
||||
let bgtoken = resolveToken(token.sfw_background, ctx);
|
||||
const nsfw_bg_token = resolveToken(token.background, ctx);
|
||||
if ( nsfw_bg_token && canShowImage(nsfw_bg_token, ctx) )
|
||||
bgtoken = nsfw_bg_token;
|
||||
|
||||
if ( bgtoken ) {
|
||||
if ( bgtoken.type === 'image' )
|
||||
background = render_image({
|
||||
...bgtoken,
|
||||
aspect: undefined
|
||||
}, createElement, ctx);
|
||||
else if ( bgtoken.type === 'icon' )
|
||||
background = createElement('figure', {
|
||||
className: `ffz-i-${bgtoken.name}`
|
||||
});
|
||||
else
|
||||
background = renderWithCapture(token.background, createElement, ctx, token.markdown).content;
|
||||
}
|
||||
|
||||
content = createElement('div', {
|
||||
className: `tw-flex tw-full-width tw-overflow-hidden ${token.compact ? 'ffz--rich-header ffz--compact-header tw-align-items-center' : 'tw-justify-content-center tw-flex-column tw-flex-grow-1'}`
|
||||
}, content);
|
||||
|
@ -668,10 +756,19 @@ function header_normal(token, createElement, ctx) {
|
|||
if ( imtok ) {
|
||||
const aspect = imtok.aspect;
|
||||
|
||||
let image = render_image({
|
||||
...imtok,
|
||||
aspect: undefined
|
||||
}, createElement, ctx);
|
||||
let image;
|
||||
|
||||
if ( imtok.type === 'image' )
|
||||
image = render_image({
|
||||
...imtok,
|
||||
aspect: undefined
|
||||
}, createElement, ctx);
|
||||
|
||||
if ( imtok.type === 'icon' )
|
||||
image = createElement('figure', {
|
||||
className: `ffz-i-${imtok.name}`
|
||||
});
|
||||
|
||||
const right = token.image_side === 'right';
|
||||
|
||||
if ( image ) {
|
||||
|
@ -718,6 +815,16 @@ function header_normal(token, createElement, ctx) {
|
|||
content
|
||||
]);
|
||||
|
||||
if ( background )
|
||||
content = createElement('div', {
|
||||
className: 'ffz--rich-header--background'
|
||||
}, [
|
||||
createElement('div', {
|
||||
className: 'ffz--rich-header__background'
|
||||
}, background),
|
||||
content
|
||||
]);
|
||||
|
||||
return content;
|
||||
|
||||
}
|
||||
|
@ -783,6 +890,9 @@ function render_image(token, createElement, ctx) {
|
|||
}
|
||||
};
|
||||
|
||||
if ( token.contain )
|
||||
stuff.style.objectFit = 'contain';
|
||||
|
||||
if ( ctx.onload )
|
||||
stuff.on = {load: ctx.onload};
|
||||
|
||||
|
@ -811,6 +921,9 @@ function render_image(token, createElement, ctx) {
|
|||
}
|
||||
});
|
||||
|
||||
if ( token.contain )
|
||||
image.style.objectFit = 'contain';
|
||||
|
||||
if ( ! aspect )
|
||||
return image;
|
||||
|
||||
|
@ -840,8 +953,12 @@ TOKEN_TYPES.i18n = function(token, createElement, ctx) {
|
|||
return null;
|
||||
}
|
||||
|
||||
let key = token.key;
|
||||
if ( ctx.i18n_prefix )
|
||||
key = `${ctx.i18n_prefix}.${key}`;
|
||||
|
||||
return renderTokens(
|
||||
ctx.i18n.tList(token.key, token.phrase, token.content),
|
||||
ctx.i18n.tList(key, token.phrase, token.content),
|
||||
createElement,
|
||||
ctx,
|
||||
token.markdown
|
||||
|
|
|
@ -48,6 +48,70 @@
|
|||
margin-right: -0.5rem;
|
||||
}
|
||||
|
||||
.ffz--chat-card {
|
||||
--ffz-rich-header-outline: var(--color-background-base);
|
||||
.ffz--rich-header--background {
|
||||
margin: 0; // overflow hidden is in play
|
||||
}
|
||||
}
|
||||
|
||||
.ffz__tooltip {
|
||||
--ffz-rich-header-outline: var(--color-background-tooltip);
|
||||
.ffz--rich-header--background {
|
||||
margin: -.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ffz--rich-header--background {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
--ffz-rich-header-outline: #000;
|
||||
--color-background-base: #000;
|
||||
--color-text-base: #efeff1;
|
||||
--color-text-alt: #dedee3;
|
||||
--color-text-alt-2: #adadb8;
|
||||
|
||||
--color-background-tooltip: var(--color-background-base);
|
||||
--color-text-tooltip: var(--color-text-base);
|
||||
--color-text-tooltip-alt: var(--color-text-alt);
|
||||
--color-text-tooltip-alt-2: var(--color-text-alt-2);
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
margin: -1rem;
|
||||
margin-bottom: 0 !important;
|
||||
|
||||
background: var(--color-background-base);
|
||||
|
||||
text-shadow: -1px 1px 2px var(--ffz-rich-header-outline),
|
||||
1px 1px 2px var(--ffz-rich-header-outline),
|
||||
1px -1px 0 var(--ffz-rich-header-outline),
|
||||
-1px -1px 0 var(--ffz-rich-header-outline);
|
||||
|
||||
& > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ffz--rich-header__background {
|
||||
position: absolute !important;
|
||||
z-index: 0 !important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
opacity: 0.5;
|
||||
|
||||
& > img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ffz--overlay {
|
||||
position: relative;
|
||||
|
||||
|
@ -132,6 +196,11 @@
|
|||
height: 4.8rem;
|
||||
max-width: 25%;
|
||||
|
||||
figure {
|
||||
line-height: 4.8rem;
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
height: 100%;
|
||||
|
@ -145,6 +214,12 @@
|
|||
|
||||
.ffz--compact-header .ffz--header-image {
|
||||
height: 2.4rem;
|
||||
|
||||
figure {
|
||||
line-height: 2.4rem;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ffz--rich-gallery, .ffz--compact-header {
|
||||
|
@ -200,6 +275,18 @@
|
|||
width: unset;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.ffz--field-icon {
|
||||
position: relative;
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ffz--field__icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.ffz--twitter-badge {
|
||||
|
|
|
@ -72,6 +72,10 @@
|
|||
.ffz-i-right-open:before { content: '\e846'; } /* '' */
|
||||
.ffz-i-mastodon:before { content: '\e847'; } /* '' */
|
||||
.ffz-i-volume-up:before { content: '\e848'; } /* '' */
|
||||
.ffz-i-unmod:before { content: '\e849'; } /* '' */
|
||||
.ffz-i-mod:before { content: '\e84a'; } /* '' */
|
||||
.ffz-i-flag:before { content: '\e84b'; } /* '' */
|
||||
.ffz-i-mange-suspicious:before { content: '\e84c'; } /* '' */
|
||||
.ffz-i-move:before { content: '\f047'; } /* '' */
|
||||
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
||||
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
||||
|
@ -84,6 +88,7 @@
|
|||
.ffz-i-chat-empty:before { content: '\f0e6'; } /* '' */
|
||||
.ffz-i-download-cloud:before { content: '\f0ed'; } /* '' */
|
||||
.ffz-i-upload-cloud:before { content: '\f0ee'; } /* '' */
|
||||
.ffz-i-doc-text:before { content: '\f0f6'; } /* '' */
|
||||
.ffz-i-reply:before { content: '\f112'; } /* '' */
|
||||
.ffz-i-smile:before { content: '\f118'; } /* '' */
|
||||
.ffz-i-keyboard:before { content: '\f11c'; } /* '' */
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -72,6 +72,10 @@
|
|||
.ffz-i-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-mastodon { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-volume-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-unmod { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-mod { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-flag { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-mange-suspicious { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
@ -84,6 +88,7 @@
|
|||
.ffz-i-chat-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-download-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-upload-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-keyboard { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
|
|
@ -83,6 +83,10 @@
|
|||
.ffz-i-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-mastodon { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-volume-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-unmod { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-mod { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-flag { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-mange-suspicious { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
@ -95,6 +99,7 @@
|
|||
.ffz-i-chat-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-download-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-upload-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-keyboard { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
@font-face {
|
||||
font-family: 'ffz-fontello';
|
||||
src: url('../font/ffz-fontello.eot?19069837');
|
||||
src: url('../font/ffz-fontello.eot?19069837#iefix') format('embedded-opentype'),
|
||||
url('../font/ffz-fontello.woff2?19069837') format('woff2'),
|
||||
url('../font/ffz-fontello.woff?19069837') format('woff'),
|
||||
url('../font/ffz-fontello.ttf?19069837') format('truetype'),
|
||||
url('../font/ffz-fontello.svg?19069837#ffz-fontello') format('svg');
|
||||
src: url('../font/ffz-fontello.eot?30569253');
|
||||
src: url('../font/ffz-fontello.eot?30569253#iefix') format('embedded-opentype'),
|
||||
url('../font/ffz-fontello.woff2?30569253') format('woff2'),
|
||||
url('../font/ffz-fontello.woff?30569253') format('woff'),
|
||||
url('../font/ffz-fontello.ttf?30569253') format('truetype'),
|
||||
url('../font/ffz-fontello.svg?30569253#ffz-fontello') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
|||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
@font-face {
|
||||
font-family: 'ffz-fontello';
|
||||
src: url('../font/ffz-fontello.svg?19069837#ffz-fontello') format('svg');
|
||||
src: url('../font/ffz-fontello.svg?30569253#ffz-fontello') format('svg');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -127,6 +127,10 @@
|
|||
.ffz-i-right-open:before { content: '\e846'; } /* '' */
|
||||
.ffz-i-mastodon:before { content: '\e847'; } /* '' */
|
||||
.ffz-i-volume-up:before { content: '\e848'; } /* '' */
|
||||
.ffz-i-unmod:before { content: '\e849'; } /* '' */
|
||||
.ffz-i-mod:before { content: '\e84a'; } /* '' */
|
||||
.ffz-i-flag:before { content: '\e84b'; } /* '' */
|
||||
.ffz-i-mange-suspicious:before { content: '\e84c'; } /* '' */
|
||||
.ffz-i-move:before { content: '\f047'; } /* '' */
|
||||
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
|
||||
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
||||
|
@ -139,6 +143,7 @@
|
|||
.ffz-i-chat-empty:before { content: '\f0e6'; } /* '' */
|
||||
.ffz-i-download-cloud:before { content: '\f0ed'; } /* '' */
|
||||
.ffz-i-upload-cloud:before { content: '\f0ee'; } /* '' */
|
||||
.ffz-i-doc-text:before { content: '\f0f6'; } /* '' */
|
||||
.ffz-i-reply:before { content: '\f112'; } /* '' */
|
||||
.ffz-i-smile:before { content: '\f118'; } /* '' */
|
||||
.ffz-i-keyboard:before { content: '\f11c'; } /* '' */
|
||||
|
|
54
styles/widgets/chat-tester.scss
Normal file
54
styles/widgets/chat-tester.scss
Normal file
|
@ -0,0 +1,54 @@
|
|||
.ffz-ct--obj-open,
|
||||
.ffz-ct--obj-close {
|
||||
&[depth="1"], &[depth="5"], &[depth="9"] {
|
||||
color: var(--color-text-alt-2);
|
||||
}
|
||||
|
||||
&[depth="2"], &[depth="6"], &[depth="10"] {
|
||||
color: var(--color-text-error);
|
||||
}
|
||||
|
||||
&[depth="3"], &[depth="7"], &[depth="11"] {
|
||||
color: var(--color-text-prime);
|
||||
}
|
||||
|
||||
&[depth="4"], &[depth="8"] {
|
||||
color: var(--color-text-success);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.ffz-ct--obj-sep,
|
||||
.ffz-ct--obj-key-sep,
|
||||
|
||||
.ffz-ct--params,
|
||||
.ffz-ct--prefix,
|
||||
.ffz-ct--tags {
|
||||
color: var(--color-text-alt-2);
|
||||
}
|
||||
|
||||
.ffz-ct--obj-key,
|
||||
.ffz-ct--command,
|
||||
.ffz-ct--tag {
|
||||
color: var(--color-text-warn);
|
||||
}
|
||||
|
||||
.ffz-ct--channel,
|
||||
.ffz-ct--user {
|
||||
color: var(--color-text-success);
|
||||
}
|
||||
|
||||
.ffz-ct--literal,
|
||||
.ffz-ct--param {
|
||||
color: var(--color-text-prime);
|
||||
}
|
||||
|
||||
.ffz-ct--string,
|
||||
.ffz-ct--tag-value {
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.ffz-ct--tags,
|
||||
.ffz-ct--params {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue