1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-05 18:48:31 +00:00

4.0.0-rc18

* Added: Reason context menus for in-line timeout and ban actions.
* Fixed: Certain FFZ tool-tips using the wrong input handlers.
* Fixed: Do not update CSS whenever bits configuration changes, only when necessary. (Performance fix for the bleed purple campaign.)
* Changed: Mark certain page elements with a flag to avoid crawling them with MutationObservers. (More performance~~)
This commit is contained in:
SirStendec 2019-04-29 18:14:04 -04:00
parent 1649294bde
commit 23816fafc9
16 changed files with 464 additions and 36 deletions

View file

@ -149,7 +149,7 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`
FrankerFaceZ.Logger = Logger; FrankerFaceZ.Logger = Logger;
const VER = FrankerFaceZ.version_info = { const VER = FrankerFaceZ.version_info = {
major: 4, minor: 0, revision: 0, extra: '-rc17', major: 4, minor: 0, revision: 0, extra: '-rc18',
commit: __git_commit__, commit: __git_commit__,
build: __webpack_hash__, build: __webpack_hash__,
toString: () => toString: () =>

View file

@ -6,12 +6,14 @@
import Module from 'utilities/module'; import Module from 'utilities/module';
import {has, maybe_call, deep_copy} from 'utilities/object'; import {has, maybe_call, deep_copy} from 'utilities/object';
import {ClickOutside} from 'utilities/dom'; import {createElement, ClickOutside} from 'utilities/dom';
import Tooltip from 'utilities/tooltip'; import Tooltip from 'utilities/tooltip';
import * as ACTIONS from './types'; import * as ACTIONS from './types';
import * as RENDERERS from './renderers'; import * as RENDERERS from './renderers';
import { transformPhrase } from 'src/i18n';
const VAR_REPLACE = /\{\{(.*?)(?:\|(.*?))?\}\}/g;
export default class Actions extends Module { export default class Actions extends Module {
constructor(...args) { constructor(...args) {
@ -24,6 +26,25 @@ export default class Actions extends Module {
this.actions = {}; this.actions = {};
this.renderers = {}; this.renderers = {};
this.settings.add('chat.actions.reasons', {
default: [
{v: {text: 'One-Man Spam', i18n: 'chat.reasons.spam'}},
{v: {text: 'Posting Bad Links', i18n: 'chat.reasons.links'}},
{v: {text: 'Ban Evasion', i18n: 'chat.reasons.evasion'}},
{v: {text: 'Threats / Personal Info', i18n: 'chat.reasons.personal'}},
{v: {text: 'Hate / Harassment', i18n: 'chat.reasons.hate'}},
{v: {text: 'Ignoring Broadcaster / Moderators', i18n: 'chat.reason.ignore'}}
],
type: 'array_merge',
always_inherit: true,
ui: {
path: 'Chat > Actions > Reasons',
component: 'chat-reasons',
}
});
this.settings.add('chat.actions.inline', { this.settings.add('chat.actions.inline', {
// Filter out actions // Filter out actions
process: (ctx, val) => process: (ctx, val) =>
@ -42,7 +63,7 @@ export default class Actions extends Module {
type: 'array_merge', type: 'array_merge',
ui: { ui: {
path: 'Chat > In-Line Actions @{"description": "Here, you can define custom actions that will appear along messages in chat. If you aren\'t seeing an action you\'ve defined here in chat, please make sure that you have enabled Mod Icons in the chat settings menu."}', path: 'Chat > Actions > In-Line @{"description": "Here, you can define custom actions that will appear along messages in chat. If you aren\'t seeing an action you\'ve defined here in chat, please make sure that you have enabled Mod Icons in the chat settings menu."}',
component: 'chat-actions', component: 'chat-actions',
context: ['user', 'room', 'message'], context: ['user', 'room', 'message'],
inline: true, inline: true,
@ -149,6 +170,79 @@ export default class Actions extends Module {
} }
replaceVariables(text, data) {
return transformPhrase(
text,
data,
this.i18n.locale,
VAR_REPLACE,
{}
);
}
renderInlineReasons(data, t, tip) {
const reasons = this.parent.context.get('chat.actions.reasons'),
reason_elements = [],
room = this.parent.getRoom(data.room.id, data.room.login, true),
rules = room && room.rules;
if ( ! reasons && ! rules ) {
tip.hide();
return null;
}
const click_fn = reason => e => {
tip.hide();
data.definition.click.call(this, e, Object.assign({reason}, data));
e.preventDefault();
return false;
};
for(const reason of reasons) {
const text = this.replaceVariables(reason.i18n ? this.i18n.t(reason.i18n, reason.text) : reason.text, data);
reason_elements.push(<li class="tw-full-width tw-relative">
<a
href="#"
onClick={click_fn(text)}
class="tw-block tw-full-width tw-interactable tw-interactable--inverted tw-interactive tw-pd-05"
>
{text}
</a>
</li>)
}
if ( reasons && reasons.length && rules && rules.length )
reason_elements.push(<div class="tw-mg-y-05 tw-border-b"></div>);
for(const rule of rules) {
reason_elements.push(<li class="tw-full-width tw-relative">
<a
href="#"
onClick={click_fn(rule)}
class="tw-block tw-full-width tw-interactable tw-interactable--inverted tw-interactive tw-pd-05"
>
{rule}
</a>
</li>);
}
let reason_text;
if ( data.definition.reason_text )
reason_text = data.definition.reason_text.call(this, data, t, tip);
else
reason_text = this.i18n.t('chat.actions.select-reason', 'Please select a reason from the list below:');
return (<div class="ffz--inline-reasons">
{reason_text ? <div class="tw-pd-05 tw-border-b">
{reason_text}
</div> : null}
<ul>{reason_elements}</ul>
</div>);
}
renderInlineContext(target, data) { renderInlineContext(target, data) {
if ( target._ffz_destroy ) if ( target._ffz_destroy )
return target._ffz_destroy(); return target._ffz_destroy();
@ -166,16 +260,29 @@ export default class Actions extends Module {
target._ffz_destroy = target._ffz_outside = null; target._ffz_destroy = target._ffz_outside = null;
} }
const definition = data.definition;
let content;
if ( definition.context )
content = (t, tip) => definition.context.call(this, data, t, tip);
else if ( definition.uses_reason ) {
content = (t, tip) => this.renderInlineReasons(data, t, tip);
} else
return;
const parent = document.body.querySelector('#root>div') || document.body, const parent = document.body.querySelector('#root>div') || document.body,
tt = target._ffz_popup = new Tooltip(parent, target, { tt = target._ffz_popup = new Tooltip(parent, target, {
logger: this.log, logger: this.log,
manual: true, manual: true,
live: false,
html: true, html: true,
tooltipClass: 'ffz-action-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base', tooltipClass: 'ffz-action-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base',
arrowClass: 'tw-balloon__tail tw-overflow-hidden tw-absolute', arrowClass: 'tw-balloon__tail tw-overflow-hidden tw-absolute',
arrowInner: 'tw-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-c-background-base tw-absolute', arrowInner: 'tw-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-c-background-base tw-absolute',
innerClass: 'tw-pd-1', innerClass: '',
popper: { popper: {
placement: 'bottom', placement: 'bottom',
@ -189,7 +296,7 @@ export default class Actions extends Module {
} }
}, },
content: (t, tip) => data.definition.context.call(this, data, t, tip), content,
onShow: (t, tip) => onShow: (t, tip) =>
setTimeout(() => { setTimeout(() => {
target._ffz_outside = new ClickOutside(tip.outer, destroy) target._ffz_outside = new ClickOutside(tip.outer, destroy)
@ -343,12 +450,12 @@ export default class Actions extends Module {
if ( ! data ) if ( ! data )
return; return;
if ( ! data.definition.context )
return;
if ( target._ffz_tooltip$0 ) if ( target._ffz_tooltip$0 )
target._ffz_tooltip$0.hide(); target._ffz_tooltip$0.hide();
if ( ! data.definition.context && ! data.definition.uses_reason )
return;
this.renderInlineContext(event.target, data); this.renderInlineContext(event.target, data);
} }

View file

@ -1,12 +1,6 @@
'use strict'; 'use strict';
import {createElement} from 'utilities/dom'; import {createElement} from 'utilities/dom';
import {transformPhrase} from 'src/i18n';
const VAR_REPLACE = /\{\{(.*?)(?:\|(.*?))?\}\}/g;
const process = (input, data, locale = 'en') => transformPhrase(input, data, locale, VAR_REPLACE, {});
// ============================================================================ // ============================================================================
// Open URL // Open URL
@ -30,7 +24,7 @@ export const open_url = {
description: '%{options.url}', description: '%{options.url}',
tooltip(data) { tooltip(data) {
const url = process(data.options.url, data, this.i18n.locale); const url = this.replaceVariables(data.options.url, data);
return [ return [
(<div class="tw-border-b tw-mg-b-05">{ // eslint-disable-line react/jsx-key (<div class="tw-border-b tw-mg-b-05">{ // eslint-disable-line react/jsx-key
@ -43,7 +37,7 @@ export const open_url = {
}, },
click(event, data) { click(event, data) {
const url = process(data.options.url, data, this.i18n.locale); const url = this.replaceVariables(data.options.url, data);
const win = window.open(); const win = window.open();
if ( win ) { if ( win ) {
@ -77,18 +71,8 @@ export const chat = {
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-chat.vue'), editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-chat.vue'),
process(data) {
return transformPhrase(
data.options.command,
data,
this.i18n.locale,
VAR_REPLACE,
{}
)
},
tooltip(data) { tooltip(data) {
const msg = process(data.options.command, data, this.i18n.locale); const msg = this.replaceVariables(data.options.command, data);
return [ return [
(<div class="tw-border-b tw-mg-b-05">{ // eslint-disable-line react/jsx-key (<div class="tw-border-b tw-mg-b-05">{ // eslint-disable-line react/jsx-key
@ -101,7 +85,7 @@ export const chat = {
}, },
click(event, data) { click(event, data) {
const msg = data.definition.process.call(this, data); const msg = this.replaceVariables(data.options.command, data);
this.sendMessage(data.room.login, msg); this.sendMessage(data.room.login, msg);
} }
} }
@ -156,11 +140,16 @@ export const ban = {
defaults: {}, defaults: {},
required_context: ['room', 'user'], required_context: ['room', 'user'],
uses_reason: true,
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-ban.vue'), editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-ban.vue'),
title: 'Ban User', title: 'Ban User',
reason_text(data) {
return this.i18n.t('chat.actions.ban-reason', 'Ban %{user.login} for:', {user: data.user});
},
tooltip(data) { tooltip(data) {
return this.i18n.t('chat.actions.ban', 'Ban %{user.login}', {user: data.user}); return this.i18n.t('chat.actions.ban', 'Ban %{user.login}', {user: data.user});
}, },
@ -189,12 +178,23 @@ export const timeout = {
}, },
required_context: ['room', 'user'], required_context: ['room', 'user'],
uses_reason: true,
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-timeout.vue'), editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-timeout.vue'),
title: 'Timeout User', title: 'Timeout User',
description: '%{options.duration} second%{options.duration|en_plural}', description: '%{options.duration} second%{options.duration|en_plural}',
reason_text(data) {
return this.i18n.t('chat.actions.timeout-reason',
'Timeout %{user.login} for %{duration} second%{duration|en_plural} for:',
{
user: data.user,
duration: data.options.duration
}
);
},
tooltip(data) { tooltip(data) {
return this.i18n.t( return this.i18n.t(
'chat.actions.timeout', 'chat.actions.timeout',

View file

@ -9,7 +9,7 @@ import User from './user';
import {NEW_API, API_SERVER, WEBKIT_CSS as WEBKIT} from 'utilities/constants'; import {NEW_API, API_SERVER, WEBKIT_CSS as WEBKIT} from 'utilities/constants';
import {ManagedStyle} from 'utilities/dom'; import {ManagedStyle} from 'utilities/dom';
import {has, SourcedSet} from 'utilities/object'; import {has, SourcedSet, set_equals} from 'utilities/object';
export default class Room { export default class Room {
@ -469,7 +469,15 @@ export default class Room {
// Bits Data // Bits Data
// ======================================================================== // ========================================================================
updateBitsConfig(config) { updateBitsConfig(config, force) {
if ( ! force && this.bitsConfig && config ) {
const old_keys = new Set(Object.keys(this.bitsConfig)),
new_keys = new Set(Object.keys(config));
if ( set_equals(old_keys, new_keys) )
return;
}
this.bitsConfig = config; this.bitsConfig = config;
this.buildBitsCSS(); this.buildBitsCSS();
} }

View file

@ -0,0 +1,134 @@
<template lang="html">
<section class="ffz--widget ffz--chat-reasons">
<div class="tw-flex tw-align-items-center tw-pd-b-05">
<div class="tw-flex-grow-1">
{{ t('setting.reasons.info', 'Reasons can be selected using action context menus to add extra information to bans and timeouts.') }}
</div>
<button
v-if="! empty"
class="tw-mg-l-1 tw-button tw-button--text tw-tooltip-wrapper"
@click="clear"
>
<span class="tw-button__text ffz-i-trash">
{{ t('setting.delete-all', 'Delete All') }}
</span>
<span class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
{{ t('setting.reasons.delete-all', "Delete all of this profile's reasons.") }}
</span>
</button>
<button
v-if="empty"
class="tw-mg-l-1 tw-button tw-button--text tw-tooltip-wrapper"
@click="populate"
>
<span class="tw-button__text ffz-i-trash">
{{ t('setting.reasons.add-default', 'Add Defaults') }}
</span>
<span class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
{{ t('setting.reasons.add-default-tip', 'Add all of the default reasons to this profile.') }}
</span>
</button>
</div>
<reason-editor
:reason="default_reason"
:adding="true"
@save="new_reason"
/>
<div v-if="empty" 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.reasons.no-reasons', 'no reasons are defined in this profile') }}
</div>
<ul v-else class="ffz--term-list tw-mg-t-05">
<reason-editor
v-for="reason in val"
v-if="reason.t !== 'inherit'"
:key="reason.id"
:reason="reason.v"
@remove="remove(reason)"
@save="save(reason, $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_reason: {
text: '',
i18n: null
}
}
},
computed: {
empty() {
return ! this.val.length || this.val.length === 1 && this.hasInheritance;
},
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: {
populate() {
this.set(deep_copy(this.default_value));
},
new_reason(reason) {
if ( ! reason )
return;
const vals = Array.from(this.val);
vals.push({v: reason});
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) {
if ( val.v && new_val ) {
if ( new_val.i18n && new_val.text !== val.v.text )
new_val.i18n = null;
}
val.v = new_val;
this.set(deep_copy(this.val));
}
}
}
</script>

View file

@ -0,0 +1,144 @@
<template lang="html">
<div class="ffz--reason">
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row tw-full-width">
<div class="tw-flex-grow-1 tw-mg-r-05">
<h4 v-if="! editing" class="ffz-monospace">
{{ title }}
</h4>
<input
v-else
v-model="edit_data.text"
:placeholder="adding ? t('setting.reasons.add-placeholder', 'Add a new reason') : edit_data"
type="text"
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05"
autocapitalize="off"
autocorrect="off"
>
</div>
<div v-if="adding" class="tw-flex-shrink-0">
<button class="tw-button" @click="save">
<span class="tw-button__text">
{{ t('setting.add', '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', reason)">
<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: {
reason: Object,
adding: {
type: Boolean,
default: false
}
},
data() {
if ( this.adding )
return {
editor_id: id++,
deleting: false,
editing: true,
edit_data: deep_copy(this.reason)
}
return {
editor_id: id++,
deleting: false,
editing: false,
edit_data: null
}
},
computed: {
display() {
return this.editing ? this.edit_data : this.reason;
},
title() {
if ( this.display.i18n )
return this.t(this.display.i18n, this.display.text);
return this.display.text;
}
},
methods: {
edit() {
this.editing = true;
this.edit_data = deep_copy(this.reason);
},
toggleRemove() {
if ( this.editing )
this.edit_data.remove = ! this.edit_data.remove;
},
cancel() {
if ( this.adding ) {
this.edit_data = deep_copy(this.reason);
} else {
this.editing = false;
this.edit_data = null;
}
},
save() {
this.$emit('save', this.edit_data);
this.cancel();
}
}
}
</script>

View file

@ -433,6 +433,7 @@ export default class Metadata extends Module {
logger: this.log, logger: this.log,
manual: true, manual: true,
html: true, html: true,
live: false,
tooltipClass: 'ffz-metadata-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base tw-c-text-base', tooltipClass: 'ffz-metadata-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base tw-c-text-base',
// Hide the arrow for now, until we re-do our CSS to make it render correctly. // Hide the arrow for now, until we re-do our CSS to make it render correctly.
@ -742,6 +743,7 @@ export default class Metadata extends Module {
tt = el._ffz_popup = new Tooltip(parent, el, { tt = el._ffz_popup = new Tooltip(parent, el, {
logger: this.log, logger: this.log,
manual: true, manual: true,
live: false,
html: true, html: true,
tooltipClass: 'ffz-metadata-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base', tooltipClass: 'ffz-metadata-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base',

View file

@ -47,6 +47,8 @@ export default class Line extends Module {
cls.prototype.render = function() { cls.prototype.render = function() {
try { try {
this._ffz_no_scan = true;
const msg = t.standardizeMessage(this.props.node, this.props.video), const msg = t.standardizeMessage(this.props.node, this.props.video),
is_action = msg.is_action, is_action = msg.is_action,
user = msg.user, user = msg.user,

View file

@ -274,8 +274,12 @@ export default class EmoteMenu extends Module {
const old_render = cls.prototype.render; const old_render = cls.prototype.render;
cls.prototype.render = function() { cls.prototype.render = function() {
if ( ! this.props || ! has(this.props, 'channelOwnerID') || ! t.chat.context.get('chat.emote-menu.enabled') ) if ( ! this.props || ! has(this.props, 'channelOwnerID') || ! t.chat.context.get('chat.emote-menu.enabled') ) {
this._ffz_no_scan = false;
return old_render.call(this); return old_render.call(this);
}
this._ffz_no_scan = true;
return (<t.MenuErrorWrapper visible={this.props.visible}> return (<t.MenuErrorWrapper visible={this.props.visible}>
<t.MenuComponent <t.MenuComponent

View file

@ -1504,6 +1504,7 @@ export default class ChatHook extends Module {
if ( props.data ) { if ( props.data ) {
this.chat.badges.updateTwitchBadges(props.data.badges); this.chat.badges.updateTwitchBadges(props.data.badges);
this.updateRoomBadges(cont, props.data.user && props.data.user.broadcastBadges); this.updateRoomBadges(cont, props.data.user && props.data.user.broadcastBadges);
this.updateRoomRules(cont, props.chatRules);
} }
} }
@ -1533,6 +1534,8 @@ export default class ChatHook extends Module {
if ( cs.length !== ocs.length ) if ( cs.length !== ocs.length )
this.updateRoomBadges(cont, cs); this.updateRoomBadges(cont, cs);
this.updateRoomRules(cont, props.chatRules);
} }
updateRoomBadges(cont, badges) { // eslint-disable-line class-methods-use-this updateRoomBadges(cont, badges) { // eslint-disable-line class-methods-use-this
@ -1543,6 +1546,14 @@ export default class ChatHook extends Module {
room.updateBadges(badges); room.updateBadges(badges);
this.updateChatLines(); this.updateChatLines();
} }
updateRoomRules(cont, rules) { // eslint-disable-line class-methods-use-this
const room = cont._ffz_room;
if ( ! room )
return;
room.rules = rules;
}
} }

View file

@ -103,6 +103,8 @@ export default class ChatLine extends Module {
const old_render = cls.prototype.render; const old_render = cls.prototype.render;
cls.prototype.render = function() { try { cls.prototype.render = function() { try {
this._ffz_no_scan = true;
const msg = t.chat.standardizeMessage(this.props.message), const msg = t.chat.standardizeMessage(this.props.message),
is_action = msg.is_action, is_action = msg.is_action,
@ -215,6 +217,8 @@ export default class ChatLine extends Module {
const old_render = cls.prototype.render; const old_render = cls.prototype.render;
cls.prototype.render = function() { cls.prototype.render = function() {
this._ffz_no_scan = true;
if ( ! this.props.message || ! this.props.message.content ) if ( ! this.props.message || ! this.props.message.content )
return old_render.call(this); return old_render.call(this);
@ -276,6 +280,7 @@ export default class ChatLine extends Module {
} }
cls.prototype.render = function() { try { cls.prototype.render = function() { try {
this._ffz_no_scan = true;
const types = t.parent.message_types || {}, const types = t.parent.message_types || {},
deleted_count = this.props.deletedCount, deleted_count = this.props.deletedCount,
@ -722,6 +727,8 @@ export default class ChatLine extends Module {
const old_render = cls.prototype.render; const old_render = cls.prototype.render;
cls.prototype.render = function() { try { cls.prototype.render = function() { try {
this._ffz_no_scan = true;
if ( ! this.props.installedExtensions ) if ( ! this.props.installedExtensions )
return null; return null;

View file

@ -199,6 +199,8 @@ export default class VideoChatHook extends Module {
cls.prototype.render = function() { cls.prototype.render = function() {
try { try {
this._ffz_no_scan = true;
if ( this.state.showReplyForm || ! t.chat.context.get('chat.video-chat.enabled') ) if ( this.state.showReplyForm || ! t.chat.context.get('chat.video-chat.enabled') )
return old_render.call(this); return old_render.call(this);

View file

@ -397,7 +397,7 @@ export default class Fine extends Module {
else if ( node instanceof Node ) else if ( node instanceof Node )
node = this.getReactInstance(node); node = this.getReactInstance(node);
if ( ! node || ! this._live_waiting.length ) if ( ! node || node._ffz_no_scan || ! this._live_waiting.length )
continue; continue;
const data = this.searchAll(node, this._waiting_crit, 1000); const data = this.searchAll(node, this._waiting_crit, 1000);

View file

@ -189,11 +189,14 @@ export class ManagedStyle {
this._style = null; this._style = null;
} }
set(key, value) { set(key, value, force) {
const block = this._blocks[key]; const block = this._blocks[key];
if ( block ) if ( block ) {
if ( ! force && block.textContent === value )
return;
block.textContent = value; block.textContent = value;
else } else
this._style.appendChild(this._blocks[key] = document.createTextNode(value)); this._style.appendChild(this._blocks[key] = document.createTextNode(value));
} }

View file

@ -339,7 +339,7 @@ export class Tooltip {
tip.outer = el._ffz_out_handler = el._ffz_over_handler = null; tip.outer = el._ffz_out_handler = el._ffz_over_handler = null;
} }
if ( this.live ) if ( this.live && this.elements )
this.elements.delete(tip.target); this.elements.delete(tip.target);
tip._update = tip.rerender = tip.update = noop; tip._update = tip.rerender = tip.update = noop;

View file

@ -18,6 +18,10 @@
width: 11.2rem; width: 11.2rem;
} }
.ffz--inline-reasons {
max-width: 30rem;
}
.ffz-badge { .ffz-badge {
display: inline-block; display: inline-block;