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