From 23816fafc9be07ba144c389cc471afcdc0a76f64 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Mon, 29 Apr 2019 18:14:04 -0400 Subject: [PATCH] 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~~) --- src/main.js | 2 +- src/modules/chat/actions/index.jsx | 121 ++++++++++++++- src/modules/chat/actions/types.jsx | 40 ++--- src/modules/chat/room.js | 12 +- .../main_menu/components/chat-reasons.vue | 134 ++++++++++++++++ .../main_menu/components/reason-editor.vue | 144 ++++++++++++++++++ src/modules/metadata.jsx | 2 + src/sites/twitch-clips/modules/chat/line.jsx | 2 + .../modules/chat/emote_menu.jsx | 6 +- .../twitch-twilight/modules/chat/index.js | 11 ++ .../twitch-twilight/modules/chat/line.js | 7 + .../modules/video_chat/index.jsx | 2 + src/utilities/compat/fine.js | 2 +- src/utilities/dom.js | 9 +- src/utilities/tooltip.js | 2 +- styles/chat.scss | 4 + 16 files changed, 464 insertions(+), 36 deletions(-) create mode 100644 src/modules/main_menu/components/chat-reasons.vue create mode 100644 src/modules/main_menu/components/reason-editor.vue diff --git a/src/main.js b/src/main.js index 490d3538..1c2c2367 100644 --- a/src/main.js +++ b/src/main.js @@ -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: () => diff --git a/src/modules/chat/actions/index.jsx b/src/modules/chat/actions/index.jsx index c0fcd622..a7b7de2d 100644 --- a/src/modules/chat/actions/index.jsx +++ b/src/modules/chat/actions/index.jsx @@ -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(
  • + + {text} + +
  • ) + } + + if ( reasons && reasons.length && rules && rules.length ) + reason_elements.push(
    ); + + for(const rule of rules) { + reason_elements.push(
  • + + {rule} + +
  • ); + } + + 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 (
    + {reason_text ?
    + {reason_text} +
    : null} + +
    ); + } + + 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); } diff --git a/src/modules/chat/actions/types.jsx b/src/modules/chat/actions/types.jsx index e5db0ac2..b550fb3b 100644 --- a/src/modules/chat/actions/types.jsx +++ b/src/modules/chat/actions/types.jsx @@ -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 [ (
    { // 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 [ (
    { // 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', diff --git a/src/modules/chat/room.js b/src/modules/chat/room.js index 51f939cc..e0330ffa 100644 --- a/src/modules/chat/room.js +++ b/src/modules/chat/room.js @@ -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(); } diff --git a/src/modules/main_menu/components/chat-reasons.vue b/src/modules/main_menu/components/chat-reasons.vue new file mode 100644 index 00000000..fff1e787 --- /dev/null +++ b/src/modules/main_menu/components/chat-reasons.vue @@ -0,0 +1,134 @@ + + + \ No newline at end of file diff --git a/src/modules/main_menu/components/reason-editor.vue b/src/modules/main_menu/components/reason-editor.vue new file mode 100644 index 00000000..2a2a6fd6 --- /dev/null +++ b/src/modules/main_menu/components/reason-editor.vue @@ -0,0 +1,144 @@ + + + \ No newline at end of file diff --git a/src/modules/metadata.jsx b/src/modules/metadata.jsx index a9091402..e6917578 100644 --- a/src/modules/metadata.jsx +++ b/src/modules/metadata.jsx @@ -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', diff --git a/src/sites/twitch-clips/modules/chat/line.jsx b/src/sites/twitch-clips/modules/chat/line.jsx index a5898ed6..0407e656 100644 --- a/src/sites/twitch-clips/modules/chat/line.jsx +++ b/src/sites/twitch-clips/modules/chat/line.jsx @@ -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, diff --git a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx index d1f13e4a..b9b75416 100644 --- a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx @@ -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 (