diff --git a/src/css/vendor/search.css b/src/css/vendor/search.css index 55319e4..88541c3 100644 --- a/src/css/vendor/search.css +++ b/src/css/vendor/search.css @@ -289,6 +289,20 @@ a.ais-Hits-item:hover { border-color: var(--nav-panel-divider-color); } +#hits ul li a.selected { + background: #6db33f; + color: #fff; +} + +#hits ul li a.selected .hit-description, +#hits ul li a.selected .hit-breadcrumbs { + color: #fff; +} + +#hits ul li a.selected mark { + background-color: white; +} + @media screen and (max-width: 1023.5px) { .DocSearch-Button { border-left-width: 1px; diff --git a/src/js/vendor/search.bundle.js b/src/js/vendor/search.bundle.js index 25b43b4..46cf32d 100644 --- a/src/js/vendor/search.bundle.js +++ b/src/js/vendor/search.bundle.js @@ -5,6 +5,7 @@ const config = document.getElementById('search-script').dataset const client = algoliasearch(config.appId, config.apiKey) + let selected = null const search = instantsearch({ indexName: config.indexName, @@ -45,6 +46,7 @@ const { hits, showMore, widgetParams } = renderArgs const { container } = widgetParams lastRenderArgs = renderArgs + selected = null if (isFirstRender) { const sentinel = document.createElement('div') container.appendChild(document.createElement('ul')) @@ -127,6 +129,87 @@ } }) + const selectHit = (newSelected) => { + const hits = document.querySelectorAll('#hits>ul>li>a') + if (hits[selected]) { + hits[selected].classList.remove('selected') + selected = null + } + if (hits[newSelected]) { + hits[newSelected].classList.add('selected') + selected = newSelected + } + + if (selected) { + hits[selected].scrollIntoView() + } + } + + const openHit = (index) => { + const hits = document.querySelectorAll('#hits>ul>li>a') + if (hits[index]) { + hits[index].click() + } + } + + const renderSearchBox = (renderOptions, isFirstRender) => { + const { query, refine, clear, isSearchStalled, widgetParams } = renderOptions + if (isFirstRender) { + const input = document.createElement('input') + input.classList.add('ais-SearchBox-input') + input.placeholder = `Search in the current documentation ${config.pageVersion}` + const loadingIndicator = document.createElement('span') + loadingIndicator.textContent = 'Loading...' + const button = document.createElement('button') + button.classList.add('ais-SearchBox-reset') + button.innerHTML = '' + input.addEventListener('keydown', (event) => { + switch (event.keyCode) { + case 40: // Down + event.preventDefault() + if (selected === null) { + selectHit(0) + } else { + selectHit(selected + 1) + } + break + case 38: // Up + event.preventDefault() + if (selected === null) { + selectHit(0) + } + selectHit(Math.max(selected - 1, 0)) + break + case 13: // Enter + event.preventDefault() + if (selected !== null) { + openHit(selected) + } + break + case 9: // Tab + event.preventDefault() + break + } + }) + input.addEventListener('input', (event) => { + refine(event.target.value) + }) + button.addEventListener('click', () => { + clear() + }) + widgetParams.container.appendChild(input) + widgetParams.container.appendChild(loadingIndicator) + widgetParams.container.appendChild(button) + } + widgetParams.container.querySelector('input').value = query + widgetParams.container.querySelector('span').hidden = !isSearchStalled + } + + const searchBox = instantsearch.connectors.connectSearchBox( + renderSearchBox + ) + + // selected search.addWidgets([ instantsearch.widgets.configure({ facetFilters: [`version:${config.pageVersion}`], @@ -134,12 +217,8 @@ attributesToHighlight: ['hierarchy'], distinct: true, }), - instantsearch.widgets.searchBox({ - container: '#searchbox', - autofocus: true, - showSubmit: false, - showReset: true, - placeholder: `Search in the current documentation ${config.pageVersion}`, + searchBox({ + container: document.querySelector('#searchbox'), }), infiniteHits({ container: document.querySelector('#hits'), @@ -149,6 +228,7 @@ search.start() const open = () => { + selectHit(null) MicroModal.show('modal-1', { disableScroll: true, })