From 3285cb21a464c46abd9deb6e376a4b34167715dc Mon Sep 17 00:00:00 2001 From: weiyong <1044070878@qq.com> Date: Thu, 30 Jul 2026 15:13:35 +0800 Subject: [PATCH] feat(editor): configure Ctrl+Click table navigation --- apps/desktop/src/App.vue | 4 ++++ .../editor/EditorSettingsDialog.vue | 17 +++++++++++++++ apps/desktop/src/i18n/locales/en.ts | 2 ++ apps/desktop/src/i18n/locales/es.ts | 2 ++ apps/desktop/src/i18n/locales/it.ts | 2 ++ apps/desktop/src/i18n/locales/ja.ts | 2 ++ apps/desktop/src/i18n/locales/pt-BR.ts | 2 ++ apps/desktop/src/i18n/locales/zh-CN.ts | 2 ++ apps/desktop/src/i18n/locales/zh-TW.ts | 2 ++ .../src/lib/settings/editorSettingsDraft.ts | 1 + .../stores/__tests__/settingsStore.spec.ts | 21 +++++++++++++++++++ apps/desktop/src/stores/settingsStore.ts | 11 ++++++++++ 12 files changed, 68 insertions(+) diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index 78837e6bd8..35a876a360 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -1433,6 +1433,10 @@ async function onClickTable(table: SqlObjectNavigationTarget) { showQueryEditorDdlDialog.value = true; return; } + if (settingsStore.editorSettings.clickTableNavigationTarget === "ddl") { + queryStore.openTableStructure(target.connectionId, target.database, target.schema, target.tableName, "ddl", undefined, target.catalog); + return; + } try { await openTableTarget(target, { tableInfoTab: "ddl" }); } catch (e: any) { diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 36c7f02b5d..1e10e7e9bd 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -43,6 +43,7 @@ import { type UpdateDownloadSource, type CustomThemeColors, type CustomTheme, + type ClickTableNavigationTarget, } from "@/stores/settingsStore"; import { createRunStatementButtonDom, loadEditorTheme, editorFontTheme } from "@/lib/editor/editorThemes"; import { orderAiConfigsForDisplay } from "@/lib/ai/aiConfigOrdering"; @@ -382,6 +383,7 @@ const editOpenTabsRestoreMode = ref(settingsStore.editorSet const editDisconnectTabHandlingMode = ref(settingsStore.editorSettings.disconnectTabHandlingMode); const editReuseDataTab = ref(settingsStore.editorSettings.reuseDataTab); const editPrefillNewQueryWithSelect = ref(settingsStore.editorSettings.prefillNewQueryWithSelect); +const editClickTableNavigationTarget = ref(settingsStore.editorSettings.clickTableNavigationTarget); const editUpdateNotificationsEnabled = ref(settingsStore.editorSettings.updateNotificationsEnabled); const editSidebarHiddenTablePrefixes = ref(settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n")); const editSidebarObjectInfoMode = ref(settingsStore.editorSettings.sidebarObjectInfoMode); @@ -501,6 +503,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft { toolbarItems: { ...editToolbarItems.value }, snippets: editSnippets.value, sqlVariableSyntaxOverrides: editSqlVariableSyntaxOverrides.value, + clickTableNavigationTarget: editClickTableNavigationTarget.value, }; } @@ -751,6 +754,7 @@ function syncEditorSettingsDraftFromStore() { editDisconnectTabHandlingMode.value = settingsStore.editorSettings.disconnectTabHandlingMode; editReuseDataTab.value = settingsStore.editorSettings.reuseDataTab; editPrefillNewQueryWithSelect.value = settingsStore.editorSettings.prefillNewQueryWithSelect; + editClickTableNavigationTarget.value = settingsStore.editorSettings.clickTableNavigationTarget; editUpdateNotificationsEnabled.value = settingsStore.editorSettings.updateNotificationsEnabled; editSidebarHiddenTablePrefixes.value = settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n"); editSidebarObjectInfoMode.value = settingsStore.editorSettings.sidebarObjectInfoMode; @@ -766,6 +770,7 @@ function syncEditorSettingsDraftFromStore() { editToolbarItems.value = { ...settingsStore.editorSettings.toolbarItems }; editSnippets.value = settingsStore.editorSettings.snippets.map(editableSnippet); editSqlVariableSyntaxOverrides.value = normalizeSqlVariableSyntaxOverrides(settingsStore.editorSettings.sqlVariableSyntaxOverrides); + editClickTableNavigationTarget.value = settingsStore.editorSettings.clickTableNavigationTarget; editEditorSettingsBase.value = editorSettingsDraftFromSettings(settingsStore.editorSettings); } @@ -925,6 +930,7 @@ function resetDefaultsForTab(tab: SettingsCategory) { editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution; editContinueOnErrorOnBatch.value = DEFAULT_EDITOR_SETTINGS.continueOnErrorOnBatch; editConfirmUnsavedSqlClose.value = DEFAULT_EDITOR_SETTINGS.confirmUnsavedSqlClose; + editClickTableNavigationTarget.value = DEFAULT_EDITOR_SETTINGS.clickTableNavigationTarget; editSqlVariableSyntaxOverrides.value = normalizeSqlVariableSyntaxOverrides(DEFAULT_EDITOR_SETTINGS.sqlVariableSyntaxOverrides); } else if (tab === "formatter") { editSqlFormatter.value = normalizeSqlFormatterSettings(DEFAULT_EDITOR_SETTINGS.sqlFormatter); @@ -954,6 +960,7 @@ function resetDefaultsForTab(tab: SettingsCategory) { editDisconnectTabHandlingMode.value = DEFAULT_EDITOR_SETTINGS.disconnectTabHandlingMode; editReuseDataTab.value = DEFAULT_EDITOR_SETTINGS.reuseDataTab; editPrefillNewQueryWithSelect.value = DEFAULT_EDITOR_SETTINGS.prefillNewQueryWithSelect; + editClickTableNavigationTarget.value = DEFAULT_EDITOR_SETTINGS.clickTableNavigationTarget; editUpdateNotificationsEnabled.value = DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled; editSidebarObjectInfoMode.value = DEFAULT_EDITOR_SETTINGS.sidebarObjectInfoMode; editSidebarAllowHorizontalScroll.value = DEFAULT_EDITOR_SETTINGS.sidebarAllowHorizontalScroll; @@ -3367,6 +3374,16 @@ onUnmounted(cleanupPreviewEditor); +
+
+ +

+ {{ t("settings.clickTableNavigationTargetDescription") }} +

+
+ +
+
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 27e31eb617..7bde841d0d 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -4602,6 +4602,8 @@ export default { confirmUnsavedSqlCloseDescription: "When disabled, SQL tabs with unsaved edits close or quit without the save confirmation dialog.", prefillNewQueryWithSelect: "Prefill new query with SELECT *", prefillNewQueryWithSelectDescription: "When creating a new query, prefill the editor with SELECT * FROM based on the active table tab or the table selected in the sidebar.", + clickTableNavigationTarget: "Ctrl+Click Table Opens DDL", + clickTableNavigationTargetDescription: "When enabled, Ctrl/Cmd+clicking a table name opens the table structure editor (DDL). When disabled, it opens the table data view.", sqlVariableSyntax: "SQL variable & placeholder substitution", sqlVariableSyntaxDescription: "Choose which variable and placeholder syntaxes DBX substitutes before running SQL, per database type. All are enabled by default.", sqlVariableSyntax_positional: "Positional placeholder", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 5f93ad6d1e..fded4174ad 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -4363,6 +4363,8 @@ export default withEnglishFallback({ confirmUnsavedSqlCloseDescription: "Cuando se desactiva, las pestañas SQL con ediciones sin guardar se cierran o salen sin el diálogo de confirmación de guardado.", prefillNewQueryWithSelect: "Rellenar nueva consulta con SELECT *", prefillNewQueryWithSelectDescription: "Al crear una nueva consulta, rellena el editor con SELECT * FROM según la pestaña de tabla activa o la tabla seleccionada en la barra lateral.", + clickTableNavigationTarget: "Ctrl+Clic abre DDL de tabla", + clickTableNavigationTargetDescription: "Cuando está activado, Ctrl/Cmd+clic en el nombre de la tabla abre el editor de estructura (DDL). Cuando está desactivado, abre la vista de datos.", sqlVariableSyntax: "Sustitución de variables y marcadores SQL", sqlVariableSyntaxDescription: "Elige qué sintaxis de variables y marcadores sustituye DBX antes de ejecutar SQL, por tipo de base de datos. Todas están habilitadas de forma predeterminada.", sqlVariableSyntax_positional: "Marcador posicional", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 1a28062da0..dbbeb4ac2c 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -4361,6 +4361,8 @@ export default withEnglishFallback({ confirmUnsavedSqlCloseDescription: "Se disattivato, le schede SQL con modifiche non salvate verranno chiuse o usciranno senza la finestra di conferma di salvataggio.", prefillNewQueryWithSelect: "Precompila nuova query con SELECT *", prefillNewQueryWithSelectDescription: "Alla creazione di una nuova query, precompila l'editor con SELECT * FROM in base alla scheda tabella attiva o alla tabella selezionata nella barra laterale.", + clickTableNavigationTarget: "Ctrl+Clic apre DDL tabella", + clickTableNavigationTargetDescription: "Quando attivato, Ctrl/Cmd+clic sul nome della tabella apre l'editor di struttura (DDL). Quando disattivato, apre la vista dati.", sqlVariableSyntax: "Sostituzione di variabili e segnaposto SQL", sqlVariableSyntaxDescription: "Scegli quali sintassi di variabili e segnaposto DBX sostituisce prima di eseguire SQL, per tipo di database. Tutte abilitate per impostazione predefinita.", sqlVariableSyntax_positional: "Segnaposto posizionale", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index afba7d4fb9..44966cfe89 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -4352,6 +4352,8 @@ export default withEnglishFallback({ confirmUnsavedSqlCloseDescription: "無効時、未保存の編集があるSQLタブは保存確認ダイアログなしで閉じるか終了します。", prefillNewQueryWithSelect: "新規クエリに SELECT を自動入力", prefillNewQueryWithSelectDescription: "新規クエリ作成時、アクティブなテーブルタブまたはサイドバーで選択したテーブルに基づき、エディタに SELECT * FROM <テーブル名> を自動入力します。", + clickTableNavigationTarget: "Ctrl+クリックでテーブル構造を開く", + clickTableNavigationTargetDescription: "有効にすると、Ctrl/Cmd+クリックでテーブル構造エディタ(DDL)を開きます。無効の場合はテーブルデータビューを開きます。", sqlVariableSyntax: "SQL 変数・プレースホルダー置換", sqlVariableSyntaxDescription: "SQL 実行前に置換する変数・プレースホルダー構文をデータベース種別ごとに選択します。既定ではすべて有効です。", sqlVariableSyntax_positional: "位置プレースホルダー", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 151d572038..d47b0b9821 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -4363,6 +4363,8 @@ export default withEnglishFallback({ confirmUnsavedSqlCloseDescription: "Quando desativado, abas SQL com edições não salvas são fechadas ou o app é encerrado sem a caixa de diálogo de confirmação de salvamento.", prefillNewQueryWithSelect: "Preencher nova consulta com SELECT *", prefillNewQueryWithSelectDescription: "Ao criar uma nova consulta, preenche o editor com SELECT * FROM com base na aba de tabela ativa ou na tabela selecionada na barra lateral.", + clickTableNavigationTarget: "Ctrl+Clique abre DDL da tabela", + clickTableNavigationTargetDescription: "Quando ativado, Ctrl/Cmd+clicar no nome da tabela abre o editor de estrutura (DDL). Quando desativado, abre a visualização de dados.", sqlVariableSyntax: "Substituição de variáveis e espaços reservados SQL", sqlVariableSyntaxDescription: "Escolha quais sintaxes de variáveis e espaços reservados o DBX substitui antes de executar SQL, por tipo de banco de dados. Todas ativadas por padrão.", sqlVariableSyntax_positional: "Espaço reservado posicional", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index d023ef52fe..43d8d71541 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -4601,6 +4601,8 @@ export default withEnglishFallback({ confirmUnsavedSqlCloseDescription: "关闭后,有未保存内容的 SQL 标签页会直接关闭或退出,不再弹出保存确认。", prefillNewQueryWithSelect: "新建查询时预填充 SELECT 语句", prefillNewQueryWithSelectDescription: "新建查询时,根据当前激活的数据表标签页或侧边栏选中的表,自动在编辑器中填充 SELECT * FROM <表名>。", + clickTableNavigationTarget: "Ctrl+点击表名打开 DDL", + clickTableNavigationTargetDescription: "开启后,Ctrl/Cmd+点击表名将打开表结构编辑器(DDL);关闭则打开表数据视图。", sqlVariableSyntax: "SQL 变量与占位符替换", sqlVariableSyntaxDescription: "按数据库类型选择执行 SQL 前替换哪些变量与占位符语法,默认全部开启。", sqlVariableSyntax_positional: "位置占位符", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 10680c4d97..ed358081a8 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -4028,6 +4028,8 @@ export default withEnglishFallback({ confirmUnsavedSqlCloseDescription: "關閉後,有未儲存內容的 SQL 分頁會直接關閉或結束,不再彈出儲存確認。", prefillNewQueryWithSelect: "新建查詢時預填 SELECT 語句", prefillNewQueryWithSelectDescription: "新建查詢時,根據目前啟用的資料表分頁或側邊欄選取的資料表,自動在編輯器中填入 SELECT * FROM <資料表名稱>。", + clickTableNavigationTarget: "Ctrl+點擊表名開啟 DDL", + clickTableNavigationTargetDescription: "開啟後,Ctrl/Cmd+點擊表名將開啟表結構編輯器(DDL);關閉則開啟表資料檢視。", sqlVariableSyntax: "SQL 變數與佔位符替換", sqlVariableSyntaxDescription: "依資料庫類型選擇執行 SQL 前替換哪些變數與佔位符語法,預設全部開啟。", sqlVariableSyntax_positional: "位置佔位符", diff --git a/apps/desktop/src/lib/settings/editorSettingsDraft.ts b/apps/desktop/src/lib/settings/editorSettingsDraft.ts index a9a412e2c5..f5fd119c39 100644 --- a/apps/desktop/src/lib/settings/editorSettingsDraft.ts +++ b/apps/desktop/src/lib/settings/editorSettingsDraft.ts @@ -62,6 +62,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [ "snippets", "sqlVariableSyntaxOverrides", "continueOnErrorOnBatch", + "clickTableNavigationTarget", ] as const satisfies readonly (keyof EditorSettings)[]; export type EditorSettingsDraftKey = (typeof EDITOR_SETTINGS_DRAFT_KEYS)[number]; diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index 59c1b3aa6b..46a1a46665 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -282,6 +282,27 @@ describe("normalizeEditorSettings - continueOnErrorOnBatch", () => { }); }); +describe("normalizeEditorSettings - clickTableNavigationTarget", () => { + it("defaults clickTableNavigationTarget to 'data'", () => { + expect(normalizeEditorSettings({}).clickTableNavigationTarget).toBe("data"); + }); + + it("preserves explicit 'ddl' value", () => { + expect(normalizeEditorSettings({ clickTableNavigationTarget: "ddl" }).clickTableNavigationTarget).toBe("ddl"); + }); + + it("preserves explicit 'data' value", () => { + expect(normalizeEditorSettings({ clickTableNavigationTarget: "data" }).clickTableNavigationTarget).toBe("data"); + }); + + it("falls back to 'data' for invalid values", () => { + expect(normalizeEditorSettings({ clickTableNavigationTarget: "invalid" } as any).clickTableNavigationTarget).toBe("data"); + expect(normalizeEditorSettings({ clickTableNavigationTarget: undefined } as any).clickTableNavigationTarget).toBe("data"); + expect(normalizeEditorSettings({ clickTableNavigationTarget: null } as any).clickTableNavigationTarget).toBe("data"); + expect(normalizeEditorSettings({ clickTableNavigationTarget: 123 } as any).clickTableNavigationTarget).toBe("data"); + }); +}); + describe("normalizeEditorSettings - tabLayout", () => { it("defaults tabLayout to scroll", () => { expect(normalizeEditorSettings({}).tabLayout).toBe("scroll"); diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 83eff880ce..98320a4536 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -346,6 +346,9 @@ export const TABLE_FONT_SIZE_DEFAULT = 13; const DISCONNECT_TAB_HANDLING_MODES = ["close-tabs", "keep-tabs-clear-results", "keep-tabs-keep-results"] as const; export type DisconnectTabHandlingMode = (typeof DISCONNECT_TAB_HANDLING_MODES)[number]; +const CLICK_TABLE_NAVIGATION_TARGETS = ["data", "ddl"] as const; +export type ClickTableNavigationTarget = (typeof CLICK_TABLE_NAVIGATION_TARGETS)[number]; + export interface CustomThemeColors { keyword: string; field: string; @@ -505,6 +508,7 @@ export interface EditorSettings { objectBrowserViewMode: "list" | "grid"; sqlVariableSyntaxOverrides: SqlVariableSyntaxOverrides; continueOnErrorOnBatch: boolean; + clickTableNavigationTarget: ClickTableNavigationTarget; } export interface ToolbarItems { @@ -680,6 +684,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { objectBrowserViewMode: "list", sqlVariableSyntaxOverrides: {}, continueOnErrorOnBatch: false, + clickTableNavigationTarget: "data", }; export const STORAGE_KEY = "dbx-editor-settings"; @@ -767,6 +772,10 @@ function normalizeDisconnectTabHandlingMode(value: unknown, legacyCloseTabsOnDis return DEFAULT_EDITOR_SETTINGS.disconnectTabHandlingMode; } +function normalizeClickTableNavigationTarget(value: unknown): ClickTableNavigationTarget { + return value === "data" || value === "ddl" ? value : DEFAULT_EDITOR_SETTINGS.clickTableNavigationTarget; +} + function normalizeOpenTabsRestoreMode(value: unknown, legacyRestoreOpenTabsOnLaunch?: unknown): OpenTabsRestoreMode { if (value === "all" || value === "pinned" || value === "none") return value; if (typeof legacyRestoreOpenTabsOnLaunch === "boolean") return legacyRestoreOpenTabsOnLaunch ? "all" : "none"; @@ -1013,6 +1022,7 @@ export function normalizeEditorSettings(settings: Partial, exist objectBrowserViewMode: settings.objectBrowserViewMode === "grid" ? "grid" : DEFAULT_EDITOR_SETTINGS.objectBrowserViewMode, sqlVariableSyntaxOverrides: normalizeSqlVariableSyntaxOverrides(settings.sqlVariableSyntaxOverrides), continueOnErrorOnBatch: settings.continueOnErrorOnBatch === true, + clickTableNavigationTarget: normalizeClickTableNavigationTarget(settings.clickTableNavigationTarget), }; } @@ -1463,6 +1473,7 @@ export const useSettingsStore = defineStore("settings", () => { if (partial.objectBrowserViewMode !== undefined) editorSettings.value.objectBrowserViewMode = partial.objectBrowserViewMode === "grid" ? "grid" : "list"; if (partial.sqlVariableSyntaxOverrides !== undefined) editorSettings.value.sqlVariableSyntaxOverrides = normalizeSqlVariableSyntaxOverrides(partial.sqlVariableSyntaxOverrides); if (partial.continueOnErrorOnBatch !== undefined) editorSettings.value.continueOnErrorOnBatch = partial.continueOnErrorOnBatch === true; + if (partial.clickTableNavigationTarget !== undefined) editorSettings.value.clickTableNavigationTarget = normalizeClickTableNavigationTarget(partial.clickTableNavigationTarget); saveEditorSettings(editorSettings.value); }