feat: add configurable find/replace shortcuts with Mod-R routing

Add find and replace shortcut definitions to the shortcut registry with
Mod+F and Mod+R defaults. Route Mod-R through App → ContentArea to
dispatch to either the query editor replace panel or data grid refresh
based on focus context. Add isModRShortcut helper and tests.
This commit is contained in:
t8y2
2026-05-27 12:20:20 +08:00
parent 20fcea82b4
commit 12abe9d0e0
6 changed files with 75 additions and 11 deletions
+10 -2
View File
@@ -40,6 +40,7 @@ import {
isCloseTabShortcut,
isExecuteSqlShortcut,
isFocusSearchShortcut,
isModRShortcut,
isNewQueryShortcut,
isObjectSourceSaveShortcutTarget,
isRefreshDataShortcut,
@@ -608,6 +609,8 @@ function onAiRequestAutoExecuteSql(sql: string) {
}
function handleKeydown(e: KeyboardEvent) {
if (e.defaultPrevented) return;
const shortcuts = settingsStore.editorSettings.shortcuts;
if (isFocusSearchShortcut(e, shortcuts)) {
@@ -659,6 +662,11 @@ function handleKeydown(e: KeyboardEvent) {
tryExecute();
return;
}
if (isModRShortcut(e) && e.target instanceof Element && contentAreaRef.value?.handleModRTarget(e.target)) {
e.preventDefault();
e.stopPropagation();
return;
}
if (isDesktop && isBrowserReloadShortcut(e)) {
e.preventDefault();
e.stopPropagation();
@@ -725,7 +733,7 @@ onMounted(async () => {
aiPanelReady.value = true;
});
applyTheme();
window.addEventListener("keydown", handleKeydown, true);
window.addEventListener("keydown", handleKeydown);
window.addEventListener("dbx-open-driver-store", openDriverStoreFromEvent);
if (isDesktop) {
document.addEventListener("contextmenu", handleContextMenu);
@@ -780,7 +788,7 @@ onUnmounted(() => {
if (updateCheckTimer) {
clearInterval(updateCheckTimer);
}
window.removeEventListener("keydown", handleKeydown, true);
window.removeEventListener("keydown", handleKeydown);
window.removeEventListener("dbx-open-driver-store", openDriverStoreFromEvent);
document.removeEventListener("contextmenu", handleContextMenu);
});
+22 -1
View File
@@ -127,6 +127,7 @@ import {
isCopyCurrentRowShortcut,
isDeleteCurrentRowShortcut,
isFocusSearchShortcut,
isModRShortcut,
isToggleTransposeShortcut,
} from "@/lib/keyboardShortcuts";
import { dataGridHeaderContentWidth, scrollbarGutterWidth } from "@/lib/dataGridScrollGutter";
@@ -2874,12 +2875,24 @@ function commitGridEdit() {
nextTick(() => gridRef.value?.focus({ preventScroll: true }));
}
function openCellDetailSearch(): boolean {
return getDetailEditor()?.openSearch() ?? false;
}
async function onGridKeydown(event: KeyboardEvent) {
if (event.defaultPrevented) return;
if (isFocusSearchShortcut(event)) {
event.preventDefault();
focusSearch();
return;
}
if (isModRShortcut(event)) {
event.preventDefault();
event.stopPropagation();
await onToolbarRefresh();
return;
}
if (eventTargetAllowsNativeClipboard(event)) return;
if (isCopyCurrentRowShortcut(event, settingsStore.editorSettings.shortcuts) && copyCurrentRow()) {
event.preventDefault();
@@ -3564,12 +3577,14 @@ defineExpose({
toggleColumnVisibility,
showAllColumns,
invertColumnVisibility,
openCellDetailSearch,
});
</script>
<template>
<div
ref="gridRef"
data-grid-root
class="h-full flex flex-col overflow-hidden outline-none"
:style="gridStyle"
tabindex="0"
@@ -5178,7 +5193,12 @@ defineExpose({
@cancel="cancelDetailEdit"
@commit="commitDetailEdit"
/>
<div v-else ref="detailsEditorContainer" class="w-full h-40 rounded border overflow-hidden" />
<div
v-else
ref="detailsEditorContainer"
data-cell-detail-editor-root
class="w-full h-40 rounded border overflow-hidden"
/>
<div class="flex gap-1 mt-1">
<Button size="sm" class="h-6 text-xs" @click="commitDetailEdit">
{{ t("dangerDialog.confirm") }}
@@ -5278,6 +5298,7 @@ defineExpose({
<div
v-else
ref="valueEditorContainer"
data-cell-detail-editor-root
class="min-h-0 flex-1 w-full rounded border overflow-hidden"
/>
</div>
@@ -39,6 +39,7 @@ import type { SqlFormatDialect } from "@/lib/sqlFormatter";
type DataGridHandle = {
onToolbarRefresh: () => Promise<void> | void;
focusSearch: () => boolean;
openCellDetailSearch: () => boolean;
visibleColumnCount: number;
displayableColumnCount: number;
hiddenColumnCount: number;
@@ -256,7 +257,14 @@ function refreshData(): boolean {
return true;
}
defineExpose({ focusSearch, refreshData });
function handleModRTarget(target: Element): boolean {
if (target.closest("[data-query-editor-root]")) return queryEditorRef.value?.openReplace() ?? false;
if (target.closest("[data-cell-detail-editor-root]")) return dataGridRef.value?.openCellDetailSearch() ?? false;
if (target.closest("[data-grid-root]")) return refreshData();
return false;
}
defineExpose({ focusSearch, refreshData, handleModRTarget });
</script>
<template>
@@ -384,6 +392,7 @@ defineExpose({ focusSearch, refreshData });
<template v-else>
<DataGrid
v-if="activeTab.result"
ref="dataGridRef"
:key="`${activeTab.id}-${activeTab.activeResultIndex ?? 0}`"
:cache-key="`${activeTab.id}-${activeTab.activeResultIndex ?? 0}`"
class="flex-1 min-h-0"
@@ -88,6 +88,10 @@ export function isRefreshDataShortcut(event: ShortcutLikeEvent, shortcuts?: Part
return matchesShortcut(event, actionShortcut("refreshData", shortcuts));
}
export function isModRShortcut(event: ShortcutLikeEvent): boolean {
return matchesShortcut(event, "Mod+R");
}
export function isToggleTransposeShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
return matchesShortcut(event, actionShortcut("toggleTranspose", shortcuts));
}
+14
View File
@@ -7,6 +7,8 @@ export type ShortcutActionId =
| "newQuery"
| "closeTab"
| "focusSearch"
| "find"
| "replace"
| "refreshData"
| "toggleTranspose"
| "cancelSearch";
@@ -71,6 +73,18 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
scope: "global",
defaultShortcut: "Mod+F",
},
{
id: "find",
labelKey: "settings.shortcutFind",
scope: "editor",
defaultShortcut: "Mod+F",
},
{
id: "replace",
labelKey: "settings.shortcutReplace",
scope: "editor",
defaultShortcut: "Mod+R",
},
{
id: "refreshData",
labelKey: "settings.shortcutRefreshData",