1// Copyright 2023 The Pigweed Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4// use this file except in compliance with the License. You may obtain a copy of 5// the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations under 13// the License. 14 15window.pw = {}; 16 17// Display inline search results under the search modal. After the user types 18// text in the search box, results are shown underneath the text input box. 19// The search is restarted after each keypress. 20// 21// TODO: b/363034219 - Try to upstream this code into pydata-sphinx-theme. 22window.pw.initSearch = () => { 23 // Don't interfere with the default search UX on /search.html. 24 if (window.location.pathname.endsWith('/search.html')) { 25 return; 26 } 27 // The template //docs/layout/page.html ensures that Search is always 28 // loaded at this point. 29 // eslint-disable-next-line no-undef 30 if (!Search) { 31 return; 32 } 33 // Destroy the previous search container and create a new one. 34 window.pw.resetSearchResults(); 35 let timeoutId = null; 36 let lastQuery = ''; 37 const searchInput = document.querySelector('#search-input'); 38 // Set up the event handler to initiate searches whenever the user 39 // types stuff in the search modal textbox. 40 searchInput.addEventListener('keyup', () => { 41 const query = searchInput.value; 42 // Don't search when there's nothing in the query textbox. 43 if (query === '') { 44 return; 45 } 46 // Don't search if there is no detectable change between 47 // the last query and the current query. E.g. user presses 48 // Tab to start navigating the search results. 49 if (query === lastQuery) { 50 return; 51 } 52 // The user has changed the search query. Delete the old results 53 // and start setting up the new container. 54 window.pw.resetSearchResults(); 55 // Debounce so that the search only starts only when the 56 // user stops typing. 57 const delay_ms = 500; 58 lastQuery = query; 59 if (timeoutId) { 60 window.clearTimeout(timeoutId); 61 } 62 timeoutId = window.setTimeout(() => { 63 // eslint-disable-next-line no-undef 64 Search.performSearch(query); 65 timeoutId = null; 66 }, delay_ms); 67 }); 68}; 69 70// Deletes the old custom search results container and recreates a 71// new, empty one. 72// 73// Note that Sphinx assumes that searches are always made from /search.html 74// so there's some safeguard logic to make sure the inline search always 75// works no matter what pigweed.dev page you're on. b/365179592 76// 77// TODO: b/363034219 - Try to upstream this code into pydata-sphinx-theme. 78window.pw.resetSearchResults = () => { 79 let results = document.querySelector('#search-results'); 80 if (results) { 81 results.remove(); 82 } 83 results = document.createElement('section'); 84 results.classList.add('pw-search-results'); 85 results.id = 'search-results'; 86 // Add the new results container to the DOM. 87 let modal = document.querySelector('.search-button__search-container'); 88 modal.appendChild(results); 89 // Relative path to the root directory of the site. Sphinx's 90 // HTML builder auto-inserts this metadata on every page. 91 const root = document.documentElement.dataset.content_root; 92 // As Sphinx populates the search results, this observer makes sure that 93 // each URL is correct (i.e. doesn't 404). b/365179592 94 const linkObserver = new MutationObserver(() => { 95 const links = Array.from( 96 document.querySelectorAll('#search-results .search a'), 97 ); 98 // Check every link every time because the timing of when new results are 99 // added is unpredictable and it's not an expensive operation. 100 links.forEach((link) => { 101 // Don't use the link.href getter because the browser computes the href 102 // as a full URL. We need the relative URL that Sphinx generates. 103 const href = link.getAttribute('href'); 104 if (href.startsWith(root)) { 105 // No work needed. The root has already been prepended to the href. 106 return; 107 } 108 link.href = `${root}${href}`; 109 }); 110 }); 111 // The node that linkObserver watches doesn't exist until the user types 112 // something into the search textbox. resultsObserver just waits for 113 // that container to exist and then registers linkObserver on it. 114 let isObserved = false; 115 const resultsObserver = new MutationObserver(() => { 116 if (isObserved) { 117 return; 118 } 119 const container = document.querySelector('#search-results .search'); 120 if (!container) { 121 return; 122 } 123 linkObserver.observe(container, { childList: true }); 124 isObserved = true; 125 }); 126 resultsObserver.observe(results, { childList: true }); 127}; 128 129window.addEventListener('DOMContentLoaded', () => { 130 // Manually control when Mermaid diagrams render to prevent scrolling issues. 131 // Context: https://pigweed.dev/docs/style_guide.html#site-nav-scrolling 132 if (window.mermaid) { 133 // https://mermaid.js.org/config/usage.html#using-mermaid-run 134 window.mermaid.run(); 135 } 136 window.pw.initSearch(); 137}); 138