diff --git a/apps/web/src/components/EditorPane.tsx b/apps/web/src/components/EditorPane.tsx index fb3ce646..f438ef6c 100644 --- a/apps/web/src/components/EditorPane.tsx +++ b/apps/web/src/components/EditorPane.tsx @@ -1361,11 +1361,12 @@ const RichEditorPane = ({ class: "prose prose-slate max-w-none focus:outline-none min-h-[300px] px-4 py-3 sm:px-7", }, handleKeyDown: (view, event) => { + const shortcutKey = event.key.toLowerCase(); if ( - event.key.toLowerCase() !== "f" || + (shortcutKey !== "f" && shortcutKey !== "h") || (!event.ctrlKey && !event.metaKey) || event.altKey || - event.shiftKey + (shortcutKey === "f" && event.shiftKey) ) { return false; } @@ -1383,7 +1384,7 @@ const RichEditorPane = ({ event.preventDefault(); setNoteSearchQuery(selectedText); setNoteSearchOpen(true); - setNoteSearchReplaceOpen(false); + setNoteSearchReplaceOpen(shortcutKey === "h"); window.requestAnimationFrame(() => { noteSearchInputRef.current?.focus(); noteSearchInputRef.current?.select(); @@ -1551,6 +1552,27 @@ const RichEditorPane = ({ } }, [editor]); + useEffect(() => { + if (!noteSearchOpen) { + return; + } + + const handleNoteSearchEscape = (event: KeyboardEvent) => { + if (event.key !== "Escape" || event.defaultPrevented) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + closeNoteSearch(); + }; + + // Capture the event so Escape works even when focus has moved to the + // editor, toolbar, or another control outside the search inputs. + window.addEventListener("keydown", handleNoteSearchEscape, true); + return () => window.removeEventListener("keydown", handleNoteSearchEscape, true); + }, [closeNoteSearch, noteSearchOpen]); + const moveNoteSearchMatch = useCallback( (direction: 1 | -1) => { if (noteSearchMatches.length === 0) { diff --git a/apps/web/src/lib/app-helpers.ts b/apps/web/src/lib/app-helpers.ts index 771a584c..50461b50 100644 --- a/apps/web/src/lib/app-helpers.ts +++ b/apps/web/src/lib/app-helpers.ts @@ -210,6 +210,10 @@ export const DEFAULT_SHORTCUT_SETTINGS: ShortcutSettings = { focusReplace: { key: "h", ctrlOrMeta: true, shift: false, alt: false }, }; +const SHORTCUT_ALIASES: Partial> = { + focusReplace: [{ key: "h", ctrlOrMeta: true, shift: true, alt: false }], +}; + const SHORTCUT_ACTION_VALUES: ShortcutAction[] = ["createMemo", "createNotebook", "focusSearch", "focusReplace"]; export const getMemoTitle = (title: string | null | undefined) => title?.trim() || DEFAULT_MEMO_TITLE; @@ -415,7 +419,10 @@ export const getShortcutActionForEvent = (event: KeyboardEvent, settings: Shortc return null; } - return SHORTCUT_ACTION_VALUES.find((action) => shortcutBindingsEqual(settings[action], eventBinding)) ?? null; + return SHORTCUT_ACTION_VALUES.find((action) => + shortcutBindingsEqual(settings[action], eventBinding) || + (SHORTCUT_ALIASES[action] ?? []).some((binding) => shortcutBindingsEqual(binding, eventBinding)) + ) ?? null; }; export const compareDateDesc = (first: string, second: string) => {