diff --git a/app/assets/main.css b/app/assets/main.css index 8897b7da18..75e80729a7 100644 --- a/app/assets/main.css +++ b/app/assets/main.css @@ -287,6 +287,11 @@ html.light .shiki { } } +/* Settings-based configuration for code ligatures. On by default. */ +:root[data-code-ligatures='false'] code { + font-variant-ligatures: none; +} + /* Inline code in package descriptions */ p > span > code, .line-clamp-2 code { diff --git a/app/composables/useSettings.ts b/app/composables/useSettings.ts index 533c03042b..53b31846e8 100644 --- a/app/composables/useSettings.ts +++ b/app/composables/useSettings.ts @@ -39,6 +39,8 @@ export interface AppSettings { autoOpenURL: boolean } codeContainerFull: boolean + /** Enable/disable ligatures in code */ + codeLigatures: boolean sidebar: { collapsed: string[] } @@ -65,6 +67,7 @@ const DEFAULT_SETTINGS: AppSettings = { autoOpenURL: false, }, codeContainerFull: false, + codeLigatures: true, sidebar: { collapsed: [], }, @@ -253,3 +256,33 @@ export function useCodeContainer() { toggleCodeContainer, } } + +export const useCodeLigatures = createSharedComposable(function useCodeLigatures() { + const { settings } = useSettings() + + const codeLigatures = computed(() => settings.value.codeLigatures) + + if (import.meta.client) { + // Sync the data attribute on root to the setting + watch( + codeLigatures, + value => { + if (value) { + delete document.documentElement.dataset.codeLigatures + } else { + document.documentElement.dataset.codeLigatures = 'false' + } + }, + { immediate: true }, + ) + } + + function toggleCodeLigatures() { + settings.value.codeLigatures = !settings.value.codeLigatures + } + + return { + codeLigatures, + toggleCodeLigatures, + } +}) diff --git a/app/pages/settings.vue b/app/pages/settings.vue index e90faf551f..d9bd556f83 100644 --- a/app/pages/settings.vue +++ b/app/pages/settings.vue @@ -5,6 +5,7 @@ const { locale: currentLocale, locales, setLocale: setNuxti18nLocale } = useI18n const colorMode = useColorMode() const { currentLocaleStatus, isSourceLocale } = useI18nStatus() const keyboardShortcutsEnabled = useKeyboardShortcuts() +const { toggleCodeLigatures } = useCodeLigatures() // Escape to go back (but not when focused on form elements or modal is open) onKeyStroke( @@ -143,6 +144,16 @@ const setLocale: typeof setNuxti18nLocale = newLocale => { :description="$t('settings.enable_graph_pulse_loop_description')" v-model="settings.enableGraphPulseLooping" /> + + +
+ + +
diff --git a/app/utils/prehydrate.ts b/app/utils/prehydrate.ts index 46a579e232..230f9cbdec 100644 --- a/app/utils/prehydrate.ts +++ b/app/utils/prehydrate.ts @@ -66,5 +66,10 @@ export function initPreferencesOnPrehydrate() { if (settings.keyboardShortcuts === false) { document.documentElement.dataset.kbdShortcuts = 'false' } + + // Code font ligatures (default: true) + if (settings.codeLigatures === false) { + document.documentElement.dataset.codeLigatures = 'false' + } }) } diff --git a/i18n/locales/cs-CZ.json b/i18n/locales/cs-CZ.json index 2ca6c6e2fc..bd97b72c7d 100644 --- a/i18n/locales/cs-CZ.json +++ b/i18n/locales/cs-CZ.json @@ -280,7 +280,8 @@ "black": "Černá" }, "keyboard_shortcuts_enabled": "Povolit klávesové zkratky", - "keyboard_shortcuts_enabled_description": "Klávesové zkratky lze zakázat, pokud se střetávají s jinými zkratkami prohlížeče nebo systému" + "keyboard_shortcuts_enabled_description": "Klávesové zkratky lze zakázat, pokud se střetávají s jinými zkratkami prohlížeče nebo systému", + "enable_code_ligatures": "Zapnout ligatury v kódu" }, "i18n": { "missing_keys": "{count} chybějící překlad | {count} chybějící překlady | {count} chybějících překladů", diff --git a/i18n/locales/en.json b/i18n/locales/en.json index a142d1ab54..02979caaaa 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -280,7 +280,8 @@ "black": "Black" }, "keyboard_shortcuts_enabled": "Enable keyboard shortcuts", - "keyboard_shortcuts_enabled_description": "Keyboard shortcuts can be disabled if they conflict with other browser or system shortcuts" + "keyboard_shortcuts_enabled_description": "Keyboard shortcuts can be disabled if they conflict with other browser or system shortcuts", + "enable_code_ligatures": "Enable ligatures in code" }, "i18n": { "missing_keys": "{count} missing translation | {count} missing translations", diff --git a/i18n/schema.json b/i18n/schema.json index b3ccd57f64..7c9546204f 100644 --- a/i18n/schema.json +++ b/i18n/schema.json @@ -846,6 +846,9 @@ }, "keyboard_shortcuts_enabled_description": { "type": "string" + }, + "enable_code_ligatures": { + "type": "string" } }, "additionalProperties": false diff --git a/test/nuxt/composables/use-settings.spec.ts b/test/nuxt/composables/use-settings.spec.ts index f0a9feed33..74eb043450 100644 --- a/test/nuxt/composables/use-settings.spec.ts +++ b/test/nuxt/composables/use-settings.spec.ts @@ -46,3 +46,31 @@ describe('useSettings - keyboardShortcuts', () => { }) }) }) + +describe('useSettings - codeLigatures', () => { + beforeEach(() => { + vi.resetModules() + }) + + it('has a default value of true', async () => { + const { useSettings } = await import('~/composables/useSettings') + const codeLigatures = useSettings().settings.value.codeLigatures + expect(codeLigatures).toBe(true) + }) + + describe('useCodeLigatures', () => { + it('has a default value of true', async () => { + const { useCodeLigatures } = await import('~/composables/useSettings') + const codeLigatures = useCodeLigatures().codeLigatures + expect(codeLigatures.value).toBe(true) + }) + + it('updates after toggle', async () => { + const { useCodeLigatures } = await import('~/composables/useSettings') + const { codeLigatures, toggleCodeLigatures } = useCodeLigatures() + expect(codeLigatures.value).toBe(true) + toggleCodeLigatures() + expect(codeLigatures.value).toBe(false) + }) + }) +})