From bd5155ca41744ddc3709b6845661a58487aeb7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maty=C3=A1=C5=A1=20Volf?= Date: Sat, 11 Apr 2026 15:00:18 +0200 Subject: [PATCH 1/2] feat: Enable disabling ligatures in code --- app/assets/main.css | 5 ++++ app/composables/useSettings.ts | 33 ++++++++++++++++++++++ app/pages/settings.vue | 11 ++++++++ app/utils/prehydrate.ts | 5 ++++ i18n/locales/cs-CZ.json | 3 +- i18n/locales/en.json | 3 +- i18n/schema.json | 3 ++ test/nuxt/composables/use-settings.spec.ts | 28 ++++++++++++++++++ 8 files changed, 89 insertions(+), 2 deletions(-) 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..63a698bff6 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 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) + }) + }) +}) From 81179de6a7ee4dc3b180696a5fa0aee49b3d783a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maty=C3=A1=C5=A1=20Volf?= Date: Sat, 11 Apr 2026 17:04:06 +0200 Subject: [PATCH 2/2] Wrap composable in createSharedComposable --- app/composables/useSettings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/composables/useSettings.ts b/app/composables/useSettings.ts index 63a698bff6..53b31846e8 100644 --- a/app/composables/useSettings.ts +++ b/app/composables/useSettings.ts @@ -257,7 +257,7 @@ export function useCodeContainer() { } } -export function useCodeLigatures() { +export const useCodeLigatures = createSharedComposable(function useCodeLigatures() { const { settings } = useSettings() const codeLigatures = computed(() => settings.value.codeLigatures) @@ -285,4 +285,4 @@ export function useCodeLigatures() { codeLigatures, toggleCodeLigatures, } -} +})