1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 15:27:43 +00:00
* Added: Option to display modified Twitch emotes in the emote menu.
* Fixed: Use parenthesis counting to ensure links surrounded in `)` don't include the `)`, matching Twitch behavior. Closes #1015
* Fixed: Issue with Native Sorting for the emote menu where the first emote is sent to the end of the list.
* Fixed: Emote Menu not appearing correctly in whispers.
* Fixed: Favorites section not appearing in the Emote Menu when emoji are disabled.
* Fixed: The Stream Latency and Up-time metadata unnecessarily changing width due to Twitch's default font.
This commit is contained in:
SirStendec 2021-03-23 18:24:09 -04:00
parent 1cdff0ec67
commit c03c2e48b5
6 changed files with 183 additions and 209 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "frankerfacez", "name": "frankerfacez",
"author": "Dan Salvato LLC", "author": "Dan Salvato LLC",
"version": "4.20.84", "version": "4.20.85",
"description": "FrankerFaceZ is a Twitch enhancement suite.", "description": "FrankerFaceZ is a Twitch enhancement suite.",
"private": true, "private": true,
"license": "Apache-2.0", "license": "Apache-2.0",

View file

@ -162,41 +162,39 @@ export const Links = {
const text = token.text; const text = token.text;
let idx = 0, match; let idx = 0, match;
//if ( use_new ) {
while((match = NEW_LINK_REGEX.exec(text))) { while((match = NEW_LINK_REGEX.exec(text))) {
const nix = match.index; const nix = match.index;
if ( idx !== nix ) if ( idx !== nix )
out.push({type: 'text', text: text.slice(idx, nix)}); out.push({type: 'text', text: text.slice(idx, nix)});
let url = match[0];
if ( url.endsWith(')') ) {
let open = 1, i = url.length - 1;
while(i--) {
const chr = url[i];
if ( chr === ')' )
open++;
else if ( chr === '(' )
open--;
if ( ! open )
break;
}
if ( open )
url = url.slice(0, url.length - 1);
}
out.push({ out.push({
type: 'link', type: 'link',
url: `${match[1] ? '' : 'https://'}${match[0]}`, url: `${match[1] ? '' : 'https://'}${url}`,
is_mail: false, is_mail: false,
text: match[0] text: url
}); });
idx = nix + match[0].length; idx = nix + url.length;
} }
/*} else {
while((match = LINK_REGEX.exec(text))) {
const nix = match.index + (match[1] ? match[1].length : 0);
if ( idx !== nix )
out.push({type: 'text', text: text.slice(idx, nix)});
const is_mail = ! match[3] && match[2].indexOf('/') === -1 && match[2].indexOf('@') !== -1;
out.push({
type: 'link',
url: (match[3] ? '' : is_mail ? 'mailto:' : 'https://') + match[2],
is_mail,
text: match[2]
});
idx = nix + match[2].length;
}
}*/
if ( idx < text.length ) if ( idx < text.length )
out.push({type: 'text', text: text.slice(idx)}); out.push({type: 'text', text: text.slice(idx)});
} }
@ -1521,6 +1519,16 @@ export const AddonEmotes = {
} }
} }
/*AddonEmotes.tooltip.interactive = function(target) {
const mods = target.dataset.modifiers;
return mods && mods.length > 0;
}
AddonEmotes.tooltip.delayHide = function(target) {
const mods = target.dataset.modifiers;
return mods && mods.length > 0 ? 100 : 0;
}*/
// ============================================================================ // ============================================================================
// Emoji // Emoji

View file

