diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 75e75b60..1c0e7e8c 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -7,7 +7,9 @@ import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy }
import { CSS } from '@dnd-kit/utilities';
import { BrowserOpenURL, Environment, EventsOn, WindowFullscreen, WindowGetPosition, WindowGetSize, WindowIsFullscreen, WindowIsMaximised, WindowIsMinimised, WindowIsNormal, WindowMaximise, WindowMinimise, WindowSetDarkTheme, WindowSetLightTheme, WindowSetPosition, WindowSetSize, WindowSetSystemDefaultTheme, WindowUnfullscreen, WindowUnmaximise } from '../wailsjs/runtime';
import Sidebar from './components/Sidebar';
-import TitleBarPrimaryActions from './components/TitleBarPrimaryActions';
+import TitleBarPrimaryActions, {
+ resolveTitleBarPrimaryActionShortcut,
+} from './components/TitleBarPrimaryActions';
import TabManager from './components/TabManager';
import FloatingWorkbenchWindows from './components/FloatingWorkbenchWindows';
import FloatingAIChatWindow from './components/FloatingAIChatWindow';
@@ -2446,6 +2448,16 @@ function App() {
|| (runtimePlatform === '' && /mac/i.test(detectNavigatorPlatform()));
const useNativeMacWindowControls = isMacRuntime;
const activeShortcutPlatform = getShortcutPlatform(isMacRuntime);
+ const titleBarNewQueryShortcut = resolveTitleBarPrimaryActionShortcut(
+ shortcutOptions,
+ 'newQueryTab',
+ activeShortcutPlatform,
+ );
+ const titleBarNewConnectionShortcut = resolveTitleBarPrimaryActionShortcut(
+ shortcutOptions,
+ 'newConnection',
+ activeShortcutPlatform,
+ );
const macWindowDiagnosticsEnabled = shouldEnableMacWindowDiagnostics(
isMacRuntime,
import.meta.env.DEV,
@@ -7856,6 +7868,8 @@ function App() {
diff --git a/frontend/src/components/TitleBarPrimaryActions.test.tsx b/frontend/src/components/TitleBarPrimaryActions.test.tsx
index a781abff..303600e7 100644
--- a/frontend/src/components/TitleBarPrimaryActions.test.tsx
+++ b/frontend/src/components/TitleBarPrimaryActions.test.tsx
@@ -3,7 +3,13 @@ import { readFileSync } from 'node:fs';
import { create } from 'react-test-renderer';
import { describe, expect, it, vi } from 'vitest';
-import TitleBarPrimaryActions from './TitleBarPrimaryActions';
+import TitleBarPrimaryActions, {
+ resolveTitleBarPrimaryActionShortcut,
+} from './TitleBarPrimaryActions';
+import {
+ cloneShortcutOptions,
+ DEFAULT_SHORTCUT_OPTIONS,
+} from '../utils/shortcuts';
const appCss = readFileSync(new URL('../App.css', import.meta.url), 'utf8');
@@ -45,10 +51,13 @@ describe('TitleBarPrimaryActions', () => {
it('shows both labels in query-first order and invokes their actions', () => {
const onNewQuery = vi.fn();
const onNewConnection = vi.fn();
+ const shortcutOptions = cloneShortcutOptions(DEFAULT_SHORTCUT_OPTIONS);
const renderer = create(
,
@@ -58,6 +67,10 @@ describe('TitleBarPrimaryActions', () => {
const buttons = actions.findAllByType('button');
expect(actions.props['data-no-titlebar-toggle']).toBe('true');
expect(buttons.map((button) => button.props['aria-label'])).toEqual(['新建查询', '新建连接']);
+ expect(buttons.map((button) => button.props.title)).toEqual([
+ '新建查询 · ⌘N',
+ '新建连接 · ⌘⇧N',
+ ]);
expect(buttons.map((button) => button.children[button.children.length - 1])).toEqual(['新建查询', '新建连接']);
buttons[0].props.onClick();
@@ -65,4 +78,56 @@ describe('TitleBarPrimaryActions', () => {
expect(onNewQuery).toHaveBeenCalledTimes(1);
expect(onNewConnection).toHaveBeenCalledTimes(1);
});
+
+ it('shows both Windows shortcut labels', () => {
+ const shortcutOptions = cloneShortcutOptions(DEFAULT_SHORTCUT_OPTIONS);
+ const renderer = create(
+ ,
+ );
+
+ const buttons = renderer.root.findAllByType('button');
+ expect(buttons.map((button) => button.props.title)).toEqual([
+ 'New Query · Ctrl+N',
+ 'New Connection · Ctrl+Shift+N',
+ ]);
+ });
+
+ it('uses current platform custom bindings and hides disabled shortcuts', () => {
+ const shortcutOptions = cloneShortcutOptions(DEFAULT_SHORTCUT_OPTIONS);
+ shortcutOptions.newQueryTab.mac = { combo: 'Meta+Alt+Q', enabled: true };
+ shortcutOptions.newQueryTab.windows = { combo: 'Ctrl+Alt+W', enabled: true };
+ shortcutOptions.newConnection.mac = { combo: 'Meta+Shift+C', enabled: false };
+ shortcutOptions.newConnection.windows = { combo: 'Ctrl+Alt+C', enabled: true };
+
+ expect(resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newQueryTab', 'mac')).toBe('⌘⌥Q');
+ expect(resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newQueryTab', 'windows')).toBe('Ctrl+Alt+W');
+ expect(resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newConnection', 'mac')).toBeUndefined();
+ expect(resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newConnection', 'windows')).toBe('Ctrl+Alt+C');
+
+ const renderer = create(
+ ,
+ );
+
+ const buttons = renderer.root.findAllByType('button');
+ expect(buttons.map((button) => button.props.title)).toEqual([
+ '新建查询 · ⌘⌥Q',
+ '新建连接',
+ ]);
+ expect(buttons.map((button) => button.props['aria-label'])).toEqual(['新建查询', '新建连接']);
+ expect(buttons.every((button) => button.props.disabled !== true)).toBe(true);
+ });
});
diff --git a/frontend/src/components/TitleBarPrimaryActions.tsx b/frontend/src/components/TitleBarPrimaryActions.tsx
index 5a6efd6d..c69b4fb4 100644
--- a/frontend/src/components/TitleBarPrimaryActions.tsx
+++ b/frontend/src/components/TitleBarPrimaryActions.tsx
@@ -1,16 +1,43 @@
import React from 'react';
import { ConsoleSqlOutlined, PlusOutlined } from '@ant-design/icons';
+import {
+ getShortcutDisplayLabel,
+ resolveShortcutBinding,
+ type ShortcutOptions,
+ type ShortcutPlatform,
+} from '../utils/shortcuts';
+
+type TitleBarPrimaryShortcutAction = 'newQueryTab' | 'newConnection';
+
+export const resolveTitleBarPrimaryActionShortcut = (
+ shortcutOptions: Partial | null | undefined,
+ action: TitleBarPrimaryShortcutAction,
+ platform: ShortcutPlatform,
+): string | undefined => {
+ const binding = resolveShortcutBinding(shortcutOptions, action, platform);
+ return binding.enabled && binding.combo
+ ? getShortcutDisplayLabel(binding.combo, platform)
+ : undefined;
+};
interface TitleBarPrimaryActionsProps {
newQueryLabel: string;
newConnectionLabel: string;
+ newQueryShortcut?: string;
+ newConnectionShortcut?: string;
onNewQuery: () => void;
onNewConnection: () => void;
}
+const getActionTitle = (label: string, shortcut?: string): string => (
+ shortcut ? `${label} \u00b7 ${shortcut}` : label
+);
+
const TitleBarPrimaryActions: React.FC = ({
newQueryLabel,
newConnectionLabel,
+ newQueryShortcut,
+ newConnectionShortcut,
onNewQuery,
onNewConnection,
}) => (
@@ -24,7 +51,7 @@ const TitleBarPrimaryActions: React.FC = ({
type="button"
className="gonavi-titlebar-primary-action"
aria-label={newQueryLabel}
- title={newQueryLabel}
+ title={getActionTitle(newQueryLabel, newQueryShortcut)}
data-gonavi-new-query-action="true"
onClick={onNewQuery}
>
@@ -35,7 +62,7 @@ const TitleBarPrimaryActions: React.FC = ({
type="button"
className="gonavi-titlebar-primary-action"
aria-label={newConnectionLabel}
- title={newConnectionLabel}
+ title={getActionTitle(newConnectionLabel, newConnectionShortcut)}
data-gonavi-create-connection-action="true"
onClick={onNewConnection}
>