@@ -5,6 +5,7 @@ import { qs, escapeHtmlEntities, isBlank, getQueryParamByName, getProjectNameAnd
5
5
import { setSearchInputValue } from './search-bar'
6
6
import searchResultsTemplate from './handlebars/templates/search-results.handlebars'
7
7
import { getSearchNodes } from './globals'
8
+ import { highlightMatches } from './highlighter'
8
9
9
10
const EXCERPT_RADIUS = 80
10
11
const SEARCH_CONTAINER_SELECTOR = '#search'
@@ -24,7 +25,7 @@ lunr.Pipeline.registerFunction(docTrimmerFunction, 'docTrimmer')
24
25
window . addEventListener ( 'swup:page:view' , initialize )
25
26
initialize ( )
26
27
27
- function initialize ( ) {
28
+ function initialize ( ) {
28
29
const pathname = window . location . pathname
29
30
if ( pathname . endsWith ( '/search.html' ) || pathname . endsWith ( '/search' ) ) {
30
31
const query = getQueryParamByName ( 'q' )
@@ -33,7 +34,7 @@ function initialize () {
33
34
}
34
35
}
35
36
36
- async function search ( value , queryType ) {
37
+ async function search ( value , queryType ) {
37
38
if ( isBlank ( value ) ) {
38
39
renderResults ( { value } )
39
40
} else {
@@ -56,7 +57,7 @@ async function search (value, queryType) {
56
57
}
57
58
}
58
59
59
- async function localSearch ( value ) {
60
+ async function localSearch ( value ) {
60
61
const index = await getIndex ( )
61
62
62
63
// We cannot match on atoms :foo because that would be considered
@@ -65,7 +66,7 @@ async function localSearch (value) {
65
66
return searchResultsToDecoratedSearchItems ( index . search ( fixedValue ) )
66
67
}
67
68
68
- async function remoteSearch ( value , queryType , searchNodes ) {
69
+ async function remoteSearch ( value , queryType , searchNodes ) {
69
70
let filterNodes = searchNodes
70
71
71
72
if ( queryType === 'latest' ) {
@@ -86,7 +87,7 @@ async function remoteSearch (value, queryType, searchNodes) {
86
87
return payload . hits . map ( result => {
87
88
const [ packageName , packageVersion ] = result . document . package . split ( '-' )
88
89
89
- const doc = result . document . doc
90
+ const doc = highlightMatches ( result . document . doc , value , { multiline : true } )
90
91
const excerpts = [ doc ]
91
92
const metadata = { }
92
93
const ref = `https://hexdocs.pm/${ packageName } /${ packageVersion } /${ result . document . ref } `
@@ -107,13 +108,13 @@ async function remoteSearch (value, queryType, searchNodes) {
107
108
}
108
109
}
109
110
110
- function renderResults ( { value, results, errorMessage } ) {
111
+ function renderResults ( { value, results, errorMessage } ) {
111
112
const searchContainer = qs ( SEARCH_CONTAINER_SELECTOR )
112
113
const resultsHtml = searchResultsTemplate ( { value, results, errorMessage } )
113
114
searchContainer . innerHTML = resultsHtml
114
115
}
115
116
116
- async function getIndex ( ) {
117
+ async function getIndex ( ) {
117
118
const cachedIndex = await loadIndex ( )
118
119
if ( cachedIndex ) { return cachedIndex }
119
120
@@ -122,7 +123,7 @@ async function getIndex () {
122
123
return index
123
124
}
124
125
125
- async function loadIndex ( ) {
126
+ async function loadIndex ( ) {
126
127
try {
127
128
const serializedIndex = sessionStorage . getItem ( indexStorageKey ( ) )
128
129
if ( serializedIndex ) {
@@ -137,7 +138,7 @@ async function loadIndex () {
137
138
}
138
139
}
139
140
140
- async function saveIndex ( index ) {
141
+ async function saveIndex ( index ) {
141
142
try {
142
143
const serializedIndex = await compress ( index )
143
144
sessionStorage . setItem ( indexStorageKey ( ) , serializedIndex )
@@ -146,7 +147,7 @@ async function saveIndex (index) {
146
147
}
147
148
}
148
149
149
- async function compress ( index ) {
150
+ async function compress ( index ) {
150
151
const stream = new Blob ( [ JSON . stringify ( index ) ] , {
151
152
type : 'application/json'
152
153
} ) . stream ( ) . pipeThrough ( new window . CompressionStream ( 'gzip' ) )
@@ -156,7 +157,7 @@ async function compress (index) {
156
157
return b64encode ( buffer )
157
158
}
158
159
159
- async function decompress ( index ) {
160
+ async function decompress ( index ) {
160
161
const stream = new Blob ( [ b64decode ( index ) ] , {
161
162
type : 'application/json'
162
163
} ) . stream ( ) . pipeThrough ( new window . DecompressionStream ( 'gzip' ) )
@@ -165,7 +166,7 @@ async function decompress (index) {
165
166
return JSON . parse ( blob )
166
167
}
167
168
168
- function b64encode ( buffer ) {
169
+ function b64encode ( buffer ) {
169
170
let binary = ''
170
171
const bytes = new Uint8Array ( buffer )
171
172
const len = bytes . byteLength
@@ -175,7 +176,7 @@ function b64encode (buffer) {
175
176
return window . btoa ( binary )
176
177
}
177
178
178
- function b64decode ( str ) {
179
+ function b64decode ( str ) {
179
180
const binaryString = window . atob ( str )
180
181
const len = binaryString . length
181
182
const bytes = new Uint8Array ( new ArrayBuffer ( len ) )
@@ -185,11 +186,11 @@ function b64decode (str) {
185
186
return bytes
186
187
}
187
188
188
- function indexStorageKey ( ) {
189
+ function indexStorageKey ( ) {
189
190
return `idv5:${ getProjectNameAndVersion ( ) } `
190
191
}
191
192
192
- function createIndex ( ) {
193
+ function createIndex ( ) {
193
194
return lunr ( function ( ) {
194
195
this . ref ( 'ref' )
195
196
this . field ( 'title' , { boost : 3 } )
@@ -207,11 +208,11 @@ function createIndex () {
207
208
} )
208
209
}
209
210
210
- function docTokenSplitter ( builder ) {
211
+ function docTokenSplitter ( builder ) {
211
212
builder . pipeline . before ( lunr . stemmer , docTokenFunction )
212
213
}
213
214
214
- function docTokenFunction ( token ) {
215
+ function docTokenFunction ( token ) {
215
216
// If we have something with an arity, we split on : . to make partial
216
217
// matches easier. We split only when tokenizing, not when searching.
217
218
// Below we use ExDoc.Markdown.to_ast/2 as an example.
@@ -275,11 +276,11 @@ function docTokenFunction (token) {
275
276
return tokens
276
277
}
277
278
278
- function docTrimmer ( builder ) {
279
+ function docTrimmer ( builder ) {
279
280
builder . pipeline . before ( lunr . stemmer , docTrimmerFunction )
280
281
}
281
282
282
- function docTrimmerFunction ( token ) {
283
+ function docTrimmerFunction ( token ) {
283
284
// Preserve @ and : at the beginning of tokens,
284
285
// and ? and ! at the end of tokens. It needs to
285
286
// be done before stemming, otherwise search and
@@ -289,7 +290,7 @@ function docTrimmerFunction (token) {
289
290
} )
290
291
}
291
292
292
- function searchResultsToDecoratedSearchItems ( results ) {
293
+ function searchResultsToDecoratedSearchItems ( results ) {
293
294
return results
294
295
// If the docs are regenerated without changing its version,
295
296
// a reference may have been doc'ed false in the code but
@@ -306,11 +307,11 @@ function searchResultsToDecoratedSearchItems (results) {
306
307
} )
307
308
}
308
309
309
- function getSearchItemByRef ( ref ) {
310
+ function getSearchItemByRef ( ref ) {
310
311
return searchData . items . find ( searchItem => searchItem . ref === ref ) || null
311
312
}
312
313
313
- function getExcerpts ( searchItem , metadata ) {
314
+ function getExcerpts ( searchItem , metadata ) {
314
315
const { doc } = searchItem
315
316
const searchTerms = Object . keys ( metadata )
316
317
@@ -331,7 +332,7 @@ function getExcerpts (searchItem, metadata) {
331
332
return excerpts . slice ( 0 , 1 )
332
333
}
333
334
334
- function excerpt ( doc , sliceStart , sliceLength ) {
335
+ function excerpt ( doc , sliceStart , sliceLength ) {
335
336
const startPos = Math . max ( sliceStart - EXCERPT_RADIUS , 0 )
336
337
const endPos = Math . min ( sliceStart + sliceLength + EXCERPT_RADIUS , doc . length )
337
338
return [
0 commit comments