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.
This commit is contained in:
tianma-if committed 2026-09-15 17:51:33 +08:00
1 parent 8a48c60d72
commit 526bb940e0
2 files changed
+55 -47

No files matched your search

+18 -28
View File
@@ -425,12 +425,9 @@ const RichEditorPane = ({
const [aiInsertionTarget, setAiInsertionTarget] = useState<AiInsertionTarget | null>(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<NoteLinkHintPosition | null>(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<number | null>(null);
const mobileSaveTimerRef = useRef<number | null>(null);
const fileInputRef = useRef<HTMLInputElement | null>(null);
const noteSearchInputRef = useRef<HTMLInputElement | null>(null);
const markdownSourceEditorRef = useRef<MarkdownSourceEditorRef | null>(null);
const markdownModeSnapshotRef = useRef<MarkdownModeSnapshot | null>(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)) {
@@ -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<HTMLDivElement | null>;
noteSearchIndex: number;
noteSearchInputRef: RefObject<HTMLInputElement | null>;
noteSearchOpen: boolean;
noteSearchQuery: string;
noteSearchReplacement: string;
readOnly: boolean;
replaceFocusToken: number;
searchFocusToken: number;
memoId: string | null;
setNoteSearchIndex: Dispatch<SetStateAction<number>>;
setNoteSearchOpen: Dispatch<SetStateAction<boolean>>;
setNoteSearchReplaceOpen: Dispatch<SetStateAction<boolean>>;
};
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<HTMLInputElement | null>(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,
};
};