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' ;
2019-02-05 14:24: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' ;
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
function getGiftThemeURL ( theme ) {
return ` https://static-cdn.jtvnw.net/subs-image-assets/TUN- ${ theme } .png ` ;
}
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' ) ;
2018-04-28 17:56:03 -04: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 ) ;
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 ||
props . showTimestamps !== this . props . showTimestamps ;
}
2020-08-12 16:10:06 -04:00
cls . prototype . ffzOpenReply = function ( ) {
2020-08-13 14:00:47 -04:00
if ( this . props . reply ) {
this . setOPCardTray ( this . props . reply ) ;
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 )
] ) ;
}
2018-07-16 13:57:56 -04:00
cls . prototype . render = function ( ) { try {
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-05-18 02:10:00 -04:00
//if ( ! msg.message && msg.messageParts )
// t.chat.detokenizeMessage(msg);
2017-11-14 22:13:30 -05:00
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 ,
2020-08-15 15:45:50 -04:00
can _reply = can _replies && u && u . login !== msg . user ? . login && ! msg . reply ,
2020-08-13 14:00:47 -04:00
twitch _clickable = reply _mode === 1 && can _replies && ( ! ! msg . reply || can _reply ) ;
2018-04-28 17:56:03 -04:00
if ( u ) {
u . moderator = this . props . isCurrentUserModerator ;
u . staff = this . props . isCurrentUserStaff ;
2020-08-13 14:00:47 -04:00
u . can _reply = reply _mode === 2 && can _reply ;
2018-04-28 17:56:03 -04:00
}
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 ) ;
} catch ( err ) { /* nothing~! */ }
}
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 ) ;
2020-08-13 14:00:47 -04:00
const user _bits = [
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-13 14:00:47 -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-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
//twitch_clickable ?
// e('div', {className: 'chat-line__username-container tw-inline-block'}, user_bits) :
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 ,
2019-04-18 03:16:19 -04:00
2017-11-23 02:49:23 -05:00
/ * t h i s . s t a t e . r e n d e r D e b u g = = = 2 & & e ( ' d i v ' , {
className : 'border mg-t-05'
} , old _render . call ( this ) ) ,
this . state . renderDebug === 1 && e ( 'div' , {
className : 'message--debug' ,
style : {
fontFamily : 'monospace' ,
whiteSpace : 'pre-wrap' ,
lineHeight : '1.1em'
}
} , JSON . stringify ( [ tokens , msg . emotes ] , null , 2 ) ) * /
] : null ;
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 ) ) ,
2019-02-05 14:24:45 -05:00
( out || msg . sub _anon ) ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , u , r , e ) ,
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 ) ) ,
2019-02-05 14:24:45 -05:00
( out || msg . sub _anon ) ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , u , r , e ) ,
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 ) ) ,
2019-02-05 14:24:45 -05:00
out ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , u , r , e ) ,
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 ) ) ,
2019-11-11 14:38:49 -05:00
out ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , u , r , e ) ,
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 ) ) ,
out ? null : t . actions . renderInline ( msg , this . props . showModerationIcons , u , r , e ) ,
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 ) {
let icon , title ;
if ( can _reply ) {
icon = e ( 'figure' , { className : 'ffz-i-reply' } ) ;
title = t . i18n . t ( 'chat.actions.reply' , 'Reply to Message' ) ;
} else {
icon = e ( 'figure' , { className : 'ffz-i-threads' } ) ;
title = t . i18n . t ( 'chat.actions.reply.thread' , 'Open Thread' ) ;
}
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'
2020-08-15 15:45:50 -04:00
} , [
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
] ) ,
e ( 'div' , {
className : 'chat-line__reply-icon tw-absolute tw-border-radius-medium tw-c-background-base tw-elevation-1'
} , e ( 'button' , {
2021-02-16 17:40:27 -05:00
className : 'tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon ffz-core-button tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-tooltip ffz-tooltip--no-mouse' ,
2020-08-15 15:45:50 -04:00
'data-test-selector' : 'chat-reply-button' ,
'aria-label' : title ,
'data-title' : title ,
onClick : this . ffz _open _reply
} , e ( 'span' , {
className : 'tw-button-icon__icon'
} , icon ) ) )
] ;
}
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
2019-01-31 19:58:21 -05:00
return old _render . call ( this ) ;
} }
2017-11-14 22:13:30 -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
} )
}
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 ;
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 ;
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 ;
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
}