@@ -4,6 +4,7 @@ import lunr from 'lunr'
4
4
import { qs , escapeHtmlEntities , isBlank , getQueryParamByName , getProjectNameAndVersion } from './helpers'
5
5
import { setSearchInputValue } from './search-bar'
6
6
import searchResultsTemplate from './handlebars/templates/search-results.handlebars'
7
+ import { getSearchNodes } from './globals'
7
8
8
9
const EXCERPT_RADIUS = 80
9
10
const SEARCH_CONTAINER_SELECTOR = '#search'
@@ -27,30 +28,85 @@ 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' )
30
- search ( query )
31
+ const queryType = getQueryParamByName ( 'type' )
32
+ search ( query , queryType )
31
33
}
32
34
}
33
35
34
- async function search ( value ) {
36
+ async function search ( value , queryType ) {
35
37
if ( isBlank ( value ) ) {
36
38
renderResults ( { value } )
37
39
} else {
38
40
setSearchInputValue ( value )
39
41
40
- const index = await getIndex ( )
41
-
42
42
try {
43
- // We cannot match on atoms :foo because that would be considered
44
- // a filter. So we escape all colons not preceded by a word.
45
- const fixedValue = value . replaceAll ( / ( \B | \\ ) : / g, '\\:' )
46
- const results = searchResultsToDecoratedSearchItems ( index . search ( fixedValue ) )
43
+ let results = [ ]
44
+ const searchNodes = getSearchNodes ( )
45
+
46
+ if ( [ 'related' , 'latest' ] . includes ( queryType ) && searchNodes . length > 0 ) {
47
+ results = await remoteSearch ( value , queryType , searchNodes )
48
+ } else {
49
+ results = await localSearch ( value )
50
+ }
51
+
47
52
renderResults ( { value, results } )
48
53
} catch ( error ) {
49
54
renderResults ( { value, errorMessage : error . message } )
50
55
}
51
56
}
52
57
}
53
58
59
+ async function localSearch ( value ) {
60
+ const index = await getIndex ( )
61
+
62
+ // We cannot match on atoms :foo because that would be considered
63
+ // a filter. So we escape all colons not preceded by a word.
64
+ const fixedValue = value . replaceAll ( / ( \B | \\ ) : / g, '\\:' )
65
+ return searchResultsToDecoratedSearchItems ( index . search ( fixedValue ) )
66
+ }
67
+
68
+ async function remoteSearch ( value , queryType , searchNodes ) {
69
+ let filterNodes = searchNodes
70
+
71
+ if ( queryType === 'latest' ) {
72
+ filterNodes = searchNodes . slice ( 0 , 1 )
73
+ }
74
+
75
+ const filters = filterNodes . map ( node => `package:=${ node . name } -${ node . version } ` ) . join ( ' || ' )
76
+
77
+ const params = new URLSearchParams ( )
78
+ params . set ( 'q' , value )
79
+ params . set ( 'query_by' , 'title,doc' )
80
+ params . set ( 'filter_by' , filters )
81
+
82
+ const response = await fetch ( `https://search.hexdocs.pm/?${ params . toString ( ) } ` )
83
+ const payload = await response . json ( )
84
+
85
+ if ( Array . isArray ( payload . hits ) ) {
86
+ return payload . hits . map ( result => {
87
+ const [ packageName , packageVersion ] = result . document . package . split ( '-' )
88
+
89
+ const doc = result . document . doc
90
+ const excerpts = [ doc ]
91
+ const metadata = { }
92
+ const ref = `https://hexdocs.pm/${ packageName } /${ packageVersion } /${ result . document . ref } `
93
+ const title = result . document . title
94
+ const type = result . document . type
95
+
96
+ return {
97
+ doc,
98
+ excerpts,
99
+ metadata,
100
+ ref,
101
+ title,
102
+ type
103
+ }
104
+ } )
105
+ } else {
106
+ return [ ]
107
+ }
108
+ }
109
+
54
110
function renderResults ( { value, results, errorMessage } ) {
55
111
const searchContainer = qs ( SEARCH_CONTAINER_SELECTOR )
56
112
const resultsHtml = searchResultsTemplate ( { value, results, errorMessage } )
0 commit comments