diff --git a/package.json b/package.json index 5256fdf9..0b8188db 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.28.7", + "version": "4.29.0", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", diff --git a/res/font/OpenDyslexic-Bold.otf b/res/font/OpenDyslexic-Bold.otf new file mode 100644 index 00000000..4c492e2f Binary files /dev/null and b/res/font/OpenDyslexic-Bold.otf differ diff --git a/res/font/OpenDyslexic-BoldItalic.otf b/res/font/OpenDyslexic-BoldItalic.otf new file mode 100644 index 00000000..f71b4303 Binary files /dev/null and b/res/font/OpenDyslexic-BoldItalic.otf differ diff --git a/res/font/OpenDyslexic-Italic.otf b/res/font/OpenDyslexic-Italic.otf new file mode 100644 index 00000000..fdead4d5 Binary files /dev/null and b/res/font/OpenDyslexic-Italic.otf differ diff --git a/res/font/OpenDyslexic-Regular.otf b/res/font/OpenDyslexic-Regular.otf new file mode 100644 index 00000000..1226d2ab Binary files /dev/null and b/res/font/OpenDyslexic-Regular.otf differ diff --git a/res/font/OpenDyslexicAlta-Bold.otf b/res/font/OpenDyslexicAlta-Bold.otf new file mode 100644 index 00000000..37f6d5e2 Binary files /dev/null and b/res/font/OpenDyslexicAlta-Bold.otf differ diff --git a/res/font/OpenDyslexicAlta-BoldItalic.otf b/res/font/OpenDyslexicAlta-BoldItalic.otf new file mode 100644 index 00000000..6b5c1334 Binary files /dev/null and b/res/font/OpenDyslexicAlta-BoldItalic.otf differ diff --git a/res/font/OpenDyslexicAlta-Italic.otf b/res/font/OpenDyslexicAlta-Italic.otf new file mode 100644 index 00000000..5233fe00 Binary files /dev/null and b/res/font/OpenDyslexicAlta-Italic.otf differ diff --git a/res/font/OpenDyslexicAlta-Regular.otf b/res/font/OpenDyslexicAlta-Regular.otf new file mode 100644 index 00000000..6eb4a3ea Binary files /dev/null and b/res/font/OpenDyslexicAlta-Regular.otf differ diff --git a/res/font/OpenDyslexicMono-Regular.otf b/res/font/OpenDyslexicMono-Regular.otf new file mode 100644 index 00000000..543d46be Binary files /dev/null and b/res/font/OpenDyslexicMono-Regular.otf differ diff --git a/src/modules/chat/components/chat-rich.vue b/src/modules/chat/components/chat-rich.vue index 852003c6..bc38e7f2 100644 --- a/src/modules/chat/components/chat-rich.vue +++ b/src/modules/chat/components/chat-rich.vue @@ -200,7 +200,7 @@ export default { attrs: { 'data-title': this.t( '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() } diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index b8dbeb62..63d644ec 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -22,6 +22,7 @@ import * as TOKENIZERS from './tokenizers'; import * as RICH_PROVIDERS from './rich_providers'; 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]'; @@ -178,7 +179,8 @@ export default class Chat extends Module { path: 'Chat > Appearance >> General', title: 'Font Family', description: 'Set the font used for displaying chat messages.', - component: 'setting-text-box' + component: 'setting-combo-box', + data: () => getFontsList() } }); diff --git a/src/modules/chat/tokenizers.jsx b/src/modules/chat/tokenizers.jsx index 9822ece3..1a786ff6 100644 --- a/src/modules/chat/tokenizers.jsx +++ b/src/modules/chat/tokenizers.jsx @@ -138,7 +138,7 @@ export const Links = { url_notice = (
{this.i18n.tList( '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()} )}
); diff --git a/src/modules/main_menu/components/setting-combo-box.vue b/src/modules/main_menu/components/setting-combo-box.vue index f7f49c03..c2473df5 100644 --- a/src/modules/main_menu/components/setting-combo-box.vue +++ b/src/modules/main_menu/components/setting-combo-box.vue @@ -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" @change="onChange" > - + @@ -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: { value(val) { for(const item of this.data) @@ -108,7 +156,7 @@ export default { methods: { onChange() { - const idx = this.$refs.control.selectedIndex, + const idx = this.$refs.control.value, raw_value = this.data[idx]; if ( raw_value ) { diff --git a/src/settings/index.js b/src/settings/index.js index 990d0f79..1bed6686 100644 --- a/src/settings/index.js +++ b/src/settings/index.js @@ -388,13 +388,16 @@ export default class SettingsManager extends Module { // ======================================================================== async generateBackupFile() { + const now = new Date(), + timestamp = `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}`; + if ( await this._needsZipBackup() ) { 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(); - 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'}); } diff --git a/src/sites/clips/chat.jsx b/src/sites/clips/chat.jsx index 7d690b0e..ebfe45db 100644 --- a/src/sites/clips/chat.jsx +++ b/src/sites/clips/chat.jsx @@ -6,6 +6,7 @@ import {get} from 'utilities/object'; import {ColorAdjuster} from 'utilities/color'; +import { useFont } from 'utilities/fonts'; import Module from 'utilities/module'; @@ -88,6 +89,14 @@ export default class Chat extends Module { lh = Math.round((20/12) * size); 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 ) font = `"${font}"`; diff --git a/src/sites/shared/player.jsx b/src/sites/shared/player.jsx index 3b59cc23..91fb87e1 100644 --- a/src/sites/shared/player.jsx +++ b/src/sites/shared/player.jsx @@ -8,7 +8,8 @@ import Module from 'utilities/module'; import {createElement, on, off} from 'utilities/dom'; 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'); @@ -417,7 +418,8 @@ export default class PlayerBase extends Module { path: 'Player > Closed Captioning >> Font', title: 'Font Family', description: 'Override the font used for displaying Closed Captions.', - component: 'setting-text-box' + component: 'setting-combo-box', + data: () => getFontsList() }, changed: () => this.updateCaptionsCSS() }); @@ -1031,20 +1033,33 @@ export default class PlayerBase extends Module { updateCaptionsCSS() { // Font + const font_out = []; + const font_size = this.settings.get('player.captions.font-size'); 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.fontFamily = ''; - 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 ) font_out.push(`font-size: ${STYLE_VALIDATOR.style.fontSize} !important;`); diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index e3212f5c..bb105f57 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -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 {WEBKIT_CSS as WEBKIT} from 'utilities/constants'; import {FFZEvent} from 'utilities/events'; +import {useFont} from 'utilities/fonts'; import Module from 'utilities/module'; @@ -717,6 +718,14 @@ export default class ChatHook extends Module { lh = Math.round((20/12) * size); 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 ) font = `"${font}"`; diff --git a/src/sites/twitch-twilight/modules/chat/rich_content.jsx b/src/sites/twitch-twilight/modules/chat/rich_content.jsx index b48e630f..57093c39 100644 --- a/src/sites/twitch-twilight/modules/chat/rich_content.jsx +++ b/src/sites/twitch-twilight/modules/chat/rich_content.jsx @@ -169,7 +169,7 @@ export default class RichContent extends Module { return (
); diff --git a/src/sites/twitch-twilight/modules/css_tweaks/index.js b/src/sites/twitch-twilight/modules/css_tweaks/index.js index ea7f706c..63ea3326 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/index.js +++ b/src/sites/twitch-twilight/modules/css_tweaks/index.js @@ -7,6 +7,7 @@ import Module from 'utilities/module'; import {ManagedStyle} from 'utilities/dom'; import {has} from 'utilities/object'; +import { getFontsList, useFont } from 'utilities/fonts'; const STYLE_VALIDATOR = document.createElement('span'); @@ -378,8 +379,9 @@ export default class CSSTweaks extends Module { ui: { path: 'Appearance > Theme >> Fonts', title: 'Font Family', - description: 'Override the font used for the entire Twitch website. The old default font was: `"Helvetica Neue",Helvetica,Arial,sans-serif`', - component: 'setting-text-box' + 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-combo-box', + data: () => getFontsList() }, changed: () => this.updateFont() }); @@ -481,6 +483,14 @@ export default class CSSTweaks extends Module { updateFont() { let font = this.settings.get('layout.theme.global-font'); 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 ) font = `"${font}"`; diff --git a/src/utilities/fonts.js b/src/utilities/fonts.js new file mode 100644 index 00000000..4d936ea0 --- /dev/null +++ b/src/utilities/fonts.js @@ -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]; +} \ No newline at end of file diff --git a/styles/opendyslexic.scss b/styles/opendyslexic.scss new file mode 100644 index 00000000..215a1613 --- /dev/null +++ b/styles/opendyslexic.scss @@ -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; +} diff --git a/webpack.common.js b/webpack.common.js index 742c34ec..7699e65f 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -119,7 +119,7 @@ module.exports = { loader: 'graphql-tag/loader' }, { - test: /\.(?:eot|ttf|woff|woff2)$/, + test: /\.(?:otf|eot|ttf|woff|woff2)$/, use: [{ loader: 'file-loader', options: {