From 8ef705c4568d60c273cf7f620f557461888b0111 Mon Sep 17 00:00:00 2001 From: carl2027 Date: Tue, 23 Dec 2025 11:46:06 +0800 Subject: [PATCH 1/2] Improve search functionality - Fixed: The search dialog now correctly receives focus when opened in the current block. - Implemented direct search for selected text. --- frontend/app/element/search.tsx | 7 ++- frontend/app/store/keymodel.ts | 63 ++++++++++++++++++++++++++- frontend/app/view/term/term.tsx | 1 + frontend/app/view/webview/webview.tsx | 2 +- 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/frontend/app/element/search.tsx b/frontend/app/element/search.tsx index 031bcd97d8..b9770570a5 100644 --- a/frontend/app/element/search.tsx +++ b/frontend/app/element/search.tsx @@ -16,6 +16,7 @@ type SearchProps = SearchAtoms & { onSearch?: (search: string) => void; onNext?: () => void; onPrev?: () => void; + blockId?: string; }; const SearchComponent = ({ @@ -32,6 +33,7 @@ const SearchComponent = ({ onSearch, onNext, onPrev, + blockId, }: SearchProps) => { const [isOpen, setIsOpen] = useAtom(isOpenAtom); const [search, setSearch] = useAtom(searchAtom); @@ -144,7 +146,7 @@ const SearchComponent = ({ <> {isOpen && ( -
+
0) { + return terminalSelection.trim(); + } + } + } + + // Check for regular text selection + const selection = window.getSelection(); + if (selection && selection.rangeCount > 0 && !selection.isCollapsed) { + const selectedText = selection.toString().trim(); + if (selectedText.length > 0) { + return selectedText; + } + } + return ""; + } + + function focusSearchInput() { + setTimeout(() => { + const blockId = getFocusedBlockInStaticTab(); + if (!blockId) { + return; + } + + // Directly find the search container by data-blockid attribute + const searchContainer = document.querySelector( + `.search-container[data-blockid="${blockId}"]` + ) as HTMLElement; + if (searchContainer) { + const searchInput = searchContainer.querySelector("input") as HTMLInputElement; + if (searchInput) { + searchInput.focus(); + searchInput.select(); + } + } + }, 0); + } + function activateSearch(event: WaveKeyboardEvent): boolean { const bcm = getBlockComponentModel(getFocusedBlockInStaticTab()); - // Ctrl+f is reserved in most shells + // Ctrl+f is reserved in most shells. if (event.control && bcm.viewModel.viewType == "term") { return false; } if (bcm.viewModel.searchAtoms) { - globalStore.set(bcm.viewModel.searchAtoms.isOpen, true); + const searchAtoms = bcm.viewModel.searchAtoms; + const isOpen = globalStore.get(searchAtoms.isOpen); + const selectedText = getSelectedText(); + + // Open search dialog if not already open + if (!isOpen) { + globalStore.set(searchAtoms.isOpen, true); + } + // Set search value (use selected text if available, otherwise empty string) + globalStore.set(searchAtoms.searchValue, selectedText || ""); + // Reset search results + globalStore.set(searchAtoms.resultsIndex, 0); + globalStore.set(searchAtoms.resultsCount, 0); + focusSearchInput(); return true; } return false; diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index 387155752d..89948f81c6 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -178,6 +178,7 @@ const TerminalView = ({ blockId, model }: ViewComponentProps) => caseSensitive: false, wholeWord: false, regex: false, + blockId: blockId, }); const searchIsOpen = jotai.useAtomValue(searchProps.isOpen); const caseSensitive = useAtomValueSafe(searchProps.caseSensitive); diff --git a/frontend/app/view/webview/webview.tsx b/frontend/app/view/webview/webview.tsx index 5695f99e63..1043307253 100644 --- a/frontend/app/view/webview/webview.tsx +++ b/frontend/app/view/webview/webview.tsx @@ -821,7 +821,7 @@ const WebView = memo(({ model, onFailLoad, blockRef, initialSrc }: WebViewProps) } // Search - const searchProps = useSearch({ anchorRef: model.webviewRef, viewModel: model }); + const searchProps = useSearch({ anchorRef: model.webviewRef, viewModel: model, blockId: model.blockId }); const searchVal = useAtomValue(searchProps.searchValue); const setSearchIndex = useSetAtom(searchProps.resultsIndex); const setNumSearchResults = useSetAtom(searchProps.resultsCount); From 69a2f73bfe948f5b4fb32fc48e60976d6908a20b Mon Sep 17 00:00:00 2001 From: carl2027 Date: Thu, 8 Jan 2026 09:04:54 +0800 Subject: [PATCH 2/2] add a input ref in viewmodel --- frontend/app/element/search.tsx | 18 +++++++---- frontend/app/store/keymodel.ts | 44 ++++++--------------------- frontend/app/view/term/term.tsx | 1 - frontend/app/view/webview/webview.tsx | 2 +- 4 files changed, 23 insertions(+), 42 deletions(-) diff --git a/frontend/app/element/search.tsx b/frontend/app/element/search.tsx index b9770570a5..351639aecd 100644 --- a/frontend/app/element/search.tsx +++ b/frontend/app/element/search.tsx @@ -11,12 +11,12 @@ import "./search.scss"; type SearchProps = SearchAtoms & { anchorRef?: React.RefObject; + searchInputRef?: React.RefObject; offsetX?: number; offsetY?: number; onSearch?: (search: string) => void; onNext?: () => void; onPrev?: () => void; - blockId?: string; }; const SearchComponent = ({ @@ -28,13 +28,15 @@ const SearchComponent = ({ wholeWord: wholeWordAtom, isOpen: isOpenAtom, anchorRef, + searchInputRef: providedInputRef, offsetX = 10, offsetY = 10, onSearch, onNext, onPrev, - blockId, }: SearchProps) => { + const localInputRef = useRef(null); + const inputRef = providedInputRef || localInputRef; const [isOpen, setIsOpen] = useAtom(isOpenAtom); const [search, setSearch] = useAtom(searchAtom); const [index, setIndex] = useAtom(indexAtom); @@ -146,8 +148,9 @@ const SearchComponent = ({ <> {isOpen && ( -
+
(null); useEffect(() => { if (options?.viewModel) { options.viewModel.searchAtoms = searchAtoms; + // Store inputRef on viewModel for external access (e.g., keymodel.ts) + (options.viewModel as any).searchInputRef = searchInputRef; return () => { options.viewModel.searchAtoms = undefined; + (options.viewModel as any).searchInputRef = undefined; }; } - }, [options?.viewModel]); - return { ...searchAtoms, anchorRef, blockId: options?.blockId }; + }, [options?.viewModel, searchAtoms]); + return { ...searchAtoms, anchorRef, searchInputRef }; } const createToggleButtonDecl = ( diff --git a/frontend/app/store/keymodel.ts b/frontend/app/store/keymodel.ts index b41daf28ba..e66a317f88 100644 --- a/frontend/app/store/keymodel.ts +++ b/frontend/app/store/keymodel.ts @@ -653,27 +653,6 @@ function registerGlobalKeys() { return ""; } - function focusSearchInput() { - setTimeout(() => { - const blockId = getFocusedBlockInStaticTab(); - if (!blockId) { - return; - } - - // Directly find the search container by data-blockid attribute - const searchContainer = document.querySelector( - `.search-container[data-blockid="${blockId}"]` - ) as HTMLElement; - if (searchContainer) { - const searchInput = searchContainer.querySelector("input") as HTMLInputElement; - if (searchInput) { - searchInput.focus(); - searchInput.select(); - } - } - }, 0); - } - function activateSearch(event: WaveKeyboardEvent): boolean { const bcm = getBlockComponentModel(getFocusedBlockInStaticTab()); // Ctrl+f is reserved in most shells. @@ -681,20 +660,17 @@ function registerGlobalKeys() { return false; } if (bcm.viewModel.searchAtoms) { - const searchAtoms = bcm.viewModel.searchAtoms; - const isOpen = globalStore.get(searchAtoms.isOpen); - const selectedText = getSelectedText(); - - // Open search dialog if not already open - if (!isOpen) { - globalStore.set(searchAtoms.isOpen, true); + let selectedText = getSelectedText(); + globalStore.set(bcm.viewModel.searchAtoms.isOpen, true); + globalStore.set(bcm.viewModel.searchAtoms.searchValue, selectedText); + + // Focus the search input using the exposed searchInputRef + const searchInputRef = bcm.viewModel.searchInputRef; + if (searchInputRef?.current) { + setTimeout(() => { + searchInputRef.current?.focus(); + }, 10); } - // Set search value (use selected text if available, otherwise empty string) - globalStore.set(searchAtoms.searchValue, selectedText || ""); - // Reset search results - globalStore.set(searchAtoms.resultsIndex, 0); - globalStore.set(searchAtoms.resultsCount, 0); - focusSearchInput(); return true; } return false; diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index 89948f81c6..387155752d 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -178,7 +178,6 @@ const TerminalView = ({ blockId, model }: ViewComponentProps) => caseSensitive: false, wholeWord: false, regex: false, - blockId: blockId, }); const searchIsOpen = jotai.useAtomValue(searchProps.isOpen); const caseSensitive = useAtomValueSafe(searchProps.caseSensitive); diff --git a/frontend/app/view/webview/webview.tsx b/frontend/app/view/webview/webview.tsx index 1043307253..5695f99e63 100644 --- a/frontend/app/view/webview/webview.tsx +++ b/frontend/app/view/webview/webview.tsx @@ -821,7 +821,7 @@ const WebView = memo(({ model, onFailLoad, blockRef, initialSrc }: WebViewProps) } // Search - const searchProps = useSearch({ anchorRef: model.webviewRef, viewModel: model, blockId: model.blockId }); + const searchProps = useSearch({ anchorRef: model.webviewRef, viewModel: model }); const searchVal = useAtomValue(searchProps.searchValue); const setSearchIndex = useSetAtom(searchProps.resultsIndex); const setNumSearchResults = useSetAtom(searchProps.resultsCount);