Skip to content

Commit 0362101

Browse files
committed
improve search script
- refactor code - add keyboard shortcuts - add support for opening results in new tab - scroll list to track cursor - prevent reordering of subheadings - accomodate bad search records - don't autoselect first entry
1 parent e7855d4 commit 0362101

File tree

1 file changed

+102
-41
lines changed

1 file changed

+102
-41
lines changed

src/js/vendor/docsearch.bundle.js

Lines changed: 102 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
;(function () {
22
'use strict'
33

4-
activateSearch(require('docsearch.js/dist/cdn/docsearch.js'), document.getElementById('search-script').dataset)
4+
var CTRL_KEY_CODE = 17
5+
var S_KEY_CODE = 83
6+
var SOLIDUS_KEY_CODE = 191
57

6-
var F_KEY = 70
7-
var S_KEY = 83
8+
activateSearch(require('docsearch.js/dist/cdn/docsearch.js'), document.getElementById('search-script').dataset)
89

910
function activateSearch (docsearch, config) {
1011
appendStylesheet(config.stylesheet)
@@ -13,49 +14,54 @@
1314
advancedSyntax: true,
1415
advancedSyntaxFeatures: ['exactPhrase'],
1516
}
16-
var searchForm = document.querySelector('form.search')
17+
var searchField = document.querySelector('form.search')
1718
var controller = docsearch({
1819
appId: config.appId,
1920
apiKey: config.apiKey,
2021
indexName: config.indexName,
21-
inputSelector: '#search-query',
22-
autocompleteOptions: { autoselect: true, debug: true, hint: false, keyboardShortcuts: [], minLength: 2 },
22+
inputSelector: 'form.search #search-query',
23+
autocompleteOptions: { autoselect: false, debug: true, hint: false, keyboardShortcuts: [], minLength: 2 },
2324
algoliaOptions: algoliaOptions,
24-
transformData: transformData,
25+
transformData: protectHitOrder,
2526
})
2627
var input = controller.input
27-
var autocomplete = input.autocomplete
28-
autocomplete.setVal()
29-
input.on('autocomplete:selected', disableClose)
30-
input.data('aaAutocomplete').dropdown._ensureVisible = ensureVisible
31-
searchForm.addEventListener('click', confineEvent)
32-
document.documentElement.addEventListener('click', resetSearch.bind(autocomplete))
33-
document.documentElement.addEventListener('keydown', handleShortcuts.bind(input))
28+
var typeahead = input.data('aaAutocomplete')
29+
var dropdown = typeahead.dropdown
30+
var menu = dropdown.$menu
31+
typeahead.setVal() // clear value on page reload
32+
input.on('autocomplete:closed', clearSearch.bind(typeahead))
33+
input.on('autocomplete:selected', onSuggestionSelected)
34+
input.on('autocomplete:updated', onResultsUpdated.bind(typeahead))
35+
dropdown._ensureVisible = ensureVisible
36+
menu.off('mousedown.aa')
37+
menu.off('mouseenter.aa')
38+
menu.off('mouseleave.aa')
39+
var suggestionSelector = '.' + dropdown.cssClasses.prefix + dropdown.cssClasses.suggestion
40+
menu.on('mousedown.aa', suggestionSelector, onSuggestionMouseDown.bind(dropdown))
41+
monitorCtrlKey.call(typeahead)
42+
searchField.addEventListener('click', confineEvent)
43+
document.documentElement.addEventListener('click', clearSearch.bind(typeahead))
44+
document.addEventListener('keydown', handleShortcuts.bind(typeahead))
3445
if (input.attr('autofocus') != null) input.focus()
3546
}
3647

3748
function appendStylesheet (href) {
3849
document.head.appendChild(Object.assign(document.createElement('link'), { rel: 'stylesheet', href: href }))
3950
}
4051

41-
function confineEvent (e) {
42-
e.stopPropagation()
52+
function onResultsUpdated () {
53+
if (!isClosed(this)) getScrollableResultsContainer(this.dropdown).scrollTop(0)
4354
}
4455

45-
function disableClose (e) {
46-
e.isDefaultPrevented = function () {
47-
return true
48-
}
56+
function confineEvent (e) {
57+
e.stopPropagation()
4958
}
5059

5160
function ensureVisible (el) {
52-
var item = el.get(0)
53-
var container = item
54-
while ((container = container.parentNode) && container !== document.documentElement) {
55-
if (window.getComputedStyle(container).overflowY === 'auto') break
56-
}
57-
if (!container || container.scrollHeight === container.offsetHeight) return
61+
var container = getScrollableResultsContainer(this)[0]
62+
if (container.scrollHeight === container.offsetHeight) return
5863
var delta
64+
var item = el[0]
5965
if ((delta = 15 + item.offsetTop + item.offsetHeight - (container.offsetHeight + container.scrollTop)) > 0) {
6066
container.scrollTop += delta
6167
}
@@ -64,30 +70,85 @@
6470
}
6571
}
6672

73+
function getScrollableResultsContainer (dropdown) {
74+
var suggestionsSelector = '.' + dropdown.cssClasses.prefix + dropdown.cssClasses.suggestions
75+
return dropdown.datasets[0].$el.find(suggestionsSelector)
76+
}
77+
6778
function handleShortcuts (e) {
68-
if (e.altKey || e.metaKey || e.ctrlKey || e.shiftKey) return
69-
var keyCode = e.keyCode
70-
if (keyCode === F_KEY || keyCode === S_KEY) {
71-
this.focus()
79+
var target = e.target || {}
80+
if (e.altKey || e.shiftKey || target.isContentEditable || 'disabled' in target) return
81+
if (e.ctrlKey ? e.keyCode === SOLIDUS_KEY_CODE : e.keyCode === S_KEY_CODE) {
82+
this.$input.focus()
7283
e.preventDefault()
84+
e.stopPropagation()
85+
}
86+
}
87+
88+
function isClosed (typeahead) {
89+
var query = typeahead.getVal()
90+
return !query || query !== typeahead.dropdown.datasets[0].query
91+
}
92+
93+
function monitorCtrlKey () {
94+
this.$input.on('keydown', onCtrlKeyDown.bind(this))
95+
this.dropdown.$container.on('keyup', onCtrlKeyUp.bind(this))
96+
}
97+
98+
function onCtrlKeyDown (e) {
99+
if (e.keyCode !== CTRL_KEY_CODE) return
100+
var dropdown = this.dropdown
101+
var container = getScrollableResultsContainer(dropdown)
102+
var prevScrollTop = container.scrollTop()
103+
dropdown.getCurrentCursor().find('a').focus()
104+
container.scrollTop(prevScrollTop) // calling focus can cause the container to scroll, so restore it
105+
}
106+
107+
function onCtrlKeyUp (e) {
108+
if (e.keyCode !== CTRL_KEY_CODE) return
109+
this.$input.focus()
110+
}
111+
112+
function onSuggestionMouseDown (e) {
113+
var dropdown = this
114+
var suggestion = dropdown._getSuggestions().filter('#' + e.currentTarget.id)
115+
if (suggestion[0] === dropdown._getCursor()[0]) return
116+
dropdown._removeCursor()
117+
dropdown._setCursor(suggestion, false)
118+
}
119+
120+
function onSuggestionSelected (e) {
121+
e.isDefaultPrevented = function () {
122+
return true
73123
}
74124
}
75125

76-
function resetSearch () {
77-
this.close()
126+
function clearSearch () {
78127
this.setVal()
79128
}
80129

81-
// qualify separate occurrences of the same lvl0 title so that the order of results is preserved
82-
function transformData (hits) {
83-
var prevLvl0Title
84-
var qualifiers = {}
130+
// preserves the original order of results by qualifying unique occurrences of the same lvl0 and lvl1 values
131+
function protectHitOrder (hits) {
132+
var prevLvl0
133+
var lvl0Qualifiers = {}
134+
var lvl1Qualifiers = {}
85135
return hits.map(function (hit) {
86-
var lvl0Title = hit.hierarchy.lvl0
87-
var qualifier = qualifiers[lvl0Title]
88-
if (lvl0Title !== prevLvl0Title) qualifiers[lvl0Title] = qualifier == null ? '' : (qualifier += ' ')
89-
if (qualifier) hit.hierarchy.lvl0 = lvl0Title + qualifier
90-
prevLvl0Title = lvl0Title
136+
var lvl0 = hit.hierarchy.lvl0
137+
var lvl1 = hit.hierarchy.lvl1
138+
if (!lvl0) lvl0 = hit.hierarchy.lvl0 = hit.component + (hit.version === 'master' ? '' : ' ' + hit.version)
139+
if (!lvl1) lvl1 = hit.hierarchy.lvl1 = lvl0
140+
var lvl0Qualifier = lvl0Qualifiers[lvl0]
141+
if (lvl0 !== prevLvl0) {
142+
lvl0Qualifiers[lvl0] = lvl0Qualifier == null ? (lvl0Qualifier = '') : (lvl0Qualifier += ' ')
143+
lvl1Qualifiers = {}
144+
}
145+
if (lvl0Qualifier) hit.hierarchy.lvl0 = lvl0 + lvl0Qualifier
146+
if (lvl1 in lvl1Qualifiers) {
147+
hit.hierarchy.lvl1 = lvl1 + (lvl1Qualifiers[lvl1] += ' ')
148+
} else {
149+
lvl1Qualifiers[lvl1] = ''
150+
}
151+
prevLvl0 = lvl0
91152
return hit
92153
})
93154
}

0 commit comments

Comments
 (0)