mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-03 09:38:31 +00:00
Refactor chat line rendering to support lines with system messages, making it possible to customize the rendering of re-sub messages and eventually other types, like purchases. Fix the emote data structure being all screwed up in self-sent /me commands.
This commit is contained in:
parent
6c4966166a
commit
2e5fe7f177
7 changed files with 173 additions and 73 deletions
|
@ -1,3 +1,9 @@
|
||||||
|
<div class="list-header">4.0.0-beta1.3<span>@7493d51cfb8e1b4448f0</span> <time datetime="2017-11-23">(2017-11-23)</time></div>
|
||||||
|
<ul class="chat-menu-content menu-side-padding">
|
||||||
|
<li>Fixed: Emoticons not appearing for yourself when you send a message with <code>/me</code></li>
|
||||||
|
<li>Changed: Transform re-sub notices into a standard chat message so that we can override the rendering. This fixes colors, emotes, etc.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div class="list-header">4.0.0-beta1.3<span>@da5b35d5323e5151e3ea</span> <time datetime="2017-11-22">(2017-11-22)</time></div>
|
<div class="list-header">4.0.0-beta1.3<span>@da5b35d5323e5151e3ea</span> <time datetime="2017-11-22">(2017-11-22)</time></div>
|
||||||
<ul class="chat-menu-content menu-side-padding">
|
<ul class="chat-menu-content menu-side-padding">
|
||||||
<li>Fixed: Add a touch scroll event handler for chat scrolling, as Twitch left that out for some reason.</li>
|
<li>Fixed: Add a touch scroll event handler for chat scrolling, as Twitch left that out for some reason.</li>
|
||||||
|
|
|
@ -5,21 +5,15 @@
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
import {sanitize, createElement as e} from 'utilities/dom';
|
import {sanitize, createElement as e} from 'utilities/dom';
|
||||||
import {has} from 'utilities/object';
|
import {has, split_chars} from 'utilities/object';
|
||||||
|
|
||||||
const EMOTE_CLASS = 'chat-line__message--emote',
|
const EMOTE_CLASS = 'chat-line__message--emote',
|
||||||
LINK_REGEX = /([^\w@#%\-+=:~])?((?:(https?:\/\/)?(?:[\w@#%\-+=:~]+\.)+[a-z]{2,6}(?:\/[\w.\/@#%&()\-+=:?~]*)?))([^\w.\/@#%&()\-+=:?~]|\s|$)/g,
|
LINK_REGEX = /([^\w@#%\-+=:~])?((?:(https?:\/\/)?(?:[\w@#%\-+=:~]+\.)+[a-z]{2,6}(?:\/[\w.\/@#%&()\-+=:?~]*)?))([^\w.\/@#%&()\-+=:?~]|\s|$)/g,
|
||||||
MENTION_REGEX = /([^\w@#%\-+=:~])?(@([^\u0000-\u007F]+|\w+)+)([^\w.\/@#%&()\-+=:?~]|\s|$)/g,
|
MENTION_REGEX = /([^\w@#%\-+=:~])?(@([^\u0000-\u007F]+|\w+)+)([^\w.\/@#%&()\-+=:?~]|\s|$)/g,
|
||||||
SPLIT_REGEX = /[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDFFF]/g,
|
|
||||||
|
|
||||||
TWITCH_BASE = '//static-cdn.jtvnw.net/emoticons/v1/';
|
TWITCH_BASE = '//static-cdn.jtvnw.net/emoticons/v1/';
|
||||||
|
|
||||||
|
|
||||||
function split_chars(str) {
|
|
||||||
return str.match(SPLIT_REGEX);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Links
|
// Links
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import {ColorAdjuster} from 'utilities/color';
|
import {ColorAdjuster} from 'utilities/color';
|
||||||
import {setChildren} from 'utilities/dom';
|
import {setChildren} from 'utilities/dom';
|
||||||
import {has} from 'utilities/object';
|
import {has, split_chars} from 'utilities/object';
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
|
|
||||||
|
@ -70,7 +70,8 @@ const EVENTS = [
|
||||||
'onBanEvent',
|
'onBanEvent',
|
||||||
'onModerationEvent',
|
'onModerationEvent',
|
||||||
'onSubscriptionEvent',
|
'onSubscriptionEvent',
|
||||||
'onResubscriptionEvent',
|
//'onResubscriptionEvent',
|
||||||
|
'onSubscriptionGiftEvent',
|
||||||
'onRoomStateEvent',
|
'onRoomStateEvent',
|
||||||
'onSlowModeEvent',
|
'onSlowModeEvent',
|
||||||
'onFollowerOnlyModeEvent',
|
'onFollowerOnlyModeEvent',
|
||||||
|
@ -408,6 +409,24 @@ export default class ChatHook extends Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const old_resub = this.onResubscriptionEvent;
|
||||||
|
this.onResubscriptionEvent = function(e) {
|
||||||
|
try {
|
||||||
|
const out = i.convertMessage({message: e});
|
||||||
|
out.ffz_type = 'resub';
|
||||||
|
out.sub_months = e.months;
|
||||||
|
out.sub_plan = e.methods;
|
||||||
|
|
||||||
|
i._wrapped = e;
|
||||||
|
const ret = i.postMessage(out);
|
||||||
|
i._wrapped = null;
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
} catch(err) {
|
||||||
|
return old_resub.call(i, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.postMessage = function(e) {
|
this.postMessage = function(e) {
|
||||||
const original = this._wrapped;
|
const original = this._wrapped;
|
||||||
if ( original ) {
|
if ( original ) {
|
||||||
|
@ -420,13 +439,20 @@ export default class ChatHook extends Module {
|
||||||
e.roomLogin = c.charAt(0) === '#' ? c.slice(1) : c;
|
e.roomLogin = c.charAt(0) === '#' ? c.slice(1) : c;
|
||||||
|
|
||||||
if ( original.message ) {
|
if ( original.message ) {
|
||||||
if ( original.action )
|
const u = original.message.user;
|
||||||
e.message = original.action;
|
if ( u )
|
||||||
else
|
e.emotes = u.emotes;
|
||||||
e.message = original.message.body;
|
|
||||||
|
|
||||||
if ( original.message.user )
|
if ( original.action ) {
|
||||||
e.emotes = original.message.user.emotes;
|
e.message = original.action;
|
||||||
|
|
||||||
|
// Twitch doesn't generate a proper emote tag for echoed back
|
||||||
|
// actions, so we have to regenerate it. Fun. :D
|
||||||
|
if ( u && u.username === i.userLogin )
|
||||||
|
e.emotes = findEmotes(e.message, i.selfEmotes);
|
||||||
|
|
||||||
|
} else
|
||||||
|
e.message = original.message.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
//e.original = original;
|
//e.original = original;
|
||||||
|
@ -671,6 +697,30 @@ export function formatBitsConfig(config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function findEmotes(msg, emotes) {
|
||||||
|
const out = {};
|
||||||
|
let idx = 0;
|
||||||
|
|
||||||
|
for(const part of msg.split(' ')) {
|
||||||
|
const len = split_chars(part).length;
|
||||||
|
|
||||||
|
if ( has(emotes, part) ) {
|
||||||
|
const em = emotes[part],
|
||||||
|
matches = out[em.id] = out[em.id] || [];
|
||||||
|
|
||||||
|
matches.push({
|
||||||
|
startIndex: idx,
|
||||||
|
endIndex: idx + len - 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function extractCheerPrefix(parts) {
|
function extractCheerPrefix(parts) {
|
||||||
for(const part of parts) {
|
for(const part of parts) {
|
||||||
if ( part.type !== 3 || ! part.content.cheerAmount )
|
if ( part.type !== 3 || ! part.content.cheerAmount )
|
||||||
|
|
|
@ -7,6 +7,12 @@
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
//import {Color} from 'utilities/color';
|
//import {Color} from 'utilities/color';
|
||||||
|
|
||||||
|
const SUB_TIERS = {
|
||||||
|
1000: '$4.99',
|
||||||
|
2000: '$9.99',
|
||||||
|
3000: '$24.99'
|
||||||
|
};
|
||||||
|
|
||||||
export default class ChatLine extends Module {
|
export default class ChatLine extends Module {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
|
@ -72,16 +78,8 @@ export default class ChatLine extends Module {
|
||||||
const tokens = t.chat.tokenizeMessage(msg, {login: this.props.currentUserLogin, display: this.props.currentUserDisplayName}),
|
const tokens = t.chat.tokenizeMessage(msg, {login: this.props.currentUserLogin, display: this.props.currentUserDisplayName}),
|
||||||
fragment = t.chat.renderTokens(tokens, e);
|
fragment = t.chat.renderTokens(tokens, e);
|
||||||
|
|
||||||
const out = e('div', {
|
let cls = 'chat-line__message',
|
||||||
className: `chat-line__message ${msg.mentioned ? 'ffz-mentioned' : ''}`,
|
out = fragment.length ? [
|
||||||
//style: { backgroundColor: bg_css },
|
|
||||||
'data-room-id': this.props.channelID,
|
|
||||||
'data-room': room,
|
|
||||||
'data-user-id': user.userID,
|
|
||||||
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
|
||||||
|
|
||||||
//onClick: () => this.setState({renderDebug: ((this.state.renderDebug||0) + 1) % 3})
|
|
||||||
}, [
|
|
||||||
this.props.showTimestamps && e('span', {
|
this.props.showTimestamps && e('span', {
|
||||||
className: 'chat-line__timestamp'
|
className: 'chat-line__timestamp'
|
||||||
}, t.chat.formatTime(msg.timestamp)),
|
}, t.chat.formatTime(msg.timestamp)),
|
||||||
|
@ -125,9 +123,50 @@ export default class ChatLine extends Module {
|
||||||
lineHeight: '1.1em'
|
lineHeight: '1.1em'
|
||||||
}
|
}
|
||||||
}, JSON.stringify([tokens, msg.emotes], null, 2))*/
|
}, JSON.stringify([tokens, msg.emotes], null, 2))*/
|
||||||
]);
|
] : null;
|
||||||
|
|
||||||
return out;
|
if ( msg.ffz_type === 'resub' ) {
|
||||||
|
const plan = msg.sub_plan || {},
|
||||||
|
months = msg.sub_months || 1,
|
||||||
|
tier = SUB_TIERS[plan.plan] || '$4.99';
|
||||||
|
|
||||||
|
cls = 'chat-line__subscribe';
|
||||||
|
out = [
|
||||||
|
e('span', null, [
|
||||||
|
t.i18n.t('chat.sub.main', '%{user} just subscribed with %{plan}!', {
|
||||||
|
user: user.userDisplayName,
|
||||||
|
plan: plan.prime ?
|
||||||
|
t.i18n.t('chat.sub.twitch-prime', 'Twitch Prime') :
|
||||||
|
t.i18n.t('chat.sub.plan', 'a %{tier} sub', {tier})
|
||||||
|
}),
|
||||||
|
months > 1 ?
|
||||||
|
` ${t.i18n.t(
|
||||||
|
'chat.sub.months',
|
||||||
|
'%{user} subscribed for %{count} months in a row!',
|
||||||
|
{
|
||||||
|
user: user.userDisplayName,
|
||||||
|
count: months
|
||||||
|
})}` : null
|
||||||
|
]),
|
||||||
|
out && e('div', {
|
||||||
|
className: 'chat-line__subscribe--message',
|
||||||
|
'data-room-id': this.props.channelID,
|
||||||
|
'data-room': room,
|
||||||
|
'data-user-id': user.userID,
|
||||||
|
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
||||||
|
}, out)
|
||||||
|
];
|
||||||
|
|
||||||
|
} else if ( ! out )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return e('div', {
|
||||||
|
className: `${cls}${msg.mentioned ? 'ffz-mentioned' : ''}`,
|
||||||
|
'data-room-id': this.props.channelID,
|
||||||
|
'data-room': room,
|
||||||
|
'data-user-id': user.userID,
|
||||||
|
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
||||||
|
}, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const inst of instances)
|
for(const inst of instances)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.chat-line__message.ffz-mentioned {
|
.chat-line__message,
|
||||||
&:nth-child(2n+0) {
|
.chat-line__subscribe {
|
||||||
|
&.ffz-mentioned:nth-child(2n+0) {
|
||||||
background-color: rgba(255,127,127,.4);
|
background-color: rgba(255,127,127,.4);
|
||||||
|
|
||||||
.theme--dark & {
|
.theme--dark & {
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
.chat-line__message.ffz-mentioned {
|
.chat-line__message,
|
||||||
|
.chat-line__subscribe {
|
||||||
|
&.ffz-mentioned {
|
||||||
background-color: rgba(255,127,127,.2);
|
background-color: rgba(255,127,127,.2);
|
||||||
|
|
||||||
.theme--dark & {
|
.theme--dark & {
|
||||||
background-color: rgba(255,0,0,.2);
|
background-color: rgba(255,0,0,.2);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -167,6 +167,13 @@ export function maybe_call(fn, ctx, ...args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const SPLIT_REGEX = /[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDFFF]/g;
|
||||||
|
|
||||||
|
export function split_chars(str) {
|
||||||
|
return str.match(SPLIT_REGEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export class SourcedSet {
|
export class SourcedSet {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._cache = [];
|
this._cache = [];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue