mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-05 22:03:36 +08:00
✨ feat(query-editor): 新增编辑器内搜索入口
- 在查询编辑器工具栏新增搜索入口并直连 Monaco 查找\n- 接管 Ctrl/Cmd+F,优先打开编辑器内搜索而不是浏览器默认查找\n- 补充多语言文案与静态测试覆盖\n\nFixes #539
This commit is contained in:
@@ -731,6 +731,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
const runQueryActionRef = useRef<any>(null);
|
||||
const selectCurrentStatementActionRef = useRef<any>(null);
|
||||
const saveQueryActionRef = useRef<any>(null);
|
||||
const findInEditorActionRef = useRef<any>(null);
|
||||
const formatSqlActionRef = useRef<any>(null);
|
||||
const aiContextMenuActionDisposablesRef = useRef<any[]>([]);
|
||||
const toggleQueryResultsPanelActionRef = useRef<any>(null);
|
||||
@@ -880,6 +881,10 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
() => resolveShortcutBinding(shortcutOptions, 'toggleQueryResultsPanel', activeShortcutPlatform),
|
||||
[activeShortcutPlatform, shortcutOptions],
|
||||
);
|
||||
const findInEditorShortcutCombo = useMemo(
|
||||
() => activeShortcutPlatform === 'mac' ? 'Meta+F' : 'Ctrl+F',
|
||||
[activeShortcutPlatform],
|
||||
);
|
||||
const primaryShortcutModifierLabel = useMemo(
|
||||
() => getShortcutPrimaryModifierDisplayLabel(activeShortcutPlatform),
|
||||
[activeShortcutPlatform],
|
||||
@@ -898,6 +903,25 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
return nextVisible;
|
||||
});
|
||||
}, [tab.id, updateQueryTabDraft]);
|
||||
const handleOpenEditorFind = useCallback(() => {
|
||||
const editor = editorRef.current;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.focus?.();
|
||||
try {
|
||||
const findAction = editor.getAction?.('actions.find');
|
||||
if (findAction?.run) {
|
||||
void findAction.run();
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Fall back to Monaco's built-in command id if the action lookup fails.
|
||||
}
|
||||
|
||||
editor.trigger?.('keyboard', 'actions.find', null);
|
||||
}, []);
|
||||
const handleShowSqlExecutionLog = useCallback((mode: 'open' | 'toggle' = 'toggle') => {
|
||||
if (!isActive) {
|
||||
return;
|
||||
@@ -2732,6 +2756,20 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
}
|
||||
}
|
||||
|
||||
const findInEditorKeyBinding = comboToMonacoKeyBinding(
|
||||
findInEditorShortcutCombo, monaco.KeyMod, monaco.KeyCode
|
||||
);
|
||||
if (findInEditorKeyBinding) {
|
||||
findInEditorActionRef.current = editor.addAction({
|
||||
id: 'gonavi.findInEditor',
|
||||
label: buildQueryEditorMonacoActionLabel('query_editor.action.find_in_editor'),
|
||||
keybindings: [findInEditorKeyBinding.keyMod | findInEditorKeyBinding.keyCode],
|
||||
run: () => {
|
||||
window.dispatchEvent(new CustomEvent('gonavi:find-active-query'));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const formatBinding = formatSqlShortcutBinding;
|
||||
if (formatBinding?.enabled && formatBinding.combo) {
|
||||
const keyBinding = comboToMonacoKeyBinding(
|
||||
@@ -4998,6 +5036,40 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
};
|
||||
}, [languagePreference, saveQueryShortcutBinding]);
|
||||
|
||||
useEffect(() => {
|
||||
if (findInEditorActionRef.current) {
|
||||
findInEditorActionRef.current.dispose();
|
||||
findInEditorActionRef.current = null;
|
||||
}
|
||||
|
||||
const editor = editorRef.current;
|
||||
const monaco = monacoRef.current;
|
||||
if (!editor || !monaco) return;
|
||||
|
||||
const keyBinding = comboToMonacoKeyBinding(
|
||||
findInEditorShortcutCombo,
|
||||
monaco.KeyMod,
|
||||
monaco.KeyCode,
|
||||
);
|
||||
if (keyBinding) {
|
||||
findInEditorActionRef.current = editor.addAction({
|
||||
id: 'gonavi.findInEditor',
|
||||
label: buildQueryEditorMonacoActionLabel('query_editor.action.find_in_editor'),
|
||||
keybindings: [keyBinding.keyMod | keyBinding.keyCode],
|
||||
run: () => {
|
||||
window.dispatchEvent(new CustomEvent('gonavi:find-active-query'));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (findInEditorActionRef.current) {
|
||||
findInEditorActionRef.current.dispose();
|
||||
findInEditorActionRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [findInEditorShortcutCombo, languagePreference]);
|
||||
|
||||
useEffect(() => {
|
||||
if (formatSqlActionRef.current) {
|
||||
formatSqlActionRef.current.dispose();
|
||||
@@ -5095,6 +5167,20 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
};
|
||||
}, [isActive, handleRun, handleRunSelectedShortcut]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleFindActiveQuery = () => {
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
handleOpenEditorFind();
|
||||
};
|
||||
|
||||
window.addEventListener('gonavi:find-active-query', handleFindActiveQuery as EventListener);
|
||||
return () => {
|
||||
window.removeEventListener('gonavi:find-active-query', handleFindActiveQuery as EventListener);
|
||||
};
|
||||
}, [handleOpenEditorFind, isActive]);
|
||||
|
||||
// 监听由 TabManager 分发的专用注入事件
|
||||
useEffect(() => {
|
||||
const handleInsertSql = (e: any) => {
|
||||
@@ -5339,6 +5425,38 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const handleFindShortcut = (event: KeyboardEvent) => {
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
if (!isShortcutMatch(event, findInEditorShortcutCombo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editor = editorRef.current;
|
||||
const targetNode = resolveEventTargetNode(event.target);
|
||||
const editorHasFocus = !!editor?.hasTextFocus?.();
|
||||
const inEditorPane = !!(targetNode && editorPaneRef.current?.contains(targetNode));
|
||||
const inQueryEditor = !!(targetNode && queryEditorRootRef.current?.contains(targetNode));
|
||||
if (isEditableElement(event.target) && !inEditorPane) {
|
||||
return;
|
||||
}
|
||||
if (!editorHasFocus && !inEditorPane && !inQueryEditor && !isDocumentLevelShortcutTarget(targetNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleOpenEditorFind();
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleFindShortcut, true);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleFindShortcut, true);
|
||||
};
|
||||
}, [findInEditorShortcutCombo, handleOpenEditorFind, isActive]);
|
||||
|
||||
useEffect(() => {
|
||||
const binding = saveQueryShortcutBinding;
|
||||
if (!binding?.enabled || !binding.combo) {
|
||||
@@ -5605,6 +5723,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
onRun={handleRun}
|
||||
onCancel={handleCancel}
|
||||
onQuickSave={handleQuickSave}
|
||||
onFindInEditor={handleOpenEditorFind}
|
||||
onFormat={handleFormat}
|
||||
onToggleResultPanelVisibility={toggleResultPanelVisibility}
|
||||
onAIAction={handleAIAction}
|
||||
|
||||
Reference in New Issue
Block a user