1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 05:15:54 +00:00
* Added: Setting to display timestamps on additional types of chat messages. (Closes #983)
* Changed: Do not set a Chat Width by default.
* Changed: Have WebMunch dump a list of possible webpack bundle names when failing to find webpack.
* Fixed: Appearance issues caused by Twitch's continual migration to procedural CSS class names, requiring duplicate CSS to achieve a native look.
* Fixed: Ambiguous input field names in some FFZ Control Center widgets. (Closes #1017)
* Fixed: Stop using Algolia for tag search.
This commit is contained in:
SirStendec 2021-04-01 12:05:29 -04:00
parent fa33491eec
commit ae90b8e4fe
35 changed files with 306 additions and 222 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "frankerfacez", "name": "frankerfacez",
"author": "Dan Salvato LLC", "author": "Dan Salvato LLC",
"version": "4.20.87", "version": "4.20.89",
"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

@ -1,7 +1,7 @@
<template lang="html"> <template lang="html">
<div class="ffz--addon-info tw-elevation-1 tw-c-background-base tw-border tw-border-radius-large tw-pd-1 tw-mg-b-1 tw-flex tw-flex-nowrap"> <div class="ffz--addon-info tw-elevation-1 tw-c-background-base tw-border tw-border-radius-large tw-pd-1 tw-mg-b-1 tw-flex tw-flex-nowrap">
<div class="tw-flex tw-flex-column tw-align-center tw-flex-shrink-0 tw-mg-r-1"> <div class="tw-flex tw-flex-column tw-align-center tw-flex-shrink-0 tw-mg-r-1">
<div class="tw-card-img--size-6 tw-overflow-hidden tw-mg-b-1"> <div class="ffz-card-img--size-6 tw-overflow-hidden tw-mg-b-1">
<img :src="icon" class="tw-image"> <img :src="icon" class="tw-image">
</div> </div>

View file

@ -191,16 +191,18 @@ export default {
}, },
data() { data() {
const this_id = `badge-term$${id++}`;
if ( this.adding ) if ( this.adding )
return { return {
id: id++, id: this_id,
deleting: false, deleting: false,
editing: true, editing: true,
edit_data: deep_copy(this.term) edit_data: deep_copy(this.term)
}; };
return { return {
id: id++, id: this_id,
deleting: false, deleting: false,
editing: false, editing: false,
edit_data: null edit_data: null

View file

@ -53,7 +53,7 @@
:href="commit.author.html_url" :href="commit.author.html_url"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="tw-inline-flex tw-align-items-center tw-link tw-link--inherit tw-mg-x-05 ffz-tooltip" class="tw-inline-flex tw-align-items-center ffz-link ffz-link--inherit tw-mg-x-05 ffz-tooltip"
data-tooltip-type="link" data-tooltip-type="link"
> >
<figure <figure
@ -77,7 +77,7 @@
v-if="commit.hash" v-if="commit.hash"
class="tw-font-size-8 tw-c-text-alt-2" class="tw-font-size-8 tw-c-text-alt-2"
> >
@<a :href="commit.link" target="_blank" rel="noopener noreferrer" class="tw-link tw-link--inherit ffz-tooltip" data-tooltip-type="link">{{ commit.hash }}</a> @<a :href="commit.link" target="_blank" rel="noopener noreferrer" class="ffz-link ffz-link--inherit ffz-tooltip" data-tooltip-type="link">{{ commit.hash }}</a>
</div> </div>
<time <time
v-if="commit.date" v-if="commit.date"

View file

@ -14,7 +14,7 @@
<div class="tw-pd-x-1 tw-pd-y-05"> <div class="tw-pd-x-1 tw-pd-y-05">
<div class="tw-card tw-relative"> <div class="tw-card tw-relative">
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row"> <div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row">
<div class="tw-card-img tw-card-img--size-3 tw-flex-shrink-0 tw-overflow-hidden"> <div class="ffz-card-img ffz-card-img--size-3 tw-flex-shrink-0 tw-overflow-hidden">
<aspect :ratio="1/1.33"> <aspect :ratio="1/1.33">
<img <img
:alt="slot.item.displayName" :alt="slot.item.displayName"
@ -55,7 +55,7 @@
<a <a
v-if="can_link" v-if="can_link"
:href="`/directory/game/${i}`" :href="`/directory/game/${i}`"
class="tw-link" class="ffz-link"
@click.prevent="handleLink(i)" @click.prevent="handleLink(i)"
> >
{{ i }} {{ i }}

View file

@ -70,14 +70,14 @@
:key="addon.key" :key="addon.key"
class="tw-mg-b-05 tw-flex tw-align-items-center" class="tw-mg-b-05 tw-flex tw-align-items-center"
> >
<div class="tw-card-img--size-4 tw-overflow-hidden tw-mg-r-1"> <div class="ffz-card-img--size-4 tw-overflow-hidden tw-mg-r-1">
<img :src="addon.icon" class="tw-image"> <img :src="addon.icon" class="tw-image">
</div> </div>
<div> <div>
<a <a
v-if="addon.enabled && addon.settings" v-if="addon.enabled && addon.settings"
href="#" href="#"
class="tw-strong tw-link--inherit" class="tw-strong ffz-link--inherit"
@click.prevent="item.requestPage(addon.settings)" @click.prevent="item.requestPage(addon.settings)"
> >
{{ addon.name_i18n ? t(addon.name_i18n, addon.name) : addon.name }} {{ addon.name_i18n ? t(addon.name_i18n, addon.name) : addon.name }}
@ -114,14 +114,14 @@
:key="addon.key" :key="addon.key"
class="tw-mg-b-05 tw-flex tw-align-items-center" class="tw-mg-b-05 tw-flex tw-align-items-center"
> >
<div class="tw-card-img--size-4 tw-overflow-hidden tw-mg-r-1"> <div class="ffz-card-img--size-4 tw-overflow-hidden tw-mg-r-1">
<img :src="addon.icon" class="tw-image"> <img :src="addon.icon" class="tw-image">
</div> </div>
<div> <div>
<a <a
v-if="addon.enabled && addon.settings" v-if="addon.enabled && addon.settings"
href="#" href="#"
class="tw-strong tw-link--inherit" class="tw-strong ffz-link--inherit"
@click.prevent="item.requestPage(addon.settings)" @click.prevent="item.requestPage(addon.settings)"
> >
{{ addon.name_i18n ? t(addon.name_i18n, addon.name) : addon.name }} {{ addon.name_i18n ? t(addon.name_i18n, addon.name) : addon.name }}

View file

@ -91,7 +91,7 @@
<a <a
v-if="version.commit" v-if="version.commit"
:href="`https://www.github.com/FrankerFaceZ/FrankerFaceZ/commit/${version.commit}`" :href="`https://www.github.com/FrankerFaceZ/FrankerFaceZ/commit/${version.commit}`"
class="tw-link tw-link--inherit" class="ffz-link ffz-link--inherit"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >

View file

@ -262,16 +262,18 @@ export default {
}, },
data() { data() {
const this_id = `term$${id++}`;
if ( this.adding ) if ( this.adding )
return { return {
id: id++, id: this_id,
deleting: false, deleting: false,
editing: true, editing: true,
edit_data: deep_copy(this.term) edit_data: deep_copy(this.term)
}; };
return { return {
id: id++, id: this_id,
deleting: false, deleting: false,
editing: false, editing: false,
edit_data: null edit_data: null

View file

@ -189,7 +189,7 @@ export default {
} else } else
this.source_value = undefined; this.source_value = undefined;
this.has_value = this.profile.has(this.item.setting); this.has_value = this.profile && this.profile.has(this.item.setting);
if ( ! this.has_value ) if ( ! this.has_value )
this.value = this.isInherited ? this.source_value : this.default_value; this.value = this.isInherited ? this.source_value : this.default_value;
}, 0); }, 0);

View file

@ -29,7 +29,7 @@
:href="`/${login}`" :href="`/${login}`"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="tw-interactive tw-link tw-link--hover-color-inherit tw-link--inherit" class="tw-interactive ffz-link ffz-link--hover-color-inherit ffz-link--inherit"
> >
{{ displayName }} {{ displayName }}
</a> </a>

View file

@ -5,7 +5,7 @@
{{ t(type.i18n, type.title) }} {{ t(type.i18n, type.title) }}
</label> </label>
<div class="ffz--search-avatar tw-mg-x-05 tw-card-img--size-2"> <div class="ffz--search-avatar tw-mg-x-05 ffz-card-img--size-2">
<aspect :ratio="1/1.33"> <aspect :ratio="1/1.33">
<img <img
v-if="current" v-if="current"
@ -29,7 +29,7 @@
<div class="tw-pd-x-1 tw-pd-y-05"> <div class="tw-pd-x-1 tw-pd-y-05">
<div class="tw-card tw-relative"> <div class="tw-card tw-relative">
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row"> <div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row">
<div class="tw-card-img tw-card-img--size-3 tw-flex-shrink-0 tw-overflow-hidden"> <div class="ffz-card-img ffz-card-img--size-3 tw-flex-shrink-0 tw-overflow-hidden">
<aspect :ratio="1/1.33"> <aspect :ratio="1/1.33">
<img <img
:alt="slot.item.displayName" :alt="slot.item.displayName"

View file

@ -31,7 +31,7 @@
<div class="tw-pd-x-1 tw-pd-y-05"> <div class="tw-pd-x-1 tw-pd-y-05">
<div class="tw-card tw-relative"> <div class="tw-card tw-relative">
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row"> <div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row">
<div class="tw-card-img tw-card-img--size-3 tw-flex-shrink-0 tw-overflow-hidden"> <div class="ffz-card-img ffz-card-img--size-3 tw-flex-shrink-0 tw-overflow-hidden">
<aspect :ratio="1"> <aspect :ratio="1">
<img <img
:alt="slot.item.displayName" :alt="slot.item.displayName"

View file

@ -89,7 +89,7 @@ export default class Line extends Module {
t.chat.badges.render(msg, createElement) t.chat.badges.render(msg, createElement)
}</span> }</span>
<a <a
class="clip-chat__message-author tw-font-size-5 tw-link notranslate" class="clip-chat__message-author tw-font-size-5 ffz-link notranslate"
href={`https://www.twitch.tv/${user.login}/clips`} href={`https://www.twitch.tv/${user.login}/clips`}
style={{color}} style={{color}}
> >

View file

@ -199,7 +199,7 @@ export default class Channel extends Module {
if ( ! inst._ffz_tips ) { if ( ! inst._ffz_tips ) {
const tt = this.resolve('tooltips'); const tt = this.resolve('tooltips');
if ( tt ) { if ( tt ) {
inst._ffz_tips = tt._createInstance(el, 'tw-link', 'link', tt.getRoot()); inst._ffz_tips = tt._createInstance(el, 'ffz-link', 'link', tt.getRoot());
inst._ffz_tip_el = el; inst._ffz_tip_el = el;
} }
} }

View file

@ -440,7 +440,7 @@ export default class ChatHook extends Module {
}); });
this.settings.add('chat.width', { this.settings.add('chat.width', {
default: 340, default: null,
ui: { ui: {
path: 'Chat > Appearance >> General @{"sort": -1}', path: 'Chat > Appearance >> General @{"sort": -1}',
title: 'Width', title: 'Width',
@ -449,20 +449,28 @@ export default class ChatHook extends Module {
process(val) { process(val) {
val = parseInt(val, 10); val = parseInt(val, 10);
if ( isNaN(val) || ! isFinite(val) || val <= 0 ) if ( isNaN(val) || ! isFinite(val) || val <= 0 )
return 340; return null;
return val; return val;
} }
} }
}); });
this.settings.add('chat.effective-width', {
requires: ['chat.width', 'context.ui.rightColumnWidth'],
process(ctx) {
const val = ctx.get('chat.width');
return val == null ? (ctx.get('context.ui.rightColumnWidth') || 340) : val;
}
});
this.settings.add('chat.use-width', { this.settings.add('chat.use-width', {
requires: ['chat.width', 'context.ui.rightColumnExpanded', 'context.isWatchParty'], requires: ['chat.width', 'context.ui.rightColumnExpanded', 'context.isWatchParty'],
process(ctx) { process(ctx) {
if ( ! ctx.get('context.ui.rightColumnExpanded') || ctx.get('context.isWatchParty') ) if ( ! ctx.get('context.ui.rightColumnExpanded') || ctx.get('context.isWatchParty') )
return false; return false;
return ctx.get('chat.width') != 340; return ctx.get('chat.width') != null;
} }
}); });
@ -511,6 +519,16 @@ export default class ChatHook extends Module {
} }
}); });
this.settings.add('chat.extra-timestamps', {
default: true,
ui: {
path: 'Chat > Appearance >> Chat Lines',
title: 'Display timestamps on notices.',
description: 'When enabled, timestamps will be displayed on point redemptions, subscriptions, etc.',
component: 'setting-check-box'
}
});
this.settings.add('chat.subs.show', { this.settings.add('chat.subs.show', {
default: 3, default: 3,
ui: { ui: {
@ -670,7 +688,7 @@ export default class ChatHook extends Module {
cancelAnimationFrame(this._update_css_waiter); cancelAnimationFrame(this._update_css_waiter);
this._update_css_waiter = null; this._update_css_waiter = null;
const width = this.chat.context.get('chat.width'), const width = this.chat.context.get('chat.effective-width'),
action_size = this.chat.context.get('chat.actions.size'), action_size = this.chat.context.get('chat.actions.size'),
ts_size = this.chat.context.get('chat.timestamp-size'), ts_size = this.chat.context.get('chat.timestamp-size'),
size = this.chat.context.get('chat.font-size'), size = this.chat.context.get('chat.font-size'),
@ -808,7 +826,7 @@ export default class ChatHook extends Module {
this.chat.context.on('changed:chat.banners.prediction', this.cleanHighlights, this); this.chat.context.on('changed:chat.banners.prediction', this.cleanHighlights, this);
this.chat.context.on('changed:chat.subs.gift-banner', () => this.GiftBanner.forceUpdate(), this); this.chat.context.on('changed:chat.subs.gift-banner', () => this.GiftBanner.forceUpdate(), this);
this.chat.context.on('changed:chat.width', this.updateChatCSS, this); this.chat.context.on('changed:chat.effective-width', this.updateChatCSS, this);
this.settings.main_context.on('changed:chat.use-width', this.updateChatCSS, this); this.settings.main_context.on('changed:chat.use-width', this.updateChatCSS, this);
this.chat.context.on('changed:chat.actions.size', this.updateChatCSS, this); this.chat.context.on('changed:chat.actions.size', this.updateChatCSS, this);
this.chat.context.on('changed:chat.font-size', this.updateChatCSS, this); this.chat.context.on('changed:chat.font-size', this.updateChatCSS, this);

View file

@ -527,7 +527,8 @@ other {# messages were deleted by a moderator.}
] : user_block) ] : user_block)
]; ];
let cls = `chat-line__message${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`, let extra_ts,
cls = `chat-line__message${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`,
out = (tokens.length || ! msg.ffz_type) ? [ out = (tokens.length || ! msg.ffz_type) ? [
(this.props.showTimestamps || this.props.isHistorical) && e('span', { (this.props.showTimestamps || this.props.isHistorical) && e('span', {
className: 'chat-line__timestamp' className: 'chat-line__timestamp'
@ -570,6 +571,9 @@ other {# messages were deleted by a moderator.}
}, JSON.stringify([tokens, msg.emotes], null, 2))*/ }, JSON.stringify([tokens, msg.emotes], null, 2))*/
] : null; ] : null;
if ( out == null )
extra_ts = t.chat.context.get('chat.extra-timestamps');
if ( msg.ffz_type === 'sub_mystery' ) { if ( msg.ffz_type === 'sub_mystery' ) {
const mystery = msg.mystery; const mystery = msg.mystery;
if ( mystery ) if ( mystery )
@ -640,6 +644,9 @@ other {# messages were deleted by a moderator.}
className: `ffz-i-star${msg.sub_anon ? '-empty' : ''} tw-mg-r-05` className: `ffz-i-star${msg.sub_anon ? '-empty' : ''} tw-mg-r-05`
}), }),
e('div', null, [ e('div', null, [
out ? null : extra_ts && (this.props.showTimestamps || this.props.isHistorical) && e('span', {
className: 'chat-line__timestamp'
}, t.chat.formatTime(msg.timestamp)),
(out || msg.sub_anon) ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), (out || msg.sub_anon) ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e),
sub_msg sub_msg
]), ]),
@ -710,6 +717,9 @@ other {# messages were deleted by a moderator.}
className: 'ffz-i-star tw-mg-r-05' className: 'ffz-i-star tw-mg-r-05'
}), }),
e('div', null, [ e('div', null, [
out ? null : extra_ts && (this.props.showTimestamps || this.props.isHistorical) && e('span', {
className: 'chat-line__timestamp'
}, t.chat.formatTime(msg.timestamp)),
(out || msg.sub_anon) ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), (out || msg.sub_anon) ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e),
sub_msg sub_msg
]) ])
@ -772,6 +782,9 @@ other {# messages were deleted by a moderator.}
className: `ffz-i-${plan.prime ? 'crown' : 'star'} tw-mg-r-05` className: `ffz-i-${plan.prime ? 'crown' : 'star'} tw-mg-r-05`
}), }),
e('div', null, [ e('div', null, [
out ? null : extra_ts && (this.props.showTimestamps || this.props.isHistorical) && e('span', {
className: 'chat-line__timestamp'
}, t.chat.formatTime(msg.timestamp)),
out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e),
sub_msg sub_msg
]) ])
@ -804,6 +817,9 @@ other {# messages were deleted by a moderator.}
if ( system_msg ) { if ( system_msg ) {
cls = `ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--ritual-line${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`; cls = `ffz-notice-line user-notice-line tw-pd-y-05 tw-pd-r-2 ffz--ritual-line${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`;
out = [ out = [
out ? null : extra_ts && (this.props.showTimestamps || this.props.isHistorical) && e('span', {
className: 'chat-line__timestamp'
}, t.chat.formatTime(msg.timestamp)),
system_msg, system_msg,
out && e('div', { out && e('div', {
className: 'chat-line--inline chat-line__message', className: 'chat-line--inline chat-line__message',
@ -825,6 +841,9 @@ other {# messages were deleted by a moderator.}
cls = `ffz-notice-line ffz--points-line tw-pd-l-1 tw-pd-y-05 tw-pd-r-2${ffz_highlight ? ' ffz-custom-color ffz--points-highlight' : ''}${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`; cls = `ffz-notice-line ffz--points-line tw-pd-l-1 tw-pd-y-05 tw-pd-r-2${ffz_highlight ? ' ffz-custom-color ffz--points-highlight' : ''}${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`;
out = [ out = [
e('div', {className: 'tw-c-text-alt-2'}, [ e('div', {className: 'tw-c-text-alt-2'}, [
out ? null : extra_ts && (this.props.showTimestamps || this.props.isHistorical) && e('span', {
className: 'chat-line__timestamp'
}, t.chat.formatTime(msg.timestamp)),
out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e),
out ? out ?
t.i18n.tList('chat.points.redeemed', 'Redeemed {reward} {cost}', {reward, cost}) : t.i18n.tList('chat.points.redeemed', 'Redeemed {reward} {cost}', {reward, cost}) :

View file

@ -298,7 +298,7 @@ export default class SettingsMenu extends Module {
title: <span class="tw-strong">{this.i18n.t('chat.ffz-badge.title', 'FrankerFaceZ Badge')}</span> title: <span class="tw-strong">{this.i18n.t('chat.ffz-badge.title', 'FrankerFaceZ Badge')}</span>
})}{' '} })}{' '}
{this.i18n.tList('chat.ffz-badge.site', 'Please visit the {website} to change this badge.', { {this.i18n.tList('chat.ffz-badge.site', 'Please visit the {website} to change this badge.', {
website: (<a href="https://www.frankerfacez.com/donate" class="tw-link" rel="noopener noreferrer" target="_blank"> website: (<a href="https://www.frankerfacez.com/donate" class="ffz-link" rel="noopener noreferrer" target="_blank">
{this.i18n.t('chat.ffz-badge.site-link', 'FrankerFaceZ website')} {this.i18n.t('chat.ffz-badge.site-link', 'FrankerFaceZ website')}
</a>) </a>)
})} })}

View file

@ -6,6 +6,10 @@
width: calc(var(--ffz-chat-width) - 2rem) !important; width: calc(var(--ffz-chat-width) - 2rem) !important;
} }
.channel-root {
width: unset !important;
}
body .whispers--theatre-mode.whispers--right-column-expanded-beside { body .whispers--theatre-mode.whispers--right-column-expanded-beside {
right: var(--ffz-chat-width); right: var(--ffz-chat-width);
} }

View file

@ -4,6 +4,14 @@
.right-column { .right-column {
order: 1; order: 1;
z-index: 2; z-index: 2;
#root &:not(.right-column--collapsed) {
width: var(--ffz-chat-width);
}
}
.channel-root {
width: unset !important;
} }
#sideNav .collapse-toggle { #sideNav .collapse-toggle {

View file

@ -267,11 +267,11 @@ export default class Directory extends SiteModule {
channel_url = `/${channel.login}`, channel_url = `/${channel.login}`,
game_url = game && `/directory/game/${stream.game.name}`, game_url = game && `/directory/game/${stream.game.name}`,
user_link = <a href={channel_url} data-href={channel_url} title={channel.displayName} class="tw-link tw-link--inherit" onClick={t.routeClick}>{channel.displayName}</a>, user_link = <a href={channel_url} data-href={channel_url} title={channel.displayName} class="ffz-link ffz-link--inherit" onClick={t.routeClick}>{channel.displayName}</a>,
game_link = game && <a href={game_url} data-href={game_url} title={game.name} class="tw-link tw-link--inherit" onClick={t.routeClick}>{game.name}</a>; game_link = game && <a href={game_url} data-href={game_url} title={game.name} class="ffz-link ffz-link--inherit" onClick={t.routeClick}>{game.name}</a>;
return (<div> return (<div>
<a href={channel_url} data-href={channel_url} class="tw-link tw-link--inherit" data-test-selector="preview-card-titles__primary-link" onClick={t.routeClick}> <a href={channel_url} data-href={channel_url} class="ffz-link ffz-link--inherit" data-test-selector="preview-card-titles__primary-link" onClick={t.routeClick}>
<h3 class="tw-ellipsis tw-font-size-5 tw-strong" title={stream.title}>{stream.title}</h3> <h3 class="tw-ellipsis tw-font-size-5 tw-strong" title={stream.title}>{stream.title}</h3>
</a> </a>
<div class="preview-card-titles__subtitle-wrapper"> <div class="preview-card-titles__subtitle-wrapper">
@ -295,7 +295,7 @@ export default class Directory extends SiteModule {
nodes.length > 1 ? nodes.length > 1 ?
t.i18n.t('directory.hosted.by-many', 'Hosted by {count,number} channel{count,en_plural}', nodes.length) : t.i18n.t('directory.hosted.by-many', 'Hosted by {count,number} channel{count,en_plural}', nodes.length) :
t.i18n.tList('directory.hosted.by-one', 'Hosted by {user}', { t.i18n.tList('directory.hosted.by-one', 'Hosted by {user}', {
user: <a href={`/${nodes[0].login}`} data-href={`/${nodes[0].login}`} title={nodes[0].displayName} class="tw-link tw-link--inherit" onClick={t.routeClick}>{nodes[0].displayName}</a> user: <a href={`/${nodes[0].login}`} data-href={`/${nodes[0].login}`} title={nodes[0].displayName} class="ffz-link ffz-link--inherit" onClick={t.routeClick}>{nodes[0].displayName}</a>
}) })
}</p> }</p>
</div> </div>

View file

@ -93,6 +93,20 @@ export default class WebMunch extends Module {
if ( ! name ) { if ( ! name ) {
if ( attempts > 240 ) { if ( attempts > 240 ) {
this.log.error("Unable to find webpack's loader after one minute."); this.log.error("Unable to find webpack's loader after one minute.");
try {
const possibilities = [];
for(const key of Object.keys(window))
if ( has(window, key) && typeof key === 'string' && /webpack/i.test(key) && ! /ffz/i.test(key) )
possibilities.push(key);
if ( possibilities.length )
this.log.info('Possible Matches: ', possibilities.join(', '));
else
this.log.info('No possible matches found.');
} catch(err) { /* no-op */ }
this._resolveLoadWait(true); this._resolveLoadWait(true);
return; return;
} }

View file

@ -0,0 +1,11 @@
query FFZ_SearchLiveTags($query: String!, $categoryID: ID, $limit: Int) {
searchLiveTags(userQuery: $query, categoryID: $categoryID, limit: $limit) {
id
isAutomated
isLanguageTag
localizedDescription
localizedName
scope
tagName
}
}

View file

@ -1,9 +1,11 @@
query FFZ_FetchTags($ids: [ID!]) { query FFZ_FetchTags($ids: [ID!]) {
contentTags(ids: $ids) { contentTags(ids: $ids) {
id id
isAutomated
isLanguageTag isLanguageTag
tagName
localizedName
localizedDescription localizedDescription
localizedName
scope
tagName
} }
} }

View file

@ -1,9 +1,11 @@
query FFZ_TopTags($limit: Int) { query FFZ_TopTags($limit: Int) {
topTags(limit: $limit) { topTags(limit: $limit) {
id id
isAutomated
isLanguageTag isLanguageTag
tagName
localizedName
localizedDescription localizedDescription
localizedName
scope
tagName
} }
} }

View file

@ -825,7 +825,7 @@ TOKEN_TYPES.link = function(token, createElement, ctx) {
klass.push(`tw-block tw-border tw-border-radius-large tw-mg-y-05 tw-pd-05`); klass.push(`tw-block tw-border tw-border-radius-large tw-mg-y-05 tw-pd-05`);
if ( token.no_color ) if ( token.no_color )
klass.push(`tw-link--inherit`); klass.push(`ffz-link--inherit`);
if ( ctx.vue ) if ( ctx.vue )
return createElement('a', { return createElement('a', {

View file

@ -6,65 +6,10 @@
// ============================================================================ // ============================================================================
import Module from 'utilities/module'; import Module from 'utilities/module';
import {get, debounce, generateUUID} from 'utilities/object'; import {get, debounce} from 'utilities/object';
const LANGUAGE_MATCHER = /^auto___lang_(\w+)$/; const LANGUAGE_MATCHER = /^auto___lang_(\w+)$/;
const ALGOLIA_LANGUAGES = {
bg: 'bg-bg',
cs: 'cs-cz',
da: 'da-dk',
de: 'de-de',
el: 'el-gr',
en: 'en-us',
es: 'es-es',
'es-mx': 'es-mx',
fi: 'fi-fi',
fr: 'fr-fr',
hu: 'hu-hu',
it: 'it-it',
ja: 'ja-jp',
ko: 'ko-kr',
nl: 'nl-nl',
no: 'no-no',
pl: 'pl-pl',
'pt-br': 'pt-br',
pt: 'pt-pt',
ro: 'ro-ro',
ru: 'ru-ru',
sk: 'sk-sk',
sv: 'sv-se',
th: 'th-th',
tr: 'tr-tr',
vi: 'vi-vn',
'zh-cn': 'zh-cn',
'zh-tw': 'zh-tw'
};
/**
* Returns the Algolia Language code for a given locale
* @function getAlgoliaLanguage
*
* @param {string} locale - a string representation of a locale
* @returns {string} the Algolia Language code for the given locale
*
* @example
*
* console.log(getAlgoliaLanguage('en'));
*/
function getAlgoliaLanguage(locale) {
if ( ! locale )
return ALGOLIA_LANGUAGES.en;
locale = locale.toLowerCase();
if ( ALGOLIA_LANGUAGES[locale] )
return ALGOLIA_LANGUAGES[locale];
locale = locale.split('-')[0];
return ALGOLIA_LANGUAGES[locale] || ALGOLIA_LANGUAGES.en;
}
/** /**
* TwitchData is a container for getting different types of Twitch data * TwitchData is a container for getting different types of Twitch data
* @class TwitchData * @class TwitchData
@ -132,34 +77,6 @@ export default class TwitchData extends Module {
return session && session.locale || 'en-US' return session && session.locale || 'en-US'
} }
async searchClient() {
if ( this._search )
return this._search;
const apollo = this.apollo.client,
core = this.site.getCore(),
web_munch = this.resolve('web_munch');
if ( ! web_munch )
return null;
await web_munch.enable();
const SearchClient = await this.web_munch.findModule('algolia-search');
if ( ! SearchClient || ! apollo || ! core )
return null;
this._search = new SearchClient({
appId: core.config.algoliaApplicationID,
apiKey: core.config.algoliaAPIKey,
apolloClient: apollo,
logger: core.logger,
config: core.config,
stats: core.stats
});
return this._search;
}
// ======================================================================== // ========================================================================
// Badges // Badges
@ -350,6 +267,7 @@ export default class TwitchData extends Module {
return get('data.user.lastBroadcast', data); return get('data.user.lastBroadcast', data);
} }
// ======================================================================== // ========================================================================
// Broadcast ID // Broadcast ID
// ======================================================================== // ========================================================================
@ -665,27 +583,28 @@ export default class TwitchData extends Module {
if ( ! node || ! node.id || ! node.tagName || ! node.localizedName ) if ( ! node || ! node.id || ! node.tagName || ! node.localizedName )
return; return;
let old = null; let tag = this.tag_cache.get(node.id);
if ( this.tag_cache.has(node.id) ) if ( ! tag ) {
old = this.tag_cache.get(old);
const match = node.isLanguageTag && LANGUAGE_MATCHER.exec(node.tagName), const match = node.isLanguageTag && LANGUAGE_MATCHER.exec(node.tagName),
lang = match && match[1] || null; lang = match && match[1] || null;
const new_tag = { tag = {
id: node.id, id: node.id,
value: node.id, value: node.id,
is_auto: node.isAutomated,
is_language: node.isLanguageTag, is_language: node.isLanguageTag,
language: lang, language: lang,
name: node.tagName, name: node.tagName,
label: node.localizedName scope: node.scope
}; };
if ( node.localizedDescription ) this.tag_cache.set(node.id, tag);
new_tag.description = node.localizedDescription; }
const tag = old ? Object.assign(old, new_tag) : new_tag; if ( node.localizedName )
this.tag_cache.set(tag.id, tag); tag.label = node.localizedName;
if ( node.localizedDescription )
tag.description = node.localizedDescription;
if ( dispatch && tag.description && this._waiting_tags.has(tag.id) ) { if ( dispatch && tag.description && this._waiting_tags.has(tag.id) ) {
const promises = this._waiting_tags.get(tag.id); const promises = this._waiting_tags.get(tag.id);
@ -907,100 +826,36 @@ export default class TwitchData extends Module {
* @async * @async
* *
* @param {string} query - the search string * @param {string} query - the search string
* @param {string} [locale] - the locale to return tags from * @param {string} [locale] - UNUSED. the locale to return tags from
* @param {string} [category=null] - the category to return tags from * @param {string} [category=null] - the category to return tags from
* @returns {string[]} an array containing tags that match the query string * @returns {string[]} an array containing tags that match the query string
* *
* @example * @example
* *
* console.log(this.twitch_data.getMatchingTags("Rainbo")); * console.log(await this.twitch_data.getMatchingTags("Rainbo"));
*/ */
async getMatchingTags(query, locale, category = null) { async getMatchingTags(query, locale, category = null) {
if ( ! locale ) /*if ( ! locale )
locale = this.locale; locale = this.locale;*/
const client = await this.searchClient(); const data = await this.queryApollo({
query: await import(/* webpackChunkName: 'queries' */ './data/search-tags.gql'),
locale = getAlgoliaLanguage(locale); variables: {
query,
let nodes; categoryID: category || null,
limit: 100
if ( category ) {
const data = await client.queryForType(
'stream_tag', query, generateUUID(), {
hitsPerPage: 100,
faceFilters: [
`category_id:${category}`
],
restrictSearchableAttributes: [
`localizations.${locale}`,
'tag_name'
]
} }
); });
nodes = get('streamTags.hits', data); const nodes = data?.data?.searchLiveTags;
if ( ! Array.isArray(nodes) || ! nodes.length )
} else {
const data = await client.queryForType(
'tag', query, generateUUID(), {
hitsPerPage: 100,
facetFilters: [
['tag_scope:SCOPE_ALL', 'tag_scope:SCOPE_CATEGORY']
],
restrictSearchableAttributes: [
`localizations.${locale}`,
'tag_name'
]
}
);
nodes = get('tags.hits', data);
}
if ( ! Array.isArray(nodes) )
return []; return [];
const out = [], seen = new Set; const out = [];
for(const node of nodes) { for(const node of nodes) {
const tag_id = node.tag_id || node.objectID; const tag = this.memorizeTag(node);
if ( ! node || seen.has(tag_id) ) if ( tag )
continue;
seen.add(tag_id);
if ( ! this.tag_cache.has(tag_id) ) {
const match = node.tag_name && LANGUAGE_MATCHER.exec(node.tag_name),
lang = match && match[1] || null;
const tag = {
id: tag_id,
value: tag_id,
is_language: lang != null,
language: lang,
label: node.localizations && (node.localizations[locale] || node.localizations['en-us']) || node.tag_name
};
if ( node.description_localizations ) {
const desc = node.description_localizations[locale] || node.description_localizations['en-us'];
if ( desc )
tag.description = desc;
}
this.tag_cache.set(tag.id, tag);
out.push(tag); out.push(tag);
} else {
const tag = this.tag_cache.get(tag_id);
if ( ! tag.description && node.description_localizations ) {
const desc = node.description_localizations[locale] || node.description_localizations['en-us'];
if ( desc ) {
tag.description = desc;
this.tag_cache.set(tag.id, tag);
}
}
out.push(tag);
}
} }
return out; return out;

View file

@ -3,7 +3,7 @@
@import 'widgets'; @import 'widgets';
@import 'dialog'; @import 'dialog';
@import 'input/index'; @import 'native/index';
@import 'structure/index'; @import 'structure/index';
@import 'chat'; @import 'chat';

15
styles/native/card.scss Normal file
View file

@ -0,0 +1,15 @@
.ffz-card-img {
background-color: var(--color-background-placeholder);;
flex-shrink: 0;
width: 100%;
&--size-2 { width: 2rem }
&--size-3 { width: 3rem }
&--size-4 { width: 4rem }
&--size-6 { width: 6rem }
&--size-8 { width: 8rem }
&--size-12 { width: 12rem }
&--size-16 { width: 16rem }
&--size-24 { width: 24rem }
&--size-32 { width: 32rem }
}

View file

@ -2,3 +2,6 @@
@import "checkbox"; @import "checkbox";
@import "interactable"; @import "interactable";
@import "text"; @import "text";
@import "link";
@import "card";
@import "tag";

67
styles/native/link.scss Normal file
View file

@ -0,0 +1,67 @@
.ffz-link {
color: var(--color-text-link);
text-decoration: none;
.tw-root--hover &:hover {
color: var(--color-text-link-hover);
text-decoration: underline;
}
&:active { color: var(--color-text-link-active); }
&[data-focus-visible-added], &:focus { color: var(--color-text-link-focus); }
&:visited { color: var(--color-text-link-visited); }
&--overlay {
color: var(--color-text-overlay-link);
text-decoration: underline;
&:hover {
text-decoration: none;
}
.tw-root--hover &:hover {
color: var(--color-text-overlay-link-hover);
}
&:active { color: var(--color-text-overlay-link-active) }
&:focus, &[data-focus-visible-added] { color: var(--color-text-overlay-link-focus) }
&:visited { color: var(--color-text-overlay-link-visited) }
}
&--underline { text-decoration: underline; }
&--inherit {
&, &:visited, &:focus, &[data-focus-visible-added], &:active {
color: inherit;
}
.tw-root--hover &:hover {
color: var(--color-text-link-hover);
}
}
.tw-root--hover &--hover-color-inherit:hover {
color: inherit;
}
.tw-root--hover &--hover-underline-none {
&, &:hover {
text-decoration: none;
}
}
&--button {
&:active {
outline: none
}
}
&--disabled {
opacity: .5;
.tw-root--hover &:hover {
cursor: not-allowed;
text-decoration: none;
}
}
}

62
styles/native/tag.scss Normal file
View file

@ -0,0 +1,62 @@
.ffz-tag {
background-color: var(--color-background-tag-default);
border: var(--border-width-tag) solid transparent;
color: var(--color-text-tag);
height: 2rem;
&:not(:disabled) {
&:focus, &[data-focus-visible-added] {
background: var(--color-background-tag-hover);
border: var(--border-width-tag) solid var(--color-border-input-focus);
}
.tw-root--hover &:hover {
background: var(--color-background-tag-hover);
color: var(--color-text-tag);
text-decoration: none;
}
&:active {
background: var(--color-background-tag-active);
}
}
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
&__content {
padding: 0 .8rem;
&--icon {
padding-right: .4rem
}
}
&__icon {
height: 1.6rem;
width: 1.6rem;
}
&--overlay {
background: rgba(0,0,0,0.3);
border: var(--border-width-tag) solid var(--color-border-input-overlay);
color: var(--color-text-overlay);
&:not(:disabled) {
&:focus, &[data-focus-visible-added] {
border: var(--border-width-tag) solid var(--color-border-input-overlay-focus);
}
.tw-root--hover &:hover {
background: var(--color-background-interactable-overlay-hover);
color: var(--color-text-overlay);
}
&:active {
background: var(--color-background-interactable-overlay-active);
}
}
}
}