astephanus ad8df5c9ad
Redesigned header / footer integration copied from project site. (#5702)
* Adds remote loading of header component from project site

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Integrates the redesigned header; removes the old copy-banner and search field

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Updates footer design and menu items

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Consolidates SCSS mixins and color palette

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Reduce classname verbosity slightly

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Removes redundant mobile menu button handler

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Corects spacing issue

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Corrects nav menu margins

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Corrects header logo clipping

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Updates navigation menu items

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Corrects top level navigation hover states

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Adds smoother responsive margins between top navigation header menu items

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

* Remove deleted SCSS file

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>

---------

Signed-off-by: Aaron Stephanus <taoist.futility@pm.me>
2023-12-12 15:59:47 -08:00

264 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(() => {
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
? `
<div class="${searchResultClassName}">
<a href="${sanitizeAttribute(result.url)}">
<cite>${getBreadcrumbs(result)}</cite>
${sanitizeText(result.title || 'Unnamed Document')}
</a>
<span>${sanitizeText(result.content?.replace?.(/\n/g, '&hellip; '))}</span>
</div>
`
: ''
);
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('<span>No results found!</span>'));
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?.(/</g, '&lt;');
};
const sanitizeAttribute = text => {
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
}
};
});
})();