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"
/>
+
+
+
+
+
+ toggleCodeLigatures()"
+ />
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)
+ })
+ })
+})