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:
parent
1649294bde
commit
23816fafc9
16 changed files with 464 additions and 36 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: '-rc17',
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc18',
|
||||
commit: __git_commit__,
|
||||
build: __webpack_hash__,
|
||||
toString: () =>
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
|
||||
import Module from 'utilities/module';
|
||||
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 * as ACTIONS from './types';
|
||||
import * as RENDERERS from './renderers';
|
||||
import { transformPhrase } from 'src/i18n';
|
||||
|
||||
const VAR_REPLACE = /\{\{(.*?)(?:\|(.*?))?\}\}/g;
|
||||
|
||||
export default class Actions extends Module {
|
||||
constructor(...args) {
|
||||
|
@ -24,6 +26,25 @@ export default class Actions extends Module {
|
|||
this.actions = {};
|
||||
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', {
|
||||
// Filter out actions
|
||||
process: (ctx, val) =>
|
||||
|
@ -42,7 +63,7 @@ export default class Actions extends Module {
|
|||
|
||||
type: 'array_merge',
|
||||
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',
|
||||
context: ['user', 'room', 'message'],
|
||||
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) {
|
||||
if ( target._ffz_destroy )
|
||||
return target._ffz_destroy();
|
||||
|
@ -166,16 +260,29 @@ export default class Actions extends Module {
|
|||
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,
|
||||
tt = target._ffz_popup = new Tooltip(parent, target, {
|
||||
logger: this.log,
|
||||
manual: true,
|
||||
live: false,
|
||||
html: true,
|
||||
|
||||
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',
|
||||
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: {
|
||||
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) =>
|
||||
setTimeout(() => {
|
||||
target._ffz_outside = new ClickOutside(tip.outer, destroy)
|
||||
|
@ -343,12 +450,12 @@ export default class Actions extends Module {
|
|||
if ( ! data )
|
||||
return;
|
||||
|
||||
if ( ! data.definition.context )
|
||||
return;
|
||||
|
||||
if ( target._ffz_tooltip$0 )
|
||||
target._ffz_tooltip$0.hide();
|
||||
|
||||
if ( ! data.definition.context && ! data.definition.uses_reason )
|
||||
return;
|
||||
|
||||
this.renderInlineContext(event.target, data);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
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
|
||||
|
@ -30,7 +24,7 @@ export const open_url = {
|
|||
description: '%{options.url}',
|
||||
|
||||
tooltip(data) {
|
||||
const url = process(data.options.url, data, this.i18n.locale);
|
||||
const url = this.replaceVariables(data.options.url, data);
|
||||
|
||||
return [
|
||||
(<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) {
|
||||
const url = process(data.options.url, data, this.i18n.locale);
|
||||
const url = this.replaceVariables(data.options.url, data);
|
||||
|
||||
const win = window.open();
|
||||
if ( win ) {
|
||||
|
@ -77,18 +71,8 @@ export const chat = {
|
|||
|
||||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-chat.vue'),
|
||||
|
||||
process(data) {
|
||||
return transformPhrase(
|
||||
data.options.command,
|
||||
data,
|
||||
this.i18n.locale,
|
||||
VAR_REPLACE,
|
||||
{}
|
||||
)
|
||||
},
|
||||
|
||||
tooltip(data) {
|
||||
const msg = process(data.options.command, data, this.i18n.locale);
|
||||
const msg = this.replaceVariables(data.options.command, data);
|
||||
|
||||
return [
|
||||
(<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) {
|
||||
const msg = data.definition.process.call(this, data);
|
||||
const msg = this.replaceVariables(data.options.command, data);
|
||||
this.sendMessage(data.room.login, msg);
|
||||
}
|
||||
}
|
||||
|
@ -156,11 +140,16 @@ export const ban = {
|
|||
defaults: {},
|
||||
|
||||
required_context: ['room', 'user'],
|
||||
uses_reason: true,
|
||||
|
||||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-ban.vue'),
|
||||
|
||||
title: 'Ban User',
|
||||
|
||||
reason_text(data) {
|
||||
return this.i18n.t('chat.actions.ban-reason', 'Ban %{user.login} for:', {user: data.user});
|
||||
},
|
||||
|
||||
tooltip(data) {
|
||||
return this.i18n.t('chat.actions.ban', 'Ban %{user.login}', {user: data.user});
|
||||
},
|
||||
|
@ -189,12 +178,23 @@ export const timeout = {
|
|||
},
|
||||
|
||||
required_context: ['room', 'user'],
|
||||
uses_reason: true,
|
||||
|
||||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-timeout.vue'),
|
||||
|
||||
title: 'Timeout User',
|
||||
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) {
|
||||
return this.i18n.t(
|
||||
'chat.actions.timeout',
|
||||
|
|
|
@ -9,7 +9,7 @@ import User from './user';
|
|||
import {NEW_API, API_SERVER, WEBKIT_CSS as WEBKIT} from 'utilities/constants';
|
||||
|
||||
import {ManagedStyle} from 'utilities/dom';
|
||||
import {has, SourcedSet} from 'utilities/object';
|
||||
import {has, SourcedSet, set_equals} from 'utilities/object';
|
||||
|
||||
|
||||
export default class Room {
|
||||
|
@ -469,7 +469,15 @@ export default class Room {
|
|||
// 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.buildBitsCSS();
|
||||
}
|
||||
|
|
134
src/modules/main_menu/components/chat-reasons.vue
Normal file
134
src/modules/main_menu/components/chat-reasons.vue
Normal 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>
|
144
src/modules/main_menu/components/reason-editor.vue
Normal file
144
src/modules/main_menu/components/reason-editor.vue
Normal 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>
|
|
@ -433,6 +433,7 @@ export default class Metadata extends Module {
|
|||
logger: this.log,
|
||||
manual: 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',
|
||||
// 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, {
|
||||
logger: this.log,
|
||||
manual: true,
|
||||
live: false,
|
||||
html: true,
|
||||
|
||||
tooltipClass: 'ffz-metadata-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base',
|
||||
|
|
|
@ -47,6 +47,8 @@ export default class Line extends Module {
|
|||
|
||||
cls.prototype.render = function() {
|
||||
try {
|
||||
this._ffz_no_scan = true;
|
||||
|
||||
const msg = t.standardizeMessage(this.props.node, this.props.video),
|
||||
is_action = msg.is_action,
|
||||
user = msg.user,
|
||||
|
|
|
@ -274,8 +274,12 @@ export default class EmoteMenu extends Module {
|
|||
const old_render = cls.prototype.render;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
this._ffz_no_scan = true;
|
||||
|
||||
return (<t.MenuErrorWrapper visible={this.props.visible}>
|
||||
<t.MenuComponent
|
||||
|
|
|
@ -1504,6 +1504,7 @@ export default class ChatHook extends Module {
|
|||
if ( props.data ) {
|
||||
this.chat.badges.updateTwitchBadges(props.data.badges);
|
||||
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 )
|
||||
this.updateRoomBadges(cont, cs);
|
||||
|
||||
this.updateRoomRules(cont, props.chatRules);
|
||||
}
|
||||
|
||||
updateRoomBadges(cont, badges) { // eslint-disable-line class-methods-use-this
|
||||
|
@ -1543,6 +1546,14 @@ export default class ChatHook extends Module {
|
|||
room.updateBadges(badges);
|
||||
this.updateChatLines();
|
||||
}
|
||||
|
||||
updateRoomRules(cont, rules) { // eslint-disable-line class-methods-use-this
|
||||
const room = cont._ffz_room;
|
||||
if ( ! room )
|
||||
return;
|
||||
|
||||
room.rules = rules;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -103,6 +103,8 @@ export default class ChatLine extends Module {
|
|||
const old_render = cls.prototype.render;
|
||||
|
||||
cls.prototype.render = function() { try {
|
||||
this._ffz_no_scan = true;
|
||||
|
||||
const msg = t.chat.standardizeMessage(this.props.message),
|
||||
is_action = msg.is_action,
|
||||
|
||||
|
@ -215,6 +217,8 @@ export default class ChatLine extends Module {
|
|||
const old_render = cls.prototype.render;
|
||||
|
||||
cls.prototype.render = function() {
|
||||
this._ffz_no_scan = true;
|
||||
|
||||
if ( ! this.props.message || ! this.props.message.content )
|
||||
return old_render.call(this);
|
||||
|
||||
|
@ -276,6 +280,7 @@ export default class ChatLine extends Module {
|
|||
}
|
||||
|
||||
cls.prototype.render = function() { try {
|
||||
this._ffz_no_scan = true;
|
||||
|
||||
const types = t.parent.message_types || {},
|
||||
deleted_count = this.props.deletedCount,
|
||||
|
@ -722,6 +727,8 @@ export default class ChatLine extends Module {
|
|||
const old_render = cls.prototype.render;
|
||||
|
||||
cls.prototype.render = function() { try {
|
||||
this._ffz_no_scan = true;
|
||||
|
||||
if ( ! this.props.installedExtensions )
|
||||
return null;
|
||||
|
||||
|
|
|
@ -199,6 +199,8 @@ export default class VideoChatHook extends Module {
|
|||
|
||||
cls.prototype.render = function() {
|
||||
try {
|
||||
this._ffz_no_scan = true;
|
||||
|
||||
if ( this.state.showReplyForm || ! t.chat.context.get('chat.video-chat.enabled') )
|
||||
return old_render.call(this);
|
||||
|
||||
|
|
|
@ -397,7 +397,7 @@ export default class Fine extends Module {
|
|||
else if ( node instanceof Node )
|
||||
node = this.getReactInstance(node);
|
||||
|
||||
if ( ! node || ! this._live_waiting.length )
|
||||
if ( ! node || node._ffz_no_scan || ! this._live_waiting.length )
|
||||
continue;
|
||||
|
||||
const data = this.searchAll(node, this._waiting_crit, 1000);
|
||||
|
|
|
@ -189,11 +189,14 @@ export class ManagedStyle {
|
|||
this._style = null;
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
set(key, value, force) {
|
||||
const block = this._blocks[key];
|
||||
if ( block )
|
||||
if ( block ) {
|
||||
if ( ! force && block.textContent === value )
|
||||
return;
|
||||
|
||||
block.textContent = value;
|
||||
else
|
||||
} else
|
||||
this._style.appendChild(this._blocks[key] = document.createTextNode(value));
|
||||
}
|
||||
|
||||
|
|
|
@ -339,7 +339,7 @@ export class Tooltip {
|
|||
tip.outer = el._ffz_out_handler = el._ffz_over_handler = null;
|
||||
}
|
||||
|
||||
if ( this.live )
|
||||
if ( this.live && this.elements )
|
||||
this.elements.delete(tip.target);
|
||||
|
||||
tip._update = tip.rerender = tip.update = noop;
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
width: 11.2rem;
|
||||
}
|
||||
|
||||
.ffz--inline-reasons {
|
||||
max-width: 30rem;
|
||||
}
|
||||
|
||||
|
||||
.ffz-badge {
|
||||
display: inline-block;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue