@@ -539,6 +539,90 @@ function extractContentSnippet(noteId: string, searchTokens: string[], maxLength
539539 }
540540}
541541
542+ function extractAttributeSnippet ( noteId : string , searchTokens : string [ ] , maxLength : number = 200 ) : string {
543+ const note = becca . notes [ noteId ] ;
544+ if ( ! note ) {
545+ return "" ;
546+ }
547+
548+ try {
549+ // Get all attributes for this note
550+ const attributes = note . getAttributes ( ) ;
551+ if ( ! attributes || attributes . length === 0 ) {
552+ return "" ;
553+ }
554+
555+ let matchingAttributes : Array < { name : string , value : string , type : string } > = [ ] ;
556+
557+ // Look for attributes that match the search tokens
558+ for ( const attr of attributes ) {
559+ const attrName = attr . name ?. toLowerCase ( ) || "" ;
560+ const attrValue = attr . value ?. toLowerCase ( ) || "" ;
561+ const attrType = attr . type || "" ;
562+
563+ // Check if any search token matches the attribute name or value
564+ const hasMatch = searchTokens . some ( token => {
565+ const normalizedToken = normalizeString ( token . toLowerCase ( ) ) ;
566+ return attrName . includes ( normalizedToken ) || attrValue . includes ( normalizedToken ) ;
567+ } ) ;
568+
569+ if ( hasMatch ) {
570+ matchingAttributes . push ( {
571+ name : attr . name || "" ,
572+ value : attr . value || "" ,
573+ type : attrType
574+ } ) ;
575+ }
576+ }
577+
578+ if ( matchingAttributes . length === 0 ) {
579+ return "" ;
580+ }
581+
582+ // Limit to 4 lines maximum, similar to content snippet logic
583+ const lines : string [ ] = [ ] ;
584+ for ( const attr of matchingAttributes . slice ( 0 , 4 ) ) {
585+ let line = "" ;
586+ if ( attr . type === "label" ) {
587+ line = attr . value ? `#${ attr . name } ="${ attr . value } "` : `#${ attr . name } ` ;
588+ } else if ( attr . type === "relation" ) {
589+ // For relations, show the target note title if possible
590+ const targetNote = attr . value ? becca . notes [ attr . value ] : null ;
591+ const targetTitle = targetNote ? targetNote . title : attr . value ;
592+ line = `~${ attr . name } ="${ targetTitle } "` ;
593+ }
594+
595+ if ( line ) {
596+ lines . push ( line ) ;
597+ }
598+ }
599+
600+ let snippet = lines . join ( '\n' ) ;
601+
602+ // Apply length limit while preserving line structure
603+ if ( snippet . length > maxLength ) {
604+ // Try to truncate at word boundaries but keep lines intact
605+ const truncated = snippet . substring ( 0 , maxLength ) ;
606+ const lastNewline = truncated . lastIndexOf ( '\n' ) ;
607+
608+ if ( lastNewline > maxLength / 2 ) {
609+ // If we can keep most content by truncating to last complete line
610+ snippet = truncated . substring ( 0 , lastNewline ) ;
611+ } else {
612+ // Otherwise just truncate and add ellipsis
613+ const lastSpace = truncated . lastIndexOf ( ' ' ) ;
614+ snippet = truncated . substring ( 0 , lastSpace > maxLength / 2 ? lastSpace : maxLength - 3 ) ;
615+ snippet = snippet + "..." ;
616+ }
617+ }
618+
619+ return snippet ;
620+ } catch ( e ) {
621+ log . error ( `Error extracting attribute snippet for note ${ noteId } : ${ e } ` ) ;
622+ return "" ;
623+ }
624+ }
625+
542626function searchNotesForAutocomplete ( query : string , fastSearch : boolean = true ) {
543627 const searchContext = new SearchContext ( {
544628 fastSearch : fastSearch ,
@@ -553,9 +637,10 @@ function searchNotesForAutocomplete(query: string, fastSearch: boolean = true) {
553637
554638 const trimmed = allSearchResults . slice ( 0 , 200 ) ;
555639
556- // Extract content snippets
640+ // Extract content and attribute snippets
557641 for ( const result of trimmed ) {
558642 result . contentSnippet = extractContentSnippet ( result . noteId , searchContext . highlightedTokens ) ;
643+ result . attributeSnippet = extractAttributeSnippet ( result . noteId , searchContext . highlightedTokens ) ;
559644 }
560645
561646 highlightSearchResults ( trimmed , searchContext . highlightedTokens , searchContext . ignoreInternalAttributes ) ;
@@ -569,6 +654,8 @@ function searchNotesForAutocomplete(query: string, fastSearch: boolean = true) {
569654 highlightedNotePathTitle : result . highlightedNotePathTitle ,
570655 contentSnippet : result . contentSnippet ,
571656 highlightedContentSnippet : result . highlightedContentSnippet ,
657+ attributeSnippet : result . attributeSnippet ,
658+ highlightedAttributeSnippet : result . highlightedAttributeSnippet ,
572659 icon : icon ?? "bx bx-note"
573660 } ;
574661 } ) ;
@@ -599,6 +686,14 @@ function highlightSearchResults(searchResults: SearchResult[], highlightedTokens
599686 // Remove any stray < { } that might interfere with our highlighting markers
600687 result . highlightedContentSnippet = result . highlightedContentSnippet . replace ( / [ < { } ] / g, "" ) ;
601688 }
689+
690+ // Initialize highlighted attribute snippet
691+ if ( result . attributeSnippet ) {
692+ // Escape HTML but preserve newlines for later conversion to <br>
693+ result . highlightedAttributeSnippet = escapeHtml ( result . attributeSnippet ) ;
694+ // Remove any stray < { } that might interfere with our highlighting markers
695+ result . highlightedAttributeSnippet = result . highlightedAttributeSnippet . replace ( / [ < { } ] / g, "" ) ;
696+ }
602697 }
603698
604699 function wrapText ( text : string , start : number , length : number , prefix : string , suffix : string ) {
@@ -635,6 +730,16 @@ function highlightSearchResults(searchResults: SearchResult[], highlightedTokens
635730 contentRegex . lastIndex += 2 ;
636731 }
637732 }
733+
734+ // Highlight in attribute snippet
735+ if ( result . highlightedAttributeSnippet ) {
736+ const attributeRegex = new RegExp ( escapeRegExp ( token ) , "gi" ) ;
737+ while ( ( match = attributeRegex . exec ( normalizeString ( result . highlightedAttributeSnippet ) ) ) !== null ) {
738+ result . highlightedAttributeSnippet = wrapText ( result . highlightedAttributeSnippet , match . index , token . length , "{" , "}" ) ;
739+ // 2 characters are added, so we need to adjust the index
740+ attributeRegex . lastIndex += 2 ;
741+ }
742+ }
638743 }
639744 }
640745
@@ -649,6 +754,13 @@ function highlightSearchResults(searchResults: SearchResult[], highlightedTokens
649754 // Convert newlines to <br> tags for HTML display
650755 result . highlightedContentSnippet = result . highlightedContentSnippet . replace ( / \n / g, "<br>" ) ;
651756 }
757+
758+ if ( result . highlightedAttributeSnippet ) {
759+ // Replace highlighting markers with HTML tags
760+ result . highlightedAttributeSnippet = result . highlightedAttributeSnippet . replace ( / { / g, "<b>" ) . replace ( / } / g, "</b>" ) ;
761+ // Convert newlines to <br> tags for HTML display
762+ result . highlightedAttributeSnippet = result . highlightedAttributeSnippet . replace ( / \n / g, "<br>" ) ;
763+ }
652764 }
653765}
654766
0 commit comments