1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
* 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:
SirStendec 2023-04-17 16:56:46 -04:00
parent 3d4b4f6225
commit 2db7122c1d
10 changed files with 199 additions and 18 deletions

View file

@ -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",

View file

@ -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;
}

View file

@ -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">

View file

@ -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() {

View file

@ -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();

View 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 }}&times;{{ 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>

View file

@ -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')
};
}

View file

@ -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();

View file

@ -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;
}

View file

@ -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 */