From 23de5d22297db006e77ce797bc673604a84e10ba Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Thu, 22 May 2025 11:07:24 +0500 Subject: [PATCH] web: add full support for highlighting results in notes search --- apps/web/src/components/editor/index.tsx | 20 +++++++++++ .../src/components/search-result/index.tsx | 34 +++++++++++++++---- apps/web/src/stores/editor-store.ts | 23 +++++++++---- apps/web/src/views/all-notes.tsx | 17 ++++++---- 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/apps/web/src/components/editor/index.tsx b/apps/web/src/components/editor/index.tsx index b8b4ea144..1f72f7920 100644 --- a/apps/web/src/components/editor/index.tsx +++ b/apps/web/src/components/editor/index.tsx @@ -475,6 +475,7 @@ export function Editor(props: EditorProps) { ); const setEditorSaveState = useEditorStore((store) => store.setSaveState); useScrollToBlock(session); + useScrollToSearchResult(session); useEffect(() => { if (!autoSaveToast.show) { @@ -837,6 +838,25 @@ function useScrollToBlock(session: EditorSession) { }, [session.id, session.type, blockId]); } +function useScrollToSearchResult(session: EditorSession) { + const index = useEditorStore( + (store) => store.getSession(session.id)?.activeSearchResultIndex + ); + useEffect(() => { + if (index === undefined) return; + const scrollContainer = document.getElementById( + `editorScroll_${session.id}` + ); + const elements = scrollContainer?.getElementsByTagName("nn-search-result"); + setTimeout(() => + elements?.item(index)?.scrollIntoView({ block: "center" }) + ); + useEditorStore.getState().updateSession(session.id, [session.type], { + activeSearchResultIndex: undefined + }); + }, [session.id, session.type, index]); +} + function isFile(e: DragEvent) { return ( e.dataTransfer && diff --git a/apps/web/src/components/search-result/index.tsx b/apps/web/src/components/search-result/index.tsx index 3e4a8a0b2..bdc8cfe88 100644 --- a/apps/web/src/components/search-result/index.tsx +++ b/apps/web/src/components/search-result/index.tsx @@ -27,7 +27,7 @@ import { ChevronDown, ChevronRight } from "../icons"; type SearchResultProps = { item: HighlightedResult; - match?: Match[]; + matchIndex?: number; depth: number; isExpandable: boolean; isExpanded: boolean; @@ -36,23 +36,43 @@ type SearchResultProps = { }; function SearchResult(props: SearchResultProps) { - const { item, match, collapse, depth, expand, isExpandable, isExpanded } = - props; + const { + item, + matchIndex, + collapse, + depth, + expand, + isExpandable, + isExpanded + } = props; const isOpened = useEditorStore((store) => store.isNoteOpen(item.id)); + const match = matchIndex !== undefined ? item.content[matchIndex] : undefined; return ( + onClick={() => { + let activeIndex = 0; + for (let i = 0; i <= (matchIndex || 0) - 1; ++i) { + activeIndex += item.content[i].length; + } useEditorStore .getState() - .openSession(item.id, { considerPinnedTab: true }) - } + .openSession(item.id, { + rawContent: item.rawContent, + force: true, + activeSearchResultIndex: activeIndex + }); + }} onMiddleClick={() => - useEditorStore.getState().openSession(item.id, { openInNewTab: true }) + useEditorStore.getState().openSession(item.id, { + openInNewTab: true, + rawContent: item.rawContent, + force: true + }) } title={ diff --git a/apps/web/src/stores/editor-store.ts b/apps/web/src/stores/editor-store.ts index cb2e120bd..d7f563a8d 100644 --- a/apps/web/src/stores/editor-store.ts +++ b/apps/web/src/stores/editor-store.ts @@ -83,6 +83,10 @@ export type BaseEditorSession = { * The id of block to scroll to after opening the session successfully. */ activeBlockId?: string; + /** + * The index of search result to scroll to after opening the session successfully. + */ + activeSearchResultIndex?: number; }; export type LockedEditorSession = BaseEditorSession & { @@ -660,10 +664,8 @@ class EditorStore extends BaseStore { activeBlockId?: string; silent?: boolean; openInNewTab?: boolean; - /** - * Should be true if we want to open a new tab when the active tab is pinned - */ - considerPinnedTab?: boolean; + rawContent?: string; + activeSearchResultIndex?: number; } = {} ): Promise => { const { @@ -787,6 +789,7 @@ class EditorStore extends BaseStore { id: sessionId, content, activeBlockId: options.activeBlockId, + activeSearchResultIndex: options.activeSearchResultIndex, tabId }, options.silent @@ -803,10 +806,14 @@ class EditorStore extends BaseStore { type: "readonly", note, id: sessionId, - content, + content: + options.rawContent && content + ? { data: options.rawContent, type: content.type } + : content, color: colors[0]?.fromId, tags, activeBlockId: options.activeBlockId, + activeSearchResultIndex: options.activeSearchResultIndex, tabId }, options.silent @@ -822,8 +829,12 @@ class EditorStore extends BaseStore { attachmentsLength, tags, color: colors[0]?.fromId, - content, + content: + options.rawContent && content + ? { ...content, data: options.rawContent } + : content, activeBlockId: options.activeBlockId, + activeSearchResultIndex: options.activeSearchResultIndex, tabId }, options.silent diff --git a/apps/web/src/views/all-notes.tsx b/apps/web/src/views/all-notes.tsx index 021810672..3d6302fbf 100644 --- a/apps/web/src/views/all-notes.tsx +++ b/apps/web/src/views/all-notes.tsx @@ -41,7 +41,7 @@ function Home() { const treeRef = useRef< VirtualizedTreeHandle<{ item: HighlightedResult; - match?: Match[]; + matchIndex?: number; }> >(null); const notes = useStore((store) => store.notes); @@ -124,7 +124,7 @@ function Home() { getChildNodes={async (parent) => { const nodes: TreeNode<{ item: HighlightedResult; - match?: Match[]; + matchIndex?: number; }>[] = []; if (parent.id === "root") { for (let i = 0; i < filteredItems.length; ++i) { @@ -140,13 +140,16 @@ function Home() { }); } } else { - let i = 0; - for (const match of parent.data.item.content || []) { + for ( + let i = 0; + i < (parent.data.item.content.length || 0); + ++i + ) { nodes.push({ - data: { item: parent.data.item, match }, + data: { item: parent.data.item, matchIndex: i }, depth: parent.depth + 1, parentId: parent.id, - id: parent.id + i++, + id: parent.id + i, hasChildren: false }); } @@ -159,7 +162,7 @@ function Home() { depth={node.depth} isExpandable={node.hasChildren} item={node.data.item} - match={node.data.match} + matchIndex={node.data.matchIndex} isExpanded={expanded} collapse={collapse} expand={expand}