xref: /aosp_15_r20/external/pigweed/docs/_static/js/pigweed.js (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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