From 0fb985737899f6768636be8c96cd38c7fdaaba60 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Wed, 22 Jul 2026 17:45:51 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(query-editor):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=9F=A5=E6=89=BE=E6=A1=86=E5=85=A8=E9=80=89=E8=A2=AB?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E5=BF=AB=E6=8D=B7=E9=94=AE=E6=8B=A6?= =?UTF-8?q?=E6=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在可编辑目标内放行 Ctrl/Cmd+A 原生全选 - 保留文档级快捷键对 Monaco 编辑器的兜底全选 - 补充查找输入框与源码范围回归测试 --- .../QueryEditor.external-sql-save.test.tsx | 68 +++++++++++++++++++ .../QueryEditor.results-and-drop.test.tsx | 7 +- frontend/src/components/QueryEditor.tsx | 2 +- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/QueryEditor.external-sql-save.test.tsx b/frontend/src/components/QueryEditor.external-sql-save.test.tsx index a1d1b3a1..b7cb96cf 100644 --- a/frontend/src/components/QueryEditor.external-sql-save.test.tsx +++ b/frontend/src/components/QueryEditor.external-sql-save.test.tsx @@ -5036,6 +5036,74 @@ describe('QueryEditor external SQL save', () => { expect(document.execCommand).not.toHaveBeenCalled(); }); + it('leaves Ctrl/Cmd+A inside Monaco find inputs while retaining the editor fallback', async () => { + const windowListeners: Record void)[]> = {}; + vi.stubGlobal('window', { + addEventListener: vi.fn((type: string, listener: (event?: any) => void) => { + windowListeners[type] ||= []; + windowListeners[type].push(listener); + }), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + setTimeout, + clearTimeout, + requestAnimationFrame: vi.fn((callback: FrameRequestCallback) => { + callback(0); + return 1; + }), + cancelAnimationFrame: vi.fn(), + innerHeight: 900, + }); + + await act(async () => { + create(); + }); + + const OriginalHTMLElement = globalThis.HTMLElement; + class EditableInputTarget { + tagName = 'INPUT'; + isContentEditable = false; + closest = vi.fn(() => null); + } + vi.stubGlobal('HTMLElement', EditableInputTarget as any); + + editorState.editor.trigger.mockClear(); + editorState.editor.focus.mockClear(); + const findInputEvent = { + ctrlKey: true, + metaKey: false, + altKey: false, + shiftKey: false, + key: 'a', + target: new EditableInputTarget(), + preventDefault: vi.fn(), + stopPropagation: vi.fn(), + }; + await act(async () => { + windowListeners.keydown?.forEach((listener) => listener(findInputEvent)); + }); + + expect(findInputEvent.preventDefault).not.toHaveBeenCalled(); + expect(findInputEvent.stopPropagation).not.toHaveBeenCalled(); + expect(editorState.editor.trigger).not.toHaveBeenCalledWith('keyboard', 'editor.action.selectAll', null); + expect(editorState.editor.focus).not.toHaveBeenCalled(); + + const documentLevelEvent = { + ...findInputEvent, + target: null, + preventDefault: vi.fn(), + stopPropagation: vi.fn(), + }; + await act(async () => { + windowListeners.keydown?.forEach((listener) => listener(documentLevelEvent)); + }); + + expect(documentLevelEvent.preventDefault).toHaveBeenCalled(); + expect(documentLevelEvent.stopPropagation).toHaveBeenCalled(); + expect(editorState.editor.trigger).toHaveBeenCalledWith('keyboard', 'editor.action.selectAll', null); + vi.stubGlobal('HTMLElement', OriginalHTMLElement); + }); + it('intercepts Ctrl/Cmd+D at window level and duplicates the current line below', async () => { storeState.shortcutOptions.duplicateCurrentLine.mac = { enabled: true, combo: 'Meta+D' }; storeState.shortcutOptions.duplicateCurrentLine.windows = { enabled: true, combo: 'Ctrl+D' }; diff --git a/frontend/src/components/QueryEditor.results-and-drop.test.tsx b/frontend/src/components/QueryEditor.results-and-drop.test.tsx index 5110c209..0bb3220c 100644 --- a/frontend/src/components/QueryEditor.results-and-drop.test.tsx +++ b/frontend/src/components/QueryEditor.results-and-drop.test.tsx @@ -3289,8 +3289,13 @@ describe('QueryEditor external SQL save', () => { it('keeps editor select-all scoped away from non-editor editable targets', () => { const source = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8'); + const selectAllSource = source.slice( + source.indexOf('const handleSelectAllInEditor = (event: KeyboardEvent) => {'), + source.indexOf("window.addEventListener('keydown', handleSelectAllInEditor, true);"), + ); - expect(source).toContain("if (isEditableElement(event.target) && !inEditorPane) {"); + expect(selectAllSource).toContain("if (isEditableElement(event.target)) {"); + expect(selectAllSource).not.toContain("if (isEditableElement(event.target) && !inEditorPane) {"); }); it('keeps the embedded sql execution log limited to v2 query editor result tabs', () => { diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx index 1f681210..91ccb5e5 100644 --- a/frontend/src/components/QueryEditor.tsx +++ b/frontend/src/components/QueryEditor.tsx @@ -7533,7 +7533,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc const editorHasFocus = !!editor.hasTextFocus?.(); const inEditorPane = !!(targetNode && editorPaneRef.current?.contains(targetNode)); const inQueryEditor = !!(targetNode && queryEditorRootRef.current?.contains(targetNode)); - if (isEditableElement(event.target) && !inEditorPane) { + if (isEditableElement(event.target)) { return; } if (!editorHasFocus && !inEditorPane) {