diff --git a/fontello.config.json b/fontello.config.json index bc7f5056..4bfe01e2 100644 --- a/fontello.config.json +++ b/fontello.config.json @@ -773,6 +773,12 @@ "search": [ "threads" ] + }, + { + "uid": "399ef63b1e23ab1b761dfbb5591fa4da", + "css": "right-open", + "code": 59462, + "src": "fontawesome" } ] } \ No newline at end of file diff --git a/package.json b/package.json index 84dfa87e..d73e8aa8 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.20.90", + "version": "4.20.91", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", diff --git a/res/font/ffz-fontello.eot b/res/font/ffz-fontello.eot index 18c05f57..a3d19d54 100644 Binary files a/res/font/ffz-fontello.eot and b/res/font/ffz-fontello.eot differ diff --git a/res/font/ffz-fontello.svg b/res/font/ffz-fontello.svg index dbec2186..f7569219 100644 --- a/res/font/ffz-fontello.svg +++ b/res/font/ffz-fontello.svg @@ -1,7 +1,7 @@ -Copyright (C) 2020 by original authors @ fontello.com +Copyright (C) 2021 by original authors @ fontello.com @@ -146,6 +146,8 @@ + + diff --git a/res/font/ffz-fontello.ttf b/res/font/ffz-fontello.ttf index e704244e..c5dedefc 100644 Binary files a/res/font/ffz-fontello.ttf and b/res/font/ffz-fontello.ttf differ diff --git a/res/font/ffz-fontello.woff b/res/font/ffz-fontello.woff index 8ebdc8d2..7029d051 100644 Binary files a/res/font/ffz-fontello.woff and b/res/font/ffz-fontello.woff differ diff --git a/res/font/ffz-fontello.woff2 b/res/font/ffz-fontello.woff2 index e56d7e48..14edc0ea 100644 Binary files a/res/font/ffz-fontello.woff2 and b/res/font/ffz-fontello.woff2 differ diff --git a/src/modules/chat/badges.jsx b/src/modules/chat/badges.jsx index e11e93b4..fdd46326 100644 --- a/src/modules/chat/badges.jsx +++ b/src/modules/chat/badges.jsx @@ -558,7 +558,7 @@ export default class Badges extends Module { } - render(msg, createElement, skip_hide = false) { // eslint-disable-line class-methods-use-this + render(msg, createElement, skip_hide = false, skip_click = false) { // eslint-disable-line class-methods-use-this if ( ! msg.badges && ! msg.ffz_badges ) return null; @@ -769,7 +769,8 @@ export default class Badges extends Module { props['data-tooltip-type'] = 'badge'; props['data-badge-data'] = JSON.stringify(data.badges); - props.onClick = this.handleClick; + if ( ! skip_click ) + props.onClick = this.handleClick; if ( data.replaced ) props['data-replaced'] = data.replaced; diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index 6294cc95..70cd3e87 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -206,7 +206,7 @@ export default class Chat extends Module { }); this.settings.add('chat.rich.hide-tokens', { - default: true, + default: false, ui: { path: 'Chat > Appearance >> Rich Content', title: 'Hide matching links for rich content.', @@ -262,6 +262,13 @@ export default class Chat extends Module { } }); + this.settings.addUI('chat.filtering.pad-bottom', { + path: 'Chat > Filtering > Highlight', + sort: 1000, + component: 'setting-spacer', + top: '30rem' + }); + this.settings.add('chat.filtering.click-to-reveal', { default: false, ui: { @@ -1694,7 +1701,7 @@ export default class Chat extends Module { for(const token of tokens) { for(const provider of providers) if ( provider.test.call(this, token, msg) ) { - token.hidden = this.context.get('chat.rich.hide-tokens') && provider.hide_token; + token.hidden = provider.can_hide_token && (this.context.get('chat.rich.hide-tokens') || provider.hide_token); return provider.process.call(this, token); } } diff --git a/src/modules/chat/rich_providers.js b/src/modules/chat/rich_providers.js index 678bab96..4a5e5f9e 100644 --- a/src/modules/chat/rich_providers.js +++ b/src/modules/chat/rich_providers.js @@ -28,7 +28,7 @@ import {truncate} from 'utilities/object'; export const Links = { type: 'link', - hide_token: false, + can_hide_token: true, priority: -10, test(token) { @@ -77,7 +77,7 @@ export const Links = { export const Users = { type: 'user', - hide_token: false, + can_hide_token: true, test(token) { if ( token.type !== 'link' || (! this.context.get('chat.rich.all-links') && ! token.force_rich) ) @@ -190,7 +190,7 @@ export const Users = { export const Clips = { type: 'clip', - hide_token: false, + can_hide_token: true, test(token) { if ( token.type !== 'link' ) @@ -278,7 +278,7 @@ export const Clips = { export const Videos = { type: 'video', - hide_token: false, + can_hide_token: true, test(token) { return token.type === 'link' && VIDEO_URL.test(token.url) diff --git a/src/modules/main_menu/index.js b/src/modules/main_menu/index.js index 01d9d7c0..a3c3749b 100644 --- a/src/modules/main_menu/index.js +++ b/src/modules/main_menu/index.js @@ -298,6 +298,9 @@ export default class MainMenu extends Module { return; this.requestPage(path); + + if ( ! this.showing ) + this.emit('site.menu_button:clicked'); } diff --git a/src/settings/index.js b/src/settings/index.js index ba09a2c0..e0e8145c 100644 --- a/src/settings/index.js +++ b/src/settings/index.js @@ -99,6 +99,9 @@ export default class SettingsManager extends Module { this.provider = provider; this.log.info(`Using Provider: ${provider.constructor.name}`); provider.on('changed', this._onProviderChange, this); + provider.on('quota-exceeded', err => { + this.emit(':quota-exceeded', err); + }); provider.on('change-provider', () => { this.emit(':change-provider'); }); diff --git a/src/settings/providers.js b/src/settings/providers.js index b9f7ab4f..6f9dc206 100644 --- a/src/settings/providers.js +++ b/src/settings/providers.js @@ -262,7 +262,18 @@ export class LocalStorageProvider extends SettingsProvider { } this._cached.set(key, value); - localStorage.setItem(this.prefix + key, JSON.stringify(value)); + try { + localStorage.setItem(this.prefix + key, JSON.stringify(value)); + } catch(err) { + if ( this.manager ) + this.manager.log.error(`An error occurred while trying to save a value to localStorage for key "${this.prefix + key}"`); + + if ( /quota/i.test(err.toString()) ) + this.emit('quota-exceeded', err); + + throw err; + } + this.broadcast({type: 'set', key}); this.emit('set', key, value, false); } diff --git a/src/sites/twitch-twilight/modules/chat/settings_menu.jsx b/src/sites/twitch-twilight/modules/chat/settings_menu.jsx index 6bb97c5e..71d48f69 100644 --- a/src/sites/twitch-twilight/modules/chat/settings_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/settings_menu.jsx @@ -18,6 +18,16 @@ export default class SettingsMenu extends Module { this.inject('chat.badges'); this.inject('site.fine'); this.inject('site.web_munch'); + this.inject('site.css_tweaks'); + + this.settings.add('chat.input.hide-identity', { + default: false, + ui: { + path: 'Chat > Input >> Appearance', + title: 'Display "Chat Identity" in the chat settings menu rather than the input box.', + component: 'setting-check-box' + } + }); this.SettingsMenu = this.fine.define( 'chat-settings', @@ -35,6 +45,12 @@ export default class SettingsMenu extends Module { async onEnable() { this.on('i18n:update', () => this.SettingsMenu.forceUpdate()); this.chat.context.on('changed:chat.scroller.freeze', () => this.SettingsMenu.forceUpdate()); + this.chat.context.on('changed:chat.input.hide-identity', val => { + this.css_tweaks.toggle('hide-chat-identity', val); + this.SettingsMenu.forceUpdate(); + }); + + this.css_tweaks.toggle('hide-chat-identity', this.chat.context.get('chat.input.hide-identity')); const t = this, React = await this.web_munch.findModule('react'); @@ -101,71 +117,87 @@ export default class SettingsMenu extends Module { return val; } - cls.prototype.render = function() { - try { - if ( this.state.ffzPauseMenu ) { - if ( ! this.ffzSettingsClick ) - this.ffzSettingsClick = e => t.click(this, e); + cls.prototype.ffzRenderIdentity = function() { + if ( ! this.state || ! this.props || this.state.moderatorMode || this.state.chatAppearance || this.state.chatPause || this.state.followerMode || this.state.recentRaids || this.state.repliesAppearance || this.state.slowMode || this.props.isShowingChatFilterSettings || this.props.isShowingDeletedMessageDisplaySettings || ! this.props.isLoggedIn || ! this.props.onClickEditAppearance ) + return null; - if ( ! this.ffzPauseClick ) - this.ffzPauseClick = () => this.setState({ffzPauseMenu: ! this.state.ffzPauseMenu}); + if ( ! t.chat.context.get('chat.input.hide-identity') ) + return null; - return (
-
-
-
-
- -
-
-

- { t.i18n.t('chat.settings.pause', 'Pause Chat') } -

-
-
-
-
-
-
-

- { t.i18n.t('chat.settings.pause-explain', 'FrankerFaceZ overrides the behavior of Pause Chat entirely. Please use FFZ\'s Scrolling settings within the FFZ Control Center under Chat > Behavior.') } -

-
- -
-
-
+ const user = this.props.data?.currentUser, + raw_badges = this.props.data?.user?.self?.displayBadges; + + if ( ! user || ! user.login || ! Array.isArray(raw_badges) ) + return null; + + const is_intl = user.login && user.displayName && user.displayName.trim().toLowerCase() !== user.login, + color = t.parent.colors.process(user.chatColor), + badges = {}; + + for(const badge of raw_badges) { + if ( badge?.setID && badge.version ) + badges[badge.setID] = badge.version; + } + + return (
+
+

+ { t.i18n.t('chat.identity-menu', 'Chat Identity') } +

+
+
+
) + +
+
); + } + + cls.prototype.render = function() { + const out = old_render.call(this); + + try { + const children = out?.props?.children?.props?.children?.[1]?.props?.children?.props?.children; + if ( Array.isArray(children) ) { + const extra = this.ffzRenderIdentity(); + if ( extra ) + children.unshift(extra); } } catch(err) { t.log.error('Error rendering chat settings menu.', err); } - return old_render.call(this); + return out; } this.SettingsMenu.forceUpdate(); @@ -228,9 +260,10 @@ export default class SettingsMenu extends Module { }, this.badges.render({ user, badges, + ffz_badges: this.badges.getBadges(user.id, user.login, inst.props.channelID, inst.props.channelLogin), roomID: inst.props.channelID, roomLogin: inst.props.channelLogin - }, createElement, true)), + }, createElement, true, true)), {user.displayName || user.login} @@ -292,17 +325,25 @@ export default class SettingsMenu extends Module { } else if ( ! badge ) return; - cont.appendChild(
-

- {this.i18n.tList('chat.ffz-badge.about', '{title}: This badge appears globally for users with FrankerFaceZ.', { - title: {this.i18n.t('chat.ffz-badge.title', 'FrankerFaceZ Badge')} - })}{' '} - {this.i18n.tList('chat.ffz-badge.site', 'Please visit the {website} to change this badge.', { - website: ( - {this.i18n.t('chat.ffz-badge.site-link', 'FrankerFaceZ website')} - ) - })} -

+ const out = (
+
+

+ { this.i18n.t('chat.ffz-badge.title', 'FrankerFaceZ Badge') } +

+
+
+

+ { this.i18n.tList( + 'chat.ffz-badge.about', + 'This badge appears globally for users with FrankerFaceZ. Please visit the {website} to change this badge.', + { + website: ( + {this.i18n.t('chat.ffz-badge.site-link', 'FrankerFaceZ website')} + ) + } + ) } +

+
@@ -321,6 +362,12 @@ export default class SettingsMenu extends Module {
); + + const after = cont.querySelector('[data-test-selector="global-badges-test-selector"]')?.nextElementSibling; + if ( after ) + cont.insertBefore(out, after); + else + cont.appendChild(out); } diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/hide-chat-identity.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/hide-chat-identity.scss new file mode 100644 index 00000000..8d4c673c --- /dev/null +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/hide-chat-identity.scss @@ -0,0 +1,7 @@ +.chat-input__badge-carousel { + display: none !important; +} + +.chat-input__textarea .tw-textarea { + padding-left: 1rem !important; +} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/menu_button.jsx b/src/sites/twitch-twilight/modules/menu_button.jsx index 7aa22663..0d9fcf67 100644 --- a/src/sites/twitch-twilight/modules/menu_button.jsx +++ b/src/sites/twitch-twilight/modules/menu_button.jsx @@ -9,6 +9,7 @@ import {SiteModule} from 'utilities/module'; import {createElement, ClickOutside, setChildren} from 'utilities/dom'; import Twilight from 'site'; +import getMD from 'src/utilities/markdown'; export default class MenuButton extends SiteModule { @@ -257,6 +258,17 @@ export default class MenuButton extends SiteModule { this.on('i18n:changed-strings', this.update); this.on('i18n:update', this.update); this.on('addons:data-loaded', this.update); + + this.once('settings:quota-exceeded', () => { + this.addError( + 'site.menu_button.quota-exceeded', + 'Your local storage space for this website is full, and settings cannot be saved. Please backup your settings and switch to a higher capacity provider in [Data Management > Storage >> Provider](~data_management.storage.tabs.provider).', + 'ffz-i-attention', + true + ); + this.update(); + }); + this.on('settings:change-provider', () => { this.addError('site.menu_button.changed', 'The FrankerFaceZ settings provider has changed. Please refresh this tab to avoid strange behavior.' @@ -266,11 +278,12 @@ export default class MenuButton extends SiteModule { } - addError(i18n, text, icon = 'ffz-i-attention') { + addError(i18n, text, icon = 'ffz-i-attention', use_markdown = false) { this.addToast({ icon, text_i18n: i18n, - text + text, + markdown: use_markdown }); } @@ -425,7 +438,8 @@ export default class MenuButton extends SiteModule { { data.title_i18n ? this.i18n.tList(data.title_i18n, data.title, data) : data.title} ) : null } { data.text ? ( - { data.text_i18n ? this.i18n.tList(data.text_i18n, data.text, data) : data.text} + { data.markdown ? : null} + { data.markdown ? null : (data.text_i18n ? this.i18n.tList(data.text_i18n, data.text, data) : data.text)} ) : null }
) : null} { ! data.unclosable && (