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' ;
2023-11-13 20:47:45 -05:00
import { has } 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' ;
2023-11-13 20:47:45 -05:00
2024-06-17 14:09:46 -04:00
import { getRewardTitle , getRewardCost , isGiantEmoteReward , doesRewardCostBits , isMessageEffect } from './points' ;
2024-01-17 14:28:21 -05:00
import awaitMD , { getMD } from 'utilities/markdown' ;
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' ) ;
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 } `
}
} ;
2024-01-17 14:28:21 -05:00
this . line _types . notice = {
renderNotice : ( msg , current _user , room , inst , e ) => {
const data = msg . ffz _data ;
let content = this . line _types . notice . renderContent ( msg , current _user , room , inst , e ) ;
if ( ! data . icon )
return content ;
if ( typeof content === 'string' )
content = e ( 'span' , { } , content ) ;
2024-01-17 14:46:48 -05:00
if ( typeof data . icon === 'function' ) {
try {
content . ffz _icon = data . icon ( data , inst , e ) ;
} catch ( err ) {
this . log . capture ( err ) ;
this . log . error ( 'Error using custom renderer for notice:' , err ) ;
}
} else if ( data . icon instanceof URL )
content . ffz _icon = e ( 'img' , {
className : 'ffz-notice-icon tw-mg-r-05' ,
src : data . icon . toString ( )
} ) ;
else
content . ffz _icon = e ( 'span' , {
className : ` ${ data . icon } tw-mg-r-05 `
} ) ;
2024-01-17 14:28:21 -05:00
return content ;
} ,
renderContent : ( msg , current _user , room , inst , e ) => {
const data = msg . ffz _data ;
if ( data . renderer )
try {
return data . renderer ( data , inst , e ) ;
} catch ( err ) {
this . log . capture ( err ) ;
this . log . error ( 'Error using custom renderer for notice:' , err ) ;
return ` Error rendering notice. `
}
const text = data . i18n ? this . i18n . t ( data . i18n , data . messgae , data ) : data . message ;
if ( data . markdown ) {
const md = getMD ( ) ;
if ( ! md ) {
awaitMD ( ) . then ( ( ) => inst . forceUpdate ( ) ) ;
return 'Loading...' ;
}
return e ( 'span' , {
dangerouslySetInnerHTML : {
_ _html : getMD ( ) . renderInline ( text )
}
} ) ;
}
if ( data . tokenize ) {
const tokens = data . ffz _tokens = data . ffz _tokens || this . chat . tokenizeMessage ( {
2024-03-21 15:44:11 -04:00
badges : { } ,
2024-01-17 14:28:21 -05:00
message : text ,
id : msg . id ,
2024-01-17 14:46:48 -05:00
user : msg . user ,
2024-01-24 12:12:08 -05:00
roomLogin : msg . roomLogin ,
2024-01-17 14:46:48 -05:00
roomID : msg . roomID
} ) ;
2024-01-17 14:28:21 -05:00
return this . chat . renderTokens ( tokens , e ) ;
}
return text ;
}
} ;
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 ,
2024-06-14 14:27:26 -04:00
is _bits = doesRewardCostBits ( msg . ffz _reward ) ,
2022-12-18 17:30:34 -05:00
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' } , [
2024-06-14 14:27:26 -04:00
e ( 'span' , { className : is _bits ? 'ffz-i-bits' : 'ffz--points-icon' } ) ,
2022-12-18 17:30:34 -05:00
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 || { } ,
2023-09-26 17:40:25 -04:00
tier = SUB _TIERS [ plan . plan ] || 1 ,
multi = msg . sub _multi ,
has _multi = ( multi ? . count ? ? 0 ) > 1 && multi . tenure === 0 ;
const sub _msg = this . i18n . tList (
` chat.sub.main ${ has _multi ? '-multi' : '' } ` ,
` {user} subscribed {plan} ${ has _multi ? ' for {multi, plural, one {# month} other {# months}} in advance' : '' } . ` ,
{
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 ) ) ,
plan : plan . prime ?
this . i18n . t ( 'chat.sub.twitch-prime' , 'with Prime Gaming' ) :
this . i18n . t ( 'chat.sub.plan' , 'at Tier {tier}' , { tier } ) ,
multi : has _multi
? multi . count
: 1
}
) ;
2022-12-18 17:30:34 -05:00
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 ) ;
2024-03-16 20:34:27 -04:00
this . on ( 'chat:update-line' , this . updateLineById , 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 ( ) ;
} ) ;
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
2023-11-02 21:39:52 -04:00
this . on ( 'chat:get-messages' , ( include _chat , include _whisper , include _video , messages ) => {
if ( include _chat ) {
for ( const inst of this . ChatLine . instances ) {
const msg = inst . props . message ;
if ( msg )
messages . push ( {
message : msg ,
_instance : inst ,
update : ( ) => inst . forceUpdate ( )
} ) ;
}
for ( const inst of this . ExtensionLine . instances ) {
const msg = inst . props . message ;
if ( msg )
messages . push ( {
message : msg ,
_instance : inst ,
update : ( ) => inst . forceUpdate ( )
} ) ;
}
}
if ( include _whisper )
for ( const inst of this . WhisperLine . instances ) {
const msg = inst . props . message ;
if ( msg && msg . _ffz _message )
messages . push ( {
message : msg . _ffz _message ,
_instance : inst ,
update : ( ) => inst . forceUpdate ( )
} ) ;
}
} ) ;
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 ,
2023-11-02 21:39:52 -04:00
React = await this . site . findReact ( ) ;
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 )
] ) ;
}
2024-09-20 13:30:11 -04:00
cls . prototype . ffzRenderSharedChatPill = function ( ) {
let style = t . chat . context . get ( 'chat.shared-chat.style' ) ;
if ( style == null )
style = this . props . isCurrentUserModerator ? 2 : 0 ;
if ( style === 0 )
return null ;
const msg = this . props . message ,
source _id = msg . sourceRoomID ,
source = source _id && this . props . sharedChatDataByChannelID ? . get ( source _id ) ,
in _source = ! source || source _id === ( msg . roomId ? ? this . props . channelID ) ;
if ( ! source )
return null ;
const title = t . i18n . t ( 'chat.sent-from-source' , 'Sent from {source}' , { source : source . displayName ? ? source . login } ) ;
if ( style === 1 )
return e ( 'span' , {
className : ` ffz-pill ffz-tooltip tw-mg-r-05 ${ in _source ? 'ffz-pill--brand' : '' } ` ,
'data-title' : title
} , ` ${ source . displayName ? ? source . login } ` ) ;
if ( style === 2 )
return e ( 'figure' , {
className : ` ffz-tooltip ffz-tooltip--no-mouse ffz-shared-chat-badge ffz-avatar tw-border-radius-rounded ${ in _source ? 'ffz-shared-chat-badge--active' : '' } tw-mg-r-05 ` ,
'data-title' : t . i18n . t ( 'chat.sent-from-source' , 'Sent from {source}' , { source : source . displayName } ) ,
} , e ( 'img' , {
className : 'tw-block tw-border-radius-rounded tw-image' ,
src : source . profileImageURL ,
alt : source . displayName
} ) ) ;
return null ;
}
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
2024-09-20 13:30:11 -04:00
// First, we need to handle Shared Chat.
const source _id = msg . sourceRoomID ,
source = source _id && this . props . sharedChatDataByChannelID ? . get ( source _id ) ;
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
2024-09-20 13:30:11 -04:00
const is _in _source = ! source || source _id === room _id ,
source _room = is _in _source
? current _room
: { id : source _id , login : source . login } ;
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
}
2023-11-13 20:47:45 -05:00
const fe = t . makeEvent ( {
2022-02-11 15:17:32 -05:00
inst : this ,
event ,
message : msg ,
user : target _user ,
2024-09-20 13:30:11 -04:00
room : current _room ,
source : source _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 } ) ` ) ;
}
}
2023-12-19 16:24:33 -05:00
// Are we listing highlight reasons?
let highlight _tags = null ;
const hl _position = t . chat . context . get ( 'chat.filtering.show-reasons' ) ;
if ( msg . highlights ? . size && hl _position ) {
highlight _tags = [ ] ;
for ( const tag of msg . highlights ) {
const reason = t . chat . _hl _reasons [ tag ] ;
let label ,
tooltip ;
if ( reason ) {
tooltip = reason . i18n _key
? t . i18n . t ( reason . i18n _key , reason . title )
: reason . title ;
label = reason . i18n _label
? t . i18n . t ( reason . i18n _label , reason . label )
: reason . label ;
if ( label === tooltip )
tooltip = null ;
}
if ( ! label )
label = tag ;
highlight _tags . push ( e ( 'span' , {
className : ` ffz-pill ffz-highlight-tag ${ reason ? ' ffz-tooltip' : '' } ` ,
'data-title' : tooltip
} , label ) ) ;
}
if ( highlight _tags . length > 0 )
highlight _tags = e ( 'span' , {
className : ` ffz-highlight-tags ${
hl _position === 1
? 'ffz-highlight-tags__above'
: 'tw-mg-r-05'
} `
} , highlight _tags ) ;
else
highlight _tags = null ;
}
2022-12-18 17:30:34 -05:00
// 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 ,
2024-06-14 14:27:26 -04:00
is _giant _emote = this . props . isLastEmoteGigantified || isGiantEmoteReward ( msg . ffz _reward ) ,
2022-12-18 17:30:34 -05: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 ;
const raw _color = t . overrides . getColor ( user . id ) || user . color ,
color = t . parent . colors . process ( raw _color ) ;
2024-06-14 14:27:26 -04:00
const rich _content = show && FFZRichContent && t . chat . pluckRichContent ( tokens , msg ) ,
giant _emote = is _giant _emote && t . chat . pluckLastEmote ( tokens , msg ) ;
2022-12-18 17:30:34 -05:00
// First, render the user block.
const username = t . chat . formatUser ( user , e ) ,
override _name = t . overrides . getName ( user . id ) ;
2023-11-02 21:39:52 -04:00
let user _class = msg . ffz _user _class ;
2023-11-03 14:40:58 -04:00
if ( user _class instanceof Set )
user _class = [ ... user _class ] . join ( ' ' ) ;
else if ( Array . isArray ( user _class ) )
2023-11-02 21:39:52 -04:00
user _class = user _class . join ( ' ' ) ;
2024-09-20 13:30:11 -04:00
const want _source _tip = source && t . chat . context . get ( 'chat.shared-chat.username-tooltip' ) ;
2022-12-18 17:30:34 -05:00
const user _props = {
2024-09-20 13:30:11 -04:00
className : ` chat-line__username notranslate ${ override _name ? ' ffz--name-override' : '' } ${ ( override _name || want _source _tip ) ? ' tw-relative ffz-il-tooltip__container' : '' } ${ user _class ? ? '' } ` ,
2022-12-18 17:30:34 -05:00
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' , {
2024-09-20 13:30:11 -04:00
className : 'ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-center'
} , [
username ,
want _source _tip
? e ( 'div' , {
className : 'tw-font-size-8 tw-mg-t-05'
} , t . i18n . t ( 'chat.sent-from-source' , 'Sent from {source}' , { source : source . displayName ? ? source . login } ) )
: null
]
)
2022-12-18 17:30:34 -05:00
]
2024-09-20 13:30:11 -04:00
: want _source _tip
? [
username ,
e ( 'span' , {
className : 'ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-center'
} , t . i18n . t ( 'chat.sent-from-source' , 'Sent from {source}' , { source : source . displayName ? ? source . login } ) )
]
: username
2022-12-18 17:30:34 -05:00
) ;
// 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 ,
2024-09-20 13:30:11 -04:00
source ? this . ffzRenderSharedChatPill ( ) : null ,
2022-12-18 17:30:34 -05:00
// The preamble
timestamp ,
2024-01-17 14:28:21 -05:00
msg . ffz _no _actions ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , current _user , current _room , e , this ) ,
2022-12-18 17:30:34 -05:00
this . renderInlineHighlight ? this . renderInlineHighlight ( ) : null ,
2023-12-19 16:24:33 -05:00
hl _position === 2 ? highlight _tags : null ,
2022-12-18 17:30:34 -05:00
// 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 )
2024-06-14 14:27:26 -04:00
: null ,
// Giant Emote
giant _emote
? e ( 'div' , { className : 'chat-line__message--ffz-giant-emote' } , t . chat . renderGiantEmote ( giant _emote , e ) )
2022-12-18 17:30:34 -05:00
: 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 ;
2024-01-17 14:28:21 -05:00
const actions = msg . ffz _no _actions ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , current _user , current _room , e , this ) ;
2022-12-18 17:30:34 -05:00
2023-12-19 16:24:33 -05:00
if ( is _raw ) {
notice . ffz _target . unshift (
2024-09-20 13:30:11 -04:00
source ? this . ffzRenderSharedChatPill ( ) : null ,
2023-12-19 16:24:33 -05:00
notice . ffz _icon ? ? null ,
timestamp ,
actions ,
hl _position === 2 ? highlight _tags : null
) ;
2022-12-18 17:30:34 -05:00
2023-12-19 16:24:33 -05:00
} else
2022-12-18 17:30:34 -05:00
notice = [
2024-09-20 13:30:11 -04:00
source ? this . ffzRenderSharedChatPill ( ) : null ,
2022-12-18 17:30:34 -05:00
notice . ffz _icon ? ? null ,
timestamp ,
actions ,
2023-12-19 16:24:33 -05:00
hl _position === 2 ? highlight _tags : null ,
2022-12-18 17:30:34 -05:00
notice
] ;
} else {
2023-12-19 16:24:33 -05:00
if ( is _raw ) {
if ( notice . ffz _icon )
notice . ffz _target . unshift ( notice . ffz _icon ) ;
} else if ( notice . ffz _icon )
2022-12-18 17:30:34 -05:00
notice = [
notice . ffz _icon ,
2023-12-19 16:24:33 -05:00
notice ,
2022-12-18 17:30:34 -05:00
] ;
message = e (
'div' ,
{
className : 'chat-line--inline chat-line__message' ,
2024-09-20 13:30:11 -04:00
'data-room-id' : source _room ? . id ? ? msg . roomId ? ? current _room . id ,
'data-room' : source _room ? . login ? ? msg . roomLogin ,
2022-12-18 17:30:34 -05:00
'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 ) ;
2023-12-19 16:24:33 -05:00
if ( highlight _tags && hl _position === 1 ) {
out = [ highlight _tags , notice , message ? ? null ] ;
} else if ( message )
2022-12-18 17:30:34 -05:00
out = [ notice , message ] ;
else
out = notice ;
} else {
klass = ` ${ klass } chat-line__message ` ;
2023-12-19 16:24:33 -05:00
if ( highlight _tags && hl _position === 1 )
out = [ highlight _tags , message ] ;
else
out = message ;
2022-12-18 17:30:34 -05:00
}
// Check for hover actions, as those require we wrap the output in a few extra elements.
2024-01-17 14:28:21 -05:00
const hover _actions = ( user && msg . id && ! msg . ffz _no _actions )
2022-12-18 17:30:34 -05:00
? 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 } ,
2024-09-20 13:30:11 -04:00
'data-room-id' : source _room ? . id ? ? msg . roomId ? ? current _room . id ,
'data-room' : source _room ? . login ? ? msg . roomLogin ,
2022-12-18 17:30:34 -05:00
'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
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 ) ;
}
}
}
2024-03-16 20:34:27 -04:00
updateLineById ( id , clear _tokens = true , clear _badges = null ) {
if ( clear _badges == null )
clear _badges = clear _tokens ;
for ( const inst of this . ChatLine . instances ) {
const msg = inst . props . message ;
if ( msg ? . id === id ) {
if ( clear _badges )
msg . ffz _badges = msg . ffz _badge _cache = null ;
if ( clear _tokens ) {
msg . ffz _tokens = null ;
msg . ffz _reply = null ;
msg . highlights = msg . mentioned = msg . mention _color = msg . color _priority = null ;
}
inst . forceUpdate ( ) ;
return ;
}
}
for ( const inst of this . WhisperLine . instances ) {
const msg = inst . props . message ? . _ffz _message ;
if ( msg ? . id === id ) {
// TODO: Better support for clear_tokens and clear_badges
if ( clear _badges || clear _tokens )
msg . _ffz _message = null ;
inst . forceUpdate ( ) ;
return ;
}
}
}
2023-03-03 15:24:20 -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
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
}
2023-12-19 16:24:33 -05:00
}