mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-12 09:29:43 +08:00
🐛 fix(shortcuts): 同步侧边栏搜索快捷键提示
- 侧边栏 v2 搜索入口改为读取用户快捷键配置 - 修复搜索入口固定显示默认 ⌘K 的问题 - 按 macOS 语义使用 Cmd+F 作为查找类快捷键 - 移除快捷键描述中的硬编码默认组合 - 补充快捷键展示与平台冲突判断测试
This commit is contained in:
@@ -2129,7 +2129,7 @@ function App() {
|
||||
for (const action of SHORTCUT_ACTION_ORDER) {
|
||||
const binding = resolveShortcutBinding(shortcutOptions, action, activeShortcutPlatform);
|
||||
if (!binding?.enabled || !binding.combo) continue;
|
||||
const conflicts = findReservedConflicts(normalizeShortcutCombo(binding.combo));
|
||||
const conflicts = findReservedConflicts(normalizeShortcutCombo(binding.combo), activeShortcutPlatform);
|
||||
if (conflicts.length > 0) {
|
||||
map[action] = conflicts;
|
||||
}
|
||||
@@ -3001,7 +3001,7 @@ function App() {
|
||||
return;
|
||||
}
|
||||
|
||||
const reservedConflicts = findReservedConflicts(normalizedCombo);
|
||||
const reservedConflicts = findReservedConflicts(normalizedCombo, activeShortcutPlatform);
|
||||
if (reservedConflicts.length > 0) {
|
||||
const { hasMonaco, hasOther, monacoLabels, otherLabels, otherContexts } = splitConflictsByContext(reservedConflicts);
|
||||
if (hasMonaco) {
|
||||
|
||||
@@ -66,6 +66,7 @@ const mocks = vi.hoisted(() => ({
|
||||
blur: 0,
|
||||
uiVersion: 'legacy',
|
||||
} as any,
|
||||
shortcutOptions: null as any,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -148,7 +149,7 @@ vi.mock('../store', () => ({
|
||||
setSidebarTablePinned: mocks.noop,
|
||||
addSqlLog: mocks.noop,
|
||||
sqlLogs: [],
|
||||
shortcutOptions: cloneShortcutOptions(DEFAULT_SHORTCUT_OPTIONS),
|
||||
shortcutOptions: mocks.state.shortcutOptions ?? cloneShortcutOptions(DEFAULT_SHORTCUT_OPTIONS),
|
||||
setAIPanelVisible: mocks.noop,
|
||||
addAIContext: mocks.noop,
|
||||
}),
|
||||
@@ -183,6 +184,14 @@ vi.mock('../../wailsjs/runtime/runtime', () => ({
|
||||
EventsOn: mocks.noop,
|
||||
}));
|
||||
|
||||
vi.mock('../utils/appearance', async () => {
|
||||
const actual = await vi.importActual<typeof import('../utils/appearance')>('../utils/appearance');
|
||||
return {
|
||||
...actual,
|
||||
isMacLikePlatform: () => true,
|
||||
};
|
||||
});
|
||||
|
||||
describe('Sidebar locate toolbar', () => {
|
||||
beforeEach(() => {
|
||||
mocks.state.connections = [];
|
||||
@@ -203,6 +212,7 @@ describe('Sidebar locate toolbar', () => {
|
||||
blur: 0,
|
||||
uiVersion: 'legacy',
|
||||
};
|
||||
mocks.state.shortcutOptions = cloneShortcutOptions(DEFAULT_SHORTCUT_OPTIONS);
|
||||
});
|
||||
|
||||
it('resolves the table name used by the sidebar copy action', () => {
|
||||
@@ -388,8 +398,11 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(markup).toContain('gn-v2-explorer-command-trigger');
|
||||
expect(markup).toContain('搜索表、连接、动作... 或问 AI');
|
||||
expect(markup).toContain('gn-v2-search-shortcut');
|
||||
expect(markup).toContain('<kbd>Ctrl</kbd>');
|
||||
expect(markup).toContain('<kbd>⌘</kbd>');
|
||||
expect(markup).toContain('<kbd>K</kbd>');
|
||||
expect(source).toContain("const focusSidebarSearchShortcut = resolveShortcutDisplay(shortcutOptions, 'focusSidebarSearch', activeShortcutPlatform);");
|
||||
expect(source).not.toContain('<kbd>⌘</kbd>');
|
||||
expect(source).not.toContain('<kbd>K</kbd>');
|
||||
expect(markup).toContain('gn-v2-explorer-filter-tabs');
|
||||
expect(markup).toContain('全部');
|
||||
expect(markup).toContain('视图');
|
||||
@@ -439,6 +452,18 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(contextMenuFunction).not.toContain('setActiveContext');
|
||||
});
|
||||
|
||||
it('renders the v2 search shortcut from the user shortcut settings', () => {
|
||||
mocks.state.shortcutOptions = cloneShortcutOptions(DEFAULT_SHORTCUT_OPTIONS);
|
||||
mocks.state.shortcutOptions.focusSidebarSearch.mac = { combo: 'Meta+F', enabled: true };
|
||||
|
||||
const markup = renderToStaticMarkup(<Sidebar uiVersion="v2" />);
|
||||
|
||||
expect(markup).toContain('gn-v2-search-shortcut');
|
||||
expect(markup).toContain('<kbd>⌘</kbd>');
|
||||
expect(markup).toContain('<kbd>F</kbd>');
|
||||
expect(markup).not.toContain('<kbd>K</kbd>');
|
||||
});
|
||||
|
||||
it('keeps the v2 command search footer hints tied to real prefix actions', () => {
|
||||
const source = readFileSync(new URL('./Sidebar.tsx', import.meta.url), 'utf8');
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ import { resolveConnectionAccentColor, resolveConnectionIconType } from '../util
|
||||
import { buildJVMTabTitle } from '../utils/jvmRuntimePresentation';
|
||||
import { buildJVMDiagnosticActionDescriptor, buildJVMMonitoringActionDescriptors } from '../utils/jvmSidebarActions';
|
||||
import { buildTableSelectQuery } from '../utils/objectQueryTemplates';
|
||||
import { getShortcutPlatform, getShortcutPrimaryModifierDisplayLabel, resolveShortcutDisplay } from '../utils/shortcuts';
|
||||
import { getShortcutPlatform, resolveShortcutDisplay } from '../utils/shortcuts';
|
||||
import { buildExternalSQLDirectoryId, buildExternalSQLRootNode, buildExternalSQLTabId, type ExternalSQLTreeNode } from '../utils/externalSqlTree';
|
||||
import JVMModeBadge from './jvm/JVMModeBadge';
|
||||
import {
|
||||
@@ -914,7 +914,10 @@ const Sidebar: React.FC<{
|
||||
const disableLocalBackdropFilter = isMacLikePlatform();
|
||||
const autoFetchVisible = useAutoFetchVisibility();
|
||||
const activeShortcutPlatform = getShortcutPlatform(isMacLikePlatform());
|
||||
const primaryShortcutModifierLabel = getShortcutPrimaryModifierDisplayLabel(activeShortcutPlatform);
|
||||
const focusSidebarSearchShortcut = resolveShortcutDisplay(shortcutOptions, 'focusSidebarSearch', activeShortcutPlatform);
|
||||
const focusSidebarSearchShortcutTokens = focusSidebarSearchShortcut === '-'
|
||||
? []
|
||||
: focusSidebarSearchShortcut.match(/Ctrl|Alt|Shift|Esc|Space|[⌘⌃⌥⇧↵↑↓←→]|[^+]/g) ?? [];
|
||||
const [treeData, setTreeData] = useState<TreeNode[]>([]);
|
||||
const activeTab = useMemo(() => tabs.find(tab => tab.id === activeTabId) || null, [tabs, activeTabId]);
|
||||
const activeTabLocateRequest = useMemo(() => normalizeSidebarLocateObjectRequestFromTab(activeTab), [activeTab]);
|
||||
@@ -8003,10 +8006,13 @@ const Sidebar: React.FC<{
|
||||
>
|
||||
<SearchOutlined />
|
||||
<span>搜索表、连接、动作... 或问 AI</span>
|
||||
<span className="gn-v2-search-shortcut" aria-hidden="true">
|
||||
<kbd>{primaryShortcutModifierLabel}</kbd>
|
||||
<kbd>K</kbd>
|
||||
</span>
|
||||
{focusSidebarSearchShortcutTokens.length > 0 ? (
|
||||
<span className="gn-v2-search-shortcut" aria-hidden="true">
|
||||
{focusSidebarSearchShortcutTokens.map((token, index) => (
|
||||
<kbd key={`${token}-${index}`}>{token}</kbd>
|
||||
))}
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
) : (
|
||||
<Input
|
||||
|
||||
@@ -82,6 +82,18 @@ describe('findReservedConflicts', () => {
|
||||
const results = findReservedConflicts('Ctrl+F');
|
||||
expect(results[0].monacoCommandId).toBe('actions.find');
|
||||
});
|
||||
|
||||
it('uses Command instead of Control for macOS find shortcut conflicts', () => {
|
||||
expect(findReservedConflicts('Ctrl+F', 'mac')).toEqual([]);
|
||||
expect(findReservedConflicts('Meta+F', 'mac')[0]).toMatchObject({
|
||||
label: '编辑器查找',
|
||||
monacoCommandId: 'actions.find',
|
||||
});
|
||||
expect(findReservedConflicts('Ctrl+F', 'windows')[0]).toMatchObject({
|
||||
label: '编辑器查找',
|
||||
monacoCommandId: 'actions.find',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── describeConflictContext ─────────────────────────────────────────
|
||||
@@ -160,6 +172,12 @@ describe('shortcut defaults', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps configurable shortcut descriptions free of hardcoded shortcut labels', () => {
|
||||
Object.values(SHORTCUT_ACTION_META).forEach((meta) => {
|
||||
expect(meta.description).not.toMatch(/⌘|⌃|Ctrl|Meta|Cmd|Command|Alt\+/);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses Navicat-inspired defaults separately for macOS and Windows/Linux', () => {
|
||||
expect(DEFAULT_SHORTCUT_OPTIONS.runQuery).toEqual({
|
||||
mac: { combo: 'Meta+R', enabled: true },
|
||||
|
||||
@@ -159,7 +159,7 @@ export const SHORTCUT_ACTION_META: Record<ShortcutAction, ShortcutActionMeta> =
|
||||
},
|
||||
toggleMacFullscreen: {
|
||||
label: '切换原生全屏',
|
||||
description: 'macOS 原生窗口控制模式下的全屏切换(⌃⌘F)',
|
||||
description: 'macOS 原生窗口控制模式下的全屏切换',
|
||||
platformOnly: 'mac',
|
||||
},
|
||||
resetWindowZoom: {
|
||||
@@ -504,6 +504,7 @@ export interface ReservedShortcut {
|
||||
label: string;
|
||||
context: ConflictContext;
|
||||
monacoCommandId?: string;
|
||||
platforms?: ShortcutPlatform[];
|
||||
}
|
||||
|
||||
export interface ConflictInfo {
|
||||
@@ -522,29 +523,29 @@ export const RESERVED_SHORTCUTS: ReservedShortcut[] = [
|
||||
{ combo: 'Ctrl+Shift+N', label: '浏览器新建隐身窗口', context: 'global' },
|
||||
|
||||
// Monaco editor built-in shortcuts
|
||||
{ combo: 'Ctrl+F', label: '编辑器查找', context: 'monaco', monacoCommandId: 'actions.find' },
|
||||
{ combo: 'Meta+F', label: '编辑器查找', context: 'monaco', monacoCommandId: 'actions.find' },
|
||||
{ combo: 'Ctrl+H', label: '编辑器替换', context: 'monaco', monacoCommandId: 'editor.action.startFindReplaceAction' },
|
||||
{ combo: 'Meta+H', label: '编辑器替换', context: 'monaco', monacoCommandId: 'editor.action.startFindReplaceAction' },
|
||||
{ combo: 'Ctrl+G', label: '编辑器跳转行', context: 'monaco', monacoCommandId: 'editor.action.gotoLine' },
|
||||
{ combo: 'Meta+G', label: '编辑器跳转行', context: 'monaco', monacoCommandId: 'editor.action.gotoLine' },
|
||||
{ combo: 'Ctrl+P', label: '编辑器快速打开', context: 'monaco', monacoCommandId: 'actions.quickOpen' },
|
||||
{ combo: 'Meta+P', label: '编辑器快速打开', context: 'monaco', monacoCommandId: 'actions.quickOpen' },
|
||||
{ combo: 'Ctrl+Shift+F', label: '编辑器全局查找', context: 'monaco', monacoCommandId: 'actions.quickOpenNavigate' },
|
||||
{ combo: 'Meta+Shift+F', label: '编辑器全局查找', context: 'monaco', monacoCommandId: 'actions.quickOpenNavigate' },
|
||||
{ combo: 'Ctrl+D', label: '编辑器添加选区', context: 'monaco', monacoCommandId: 'editor.action.addSelectionToNextFindMatch' },
|
||||
{ combo: 'Meta+D', label: '编辑器添加选区', context: 'monaco', monacoCommandId: 'editor.action.addSelectionToNextFindMatch' },
|
||||
{ combo: 'Ctrl+Shift+K', label: '编辑器删除行', context: 'monaco', monacoCommandId: 'editor.action.deleteLines' },
|
||||
{ combo: 'Meta+Shift+K', label: '编辑器删除行', context: 'monaco', monacoCommandId: 'editor.action.deleteLines' },
|
||||
{ combo: 'Ctrl+Enter', label: '编辑器在下方插入行', context: 'monaco', monacoCommandId: 'editor.action.insertLineAfter' },
|
||||
{ combo: 'Meta+Enter', label: '编辑器在下方插入行', context: 'monaco', monacoCommandId: 'editor.action.insertLineAfter' },
|
||||
{ combo: 'Ctrl+Shift+Enter', label: '编辑器在上方插入行', context: 'monaco', monacoCommandId: 'editor.action.insertLineBefore' },
|
||||
{ combo: 'Meta+Shift+Enter', label: '编辑器在上方插入行', context: 'monaco', monacoCommandId: 'editor.action.insertLineBefore' },
|
||||
{ combo: 'Ctrl+F', label: '编辑器查找', context: 'monaco', monacoCommandId: 'actions.find', platforms: ['windows'] },
|
||||
{ combo: 'Meta+F', label: '编辑器查找', context: 'monaco', monacoCommandId: 'actions.find', platforms: ['mac'] },
|
||||
{ combo: 'Ctrl+H', label: '编辑器替换', context: 'monaco', monacoCommandId: 'editor.action.startFindReplaceAction', platforms: ['windows'] },
|
||||
{ combo: 'Meta+H', label: '编辑器替换', context: 'monaco', monacoCommandId: 'editor.action.startFindReplaceAction', platforms: ['mac'] },
|
||||
{ combo: 'Ctrl+G', label: '编辑器跳转行', context: 'monaco', monacoCommandId: 'editor.action.gotoLine', platforms: ['windows'] },
|
||||
{ combo: 'Meta+G', label: '编辑器跳转行', context: 'monaco', monacoCommandId: 'editor.action.gotoLine', platforms: ['mac'] },
|
||||
{ combo: 'Ctrl+P', label: '编辑器快速打开', context: 'monaco', monacoCommandId: 'actions.quickOpen', platforms: ['windows'] },
|
||||
{ combo: 'Meta+P', label: '编辑器快速打开', context: 'monaco', monacoCommandId: 'actions.quickOpen', platforms: ['mac'] },
|
||||
{ combo: 'Ctrl+Shift+F', label: '编辑器全局查找', context: 'monaco', monacoCommandId: 'actions.quickOpenNavigate', platforms: ['windows'] },
|
||||
{ combo: 'Meta+Shift+F', label: '编辑器全局查找', context: 'monaco', monacoCommandId: 'actions.quickOpenNavigate', platforms: ['mac'] },
|
||||
{ combo: 'Ctrl+D', label: '编辑器添加选区', context: 'monaco', monacoCommandId: 'editor.action.addSelectionToNextFindMatch', platforms: ['windows'] },
|
||||
{ combo: 'Meta+D', label: '编辑器添加选区', context: 'monaco', monacoCommandId: 'editor.action.addSelectionToNextFindMatch', platforms: ['mac'] },
|
||||
{ combo: 'Ctrl+Shift+K', label: '编辑器删除行', context: 'monaco', monacoCommandId: 'editor.action.deleteLines', platforms: ['windows'] },
|
||||
{ combo: 'Meta+Shift+K', label: '编辑器删除行', context: 'monaco', monacoCommandId: 'editor.action.deleteLines', platforms: ['mac'] },
|
||||
{ combo: 'Ctrl+Enter', label: '编辑器在下方插入行', context: 'monaco', monacoCommandId: 'editor.action.insertLineAfter', platforms: ['windows'] },
|
||||
{ combo: 'Meta+Enter', label: '编辑器在下方插入行', context: 'monaco', monacoCommandId: 'editor.action.insertLineAfter', platforms: ['mac'] },
|
||||
{ combo: 'Ctrl+Shift+Enter', label: '编辑器在上方插入行', context: 'monaco', monacoCommandId: 'editor.action.insertLineBefore', platforms: ['windows'] },
|
||||
{ combo: 'Meta+Shift+Enter', label: '编辑器在上方插入行', context: 'monaco', monacoCommandId: 'editor.action.insertLineBefore', platforms: ['mac'] },
|
||||
{ combo: 'F2', label: '编辑器重命名符号', context: 'monaco', monacoCommandId: 'editor.action.rename' },
|
||||
|
||||
// DataGrid shortcuts
|
||||
{ combo: 'Ctrl+C', label: '数据表格复制', context: 'datagrid' },
|
||||
{ combo: 'Meta+C', label: '数据表格复制', context: 'datagrid' },
|
||||
{ combo: 'Ctrl+C', label: '数据表格复制', context: 'datagrid', platforms: ['windows'] },
|
||||
{ combo: 'Meta+C', label: '数据表格复制', context: 'datagrid', platforms: ['mac'] },
|
||||
];
|
||||
|
||||
const CONTEXT_DESCRIPTION: Record<ConflictContext, string> = {
|
||||
@@ -572,14 +573,14 @@ export const splitConflictsByContext = (conflicts: ConflictInfo[]) => {
|
||||
};
|
||||
|
||||
export const findReservedConflict = (normalizedCombo: string): ConflictInfo | null => {
|
||||
const conflict = RESERVED_SHORTCUTS.find((r) => r.combo === normalizedCombo);
|
||||
const conflict = findReservedConflicts(normalizedCombo)[0];
|
||||
if (!conflict) return null;
|
||||
return { label: conflict.label, context: conflict.context, monacoCommandId: conflict.monacoCommandId };
|
||||
return conflict;
|
||||
};
|
||||
|
||||
export const findReservedConflicts = (normalizedCombo: string): ConflictInfo[] => {
|
||||
export const findReservedConflicts = (normalizedCombo: string, platform?: ShortcutPlatform): ConflictInfo[] => {
|
||||
return RESERVED_SHORTCUTS
|
||||
.filter((r) => r.combo === normalizedCombo)
|
||||
.filter((r) => r.combo === normalizedCombo && (!platform || !r.platforms || r.platforms.includes(platform)))
|
||||
.map((r) => ({ label: r.label, context: r.context, monacoCommandId: r.monacoCommandId }));
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user