mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.20.21
* Added: Display rich tool-tips for channel panels. * Fixed: Hide Unfollow button in theater mode with the appropriate setting. (Closes #860) * Fixed: Automatically open Theater Mode not working when the channel is window is not visible. (Closes #861) * Fixed: Game titles not appearing in clip embeds. * Fixed: Featured Follow metadata failing when trying to open the menu. * Debug Added: Setting to choose the link resolver. * Debug Added: Test UI for working on link resolvers.
This commit is contained in:
parent
05e8428a4a
commit
eec65551fb
14 changed files with 519 additions and 86 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.20.20",
|
"version": "4.20.21",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -6,13 +6,12 @@ import {ALLOWED_ATTRIBUTES, ALLOWED_TAGS} from 'utilities/constants';
|
||||||
const ERROR_IMAGE = 'https://static-cdn.jtvnw.net/emoticons/v1/58765/2.0';
|
const ERROR_IMAGE = 'https://static-cdn.jtvnw.net/emoticons/v1/58765/2.0';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['data', 'url'],
|
props: ['data', 'url', 'events'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loaded: false,
|
loaded: false,
|
||||||
error: false,
|
error: null,
|
||||||
html: null,
|
|
||||||
title: this.t('card.loading', 'Loading...'),
|
title: this.t('card.loading', 'Loading...'),
|
||||||
title_tokens: null,
|
title_tokens: null,
|
||||||
desc_1: null,
|
desc_1: null,
|
||||||
|
@ -26,58 +25,96 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
watch: {
|
||||||
let data;
|
data() {
|
||||||
try {
|
this.reset();
|
||||||
data = this.data.getData();
|
this.load();
|
||||||
if ( data instanceof Promise ) {
|
}
|
||||||
const to_wait = has(this.data, 'timeout') ? this.data.timeout : 1000;
|
},
|
||||||
if ( to_wait )
|
|
||||||
data = await timeout(data, to_wait);
|
|
||||||
else
|
|
||||||
data = await data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! data )
|
created() {
|
||||||
data = {
|
if ( this.events ) {
|
||||||
error: true,
|
this._events = this.events;
|
||||||
title: this.t('card.error', 'An error occured.'),
|
this._events.on('chat:update-link-resolver', this.checkRefresh, this);
|
||||||
desc_1: this.t('card.empty', 'No data was returned.')
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
data = {
|
|
||||||
error: true,
|
|
||||||
title: this.t('card.error', 'An error occured.'),
|
|
||||||
desc_1: String(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loaded = true;
|
this.load();
|
||||||
this.error = data.error;
|
},
|
||||||
this.html = data.html;
|
|
||||||
this.title = data.title;
|
beforeDestroy() {
|
||||||
this.title_tokens = data.title_tokens;
|
if ( this._events ) {
|
||||||
this.desc_1 = data.desc_1;
|
this._events.off('chat:update-link-resolver', this.checkRefresh, this);
|
||||||
this.desc_1_tokens = data.desc_1_tokens;
|
this._events = null;
|
||||||
this.desc_2 = data.desc_2;
|
}
|
||||||
this.desc_2_tokens = data.desc_2_tokens;
|
|
||||||
this.image = data.image;
|
|
||||||
this.image_square = data.image_square;
|
|
||||||
this.image_title = data.image_title;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
checkRefresh(url) {
|
||||||
|
if ( ! url || (url && url === this.url) ) {
|
||||||
|
this.reset();
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.loaded = false;
|
||||||
|
this.error = null;
|
||||||
|
this.title = this.t('card.loading', 'Loading...');
|
||||||
|
this.title_tokens = null;
|
||||||
|
this.desc_1 = null;
|
||||||
|
this.desc_1_tokens = null;
|
||||||
|
this.desc_2 = null;
|
||||||
|
this.desc_2_tokens = null;
|
||||||
|
this.image = null;
|
||||||
|
this.image_title = null;
|
||||||
|
this.image_square = null;
|
||||||
|
this.accent = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = this.data.getData();
|
||||||
|
if ( data instanceof Promise ) {
|
||||||
|
const to_wait = has(this.data, 'timeout') ? this.data.timeout : 1000;
|
||||||
|
if ( to_wait )
|
||||||
|
data = await timeout(data, to_wait);
|
||||||
|
else
|
||||||
|
data = await data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! data )
|
||||||
|
data = {
|
||||||
|
error: true,
|
||||||
|
title: this.t('card.error', 'An error occured.'),
|
||||||
|
desc_1: this.t('card.empty', 'No data was returned.')
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
data = {
|
||||||
|
error: true,
|
||||||
|
title: this.t('card.error', 'An error occured.'),
|
||||||
|
desc_1: String(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
this.error = data.error;
|
||||||
|
this.title = data.title;
|
||||||
|
this.title_tokens = data.title_tokens;
|
||||||
|
this.desc_1 = data.desc_1;
|
||||||
|
this.desc_1_tokens = data.desc_1_tokens;
|
||||||
|
this.desc_2 = data.desc_2;
|
||||||
|
this.desc_2_tokens = data.desc_2_tokens;
|
||||||
|
this.image = data.image;
|
||||||
|
this.image_square = data.image_square;
|
||||||
|
this.image_title = data.image_title;
|
||||||
|
this.accent = data.accent;
|
||||||
|
},
|
||||||
|
|
||||||
renderCard(h) {
|
renderCard(h) {
|
||||||
if ( this.data.renderBody )
|
if ( this.data.renderBody )
|
||||||
return [this.data.renderBody(h)];
|
return [this.data.renderBody(h)];
|
||||||
|
|
||||||
if ( this.html )
|
|
||||||
return [h('div', {
|
|
||||||
domProps: {
|
|
||||||
innerHTML: this.html
|
|
||||||
}
|
|
||||||
})];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
this.renderImage(h),
|
this.renderImage(h),
|
||||||
this.renderDescription(h)
|
this.renderDescription(h)
|
||||||
|
|
|
@ -71,6 +71,32 @@ export default class Chat extends Module {
|
||||||
// Settings
|
// Settings
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
|
this.settings.add('debug.link-resolver.source', {
|
||||||
|
default: null,
|
||||||
|
ui: {
|
||||||
|
path: 'Debugging > Data Sources >> Links',
|
||||||
|
title: 'Link Resolver',
|
||||||
|
component: 'setting-select-box',
|
||||||
|
force_seen: true,
|
||||||
|
data: [
|
||||||
|
{value: null, title: 'Automatic'},
|
||||||
|
{value: 'dev', title: 'localhost'},
|
||||||
|
{value: 'test', title: 'API Test'},
|
||||||
|
{value: 'prod', title: 'API Production' },
|
||||||
|
{value: 'socket', title: 'Socket Cluster (Deprecated)'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
changed: () => this.clearLinkCache()
|
||||||
|
});
|
||||||
|
|
||||||
|
this.settings.addUI('debug.link-resolver.test', {
|
||||||
|
path: 'Debugging > Data Sources >> Links',
|
||||||
|
component: 'link-tester',
|
||||||
|
getChat: () => this,
|
||||||
|
force_seen: true
|
||||||
|
});
|
||||||
|
|
||||||
this.settings.add('chat.font-size', {
|
this.settings.add('chat.font-size', {
|
||||||
default: 12,
|
default: 12,
|
||||||
ui: {
|
ui: {
|
||||||
|
@ -1506,6 +1532,33 @@ export default class Chat extends Module {
|
||||||
// Twitch Crap
|
// Twitch Crap
|
||||||
// ====
|
// ====
|
||||||
|
|
||||||
|
clearLinkCache(url) {
|
||||||
|
if ( url ) {
|
||||||
|
const info = this._link_info[url];
|
||||||
|
if ( ! info[0] ) {
|
||||||
|
for(const pair of info[2])
|
||||||
|
pair[1]();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._link_info[url] = null;
|
||||||
|
this.emit(':update-link-resolver', url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const old = this._link_info;
|
||||||
|
this._link_info = {};
|
||||||
|
|
||||||
|
for(const info of Object.values(old)) {
|
||||||
|
if ( ! info[0] ) {
|
||||||
|
for(const pair of info[2])
|
||||||
|
pair[1]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit(':update-link-resolver');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
get_link_info(url, no_promises) {
|
get_link_info(url, no_promises) {
|
||||||
let info = this._link_info[url];
|
let info = this._link_info[url];
|
||||||
const expires = info && info[1];
|
const expires = info && info[1];
|
||||||
|
@ -1536,15 +1589,23 @@ export default class Chat extends Module {
|
||||||
cbs[success ? 0 : 1](data);
|
cbs[success ? 0 : 1](data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( this.experiments.getAssignment('api_links') )
|
let provider = this.settings.get('debug.link-resolver.source');
|
||||||
timeout(fetch(`https://api-test.frankerfacez.com/v2/link?url=${encodeURIComponent(url)}`).then(r => r.json()), 15000)
|
if ( provider == null )
|
||||||
.then(data => handle(true, data))
|
provider = this.experiments.getAssignment('api_links') ? 'test' : 'socket';
|
||||||
.catch(err => handle(false, err));
|
|
||||||
|
|
||||||
else
|
if ( provider === 'socket' ) {
|
||||||
timeout(this.socket.call('get_link', url), 15000)
|
timeout(this.socket.call('get_link', url), 15000)
|
||||||
.then(data => handle(true, data))
|
.then(data => handle(true, data))
|
||||||
.catch(err => handle(false, err));
|
.catch(err => handle(false, err));
|
||||||
|
} else {
|
||||||
|
const host = provider === 'dev' ? 'https://localhost:8002/' :
|
||||||
|
provider === 'test' ? 'https://api-test.frankerfacez.com/v2/link' :
|
||||||
|
'https://api.frankerfacez.com/v2/link';
|
||||||
|
|
||||||
|
timeout(fetch(`${host}?url=${encodeURIComponent(url)}`).then(r => r.json()), 15000)
|
||||||
|
.then(data => handle(true, data))
|
||||||
|
.catch(err => handle(false, err));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -63,10 +63,14 @@ export const Links = {
|
||||||
url: token.url,
|
url: token.url,
|
||||||
accent: data.accent,
|
accent: data.accent,
|
||||||
image: this.context.get('tooltip.link-images') ? (data.image_safe || this.context.get('tooltip.link-nsfw-images') ) ? data.preview || data.image : null : null,
|
image: this.context.get('tooltip.link-images') ? (data.image_safe || this.context.get('tooltip.link-nsfw-images') ) ? data.preview || data.image : null : null,
|
||||||
|
image_title: data.image_title,
|
||||||
image_square: data.image_square,
|
image_square: data.image_square,
|
||||||
title: data.title,
|
title: data.title,
|
||||||
|
title_tokens: data.title_tokens,
|
||||||
desc_1: data.desc_1,
|
desc_1: data.desc_1,
|
||||||
desc_2: data.desc_2
|
desc_1_tokens: data.desc_1_tokens,
|
||||||
|
desc_2: data.desc_2,
|
||||||
|
desc_2_tokens: data.desc_2_tokens
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,7 +231,7 @@ export const Clips = {
|
||||||
} else if ( game ) {
|
} else if ( game ) {
|
||||||
desc_1_tokens = this.i18n.tList('clip.desc.1.playing', '{user} playing {game}', {
|
desc_1_tokens = this.i18n.tList('clip.desc.1.playing', '{user} playing {game}', {
|
||||||
user: {class: 'tw-semibold', content: user},
|
user: {class: 'tw-semibold', content: user},
|
||||||
game: {class: 'tw-semibold', game_display}
|
game: {class: 'tw-semibold', content: game_display}
|
||||||
});
|
});
|
||||||
desc_1 = this.i18n.t('clip.desc.1.playing', '{user} playing {game}', {
|
desc_1 = this.i18n.t('clip.desc.1.playing', '{user} playing {game}', {
|
||||||
user,
|
user,
|
||||||
|
|
|
@ -47,7 +47,9 @@ export const Links = {
|
||||||
if ( target.dataset.isMail === 'true' )
|
if ( target.dataset.isMail === 'true' )
|
||||||
return [this.i18n.t('tooltip.email-link', 'E-Mail {address}', {address: target.textContent})];
|
return [this.i18n.t('tooltip.email-link', 'E-Mail {address}', {address: target.textContent})];
|
||||||
|
|
||||||
return this.get_link_info(target.dataset.url).then(data => {
|
const url = target.dataset.url || target.href;
|
||||||
|
|
||||||
|
return this.get_link_info(url).then(data => {
|
||||||
if ( ! data || (data.v || 1) > TOOLTIP_VERSION )
|
if ( ! data || (data.v || 1) > TOOLTIP_VERSION )
|
||||||
return '';
|
return '';
|
||||||
|
|
||||||
|
|
230
src/modules/main_menu/components/link-tester.vue
Normal file
230
src/modules/main_menu/components/link-tester.vue
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
<template>
|
||||||
|
<div class="ffz--link-tester">
|
||||||
|
<div class="ffz--widget ffz--select-box">
|
||||||
|
<div class="tw-flex tw-align-items-start">
|
||||||
|
<label for="selector" class="tw-mg-y-05">
|
||||||
|
{{ t('debug.link-provider.url', 'Test URL') }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="tw-flex tw-flex-column tw-mg-05">
|
||||||
|
<select
|
||||||
|
id="selector"
|
||||||
|
ref="selector"
|
||||||
|
class="tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-font-size-6 tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05"
|
||||||
|
@change="onSelectChange"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="i in stock_urls"
|
||||||
|
:key="i"
|
||||||
|
:selected="i === raw_url"
|
||||||
|
>
|
||||||
|
{{ i }}
|
||||||
|
</option>
|
||||||
|
<option :selected="isCustomURL">
|
||||||
|
{{ t('setting.combo-box.custom', 'Custom') }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
ref="text"
|
||||||
|
v-model="raw_url"
|
||||||
|
:disabled="! isCustomURL"
|
||||||
|
class="ffz-mg-t-1p tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tw-flex tw-mg-b-1">
|
||||||
|
<div class="tw-flex-grow-1" />
|
||||||
|
<button
|
||||||
|
class="tw-mg-l-1 tw-button tw-button--text"
|
||||||
|
@click="refresh"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-arrows-cw">
|
||||||
|
{{ t('debug.link-provider.refresh', 'Refresh') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="tw-flex tw-mg-b-1 tw-full-width">
|
||||||
|
<label>
|
||||||
|
{{ t('debug.link-provider.embed', 'Rich Embed') }}
|
||||||
|
</label>
|
||||||
|
<div class="tw-full-width tw-overflow-hidden">
|
||||||
|
<chat-rich
|
||||||
|
v-if="rich_data"
|
||||||
|
:data="rich_data"
|
||||||
|
:url="url"
|
||||||
|
:events="events"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tw-flex tw-mg-b-1 tw-full-width">
|
||||||
|
<label>
|
||||||
|
{{ t('debug.link-provider.link', 'Chat Link') }}
|
||||||
|
</label>
|
||||||
|
<div class="tw-full-width tw-overflow-hidden">
|
||||||
|
<a
|
||||||
|
v-if="url"
|
||||||
|
:href="url"
|
||||||
|
:data-url="url"
|
||||||
|
class="ffz-tooltip"
|
||||||
|
data-tooltip-type="link"
|
||||||
|
data-force-tooltip="true"
|
||||||
|
data-is-mail="false"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ url }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tw-flex tw-mg-b-1 tw-full-width">
|
||||||
|
<label>
|
||||||
|
{{ t('debug.link-provider.raw', 'Raw Data') }}
|
||||||
|
</label>
|
||||||
|
<div class="tw-full-width tw-overflow-hidden ffz--example-report">
|
||||||
|
<div v-if="url" class="tw-c-background-alt-2 tw-font-size-5 tw-pd-y-05 tw-pd-x-1 tw-border-radius-large">
|
||||||
|
<div v-if="raw_loading" class="tw-align-center">
|
||||||
|
<h1 class="tw-mg-5 ffz-i-zreknarf loading" />
|
||||||
|
</div>
|
||||||
|
<code v-else>{{ raw_data }}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { deep_copy } from 'utilities/object'
|
||||||
|
import { debounce } from '../../../utilities/object';
|
||||||
|
|
||||||
|
const STOCK_URLS = [
|
||||||
|
'https://www.twitch.tv/sirstendec',
|
||||||
|
'https://discord.gg/UrAkGhT',
|
||||||
|
'https://www.youtube.com/watch?v=CAL4WMpBNs0',
|
||||||
|
'https://xkcd.com/221/',
|
||||||
|
'https://github.com/FrankerFaceZ/FrankerFaceZ',
|
||||||
|
'https://twitter.com/frankerfacez',
|
||||||
|
'https://twitter.com/FrankerFaceZ/status/1240717057630625792'
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
'chat-rich': async () => {
|
||||||
|
const stuff = await import(/* webpackChunkName: "chat" */ 'src/modules/chat/components');
|
||||||
|
return stuff.default('./chat-rich.vue').default;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
props: ['item', 'context'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
stock_urls: deep_copy(STOCK_URLS),
|
||||||
|
raw_url: STOCK_URLS[Math.floor(Math.random() * STOCK_URLS.length)],
|
||||||
|
rich_data: null,
|
||||||
|
isCustomURL: false,
|
||||||
|
raw_loading: false,
|
||||||
|
raw_data: null,
|
||||||
|
events: {
|
||||||
|
on: (...args) => this.item.getChat().on(...args),
|
||||||
|
off: (...args) => this.item.getChat().off(...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
url() {
|
||||||
|
try {
|
||||||
|
return new URL(this.raw_url).toString();
|
||||||
|
} catch(err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
url() {
|
||||||
|
this.rebuildData();
|
||||||
|
},
|
||||||
|
|
||||||
|
rich_data() {
|
||||||
|
this.refreshRaw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.rebuildData = debounce(this.rebuildData, 250);
|
||||||
|
this.refreshRaw = debounce(this.refreshRaw, 250);
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.chat = this.item.getChat();
|
||||||
|
this.chat.on('chat:update-link-resolver', this.checkRefreshRaw, this);
|
||||||
|
this.rebuildData();
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.chat.off('chat:update-link-resolver', this.checkRefreshRaw, this);
|
||||||
|
this.chat = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
checkRefreshRaw(url) {
|
||||||
|
if ( ! url || (url && url === this.url) )
|
||||||
|
this.refreshRaw();
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshRaw() {
|
||||||
|
this.raw_data = null;
|
||||||
|
if ( ! this.rich_data ) {
|
||||||
|
this.raw_loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.raw_loading = true;
|
||||||
|
try {
|
||||||
|
this.raw_data = JSON.stringify(await this.chat.get_link_info(this.url), null, '\t');
|
||||||
|
} catch(err) {
|
||||||
|
this.raw_data = `Error\n\n${err.toString()}`;
|
||||||
|
}
|
||||||
|
this.raw_loading = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
rebuildData() {
|
||||||
|
if ( ! this.url )
|
||||||
|
return this.rich_data = null;
|
||||||
|
|
||||||
|
const token = {
|
||||||
|
type: 'link',
|
||||||
|
force_rich: true,
|
||||||
|
is_mail: false,
|
||||||
|
url: this.url,
|
||||||
|
text: this.url
|
||||||
|
};
|
||||||
|
|
||||||
|
this.rich_data = this.chat.rich_providers.link.process.call(this.chat, token);
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.chat.clearLinkCache(this.url);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSelectChange() {
|
||||||
|
const idx = this.$refs.selector.selectedIndex,
|
||||||
|
raw_value = this.stock_urls[idx];
|
||||||
|
|
||||||
|
if ( raw_value ) {
|
||||||
|
this.raw_url = raw_value;
|
||||||
|
this.isCustomURL = false;
|
||||||
|
} else
|
||||||
|
this.isCustomURL = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
onTextChange() {
|
||||||
|
this.raw_url = this.$refs.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -78,22 +78,22 @@ export default class TooltipProvider extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_createInstance(container) {
|
_createInstance(container, klass = 'ffz-tooltip', default_type) {
|
||||||
return new Tooltip(container, 'ffz-tooltip', {
|
return new Tooltip(container, klass, {
|
||||||
html: true,
|
html: true,
|
||||||
i18n: this.i18n,
|
i18n: this.i18n,
|
||||||
live: true,
|
live: true,
|
||||||
|
|
||||||
delayHide: this.checkDelayHide.bind(this),
|
delayHide: this.checkDelayHide.bind(this, default_type),
|
||||||
delayShow: this.checkDelayShow.bind(this),
|
delayShow: this.checkDelayShow.bind(this, default_type),
|
||||||
content: this.process.bind(this),
|
content: this.process.bind(this, default_type),
|
||||||
interactive: this.checkInteractive.bind(this),
|
interactive: this.checkInteractive.bind(this, default_type),
|
||||||
hover_events: this.checkHoverEvents.bind(this),
|
hover_events: this.checkHoverEvents.bind(this, default_type),
|
||||||
|
|
||||||
onShow: this.delegateOnShow.bind(this),
|
onShow: this.delegateOnShow.bind(this, default_type),
|
||||||
onHide: this.delegateOnHide.bind(this),
|
onHide: this.delegateOnHide.bind(this, default_type),
|
||||||
|
|
||||||
popperConfig: this.delegatePopperConfig.bind(this),
|
popperConfig: this.delegatePopperConfig.bind(this, default_type),
|
||||||
popper: {
|
popper: {
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
modifiers: {
|
modifiers: {
|
||||||
|
@ -132,8 +132,8 @@ export default class TooltipProvider extends Module {
|
||||||
this.tips.cleanup();
|
this.tips.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
delegatePopperConfig(target, tip, pop_opts) {
|
delegatePopperConfig(default_type, target, tip, pop_opts) {
|
||||||
const type = target.dataset.tooltipType,
|
const type = target.dataset.tooltipType || default_type,
|
||||||
handler = this.types[type];
|
handler = this.types[type];
|
||||||
|
|
||||||
if ( handler && handler.popperConfig )
|
if ( handler && handler.popperConfig )
|
||||||
|
@ -142,24 +142,24 @@ export default class TooltipProvider extends Module {
|
||||||
return pop_opts;
|
return pop_opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
delegateOnShow(target, tip) {
|
delegateOnShow(default_type, target, tip) {
|
||||||
const type = target.dataset.tooltipType,
|
const type = target.dataset.tooltipType || default_type,
|
||||||
handler = this.types[type];
|
handler = this.types[type];
|
||||||
|
|
||||||
if ( handler && handler.onShow )
|
if ( handler && handler.onShow )
|
||||||
handler.onShow(target, tip);
|
handler.onShow(target, tip);
|
||||||
}
|
}
|
||||||
|
|
||||||
delegateOnHide(target, tip) {
|
delegateOnHide(default_type, target, tip) {
|
||||||
const type = target.dataset.tooltipType,
|
const type = target.dataset.tooltipType || default_type,
|
||||||
handler = this.types[type];
|
handler = this.types[type];
|
||||||
|
|
||||||
if ( handler && handler.onHide )
|
if ( handler && handler.onHide )
|
||||||
handler.onHide(target, tip);
|
handler.onHide(target, tip);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkDelayShow(target, tip) {
|
checkDelayShow(default_type, target, tip) {
|
||||||
const type = target.dataset.tooltipType,
|
const type = target.dataset.tooltipType || default_type,
|
||||||
handler = this.types[type];
|
handler = this.types[type];
|
||||||
|
|
||||||
if ( has(handler, 'delayShow') )
|
if ( has(handler, 'delayShow') )
|
||||||
|
@ -168,8 +168,8 @@ export default class TooltipProvider extends Module {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkDelayHide(target, tip) {
|
checkDelayHide(default_type, target, tip) {
|
||||||
const type = target.dataset.tooltipType,
|
const type = target.dataset.tooltipType || default_type,
|
||||||
handler = this.types[type];
|
handler = this.types[type];
|
||||||
|
|
||||||
if ( has(handler, 'delayHide') )
|
if ( has(handler, 'delayHide') )
|
||||||
|
@ -178,8 +178,8 @@ export default class TooltipProvider extends Module {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkInteractive(target, tip) {
|
checkInteractive(default_type, target, tip) {
|
||||||
const type = target.dataset.tooltipType,
|
const type = target.dataset.tooltipType || default_type,
|
||||||
handler = this.types[type];
|
handler = this.types[type];
|
||||||
|
|
||||||
if ( has(handler, 'interactive') )
|
if ( has(handler, 'interactive') )
|
||||||
|
@ -188,8 +188,8 @@ export default class TooltipProvider extends Module {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkHoverEvents(target, tip) {
|
checkHoverEvents(default_type, target, tip) {
|
||||||
const type = target.dataset.tooltipType,
|
const type = target.dataset.tooltipType || default_type,
|
||||||
handler = this.types[type];
|
handler = this.types[type];
|
||||||
|
|
||||||
if ( has(handler, 'hover_events') )
|
if ( has(handler, 'hover_events') )
|
||||||
|
@ -198,8 +198,8 @@ export default class TooltipProvider extends Module {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
process(target, tip) {
|
process(default_type, target, tip) {
|
||||||
const type = target.dataset.tooltipType || 'text',
|
const type = target.dataset.tooltipType || default_type || 'text',
|
||||||
handler = this.types[type];
|
handler = this.types[type];
|
||||||
|
|
||||||
if ( ! handler )
|
if ( ! handler )
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {debounce} from 'utilities/object';
|
||||||
import { createElement, setChildren } from 'utilities/dom';
|
import { createElement, setChildren } from 'utilities/dom';
|
||||||
|
|
||||||
|
|
||||||
const USER_PAGES = ['user', 'user-home', 'video', 'user-video', 'user-clip', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following'];
|
const USER_PAGES = ['user', 'user-home', 'user-about', 'video', 'user-video', 'user-clip', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following'];
|
||||||
|
|
||||||
export default class Channel extends Module {
|
export default class Channel extends Module {
|
||||||
|
|
||||||
|
@ -31,6 +31,17 @@ export default class Channel extends Module {
|
||||||
this.inject('metadata');
|
this.inject('metadata');
|
||||||
this.inject('socket');
|
this.inject('socket');
|
||||||
|
|
||||||
|
this.settings.add('channel.panel-tips', {
|
||||||
|
default: true,
|
||||||
|
ui: {
|
||||||
|
path: 'Channel > Behavior >> Panels',
|
||||||
|
title: 'Display rich tool-tips for links in channel panels.',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
},
|
||||||
|
|
||||||
|
changed: () => this.updatePanelTips()
|
||||||
|
});
|
||||||
|
|
||||||
this.settings.add('channel.auto-click-chat', {
|
this.settings.add('channel.auto-click-chat', {
|
||||||
default: false,
|
default: false,
|
||||||
ui: {
|
ui: {
|
||||||
|
@ -61,6 +72,13 @@ export default class Channel extends Module {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this.ChannelPanels = this.fine.define(
|
||||||
|
'channel-panels',
|
||||||
|
n => n.layoutMasonry && n.updatePanelOrder && n.onExtensionPoppedOut,
|
||||||
|
USER_PAGES
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
this.ChannelRoot = this.elemental.define(
|
this.ChannelRoot = this.elemental.define(
|
||||||
'channel-root', '.channel-root',
|
'channel-root', '.channel-root',
|
||||||
USER_PAGES,
|
USER_PAGES,
|
||||||
|
@ -91,6 +109,14 @@ export default class Channel extends Module {
|
||||||
|
|
||||||
this.on('i18n:update', this.updateLinks, this);
|
this.on('i18n:update', this.updateLinks, this);
|
||||||
|
|
||||||
|
this.ChannelPanels.on('mount', this.updatePanelTips, this);
|
||||||
|
this.ChannelPanels.on('update', this.updatePanelTips, this);
|
||||||
|
this.ChannelPanels.on('unmount', this.removePanelTips, this);
|
||||||
|
this.ChannelPanels.ready((cls, instances) => {
|
||||||
|
for(const inst of instances)
|
||||||
|
this.updatePanelTips(inst);
|
||||||
|
});
|
||||||
|
|
||||||
this.ChannelRoot.on('mount', this.updateRoot, this);
|
this.ChannelRoot.on('mount', this.updateRoot, this);
|
||||||
this.ChannelRoot.on('mutate', this.updateRoot, this);
|
this.ChannelRoot.on('mutate', this.updateRoot, this);
|
||||||
this.ChannelRoot.on('unmount', this.removeRoot, this);
|
this.ChannelRoot.on('unmount', this.removeRoot, this);
|
||||||
|
@ -107,6 +133,35 @@ export default class Channel extends Module {
|
||||||
this.checkNavigation();
|
this.checkNavigation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePanelTips(inst) {
|
||||||
|
if ( ! inst ) {
|
||||||
|
for(const inst of this.ChannelPanels.instances) {
|
||||||
|
if ( inst )
|
||||||
|
this.updatePanelTips(inst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const el = this.fine.getChildNode(inst);
|
||||||
|
if ( ! el || ! this.settings.get('channel.panel-tips') )
|
||||||
|
return this.removePanelTips(inst);
|
||||||
|
|
||||||
|
if ( inst._ffz_tips && inst._ffz_tip_el !== el )
|
||||||
|
this.removePanelTips(inst);
|
||||||
|
|
||||||
|
if ( ! inst._ffz_tips ) {
|
||||||
|
inst._ffz_tips = this.resolve('tooltips')._createInstance(el, 'tw-link', 'link');
|
||||||
|
inst._ffz_tip_el = el;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removePanelTips(inst) { // eslint-disable-line class-methods-use-this
|
||||||
|
if ( inst._ffz_tips ) {
|
||||||
|
inst._ffz_tips.destroy();
|
||||||
|
inst._ffz_tips = null;
|
||||||
|
inst._ffz_tip_el = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
checkNavigation() {
|
checkNavigation() {
|
||||||
if ( ! this.settings.get('channel.auto-click-chat') || this.router.current_name !== 'user-home' )
|
if ( ! this.settings.get('channel.auto-click-chat') || this.router.current_name !== 'user-home' )
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -38,7 +38,7 @@ export default class RichContent extends Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async load() {
|
||||||
try {
|
try {
|
||||||
let data = this.props.getData();
|
let data = this.props.getData();
|
||||||
if ( data instanceof Promise ) {
|
if ( data instanceof Promise ) {
|
||||||
|
@ -75,6 +75,28 @@ export default class RichContent extends Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkReload(url) {
|
||||||
|
if ( ! url || (url && this.props.url === url) )
|
||||||
|
this.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() {
|
||||||
|
this.setState({
|
||||||
|
loaded: false,
|
||||||
|
error: false
|
||||||
|
}, () => this.load());
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
t.on('chat:update-link-resolver', this.checkReload, this);
|
||||||
|
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
t.off('chat:update-link-resolver', this.checkReload, this);
|
||||||
|
}
|
||||||
|
|
||||||
renderCardImage() {
|
renderCardImage() {
|
||||||
return (<div class={`chat-card__preview-img tw-align-items-center tw-c-background-alt-2 tw-flex tw-flex-shrink-0 tw-justify-content-center${this.state.image_square ? ' square' : ''}`}>
|
return (<div class={`chat-card__preview-img tw-align-items-center tw-c-background-alt-2 tw-flex tw-flex-shrink-0 tw-justify-content-center${this.state.image_square ? ' square' : ''}`}>
|
||||||
{this.state.error ?
|
{this.state.error ?
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {has} from 'utilities/object';
|
||||||
const STYLE_VALIDATOR = document.createElement('span');
|
const STYLE_VALIDATOR = document.createElement('span');
|
||||||
|
|
||||||
const CLASSES = {
|
const CLASSES = {
|
||||||
'unfollow': '.follow-btn__follow-btn--following',
|
'unfollow': '.follow-btn__follow-btn--following,.follow-btn--following',
|
||||||
'top-discover': '.navigation-link[data-a-target="discover-link"]',
|
'top-discover': '.navigation-link[data-a-target="discover-link"]',
|
||||||
'side-nav': '.side-nav',
|
'side-nav': '.side-nav',
|
||||||
'side-rec-channels': '.side-nav .recommended-channels,.side-nav .side-nav-section + .side-nav-section:not(.online-friends)',
|
'side-rec-channels': '.side-nav .recommended-channels,.side-nav .side-nav-section + .side-nav-section:not(.online-friends)',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
query($logins: [String!]) {
|
query FFZ_FollowsList($logins: [String!]) {
|
||||||
users(logins: $logins) {
|
users(logins: $logins) {
|
||||||
id
|
id
|
||||||
login
|
login
|
||||||
|
|
|
@ -1511,7 +1511,7 @@ export default class Player extends Module {
|
||||||
|
|
||||||
tryTheatreMode(inst) {
|
tryTheatreMode(inst) {
|
||||||
if ( ! inst._ffz_theater_timer )
|
if ( ! inst._ffz_theater_timer )
|
||||||
inst._ffz_theater_timer = requestAnimationFrame(() => {
|
inst._ffz_theater_timer = setTimeout(() => {
|
||||||
inst._ffz_theater_timer = null;
|
inst._ffz_theater_timer = null;
|
||||||
|
|
||||||
if ( ! this.settings.get('player.theatre.auto-enter') || ! inst._ffz_mounted )
|
if ( ! this.settings.get('player.theatre.auto-enter') || ! inst._ffz_mounted )
|
||||||
|
@ -1525,7 +1525,7 @@ export default class Player extends Module {
|
||||||
|
|
||||||
if ( inst.props.onTheatreModeEnabled )
|
if ( inst.props.onTheatreModeEnabled )
|
||||||
inst.props.onTheatreModeEnabled();
|
inst.props.onTheatreModeEnabled();
|
||||||
});
|
}, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channel-panels {
|
||||||
|
.default-panel {
|
||||||
|
& > .tw-link {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tw-root--theme-ffz, .tw-root--theme-ffz.tw-root--theme-dark, .tw-root--theme-dark, body {
|
.tw-root--theme-ffz, .tw-root--theme-ffz.tw-root--theme-dark, .tw-root--theme-dark, body {
|
||||||
.ffz-stat > .tw-button--text,
|
.ffz-stat > .tw-button--text,
|
||||||
.ffz-stat.tw-button--text {
|
.ffz-stat.tw-button--text {
|
||||||
|
|
|
@ -59,6 +59,16 @@ textarea.tw-input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ffz--link-tester {
|
||||||
|
code {
|
||||||
|
tab-size: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
min-width: 15rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.ffz-min-width-unset {
|
.ffz-min-width-unset {
|
||||||
min-width: unset !important;
|
min-width: unset !important;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue