mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.29.0
* Added: Support for OpenDyslexic and Google Fonts. Font Family settings now provide a list of fonts to choose from, in addition to allowing a custom font to be entered. * Changed: When saving a settings backup, the current date will be included in the filename by default. (Closes #1084)
This commit is contained in:
parent
a529e7fd27
commit
04cfbe6ed9
23 changed files with 394 additions and 28 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.28.7",
|
"version": "4.29.0",
|
||||||
"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",
|
||||||
|
|
BIN
res/font/OpenDyslexic-Bold.otf
Normal file
BIN
res/font/OpenDyslexic-Bold.otf
Normal file
Binary file not shown.
BIN
res/font/OpenDyslexic-BoldItalic.otf
Normal file
BIN
res/font/OpenDyslexic-BoldItalic.otf
Normal file
Binary file not shown.
BIN
res/font/OpenDyslexic-Italic.otf
Normal file
BIN
res/font/OpenDyslexic-Italic.otf
Normal file
Binary file not shown.
BIN
res/font/OpenDyslexic-Regular.otf
Normal file
BIN
res/font/OpenDyslexic-Regular.otf
Normal file
Binary file not shown.
BIN
res/font/OpenDyslexicAlta-Bold.otf
Normal file
BIN
res/font/OpenDyslexicAlta-Bold.otf
Normal file
Binary file not shown.
BIN
res/font/OpenDyslexicAlta-BoldItalic.otf
Normal file
BIN
res/font/OpenDyslexicAlta-BoldItalic.otf
Normal file
Binary file not shown.
BIN
res/font/OpenDyslexicAlta-Italic.otf
Normal file
BIN
res/font/OpenDyslexicAlta-Italic.otf
Normal file
Binary file not shown.
BIN
res/font/OpenDyslexicAlta-Regular.otf
Normal file
BIN
res/font/OpenDyslexicAlta-Regular.otf
Normal file
Binary file not shown.
BIN
res/font/OpenDyslexicMono-Regular.otf
Normal file
BIN
res/font/OpenDyslexicMono-Regular.otf
Normal file
Binary file not shown.
|
@ -200,7 +200,7 @@ export default {
|
||||||
attrs: {
|
attrs: {
|
||||||
'data-title': this.t(
|
'data-title': this.t(
|
||||||
'tooltip.link-unsafe',
|
'tooltip.link-unsafe',
|
||||||
"Caution: This URL is on Google's Safe Browsing List for: {reasons}",
|
'Caution: This URL is has been flagged as potentially harmful by: {reasons}',
|
||||||
{
|
{
|
||||||
reasons: reasons.toLowerCase()
|
reasons: reasons.toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import * as TOKENIZERS from './tokenizers';
|
||||||
import * as RICH_PROVIDERS from './rich_providers';
|
import * as RICH_PROVIDERS from './rich_providers';
|
||||||
|
|
||||||
import Actions from './actions';
|
import Actions from './actions';
|
||||||
|
import { getFontsList } from 'src/utilities/fonts';
|
||||||
|
|
||||||
export const SEPARATORS = '[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]';
|
export const SEPARATORS = '[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]';
|
||||||
|
|
||||||
|
@ -178,7 +179,8 @@ export default class Chat extends Module {
|
||||||
path: 'Chat > Appearance >> General',
|
path: 'Chat > Appearance >> General',
|
||||||
title: 'Font Family',
|
title: 'Font Family',
|
||||||
description: 'Set the font used for displaying chat messages.',
|
description: 'Set the font used for displaying chat messages.',
|
||||||
component: 'setting-text-box'
|
component: 'setting-combo-box',
|
||||||
|
data: () => getFontsList()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ export const Links = {
|
||||||
url_notice = (<div class="ffz-i-attention">
|
url_notice = (<div class="ffz-i-attention">
|
||||||
{this.i18n.tList(
|
{this.i18n.tList(
|
||||||
'tooltip.link-unsafe',
|
'tooltip.link-unsafe',
|
||||||
"Caution: This URL is on Google's Safe Browsing List for: {reasons}",
|
'Caution: This URL is has been flagged as potentially harmful by: {reasons}',
|
||||||
{reasons: reasons.toLowerCase()}
|
{reasons: reasons.toLowerCase()}
|
||||||
)}
|
)}
|
||||||
</div>);
|
</div>);
|
||||||
|
|
|
@ -16,13 +16,31 @@
|
||||||
class="tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05"
|
class="tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
>
|
>
|
||||||
<option
|
<template v-for="i in nested_data">
|
||||||
v-for="i in data"
|
<optgroup
|
||||||
:key="i.value"
|
v-if="i.entries"
|
||||||
:selected="i.value === value"
|
:key="i.key"
|
||||||
>
|
:disabled="i.disabled"
|
||||||
{{ i.i18n_key ? t(i.i18n_key, i.title, i) : i.title }}
|
:label="i.i18n_key ? t(i.i18n_key, i.title, i) : i.title"
|
||||||
</option>
|
>
|
||||||
|
<option
|
||||||
|
v-for="j in i.entries"
|
||||||
|
:key="j.value"
|
||||||
|
:selected="j.value === value"
|
||||||
|
:value="j.v"
|
||||||
|
>
|
||||||
|
{{ j.i18n_key ? t(j.i18n_key, j.title, j) : j.title }}
|
||||||
|
</option>
|
||||||
|
</optgroup>
|
||||||
|
<option
|
||||||
|
v-else
|
||||||
|
:key="i.value"
|
||||||
|
:selected="i.value === value"
|
||||||
|
:value="i.v"
|
||||||
|
>
|
||||||
|
{{ i.i18n_key ? t(i.i18n_key, i.title, i) : i.title }}
|
||||||
|
</option>
|
||||||
|
</template>
|
||||||
<option :selected="isCustom">
|
<option :selected="isCustom">
|
||||||
{{ t('setting.combo-box.custom', 'Custom') }}
|
{{ t('setting.combo-box.custom', 'Custom') }}
|
||||||
</option>
|
</option>
|
||||||
|
@ -91,6 +109,36 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
nested_data() {
|
||||||
|
const out = [];
|
||||||
|
let current_group = null;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
for(const entry of this.data) {
|
||||||
|
if ( entry.separator ) {
|
||||||
|
current_group = {
|
||||||
|
key: entry.key ?? i,
|
||||||
|
entries: [],
|
||||||
|
i18n_key: entry.i18n_key,
|
||||||
|
title: entry.title,
|
||||||
|
disabled: entry.disabled
|
||||||
|
};
|
||||||
|
|
||||||
|
out.push(current_group);
|
||||||
|
|
||||||
|
} else if ( current_group != null )
|
||||||
|
current_group.entries.push(Object.assign({v: i}, entry));
|
||||||
|
else
|
||||||
|
out.push(Object.assign({v: i}, entry));
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
value(val) {
|
value(val) {
|
||||||
for(const item of this.data)
|
for(const item of this.data)
|
||||||
|
@ -108,7 +156,7 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onChange() {
|
onChange() {
|
||||||
const idx = this.$refs.control.selectedIndex,
|
const idx = this.$refs.control.value,
|
||||||
raw_value = this.data[idx];
|
raw_value = this.data[idx];
|
||||||
|
|
||||||
if ( raw_value ) {
|
if ( raw_value ) {
|
||||||
|
|
|
@ -388,13 +388,16 @@ export default class SettingsManager extends Module {
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
async generateBackupFile() {
|
async generateBackupFile() {
|
||||||
|
const now = new Date(),
|
||||||
|
timestamp = `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}`;
|
||||||
|
|
||||||
if ( await this._needsZipBackup() ) {
|
if ( await this._needsZipBackup() ) {
|
||||||
const blob = await this._getZipBackup();
|
const blob = await this._getZipBackup();
|
||||||
return new File([blob], 'ffz-settings.zip', {type: 'application/zip'});
|
return new File([blob], `ffz-settings (${timestamp}).zip`, {type: 'application/zip'});
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = await this.getSettingsDump();
|
const settings = await this.getSettingsDump();
|
||||||
return new File([JSON.stringify(settings)], 'ffz-settings.json', {type: 'application/json;charset=utf-8'});
|
return new File([JSON.stringify(settings)], `ffz-settings (${timestamp}).json`, {type: 'application/json;charset=utf-8'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import {get} from 'utilities/object';
|
import {get} from 'utilities/object';
|
||||||
import {ColorAdjuster} from 'utilities/color';
|
import {ColorAdjuster} from 'utilities/color';
|
||||||
|
import { useFont } from 'utilities/fonts';
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
|
|
||||||
|
@ -88,6 +89,14 @@ export default class Chat extends Module {
|
||||||
lh = Math.round((20/12) * size);
|
lh = Math.round((20/12) * size);
|
||||||
|
|
||||||
let font = this.chat.context.get('chat.font-family') || 'inherit';
|
let font = this.chat.context.get('chat.font-family') || 'inherit';
|
||||||
|
const [processed, unloader] = useFont(font);
|
||||||
|
font = processed;
|
||||||
|
|
||||||
|
if ( this._font_unloader )
|
||||||
|
this._font_unloader();
|
||||||
|
|
||||||
|
this._font_unloader = unloader;
|
||||||
|
|
||||||
if ( font.indexOf(' ') !== -1 && font.indexOf(',') === -1 && font.indexOf('"') === -1 && font.indexOf("'") === -1 )
|
if ( font.indexOf(' ') !== -1 && font.indexOf(',') === -1 && font.indexOf('"') === -1 && font.indexOf("'") === -1 )
|
||||||
font = `"${font}"`;
|
font = `"${font}"`;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,8 @@ import Module from 'utilities/module';
|
||||||
|
|
||||||
import {createElement, on, off} from 'utilities/dom';
|
import {createElement, on, off} from 'utilities/dom';
|
||||||
import {isValidShortcut, debounce, has} from 'utilities/object';
|
import {isValidShortcut, debounce, has} from 'utilities/object';
|
||||||
import { IS_FIREFOX } from 'src/utilities/constants';
|
import { IS_FIREFOX } from 'utilities/constants';
|
||||||
|
import { getFontsList, useFont } from 'utilities/fonts';
|
||||||
|
|
||||||
const STYLE_VALIDATOR = createElement('span');
|
const STYLE_VALIDATOR = createElement('span');
|
||||||
|
|
||||||
|
@ -417,7 +418,8 @@ export default class PlayerBase extends Module {
|
||||||
path: 'Player > Closed Captioning >> Font',
|
path: 'Player > Closed Captioning >> Font',
|
||||||
title: 'Font Family',
|
title: 'Font Family',
|
||||||
description: 'Override the font used for displaying Closed Captions.',
|
description: 'Override the font used for displaying Closed Captions.',
|
||||||
component: 'setting-text-box'
|
component: 'setting-combo-box',
|
||||||
|
data: () => getFontsList()
|
||||||
},
|
},
|
||||||
changed: () => this.updateCaptionsCSS()
|
changed: () => this.updateCaptionsCSS()
|
||||||
});
|
});
|
||||||
|
@ -1031,20 +1033,33 @@ export default class PlayerBase extends Module {
|
||||||
|
|
||||||
updateCaptionsCSS() {
|
updateCaptionsCSS() {
|
||||||
// Font
|
// Font
|
||||||
|
const font_out = [];
|
||||||
|
|
||||||
const font_size = this.settings.get('player.captions.font-size');
|
const font_size = this.settings.get('player.captions.font-size');
|
||||||
let font_family = this.settings.get('player.captions.font-family');
|
let font_family = this.settings.get('player.captions.font-family');
|
||||||
if ( font_family.indexOf(' ') !== -1 && font_family.indexOf(',') === -1 && font_family.indexOf('"') === -1 && font_family.indexOf("'") === -1 )
|
|
||||||
font_family = `"${font_family}"`;
|
if ( font_family && font_family.length ) {
|
||||||
|
const [processed, unloader] = useFont(font_family);
|
||||||
|
font_family = processed;
|
||||||
|
|
||||||
|
if ( this._font_unloader )
|
||||||
|
this._font_unloader();
|
||||||
|
|
||||||
|
this._font_unloader = unloader;
|
||||||
|
|
||||||
|
if ( font_family.indexOf(' ') !== -1 && font_family.indexOf(',') === -1 && font_family.indexOf('"') === -1 && font_family.indexOf("'") === -1 )
|
||||||
|
font_family = `"${font_family}"`;
|
||||||
|
|
||||||
|
STYLE_VALIDATOR.style.fontFamily = '';
|
||||||
|
STYLE_VALIDATOR.style.fontFamily = font_family;
|
||||||
|
|
||||||
|
if ( STYLE_VALIDATOR.style.fontFamily )
|
||||||
|
font_out.push(`font-family: ${STYLE_VALIDATOR.style.fontFamily} !important;`);
|
||||||
|
}
|
||||||
|
|
||||||
STYLE_VALIDATOR.style.fontSize = '';
|
STYLE_VALIDATOR.style.fontSize = '';
|
||||||
STYLE_VALIDATOR.style.fontFamily = '';
|
|
||||||
|
|
||||||
STYLE_VALIDATOR.style.fontSize = font_size;
|
STYLE_VALIDATOR.style.fontSize = font_size;
|
||||||
STYLE_VALIDATOR.style.fontFamily = font_family;
|
|
||||||
|
|
||||||
const font_out = [];
|
|
||||||
if ( STYLE_VALIDATOR.style.fontFamily )
|
|
||||||
font_out.push(`font-family: ${STYLE_VALIDATOR.style.fontFamily} !important;`);
|
|
||||||
if ( STYLE_VALIDATOR.style.fontSize )
|
if ( STYLE_VALIDATOR.style.fontSize )
|
||||||
font_out.push(`font-size: ${STYLE_VALIDATOR.style.fontSize} !important;`);
|
font_out.push(`font-size: ${STYLE_VALIDATOR.style.fontSize} !important;`);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {Color, ColorAdjuster} from 'utilities/color';
|
||||||
import {get, has, make_enum, shallow_object_equals, set_equals, deep_equals} from 'utilities/object';
|
import {get, has, make_enum, shallow_object_equals, set_equals, deep_equals} from 'utilities/object';
|
||||||
import {WEBKIT_CSS as WEBKIT} from 'utilities/constants';
|
import {WEBKIT_CSS as WEBKIT} from 'utilities/constants';
|
||||||
import {FFZEvent} from 'utilities/events';
|
import {FFZEvent} from 'utilities/events';
|
||||||
|
import {useFont} from 'utilities/fonts';
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
|
|
||||||
|
@ -717,6 +718,14 @@ export default class ChatHook extends Module {
|
||||||
lh = Math.round((20/12) * size);
|
lh = Math.round((20/12) * size);
|
||||||
|
|
||||||
let font = this.chat.context.get('chat.font-family') || 'inherit';
|
let font = this.chat.context.get('chat.font-family') || 'inherit';
|
||||||
|
const [processed, unloader] = useFont(font);
|
||||||
|
font = processed;
|
||||||
|
|
||||||
|
if ( this._font_unloader )
|
||||||
|
this._font_unloader();
|
||||||
|
|
||||||
|
this._font_unloader = unloader;
|
||||||
|
|
||||||
if ( font.indexOf(' ') !== -1 && font.indexOf(',') === -1 && font.indexOf('"') === -1 && font.indexOf("'") === -1 )
|
if ( font.indexOf(' ') !== -1 && font.indexOf(',') === -1 && font.indexOf('"') === -1 && font.indexOf("'") === -1 )
|
||||||
font = `"${font}"`;
|
font = `"${font}"`;
|
||||||
|
|
||||||
|
|
|
@ -169,7 +169,7 @@ export default class RichContent extends Module {
|
||||||
|
|
||||||
return (<div
|
return (<div
|
||||||
class="ffz--corner-flag ffz--corner-flag__warn ffz-tooltip ffz-tooltip--no-mouse"
|
class="ffz--corner-flag ffz--corner-flag__warn ffz-tooltip ffz-tooltip--no-mouse"
|
||||||
data-title={t.i18n.t('tooltip.link-unsafe', "Caution: This URL is on Google's Safe Browsing List for: {reasons}", {reasons})}
|
data-title={t.i18n.t('tooltip.link-unsafe', 'Caution: This URL is has been flagged as potentially harmful by: {reasons}', {reasons})}
|
||||||
>
|
>
|
||||||
<figure class="ffz-i-attention" />
|
<figure class="ffz-i-attention" />
|
||||||
</div>);
|
</div>);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
import {ManagedStyle} from 'utilities/dom';
|
import {ManagedStyle} from 'utilities/dom';
|
||||||
import {has} from 'utilities/object';
|
import {has} from 'utilities/object';
|
||||||
|
import { getFontsList, useFont } from 'utilities/fonts';
|
||||||
|
|
||||||
const STYLE_VALIDATOR = document.createElement('span');
|
const STYLE_VALIDATOR = document.createElement('span');
|
||||||
|
|
||||||
|
@ -378,8 +379,9 @@ export default class CSSTweaks extends Module {
|
||||||
ui: {
|
ui: {
|
||||||
path: 'Appearance > Theme >> Fonts',
|
path: 'Appearance > Theme >> Fonts',
|
||||||
title: 'Font Family',
|
title: 'Font Family',
|
||||||
description: 'Override the font used for the entire Twitch website. The old default font was: `"Helvetica Neue",Helvetica,Arial,sans-serif`',
|
description: 'Override the font used for the entire Twitch website. The old default font was: `"Helvetica Neue",Helvetica,Arial,sans-serif`\n\nAny font available via [Google Fonts](https://fonts.google.com/) can be loaded by prefixing the font name with `google:`.',
|
||||||
component: 'setting-text-box'
|
component: 'setting-combo-box',
|
||||||
|
data: () => getFontsList()
|
||||||
},
|
},
|
||||||
changed: () => this.updateFont()
|
changed: () => this.updateFont()
|
||||||
});
|
});
|
||||||
|
@ -481,6 +483,14 @@ export default class CSSTweaks extends Module {
|
||||||
updateFont() {
|
updateFont() {
|
||||||
let font = this.settings.get('layout.theme.global-font');
|
let font = this.settings.get('layout.theme.global-font');
|
||||||
if ( font && font.length ) {
|
if ( font && font.length ) {
|
||||||
|
const [processed, unloader] = useFont(font);
|
||||||
|
font = processed;
|
||||||
|
|
||||||
|
if ( this._font_unloader )
|
||||||
|
this._font_unloader();
|
||||||
|
|
||||||
|
this._font_unloader = unloader;
|
||||||
|
|
||||||
if ( font.indexOf(' ') !== -1 && font.indexOf(',') === -1 && font.indexOf('"') === -1 && font.indexOf("'") === -1 )
|
if ( font.indexOf(' ') !== -1 && font.indexOf(',') === -1 && font.indexOf('"') === -1 && font.indexOf("'") === -1 )
|
||||||
font = `"${font}"`;
|
font = `"${font}"`;
|
||||||
|
|
||||||
|
|
210
src/utilities/fonts.js
Normal file
210
src/utilities/fonts.js
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
import { createElement } from "./dom";
|
||||||
|
|
||||||
|
const KNOWN_FONTS = [
|
||||||
|
'Roobert', // Twitch Default
|
||||||
|
'Arial',
|
||||||
|
'Arial Black',
|
||||||
|
'Verdana',
|
||||||
|
'Helvetica',
|
||||||
|
'Tahoma',
|
||||||
|
'Trebuchet MS',
|
||||||
|
'Impact',
|
||||||
|
'Didot',
|
||||||
|
'American Typewriter',
|
||||||
|
'Lucida Console',
|
||||||
|
'Monaco',
|
||||||
|
'Bradley Hand',
|
||||||
|
'Times New Roman',
|
||||||
|
'Georgia',
|
||||||
|
'Garamond',
|
||||||
|
'Courier New',
|
||||||
|
'Brush Script MT',
|
||||||
|
'Comic Sans MS',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const VALID_FONTS = KNOWN_FONTS.filter(font => document.fonts.check(`16px ${font}`)).sort();
|
||||||
|
|
||||||
|
|
||||||
|
/* Google Font Handling */
|
||||||
|
|
||||||
|
const GOOGLE_FONTS = [
|
||||||
|
'Roboto',
|
||||||
|
'Open Sans',
|
||||||
|
'Noto Sans JP',
|
||||||
|
'Lato',
|
||||||
|
'Montserrat',
|
||||||
|
'Roboto Condensed',
|
||||||
|
'Source Sans Pro',
|
||||||
|
'Oswald',
|
||||||
|
'Poppins',
|
||||||
|
'Noto Sans',
|
||||||
|
'Roboto Mono',
|
||||||
|
'Raleway',
|
||||||
|
'Ubuntu',
|
||||||
|
'Merriweather',
|
||||||
|
'Nunito',
|
||||||
|
'PT Sans',
|
||||||
|
'Roboto Slab',
|
||||||
|
'Playfair Display',
|
||||||
|
'Lora',
|
||||||
|
'Rubik',
|
||||||
|
'Mukta',
|
||||||
|
'Noto Sans KR',
|
||||||
|
'Work Sans',
|
||||||
|
'Nunito Sans',
|
||||||
|
'Nanum Gothic',
|
||||||
|
'Inter',
|
||||||
|
'Quicksand',
|
||||||
|
'PT Serif',
|
||||||
|
'Hind Siliguri',
|
||||||
|
'Titilium Web',
|
||||||
|
'Fira Sans',
|
||||||
|
'Noto Serif',
|
||||||
|
'Noto Sans TC',
|
||||||
|
'Karla'
|
||||||
|
];
|
||||||
|
|
||||||
|
const LOADED_GOOGLE = new Map();
|
||||||
|
const LOADED_GOOGLE_LINKS = new Map();
|
||||||
|
|
||||||
|
function loadGoogleFont(font) {
|
||||||
|
if ( LOADED_GOOGLE_LINKS.has(font) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const name = encodeURIComponent(font);
|
||||||
|
|
||||||
|
const link = createElement('link', {
|
||||||
|
id: `ffz-font-${name}`,
|
||||||
|
rel: 'stylesheet',
|
||||||
|
href: `https://fonts.googleapis.com/css2?family=${name}`
|
||||||
|
});
|
||||||
|
|
||||||
|
LOADED_GOOGLE_LINKS.set(font, link);
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unloadGoogleFont(font) {
|
||||||
|
const link = LOADED_GOOGLE_LINKS.get(font);
|
||||||
|
if ( ! link )
|
||||||
|
return;
|
||||||
|
|
||||||
|
LOADED_GOOGLE_LINKS.delete(font);
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* OpenDyslexic Font */
|
||||||
|
|
||||||
|
const OD_FONTS = [
|
||||||
|
'OpenDyslexic',
|
||||||
|
'OpenDyslexicAlta',
|
||||||
|
'OpenDyslexicMono'
|
||||||
|
];
|
||||||
|
|
||||||
|
import OD_URL from 'styles/opendyslexic.scss';
|
||||||
|
|
||||||
|
let od_count = 0;
|
||||||
|
let od_link = null;
|
||||||
|
|
||||||
|
function loadOpenDyslexic() {
|
||||||
|
if ( od_link )
|
||||||
|
return;
|
||||||
|
|
||||||
|
od_link = createElement('link', {
|
||||||
|
id: `ffz-font-opendyslexic`,
|
||||||
|
rel: 'stylesheet',
|
||||||
|
type: 'text/css',
|
||||||
|
href: OD_URL
|
||||||
|
});
|
||||||
|
|
||||||
|
document.head.appendChild(od_link);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function unloadOpenDyslexic() {
|
||||||
|
if ( ! od_link )
|
||||||
|
return;
|
||||||
|
|
||||||
|
od_link.remove();
|
||||||
|
od_link = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Using and Listing Fonts */
|
||||||
|
|
||||||
|
export function getFontsList() {
|
||||||
|
const out = [
|
||||||
|
{value: '', i18n_key: 'setting.font.default', title: 'Default'},
|
||||||
|
{separator: true, i18n_key: 'setting.font.builtin', title: 'Built-in Fonts'},
|
||||||
|
];
|
||||||
|
|
||||||
|
for(const font of VALID_FONTS)
|
||||||
|
out.push({value: font, title: font});
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
separator: true, i18n_key: 'setting.font.dyslexic', title: 'Dyslexia Fonts'
|
||||||
|
});
|
||||||
|
|
||||||
|
for(const font of OD_FONTS)
|
||||||
|
out.push({value: font, title: font});
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
separator: true, i18n_key: 'setting.font.google', title: 'Google Fonts'
|
||||||
|
});
|
||||||
|
|
||||||
|
for(const font of GOOGLE_FONTS)
|
||||||
|
out.push({value: `google:${font}`, title: font});
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function useFont(font) {
|
||||||
|
if ( ! font )
|
||||||
|
return [font, null];
|
||||||
|
|
||||||
|
if ( OD_FONTS.includes(font) ) {
|
||||||
|
od_count++;
|
||||||
|
if ( od_count === 1 )
|
||||||
|
loadOpenDyslexic();
|
||||||
|
|
||||||
|
let unloaded = false;
|
||||||
|
const unloader = () => {
|
||||||
|
if ( ! unloaded ) {
|
||||||
|
unloaded = true;
|
||||||
|
od_count--;
|
||||||
|
if ( ! od_count )
|
||||||
|
unloadOpenDyslexic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [font, unloader];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( font.startsWith('google:') ) {
|
||||||
|
const name = font.slice(7),
|
||||||
|
count = (LOADED_GOOGLE.get(name) ?? 0) + 1;
|
||||||
|
|
||||||
|
LOADED_GOOGLE.set(name, count);
|
||||||
|
if ( count === 1 )
|
||||||
|
loadGoogleFont(name);
|
||||||
|
|
||||||
|
let unloaded = false;
|
||||||
|
const unloader = () => {
|
||||||
|
if ( ! unloaded ) {
|
||||||
|
unloaded = true;
|
||||||
|
const count = (LOADED_GOOGLE.get(name) ?? 0) - 1;
|
||||||
|
if ( count > 0 ) {
|
||||||
|
LOADED_GOOGLE.set(name, count);
|
||||||
|
} else {
|
||||||
|
LOADED_GOOGLE.delete(name);
|
||||||
|
unloadGoogleFont(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [name, unloader];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [font, null];
|
||||||
|
}
|
60
styles/opendyslexic.scss
Normal file
60
styles/opendyslexic.scss
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexic';
|
||||||
|
src: url('~res/font/OpenDyslexic-Regular.otf');
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexic';
|
||||||
|
src: url('~res/font/OpenDyslexic-Italic.otf');
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexic';
|
||||||
|
src: url('~res/font/OpenDyslexic-Bold.otf');
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexic';
|
||||||
|
src: url('~res/font/OpenDyslexic-BoldItalic.otf');
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexicmono';
|
||||||
|
src: url('~res/font/OpenDyslexicMono-Regular.otf');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexicalta';
|
||||||
|
src: url('~res/font/OpenDyslexicAlta-Regular.otf');
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexicalta';
|
||||||
|
src: url('~res/font/OpenDyslexicAlta-Italic.otf');
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexicalta';
|
||||||
|
src: url('~res/font/OpenDyslexicAlta-Bold.otf');
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexicalta';
|
||||||
|
src: url('~res/font/OpenDyslexicAlta-BoldItalic.otf');
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
|
@ -119,7 +119,7 @@ module.exports = {
|
||||||
loader: 'graphql-tag/loader'
|
loader: 'graphql-tag/loader'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(?:eot|ttf|woff|woff2)$/,
|
test: /\.(?:otf|eot|ttf|woff|woff2)$/,
|
||||||
use: [{
|
use: [{
|
||||||
loader: 'file-loader',
|
loader: 'file-loader',
|
||||||
options: {
|
options: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue