/ docs / manual / _static / sphinx_highlight.js
sphinx_highlight.js
  1  /* Highlighting utilities for Sphinx HTML documentation. */
  2  "use strict";
  3  
  4  const SPHINX_HIGHLIGHT_ENABLED = true
  5  
  6  /**
  7   * highlight a given string on a node by wrapping it in
  8   * span elements with the given class name.
  9   */
 10  const _highlight = (node, addItems, text, className) => {
 11    if (node.nodeType === Node.TEXT_NODE) {
 12      const val = node.nodeValue;
 13      const parent = node.parentNode;
 14      const pos = val.toLowerCase().indexOf(text);
 15      if (
 16        pos >= 0 &&
 17        !parent.classList.contains(className) &&
 18        !parent.classList.contains("nohighlight")
 19      ) {
 20        let span;
 21  
 22        const closestNode = parent.closest("body, svg, foreignObject");
 23        const isInSVG = closestNode && closestNode.matches("svg");
 24        if (isInSVG) {
 25          span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
 26        } else {
 27          span = document.createElement("span");
 28          span.classList.add(className);
 29        }
 30  
 31        span.appendChild(document.createTextNode(val.substr(pos, text.length)));
 32        parent.insertBefore(
 33          span,
 34          parent.insertBefore(
 35            document.createTextNode(val.substr(pos + text.length)),
 36            node.nextSibling
 37          )
 38        );
 39        node.nodeValue = val.substr(0, pos);
 40  
 41        if (isInSVG) {
 42          const rect = document.createElementNS(
 43            "http://www.w3.org/2000/svg",
 44            "rect"
 45          );
 46          const bbox = parent.getBBox();
 47          rect.x.baseVal.value = bbox.x;
 48          rect.y.baseVal.value = bbox.y;
 49          rect.width.baseVal.value = bbox.width;
 50          rect.height.baseVal.value = bbox.height;
 51          rect.setAttribute("class", className);
 52          addItems.push({ parent: parent, target: rect });
 53        }
 54      }
 55    } else if (node.matches && !node.matches("button, select, textarea")) {
 56      node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
 57    }
 58  };
 59  const _highlightText = (thisNode, text, className) => {
 60    let addItems = [];
 61    _highlight(thisNode, addItems, text, className);
 62    addItems.forEach((obj) =>
 63      obj.parent.insertAdjacentElement("beforebegin", obj.target)
 64    );
 65  };
 66  
 67  /**
 68   * Small JavaScript module for the documentation.
 69   */
 70  const SphinxHighlight = {
 71  
 72    /**
 73     * highlight the search words provided in localstorage in the text
 74     */
 75    highlightSearchWords: () => {
 76      if (!SPHINX_HIGHLIGHT_ENABLED) return;  // bail if no highlight
 77  
 78      // get and clear terms from localstorage
 79      const url = new URL(window.location);
 80      const highlight =
 81          localStorage.getItem("sphinx_highlight_terms")
 82          || url.searchParams.get("highlight")
 83          || "";
 84      localStorage.removeItem("sphinx_highlight_terms")
 85      url.searchParams.delete("highlight");
 86      window.history.replaceState({}, "", url);
 87  
 88      // get individual terms from highlight string
 89      const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
 90      if (terms.length === 0) return; // nothing to do
 91  
 92      // There should never be more than one element matching "div.body"
 93      const divBody = document.querySelectorAll("div.body");
 94      const body = divBody.length ? divBody[0] : document.querySelector("body");
 95      window.setTimeout(() => {
 96        terms.forEach((term) => _highlightText(body, term, "highlighted"));
 97      }, 10);
 98  
 99      const searchBox = document.getElementById("searchbox");
100      if (searchBox === null) return;
101      searchBox.appendChild(
102        document
103          .createRange()
104          .createContextualFragment(
105            '<p class="highlight-link">' +
106              '<a href="javascript:SphinxHighlight.hideSearchWords()">' +
107              _("Hide Search Matches") +
108              "</a></p>"
109          )
110      );
111    },
112  
113    /**
114     * helper function to hide the search marks again
115     */
116    hideSearchWords: () => {
117      document
118        .querySelectorAll("#searchbox .highlight-link")
119        .forEach((el) => el.remove());
120      document
121        .querySelectorAll("span.highlighted")
122        .forEach((el) => el.classList.remove("highlighted"));
123      localStorage.removeItem("sphinx_highlight_terms")
124    },
125  
126    initEscapeListener: () => {
127      // only install a listener if it is really needed
128      if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
129  
130      document.addEventListener("keydown", (event) => {
131        // bail for input elements
132        if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
133        // bail with special keys
134        if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
135        if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
136          SphinxHighlight.hideSearchWords();
137          event.preventDefault();
138        }
139      });
140    },
141  };
142  
143  _ready(SphinxHighlight.highlightSearchWords);
144  _ready(SphinxHighlight.initEscapeListener);