mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-05 10:38:30 +00:00
4.0.0-rc17
* Added: Highlight messages based on usernames and badges. * Added: Block messages based on usernames and badges. * Fixed: Display the number of months a subscriber has subscribed in badge tool-tips. * Fixed: Rendering of chat messages sent from Twitch Extensions.
This commit is contained in:
parent
b9cca1053d
commit
1649294bde
10 changed files with 809 additions and 82 deletions
|
@ -149,7 +149,7 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`
|
|||
FrankerFaceZ.Logger = Logger;
|
||||
|
||||
const VER = FrankerFaceZ.version_info = {
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc16.3',
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc17',
|
||||
commit: __git_commit__,
|
||||
build: __webpack_hash__,
|
||||
toString: () =>
|
||||
|
|
|
@ -155,7 +155,39 @@ export default class Badges extends Module {
|
|||
path: 'Chat > Badges >> tabs ~> Visibility',
|
||||
title: 'Visibility',
|
||||
component: 'badge-visibility',
|
||||
data: () => {
|
||||
data: () => this.getSettingsBadges(true)
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.badges.custom-mod', {
|
||||
default: true,
|
||||
ui: {
|
||||
path: 'Chat > Badges >> tabs ~> Appearance',
|
||||
title: 'Use custom moderator badges where available.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
})
|
||||
|
||||
this.settings.add('chat.badges.style', {
|
||||
default: 0,
|
||||
ui: {
|
||||
path: 'Chat > Badges >> tabs ~> Appearance',
|
||||
title: 'Style',
|
||||
component: 'setting-select-box',
|
||||
data: [
|
||||
{value: 0, title: 'Default'},
|
||||
{value: 1, title: 'Rounded'},
|
||||
{value: 2, title: 'Circular'},
|
||||
{value: 3, title: 'Circular (Color Only)'},
|
||||
{value: 4, title: 'Circular (Color Only, Small)'},
|
||||
{value: 5, title: 'Transparent'},
|
||||
{value: 6, title: 'Transparent (Colored)'}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getSettingsBadges(include_addons) {
|
||||
const twitch = [],
|
||||
game = [],
|
||||
ffz = [],
|
||||
|
@ -194,6 +226,7 @@ export default class Badges extends Module {
|
|||
});
|
||||
}
|
||||
|
||||
if ( include_addons )
|
||||
for(const key in this.badges)
|
||||
if ( has(this.badges, key) ) {
|
||||
const badge = this.badges[key],
|
||||
|
@ -216,36 +249,6 @@ export default class Badges extends Module {
|
|||
{title: 'Add-on', badges: addon}
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.badges.custom-mod', {
|
||||
default: true,
|
||||
ui: {
|
||||
path: 'Chat > Badges >> tabs ~> Appearance',
|
||||
title: 'Use custom moderator badges where available.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
})
|
||||
|
||||
this.settings.add('chat.badges.style', {
|
||||
default: 0,
|
||||
ui: {
|
||||
path: 'Chat > Badges >> tabs ~> Appearance',
|
||||
title: 'Style',
|
||||
component: 'setting-select-box',
|
||||
data: [
|
||||
{value: 0, title: 'Default'},
|
||||
{value: 1, title: 'Rounded'},
|
||||
{value: 2, title: 'Circular'},
|
||||
{value: 3, title: 'Circular (Color Only)'},
|
||||
{value: 4, title: 'Circular (Color Only, Small)'},
|
||||
{value: 5, title: 'Transparent'},
|
||||
{value: 6, title: 'Transparent (Colored)'}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onEnable() {
|
||||
|
@ -278,9 +281,19 @@ export default class Badges extends Module {
|
|||
if ( ! bd )
|
||||
continue;
|
||||
|
||||
let title = bd.title || global_badge.title;
|
||||
if ( d.data ) {
|
||||
if ( d.badge === 'subscriber' ) {
|
||||
title = this.i18n.t('badges.subscriber.months', '%{title} (%{count} Month%{count|en_plural})', {
|
||||
title,
|
||||
count: d.data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
out.push(<div class="ffz-badge-tip">
|
||||
{show_previews && <img class="preview-image ffz-badge" src={bd.image4x} />}
|
||||
{bd.title || global_badge.title}
|
||||
{title}
|
||||
</div>);
|
||||
|
||||
/*out.push(e('div', {className: 'ffz-badge-tip'}, [
|
||||
|
@ -332,6 +345,7 @@ export default class Badges extends Module {
|
|||
out = [],
|
||||
slotted = {},
|
||||
twitch_badges = msg.badges || {},
|
||||
dynamic_data = msg.badgeDynamicData || {},
|
||||
|
||||
user = msg.user || {},
|
||||
user_id = user.id,
|
||||
|
@ -357,7 +371,8 @@ export default class Badges extends Module {
|
|||
else
|
||||
slot = last_slot++;
|
||||
|
||||
const urls = badge_id === 'moderator' && custom_mod && room && room.data && room.data.mod_urls,
|
||||
const data = dynamic_data[badge_id],
|
||||
urls = badge_id === 'moderator' && custom_mod && room && room.data && room.data.mod_urls,
|
||||
badges = [];
|
||||
|
||||
if ( urls ) {
|
||||
|
@ -366,14 +381,16 @@ export default class Badges extends Module {
|
|||
provider: 'ffz',
|
||||
image: urls[4] || urls[2] || urls[1],
|
||||
color: '#34ae0a',
|
||||
title: bd ? bd.title : 'Moderator'
|
||||
title: bd ? bd.title : 'Moderator',
|
||||
data
|
||||
});
|
||||
|
||||
} else
|
||||
badges.push({
|
||||
provider: 'twitch',
|
||||
badge: badge_id,
|
||||
version
|
||||
version,
|
||||
data
|
||||
});
|
||||
|
||||
slotted[slot] = {
|
||||
|
|
|
@ -186,6 +186,7 @@ export default class Chat extends Module {
|
|||
ui: {
|
||||
path: 'Chat > Behavior >> Deleted Messages',
|
||||
title: 'Deleted Message Style',
|
||||
description: 'This style will be applied to deleted messages showed in Detailed rendering mode to differentiate them from normal chat messages.',
|
||||
component: 'setting-select-box',
|
||||
data: [
|
||||
{value: 0, title: 'Faded'},
|
||||
|
@ -200,8 +201,8 @@ export default class Chat extends Module {
|
|||
default: false,
|
||||
ui: {
|
||||
path: 'Chat > Behavior >> Deleted Messages',
|
||||
title: 'Deleted Message Rendering',
|
||||
description: 'This, when set, overrides the mode selected in Twitch Chat settings. We do this to allow non-moderators access to the setting.',
|
||||
title: 'Rendering Mode',
|
||||
description: 'This, when set, overrides the mode selected in Twitch chat settings. We do this to allow non-moderators access to the setting.',
|
||||
component: 'setting-select-box',
|
||||
data: [
|
||||
{value: false, title: 'Do Not Override'},
|
||||
|
@ -216,7 +217,7 @@ export default class Chat extends Module {
|
|||
default: 1,
|
||||
ui: {
|
||||
path: 'Chat > Behavior >> Deleted Messages',
|
||||
title: 'Display Deletion Reason',
|
||||
title: 'Display Reason',
|
||||
component: 'setting-select-box',
|
||||
data: [
|
||||
{value: 0, title: 'Never'},
|
||||
|
@ -285,6 +286,177 @@ export default class Chat extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.filtering.highlight-basic-users', {
|
||||
default: [],
|
||||
type: 'array_merge',
|
||||
always_inherit: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Highlight Users',
|
||||
component: 'basic-terms',
|
||||
colored: true,
|
||||
words: false
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.filtering.highlight-basic-users--color-regex', {
|
||||
requires: ['chat.filtering.highlight-basic-users'],
|
||||
process(ctx) {
|
||||
const val = ctx.get('chat.filtering.highlight-basic-users');
|
||||
if ( ! val || ! val.length )
|
||||
return null;
|
||||
|
||||
const colors = new Map;
|
||||
|
||||
for(const item of val) {
|
||||
const c = item.c || null,
|
||||
t = item.t;
|
||||
|
||||
let v = item.v;
|
||||
|
||||
if ( t === 'glob' )
|
||||
v = glob_to_regex(v);
|
||||
|
||||
else if ( t !== 'raw' )
|
||||
v = escape_regex(v);
|
||||
|
||||
if ( ! v || ! v.length )
|
||||
continue;
|
||||
|
||||
try {
|
||||
new RegExp(v);
|
||||
} catch(err) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( colors.has(c) )
|
||||
colors.get(c).push(v);
|
||||
else {
|
||||
colors.set(c, [v]);
|
||||
}
|
||||
}
|
||||
|
||||
for(const [key, list] of colors) {
|
||||
colors.set(key, new RegExp(`^${list.join('|')}$`, 'gi'));
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.settings.add('chat.filtering.highlight-basic-users-blocked', {
|
||||
default: [],
|
||||
type: 'array_merge',
|
||||
always_inherit: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Blocked Users',
|
||||
component: 'basic-terms',
|
||||
removable: true,
|
||||
words: false
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.settings.add('chat.filtering.highlight-basic-users-blocked--regex', {
|
||||
requires: ['chat.filtering.highlight-basic-blocked'],
|
||||
process(ctx) {
|
||||
const val = ctx.get('chat.filtering.highlight-basic-users-blocked');
|
||||
if ( ! val || ! val.length )
|
||||
return null;
|
||||
|
||||
const out = [[], []];
|
||||
|
||||
for(const item of val) {
|
||||
const t = item.t;
|
||||
let v = item.v;
|
||||
|
||||
if ( t === 'glob' )
|
||||
v = glob_to_regex(v);
|
||||
|
||||
else if ( t !== 'raw' )
|
||||
v = escape_regex(v);
|
||||
|
||||
if ( ! v || ! v.length )
|
||||
continue;
|
||||
|
||||
out[item.remove ? 1 : 0].push(v);
|
||||
}
|
||||
|
||||
return out.map(data => {
|
||||
if ( ! data.length )
|
||||
return null;
|
||||
|
||||
return new RegExp(`^${data.join('|')}$`, 'gi');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.settings.add('chat.filtering.highlight-basic-badges', {
|
||||
default: [],
|
||||
type: 'array_merge',
|
||||
always_inherit: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Highlight Badges',
|
||||
component: 'badge-highlighting',
|
||||
colored: true,
|
||||
data: () => this.badges.getSettingsBadges()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.settings.add('chat.filtering.highlight-basic-badges--colors', {
|
||||
requires: ['chat.filtering.highlight-basic-badges'],
|
||||
process(ctx) {
|
||||
const val = ctx.get('chat.filtering.highlight-basic-badges');
|
||||
if ( ! val || ! val.length )
|
||||
return null;
|
||||
|
||||
const colors = new Map;
|
||||
|
||||
for(const item of val) {
|
||||
const c = item.c || null,
|
||||
v = item.v;
|
||||
|
||||
colors.set(v, c);
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.settings.add('chat.filtering.highlight-basic-badges-blocked', {
|
||||
default: [],
|
||||
type: 'array_merge',
|
||||
always_inherit: true,
|
||||
ui: {
|
||||
path: 'Chat > Filtering >> Blocked Badges',
|
||||
component: 'badge-highlighting',
|
||||
removable: true,
|
||||
data: () => this.badges.getSettingsBadges()
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.filtering.highlight-basic-badges-blocked--list', {
|
||||
requires: ['chat.filtering.highlight-basic-badges-blocked'],
|
||||
process(ctx) {
|
||||
const val = ctx.get('chat.filtering.highlight-basic-badges-blocked');
|
||||
if ( ! val || ! val.length )
|
||||
return null;
|
||||
|
||||
const out = [[], []];
|
||||
for(const item of val)
|
||||
if ( item.v )
|
||||
out[item.remove ? 1 : 0].push(item.v);
|
||||
|
||||
if ( ! out[0].length && ! out[1].length )
|
||||
return null;
|
||||
|
||||
return out;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.settings.add('chat.filtering.highlight-basic-terms', {
|
||||
default: [],
|
||||
|
@ -297,7 +469,6 @@ export default class Chat extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
this.settings.add('chat.filtering.highlight-basic-terms--color-regex', {
|
||||
requires: ['chat.filtering.highlight-basic-terms'],
|
||||
process(ctx) {
|
||||
|
@ -636,10 +807,10 @@ export default class Chat extends Module {
|
|||
if ( id && typeof id === 'number' )
|
||||
id = `${id}`;
|
||||
|
||||
if ( this.user_ids[id] )
|
||||
if ( id && this.user_ids[id] )
|
||||
user = this.user_ids[id];
|
||||
|
||||
else if ( this.users[login] && ! no_login )
|
||||
else if ( login && this.users[login] && ! no_login )
|
||||
user = this.users[login];
|
||||
|
||||
if ( user && user.destroyed )
|
||||
|
@ -696,10 +867,10 @@ export default class Chat extends Module {
|
|||
if ( id && typeof id === 'number' )
|
||||
id = `${id}`;
|
||||
|
||||
if ( this.room_ids[id] )
|
||||
if ( id && this.room_ids[id] )
|
||||
room = this.room_ids[id];
|
||||
|
||||
else if ( this.rooms[login] && ! no_login )
|
||||
else if ( login && this.rooms[login] && ! no_login )
|
||||
room = this.rooms[login];
|
||||
|
||||
if ( room && room.destroyed )
|
||||
|
@ -863,7 +1034,9 @@ export default class Chat extends Module {
|
|||
if ( ! user )
|
||||
user = msg.user = {};
|
||||
|
||||
user.color = user.color || user.chatColor || null;
|
||||
const ext = msg.extension || {};
|
||||
|
||||
user.color = user.color || user.chatColor || ext.chatColor || null;
|
||||
user.type = user.type || user.userType || null;
|
||||
user.id = user.id || user.userID || null;
|
||||
user.login = user.login || user.userLogin || null;
|
||||
|
@ -888,7 +1061,13 @@ export default class Chat extends Module {
|
|||
// Standardize Badges
|
||||
if ( ! msg.badges && user.displayBadges ) {
|
||||
const b = msg.badges = {};
|
||||
for(const item of msg.user.displayBadges)
|
||||
for(const item of user.displayBadges)
|
||||
b[item.setID] = item.version;
|
||||
}
|
||||
|
||||
if ( ! msg.badges && ext.displayBadges ) {
|
||||
const b = msg.badges = {};
|
||||
for(const item of ext.displayBadges)
|
||||
b[item.setID] = item.version;
|
||||
}
|
||||
|
||||
|
|
|
@ -185,10 +185,10 @@ export default class Room {
|
|||
if ( id && typeof id === 'number' )
|
||||
id = `${id}`;
|
||||
|
||||
if ( this.user_ids[id] )
|
||||
if ( id && this.user_ids[id] )
|
||||
user = this.user_ids[id];
|
||||
|
||||
else if ( this.users[login] && ! no_login )
|
||||
else if ( login && this.users[login] && ! no_login )
|
||||
user = this.users[login];
|
||||
|
||||
if ( user && user.destroyed )
|
||||
|
|
|
@ -268,6 +268,119 @@ export const Mentions = {
|
|||
// Custom Highlight Terms
|
||||
// ============================================================================
|
||||
|
||||
export const UserHighlights = {
|
||||
type: 'user_highlight',
|
||||
priority: 90,
|
||||
|
||||
process(tokens, msg, user) {
|
||||
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-users--color-regex');
|
||||
if ( ! colors || ! colors.size )
|
||||
return tokens;
|
||||
|
||||
const u = msg.user;
|
||||
for(const [color, regex] of colors) {
|
||||
if ( regex.test(u.login) || regex.test(u.displayName) ) {
|
||||
msg.mentioned = true;
|
||||
if ( color ) {
|
||||
msg.mention_color = color;
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
export const BlockedUsers = {
|
||||
type: 'user_block',
|
||||
priority: 100,
|
||||
|
||||
process(tokens, msg, user) {
|
||||
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
||||
return tokens;
|
||||
|
||||
const u = msg.user,
|
||||
regexes = this.context.get('chat.filtering.highlight-basic-users-blocked--regex');
|
||||
if ( ! regexes )
|
||||
return tokens;
|
||||
|
||||
if ( regexes[1] && (regexes[1].test(u.login) || regexes[1].test(u.displayName)) ) {
|
||||
msg.deleted = true;
|
||||
msg.ffz_removed = true;
|
||||
}
|
||||
|
||||
if ( ! msg.deleted && regexes[0] && (regexes[0].test(u.login) || regexes[0].test(u.displayName)) )
|
||||
msg.deleted = true;
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
export const BadgeHighlights = {
|
||||
type: 'badge_highlight',
|
||||
priority: 80,
|
||||
|
||||
process(tokens, msg, user) {
|
||||
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
||||
return tokens;
|
||||
|
||||
const badges = msg.badges;
|
||||
if ( ! badges )
|
||||
return tokens;
|
||||
|
||||
const colors = this.context.get('chat.filtering.highlight-basic-badges--colors');
|
||||
if ( ! colors || ! colors.size )
|
||||
return tokens;
|
||||
|
||||
for(const badge of Object.keys(badges)) {
|
||||
if ( colors.has(badge) ) {
|
||||
const color = colors.get(badge);
|
||||
msg.mentioned = true;
|
||||
if ( color ) {
|
||||
msg.mention_color = color;
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
export const BlockedBadges = {
|
||||
type: 'badge_block',
|
||||
priority: 100,
|
||||
process(tokens, msg, user) {
|
||||
if ( user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own') )
|
||||
return tokens;
|
||||
|
||||
const badges = msg.badges;
|
||||
if ( ! badges )
|
||||
return tokens;
|
||||
|
||||
const list = this.context.get('chat.filtering.highlight-basic-badges-blocked--list');
|
||||
if ( ! list || (! list[0].length && ! list[1].length) )
|
||||
return tokens;
|
||||
|
||||
for(const badge of Object.keys(badges)) {
|
||||
if ( list[1].includes(badge) ) {
|
||||
msg.deleted = true;
|
||||
msg.ffz_removed = true;
|
||||
return tokens;
|
||||
}
|
||||
|
||||
if ( ! msg.deleted && list[0].includes(badge) )
|
||||
msg.deleted = true;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
export const CustomHighlights = {
|
||||
type: 'highlight',
|
||||
priority: 100,
|
||||
|
@ -310,7 +423,7 @@ export const CustomHighlights = {
|
|||
out.push({type: 'text', text: text.slice(idx, nix)});
|
||||
|
||||
msg.mentioned = true;
|
||||
msg.mention_color = color;
|
||||
msg.mention_color = color || msg.mention_color;
|
||||
|
||||
out.push({
|
||||
type: 'highlight',
|
||||
|
|
99
src/modules/main_menu/components/badge-highlighting.vue
Normal file
99
src/modules/main_menu/components/badge-highlighting.vue
Normal file
|
@ -0,0 +1,99 @@
|
|||
<template lang="html">
|
||||
<section class="ffz--widget ffz--badge-highlighting">
|
||||
<badge-term-editor
|
||||
:term="default_term"
|
||||
:badges="data"
|
||||
:colored="item.colored"
|
||||
:removable="item.removable"
|
||||
:adding="true"
|
||||
@save="new_term"
|
||||
/>
|
||||
<div v-if="! val.length || val.length === 1 && hasInheritance" class="tw-mg-t-05 tw-c-text-alt-2 tw-font-size-4 tw-align-center tw-c-text-alt-2 tw-pd-05">
|
||||
{{ t('setting.terms.no-terms', 'no terms are defined in this profile') }}
|
||||
</div>
|
||||
<ul v-else class="ffz--term-list tw-mg-t-05">
|
||||
<badge-term-editor
|
||||
v-for="term in val"
|
||||
v-if="term.t !== 'inherit'"
|
||||
:key="term.id"
|
||||
:term="term.v"
|
||||
:badges="data"
|
||||
:colored="item.colored"
|
||||
:removable="item.removable"
|
||||
@remove="remove(term)"
|
||||
@save="save(term, $event)"
|
||||
/>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import SettingMixin from '../setting-mixin';
|
||||
import {deep_copy} from 'utilities/object';
|
||||
|
||||
let last_id = 0;
|
||||
|
||||
export default {
|
||||
mixins: [SettingMixin],
|
||||
props: ['item', 'context'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
default_term: {
|
||||
v: 'broadcaster',
|
||||
c: '',
|
||||
remove: false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasInheritance() {
|
||||
for(const val of this.val)
|
||||
if ( val.t === 'inherit' )
|
||||
return true;
|
||||
},
|
||||
|
||||
val() {
|
||||
if ( ! this.has_value )
|
||||
return [];
|
||||
|
||||
return this.value.map(x => {
|
||||
x.id = x.id || `${Date.now()}-${Math.random()}-${last_id++}`;
|
||||
return x;
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
new_term(term) {
|
||||
if ( ! term.v )
|
||||
return;
|
||||
|
||||
const vals = Array.from(this.val);
|
||||
vals.push({v: term});
|
||||
this.set(deep_copy(vals));
|
||||
},
|
||||
|
||||
remove(val) {
|
||||
const vals = Array.from(this.val),
|
||||
idx = vals.indexOf(val);
|
||||
|
||||
if ( idx !== -1 ) {
|
||||
vals.splice(idx, 1);
|
||||
if ( vals.length )
|
||||
this.set(deep_copy(vals));
|
||||
else
|
||||
this.clear();
|
||||
}
|
||||
},
|
||||
|
||||
save(val, new_val) {
|
||||
val.v = new_val;
|
||||
this.set(deep_copy(this.val));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
213
src/modules/main_menu/components/badge-term-editor.vue
Normal file
213
src/modules/main_menu/components/badge-term-editor.vue
Normal file
|
@ -0,0 +1,213 @@
|
|||
<template lang="html">
|
||||
<div class="ffz--term ffz--badge-term">
|
||||
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row tw-full-width">
|
||||
<div class="tw-mg-r-1">
|
||||
<img
|
||||
v-if="current"
|
||||
:src="current.image"
|
||||
class="ffz--badge-term-image"
|
||||
>
|
||||
</div>
|
||||
<div class="tw-flex-grow-1 tw-mg-r-05">
|
||||
<h4 v-if="! editing && ! current" class="ffz-monospace">
|
||||
<pre>{{ t('setting.terms.invalid-badge', 'unknown/unloaded badge') }}</pre>
|
||||
</h4>
|
||||
<h4 v-if="! editing && current">
|
||||
{{ current.name }}
|
||||
</h4>
|
||||
<select
|
||||
v-if="editing"
|
||||
v-model="edit_data.v"
|
||||
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-x-1 tw-pd-y-05 tw-mg-y-05">
|
||||
<optgroup
|
||||
v-for="section in badges"
|
||||
:key="section.title"
|
||||
:label="section.title"
|
||||
>
|
||||
<option
|
||||
v-for="badge in section.badges"
|
||||
:key="badge.id"
|
||||
:value="badge.id"
|
||||
>
|
||||
{{ badge.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="colored" class="tw-flex-shrink-0 tw-mg-r-05">
|
||||
<color-picker v-if="editing" v-model="edit_data.c" :nullable="true" :show-input="false" :open-up="true" />
|
||||
<div v-else-if="term.c" class="ffz-color-preview">
|
||||
<figure :style="`background-color: ${term.c}`">
|
||||
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="removable" class="tw-flex-shrink-0 tw-mg-r-05 tw-tooltip-wrapper">
|
||||
<button
|
||||
v-if="editing"
|
||||
:class="{active: edit_data.remove}"
|
||||
class="tw-button ffz-directory-toggle-block"
|
||||
@click="toggleRemove"
|
||||
>
|
||||
<span
|
||||
:class="edit_data.remove ? 'ffz-i-eye-off' : 'ffz-i-eye'"
|
||||
class="tw-button__text"
|
||||
/>
|
||||
</button>
|
||||
<span
|
||||
v-else-if="term.remove"
|
||||
class="ffz-i-eye-off 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>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="adding" class="tw-flex-shrink-0">
|
||||
<button class="tw-button" @click="save">
|
||||
<span class="tw-button__text">
|
||||
{{ t('setting.terms.add-term', 'Add') }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else-if="editing" class="tw-flex-shrink-0">
|
||||
<button class="tw-button tw-button--text tw-tooltip-wrapper" @click="save">
|
||||
<span class="tw-button__text ffz-i-floppy" />
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
{{ t('setting.save', 'Save') }}
|
||||
</div>
|
||||
</button>
|
||||
<button class="tw-button tw-button--text tw-tooltip-wrapper" @click="cancel">
|
||||
<span class="tw-button__text ffz-i-cancel" />
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
{{ t('setting.cancel', 'Cancel') }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else-if="deleting" class="tw-flex-shrink-0">
|
||||
<button class="tw-button tw-button--text tw-tooltip-wrapper" @click="$emit('remove', term)">
|
||||
<span class="tw-button__text ffz-i-trash" />
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
{{ t('setting.delete', 'Delete') }}
|
||||
</div>
|
||||
</button>
|
||||
<button class="tw-button tw-button--text tw-tooltip-wrapper" @click="deleting = false">
|
||||
<span class="tw-button__text ffz-i-cancel" />
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
{{ t('setting.cancel', 'Cancel') }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="tw-flex-shrink-0">
|
||||
<button class="tw-button tw-button--text tw-tooltip-wrapper" @click="edit">
|
||||
<span class="tw-button__text ffz-i-cog" />
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
{{ t('setting.edit', 'Edit') }}
|
||||
</div>
|
||||
</button>
|
||||
<button class="tw-button tw-button--text tw-tooltip-wrapper" @click="deleting = true">
|
||||
<span class="tw-button__text ffz-i-trash" />
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
{{ t('setting.delete', 'Delete') }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {deep_copy} from 'utilities/object';
|
||||
|
||||
let id = 0;
|
||||
|
||||
export default {
|
||||
props: {
|
||||
badges: Array,
|
||||
term: Object,
|
||||
colored: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
removable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
adding: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
if ( this.adding )
|
||||
return {
|
||||
editor_id: id++,
|
||||
deleting: false,
|
||||
editing: true,
|
||||
edit_data: deep_copy(this.term)
|
||||
};
|
||||
|
||||
return {
|
||||
editor_id: id++,
|
||||
deleting: false,
|
||||
editing: false,
|
||||
edit_data: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
display() {
|
||||
return this.editing ? this.edit_data : this.term;
|
||||
},
|
||||
|
||||
current() {
|
||||
if ( ! this.badges || ! this.display || ! this.display.v )
|
||||
return null;
|
||||
|
||||
const v = this.display.v;
|
||||
|
||||
for(const section of this.badges) {
|
||||
if ( ! section || ! section.badges )
|
||||
continue;
|
||||
|
||||
for(const badge of section.badges)
|
||||
if ( badge.id === v )
|
||||
return badge;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
edit() {
|
||||
this.editing = true;
|
||||
this.edit_data = Object.assign({remove: false}, deep_copy(this.term));
|
||||
},
|
||||
|
||||
toggleRemove() {
|
||||
if ( this.editing )
|
||||
this.edit_data.remove = ! this.edit_data.remove;
|
||||
},
|
||||
|
||||
cancel() {
|
||||
if ( this.adding )
|
||||
this.edit_data = deep_copy(this.term);
|
||||
else {
|
||||
this.editing = false;
|
||||
this.edit_data = null
|
||||
}
|
||||
},
|
||||
|
||||
save() {
|
||||
this.$emit('save', this.edit_data);
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -3,6 +3,7 @@
|
|||
<term-editor
|
||||
:term="default_term"
|
||||
:colored="item.colored"
|
||||
:words="item.words"
|
||||
:removable="item.removable"
|
||||
:adding="true"
|
||||
@save="new_term"
|
||||
|
@ -17,6 +18,7 @@
|
|||
:key="term.id"
|
||||
:term="term.v"
|
||||
:colored="item.colored"
|
||||
:words="item.words"
|
||||
:removable="item.removable"
|
||||
@remove="remove(term)"
|
||||
@save="save(term, $event)"
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
>
|
||||
<option value="text">{{ t('setting.terms.type.text', 'Text') }}</option>
|
||||
<option value="glob">{{ t('setting.terms.type.glob', 'Glob') }}</option>
|
||||
<option value="regex">{{ t('setting.terms.type.regex-word', 'Regex (Word)') }}</option>
|
||||
<option v-if="words" value="regex">{{ t('setting.terms.type.regex-word', 'Regex (Word)') }}</option>
|
||||
<option value="raw">{{ t('setting.terms.type.regex', 'Regex') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -137,6 +137,10 @@ let id = 0;
|
|||
export default {
|
||||
props: {
|
||||
term: Object,
|
||||
words: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
colored: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
|
|
@ -36,13 +36,19 @@ export default class ChatLine extends Module {
|
|||
|
||||
this.ChatLine = this.fine.define(
|
||||
'chat-line',
|
||||
n => n.renderMessageBody && n.props && !has(n.props, 'hasModPermissions'),
|
||||
n => n.renderMessageBody && n.props && ! n.onExtensionNameClick && !has(n.props, 'hasModPermissions'),
|
||||
Twilight.CHAT_ROUTES
|
||||
);
|
||||
|
||||
this.ExtensionLine = this.fine.define(
|
||||
'extension-line',
|
||||
n => n.renderMessageBody && n.onExtensionNameClick,
|
||||
Twilight.CHAT_ROUTES
|
||||
);
|
||||
|
||||
this.ChatRoomLine = this.fine.define(
|
||||
'chat-room-line',
|
||||
n => n.renderMessageBody && n.props && has(n.props, 'hasModPermissions'),
|
||||
n => n.renderMessageBody && n.props && ! n.onExtensionNameClick && has(n.props, 'hasModPermissions'),
|
||||
Twilight.CHAT_ROUTES
|
||||
);
|
||||
|
||||
|
@ -78,7 +84,11 @@ export default class ChatLine extends Module {
|
|||
this.chat.context.on('changed:chat.filtering.process-own', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.timestamp-format', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.filtering.highlight-basic-terms--color-regex', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.filtering.highlight-basic-users--color-regex', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.filtering.highlight-basic-badges--colors', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.filtering.highlight-basic-blocked--regex', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.filtering.highlight-basic-users-blocked--regex', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.filtering.highlight-basic-badges-blocked--list', this.updateLines, this);
|
||||
|
||||
const t = this,
|
||||
React = await this.web_munch.findModule('react');
|
||||
|
@ -692,6 +702,8 @@ export default class ChatLine extends Module {
|
|||
}, out);
|
||||
|
||||
} catch(err) {
|
||||
t.log.info(err);
|
||||
|
||||
t.log.capture(err, {
|
||||
extra: {
|
||||
props: this.props
|
||||
|
@ -704,6 +716,85 @@ export default class ChatLine extends Module {
|
|||
// Do this after a short delay to hopefully reduce the chance of React
|
||||
// freaking out on us.
|
||||
setTimeout(() => this.ChatLine.forceUpdate());
|
||||
});
|
||||
|
||||
this.ExtensionLine.ready(cls => {
|
||||
const old_render = cls.prototype.render;
|
||||
|
||||
cls.prototype.render = function() { try {
|
||||
if ( ! this.props.installedExtensions )
|
||||
return null;
|
||||
|
||||
const msg = t.chat.standardizeMessage(this.props.message),
|
||||
ext = msg && msg.extension;
|
||||
if( ! ext )
|
||||
return null;
|
||||
|
||||
if ( ! this.props.installedExtensions.some(val => {
|
||||
const e = val.extension;
|
||||
return e && e.clientID === ext.clientID && e.version === ext.version;
|
||||
}) )
|
||||
return null;
|
||||
|
||||
const color = t.parent.colors.process(ext.chatColor);
|
||||
let room = msg.roomLogin ? msg.roomLogin : msg.channel ? msg.channel.slice(1) : undefined;
|
||||
if ( ! room && this.props.channelID ) {
|
||||
const r = t.chat.getRoom(this.props.channelID, null, true);
|
||||
if ( r && r.login )
|
||||
room = msg.roomLogin = r.login;
|
||||
}
|
||||
|
||||
const u = t.site.getUser(),
|
||||
r = {id: this.props.channelID, login: room},
|
||||
|
||||
tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u, r),
|
||||
rich_content = FFZRichContent && t.chat.pluckRichContent(tokens, msg),
|
||||
bg_css = msg.mentioned && msg.mention_color ? t.parent.inverse_colors.process(msg.mention_color) : null;
|
||||
|
||||
if ( ! tokens.length )
|
||||
return null;
|
||||
|
||||
return e('div', {
|
||||
className: `chat-line__message${msg.mentioned ? ' ffz-mentioned' : ''}${bg_css ? ' ffz-custom-color' : ''}`,
|
||||
style: {backgroundColor: bg_css},
|
||||
'data-room-id': r.id,
|
||||
'data-room': r.login,
|
||||
'data-extension': ext.clientID
|
||||
}, [
|
||||
this.props.showTimestamps && e('span', {
|
||||
className: 'chat-line__timestamp'
|
||||
}, t.chat.formatTime(msg.timestamp)),
|
||||
e('span', {
|
||||
className: 'chat-line__message--badges'
|
||||
}, t.chat.badges.render(msg, e)),
|
||||
e('button', {
|
||||
className: 'chat-line__username notranslate',
|
||||
style: { color },
|
||||
onClick: this.onExtensionNameClick
|
||||
}, e('span', {
|
||||
className: 'chat-author__display-name'
|
||||
}, ext.displayName)),
|
||||
e('span', null, ': '),
|
||||
e('span', {
|
||||
className: 'message'
|
||||
}, t.chat.renderTokens(tokens, e)),
|
||||
rich_content && e(FFZRichContent, rich_content)
|
||||
]);
|
||||
|
||||
} catch(err) {
|
||||
t.log.info(err);
|
||||
t.log.capture(err, {
|
||||
extra: {
|
||||
props: this.props
|
||||
}
|
||||
});
|
||||
|
||||
return old_render.call(this);
|
||||
} }
|
||||
|
||||
// Do this after a short delay to hopefully reduce the chance of React
|
||||
// freaking out on us.
|
||||
setTimeout(() => this.ExtensionLine.forceUpdate());
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -722,6 +813,14 @@ export default class ChatLine extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
for(const inst of this.ExtensionLine.instances) {
|
||||
const msg = inst.props.message;
|
||||
if ( msg ) {
|
||||
msg.ffz_tokens = null;
|
||||
msg.mentioned = msg.mention_color = null;
|
||||
}
|
||||
}
|
||||
|
||||
for(const inst of this.ChatRoomLine.instances) {
|
||||
const msg = inst.props.message;
|
||||
if ( msg ) {
|
||||
|
@ -737,6 +836,7 @@ export default class ChatLine extends Module {
|
|||
}
|
||||
|
||||
this.ChatLine.forceUpdate();
|
||||
this.ExtensionLine.forceUpdate();
|
||||
this.ChatRoomLine.forceUpdate();
|
||||
this.WhisperLine.forceUpdate();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue