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