mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.46.0
* Added: Profile rule to allow profiles to only be active on a specific monitor. This currently relies on a Chromium-specific API and is not available for Firefox users. (Closes #1358) * Added: Simple 'And' profile rule for building complex behaviors without needing a workaround. * Changed: Update the link detection regex to match Twitch's native behavior. (Closes #1353) * Changed: When the add-ons list is being filtered by a search term, display a notice to the user. * Fixed: Unable to set keys for Message Hover actions. (Closes #1316) * Fixed: The Wide emote effect not correctly being disabled. * Fixed: Long emote names causing the favorite button on the emote card to appear incorrectly. * Fixed: Whenever a user's last profile is removed, automatically create a new profile. * Fixed: When scrolling the emote menu, take best efforts to ensure other page elements are not scrolled incorrectly. This notably fixes a scrolling issue on mod view. * Fixed: If users have disabled the `document.fonts` API, do not attempt to filter the list of installed fonts for the font selector controls.
This commit is contained in:
parent
3d4b4f6225
commit
2db7122c1d
10 changed files with 199 additions and 18 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.45.1",
|
||||
"version": "4.46.0",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
|
|
|
@ -22,7 +22,8 @@ const SHRINK_X = MODIFIER_FLAGS.ShrinkX,
|
|||
const EMOTE_CLASS = 'chat-image chat-line__message--emote',
|
||||
//WHITESPACE = /^\s*$/,
|
||||
//LINK_REGEX = /([^\w@#%\-+=:~])?((?:(https?:\/\/)?(?:[\w@#%\-+=:~]+\.)+[a-z]{2,6}(?:\/[\w./@#%&()\-+=:?~]*)?))([^\w./@#%&()\-+=:?~]|\s|$)/g,
|
||||
NEW_LINK_REGEX = /(?:(https?:\/\/)?((?:[\w#%\-+=:~]+\.)+[a-z]{2,10}(?:\/[\w./#%&@()\-+=:?~]*)?))/g,
|
||||
NEW_LINK_REGEX = /(?:(https?:\/\/)?((?:[\w#%\-+=:~]+\.)+[a-z]{2,10}(?:\/[\w./#%&@()\-+=:?~]*[^\.!,?])?))/g,
|
||||
//OLD_NEW_LINK_REGEX = /(?:(https?:\/\/)?((?:[\w#%\-+=:~]+\.)+[a-z]{2,10}(?:\/[\w./#%&@()\-+=:?~]*)?))/g,
|
||||
//MENTION_REGEX = /([^\w@#%\-+=:~])?(@([^\u0000-\u007F]+|\w+)+)([^\w./@#%&()\-+=:?~]|\s|$)/g; // eslint-disable-line no-control-regex
|
||||
MENTION_REGEX = /^(['"*([{<\\/]*)(@)((?:[^\u0000-\u007F]|[\w-])+)(?:\b|$)/; // eslint-disable-line no-control-regex
|
||||
|
||||
|
@ -1275,9 +1276,9 @@ export const AddonEmotes = {
|
|||
if ( effects ) {
|
||||
this.emotes.ensureEffect(effects);
|
||||
|
||||
if ( (effects & SHRINK_X) === SHRINK_X )
|
||||
if ( (effects & SHRINK_X) === SHRINK_X && this.emotes.effects_enabled?.ShrinkX )
|
||||
style.width *= 0.5;
|
||||
if ( (effects & STRETCH_X) === STRETCH_X )
|
||||
if ( (effects & STRETCH_X) === STRETCH_X && this.emotes.effects_enabled?.GrowX )
|
||||
style.width *= 2;
|
||||
/*if ( (effects & SHRINK_Y) === SHRINK_Y )
|
||||
style.height *= 0.5;
|
||||
|
@ -1538,11 +1539,11 @@ export const AddonEmotes = {
|
|||
|
||||
let changed = false;
|
||||
|
||||
if ( (effects & SHRINK_X) === SHRINK_X ) {
|
||||
if ( (effects & SHRINK_X) === SHRINK_X && this.emotes.effects_enabled?.ShrinkX ) {
|
||||
style.width *= 0.5;
|
||||
changed = true;
|
||||
}
|
||||
if ( (effects & STRETCH_X) === STRETCH_X ) {
|
||||
if ( (effects & STRETCH_X) === STRETCH_X && this.emotes.effects_enabled?.GrowX ) {
|
||||
style.width *= 2;
|
||||
changed = true;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="tw-align-left tw-flex-grow-1 tw-ellipsis tw-mg-x-1 tw-mg-y-05 viewer-card__display-name">
|
||||
<div class="tw-align-left tw-flex-grow-1 tw-ellipsis tw-mg-l-1 tw-mg-y-05 viewer-card__display-name">
|
||||
<h4 class="tw-inline tw-ellipsis" :title="emote ? emote.name : raw_emote.name">
|
||||
{{ emote ? emote.name : raw_emote.name }}
|
||||
</h4>
|
||||
|
@ -76,7 +76,7 @@
|
|||
v-if="canFavorite"
|
||||
:data-title="favoriteLabel"
|
||||
:aria-label="favoriteLabel"
|
||||
class="viewer-card-drag-cancel tw-align-self-start tw-align-items-center tw-align-middle tw-border-radius-medium tw-button-icon tw-core-button tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-tooltip"
|
||||
class="tw-flex-shrink-0 viewer-card-drag-cancel tw-align-self-start tw-align-items-center tw-align-middle tw-border-radius-medium tw-button-icon tw-core-button tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-tooltip"
|
||||
@click="toggleFavorite"
|
||||
>
|
||||
<span class="tw-button-icon__icon">
|
||||
|
|
|
@ -746,8 +746,10 @@ export default {
|
|||
if ( this.$refs.key_meta.checked )
|
||||
i |= 8;
|
||||
|
||||
this.edit_data.display.hover = this.$refs.key_hover.checked;
|
||||
this.edit_data.display.keys = i;
|
||||
|
||||
if ( this.has_hover_modifier )
|
||||
this.edit_data.display.hover = this.$refs.key_hover.checked;
|
||||
},
|
||||
|
||||
edit() {
|
||||
|
|
|
@ -63,6 +63,16 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="ready && visible_addons.length !== listed_addons.length" class="tw-align-center tw-pd-1">
|
||||
{{ t('addon.displaying', 'Displaying {visible, number} of {total, plural, one {# add-on} other {# add-ons} }.', {
|
||||
visible: visible_addons.length,
|
||||
total: listed_addons.length
|
||||
}) }}
|
||||
<template v-if="filter && filter.length">
|
||||
{{ t('addon.displaying.filtered', 'The visible add-ons are being filtered by your search. Clear it to view all available add-ons.') }}
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="visible_unlisted" class="tw-flex tw-align-items-center">
|
||||
<div class="tw-flex-grow-1" />
|
||||
<div
|
||||
|
@ -136,6 +146,10 @@ export default {
|
|||
return this.sorted_addons.filter(addon => this.shouldShow(addon));
|
||||
},
|
||||
|
||||
listed_addons() {
|
||||
return this.sorted_addons.filter(addon => ! addon.unlisted)
|
||||
},
|
||||
|
||||
sorted_addons() {
|
||||
const addons = this.item.getAddons();
|
||||
|
||||
|
|
65
src/settings/components/monitor.vue
Normal file
65
src/settings/components/monitor.vue
Normal file
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<section class="tw-flex-grow-1 tw-align-self-start">
|
||||
<div class="tw-flex tw-align-items-center">
|
||||
<label :for="'label$' + id">
|
||||
{{ t(type.i18n, type.title) }}
|
||||
</label>
|
||||
|
||||
<select
|
||||
:id="'label$' + id"
|
||||
v-model="value.data.label"
|
||||
class="tw-flex-grow-1 tw-mg-l-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-select"
|
||||
>
|
||||
<template v-for="mon in monitors">
|
||||
<option :value="mon.label">
|
||||
{{ mon.label }} ({{ mon.width }}×{{ mon.height }})
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
let last_id = 0;
|
||||
|
||||
export default {
|
||||
props: ['value', 'type', 'filters', 'context'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
id: last_id++,
|
||||
has_monitors: true,
|
||||
monitors: []
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.detectMonitors();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async detectMonitors() {
|
||||
let data;
|
||||
try {
|
||||
data = await window.getScreenDetails();
|
||||
} catch(err) {
|
||||
console.error('Unable to get screen details', err);
|
||||
this.has_monitors = false;
|
||||
this.monitors = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this.monitors = [];
|
||||
for(const mon of data.screens)
|
||||
this.monitors.push({
|
||||
label: mon.label,
|
||||
width: mon.width,
|
||||
height: mon.height
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -37,6 +37,21 @@ export const Invert = {
|
|||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/nested.vue')
|
||||
};
|
||||
|
||||
export const And = {
|
||||
createTest(config, rule_types, rebuild) {
|
||||
return createTester(config, rule_types, false, false, rebuild);
|
||||
},
|
||||
|
||||
childRules: true,
|
||||
|
||||
tall: true,
|
||||
title: 'And',
|
||||
i18n: 'settings.filter.and',
|
||||
|
||||
default: () => [],
|
||||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/nested.vue')
|
||||
};
|
||||
|
||||
export const Or = {
|
||||
createTest(config, rule_types, rebuild) {
|
||||
return createTester(config, rule_types, false, true, rebuild);
|
||||
|
@ -83,7 +98,7 @@ export const Constant = {
|
|||
default: true,
|
||||
|
||||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/basic-toggle.vue')
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Context Stuff
|
||||
|
@ -374,4 +389,54 @@ export const Title = {
|
|||
}),
|
||||
|
||||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/title.vue')
|
||||
};
|
||||
};
|
||||
|
||||
// Monitor Stuff
|
||||
|
||||
export let Monitor = null;
|
||||
|
||||
if ( window.getScreenDetails ) {
|
||||
|
||||
Monitor = {
|
||||
_used: false,
|
||||
details: undefined,
|
||||
|
||||
used: () => {
|
||||
const out = Monitor._used;
|
||||
Monitor._used = false;
|
||||
return out;
|
||||
},
|
||||
|
||||
createTest(config = {}, _, reload) {
|
||||
if ( ! config.label )
|
||||
return () => false;
|
||||
|
||||
Monitor._used = true;
|
||||
if ( Monitor.details === undefined )
|
||||
FrankerFaceZ.get().resolve('settings').createMonitorUpdate().then(() => {
|
||||
reload();
|
||||
});
|
||||
|
||||
return () => {
|
||||
Monitor._used = true;
|
||||
const details = Monitor.details,
|
||||
screen = details?.currentScreen;
|
||||
|
||||
if ( ! screen )
|
||||
return false;
|
||||
|
||||
return screen.label === config.label;
|
||||
};
|
||||
},
|
||||
|
||||
default: () => ({
|
||||
label: null
|
||||
}),
|
||||
|
||||
title: 'Current Monitor',
|
||||
i18n: 'settings.filter.monitor',
|
||||
|
||||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/monitor.vue')
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ export default class SettingsManager extends Module {
|
|||
this.filters = {};
|
||||
|
||||
for(const key in FILTERS)
|
||||
if ( has(FILTERS, key) )
|
||||
if ( has(FILTERS, key) && FILTERS[key] )
|
||||
this.filters[key] = FILTERS[key];
|
||||
|
||||
|
||||
|
@ -247,8 +247,28 @@ export default class SettingsManager extends Module {
|
|||
}
|
||||
|
||||
|
||||
async createMonitorUpdate() {
|
||||
const Monitor = FILTERS?.Monitor;
|
||||
if ( ! Monitor || Monitor.details !== undefined )
|
||||
return;
|
||||
|
||||
Monitor.details = null;
|
||||
try {
|
||||
Monitor.details = await window.getScreenDetails();
|
||||
Monitor.details.addEventListener('currentscreenchange', () => {
|
||||
for(const context of this.__contexts)
|
||||
context.selectProfiles();
|
||||
});
|
||||
|
||||
} catch(err) {
|
||||
this.log.error('Unable to get monitor details', err);
|
||||
Monitor.details = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
updateClock() {
|
||||
const captured = require('./filters').Time.captured();
|
||||
const captured = FILTERS?.Time?.captured?.();
|
||||
if ( ! captured?.length )
|
||||
return;
|
||||
|
||||
|
@ -918,7 +938,19 @@ export default class SettingsManager extends Module {
|
|||
|
||||
|
||||
_saveProfiles() {
|
||||
this.provider.set('profiles', this.__profiles.filter(prof => ! prof.ephemeral).map(prof => prof.data));
|
||||
const out = this.__profiles.filter(prof => ! prof.ephemeral).map(prof => prof.data);
|
||||
|
||||
// Ensure that we always have a non-ephemeral profile.
|
||||
if ( ! out ) {
|
||||
this.createProfile({
|
||||
name: 'Default Profile',
|
||||
i18n_key: 'setting.profiles.default',
|
||||
description: 'Settings that apply everywhere on Twitch.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.provider.set('profiles', out);
|
||||
for(const context of this.__contexts)
|
||||
context.selectProfiles();
|
||||
|
||||
|
|
|
@ -1212,7 +1212,7 @@ export default class EmoteMenu extends Module {
|
|||
requestAnimationFrame(() => {
|
||||
const el = this.nav_ref?.querySelector?.(`button[data-key="${this.state.active_nav}"]`);
|
||||
if ( el )
|
||||
el.scrollIntoView({block: 'nearest'});
|
||||
el.scrollIntoView({block: 'nearest', inline: 'start'});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1342,7 +1342,7 @@ export default class EmoteMenu extends Module {
|
|||
const el = this.ref?.querySelector?.(`section[data-key="${key}"]`);
|
||||
if ( el ) {
|
||||
this.lock_active = true;
|
||||
el.scrollIntoView();
|
||||
el.scrollIntoView({block: 'nearest', inline: 'start'});
|
||||
this.setState({
|
||||
active_nav: key
|
||||
});
|
||||
|
@ -1381,7 +1381,7 @@ export default class EmoteMenu extends Module {
|
|||
el = set && this.ref?.querySelector?.(`section[data-key="${set.key}"]`);
|
||||
|
||||
if ( el )
|
||||
el.scrollIntoView();
|
||||
el.scrollIntoView({block: 'nearest', inline: 'start'});
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,9 @@ const KNOWN_FONTS = [
|
|||
'Comic Sans MS',
|
||||
];
|
||||
|
||||
export const VALID_FONTS = KNOWN_FONTS.filter(font => document.fonts.check(`16px ${font}`)).sort();
|
||||
export const VALID_FONTS = document.fonts?.check
|
||||
? KNOWN_FONTS.filter(font => document.fonts.check(`16px ${font}`)).sort()
|
||||
: KNOWN_FONTS.sort();
|
||||
|
||||
|
||||
/* Google Font Handling */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue