From eba3a43805b5777aebb677df88bf5c8533743ead Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 26 Jun 2024 14:17:22 +0300
Subject: better font control custom input

---
 src/components/font_control/font_control.vue | 101 +++++++++++++++------------
 1 file changed, 55 insertions(+), 46 deletions(-)

(limited to 'src/components/font_control/font_control.vue')

diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue
index d2d1b388..e73862a0 100644
--- a/src/components/font_control/font_control.vue
+++ b/src/components/font_control/font_control.vue
@@ -1,6 +1,6 @@
 <template>
   <div
-    class="font-control style-control"
+    class="font-control"
     :class="{ custom: isCustom }"
   >
     <label
@@ -10,43 +10,59 @@
     >
       {{ label }}
     </label>
-    <input
+    {{ ' ' }}
+    <Checkbox
       v-if="typeof fallback !== 'undefined'"
       :id="name + '-o'"
-      :aria-labelledby="name + '-label'"
-      class="input -checkbox opt exlcude-disabled visible-for-screenreader-only"
-      type="checkbox"
-      :checked="present"
+      :modelValue="present"
       @change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
     >
-    <label
-      v-if="typeof fallback !== 'undefined'"
-      class="opt-l"
-      :for="name + '-o'"
-      :aria-hidden="true"
-    />
-    {{ ' ' }}
-    <Select
-      :id="name + '-font-switcher'"
-      v-model="preset"
-      :disabled="!present"
-      class="font-switcher"
-    >
-      <option
-        v-for="option in availableOptions"
-        :key="option"
-        :value="option"
+      {{ $t('settings.style.themes3.define') }}
+    </Checkbox>
+    <p>
+      <Select
+        :id="name + '-font-switcher'"
+        v-model="preset"
+        :disabled="!present"
+        class="font-switcher"
       >
-        {{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
-      </option>
-    </Select>
-    <input
-      v-if="isCustom"
-      :id="name"
-      v-model="family"
-      class="input custom-font"
-      type="text"
-    >
+        <option
+          v-for="option in availableOptions"
+          :key="option"
+          :value="option"
+        >
+          {{ $t('settings.style.themes3.font.' + option) }}
+        </option>
+      </Select>
+    </p>
+    <p>
+      <input
+        v-if="isCustom"
+        :id="name"
+        v-model="familyCustom"
+        class="input custom-font"
+        type="text"
+      >
+      <span
+        v-if="invalidCustom"
+        class="InvalidIndicator"
+      >
+        <Popover
+          trigger="hover"
+          :trigger-attrs="{ 'aria-label': $t('settings.style.themes3.font.invalid_custom_reserved') }"
+        >
+          <template #trigger>
+            &nbsp;
+            <FAIcon icon="exclamation-triangle" />
+          </template>
+          <template #content>
+            <div class="invalid-tooltip">
+              {{ $t('settings.style.themes3.font.invalid_custom_reserved') }}
+            </div>
+          </template>
+        </Popover>
+      </span>
+    </p>
   </div>
 </template>
 
@@ -55,20 +71,13 @@
 <style lang="scss">
 .font-control {
   input.custom-font {
-    min-width: 10em;
+    min-width: 12em;
   }
+}
 
-  &.custom {
-    /* TODO Should make proper joiners... */
-    .font-switcher {
-      border-top-right-radius: 0;
-      border-bottom-right-radius: 0;
-    }
-
-    .custom-font {
-      border-top-left-radius: 0;
-      border-bottom-left-radius: 0;
-    }
-  }
+.invalid-tooltip {
+  margin: 0.5em 1em;
+  min-width: 10em;
+  text-align: center;
 }
 </style>
-- 
cgit v1.2.3-70-g09d2


From d5d37849ea6712fa00a077cb219800553b5689ab Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 26 Jun 2024 17:05:59 +0300
Subject: font selector with proper styles and functionality + local font
 selector

---
 src/App.scss                                       |  35 ++++---
 src/components/font_control/font_control.js        |  61 +++++++++---
 src/components/font_control/font_control.vue       | 102 +++++++++++++++++----
 .../settings_modal/tabs/theme_tab/theme_tab.scss   |   4 +
 src/i18n/en.json                                   |   9 +-
 src/modules/interface.js                           |  24 ++++-
 6 files changed, 185 insertions(+), 50 deletions(-)

(limited to 'src/components/font_control/font_control.vue')

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 }}
     </label>
-    {{ ' ' }}
-    <Checkbox
-      v-if="typeof fallback !== 'undefined'"
-      :id="name + '-o'"
-      :modelValue="present"
-      @change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
-    >
-      {{ $t('settings.style.themes3.define') }}
-    </Checkbox>
     <p>
       <Select
         :id="name + '-font-switcher'"
@@ -34,15 +25,89 @@
           {{ $t('settings.style.themes3.font.' + option) }}
         </option>
       </Select>
+      {{ ' ' }}
+      <Checkbox
+        v-if="typeof fallback !== 'undefined'"
+        :id="name + '-o'"
+        :modelValue="present"
+        @change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
+      >
+        {{ $t('settings.style.themes3.define') }}
+      </Checkbox>
     </p>
-    <p>
-      <input
-        v-if="isCustom"
-        :id="name"
-        v-model="familyCustom"
-        class="input custom-font"
-        type="text"
+    <p v-if="isCustom">
+      <label
+        v-if="fontsList !== null && !manualEntry"
+        :id="name + '-label'"
+        :for="preset === 'custom' ? name : name + '-font-switcher'"
+        class="label"
       >
+        {{ $t('settings.style.themes3.font.select') }}
+      </label>
+      <label
+        v-else
+        :id="name + '-label'"
+        :for="preset === 'custom' ? name : name + '-font-switcher'"
+        class="label"
+      >
+        <i18n-t
+          keypath="settings.style.themes3.font.entry"
+          tag="span"
+        >
+          <template #fontFamily>
+            <code>font-family</code>
+          </template>
+        </i18n-t>
+      </label>
+      {{ ' ' }}
+      <span class="btn-group">
+        <button
+          v-if="fontsListCapable && (fontsList === null || manualEntry)"
+          class="btn button-default"
+          @click="lookupLocalFonts"
+          :title="$t('settings.style.themes3.font.lookup_local_fonts')"
+        >
+          <FAIcon
+            fixed-width
+            icon="font"
+          />
+        </button>
+          <input
+            v-if="fontsLists === null || manualEntry"
+            :id="name"
+            v-model="familyCustom"
+            class="input custom-font"
+            type="text"
+          >
+      </span>
+      <span class="btn-group">
+        <button
+          v-if="fontsList !== null && !manualEntry"
+          class="btn button-default"
+          @click="toggleManualEntry"
+          :title="$t('settings.style.themes3.font.enter_manually')"
+        >
+          <FAIcon
+            fixed-width
+            icon="keyboard"
+          />
+        </button>
+        <Select
+          v-if="fontsList !== null && !manualEntry"
+          :id="name + '-local-font-switcher'"
+          v-model="familyCustom"
+          class="custom-font"
+        >
+          <option
+            v-for="option in fontsList.values()"
+            :key="option"
+            :value="option"
+            :style="{ fontFamily: option }"
+          >
+            {{ option }}
+          </option>
+        </Select>
+      </span>
       <span
         v-if="invalidCustom"
         class="InvalidIndicator"
@@ -70,8 +135,9 @@
 
 <style lang="scss">
 .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)
     }
-- 
cgit v1.2.3-70-g09d2


From 925fd863fe2be2cd273507dbb2688efb2d0ae07e Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 27 Jun 2024 00:34:25 +0300
Subject: Massively simplify and improve font input component

---
 src/components/font_control/font_control.js  |  77 ++------------
 src/components/font_control/font_control.vue | 147 +++++++++++++--------------
 src/i18n/en.json                             |  20 ++--
 src/modules/interface.js                     |   9 +-
 4 files changed, 99 insertions(+), 154 deletions(-)

(limited to 'src/components/font_control/font_control.vue')

diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js
index 1e33338f..f6f45c8c 100644
--- a/src/components/font_control/font_control.js
+++ b/src/components/font_control/font_control.js
@@ -1,4 +1,3 @@
-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'
@@ -16,8 +15,6 @@ library.add(
   faFont
 )
 
-const PRESET_FONTS = new Set(['serif', 'sans-serif', 'monospace', 'inherit'])
-
 export default {
   components: {
     Select,
@@ -27,94 +24,36 @@ export default {
   props: [
     'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
   ],
+  mounted () {
+    this.$store.dispatch('queryLocalFonts')
+  },
   emits: ['update:modelValue'],
   data () {
     return {
       manualEntry: true,
-      localValue: clone(this.modelValue),
-      familyCustomLocal: null,
       availableOptions: [
         this.noInherit ? '' : 'inherit',
         'serif',
         'sans-serif',
         'monospace',
-        'local',
         ...(this.options || [])
       ].filter(_ => _)
     }
   },
-  beforeUpdate () {
-    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 () {
-      return typeof this.localValue !== 'undefined'
-    },
-    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.familyCustomLocal
-      },
-      set (v) {
-        this.familyCustomLocal = v
-        if (!this.isInvalidFamily(v)) {
-          set(this.localValue, 'family', v)
-          this.$emit('update:modelValue', this.localValue)
-        }
-      }
-    },
-    invalidCustom () {
-      return this.isInvalidFamily(this.familyCustomLocal)
+      return typeof this.modelValue !== 'undefined'
     },
-    isCustom () {
-      return !PRESET_FONTS.has(this.defaultValue.family)
+    localFontsList () {
+      return this.$store.state.interface.localFonts?.values()
     },
-    preset: {
-      get () {
-        if (PRESET_FONTS.has(this.family)) {
-          return this.family
-        } else {
-          return 'local'
-        }
-      },
-      set (v) {
-        this.family = v === 'local' ? '' : v
-      }
+    localFontsSize () {
+      return this.$store.state.interface.localFonts?.size
     }
   }
 }
diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue
index f1b126be..f3ab41a2 100644
--- a/src/components/font_control/font_control.vue
+++ b/src/components/font_control/font_control.vue
@@ -10,42 +10,18 @@
     >
       {{ label }}
     </label>
-    <p>
-      <Select
-        :id="name + '-font-switcher'"
-        v-model="preset"
-        :disabled="!present"
-        class="font-switcher"
-      >
-        <option
-          v-for="option in availableOptions"
-          :key="option"
-          :value="option"
-        >
-          {{ $t('settings.style.themes3.font.' + option) }}
-        </option>
-      </Select>
-      {{ ' ' }}
-      <Checkbox
-        v-if="typeof fallback !== 'undefined'"
-        :id="name + '-o'"
-        :modelValue="present"
-        @change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
-      >
-        {{ $t('settings.style.themes3.define') }}
-      </Checkbox>
-    </p>
-    <p v-if="isCustom">
-      <label
-        v-if="fontsList !== null && !manualEntry"
-        :id="name + '-label'"
-        :for="preset === 'custom' ? name : name + '-font-switcher'"
-        class="label"
-      >
-        {{ $t('settings.style.themes3.font.select') }}
-      </label>
+    {{ ' ' }}
+    <Checkbox
+      v-if="typeof fallback !== 'undefined'"
+      :id="name + '-o'"
+      :modelValue="present"
+      @change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
+    >
+      {{ $t('settings.style.themes3.define') }}
+    </Checkbox>
+    <p v-if="modelValue?.family">
       <label
-        v-else
+        v-if="manualEntry"
         :id="name + '-label'"
         :for="preset === 'custom' ? name : name + '-font-switcher'"
         class="label"
@@ -59,12 +35,22 @@
           </template>
         </i18n-t>
       </label>
+      <label
+        v-else
+        :id="name + '-label'"
+        :for="preset === 'custom' ? name : name + '-font-switcher'"
+        class="label"
+      >
+        {{ $t('settings.style.themes3.font.select') }}
+      </label>
       {{ ' ' }}
-      <span class="btn-group">
+      <span
+        v-if="manualEntry"
+        class="btn-group"
+      >
         <button
-          v-if="fontsListCapable && (fontsList === null || manualEntry)"
           class="btn button-default"
-          @click="lookupLocalFonts"
+          @click="toggleManualEntry"
           :title="$t('settings.style.themes3.font.lookup_local_fonts')"
         >
           <FAIcon
@@ -72,17 +58,19 @@
             icon="font"
           />
         </button>
-          <input
-            v-if="fontsLists === null || manualEntry"
-            :id="name"
-            v-model="familyCustom"
-            class="input custom-font"
-            type="text"
-          >
+        <input
+          :id="name"
+          :model-value="modelValue.family"
+          class="input custom-font"
+          type="text"
+          @update:modelValue="$emit('update:modelValue', { ...(modelValue || {}), family: $event.target.value })"
+        >
       </span>
-      <span class="btn-group">
+      <span
+        v-else
+        class="btn-group"
+      >
         <button
-          v-if="fontsList !== null && !manualEntry"
           class="btn button-default"
           @click="toggleManualEntry"
           :title="$t('settings.style.themes3.font.enter_manually')"
@@ -93,40 +81,49 @@
           />
         </button>
         <Select
-          v-if="fontsList !== null && !manualEntry"
           :id="name + '-local-font-switcher'"
-          v-model="familyCustom"
+          :model-value="modelValue.family"
           class="custom-font"
+          @update:modelValue="$emit('update:modelValue', { ...(modelValue || {}), family: $event.target.value })"
         >
-          <option
-            v-for="option in fontsList.values()"
-            :key="option"
-            :value="option"
-            :style="{ fontFamily: option }"
+          <optgroup
+            :label="$t('settings.style.themes3.font.group-builtin')"
+          >
+            <option
+              v-for="option in availableOptions"
+              :key="option"
+              :value="option"
+              :style="{ fontFamily: option === 'inherit' ? null : option }"
+            >
+              {{ $t('settings.style.themes3.font.builtin.' + option) }}
+            </option>
+          </optgroup>
+          <optgroup
+            v-if="localFontsSize > 0"
+            :label="$t('settings.style.themes3.font.group-local')"
+          >
+            <option
+              v-for="option in localFontsList"
+              :key="option"
+              :value="option"
+              :style="{ fontFamily: option }"
+            >
+              {{ option }}
+            </option>
+          </optgroup>
+          <optgroup
+            v-else
+            :label="$t('settings.style.themes3.font.group-local')"
           >
-            {{ option }}
-          </option>
+            <option disabled>
+              {{ $t('settings.style.themes3.font.local-unavailable1') }}
+            </option>
+            <option disabled>
+              {{ $t('settings.style.themes3.font.local-unavailable2') }}
+            </option>
+          </optgroup>
         </Select>
       </span>
-      <span
-        v-if="invalidCustom"
-        class="InvalidIndicator"
-      >
-        <Popover
-          trigger="hover"
-          :trigger-attrs="{ 'aria-label': $t('settings.style.themes3.font.invalid_custom_reserved') }"
-        >
-          <template #trigger>
-            &nbsp;
-            <FAIcon icon="exclamation-triangle" />
-          </template>
-          <template #content>
-            <div class="invalid-tooltip">
-              {{ $t('settings.style.themes3.font.invalid_custom_reserved') }}
-            </div>
-          </template>
-        </Popover>
-      </span>
     </p>
   </div>
 </template>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 4b216165..d995163a 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -748,17 +748,21 @@
       "themes3": {
         "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": "Empty or reserved font name, it will not be saved as custom font - use dropdown instead",
+          "group-builtin": "Browser default fonts",
+          "builtin" : {
+            "serif": "Serif",
+            "sans-serif": "Sans-serif",
+            "monospace": "Monospace",
+            "inherit": "Unchanged"
+          },
+          "group-local": "Locally installed fonts",
+          "local-unavailable1": "List of locally installed fonts unavailalbe",
+          "local-unavailable2": "Use manual entry to specify custom font",
           "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"
+          "entry": "Enter {fontFamily}",
+          "select": "Select font"
         }
       },
       "switcher": {
diff --git a/src/modules/interface.js b/src/modules/interface.js
index bee503c5..2ccfeef3 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -108,7 +108,6 @@ const interfaceMod = {
       state.lastTimeline = value
     },
     setFontsList (state, value) {
-      console.log(value)
       state.localFonts = new Set(value.map(font => font.family))
     }
   },
@@ -184,10 +183,16 @@ const interfaceMod = {
         commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile)
       }
     },
-    queryLocalFonts ({ commit, dispatch }) {
+    queryLocalFonts ({ commit, dispatch, state }) {
+      if (state.localFonts !== null) return
+      commit('setFontsList', [])
+      if (!state.browserSupport.localFonts) {
+        return
+      }
       window
         .queryLocalFonts()
         .then((fonts) => {
+          console.log(fonts)
           commit('setFontsList', fonts)
         })
         .catch((e) => {
-- 
cgit v1.2.3-70-g09d2


From 665faf892a418fa1e7aeda1d1c82d926a2ed4f3a Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 21 Jul 2024 23:27:11 +0300
Subject: font overrides + fixes for font component

---
 src/components/font_control/font_control.vue       |  2 +-
 .../settings_modal/tabs/appearance_tab.js          | 14 +++++++-
 .../settings_modal/tabs/appearance_tab.vue         | 18 +++++-----
 src/modules/config.js                              |  9 +++--
 src/modules/interface.js                           | 40 ++++++++++++++++++++++
 5 files changed, 70 insertions(+), 13 deletions(-)

(limited to 'src/components/font_control/font_control.vue')

diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue
index f3ab41a2..b5423a8c 100644
--- a/src/components/font_control/font_control.vue
+++ b/src/components/font_control/font_control.vue
@@ -84,7 +84,7 @@
           :id="name + '-local-font-switcher'"
           :model-value="modelValue.family"
           class="custom-font"
-          @update:modelValue="$emit('update:modelValue', { ...(modelValue || {}), family: $event.target.value })"
+          @update:modelValue="v => $emit('update:modelValue', { ...(modelValue || {}), family: v })"
         >
           <optgroup
             :label="$t('settings.style.themes3.font.group-builtin')"
diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js
index 7ec22641..98d38288 100644
--- a/src/components/settings_modal/tabs/appearance_tab.js
+++ b/src/components/settings_modal/tabs/appearance_tab.js
@@ -151,9 +151,21 @@ const AppearanceTab = {
     ...SharedComputedObject()
   },
   methods: {
+    updateFont (key, value) {
+      console.log(key, value)
+      this.$store.dispatch('setOption', {
+        name: 'theme3hacks',
+        value: {
+          ...this.mergedConfig.theme3hacks,
+          fonts: {
+            ...this.mergedConfig.theme3hacks.fonts,
+            [key]: value
+          }
+        }
+      })
+    },
     isThemeActive (key) {
       const { theme } = this.mergedConfig
-      console.log(key, theme)
       return key === theme
     },
     setTheme (name) {
diff --git a/src/components/settings_modal/tabs/appearance_tab.vue b/src/components/settings_modal/tabs/appearance_tab.vue
index 748a69ce..3d3655d7 100644
--- a/src/components/settings_modal/tabs/appearance_tab.vue
+++ b/src/components/settings_modal/tabs/appearance_tab.vue
@@ -73,42 +73,42 @@
           <ul class="setting-list">
             <li>
               <FontControl
-                :model-value="fontsOverride.interface"
+                :model-value="mergedConfig.theme3hacks.fonts.interface"
                 name="ui"
                 :label="$t('settings.style.fonts.components.interface')"
                 :fallback="{ family: 'sans-serif' }"
                 no-inherit="1"
-                @update:modelValue="v => $store.dispatch('setOption', { name: 'fontsOverride', value: { ...fontsOverride, interface: v } })"
+                @update:modelValue="v => updateFont('interface', v)"
               />
             </li>
             <li>
               <FontControl
                 v-if="expertLevel > 0"
-                :model-value="fontsOverride.input"
+                :model-value="mergedConfig.theme3hacks.fonts.input"
                 name="input"
                 :fallback="{ family: 'inherit' }"
                 :label="$t('settings.style.fonts.components.input')"
-                @update:modelValue="v => $store.dispatch('setOption', { name: 'fontsOverride', value: { ...fontsOverride, input: v } })"
+                @update:modelValue="v => updateFont('input', v)"
               />
             </li>
             <li>
               <FontControl
                 v-if="expertLevel > 0"
-                :model-value="fontsOverride.post"
+                :model-value="mergedConfig.theme3hacks.fonts.post"
                 name="post"
                 :fallback="{ family: 'inherit' }"
                 :label="$t('settings.style.fonts.components.post')"
-                @update:modelValue="v => $store.dispatch('setOption', { name: 'fontsOverride', value: { ...fontsOverride, post: v } })"
+                @update:modelValue="v => updateFont('post', v)"
               />
             </li>
             <li>
               <FontControl
                 v-if="expertLevel > 0"
-                :model-value="fontsOverride.postCode"
+                :model-value="mergedConfig.theme3hacks.fonts.monospace"
                 name="postCode"
                 :fallback="{ family: 'monospace' }"
-                :label="$t('settings.style.fonts.components.postCode')"
-                @update:modelValue="v => $store.dispatch('setOption', { name: 'fontsOverride', value: { ...fontsOverride, postCode: v } })"
+                :label="$t('settings.style.fonts.components.monospace')"
+                @update:modelValue="v => updateFont('monospace', v)"
               />
             </li>
           </ul>
diff --git a/src/modules/config.js b/src/modules/config.js
index e54a883c..88e339cf 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -52,7 +52,13 @@ export const defaultState = {
   palette: null, // not used yet, will be used for V3
   theme3hacks: { // Hacks, user overrides that are independent of theme used
     underlay: 'none',
-    badgeColor: null
+    badgeColor: null,
+    fonts: {
+      interface: undefined,
+      input: undefined,
+      post: undefined,
+      monospace: undefined
+    }
   },
 
   hideISP: false,
@@ -147,7 +153,6 @@ export const defaultState = {
   navbarSize: undefined, // instance default
   panelHeaderSize: undefined, // instance default
   forcedRoundness: undefined, // instance default
-  fontsOverride: undefined, // instance default
   navbarColumnStretch: false,
   greentext: undefined, // instance default
   useAtIcon: undefined, // instance default
diff --git a/src/modules/interface.js b/src/modules/interface.js
index 38ee88db..206c68a7 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -274,6 +274,46 @@ const interfaceMod = {
 
           Object.entries(theme3hacks).forEach(([key, value]) => {
             switch (key) {
+              case 'fonts': {
+                Object.entries(theme3hacks.fonts).forEach(([fontKey, font]) => {
+                  if (!font?.family) return
+                  switch (fontKey) {
+                    case 'interface':
+                      hacks.push({
+                        component: 'Root',
+                        directives: {
+                          '--font': 'generic | ' + font.family
+                        }
+                      })
+                      break
+                    case 'input':
+                      hacks.push({
+                        component: 'Input',
+                        directives: {
+                          '--font': 'generic | ' + font.family
+                        }
+                      })
+                      break
+                    case 'post':
+                      hacks.push({
+                        component: 'RichContent',
+                        directives: {
+                          '--font': 'generic | ' + font.family
+                        }
+                      })
+                      break
+                    case 'monospace':
+                      hacks.push({
+                        component: 'Root',
+                        directives: {
+                          '--monoFont': 'generic | ' + font.family
+                        }
+                      })
+                      break
+                  }
+                })
+                break
+              }
               case 'underlay': {
                 if (value !== 'none') {
                   const newRule = {
-- 
cgit v1.2.3-70-g09d2


From 4d3d07515163bfba4304bd22f9ed7f90d32cea28 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 21 Jul 2024 23:45:21 +0300
Subject: fix font selector losing list of local fonts when selecting one

---
 src/components/font_control/font_control.js  | 5 +++--
 src/components/font_control/font_control.vue | 2 +-
 src/modules/interface.js                     | 3 ++-
 3 files changed, 6 insertions(+), 4 deletions(-)

(limited to 'src/components/font_control/font_control.vue')

diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js
index 08d0907e..13daa095 100644
--- a/src/components/font_control/font_control.js
+++ b/src/components/font_control/font_control.js
@@ -50,10 +50,11 @@ export default {
       return typeof this.modelValue !== 'undefined'
     },
     localFontsList () {
-      return this.$store.state.interface.localFonts?.values()
+      console.log('FONTLIST', this.$store.state.interface.localFonts?.values())
+      return this.$store.state.interface.localFonts
     },
     localFontsSize () {
-      return this.$store.state.interface.localFonts?.size
+      return this.$store.state.interface.localFonts?.length
     }
   }
 }
diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue
index b5423a8c..fca3b360 100644
--- a/src/components/font_control/font_control.vue
+++ b/src/components/font_control/font_control.vue
@@ -82,7 +82,7 @@
         </button>
         <Select
           :id="name + '-local-font-switcher'"
-          :model-value="modelValue.family"
+          :model-value="modelValue?.family"
           class="custom-font"
           @update:modelValue="v => $emit('update:modelValue', { ...(modelValue || {}), family: v })"
         >
diff --git a/src/modules/interface.js b/src/modules/interface.js
index 206c68a7..d4f0017a 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -112,7 +112,8 @@ const interfaceMod = {
       state.lastTimeline = value
     },
     setFontsList (state, value) {
-      state.localFonts = new Set(value.map(font => font.family))
+      // Set is used here so that we filter out duplicate fonts (possibly same font but with different weight)
+      state.localFonts = [...(new Set(value.map(font => font.family))).values()]
     }
   },
   actions: {
-- 
cgit v1.2.3-70-g09d2

