FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
github-docs/javascripts/search.js at main · 0xdevalias/github-docs · GitHub
0xdevalias
/
github-docs
Public
forked from
github/docs
Notifications
You must be signed in to change notification settings
Fork
0
Star
1
Code
Pull requests
0
Actions
Projects
Security and quality
0
Insights
Additional navigation options
Code
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
github-docs
/
javascripts
/
search.js
Copy path
More file actions
More file actions
Latest commit
History
History
History
286 lines (243 loc) · 8.65 KB
Breadcrumbs
github-docs
/
javascripts
/
search.js
Copy path
File metadata and controls
286 lines (243 loc) · 8.65 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
import
{
tags
}
from
'./hyperscript'
import
{
sendEvent
}
from
'./events'
import
searchWithYourKeyboard
from
'search-with-your-keyboard'
let
$searchInputContainer
let
$searchResultsContainer
let
$searchOverlay
let
$searchInput
// This is our default placeholder, but it can be localized with a <meta> tag
let
placeholder
=
'Search topics, products...'
let
version
let
language
export
default
function
search
(
)
{
// First, only initialize search if the elements are on the page
$searchInputContainer
=
document
.
getElementById
(
'search-input-container'
)
$searchResultsContainer
=
document
.
getElementById
(
'search-results-container'
)
if
(
!
$searchInputContainer
||
!
$searchResultsContainer
)
return
// This overlay exists so if you click off the search, it closes
$searchOverlay
=
document
.
querySelector
(
'.search-overlay-desktop'
)
// There's an index for every version/language combination
const
{
languages
,
versions
,
nonEnterpriseDefaultVersion
}
=
JSON
.
parse
(
document
.
getElementById
(
'search-options'
)
.
text
)
version
=
deriveVersionFromPath
(
versions
,
nonEnterpriseDefaultVersion
)
language
=
deriveLanguageCodeFromPath
(
languages
)
// Find search placeholder text in a <meta> tag, falling back to a default
const
$placeholderMeta
=
document
.
querySelector
(
'meta[name="site.data.ui.search.placeholder"]'
)
if
(
$placeholderMeta
)
{
placeholder
=
$placeholderMeta
.
content
}
// Write the search form into its container
$searchInputContainer
.
append
(
tmplSearchInput
(
)
)
$searchInput
=
$searchInputContainer
.
querySelector
(
'input'
)
// Prevent 'enter' from refreshing the page
$searchInputContainer
.
querySelector
(
'form'
)
.
addEventListener
(
'submit'
,
evt
=>
evt
.
preventDefault
(
)
)
// Search when the user finished typing
$searchInput
.
addEventListener
(
'keyup'
,
debounce
(
onSearch
)
)
// Adds ability to navigate search results with keyboard (up, down, enter, esc)
searchWithYourKeyboard
(
'#search-input-container input'
,
'.ais-Hits-item'
)
// If the user already has a query in the URL, parse it and search away
parseExistingSearch
(
)
// If not on home page, decide if search panel should be open
toggleSearchDisplay
(
)
// must come after parseExistingSearch
}
// The home page and 404 pages have a standalone search
function
hasStandaloneSearch
(
)
{
return
document
.
getElementById
(
'landing'
)
||
document
.
querySelector
(
'body.error-404'
)
!==
null
}
function
toggleSearchDisplay
(
)
{
// Clear/close search, if ESC is clicked
document
.
addEventListener
(
'keyup'
,
(
e
)
=>
{
if
(
e
.
key
===
'Escape'
)
{
closeSearch
(
)
}
}
)
// If not on homepage...
if
(
hasStandaloneSearch
(
)
)
return
// Open panel if input is clicked
$searchInput
.
addEventListener
(
'focus'
,
openSearch
)
// Close panel if overlay is clicked
if
(
$searchOverlay
)
{
$searchOverlay
.
addEventListener
(
'click'
,
closeSearch
)
}
// Open panel if page loads with query in the params/input
if
(
$searchInput
.
value
)
{
openSearch
(
)
}
}
// On most pages, opens the search panel
function
openSearch
(
)
{
$searchInput
.
classList
.
add
(
'js-open'
)
$searchResultsContainer
.
classList
.
add
(
'js-open'
)
$searchOverlay
.
classList
.
add
(
'js-open'
)
}
// Close panel if not on homepage
function
closeSearch
(
)
{
if
(
!
hasStandaloneSearch
(
)
)
{
$searchInput
.
classList
.
remove
(
'js-open'
)
$searchResultsContainer
.
classList
.
remove
(
'js-open'
)
$searchOverlay
.
classList
.
remove
(
'js-open'
)
}
$searchInput
.
value
=
''
onSearch
(
)
}
function
deriveLanguageCodeFromPath
(
languageCodes
)
{
let
languageCode
=
location
.
pathname
.
split
(
'/'
)
[
1
]
if
(
!
languageCodes
.
includes
(
languageCode
)
)
languageCode
=
'en'
return
languageCode
}
function
deriveVersionFromPath
(
allVersions
,
nonEnterpriseDefaultVersion
)
{
// fall back to the non-enterprise default version (FPT currently) on the homepage, 404 page, etc.
const
versionStr
=
location
.
pathname
.
split
(
'/'
)
[
2
]
||
nonEnterpriseDefaultVersion
return
allVersions
[
versionStr
]
||
allVersions
[
nonEnterpriseDefaultVersion
]
}
// Wait for the event to stop triggering for X milliseconds before responding
function
debounce
(
fn
,
delay
=
300
)
{
let
timer
return
(
...
args
)
=>
{
clearTimeout
(
timer
)
timer
=
setTimeout
(
(
)
=>
fn
.
apply
(
null
,
args
)
,
delay
)
}
}
// When the user finishes typing, update the results
async
function
onSearch
(
)
{
const
query
=
$searchInput
.
value
// Update the URL with the search parameters in the query string
const
pushUrl
=
new
URL
(
location
)
pushUrl
.
search
=
query
?
new
URLSearchParams
(
{
query
}
)
:
''
history
.
pushState
(
{
}
,
''
,
pushUrl
)
// If there's a query, call the endpoint
// Otherwise, there's no results by default
let
results
=
[
]
if
(
query
.
trim
(
)
)
{
const
endpointUrl
=
new
URL
(
location
.
origin
)
endpointUrl
.
pathname
=
'/search'
endpointUrl
.
search
=
new
URLSearchParams
(
{
language
,
version
,
query
}
)
const
response
=
await
fetch
(
endpointUrl
,
{
method
:
'GET'
,
headers
:
{
'Content-Type'
:
'application/json'
}
}
)
results
=
response
.
ok
?
await
response
.
json
(
)
:
[
]
}
// Either way, update the display
$searchResultsContainer
.
querySelectorAll
(
'*'
)
.
forEach
(
el
=>
el
.
remove
(
)
)
$searchResultsContainer
.
append
(
tmplSearchResults
(
results
)
)
toggleStandaloneSearch
(
)
// Analytics tracking
if
(
query
.
trim
(
)
)
{
sendEvent
(
{
type
:
'search'
,
search_query
:
query
// search_context
}
)
}
}
// If on homepage, toggle results container if query is present
function
toggleStandaloneSearch
(
)
{
if
(
!
hasStandaloneSearch
(
)
)
return
const
query
=
$searchInput
.
value
const
queryPresent
=
query
&&
query
.
length
>
0
const
$results
=
document
.
querySelector
(
'.ais-Hits'
)
// Primer classNames for showing and hiding the results container
const
activeClass
=
$searchResultsContainer
.
getAttribute
(
'data-active-class'
)
const
inactiveClass
=
$searchResultsContainer
.
getAttribute
(
'data-inactive-class'
)
if
(
!
activeClass
)
{
console
.
error
(
'container is missing required `data-active-class` attribute'
,
$searchResultsContainer
)
return
}
if
(
!
inactiveClass
)
{
console
.
error
(
'container is missing required `data-inactive-class` attribute'
,
$searchResultsContainer
)
return
}
// hide the container when no query is present
$searchResultsContainer
.
classList
.
toggle
(
activeClass
,
queryPresent
)
$searchResultsContainer
.
classList
.
toggle
(
inactiveClass
,
!
queryPresent
)
if
(
queryPresent
&&
$results
)
$results
.
style
.
display
=
'block'
}
// If the user shows up with a query in the URL, go ahead and search for it
function
parseExistingSearch
(
)
{
const
params
=
new
URLSearchParams
(
location
.
search
)
if
(
!
params
.
has
(
'query'
)
)
return
$searchInput
.
value
=
params
.
get
(
'query'
)
onSearch
(
)
}
/** * Template functions ***/
function
tmplSearchInput
(
)
{
// only autofocus on the homepage, and only if no #hash is present in the URL
const
autofocus
=
(
hasStandaloneSearch
(
)
&&
!
location
.
hash
.
length
)
||
null
const
{
div
,
form
,
input
,
button
}
=
tags
return
div
(
{
class
:
'ais-SearchBox'
}
,
form
(
{
role
:
'search'
,
class
:
'ais-SearchBox-form'
,
novalidate
:
true
}
,
input
(
{
class
:
'ais-SearchBox-input'
,
type
:
'search'
,
placeholder
,
autofocus
,
autocomplete
:
'off'
,
autocorrect
:
'off'
,
autocapitalize
:
'off'
,
spellcheck
:
'false'
,
maxlength
:
'512'
}
)
,
button
(
{
class
:
'ais-SearchBox-submit'
,
type
:
'submit'
,
title
:
'Submit the search query.'
,
hidden
:
true
}
)
)
)
}
function
tmplSearchResults
(
items
)
{
const
{
div
,
ol
,
li
}
=
tags
return
div
(
{
class
:
'ais-Hits'
,
style
:
'display:block'
}
,
ol
(
{
class
:
'ais-Hits-list'
}
,
items
.
map
(
item
=>
li
(
{
class
:
'ais-Hits-item'
}
,
tmplSearchResult
(
item
)
)
)
)
)
}
function
tmplSearchResult
(
{
url
,
breadcrumbs
,
heading
,
title
,
content
}
)
{
const
{
div
,
a
}
=
tags
return
div
(
{
class
:
'search-result border-top border-gray-light py-3 px-2'
}
,
a
(
{
href
:
url
,
class
:
'no-underline'
}
,
div
(
{
class
:
'search-result-breadcrumbs d-block text-gray-dark opacity-60 text-small pb-1'
}
,
// Remove redundant title from the end of breadcrumbs
markify
(
(
breadcrumbs
||
''
)
.
replace
(
` /
${
title
}
`
,
''
)
)
)
,
div
(
{
class
:
'search-result-title d-block h4-mktg text-gray-dark'
}
,
// Display page title and heading (if present exists)
markify
(
heading
?
`
${
title
}
:
${
heading
}
`
:
title
)
)
,
div
(
{
class
:
'search-result-content d-block text-gray'
}
,
markify
(
content
)
)
)
)
}
// Convert mark tags in search responses
function
markify
(
text
)
{
const
{
mark
}
=
tags
return
text
.
split
(
/
<
\/
?
m
a
r
k
>
/
g
)
.
map
(
(
el
,
i
)
=>
i
%
2
?
mark
(
el
)
:
el
)
}
Back
|
FazBrowse Home
|
New Git URL