From 4af43e35e19e12a93e91d988ac849bf11c850cc8 Mon Sep 17 00:00:00 2001 From: rarnu Date: Sat, 2 May 2026 14:58:55 +0800 Subject: [PATCH] =?UTF-8?q?add:=20=E5=A2=9E=E5=8A=A0=E5=AF=B9CodeMirror?= =?UTF-8?q?=E7=9A=84=E8=AE=BE=E7=BD=AE=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 8 + pnpm-lock.yaml | 114 ++++++++++++ src-tauri/Cargo.lock | 2 +- src/App.vue | 14 +- .../editor/EditorSettingsDialog.vue | 173 ++++++++++++++++++ src/components/editor/QueryEditor.vue | 88 +++++++-- src/i18n/locales/en.ts | 10 + src/i18n/locales/zh-CN.ts | 10 + src/stores/settingsStore.ts | 69 ++++++- 9 files changed, 465 insertions(+), 23 deletions(-) create mode 100644 src/components/editor/EditorSettingsDialog.vue diff --git a/package.json b/package.json index 5300a177d5..e16a11c922 100644 --- a/package.json +++ b/package.json @@ -10,15 +10,23 @@ "tauri": "tauri" }, "dependencies": { + "@babel/runtime": "^7.29.2", "@codemirror/autocomplete": "^6.20.1", "@codemirror/lang-sql": "^6.10.0", "@codemirror/state": "^6.6.0", "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.41.1", + "@lottiefiles/dotlottie-web": "^0.71.0", "@tauri-apps/api": "^2", "@tauri-apps/plugin-dialog": "^2.7.0", "@tauri-apps/plugin-fs": "^2.5.0", "@tauri-apps/plugin-shell": "^2", + "@uiw/codemirror-theme-duotone": "^4.25.9", + "@uiw/codemirror-theme-material": "^4.25.9", + "@uiw/codemirror-theme-nord": "^4.25.9", + "@uiw/codemirror-theme-okaidia": "^4.25.9", + "@uiw/codemirror-theme-vscode": "^4.25.9", + "@uiw/codemirror-theme-xcode": "^4.25.9", "@vueuse/core": "^14.2.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f862604c20..95d28f23dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@babel/runtime': + specifier: ^7.29.2 + version: 7.29.2 '@codemirror/autocomplete': specifier: ^6.20.1 version: 6.20.1 @@ -23,6 +26,9 @@ importers: '@codemirror/view': specifier: ^6.41.1 version: 6.41.1 + '@lottiefiles/dotlottie-web': + specifier: ^0.71.0 + version: 0.71.0 '@tauri-apps/api': specifier: ^2 version: 2.10.1 @@ -35,6 +41,24 @@ importers: '@tauri-apps/plugin-shell': specifier: ^2 version: 2.3.5 + '@uiw/codemirror-theme-duotone': + specifier: ^4.25.9 + version: 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + '@uiw/codemirror-theme-material': + specifier: ^4.25.9 + version: 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + '@uiw/codemirror-theme-nord': + specifier: ^4.25.9 + version: 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + '@uiw/codemirror-theme-okaidia': + specifier: ^4.25.9 + version: 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + '@uiw/codemirror-theme-vscode': + specifier: ^4.25.9 + version: 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + '@uiw/codemirror-theme-xcode': + specifier: ^4.25.9 + version: 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) '@vueuse/core': specifier: ^14.2.1 version: 14.2.1(vue@3.5.33(typescript@5.6.3)) @@ -233,6 +257,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -557,6 +585,9 @@ packages: '@lezer/lr@1.4.10': resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==} + '@lottiefiles/dotlottie-web@0.71.0': + resolution: {integrity: sha512-CliQttL0Rk0or/aDQkqUJXnC9Cm5TxzNdOqy/YlzKlU6mu+XgTMwyt5/HpPiltlMeaqBmM9mOzfevpyP4QmudQ==} + '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} @@ -946,6 +977,31 @@ packages: '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + '@uiw/codemirror-theme-duotone@4.25.9': + resolution: {integrity: sha512-6IPZncdrtcgnU1EtQ1/IzaULZ+Jw5uAeVeQCae+rFBnW/m6Q8nWB8+iVnk8kCevgjT5ScZmRd9h4yqtSeJbUwQ==} + + '@uiw/codemirror-theme-material@4.25.9': + resolution: {integrity: sha512-6f2x+gmj2hHagqy6VkpnPbK7SWyP6kKruGgqpyIy09/f9pAUCqkW8mRY5ZEr28tA+YEGQaSY0Z2IBCHl8OKJog==} + + '@uiw/codemirror-theme-nord@4.25.9': + resolution: {integrity: sha512-5c568xmMidwICADxACB1zIhKoEgqbdVrdeOUZ2p5pE6NNKGR4ATzk9OSqhvr1ZhZPNOktxqSLLRzihFaZG0bDQ==} + + '@uiw/codemirror-theme-okaidia@4.25.9': + resolution: {integrity: sha512-lIJFUs/ws0prQz+dVo5ZIp0o6vxW7p6nf8iRFETN5S3KA3nJUR2cTF6u8mYLFwHMrFs2eReRsFyH94wjmuPWvg==} + + '@uiw/codemirror-theme-vscode@4.25.9': + resolution: {integrity: sha512-9KTnScHTSk97yGnyNYvDm6QZuBCdbO1OzMQ5bHtoBSPSVtH0LjY3bS6CXsBagb22v8OLPx/XwrBYOjKFp409CQ==} + + '@uiw/codemirror-theme-xcode@4.25.9': + resolution: {integrity: sha512-sMiDpOiW0iiNsLyqL1Vx6wZKOSoVUNfmWbBDtaYzlkRcKzkyJQp68cPIq5VG8Mhl2z+PX5cPbOA0nZEegNLicA==} + + '@uiw/codemirror-themes@4.25.9': + resolution: {integrity: sha512-DAHKb/L9ELwjY4nCf/MP/mIllHOn4GQe7RR4x8AMJuNeh9nGRRoo1uPxrxMmUL/bKqe6kDmDbIZ2AlhlqyIJuw==} + peerDependencies: + '@codemirror/language': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + '@unovue/detypes@0.8.5': resolution: {integrity: sha512-Yz4JeWOHGa+w/3YudVdng8hgN/VGW9cvp8xmFkmPPFzalGblLPPSpIRiwVo853yLstMZO2LLwe0vOoLAQsUQXw==} engines: {node: '>=18'} @@ -2867,6 +2923,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/runtime@7.29.2': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -3174,6 +3232,8 @@ snapshots: dependencies: '@lezer/common': 1.5.2 + '@lottiefiles/dotlottie-web@0.71.0': {} + '@marijn/find-cluster-break@1.0.2': {} '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': @@ -3455,6 +3515,60 @@ snapshots: '@types/web-bluetooth@0.0.21': {} + '@uiw/codemirror-theme-duotone@4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1)': + dependencies: + '@uiw/codemirror-themes': 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + + '@uiw/codemirror-theme-material@4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1)': + dependencies: + '@uiw/codemirror-themes': 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + + '@uiw/codemirror-theme-nord@4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1)': + dependencies: + '@uiw/codemirror-themes': 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + + '@uiw/codemirror-theme-okaidia@4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1)': + dependencies: + '@uiw/codemirror-themes': 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + + '@uiw/codemirror-theme-vscode@4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1)': + dependencies: + '@uiw/codemirror-themes': 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + + '@uiw/codemirror-theme-xcode@4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1)': + dependencies: + '@uiw/codemirror-themes': 4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + + '@uiw/codemirror-themes@4.25.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1)': + dependencies: + '@codemirror/language': 6.12.3 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.41.1 + '@unovue/detypes@0.8.5': dependencies: '@babel/core': 7.29.0 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index d05679a054..ae04040f12 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1548,7 +1548,7 @@ dependencies = [ [[package]] name = "dbx" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "chrono", diff --git a/src/App.vue b/src/App.vue index b4848bfbdb..b1efcd8e1b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,7 +1,7 @@ + + diff --git a/src/components/editor/QueryEditor.vue b/src/components/editor/QueryEditor.vue index 55a8c74d8a..50a46df4d1 100644 --- a/src/components/editor/QueryEditor.vue +++ b/src/components/editor/QueryEditor.vue @@ -2,9 +2,11 @@ import { ref, onMounted, onBeforeUnmount, watch, shallowRef } from "vue"; import type { CompletionContext } from "@codemirror/autocomplete"; import type { EditorView as EditorViewType } from "@codemirror/view"; +import type { Extension } from "@codemirror/state"; import { resolveExecutableSql } from "@/lib/sqlExecutionTarget"; import { formatSqlText, type SqlFormatDialect } from "@/lib/sqlFormatter"; import { useConnectionStore } from "@/stores/connectionStore"; +import { useSettingsStore, type EditorTheme } from "@/stores/settingsStore"; import { buildSqlCompletionItemsFromContext, getSqlCompletionContext, @@ -29,48 +31,72 @@ const emit = defineEmits<{ const editorRef = ref(); const view = shallowRef(null); const connectionStore = useConnectionStore(); -const DEFAULT_FONT_SIZE = 13; +const settingsStore = useSettingsStore(); const MIN_FONT_SIZE = 10; const MAX_FONT_SIZE = 24; let editorViewModule: typeof import("@codemirror/view") | null = null; let fontSizeTheme: import("@codemirror/state").Compartment | null = null; +let fontFamilyTheme: import("@codemirror/state").Compartment | null = null; +let codeMirrorTheme: import("@codemirror/state").Compartment | null = null; -const savedFontSize = Number(localStorage.getItem("dbx-query-editor-font-size")); -const fontSize = ref( - Number.isFinite(savedFontSize) - ? Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, savedFontSize)) - : DEFAULT_FONT_SIZE, -); +async function loadTheme(theme: EditorTheme): Promise { + switch (theme) { + case "one-dark": + return (await import("@codemirror/theme-one-dark")).oneDark; + case "vscode-dark": + return (await import("@uiw/codemirror-theme-vscode")).vscodeDark; + case "vscode-light": + return (await import("@uiw/codemirror-theme-vscode")).vscodeLight; + case "nord": + return (await import("@uiw/codemirror-theme-nord")).nord; + case "okaidia": + return (await import("@uiw/codemirror-theme-okaidia")).okaidia; + case "material": + return (await import("@uiw/codemirror-theme-material")).materialDark; + case "duotone-light": + return (await import("@uiw/codemirror-theme-duotone")).duotoneLight; + case "duotone-dark": + return (await import("@uiw/codemirror-theme-duotone")).duotoneDark; + case "xcode": + return (await import("@uiw/codemirror-theme-xcode")).xcodeLight; + default: + return (await import("@codemirror/theme-one-dark")).oneDark; + } +} -function fontTheme(EditorView: typeof import("@codemirror/view").EditorView, size: number) { +function fontTheme(EditorView: typeof import("@codemirror/view").EditorView, size: number, family: string) { return EditorView.theme({ "&": { height: "100%", fontSize: `${size}px` }, ".cm-scroller": { overflow: "auto" }, - ".cm-content": { fontFamily: "'JetBrains Mono', 'Fira Code', monospace" }, + ".cm-content": { fontFamily: family }, }); } function setFontSize(size: number) { const next = Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, size)); - fontSize.value = next; - localStorage.setItem("dbx-query-editor-font-size", String(next)); - if (view.value && fontSizeTheme && editorViewModule) { + const ss = settingsStore.editorSettings; + ss.fontSize = next; + settingsStore.updateEditorSettings({ fontSize: next }); + if (view.value && fontSizeTheme && fontFamilyTheme && editorViewModule) { view.value.dispatch({ - effects: fontSizeTheme.reconfigure(fontTheme(editorViewModule.EditorView, next)), + effects: [ + fontSizeTheme.reconfigure(fontTheme(editorViewModule.EditorView, next, ss.fontFamily)), + fontFamilyTheme.reconfigure(fontTheme(editorViewModule.EditorView, next, ss.fontFamily)), + ], }); } } function zoomIn() { - setFontSize(fontSize.value + 1); + setFontSize(settingsStore.editorSettings.fontSize + 1); } function zoomOut() { - setFontSize(fontSize.value - 1); + setFontSize(settingsStore.editorSettings.fontSize - 1); } function resetZoom() { - setFontSize(DEFAULT_FONT_SIZE); + setFontSize(13); } function selectedSqlFromView(currentView: EditorViewType): string { @@ -162,18 +188,20 @@ onMounted(async () => { { EditorState, Compartment }, { sql, MySQL, PostgreSQL, SQLDialect }, { basicSetup }, - { oneDark }, { autocompletion, startCompletion }, ] = await Promise.all([ import("@codemirror/view"), import("@codemirror/state"), import("@codemirror/lang-sql"), import("codemirror"), - import("@codemirror/theme-one-dark"), import("@codemirror/autocomplete"), ]); editorViewModule = { EditorView, keymap } as typeof import("@codemirror/view"); fontSizeTheme = new Compartment(); + fontFamilyTheme = new Compartment(); + codeMirrorTheme = new Compartment(); + + const ss = settingsStore.editorSettings; const baseDialect = props.dialect === "postgres" ? PostgreSQL : MySQL; const extraKeywords = "PIVOT UNPIVOT EXCLUDE REPLACE QUALIFY ASOF POSITIONAL ANTI SEMI SAMPLE TABLESAMPLE STRUCT MAP LIST ARRAY LAMBDA UNNEST LATERAL FILTER RECURSIVE SUMMARIZE PRAGMA READ_CSV READ_PARQUET READ_JSON DESCRIBE SHOW COPY EXPORT IMPORT"; @@ -220,6 +248,8 @@ onMounted(async () => { }, ]); + const theme = await loadTheme(ss.theme); + const state = EditorState.create({ doc: props.modelValue, extensions: [ @@ -231,7 +261,7 @@ onMounted(async () => { async (context: CompletionContext) => provideSqlCompletions(context.state, context.pos), ], }), - oneDark, + codeMirrorTheme.of(theme), runKeymap, EditorView.updateListener.of((update) => { if (update.docChanged) { @@ -248,7 +278,8 @@ onMounted(async () => { emit("selectionChange", selectedSqlFromView(update.view)); } }), - fontSizeTheme.of(fontTheme(EditorView, fontSize.value)), + fontSizeTheme.of(fontTheme(EditorView, ss.fontSize, ss.fontFamily)), + fontFamilyTheme.of(fontTheme(EditorView, ss.fontSize, ss.fontFamily)), EditorView.domEventHandlers({ wheel(event) { if (!event.metaKey && !event.ctrlKey) return false; @@ -282,6 +313,23 @@ watch( } ); +// Reactively apply editor settings changes (theme, font family, font size) +watch( + () => settingsStore.editorSettings, + async (ss) => { + if (!view.value || !codeMirrorTheme || !fontFamilyTheme || !fontSizeTheme || !editorViewModule) return; + const themeExt = await loadTheme(ss.theme); + view.value.dispatch({ + effects: [ + codeMirrorTheme.reconfigure(themeExt), + fontFamilyTheme.reconfigure(fontTheme(editorViewModule.EditorView, ss.fontSize, ss.fontFamily)), + fontSizeTheme.reconfigure(fontTheme(editorViewModule.EditorView, ss.fontSize, ss.fontFamily)), + ], + }); + }, + { deep: true } +); + onBeforeUnmount(() => { view.value?.destroy(); }); diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 8802f1943c..f2fce6f6a1 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -358,4 +358,14 @@ export default { executeSync: "Execute Sync", syncSuccess: "Sync executed successfully", }, + settings: { + title: "Settings", + fontFamily: "Font Family", + selectFont: "Select font...", + fontSize: "Font Size", + theme: "Theme", + selectTheme: "Select theme...", + apply: "Apply", + resetDefaults: "Reset Defaults", + }, }; diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index b66973236e..07139ef40b 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -358,4 +358,14 @@ export default { executeSync: "执行同步", syncSuccess: "同步执行成功", }, + settings: { + title: "设置", + fontFamily: "字体", + selectFont: "选择字体...", + fontSize: "字号", + theme: "主题", + selectTheme: "选择主题...", + apply: "应用", + resetDefaults: "恢复默认", + }, }; diff --git a/src/stores/settingsStore.ts b/src/stores/settingsStore.ts index c7260b67c5..75b70c7655 100644 --- a/src/stores/settingsStore.ts +++ b/src/stores/settingsStore.ts @@ -19,10 +19,69 @@ const defaultConfigs: Record> = { custom: { provider: "custom", endpoint: "", model: "", apiStyle: "completions" }, }; +export type EditorTheme = "one-dark" | "vscode-dark" | "vscode-light" | "nord" | "okaidia" | "material" | "duotone-light" | "duotone-dark" | "xcode"; + +export interface EditorSettings { + fontFamily: string; + fontSize: number; + theme: EditorTheme; +} + +export const EDITOR_THEMES: { value: EditorTheme; label: string; dark: boolean }[] = [ + { value: "one-dark", label: "One Dark", dark: true }, + { value: "vscode-dark", label: "VS Dark+", dark: true }, + { value: "vscode-light", label: "VS Light+", dark: false }, + { value: "nord", label: "Nord", dark: true }, + { value: "okaidia", label: "Okaidia", dark: true }, + { value: "material", label: "Material", dark: true }, + { value: "duotone-light", label: "Duotone Light", dark: false }, + { value: "duotone-dark", label: "Duotone Dark", dark: true }, + { value: "xcode", label: "Xcode", dark: false }, +]; + +export const FONT_FAMILIES: { value: string; label: string }[] = [ + { value: "'JetBrains Mono', 'Fira Code', monospace", label: "JetBrains Mono" }, + { value: "'Fira Code', monospace", label: "Fira Code" }, + { value: "'Cascadia Code', monospace", label: "Cascadia Code" }, + { value: "'Source Code Pro', monospace", label: "Source Code Pro" }, + { value: "'SF Mono', 'Menlo', monospace", label: "SF Mono / Menlo" }, + { value: "'Consolas', 'Courier New', monospace", label: "Consolas" }, + { value: "monospace", label: "System Monospace" }, +]; + +const DEFAULT_EDITOR_SETTINGS: EditorSettings = { + fontFamily: "'JetBrains Mono', 'Fira Code', monospace", + fontSize: 13, + theme: "one-dark", +}; + +export const STORAGE_KEY = "dbx-editor-settings"; + +function loadEditorSettings(): EditorSettings { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) { + const parsed = JSON.parse(raw) as Partial; + return { + fontFamily: parsed.fontFamily ?? DEFAULT_EDITOR_SETTINGS.fontFamily, + fontSize: parsed.fontSize ?? DEFAULT_EDITOR_SETTINGS.fontSize, + theme: parsed.theme ?? DEFAULT_EDITOR_SETTINGS.theme, + }; + } + } catch { /* ignore */ } + return { ...DEFAULT_EDITOR_SETTINGS }; +} + +function saveEditorSettings(settings: EditorSettings) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); +} + export const useSettingsStore = defineStore("settings", () => { const aiConfig = ref({ ...defaultConfigs.claude, apiKey: "", apiStyle: "completions" }); const isAiConfigLoaded = ref(false); + const editorSettings = ref(loadEditorSettings()); + async function initAiConfig() { if (isAiConfigLoaded.value) return; const legacy = localStorage.getItem("dbx-ai-config"); @@ -50,5 +109,13 @@ export const useSettingsStore = defineStore("settings", () => { return !!aiConfig.value.apiKey && !!aiConfig.value.endpoint; } - return { aiConfig, isAiConfigLoaded, initAiConfig, updateAiConfig, isConfigured }; + function updateEditorSettings(partial: Partial) { + Object.assign(editorSettings.value, partial); + saveEditorSettings(editorSettings.value); + } + + return { + aiConfig, isAiConfigLoaded, initAiConfig, updateAiConfig, isConfigured, + editorSettings, updateEditorSettings, + }; });