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