mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-28 23:37:41 +00:00
4.9.6
* Added: Previews for rich chat embeds and rich tool-tips to the settings menu. * Changed: Disabled Smooth Scrolling for now, as no one has been able to get it working correctly after Twitch's changes to how chat scrolling works.
This commit is contained in:
parent
512fe8898d
commit
1a171939ac
15 changed files with 178 additions and 40 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.9.5",
|
"version": "4.9.6",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
3
src/modules/chat/components.js
Normal file
3
src/modules/chat/components.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
export default require.context('./components', false, /\.vue$/);
|
|
@ -2,34 +2,41 @@
|
||||||
<a :href="url" class="chat-card__link" target="_blank" rel="noreferrer noopener">
|
<a :href="url" class="chat-card__link" target="_blank" rel="noreferrer noopener">
|
||||||
<div class="ffz--chat-card tw-elevation-1 tw-mg-t">
|
<div class="ffz--chat-card tw-elevation-1 tw-mg-t">
|
||||||
<div class="tw-c-background-base tw-flex tw-flex-nowrap tw-pd-05">
|
<div class="tw-c-background-base tw-flex tw-flex-nowrap tw-pd-05">
|
||||||
<div class="chat-card__preview-img tw-c-background-alt-2 tw-align-items-center tw-flex tw-flex-shrink-0 tw-justify-content-center">
|
<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">
|
||||||
<div class="tw-card-img tw-flex-shrink-0 tw-flex tw-justify-content-center">
|
<img
|
||||||
<img
|
v-if="error"
|
||||||
v-if="error"
|
class="chat-card__error-img"
|
||||||
class="chat-card__error-img"
|
src=""
|
||||||
src=""
|
>
|
||||||
data-test-selector="chat-card-error"
|
<div
|
||||||
>
|
v-else
|
||||||
<figure
|
class="tw-card-img tw-flex-shrink-0 tw-overflow-hidden"
|
||||||
v-else
|
>
|
||||||
class="tw-aspect tw-aspect--16x9 tw-aspect--align-top"
|
<aspect :ratio="16/9">
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
v-if="loaded && image"
|
v-if="loaded && image"
|
||||||
:src="image"
|
:src="image"
|
||||||
:alt="title"
|
:alt="title"
|
||||||
class="tw-image"
|
class="tw-image"
|
||||||
>
|
>
|
||||||
</figure>
|
</aspect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:class="{'ffz--two-line': desc_2}"
|
:class="{'ffz--two-line': desc_2}"
|
||||||
class="tw-overflow-hidden tw-align-items-center tw-flex"
|
class="ffz--card-text tw-overflow-hidden tw-align-items-center tw-flex"
|
||||||
>
|
>
|
||||||
<div class="tw-full-width tw-pd-l-1">
|
<div class="tw-full-width tw-pd-l-1">
|
||||||
<div class="chat-card__title tw-ellipsis">
|
<div class="chat-card__title tw-ellipsis">
|
||||||
<span
|
<span
|
||||||
|
v-if="! loaded"
|
||||||
|
class="tw-font-size-5"
|
||||||
|
data-test-selector="chat-card-title"
|
||||||
|
>
|
||||||
|
{{ t('card.loading', 'Loading...') }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
:title="title"
|
:title="title"
|
||||||
class="tw-font-size-5"
|
class="tw-font-size-5"
|
||||||
data-test-selector="chat-card-title"
|
data-test-selector="chat-card-title"
|
||||||
|
@ -37,7 +44,7 @@
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-ellipsis">
|
<div v-if="loaded" class="tw-ellipsis">
|
||||||
<span
|
<span
|
||||||
:title="desc_1"
|
:title="desc_1"
|
||||||
class="tw-c-text-alt-2 tw-font-size-6"
|
class="tw-c-text-alt-2 tw-font-size-6"
|
||||||
|
@ -46,7 +53,7 @@
|
||||||
{{ desc_1 }}
|
{{ desc_1 }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="desc_2" class="tw-ellipsis">
|
<div v-if="loaded && desc_2" class="tw-ellipsis">
|
||||||
<span
|
<span
|
||||||
:title="desc_2"
|
:title="desc_2"
|
||||||
class="tw-c-text-alt-2 tw-font-size-6"
|
class="tw-c-text-alt-2 tw-font-size-6"
|
||||||
|
|
|
@ -135,7 +135,11 @@ export default class Chat extends Module {
|
||||||
path: 'Chat > Appearance >> Rich Content',
|
path: 'Chat > Appearance >> Rich Content',
|
||||||
title: 'Display rich content embeds for all links.',
|
title: 'Display rich content embeds for all links.',
|
||||||
description: '*Streamers: Please be aware that this is a potential vector for NSFW imagery via thumbnails, so be mindful when capturing chat with this enabled.*',
|
description: '*Streamers: Please be aware that this is a potential vector for NSFW imagery via thumbnails, so be mindful when capturing chat with this enabled.*',
|
||||||
component: 'setting-check-box'
|
component: 'setting-check-box',
|
||||||
|
extra: {
|
||||||
|
component: 'chat-rich-example',
|
||||||
|
getChat: () => this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -672,7 +676,10 @@ export default class Chat extends Module {
|
||||||
sort: -1,
|
sort: -1,
|
||||||
path: 'Chat > Tooltips >> Links',
|
path: 'Chat > Tooltips >> Links',
|
||||||
title: 'Display rich tooltips for links.',
|
title: 'Display rich tooltips for links.',
|
||||||
component: 'setting-check-box'
|
component: 'setting-check-box',
|
||||||
|
extra: {
|
||||||
|
component: 'chat-tooltip-example'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ export const Links = {
|
||||||
},
|
},
|
||||||
|
|
||||||
tooltip(target, tip) {
|
tooltip(target, tip) {
|
||||||
if ( ! this.context.get('tooltip.rich-links') )
|
if ( ! this.context.get('tooltip.rich-links') && ! target.dataset.forceTooltip )
|
||||||
return '';
|
return '';
|
||||||
|
|
||||||
if ( target.dataset.isMail === 'true' )
|
if ( target.dataset.isMail === 'true' )
|
||||||
|
|
57
src/modules/main_menu/components/chat-rich-example.vue
Normal file
57
src/modules/main_menu/components/chat-rich-example.vue
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="tw-c-text-alt tw-mg-b-05">
|
||||||
|
{{ t('setting.example', 'Example:') }}
|
||||||
|
</div>
|
||||||
|
<chat-rich
|
||||||
|
:data="data"
|
||||||
|
:url="url"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const VIDEOS = [
|
||||||
|
'https://www.youtube.com/watch?v=BFSWlDpA6C4'
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
'chat-rich': async () => {
|
||||||
|
const stuff = await import(/* webpackChunkName: "chat" */ 'src/modules/chat/components');
|
||||||
|
return stuff.default('./chat-rich.vue').default;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
props: ['context', 'item'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const url = VIDEOS[Math.floor(Math.random() * VIDEOS.length)],
|
||||||
|
token = {
|
||||||
|
type: 'link',
|
||||||
|
is_mail: false,
|
||||||
|
url,
|
||||||
|
text: url
|
||||||
|
},
|
||||||
|
chat = this.item.extra.getChat();
|
||||||
|
|
||||||
|
let data = null;
|
||||||
|
if ( chat.__rich_providers ) {
|
||||||
|
for(const provider of chat.__rich_providers) {
|
||||||
|
if ( provider.test.call(chat, token, url) ) {
|
||||||
|
data = provider.process.call(chat, token);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
38
src/modules/main_menu/components/chat-tooltip-example.vue
Normal file
38
src/modules/main_menu/components/chat-tooltip-example.vue
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<template>
|
||||||
|
<div class="tw-c-text-alt-2">
|
||||||
|
{{ t('setting.tooltip-example', 'For an example, please hover this link:') }}
|
||||||
|
<a
|
||||||
|
: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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const VIDEOS = [
|
||||||
|
'https://www.youtube.com/watch?v=BFSWlDpA6C4'
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['context', 'item'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const url = VIDEOS[Math.floor(Math.random() * VIDEOS.length)];
|
||||||
|
|
||||||
|
return {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -44,6 +44,12 @@
|
||||||
>
|
>
|
||||||
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
||||||
</section>
|
</section>
|
||||||
|
<section
|
||||||
|
v-if="item.extra"
|
||||||
|
style="padding-left:2.2rem"
|
||||||
|
>
|
||||||
|
<component :is="item.extra.component" :context="context" :item="item" />
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,9 @@
|
||||||
>
|
>
|
||||||
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
||||||
</section>
|
</section>
|
||||||
|
<section v-if="item.extra">
|
||||||
|
<component :is="item.extra.component" :context="context" :item="item" />
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,9 @@
|
||||||
>
|
>
|
||||||
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
||||||
</section>
|
</section>
|
||||||
|
<section v-if="item.extra">
|
||||||
|
<component :is="item.extra.component" :context="context" :item="item" />
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,9 @@
|
||||||
>
|
>
|
||||||
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
||||||
</section>
|
</section>
|
||||||
|
<section v-if="item.extra">
|
||||||
|
<component :is="item.extra.component" :context="context" :item="item" />
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
>
|
>
|
||||||
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
||||||
</section>
|
</section>
|
||||||
|
<section v-if="item.extra">
|
||||||
|
<component :is="item.extra.component" :context="context" :item="item" />
|
||||||
|
</section>
|
||||||
<div v-for="(i, idx) in data" :key="idx" class="tw-mg-l-1">
|
<div v-for="(i, idx) in data" :key="idx" class="tw-mg-l-1">
|
||||||
<input
|
<input
|
||||||
:id="item.full_key + idx"
|
:id="item.full_key + idx"
|
||||||
|
|
|
@ -48,6 +48,9 @@
|
||||||
>
|
>
|
||||||
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
||||||
</section>
|
</section>
|
||||||
|
<section v-if="item.extra">
|
||||||
|
<component :is="item.extra.component" :context="context" :item="item" />
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,9 @@
|
||||||
>
|
>
|
||||||
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
<markdown :source="t(item.desc_i18n_key || `${item.i18n_key}.description`, item.description, item)" />
|
||||||
</section>
|
</section>
|
||||||
|
<section v-if="item.extra">
|
||||||
|
<component :is="item.extra.component" :context="context" :item="item" />
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default class Scroller extends Module {
|
||||||
this.settings.add('chat.scroller.freeze', {
|
this.settings.add('chat.scroller.freeze', {
|
||||||
default: 0,
|
default: 0,
|
||||||
ui: {
|
ui: {
|
||||||
path: 'Chat > Behavior >> Scrolling @{"description": "Please note that FrankerFaceZ is dependent on Twitch\'s own scrolling code working correctly. There are bugs with Twitch\'s scrolling code that have existed for more than six months. If you are using Firefox, Edge, or other non-Webkit browsers, expect to have issues."}',
|
path: 'Chat > Behavior >> Scrolling',
|
||||||
title: 'Pause Chat Scrolling',
|
title: 'Pause Chat Scrolling',
|
||||||
description: 'Automatically stop chat from scrolling when moving the mouse over it or holding a key.',
|
description: 'Automatically stop chat from scrolling when moving the mouse over it or holding a key.',
|
||||||
component: 'setting-select-box',
|
component: 'setting-select-box',
|
||||||
|
@ -87,7 +87,7 @@ export default class Scroller extends Module {
|
||||||
ui: {
|
ui: {
|
||||||
path: 'Chat > Behavior >> Scrolling',
|
path: 'Chat > Behavior >> Scrolling',
|
||||||
title: 'Smooth Scrolling',
|
title: 'Smooth Scrolling',
|
||||||
description: 'Smoothly slide new chat messages into view. Speed will increase as necessary to keep up with chat.',
|
description: '**Note:** This setting has been disabled until such time as someone is able to fix major bugs with it.\n\nSmoothly slide new chat messages into view. Speed will increase as necessary to keep up with chat.',
|
||||||
component: 'setting-select-box',
|
component: 'setting-select-box',
|
||||||
data: [
|
data: [
|
||||||
{value: 0, title: 'Disabled'},
|
{value: 0, title: 'Disabled'},
|
||||||
|
@ -145,8 +145,9 @@ export default class Scroller extends Module {
|
||||||
inst.ffzMaybeUnpause();
|
inst.ffzMaybeUnpause();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.smooth_scroll = this.chat.context.get('chat.scroller.smooth-scroll');
|
this.smooth_scroll = 0; // this.chat.context.get('chat.scroller.smooth-scroll');
|
||||||
this.chat.context.on('changed:chat.scroller.smooth-scroll', val => {
|
this.chat.context.on('changed:chat.scroller.smooth-scroll', val => {
|
||||||
|
val = 0;
|
||||||
this.smooth_scroll = val;
|
this.smooth_scroll = val;
|
||||||
|
|
||||||
for(const inst of this.ChatScroller.instances)
|
for(const inst of this.ChatScroller.instances)
|
||||||
|
@ -167,15 +168,11 @@ export default class Scroller extends Module {
|
||||||
|
|
||||||
if ( old_snapshot )
|
if ( old_snapshot )
|
||||||
cls.prototype.getSnapshotBeforeUpdate = function() {
|
cls.prototype.getSnapshotBeforeUpdate = function() {
|
||||||
let auto_state;
|
|
||||||
if ( this.state ) {
|
|
||||||
auto_state = this.state.isAutoScrolling;
|
|
||||||
this.state.isAutoScrolling = false;
|
|
||||||
}
|
|
||||||
const out = old_snapshot.call(this);
|
const out = old_snapshot.call(this);
|
||||||
if ( this.state )
|
this._ffz_snapshot = out || (this.lastLine && {
|
||||||
this.state.isAutoScrolling = auto_state;
|
lastLine: this.lastLine,
|
||||||
this._ffz_snapshot = out;
|
offsetTop: this.lastLine.offsetTop
|
||||||
|
}) || null;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,16 +262,20 @@ export default class Scroller extends Module {
|
||||||
// WIP: Trying to fix the scroll position changing so that we can
|
// WIP: Trying to fix the scroll position changing so that we can
|
||||||
// smooth scroll from the previous position.
|
// smooth scroll from the previous position.
|
||||||
if ( inst.ffz_smooth_scroll && ! inst._ffz_one_fast_scroll && inst._ffz_snapshot ) {
|
if ( inst.ffz_smooth_scroll && ! inst._ffz_one_fast_scroll && inst._ffz_snapshot ) {
|
||||||
const adjustment = inst._ffz_snapshot && inst._ffz_snapshot.lastLine ? inst._ffz_snapshot.offsetTop - inst._ffz_snapshot.lastLine.offsetTop : 0;
|
const adjustment = inst._ffz_snapshot && inst._ffz_snapshot.lastLine ? inst._ffz_snapshot.lastLine.offsetTop - inst._ffz_snapshot.offsetTop: 0;
|
||||||
if ( inst.scroll && inst.scroll.scrollContent && adjustment > 0 )
|
if ( inst.scroll && inst.scroll.scrollContent && adjustment != 0 )
|
||||||
inst.scroll.scrollContent.scrollTop -= adjustment;
|
inst.scroll.scrollContent.scrollTop += adjustment;
|
||||||
}
|
}
|
||||||
|
|
||||||
inst._ffz_snapshot = null;
|
inst._ffz_snapshot = null;
|
||||||
if ( inst.state.isPaused || inst._ffz_scroll_frame )
|
if ( inst.state.isAutoScrolling && ! inst.state.isPaused ) {
|
||||||
return;
|
if ( inst.ffz_smooth_scroll && ! inst._ffz_one_fast_scroll )
|
||||||
|
inst.smoothScrollBottom();
|
||||||
this._ffz_scroll_frame = requestAnimationFrame(inst.ffz_doScroll);
|
else {
|
||||||
|
inst._ffz_one_fast_scroll = false;
|
||||||
|
inst.ffz_oldScroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New Scroll Event Handling
|
// New Scroll Event Handling
|
||||||
|
@ -617,7 +618,7 @@ export default class Scroller extends Module {
|
||||||
} else if ( difference > 200 ) {
|
} else if ( difference > 200 ) {
|
||||||
// we are starting to fall behind, speed it up a bit
|
// we are starting to fall behind, speed it up a bit
|
||||||
step += step * Math.floor(difference / 200);
|
step += step * Math.floor(difference / 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
const smoothAnimation = () => {
|
const smoothAnimation = () => {
|
||||||
if ( this.state.isPaused || ! this.state.isAutoScrolling )
|
if ( this.state.isPaused || ! this.state.isAutoScrolling )
|
||||||
|
@ -631,10 +632,11 @@ export default class Scroller extends Module {
|
||||||
// we need to move at least one full pixel for scrollTop to do anything in this delta.
|
// we need to move at least one full pixel for scrollTop to do anything in this delta.
|
||||||
if ( current_step >= 1 ) {
|
if ( current_step >= 1 ) {
|
||||||
const scroll_top = scroll_content.scrollTop,
|
const scroll_top = scroll_content.scrollTop,
|
||||||
|
next_top = scroll_top + current_step,
|
||||||
target_top = scroll_content.scrollHeight - scroll_content.clientHeight;
|
target_top = scroll_content.scrollHeight - scroll_content.clientHeight;
|
||||||
|
|
||||||
old_time = current_time;
|
old_time = current_time;
|
||||||
if ( scroll_top < target_top ) {
|
if ( next_top < target_top ) {
|
||||||
scroll_content.scrollTop = scroll_top + current_step;
|
scroll_content.scrollTop = scroll_top + current_step;
|
||||||
this._ffz_smooth_animation = requestAnimationFrame(smoothAnimation);
|
this._ffz_smooth_animation = requestAnimationFrame(smoothAnimation);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue