From 5a7a4f3ea9da0af542eb4f8612aacece6956459a Mon Sep 17 00:00:00 2001 From: SirStendec Date: Thu, 19 Jul 2018 22:03:01 -0400 Subject: [PATCH] 4.0.0-rc7 * Added: Initial support for Chat on Videos. Please note that moderation features are not currently available when FFZ features for Chat on Videos are enabled. There is a setting to disable custom FFZ rendering which makes moderation tools accessible. More work has also been done on getting custom viewer cards ready. --- res/font/ffz-fontello.eot | Bin 20676 -> 20812 bytes res/font/ffz-fontello.svg | 2 + res/font/ffz-fontello.ttf | Bin 20492 -> 20628 bytes res/font/ffz-fontello.woff | Bin 12784 -> 12876 bytes res/font/ffz-fontello.woff2 | Bin 10704 -> 10816 bytes src/main.js | 2 +- src/modules/chat/actions/index.jsx | 6 +- src/modules/chat/actions/types.jsx | 18 +- src/modules/chat/tokenizers.jsx | 4 +- .../main_menu/components/changelog.vue | 2 +- .../main_menu/components/chat-actions.vue | 18 +- src/modules/viewer_cards/components/main.vue | 174 +++++--- .../twitch-twilight/modules/chat/line.js | 4 +- .../styles/chat-borders-3d-inset.scss | 18 + .../css_tweaks/styles/chat-borders-3d.scss | 16 + .../css_tweaks/styles/chat-borders-wide.scss | 9 + .../css_tweaks/styles/chat-borders.scss | 9 + .../modules/css_tweaks/styles/chat-font.scss | 1 + .../styles/chat-mention-bg-alt.scss | 8 + .../css_tweaks/styles/chat-mention-bg.scss | 1 + .../modules/css_tweaks/styles/chat-rows.scss | 11 + .../modules/video_chat/index.jsx | 421 ++++++++++++++++++ src/sites/twitch-twilight/styles/chat.scss | 5 + src/utilities/time.js | 11 + styles/icons.scss | 1 + 25 files changed, 679 insertions(+), 62 deletions(-) create mode 100644 src/sites/twitch-twilight/modules/video_chat/index.jsx diff --git a/res/font/ffz-fontello.eot b/res/font/ffz-fontello.eot index d7b266ca6d8b22e47f2c86436656c1634a89b822..08c8521672dbfe102c44ca00ba7aea57801d6e5f 100644 GIT binary patch delta 884 zcmXw$T}V@59LAsboSivx`O!9~a~HGL2W&H&O-;9{Q<11d(>^39)|^h+++iD{AV|=a zkSM2%GH=QokuIdsU6;~DNm>y`1Vt1GVQ8dD`O*LEwfFEl&-*;@`E%a)|IR;T3y+v| zz74oLAysNx%uS0Eb@xVUFSTz1tQElJP%J24zjCgT{0txop@Ec5ySzOKG!E0cty}Ji zjZTbrlAi*y<(^919N0Y*w$3pCfPU55~gYhOTY$OMtK= z$CIgeKrjOJs-Kn$d{*Byyy2N^l$1K9f|I1H9F&p%;NTRnPTf<1o3xw*RRbJQ^VU~5@R0t_aDlFX zCR-d-lK$nO3iwGKRKXe2*Bn%nzTu#T^eqQ;)^tbK>d~9juw9STD@0*LVV%jCB`~mN zwcT+r)_u$^NCggqpOu%m3(S@IM(vRjmcvS!C;x%uuxdP3hs0dA?Umj4I!T~)q4g1|m*28lvp;;wZQoPH{rkR2l=7@Q|&|dhZXsqau#cjE2 zc~Wf6)cVq!{u+__6n9`yVE*)Mpq}L%48{8g!ii)s)f?|mD&HGEN*=L&*OOjo+{*q3 DkPqD0 delta 750 zcmYk3OH30{7=_O~S{vI0BGgLbq6?Hn!8Z?O084Asm5&l*gT&Ofv@sMWl#t>E46a-l z+93uFE_9`?FilswaKXX_3)BV@HF2X!W2_>fp^4IZ#v2mv%{OPxx%Z!&|IX4AY4NdS zS-c21(w260HaXCdK0P&_< zre}hHzaFsbu~1THUd#3GR9kFlV(`Pq6=v3Xpfw+lh9bN2_8g#@``Y3RY+kjAp5I4f zJTVqbt@x{-87wha8yX3RX8NaZ09~)>ZzV!0U3%s)fbI@@&u}Oa)qIgZ^j86SOCL#& zrCyd^176b``e;=D>OIKX3wMkKsb9(%4*C4qI@U@=3=2ge%|vCDN32P1rsl3@DqCi* z;s?E(4KgVlFy`e;&S8kDV|$Kb)R2`47F)Bo6N^2WQQr&LL}W*CUpCkBv!<_OAUS)zMk~Rt@JwJe5;?c6)l#LXZYlwK z8)P%(;H@f`t2cSwTh&r^THXBbZ-^b5QSQnaFS_qHrRoax51lsW`0hDZn=9j5-?Pwg tt>KLu?)~m5_c!r1XxZVOPDRp;=e? + + diff --git a/res/font/ffz-fontello.ttf b/res/font/ffz-fontello.ttf index 8703b423212a67b4de0eedd227f55b71a022f6e8..5683ac5a4baa36aaff530213c3a933e4e87fa5ab 100644 GIT binary patch delta 822 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 Of&(8dfeB8mKK}#LE)sA6 delta 10334 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 diff --git a/res/font/ffz-fontello.woff2 b/res/font/ffz-fontello.woff2 index e2f9132e21728267f14b426dade20157579ce041..049f24ac22bda0d290caa272bdee6059c0eed23a 100644 GIT binary patch literal 10816 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

X9N)#=Gi_Oh(?^d*7_7%)T;^CAoWcL*w~M$) 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 uvq<>45TaA%(x2uL;t>~+n*64> diff --git a/src/modules/chat/actions/index.jsx b/src/modules/chat/actions/index.jsx index afbfce7f..ee21a461 100644 --- a/src/modules/chat/actions/index.jsx +++ b/src/modules/chat/actions/index.jsx @@ -43,6 +43,7 @@ export default class Actions extends Module { ui: { path: 'Chat > In-Line Actions @{"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."}', component: 'chat-actions', + context: ['user', 'room'], inline: true, data: () => { @@ -60,11 +61,11 @@ export default class Actions extends Module { this.settings.add('chat.actions.viewer-card', { // Filter out actions process: (ctx, val) => - val.filter(x => x.appearance && + 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: [ {v: {action: 'friend'}}, @@ -80,6 +81,7 @@ export default class Actions extends Module { _ui: { path: 'Chat > Viewer Cards >> tabs ~> Actions @{"description": "Here, you define what actions are available on viewer cards."}', component: 'chat-actions', + context: ['user', 'room', 'product'], data: () => { const chat = this.resolve('site.chat'); diff --git a/src/modules/chat/actions/types.jsx b/src/modules/chat/actions/types.jsx index 87af4be3..d7f2296e 100644 --- a/src/modules/chat/actions/types.jsx +++ b/src/modules/chat/actions/types.jsx @@ -64,6 +64,8 @@ export const chat = { } }], + required_context: ['room'], + defaults: { command: '@{{user.login}} HeyGuys' }, @@ -121,6 +123,8 @@ export const ban = { } }], + required_context: ['room', 'user'], + editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-ban.vue'), title: 'Ban User', @@ -152,6 +156,8 @@ export const timeout = { duration: 600 }, + required_context: ['room', 'user'], + editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-timeout.vue'), title: 'Timeout User', @@ -187,6 +193,8 @@ export const unban = { } }], + required_context: ['room', 'user'], + title: 'Unban User', tooltip(data) { @@ -212,6 +220,8 @@ export const untimeout = { } }], + required_context: ['room', 'user'], + title: 'Untimeout User', tooltip(data) { @@ -236,6 +246,8 @@ export const whisper = { } }], + required_context: ['user'], + title: 'Whisper User', tooltip(data) { @@ -269,7 +281,7 @@ export const whisper = { // Gift Subscription // ============================================================================ -/*export const gift_sub = { +export const gift_sub = { presets: [{ appearance: { type: 'icon', @@ -277,6 +289,8 @@ export const whisper = { } }], + required_context: ['room', 'user', 'product'], + title: 'Gift Subscription', tooltip(data) { @@ -288,4 +302,4 @@ export const whisper = { Woop woop. ); } -}*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/modules/chat/tokenizers.jsx b/src/modules/chat/tokenizers.jsx index b6ae310a..b6beffda 100644 --- a/src/modules/chat/tokenizers.jsx +++ b/src/modules/chat/tokenizers.jsx @@ -789,8 +789,8 @@ export const AddonEmotes = { return tokens; const emotes = this.emotes.getEmotes( - msg.user.userID, - msg.user.userLogin, + msg.user.id, + msg.user.login, msg.roomID, msg.roomLogin ), diff --git a/src/modules/main_menu/components/changelog.vue b/src/modules/main_menu/components/changelog.vue index ca072e3e..17548e01 100644 --- a/src/modules/main_menu/components/changelog.vue +++ b/src/modules/main_menu/components/changelog.vue @@ -51,7 +51,7 @@ import {get} from 'utilities/object'; -const TITLE_MATCH = /^(\d+\.\d+\.\d+(?:\-[^\n]+)?)\n+/; +const TITLE_MATCH = /^(\d+\.\d+\.\d+(?:-[^\n]+)?)\n+/; export default { diff --git a/src/modules/main_menu/components/chat-actions.vue b/src/modules/main_menu/components/chat-actions.vue index 2f96c1ca..7847bcf1 100644 --- a/src/modules/main_menu/components/chat-actions.vue +++ b/src/modules/main_menu/components/chat-actions.vue @@ -245,7 +245,8 @@ export default { }, presets() { - const out = []; + const out = [], + contexts = this.item.context || []; out.push({ disabled: this.hasInheritance, @@ -282,7 +283,19 @@ export default { for(const key in this.data.actions) { // eslint-disable-line guard-for-in const act = this.data.actions[key]; - if ( act && act.presets ) + if ( act && act.presets ) { + if ( act.required_context ) { + let okay = true; + for(const ctx of act.required_context) + if ( ! contexts.includes(ctx) ) { + okay = false; + break; + } + + if ( ! okay ) + continue; + } + for(const preset of act.presets) { if ( typeof act.title !== 'string' && ! preset.title ) continue; @@ -298,6 +311,7 @@ export default { } }, preset)); } + } } return out; diff --git a/src/modules/viewer_cards/components/main.vue b/src/modules/viewer_cards/components/main.vue index d3b5df71..846a8a08 100644 --- a/src/modules/viewer_cards/components/main.vue +++ b/src/modules/viewer_cards/components/main.vue @@ -1,54 +1,17 @@ @@ -56,10 +19,119 @@ \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js index 9123b53d..040cb341 100644 --- a/src/sites/twitch-twilight/modules/chat/line.js +++ b/src/sites/twitch-twilight/modules/chat/line.js @@ -282,7 +282,7 @@ export default class ChatLine extends Module { bg_css = msg.mentioned && msg.mention_color ? t.parent.inverse_colors.process(msg.mention_color) : null; if ( ! this.ffz_user_click_handler ) - this.ffz_user_click_handler = this.usernameClickHandler; // event => ! event.ctrlKey ? this.usernameClickHandler(event) : t.viewer_cards.openCard(r, user, event); + this.ffz_user_click_handler = this.usernameClickHandler; //event => event.ctrlKey ? this.usernameClickHandler(event) : t.viewer_cards.openCard(r, user, event); let cls = `chat-line__message${show_class ? ' ffz--deleted-message' : ''}`, out = (tokens.length || ! msg.ffz_type) ? [ @@ -457,5 +457,7 @@ export default class ChatLine extends Module { this.ChatLine.forceUpdate(); this.ChatRoomLine.forceUpdate(); this.WhisperLine.forceUpdate(); + + this.emit('chat:updated-lines'); } } \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-3d-inset.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-3d-inset.scss index ac12ac76..07f45a32 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-3d-inset.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-3d-inset.scss @@ -2,6 +2,8 @@ .thread-message__timestamp, .thread-message__warning, +.vod-message, + .chat-line__message:not(.chat-line--inline), .chat-line__moderation, .chat-line__status, @@ -22,6 +24,22 @@ } } +.vod-message { + padding-top: calc(.5rem - 1px) !important; + + border-top: 1px solid #aaa; + border-bottom-color: var rgba(255,255,255,0.5); + + .tw-theme--dark & { + border-top-color: #000; + border-bottom-color: rgba(255,255,255,0.1); + } +} + +.video-chat__message-list-wrapper li:first-child .vod-message { + border-top-color: transparent !important; +} + .thread-message__message, .thread-message__timestamp, .thread-message__warning { diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-3d.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-3d.scss index ce400817..0d78fa6a 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-3d.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-3d.scss @@ -22,6 +22,22 @@ } } +.vod-message { + padding-top: calc(.5rem - 1px) !important; + + border-top: 1px solid rgba(255,255,255,0.5); + border-bottom-color: #aaa; + + .tw-theme--dark & { + border-top-color: rgba(255,255,255,0.1); + border-bottom-color: #000; + } +} + +.video-chat__message-list-wrapper li:first-child .vod-message { + border-top-color: transparent !important; +} + .thread-message__message, .thread-message__timestamp, .thread-message__warning { diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-wide.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-wide.scss index 27ecd26b..ef51346e 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-wide.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders-wide.scss @@ -15,6 +15,15 @@ } } +.vod-message { + padding-top: calc(.5rem - 1px) !important; + border-top: 1px solid var(--ffz-border-color); +} + +.video-chat__message-list-wrapper li:first-child .vod-message { + border-top-color: transparent !important; +} + .thread-message__message, .thread-message__timestamp, .thread-message__warning { diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders.scss index c6ac2e54..f439bf7d 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-borders.scss @@ -15,6 +15,15 @@ } } +.vod-message { + padding-bottom: calc(.5rem - 1px) !important; + border-bottom: 1px solid var(--ffz-border-color); +} + +.video-chat__message-list-wrapper li:last-child .vod-message { + border-bottom-color: transparent !important; +} + .thread-message__message, .thread-message__timestamp, .thread-message__warning { diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-font.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-font.scss index fe3bf8c1..e5612089 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-font.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-font.scss @@ -1,3 +1,4 @@ +.video-chat__message-list-wrapper, .whispers-thread__content, .chat-list { font-size: var(--ffz-chat-font-size); diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-bg-alt.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-bg-alt.scss index 741d99f1..528d8745 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-bg-alt.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-bg-alt.scss @@ -7,4 +7,12 @@ background-color: rgba(255,0,0,.3) !important; } } +} + +.video-chat__message-list-wrapper li:nth-child(2n+0) .vod-message.ffz-mentioned:not(.ffz-custom-color) { + background-color: rgba(255,127,127,.4) !important; + + .tw-theme--dark & { + background-color: rgba(255,0,0,.3) !important; + } } \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-bg.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-bg.scss index 7ce2602a..ea113526 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-bg.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-bg.scss @@ -1,3 +1,4 @@ +.vod-message, .chat-line__message:not(.chat-line--inline), .user-notice-line { &.ffz-mentioned:not(.ffz-custom-color) { diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-rows.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-rows.scss index d3ce8966..44d582b4 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-rows.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-rows.scss @@ -21,3 +21,14 @@ } } +.video-chat__message-list-wrapper li .vod-message:not(.ffz-custom-color) { + background-color: transparent !important; +} + +.video-chat__message-list-wrapper li:nth-child(2n+0) .vod-message:not(.ffz-custom-color) { + background-color: rgba(0,0,0,0.1) !important; + + .tw-theme--dark & { + background-color: rgba(255,255,255,0.05) !important; + } +} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/video_chat/index.jsx b/src/sites/twitch-twilight/modules/video_chat/index.jsx new file mode 100644 index 00000000..2505834d --- /dev/null +++ b/src/sites/twitch-twilight/modules/video_chat/index.jsx @@ -0,0 +1,421 @@ +'use strict'; + +// ============================================================================ +// Video Chat Hooks +// ============================================================================ + +import {has, get} from 'utilities/object'; +import {print_duration} from 'utilities/time'; +import {ClickOutside} from 'utilities/dom'; +import {formatBitsConfig} from '../chat'; + +import Module from 'utilities/module'; + + +export default class VideoChatHook extends Module { + constructor(...args) { + super(...args); + + this.should_enable = true; + + this.inject('i18n'); + this.inject('settings'); + + this.inject('site'); + this.inject('site.router'); + this.inject('site.fine'); + this.inject('site.web_munch'); + + this.inject('chat'); + this.injectAs('site_chat', 'site.chat'); + this.inject('site.chat.chat_line.rich_content'); + + this.VideoChatController = this.fine.define( + 'video-chat-controller', + n => n.onMessageScrollAreaMount && n.createReply, + ['video'] + ); + + this.VideoChatLine = this.fine.define( + 'video-chat-line', + n => n.onReplyClickHandler && n.shouldFocusMessage, + ['video'] + ); + + // Settings + + this.settings.add('chat.video-chat.enabled', { + default: true, + ui: { + path: 'Chat > Chat on Videos @{"description": "This feature is currently in beta. As such, you may experience issues when using FFZ features with Chat on Videos."} >> General', + title: 'Enable FrankerFaceZ features for Chat on Videos.', + description: 'Display FFZ badges, emotes, and other features in Chat on Videos. Moderation features may be unavailable when this is enabled.', + component: 'setting-check-box' + } + }); + } + + + async onEnable() { + this.chat.context.on('changed:chat.video-chat.enabled', this.updateLines, this); + this.on('chat:updated-lines', this.updateLines, this); + + this.VideoChatController.on('mount', this.chatMounted, this); + this.VideoChatController.on('unmount', this.chatUnmounted, this); + this.VideoChatController.on('receive-props', this.chatUpdated, this); + + this.VideoChatController.ready((cls, instances) => { + for(const inst of instances) { + this.chatMounted(inst); + } + }); + + const t = this, + React = await this.web_munch.findModule('react'); + if ( ! React ) + return; + + /*this.MessageMenu = class FFZMessageMenu extends React.Component { + constructor(props) { + super(props); + + this.onClick = () => this.setState({open: ! this.state.open}); + this.onClickOutside = () => this.state.open && this.setState({open: false}); + + this.element = null; + this.saveRef = element => this.element = element; + + this.state = { + open: false + } + } + + componentDidMount() { + if ( this.element ) + this._clicker = new ClickOutside(this.element, this.onClickOutside); + } + + componentWillUnmount() { + this._clicker.destroy(); + this._clicker = null; + } + + render() { + const is_open = this.state.open; + + return (

+
+ +
+
+
+
+
+ + +
+ +
+
+
+
) + } + }*/ + + const createElement = React.createElement, + FFZRichContent = this.rich_content && this.rich_content.RichContent; + + this.VideoChatLine.ready(cls => { + const old_render = cls.prototype.render; + + cls.prototype.ffzRenderMessage = function(msg) { + const is_action = msg.is_action, + user = msg.user, + color = t.site_chat.colors.process(user.color), + + tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, user), + rich_content = FFZRichContent && t.chat.pluckRichContent(tokens, msg); + + return (
+
+ { + t.chat.badges.render(msg, createElement) + } + + { user.displayName } + {user.isIntl && } + +
+ {is_action ? ' ' : ': '} + { t.chat.renderTokens(tokens, createElement) } + {rich_content && createElement(FFZRichContent, rich_content)} +
+
+
); + } + + cls.prototype.ffzRenderExpanded = function(msg) { + if ( ! msg._reply_handler ) + msg._reply_handler = () => this.onReplyClickHandler(msg.user.login); + + return (
+ + + • { t.i18n.t('video-chat.time', '%{time|humanTime} ago', { + time: msg.timestamp + }) } + + +
) + } + + cls.prototype.render = function() { + try { + if ( this.state.showReplyForm || ! t.chat.context.get('chat.video-chat.enabled') ) + return old_render.call(this); + + t.log.info('Video Chat', this); + + const context = this.props.messageContext, + msg = t.standardizeMessage(context.comment, context.author), + main_message = this.ffzRenderMessage(msg), + + bg_css = msg.mentioned && msg.mention_color ? t.site_chat.inverse_colors.process(msg.mention_color) : null; + + if ( msg.ffz_removed ) + return null; + + return (
+ {this.props.hideTimestamp || (
+
+
+ + +
+
+
)} +
+ { main_message } + { this.props.isExpandedLayout && this.ffzRenderExpanded(msg) } + { context.replies.length > 0 && (
+ { context.comment.moreReplies && (
+ +
)} +
    { + context.replies.map(reply => (
  • + { this.ffzRenderMessage(t.standardizeMessage(reply.comment, reply.author)) } + { this.props.isExpandedLayout && this.ffzRenderExpanded(msg) } +
  • )) + }
+
)} +
+
) + + } catch(err) { + t.log.error('Problem rendering Chat', err); + return old_render.call(this); + } + } + + // Do this after a short delay to hopefully reduce the chance of React + // freaking out on us. + setTimeout(() => this.VideoChatLine.forceUpdate()); + }) + } + + + updateLines() { + for(const inst of this.VideoChatLine.instances) { + const context = inst.props.messageContext; + if ( ! context.comment ) + continue; + + context.comment._ffz_message = null; + + if ( Array.isArray(context.replies) ) + for(const reply of context.replies) + if ( reply.comment ) + reply.comment._ffz_message = null; + } + + this.VideoChatLine.forceUpdate(); + } + + + // ======================================================================== + // Message Standardization + // ======================================================================== + + standardizeMessage(comment, author) { // eslint-disable-line class-methods-use-this + if ( comment._ffz_message ) + return comment._ffz_message; + + const room = this.chat.getRoom(comment.channelId, null, true, true); + + const out = comment._ffz_message = { + user: { + color: comment.message.userColor, + id: author.id, + login: author.name, + displayName: author.displayName, + isIntl: author.name && author.displayName && author.displayName.trim().toLowerCase() !== author.name, + type: author.type + }, + roomLogin: room && room.login, + roomID: room && room.id, + badges: comment.userBadges, + messageParts: comment.message.tokens, + is_action: comment.message.isAction, + more_replies: comment.moreReplies, + timestamp: comment.createdAt + }; + + this.chat.detokenizeMessage(out); + + return out; + } + + + // ======================================================================== + // Room Handling + // ======================================================================== + + addRoom(thing, props) { + if ( ! props ) + props = thing.props; + + const channel = get('data.video.owner', props); + if ( ! channel || ! channel.id ) + return null; + + const room = thing._ffz_room = this.chat.getRoom(channel.id, channel.login && channel.login.toLowerCase(), false, true); + room.ref(thing); + return room; + } + + + removeRoom(thing) { // eslint-disable-line class-methods-use-this + if ( ! thing._ffz_room ) + return; + + thing._ffz_room.unref(thing); + thing._ffz_room = null; + } + + + // ======================================================================== + // Video Chat Controller + // ======================================================================== + + chatMounted(chat, props) { + if ( ! props ) + props = chat.props; + + if ( ! this.addRoom(chat, props) ) + return; + + this.chat.badges.updateTwitchBadges(get('data.badges', props)); + + this.updateRoomBadges(chat, get('data.video.owner.broadcastBadges', props)); + this.updateRoomBitsConfig(chat, props.bitsConfig); + } + + + chatUpdated(chat, props) { + if ( get('data.video.owner.id', props) !== get('data.video.owner.id', chat.props) ) { + this.removeRoom(chat); + this.chatMounted(chat, props); + return; + } + + const new_badges = get('data.badges', props), + old_badges = get('data.badges', chat.props), + + new_room_badges = get('data.video.owner.broadcastBadges', props), + old_room_badges = get('data.video.owner.broadcastBadges', chat.props); + + if ( new_badges !== old_badges ) + this.chat.badges.updateTwitchBadges(new_badges); + + if ( new_room_badges !== old_room_badges ) + this.updateRoomBadges(chat, new_room_badges); + + if ( props.bitsConfig !== chat.props.bitsConfig ) + this.updateRoomBitsConfig(chat, props.bitsConfig); + + const channel = get('data.video.owner', props); + + this.settings.updateContext({ + moderator: props.isCurrentUserModerator + }); + + this.chat.context.updateContext({ + moderator: props.isCurrentUserModerator, + channel: channel ? channel.login : null, + channelID: channel ? channel.id : null + }); + } + + + chatUnmounted(chat) { + this.removeRoom(chat); + } + + + updateRoomBadges(chat, badges) { // eslint-disable-line class-methods-use-this + const room = chat._ffz_room; + if ( ! room ) + return; + + room.updateBadges(badges); + } + + + updateRoomBitsConfig(chat, config) { // eslint-disable-line class-methods-use-this + const room = chat._ffz_room; + if ( ! room ) + return; + + room.updateBitsConfig(formatBitsConfig(config)); + } +} + + +function formatDuration(seconds) { + +} \ No newline at end of file diff --git a/src/sites/twitch-twilight/styles/chat.scss b/src/sites/twitch-twilight/styles/chat.scss index 467e5a42..c91c661c 100644 --- a/src/sites/twitch-twilight/styles/chat.scss +++ b/src/sites/twitch-twilight/styles/chat.scss @@ -1,3 +1,8 @@ +.chat-line__message--emote { + vertical-align: middle; + margin: -.5rem 0; +} + .chat-author__display-name, .chat-author__intl-login { cursor: pointer; diff --git a/src/utilities/time.js b/src/utilities/time.js index 1f58767c..f5a6dd1e 100644 --- a/src/utilities/time.js +++ b/src/utilities/time.js @@ -21,4 +21,15 @@ export function duration_to_string(elapsed, separate_days, days_only, no_hours, (!no_hours || days || hours) ? `${days && hours < 10 ? '0' : ''}${hours}:` : '' }${minutes < 10 ? '0' : ''}${minutes}${ no_seconds ? '' : `:${seconds < 10 ? '0' : ''}${seconds}`}`; +} + + +export function print_duration(seconds) { + let minutes = Math.floor(seconds / 60), + hours = Math.floor(minutes / 60); + + minutes %= 60; + seconds %= 60; + + return `${hours > 0 ? `${hours}:${minutes < 10 ? '0' : ''}` : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`; } \ No newline at end of file diff --git a/styles/icons.scss b/styles/icons.scss index 91fcf5a9..36b89ba9 100644 --- a/styles/icons.scss +++ b/styles/icons.scss @@ -105,6 +105,7 @@ .ffz-i-views:before { content: '\e828'; } /* '' */ .ffz-i-eye:before { content: '\e829'; } /* '' */ .ffz-i-eye-off:before { content: '\e82a'; } /* '' */ +.ffz-i-conversations:before { content: '\e82b'; } /* '' */ .ffz-i-link-ext:before { content: '\f08e'; } /* '' */ .ffz-i-twitter:before { content: '\f099'; } /* '' */ .ffz-i-gauge:before { content: '\f0e4'; } /* '' */