mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.20.75
* Added: Better options for highlight / block terms, letting you make rules case sensitive, match whole words, and highlight in chat or not. * Added: Documentation of glob syntax. * Changed: Split `Chat > Filtering` into several sub-categories to make it easier to find specific options. * Changed: Display seconds with the up-time metadata by default, matching Twitch. * Fixed: The Unfollow button not hiding on the standalone player. * Fixed: Loading issue for graphs on the dashboard. * API Added: Allow add-ons to target the `player` flavor.
This commit is contained in:
parent
715a92e298
commit
16ab515b4b
26 changed files with 523 additions and 220 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.20.74",
|
||||
"version": "4.20.75",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
|
|
|
@ -24,6 +24,26 @@ import Actions from './actions';
|
|||
|
||||
export const SEPARATORS = '[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]';
|
||||
|
||||
function addSeparators(str) {
|
||||
return `(^|.*?${SEPARATORS})(?:${str})(?=$|${SEPARATORS})`
|
||||
}
|
||||
|
||||
const TERM_FLAGS = ['g', 'gi'];
|
||||
|
||||
function formatTerms(data) {
|
||||
const out = [];
|
||||
|
||||
for(let i=0; i < data.length; i++) {
|
||||
const list = data[i];
|
||||
if ( list[0].length )
|
||||
list[1].push(addSeparators(list[0].join('|')));
|
||||
|
||||
out.push(list[1].length ? new RegExp(list[1].join('|'), TERM_FLAGS[i] || 'gi') : null);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const ERROR_IMAGE = 'https://static-cdn.jtvnw.net/emoticons/v1/58765/2.0';
|
||||
const EMOTE_CHARS = /[ .,!]/;
|
||||
|
||||
|
@ -227,7 +247,7 @@ export default class Chat extends Module {
|
|||
this.settings.add('chat.filtering.click-to-reveal', {
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Behavior',
|
||||
path: 'Chat > Filtering > General @{"sort":-1} >> Behavior',
|
||||
title: 'Click to reveal deleted terms.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
|
@ -283,7 +303,7 @@ export default class Chat extends Module {
|
|||
this.settings.add('chat.automod.delete-messages', {
|
||||
default: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> AutoMod Filters @{"description": "Extra configuration for Twitch\'s native `Chat Filters`."}',
|
||||
path: 'Chat > Filtering > General >> AutoMod Filters @{"description": "Extra configuration for Twitch\'s native `Chat Filters`."}',
|
||||
title: 'Mark messages as deleted if they contain filtered phrases.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
|
@ -292,7 +312,7 @@ export default class Chat extends Module {
|
|||
this.settings.add('chat.automod.remove-messages', {
|
||||
default: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> AutoMod Filters',
|
||||
path: 'Chat > Filtering > General >> AutoMod Filters',
|
||||
title: 'Remove messages entirely if they contain filtered phrases.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
|
@ -301,7 +321,7 @@ export default class Chat extends Module {
|
|||
this.settings.add('chat.automod.run-as-mod', {
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> AutoMod Filters',
|
||||
path: 'Chat > Filtering > General >> AutoMod Filters',
|
||||
title: 'Use Chat Filters as a moderator.',
|
||||
description: 'By default, Twitch\'s Chat Filters feature does not function for moderators. This overrides that behavior.',
|
||||
component: 'setting-check-box'
|
||||
|
@ -311,7 +331,7 @@ export default class Chat extends Module {
|
|||
this.settings.add('chat.filtering.process-own', {
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Behavior',
|
||||
path: 'Chat > Filtering > General >> Behavior',
|
||||
title: 'Filter your own messages.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
|
@ -372,7 +392,7 @@ export default class Chat extends Module {
|
|||
type: 'array_merge',
|
||||
always_inherit: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Highlight Users',
|
||||
path: 'Chat > Filtering > Highlight >> Users',
|
||||
component: 'basic-terms',
|
||||
colored: true,
|
||||
words: false
|
||||
|
@ -431,7 +451,7 @@ export default class Chat extends Module {
|
|||
type: 'array_merge',
|
||||
always_inherit: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Blocked Users',
|
||||
path: 'Chat > Filtering > Block >> Users',
|
||||
component: 'basic-terms',
|
||||
removable: true,
|
||||
words: false
|
||||
|
@ -480,7 +500,7 @@ export default class Chat extends Module {
|
|||
type: 'array_merge',
|
||||
always_inherit: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Highlight Badges',
|
||||
path: 'Chat > Filtering > Highlight >> Badges',
|
||||
component: 'badge-highlighting',
|
||||
colored: true,
|
||||
data: () => this.badges.getSettingsBadges()
|
||||
|
@ -516,7 +536,7 @@ export default class Chat extends Module {
|
|||
type: 'array_merge',
|
||||
always_inherit: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Blocked Badges @{"description": "**Note:** This section is for filtering messages out of chat from users with specific badges. If you wish to hide a badge, go to [Chat > Badges >> Visibility](~chat.badges.tabs.visibility)."}',
|
||||
path: 'Chat > Filtering > Block >> Badges @{"description": "**Note:** This section is for filtering messages out of chat from users with specific badges. If you wish to hide a badge, go to [Chat > Badges >> Visibility](~chat.badges.tabs.visibility)."}',
|
||||
component: 'badge-highlighting',
|
||||
removable: true,
|
||||
data: () => this.badges.getSettingsBadges()
|
||||
|
@ -549,35 +569,39 @@ export default class Chat extends Module {
|
|||
type: 'array_merge',
|
||||
always_inherit: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Highlight Terms',
|
||||
path: 'Chat > Filtering > Highlight >> Terms @{"description": "Please see [Chat > Filtering > Syntax Help](~) for details on how to use terms."}',
|
||||
component: 'basic-terms',
|
||||
colored: true
|
||||
colored: true,
|
||||
highlight: true
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.filtering.highlight-basic-terms--color-regex', {
|
||||
requires: ['chat.filtering.highlight-basic-terms'],
|
||||
requires: ['chat.filtering.highlight-tokens', 'chat.filtering.highlight-basic-terms'],
|
||||
equals: 'requirements',
|
||||
process(ctx) {
|
||||
const can_highlight = ctx.get('chat.filtering.highlight-tokens');
|
||||
const val = ctx.get('chat.filtering.highlight-basic-terms');
|
||||
if ( ! val || ! val.length )
|
||||
return null;
|
||||
|
||||
const colors = new Map;
|
||||
let has_highlight = false,
|
||||
has_non = false;
|
||||
|
||||
for(const item of val) {
|
||||
const c = item.c || null,
|
||||
t = item.t;
|
||||
highlight = can_highlight && (has(item, 'h') ? item.h : true),
|
||||
sensitive = item.s,
|
||||
t = item.t,
|
||||
word = has(item, 'w') ? item.w : t !== 'raw';
|
||||
|
||||
let v = item.v, word = true;
|
||||
let v = item.v;
|
||||
|
||||
if ( t === 'glob' )
|
||||
v = glob_to_regex(v);
|
||||
|
||||
else if ( t === 'raw' )
|
||||
word = false;
|
||||
|
||||
else if ( t !== 'regex' )
|
||||
else if ( t !== 'regex' && t !== 'raw' )
|
||||
v = escape_regex(v);
|
||||
|
||||
if ( ! v || ! v.length )
|
||||
|
@ -589,23 +613,55 @@ export default class Chat extends Module {
|
|||
continue;
|
||||
}
|
||||
|
||||
if ( colors.has(c) )
|
||||
colors.get(c)[word ? 0 : 1].push(v);
|
||||
else {
|
||||
const vals = [[],[]];
|
||||
colors.set(c, vals);
|
||||
vals[word ? 0 : 1].push(v);
|
||||
}
|
||||
if ( highlight )
|
||||
has_highlight = true;
|
||||
else
|
||||
has_non = true;
|
||||
|
||||
let data = colors.get(c);
|
||||
if ( ! data )
|
||||
colors.set(c, data = [
|
||||
[ // highlight
|
||||
[ // sensitive
|
||||
[], [] // word
|
||||
],
|
||||
[
|
||||
[], []
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
[], []
|
||||
],
|
||||
[
|
||||
[], []
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
data[highlight ? 0 : 1][sensitive ? 0 : 1][word ? 0 : 1].push(v);
|
||||
}
|
||||
|
||||
if ( ! has_highlight && ! has_non )
|
||||
return null;
|
||||
|
||||
const out = {
|
||||
hl: has_highlight ? new Map : null,
|
||||
non: has_non ? new Map : null
|
||||
};
|
||||
|
||||
for(const [key, list] of colors) {
|
||||
if ( list[0].length )
|
||||
list[1].push(`(^|.*?${SEPARATORS})(?:${list[0].join('|')})(?=$|${SEPARATORS})`);
|
||||
const highlights = formatTerms(list[0]),
|
||||
non_highlights = formatTerms(list[1]);
|
||||
|
||||
colors.set(key, new RegExp(list[1].join('|'), 'gi'));
|
||||
if ( highlights[0] || highlights[1] )
|
||||
out.hl.set(key, highlights);
|
||||
|
||||
if ( non_highlights[0] || non_highlights[1] )
|
||||
out.non.set(key, non_highlights);
|
||||
}
|
||||
|
||||
return colors;
|
||||
return out;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -615,7 +671,7 @@ export default class Chat extends Module {
|
|||
type: 'array_merge',
|
||||
always_inherit: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Blocked Terms',
|
||||
path: 'Chat > Filtering > Block >> Terms @{"description": "Please see [Chat > Filtering > Syntax Help](~) for details on how to use terms."}',
|
||||
component: 'basic-terms',
|
||||
removable: true
|
||||
}
|
||||
|
@ -636,16 +692,14 @@ export default class Chat extends Module {
|
|||
];
|
||||
|
||||
for(const item of val) {
|
||||
const t = item.t;
|
||||
let v = item.v, word = true;
|
||||
const t = item.t,
|
||||
word = has(item, 'w') ? item.w : t !== 'raw';
|
||||
let v = item.v;
|
||||
|
||||
if ( t === 'glob' )
|
||||
v = glob_to_regex(v);
|
||||
|
||||
else if ( t === 'raw' )
|
||||
word = false;
|
||||
|
||||
else if ( t !== 'regex' )
|
||||
else if ( t !== 'regex' && t !== 'raw' )
|
||||
v = escape_regex(v);
|
||||
|
||||
if ( ! v || ! v.length )
|
||||
|
@ -677,7 +731,7 @@ export default class Chat extends Module {
|
|||
default: false,
|
||||
ui: {
|
||||
component: 'setting-check-box',
|
||||
path: 'Chat > Filtering >> Appearance',
|
||||
path: 'Chat > Filtering > General >> Appearance',
|
||||
title: 'Display mentions in chat with username colors.',
|
||||
description: '**Note:** Not compatible with color overrides as mentions do not include user IDs.'
|
||||
}
|
||||
|
@ -687,7 +741,7 @@ export default class Chat extends Module {
|
|||
default: true,
|
||||
ui: {
|
||||
component: 'setting-check-box',
|
||||
path: 'Chat > Filtering >> Appearance',
|
||||
path: 'Chat > Filtering > General >> Appearance',
|
||||
title: 'Display mentions in chat with a bold font.'
|
||||
}
|
||||
});
|
||||
|
@ -695,7 +749,7 @@ export default class Chat extends Module {
|
|||
this.settings.add('chat.filtering.mention-color', {
|
||||
default: '',
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Appearance',
|
||||
path: 'Chat > Filtering > General >> Appearance',
|
||||
title: 'Custom Highlight Color',
|
||||
component: 'setting-color-box',
|
||||
description: 'If this is set, highlighted messages with no default color set will use this color rather than red.'
|
||||
|
@ -705,7 +759,7 @@ export default class Chat extends Module {
|
|||
this.settings.add('chat.filtering.highlight-mentions', {
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Appearance',
|
||||
path: 'Chat > Filtering > General >> Appearance',
|
||||
title: 'Highlight messages that mention you.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
|
@ -714,7 +768,7 @@ export default class Chat extends Module {
|
|||
this.settings.add('chat.filtering.highlight-tokens', {
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Appearance',
|
||||
path: 'Chat > Filtering > General >> Appearance',
|
||||
title: 'Highlight matched words in chat.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
|
|
|
@ -564,11 +564,35 @@ export const CustomHighlights = {
|
|||
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
||||
return tokens;
|
||||
|
||||
const colors = this.context.get('chat.filtering.highlight-basic-terms--color-regex');
|
||||
if ( ! colors || ! colors.size )
|
||||
const data = this.context.get('chat.filtering.highlight-basic-terms--color-regex');
|
||||
if ( ! data )
|
||||
return tokens;
|
||||
|
||||
for(const [color, regex] of colors) {
|
||||
if ( data.non ) {
|
||||
for(const [color, regexes] of data.non) {
|
||||
let matched = false;
|
||||
if ( regexes[0] ) {
|
||||
regexes[0].lastIndex = 0;
|
||||
matched = regexes[0].test(msg.message);
|
||||
}
|
||||
if ( ! matched && regexes[1] ) {
|
||||
regexes[1].lastIndex = 0;
|
||||
matched = regexes[1].test(msg.message);
|
||||
}
|
||||
|
||||
if ( matched ) {
|
||||
(msg.highlights = (msg.highlights || new Set())).add('term');
|
||||
msg.mentioned = true;
|
||||
msg.mention_color = color || msg.mention_color;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! data.hl )
|
||||
return tokens;
|
||||
|
||||
for(const [color, regexes] of data.hl) {
|
||||
const out = [];
|
||||
for(const token of tokens) {
|
||||
if ( token.type !== 'text' ) {
|
||||
|
@ -576,11 +600,23 @@ export const CustomHighlights = {
|
|||
continue;
|
||||
}
|
||||
|
||||
regex.lastIndex = 0;
|
||||
const text = token.text;
|
||||
let idx = 0, match;
|
||||
|
||||
while((match = regex.exec(text))) {
|
||||
while(idx < text.length) {
|
||||
if ( regexes[0] )
|
||||
regexes[0].lastIndex = idx;
|
||||
if ( regexes[1] )
|
||||
regexes[1].lastIndex = idx;
|
||||
|
||||
match = regexes[0] ? regexes[0].exec(text) : null;
|
||||
const second = regexes[1] ? regexes[1].exec(text) : null;
|
||||
if ( second && (! match || match.index > second.index) )
|
||||
match = second;
|
||||
|
||||
if ( ! match )
|
||||
break;
|
||||
|
||||
const raw_nix = match.index,
|
||||
offset = match[1] ? match[1].length : 0,
|
||||
nix = raw_nix + offset;
|
||||
|
@ -590,7 +626,8 @@ export const CustomHighlights = {
|
|||
|
||||
(msg.highlights = (msg.highlights || new Set())).add('term');
|
||||
msg.mentioned = true;
|
||||
msg.mention_color = color || msg.mention_color;
|
||||
if ( ! msg.mention_color )
|
||||
msg.mention_color = color;
|
||||
|
||||
out.push({
|
||||
type: 'highlight',
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<term-editor
|
||||
:term="default_term"
|
||||
:colored="item.colored"
|
||||
:highlight="item.highlight"
|
||||
:words="item.words"
|
||||
:removable="item.removable"
|
||||
:adding="true"
|
||||
|
@ -17,6 +18,7 @@
|
|||
:key="term.id"
|
||||
:term="term.v"
|
||||
:colored="item.colored"
|
||||
:highlight="item.highlight"
|
||||
:words="item.words"
|
||||
:removable="item.removable"
|
||||
@remove="remove(term)"
|
||||
|
@ -29,7 +31,7 @@
|
|||
<script>
|
||||
|
||||
import SettingMixin from '../setting-mixin';
|
||||
import {deep_copy} from 'utilities/object';
|
||||
import {deep_copy, has} from 'utilities/object';
|
||||
|
||||
let last_id = 0;
|
||||
|
||||
|
@ -43,6 +45,9 @@ export default {
|
|||
v: '',
|
||||
t: 'text',
|
||||
c: '',
|
||||
s: false,
|
||||
h: false,
|
||||
w: true,
|
||||
remove: false
|
||||
}
|
||||
}
|
||||
|
@ -61,9 +66,21 @@ export default {
|
|||
const out = [];
|
||||
|
||||
if ( Array.isArray(this.val) )
|
||||
for(const term of this.val)
|
||||
for(const term of this.val) {
|
||||
if ( term && term.v ) {
|
||||
if ( ! has(term.v, 'w') )
|
||||
term.v.w = term.v.t !== 'raw';
|
||||
|
||||
if ( ! has(term.v, 'h') )
|
||||
term.v.h = true;
|
||||
|
||||
if ( term.v.t === 'raw' )
|
||||
term.v.t = 'regex';
|
||||
}
|
||||
|
||||
if ( term && term.t !== 'inherit' )
|
||||
out.push(term);
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
|
|
@ -294,6 +294,12 @@ export default {
|
|||
|
||||
this.$nextTick(bits);
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
const el = this.$el.querySelector(`.ffz--menu-tree li[data-key="${item.full_key}"]`);
|
||||
if ( el )
|
||||
el.scrollIntoView();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
v-for="item in displayed"
|
||||
:key="item.full_key"
|
||||
:class="[currentItem === item ? 'active' : '']"
|
||||
:data-key="item.full_key"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template lang="html">
|
||||
<div class="ffz--term">
|
||||
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row tw-full-width">
|
||||
<div class="tw-align-items-center tw-flex tw-flex-wrap tw-flex-row tw-full-width">
|
||||
<div v-if="! is_valid" class="tw-relative tw-tooltip__container tw-mg-r-05">
|
||||
<figure class="tw-c-text-error ffz-i-attention" />
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-left">
|
||||
|
@ -48,37 +48,131 @@
|
|||
<option value="glob">
|
||||
{{ t('setting.terms.type.glob', 'Glob') }}
|
||||
</option>
|
||||
<option v-if="words" value="regex">
|
||||
<!--option v-if="words" value="regex">
|
||||
{{ t('setting.terms.type.regex-word', 'Regex (Word)') }}
|
||||
</option>
|
||||
</option-->
|
||||
<option value="raw">
|
||||
{{ t('setting.terms.type.regex', 'Regex') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="removable" class="tw-flex-shrink-0 tw-mg-r-05 tw-relative tw-tooltip__container">
|
||||
<button
|
||||
<div
|
||||
v-if="editing || display.s"
|
||||
class="tw-flex-shrink-0 tw-mg-r-05 tw-mg-y-05 tw-flex tw-align-items-center ffz-checkbox tw-relative tw-tooltip__container"
|
||||
>
|
||||
<input
|
||||
v-if="editing"
|
||||
:class="{active: edit_data.remove}"
|
||||
class="tw-button ffz-directory-toggle-block"
|
||||
@click="toggleRemove"
|
||||
:id="'sensitive$' + id"
|
||||
v-model="edit_data.s"
|
||||
type="checkbox"
|
||||
class="ffz-min-width-unset ffz-checkbox__input"
|
||||
>
|
||||
<span
|
||||
:class="edit_data.remove ? 'ffz-i-eye-off' : 'ffz-i-eye'"
|
||||
class="tw-button__text"
|
||||
<label
|
||||
v-if="editing"
|
||||
:for="'sensitive$' + id"
|
||||
class="ffz-min-width-unset ffz-checkbox__label"
|
||||
>
|
||||
<span class="tw-mg-l-05">
|
||||
{{ t('settings.terms.sensitive', 'Aa') }}
|
||||
</span>
|
||||
</label>
|
||||
<div v-else-if="display.s" class="tw-relative tw-tooltip__container">
|
||||
<span class="tw-mg-l-05">
|
||||
{{ t('settings.terms.sensitive', 'Aa') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
{{ t('settings.terms.sensitive.tip', 'Case Sensitive') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="words && (editing || display.w)"
|
||||
class="tw-flex-shrink-0 tw-mg-r-05 tw-mg-y-05 tw-flex tw-align-items-center ffz-checkbox tw-relative tw-tooltip__container"
|
||||
>
|
||||
<input
|
||||
v-if="editing"
|
||||
:id="'word$' + id"
|
||||
v-model="edit_data.w"
|
||||
type="checkbox"
|
||||
class="ffz-min-width-unset ffz-checkbox__input"
|
||||
>
|
||||
<label
|
||||
v-if="editing"
|
||||
:for="'word$' + id"
|
||||
class="ffz-min-width-unset ffz-checkbox__label"
|
||||
>
|
||||
<span class="tw-mg-l-05 ffz--whole-word">
|
||||
{{ t('settings.terms.word', 'Abc') }}
|
||||
</span>
|
||||
</label>
|
||||
<div v-else-if="display.w" class="tw-relative tw-tooltip__container">
|
||||
<span class="tw-mg-l-05 ffz--whole-word">
|
||||
{{ t('settings.terms.word', 'Abc') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
{{ t('settings.terms.word.tip', 'Match Whole Words') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="highlight && (editing || display.h)"
|
||||
class="tw-flex-shrink-0 tw-mg-r-05 tw-mg-y-05 tw-flex tw-align-items-center ffz-checkbox tw-relative tw-tooltip__container"
|
||||
>
|
||||
<input
|
||||
v-if="editing"
|
||||
:id="'highlight$' + id"
|
||||
v-model="edit_data.h"
|
||||
type="checkbox"
|
||||
class="ffz-min-width-unset ffz-checkbox__input"
|
||||
>
|
||||
<label
|
||||
v-if="editing"
|
||||
:for="'highlight$' + id"
|
||||
class="ffz-min-width-unset ffz-checkbox__label"
|
||||
>
|
||||
<span class="tw-mg-l-05 ffz--highlight">
|
||||
{{ t('settings.terms.highlight', 'Hlt') }}
|
||||
</span>
|
||||
</label>
|
||||
<div v-else-if="display.h" class="tw-relative tw-tooltip__container">
|
||||
<span class="tw-mg-l-05 ffz--highlight">
|
||||
{{ t('settings.terms.highlight', 'Hlt') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
<markdown
|
||||
:source="t(
|
||||
'settings.terms.highlight.tip',
|
||||
'Highlight Matches\n\nThis requires the \'Highlight matched words in chat.\' setting to work.'
|
||||
)"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="removable && (editing || display.remove)"
|
||||
class="tw-flex-shrink-0 tw-mg-r-05 tw-mg-y-05 tw-flex tw-align-items-center ffz-checkbox tw-relative tw-tooltip__container"
|
||||
>
|
||||
<input
|
||||
v-if="editing"
|
||||
:id="'remove$' + id"
|
||||
v-model="edit_data.remove"
|
||||
type="checkbox"
|
||||
class="ffz-min-width-unset ffz-checkbox__input"
|
||||
>
|
||||
|
||||
<label
|
||||
v-if="editing"
|
||||
:for="'remove$' + id"
|
||||
class="ffz-min-width-unset ffz-checkbox__label"
|
||||
>
|
||||
<span class="tw-mg-l-05 ffz-i-trash" />
|
||||
</label>
|
||||
<span
|
||||
v-else-if="term.remove"
|
||||
class="ffz-i-eye-off tw-pd-x-1"
|
||||
class="ffz-i-trash tw-pd-x-1"
|
||||
/>
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
<span v-if="display.remove">
|
||||
{{ t('setting.terms.remove.on', 'Remove matching messages from chat.') }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('setting.terms.remove.off', 'Do not remove matching messages from chat.') }}
|
||||
</span>
|
||||
{{ t('setting.terms.remove.on', 'Remove matching messages from chat.') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="adding" class="tw-flex-shrink-0">
|
||||
|
@ -145,6 +239,10 @@ let id = 0;
|
|||
export default {
|
||||
props: {
|
||||
term: Object,
|
||||
highlight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
words: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
|
@ -166,14 +264,14 @@ export default {
|
|||
data() {
|
||||
if ( this.adding )
|
||||
return {
|
||||
editor_id: id++,
|
||||
id: id++,
|
||||
deleting: false,
|
||||
editing: true,
|
||||
edit_data: deep_copy(this.term)
|
||||
};
|
||||
|
||||
return {
|
||||
editor_id: id++,
|
||||
id: id++,
|
||||
deleting: false,
|
||||
editing: false,
|
||||
edit_data: null
|
||||
|
|
|
@ -85,6 +85,13 @@ export default class MainMenu extends Module {
|
|||
key: 'faq'
|
||||
});
|
||||
|
||||
this.settings.addUI('chat.filtering.syntax-help', {
|
||||
path: 'Chat > Filtering > Syntax Help @{"profile_warning": false}',
|
||||
component: 'md-page',
|
||||
key: 'term-syntax',
|
||||
force_seen: true
|
||||
});
|
||||
|
||||
/*this.settings.addUI('privacy', {
|
||||
path: 'Home > Privacy @{"profile_warning": false}',
|
||||
component: 'md-page',
|
||||
|
|
78
src/modules/main_menu/term-syntax.md
Normal file
78
src/modules/main_menu/term-syntax.md
Normal file
|
@ -0,0 +1,78 @@
|
|||
This is a guide to the different term syntax options available. Internally,
|
||||
FrankerFaceZ converts your highlight and block terms into regular
|
||||
expressions.
|
||||
|
||||
|
||||
### Types
|
||||
|
||||
#### 1. Text
|
||||
|
||||
Text has no special behavior. Any characters with special meaning in regular
|
||||
expressions are escaped.
|
||||
|
||||
|
||||
#### 2. Glob
|
||||
|
||||
[Globs](https://en.wikipedia.org/wiki/Glob_(programming)) provide simple, easy
|
||||
to write pattern matching with wildcard characters. For example, the glob
|
||||
`cir*` would match all words starting with the letters `cir`, including just
|
||||
`cir` by itself. The `*` is a wildcard matching any number of characters.
|
||||
|
||||
| Wildcard | Description | Example | Matches | Does Not Match |
|
||||
| :------: | :---------- | :-----: | :-----: | :------------: |
|
||||
| `*` | Matches any number of non-space characters, including none. | `cir*` | `cir`, `circle`, `cirFairy`, `circumstance` | `ci`, `sir`, `pizza` |
|
||||
| `**` | Matches any number of any characters, including none. Unlike a single `*`, this will match space characters. | `hello**!` | `hello!`, `hello, streamer!` |
|
||||
| `?` | Matches any single character. | `?at` | `cat`, `hat`, `pat` | `at`
|
||||
| `[abc]` | Matches one character from within the brackets. | `[cp]at` | `cat`, `pat` | `hat`, `at`
|
||||
| `[a-z]` | Matches one character from the range within the brackets. | `Kappa[0-9]` | `Kappa1`, `Kappa2`, ... `Kappa0` | `Kappa`, `KappaHD`
|
||||
| `[!abc]` | Matches one character that is *not* within the brackets. | `[!cp]at` | `bat`, `rat`, `hat` | `at`, `cat`, `pat`
|
||||
| `[!a-z]` | Matches one character that is *not* within the range within the brackets. | `Kappa[!0-9]` | `Kappa?`, `KappaF` | `Kappa`, `Kappa0`, `Kappa4`
|
||||
| `'{'abc,d?f'}'` | Matches one of the possibilities from a comma-separated list. | `cir'{'no,Fairy'}'` | `cirno`, `cirFairy` | `cir`, `circle`
|
||||
|
||||
|
||||
#### 3. Regex
|
||||
|
||||
[Regular Expressions](https://en.wikipedia.org/wiki/Regular_expression) are complex
|
||||
pattern strings used in programming. They are meant for advanced users.
|
||||
|
||||
FrankerFaceZ uses your browser's built-in engine for handling regular expressions,
|
||||
with all the limitations that come with it. Regex terms are packaged into the
|
||||
generated regular expressions the same as both other modes. You should not and can
|
||||
not use capture groups.
|
||||
|
||||
FrankerFaceZ uses rudimentary logic to ensure your regular expression is not
|
||||
catastrophically slow, but you should still be careful to avoid slow expressions
|
||||
as they are run frequently.
|
||||
|
||||
|
||||
### Modes
|
||||
|
||||
#### Case Sensitive
|
||||
|
||||
This attempts to match your term in a case-sensitive manner. Effectively, this
|
||||
option disables the `/i` flag on the generated regular expression. Due to
|
||||
limitations in your browser's regex engine, case insensitivity may not work on
|
||||
some characters.
|
||||
|
||||
|
||||
#### Match Whole Word
|
||||
|
||||
This requires that your term is an entire word. For example, the term `test`
|
||||
without "Match Whole Word" could just as easily match `testing` or `tested`
|
||||
as it matches `test`. With "Match Whole Word" enabled, it will **only** match
|
||||
`test`.
|
||||
|
||||
This is done by wrapping the generated regular expression in extra pattern
|
||||
matchers for non-word characters, such as spaces and punctuation.
|
||||
|
||||
|
||||
#### Highlight Matches
|
||||
|
||||
When this is enabled, and the matching setting in [Chat > Filtering > General](~)
|
||||
is enabled, any matched terms will be highlighted in chat so you can see what
|
||||
exactly matched your term.
|
||||
|
||||
Any matches will not be displayed as emotes, links, etc.
|
||||
|
||||
This is a bit slower than not highlighting the match, so you may wish to only use
|
||||
this when testing and then disable it when you know your term works how you wish.
|
|
@ -71,7 +71,7 @@ export default class Metadata extends Module {
|
|||
});
|
||||
|
||||
this.settings.add('metadata.uptime', {
|
||||
default: 1,
|
||||
default: 2,
|
||||
|
||||
ui: {
|
||||
path: 'Channel > Metadata >> Player',
|
||||
|
|
|
@ -10,18 +10,20 @@ import {DEBUG} from 'utilities/constants';
|
|||
import {timeout} from 'utilities/object';
|
||||
|
||||
import SettingsManager from './settings/index';
|
||||
import AddonManager from './addons';
|
||||
import ExperimentManager from './experiments';
|
||||
import {TranslationManager} from './i18n';
|
||||
import Site from './sites/player';
|
||||
|
||||
class FFZPlayer extends Module {
|
||||
class FrankerFaceZ extends Module {
|
||||
constructor() {
|
||||
super();
|
||||
const start_time = performance.now(),
|
||||
VER = FFZPlayer.version_info;
|
||||
VER = FrankerFaceZ.version_info;
|
||||
|
||||
FFZPlayer.instance = this;
|
||||
FrankerFaceZ.instance = this;
|
||||
|
||||
this.flavor = 'player';
|
||||
this.name = 'ffz_player';
|
||||
this.__state = 0;
|
||||
this.__modules.core = this;
|
||||
|
@ -49,6 +51,7 @@ class FFZPlayer extends Module {
|
|||
this.inject('experiments', ExperimentManager);
|
||||
this.inject('i18n', TranslationManager);
|
||||
this.inject('site', Site);
|
||||
this.inject('addons', AddonManager);
|
||||
|
||||
// ========================================================================
|
||||
// Startup
|
||||
|
@ -65,7 +68,7 @@ class FFZPlayer extends Module {
|
|||
}
|
||||
|
||||
static get() {
|
||||
return FFZPlayer.instance;
|
||||
return FrankerFaceZ.instance;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
|
@ -111,9 +114,9 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`).join('\n\n'
|
|||
}
|
||||
|
||||
|
||||
FFZPlayer.Logger = Logger;
|
||||
FrankerFaceZ.Logger = Logger;
|
||||
|
||||
const VER = FFZPlayer.version_info = {
|
||||
const VER = FrankerFaceZ.version_info = {
|
||||
major: __version_major__,
|
||||
minor: __version_minor__,
|
||||
revision: __version_patch__,
|
||||
|
@ -124,15 +127,16 @@ const VER = FFZPlayer.version_info = {
|
|||
`${VER.major}.${VER.minor}.${VER.revision}${VER.extra || ''}${DEBUG ? '-dev' : ''}`
|
||||
}
|
||||
|
||||
// We don't support addons in the player right now, so
|
||||
/*FFZPlayer.utilities = {
|
||||
// We don't support addons in the player right now, so a few
|
||||
// of these are unavailable.
|
||||
FrankerFaceZ.utilities = {
|
||||
addon: require('utilities/addon'),
|
||||
color: require('utilities/color'),
|
||||
//color: require('utilities/color'),
|
||||
constants: require('utilities/constants'),
|
||||
dom: require('utilities/dom'),
|
||||
events: require('utilities/events'),
|
||||
fontAwesome: require('utilities/font-awesome'),
|
||||
graphql: require('utilities/graphql'),
|
||||
//fontAwesome: require('utilities/font-awesome'),
|
||||
//graphql: require('utilities/graphql'),
|
||||
logging: require('utilities/logging'),
|
||||
module: require('utilities/module'),
|
||||
object: require('utilities/object'),
|
||||
|
@ -141,8 +145,8 @@ const VER = FFZPlayer.version_info = {
|
|||
i18n: require('utilities/translation-core'),
|
||||
dayjs: require('dayjs'),
|
||||
popper: require('popper.js').default
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
window.FFZPlayer = FFZPlayer;
|
||||
window.ffz = new FFZPlayer();
|
||||
window.FrankerFaceZ = FrankerFaceZ;
|
||||
window.ffz = new FrankerFaceZ();
|
|
@ -50,7 +50,7 @@
|
|||
type="checkbox"
|
||||
class="ffz-min-width-unset ffz-checkbox__input"
|
||||
>
|
||||
<label :for="'sensitive$' + id" class="ffz-checkbox__label tw-relative tw-tooltip__container">
|
||||
<label :for="'sensitive$' + id" class="ffz-min-width-unset ffz-checkbox__label tw-relative tw-tooltip__container">
|
||||
<span class="tw-mg-l-05">
|
||||
Aa
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// CSS Tweaks for Twitch Twilight
|
||||
// ============================================================================
|
||||
|
||||
import Module from 'utilities/module';
|
||||
import {ManagedStyle} from 'utilities/dom';
|
||||
import {has} from 'utilities/object';
|
||||
|
||||
|
||||
const CLASSES = {
|
||||
'player-ext': '.video-player .extension-taskbar,.video-player .extension-container,.video-player .extensions-dock__layout,.video-player .extensions-notifications,.video-player .extensions-video-overlay-size-container,.video-player .extensions-dock__layout',
|
||||
'player-ext-hover': '.video-player__overlay[data-controls="false"] .extension-taskbar,.video-player__overlay[data-controls="false"] .extension-container,.video-player__overlay[data-controls="false"] .extensions-dock__layout,.video-player__overlay[data-controls="false"] .extensions-notifications,.video-player__overlay[data-controls="false"] .extensions-video-overlay-size-container',
|
||||
};
|
||||
|
||||
|
||||
export default class CSSTweaks extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.should_enable = true;
|
||||
|
||||
this.inject('settings');
|
||||
|
||||
this.style = new ManagedStyle;
|
||||
this.chunks = {};
|
||||
this.chunks_loaded = false;
|
||||
}
|
||||
|
||||
|
||||
toggleHide(key, val) {
|
||||
const k = `hide--${key}`;
|
||||
if ( ! val ) {
|
||||
this.style.delete(k);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! has(CLASSES, key) )
|
||||
throw new Error(`cannot find class for "${key}"`);
|
||||
|
||||
this.style.set(k, `${CLASSES[key]} { display: none !important }`);
|
||||
}
|
||||
|
||||
|
||||
async toggle(key, val) {
|
||||
if ( ! val ) {
|
||||
this.style.delete(key);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! this.chunks_loaded )
|
||||
await this.populate();
|
||||
|
||||
if ( ! has(this.chunks, key) )
|
||||
throw new Error(`cannot find chunk "${key}"`);
|
||||
|
||||
this.style.set(key, this.chunks[key]);
|
||||
}
|
||||
|
||||
|
||||
set(key, val) { return this.style.set(key, val) }
|
||||
delete(key) { return this.style.delete(key) }
|
||||
|
||||
setVariable(key, val, scope = 'body') {
|
||||
this.style.set(`var--${key}`, `${scope} { --ffz-${key}: ${val}; }`);
|
||||
}
|
||||
|
||||
deleteVariable(key) { this.style.delete(`var--${key}`) }
|
||||
|
||||
|
||||
populate() {
|
||||
if ( this.chunks_loaded )
|
||||
return;
|
||||
|
||||
return new Promise(async r => {
|
||||
const raw = (await import(/* webpackChunkName: "player-css-tweaks" */ './styles.js')).default;
|
||||
for(const key of raw.keys()) {
|
||||
const k = key.slice(2, key.length - (key.endsWith('.scss') ? 5 : 4));
|
||||
this.chunks[k] = raw(key).default;
|
||||
}
|
||||
|
||||
this.chunks_loaded = true;
|
||||
r();
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
export default require.context('!raw-loader!sass-loader!./styles', false, /\.s?css$/);
|
|
@ -10,7 +10,7 @@ import BaseSite from '../base';
|
|||
|
||||
import Fine from 'utilities/compat/fine';
|
||||
import Player from './player';
|
||||
import CSSTweaks from './css_tweaks';
|
||||
import CSSTweaks from 'utilities/css-tweaks';
|
||||
import Tooltips from 'src/modules/tooltips';
|
||||
|
||||
import MAIN_URL from './styles/player-main.scss';
|
||||
|
@ -29,6 +29,16 @@ export default class PlayerSite extends BaseSite {
|
|||
this.inject('tooltips', Tooltips);
|
||||
this.inject('css_tweaks', CSSTweaks);
|
||||
|
||||
this.css_tweaks.loader = require.context(
|
||||
'!raw-loader!sass-loader!./css_tweaks', false, /\.s?css$/, 'lazy-once'
|
||||
);
|
||||
|
||||
this.css_tweaks.rules = {
|
||||
'unfollow-button': '.follow-btn--following',
|
||||
'player-ext': '.video-player .extension-taskbar,.video-player .extension-container,.video-player .extensions-dock__layout,.video-player .extensions-notifications,.video-player .extensions-video-overlay-size-container,.video-player .extensions-dock__layout',
|
||||
'player-ext-hover': '.video-player__overlay[data-controls="false"] .extension-taskbar,.video-player__overlay[data-controls="false"] .extension-container,.video-player__overlay[data-controls="false"] .extensions-dock__layout,.video-player__overlay[data-controls="false"] .extensions-notifications,.video-player__overlay[data-controls="false"] .extensions-video-overlay-size-container'
|
||||
};
|
||||
|
||||
this.DataSource = this.fine.define(
|
||||
'data-source',
|
||||
n => n.consentMetadata && n.onPlaying && n.props && n.props.data
|
||||
|
@ -38,11 +48,21 @@ export default class PlayerSite extends BaseSite {
|
|||
'player-menu',
|
||||
n => n.closeSettingsMenu && n.state && n.state.activeMenu && n.getMaxMenuHeight
|
||||
);
|
||||
|
||||
document.head.appendChild(createElement('link', {
|
||||
href: MAIN_URL,
|
||||
rel: 'stylesheet',
|
||||
type: 'text/css',
|
||||
crossOrigin: 'anonymous'
|
||||
}));
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
this.settings = this.resolve('settings');
|
||||
|
||||
this.settings.getChanges('channel.hide-unfollow', val =>
|
||||
this.css_tweaks.toggleHide('unfollow-button', val));
|
||||
|
||||
this.DataSource.on('mount', this.updateData, this);
|
||||
this.DataSource.on('update', this.updateData, this);
|
||||
this.DataSource.ready((cls, instances) => {
|
||||
|
@ -81,13 +101,6 @@ export default class PlayerSite extends BaseSite {
|
|||
},
|
||||
route_data: ['/']
|
||||
});
|
||||
|
||||
document.head.appendChild(createElement('link', {
|
||||
href: MAIN_URL,
|
||||
rel: 'stylesheet',
|
||||
type: 'text/css',
|
||||
crossOrigin: 'anonymous'
|
||||
}));
|
||||
}
|
||||
|
||||
awaitTwitchData() {
|
||||
|
|
|
@ -32,7 +32,7 @@ export default class Metadata extends Module {
|
|||
});
|
||||
|
||||
this.settings.add('metadata.uptime', {
|
||||
default: 1,
|
||||
default: 2,
|
||||
changed: () => this.updateMetadata('uptime')
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,29 @@
|
|||
}
|
||||
}
|
||||
|
||||
div[data-a-target="player-overlay-video-stats"] {
|
||||
.tw-table-cell + .tw-table-cell {
|
||||
font-family: "Helvetica Neue",sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
&.tw-c-background-base {
|
||||
background-color: rgba(0,0,0,0.5) !important;
|
||||
}
|
||||
|
||||
.tw-table-heading {
|
||||
background-color: rgba(0,0,0,0.6) !important;
|
||||
}
|
||||
|
||||
tbody .tw-table-row:nth-child(2n+0) {
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.tw-root--theme-dark > .tw-absolute.scrollable-area {
|
||||
height: calc(100% - 5rem);
|
||||
}
|
||||
|
||||
.ffz--player-pip,
|
||||
.ffz--player-reset {
|
||||
&:before {
|
||||
|
|
|
@ -285,7 +285,7 @@ export default class ChatHook extends Module {
|
|||
},
|
||||
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Blocked Message Types @{"description":"This filter allows you to remove all messages of a certain type from Twitch chat. It can be used to filter system messages, such as Hosts or Raids. Some types, such as moderation actions, cannot be blocked to prevent chat functionality from breaking."}',
|
||||
path: 'Chat > Filtering > Block >> Message Types @{"description":"This filter allows you to remove all messages of a certain type from Twitch chat. It can be used to filter system messages, such as Hosts or Raids. Some types, such as moderation actions, cannot be blocked to prevent chat functionality from breaking."}',
|
||||
component: 'blocked-types',
|
||||
data: () => Object
|
||||
.keys(this.chat_types)
|
||||
|
@ -495,7 +495,7 @@ export default class ChatHook extends Module {
|
|||
this.settings.add('chat.rituals.show', {
|
||||
default: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Rituals',
|
||||
path: 'Chat > Filtering > General >> Rituals',
|
||||
title: 'Display ritual messages such as "User is new here! Say Hello!".',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// ============================================================================
|
||||
|
||||
import Module from 'utilities/module';
|
||||
import {has} from 'utilities/object';
|
||||
import {has, sleep} from 'utilities/object';
|
||||
import { DEBUG } from '../constants';
|
||||
|
||||
const NAMES = [
|
||||
|
@ -107,9 +107,12 @@ export default class WebMunch extends Module {
|
|||
this._original_loader = thing.push;
|
||||
|
||||
// Wrap all existing modules in case any of them haven't been required yet.
|
||||
for(const chunk of thing)
|
||||
if ( chunk && chunk[1] )
|
||||
this.processModulesV4(chunk[1]);
|
||||
// However, there's an issue with this causing loading issues on the
|
||||
// dashboard. Somehow. Not sure, so just don't do it on that page.
|
||||
if ( ! location.hostname.includes('dashboard') )
|
||||
for(const chunk of thing)
|
||||
if ( chunk && chunk[1] )
|
||||
this.processModulesV4(chunk[1]);
|
||||
|
||||
try {
|
||||
thing.push = this.webpackJsonpv4.bind(this);
|
||||
|
@ -146,29 +149,27 @@ export default class WebMunch extends Module {
|
|||
processModulesV4(modules) {
|
||||
const t = this;
|
||||
|
||||
for(const mod_id in modules)
|
||||
if ( has(modules, mod_id) ) {
|
||||
this._known_ids.add(mod_id);
|
||||
const original_module = modules[mod_id];
|
||||
modules[mod_id] = function(module, exports, require, ...args) {
|
||||
if ( ! t._require && typeof require === 'function' ) {
|
||||
t.log.info(`require() grabbed from invocation of module ${mod_id}`);
|
||||
t._require = require;
|
||||
if ( t._resolve_require ) {
|
||||
try {
|
||||
for(const fn of t._resolve_require)
|
||||
fn(require);
|
||||
} catch(err) {
|
||||
t.log.error('An error occurred running require callbacks.', err);
|
||||
}
|
||||
|
||||
t._resolve_require = null;
|
||||
for(const [mod_id, original_module] of Object.entries(modules)) {
|
||||
this._known_ids.add(mod_id);
|
||||
modules[mod_id] = function(module, exports, require, ...args) {
|
||||
if ( ! t._require && typeof require === 'function' ) {
|
||||
t.log.info(`require() grabbed from invocation of module ${mod_id}`);
|
||||
t._require = require;
|
||||
if ( t._resolve_require ) {
|
||||
try {
|
||||
for(const fn of t._resolve_require)
|
||||
fn(require);
|
||||
} catch(err) {
|
||||
t.log.error('An error occurred running require callbacks.', err);
|
||||
}
|
||||
}
|
||||
|
||||
return original_module.call(this, module, exports, require, ...args);
|
||||
t._resolve_require = null;
|
||||
}
|
||||
}
|
||||
|
||||
return original_module.call(this, module, exports, require, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -500,10 +500,28 @@ export function glob_to_regex(input) {
|
|||
if ( CONTROL_CHARS.includes(char) )
|
||||
output += `\\${char}`;
|
||||
|
||||
else if ( char === '?' )
|
||||
else if ( char === '\\' ) {
|
||||
i++;
|
||||
const next = input[i];
|
||||
if ( next ) {
|
||||
if ( CONTROL_CHARS.includes(next) )
|
||||
output += `\\${next}`;
|
||||
else
|
||||
output += next;
|
||||
}
|
||||
|
||||
} else if ( char === '?' )
|
||||
output += '.';
|
||||
|
||||
else if ( char === '[' || char === ']' )
|
||||
else if ( char === '[' ) {
|
||||
output += char;
|
||||
const next = input[i + 1];
|
||||
if ( next === '!' ) {
|
||||
i++;
|
||||
output += '^';
|
||||
}
|
||||
|
||||
} else if ( char === ']' )
|
||||
output += char;
|
||||
|
||||
else if ( char === '{' ) {
|
||||
|
@ -529,16 +547,16 @@ export function glob_to_regex(input) {
|
|||
if ( count > 1 )
|
||||
output += '.*?';
|
||||
else
|
||||
output += '[^ ]*?';
|
||||
output += '[^\\s]*?';
|
||||
|
||||
} else
|
||||
output += char;
|
||||
}
|
||||
|
||||
while(groups > 0) {
|
||||
/*while(groups > 0) {
|
||||
output += ')';
|
||||
groups--;
|
||||
}
|
||||
}*/
|
||||
|
||||
return output;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.ffz--whole-word {
|
||||
border: 1px dotted;
|
||||
}
|
||||
|
||||
.ffz-cheer-preview {
|
||||
height: 11.2rem;
|
||||
width: 11.2rem;
|
||||
|
|
|
@ -258,10 +258,41 @@ textarea.ffz-input {
|
|||
|
||||
|
||||
.ffz--home {
|
||||
h2, p {
|
||||
h2, p, table {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid var(--color-border-base);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border-base);
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 0.5rem;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-right: 1px solid var(--color-border-base);
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
background-color: var(--color-background-alt);
|
||||
}
|
||||
|
||||
th,
|
||||
tr:nth-child(2n + 0) td {
|
||||
background-color: var(--color-background-base);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
margin: 1em 0;
|
||||
|
@ -343,6 +374,7 @@ textarea.ffz-input {
|
|||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.ffz--home,
|
||||
.ffz--changelog,
|
||||
.ffz--widget {
|
||||
code {
|
||||
|
@ -352,7 +384,7 @@ textarea.ffz-input {
|
|||
background-color: rgba(0,0,0,0.1);
|
||||
|
||||
.tw-root--theme-dark & {
|
||||
background-color: rgba(255,255,255,0.05);
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@
|
|||
}
|
||||
|
||||
li li div { padding-left: 1rem }
|
||||
li li li div { padding-left: 1.5rem }
|
||||
li li li li div { padding-left: 2rem }
|
||||
li li li div { padding-left: 2rem }
|
||||
li li li li div { padding-left: 2.5rem }
|
||||
|
||||
.pill {
|
||||
font-size: 0.9rem;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue