1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 15:27:43 +00:00

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)
This commit is contained in:
SirStendec 2018-12-13 15:21:57 -05:00
parent 9117ac89d2
commit d0789481fa
12 changed files with 375 additions and 245 deletions

View file

@ -149,7 +149,7 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`
FrankerFaceZ.Logger = Logger; FrankerFaceZ.Logger = Logger;
const VER = FrankerFaceZ.version_info = { 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__, commit: __git_commit__,
build: __webpack_hash__, build: __webpack_hash__,
toString: () => toString: () =>

View file

@ -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', { this.settings.add('chat.scrollback-length', {
default: 150, default: 150,
ui: { 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 standardizeMessage(msg) { // eslint-disable-line class-methods-use-this
if ( ! msg ) if ( ! msg )
return msg; return msg;
@ -1007,15 +1053,15 @@ export default class Chat extends Module {
} }
pluckRichContent(tokens) { // eslint-disable-line class-methods-use-this pluckRichContent(tokens, msg) { // eslint-disable-line class-methods-use-this
if ( ! this.context.get('chat.rich.enabled') ) if ( ! this.context.get('chat.rich.enabled') || this.context.get('chat.rich.minimum-level') > this.getUserLevel(msg) )
return; return;
const providers = this.__rich_providers; const providers = this.__rich_providers;
for(const token of tokens) { for(const token of tokens) {
for(const provider of providers) 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; token.hidden = this.context.get('chat.rich.hide-tokens') && provider.hide_token;
return provider.process.call(this, token); return provider.process.call(this, token);
} }

View file

@ -11,6 +11,43 @@ import GET_CLIP from './clip_info.gql';
import GET_VIDEO from './video_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 // Clips
// ============================================================================ // ============================================================================

View file

@ -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.badges.custom-mod', this.updateLines, this);
this.chat.context.on('changed:chat.rich.enabled', 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.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) => { this.ChatLine.ready((cls, instances) => {
const t = this, 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() { updateLines() {
for(const inst of this.ChatLine.instances) { for(const inst of this.ChatLine.instances) {
const msg = inst.props.node; const msg = inst.props.node;

View file

@ -206,6 +206,7 @@ Twilight.ROUTES = {
'prime': '/prime', 'prime': '/prime',
'turbo': '/turbo', 'turbo': '/turbo',
'user': '/:userName', 'user': '/:userName',
'embed-chat': '/embed/:userName/chat'
} }

View file

@ -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.rituals.show', this.updateLines, this);
this.chat.context.on('changed:chat.rich.enabled', 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.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.actions.inline', this.updateLines, this);
this.chat.context.on('changed:chat.filtering.show-deleted', 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); 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() { updateLines() {
for(const inst of this.ChatLine.instances) { for(const inst of this.ChatLine.instances) {
const msg = inst.props.message; const msg = inst.props.message;

View file

@ -140,11 +140,23 @@ export default class RichContent extends Module {
</div>) </div>)
} }
renderCardBody() {
if ( this.props.renderBody )
return this.props.renderBody(this.state, this, createElement);
if ( this.state.html )
return <div dangerouslySetInnerHTML={{__html: this.state.html}} />;
return [
this.renderCardImage(),
this.renderCardDescription()
];
}
renderCard() { renderCard() {
return (<div class="ffz--chat-card tw-elevation-1 tw-mg-t"> return (<div class="ffz--chat-card tw-elevation-1 tw-mg-t">
<div class="tw-c-background-base tw-flex tw-flex-nowrap tw-pd-05"> <div class="tw-c-background-base tw-flex tw-flex-nowrap tw-pd-05">
{this.renderCardImage()} {this.renderCardBody()}
{this.renderCardDescription()}
</div> </div>
</div>) </div>)
} }
@ -153,8 +165,13 @@ export default class RichContent extends Module {
if ( ! this.state.url ) if ( ! this.state.url )
return this.renderCard(); return this.renderCard();
const tooltip = this.props.card_tooltip;
return (<a return (<a
class="chat-card__link" class={`${tooltip ? 'ffz-tooltip ' : ''} chat-card__link`}
data-tooltip-type="link"
data-url={this.state.url}
data-is-mail={false}
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
href={this.state.url} href={this.state.url}

View file

@ -1,8 +1,8 @@
.message > .chat-line__message--emote { .message > span > .chat-line__message--emote {
vertical-align: baseline; vertical-align: baseline;
padding-top: 5px; padding-top: 5px;
} }
.message > .chat-line__message--emote.ffz-emoji { .message > span > .chat-line__message--emote.ffz-emoji {
padding-top: 0px; padding-top: 0px;
} }

View file

@ -1,3 +1,3 @@
.message > .chat-line__message--emote { .message > span > .chat-line__message--emote {
margin: -1px 0 0; margin: -1px 0 0;
} }

View file

@ -52,8 +52,11 @@ This is a very early feature and will change as there is time.`,
}); });
this.settings.add('theme.is-dark', { 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) { 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); return ctx.get('context.ui.theatreModeEnabled') || (ctx.get('theme.can-dark') && ctx.get('context.ui.theme') === 1);
}, },
changed: () => this.updateCSS() changed: () => this.updateCSS()

View file

@ -42,7 +42,15 @@
} }
} }
.ffz-tooltip.chat-card__link > * {
pointer-events: none;
}
.ffz--chat-card { .ffz--chat-card {
.chat-card__title {
max-width: unset;
}
.ffz--two-line { .ffz--two-line {
.tw-ellipsis { line-height: 1.4rem } .tw-ellipsis { line-height: 1.4rem }
.chat-card__title { line-height: 1.5rem } .chat-card__title { line-height: 1.5rem }

View file

@ -231,242 +231,241 @@ body {
}*/ }*/
} }
.ffz-rich-tip .body { line-height: 1.5em }
.ffz-rich-tip .tweet-heading:before { .ffz--chat-card,
content: ''; .ffz-rich-tip {
position: absolute; .body { line-height: 1.5em }
top: 24px;
right: 24px;
width: 20px;
height: 16px;
background: url("//cdn.frankerfacez.com/script/twitter_sprites.png") -38px -15px;
}
.ffz-rich-tip .stats:after, .tweet-heading:before {
.ffz-rich-tip .heading:after { content: '';
content: ''; position: absolute;
display: table; top: 24px;
clear: both; right: 24px;
} width: 20px;
height: 16px;
background: url("//cdn.frankerfacez.com/script/twitter_sprites.png") -38px -15px;
}
.ffz-rich-tip .avatar { .stats:after, .heading:after {
float: left; content: '';
margin-right: 8px; display: table;
max-height: 48px; clear: both;
max-width: 100px; }
} .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 { content: '';
border-radius: 50%; display: inline-block;
} background: url("//cdn.frankerfacez.com/script/twitter_sprites.png");
margin: 0 5px 0 10px;
.ffz-rich-tip .heading .title { }
font-weight: bold; .stat {
display: block; &.likes:before {
text-align: justify; width: 17px;
} height: 14px;
margin-bottom: -3px;
.ffz-rich-tip .display-name { }
padding-top: 3px; &.retweets:before {
font-size: 14px; width: 20px;
display: block; height: 12px;
} background-position: -34px 0;
margin-bottom: -2px;
.ffz-rich-tip .display-name.big-name { }
font-size: 20px; }
padding-top: 18px; .media {
} position: relative;
text-align: center;
.ffz-rich-tip .quoted .display-name { &[data-count]:not([data-count="1"]) {
padding: 0 5px 0 0; display: flex;
display: inline; margin: 8px -5px -5px 0;
font-size: 12px; flex-flow: column wrap;
} height: 329px;
}
.ffz-rich-tip .twitch-heading .tip-badge.verified { .duration {
height: 12px; position: absolute;
width: 12px; bottom: 0;
margin: 2px 0 -1px 5px; right: 0;
background: url("https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/1"); margin: 4px;
background-size: contain; background: #000;
display: inline-block; color: #fff;
} opacity: .8;
padding: 2px 4px;
.ffz-rich-tip .twitch-heading .big-name .tip-badge.verified { border-radius: 2px;
height: 18px; z-index: 10;
width: 18px; font-weight: 500;
margin: 1px 0 0 10px; }
} }
.sixteen-nine {
.ffz-rich-tip .tweet-heading .tip-badge { width: 100%;
height: 12px; overflow: hidden;
width: 12px; margin: 0;
margin: 2px 0 -1px 5px; padding-top: 56.25%;
background: url("//cdn.frankerfacez.com/script/twitter_sprites.png"); position: relative;
display: inline-block; img {
} position: absolute;
top: 50%;
.ffz-rich-tip .tip-badge.verified { left: 50%;
background-position: 0 -15px; width: 100%;
} transform: translate(-50%, -50%);
}
.ffz-rich-tip .tip-badge.translator { }
background-position: -12px -15px; .media {
} video {
width: 100%;
.ffz-rich-tip .tip-badge.protected { max-height: 324px;
background-position: -24px -15px; }
width: 14px; &[data-count="4"] {
} flex-flow: row wrap;
}
.ffz-rich-tip .emoji { &[data-count="2"] {
height: 1em; height: 164.5px;
vertical-align: middle; }
} img {
max-height: 324px;
.ffz-rich-tip .quoted > div:not(:first-child), }
.ffz-rich-tip > div:not(:first-child) { }
margin-top: 8px; .quoted .media {
} &[data-count]:not([data-count="1"]), &[data-count="2"] {
height: 150px;
.ffz-rich-tip .quote-heading + .body, }
.ffz-rich-tip .replying + .body { video, img {
margin-top: 0 !important; max-height: 150px;
} }
}
.ffz-rich-tip .replying { .media {
opacity: 0.6; span {
font-size: 10px; background: no-repeat center center;
} background-size: cover;
}
.ffz-rich-tip .subtitle, &[data-count="2"] span, &[data-count="3"] span, &[data-count="4"] span {
.ffz-rich-tip .quoted .body, width: calc(50% - 5px);
.ffz-rich-tip .stats, height: calc(50% - 5px);
.ffz-rich-tip .username { opacity: 0.6 } margin: 0 5px 5px 0;
}
.ffz-rich-tip .stats { display: flex } &[data-count="2"] span, &[data-count="3"] span:first-of-type {
.ffz-rich-tip .stats .wide-stat, height: 100%;
.ffz-rich-tip time { flex-grow: 1 } }
}
.ffz-rich-tip .tweet-stats .stat:before { .profile-stats {
content: ''; display: flex;
display: inline-block; flex-flow: row;
background: url("//cdn.frankerfacez.com/script/twitter_sprites.png"); flex-wrap: wrap;
margin: 0 5px 0 10px; div {
} flex-grow: 1;
font-size: 16px;
.ffz-rich-tip .stat.likes:before { margin-right: 10px;
width: 17px; height: 14px; }
margin-bottom: -3px; span {
} display: block;
font-size: 12px;
.ffz-rich-tip .stat.retweets:before { opacity: 0.7;
width: 20px; height: 12px; }
background-position: -34px 0; }
margin-bottom: -2px; .quoted {
} border: 1px solid #474747;
border-radius: 5px;
.ffz-rich-tip .media { position: relative; text-align: center } padding: 8px;
}
.ffz-rich-tip .media[data-count]:not([data-count="1"]) { .media[data-type="video"]:after {
display: flex; position: absolute;
margin: 8px -5px -5px 0; content: '';
flex-flow: column wrap; width: 64px;
height: 329px; height: 64px;
} top: calc(50% - 32px);
left: calc(50% - 32px);
.ffz-rich-tip .media .duration { 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");
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");
} }