1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +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",
"author": "Dan Salvato LLC",
"version": "4.20.84",
"version": "4.20.85",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"private": true,
"license": "Apache-2.0",

View file

@ -162,41 +162,39 @@ export const Links = {
const text = token.text;
let idx = 0, match;
//if ( use_new ) {
while((match = NEW_LINK_REGEX.exec(text))) {
const nix = match.index;
if ( 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({
type: 'link',
url: `${match[1] ? '' : 'https://'}${match[0]}`,
url: `${match[1] ? '' : 'https://'}${url}`,
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 )
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

View file

@ -13,6 +13,7 @@ import Twilight from 'site';
import Module from 'utilities/module';
import SUB_STATUS from './sub_status.gql';
import Tooltip from 'src/utilities/tooltip';
const TIERS = {
1000: 'Tier 1',
@ -95,9 +96,9 @@ const EMOTE_SORTERS = [
return 0;
},
function native_asc(a, b) {
if ( a.order || b.order ) {
if ( a.order && ! b.order ) return -1;
if ( b.order && ! a.order ) return 1;
if ( a.order != null || b.order != null ) {
if ( a.order && b.order == null ) return -1;
if ( b.order && a.order == null ) 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;
},
function native_desc(a, b) {
if ( a.order || b.order ) {
if ( a.order && ! b.order ) return 1;
if ( b.order && ! a.order ) return -1;
if ( a.order != null || b.order != null ) {
if ( a.order && b.order == null ) return 1;
if ( b.order && a.order == null ) 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', {
default: true,
ui: {
@ -349,6 +363,7 @@ export default class EmoteMenu extends Module {
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.fix-bad-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}>
<t.MenuComponent
source={this.props.emotePickerSource}
visible={this.props.visible}
toggleVisibility={this.props.toggleVisibility}
channel_data={this.props.channelData}
@ -449,40 +465,6 @@ export default class EmoteMenu extends Module {
React = this.web_munch.getModule('react'),
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 {
constructor(props) {
super(props);
@ -617,7 +599,6 @@ export default class EmoteMenu extends Module {
this.state = {
active: false,
open_menu: null,
activeEmote: -1,
hidden: hidden && props.data && hidden.includes(props.data.hide_key || 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.clickHeading = this.clickHeading.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});
@ -684,29 +663,6 @@ export default class EmoteMenu extends Module {
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) {
if ( event.keyCode === KEYS.Enter || event.keyCode === KEYS.Space )
this.clickHeading();
@ -889,7 +845,7 @@ export default class EmoteMenu extends Module {
const visibility = this.props.visibility_control,
modifiers = this.props.emote_modifiers[emote.id],
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,
hidden = visibility && emote.hidden;
@ -910,6 +866,7 @@ export default class EmoteMenu extends Module {
data-id={emote.id}
data-set={emote.set_id}
data-code={emote.code}
data-modifiers={modifiers}
data-variant={emote.variant}
data-no-source={source}
data-name={emote.name}
@ -917,7 +874,6 @@ export default class EmoteMenu extends Module {
data-locked={emote.locked}
data-sellout={sellout}
onClick={(this.props.visibility_control || !emote.locked) && this.clickEmote}
onContextMenu={this.contextEmote}
>
<figure class="emote-picker__emote-figure">
<img
@ -933,11 +889,6 @@ export default class EmoteMenu extends Module {
{! visibility && emote.favorite && <figure class="ffz--favorite ffz-i-star" />}
{! visibility && locked && <figure class="ffz-i-lock" />}
{hidden && <figure class="ffz-i-eye-off" />}
{has_menu && <t.EmoteModifierPicker
emote={emote}
modifiers={modifiers}
close={this.closeEmoteModMenu}
/>}
</button>)
}
@ -1549,78 +1500,75 @@ export default class EmoteMenu extends Module {
tone_choices = state.tone_emoji = [],
categories = {};
if ( ! t.chat.context.get('chat.emote-menu.show-emoji') ) {
state.has_emoji_tab = false;
return state;
}
if ( t.chat.context.get('chat.emote-menu.show-emoji') ) {
let style = t.chat.context.get('chat.emoji.style') || 'twitter';
if ( ! IMAGE_PATHS[style] )
style = 'twitter';
let style = t.chat.context.get('chat.emoji.style') || 'twitter';
if ( ! IMAGE_PATHS[style] )
style = 'twitter';
for(const emoji of Object.values(t.emoji.emoji)) {
if ( ! emoji || ! emoji.has[style] || HIDDEN_CATEGORIES.includes(emoji.category) )
continue;
for(const emoji of Object.values(t.emoji.emoji)) {
if ( ! emoji || ! emoji.has[style] || HIDDEN_CATEGORIES.includes(emoji.category) )
continue;
if ( emoji.variants ) {
for(const name of emoji.names)
if ( TONE_EMOJI.includes(name) ) {
tone_choices.push(emoji);
break;
}
}
if ( emoji.variants ) {
for(const name of emoji.names)
if ( TONE_EMOJI.includes(name) ) {
tone_choices.push(emoji);
break;
}
}
const is_fav = emoji_favorites.includes(emoji.code),
toned = emoji.variants && emoji.variants[tone],
has_tone = toned && toned.has[style],
source = has_tone ? toned : emoji;
const is_fav = emoji_favorites.includes(emoji.code),
toned = emoji.variants && emoji.variants[tone],
has_tone = toned && toned.has[style],
source = has_tone ? toned : emoji;
let cat = categories[emoji.category];
if ( ! cat ) {
cat = categories[emoji.category] = [];
let cat = categories[emoji.category];
if ( ! cat ) {
cat = categories[emoji.category] = [];
sets.push({
key: `emoji-${emoji.category}`,
sort_key: CATEGORY_SORT.indexOf(emoji.category),
emoji: true,
image: t.emoji.getFullImage(source.image),
i18n: `emoji.category.${emoji.category.toSnakeCase()}`,
title: CATEGORIES[emoji.category] || emoji.category,
src: 'emoji',
source: 'Emoji',
source_i18n: 'emote-menu.emoji',
emotes: cat
});
}
sets.push({
key: `emoji-${emoji.category}`,
sort_key: CATEGORY_SORT.indexOf(emoji.category),
const em = {
provider: 'emoji',
id: emoji.sort,
emoji: true,
image: t.emoji.getFullImage(source.image),
i18n: `emoji.category.${emoji.category.toSnakeCase()}`,
title: CATEGORIES[emoji.category] || emoji.category,
src: 'emoji',
source: 'Emoji',
source_i18n: 'emote-menu.emoji',
emotes: cat
});
code: emoji.code,
name: source.raw,
variant: has_tone && tone,
hidden: emoji.hidden,
search: emoji.names[0],
extra: emoji.names.length > 1 ? emoji.names.map(x => x.toLowerCase()) : null,
height: 18,
width: 18,
x: source.sheet_x,
y: source.sheet_y,
favorite: is_fav,
src: t.emoji.getFullImage(source.image),
srcSet: t.emoji.getFullImageSet(source.image)
};
cat.push(em);
if ( is_fav )
favorites.push(em);
}
const em = {
provider: 'emoji',
id: emoji.sort,
emoji: true,
code: emoji.code,
name: source.raw,
variant: has_tone && tone,
hidden: emoji.hidden,
search: emoji.names[0],
extra: emoji.names.length > 1 ? emoji.names.map(x => x.toLowerCase()) : null,
height: 18,
width: 18,
x: source.sheet_x,
y: source.sheet_y,
favorite: is_fav,
src: t.emoji.getFullImage(source.image),
srcSet: t.emoji.getFullImageSet(source.image)
};
cat.push(em);
if ( is_fav )
favorites.push(em);
}
state.has_emoji_tab = sets.length > 0;
@ -1871,51 +1819,63 @@ export default class EmoteMenu extends Module {
const id = emote.id,
name = KNOWN_CODES[emote.token] || emote.token,
mapped = emote_map && emote_map[name],
overridden = mapped && mapped.id != id,
replacement = REPLACEMENTS[id],
is_fav = twitch_favorites.includes(id);
let src, srcSet;
if ( replacement && t.chat.context.get('chat.fix-bad-emotes') )
src = `${REPLACEMENT_BASE}${replacement}`;
else {
const base = `${TWITCH_EMOTE_BASE}${id}`;
src = `${base}/1.0`;
srcSet = `${src} 1x, ${base}/2.0 2x`
}
/*if ( Array.isArray(emote.modifiers) && emote.modifiers.length )
modifiers[id] = emote.modifiers;*/
const em = {
provider: 'twitch',
id,
set_id,
name,
src,
srcSet,
order: order++,
overridden: overridden ? mapped.id : null,
misc: ! chan,
bits: is_bits,
hidden: twitch_hidden.includes(id),
favorite: is_fav
};
mapped = emote_map && emote_map[name];
if ( ! is_points )
t.emotes.setTwitchEmoteSet(id, set_id);
emotes.push(em);
//if ( Array.isArray(emote.modifiers) && emote.modifiers.length )
// modifiers[id] = emote.modifiers.map(x => x.code);
if ( is_current_bits )
bits_unlocked.push(em);
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}`);
}
if ( is_fav && ! twitch_seen.has(id) )
favorites.push(em);
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];
twitch_seen.add(id);
let src, srcSet;
if ( replacement && t.chat.context.get('chat.fix-bad-emotes') )
src = `${REPLACEMENT_BASE}${replacement}`;
else {
const base = `${TWITCH_EMOTE_BASE}${new_id}`;
src = `${base}/1.0`;
srcSet = `${src} 1x, ${base}/2.0 2x`
}
const em = {
provider: 'twitch',
id: new_id,
set_id,
name: new_name,
src,
srcSet,
order: order++,
overridden: overridden ? mapped.id : null,
misc: ! chan,
bits: is_bits,
hidden: twitch_hidden.includes(new_id),
favorite: is_fav
};
emotes.push(em);
if ( is_current_bits )
bits_unlocked.push(em);
if ( is_fav && ! twitch_seen.has(new_id) )
favorites.push(em);
twitch_seen.add(new_id);
}
}
if ( emotes.length ) {
@ -1998,9 +1958,6 @@ export default class EmoteMenu extends Module {
seen = twitch_seen.has(id),
is_fav = twitch_favorites.includes(id);
/*if ( Array.isArray(emote.modifiers) && emote.modifiers.length )
modifiers[id] = emote.modifiers;*/
const em = {
provider: 'twitch',
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'}}>
<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"
role="dialog"
>
<div class="emote-picker">
<div class={`emote-picker${whisper ? '__whisper' : ''}`}>
<div class="tw-flex">
<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-simplebar
>
@ -2379,9 +2337,9 @@ export default class EmoteMenu extends Module {
</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
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-simplebar
>

View file

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

View file

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

View file

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