@@ -120,6 +120,7 @@ const MainApp: React.FC = () => {
120120 const [ contextMenu , setContextMenu ] = useState < { isOpen : boolean ; position : { x : number , y : number } , items : MenuItem [ ] } > ( { isOpen : false , position : { x : 0 , y : 0 } , items : [ ] } ) ;
121121 const [ isDraggingFile , setIsDraggingFile ] = useState ( false ) ;
122122 const [ formatTrigger , setFormatTrigger ] = useState ( 0 ) ;
123+ const [ bodySearchMatches , setBodySearchMatches ] = useState < Map < string , string > > ( new Map ( ) ) ;
123124
124125
125126 const isSidebarResizing = useRef ( false ) ;
@@ -171,9 +172,20 @@ const MainApp: React.FC = () => {
171172 } , [ theme , settings . markdownCodeBlockBackgroundLight , settings . markdownCodeBlockBackgroundDark , settingsLoaded ] ) ;
172173
173174
175+ const itemsWithSearchMetadata = useMemo ( ( ) => {
176+ const trimmed = searchTerm . trim ( ) ;
177+ if ( ! trimmed || bodySearchMatches . size === 0 ) {
178+ return items ;
179+ }
180+ return items . map ( item => {
181+ const snippet = bodySearchMatches . get ( item . id ) ;
182+ return snippet ? { ...item , searchSnippet : snippet } : item ;
183+ } ) ;
184+ } , [ items , bodySearchMatches , searchTerm ] ) ;
185+
174186 const activeNode = useMemo ( ( ) => {
175- return items . find ( p => p . id === activeNodeId ) || null ;
176- } , [ items , activeNodeId ] ) ;
187+ return itemsWithSearchMetadata . find ( p => p . id === activeNodeId ) || null ;
188+ } , [ itemsWithSearchMetadata , activeNodeId ] ) ;
177189
178190 const activeTemplate = useMemo ( ( ) => {
179191 return templates . find ( t => t . template_id === activeTemplateId ) || null ;
@@ -184,35 +196,71 @@ const MainApp: React.FC = () => {
184196 } , [ activeNode ] ) ;
185197
186198
199+ useEffect ( ( ) => {
200+ const term = searchTerm . trim ( ) ;
201+ if ( ! term ) {
202+ setBodySearchMatches ( new Map ( ) ) ;
203+ return ;
204+ }
205+
206+ let isCancelled = false ;
207+
208+ repository . searchDocumentsByBody ( term , 200 )
209+ . then ( results => {
210+ if ( ! isCancelled ) {
211+ setBodySearchMatches ( new Map ( results . map ( result => [ result . nodeId , result . snippet ] ) ) ) ;
212+ }
213+ } )
214+ . catch ( error => {
215+ if ( ! isCancelled ) {
216+ console . error ( 'Failed to search document bodies:' , error ) ;
217+ setBodySearchMatches ( new Map ( ) ) ;
218+ }
219+ } ) ;
220+
221+ return ( ) => {
222+ isCancelled = true ;
223+ } ;
224+ } , [ searchTerm ] ) ;
225+
187226 const { documentTree, navigableItems } = useMemo ( ( ) => {
188- let itemsToBuildFrom = items ;
227+ let itemsToBuildFrom = itemsWithSearchMetadata ;
189228 if ( searchTerm . trim ( ) ) {
190229 const lowerCaseSearchTerm = searchTerm . toLowerCase ( ) ;
191230 const visibleIds = new Set < string > ( ) ;
192- const originalItemsById : Map < string , DocumentOrFolder > = new Map ( items . map ( i => [ i . id , i ] ) ) ;
231+ const originalItemsById : Map < string , DocumentOrFolder > = new Map ( itemsWithSearchMetadata . map ( i => [ i . id , i ] ) ) ;
193232 const getAncestors = ( itemId : string ) => {
194233 let current = originalItemsById . get ( itemId ) ;
195234 while ( current && current . parentId ) {
196- visibleIds . add ( current . parentId ) ;
197- current = originalItemsById . get ( current . parentId ) ;
235+ visibleIds . add ( current . parentId ) ;
236+ current = originalItemsById . get ( current . parentId ) ;
198237 }
199238 } ;
200239 const getDescendantIdsRecursive = ( itemId : string ) : Set < string > => {
201240 const descendantIds = new Set < string > ( ) ;
202241 const findChildren = ( parentId : string ) => {
203- items . forEach ( p => { if ( p . parentId === parentId ) { descendantIds . add ( p . id ) ; if ( p . type === 'folder' ) findChildren ( p . id ) ; } } ) ;
242+ itemsWithSearchMetadata . forEach ( p => {
243+ if ( p . parentId === parentId ) {
244+ descendantIds . add ( p . id ) ;
245+ if ( p . type === 'folder' ) findChildren ( p . id ) ;
246+ }
247+ } ) ;
204248 } ;
205249 findChildren ( itemId ) ;
206250 return descendantIds ;
207251 } ;
208- items . forEach ( item => {
209- if ( item . title . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ) {
252+ itemsWithSearchMetadata . forEach ( item => {
253+ const titleMatch = item . title . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ;
254+ const bodyMatch = Boolean ( item . searchSnippet ) ;
255+ if ( titleMatch || bodyMatch ) {
210256 visibleIds . add ( item . id ) ;
211257 getAncestors ( item . id ) ;
212- if ( item . type === 'folder' ) getDescendantIdsRecursive ( item . id ) . forEach ( id => visibleIds . add ( id ) ) ;
258+ if ( titleMatch && item . type === 'folder' ) {
259+ getDescendantIdsRecursive ( item . id ) . forEach ( id => visibleIds . add ( id ) ) ;
260+ }
213261 }
214262 } ) ;
215- itemsToBuildFrom = items . filter ( item => visibleIds . has ( item . id ) ) ;
263+ itemsToBuildFrom = itemsWithSearchMetadata . filter ( item => visibleIds . has ( item . id ) ) ;
216264 }
217265 const itemsById = new Map < string , DocumentNode > ( itemsToBuildFrom . map ( p => [ p . id , { ...p , children : [ ] } ] ) ) ;
218266 const rootNodes : DocumentNode [ ] = [ ] ;
@@ -224,27 +272,27 @@ const MainApp: React.FC = () => {
224272 rootNodes . push ( node ) ;
225273 }
226274 }
227-
275+
228276 const finalTree = rootNodes ;
229277
230- const displayExpandedIds = searchTerm . trim ( )
231- ? new Set ( itemsToBuildFrom . filter ( i => i . type === 'folder' ) . map ( i => i . id ) )
278+ const displayExpandedIds = searchTerm . trim ( )
279+ ? new Set ( itemsToBuildFrom . filter ( i => i . type === 'folder' ) . map ( i => i . id ) )
232280 : expandedFolderIds ;
233281
234282 const flatList : NavigableItem [ ] = [ ] ;
235283 const flatten = ( nodes : DocumentNode [ ] ) => {
236- for ( const node of nodes ) {
237- flatList . push ( { id : node . id , type : node . type , parentId : node . parentId } ) ;
238- if ( node . type === 'folder' && displayExpandedIds . has ( node . id ) ) {
239- flatten ( node . children ) ;
284+ for ( const node of nodes ) {
285+ flatList . push ( { id : node . id , type : node . type , parentId : node . parentId } ) ;
286+ if ( node . type === 'folder' && displayExpandedIds . has ( node . id ) ) {
287+ flatten ( node . children ) ;
288+ }
240289 }
241- }
242290 } ;
243291 flatten ( finalTree ) ;
244292 templates . forEach ( t => flatList . push ( { id : t . template_id , type : 'template' , parentId : null } ) ) ;
245293
246294 return { documentTree : finalTree , navigableItems : flatList } ;
247- } , [ items , templates , searchTerm , expandedFolderIds ] ) ;
295+ } , [ itemsWithSearchMetadata , templates , searchTerm , expandedFolderIds ] ) ;
248296
249297 useEffect ( ( ) => {
250298 if ( window . electronAPI ?. getAppVersion ) {
@@ -1100,8 +1148,8 @@ const MainApp: React.FC = () => {
11001148 style = { { width : `${ sidebarWidth } px` } }
11011149 className = "bg-secondary border-r border-border-color flex flex-col flex-shrink-0"
11021150 >
1103- < Sidebar
1104- documents = { items }
1151+ < Sidebar
1152+ documents = { itemsWithSearchMetadata }
11051153 documentTree = { documentTree }
11061154 navigableItems = { navigableItems }
11071155 selectedIds = { selectedIds }
0 commit comments