mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-02 17:18: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>
|
||||
<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>
|
||||
|
|
|
@ -5,21 +5,15 @@
|
|||
// ============================================================================
|
||||
|
||||
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',
|
||||
LINK_REGEX = /([^\w@#%\-+=:~])?((?:(https?:\/\/)?(?:[\w@#%\-+=:~]+\.)+[a-z]{2,6}(?:\/[\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/';
|
||||
|
||||
|
||||
function split_chars(str) {
|
||||
return str.match(SPLIT_REGEX);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Links
|
||||
// ============================================================================
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import {ColorAdjuster} from 'utilities/color';
|
||||
import {setChildren} from 'utilities/dom';
|
||||
import {has} from 'utilities/object';
|
||||
import {has, split_chars} from 'utilities/object';
|
||||
|
||||
import Module from 'utilities/module';
|
||||
|
||||
|
@ -70,7 +70,8 @@ const EVENTS = [
|
|||
'onBanEvent',
|
||||
'onModerationEvent',
|
||||
'onSubscriptionEvent',
|
||||
'onResubscriptionEvent',
|
||||
//'onResubscriptionEvent',
|
||||
'onSubscriptionGiftEvent',
|
||||
'onRoomStateEvent',
|
||||
'onSlowModeEvent',
|
||||
'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) {
|
||||
const original = this._wrapped;
|
||||
if ( original ) {
|
||||
|
@ -420,13 +439,20 @@ export default class ChatHook extends Module {
|
|||
e.roomLogin = c.charAt(0) === '#' ? c.slice(1) : c;
|
||||
|
||||
if ( original.message ) {
|
||||
if ( original.action )
|
||||
e.message = original.action;
|
||||
else
|
||||
e.message = original.message.body;
|
||||
const u = original.message.user;
|
||||
if ( u )
|
||||
e.emotes = u.emotes;
|
||||
|
||||
if ( original.message.user )
|
||||
e.emotes = original.message.user.emotes;
|
||||
if ( original.action ) {
|
||||
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;
|
||||
|
@ -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) {
|
||||
for(const part of parts) {
|
||||
if ( part.type !== 3 || ! part.content.cheerAmount )
|
||||
|
|
|
@ -7,6 +7,12 @@
|
|||
import Module from 'utilities/module';
|
||||
//import {Color} from 'utilities/color';
|
||||
|
||||
const SUB_TIERS = {
|
||||
1000: '$4.99',
|
||||
2000: '$9.99',
|
||||
3000: '$24.99'
|
||||
};
|
||||
|
||||
export default class ChatLine extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
@ -72,62 +78,95 @@ export default class ChatLine extends Module {
|
|||
const tokens = t.chat.tokenizeMessage(msg, {login: this.props.currentUserLogin, display: this.props.currentUserDisplayName}),
|
||||
fragment = t.chat.renderTokens(tokens, e);
|
||||
|
||||
const out = e('div', {
|
||||
className: `chat-line__message ${msg.mentioned ? 'ffz-mentioned' : ''}`,
|
||||
//style: { backgroundColor: bg_css },
|
||||
let cls = 'chat-line__message',
|
||||
out = fragment.length ? [
|
||||
this.props.showTimestamps && e('span', {
|
||||
className: 'chat-line__timestamp'
|
||||
}, t.chat.formatTime(msg.timestamp)),
|
||||
this.renderModerationIcons(),
|
||||
e('span', {
|
||||
className: 'chat-line__message--badges'
|
||||
}, t.chat.renderBadges(msg, e)),
|
||||
e('a', {
|
||||
className: 'chat-author__display-name',
|
||||
style: { color },
|
||||
onClick: this.usernameClickHandler
|
||||
}, user.userDisplayName),
|
||||
user.isIntl && e('span', {
|
||||
className: 'chat-author__intl-login',
|
||||
style: { color },
|
||||
onClick: this.usernameClickHandler
|
||||
}, ` (${user.userLogin})`),
|
||||
e('span', null, is_action ? ' ' : ': '),
|
||||
show ?
|
||||
e('span', {
|
||||
className:'message',
|
||||
style: is_action ? { color } : null
|
||||
}, fragment)
|
||||
:
|
||||
e('span', {
|
||||
className: 'chat-line__message--deleted',
|
||||
}, e('a', {
|
||||
href: '',
|
||||
onClick: this.alwaysShowMessage
|
||||
}, `<message deleted>`)),
|
||||
|
||||
/*this.state.renderDebug === 2 && e('div', {
|
||||
className: 'border mg-t-05'
|
||||
}, old_render.call(this)),
|
||||
|
||||
this.state.renderDebug === 1 && e('div', {
|
||||
className: 'message--debug',
|
||||
style: {
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
lineHeight: '1.1em'
|
||||
}
|
||||
}, JSON.stringify([tokens, msg.emotes], null, 2))*/
|
||||
] : null;
|
||||
|
||||
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(),
|
||||
|
||||
//onClick: () => this.setState({renderDebug: ((this.state.renderDebug||0) + 1) % 3})
|
||||
}, [
|
||||
this.props.showTimestamps && e('span', {
|
||||
className: 'chat-line__timestamp'
|
||||
}, t.chat.formatTime(msg.timestamp)),
|
||||
this.renderModerationIcons(),
|
||||
e('span', {
|
||||
className: 'chat-line__message--badges'
|
||||
}, t.chat.renderBadges(msg, e)),
|
||||
e('a', {
|
||||
className: 'chat-author__display-name',
|
||||
style: { color },
|
||||
onClick: this.usernameClickHandler
|
||||
}, user.userDisplayName),
|
||||
user.isIntl && e('span', {
|
||||
className: 'chat-author__intl-login',
|
||||
style: { color },
|
||||
onClick: this.usernameClickHandler
|
||||
}, ` (${user.userLogin})`),
|
||||
e('span', null, is_action ? ' ' : ': '),
|
||||
show ?
|
||||
e('span', {
|
||||
className:'message',
|
||||
style: is_action ? { color } : null
|
||||
}, fragment)
|
||||
:
|
||||
e('span', {
|
||||
className: 'chat-line__message--deleted',
|
||||
}, e('a', {
|
||||
href: '',
|
||||
onClick: this.alwaysShowMessage
|
||||
}, `<message deleted>`)),
|
||||
|
||||
/*this.state.renderDebug === 2 && e('div', {
|
||||
className: 'border mg-t-05'
|
||||
}, old_render.call(this)),
|
||||
|
||||
this.state.renderDebug === 1 && e('div', {
|
||||
className: 'message--debug',
|
||||
style: {
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
lineHeight: '1.1em'
|
||||
}
|
||||
}, JSON.stringify([tokens, msg.emotes], null, 2))*/
|
||||
]);
|
||||
|
||||
return out;
|
||||
}, out);
|
||||
}
|
||||
|
||||
for(const inst of instances)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.chat-line__message.ffz-mentioned {
|
||||
&:nth-child(2n+0) {
|
||||
.chat-line__message,
|
||||
.chat-line__subscribe {
|
||||
&.ffz-mentioned:nth-child(2n+0) {
|
||||
background-color: rgba(255,127,127,.4);
|
||||
|
||||
.theme--dark & {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
.chat-line__message.ffz-mentioned {
|
||||
background-color: rgba(255,127,127,.2);
|
||||
.chat-line__message,
|
||||
.chat-line__subscribe {
|
||||
&.ffz-mentioned {
|
||||
background-color: rgba(255,127,127,.2);
|
||||
|
||||
.theme--dark & {
|
||||
background-color: rgba(255,0,0,.2);
|
||||
.theme--dark & {
|
||||
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 {
|
||||
constructor() {
|
||||
this._cache = [];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue