From 93de0769d33ff3345696a125679e1a6e557b5ca6 Mon Sep 17 00:00:00 2001 From: Kayce Basques Date: Mon, 9 Sep 2024 17:25:45 +0000 Subject: [PATCH] docs: Fix inline search results URLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: 365179592 Change-Id: I3bf5a26c8179a0ea57475d5ef08d0d31e09cfa39 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/234551 Commit-Queue: Kayce Basques Presubmit-Verified: CQ Bot Account Lint: Lint 🤖 Reviewed-by: Anthony DiGirolamo --- docs/_static/js/pigweed.js | 71 +++++++++++++++++++++++++++++++------- docs/layout/layout.html | 1 + 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/docs/_static/js/pigweed.js b/docs/_static/js/pigweed.js index 9a31c24eb2..1aef941504 100644 --- a/docs/_static/js/pigweed.js +++ b/docs/_static/js/pigweed.js @@ -16,37 +16,36 @@ 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 anew after each keypress. +// The search is restarted after each keypress. // // TODO: b/363034219 - Try to upstream this code into pydata-sphinx-theme. window.pw.initSearch = () => { - // The search page has its own UI for running searches and displaying - // results. This logic isn't needed on /search.html. + // Don't interfere with the default search UX on /search.html. if (window.location.pathname.endsWith('/search.html')) { return; } - // Search class is provided by Sphinx's built-in search tools. - // The template //docs/layout/page.html ensures that Sphinx's - // search scripts are always loaded before pigweed.js. + // 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'); - // Kick off the search after the user types something. + // 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 text box. + // 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. This prevents the - // search from re-running if the user presses Tab to start - // navigating the search results. + // the last query and the current query. E.g. user presses + // Tab to start navigating the search results. if (query === lastQuery) { return; } @@ -69,6 +68,10 @@ window.pw.initSearch = () => { // 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'); @@ -78,8 +81,50 @@ window.pw.resetSearchResults = () => { results = document.createElement('section'); results.classList.add('pw-search-results'); results.id = 'search-results'; - let container = document.querySelector('.search-button__search-container'); - container.appendChild(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', () => { diff --git a/docs/layout/layout.html b/docs/layout/layout.html index d5082d7db2..9e144226ab 100644 --- a/docs/layout/layout.html +++ b/docs/layout/layout.html @@ -26,6 +26,7 @@ {% else %} {# Load Sphinx's built-in search tools so that our custom inline search experience can work on any page. See //docs/_static/js/pigweed.js #} +