(() => { document.addEventListener('DOMContentLoaded', () => { // // Search field behaviors // const elInput = document.getElementById('search-input'); const elResults = document.getElementById('search-results')?.querySelector?.('.top-banner-search--field-with-results--field--wrapper--search-component--search-results-wrapper') ?? null; const elOverlay = document.querySelector('.top-banner-search--overlay'); const elSpinner = document.querySelector('.top-banner-search--field-with-results--field--wrapper--search-component--search-spinner'); if (!elInput || !elResults || !elOverlay) return; const CLASSNAME_SPINNING = 'spinning'; const CLASSNAME_HIGHLIGHTED = 'highlighted'; const canSmoothScroll = 'scrollBehavior' in document.documentElement.style; const docsVersion = elInput.getAttribute('data-docs-version'); let _showingResults = false, animationFrame, debounceTimer, lastQuery; const abortControllers = []; elInput.addEventListener('input', e => { debounceInput(); }); elInput.addEventListener('keydown', e => { switch (e.key) { case 'Esc': case 'Escape': hideResults(true); elInput.value = ''; break; case 'ArrowUp': e.preventDefault(); highlightNextResult(false); break; case 'ArrowDown': e.preventDefault(); highlightNextResult(); break; case 'Enter': e.preventDefault(); navToHighlightedResult(); break; } }); elInput.addEventListener('focus', e => { if (!_showingResults && elResults.textContent) showResults(); }); elResults.addEventListener('pointerenter', e => { cancelAnimationFrame(animationFrame); animationFrame = requestAnimationFrame(() => { highlightResult(e.target?.closest('.top-banner-search--field-with-results--field--wrapper--search-component--search-results--result')); }); }, true); elResults.addEventListener('focus', e => { highlightResult(e.target?.closest('.top-banner-search--field-with-results--field--wrapper--search-component--search-results--result')); }, true); const debounceInput = () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(doSearch, 300); }; const abortPreviousCalls = () => { while (abortControllers.length) abortControllers.pop()?.abort?.(); }; const getBreadcrumbs = result => { const crumbs = [...result.ancestors]; if (result.type === 'DOCS') crumbs.unshift(`OpenSearch ${result.versionLabel || result.version}`); else if (result.type) crumbs.unshift(result.type); return sanitizeText(crumbs.join(' › ')); } const doSearch = async () => { const query = elInput.value.replace(/[^a-z0-9-_. ]+/ig, ' '); if (query.length < 3) return hideResults(true); if (query === lastQuery) return; recordEvent('search', { search_term: query, docs_version: docsVersion }); lastQuery = query; abortPreviousCalls(); elSpinner?.classList.add(CLASSNAME_SPINNING); if (!_showingResults) document.documentElement.classList.add('search-active'); try { const controller = new AbortController(); abortControllers.unshift(abortControllers); const startTime = Date.now(); const response = await fetch(`https://search-api.opensearch.org/search?q=${query}&v=${docsVersion}`, { signal: controller.signal }); const data = await response.json(); const searchResultClassName = 'top-banner-search--field-with-results--field--wrapper--search-component--search-results--result'; recordEvent('view_search_results', { search_term: query, docs_version: docsVersion, duration: Date.now() - startTime, results_num: data?.results?.length || 0 }); if (!Array.isArray(data?.results) || data.results.length === 0) { return showNoResults(); } const chunks = data.results.map(result => result ? `
${getBreadcrumbs(result)} ${sanitizeText(result.title || 'Unnamed Document')} ${sanitizeText(result.content?.replace?.(/\n/g, '… '))}
` : '' ); emptyResults(); elResults.appendChild(document.createRange().createContextualFragment(chunks.join(''))); showResults(); } catch (ex) { showNoResults(); } elSpinner?.classList.remove(CLASSNAME_SPINNING); } const hideResults = destroy => { _showingResults = false; elSpinner?.classList.remove(CLASSNAME_SPINNING); document.documentElement.classList.remove('search-active'); elResults.setAttribute('aria-expanded', 'false'); document.body.removeEventListener('pointerdown', handlePointerDown, false); if (destroy) { abortPreviousCalls(); emptyResults(); lastQuery = ''; } }; const showResults = () => { if (!_showingResults) { _showingResults = true; document.documentElement.classList.add('search-active'); elResults.setAttribute('aria-expanded', 'true'); document.body.addEventListener('pointerdown', handlePointerDown, false); } elResults.scrollTo(0, 0); }; const showNoResults = () => { emptyResults(); elResults.appendChild(document.createRange().createContextualFragment('No results found!')); showResults(); elSpinner?.classList.remove(CLASSNAME_SPINNING); }; const emptyResults = () => { //ToDo: Replace with `elResults.replaceChildren();` when https://caniuse.com/?search=replaceChildren shows above 90% can use it while (elResults.firstChild) elResults.firstChild.remove(); }; const sanitizeText = text => { return text?.replace?.(/ { return text?.replace?.(/[>"]+/g, ''); }; const handlePointerDown = e => { const matchSelectors = [ '.top-banner-search--field-with-results--field--wrapper--search-component--input-wrap', '.top-banner-search--field-with-results--field--wrapper--search-component--input-wrap *', '.top-banner-search--field-with-results--field--wrapper--search-component--search-results', '.top-banner-search--field-with-results--field--wrapper--search-component--search-results *', ].join(', '); if (e.target.matches(matchSelectors)) return; e.preventDefault(); elInput.blur(); hideResults(); }; const highlightResult = node => { const searchResultClassName = 'top-banner-search--field-with-results--field--wrapper--search-component--search-results--result'; if (!node || !_showingResults || node.classList.contains(CLASSNAME_HIGHLIGHTED)) return; elResults.querySelectorAll(`.${searchResultClassName}.highlighted`).forEach(el => { el.classList.remove(CLASSNAME_HIGHLIGHTED); }); node.classList.add(CLASSNAME_HIGHLIGHTED); elInput.focus(); }; const highlightNextResult = (down = true) => { const searchResultClassName = 'top-banner-search--field-with-results--field--wrapper--search-component--search-results--result'; const highlighted = elResults.querySelector(`.${searchResultClassName}.highlighted`); let nextResult; if (highlighted) { highlighted.classList.remove(CLASSNAME_HIGHLIGHTED); nextResult = highlighted[down ? 'nextElementSibling' : 'previousElementSibling'] } else { nextResult = elResults.querySelector(`.${searchResultClassName}:${down ? 'first' : 'last'}-child`); } if (nextResult) { nextResult.classList.add(CLASSNAME_HIGHLIGHTED); if (down) { if (canSmoothScroll) { nextResult.scrollIntoView({ behavior: "smooth", block: "end" }); } else { nextResult.scrollIntoView(false) } } else if ( nextResult.offsetTop < elResults.scrollTop || nextResult.offsetTop + nextResult.clientHeight > elResults.scrollTop + elResults.clientHeight ) { if (canSmoothScroll) { elResults.scrollTo({ behavior: "smooth", top: nextResult.offsetTop, left: 0 }); } else { elResults.scrollTo(0, nextResult.offsetTop); } } } else { elResults.scrollTo(0, 0); } }; const navToHighlightedResult = () => { const searchResultClassName = 'top-banner-search--field-with-results--field--wrapper--search-component--search-results--result'; elResults.querySelector(`.${searchResultClassName}.highlighted a[href]`)?.click?.(); }; const recordEvent = (name, data) => { try { gtag?.('event', name, data); } catch (e) { // Do nothing } }; }); })();