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