diff --git a/package.json b/package.json index e5993f9c..38244bd1 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.20.2", + "version": "4.20.3", "description": "FrankerFaceZ is a Twitch enhancement suite.", "license": "Apache-2.0", "scripts": { diff --git a/src/modules/main_menu/index.js b/src/modules/main_menu/index.js index 9d20afe3..2e746edd 100644 --- a/src/modules/main_menu/index.js +++ b/src/modules/main_menu/index.js @@ -164,18 +164,27 @@ export default class MainMenu extends Module { this.on('i18n:update', this.scheduleUpdate, this); this.dialog.on('show', () => { + if ( this.dialog.maximized ) + this.runFix(1); + this.showing = true; this.opened = true; this.updateButtonUnseen(); this.emit('show') }); this.dialog.on('hide', () => { + if ( this.dialog.maximized ) + this.runFix(-1); + this.showing = false; this.emit('hide'); this.destroyDialog(); }); this.dialog.on('resize', () => { + if ( this.dialog.visible ) + this.runFix(this.dialog.maximized ? 1 : -1); + if ( this._vue ) this._vue.$children[0].maximized = this.dialog.maximized }); @@ -189,6 +198,12 @@ export default class MainMenu extends Module { this.off('site.menu_button:clicked', this.dialog.toggleVisible, this.dialog); } + runFix(amount) { + this.settings.updateContext({ + force_chat_fix: (this.settings.get('context.force_chat_fix') || 0) + amount + }); + } + requestPage(page) { const vue = get('_vue.$children.0', this); diff --git a/src/sites/twitch-twilight/modules/channel.js b/src/sites/twitch-twilight/modules/channel.js index cd00e39f..cd92cfd9 100644 --- a/src/sites/twitch-twilight/modules/channel.js +++ b/src/sites/twitch-twilight/modules/channel.js @@ -21,12 +21,22 @@ export default class Channel extends Module { this.inject('i18n'); this.inject('settings'); this.inject('site.css_tweaks'); - this.inject('site.fine'); this.inject('site.elemental'); + this.inject('site.fine'); + this.inject('site.router'); this.inject('site.twitch_data'); this.inject('metadata'); this.inject('socket'); + this.settings.add('channel.auto-click-chat', { + default: false, + ui: { + path: 'Channel > Behavior >> General', + title: 'Automatically open chat when opening an offline channel page.', + component: 'setting-check-box' + } + }); + /*this.SideNav = this.elemental.define( 'side-nav', '.side-bar-contents .side-nav-section:first-child', null, @@ -61,6 +71,20 @@ export default class Channel extends Module { this.InfoBar.on('mutate', this.updateBar, this); this.InfoBar.on('unmount', this.removeBar, this); this.InfoBar.each(el => this.updateBar(el)); + + this.router.on(':route', route => { + if ( route?.name === 'user' ) + setTimeout(this.maybeClickChat.bind(this), 1000); + }, this); + this.maybeClickChat(); + } + + maybeClickChat() { + if ( this.settings.get('channel.auto-click-chat') && this.router.current_name === 'user' ) { + const el = document.querySelector('a[data-a-target="channel-home-tab-Chat"]'); + if ( el ) + el.click(); + } } /*updateHidden(el) { // eslint-disable-line class-methods-use-this diff --git a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx index caba4cce..f9f4384f 100644 --- a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx @@ -5,7 +5,7 @@ // ============================================================================ import {has, get, once, maybe_call, set_equals} from 'utilities/object'; -import {TWITCH_GLOBAL_SETS, EmoteTypes, TWITCH_POINTS_SETS, TWITCH_PRIME_SETS, WEBKIT_CSS as WEBKIT, IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants'; +import {TWITCH_GLOBAL_SETS, EmoteTypes, TWITCH_POINTS_SETS, TWITCH_PRIME_SETS, WEBKIT_CSS as WEBKIT, IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS, KEYS} from 'utilities/constants'; import {ClickOutside} from 'utilities/dom'; import Twilight from 'site'; @@ -506,11 +506,14 @@ export default class EmoteMenu extends Module { hidden = storage.get('emote-menu.hidden-sets'); this.state = { + active: false, + activeEmote: -1, hidden: hidden && props.data && hidden.includes(props.data.hide_key || props.data.key), collapsed: collapsed && props.data && collapsed.includes(props.data.key), intersecting: window.IntersectionObserver ? false : true } + this.keyHeading = this.keyHeading.bind(this); this.clickHeading = this.clickHeading.bind(this); this.clickEmote = this.clickEmote.bind(this); @@ -521,15 +524,23 @@ export default class EmoteMenu extends Module { } componentDidMount() { + this.props.addSection(this); + if ( this.ref ) this.props.startObserving(this.ref, this); } componentWillUnmount() { + this.props.removeSection(this); + if ( this.ref ) this.props.stopObserving(this.ref); } + keyInteract(code) { + + } + clickEmote(event) { if ( this.props.visibility_control ) { const ds = event.currentTarget.dataset; @@ -560,6 +571,11 @@ export default class EmoteMenu extends Module { this.props.onClickToken(event.currentTarget.dataset.name) } + keyHeading(event) { + if ( event.keyCode === KEYS.Enter || event.keyCode === KEYS.Space ) + this.clickHeading(); + } + clickHeading() { if ( this.props.visibility_control ) { const hidden = storage.get('emote-menu.hidden-sets') || [], @@ -672,7 +688,7 @@ export default class EmoteMenu extends Module { source = 'FrankerFaceZ'; return (
- {show_heading ? ( + {show_heading ? ( {image}
{(data.i18n ? t.i18n.t(data.i18n, data.title) : data.title) || t.i18n.t('emote-menu.unknown', 'Unknown Source')} @@ -928,6 +944,9 @@ export default class EmoteMenu extends Module { this.createObserver(); } + this.sections = []; + this.activeSection = -1; + this.state = { tab: null, tone: t.settings.provider.get('emoji-tone', null) @@ -940,6 +959,22 @@ export default class EmoteMenu extends Module { this.observing = new Map; + this.addSection = inst => { + if ( ! this.sections.includes(inst) ) + this.sections.push(inst); + } + + this.removeSection = inst => { + const idx = this.sections.indexOf(inst); + if ( idx !== -1 ) { + this.sections.splice(idx); + if ( idx === this.activeSection ) + this.activeSection = -1; + else if ( idx < this.activeSection ) + this.activeSection--; + } + } + this.startObserving = this.startObserving.bind(this); this.stopObserving = this.stopObserving.bind(this); this.handleObserve = this.handleObserve.bind(this); @@ -1161,8 +1196,13 @@ export default class EmoteMenu extends Module { } handleKeyDown(event) { - if ( event.keyCode === 27 ) + const code = event.keyCode; + if ( code === KEYS.Escape ) this.props.toggleVisibility(); + else + return; + + event.preventDefault(); } loadData(force = false, props, state) { @@ -2042,6 +2082,8 @@ export default class EmoteMenu extends Module { filtered: this.state.filtered, visibility_control: visibility, onClickToken: this.props.onClickToken, + addSection: this.addSection, + removeSection: this.removeSection, startObserving: this.startObserving, stopObserving: this.stopObserving } diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index 8b8710ab..93f472ff 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -1818,6 +1818,7 @@ export default class ChatHook extends Module { login: e.recipientLogin, displayName: e.recipientName }; + out.sub_months = e.giftMonths; out.sub_plan = e.methods; out.sub_total = e.senderCount; @@ -1865,6 +1866,7 @@ export default class ChatHook extends Module { login: e.recipientLogin, displayName: e.recipientName }; + out.sub_months = e.giftMonths; out.sub_plan = e.methods; out.sub_total = e.senderCount; diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js index c11554b1..91b90b00 100644 --- a/src/sites/twitch-twilight/modules/chat/line.js +++ b/src/sites/twitch-twilight/modules/chat/line.js @@ -462,9 +462,13 @@ other {# messages were deleted by a moderator.} } else if ( msg.ffz_type === 'sub_gift' ) { const plan = msg.sub_plan || {}, + months = msg.sub_months || 1, tier = SUB_TIERS[plan.plan] || 1; - const sub_msg = t.i18n.tList('chat.sub.mystery', '{user} gifted a {plan} Sub to {recipient}! ', { + let sub_msg; + + const bits = { + months, user: (msg.sub_anon || user.username === 'ananonymousgifter') ? t.i18n.t('chat.sub.anonymous-gifter', 'An anonymous gifter') : e('span', { @@ -484,7 +488,13 @@ other {# messages were deleted by a moderator.} }, e('span', { className: 'tw-c-text-base tw-strong' }, msg.sub_recipient.displayName)) - }); + }; + + + if ( months <= 1 ) + sub_msg = t.i18n.tList('chat.sub.mystery', '{user} gifted a {plan} Sub to {recipient}! ', bits); + else + sub_msg = t.i18n.tList('chat.sub.gift-months', '{user} gifted {months,number} month{months,en_plural} of {plan} Sub to {recipient}!', bits); if ( msg.sub_total === 1 ) sub_msg.push(t.i18n.t('chat.sub.gift-first', "It's their first time gifting a Sub in the channel!")); diff --git a/src/sites/twitch-twilight/modules/css_tweaks/index.js b/src/sites/twitch-twilight/modules/css_tweaks/index.js index 352eac9a..f6a4300b 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/index.js +++ b/src/sites/twitch-twilight/modules/css_tweaks/index.js @@ -75,11 +75,20 @@ export default class CSSTweaks extends Module { }); this.settings.add('layout.use-chat-fix', { - requires: ['layout.swap-sidebars', 'layout.use-portrait', 'chat.use-width'], + requires: ['context.force_chat_fix', 'layout.swap-sidebars', 'layout.use-portrait', 'chat.use-width'], process(ctx) { - return ctx.get('layout.swap-sidebars') || ctx.get('layout.use-portrait') || ctx.get('chat.use-width') + return ctx.get('context.force_chat_fix') || ctx.get('layout.swap-sidebars') || ctx.get('layout.use-portrait') || ctx.get('chat.use-width') }, changed: val => { + if ( val ) + this.toggle('chat-no-animate', true); + else if ( ! val && ! this._no_anim_timer ) + this._no_anim_timer = requestAnimationFrame(() => { + this._no_anim_timer = null; + if ( ! this.settings.get('layout.use-chat-fix') ) + this.toggle('chat-no-animate', false); + }); + this.toggle('chat-fix', val); this.emit('site.player:fix-player'); } diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss index d572edea..78315190 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss @@ -1,20 +1,29 @@ -.channel-root__scroll-area--theatre-mode .channel-info-bar { - position: fixed; - bottom: 10rem; - left: 5rem; - right: calc(var(--ffz-chat-width) + 25rem); - z-index: 3500; - opacity: 0; - width: unset !important; +.channel-root__scroll-area--theatre-mode { + .channel-info-content > div:first-child, + .channel-info-bar { + position: fixed; + bottom: 10rem; + left: 5rem; + right: calc(var(--ffz-chat-width) + 40rem); + z-index: 3500; + opacity: 0; + width: unset !important; - pointer-events: none; + border: 1px solid var(--color-background-alt); + border-radius: 1rem; - .tw-tooltip-wrapper, .tw-stat, .ffz-stat, button, a { - pointer-events: all; + pointer-events: none; + + .tw-tooltip-wrapper, .tw-stat, .ffz-stat, button, a { + pointer-events: all; + } } -} -.channel-root__scroll-area--theatre-mode:hover .channel-info-bar { - background-color: var(--color-background-base); - opacity: 0.75; + &:hover { + .channel-info-content > div:first-child, + .channel-info-bar { + background-color: var(--color-background-base); + opacity: 0.75; + } + } } \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/player.jsx b/src/sites/twitch-twilight/modules/player.jsx index e81c7cd0..0f599f42 100644 --- a/src/sites/twitch-twilight/modules/player.jsx +++ b/src/sites/twitch-twilight/modules/player.jsx @@ -1495,11 +1495,19 @@ export default class Player extends Module { tryTheatreMode(inst) { - if ( ! this.settings.get('player.theatre.auto-enter') ) - return; + if ( ! inst._ffz_theater_timer ) + inst._ffz_theater_timer = setTimeout(() => { + inst._ffz_theater_timer = null; - if ( inst?.props?.onTheatreModeEnabled ) - inst.props.onTheatreModeEnabled(); + if ( ! this.settings.get('player.theatre.auto-enter') || ! inst._ffz_mounted ) + return; + + if ( inst.props.channelHomeLive || inst.props.channelHomeCarousel ) + return; + + if ( inst?.props?.onTheatreModeEnabled ) + inst.props.onTheatreModeEnabled(); + }, 250); } diff --git a/src/utilities/constants.js b/src/utilities/constants.js index 02c138a6..1da69c4f 100644 --- a/src/utilities/constants.js +++ b/src/utilities/constants.js @@ -19,9 +19,17 @@ export const LV_SOCKET_SERVER = 'wss://cbenni.com/socket.io/'; export const KEYS = { - Space: 32, Enter: 13, Escape: 27, + Space: 32, + PageUp: 33, + PageDown: 34, + End: 35, + Home: 36, + ArrowLeft: 37, + ArrowUp: 38, + ArrowRight: 39, + ArrowDown: 40 };