1/* Highlighting utilities for Sphinx HTML documentation. */ 2"use strict"; 3 4const 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 */ 10const _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 const rest = document.createTextNode(val.substr(pos + text.length)); 33 parent.insertBefore(span, parent.insertBefore(rest, node.nextSibling)); 34 node.nodeValue = val.substr(0, pos); 35 /* There may be more occurrences of search term in this node. So call this 36 * function recursively on the remaining fragment. 37 */ 38 _highlight(rest, addItems, text, className); 39 40 if (isInSVG) { 41 const rect = document.createElementNS( 42 "http://www.w3.org/2000/svg", 43 "rect", 44 ); 45 const bbox = parent.getBBox(); 46 rect.x.baseVal.value = bbox.x; 47 rect.y.baseVal.value = bbox.y; 48 rect.width.baseVal.value = bbox.width; 49 rect.height.baseVal.value = bbox.height; 50 rect.setAttribute("class", className); 51 addItems.push({ parent: parent, target: rect }); 52 } 53 } 54 } else if (node.matches && !node.matches("button, select, textarea")) { 55 node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 56 } 57}; 58const _highlightText = (thisNode, text, className) => { 59 let addItems = []; 60 _highlight(thisNode, addItems, text, className); 61 addItems.forEach((obj) => 62 obj.parent.insertAdjacentElement("beforebegin", obj.target), 63 ); 64}; 65 66/** 67 * Small JavaScript module for the documentation. 68 */ 69const SphinxHighlight = { 70 /** 71 * highlight the search words provided in localstorage in the text 72 */ 73 highlightSearchWords: () => { 74 if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 75 76 // get and clear terms from localstorage 77 const url = new URL(window.location); 78 const highlight = 79 localStorage.getItem("sphinx_highlight_terms") 80 || url.searchParams.get("highlight") 81 || ""; 82 localStorage.removeItem("sphinx_highlight_terms"); 83 // Update history only if '?highlight' is present; otherwise it 84 // clears text fragments (not set in window.location by the browser) 85 if (url.searchParams.has("highlight")) { 86 url.searchParams.delete("highlight"); 87 window.history.replaceState({}, "", url); 88 } 89 90 // get individual terms from highlight string 91 const terms = highlight 92 .toLowerCase() 93 .split(/\s+/) 94 .filter((x) => x); 95 if (terms.length === 0) return; // nothing to do 96 97 // There should never be more than one element matching "div.body" 98 const divBody = document.querySelectorAll("div.body"); 99 const body = divBody.length ? divBody[0] : document.querySelector("body"); 100 window.setTimeout(() => { 101 terms.forEach((term) => _highlightText(body, term, "highlighted")); 102 }, 10); 103 104 const searchBox = document.getElementById("searchbox"); 105 if (searchBox === null) return; 106 searchBox.appendChild( 107 document 108 .createRange() 109 .createContextualFragment( 110 '<p class="highlight-link">' 111 + '<a href="javascript:SphinxHighlight.hideSearchWords()">' 112 + _("Hide Search Matches") 113 + "</a></p>", 114 ), 115 ); 116 }, 117 118 /** 119 * helper function to hide the search marks again 120 */ 121 hideSearchWords: () => { 122 document 123 .querySelectorAll("#searchbox .highlight-link") 124 .forEach((el) => el.remove()); 125 document 126 .querySelectorAll("span.highlighted") 127 .forEach((el) => el.classList.remove("highlighted")); 128 localStorage.removeItem("sphinx_highlight_terms"); 129 }, 130 131 initEscapeListener: () => { 132 // only install a listener if it is really needed 133 if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; 134 135 document.addEventListener("keydown", (event) => { 136 // bail for input elements 137 if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) 138 return; 139 // bail with special keys 140 if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) 141 return; 142 if ( 143 DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 144 && event.key === "Escape" 145 ) { 146 SphinxHighlight.hideSearchWords(); 147 event.preventDefault(); 148 } 149 }); 150 }, 151}; 152 153_ready(() => { 154 /* Do not call highlightSearchWords() when we are on the search page. 155 * It will highlight words from the *previous* search query. 156 */ 157 if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); 158 SphinxHighlight.initEscapeListener(); 159});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.