diff --git a/package.json b/package.json index d5b2b4e3..68ed34f6 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.30.1", + "version": "4.31.0", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", diff --git a/src/modules/chat/badges.jsx b/src/modules/chat/badges.jsx index 4d0c5dd2..1ca672b4 100644 --- a/src/modules/chat/badges.jsx +++ b/src/modules/chat/badges.jsx @@ -412,50 +412,17 @@ export default class Badges extends Module { tip.add_class = 'ffz__tooltip--badges'; const show_previews = this.parent.context.get('tooltip.badge-images'); - let container = target.parentElement.parentElement; - if ( ! container.dataset.roomId ) - container = target.closest('[data-room-id]'); + const ds = this.getBadgeData(target); - const room_id = container?.dataset?.roomId, - room_login = container?.dataset?.room, - out = []; + const out = []; - let data; - if ( target.dataset.badgeData ) - data = JSON.parse(target.dataset.badgeData); - else { - const badge_idx = target.dataset.badgeIdx; - let message; - - if ( container.message ) - message = container.message; - else { - const fine = this.resolve('site.fine'); - - if ( fine ) { - message = container[fine.accessor]?.return?.stateNode?.props?.message; - if ( ! message ) - message = fine.searchParent(container, n => n.props?.message)?.props?.message; - if ( ! message ) - message = fine.searchParent(container, n => n.props?.node)?.props?.node?._ffz_message; - if ( ! message ) - message = fine.searchParent(container, n => n.props?.messageContext)?.props?.messageContext?.comment?._ffz_message; - } - } - - if ( message?._ffz_message) - message = message._ffz_message; - if ( message ) - data = message.ffz_badge_cache?.[badge_idx]?.[1]?.badges; - } - - if ( data == null ) + if ( ds.data == null ) return out; - for(const d of data) { + for(const d of ds.data) { const p = d.provider; if ( p === 'twitch' ) { - const bd = this.getTwitchBadge(d.badge, d.version, room_id, room_login), + const bd = this.getTwitchBadge(d.badge, d.version, ds.room_id, ds.room_login), global_badge = this.getTwitchBadge(d.badge, d.version, null, null, true) || {}; if ( ! bd ) continue; @@ -489,14 +456,6 @@ export default class Badges extends Module { {title} ); - /*out.push(e('div', {className: 'ffz-badge-tip'}, [ - show_previews && e('img', { - className: 'preview-image ffz-badge', - src: bd.image4x - }), - bd.title - ]));*/ - } else if ( p === 'ffz' ) { out.push(
{show_previews && d.image &&
} {d.title}
); - - /*out.push(e('div', {className: 'ffz-badge-tip'}, [ - show_previews && e('div', { - className: 'preview-image ffz-badge', - style: { - backgroundColor: d.color, - backgroundImage: `url("${d.image}")` - } - }), - d.title - ]));*/ } } @@ -527,31 +475,74 @@ export default class Badges extends Module { } + getBadgeData(target) { + let container = target.parentElement?.parentElement; + if ( ! container?.dataset?.roomId ) + container = target.closest('[data-room-id]'); + + const room_id = container?.dataset?.roomId, + room_login = container?.dataset?.room, + + user_id = container?.dataset?.userId, + user_login = container?.dataset?.user; + + let data; + if (target.dataset.badgeData ) + data = JSON.parse(target.dataset.badgeData); + else { + const badge_idx = target.dataset.badgeIdx; + let message; + + if ( container.message ) + message = container.message; + else { + const fine = this.resolve('site.fine'); + + if ( fine ) { + message = container[fine.accessor]?.return?.stateNode?.props?.message; + if ( ! message ) + message = fine.searchParent(container, n => n.props?.message)?.props?.message; + if ( ! message ) + message = fine.searchParent(container, n => n.props?.node)?.props?.node?._ffz_message; + if ( ! message ) + message = fine.searchParent(container, n => n.props?.messageContext)?.props?.messageContext?.comment?._ffz_message; + if ( ! message ) + message = fine.searchParent(container, n => n._ffzIdentityMsg, 50)?._ffzIdentityMsg; + } + } + + if ( message?._ffz_message) + message = message._ffz_message; + if ( message ) + data = message.ffz_badge_cache?.[badge_idx]?.[1]?.badges; + } + + return { + room_id: room_id, + room_login: room_login, + user_id: user_id, + user_login: user_login, + data + }; + } + + handleClick(event) { if ( ! this.parent.context.get('chat.badges.clickable') ) return; const target = event.target; - let container = target.parentElement.parentElement; - if ( ! container.dataset.roomId ) - container = target.closest('[data-room-id]'); + const ds = this.getBadgeData(target); - const ds = container?.dataset, - room_id = ds?.roomId, - room_login = ds?.room, - user_id = ds?.userId, - user_login = ds?.user, - data = JSON.parse(target.dataset.badgeData); - - if ( data == null ) + if ( ds.data == null ) return; let url = null; - for(const d of data) { + for(const d of ds.data) { const p = d.provider; if ( p === 'twitch' ) { - const bd = this.getTwitchBadge(d.badge, d.version, room_id, room_login), + const bd = this.getTwitchBadge(d.badge, d.version, ds.room_id, ds.room_login), global_badge = this.getTwitchBadge(d.badge, d.version, null, null, true) || {}; if ( ! bd ) continue; @@ -560,8 +551,8 @@ export default class Badges extends Module { url = bd.click_url; else if ( global_badge.click_url ) url = global_badge.click_url; - else if ( (bd.click_action === 'sub' || global_badge.click_action === 'sub') && room_login ) - url = `https://www.twitch.tv/subs/${room_login}`; + else if ( (bd.click_action === 'sub' || global_badge.click_action === 'sub') && ds.room_login ) + url = `https://www.twitch.tv/subs/${ds.room_login}`; else continue; @@ -570,7 +561,7 @@ export default class Badges extends Module { } else if ( p === 'ffz' ) { const badge = this.badges[target.dataset.badge]; if ( badge?.click_handler ) { - url = badge.click_handler(user_id, user_login, room_id, room_login, data, event); + url = badge.click_handler(ds.user_id, ds.user_login, ds.room_id, ds.room_login, ds.data, event); break; } diff --git a/src/modules/chat/components/chat-rich.vue b/src/modules/chat/components/chat-rich.vue index bc38e7f2..48fc3e20 100644 --- a/src/modules/chat/components/chat-rich.vue +++ b/src/modules/chat/components/chat-rich.vue @@ -8,15 +8,18 @@ let tokenizer; export default { - props: ['data', 'url', 'events', 'forceFull', 'forceUnsafe', 'forceMedia'], + props: ['data', 'url', 'events', 'forceFull', 'forceUnsafe', 'forceMedia', 'forceMid'], data() { return { has_tokenizer: false, loaded: false, + version: null, + fragments: {}, error: null, accent: null, short: null, + mid: null, full: null, unsafe: false, urls: null, @@ -103,9 +106,12 @@ export default { this.loaded = false; this.error = null; + this.version = null; this.accent = null; this.short = null; + this.mid = null; this.full = null; + this.fragments = {}; this.unsafe = false; this.urls = null; this.allow_media = false; @@ -164,10 +170,13 @@ export default { } this.loaded = true; + this.version = data.v; this.error = data.error; this.accent = data.accent; this.short = data.short; + this.mid = data.mid; this.full = data.full; + this.fragments = data.fragments ?? {}; this.unsafe = data.unsafe; this.urls = data.urls; this.allow_media = data.allow_media; @@ -214,14 +223,22 @@ export default { }, renderBody(h) { - if ( this.has_tokenizer && this.loaded && (this.forceFull ? this.full : this.short) ) { + let body = this.forceFull ? this.full : + this.forceMid ? this.mid : this.short; + + if ( this.has_tokenizer && this.version && this.version > tokenizer.VERSION ) + body = null; + + if ( this.has_tokenizer && this.loaded && body ) { return h('div', { class: 'ffz--card-rich tw-full-width tw-overflow-hidden tw-flex tw-flex-column' - }, tokenizer.renderTokens(this.forceFull ? this.full : this.short, h, { + }, tokenizer.renderTokens(body, h, { vue: true, tList: (...args) => this.tList(...args), i18n: this.getI18n(), + fragments: this.fragments, + allow_media: this.forceMedia ?? this.allow_media, allow_unsafe: this.forceUnsafe ?? this.allow_unsafe })); @@ -234,6 +251,9 @@ export default { if ( this.loaded && this.forceFull && ! this.full ) { description = 'null'; + } else if ( this.loaded && this.forceMid && ! this.mid ) { + description = 'null -- will use short instead'; + } else if ( this.error ) { title = this.t('card.error', 'An error occurred.'); description = this.error; diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index 63d644ec..f9ee9afd 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -224,6 +224,16 @@ export default class Chat extends Module { } }); + this.settings.add('chat.rich.want-mid', { + default: false, + ui: { + path: 'Chat > Appearance >> Rich Content', + title: 'Display larger rich content in chat.', + description: 'This enables the use of bigger rich content embeds in chat. This is **not** recommended for most users and/or chats.\n\n**Note:** Enabling this may cause chat to scroll at inopportune times due to content loading. Moderators should not use this feature.', + component: 'setting-check-box' + } + }); + this.settings.add('chat.rich.hide-tokens', { default: false, ui: { @@ -1879,11 +1889,13 @@ export default class Chat extends Module { const providers = this.__rich_providers; + const want_mid = this.context.get('chat.rich.want-mid'); + for(const token of tokens) { for(const provider of providers) if ( provider.test.call(this, token, msg) ) { token.hidden = provider.can_hide_token && (this.context.get('chat.rich.hide-tokens') || provider.hide_token); - return provider.process.call(this, token); + return provider.process.call(this, token, want_mid); } } } diff --git a/src/modules/chat/rich_providers.js b/src/modules/chat/rich_providers.js index da1f2590..7af6a449 100644 --- a/src/modules/chat/rich_providers.js +++ b/src/modules/chat/rich_providers.js @@ -38,11 +38,12 @@ export const Links = { return token.type === 'link' }, - process(token) { + process(token, want_mid) { return { card_tooltip: true, url: token.url, timeout: 0, + want_mid, getData: async (refresh = false) => { let data; diff --git a/src/modules/chat/tokenizers.jsx b/src/modules/chat/tokenizers.jsx index 1a786ff6..30dd002b 100644 --- a/src/modules/chat/tokenizers.jsx +++ b/src/modules/chat/tokenizers.jsx @@ -56,8 +56,6 @@ function datasetBool(value) { return value == null ? null : value === 'true'; } -const TOOLTIP_VERSION = 5; - export const Links = { type: 'link', priority: 50, @@ -91,12 +89,15 @@ export const Links = { import(/* webpackChunkName: 'rich_tokens' */ 'utilities/rich_tokens'), this.get_link_info(url) ]).then(([rich_tokens, data]) => { - if ( ! data || (data.v || 1) > TOOLTIP_VERSION ) + if ( ! data || (data.v || 1) > rich_tokens.VERSION ) return ''; const ctx = { tList: (...args) => this.i18n.tList(...args), i18n: this.i18n, + + fragments: data.fragments, + allow_media: show_images, allow_unsafe: show_unsafe, onload: () => requestAnimationFrame(() => tip.update()) @@ -111,12 +112,14 @@ export const Links = { if ( data.full ) { content = rich_tokens.renderTokens(data.full, createElement, ctx); - } else { - if ( data.short ) { - content = rich_tokens.renderTokens(data.short, createElement, ctx); - } else - content = this.i18n.t('card.empty', 'No data was returned.'); - } + } else if ( data.mid ) { + content = rich_tokens.renderTokens(data.mid, createElement, ctx); + + } else if ( data.short ) { + content = rich_tokens.renderTokens(data.short, createElement, ctx); + + } else + content = this.i18n.t('card.empty', 'No data was returned.'); if ( ! data.urls ) return content; @@ -1038,7 +1041,7 @@ export const CheerEmotes = { if ( length > 12 ) { out.push(
); - out.push(this.i18n.t('tooltip.bits.more', '(and {count} more)', length-12)); + out.push(this.i18n.t('tooltip.bits.more', '(and {count, number} more)', length-12)); } } diff --git a/src/modules/main_menu/components/link-tester.vue b/src/modules/main_menu/components/link-tester.vue index 83c258ec..4ca1bcb9 100644 --- a/src/modules/main_menu/components/link-tester.vue +++ b/src/modules/main_menu/components/link-tester.vue @@ -156,6 +156,22 @@ />
+
+ +
+ +
+
+
+ +
+ {{ tNumber(length) }} +
+