2018-04-28 17:56:03 -04:00
'use strict' ;
// ============================================================================
// Emoji Handling
// ============================================================================
import Module from 'utilities/module' ;
import { has , maybe _call , deep _copy } from 'utilities/object' ;
2019-04-29 18:14:04 -04:00
import { createElement , ClickOutside } from 'utilities/dom' ;
2018-04-28 17:56:03 -04:00
import Tooltip from 'utilities/tooltip' ;
import * as ACTIONS from './types' ;
import * as RENDERERS from './renderers' ;
2019-04-29 18:14:04 -04:00
import { transformPhrase } from 'src/i18n' ;
2018-04-28 17:56:03 -04:00
2019-04-29 18:14:04 -04:00
const VAR _REPLACE = /\{\{(.*?)(?:\|(.*?))?\}\}/g ;
2018-04-28 17:56:03 -04:00
export default class Actions extends Module {
constructor ( ... args ) {
super ( ... args ) ;
this . inject ( 'settings' ) ;
this . inject ( 'tooltips' ) ;
this . inject ( 'i18n' ) ;
this . actions = { } ;
this . renderers = { } ;
2019-04-29 18:14:04 -04:00
this . settings . add ( 'chat.actions.reasons' , {
default : [
{ v : { text : 'One-Man Spam' , i18n : 'chat.reasons.spam' } } ,
{ v : { text : 'Posting Bad Links' , i18n : 'chat.reasons.links' } } ,
{ v : { text : 'Ban Evasion' , i18n : 'chat.reasons.evasion' } } ,
{ v : { text : 'Threats / Personal Info' , i18n : 'chat.reasons.personal' } } ,
{ v : { text : 'Hate / Harassment' , i18n : 'chat.reasons.hate' } } ,
{ v : { text : 'Ignoring Broadcaster / Moderators' , i18n : 'chat.reason.ignore' } }
] ,
type : 'array_merge' ,
always _inherit : true ,
2019-05-03 22:36:26 -04:00
// Clean up after Vue being stupid.
process ( ctx , val ) {
if ( Array . isArray ( val ) )
for ( const entry of val )
if ( entry . i18n && typeof entry . i18n !== 'string' )
delete entry . i18n ;
return val ;
} ,
2019-04-29 18:14:04 -04:00
ui : {
2019-05-03 19:30:46 -04:00
path : 'Chat > Actions > Reasons >> Custom Reasons' ,
2019-04-29 18:14:04 -04:00
component : 'chat-reasons' ,
}
} ) ;
2018-04-28 17:56:03 -04:00
this . settings . add ( 'chat.actions.inline' , {
// Filter out actions
process : ( ctx , val ) =>
val . filter ( x => x . appearance &&
this . renderers [ x . appearance . type ] &&
( ! this . renderers [ x . appearance . type ] . load || this . renderers [ x . appearance . type ] . load ( x . appearance ) ) &&
( ! x . action || this . actions [ x . action ] )
) ,
default : [
2018-09-03 14:32:58 -04:00
{ v : { action : 'ban' , appearance : { type : 'icon' , icon : 'ffz-i-block' } , options : { } , display : { mod : true , mod _icons : true , deleted : false } } } ,
{ v : { action : 'unban' , appearance : { type : 'icon' , icon : 'ffz-i-ok' } , options : { } , display : { mod : true , mod _icons : true , deleted : true } } } ,
2018-04-28 17:56:03 -04:00
{ v : { action : 'timeout' , appearance : { type : 'icon' , icon : 'ffz-i-clock' } , display : { mod : true , mod _icons : true } } } ,
2018-12-05 14:51:03 -05:00
{ v : { action : 'msg_delete' , appearance : { type : 'icon' , icon : 'ffz-i-trash' } , options : { } , display : { mod : true , mod _icons : true } } }
2018-04-28 17:56:03 -04:00
] ,
type : 'array_merge' ,
ui : {
2019-04-29 18:14:04 -04:00
path : 'Chat > Actions > In-Line @{"description": "Here, you can define custom actions that will appear along messages in chat. If you aren\'t seeing an action you\'ve defined here in chat, please make sure that you have enabled Mod Icons in the chat settings menu."}' ,
2018-04-28 17:56:03 -04:00
component : 'chat-actions' ,
2018-10-16 02:13:14 -04:00
context : [ 'user' , 'room' , 'message' ] ,
2018-04-28 17:56:03 -04:00
inline : true ,
2019-05-16 14:46:26 -04:00
modifiers : true ,
2018-04-28 17:56:03 -04:00
data : ( ) => {
const chat = this . resolve ( 'site.chat' ) ;
return {
color : val => chat && chat . colors ? chat . colors . process ( val ) : val ,
actions : deep _copy ( this . actions ) ,
renderers : deep _copy ( this . renderers )
}
}
}
} ) ;
2019-08-06 15:39:45 -04:00
this . settings . add ( 'chat.actions.user-context' , {
// Filter out actions
process : ( ctx , val ) =>
val . filter ( x => x . type || ( x . appearance &&
this . renderers [ x . appearance . type ] &&
( ! this . renderers [ x . appearance . type ] . load || this . renderers [ x . appearance . type ] . load ( x . appearance ) ) &&
( ! x . action || this . actions [ x . action ] )
) ) ,
default : [ ] ,
type : 'array_merge' ,
ui : {
path : 'Chat > Actions > User Context @{"description": "Here, you can define custom actions that will appear in a context menu when you right-click a username in chat."}' ,
component : 'chat-actions' ,
context : [ 'user' , 'room' , 'message' ] ,
mod _icons : true ,
data : ( ) => {
const chat = this . resolve ( 'site.chat' ) ;
return {
color : val => chat && chat . colors ? chat . colors . process ( val ) : val ,
actions : deep _copy ( this . actions ) ,
renderers : deep _copy ( this . renderers )
}
}
}
} )
2019-05-07 15:04:12 -04:00
this . settings . add ( 'chat.actions.room' , {
// Filter out actions
process : ( ctx , val ) =>
val . filter ( x => x . type || ( x . appearance &&
this . renderers [ x . appearance . type ] &&
( ! this . renderers [ x . appearance . type ] . load || this . renderers [ x . appearance . type ] . load ( x . appearance ) ) &&
( ! x . action || this . actions [ x . action ] )
) ) ,
default : [ ] ,
type : 'array_merge' ,
ui : {
path : 'Chat > Actions > Room @{"description": "Here, you can define custom actions that will appear above the chat input box."}' ,
component : 'chat-actions' ,
2019-08-12 22:52:57 -04:00
context : [ 'room' , 'room-mode' ] ,
2019-05-07 15:04:12 -04:00
inline : true ,
data : ( ) => {
const chat = this . resolve ( 'site.chat' ) ;
return {
color : val => chat && chat . colors ? chat . colors . process ( val ) : val ,
actions : deep _copy ( this . actions ) ,
renderers : deep _copy ( this . renderers )
}
}
}
} ) ;
2019-08-13 16:22:04 -04:00
this . settings . add ( 'chat.actions.room-above' , {
default : false ,
ui : {
path : 'Chat > Actions > Room >> General' ,
component : 'setting-check-box' ,
title : 'Display Room Actions above the chat input box.'
}
} ) ;
2019-05-03 19:30:46 -04:00
this . settings . add ( 'chat.actions.rules-as-reasons' , {
default : true ,
ui : {
path : 'Chat > Actions > Reasons >> Rules' ,
component : 'setting-check-box' ,
title : "Include the current room's rules in the list of reasons."
}
} ) ;
2018-04-28 17:56:03 -04:00
this . handleClick = this . handleClick . bind ( this ) ;
this . handleContext = this . handleContext . bind ( this ) ;
2019-08-06 15:39:45 -04:00
this . handleUserContext = this . handleUserContext . bind ( this ) ;
2018-04-28 17:56:03 -04:00
}
onEnable ( ) {
this . tooltips . types . action = ( target , tip ) => {
const data = this . getData ( target ) ;
if ( ! data )
return this . i18n . t ( 'chat.actions.unknown' , 'Unknown Action Type' ) ;
if ( ! data . definition . tooltip )
return ` Error: The " ${ data . action } " action provider does not have tooltip support. ` ;
if ( data . tip && data . tip . length )
2019-08-27 16:18:12 -04:00
return this . replaceVariables ( data . tip , data ) ;
2018-04-28 17:56:03 -04:00
return maybe _call ( data . definition . tooltip , this , data , target , tip ) ;
}
for ( const key in ACTIONS )
if ( has ( ACTIONS , key ) )
this . addAction ( key , ACTIONS [ key ] ) ;
for ( const key in RENDERERS )
if ( has ( RENDERERS , key ) )
this . addRenderer ( key , RENDERERS [ key ] ) ;
}
addAction ( key , data ) {
if ( has ( this . actions , key ) )
return this . log . warn ( ` Attempted to add action " ${ key } " which is already defined. ` ) ;
this . actions [ key ] = data ;
for ( const ctx of this . settings . _ _contexts )
ctx . update ( 'chat.actions.inline' ) ;
}
addRenderer ( key , data ) {
if ( has ( this . renderers , key ) )
return this . log . warn ( ` Attempted to add renderer " ${ key } " which is already defined. ` ) ;
this . renderers [ key ] = data ;
for ( const ctx of this . settings . _ _contexts )
ctx . update ( 'chat.actions.inline' ) ;
}
2019-04-29 18:14:04 -04:00
replaceVariables ( text , data ) {
return transformPhrase (
text ,
data ,
this . i18n . locale ,
VAR _REPLACE ,
{ }
) ;
}
renderInlineReasons ( data , t , tip ) {
const reasons = this . parent . context . get ( 'chat.actions.reasons' ) ,
reason _elements = [ ] ,
2019-05-03 19:30:46 -04:00
room = this . parent . context . get ( 'chat.actions.rules-as-reasons' ) && this . parent . getRoom ( data . room . id , data . room . login , true ) ,
2019-04-29 18:14:04 -04:00
rules = room && room . rules ;
if ( ! reasons && ! rules ) {
tip . hide ( ) ;
return null ;
}
const click _fn = reason => e => {
tip . hide ( ) ;
data . definition . click . call ( this , e , Object . assign ( { reason } , data ) ) ;
e . preventDefault ( ) ;
return false ;
} ;
2019-04-29 21:14:23 -04:00
if ( reasons && reasons . length ) {
for ( const reason of reasons ) {
2019-05-03 19:30:46 -04:00
const text = this . replaceVariables ( ( typeof reason . i18n === 'string' ) ? this . i18n . t ( reason . i18n , reason . text ) : reason . text , data ) ;
2019-04-29 21:14:23 -04:00
reason _elements . push ( < li class = "tw-full-width tw-relative" >
< a
href = "#"
2019-08-09 14:24:26 -04:00
class = "tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive tw-pd-05"
2019-06-20 15:15:54 -04:00
onClick = { click _fn ( text ) }
2019-04-29 21:14:23 -04:00
>
{ text }
< / a >
< / li > )
}
2019-04-29 18:14:04 -04:00
}
2019-04-29 21:14:23 -04:00
if ( rules && rules . length ) {
if ( reasons && reasons . length )
reason _elements . push ( < div class = "tw-mg-y-05 tw-border-b" > < / div > ) ;
for ( const rule of rules ) {
reason _elements . push ( < li class = "tw-full-width tw-relative" >
< a
href = "#"
2019-08-09 14:24:26 -04:00
class = "tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive tw-pd-05"
2019-06-20 15:15:54 -04:00
onClick = { click _fn ( rule ) }
2019-04-29 21:14:23 -04:00
>
{ rule }
< / a >
< / li > ) ;
}
2019-04-29 18:14:04 -04:00
}
let reason _text ;
if ( data . definition . reason _text )
reason _text = data . definition . reason _text . call ( this , data , t , tip ) ;
else
reason _text = this . i18n . t ( 'chat.actions.select-reason' , 'Please select a reason from the list below:' ) ;
return ( < div class = "ffz--inline-reasons" >
{ reason _text ? < div class = "tw-pd-05 tw-border-b" >
{ reason _text }
< / div > : null }
< ul > { reason _elements } < / ul >
< / div > ) ;
}
2018-04-28 17:56:03 -04:00
renderInlineContext ( target , data ) {
2019-08-06 15:39:45 -04:00
const definition = data . definition ;
let content ;
if ( definition . context )
content = ( t , tip ) => definition . context . call ( this , data , t , tip ) ;
else if ( definition . uses _reason ) {
content = ( t , tip ) => this . renderInlineReasons ( data , t , tip ) ;
} else
return ;
return this . renderPopup ( target , content ) ;
}
renderPopup ( target , content ) {
2018-04-28 17:56:03 -04:00
if ( target . _ffz _destroy )
return target . _ffz _destroy ( ) ;
const destroy = target . _ffz _destroy = ( ) => {
if ( target . _ffz _outside )
target . _ffz _outside . destroy ( ) ;
if ( target . _ffz _popup ) {
const fp = target . _ffz _popup ;
target . _ffz _popup = null ;
fp . destroy ( ) ;
}
2020-07-10 20:08:29 -04:00
if ( target . _ffz _on _destroy )
target . _ffz _on _destroy ( ) ;
target . _ffz _destroy = target . _ffz _outside = target . _ffz _on _destroy = null ;
2018-04-28 17:56:03 -04:00
}
2019-04-12 17:34:01 -04:00
const parent = document . body . querySelector ( '#root>div' ) || document . body ,
2018-04-28 17:56:03 -04:00
tt = target . _ffz _popup = new Tooltip ( parent , target , {
logger : this . log ,
manual : true ,
2019-04-29 18:14:04 -04:00
live : false ,
2018-04-28 17:56:03 -04:00
html : true ,
2019-06-12 21:13:53 -04:00
hover _events : true ,
2019-08-07 14:13:04 -04:00
no _update : true ,
2018-04-28 17:56:03 -04:00
2018-09-25 18:37:14 -04:00
tooltipClass : 'ffz-action-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base' ,
2018-04-28 17:56:03 -04:00
arrowClass : 'tw-balloon__tail tw-overflow-hidden tw-absolute' ,
2018-09-25 18:37:14 -04:00
arrowInner : 'tw-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-c-background-base tw-absolute' ,
2019-04-29 18:14:04 -04:00
innerClass : '' ,
2018-04-28 17:56:03 -04:00
popper : {
placement : 'bottom' ,
modifiers : {
preventOverflow : {
boundariesElement : parent
} ,
flip : {
behavior : [ 'bottom' , 'top' , 'left' , 'right' ]
}
}
} ,
2019-04-29 18:14:04 -04:00
content ,
2019-06-12 21:13:53 -04:00
onShow : async ( t , tip ) => {
await tip . waitForDom ( ) ;
target . _ffz _outside = new ClickOutside ( tip . outer , destroy )
} ,
onMove : ( target , tip , event ) => {
this . emit ( 'tooltips:mousemove' , target , tip , event )
} ,
onLeave : ( target , tip , event ) => {
this . emit ( 'tooltips:leave' , target , tip , event ) ;
} ,
2018-04-28 17:56:03 -04:00
onHide : destroy
} ) ;
tt . _enter ( target ) ;
}
2019-04-18 21:07:11 -04:00
getUserLevel ( room , user ) { // eslint-disable-line class-methods-use-this
if ( ! room || ! user )
return 0 ;
if ( room . id === user . id || room . login === user . login )
return 5 ;
else if ( user . moderator || user . type === 'mod' || ( user . badges && user . badges . moderator ) )
return 3 ;
return 0 ;
}
2019-08-13 16:22:04 -04:00
renderRoom ( mod _icons , current _user , current _room , is _above , createElement ) {
2019-05-07 15:04:12 -04:00
const actions = [ ] ,
chat = this . resolve ( 'site.chat' ) ;
for ( const data of this . parent . context . get ( 'chat.actions.room' ) ) {
if ( ! data || ! data . action || ! data . appearance )
continue ;
2019-11-25 17:50:20 -05:00
let ap = data . appearance || { } ;
const disp = data . display || { } ,
act = this . actions [ data . action ] ;
2019-05-07 15:04:12 -04:00
2019-11-25 17:50:20 -05:00
if ( ! act || disp . disabled ||
2019-05-07 15:04:12 -04:00
( disp . mod _icons != null && disp . mod _icons !== ! ! mod _icons ) ||
( disp . mod != null && disp . mod !== ( current _user ? ! ! current _user . mod : false ) ) ||
2019-08-12 22:52:57 -04:00
( disp . staff != null && disp . staff !== ( current _user ? ! ! current _user . staff : false ) ) ||
( disp . emoteOnly != null && disp . emoteOnly !== current _room . emoteOnly ) ||
( disp . slowMode != null && disp . slowMode !== current _room . slowMode ) ||
2019-08-27 16:18:12 -04:00
( disp . subsMode != null && disp . subsMode !== current _room . subsMode ) ||
( disp . r9kMode != null && disp . r9kMode !== current _room . r9kMode ) ||
( disp . followersOnly != null && disp . followersOnly !== current _room . followersOnly ) )
2019-05-07 15:04:12 -04:00
continue ;
2019-11-25 17:50:20 -05:00
if ( act . override _appearance ) {
const out = act . override _appearance . call ( this , Object . assign ( { } , ap ) , data , null , current _room , current _user , mod _icons ) ;
if ( out )
ap = out ;
}
const def = this . renderers [ ap . type ] ;
if ( ! def )
continue ;
2019-05-07 15:04:12 -04:00
const has _color = def . colored && ap . color ,
2019-11-25 17:50:20 -05:00
disabled = maybe _call ( act . disabled , this , data , null , current _room , current _user , mod _icons ) || false ,
2019-05-07 15:04:12 -04:00
color = has _color && ( chat && chat . colors ? chat . colors . process ( ap . color ) : ap . color ) ,
contents = def . render . call ( this , ap , createElement , color ) ;
actions . push ( < button
2019-11-25 17:50:20 -05:00
class = { ` ffz-tooltip tw-pd-x-05 ffz-mod-icon mod-icon tw-c-text-alt-2 ${ disabled ? ' disabled' : '' } ${ has _color ? ' colored' : '' } ` }
2019-05-07 15:04:12 -04:00
data - tooltip - type = "action"
data - action = { data . action }
data - options = { data . options ? JSON . stringify ( data . options ) : null }
data - tip = { ap . tooltip }
onClick = { this . handleClick }
onContextMenu = { this . handleContext }
>
{ contents }
< / button > ) ;
}
if ( ! actions . length )
return null ;
const room = current _room && JSON . stringify ( current _room ) ;
return ( < div
2019-08-27 16:18:12 -04:00
class = { ` ffz--room-actions ffz-action-data tw-flex tw-flex-grow-1 tw-flex-wrap tw-align-items-center ${ is _above ? 'tw-pd-y-05 tw-border-t' : 'tw-mg-x-05' } ` }
2019-05-07 15:04:12 -04:00
data - room = { room }
>
{ actions }
< / div > )
}
2019-08-06 15:39:45 -04:00
renderUserContext ( target , actions ) {
const fine = this . resolve ( 'site.fine' ) ,
site = this . resolve ( 'site' ) ,
chat = this . resolve ( 'site.chat' ) ,
line = fine && fine . searchParent ( target , n => n . props && n . props . message ) ;
const msg = line ? . props ? . message ;
if ( ! msg || ! site || ! chat )
return ;
let room = msg . roomLogin ? msg . roomLogin : msg . channel ? msg . channel . slice ( 1 ) : undefined ;
if ( ! room && line . props . channelID ) {
const r = this . parent . getRoom ( line . props . channelID , null , true ) ;
if ( r && r . login )
room = msg . roomLogin = r . login ;
}
const u = site . getUser ( ) ,
r = { id : line . props . channelID , login : room } ;
msg . roomId = r . id ;
if ( u ) {
u . moderator = line . props . isCurrentUserModerator ;
u . staff = line . props . isCurrentUserStaff ;
}
const current _level = this . getUserLevel ( r , u ) ,
msg _level = this . getUserLevel ( r , msg . user ) ;
let mod _icons = line . props . showModerationIcons ;
if ( current _level < 3 )
mod _icons = false ;
return this . renderPopup ( target , ( t , tip ) => {
const lines = [ ] ;
let line = null ;
const handle _click = event => {
this . handleClick ( event ) ;
2020-07-10 20:08:29 -04:00
tip . hide ( ) ;
2019-08-06 15:39:45 -04:00
} ;
for ( const data of actions ) {
if ( ! data )
continue ;
if ( data . type === 'new-line' ) {
line = null ;
continue ;
} else if ( data . type === 'space-small' ) {
if ( ! line )
lines . push ( line = [ ] ) ;
line . push ( < div class = "tw-pd-x-1" / > ) ;
continue ;
} else if ( data . type === 'space' ) {
if ( ! line )
lines . push ( line = [ ] ) ;
line . push ( < div class = "tw-flex-grow-1" / > ) ;
continue ;
} else if ( ! data . action || ! data . appearance )
continue ;
2019-11-25 17:50:20 -05:00
let ap = data . appearance || { } ;
const disp = data . display || { } ,
act = this . actions [ data . action ] ;
2019-08-06 15:39:45 -04:00
2019-11-25 17:50:20 -05:00
if ( ! act || disp . disabled ||
2019-08-06 15:39:45 -04:00
( disp . mod _icons != null && disp . mod _icons !== ! ! mod _icons ) ||
( disp . mod != null && disp . mod !== ( current _level > msg _level ) ) ||
2019-08-09 14:24:26 -04:00
( disp . staff != null && disp . staff !== ( u ? ! ! u . staff : false ) ) ||
( disp . deleted != null && disp . deleted !== ! ! msg . deleted ) )
2019-08-06 15:39:45 -04:00
continue ;
2019-11-25 17:50:20 -05:00
if ( act . override _appearance ) {
const out = act . override _appearance . call ( this , Object . assign ( { } , ap ) , data , msg , r , u , mod _icons ) ;
if ( out )
ap = out ;
}
const def = this . renderers [ ap . type ] ;
if ( ! def )
continue ;
2019-08-06 15:39:45 -04:00
const has _color = def . colored && ap . color ,
2019-11-25 17:50:20 -05:00
disabled = maybe _call ( act . disabled , this , data , msg , r , u , mod _icons ) || false ,
2019-08-06 15:39:45 -04:00
color = has _color && ( chat && chat . colors ? chat . colors . process ( ap . color ) : ap . color ) ,
contents = def . render . call ( this , ap , createElement , color ) ;
if ( ! line )
lines . push ( line = [ ] ) ;
const btn = ( < button
2019-11-25 17:50:20 -05:00
class = { ` ffz-tooltip ffz-tooltip--no-mouse tw-button tw-button--text ${ disabled ? ' tw-button--disabled disabled' : '' } ` }
disabled = { disabled }
2019-08-06 15:39:45 -04:00
data - tooltip - type = "action"
data - action = { data . action }
data - options = { data . options ? JSON . stringify ( data . options ) : null }
2019-08-09 14:24:26 -04:00
onClick = { handle _click } // eslint-disable-line react/jsx-no-bind
2019-08-06 15:39:45 -04:00
onContextMenu = { this . handleContext }
>
< span class = "tw-button__text" >
{ contents }
< / span >
< / button > ) ;
if ( ap . tooltip )
btn . dataset . tip = ap . tooltip ;
line . push ( btn ) ;
}
const out = ( < div class = "ffz-action-data tw-pd-05" data - source = "msg" >
< div class = "tw-pd-b-05 tw-border-b" >
< strong > { msg . user . displayName || msg . user . login } < / strong > ...
< / div >
{ lines . map ( line => {
if ( ! line || ! line . length )
return null ;
return ( < div class = "tw-flex tw-flex-no-wrap" >
{ line }
< / div > ) ;
} ) }
< / div > ) ;
out . ffz _message = msg ;
return out ;
} ) ;
}
2018-04-28 17:56:03 -04:00
renderInline ( msg , mod _icons , current _user , current _room , createElement ) {
const actions = [ ] ;
2018-05-18 02:10:00 -04:00
if ( msg . user && current _user && current _user . login === msg . user . login )
2018-04-28 17:56:03 -04:00
return ;
2019-04-18 21:07:11 -04:00
const current _level = this . getUserLevel ( current _room , current _user ) ,
msg _level = this . getUserLevel ( current _room , msg . user ) ;
2019-05-08 15:39:14 -04:00
if ( current _level < 3 )
mod _icons = false ;
2019-05-16 14:46:26 -04:00
const chat = this . resolve ( 'site.chat' ) ,
modified = [ ] ;
let had _action = false ;
2018-04-28 17:56:03 -04:00
for ( const data of this . parent . context . get ( 'chat.actions.inline' ) ) {
if ( ! data . action || ! data . appearance )
continue ;
2019-11-25 17:50:20 -05:00
let ap = data . appearance || { } ;
const disp = data . display || { } ,
2019-05-16 14:46:26 -04:00
keys = disp . keys ,
2019-11-25 17:50:20 -05:00
act = this . actions [ data . action ] ;
2018-04-28 17:56:03 -04:00
2019-11-25 17:50:20 -05:00
if ( ! act || disp . disabled ||
2018-05-10 19:56:39 -04:00
( disp . mod _icons != null && disp . mod _icons !== ! ! mod _icons ) ||
2019-04-18 21:07:11 -04:00
( disp . mod != null && disp . mod !== ( current _level > msg _level ) ) ||
2018-05-10 19:56:39 -04:00
( disp . staff != null && disp . staff !== ( current _user ? ! ! current _user . staff : false ) ) ||
( disp . deleted != null && disp . deleted !== ! ! msg . deleted ) )
2018-04-28 17:56:03 -04:00
continue ;
2019-11-25 17:50:20 -05:00
if ( act . override _appearance ) {
const out = act . override _appearance . call ( this , Object . assign ( { } , ap ) , data , msg , current _room , current _user , mod _icons ) ;
if ( out )
ap = out ;
}
const def = this . renderers [ ap . type ] ;
if ( ! def )
continue ;
2018-04-28 17:56:03 -04:00
const has _color = def . colored && ap . color ,
2019-11-25 17:50:20 -05:00
disabled = maybe _call ( act . disabled , this , data , msg , current _room , current _user , mod _icons ) || false ,
2018-04-28 17:56:03 -04:00
color = has _color && ( chat && chat . colors ? chat . colors . process ( ap . color ) : ap . color ) ,
contents = def . render . call ( this , ap , createElement , color ) ;
2019-05-16 14:46:26 -04:00
let list = actions ;
if ( keys )
list = modified ;
had _action = true ;
list . push ( < button
2019-11-25 17:50:20 -05:00
class = { ` ffz-tooltip ffz-mod-icon mod-icon tw-c-text-alt-2 ${ disabled ? ' disabled' : '' } ${ has _color ? ' colored' : '' } ${ keys ? ` ffz-modifier- ${ keys } ` : '' } ` }
disabled = { disabled }
2018-04-28 17:56:03 -04:00
data - tooltip - type = "action"
data - action = { data . action }
data - options = { data . options ? JSON . stringify ( data . options ) : null }
data - tip = { ap . tooltip }
onClick = { this . handleClick }
onContextMenu = { this . handleContext }
>
{ contents }
< / button > ) ;
}
2019-05-16 14:46:26 -04:00
if ( ! had _action )
2018-04-28 17:56:03 -04:00
return null ;
2019-05-07 15:04:12 -04:00
/ * c o n s t r o o m = c u r r e n t _ r o o m & & J S O N . s t r i n g i f y ( c u r r e n t _ r o o m ) ,
2018-04-28 17:56:03 -04:00
user = msg . user && JSON . stringify ( {
2018-05-18 02:10:00 -04:00
login : msg . user . login ,
displayName : msg . user . displayName ,
id : msg . user . id ,
type : msg . user . type
2019-05-07 15:04:12 -04:00
} ) ; * /
2018-04-28 17:56:03 -04:00
2019-05-16 14:51:51 -04:00
let out = null ;
if ( actions . length )
out = ( < div
class = "ffz--inline-actions ffz-action-data tw-inline-block tw-mg-r-05"
data - source = "line"
>
{ actions }
< / div > ) ;
2019-05-16 14:46:26 -04:00
if ( modified . length ) {
2019-05-16 14:51:51 -04:00
const modified _out = ( < div
class = "ffz--inline-actions ffz--modifier-actions ffz-action-data"
data - source = "line"
>
{ modified }
< / div > ) ;
if ( out )
return [ out , modified _out ] ;
return modified _out ;
2019-05-16 14:46:26 -04:00
}
return out ;
2018-04-28 17:56:03 -04:00
}
getData ( element ) {
const ds = element . dataset ,
2018-07-13 14:32:12 -04:00
parent = element . closest ( '.ffz-action-data' ) ,
2018-04-28 17:56:03 -04:00
pds = parent && parent . dataset ,
action = ds && ds . action ,
definition = this . actions [ action ] ;
if ( ! definition )
return null ;
2019-05-07 15:04:12 -04:00
let user , room , message , loaded = false ;
if ( pds ) {
2019-08-06 15:39:45 -04:00
if ( pds . source === 'msg' && parent . ffz _message ) {
const msg = parent . ffz _message ;
loaded = true ;
user = msg . user ? {
color : msg . user . color ,
id : msg . user . id ,
login : msg . user . login ,
displayName : msg . user . displayName ,
type : msg . user . type
} : null ;
room = {
login : msg . roomLogin ,
id : msg . roomId
} ;
message = {
id : msg . id ,
text : msg . message
} ;
} else if ( pds . source === 'line' ) {
2019-05-07 15:04:12 -04:00
const fine = this . resolve ( 'site.fine' ) ,
2019-05-08 15:49:43 -04:00
line = fine && fine . searchParent ( parent , n => n . props && n . props . message ) ;
2019-05-07 15:04:12 -04:00
if ( line && line . props && line . props . message ) {
loaded = true ;
const msg = line . props . message ;
user = msg . user ? {
color : msg . user . color ,
id : msg . user . id ,
login : msg . user . login ,
displayName : msg . user . displayName ,
type : msg . user . type
} : null ;
room = {
login : line . props . channelLogin ,
id : line . props . channelID
}
message = {
id : msg . id ,
text : msg . message
}
}
}
if ( ! loaded ) {
user = pds . user ? JSON . parse ( pds . user ) : null ;
room = pds . room ? JSON . parse ( pds . room ) : null ;
message = pds . message ? JSON . parse ( pds . message ) : pds . msgId ? { id : pds . msgId } : null ;
}
}
const data = {
action ,
definition ,
tip : ds . tip ,
options : ds . options ? JSON . parse ( ds . options ) : null ,
user ,
room ,
message ,
message _id : message ? message . id : null
} ;
2018-04-28 17:56:03 -04:00
if ( definition . defaults )
data . options = Object . assign ( { } , maybe _call ( definition . defaults , this , data , element ) , data . options ) ;
return data ;
}
handleClick ( event ) {
const target = event . target ,
data = this . getData ( target ) ;
if ( ! data )
return ;
2019-11-25 17:50:20 -05:00
if ( target . classList . contains ( 'disabled' ) )
return ;
2018-04-28 17:56:03 -04:00
if ( ! data . definition . click ) {
if ( data . definition . context )
return this . handleContext ( event ) ;
return this . log . warn ( ` No click handler for action provider " ${ data . action } " ` ) ;
}
if ( target . _ffz _tooltip$0 )
target . _ffz _tooltip$0 . hide ( ) ;
return data . definition . click . call ( this , event , data ) ;
}
handleContext ( event ) {
if ( event . shiftKey )
return ;
event . preventDefault ( ) ;
const target = event . target ,
data = this . getData ( event . target ) ;
if ( ! data )
return ;
2019-11-25 17:50:20 -05:00
if ( target . classList . contains ( 'disabled' ) )
return ;
2018-04-28 17:56:03 -04:00
if ( target . _ffz _tooltip$0 )
target . _ffz _tooltip$0 . hide ( ) ;
2019-04-29 18:14:04 -04:00
if ( ! data . definition . context && ! data . definition . uses _reason )
return ;
2019-08-06 15:39:45 -04:00
this . renderInlineContext ( target , data ) ;
2018-04-28 17:56:03 -04:00
}
2019-08-06 15:39:45 -04:00
handleUserContext ( event ) {
if ( event . shiftKey )
return ;
const actions = this . parent . context . get ( 'chat.actions.user-context' ) ;
if ( ! Array . isArray ( actions ) || ! actions . length )
return ;
event . preventDefault ( ) ;
const target = event . target ;
if ( target . _ffz _tooltip$0 )
target . _ffz _tooltip$0 . hide ( ) ;
this . renderUserContext ( target , actions ) ;
}
2018-04-28 17:56:03 -04:00
2019-06-27 23:19:05 -04:00
pasteMessage ( room , message ) {
return this . resolve ( 'site.chat.input' ) . pasteMessage ( room , message ) ;
}
2018-04-28 17:56:03 -04:00
sendMessage ( room , message ) {
return this . resolve ( 'site.chat' ) . sendMessage ( room , message ) ;
}
}