@@ -8,22 +8,34 @@ import {
8
8
} from "@anaralabs/lector" ;
9
9
import { useEffect , useState } from "react" ;
10
10
11
+ // Define the TextPosition interface to match the one in useSearchPosition.tsx
12
+ interface TextPosition {
13
+ pageNumber : number ;
14
+ text : string ;
15
+ matchIndex : number ;
16
+ searchText ?: string ;
17
+ }
18
+
11
19
interface ResultItemProps {
12
20
result : SearchResult ;
21
+ originalSearchText ?: string ;
13
22
}
14
23
15
- const ResultItem = ( { result } : ResultItemProps ) => {
24
+ const ResultItem = ( { result, originalSearchText } : ResultItemProps ) => {
16
25
const { jumpToHighlightRects } = usePdfJump ( ) ;
17
26
const getPdfPageProxy = usePdf ( ( state ) => state . getPdfPageProxy ) ;
18
27
19
28
const onClick = async ( ) => {
20
29
const pageProxy = getPdfPageProxy ( result . pageNumber ) ;
21
30
22
- const rects = await calculateHighlightRects ( pageProxy , {
31
+ const textPosition : TextPosition = {
23
32
pageNumber : result . pageNumber ,
24
33
text : result . text ,
25
34
matchIndex : result . matchIndex ,
26
- } ) ;
35
+ searchText : originalSearchText ,
36
+ } ;
37
+
38
+ const rects = await calculateHighlightRects ( pageProxy , textPosition ) ;
27
39
28
40
jumpToHighlightRects ( rects , "pixels" ) ;
29
41
} ;
@@ -46,13 +58,51 @@ const ResultItem = ({ result }: ResultItemProps) => {
46
58
) ;
47
59
} ;
48
60
61
+ // Component for full context highlighting
62
+ const ResultItemFullHighlight = ( { result } : { result : SearchResult } ) => {
63
+ const { jumpToHighlightRects } = usePdfJump ( ) ;
64
+ const getPdfPageProxy = usePdf ( ( state ) => state . getPdfPageProxy ) ;
65
+
66
+ const onClick = async ( ) => {
67
+ const pageProxy = getPdfPageProxy ( result . pageNumber ) ;
68
+
69
+ const textPosition : TextPosition = {
70
+ pageNumber : result . pageNumber ,
71
+ text : result . text ,
72
+ matchIndex : result . matchIndex ,
73
+ // No searchText parameter = highlight full context
74
+ } ;
75
+
76
+ const rects = await calculateHighlightRects ( pageProxy , textPosition ) ;
77
+ jumpToHighlightRects ( rects , "pixels" ) ;
78
+ } ;
79
+
80
+ return (
81
+ < div
82
+ className = "flex py-2 hover:bg-gray-50 flex-col cursor-pointer"
83
+ onClick = { onClick }
84
+ >
85
+ < div className = "flex-1 min-w-0" >
86
+ < p className = "text-sm text-gray-900" > { result . text } </ p >
87
+ </ div >
88
+ < div className = "flex items-center gap-4 flex-shrink-0 text-sm text-gray-500" >
89
+ { ! result . isExactMatch && (
90
+ < span > { ( result . score * 100 ) . toFixed ( ) } % match</ span >
91
+ ) }
92
+ < span className = "ml-auto" > Page { result . pageNumber } </ span >
93
+ </ div >
94
+ </ div >
95
+ ) ;
96
+ } ;
97
+
49
98
interface ResultGroupProps {
50
99
title : string ;
51
100
results : SearchResult [ ] ;
52
101
displayCount ?: number ;
102
+ originalSearchText ?: string ;
53
103
}
54
104
55
- const ResultGroup = ( { title, results, displayCount } : ResultGroupProps ) => {
105
+ const ResultGroup = ( { title, results, displayCount, originalSearchText } : ResultGroupProps ) => {
56
106
if ( ! results . length ) return null ;
57
107
58
108
const displayResults = displayCount
@@ -67,6 +117,30 @@ const ResultGroup = ({ title, results, displayCount }: ResultGroupProps) => {
67
117
< ResultItem
68
118
key = { `${ result . pageNumber } -${ result . matchIndex } ` }
69
119
result = { result }
120
+ originalSearchText = { originalSearchText }
121
+ />
122
+ ) ) }
123
+ </ div >
124
+ </ div >
125
+ ) ;
126
+ } ;
127
+
128
+ // Modified ResultGroup for full highlighting
129
+ const ResultGroupFullHighlight = ( { title, results, displayCount } : Omit < ResultGroupProps , 'originalSearchText' > ) => {
130
+ if ( ! results . length ) return null ;
131
+
132
+ const displayResults = displayCount
133
+ ? results . slice ( 0 , displayCount )
134
+ : results ;
135
+
136
+ return (
137
+ < div className = "space-y-2" >
138
+ < h3 className = "text-sm font-medium text-gray-700" > { title } </ h3 >
139
+ < div className = "divide-y divide-gray-100" >
140
+ { displayResults . map ( ( result ) => (
141
+ < ResultItemFullHighlight
142
+ key = { `${ result . pageNumber } -${ result . matchIndex } ` }
143
+ result = { result }
70
144
/>
71
145
) ) }
72
146
</ div >
@@ -146,8 +220,101 @@ export const SearchResults = ({
146
220
147
221
return (
148
222
< div className = "flex flex-col gap-4" >
149
- < ResultGroup title = "Exact Matches" results = { results . exactMatches } />
150
- < ResultGroup title = "Similar Matches" results = { results . fuzzyMatches } />
223
+ < ResultGroup
224
+ title = "Exact Matches"
225
+ results = { results . exactMatches }
226
+ originalSearchText = { searchText }
227
+ />
228
+ < ResultGroup
229
+ title = "Similar Matches"
230
+ results = { results . fuzzyMatches }
231
+ originalSearchText = { searchText }
232
+ />
233
+
234
+ { results . hasMoreResults && (
235
+ < button
236
+ onClick = { onLoadMore }
237
+ className = "w-full py-2 px-4 text-sm text-white bg-blue-500 rounded-lg hover:bg-blue-600 transition-colors"
238
+ >
239
+ Load More Results
240
+ </ button >
241
+ ) }
242
+ </ div >
243
+ ) ;
244
+ } ;
245
+
246
+ // New component for full context highlighting
247
+ export const SearchUIFullHighlight = ( ) => {
248
+ const [ searchText , setSearchText ] = useState ( "" ) ;
249
+ const [ debouncedSearchText ] = useDebounce ( searchText , 500 ) ;
250
+ const [ limit , setLimit ] = useState ( 5 ) ;
251
+ const { searchResults : results , search } = useSearch ( ) ;
252
+
253
+ useEffect ( ( ) => {
254
+ setLimit ( 5 ) ;
255
+ search ( debouncedSearchText , { limit : 5 } ) ;
256
+ } , [ debouncedSearchText ] ) ;
257
+
258
+ const handleSearch = ( searchText : string ) => {
259
+ setSearchText ( searchText ) ;
260
+ } ;
261
+
262
+ const handleLoadMore = async ( ) => {
263
+ const newLimit = limit + 5 ;
264
+ await search ( debouncedSearchText , { limit : newLimit } ) ;
265
+ setLimit ( newLimit ) ;
266
+ } ;
267
+
268
+ return (
269
+ < div className = "flex flex-col w-80 h-full" >
270
+ < div className = "px-4 py-4 border-b border-gray-200 bg-white" >
271
+ < div className = "relative" >
272
+ < input
273
+ type = "text"
274
+ value = { searchText || "" }
275
+ onChange = { ( e ) => handleSearch ( e . target . value ) }
276
+ placeholder = "Search in document..."
277
+ className = "w-full px-4 py-2 bg-white border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
278
+ />
279
+ </ div >
280
+ </ div >
281
+ < div className = "flex-1 overflow-y-auto px-4" >
282
+ < div className = "py-4" >
283
+ < SearchResultsFullHighlight
284
+ searchText = { searchText }
285
+ results = { results }
286
+ onLoadMore = { handleLoadMore }
287
+ />
288
+ </ div >
289
+ </ div >
290
+ </ div >
291
+ ) ;
292
+ } ;
293
+
294
+ // Results component for full context highlighting
295
+ const SearchResultsFullHighlight = ( {
296
+ searchText,
297
+ results,
298
+ onLoadMore,
299
+ } : SearchResultsProps ) => {
300
+ if ( ! searchText ) return null ;
301
+
302
+ if ( ! results . exactMatches . length && ! results . fuzzyMatches . length ) {
303
+ return (
304
+ < div className = "text-center py-4 text-gray-500" > No results found</ div >
305
+ ) ;
306
+ }
307
+
308
+ return (
309
+ < div className = "flex flex-col gap-4" >
310
+ < ResultGroupFullHighlight
311
+ title = "Exact Matches"
312
+ results = { results . exactMatches }
313
+ />
314
+ < ResultGroupFullHighlight
315
+ title = "Similar Matches"
316
+ results = { results . fuzzyMatches }
317
+ />
151
318
152
319
{ results . hasMoreResults && (
153
320
< button
0 commit comments