diff --git a/res/font/ffz-fontello.eot b/res/font/ffz-fontello.eot index d7b266ca..08c85216 100644 Binary files a/res/font/ffz-fontello.eot and b/res/font/ffz-fontello.eot differ diff --git a/res/font/ffz-fontello.svg b/res/font/ffz-fontello.svg index c7cb66df..74bcef43 100644 --- a/res/font/ffz-fontello.svg +++ b/res/font/ffz-fontello.svg @@ -92,6 +92,8 @@ + + diff --git a/res/font/ffz-fontello.ttf b/res/font/ffz-fontello.ttf index 8703b423..5683ac5a 100644 Binary files a/res/font/ffz-fontello.ttf and b/res/font/ffz-fontello.ttf differ diff --git a/res/font/ffz-fontello.woff b/res/font/ffz-fontello.woff index e2199aa4..d5dfe516 100644 Binary files a/res/font/ffz-fontello.woff and b/res/font/ffz-fontello.woff differ diff --git a/res/font/ffz-fontello.woff2 b/res/font/ffz-fontello.woff2 index e2f9132e..049f24ac 100644 Binary files a/res/font/ffz-fontello.woff2 and b/res/font/ffz-fontello.woff2 differ diff --git a/src/main.js b/src/main.js index 67ae3767..b189da25 100644 --- a/src/main.js +++ b/src/main.js @@ -100,7 +100,7 @@ class FrankerFaceZ extends Module { FrankerFaceZ.Logger = Logger; const VER = FrankerFaceZ.version_info = { - major: 4, minor: 0, revision: 0, extra: '-rc6.4.1', + major: 4, minor: 0, revision: 0, extra: '-rc7', commit: __git_commit__, build: __webpack_hash__, toString: () => 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'; } /* '' */