mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.20.28
* Added: Support for Twitch's replies and threads system. The experiment is currently disabled, but if it returns we want to support it. * Added: Option to automatically skip channel trailers. * Fixed: Incorrect appearance of aspect-ratio controlled elements, due to Twitch removing their aspect ratio CSS. * Fixed: Incorrect color applied to text buttons with a custom accent color set. * API Added: `chat:get-tab-commands` event for adding custom chat commands to tab-completion. * API Added: `reply` icon.
This commit is contained in:
parent
6c0c421d0a
commit
463c9f9a45
30 changed files with 536 additions and 40 deletions
|
@ -753,6 +753,12 @@
|
|||
"css": "volume-off",
|
||||
"code": 59461,
|
||||
"src": "elusive"
|
||||
},
|
||||
{
|
||||
"uid": "c6be5a58ee4e63a5ec399c2b0d15cf2c",
|
||||
"css": "reply",
|
||||
"code": 61714,
|
||||
"src": "fontawesome"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.20.27",
|
||||
"version": "4.20.28",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
|
|
Binary file not shown.
|
@ -166,6 +166,8 @@
|
|||
|
||||
<glyph glyph-name="upload-cloud" unicode="" d="M714 368q0 8-5 13l-196 196q-5 5-13 5t-13-5l-196-196q-5-6-5-13 0-8 5-13t13-5h125v-196q0-8 5-13t12-5h108q7 0 12 5t5 13v196h125q8 0 13 5t5 13z m357-161q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 72 39 134t105 92q-1 17-1 24 0 118 84 202t202 84q87 0 159-49t105-129q40 35 93 35 59 0 101-42t42-101q0-43-23-77 72-17 119-76t46-133z" horiz-adv-x="1071.4" />
|
||||
|
||||
<glyph glyph-name="reply" unicode="" d="M1000 225q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="smile" unicode="" d="M633 250q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="keyboard" unicode="" d="M214 198v-53q0-9-9-9h-53q-9 0-9 9v53q0 9 9 9h53q9 0 9-9z m72 143v-53q0-9-9-9h-125q-9 0-9 9v53q0 9 9 9h125q9 0 9-9z m-72 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m572-286v-53q0-9-9-9h-482q-9 0-9 9v53q0 9 9 9h482q9 0 9-9z m-357 143v-53q0-9-9-9h-54q-9 0-9 9v53q0 9 9 9h54q9 0 9-9z m-72 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m214-143v-53q0-9-8-9h-54q-9 0-9 9v53q0 9 9 9h54q8 0 8-9z m-71 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m214-143v-53q0-9-9-9h-53q-9 0-9 9v53q0 9 9 9h53q9 0 9-9z m215-143v-53q0-9-9-9h-54q-9 0-9 9v53q0 9 9 9h54q9 0 9-9z m-286 286v-54q0-9-9-9h-54q-9 0-9 9v54q0 9 9 9h54q9 0 9-9z m143 0v-54q0-9-9-9h-54q-9 0-9 9v54q0 9 9 9h54q9 0 9-9z m143 0v-196q0-9-9-9h-125q-9 0-9 9v53q0 9 9 9h62v134q0 9 9 9h54q9 0 9-9z m71-420v500h-929v-500h929z m71 500v-500q0-29-20-50t-51-21h-929q-29 0-50 21t-21 50v500q0 30 21 51t50 21h929q30 0 51-21t20-51z" horiz-adv-x="1071.4" />
|
||||
|
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 42 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -14,6 +14,7 @@ import AddonManager from './addons';
|
|||
import ExperimentManager from './experiments';
|
||||
import {TranslationManager} from './i18n';
|
||||
import SocketClient from './socket';
|
||||
//import PubSubClient from './pubsub';
|
||||
import Site from 'site';
|
||||
import Vue from 'utilities/vue';
|
||||
//import Timing from 'utilities/timing';
|
||||
|
@ -56,6 +57,7 @@ class FrankerFaceZ extends Module {
|
|||
this.inject('experiments', ExperimentManager);
|
||||
this.inject('i18n', TranslationManager);
|
||||
this.inject('socket', SocketClient);
|
||||
//this.inject('pubsub', PubSubClient);
|
||||
this.inject('site', Site);
|
||||
this.inject('addons', AddonManager);
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ export default class Actions extends Module {
|
|||
),
|
||||
|
||||
default: [
|
||||
{v: {action: 'reply', appearance: {type: 'icon', icon: 'ffz-i-reply'}, options: {}, display: {}}},
|
||||
{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}}},
|
||||
{v: {action: 'timeout', appearance: {type: 'icon', icon: 'ffz-i-clock'}, display: {mod: true, mod_icons: true}}},
|
||||
|
@ -418,6 +419,9 @@ export default class Actions extends Module {
|
|||
(disp.followersOnly != null && disp.followersOnly !== current_room.followersOnly) )
|
||||
continue;
|
||||
|
||||
if ( maybe_call(act.hidden, this, data, null, current_room, current_user, mod_icons) )
|
||||
continue;
|
||||
|
||||
if ( act.override_appearance ) {
|
||||
const out = act.override_appearance.call(this, Object.assign({}, ap), data, null, current_room, current_user, mod_icons);
|
||||
if ( out )
|
||||
|
@ -539,6 +543,9 @@ export default class Actions extends Module {
|
|||
(disp.deleted != null && disp.deleted !== !!msg.deleted) )
|
||||
continue;
|
||||
|
||||
if ( maybe_call(act.hidden, this, data, msg, r, u, mod_icons) )
|
||||
continue;
|
||||
|
||||
if ( act.override_appearance ) {
|
||||
const out = act.override_appearance.call(this, Object.assign({}, ap), data, msg, r, u, mod_icons);
|
||||
if ( out )
|
||||
|
@ -600,11 +607,9 @@ export default class Actions extends Module {
|
|||
renderInline(msg, mod_icons, current_user, current_room, createElement) {
|
||||
const actions = [];
|
||||
|
||||
if ( msg.user && current_user && current_user.login === msg.user.login )
|
||||
return;
|
||||
|
||||
const current_level = this.getUserLevel(current_room, current_user),
|
||||
msg_level = this.getUserLevel(current_room, msg.user);
|
||||
msg_level = this.getUserLevel(current_room, msg.user),
|
||||
is_self = msg.user && current_user && current_user.login === msg.user.login;
|
||||
|
||||
if ( current_level < 3 )
|
||||
mod_icons = false;
|
||||
|
@ -630,6 +635,12 @@ export default class Actions extends Module {
|
|||
(disp.deleted != null && disp.deleted !== !!msg.deleted) )
|
||||
continue;
|
||||
|
||||
if ( is_self && ! act.can_self )
|
||||
continue;
|
||||
|
||||
if ( maybe_call(act.hidden, this, data, msg, current_room, current_user, mod_icons) )
|
||||
continue;
|
||||
|
||||
if ( act.override_appearance ) {
|
||||
const out = act.override_appearance.call(this, Object.assign({}, ap), data, msg, current_room, current_user, mod_icons);
|
||||
if ( out )
|
||||
|
|
|
@ -1,5 +1,54 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Send Reply
|
||||
// ============================================================================
|
||||
|
||||
export const reply = {
|
||||
presets: [{
|
||||
appearance: {
|
||||
type: 'icon',
|
||||
icon: 'ffz-i-reply'
|
||||
}
|
||||
}],
|
||||
|
||||
required_context: ['message'],
|
||||
|
||||
title: 'Reply to Message',
|
||||
description: 'Allows you to directly reply to another user\'s message.',
|
||||
|
||||
can_self: true,
|
||||
|
||||
tooltip() {
|
||||
return this.i18n.t('chat.actions.reply', 'Reply to Message')
|
||||
},
|
||||
|
||||
hidden(data, message, current_room, current_user) {
|
||||
const id = message?.id;
|
||||
if ( typeof id !== 'string' || ! /^[0-9a-f]+-[0-9a-f]+/.test(id) )
|
||||
return true;
|
||||
|
||||
if ( ! message.message || message.deleted || (current_user && current_user.login === message.user?.login) || ! current_user?.can_reply )
|
||||
return true;
|
||||
|
||||
if ( message?.reply )
|
||||
return true;
|
||||
},
|
||||
|
||||
click(event) {
|
||||
const fine = this.resolve('site.fine'),
|
||||
line = fine ? fine.searchParent(event.target, n => n.setMessageTray && n.props && n.props.message) : null;
|
||||
|
||||
if ( ! line )
|
||||
return;
|
||||
|
||||
line.ffzOpenReply();
|
||||
//line.setMessageTray(line.props.message, line.props.message.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Edit Overrides
|
||||
// ============================================================================
|
||||
|
|
|
@ -50,6 +50,7 @@ export default class Chat extends Module {
|
|||
// Bind for JSX stuff
|
||||
this.clickToReveal = this.clickToReveal.bind(this);
|
||||
this.handleMentionClick = this.handleMentionClick.bind(this);
|
||||
this.handleReplyClick = this.handleReplyClick.bind(this);
|
||||
|
||||
this.style = new ManagedStyle;
|
||||
|
||||
|
@ -1073,6 +1074,19 @@ export default class Chat extends Module {
|
|||
}
|
||||
|
||||
|
||||
handleReplyClick(event) {
|
||||
const target = event.target,
|
||||
fine = this.resolve('site.fine');
|
||||
|
||||
if ( ! target || ! fine )
|
||||
return;
|
||||
|
||||
const chat = fine.searchParent(target, n => n.props && n.props.reply && n.setOPCardTray);
|
||||
if ( chat )
|
||||
chat.setOPCardTray(chat.props.reply);
|
||||
}
|
||||
|
||||
|
||||
handleMentionClick(event) {
|
||||
if ( ! this.context.get('chat.filtering.clickable-mentions') )
|
||||
return;
|
||||
|
@ -1087,7 +1101,7 @@ export default class Chat extends Module {
|
|||
if ( ! fine )
|
||||
return;
|
||||
|
||||
const chat = fine.searchParent(event.target, n => n.props && n.props.onUsernameClick);
|
||||
const chat = fine.searchParent(target, n => n.props && n.props.onUsernameClick);
|
||||
if ( ! chat )
|
||||
return;
|
||||
|
||||
|
@ -1174,6 +1188,25 @@ export default class Chat extends Module {
|
|||
}
|
||||
|
||||
|
||||
tokenizeReply(reply) {
|
||||
if ( ! reply )
|
||||
return null;
|
||||
|
||||
return [
|
||||
{
|
||||
type: 'reply',
|
||||
text: reply.parentDisplayName,
|
||||
color: this.color_cache ? this.color_cache.get(reply.parentUserLogin) : null,
|
||||
recipient: reply.parentUserLogin
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' '
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
standardizeMessage(msg) { // eslint-disable-line class-methods-use-this
|
||||
if ( ! msg )
|
||||
return msg;
|
||||
|
@ -1487,7 +1520,7 @@ export default class Chat extends Module {
|
|||
}
|
||||
|
||||
|
||||
renderTokens(tokens, e) {
|
||||
renderTokens(tokens, e, reply) {
|
||||
if ( ! e )
|
||||
e = createElement;
|
||||
|
||||
|
@ -1505,6 +1538,10 @@ export default class Chat extends Module {
|
|||
|
||||
let res;
|
||||
|
||||
// If we have a reply, skip the initial mention.
|
||||
if ( reply && i === 0 && type === 'mention' && token.recipient && token.recipient === reply.parentUserLogin )
|
||||
continue;
|
||||
|
||||
if ( type === 'text' )
|
||||
res = e('span', {
|
||||
className: 'text-fragment',
|
||||
|
@ -1512,7 +1549,7 @@ export default class Chat extends Module {
|
|||
}, token.text);
|
||||
|
||||
else if ( tk )
|
||||
res = tk.render.call(this, token, e);
|
||||
res = tk.render.call(this, token, e, reply);
|
||||
|
||||
else
|
||||
res = e('em', {
|
||||
|
|
|
@ -232,6 +232,61 @@ Links.tooltip.delayHide = function(target) {
|
|||
}*/
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Replies (Styled Like Mentions)
|
||||
// ============================================================================
|
||||
|
||||
export const Replies = {
|
||||
type: 'reply',
|
||||
priority: 0,
|
||||
|
||||
component: () => null,
|
||||
|
||||
render(token, createElement) {
|
||||
let color = token.color;
|
||||
if ( color ) {
|
||||
const chat = this.resolve('site.chat');
|
||||
color = chat ? chat.colors.process(color) : color;
|
||||
}
|
||||
|
||||
return (<strong
|
||||
class={`chat-line__message-mention ffz-tooltip ffz--reply-mention ffz-i-reply${token.me ? ' ffz--mention-me' : ''}`}
|
||||
style={{color}}
|
||||
data-tooltip-type="reply"
|
||||
data-login={token.recipient}
|
||||
onClick={this.handleReplyClick}
|
||||
>
|
||||
{token.text}
|
||||
</strong>)
|
||||
},
|
||||
|
||||
tooltip(target) {
|
||||
const fine = this.resolve('site.fine');
|
||||
if ( ! target || ! fine )
|
||||
return null;
|
||||
|
||||
const chat = fine.searchParent(target, n => n.props && n.props.reply && n.setOPCardTray),
|
||||
reply = chat?.props?.reply;
|
||||
if ( ! reply )
|
||||
return null;
|
||||
|
||||
return [
|
||||
createElement('strong', {}, this.i18n.t('chat.reply-to', 'Replying To:')),
|
||||
'\n\n',
|
||||
createElement('div', {className: 'tw-align-left'}, [
|
||||
createElement('strong', {}, reply.parentDisplayName),
|
||||
': ',
|
||||
reply.parentMessageBody
|
||||
])
|
||||
];
|
||||
},
|
||||
|
||||
process(tokens) {
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Mentions
|
||||
// ============================================================================
|
||||
|
|
|
@ -46,6 +46,20 @@ export default class Channel extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.settings.add('channel.auto-skip-trailer', {
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Channel > Behavior >> General',
|
||||
title: 'Automatically skip channel trailers.',
|
||||
component: 'setting-check-box'
|
||||
},
|
||||
|
||||
changed: val => {
|
||||
if ( val )
|
||||
this.ChannelTrailer.each(el => this.maybeSkipTrailer(el));
|
||||
}
|
||||
})
|
||||
|
||||
this.settings.add('channel.auto-click-chat', {
|
||||
default: false,
|
||||
ui: {
|
||||
|
@ -83,6 +97,13 @@ export default class Channel extends Module {
|
|||
);
|
||||
|
||||
|
||||
this.ChannelTrailer = this.elemental.define(
|
||||
'channel-trailer', '.channel-trailer-player__wrapper',
|
||||
USER_PAGES,
|
||||
{attributes: true}, 1
|
||||
);
|
||||
|
||||
|
||||
this.ChannelRoot = this.elemental.define(
|
||||
'channel-root', '.channel-root',
|
||||
USER_PAGES,
|
||||
|
@ -115,6 +136,10 @@ export default class Channel extends Module {
|
|||
|
||||
this.on('i18n:update', this.updateLinks, this);
|
||||
|
||||
this.ChannelTrailer.on('mount', this.maybeSkipTrailer, this);
|
||||
this.ChannelTrailer.on('update', this.maybeSkipTrailer, this);
|
||||
this.ChannelTrailer.each(el => this.maybeSkipTrailer(el));
|
||||
|
||||
this.ChannelPanels.on('mount', this.updatePanelTips, this);
|
||||
this.ChannelPanels.on('update', this.updatePanelTips, this);
|
||||
this.ChannelPanels.on('unmount', this.removePanelTips, this);
|
||||
|
@ -139,6 +164,17 @@ export default class Channel extends Module {
|
|||
this.checkNavigation();
|
||||
}
|
||||
|
||||
maybeSkipTrailer(el) {
|
||||
if ( ! this.settings.get('channel.auto-skip-trailer') )
|
||||
return;
|
||||
|
||||
const inst = this.fine.searchParent(el, n => n.props && n.props.onDismiss);
|
||||
if ( inst ) {
|
||||
this.log.info('Automatically skipping channel trailer.');
|
||||
inst.props.onDismiss();
|
||||
}
|
||||
}
|
||||
|
||||
updatePanelTips(inst) {
|
||||
if ( ! inst ) {
|
||||
for(const inst of this.ChannelPanels.instances) {
|
||||
|
|
|
@ -1682,9 +1682,11 @@ export default class ChatHook extends Module {
|
|||
this._ffz_installed = true;
|
||||
|
||||
const inst = this,
|
||||
old_send = this.sendMessage;
|
||||
old_send = this.sendMessage,
|
||||
addMessage = (...args) => inst.addMessage(...args),
|
||||
sendMessage = (msg, extra) => inst.sendMessage(msg, extra);
|
||||
|
||||
inst.sendMessage = function(msg) {
|
||||
inst.sendMessage = function(msg, extra) {
|
||||
msg = msg.replace(/\s+/g, ' ');
|
||||
|
||||
if ( msg.startsWith('/ffz') ) {
|
||||
|
@ -1698,7 +1700,10 @@ export default class ChatHook extends Module {
|
|||
|
||||
const event = new FFZEvent({
|
||||
message: msg,
|
||||
channel: inst.props.channelLogin
|
||||
extra,
|
||||
channel: inst.props.channelLogin,
|
||||
addMessage,
|
||||
sendMessage
|
||||
});
|
||||
|
||||
t.emit('chat:pre-send-message', event);
|
||||
|
@ -1706,7 +1711,7 @@ export default class ChatHook extends Module {
|
|||
if ( event.defaultPrevented )
|
||||
return;
|
||||
|
||||
return old_send.call(this, event.message);
|
||||
return old_send.call(this, event.message, event.extra);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
|
||||
import Module from 'utilities/module';
|
||||
import { findReactFragment } from 'utilities/dom';
|
||||
import { TWITCH_POINTS_SETS, TWITCH_GLOBAL_SETS, TWITCH_PRIME_SETS, KNOWN_CODES, REPLACEMENTS, REPLACEMENT_BASE, TWITCH_EMOTE_BASE } from 'utilities/constants';
|
||||
import { TWITCH_POINTS_SETS, TWITCH_GLOBAL_SETS, TWITCH_PRIME_SETS, KNOWN_CODES, REPLACEMENTS, REPLACEMENT_BASE, TWITCH_EMOTE_BASE, KEYS } from 'utilities/constants';
|
||||
|
||||
import Twilight from 'site';
|
||||
import { FFZEvent } from 'src/utilities/events';
|
||||
|
||||
export default class Input extends Module {
|
||||
constructor(...args) {
|
||||
|
@ -105,6 +106,12 @@ export default class Input extends Module {
|
|||
Twilight.CHAT_ROUTES
|
||||
);
|
||||
|
||||
this.CommandSuggestions = this.fine.define(
|
||||
'tab-cmd-suggestions',
|
||||
n => n && n.getMatches && n.doesCommandMatchTerm,
|
||||
Twilight.CHAT_ROUTES
|
||||
);
|
||||
|
||||
// Implement Twitch's unfinished emote usage object for prioritizing sorting
|
||||
this.EmoteUsageCount = {
|
||||
TriHard: 196568036,
|
||||
|
@ -220,10 +227,16 @@ export default class Input extends Module {
|
|||
this.overrideMentionMatcher(inst);
|
||||
});
|
||||
|
||||
this.CommandSuggestions.ready((cls, instances) => {
|
||||
for(const inst of instances)
|
||||
this.overrideCommandMatcher(inst);
|
||||
});
|
||||
|
||||
this.ChatInput.on('update', this.updateEmoteCompletion, this);
|
||||
this.ChatInput.on('mount', this.overrideChatInput, this);
|
||||
this.EmoteSuggestions.on('mount', this.overrideEmoteMatcher, this);
|
||||
this.MentionSuggestions.on('mount', this.overrideMentionMatcher, this);
|
||||
this.CommandSuggestions.on('mount', this.overrideCommandMatcher, this);
|
||||
|
||||
this.on('chat.emotes:change-hidden', this.uncacheTabCompletion, this);
|
||||
this.on('chat.emotes:change-set-hidden', this.uncacheTabCompletion, this);
|
||||
|
@ -311,6 +324,8 @@ export default class Input extends Module {
|
|||
|
||||
inst.onKeyDown = function(event) {
|
||||
try {
|
||||
const code = event.charCode || event.keyCode;
|
||||
|
||||
if ( inst.onEmotePickerToggle && t.chat.context.get('chat.emote-menu.shortcut') && event.key === 'e' && event.ctrlKey && ! event.altKey && ! event.shiftKey ) {
|
||||
inst.onEmotePickerToggle();
|
||||
event.preventDefault();
|
||||
|
@ -318,8 +333,6 @@ export default class Input extends Module {
|
|||
}
|
||||
|
||||
if ( inst.autocompleteInputRef && t.chat.context.get('chat.mru.enabled') && ! event.shiftKey && ! event.ctrlKey && ! event.altKey ) {
|
||||
const code = event.charCode || event.keyCode;
|
||||
|
||||
// Arrow Up
|
||||
if ( code === 38 && inst.chatInputRef.selectionStart === 0 ) {
|
||||
if ( ! inst.messageHistory.length )
|
||||
|
@ -353,6 +366,14 @@ export default class Input extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
// Let users close stuff with Escape.
|
||||
if ( code === KEYS.Escape && ! event.shiftKey && ! event.ctrlKey && ! event.altKey ) {
|
||||
if ( inst.props.isShowingEmotePicker )
|
||||
inst.props.closeEmotePicker();
|
||||
else if ( inst.props.tray && (! inst.state.value || ! inst.state.value.length) )
|
||||
inst.closeTray();
|
||||
}
|
||||
|
||||
} catch(err) {
|
||||
t.log.capture(err);
|
||||
t.log.error(err);
|
||||
|
@ -383,10 +404,64 @@ export default class Input extends Module {
|
|||
|
||||
|
||||
overrideMentionMatcher(inst) {
|
||||
inst.canBeTriggeredByTab = !this.chat.context.get('chat.tab-complete.emotes-without-colon');
|
||||
}
|
||||
|
||||
|
||||
overrideCommandMatcher(inst) {
|
||||
if ( inst._ffz_override )
|
||||
return;
|
||||
|
||||
inst.canBeTriggeredByTab = !this.chat.context.get('chat.tab-complete.emotes-without-colon');
|
||||
inst._ffz_override = true;
|
||||
inst.oldCommands = inst.getCommands;
|
||||
|
||||
const t = this;
|
||||
|
||||
inst.getCommands = function(input) { try {
|
||||
const commands = inst.props.getCommands(inst.props.permissionLevel, {
|
||||
isEditor: inst.props.isCurrentUserEditor
|
||||
});
|
||||
|
||||
const event = new FFZEvent({
|
||||
input,
|
||||
permissionLevel: inst.props.permissionLevel,
|
||||
isEditor: inst.props.isCurrentUserEditor,
|
||||
commands
|
||||
});
|
||||
|
||||
t.emit('chat:get-tab-commands', event);
|
||||
|
||||
if ( ! commands || ! commands.length )
|
||||
return null;
|
||||
|
||||
// Trim off the starting /
|
||||
const i = input.slice(1);
|
||||
|
||||
const sorted = commands.filter(cmd => inst.doesCommandMatchTerm(cmd, i)).sort(inst.sortCommands);
|
||||
const out = [];
|
||||
for(const cmd of sorted) {
|
||||
const arg = cmd.commandArgs?.[0];
|
||||
let selection;
|
||||
if ( arg?.isRequired )
|
||||
selection = `[${arg.name}]`;
|
||||
|
||||
out.push({
|
||||
current: input,
|
||||
replacement: inst.determineReplacement(cmd),
|
||||
element: inst.renderCommandSuggestion(cmd, i),
|
||||
group: cmd.ffz_group ?
|
||||
(Array.isArray(cmd.ffz_group) ? t.i18n.t(...cmd.ffz_group) : cmd.ffz_group)
|
||||
: inst.determineGroup(cmd),
|
||||
selection
|
||||
});
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
return inst.oldCommands(input);
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ export default class ChatLine extends Module {
|
|||
this.inject('site.fine');
|
||||
this.inject('site.web_munch');
|
||||
this.inject(RichContent);
|
||||
this.inject('experiments');
|
||||
|
||||
this.inject('chat.actions');
|
||||
this.inject('chat.overrides');
|
||||
|
@ -83,6 +84,85 @@ export default class ChatLine extends Module {
|
|||
this.chat.context.on('changed:chat.filtering.highlight-basic-users-blocked--regex', this.updateLines, this);
|
||||
this.chat.context.on('changed:chat.filtering.highlight-basic-badges-blocked--list', this.updateLines, this);
|
||||
|
||||
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.')
|
||||
});
|
||||
});
|
||||
|
||||
const t = this,
|
||||
React = await this.web_munch.findModule('react');
|
||||
if ( ! React )
|
||||
|
@ -160,6 +240,78 @@ export default class ChatLine extends Module {
|
|||
props.showTimestamps !== this.props.showTimestamps;
|
||||
}
|
||||
|
||||
cls.prototype.ffzOpenReply = function() {
|
||||
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;
|
||||
}
|
||||
|
||||
const user_block = [
|
||||
e('span', {
|
||||
className: 'chat-author__display-name'
|
||||
}, user.displayName),
|
||||
user.isIntl && e('span', {
|
||||
className: 'chat-author__intl-login'
|
||||
}, ` (${user.login})`)
|
||||
];
|
||||
|
||||
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', {
|
||||
className: `chat-line__username notranslate${override_name ? ' ffz--name-override tw-relative tw-tooltip-wrapper' : ''}`,
|
||||
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', {
|
||||
className: 'tw-tooltip tw-tooltip--down tw-tooltip--align-center'
|
||||
}, user_block)
|
||||
] : user_block)
|
||||
]);
|
||||
}
|
||||
|
||||
cls.prototype.render = function() { try {
|
||||
this._ffz_no_scan = true;
|
||||
|
||||
|
@ -168,6 +320,7 @@ export default class ChatLine extends Module {
|
|||
override_mode = t.chat.context.get('chat.filtering.display-deleted'),
|
||||
|
||||
msg = t.chat.standardizeMessage(this.props.message),
|
||||
reply_tokens = msg.ffz_reply = msg.ffz_reply || t.chat.tokenizeReply(this.props.reply),
|
||||
is_action = msg.messageType === types.Action,
|
||||
|
||||
user = msg.user,
|
||||
|
@ -258,12 +411,15 @@ other {# messages were deleted by a moderator.}
|
|||
//if ( ! msg.message && msg.messageParts )
|
||||
// t.chat.detokenizeMessage(msg);
|
||||
|
||||
const has_replies = this.chatRepliesTreatment ? this.chatRepliesTreatment !== 'control' : false;
|
||||
|
||||
const u = t.site.getUser(),
|
||||
r = {id: room_id, login: room};
|
||||
|
||||
if ( u ) {
|
||||
u.moderator = this.props.isCurrentUserModerator;
|
||||
u.staff = this.props.isCurrentUserStaff;
|
||||
u.can_reply = has_replies && u.login !== msg.user?.login && ! msg.deleted && ! this.props.disableReplyClick
|
||||
}
|
||||
|
||||
const tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u, r),
|
||||
|
@ -317,7 +473,6 @@ other {# messages were deleted by a moderator.}
|
|||
|
||||
const override_name = t.overrides.getName(user.id);
|
||||
|
||||
|
||||
let cls = `chat-line__message${show_class ? ' ffz--deleted-message' : ''}`,
|
||||
out = (tokens.length || ! msg.ffz_type) ? [
|
||||
this.props.showTimestamps && e('span', {
|
||||
|
@ -342,11 +497,14 @@ other {# messages were deleted by a moderator.}
|
|||
}, user_block)
|
||||
] : user_block),
|
||||
e('span', null, is_action ? ' ' : ': '),
|
||||
show && has_replies && reply_tokens ?
|
||||
t.chat.renderTokens(reply_tokens, e)
|
||||
: null,
|
||||
show ?
|
||||
e('span', {
|
||||
className:'message',
|
||||
style: is_action ? { color } : null
|
||||
}, t.chat.renderTokens(tokens, e))
|
||||
}, t.chat.renderTokens(tokens, e, has_replies ? this.props.reply : null))
|
||||
:
|
||||
e('span', {
|
||||
className: 'chat-line__message--deleted',
|
||||
|
|
|
@ -21,7 +21,7 @@ const COLORS = [
|
|||
|
||||
|
||||
const ACCENT_COLORS = {
|
||||
dark: {'c':{'accent': 9,'background-accent':8,'background-accent-alt':7,'background-accent-alt-2':6,'background-button':7,'background-button-active':7,'background-button-focus':8,'background-button-hover':8,'background-button-primary-active':7,'background-button-primary-default':9,'background-button-primary-hover':8,'background-graph':2,'background-graph-fill':8,'background-input-checkbox-checked':9,'background-input-checked':8,'background-interactable-active':9,'background-interactable-hover':8,'background-progress-countdown-status':9,'background-progress-status':9,'background-range-fill':9,'background-subscriber-stream-tag-active':4,'background-subscriber-stream-tag-default':4,'background-subscriber-stream-tag-hover':3,'background-toggle-checked':9,/*'background-tooltip':1,*/'background-top-nav':6,'border-brand':9,'border-button':7,'border-button-active':8,'border-button-focus':9,'border-button-hover':8,'border-input-checkbox-checked':9,'border-input-checkbox-focus':9,'border-input-focus':9,'border-interactable-selected':10,'border-subscriber-stream-tag':5,'border-tab-active':11,'border-tab-focus':11,'border-tab-hover':11,'border-toggle-focus':7,'border-toggle-hover':7,'border-whisper-incoming':10,'fill-brand':9,'text-button-text':8,'text-button-text-focus':'o1','text-button-text-hover':'o1','text-link':10,'text-link-active':10,'text-link-focus':10,'text-link-hover':10,'text-link-visited':10,'text-overlay-link-active':13,'text-overlay-link-focus':13,'text-overlay-link-hover':13,'text-tab-active':11,'background-chat':1,'background-chat-alt':3,'background-chat-header':2,'background-modal':3,'text-button-text-active':'o2'/*,'text-tooltip':1*/},'s':{'button-active':[8,'0 0 6px 0',''],'button-focus':[8,'0 0 6px 0',''],'input-focus':[8,'0 0 10px -2px',''],'interactable-focus':[8,'0 0 6px 0',''],'tab-focus':[11,'0 4px 6px -4px',''],'input':[5,'inset 0 0 0 1px','']}},
|
||||
dark: {'c':{'accent': 9,'background-accent':8,'background-accent-alt':7,'background-accent-alt-2':6,'background-button':7,'background-button-active':7,'background-button-focus':8,'background-button-hover':8,'background-button-primary-active':7,'background-button-primary-default':9,'background-button-primary-hover':8,'background-graph':2,'background-graph-fill':8,'background-input-checkbox-checked':9,'background-input-checked':8,'background-interactable-active':9,'background-interactable-hover':8,'background-progress-countdown-status':9,'background-progress-status':9,'background-range-fill':9,'background-subscriber-stream-tag-active':4,'background-subscriber-stream-tag-default':4,'background-subscriber-stream-tag-hover':3,'background-toggle-checked':9,/*'background-tooltip':1,*/'background-top-nav':6,'border-brand':9,'border-button':7,'border-button-active':8,'border-button-focus':9,'border-button-hover':8,'border-input-checkbox-checked':9,'border-input-checkbox-focus':9,'border-input-focus':9,'border-interactable-selected':10,'border-subscriber-stream-tag':5,'border-tab-active':11,'border-tab-focus':11,'border-tab-hover':11,'border-toggle-focus':7,'border-toggle-hover':7,'border-whisper-incoming':10,'fill-brand':9,'text-button-text':10,'text-button-text-focus':'o1','text-button-text-hover':'o1','text-link':10,'text-link-active':10,'text-link-focus':10,'text-link-hover':10,'text-link-visited':10,'text-overlay-link-active':13,'text-overlay-link-focus':13,'text-overlay-link-hover':13,'text-tab-active':11,'background-chat':1,'background-chat-alt':3,'background-chat-header':2,'background-modal':3,'text-button-text-active':'o2'/*,'text-tooltip':1*/},'s':{'button-active':[8,'0 0 6px 0',''],'button-focus':[8,'0 0 6px 0',''],'input-focus':[8,'0 0 10px -2px',''],'interactable-focus':[8,'0 0 6px 0',''],'tab-focus':[11,'0 4px 6px -4px',''],'input':[5,'inset 0 0 0 1px','']}},
|
||||
light: {'c':{'accent': 9,'background-accent':8,'background-accent-alt':7,'background-accent-alt-2':6,'background-button':7,'background-button-active':7,'background-button-focus':8,'background-button-hover':8,'background-button-primary-active':7,'background-button-primary-default':9,'background-button-primary-hover':8,'background-graph':15,'background-graph-fill':9,'background-input-checkbox-checked':9,'background-input-checked':8,'background-interactable-active':9,'background-interactable-hover':8,'background-progress-countdown-status':8,'background-progress-status':8,'background-range-fill':9,'background-subscriber-stream-tag-active':13,'background-subscriber-stream-tag-default':13,'background-subscriber-stream-tag-hover':14,'background-toggle-checked':9,/*'background-tooltip':1,*/'background-top-nav':7,'border-brand':9,'border-button':7,'border-button-active':8,'border-button-focus':9,'border-button-hover':8,'border-input-checkbox-checked':9,'border-input-checkbox-focus':9,'border-input-focus':9,'border-interactable-selected':9,'border-subscriber-stream-tag':10,'border-tab-active':8,'border-tab-focus':8,'border-tab-hover':8,'border-toggle-focus':8,'border-toggle-hover':8,'border-whisper-incoming':10,'fill-brand':9,'text-button-text':8,'text-button-text-focus':'o1','text-button-text-hover':'o1','text-link':8,'text-link-active':9,'text-link-focus':9,'text-link-hover':9,'text-link-visited':9,'text-overlay-link-active':13,'text-overlay-link-focus':13,'text-overlay-link-hover':13,'text-tab-active':8},'s':{'button-active':[8,'0 0 6px 0',''],'button-focus':[8,'0 0 6px 0',''],'input-focus':[10,'0 0 10px -2px',''],'interactable-focus':[8,'0 0 6px 1px',''],'tab-focus':[8,'0 4px 6px -4px','']}},
|
||||
accent_dark: {'c':{'accent-hover':10,'accent':9,'accent-primary-1':1,'accent-primary-2':5,'accent-primary-3':6,'accent-primary-4':7,'accent-primary-5':8},'s':{}},
|
||||
accent_light: {'c':{'accent-hover':10,'accent':9,'accent-primary-1':1,'accent-primary-2':5,'accent-primary-3':6,'accent-primary-4':7,'accent-primary-5':8},'s':{}}
|
||||
|
|
|
@ -55,6 +55,27 @@
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ffz--reply-mention {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
&:before {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
background-color: rgba(0,0,0,0.15);
|
||||
|
||||
.tw-root--theme-dark & {
|
||||
background-color: rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
text-decoration: none;
|
||||
background-color: var(--color-background-button-hover);
|
||||
color: var(--color-text-button-hover) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ffz--chat-card {
|
||||
.vod-message & {
|
||||
.ffz--card-text {
|
||||
|
|
|
@ -72,7 +72,6 @@ export default class SocketClient extends Module {
|
|||
this._host_idx = -1;
|
||||
this._host_pool = -1;
|
||||
|
||||
|
||||
this.settings.on(':changed:socket.use-cluster', () => {
|
||||
this._host = null;
|
||||
if ( this.disconnected )
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template functional>
|
||||
<div
|
||||
:class="`tw-aspect--align-${props.align||'top'}`"
|
||||
class="tw-aspect"
|
||||
:class="`ffz-aspect--align-${props.align||'top'}`"
|
||||
class="ffz-aspect"
|
||||
>
|
||||
<div
|
||||
:style="{paddingTop: (props.padding ? props.padding : (100 * (1 / (props.ratio || 1)))) + '%'}"
|
||||
class="tw-aspect__spacer"
|
||||
class="ffz-aspect__spacer"
|
||||
/>
|
||||
<slot />
|
||||
</div>
|
||||
|
|
|
@ -100,5 +100,6 @@ export default [
|
|||
"chat",
|
||||
"location",
|
||||
"link",
|
||||
"volume-off"
|
||||
"volume-off",
|
||||
"reply"
|
||||
];
|
|
@ -773,10 +773,10 @@ function render_image(token, createElement, ctx) {
|
|||
return image;
|
||||
|
||||
return createElement('div', {
|
||||
className: 'tw-aspect tw-aspect--align-center'
|
||||
className: 'ffz-aspect ffz-aspect--align-center'
|
||||
}, [
|
||||
createElement('div', {
|
||||
className: 'tw-aspect__spacer',
|
||||
className: 'ffz-aspect__spacer',
|
||||
style: {
|
||||
paddingTop: `${100 * (1 / (aspect || 1))}%`
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@
|
|||
object-fit:contain;
|
||||
}
|
||||
|
||||
.tw-aspect { img, video { object-fit: cover; } }
|
||||
.ffz-aspect { img, video { object-fit: cover; } }
|
||||
}
|
||||
|
||||
> * {
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
.ffz-i-chat-empty:before { content: '\f0e6'; } /* '' */
|
||||
.ffz-i-download-cloud:before { content: '\f0ed'; } /* '' */
|
||||
.ffz-i-upload-cloud:before { content: '\f0ee'; } /* '' */
|
||||
.ffz-i-reply:before { content: '\f112'; } /* '' */
|
||||
.ffz-i-smile:before { content: '\f118'; } /* '' */
|
||||
.ffz-i-keyboard:before { content: '\f11c'; } /* '' */
|
||||
.ffz-i-calendar-empty:before { content: '\f133'; } /* '' */
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -79,6 +79,7 @@
|
|||
.ffz-i-chat-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-download-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-upload-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-keyboard { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-calendar-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
.ffz-i-chat-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-download-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-upload-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-keyboard { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.ffz-i-calendar-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
@font-face {
|
||||
font-family: 'ffz-fontello';
|
||||
src: url('../font/ffz-fontello.eot?81632949');
|
||||
src: url('../font/ffz-fontello.eot?81632949#iefix') format('embedded-opentype'),
|
||||
url('../font/ffz-fontello.woff2?81632949') format('woff2'),
|
||||
url('../font/ffz-fontello.woff?81632949') format('woff'),
|
||||
url('../font/ffz-fontello.ttf?81632949') format('truetype'),
|
||||
url('../font/ffz-fontello.svg?81632949#ffz-fontello') format('svg');
|
||||
src: url('../font/ffz-fontello.eot?74161598');
|
||||
src: url('../font/ffz-fontello.eot?74161598#iefix') format('embedded-opentype'),
|
||||
url('../font/ffz-fontello.woff2?74161598') format('woff2'),
|
||||
url('../font/ffz-fontello.woff?74161598') format('woff'),
|
||||
url('../font/ffz-fontello.ttf?74161598') format('truetype'),
|
||||
url('../font/ffz-fontello.svg?74161598#ffz-fontello') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
|||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
@font-face {
|
||||
font-family: 'ffz-fontello';
|
||||
src: url('../font/ffz-fontello.svg?81632949#ffz-fontello') format('svg');
|
||||
src: url('../font/ffz-fontello.svg?74161598#ffz-fontello') format('svg');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -135,6 +135,7 @@
|
|||
.ffz-i-chat-empty:before { content: '\f0e6'; } /* '' */
|
||||
.ffz-i-download-cloud:before { content: '\f0ed'; } /* '' */
|
||||
.ffz-i-upload-cloud:before { content: '\f0ee'; } /* '' */
|
||||
.ffz-i-reply:before { content: '\f112'; } /* '' */
|
||||
.ffz-i-smile:before { content: '\f118'; } /* '' */
|
||||
.ffz-i-keyboard:before { content: '\f11c'; } /* '' */
|
||||
.ffz-i-calendar-empty:before { content: '\f133'; } /* '' */
|
||||
|
|
|
@ -43,6 +43,10 @@
|
|||
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
|
||||
}
|
||||
|
||||
.ffz-mod-icon .ffz-i-reply {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
|
||||
.ffz-player-icon {
|
||||
font-size: 2rem !important;
|
||||
|
|
|
@ -37,4 +37,34 @@
|
|||
|
||||
.ffz--links {
|
||||
order: 10;
|
||||
}
|
||||
|
||||
.ffz-aspect {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
> :not(.ffz-aspect__spacer) {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-aspect--overflow {
|
||||
overflow: visible
|
||||
}
|
||||
|
||||
.ffz-aspect--align-top > :not(.ffz-aspect__spacer) {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.ffz-aspect--align-center > :not(.ffz-aspect__spacer) {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.ffz-aspect--align-bottom > :not(.ffz-aspect__spacer) {
|
||||
bottom: 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue