mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-11 01:03:51 +08:00
🐛 fix(sidebar): 修复表名快捷复制
- 为表节点菜单绑定 macOS Command+C 与 Windows/Linux Ctrl+C - 使用捕获阶段处理快捷键并复制当前菜单表名 - 增加平台映射、非法组合和最新节点回归测试 Refs #736
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
handleSidebarV2ContextMenuShortcut,
|
||||
type SidebarContextMenuState,
|
||||
} from './useSidebarV2ContextMenu';
|
||||
|
||||
const createKeyEvent = (overrides: Partial<KeyboardEvent> = {}): KeyboardEvent => ({
|
||||
key: 'c',
|
||||
code: 'KeyC',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
isComposing: false,
|
||||
keyCode: 67,
|
||||
which: 67,
|
||||
preventDefault: vi.fn(),
|
||||
stopPropagation: vi.fn(),
|
||||
...overrides,
|
||||
} as unknown as KeyboardEvent);
|
||||
|
||||
const createTableMenu = (node: Record<string, unknown>): SidebarContextMenuState => ({
|
||||
x: 10,
|
||||
y: 20,
|
||||
items: [],
|
||||
kind: 'v2-table',
|
||||
node,
|
||||
});
|
||||
|
||||
describe('V2 sidebar context-menu shortcuts', () => {
|
||||
it.each([
|
||||
['mac', { metaKey: true }],
|
||||
['windows', { ctrlKey: true }],
|
||||
] as const)('routes the primary copy shortcut on %s to the current table', (platform, modifiers) => {
|
||||
const node = { key: `table-${platform}`, title: 'ir_staff' };
|
||||
const event = createKeyEvent(modifiers);
|
||||
const onTableAction = vi.fn();
|
||||
const onClose = vi.fn();
|
||||
|
||||
expect(handleSidebarV2ContextMenuShortcut({
|
||||
event,
|
||||
contextMenu: createTableMenu(node),
|
||||
shortcutPlatform: platform,
|
||||
onTableAction,
|
||||
onClose,
|
||||
})).toBe(true);
|
||||
|
||||
expect(event.preventDefault).toHaveBeenCalledTimes(1);
|
||||
expect(event.stopPropagation).toHaveBeenCalledTimes(1);
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
expect(onTableAction).toHaveBeenCalledWith(node, 'copy-table-name');
|
||||
});
|
||||
|
||||
it.each([
|
||||
createKeyEvent(),
|
||||
createKeyEvent({ altKey: true, metaKey: true }),
|
||||
createKeyEvent({ ctrlKey: true, shiftKey: true }),
|
||||
])('ignores copy-like keys that do not match the platform shortcut', (event) => {
|
||||
const onTableAction = vi.fn();
|
||||
const onClose = vi.fn();
|
||||
|
||||
expect(handleSidebarV2ContextMenuShortcut({
|
||||
event,
|
||||
contextMenu: createTableMenu({ key: 'table-a' }),
|
||||
shortcutPlatform: 'mac',
|
||||
onTableAction,
|
||||
onClose,
|
||||
})).toBe(false);
|
||||
|
||||
expect(onTableAction).not.toHaveBeenCalled();
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('uses the latest node when a table menu is replaced without changing kind', () => {
|
||||
const onTableAction = vi.fn();
|
||||
const firstNode = { key: 'table-a' };
|
||||
const latestNode = { key: 'table-b' };
|
||||
|
||||
handleSidebarV2ContextMenuShortcut({
|
||||
event: createKeyEvent({ metaKey: true }),
|
||||
contextMenu: createTableMenu(latestNode),
|
||||
shortcutPlatform: 'mac',
|
||||
onTableAction,
|
||||
onClose: vi.fn(),
|
||||
});
|
||||
|
||||
expect(onTableAction).toHaveBeenCalledWith(latestNode, 'copy-table-name');
|
||||
expect(onTableAction).not.toHaveBeenCalledWith(firstNode, 'copy-table-name');
|
||||
});
|
||||
});
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
SIDEBAR_CONTEXT_MENU_FALLBACK_WIDTH,
|
||||
resolveSidebarContextMenuPosition,
|
||||
} from '../sidebarCoreUtils';
|
||||
import { isShortcutMatch, type ShortcutPlatform } from '../../utils/shortcuts';
|
||||
|
||||
export type SidebarContextMenuState = {
|
||||
x: number;
|
||||
@@ -46,7 +47,7 @@ export type SidebarContextMenuState = {
|
||||
type SidebarV2ContextMenuOptions = {
|
||||
connections: SavedConnection[];
|
||||
connectionTags: Array<{ id: string; name: string; connectionIds: string[] }>;
|
||||
activeShortcutPlatform: any;
|
||||
activeShortcutPlatform: ShortcutPlatform;
|
||||
flattenConnectionNodes: (nodes: TreeNode[]) => TreeNode[];
|
||||
v2TreeMetrics: {
|
||||
databaseTableCounts: Map<React.Key, number>;
|
||||
@@ -73,6 +74,39 @@ type SidebarV2ContextMenuOptions = {
|
||||
handleV2ConnectionGroupContextMenuAction: (group: V2RailConnectionGroup, action: V2ConnectionGroupContextMenuActionKey) => void;
|
||||
};
|
||||
|
||||
export const handleSidebarV2ContextMenuShortcut = ({
|
||||
event,
|
||||
contextMenu,
|
||||
shortcutPlatform,
|
||||
onTableAction,
|
||||
onClose,
|
||||
}: {
|
||||
event: KeyboardEvent;
|
||||
contextMenu: SidebarContextMenuState | null;
|
||||
shortcutPlatform: ShortcutPlatform;
|
||||
onTableAction: (node: any, action: V2TableContextMenuActionKey) => void;
|
||||
onClose: () => void;
|
||||
}): boolean => {
|
||||
if (event.key === 'Escape') {
|
||||
onClose();
|
||||
return true;
|
||||
}
|
||||
if (event.defaultPrevented || contextMenu?.kind !== 'v2-table' || !contextMenu.node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const copyShortcut = shortcutPlatform === 'mac' ? 'Meta+C' : 'Ctrl+C';
|
||||
if (!isShortcutMatch(event, copyShortcut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onClose();
|
||||
onTableAction(contextMenu.node, 'copy-table-name');
|
||||
return true;
|
||||
};
|
||||
|
||||
export const useSidebarV2ContextMenu = ({
|
||||
connections,
|
||||
connectionTags,
|
||||
@@ -408,15 +442,21 @@ export const useSidebarV2ContextMenu = ({
|
||||
setContextMenu(null);
|
||||
};
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') setContextMenu(null);
|
||||
handleSidebarV2ContextMenuShortcut({
|
||||
event,
|
||||
contextMenu,
|
||||
shortcutPlatform: activeShortcutPlatform,
|
||||
onTableAction: handleV2TableContextMenuAction,
|
||||
onClose: () => setContextMenu(null),
|
||||
});
|
||||
};
|
||||
document.addEventListener('mousedown', onPointerDown);
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
document.addEventListener('keydown', onKeyDown, true);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', onPointerDown);
|
||||
document.removeEventListener('keydown', onKeyDown);
|
||||
document.removeEventListener('keydown', onKeyDown, true);
|
||||
};
|
||||
}, [contextMenu?.kind]);
|
||||
}, [activeShortcutPlatform, contextMenu, handleV2TableContextMenuAction]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!contextMenu?.kind) return;
|
||||
|
||||
Reference in New Issue
Block a user