diff --git a/res/font/ffz-fontello.eot b/res/font/ffz-fontello.eot index d7f8d3a1..00c0f8ae 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 61b5077b..bc27f344 100644 --- a/res/font/ffz-fontello.svg +++ b/res/font/ffz-fontello.svg @@ -108,6 +108,10 @@ + + + + diff --git a/res/font/ffz-fontello.ttf b/res/font/ffz-fontello.ttf index 41678442..7f775d92 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 c2849308..b331d14f 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 8481f2dd..bc9adb42 100644 Binary files a/res/font/ffz-fontello.woff2 and b/res/font/ffz-fontello.woff2 differ diff --git a/src/main.js b/src/main.js index 3114d80c..71d92b2e 100644 --- a/src/main.js +++ b/src/main.js @@ -151,7 +151,7 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}` FrankerFaceZ.Logger = Logger; const VER = FrankerFaceZ.version_info = { - major: 4, minor: 4, revision: 2, + major: 4, minor: 5, revision: 0, commit: __git_commit__, build: __webpack_hash__, toString: () => diff --git a/src/modules/tooltips.js b/src/modules/tooltips.js index 9cd32b20..b2aba3fd 100644 --- a/src/modules/tooltips.js +++ b/src/modules/tooltips.js @@ -31,6 +31,30 @@ export default class TooltipProvider extends Module { ] } + this.types.child = target => { + const child = target.querySelector(':scope > .ffz-tooltip-child'); + if ( ! child ) + return null; + + target._ffz_child = child; + child.remove(); + child.classList.remove('ffz-tooltip-child'); + return child; + }; + + this.types.child.onHide = target => { + const child = target._ffz_child; + if ( child ) { + target._ffz_child = null; + child.remove(); + + if ( ! target.querySelector(':scope > .ffz-tooltip-child') ) { + child.classList.add('ffz-tooltip-child'); + target.appendChild(child); + } + } + } + this.types.text = target => sanitize(target.dataset.title); this.types.html = target => target.dataset.title; } @@ -46,6 +70,10 @@ export default class TooltipProvider extends Module { content: this.process.bind(this), interactive: this.checkInteractive.bind(this), hover_events: this.checkHoverEvents.bind(this), + + onShow: this.delegateOnShow.bind(this), + onHide: this.delegateOnHide.bind(this), + popper: { placement: 'top', modifiers: { @@ -74,6 +102,22 @@ export default class TooltipProvider extends Module { this.tips.cleanup(); } + delegateOnShow(target, tip) { + const type = target.dataset.tooltipType, + handler = this.types[type]; + + if ( handler && handler.onShow ) + handler.onShow(target, tip); + } + + delegateOnHide(target, tip) { + const type = target.dataset.tooltipType, + handler = this.types[type]; + + if ( handler && handler.onHide ) + handler.onHide(target, tip); + } + checkDelayShow(target, tip) { const type = target.dataset.tooltipType, handler = this.types[type]; diff --git a/src/sites/twitch-twilight/modules/css_tweaks/index.js b/src/sites/twitch-twilight/modules/css_tweaks/index.js index 326be42e..ab68a870 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/index.js +++ b/src/sites/twitch-twilight/modules/css_tweaks/index.js @@ -32,6 +32,7 @@ const CLASSES = { 'dir-live-ind': '.live-channel-card:not([data-a-target*="host"]) .stream-type-indicator.stream-type-indicator--live,.stream-thumbnail__card .stream-type-indicator.stream-type-indicator--live,.preview-card .stream-type-indicator.stream-type-indicator--live,.preview-card .preview-card-stat.preview-card-stat--live', 'profile-hover': '.preview-card .tw-relative:hover .ffz-channel-avatar', 'not-live-bar': 'div[data-test-selector="non-live-video-banner-layout"]', + 'channel-live-ind': 'div[data-target="channel-header__live-indicator"]' }; @@ -220,6 +221,20 @@ export default class CSSTweaks extends Module { // Other? + this.settings.add('channel.hide-live-indicator', { + requires: ['context.route.name'], + process(ctx, val) { + return ctx.get('context.route.name') === 'user' ? val : false + }, + default: false, + ui: { + path: 'Channel > Appearance >> General', + title: 'Hide the "LIVE" indicator on live channel pages.', + component: 'setting-check-box' + }, + changed: val => this.toggleHide('channel-live-ind', val) + }); + this.settings.add('channel.round-avatars', { default: true, ui: { @@ -231,7 +246,7 @@ export default class CSSTweaks extends Module { }); this.settings.add('channel.hide-not-live-bar', { - default: true, + default: false, ui: { path: 'Channel > Appearance >> General', title: 'Hide the "Not Live" bar.', @@ -256,6 +271,7 @@ export default class CSSTweaks extends Module { this.toggle('square-avatars', ! this.settings.get('channel.round-avatars')); this.toggleHide('not-live-bar', this.settings.get('channel.hide-not-live-bar')); + this.toggleHide('channel-live-ind', this.settings.get('channel.hide-live-indicator')); const recs = this.settings.get('layout.side-nav.show-rec-channels'); this.toggleHide('side-rec-channels', recs === 0); diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait.scss index 10632bea..f593c119 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait.scss @@ -2,14 +2,22 @@ --ffz-player-width: calc(100vw - var(--ffz-portrait-extra-width)); --ffz-player-height: calc(calc(calc(var(--ffz-player-width) * 0.5625) + var(--ffz-portrait-extra-height))); --ffz-theater-height: calc(calc(100vw * 0.5625) + var(--ffz-portrait-extra-height)); + --ffz-chat-height: calc(100vh - var(--ffz-player-height)); + + & > .tw-flex-column { + .ffz--portrait-invert & { + top: var(--ffz-chat-height) !important; + } - & > .tw-full-height { height: var(--ffz-player-height) !important; } .persistent-player.persistent-player__border--mini { pointer-events: none; - bottom: calc(100vh - var(--ffz-player-height)) !important; + + body:not(.ffz--portrait-invert) & { + bottom: var(--ffz-chat-height) !important; + } > * { pointer-events: auto; @@ -17,10 +25,18 @@ } .persistent-player.persistent-player--theatre { - top: 0 !important; + .ffz--portrait-invert & { + top: unset !important; + bottom: 0 !important; + } + + body:not(.ffz--portrait-invert) & { + top: 0 !important; + bottom: unset !important; + } + left: 0 !important; right: 0 !important; - bottom: unset !important; height: var(--ffz-theater-height) !important; width: 100% !important; } @@ -37,9 +53,28 @@ bottom: 0 !important; left: 0 !important; right: 0 !important; - height: calc(100vh - var(--ffz-player-height)) !important; + height: var(--ffz-chat-height) !important; width: unset !important; - border-top: 1px solid #dad8de; + + body:not(.ffz--portrait-invert) & { + top: unset !important; + bottom: 0 !important; + border-top: 1px solid #dad8de; + + .tw-root--theme-dark & { + border-top-color: #2a2a2a; + } + } + + .ffz--portrait-invert & { + top: 0 !important; + bottom: unset !important; + border-bottom: 1px solid #dad8de; + + .tw-root--theme-dark & { + border-bottom-color: #2a2a2a; + } + } & > .tw-full-height { width: 100% !important; @@ -47,18 +82,33 @@ .right-column__toggle-visibility { position: fixed !important; - top: 6.5rem; + + body:not(.ffz--portrait-invert) & { + top: 6.5rem; + } + + .ffz--portrait-invert & { + top: calc(var(--ffz-chat-height) + 6.5rem); + } + right: .5rem; left: unset !important; transform: rotate(90deg); } .emote-picker__tab-content { - max-height: calc(calc(100vh - var(--ffz-player-height)) - 26rem); + max-height: calc(var(--ffz-chat-height) - 26rem); } &.right-column--theatre { - top: unset !important; + .ffz--portrait-invert & { + bottom: unset !important; + } + + body:not(.ffz--portrait-invert) & { + top: unset !important; + } + height: calc(100vh - var(--ffz-theater-height)) !important; .emote-picker__tab-content { @@ -66,10 +116,6 @@ } } - .tw-root--theme-dark & { - border-top-color: #2a2a2a - } - .video-chat { flex-basis: unset; } diff --git a/src/sites/twitch-twilight/modules/layout.js b/src/sites/twitch-twilight/modules/layout.js index 8eb0f1a7..250a8653 100644 --- a/src/sites/twitch-twilight/modules/layout.js +++ b/src/sites/twitch-twilight/modules/layout.js @@ -40,6 +40,16 @@ export default class Layout extends Module { } }); + this.settings.add('layout.portrait-invert', { + default: false, + ui: { + path: 'Appearance > Layout >> Channel', + title: 'When in portrait mode, place chat at the top.', + component: 'setting-check-box' + }, + changed: val => document.body.classList.toggle('ffz--portrait-invert', val) + }); + this.settings.add('layout.portrait-threshold', { default: 1.25, ui: { @@ -147,6 +157,8 @@ export default class Layout extends Module { } onEnable() { + document.body.classList.toggle('ffz--portrait-invert', this.settings.get('layout.portrait-invert')); + this.css_tweaks.toggle('portrait', this.settings.get('layout.inject-portrait')); this.css_tweaks.toggle('portrait-swapped', this.settings.get('layout.use-portrait-swapped')); this.css_tweaks.setVariable('portrait-extra-width', `${this.settings.get('layout.portrait-extra-width')}rem`); diff --git a/src/std-components/aspect.vue b/src/std-components/aspect.vue index ba1c61af..b8f46d99 100644 --- a/src/std-components/aspect.vue +++ b/src/std-components/aspect.vue @@ -4,7 +4,7 @@ class="tw-aspect" >
diff --git a/src/std-components/icon-picker.vue b/src/std-components/icon-picker.vue index 5290e632..c62e1d43 100644 --- a/src/std-components/icon-picker.vue +++ b/src/std-components/icon-picker.vue @@ -130,9 +130,13 @@ const FFZ_ICONS = [ 'up-dir', 'up-big', 'play', + 'user', + 'clip', 'link-ext', 'twitter', 'github', + 'sort-down', + 'sort-up', 'gauge', 'download-cloud', 'upload-cloud', @@ -140,6 +144,9 @@ const FFZ_ICONS = [ 'keyboard', 'calendar-empty', 'ellipsis-vert', + 'sort-alt-up', + 'sort-alt-down', + 'language', 'twitch', 'bell-off', 'trash', diff --git a/src/std-components/react-link.vue b/src/std-components/react-link.vue index d37b54c3..b83b67aa 100644 --- a/src/std-components/react-link.vue +++ b/src/std-components/react-link.vue @@ -13,7 +13,7 @@ export default { onClick(event) { this.$emit('click', event); - if ( ! event.defaultPrevented ) + if ( ! event.defaultPrevented && ! this.href.includes('//') ) this.reactNavigate(this.href, event); } } diff --git a/src/utilities/compat/fine-router.js b/src/utilities/compat/fine-router.js index 8e94ee43..8317be3d 100644 --- a/src/utilities/compat/fine-router.js +++ b/src/utilities/compat/fine-router.js @@ -73,11 +73,26 @@ export default class FineRouter extends Module { this.emit(':route', null, null); } - getURL(route, data, opts) { + getURL(route, data, opts, ...args) { const r = this.routes[route]; if ( ! r ) throw new Error(`unable to find route "${route}"`); + if ( typeof data !== 'object' ) { + const parts = [data, opts, ...args]; + data = {}; + + let i = 0; + for(const part of r.parts) { + if ( part && part.name ) { + data[part.name] = parts[i]; + i++; + if ( i >= parts.length ) + break; + } + } + } + return r.url(data, opts); } diff --git a/src/utilities/time.js b/src/utilities/time.js index cac7ca41..65df2367 100644 --- a/src/utilities/time.js +++ b/src/utilities/time.js @@ -22,9 +22,11 @@ export function duration_to_string(elapsed, separate_days, days_only, no_hours, days = days > 0 ? `${days} days, ` : ''; } + const show_hours = (!no_hours || days || hours); + return `${days}${ - (!no_hours || days || hours) ? `${days && hours < 10 ? '0' : ''}${hours}:` : '' - }${minutes < 10 ? '0' : ''}${minutes}${ + show_hours ? `${days && hours < 10 ? '0' : ''}${hours}:` : '' + }${show_hours && minutes < 10 ? '0' : ''}${minutes}${ no_seconds ? '' : `:${seconds < 10 ? '0' : ''}${seconds}`}`; } diff --git a/src/utilities/twitch-data.js b/src/utilities/twitch-data.js index 212f5976..1c82d8ae 100644 --- a/src/utilities/twitch-data.js +++ b/src/utilities/twitch-data.js @@ -378,8 +378,11 @@ export default class TwitchData extends Module { if ( this.tag_cache.has(id) ) out = this.tag_cache.get(id); - if ( ! out || (want_description && ! out.description) ) - this.getTag(id, want_description).then(tag => callback(id, tag)).catch(err => callback(id, null, err)); + if ( (want_description && (! out || ! out.description)) || (! out && callback) ) { + const promise = this.getTag(id, want_description); + if ( callback ) + promise.then(tag => callback(id, tag)).catch(err => callback(id, null, err)); + } return out; } diff --git a/src/utilities/vue.js b/src/utilities/vue.js index 44aa5bb3..897c67a1 100644 --- a/src/utilities/vue.js +++ b/src/utilities/vue.js @@ -170,6 +170,10 @@ export class Vue extends Module { router.history.push(url); } }, + getReactURL(route, data, opts, ...args) { + const router = t.resolve('site.router'); + return router.getURL(route, data, opts, ...args); + }, t(key, phrase, options) { return this.$i18n.t_(key, phrase, options); }, diff --git a/styles/icons.scss b/styles/icons.scss index cce8b72d..0b56e7bf 100644 --- a/styles/icons.scss +++ b/styles/icons.scss @@ -125,6 +125,8 @@ .ffz-i-up-dir:before { content: '\e830'; } /* '' */ .ffz-i-up-big:before { content: '\e831'; } /* '' */ .ffz-i-play:before { content: '\e832'; } /* '' */ +.ffz-i-user:before { content: '\e833'; } /* '' */ +.ffz-i-clip:before { content: '\e834'; } /* '' */ .ffz-i-link-ext:before { content: '\f08e'; } /* '' */ .ffz-i-twitter:before { content: '\f099'; } /* '' */ .ffz-i-github:before { content: '\f09b'; } /* '' */ diff --git a/styles/tooltips.scss b/styles/tooltips.scss index 9bc1b1c8..bdefdb50 100644 --- a/styles/tooltips.scss +++ b/styles/tooltips.scss @@ -8,6 +8,18 @@ body { } +.ffz-tooltip.ffz-tooltip--no-mouse { + > * { + pointer-events: none; + } +} + + +.ffz-tooltip-child { + display: none !important; +} + + .tw-balloon { &[x-placement^="bottom"] > .tw-balloon__tail { bottom: 100%;