From 526bb940e022a0be3d43ba7d1da0ccce445aae14 Mon Sep 17 00:00:00 2001 From: tianma-if Date: Tue, 15 Sep 2026 17:51:33 +0800 Subject: [PATCH] refactor(web): keep note search state in its controller EditorPane no longer owns search open/query/replace state. Selection shortcuts and plugin jumps go through the same hook. --- apps/web/src/components/EditorPane.tsx | 46 ++++++--------- .../editor/useEditorNoteSearchController.ts | 56 ++++++++++++------- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/apps/web/src/components/EditorPane.tsx b/apps/web/src/components/EditorPane.tsx index ec69857d..ad25a8b0 100644 --- a/apps/web/src/components/EditorPane.tsx +++ b/apps/web/src/components/EditorPane.tsx @@ -425,12 +425,9 @@ const RichEditorPane = ({ const [aiInsertionTarget, setAiInsertionTarget] = useState(null); const [mobileNotebookSheetOpen, setMobileNotebookSheetOpen] = useState(false); const [notebookUpdatePending, setNotebookUpdatePending] = useState(false); - const [noteSearchOpen, setNoteSearchOpen] = useState(false); - const [noteSearchQuery, setNoteSearchQuery] = useState(""); - const [noteSearchReplaceOpen, setNoteSearchReplaceOpen] = useState(false); - const [noteSearchReplacement, setNoteSearchReplacement] = useState(""); - const [noteSearchIndex, setNoteSearchIndex] = useState(0); const handledPluginNavigationRequestRef = useRef(0); + const openNoteSearchFromSelectionRef = useRef<(text: string, showReplace?: boolean) => void>(() => undefined); + const closeNoteReplaceRef = useRef<() => void>(() => undefined); const [noteLinkPickerOpen, setNoteLinkPickerOpen] = useState(false); const [noteLinkQuery, setNoteLinkQuery] = useState(""); const [noteLinkHintPosition, setNoteLinkHintPosition] = useState(null); @@ -571,7 +568,7 @@ const RichEditorPane = ({ setAiAssistantOpen(false); setAiSelection(null); setAiInsertionTarget(null); - setNoteSearchReplaceOpen(false); + closeNoteReplaceRef.current(); setExternalLinkDialogOpen(false); setNoteLinkPickerOpen(false); }, [desktopReadingProtection]); @@ -587,7 +584,6 @@ const RichEditorPane = ({ const mobileDraftTimerRef = useRef(null); const mobileSaveTimerRef = useRef(null); const fileInputRef = useRef(null); - const noteSearchInputRef = useRef(null); const markdownSourceEditorRef = useRef(null); const markdownModeSnapshotRef = useRef(null); const openExternalLinkDialogRef = useRef<() => void>(() => undefined); @@ -1124,13 +1120,7 @@ const RichEditorPane = ({ } event.preventDefault(); - setNoteSearchQuery(selectedText); - setNoteSearchOpen(true); - setNoteSearchReplaceOpen(shortcutKey === "h"); - window.requestAnimationFrame(() => { - noteSearchInputRef.current?.focus(); - noteSearchInputRef.current?.select(); - }); + openNoteSearchFromSelectionRef.current(selectedText, shortcutKey === "h"); return true; }, handleTextInput: (view, from, to, text) => { @@ -1656,31 +1646,35 @@ const RichEditorPane = ({ }, [editor]); const { + closeReplace: closeNoteReplace, closeSearch: closeNoteSearch, + inputRef: noteSearchInputRef, matchLabel: noteSearchMatchLabel, matches: noteSearchMatches, moveMatch: moveNoteSearchMatch, + openFromSelection: openNoteSearchFromSelection, openReplace: openNoteReplace, openSearch: openNoteSearch, + openWithQuery: openNoteSearchWithQuery, + query: noteSearchQuery, replaceAllMatches: replaceAllNoteSearchMatches, + replaceOpen: noteSearchReplaceOpen, + replacement: noteSearchReplacement, + searchOpen: noteSearchOpen, + setQuery: setNoteSearchQuery, + setReplacement: setNoteSearchReplacement, } = useEditorNoteSearchController({ contentSearchQuery, dirtyVersion, editor, editorScrollContainerRef, memoId: memo?.id ?? null, - noteSearchIndex, - noteSearchInputRef, - noteSearchOpen, - noteSearchQuery, - noteSearchReplacement, readOnly: effectiveReadOnly, replaceFocusToken, searchFocusToken, - setNoteSearchIndex, - setNoteSearchOpen, - setNoteSearchReplaceOpen, }); + openNoteSearchFromSelectionRef.current = openNoteSearchFromSelection; + closeNoteReplaceRef.current = closeNoteReplace; useEffect(() => { if ( @@ -1692,12 +1686,8 @@ const RichEditorPane = ({ || !isEditorReady(editor) ) return; handledPluginNavigationRequestRef.current = pluginNavigationRequest.id; - setNoteSearchQuery(pluginNavigationRequest.search); - setNoteSearchIndex(0); - setNoteSearchReplaceOpen(false); - setNoteSearchOpen(true); - window.requestAnimationFrame(() => noteSearchInputRef.current?.focus()); - }, [editor, hydratedEditorMemoId, memo?.id, pluginNavigationRequest]); + openNoteSearchWithQuery(pluginNavigationRequest.search); + }, [editor, hydratedEditorMemoId, memo?.id, openNoteSearchWithQuery, pluginNavigationRequest]); useEffect(() => { if (!isEditorReady(editor)) { diff --git a/apps/web/src/components/editor/useEditorNoteSearchController.ts b/apps/web/src/components/editor/useEditorNoteSearchController.ts index 4df089e5..b82e3c88 100644 --- a/apps/web/src/components/editor/useEditorNoteSearchController.ts +++ b/apps/web/src/components/editor/useEditorNoteSearchController.ts @@ -3,9 +3,8 @@ import { useEffect, useMemo, useRef, - type Dispatch, + useState, type RefObject, - type SetStateAction, } from "react"; import type { Editor } from "@tiptap/react"; import { @@ -23,18 +22,10 @@ type EditorNoteSearchControllerOptions = { dirtyVersion: number; editor: Editor | null; editorScrollContainerRef: RefObject; - noteSearchIndex: number; - noteSearchInputRef: RefObject; - noteSearchOpen: boolean; - noteSearchQuery: string; - noteSearchReplacement: string; readOnly: boolean; replaceFocusToken: number; searchFocusToken: number; memoId: string | null; - setNoteSearchIndex: Dispatch>; - setNoteSearchOpen: Dispatch>; - setNoteSearchReplaceOpen: Dispatch>; }; const isEditorReady = (editor: Editor | null | undefined): editor is Editor => @@ -54,18 +45,16 @@ export const useEditorNoteSearchController = ({ editor, editorScrollContainerRef, memoId, - noteSearchIndex, - noteSearchInputRef, - noteSearchOpen, - noteSearchQuery, - noteSearchReplacement, readOnly, replaceFocusToken, searchFocusToken, - setNoteSearchIndex, - setNoteSearchOpen, - setNoteSearchReplaceOpen, }: EditorNoteSearchControllerOptions) => { + const [noteSearchOpen, setNoteSearchOpen] = useState(false); + const [noteSearchQuery, setNoteSearchQuery] = useState(""); + const [noteSearchReplaceOpen, setNoteSearchReplaceOpen] = useState(false); + const [noteSearchReplacement, setNoteSearchReplacement] = useState(""); + const [noteSearchIndex, setNoteSearchIndex] = useState(0); + const noteSearchInputRef = useRef(null); const automaticSelectionRef = useRef<{ editor: Editor; identity: string } | null>(null); const noteSearchMatches = useMemo( () => getEditorSearchMatches(editor, noteSearchQuery), @@ -145,7 +134,26 @@ export const useEditorNoteSearchController = ({ setNoteSearchOpen(true); setNoteSearchReplaceOpen(showReplace); focusSearchInput(); - }, [focusSearchInput, setNoteSearchOpen, setNoteSearchReplaceOpen]); + }, [focusSearchInput]); + + const openFromSelection = useCallback((text: string, showReplace = false) => { + setNoteSearchQuery(text); + setNoteSearchOpen(true); + setNoteSearchReplaceOpen(showReplace); + focusSearchInput(); + }, [focusSearchInput]); + + const openWithQuery = useCallback((query: string) => { + setNoteSearchQuery(query); + setNoteSearchIndex(0); + setNoteSearchReplaceOpen(false); + setNoteSearchOpen(true); + window.requestAnimationFrame(() => noteSearchInputRef.current?.focus()); + }, []); + + const closeReplace = useCallback(() => { + setNoteSearchReplaceOpen(false); + }, []); const openReplace = useCallback(() => { if (readOnly) return; @@ -271,7 +279,9 @@ export const useEditorNoteSearchController = ({ ]); return { + closeReplace, closeSearch, + inputRef: noteSearchInputRef, matchLabel: formatNoteSearchMatchLabel( noteSearchQuery, noteSearchIndex, @@ -279,8 +289,16 @@ export const useEditorNoteSearchController = ({ ), matches: noteSearchMatches, moveMatch, + openFromSelection, openReplace, openSearch, + openWithQuery, + query: noteSearchQuery, replaceAllMatches, + replaceOpen: noteSearchReplaceOpen, + replacement: noteSearchReplacement, + searchOpen: noteSearchOpen, + setQuery: setNoteSearchQuery, + setReplacement: setNoteSearchReplacement, }; };