2017-11-14 22:13:30 -05:00
'use strict' ;
// ============================================================================
// Chat Line
// ============================================================================
2018-03-14 13:58:04 -04:00
import Twilight from 'site' ;
2017-11-14 22:13:30 -05:00
import Module from 'utilities/module' ;
2018-04-03 19:28:06 -04:00
import RichContent from './rich_content' ;
2022-12-18 17:30:34 -05:00
import { has , maybe _call } from 'utilities/object' ;
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
import { KEYS , RERENDER _SETTINGS , UPDATE _BADGE _SETTINGS , UPDATE _TOKEN _SETTINGS } from 'utilities/constants' ;
2019-12-06 15:56:58 -05:00
import { print _duration } from 'utilities/time' ;
import { FFZEvent } from 'utilities/events' ;
2019-11-11 14:38:49 -05:00
import { getRewardTitle , getRewardCost , isHighlightedReward } from './points' ;
2018-04-03 19:28:06 -04:00
2017-11-23 02:49:23 -05:00
const SUB _TIERS = {
2018-03-11 14:04:55 -04:00
1000 : 1 ,
2000 : 2 ,
3000 : 3
2017-11-23 02:49:23 -05:00
} ;
2021-09-04 20:14:58 -04:00
2017-11-14 22:13:30 -05:00
export default class ChatLine extends Module {
constructor ( ... args ) {
super ( ... args ) ;
this . inject ( 'settings' ) ;
this . inject ( 'i18n' ) ;
this . inject ( 'chat' ) ;
2018-04-28 17:56:03 -04:00
this . inject ( 'site' ) ;
2017-11-14 22:13:30 -05:00
this . inject ( 'site.fine' ) ;
this . inject ( 'site.web_munch' ) ;
2018-04-03 19:28:06 -04:00
this . inject ( RichContent ) ;
2020-08-12 16:10:06 -04:00
this . inject ( 'experiments' ) ;
2017-11-14 22:13:30 -05:00
2018-04-28 17:56:03 -04:00
this . inject ( 'chat.actions' ) ;
2020-01-11 17:13:56 -05:00
this . inject ( 'chat.overrides' ) ;
2023-03-03 15:24:20 -05:00
this . inject ( 'chat.emotes' ) ;
2018-04-28 17:56:03 -04:00
2022-12-18 17:30:34 -05:00
this . line _types = { } ;
2022-02-11 15:17:32 -05:00
2023-01-19 17:00:09 -05:00
this . line _types . unknown = {
renderNotice : ( msg , current _user , room , inst , e ) => {
return ` Unknown message type: ${ msg . ffz _type } `
}
} ;
2023-06-26 13:11:27 -04:00
this . line _types . hype = {
renderNotice : ( msg , current _user , room , inst , e ) => {
const setting = this . chat . context . get ( 'chat.hype.message-style' ) ;
if ( setting === 0 )
return null ;
// We need to get the message's tokens to see if it has a message or not.
const user = msg . user ,
tokens = msg . ffz _tokens = msg . ffz _tokens || this . chat . tokenizeMessage ( msg , current _user ) ,
has _message = tokens . length > 0 ;
let amount = msg . hype _amount ;
const digits = msg . hype _exponent ? ? 0 ;
if ( digits > 0 )
amount /= Math . pow ( 10 , digits ) ;
try {
// TODO: Cache formatter?
const fmt = new Intl . NumberFormat ( navigator . languages , {
style : 'currency' ,
currency : msg . hype _currency ,
compactDisplay : 'short' ,
minimumFractionDigits : digits ,
maximumFractionDigits : digits
} ) ;
amount = fmt . format ( amount ) ;
} catch ( err ) {
amount = ` ${ msg . hype _currency } ${ amount } ` ;
}
if ( ! has _message )
return this . i18n . tList ( 'chat.hype-chat.user' , '{user} sent a Hype Chat for {amount}!' , {
amount ,
user : e ( 'span' , {
role : 'button' ,
className : 'chatter-name' ,
onClick : inst . ffz _user _click _handler ,
onContextMenu : this . actions . handleUserContext
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
} , user . displayName ) )
} ) ;
return this . i18n . tList (
'chat.hype-chat' ,
'Sent a Hype Chat for {amount}!' ,
{
amount
}
)
}
} ;
2022-12-18 17:30:34 -05:00
this . line _types . cheer = {
renderNotice : ( msg , current _user , room , inst , e ) => {
return this . i18n . tList (
'chat.bits-message' ,
'Cheered {count, plural, one {# Bit} other {# Bits}}' ,
{
count : msg . bits || 0
}
) ;
}
} ;
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
this . line _types . points = {
getClass : ( msg ) => {
const highlight = msg . ffz _reward _highlight && this . chat . context . get ( 'chat.points.allow-highlight' ) === 2 ;
return ` ffz--points-line tw-pd-l-1 tw-pd-r-2 ${ highlight ? 'ffz-custom-color ffz--points-highlight' : '' } ` ;
} ,
renderNotice : ( msg , current _user , room , inst , e ) => {
if ( ! msg . ffz _reward )
return null ;
// We need to get the message's tokens to see if it has a message or not.
const user = msg . user ,
tokens = msg . ffz _tokens = msg . ffz _tokens || this . chat . tokenizeMessage ( msg , current _user ) ,
has _message = tokens . length > 0 ;
// Elements for the reward and cost with nice formatting.
const reward = e ( 'span' , { className : 'ffz--points-reward' } , getRewardTitle ( msg . ffz _reward , this . i18n ) ) ,
cost = e ( 'span' , { className : 'ffz--points-cost' } , [
e ( 'span' , { className : 'ffz--points-icon' } ) ,
this . i18n . formatNumber ( getRewardCost ( msg . ffz _reward ) )
] ) ;
if ( ! has _message )
return this . i18n . tList ( 'chat.points.user-redeemed' , '{user} redeemed {reward} {cost}' , {
reward , cost ,
user : e ( 'span' , {
role : 'button' ,
className : 'chatter-name' ,
onClick : inst . ffz _user _click _handler ,
onContextMenu : this . actions . handleUserContext
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
} , user . displayName ) )
} ) ;
return this . i18n . tList ( 'chat.points.redeemed' , 'Redeemed {reward} {cost}' , { reward , cost } ) ;
}
} ;
this . line _types . resub = {
getClass : ( ) => ` ffz--subscribe-line tw-pd-r-2 ` ,
renderNotice : ( msg , current _user , room , inst , e ) => {
const months = msg . sub _cumulative || msg . sub _months ,
setting = this . chat . context . get ( 'chat.subs.show' ) ;
if ( ! ( setting === 3 || ( setting === 1 && out && months > 1 ) || ( setting === 2 && months > 1 ) ) )
return null ;
const user = msg . user ,
plan = msg . sub _plan || { } ,
tier = SUB _TIERS [ plan . plan ] || 1 ;
const sub _msg = this . i18n . tList ( 'chat.sub.main' , '{user} subscribed {plan}. ' , {
user : e ( 'span' , {
2022-02-11 15:17:32 -05:00
role : 'button' ,
className : 'chatter-name' ,
2022-12-18 17:30:34 -05:00
onClick : inst . ffz _user _click _handler ,
onContextMenu : this . actions . handleUserContext
2022-02-11 15:17:32 -05:00
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
2022-12-18 17:30:34 -05:00
} , user . displayName ) ) ,
plan : plan . prime ?
this . i18n . t ( 'chat.sub.twitch-prime' , 'with Prime Gaming' ) :
this . i18n . t ( 'chat.sub.plan' , 'at Tier {tier}' , { tier } )
} ) ;
if ( msg . sub _share _streak && msg . sub _streak > 1 ) {
sub _msg . push ( this . i18n . t (
'chat.sub.cumulative-months' ,
"They've subscribed for {cumulative,number} months, currently on a {streak,number} month streak!" ,
{
cumulative : msg . sub _cumulative ,
streak : msg . sub _streak
}
) ) ;
} else if ( months > 1 ) {
sub _msg . push ( this . i18n . t (
'chat.sub.months' ,
"They've subscribed for {count,number} months!" ,
{
count : months
}
) ) ;
}
if ( ! this . chat . context . get ( 'chat.subs.compact' ) )
sub _msg . ffz _icon = e ( 'span' , {
className : ` ffz-i- ${ plan . prime ? 'crown' : 'star' } tw-mg-r-05 `
} ) ;
return sub _msg ;
}
} ;
this . line _types . ritual = {
getClass : ( ) => ` ffz--ritual-line tw-pd-r-2 ` ,
renderNotice : ( msg , current _user , room , inst , e ) => {
const user = msg . user ;
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
if ( msg . ritual === 'new_chatter' ) {
return this . i18n . tList ( 'chat.ritual' , '{user} is new here. Say hello!' , {
user : e ( 'span' , {
role : 'button' ,
className : 'chatter-name' ,
onClick : inst . ffz _user _click _handler ,
onContextMenu : this . actions . handleUserContext
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
} , user . displayName ) )
2022-02-11 15:17:32 -05:00
} ) ;
}
2022-12-18 17:30:34 -05:00
}
} ;
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
this . line _types . sub _gift = {
getClass : ( ) => 'ffz--subscribe-line' ,
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
renderNotice : ( msg , current _user , room , inst , e ) => {
const user = msg . user ,
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
plan = msg . sub _plan || { } ,
months = msg . sub _months || 1 ,
tier = SUB _TIERS [ plan . plan ] || 1 ;
let sub _msg ;
const bits = {
months ,
user : ( msg . sub _anon || user . username === 'ananonymousgifter' ) ?
this . i18n . t ( 'chat.sub.anonymous-gifter' , 'An anonymous gifter' ) :
e ( 'span' , {
role : 'button' ,
className : 'chatter-name' ,
onClick : inst . ffz _user _click _handler ,
onContextMenu : this . actions . handleUserContext
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
} , user . displayName ) ) ,
plan : plan . plan === 'custom' ? '' :
this . i18n . t ( 'chat.sub.gift-plan' , 'Tier {tier}' , { tier } ) ,
recipient : e ( 'span' , {
2022-02-11 15:17:32 -05:00
role : 'button' ,
2022-12-18 17:30:34 -05:00
className : 'chatter-name' ,
2022-02-11 15:17:32 -05:00
onClick : inst . ffz _user _click _handler ,
2022-12-18 17:30:34 -05:00
'data-user' : JSON . stringify ( msg . sub _recipient )
2022-02-11 15:17:32 -05:00
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
2022-12-18 17:30:34 -05:00
} , msg . sub _recipient . displayName ) )
} ;
if ( months <= 1 )
sub _msg = this . i18n . tList ( 'chat.sub.mystery' , '{user} gifted a {plan} Sub to {recipient}! ' , bits ) ;
else
sub _msg = this . i18n . tList ( 'chat.sub.gift-months' , '{user} gifted {months, plural, one {# month} other {# months}} of {plan} Sub to {recipient}!' , bits ) ;
if ( msg . sub _total === 1 )
sub _msg . push ( this . i18n . t ( 'chat.sub.gift-first' , "It's their first time gifting a Sub in the channel!" ) ) ;
else if ( msg . sub _total > 1 )
sub _msg . push ( this . i18n . t ( 'chat.sub.gift-total' , "They've gifted {count,number} Subs in the channel!" , {
count : msg . sub _total
} ) ) ;
if ( ! this . chat . context . get ( 'chat.subs.compact' ) )
sub _msg . ffz _icon = e ( 'span' , {
className : ` ffz-i- ${ plan . prime ? 'crown' : 'star' } tw-mg-r-05 `
} ) ;
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
return sub _msg ;
2022-02-11 15:17:32 -05:00
}
2022-12-18 17:30:34 -05:00
}
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
this . line _types . sub _mystery = {
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
getClass : ( ) => 'ffz--subscribe-line' ,
renderNotice : ( msg , user , room , inst , e ) => {
const mystery = msg . mystery ;
if ( mystery )
mystery . line = inst ;
const sub _msg = this . i18n . tList ( 'chat.sub.gift' , "{user} is gifting {count, plural, one {# Tier {tier} Sub} other {# Tier {tier} Subs}} to {channel}'s community! " , {
user : ( msg . sub _anon || msg . user . username === 'ananonymousgifter' ) ?
this . i18n . t ( 'chat.sub.anonymous-gifter' , 'An anonymous gifter' ) :
e ( 'span' , {
role : 'button' ,
className : 'chatter-name' ,
onClick : inst . ffz _user _click _handler ,
onContextMenu : this . actions . handleUserContext
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
} , msg . user . displayName ) ) ,
count : msg . sub _count ,
tier : SUB _TIERS [ msg . sub _plan ] || 1 ,
channel : msg . roomLogin
} ) ;
if ( msg . sub _total === 1 )
sub _msg . push ( this . i18n . t ( 'chat.sub.gift-first' , "It's their first time gifting a Sub in the channel!" ) ) ;
else if ( msg . sub _total > 1 )
sub _msg . push ( this . i18n . t ( 'chat.sub.gift-total' , "They've gifted {count} Subs in the channel!" , {
count : msg . sub _total
} ) ) ;
if ( ! inst . ffz _click _expand )
inst . ffz _click _expand = ( ) => {
inst . setState ( {
ffz _expanded : ! inst . state . ffz _expanded
} ) ;
}
const expanded = this . chat . context . get ( 'chat.subs.merge-gifts-visibility' ) ?
! inst . state . ffz _expanded : inst . state . ffz _expanded ;
let sub _list = null ;
if ( expanded && mystery && mystery . recipients && mystery . recipients . length > 0 ) {
const the _list = [ ] ;
for ( const x of mystery . recipients ) {
if ( the _list . length )
the _list . push ( ', ' ) ;
the _list . push ( e ( 'span' , {
role : 'button' ,
className : 'ffz--giftee-name' ,
onClick : inst . ffz _user _click _handler ,
'data-user' : JSON . stringify ( x )
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
} , x . displayName ) ) ) ;
}
sub _list = e ( 'div' , {
className : 'tw-mg-t-05 tw-border-t tw-pd-t-05 tw-c-text-alt-2'
} , the _list ) ;
}
const target = [
sub _msg
] ;
if ( mystery )
target . push ( e ( 'span' , {
className : ` tw-pd-l-05 tw-font-size-4 ffz-i- ${ expanded ? 'down' : 'right' } -dir `
} ) ) ;
const out = [
2022-02-11 15:17:32 -05:00
e ( 'div' , {
2022-12-18 17:30:34 -05:00
className : 'tw-full-width tw-c-text-alt-2' ,
2022-02-11 15:17:32 -05:00
onClick : inst . ffz _click _expand
2022-12-18 17:30:34 -05:00
} , target ) ,
2022-02-11 15:17:32 -05:00
sub _list
2022-12-18 17:30:34 -05:00
] ;
if ( ! this . chat . context . get ( 'chat.subs.compact' ) )
out . ffz _icon = e ( 'span' , {
className : ` ffz-i-star ${ msg . sub _anon ? '-empty' : '' } tw-mg-r-05 `
} ) ;
out . ffz _target = target ;
return out ;
}
} ;
2022-02-11 15:17:32 -05:00
2017-11-14 22:13:30 -05:00
this . ChatLine = this . fine . define (
'chat-line' ,
2019-04-28 17:28:16 -04:00
n => n . renderMessageBody && n . props && ! n . onExtensionNameClick && ! has ( n . props , 'hasModPermissions' ) ,
Twilight . CHAT _ROUTES
) ;
this . ExtensionLine = this . fine . define (
'extension-line' ,
n => n . renderMessageBody && n . onExtensionNameClick ,
2018-03-14 13:58:04 -04:00
Twilight . CHAT _ROUTES
2018-02-22 18:23:44 -05:00
) ;
2018-07-13 14:32:12 -04:00
this . WhisperLine = this . fine . define (
'whisper-line' ,
2020-03-31 18:14:27 -04:00
n => n . props && n . props . message && has ( n . props , 'reportOutgoingWhisperRendered' )
2018-07-13 14:32:12 -04:00
)
2017-11-14 22:13:30 -05:00
}
2018-05-18 17:48:10 -04:00
async onEnable ( ) {
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
this . on ( 'chat.overrides:changed' , id => this . updateLinesByUser ( id , null , false , false ) , this ) ;
2021-03-02 16:55:25 -05:00
this . on ( 'chat:update-lines-by-user' , this . updateLinesByUser , this ) ;
2020-07-24 17:55:11 -04:00
this . on ( 'chat:update-lines' , this . updateLines , this ) ;
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
this . on ( 'chat:rerender-lines' , this . rerenderLines , this ) ;
this . on ( 'chat:update-line-tokens' , this . updateLineTokens , this ) ;
this . on ( 'chat:update-line-badges' , this . updateLineBadges , this ) ;
this . on ( 'i18n:update' , this . rerenderLines , this ) ;
2023-03-03 15:24:20 -05:00
this . on ( 'chat.emotes:update-effects' , this . checkEffects , this ) ;
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
2023-03-10 17:06:12 -05:00
this . can _reprocess = true ;
2023-04-24 15:09:21 -04:00
this . on ( 'chat:reload-data' , ( ) => this . can _reprocess = true ) ;
2023-03-10 17:06:12 -05:00
this . on ( 'chat:room-add' , ( ) => this . can _reprocess = true ) ;
this . on ( 'load_tracker:complete:chat-data' , ( ) => {
const val = this . chat . context . get ( 'chat.update-when-loaded' ) ;
if ( ! val || ! this . can _reprocess )
return ;
this . can _reprocess = false ;
this . log . info ( 'Reprocessing chat lines due to data loads.' ) ;
this . updateLines ( ) ;
} ) ;
2023-04-19 17:19:10 -04:00
/ * t h i s . o n ( ' e x p e r i m e n t s : c h a n g e d : l i n e _ r e n d e r e r ' , ( ) = > {
2022-12-18 17:30:34 -05:00
const value = this . experiments . get ( 'line_renderer' ) ,
cls = this . ChatLine . _class ;
this . log . debug ( 'Changing line renderer:' , value ? 'new' : 'old' ) ;
if ( cls ) {
cls . prototype . render = this . experiments . get ( 'line_renderer' )
? cls . prototype . ffzNewRender
: cls . prototype . ffzOldRender ;
this . rerenderLines ( ) ;
}
2023-04-19 17:19:10 -04:00
} ) ; * /
2022-12-18 17:30:34 -05:00
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
for ( const setting of RERENDER _SETTINGS )
this . chat . context . on ( ` changed: ${ setting } ` , this . rerenderLines , this ) ;
for ( const setting of UPDATE _TOKEN _SETTINGS )
this . chat . context . on ( ` changed: ${ setting } ` , this . updateLineTokens , this ) ;
for ( const setting of UPDATE _BADGE _SETTINGS )
this . chat . context . on ( ` changed: ${ setting } ` , this . updateLineBadges , this ) ;
2018-12-13 15:21:57 -05:00
this . chat . context . on ( 'changed:tooltip.link-images' , this . maybeUpdateLines , this ) ;
this . chat . context . on ( 'changed:tooltip.link-nsfw-images' , this . maybeUpdateLines , this ) ;
2017-11-14 22:13:30 -05:00
2020-08-12 16:10:06 -04:00
this . on ( 'chat:get-tab-commands' , e => {
if ( this . experiments . getTwitchAssignmentByName ( 'chat_replies' ) === 'control' )
return ;
e . commands . push ( {
name : 'reply' ,
description : 'Reply to a user\'s last message.' ,
permissionLevel : 0 ,
ffz _group : 'FrankerFaceZ' ,
commandArgs : [
{ name : 'username' , isRequired : true } ,
{ name : 'message' , isRequired : false }
]
} )
} ) ;
this . on ( 'chat:pre-send-message' , e => {
if ( this . experiments . getTwitchAssignmentByName ( 'chat_replies' ) === 'control' )
return ;
const msg = e . message ,
types = this . parent . chat _types || { } ;
let user , message ;
if ( /^\/reply ?/i . test ( msg ) )
user = msg . slice ( 7 ) . trim ( ) ;
else
return ;
e . preventDefault ( ) ;
const idx = user . indexOf ( ' ' ) ;
if ( idx !== - 1 ) {
message = user . slice ( idx + 1 ) ;
user = user . slice ( 0 , idx ) ;
}
if ( user . startsWith ( '@' ) )
user = user . slice ( 1 ) ;
if ( user && user . length ) {
user = user . toLowerCase ( ) ;
const lines = Array . from ( this . ChatLine . instances ) ;
let i = lines . length ;
while ( i -- ) {
const line = lines [ i ] ,
msg = line ? . props ? . message ,
u = msg ? . user ;
if ( ! u )
continue ;
if ( u . login === user || u . displayName ? . toLowerCase ? . ( ) === user ) {
if ( message ) {
e . sendMessage ( message , {
reply : {
parentDeleted : msg . deleted || false ,
parentDisplayName : u . displayName ,
parentMessageBody : msg . message ,
parentMsgId : msg . id ,
parentUid : u . id ,
parentUserLogin : u . login
}
} ) ;
} else
requestAnimationFrame ( ( ) => line . ffzOpenReply ( ) ) ;
return ;
}
}
}
e . addMessage ( {
type : types . Notice ,
message : this . i18n . t ( 'chat.reply.bad-user' , 'Invalid user or no known message to reply to.' )
} ) ;
} ) ;
2017-11-14 22:13:30 -05:00
const t = this ,
2018-05-18 17:48:10 -04:00
React = await this . web _munch . findModule ( 'react' ) ;
2017-11-14 22:13:30 -05:00
if ( ! React )
return ;
2018-04-03 19:28:06 -04:00
const e = React . createElement ,
FFZRichContent = this . rich _content && this . rich _content . RichContent ;
2017-11-14 22:13:30 -05:00
2018-05-18 02:10:00 -04:00
2018-07-13 14:32:12 -04:00
this . WhisperLine . ready ( cls => {
const old _render = cls . prototype . render ;
cls . prototype . render = function ( ) {
2019-04-29 18:14:04 -04:00
this . _ffz _no _scan = true ;
2020-09-29 14:15:43 -04:00
if ( ! this . props . message || ! this . props . message . content || ! this . props . message . from )
2018-07-13 14:32:12 -04:00
return old _render . call ( this ) ;
2020-09-29 14:15:43 -04:00
try {
const msg = t . chat . standardizeWhisper ( this . props . message ) ,
is _action = msg . is _action ,
2021-04-28 16:27:58 -04:00
action _style = is _action ? t . chat . context . get ( 'chat.me-style' ) : 0 ,
action _italic = action _style >= 2 ,
action _color = action _style === 1 || action _style === 3 ,
2020-09-29 14:15:43 -04:00
user = msg . user ,
raw _color = t . overrides . getColor ( user . id ) || user . color ,
color = t . parent . colors . process ( raw _color ) ,
2021-04-30 17:38:49 -04:00
tokens = msg . ffz _tokens = msg . ffz _tokens || t . chat . tokenizeMessage ( msg , null ) ,
2020-09-29 14:15:43 -04:00
contents = t . chat . renderTokens ( tokens , e ) ,
override _name = t . overrides . getName ( user . id ) ;
return e ( 'div' , { className : 'thread-message__message' } ,
e ( 'div' , { className : 'tw-pd-x-1 tw-pd-y-05' } , [
e ( 'span' , {
className : ` thread-message__message--user-name notranslate ${ override _name ? ' ffz--name-override' : '' } ` ,
style : {
color
}
} , override _name || user . displayName ) ,
e ( 'span' , null , is _action ? ' ' : ': ' ) ,
e ( 'span' , {
2021-04-28 16:27:58 -04:00
className : ` message ${ action _italic ? ' chat-line__message-body--italicized' : '' } ` ,
2020-09-29 14:15:43 -04:00
style : {
2021-04-28 16:27:58 -04:00
color : action _color && color
2020-09-29 14:15:43 -04:00
}
} , contents )
] )
) ;
} catch ( err ) {
t . log . error ( err ) ;
t . log . capture ( err , {
extra : {
props : this . props
}
} ) ;
return old _render . call ( this ) ;
}
2018-07-13 14:32:12 -04:00
}
// Do this after a short delay to hopefully reduce the chance of React
// freaking out on us.
setTimeout ( ( ) => this . WhisperLine . forceUpdate ( ) ) ;
} ) ;
2018-03-14 13:58:04 -04:00
this . ChatLine . ready ( cls => {
2018-07-13 14:32:12 -04:00
const old _render = cls . prototype . render ;
2017-11-14 22:13:30 -05:00
cls . prototype . shouldComponentUpdate = function ( props , state ) {
2018-12-03 18:08:32 -05:00
const show = state && state . alwaysShowMessage || ! props . message . deleted ,
2017-11-14 22:13:30 -05:00
old _show = this . _ffz _show ;
// We can't just compare props.message.deleted to this.props.message.deleted
// because the message object is the same object. So, store the old show
// state for later reference.
this . _ffz _show = show ;
return show !== old _show ||
2019-04-12 17:34:01 -04:00
( state && this . state && ( state . ffz _expanded !== this . state . ffz _expanded ) ) ||
2017-11-14 22:13:30 -05:00
//state.renderDebug !== this.state.renderDebug ||
2019-04-18 03:16:19 -04:00
props . deletedMessageDisplay !== this . props . deletedMessageDisplay ||
props . deletedCount !== this . props . deletedCount ||
2017-11-14 22:13:30 -05:00
props . message !== this . props . message ||
props . isCurrentUserModerator !== this . props . isCurrentUserModerator ||
props . showModerationIcons !== this . props . showModerationIcons ||
2022-12-07 16:52:07 -05:00
props . showTimestamps !== this . props . showTimestamps ||
( props . pinnedMessage ? . message ? . id === props . message ? . id ) !== ( this . props . pinnedMessage ? . message ? . id === this . props . message ? . id ) ;
2017-11-14 22:13:30 -05:00
}
2020-08-12 16:10:06 -04:00
cls . prototype . ffzOpenReply = function ( ) {
2023-03-10 17:06:12 -05:00
if ( this . onMessageClick ) {
return this . onMessageClick ( ) ;
}
2020-08-13 14:00:47 -04:00
if ( this . props . reply ) {
this . setOPCardTray ( this . props . reply ) ;
return ;
}
2022-12-07 16:52:07 -05:00
if ( this . props . hasReply ) {
const msg = this . props . message ;
if ( msg ? . user ) {
this . setOPCardTray ( {
parentMsgId : msg . id ,
parentUid : msg . user . userID ,
parentUserLogin : msg . user . userLogin ,
parentDeleted : msg . deleted ,
parentDisplayName : msg . user . userDisplayName ,
parentMessageBody : msg . messageBody
} ) ;
return ;
}
}
2020-08-12 16:10:06 -04:00
const old _render _author = this . renderMessageAuthor ;
this . renderMessageAuthor = ( ) => this . ffzReplyAuthor ( ) ;
const tokens = this . props . message ? . ffz _tokens ;
if ( ! tokens )
return ;
this . setMessageTray ( this . props . message , t . chat . renderTokens ( tokens , e ) ) ;
this . renderMessageAuthor = old _render _author ;
}
cls . prototype . ffzReplyAuthor = function ( ) {
const msg = t . chat . standardizeMessage ( this . props . message ) ,
user = msg . user ,
raw _color = t . overrides . getColor ( user . id ) || user . color ,
color = t . parent . colors . process ( raw _color ) ;
let room = msg . roomLogin ? msg . roomLogin : msg . channel ? msg . channel . slice ( 1 ) : undefined ,
room _id = msg . roomId ? msg . roomId : this . props . channelID ;
if ( ! room && room _id ) {
const r = t . chat . getRoom ( room _id , null , true ) ;
if ( r && r . login )
room = msg . roomLogin = r . login ;
}
if ( ! room _id && room ) {
const r = t . chat . getRoom ( null , room _id , true ) ;
if ( r && r . id )
room _id = msg . roomId = r . id ;
}
2021-06-08 19:13:22 -04:00
const user _block = t . chat . formatUser ( user , e ) ;
2020-08-12 16:10:06 -04:00
const override _name = t . overrides . getName ( user . id ) ;
return e ( 'span' , {
'data-room-id' : room _id ,
'data-room' : room ,
'data-user-id' : user . userID ,
'data-user' : user . userLogin && user . userLogin . toLowerCase ( )
} , [
//t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e),
e ( 'span' , {
className : 'chat-line__message--badges'
} , t . chat . badges . render ( msg , e ) ) ,
e ( 'span' , {
2021-05-17 17:02:23 -04:00
className : ` chat-line__username notranslate ${ override _name ? ' ffz--name-override tw-relative ffz-il-tooltip__container' : '' } ` ,
2020-08-12 16:10:06 -04:00
role : 'button' ,
style : { color } ,
onClick : this . ffz _user _click _handler ,
onContextMenu : t . actions . handleUserContext
} , override _name ? [
e ( 'span' , {
className : 'chat-author__display-name'
} , override _name ) ,
e ( 'div' , {
2021-05-17 17:02:23 -04:00
className : 'ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-center'
2020-08-12 16:10:06 -04:00
} , user _block )
] : user _block )
] ) ;
}
2022-12-18 17:30:34 -05:00
cls . prototype . ffzNewRender = function ( ) { try {
this . _ffz _no _scan = true ;
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
const msg = t . chat . standardizeMessage ( this . props . message ) ,
override _mode = t . chat . context . get ( 'chat.filtering.display-deleted' ) ;
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
// Before anything else, check to see if the deleted message view is set
// to BRIEF and the message is deleted. In that case we can exit very
// early.
let mod _mode = this . props . deletedMessageDisplay ;
if ( override _mode )
mod _mode = override _mode ;
else if ( ! this . props . isCurrentUserModerator && mod _mode === 'DETAILED' )
mod _mode = 'LEGACY' ;
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
if ( mod _mode === 'BRIEF' && msg . deleted ) {
const deleted _count = this . props . deletedCount ;
if ( deleted _count == null )
return null ;
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
return e (
'div' , {
className : 'chat-line__status'
} ,
t . i18n . t ( 'chat.deleted-messages' , ` {count,plural,
one { One message was deleted by a moderator . }
other { # messages were deleted by a moderator . }
} ` , {
count : deleted _count
} )
) ;
}
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
// Get the current room id and login. We might need to look these up.
2022-02-11 15:17:32 -05:00
let room = msg . roomLogin ? msg . roomLogin : msg . channel ? msg . channel . slice ( 1 ) : undefined ,
room _id = msg . roomId ? msg . roomId : this . props . channelID ;
if ( ! room && room _id ) {
const r = t . chat . getRoom ( room _id , null , true ) ;
if ( r && r . login )
room = msg . roomLogin = r . login ;
}
if ( ! room _id && room ) {
const r = t . chat . getRoom ( null , room , true ) ;
if ( r && r . id )
room _id = msg . roomId = r . id ;
}
2022-12-18 17:30:34 -05:00
// Construct the current room and current user objects.
const current _user = t . site . getUser ( ) ,
current _room = { id : room _id , login : room } ;
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
const reply _mode = t . chat . context . get ( 'chat.replies.style' ) ,
has _replies = this . props && ! ! ( this . props . hasReply || this . props . reply || ! this . props . replyRestrictedReason ) ,
2022-02-11 15:17:32 -05:00
can _replies = has _replies && msg . message && ! msg . deleted && ! this . props . disableReplyClick ,
2022-12-18 17:30:34 -05:00
can _reply = can _replies && ( has _replies || ( current _user && current _user . login !== msg . user ? . login ) ) ;
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
if ( current _user ) {
current _user . moderator = this . props . isCurrentUserModerator ;
current _user . staff = this . props . isCurrentUserStaff ;
current _user . reply _mode = reply _mode ;
current _user . can _reply = can _reply ;
2022-02-11 15:17:32 -05:00
}
2022-12-18 17:30:34 -05:00
// Set up our click handlers as necessary.
2022-02-11 15:17:32 -05:00
if ( ! this . ffz _open _reply )
this . ffz _open _reply = this . ffzOpenReply . bind ( this ) ;
if ( ! this . ffz _user _click _handler ) {
if ( this . props . onUsernameClick )
this . ffz _user _click _handler = event => {
if ( this . isKeyboardEvent ( event ) && event . keyCode !== KEYS . Space && event . keyCode !== KEYS . Enter )
return ;
const target = event . currentTarget ,
ds = target && target . dataset ;
let target _user = msg . user ;
if ( ds && ds . user ) {
try {
target _user = JSON . parse ( ds . user ) ;
2022-12-18 17:30:34 -05:00
} catch ( err ) { /* nothing~! */ }
2022-02-11 15:17:32 -05:00
}
const fe = new FFZEvent ( {
inst : this ,
event ,
message : msg ,
user : target _user ,
2022-12-18 17:30:34 -05:00
room : current _room
2022-02-11 15:17:32 -05:00
} ) ;
t . emit ( 'chat:user-click' , fe ) ;
if ( fe . defaultPrevented )
return ;
this . props . onUsernameClick ( target _user . login , 'chat_message' , msg . id , target . getBoundingClientRect ( ) . bottom ) ;
}
else
this . ffz _user _click _handler = this . openViewerCard || this . usernameClickHandler ; //event => event.ctrlKey ? this.usernameClickHandler(event) : t.viewer_cards.openCard(r, user, event);
}
2022-12-18 17:30:34 -05:00
let notice ;
let klass ;
let bg _css = null ;
2022-02-11 15:17:32 -05:00
// Do we have a special renderer?
2022-12-18 17:30:34 -05:00
let type = msg . ffz _type && t . line _types [ msg . ffz _type ] ;
if ( ! type && msg . bits > 0 && t . chat . context . get ( 'chat.bits.cheer-notice' ) )
type = t . line _types . cheer ;
2023-01-19 17:00:09 -05:00
if ( ! type && msg . ffz _type )
type = t . line _types . unknown ;
2022-12-18 17:30:34 -05:00
if ( type ) {
if ( type . render )
return type . render ( msg , current _user , current _room , this , e ) ;
if ( type . renderNotice )
notice = type . renderNotice ( msg , current _user , current _room , this , e ) ;
if ( type . getClass )
klass = type . getClass ( msg , current _user , current _room , this , e ) ;
}
// Render the line.
const user = msg . user ,
anim _hover = t . chat . context . get ( 'chat.emotes.animated' ) === 2 ;
// Cache the lower login
if ( user && ! user . lowerLogin && user . userLogin )
user . lowerLogin = user . userLogin . toLowerCase ( ) ;
// Ensure we have a string for klass.
klass = klass || '' ;
// RENDERING: Start~
// First, check how we should handle a deleted message.
let show ;
let deleted ;
let mod _action = null ;
if ( mod _mode === 'BRIEF' ) {
// We already handle msg.deleted for BRIEF earlier than this.
show = true ;
deleted = false ;
} else if ( mod _mode === 'DETAILED' ) {
show = true ;
deleted = msg . deleted ;
} else {
show = this . state ? . alwaysShowMessage || ! msg . deleted ;
deleted = false ;
}
if ( msg . deleted ) {
const show _mode = t . chat . context . get ( 'chat.filtering.display-mod-action' ) ;
if ( show _mode === 2 || ( show _mode === 1 && mod _mode === 'DETAILED' ) ) {
const action = msg . modActionType ;
if ( action === 'timeout' )
mod _action = t . i18n . t ( 'chat.mod-action.timeout' ,
'{duration} Timeout'
, {
duration : print _duration ( msg . duration || 1 )
} ) ;
else if ( action === 'ban' )
mod _action = t . i18n . t ( 'chat.mod-action.ban' , 'Banned' ) ;
else if ( action === 'delete' || ! action )
mod _action = t . i18n . t ( 'chat.mod-action.delete' , 'Deleted' ) ;
if ( mod _action && msg . modLogin )
mod _action = t . i18n . t ( 'chat.mod-action.by' , '{action} by {login}' , {
login : msg . modLogin ,
action : mod _action
} ) ;
if ( mod _action )
mod _action = e ( 'span' , {
className : 'tw-pd-l-05' ,
'data-test-selector' : 'chat-deleted-message-attribution'
} , ` ( ${ mod _action } ) ` ) ;
}
}
// Check to see if we have message content to render.
const tokens = msg . ffz _tokens = msg . ffz _tokens || t . chat . tokenizeMessage ( msg , current _user ) ,
has _message = tokens . length > 0 || ! notice ;
let message ;
if ( has _message ) {
// Let's calculate some remaining values that we need.
const reply _tokens = ( reply _mode === 2 || ( reply _mode === 1 && this . props . repliesAppearancePreference && this . props . repliesAppearancePreference !== 'expanded' ) )
? ( msg . ffz _reply = msg . ffz _reply || t . chat . tokenizeReply ( this . props . reply ) )
: null ;
const is _action = t . parent . message _types && t . parent . message _types . Action === msg . messageType ,
action _style = is _action ? t . chat . context . get ( 'chat.me-style' ) : 0 ,
action _italic = action _style >= 2 ,
action _color = action _style === 1 || action _style === 3 ;
const raw _color = t . overrides . getColor ( user . id ) || user . color ,
color = t . parent . colors . process ( raw _color ) ;
const rich _content = show && FFZRichContent && t . chat . pluckRichContent ( tokens , msg ) ;
// First, render the user block.
const username = t . chat . formatUser ( user , e ) ,
override _name = t . overrides . getName ( user . id ) ;
const user _props = {
className : ` chat-line__username notranslate ${ override _name ? ' ffz--name-override tw-relative ffz-il-tooltip__container' : '' } ${ msg . ffz _user _class ? ? '' } ` ,
role : 'button' ,
style : { color } ,
onClick : this . ffz _user _click _handler ,
onContextMenu : t . actions . handleUserContext
} ;
if ( msg . ffz _user _props )
Object . assign ( user _props , msg . ffz _user _props ) ;
if ( msg . ffz _user _style )
Object . assign ( user _props . style , msg . ffz _user _style ) ;
const user _block = e (
'span' ,
user _props ,
override _name
? [
e ( 'span' , {
className : 'chat-author__display-name'
} , override _name ) ,
e ( 'div' , {
className : 'ffz-il-tooltip ffz-il-tooldip--down ffz-il-tooltip--align-center'
} , username )
]
: username
) ;
// The timestamp.
const timestamp = ( this . props . showTimestamps || this . props . isHistorical )
? e ( 'span' , { className : 'chat-line__timestamp' } , t . chat . formatTime ( msg . timestamp ) )
: null ;
// The reply token for FFZ style.
const reply _token = show && has _replies && reply _tokens
? t . chat . renderTokens ( reply _tokens , e )
: null ;
// Check for a Twitch-style points highlight.
const twitch _highlight = msg . ffz _reward _highlight && t . chat . context . get ( 'chat.points.allow-highlight' ) === 1 ;
// The reply element for Twitch style.
const twitch _reply = reply _mode === 1 && this . props . reply && this . props . repliesAppearancePreference && this . props . repliesAppearancePreference === 'expanded'
? this . renderReplyLine ( )
: null ;
// Now assemble the pieces.
message = [
twitch _reply ,
// The preamble
timestamp ,
t . actions . renderInline ( msg , this . props . showModerationIcons , current _user , current _room , e , this ) ,
this . renderInlineHighlight ? this . renderInlineHighlight ( ) : null ,
// Badges
e ( 'span' , {
className : 'chat-line__message--badges'
} , t . chat . badges . render ( msg , e ) ) ,
// User
user _block ,
// The separator
e ( 'span' , { 'aria-hidden' : true } , is _action ? ' ' : ': ' ) ,
// Reply Token
reply _token ,
// Message
show
? e (
'span' ,
{
className : ` message ${ action _italic ? 'chat-line__message-body--italicized' : '' } ${ twitch _highlight ? 'chat-line__message-body--highlighted' : '' } ` ,
style : action _color ? { color } : null
} ,
t . chat . renderTokens (
tokens , e , ( reply _mode !== 0 && has _replies ) ? this . props . reply : null
)
)
: e (
'span' ,
{
className : 'chat-line__message--deleted'
} ,
e ( 'a' , {
href : '' ,
onClick : this . alwaysShowMessage
} , t . i18n . t ( 'chat.message-deleted' , '<message deleted>' ) )
) ,
// Moderation Action
mod _action ,
// Rich Content
rich _content
? e ( FFZRichContent , rich _content )
: null
] ;
}
// Is there a notice?
let out ;
if ( notice ) {
const is _raw = Array . isArray ( notice . ffz _target ) ;
if ( ! message ) {
const want _ts = t . chat . context . get ( 'chat.extra-timestamps' ) ,
timestamp = want _ts && ( this . props . showTimestamps || this . props . isHistorical )
? e ( 'span' , { className : 'chat-line__timestamp' } , t . chat . formatTime ( msg . timestamp ) )
: null ;
const actions = t . actions . renderInline ( msg , this . props . showModerationIcons , current _user , current _room , e , this ) ;
if ( is _raw )
notice . ffz _target . unshift ( notice . ffz _icon ? ? null , timestamp , actions ) ;
else
notice = [
notice . ffz _icon ? ? null ,
timestamp ,
actions ,
notice
] ;
} else {
if ( notice . ffz _icon )
notice = [
notice . ffz _icon ,
notice
] ;
message = e (
'div' ,
{
className : 'chat-line--inline chat-line__message' ,
'data-room-id' : msg . roomId ? ? current _room . id ,
'data-room' : msg . roomLogin ,
'data-user-id' : user ? . userID ,
'data-user' : user ? . lowerLogin ,
} ,
message
) ;
}
klass = ` ${ klass } ffz-notice-line user-notice-line tw-pd-y-05 ` ;
2022-02-11 15:17:32 -05:00
2022-12-18 17:30:34 -05:00
if ( ! is _raw )
notice = e ( 'div' , {
className : 'tw-c-text-alt-2'
} , notice ) ;
if ( message )
out = [ notice , message ] ;
else
out = notice ;
} else {
klass = ` ${ klass } chat-line__message ` ;
out = message ;
}
// Check for hover actions, as those require we wrap the output in a few extra elements.
const hover _actions = ( user && msg . id )
? t . actions . renderHover ( msg , this . props . showModerationIcons , current _user , current _room , e , this )
: null ;
if ( hover _actions ) {
klass = ` ${ klass } tw-relative ` ;
out = [
e ( 'div' , {
className : 'chat-line__message-highlight tw-absolute tw-border-radius-medium tw-top-0 tw-bottom-0 tw-right-0 tw-left-0' ,
'data-test-selector' : 'chat-message-highlight'
} ) ,
e ( 'div' , {
className : 'chat-line__message-container tw-relative'
} , out ) ,
hover _actions
] ;
}
// If we don't have an override background color, try to assign
// a value based on the mention.
if ( bg _css == null )
bg _css = msg . mentioned && msg . mention _color
? t . parent . inverse _colors . process ( msg . mention _color )
: null ;
// Now, return the final chat line color.
return e ( 'div' , {
className : ` ${ klass } ${ deleted ? ' ffz--deleted-message' : '' } ${ msg . mentioned ? ' ffz-mentioned' : '' } ${ bg _css ? ' ffz-custom-color' : '' } ` ,
style : { backgroundColor : bg _css } ,
'data-room-id' : msg . roomId ? ? current _room . id ,
'data-room' : msg . roomLogin ,
'data-user-id' : user ? . userID ,
'data-user' : user ? . lowerLogin ,
onMouseOver : anim _hover ? t . chat . emotes . animHover : null ,
onMouseOut : anim _hover ? t . chat . emotes . animLeave : null
} , out ) ;
2022-02-11 15:17:32 -05:00
} catch ( err ) {
t . log . error ( err ) ;
t . log . capture ( err , {
extra : {
props : this . props
}
} ) ;
try {
console . log ( 'trying old 1' ) ;
return old _render . call ( this ) ;
} catch ( e2 ) {
t . log . error ( 'An error in Twitch shit.' , e2 ) ;
t . log . capture ( e2 , {
extra : {
props : this . props
}
} ) ;
return 'An error occurred rendering this chat line.' ;
}
2022-12-18 17:30:34 -05:00
} } ;
2022-02-11 15:17:32 -05:00
2023-04-19 17:19:10 -04:00
/ * c l s . p r o t o t y p e . f f z O l d R e n d e r = f u n c t i o n ( ) { t r y {
2019-04-29 18:14:04 -04:00
this . _ffz _no _scan = true ;
2018-07-13 14:32:12 -04:00
2018-04-28 17:56:03 -04:00
const types = t . parent . message _types || { } ,
2019-04-18 03:16:19 -04:00
deleted _count = this . props . deletedCount ,
2020-08-13 14:00:47 -04:00
reply _mode = t . chat . context . get ( 'chat.replies.style' ) ,
2021-03-20 18:47:12 -04:00
anim _hover = t . chat . context . get ( 'chat.emotes.animated' ) === 2 ,
2019-04-18 21:07:11 -04:00
override _mode = t . chat . context . get ( 'chat.filtering.display-deleted' ) ,
2017-11-17 14:59:46 -05:00
2018-05-18 02:10:00 -04:00
msg = t . chat . standardizeMessage ( this . props . message ) ,
2020-12-01 18:19:17 -05:00
reply _tokens = ( reply _mode === 2 || ( reply _mode === 1 && this . props . repliesAppearancePreference && this . props . repliesAppearancePreference !== 'expanded' ) ) ? ( msg . ffz _reply = msg . ffz _reply || t . chat . tokenizeReply ( this . props . reply ) ) : null ,
2019-01-31 19:58:21 -05:00
is _action = msg . messageType === types . Action ,
2021-04-28 16:27:58 -04:00
action _style = is _action ? t . chat . context . get ( 'chat.me-style' ) : 0 ,
action _italic = action _style >= 2 ,
action _color = action _style === 1 || action _style === 3 ,
2018-02-22 18:23:44 -05:00
2019-01-31 19:58:21 -05:00
user = msg . user ,
2020-01-11 17:13:56 -05:00
raw _color = t . overrides . getColor ( user . id ) || user . color ,
color = t . parent . colors . process ( raw _color ) ;
2019-04-18 03:16:19 -04:00
2019-04-18 21:07:11 -04:00
let mod _mode = this . props . deletedMessageDisplay ;
let show , show _class , mod _action = null ;
2021-01-27 17:36:01 -05:00
const highlight _mode = t . chat . context . get ( 'chat.points.allow-highlight' ) ,
highlight = highlight _mode > 0 && msg . ffz _type === 'points' && msg . ffz _reward && isHighlightedReward ( msg . ffz _reward ) ,
twitch _highlight = highlight && highlight _mode == 1 ,
ffz _highlight = highlight && highlight _mode == 2 ;
2019-04-18 21:07:11 -04:00
if ( ! this . props . isCurrentUserModerator && mod _mode == 'DETAILED' )
mod _mode = 'LEGACY' ;
if ( override _mode )
mod _mode = override _mode ;
2019-04-18 03:16:19 -04:00
if ( mod _mode === 'BRIEF' ) {
if ( msg . deleted ) {
if ( deleted _count == null )
return null ;
return e ( 'div' , {
className : 'chat-line__status'
2019-05-03 19:30:46 -04:00
} , t . i18n . t ( 'chat.deleted-messages' , ` {count,plural,
one { One message was deleted by a moderator . }
other { # messages were deleted by a moderator . }
} ` , {
2019-04-18 03:16:19 -04:00
count : deleted _count
} ) ) ;
}
2018-05-22 17:23:20 -04:00
2019-04-18 03:16:19 -04:00
show = true ;
show _class = false ;
2018-05-22 17:23:20 -04:00
2019-04-18 03:16:19 -04:00
} else if ( mod _mode === 'DETAILED' ) {
2018-05-22 17:23:20 -04:00
show = true ;
show _class = msg . deleted ;
2019-04-18 03:16:19 -04:00
2019-04-18 21:07:11 -04:00
} else {
show = this . state && this . state . alwaysShowMessage || ! msg . deleted ;
show _class = false ;
}
if ( msg . deleted ) {
const show _mode = t . chat . context . get ( 'chat.filtering.display-mod-action' ) ;
if ( show _mode === 2 || ( show _mode === 1 && mod _mode === 'DETAILED' ) ) {
2019-04-18 03:16:19 -04:00
const action = msg . modActionType ;
if ( action === 'timeout' )
mod _action = t . i18n . t ( 'chat.mod-action.timeout' ,
2019-05-03 19:30:46 -04:00
'{duration} Timeout'
2019-04-18 03:16:19 -04:00
, {
duration : print _duration ( msg . duration || 1 )
} ) ;
else if ( action === 'ban' )
mod _action = t . i18n . t ( 'chat.mod-action.ban' , 'Banned' ) ;
else if ( action === 'delete' || ! action )
mod _action = t . i18n . t ( 'chat.mod-action.delete' , 'Deleted' ) ;
if ( mod _action && msg . modLogin )
2019-05-03 19:30:46 -04:00
mod _action = t . i18n . t ( 'chat.mod-action.by' , '{action} by {login}' , {
2019-04-18 03:16:19 -04:00
login : msg . modLogin ,
action : mod _action
} ) ;
if ( mod _action )
mod _action = e ( 'span' , {
className : 'tw-pd-l-05' ,
'data-test-selector' : 'chat-deleted-message-attribution'
} , ` ( ${ mod _action } ) ` ) ;
}
2018-05-22 17:23:20 -04:00
}
2017-11-14 22:13:30 -05:00
2019-11-11 14:38:49 -05:00
let room = msg . roomLogin ? msg . roomLogin : msg . channel ? msg . channel . slice ( 1 ) : undefined ,
room _id = msg . roomId ? msg . roomId : this . props . channelID ;
2018-04-29 01:28:19 -04:00
2019-11-11 14:38:49 -05:00
if ( ! room && room _id ) {
const r = t . chat . getRoom ( room _id , null , true ) ;
2018-04-29 01:28:19 -04:00
if ( r && r . login )
room = msg . roomLogin = r . login ;
}
2019-11-11 14:38:49 -05:00
if ( ! room _id && room ) {
2021-04-30 17:38:49 -04:00
const r = t . chat . getRoom ( null , room , true ) ;
2019-11-11 14:38:49 -05:00
if ( r && r . id )
room _id = msg . roomId = r . id ;
}
2018-04-28 17:56:03 -04:00
const u = t . site . getUser ( ) ,
2019-11-11 14:38:49 -05:00
r = { id : room _id , login : room } ;
2018-04-28 17:56:03 -04:00
2021-11-10 18:27:52 -05:00
const has _replies = this . props && ! ! ( this . props . hasReply || this . props . reply || ! this . props . replyRestrictedReason ) ,
2020-08-13 14:00:47 -04:00
can _replies = has _replies && msg . message && ! msg . deleted && ! this . props . disableReplyClick ,
2022-12-07 16:52:07 -05:00
can _reply = can _replies && ( has _replies || ( u && u . login !== msg . user ? . login ) ) ;
2020-08-13 14:00:47 -04:00
2018-04-28 17:56:03 -04:00
if ( u ) {
u . moderator = this . props . isCurrentUserModerator ;
u . staff = this . props . isCurrentUserStaff ;
2022-12-07 16:52:07 -05:00
u . reply _mode = reply _mode ;
u . can _reply = can _reply ;
2018-04-28 17:56:03 -04:00
}
2022-12-07 16:52:07 -05:00
const hover _actions = t . actions . renderHover ( msg , this . props . showModerationIcons , u , r , e , this ) ,
twitch _clickable = hover _actions != null ;
2021-04-30 17:38:49 -04:00
const tokens = msg . ffz _tokens = msg . ffz _tokens || t . chat . tokenizeMessage ( msg , u ) ,
2018-05-31 18:34:15 -04:00
rich _content = FFZRichContent && t . chat . pluckRichContent ( tokens , msg ) ,
bg _css = msg . mentioned && msg . mention _color ? t . parent . inverse _colors . process ( msg . mention _color ) : null ;
2017-11-14 22:13:30 -05:00
2020-08-13 14:00:47 -04:00
if ( ! this . ffz _open _reply )
this . ffz _open _reply = this . ffzOpenReply . bind ( this ) ;
2019-02-05 14:24:45 -05:00
if ( ! this . ffz _user _click _handler ) {
if ( this . props . onUsernameClick )
this . ffz _user _click _handler = event => {
if ( this . isKeyboardEvent ( event ) && event . keyCode !== KEYS . Space && event . keyCode !== KEYS . Enter )
return ;
const target = event . currentTarget ,
ds = target && target . dataset ;
let target _user = user ;
if ( ds && ds . user ) {
try {
target _user = JSON . parse ( ds . user ) ;
2023-04-19 17:19:10 -04:00
} catch ( err ) { /* nothing~! * / }
2019-02-05 14:24:45 -05:00
}
2019-12-06 15:56:58 -05:00
const fe = new FFZEvent ( {
inst : this ,
event ,
message : msg ,
user : target _user ,
room : r
} ) ;
t . emit ( 'chat:user-click' , fe ) ;
if ( fe . defaultPrevented )
return ;
this . props . onUsernameClick ( target _user . login , 'chat_message' , msg . id , target . getBoundingClientRect ( ) . bottom ) ;
2019-02-05 14:24:45 -05:00
}
else
this . ffz _user _click _handler = this . openViewerCard || this . usernameClickHandler ; //event => event.ctrlKey ? this.usernameClickHandler(event) : t.viewer_cards.openCard(r, user, event);
}
2018-05-10 19:56:39 -04:00
2020-01-11 17:13:56 -05:00
2021-06-08 19:13:22 -04:00
const user _block = t . chat . formatUser ( user , e ) ;
2020-01-11 17:13:56 -05:00
const override _name = t . overrides . getName ( user . id ) ;
2021-12-28 13:46:26 -05:00
const user _props = {
className : ` chat-line__username notranslate ${ override _name ? ' ffz--name-override tw-relative ffz-il-tooltip__container' : '' } ${ msg . ffz _user _class ? ? '' } ` ,
role : 'button' ,
style : { color } ,
onClick : this . ffz _user _click _handler ,
onContextMenu : t . actions . handleUserContext
} ;
if ( msg . ffz _user _props )
Object . assign ( user _props , msg . ffz _user _props ) ;
if ( msg . ffz _user _style )
Object . assign ( user _props . style , msg . ffz _user _style ) ;
2020-08-13 14:00:47 -04:00
const user _bits = [
2022-10-14 17:23:45 -04:00
t . actions . renderInline ( msg , this . props . showModerationIcons , u , r , e , this ) ,
2022-05-05 13:41:49 -04:00
this . renderInlineHighlight ? this . renderInlineHighlight ( ) : null ,
2020-08-13 14:00:47 -04:00
e ( 'span' , {
className : 'chat-line__message--badges'
} , t . chat . badges . render ( msg , e ) ) ,
2021-12-28 13:46:26 -05:00
e ( 'span' , user _props , override _name ? [
2020-08-13 14:00:47 -04:00
e ( 'span' , {
className : 'chat-author__display-name'
} , override _name ) ,
e ( 'div' , {
2021-05-17 17:02:23 -04:00
className : 'ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-center'
2020-08-13 14:00:47 -04:00
} , user _block )
] : user _block )
] ;
2021-04-01 12:05:29 -04:00
let extra _ts ,
cls = ` chat-line__message ${ show _class ? ' ffz--deleted-message' : '' } ${ twitch _clickable ? ' tw-relative' : '' } ` ,
2018-03-01 04:13:52 -05:00
out = ( tokens . length || ! msg . ffz _type ) ? [
2020-11-23 18:12:07 -05:00
( this . props . showTimestamps || this . props . isHistorical ) && e ( 'span' , {
2017-11-23 02:49:23 -05:00
className : 'chat-line__timestamp'
} , t . chat . formatTime ( msg . timestamp ) ) ,
2020-08-15 15:45:50 -04:00
user _bits ,
2020-08-13 14:00:47 -04:00
e ( 'span' , { 'aria-hidden' : true } , is _action ? ' ' : ': ' ) ,
2020-08-12 16:10:06 -04:00
show && has _replies && reply _tokens ?
t . chat . renderTokens ( reply _tokens , e )
: null ,
2017-11-23 02:49:23 -05:00
show ?
e ( 'span' , {
2021-04-28 16:27:58 -04:00
className : ` message ${ action _italic ? 'chat-line__message-body--italicized' : '' } ${ twitch _highlight ? 'chat-line__message-body--highlighted' : '' } ` ,
style : action _color ? { color } : null
2020-08-13 14:00:47 -04:00
} , t . chat . renderTokens ( tokens , e , ( reply _mode !== 0 && has _replies ) ? this . props . reply : null ) )
2017-11-23 02:49:23 -05:00
:
e ( 'span' , {
className : 'chat-line__message--deleted' ,
} , e ( 'a' , {
href : '' ,
onClick : this . alwaysShowMessage
2019-01-31 19:58:21 -05:00
} , t . i18n . t ( 'chat.message-deleted' , '<message deleted>' ) ) ) ,
2017-11-23 02:49:23 -05:00
2018-04-03 19:28:06 -04:00
show && rich _content && e ( FFZRichContent , rich _content ) ,
2019-04-18 21:07:11 -04:00
mod _action ,
2017-11-23 02:49:23 -05:00
] : null ;
2021-04-01 12:05:29 -04:00
if ( out == null )
extra _ts = t . chat . context . get ( 'chat.extra-timestamps' ) ;
2019-02-05 14:24:45 -05:00
if ( msg . ffz _type === 'sub_mystery' ) {
const mystery = msg . mystery ;
if ( mystery )
msg . mystery . line = this ;
2021-11-12 16:58:35 -05:00
const sub _msg = t . i18n . tList ( 'chat.sub.gift' , "{user} is gifting {count, plural, one {# Tier {tier} Sub} other {# Tier {tier} Subs}} to {channel}'s community! " , {
2019-02-05 14:24:45 -05:00
user : ( msg . sub _anon || user . username === 'ananonymousgifter' ) ?
t . i18n . t ( 'chat.sub.anonymous-gifter' , 'An anonymous gifter' ) :
e ( 'span' , {
role : 'button' ,
className : 'chatter-name' ,
onClick : this . ffz _user _click _handler
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
2020-07-22 21:31:41 -04:00
} , user . displayName ) ) ,
2019-02-05 14:24:45 -05:00
count : msg . sub _count ,
tier : SUB _TIERS [ msg . sub _plan ] || 1 ,
channel : msg . roomLogin
} ) ;
if ( msg . sub _total === 1 )
sub _msg . push ( t . i18n . t ( 'chat.sub.gift-first' , "It's their first time gifting a Sub in the channel!" ) ) ;
else if ( msg . sub _total > 1 )
2019-05-03 19:30:46 -04:00
sub _msg . push ( t . i18n . t ( 'chat.sub.gift-total' , "They've gifted {count} Subs in the channel!" , {
2019-02-05 14:24:45 -05:00
count : msg . sub _total
} ) ) ;
if ( ! this . ffz _click _expand )
this . ffz _click _expand = ( ) => {
this . setState ( {
ffz _expanded : ! this . state . ffz _expanded
} ) ;
}
2019-03-14 21:43:44 -04:00
const expanded = t . chat . context . get ( 'chat.subs.merge-gifts-visibility' ) ?
! this . state . ffz _expanded : this . state . ffz _expanded ;
2019-02-05 14:24:45 -05:00
let sub _list = null ;
2019-03-14 21:43:44 -04:00
if ( expanded && mystery && mystery . recipients && mystery . recipients . length > 0 ) {
2019-02-05 14:24:45 -05:00
const the _list = [ ] ;
for ( const x of mystery . recipients ) {
if ( the _list . length )
the _list . push ( ', ' ) ;
the _list . push ( e ( 'span' , {
role : 'button' ,
2019-02-12 17:39:54 -05:00
className : 'ffz--giftee-name' ,
2019-02-05 14:24:45 -05:00
onClick : this . ffz _user _click _handler ,
'data-user' : JSON . stringify ( x )
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
} , x . displayName ) ) ) ;
}
sub _list = e ( 'div' , {
className : 'tw-mg-t-05 tw-border-t tw-pd-t-05 tw-c-text-alt-2'
} , the _list ) ;
}
2020-08-15 15:45:50 -04:00
cls = ` ffz-notice-line user-notice-line tw-pd-y-05 ffz--subscribe-line ${ show _class ? ' ffz--deleted-message' : '' } ${ twitch _clickable ? ' tw-relative' : '' } ` ;
2019-02-05 14:24:45 -05:00
out = [
e ( 'div' , {
className : 'tw-flex tw-c-text-alt-2' ,
onClick : this . ffz _click _expand
} , [
t . chat . context . get ( 'chat.subs.compact' ) ? null :
e ( 'figure' , {
className : ` ffz-i-star ${ msg . sub _anon ? '-empty' : '' } tw-mg-r-05 `
} ) ,
e ( 'div' , null , [
2021-04-01 12:05:29 -04:00
out ? null : extra _ts && ( this . props . showTimestamps || this . props . isHistorical ) && e ( 'span' , {
className : 'chat-line__timestamp'
} , t . chat . formatTime ( msg . timestamp ) ) ,
2022-10-14 17:23:45 -04:00
( out || msg . sub _anon ) ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , u , r , e , this ) ,
2019-02-05 14:24:45 -05:00
sub _msg
] ) ,
mystery ? e ( 'div' , {
2019-02-12 17:39:54 -05:00
className : 'tw-pd-l-05 tw-font-size-4'
2019-02-05 14:24:45 -05:00
} , e ( 'figure' , {
2019-03-14 21:43:44 -04:00
className : ` ffz-i- ${ expanded ? 'down' : 'right' } -dir tw-pd-y-1 `
2019-02-05 14:24:45 -05:00
} ) ) : null
] ) ,
sub _list ,
out && e ( 'div' , {
className : 'chat-line--inline chat-line__message' ,
2019-11-11 14:38:49 -05:00
'data-room-id' : room _id ,
2019-02-05 14:24:45 -05:00
'data-room' : room ,
'data-user-id' : user . userID ,
'data-user' : user . userLogin && user . userLogin . toLowerCase ( ) ,
} , out )
] ;
} else if ( msg . ffz _type === 'sub_gift' ) {
2017-11-23 02:49:23 -05:00
const plan = msg . sub _plan || { } ,
2020-07-02 14:54:46 -04:00
months = msg . sub _months || 1 ,
2018-03-11 14:04:55 -04:00
tier = SUB _TIERS [ plan . plan ] || 1 ;
2017-11-23 02:49:23 -05:00
2020-07-02 14:54:46 -04:00
let sub _msg ;
const bits = {
months ,
2019-02-05 14:24:45 -05:00
user : ( msg . sub _anon || user . username === 'ananonymousgifter' ) ?
t . i18n . t ( 'chat.sub.anonymous-gifter' , 'An anonymous gifter' ) :
e ( 'span' , {
role : 'button' ,
className : 'chatter-name' ,
onClick : this . ffz _user _click _handler
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
2020-07-22 21:31:41 -04:00
} , user . displayName ) ) ,
2019-02-05 14:24:45 -05:00
plan : plan . plan === 'custom' ? '' :
2019-05-03 19:30:46 -04:00
t . i18n . t ( 'chat.sub.gift-plan' , 'Tier {tier}' , { tier } ) ,
2019-02-05 14:24:45 -05:00
recipient : e ( 'span' , {
role : 'button' ,
2019-01-31 19:58:21 -05:00
className : 'chatter-name' ,
2019-02-05 14:24:45 -05:00
onClick : this . ffz _user _click _handler ,
'data-user' : JSON . stringify ( msg . sub _recipient )
2019-01-31 19:58:21 -05:00
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
2019-02-05 14:24:45 -05:00
} , msg . sub _recipient . displayName ) )
2020-07-02 14:54:46 -04:00
} ;
if ( months <= 1 )
sub _msg = t . i18n . tList ( 'chat.sub.mystery' , '{user} gifted a {plan} Sub to {recipient}! ' , bits ) ;
else
2021-11-12 16:58:35 -05:00
sub _msg = t . i18n . tList ( 'chat.sub.gift-months' , '{user} gifted {months, plural, one {# month} other {# months}} of {plan} Sub to {recipient}!' , bits ) ;
2019-01-31 19:58:21 -05:00
2019-02-05 14:24:45 -05:00
if ( msg . sub _total === 1 )
sub _msg . push ( t . i18n . t ( 'chat.sub.gift-first' , "It's their first time gifting a Sub in the channel!" ) ) ;
else if ( msg . sub _total > 1 )
2019-05-03 19:30:46 -04:00
sub _msg . push ( t . i18n . t ( 'chat.sub.gift-total' , "They've gifted {count,number} Subs in the channel!" , {
2019-02-05 14:24:45 -05:00
count : msg . sub _total
} ) ) ;
2019-01-31 19:58:21 -05:00
2020-08-15 15:45:50 -04:00
cls = ` ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--subscribe-line ${ show _class ? ' ffz--deleted-message' : '' } ${ twitch _clickable ? ' tw-relative' : '' } ` ;
2017-11-23 02:49:23 -05:00
out = [
2019-02-05 14:24:45 -05:00
e ( 'div' , { className : 'tw-flex tw-c-text-alt-2' } , [
t . chat . context . get ( 'chat.subs.compact' ) ? null :
e ( 'figure' , {
className : 'ffz-i-star tw-mg-r-05'
} ) ,
e ( 'div' , null , [
2021-04-01 12:05:29 -04:00
out ? null : extra _ts && ( this . props . showTimestamps || this . props . isHistorical ) && e ( 'span' , {
className : 'chat-line__timestamp'
} , t . chat . formatTime ( msg . timestamp ) ) ,
2022-10-14 17:23:45 -04:00
( out || msg . sub _anon ) ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , u , r , e , this ) ,
2019-02-05 14:24:45 -05:00
sub _msg
] )
] ) ,
2017-11-23 02:49:23 -05:00
out && e ( 'div' , {
2018-04-10 21:13:34 -04:00
className : 'chat-line--inline chat-line__message' ,
2019-11-11 14:38:49 -05:00
'data-room-id' : room _id ,
2017-11-23 02:49:23 -05:00
'data-room' : room ,
'data-user-id' : user . userID ,
'data-user' : user . userLogin && user . userLogin . toLowerCase ( ) ,
} , out )
] ;
2019-02-05 14:24:45 -05:00
} else if ( msg . ffz _type === 'resub' ) {
const months = msg . sub _cumulative || msg . sub _months ,
setting = t . chat . context . get ( 'chat.subs.show' ) ;
if ( setting === 3 || ( setting === 1 && out && months > 1 ) || ( setting === 2 && months > 1 ) ) {
const plan = msg . sub _plan || { } ,
tier = SUB _TIERS [ plan . plan ] || 1 ;
2019-05-03 19:30:46 -04:00
const sub _msg = t . i18n . tList ( 'chat.sub.main' , '{user} subscribed {plan}. ' , {
2019-02-05 14:24:45 -05:00
user : e ( 'span' , {
role : 'button' ,
className : 'chatter-name' ,
onClick : this . ffz _user _click _handler
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
2020-07-22 21:31:41 -04:00
} , user . displayName ) ) ,
2019-02-05 14:24:45 -05:00
plan : plan . prime ?
2020-11-15 17:33:55 -05:00
t . i18n . t ( 'chat.sub.twitch-prime' , 'with Prime Gaming' ) :
2019-05-03 19:30:46 -04:00
t . i18n . t ( 'chat.sub.plan' , 'at Tier {tier}' , { tier } )
2019-02-05 14:24:45 -05:00
} ) ;
if ( msg . sub _share _streak && msg . sub _streak > 1 ) {
sub _msg . push ( t . i18n . t (
'chat.sub.cumulative-months' ,
2019-05-03 19:30:46 -04:00
"They've subscribed for {cumulative,number} months, currently on a {streak,number} month streak!" ,
2019-02-05 14:24:45 -05:00
{
cumulative : msg . sub _cumulative ,
streak : msg . sub _streak
}
) ) ;
} else if ( months > 1 ) {
sub _msg . push ( t . i18n . t (
'chat.sub.months' ,
2019-05-03 19:30:46 -04:00
"They've subscribed for {count,number} months!" ,
2019-02-05 14:24:45 -05:00
{
count : months
}
) ) ;
}
2020-08-15 15:45:50 -04:00
cls = ` ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--subscribe-line ${ show _class ? ' ffz--deleted-message' : '' } ${ twitch _clickable ? ' tw-relative' : '' } ` ;
2019-02-05 14:24:45 -05:00
out = [
e ( 'div' , { className : 'tw-flex tw-c-text-alt-2' } , [
t . chat . context . get ( 'chat.subs.compact' ) ? null :
e ( 'figure' , {
className : ` ffz-i- ${ plan . prime ? 'crown' : 'star' } tw-mg-r-05 `
} ) ,
e ( 'div' , null , [
2021-04-01 12:05:29 -04:00
out ? null : extra _ts && ( this . props . showTimestamps || this . props . isHistorical ) && e ( 'span' , {
className : 'chat-line__timestamp'
} , t . chat . formatTime ( msg . timestamp ) ) ,
2022-10-14 17:23:45 -04:00
out ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , u , r , e , this ) ,
2019-02-05 14:24:45 -05:00
sub _msg
] )
] ) ,
out && e ( 'div' , {
className : 'chat-line--inline chat-line__message' ,
2019-11-11 14:38:49 -05:00
'data-room-id' : room _id ,
2019-02-05 14:24:45 -05:00
'data-room' : room ,
'data-user-id' : user . userID ,
'data-user' : user . userLogin && user . userLogin . toLowerCase ( ) ,
} , out )
] ;
}
2018-02-02 16:00:58 -05:00
} else if ( msg . ffz _type === 'ritual' && t . chat . context . get ( 'chat.rituals.show' ) ) {
let system _msg ;
if ( msg . ritual === 'new_chatter' )
2018-04-10 21:13:34 -04:00
system _msg = e ( 'div' , { className : 'tw-c-text-alt-2' } , [
2019-05-03 19:30:46 -04:00
t . i18n . tList ( 'chat.ritual' , '{user} is new here. Say hello!' , {
2019-02-05 14:24:45 -05:00
user : e ( 'span' , {
role : 'button' ,
2018-07-09 21:35:31 -04:00
className : 'chatter-name' ,
onClick : this . ffz _user _click _handler
} , e ( 'span' , {
2018-09-25 18:37:14 -04:00
className : 'tw-c-text-base tw-strong'
2020-07-22 21:31:41 -04:00
} , user . displayName ) )
2018-04-10 21:13:34 -04:00
} )
] ) ;
2018-02-02 16:00:58 -05:00
if ( system _msg ) {
2020-08-15 15:45:50 -04:00
cls = ` ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--ritual-line ${ show _class ? ' ffz--deleted-message' : '' } ${ twitch _clickable ? ' tw-relative' : '' } ` ;
2018-02-02 16:00:58 -05:00
out = [
2021-04-01 12:05:29 -04:00
out ? null : extra _ts && ( this . props . showTimestamps || this . props . isHistorical ) && e ( 'span' , {
className : 'chat-line__timestamp'
} , t . chat . formatTime ( msg . timestamp ) ) ,
2018-02-02 16:00:58 -05:00
system _msg ,
out && e ( 'div' , {
2018-04-10 21:13:34 -04:00
className : 'chat-line--inline chat-line__message' ,
2019-11-11 14:38:49 -05:00
'data-room-id' : room _id ,
2018-02-02 16:00:58 -05:00
'data-room' : room ,
'data-user-id' : user . userID ,
'data-user' : user . userLogin && user . userLogin . toLowerCase ( ) ,
} , out )
] ;
}
2019-11-11 14:38:49 -05:00
} else if ( msg . ffz _type === 'points' && msg . ffz _reward ) {
const reward = e ( 'span' , { className : 'ffz--points-reward' } , getRewardTitle ( msg . ffz _reward , t . i18n ) ) ,
cost = e ( 'span' , { className : 'ffz--points-cost' } , [
e ( 'span' , { className : 'ffz--points-icon' } ) ,
t . i18n . formatNumber ( getRewardCost ( msg . ffz _reward ) )
] ) ;
2021-01-27 17:36:01 -05:00
cls = ` ffz-notice-line ffz--points-line tw-pd-l-1 tw-pd-y-05 tw-pd-r-2 ${ ffz _highlight ? ' ffz-custom-color ffz--points-highlight' : '' } ${ show _class ? ' ffz--deleted-message' : '' } ${ twitch _clickable ? ' tw-relative' : '' } ` ;
2019-11-11 14:38:49 -05:00
out = [
e ( 'div' , { className : 'tw-c-text-alt-2' } , [
2021-04-01 12:05:29 -04:00
out ? null : extra _ts && ( this . props . showTimestamps || this . props . isHistorical ) && e ( 'span' , {
className : 'chat-line__timestamp'
} , t . chat . formatTime ( msg . timestamp ) ) ,
2022-10-14 17:23:45 -04:00
out ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , u , r , e , this ) ,
2019-11-11 14:38:49 -05:00
out ?
t . i18n . tList ( 'chat.points.redeemed' , 'Redeemed {reward} {cost}' , { reward , cost } ) :
t . i18n . tList ( 'chat.points.user-redeemed' , '{user} redeemed {reward} {cost}' , {
reward , cost ,
user : e ( 'span' , {
role : 'button' ,
className : 'chatter-name' ,
onClick : this . ffz _user _click _handler
} , e ( 'span' , {
className : 'tw-c-text-base tw-strong'
2020-07-22 21:31:41 -04:00
} , user . displayName ) )
2019-11-11 14:38:49 -05:00
} )
] ) ,
out && e ( 'div' , {
className : 'chat-line--inline chat-line__message' ,
'data-room-id' : room _id ,
'data-room' : room ,
'data-user-id' : user . userID ,
'data-user' : user . userLogin && user . userLogin . toLowerCase ( )
} , out )
]
2021-07-12 13:46:04 -04:00
} else if ( msg . bits > 0 && t . chat . context . get ( 'chat.bits.cheer-notice' ) ) {
cls = ` ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--ritual-line ${ show _class ? ' ffz--deleted-message' : '' } ${ twitch _clickable ? ' tw-relative' : '' } ` ;
out = [
e ( 'div' , { className : 'tw-c-text-alt-2' } , [
out ? null : extra _ts && ( this . props . showTimestamps || this . props . isHistorical ) && e ( 'span' , {
className : 'chat-line__timestamp'
} , t . chat . formatTime ( msg . timestamp ) ) ,
2022-10-14 17:23:45 -04:00
out ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , u , r , e , this ) ,
2021-07-12 13:46:04 -04:00
t . i18n . tList ( 'chat.bits-message' , 'Cheered {count, plural, one {# Bit} other {# Bits}}' , { count : msg . bits || 0 } )
] ) ,
out && e ( 'div' , {
className : 'chat-line--inline chat-line__message' ,
'data-room-id' : room _id ,
'data-room' : room ,
'data-user-id' : user . userID ,
'data-user' : user . userLogin && user . userLogin . toLowerCase ( ) ,
} , out )
] ;
2018-02-02 16:00:58 -05:00
}
if ( ! out )
2017-11-23 02:49:23 -05:00
return null ;
2020-08-15 15:45:50 -04:00
if ( twitch _clickable ) {
out = [
e ( 'div' , {
className : 'chat-line__message-highlight tw-absolute tw-border-radius-medium tw-top-0 tw-bottom-0 tw-right-0 tw-left-0' ,
'data-test-selector' : 'chat-message-highlight'
} ) ,
e ( 'div' , {
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
className : 'chat-line__message-container tw-relative'
2022-12-07 16:52:07 -05:00
} , reply _mode == 1 ? [
2020-12-01 18:19:17 -05:00
this . props . repliesAppearancePreference && this . props . repliesAppearancePreference === 'expanded' ? this . renderReplyLine ( ) : null ,
2020-08-15 15:45:50 -04:00
out
2022-12-07 16:52:07 -05:00
] : out ) ,
hover _actions
2020-08-15 15:45:50 -04:00
] ;
}
2017-11-23 02:49:23 -05:00
return e ( 'div' , {
2018-05-31 18:34:15 -04:00
className : ` ${ cls } ${ msg . mentioned ? ' ffz-mentioned' : '' } ${ bg _css ? ' ffz-custom-color' : '' } ` ,
2018-04-28 17:56:03 -04:00
style : { backgroundColor : bg _css } ,
2019-11-11 14:38:49 -05:00
'data-room-id' : room _id ,
2017-11-14 22:13:30 -05:00
'data-room' : room ,
'data-user-id' : user . userID ,
'data-user' : user . userLogin && user . userLogin . toLowerCase ( ) ,
2021-03-20 18:47:12 -04:00
onMouseOver : anim _hover ? t . chat . emotes . animHover : null ,
onMouseOut : anim _hover ? t . chat . emotes . animLeave : null
2017-11-23 02:49:23 -05:00
} , out ) ;
2018-07-13 14:32:12 -04:00
2019-01-31 19:58:21 -05:00
} catch ( err ) {
2019-04-28 17:28:16 -04:00
t . log . info ( err ) ;
2019-01-31 19:58:21 -05:00
t . log . capture ( err , {
extra : {
props : this . props
}
} ) ;
2018-07-13 14:32:12 -04:00
2022-02-11 15:17:32 -05:00
try {
return old _render . call ( this ) ;
} catch ( e2 ) {
t . log . error ( 'An error in Twitch rendering.' , e2 ) ;
t . log . capture ( e2 , {
extra : {
props : this . props
}
} ) ;
return 'An error occurred rendering this chat line.' ;
}
2023-04-19 17:19:10 -04:00
} } * /
2017-11-14 22:13:30 -05:00
2023-04-19 17:19:10 -04:00
/ * c l s . p r o t o t y p e . r e n d e r = t h i s . e x p e r i m e n t s . g e t ( ' l i n e _ r e n d e r e r ' )
2022-12-18 17:30:34 -05:00
? cls . prototype . ffzNewRender
2023-04-19 17:19:10 -04:00
: cls . prototype . ffzOldRender ; * /
cls . prototype . render = cls . prototype . ffzNewRender ;
2022-12-18 17:30:34 -05:00
2018-03-01 04:13:52 -05:00
// Do this after a short delay to hopefully reduce the chance of React
// freaking out on us.
setTimeout ( ( ) => this . ChatLine . forceUpdate ( ) ) ;
2019-04-28 17:28:16 -04:00
} ) ;
this . ExtensionLine . ready ( cls => {
const old _render = cls . prototype . render ;
cls . prototype . render = function ( ) { try {
2019-04-29 18:14:04 -04:00
this . _ffz _no _scan = true ;
2019-04-28 17:28:16 -04:00
if ( ! this . props . installedExtensions )
return null ;
const msg = t . chat . standardizeMessage ( this . props . message ) ,
ext = msg && msg . extension ;
if ( ! ext )
return null ;
if ( ! this . props . installedExtensions . some ( val => {
const e = val . extension ;
2019-08-27 16:18:12 -04:00
return e && ( e . clientId || e . clientID ) === ( ext . clientId || ext . clientID ) && e . version === ext . version ;
2019-04-28 17:28:16 -04:00
} ) )
return null ;
const color = t . parent . colors . process ( ext . chatColor ) ;
let room = msg . roomLogin ? msg . roomLogin : msg . channel ? msg . channel . slice ( 1 ) : undefined ;
if ( ! room && this . props . channelID ) {
const r = t . chat . getRoom ( this . props . channelID , null , true ) ;
if ( r && r . login )
room = msg . roomLogin = r . login ;
}
const u = t . site . getUser ( ) ,
r = { id : this . props . channelID , login : room } ,
2021-04-30 17:38:49 -04:00
tokens = msg . ffz _tokens = msg . ffz _tokens || t . chat . tokenizeMessage ( msg , u ) ,
2019-04-28 17:28:16 -04:00
rich _content = FFZRichContent && t . chat . pluckRichContent ( tokens , msg ) ,
bg _css = msg . mentioned && msg . mention _color ? t . parent . inverse _colors . process ( msg . mention _color ) : null ;
if ( ! tokens . length )
return null ;
return e ( 'div' , {
className : ` chat-line__message ${ msg . mentioned ? ' ffz-mentioned' : '' } ${ bg _css ? ' ffz-custom-color' : '' } ` ,
style : { backgroundColor : bg _css } ,
'data-room-id' : r . id ,
'data-room' : r . login ,
'data-extension' : ext . clientID
} , [
this . props . showTimestamps && e ( 'span' , {
className : 'chat-line__timestamp'
} , t . chat . formatTime ( msg . timestamp ) ) ,
e ( 'span' , {
className : 'chat-line__message--badges'
} , t . chat . badges . render ( msg , e ) ) ,
2019-05-16 14:46:26 -04:00
e ( 'span' , {
2019-04-28 17:28:16 -04:00
className : 'chat-line__username notranslate' ,
2019-05-16 14:46:26 -04:00
role : 'button' ,
2019-04-28 17:28:16 -04:00
style : { color } ,
onClick : this . onExtensionNameClick
} , e ( 'span' , {
className : 'chat-author__display-name'
} , ext . displayName ) ) ,
e ( 'span' , null , ': ' ) ,
e ( 'span' , {
className : 'message'
} , t . chat . renderTokens ( tokens , e ) ) ,
rich _content && e ( FFZRichContent , rich _content )
] ) ;
} catch ( err ) {
t . log . info ( err ) ;
t . log . capture ( err , {
extra : {
props : this . props
}
} ) ;
return old _render . call ( this ) ;
} }
// Do this after a short delay to hopefully reduce the chance of React
// freaking out on us.
setTimeout ( ( ) => this . ExtensionLine . forceUpdate ( ) ) ;
2017-11-14 22:13:30 -05:00
} )
}
2023-03-03 15:24:20 -05:00
checkEffects ( ) {
for ( const inst of this . ChatLine . instances ) {
const msg = inst . props . message ,
tokens = msg ? . ffz _tokens ;
if ( tokens )
for ( const token of tokens ) {
if ( token . type === 'emote' && token . modifier _flags )
this . emotes . ensureEffect ( token . modifier _flags ) ;
}
}
for ( const inst of this . WhisperLine . instances ) {
const msg = inst . props . message ? . _ffz _message ,
tokens = msg ? . ffz _tokens ;
if ( tokens )
for ( const token of tokens ) {
if ( token . type === 'emote' && token . modifier _flags )
this . emotes . ensureEffect ( token . modifier _flags ) ;
}
}
}
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
updateLinesByUser ( id , login , clear _tokens = true , clear _badges = true ) {
2020-01-11 17:13:56 -05:00
for ( const inst of this . ChatLine . instances ) {
const msg = inst . props . message ,
user = msg ? . user ;
2021-03-02 16:55:25 -05:00
if ( user && ( ( id && id == user . id ) || ( login && login == user . login ) ) ) {
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
if ( clear _badges )
msg . ffz _badges = msg . ffz _badge _cache = null ;
if ( clear _tokens ) {
msg . ffz _tokens = null ;
2023-09-06 17:20:05 -04:00
msg . ffz _reply = null ;
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
msg . highlights = msg . mentioned = msg . mention _color = msg . color _priority = null ;
}
2020-01-11 17:13:56 -05:00
inst . forceUpdate ( ) ;
2021-03-02 16:55:25 -05:00
}
2020-01-11 17:13:56 -05:00
}
for ( const inst of this . WhisperLine . instances ) {
const msg = inst . props . message ? . _ffz _message ,
user = msg ? . user ;
2021-03-02 16:55:25 -05:00
if ( user && ( ( id && id == user . id ) || ( login && login == user . login ) ) ) {
msg . _ffz _message = null ;
2020-01-11 17:13:56 -05:00
inst . forceUpdate ( ) ;
2021-03-02 16:55:25 -05:00
}
2020-01-11 17:13:56 -05:00
}
}
2018-12-13 15:21:57 -05:00
maybeUpdateLines ( ) {
if ( this . chat . context . get ( 'chat.rich.all-links' ) )
this . updateLines ( ) ;
}
2017-11-14 22:13:30 -05:00
updateLines ( ) {
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
return this . _updateLines ( ) ;
}
rerenderLines ( ) {
return this . _updateLines ( false , false ) ;
}
updateLineTokens ( ) {
return this . _updateLines ( true , false ) ;
}
updateLineBadges ( ) {
return this . _updateLines ( false , true ) ;
}
_updateLines ( clear _tokens = true , clear _badges = true ) {
2017-12-01 15:33:06 -05:00
for ( const inst of this . ChatLine . instances ) {
const msg = inst . props . message ;
2018-05-31 18:34:15 -04:00
if ( msg ) {
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
if ( clear _badges )
msg . ffz _badge _cache = msg . ffz _badges = null ;
if ( clear _tokens ) {
msg . ffz _tokens = null ;
2023-09-06 17:20:05 -04:00
msg . ffz _reply = null ;
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
msg . highlights = msg . mentioned = msg . mention _color = msg . mention _priority = msg . clear _priority = null ;
}
2018-05-31 18:34:15 -04:00
}
2017-12-01 15:33:06 -05:00
}
2018-03-01 04:13:52 -05:00
2019-04-28 17:28:16 -04:00
for ( const inst of this . ExtensionLine . instances ) {
const msg = inst . props . message ;
if ( msg ) {
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
if ( clear _badges )
msg . ffz _badge _cache = msg . ffz _badges = null ;
if ( clear _tokens ) {
msg . ffz _tokens = null ;
2023-09-06 17:20:05 -04:00
msg . ffz _reply = null ;
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
msg . highlights = msg . mentioned = msg . mention _color = msg . mention _priority = msg . clear _priority = null ;
}
2019-04-28 17:28:16 -04:00
}
}
2018-07-13 14:32:12 -04:00
for ( const inst of this . WhisperLine . instances ) {
const msg = inst . props . message ;
if ( msg && msg . _ffz _message )
msg . _ffz _message = null ;
}
2018-05-18 02:10:00 -04:00
this . ChatLine . forceUpdate ( ) ;
2019-04-28 17:28:16 -04:00
this . ExtensionLine . forceUpdate ( ) ;
2018-07-13 14:32:12 -04:00
this . WhisperLine . forceUpdate ( ) ;
2018-07-19 22:03:01 -04:00
this . emit ( 'chat:updated-lines' ) ;
2018-05-18 02:10:00 -04:00
}
2017-11-14 22:13:30 -05:00
}