diff --git a/src/App.scss b/src/App.scss index 6b8875bf..9225075b 100644 --- a/src/App.scss +++ b/src/App.scss @@ -375,7 +375,6 @@ nav { user-select: none; color: var(--text); border: none; - border-radius: var(--roundness); cursor: pointer; background-color: var(--background); box-shadow: var(--shadow); @@ -511,7 +510,6 @@ textarea { --_padding: 0.5em; border: none; - border-radius: var(--roundness); background-color: var(--background); color: var(--text); box-shadow: var(--shadow); @@ -617,6 +615,17 @@ textarea { } } +.input, +.button-default { + --_roundness-left: var(--roundness); + --_roundness-right: var(--roundness); + + border-top-left-radius: var(--_roundness-left); + border-bottom-left-radius: var(--_roundness-left); + border-top-right-radius: var(--_roundness-right); + border-bottom-right-radius: var(--_roundness-right); +} + // Textareas should have stock line-height + vertical padding instead of huge line-height textarea.input { padding: var(--_padding); @@ -662,22 +671,20 @@ option { display: inline-flex; vertical-align: middle; - button, - .button-dropdown { + > * { + --_roundness-left: 0; + --_roundness-right: 0; + position: relative; flex: 1 1 auto; + } - &:not(:last-child), - &:not(:last-child) .button-default { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } + > *:first-child { + --_roundness-left: var(--roundness); + } - &:not(:first-child), - &:not(:first-child) .button-default { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } + > *:last-child { + --_roundness-right: var(--roundness); } } diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js index 52c3e70a..1e33338f 100644 --- a/src/components/font_control/font_control.js +++ b/src/components/font_control/font_control.js @@ -1,13 +1,19 @@ -import { set } from 'lodash' +import { set, clone } from 'lodash' import Select from '../select/select.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' import Popover from 'src/components/popover/popover.vue' import { library } from '@fortawesome/fontawesome-svg-core' -import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' +import { + faExclamationTriangle, + faKeyboard, + faFont +} from '@fortawesome/free-solid-svg-icons' library.add( - faExclamationTriangle + faExclamationTriangle, + faKeyboard, + faFont ) const PRESET_FONTS = new Set(['serif', 'sans-serif', 'monospace', 'inherit']) @@ -24,20 +30,38 @@ export default { emits: ['update:modelValue'], data () { return { - localValue: this.modelValue, - customFamily: '', + manualEntry: true, + localValue: clone(this.modelValue), + familyCustomLocal: null, availableOptions: [ this.noInherit ? '' : 'inherit', - 'local', - ...(this.options || []), 'serif', + 'sans-serif', 'monospace', - 'sans-serif' + 'local', + ...(this.options || []) ].filter(_ => _) } }, beforeUpdate () { - this.localValue = this.modelValue + this.localValue = clone(this.modelValue) + if (this.familyCustomLocal === null && !this.isInvalidFamily(this.modelValue?.family)) { + this.familyCustomLocal = this.modelValue?.family + } + }, + methods: { + lookupLocalFonts () { + if (!this.fontsList) { + this.$store.dispatch('queryLocalFonts') + } + this.toggleManualEntry() + }, + isInvalidFamily (value) { + return PRESET_FONTS.has(value) || (value === '') + }, + toggleManualEntry () { + this.manualEntry = !this.manualEntry + } }, computed: { present () { @@ -46,32 +70,39 @@ export default { defaultValue () { return this.localValue || this.fallback || {} }, + fontsListCapable () { + return this.$store.state.interface.browserSupport.localFonts + }, + fontsList () { + return this.$store.state.interface.localFonts + }, family: { get () { return this.defaultValue.family }, set (v) { + this.familyCustomLocal = '' set(this.localValue, 'family', v) this.$emit('update:modelValue', this.localValue) } }, familyCustom: { get () { - return this.customFamily + return this.familyCustomLocal }, set (v) { - this.customFamily = v - if (!PRESET_FONTS.has(this.customFamily)) { + this.familyCustomLocal = v + if (!this.isInvalidFamily(v)) { set(this.localValue, 'family', v) - this.$emit('update:modelValue', this.customFamily) + this.$emit('update:modelValue', this.localValue) } } }, invalidCustom () { - return PRESET_FONTS.has(this.customFamily) + return this.isInvalidFamily(this.familyCustomLocal) }, isCustom () { - return !PRESET_FONTS.has(this.family) + return !PRESET_FONTS.has(this.defaultValue.family) }, preset: { get () { diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue index e73862a0..f1b126be 100644 --- a/src/components/font_control/font_control.vue +++ b/src/components/font_control/font_control.vue @@ -10,15 +10,6 @@ > {{ label }} - {{ ' ' }} - - {{ $t('settings.style.themes3.define') }} -

+ {{ ' ' }} + + {{ $t('settings.style.themes3.define') }} +

-

- + + + {{ ' ' }} + + + + + + + + .font-control { - input.custom-font { - min-width: 12em; + .custom-font { + min-width: 20em; + max-width: 20em; } } diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss index 5e633120..4cb37c1e 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss @@ -10,6 +10,10 @@ margin-right: 0.25em; } + .btn-group .btn { + margin: 0; + } + .style-control { display: flex; align-items: baseline; diff --git a/src/i18n/en.json b/src/i18n/en.json index bd18c6ef..4b216165 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -746,14 +746,19 @@ "more_settings": "More settings", "style": { "themes3": { - "define": "Include in theme", + "define": "Override", "font": { "local": "Local font (must be installed on computer)", "serif": "Serif (browser default)", "sans-serif": "Sans-serif (browser default)", "monospace": "Monospace (browser default)", "inherit": "Same as parent component", - "invalid_custom_reserved": "This is a reserved font name, it will not be saved as custom font - use dropdown instead" + "invalid_custom_reserved": "Empty or reserved font name, it will not be saved as custom font - use dropdown instead", + "font_list_unavailable": "Couldn't get locally installed fonts: {error}", + "lookup_local_fonts": "Load list of fonts installed on this computer", + "enter_manually": "Enter font name family manually", + "entry": "Font's {fontFamily}", + "select": "Select local font" } }, "switcher": { diff --git a/src/modules/interface.js b/src/modules/interface.js index e21b4204..bee503c5 100644 --- a/src/modules/interface.js +++ b/src/modules/interface.js @@ -1,4 +1,5 @@ const defaultState = { + localFonts: null, themeApplied: false, temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout temporaryChangesConfirm: () => {}, // used for applying temporary options @@ -17,7 +18,8 @@ const defaultState = { cssFilter: window.CSS && window.CSS.supports && ( window.CSS.supports('filter', 'drop-shadow(0 0)') || window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)') - ) + ), + localFonts: typeof window.queryLocalFonts === 'function' }, layoutType: 'normal', globalNotices: [], @@ -104,6 +106,10 @@ const interfaceMod = { }, setLastTimeline (state, value) { state.lastTimeline = value + }, + setFontsList (state, value) { + console.log(value) + state.localFonts = new Set(value.map(font => font.family)) } }, actions: { @@ -178,6 +184,22 @@ const interfaceMod = { commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile) } }, + queryLocalFonts ({ commit, dispatch }) { + window + .queryLocalFonts() + .then((fonts) => { + commit('setFontsList', fonts) + }) + .catch((e) => { + dispatch('pushGlobalNotice', { + messageKey: 'settings.style.themes3.font.font_list_unavailable', + messageArgs: { + error: e + }, + level: 'error' + }) + }) + }, setLastTimeline ({ commit }, value) { commit('setLastTimeline', value) }