From d0789481fa622e5b8b6bd2050c20ca8b09f293b6 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Thu, 13 Dec 2018 15:21:57 -0500 Subject: [PATCH] 4.0.0-rc13.16 * Added: Option to display rich embeds in chat for all links and not just those to Twitch Clips and Videos. (Closes #554) * Added: Option to only display rich embeds for links posted by users of a certain level. (Broadcaster, VIP, etc.) * Fixed: Implement logic for determining if embedded chat is dark or not, since embedded chat is a special snowflake. (Closes #557) * Fixed: Emote Alignment setting broke due to DOM changes in last update. (Closes #555) --- src/main.js | 2 +- src/modules/chat/index.js | 52 +- src/modules/chat/rich_providers.js | 37 ++ src/sites/twitch-clips/modules/chat/line.jsx | 10 + src/sites/twitch-twilight/index.js | 1 + .../twitch-twilight/modules/chat/line.js | 9 + .../modules/chat/rich_content.jsx | 23 +- .../styles/emote-alignment-baseline.scss | 4 +- .../styles/emote-alignment-padded.scss | 2 +- .../twitch-twilight/modules/theme/index.js | 5 +- src/sites/twitch-twilight/styles/chat.scss | 8 + styles/tooltips.scss | 467 +++++++++--------- 12 files changed, 375 insertions(+), 245 deletions(-) diff --git a/src/main.js b/src/main.js index 8533de44..3831522c 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: '-rc13.15', + major: 4, minor: 0, revision: 0, extra: '-rc13.16', commit: __git_commit__, build: __webpack_hash__, toString: () => diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index 0a5aed16..33c4f90d 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -125,6 +125,32 @@ export default class Chat extends Module { } }); + this.settings.add('chat.rich.all-links', { + default: false, + ui: { + path: 'Chat > Appearance >> Rich Content', + title: 'Display rich content embeds for all links.', + component: 'setting-check-box' + } + }); + + this.settings.add('chat.rich.minimum-level', { + default: 0, + ui: { + path: 'Chat > Appearance >> Rich Content', + title: 'Required User Level', + description: 'Only display rich content embeds on messages posted by users with this level or higher.', + component: 'setting-select-box', + data: [ + {value: 4, title: 'Broadcaster'}, + {value: 3, title: 'Moderator'}, + {value: 2, title: 'VIP'}, + {value: 1, title: 'Subscriber'}, + {value: 0, title: 'Everyone'} + ] + } + }); + this.settings.add('chat.scrollback-length', { default: 150, ui: { @@ -759,6 +785,26 @@ export default class Chat extends Module { } + getUserLevel(msg) { // eslint-disable-line class-methods-use-this + if ( ! msg || ! msg.user ) + return 0; + + if ( msg.user.login === msg.roomLogin || msg.badges.broadcaster ) + return 4; + + if ( msg.badges.moderator ) + return 3; + + if ( msg.badges.vip ) + return 2; + + if ( msg.badges.subscriber ) + return 1; + + return 0; + } + + standardizeMessage(msg) { // eslint-disable-line class-methods-use-this if ( ! msg ) return msg; @@ -1007,15 +1053,15 @@ export default class Chat extends Module { } - pluckRichContent(tokens) { // eslint-disable-line class-methods-use-this - if ( ! this.context.get('chat.rich.enabled') ) + pluckRichContent(tokens, msg) { // eslint-disable-line class-methods-use-this + if ( ! this.context.get('chat.rich.enabled') || this.context.get('chat.rich.minimum-level') > this.getUserLevel(msg) ) return; const providers = this.__rich_providers; for(const token of tokens) { for(const provider of providers) - if ( provider.test.call(this, token) ) { + if ( provider.test.call(this, token, msg) ) { token.hidden = 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 768e6f3d..6f8072eb 100644 --- a/src/modules/chat/rich_providers.js +++ b/src/modules/chat/rich_providers.js @@ -11,6 +11,43 @@ import GET_CLIP from './clip_info.gql'; import GET_VIDEO from './video_info.gql'; +// ============================================================================ +// General Links +// ============================================================================ + +export const Links = { + type: 'link', + hide_token: false, + priority: -10, + + test(token) { + if ( ! this.context.get('chat.rich.all-links') ) + return false; + + return token.type === 'link' + }, + + process(token) { + return { + card_tooltip: true, + url: token.url, + + getData: async () => { + const data = await this.get_link_info(token.url); + + return { + url: token.url, + image: this.context.get('tooltip.link-images') ? (data.image_safe || this.context.get('tooltip.link-nsfw-images') ) ? data.preview || data.image : null : null, + title: data.title, + desc_1: data.desc_1, + desc_2: data.desc_2 + } + } + } + } +} + + // ============================================================================ // Clips // ============================================================================ diff --git a/src/sites/twitch-clips/modules/chat/line.jsx b/src/sites/twitch-clips/modules/chat/line.jsx index ce1c7c06..a5898ed6 100644 --- a/src/sites/twitch-clips/modules/chat/line.jsx +++ b/src/sites/twitch-clips/modules/chat/line.jsx @@ -36,6 +36,10 @@ export default class Line extends Module { this.chat.context.on('changed:chat.badges.custom-mod', this.updateLines, this); this.chat.context.on('changed:chat.rich.enabled', this.updateLines, this); this.chat.context.on('changed:chat.rich.hide-tokens', this.updateLines, this); + this.chat.context.on('changed:chat.rich.all-links', this.updateLines, this); + this.chat.context.on('changed:chat.rich.minimum-level', this.updateLines, this); + this.chat.context.on('changed:tooltip.link-images', this.maybeUpdateLines, this); + this.chat.context.on('changed:tooltip.link-nsfw-images', this.maybeUpdateLines, this); this.ChatLine.ready((cls, instances) => { const t = this, @@ -85,6 +89,12 @@ export default class Line extends Module { } + maybeUpdateLines() { + if ( this.chat.context.get('chat.rich.all-links') ) + this.updateLines(); + } + + updateLines() { for(const inst of this.ChatLine.instances) { const msg = inst.props.node; diff --git a/src/sites/twitch-twilight/index.js b/src/sites/twitch-twilight/index.js index 3e6a3f9b..7e55397c 100644 --- a/src/sites/twitch-twilight/index.js +++ b/src/sites/twitch-twilight/index.js @@ -206,6 +206,7 @@ Twilight.ROUTES = { 'prime': '/prime', 'turbo': '/turbo', 'user': '/:userName', + 'embed-chat': '/embed/:userName/chat' } diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js index c4603d9b..003c1791 100644 --- a/src/sites/twitch-twilight/modules/chat/line.js +++ b/src/sites/twitch-twilight/modules/chat/line.js @@ -58,6 +58,10 @@ export default class ChatLine extends Module { this.chat.context.on('changed:chat.rituals.show', this.updateLines, this); this.chat.context.on('changed:chat.rich.enabled', this.updateLines, this); this.chat.context.on('changed:chat.rich.hide-tokens', this.updateLines, this); + this.chat.context.on('changed:chat.rich.all-links', this.updateLines, this); + this.chat.context.on('changed:chat.rich.minimum-level', this.updateLines, this); + this.chat.context.on('changed:tooltip.link-images', this.maybeUpdateLines, this); + this.chat.context.on('changed:tooltip.link-nsfw-images', this.maybeUpdateLines, this); this.chat.context.on('changed:chat.actions.inline', this.updateLines, this); this.chat.context.on('changed:chat.filtering.show-deleted', this.updateLines, this); this.chat.context.on('changed:chat.filtering.process-own', this.updateLines, this); @@ -433,6 +437,11 @@ export default class ChatLine extends Module { } + maybeUpdateLines() { + if ( this.chat.context.get('chat.rich.all-links') ) + this.updateLines(); + } + updateLines() { for(const inst of this.ChatLine.instances) { const msg = inst.props.message; diff --git a/src/sites/twitch-twilight/modules/chat/rich_content.jsx b/src/sites/twitch-twilight/modules/chat/rich_content.jsx index 26addb80..a1b60ae0 100644 --- a/src/sites/twitch-twilight/modules/chat/rich_content.jsx +++ b/src/sites/twitch-twilight/modules/chat/rich_content.jsx @@ -140,11 +140,23 @@ export default class RichContent extends Module { ) } + renderCardBody() { + if ( this.props.renderBody ) + return this.props.renderBody(this.state, this, createElement); + + if ( this.state.html ) + return
; + + return [ + this.renderCardImage(), + this.renderCardDescription() + ]; + } + renderCard() { return (
- {this.renderCardImage()} - {this.renderCardDescription()} + {this.renderCardBody()}
) } @@ -153,8 +165,13 @@ export default class RichContent extends Module { if ( ! this.state.url ) return this.renderCard(); + const tooltip = this.props.card_tooltip; + return ( .chat-line__message--emote { +.message > span > .chat-line__message--emote { vertical-align: baseline; padding-top: 5px; } -.message > .chat-line__message--emote.ffz-emoji { +.message > span > .chat-line__message--emote.ffz-emoji { padding-top: 0px; } diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/emote-alignment-padded.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/emote-alignment-padded.scss index 8ea67d3d..d6909697 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/emote-alignment-padded.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/emote-alignment-padded.scss @@ -1,3 +1,3 @@ -.message > .chat-line__message--emote { +.message > span > .chat-line__message--emote { margin: -1px 0 0; } diff --git a/src/sites/twitch-twilight/modules/theme/index.js b/src/sites/twitch-twilight/modules/theme/index.js index 88afac5e..9da9005c 100644 --- a/src/sites/twitch-twilight/modules/theme/index.js +++ b/src/sites/twitch-twilight/modules/theme/index.js @@ -52,8 +52,11 @@ This is a very early feature and will change as there is time.`, }); this.settings.add('theme.is-dark', { - requires: ['theme.can-dark', 'context.ui.theme', 'context.ui.theatreModeEnabled'], + requires: ['theme.can-dark', 'context.ui.theme', 'context.ui.theatreModeEnabled', 'context.route.name', 'context.location.search'], process(ctx) { + if ( ctx.get('context.route.name') === 'embed-chat' ) + return (ctx.get('context.location.search')||'').includes('dark'); + return ctx.get('context.ui.theatreModeEnabled') || (ctx.get('theme.can-dark') && ctx.get('context.ui.theme') === 1); }, changed: () => this.updateCSS() diff --git a/src/sites/twitch-twilight/styles/chat.scss b/src/sites/twitch-twilight/styles/chat.scss index 2185d2bc..fcd51e81 100644 --- a/src/sites/twitch-twilight/styles/chat.scss +++ b/src/sites/twitch-twilight/styles/chat.scss @@ -42,7 +42,15 @@ } } +.ffz-tooltip.chat-card__link > * { + pointer-events: none; +} + .ffz--chat-card { + .chat-card__title { + max-width: unset; + } + .ffz--two-line { .tw-ellipsis { line-height: 1.4rem } .chat-card__title { line-height: 1.5rem } diff --git a/styles/tooltips.scss b/styles/tooltips.scss index 1a01a616..9bc1b1c8 100644 --- a/styles/tooltips.scss +++ b/styles/tooltips.scss @@ -231,242 +231,241 @@ body { }*/ } -.ffz-rich-tip .body { line-height: 1.5em } -.ffz-rich-tip .tweet-heading:before { - content: ''; - position: absolute; - top: 24px; - right: 24px; - width: 20px; - height: 16px; - background: url("//cdn.frankerfacez.com/script/twitter_sprites.png") -38px -15px; -} +.ffz--chat-card, +.ffz-rich-tip { + .body { line-height: 1.5em } -.ffz-rich-tip .stats:after, -.ffz-rich-tip .heading:after { - content: ''; - display: table; - clear: both; -} + .tweet-heading:before { + content: ''; + position: absolute; + top: 24px; + right: 24px; + width: 20px; + height: 16px; + background: url("//cdn.frankerfacez.com/script/twitter_sprites.png") -38px -15px; + } -.ffz-rich-tip .avatar { - float: left; - margin-right: 8px; - max-height: 48px; - max-width: 100px; -} + .stats:after, .heading:after { + content: ''; + display: table; + clear: both; + } + .avatar { + float: left; + margin-right: 8px; + max-height: 48px; + max-width: 100px; + } + .tweet-heading .avatar { + border-radius: 50%; + } + .heading .title { + font-weight: bold; + display: block; + text-align: justify; + } + .display-name { + padding-top: 3px; + font-size: 14px; + display: block; + &.big-name { + font-size: 20px; + padding-top: 18px; + } + } + .quoted .display-name { + padding: 0 5px 0 0; + display: inline; + font-size: 12px; + } + .twitch-heading { + .tip-badge.verified { + height: 12px; + width: 12px; + margin: 2px 0 -1px 5px; + background: url("https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/1"); + background-size: contain; + display: inline-block; + } + .big-name .tip-badge.verified { + height: 18px; + width: 18px; + margin: 1px 0 0 10px; + } + } + .tweet-heading .tip-badge { + height: 12px; + width: 12px; + margin: 2px 0 -1px 5px; + background: url("//cdn.frankerfacez.com/script/twitter_sprites.png"); + display: inline-block; + } + .tip-badge { + &.verified { + background-position: 0 -15px; + } + &.translator { + background-position: -12px -15px; + } + &.protected { + background-position: -24px -15px; + width: 14px; + } + } + .emoji { + height: 1em; + vertical-align: middle; + } + .quoted > div:not(:first-child), > div:not(:first-child) { + margin-top: 8px; + } + .quote-heading + .body { + margin-top: 0 !important; + } + .replying { + + .body { + margin-top: 0 !important; + } + opacity: 0.6; + font-size: 10px; + } + .subtitle, .quoted .body, .stats, .username { + opacity: 0.6; + } + .stats { + display: flex; + .wide-stat { + flex-grow: 1; + } + } + time { + flex-grow: 1; + } + .tweet-stats .stat:before { + .tw-root--theme-dark & { + filter: invert(100%); + } -.ffz-rich-tip .tweet-heading .avatar { - border-radius: 50%; -} - -.ffz-rich-tip .heading .title { - font-weight: bold; - display: block; - text-align: justify; -} - -.ffz-rich-tip .display-name { - padding-top: 3px; - font-size: 14px; - display: block; -} - -.ffz-rich-tip .display-name.big-name { - font-size: 20px; - padding-top: 18px; -} - -.ffz-rich-tip .quoted .display-name { - padding: 0 5px 0 0; - display: inline; - font-size: 12px; -} - -.ffz-rich-tip .twitch-heading .tip-badge.verified { - height: 12px; - width: 12px; - margin: 2px 0 -1px 5px; - background: url("https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/1"); - background-size: contain; - display: inline-block; -} - -.ffz-rich-tip .twitch-heading .big-name .tip-badge.verified { - height: 18px; - width: 18px; - margin: 1px 0 0 10px; -} - -.ffz-rich-tip .tweet-heading .tip-badge { - height: 12px; - width: 12px; - margin: 2px 0 -1px 5px; - background: url("//cdn.frankerfacez.com/script/twitter_sprites.png"); - display: inline-block; -} - -.ffz-rich-tip .tip-badge.verified { - background-position: 0 -15px; -} - -.ffz-rich-tip .tip-badge.translator { - background-position: -12px -15px; -} - -.ffz-rich-tip .tip-badge.protected { - background-position: -24px -15px; - width: 14px; -} - -.ffz-rich-tip .emoji { - height: 1em; - vertical-align: middle; -} - -.ffz-rich-tip .quoted > div:not(:first-child), -.ffz-rich-tip > div:not(:first-child) { - margin-top: 8px; -} - -.ffz-rich-tip .quote-heading + .body, -.ffz-rich-tip .replying + .body { - margin-top: 0 !important; -} - -.ffz-rich-tip .replying { - opacity: 0.6; - font-size: 10px; -} - -.ffz-rich-tip .subtitle, -.ffz-rich-tip .quoted .body, -.ffz-rich-tip .stats, -.ffz-rich-tip .username { opacity: 0.6 } - -.ffz-rich-tip .stats { display: flex } -.ffz-rich-tip .stats .wide-stat, -.ffz-rich-tip time { flex-grow: 1 } - -.ffz-rich-tip .tweet-stats .stat:before { - content: ''; - display: inline-block; - background: url("//cdn.frankerfacez.com/script/twitter_sprites.png"); - margin: 0 5px 0 10px; -} - -.ffz-rich-tip .stat.likes:before { - width: 17px; height: 14px; - margin-bottom: -3px; -} - -.ffz-rich-tip .stat.retweets:before { - width: 20px; height: 12px; - background-position: -34px 0; - margin-bottom: -2px; -} - -.ffz-rich-tip .media { position: relative; text-align: center } - -.ffz-rich-tip .media[data-count]:not([data-count="1"]) { - display: flex; - margin: 8px -5px -5px 0; - flex-flow: column wrap; - height: 329px; -} - -.ffz-rich-tip .media .duration { - position: absolute; - bottom: 0; right: 0; - margin: 4px; - background: #000; - color: #fff; - opacity: .8; - padding: 2px 4px; - border-radius: 2px; - z-index: 10; - font-weight: 500; -} - -.ffz-rich-tip .sixteen-nine { - width: 100%; - overflow: hidden; - margin: 0; - padding-top: 56.25%; - position: relative; -} - -.ffz-rich-tip .sixteen-nine img { - position: absolute; - top: 50%; - left: 50%; - width: 100%; - transform: translate(-50%, -50%); -} - -.ffz-rich-tip .media video { - width: 100%; - max-height: 324px; -} - -.ffz-rich-tip .media[data-count="4"] { flex-flow: row wrap } - -.ffz-rich-tip .media[data-count="2"] { height: 164.5px } -.ffz-rich-tip .media img { max-height: 324px } - -.ffz-rich-tip .quoted .media[data-count]:not([data-count="1"]) { height: 150px } -.ffz-rich-tip .quoted .media[data-count="2"] { height: 150px } -.ffz-rich-tip .quoted .media video, -.ffz-rich-tip .quoted .media img { max-height: 150px } - -.ffz-rich-tip .media span { - background: no-repeat center center; - background-size: cover; -} - -.ffz-rich-tip .media[data-count="2"] span, -.ffz-rich-tip .media[data-count="3"] span, -.ffz-rich-tip .media[data-count="4"] span { - width: calc(50% - 5px); - height: calc(50% - 5px); - margin: 0 5px 5px 0; -} - -.ffz-rich-tip .media[data-count="2"] span, -.ffz-rich-tip .media[data-count="3"] span:first-of-type { - height: 100%; -} - -.ffz-rich-tip .profile-stats { - display: flex; - flex-flow: row; - flex-wrap: wrap; -} - -.ffz-rich-tip .profile-stats div { - flex-grow: 1; - font-size: 16px; - margin-right: 10px; -} - -.ffz-rich-tip .profile-stats span { - display: block; - font-size: 12px; - opacity: 0.7; -} - -.ffz-rich-tip .quoted { - border: 1px solid #474747; - border-radius: 5px; - padding: 8px; -} - -.ffz-rich-tip .media[data-type="video"]:after { - position: absolute; - content: ''; - width: 64px; height: 64px; - top: calc(50% - 32px); - left: calc(50% - 32px); - background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='%23FFF' d='M15 10.001c0 .299-.305.514-.305.514l-8.561 5.303C5.51 16.227 5 15.924 5 15.149V4.852c0-.777.51-1.078 1.135-.67l8.561 5.305c-.001 0 .304.215.304.514z'/%3E%3C/svg%3E"); + content: ''; + display: inline-block; + background: url("//cdn.frankerfacez.com/script/twitter_sprites.png"); + margin: 0 5px 0 10px; + } + .stat { + &.likes:before { + width: 17px; + height: 14px; + margin-bottom: -3px; + } + &.retweets:before { + width: 20px; + height: 12px; + background-position: -34px 0; + margin-bottom: -2px; + } + } + .media { + position: relative; + text-align: center; + &[data-count]:not([data-count="1"]) { + display: flex; + margin: 8px -5px -5px 0; + flex-flow: column wrap; + height: 329px; + } + .duration { + position: absolute; + bottom: 0; + right: 0; + margin: 4px; + background: #000; + color: #fff; + opacity: .8; + padding: 2px 4px; + border-radius: 2px; + z-index: 10; + font-weight: 500; + } + } + .sixteen-nine { + width: 100%; + overflow: hidden; + margin: 0; + padding-top: 56.25%; + position: relative; + img { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + transform: translate(-50%, -50%); + } + } + .media { + video { + width: 100%; + max-height: 324px; + } + &[data-count="4"] { + flex-flow: row wrap; + } + &[data-count="2"] { + height: 164.5px; + } + img { + max-height: 324px; + } + } + .quoted .media { + &[data-count]:not([data-count="1"]), &[data-count="2"] { + height: 150px; + } + video, img { + max-height: 150px; + } + } + .media { + span { + background: no-repeat center center; + background-size: cover; + } + &[data-count="2"] span, &[data-count="3"] span, &[data-count="4"] span { + width: calc(50% - 5px); + height: calc(50% - 5px); + margin: 0 5px 5px 0; + } + &[data-count="2"] span, &[data-count="3"] span:first-of-type { + height: 100%; + } + } + .profile-stats { + display: flex; + flex-flow: row; + flex-wrap: wrap; + div { + flex-grow: 1; + font-size: 16px; + margin-right: 10px; + } + span { + display: block; + font-size: 12px; + opacity: 0.7; + } + } + .quoted { + border: 1px solid #474747; + border-radius: 5px; + padding: 8px; + } + .media[data-type="video"]:after { + position: absolute; + content: ''; + width: 64px; + height: 64px; + top: calc(50% - 32px); + left: calc(50% - 32px); + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='%23FFF' d='M15 10.001c0 .299-.305.514-.305.514l-8.561 5.303C5.51 16.227 5 15.924 5 15.149V4.852c0-.777.51-1.078 1.135-.67l8.561 5.305c-.001 0 .304.215.304.514z'/%3E%3C/svg%3E"); + } } \ No newline at end of file