blob: 1aef94150487b655f6a3dd445fed37e62e45ac0b [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
window.pw = {};
// Display inline search results under the search modal. After the user types
// text in the search box, results are shown underneath the text input box.
// The search is restarted after each keypress.
//
// TODO: b/363034219 - Try to upstream this code into pydata-sphinx-theme.
window.pw.initSearch = () => {
// Don't interfere with the default search UX on /search.html.
if (window.location.pathname.endsWith('/search.html')) {
return;
}
// The template //docs/layout/page.html ensures that Search is always
// loaded at this point.
// eslint-disable-next-line no-undef
if (!Search) {
return;
}
// Destroy the previous search container and create a new one.
window.pw.resetSearchResults();
let timeoutId = null;
let lastQuery = '';
const searchInput = document.querySelector('#search-input');
// Set up the event handler to initiate searches whenever the user
// types stuff in the search modal textbox.
searchInput.addEventListener('keyup', () => {
const query = searchInput.value;
// Don't search when there's nothing in the query textbox.
if (query === '') {
return;
}
// Don't search if there is no detectable change between
// the last query and the current query. E.g. user presses
// Tab to start navigating the search results.
if (query === lastQuery) {
return;
}
// Debounce so that the search only starts only when the
// user stops typing.
const delay_ms = 500;
lastQuery = query;
if (timeoutId) {
window.clearTimeout(timeoutId);
}
timeoutId = window.setTimeout(() => {
// The user has changed the search query. Delete the old results.
window.pw.resetSearchResults();
// eslint-disable-next-line no-undef
Search.performSearch(query);
timeoutId = null;
}, delay_ms);
});
};
// Resets the custom search results container to an empty state.
//
// Note that Sphinx assumes that searches are always made from /search.html
// so there's some safeguard logic to make sure the inline search always
// works no matter what pigweed.dev page you're on. b/365179592
//
// TODO: b/363034219 - Try to upstream this code into pydata-sphinx-theme.
window.pw.resetSearchResults = () => {
let results = document.querySelector('#search-results');
if (results) {
results.remove();
}
results = document.createElement('section');
results.classList.add('pw-search-results');
results.id = 'search-results';
// The template //docs/layout/page.html ensures that DOCUMENTATION_OPTIONS
// is always loaded at this point.
// eslint-disable-next-line no-undef
if (!DOCUMENTATION_OPTIONS || !DOCUMENTATION_OPTIONS.URL_ROOT) {
return;
}
// eslint-disable-next-line no-undef
const urlRoot = DOCUMENTATION_OPTIONS.URL_ROOT;
// As Sphinx populates the search results, this observer makes sure that
// each URL is correct (i.e. doesn't 404). b/365179592
const linkObserver = new MutationObserver(() => {
const links = Array.from(
document.querySelectorAll('#search-results .search a'),
);
// Check every link every time because the timing of when new results are
// added is unpredictable and it's not an expensive operation.
links.forEach((link) => {
// Don't use the link.href getter because the browser computes the href
// as a full URL. We need the relative URL that Sphinx generates.
const href = link.getAttribute('href');
if (!href.startsWith(urlRoot)) {
link.href = `${urlRoot}${href}`;
}
});
});
// The node that linkObserver watches doesn't exist until the user types
// something into the search textbox. The next observer just waits for
// that node to exist and then registers linkObserver on it.
let isObserved = false;
const resultsObserver = new MutationObserver(() => {
if (isObserved) {
return;
}
const container = document.querySelector('#search-results .search');
if (!container) {
return;
}
linkObserver.observe(container, { childList: true });
isObserved = true;
});
resultsObserver.observe(results, { childList: true });
// Add the new nodes to the DOM.
let modal = document.querySelector('.search-button__search-container');
modal.appendChild(results);
};
window.addEventListener('DOMContentLoaded', () => {
// Manually control when Mermaid diagrams render to prevent scrolling issues.
// Context: https://pigweed.dev/docs/style_guide.html#site-nav-scrolling
if (window.mermaid) {
// https://mermaid.js.org/config/usage.html#using-mermaid-run
window.mermaid.run();
}
window.pw.initSearch();
});