@ -13,6 +13,7 @@ import Twilight from 'site';
import Module from 'utilities/module'; import Module from 'utilities/module';
import SUB_STATUS from './sub_status.gql'; import SUB_STATUS from './sub_status.gql';
import Tooltip from 'src/utilities/tooltip';
const TIERS = { const TIERS = {
1000: 'Tier 1', 1000: 'Tier 1',
@ -95,9 +96,9 @@ const EMOTE_SORTERS = [
return 0; return 0;
}, },
function native_asc(a, b) { function native_asc(a, b) {
if ( a.order || b.order ) { if ( a.order != null || b.order != null ) {
if ( a.order && ! b.order ) return -1; if ( a.order && b.order == null ) return -1;
if ( b.order && ! a.order ) return 1; if ( b.order && a.order == null ) return 1;
if ( a.order < b.order ) return -1; if ( a.order < b.order ) return -1;
if ( a.order > b.order ) return 1; if ( a.order > b.order ) return 1;
@ -111,9 +112,9 @@ const EMOTE_SORTERS = [
return 0; return 0;
}, },
function native_desc(a, b) { function native_desc(a, b) {
if ( a.order || b.order ) { if ( a.order != null || b.order != null ) {
if ( a.order && ! b.order ) return 1; if ( a.order && b.order == null ) return 1;
if ( b.order && ! a.order ) return -1; if ( b.order && a.order == null ) return -1;
if ( a.order < b.order ) return 1; if ( a.order < b.order ) return 1;
if ( a.order > b.order ) return -1; if ( a.order > b.order ) return -1;
@ -174,6 +175,19 @@ export default class EmoteMenu extends Module {
} }
}); });
this.settings.add('chat.emote-menu.modifiers', {
default: 0,
ui: {
path: 'Chat > Emote Menu >> General',
title: 'Emote Modifiers',
component: 'setting-select-box',
data: [
{value: 0, title: 'Disabled'},
{value: 1, title: 'In-Line'}
]
}
});
this.settings.add('chat.emote-menu.enabled', { this.settings.add('chat.emote-menu.enabled', {
default: true, default: true,
ui: { ui: {
@ -349,6 +363,7 @@ export default class EmoteMenu extends Module {
inst.rebuildData(); inst.rebuildData();
} }
this.chat.context.on('changed:chat.emote-menu.modifiers', rebuild);
this.chat.context.on('changed:chat.emote-menu.show-emoji', rebuild); this.chat.context.on('changed:chat.emote-menu.show-emoji', rebuild);
this.chat.context.on('changed:chat.fix-bad-emotes', rebuild); this.chat.context.on('changed:chat.fix-bad-emotes', rebuild);
this.chat.context.on('changed:chat.emote-menu.sort-emotes', rebuild); this.chat.context.on('changed:chat.emote-menu.sort-emotes', rebuild);
@ -394,6 +409,7 @@ export default class EmoteMenu extends Module {
return (<t.MenuErrorWrapper visible={this.props.visible}> return (<t.MenuErrorWrapper visible={this.props.visible}>
<t.MenuComponent <t.MenuComponent
source={this.props.emotePickerSource}
visible={this.props.visible} visible={this.props.visible}
toggleVisibility={this.props.toggleVisibility} toggleVisibility={this.props.toggleVisibility}
channel_data={this.props.channelData} channel_data={this.props.channelData}
@ -449,40 +465,6 @@ export default class EmoteMenu extends Module {
React = this.web_munch.getModule('react'), React = this.web_munch.getModule('react'),
createElement = React && React.createElement; createElement = React && React.createElement;
this.EmoteModifierPicker = class FFZEmoteModifierPicker extends React.Component {
constructor(props) {
super(props);
this.onClickOutside = () => this.props.close();
this.element = null;
this.saveRef = element => this.element = element;
this.state = {
};
}
componentDidMount() {
if ( this.element )
this._clicker = new ClickOutside(this.element, this.onClickOutside);
}
componentWillUnmount() {
if ( this._clicker ) {
this._clicker.destroy();
this._clicker = null;
}
}
render() {
return (<div ref={this.saveRef} class="ffz--modifier-picker tw-absolute ffz-balloon tw-tooltip-down tw-tooltip--align-center ffz-balloon tw-block">
<div class="tw-border-b tw-border-l tw-border-r tw-border-t tw-border-radius-medium tw-c-background-base tw-elevation-1">
</div>
</div>)
}
}
this.EmojiTonePicker = class FFZEmojiTonePicker extends React.Component { this.EmojiTonePicker = class FFZEmojiTonePicker extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -617,7 +599,6 @@ export default class EmoteMenu extends Module {
this.state = { this.state = {
active: false, active: false,
open_menu: null,
activeEmote: -1, activeEmote: -1,
hidden: hidden && props.data && hidden.includes(props.data.hide_key || props.data.key), hidden: hidden && props.data && hidden.includes(props.data.hide_key || props.data.key),
collapsed: collapsed && props.data && collapsed.includes(props.data.key), collapsed: collapsed && props.data && collapsed.includes(props.data.key),
@ -627,8 +608,6 @@ export default class EmoteMenu extends Module {
this.keyHeading = this.keyHeading.bind(this); this.keyHeading = this.keyHeading.bind(this);
this.clickHeading = this.clickHeading.bind(this); this.clickHeading = this.clickHeading.bind(this);
this.clickEmote = this.clickEmote.bind(this); this.clickEmote = this.clickEmote.bind(this);
this.contextEmote = this.contextEmote.bind(this);
this.closeEmoteModMenu = this.closeEmoteModMenu.bind(this);
this.mouseEnter = () => this.state.intersecting || this.setState({intersecting: true}); this.mouseEnter = () => this.state.intersecting || this.setState({intersecting: true});
@ -684,29 +663,6 @@ export default class EmoteMenu extends Module {
this.props.onClickToken(event.currentTarget.dataset.name) this.props.onClickToken(event.currentTarget.dataset.name)
} }
contextEmote(event) {
if ( event.ctrlKey || event.shiftKey )
return;
event.preventDefault();
const ds = event.currentTarget.dataset;
if ( ds.provider !== 'twitch' )
return;
const modifiers = this.props.emote_modifiers[ds.id];
if ( Array.isArray(modifiers) && modifiers.length )
this.setState({
open_menu: ds.id
});
}
closeEmoteModMenu() {
this.setState({
open_menu: null
});
}
keyHeading(event) { keyHeading(event) {
if ( event.keyCode === KEYS.Enter || event.keyCode === KEYS.Space ) if ( event.keyCode === KEYS.Enter || event.keyCode === KEYS.Space )
this.clickHeading(); this.clickHeading();
@ -889,7 +845,7 @@ export default class EmoteMenu extends Module {
const visibility = this.props.visibility_control, const visibility = this.props.visibility_control,
modifiers = this.props.emote_modifiers[emote.id], modifiers = this.props.emote_modifiers[emote.id],
has_modifiers = Array.isArray(modifiers) && modifiers.length > 0, has_modifiers = Array.isArray(modifiers) && modifiers.length > 0,
has_menu = has_modifiers && this.state.open_menu == emote.id, //has_menu = has_modifiers && this.state.open_menu == emote.id,
animated = this.props.animated, animated = this.props.animated,
hidden = visibility && emote.hidden; hidden = visibility && emote.hidden;
@ -910,6 +866,7 @@ export default class EmoteMenu extends Module {
data-id={emote.id} data-id={emote.id}
data-set={emote.set_id} data-set={emote.set_id}
data-code={emote.code} data-code={emote.code}
data-modifiers={modifiers}
data-variant={emote.variant} data-variant={emote.variant}
data-no-source={source} data-no-source={source}
data-name={emote.name} data-name={emote.name}
@ -917,7 +874,6 @@ export default class EmoteMenu extends Module {
data-locked={emote.locked} data-locked={emote.locked}
data-sellout={sellout} data-sellout={sellout}
onClick={(this.props.visibility_control || !emote.locked) && this.clickEmote} onClick={(this.props.visibility_control || !emote.locked) && this.clickEmote}
onContextMenu={this.contextEmote}
> >
<figure class="emote-picker__emote-figure"> <figure class="emote-picker__emote-figure">
<img <img
@ -933,11 +889,6 @@ export default class EmoteMenu extends Module {
{! visibility && emote.favorite && <figure class="ffz--favorite ffz-i-star" />} {! visibility && emote.favorite && <figure class="ffz--favorite ffz-i-star" />}
{! visibility && locked && <figure class="ffz-i-lock" />} {! visibility && locked && <figure class="ffz-i-lock" />}
{hidden && <figure class="ffz-i-eye-off" />} {hidden && <figure class="ffz-i-eye-off" />}
{has_menu && <t.EmoteModifierPicker
emote={emote}
modifiers={modifiers}
close={this.closeEmoteModMenu}
/>}
</button>) </button>)
} }
@ -1549,11 +1500,7 @@ export default class EmoteMenu extends Module {
tone_choices = state.tone_emoji = [], tone_choices = state.tone_emoji = [],
categories = {}; categories = {};
if ( ! t.chat.context.get('chat.emote-menu.show-emoji') ) { if ( t.chat.context.get('chat.emote-menu.show-emoji') ) {
state.has_emoji_tab = false;
return state;
}
let style = t.chat.context.get('chat.emoji.style') || 'twitter'; let style = t.chat.context.get('chat.emoji.style') || 'twitter';
if ( ! IMAGE_PATHS[style] ) if ( ! IMAGE_PATHS[style] )
style = 'twitter'; style = 'twitter';
@ -1622,6 +1569,7 @@ export default class EmoteMenu extends Module {
if ( is_fav ) if ( is_fav )
favorites.push(em); favorites.push(em);
} }
}
state.has_emoji_tab = sets.length > 0; state.has_emoji_tab = sets.length > 0;
@ -1871,51 +1819,63 @@ export default class EmoteMenu extends Module {
const id = emote.id, const id = emote.id,
name = KNOWN_CODES[emote.token] || emote.token, name = KNOWN_CODES[emote.token] || emote.token,
mapped = emote_map && emote_map[name], mapped = emote_map && emote_map[name];
overridden = mapped && mapped.id != id,
replacement = REPLACEMENTS[id], if ( ! is_points )
is_fav = twitch_favorites.includes(id); t.emotes.setTwitchEmoteSet(id, set_id);
//if ( Array.isArray(emote.modifiers) && emote.modifiers.length )
// modifiers[id] = emote.modifiers.map(x => x.code);
const modes = [''];
if ( Array.isArray(emote.modifiers) && emote.modifiers.length ) {
if ( t.chat.context.get('chat.emote-menu.modifiers') === 1 )
for(const mod of emote.modifiers)
modes.push(`_${mod.code}`);
}
for(const mode of modes) {
const new_id = `${id}${mode}`,
new_name = `${name}${mode}`,
is_fav = twitch_favorites.includes(new_id),
overridden = mapped && mapped.id != new_id,
replacement = REPLACEMENTS[new_id];
let src, srcSet; let src, srcSet;
if ( replacement && t.chat.context.get('chat.fix-bad-emotes') ) if ( replacement && t.chat.context.get('chat.fix-bad-emotes') )
src = `${REPLACEMENT_BASE}${replacement}`; src = `${REPLACEMENT_BASE}${replacement}`;
else { else {
const base = `${TWITCH_EMOTE_BASE}${id}`; const base = `${TWITCH_EMOTE_BASE}${new_id}`;
src = `${base}/1.0`; src = `${base}/1.0`;
srcSet = `${src} 1x, ${base}/2.0 2x` srcSet = `${src} 1x, ${base}/2.0 2x`
} }
/*if ( Array.isArray(emote.modifiers) && emote.modifiers.length )
modifiers[id] = emote.modifiers;*/
const em = { const em = {
provider: 'twitch', provider: 'twitch',
id, id: new_id,
set_id, set_id,
name, name: new_name,
src, src,
srcSet, srcSet,
order: order++, order: order++,
overridden: overridden ? mapped.id : null, overridden: overridden ? mapped.id : null,
misc: ! chan, misc: ! chan,
bits: is_bits, bits: is_bits,
hidden: twitch_hidden.includes(id), hidden: twitch_hidden.includes(new_id),
favorite: is_fav favorite: is_fav
}; };
if ( ! is_points )
t.emotes.setTwitchEmoteSet(id, set_id);
emotes.push(em); emotes.push(em);
if ( is_current_bits ) if ( is_current_bits )
bits_unlocked.push(em); bits_unlocked.push(em);
if ( is_fav && ! twitch_seen.has(id) ) if ( is_fav && ! twitch_seen.has(new_id) )
favorites.push(em); favorites.push(em);
twitch_seen.add(id); twitch_seen.add(new_id);
}
} }
if ( emotes.length ) { if ( emotes.length ) {
@ -1998,9 +1958,6 @@ export default class EmoteMenu extends Module {
seen = twitch_seen.has(id), seen = twitch_seen.has(id),
is_fav = twitch_favorites.includes(id); is_fav = twitch_favorites.includes(id);
/*if ( Array.isArray(emote.modifiers) && emote.modifiers.length )
modifiers[id] = emote.modifiers;*/
const em = { const em = {
provider: 'twitch', provider: 'twitch',
id, id,
@ -2337,7 +2294,8 @@ export default class EmoteMenu extends Module {
} }
} }
const visibility = this.state.visibility_control; const visibility = this.state.visibility_control,
whisper = this.props.source === 'whisper';
return (<div class={`tw-block${this.props.visible ? '' : ' tw-hide'}`} style={{display: this.props.visible ? null : 'none !important'}}> return (<div class={`tw-block${this.props.visible ? '' : ' tw-hide'}`} style={{display: this.props.visible ? null : 'none !important'}}>
<div class="tw-absolute tw-attached tw-attached--right tw-attached--up"> <div class="tw-absolute tw-attached tw-attached--right tw-attached--up">
@ -2346,10 +2304,10 @@ export default class EmoteMenu extends Module {
data-a-target="emote-picker" data-a-target="emote-picker"
role="dialog" role="dialog"
> >
<div class="emote-picker"> <div class={`emote-picker${whisper ? '__whisper' : ''}`}>
<div class="tw-flex"> <div class="tw-flex">
<div <div
class="emote-picker__tab-content tw-full-width scrollable-area scrollable-area--suppress-scroll-x" class={`emote-picker__tab-content${whisper ? '-whisper' : ''} tw-full-width scrollable-area scrollable-area--suppress-scroll-x`}
data-test-selector="scrollable-area-wrapper" data-test-selector="scrollable-area-wrapper"
data-simplebar data-simplebar
> >
@ -2379,9 +2337,9 @@ export default class EmoteMenu extends Module {
</div> </div>
</div> </div>
</div> </div>
{(! loading && this.state.quickNav && ! is_favs) && (<div class="emote-picker__nav_content tw-block tw-border-radius-none tw-c-background-alt-2"> {(! loading && this.state.quickNav && ! is_favs) && (<div class={`emote-picker__nav_content${whisper ? '-whisper' : ''} tw-block tw-border-radius-none tw-c-background-alt-2`}>
<div <div
class="emote-picker__nav-content-overflow scrollable-area scrollable-area--suppress-scroll-x" class={`emote-picker__nav-content-overflow${whisper ? '-whisper' : ''} scrollable-area scrollable-area--suppress-scroll-x`}
data-test-selector="scrollable-area-wrapper" data-test-selector="scrollable-area-wrapper"
data-simplebar data-simplebar
> >

View file

@ -53,6 +53,7 @@
.ffz-stat-text { .ffz-stat-text {
font-size: 1.2rem; font-size: 1.2rem;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
font-family: "Helvetica Neue",sans-serif;
} }
.ffz-stat--fix-padding { .ffz-stat--fix-padding {

View file

@ -289,6 +289,8 @@
} }
.ffz--emote-picker { .ffz--emote-picker {
white-space: normal;
section:not(.filtered) heading { section:not(.filtered) heading {
cursor: pointer; cursor: pointer;
} }

View file

@ -44,9 +44,14 @@
display: inline-block; display: inline-block;
line-height: 3rem; line-height: 3rem;
margin-right: .5rem; margin-right: .5rem;
border-top: 1px solid transparent;
&:hover, &.active { &.active {
border-top: 1px solid #6441a4; border-top-color: var(--color-border-tab-active);
}
&:hover,&:focus {
border-top-color: var(--color-border-top-hover)
} }
} }
} }