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 const rest = document.createTextNode(val.substr(pos + text.length)); 33 parent.insertBefore( 34 span, 35 parent.insertBefore( 36 rest, 37 node.nextSibling 38 ) 39 ); 40 node.nodeValue = val.substr(0, pos); 41 /* There may be more occurrences of search term in this node. So call this 42 * function recursively on the remaining fragment. 43 */ 44 _highlight(rest, addItems, text, className); 45 46 if (isInSVG) { 47 const rect = document.createElementNS( 48 "http://www.w3.org/2000/svg", 49 "rect" 50 ); 51 const bbox = parent.getBBox(); 52 rect.x.baseVal.value = bbox.x; 53 rect.y.baseVal.value = bbox.y; 54 rect.width.baseVal.value = bbox.width; 55 rect.height.baseVal.value = bbox.height; 56 rect.setAttribute("class", className); 57 addItems.push({ parent: parent, target: rect }); 58 } 59 } 60 } else if (node.matches && !node.matches("button, select, textarea")) { 61 node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 62 } 63 }; 64 const _highlightText = (thisNode, text, className) => { 65 let addItems = []; 66 _highlight(thisNode, addItems, text, className); 67 addItems.forEach((obj) => 68 obj.parent.insertAdjacentElement("beforebegin", obj.target) 69 ); 70 }; 71 72 /** 73 * Small JavaScript module for the documentation. 74 */ 75 const SphinxHighlight = { 76 77 /** 78 * highlight the search words provided in localstorage in the text 79 */ 80 highlightSearchWords: () => { 81 if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 82 83 // get and clear terms from localstorage 84 const url = new URL(window.location); 85 const highlight = 86 localStorage.getItem("sphinx_highlight_terms") 87 || url.searchParams.get("highlight") 88 || ""; 89 localStorage.removeItem("sphinx_highlight_terms") 90 url.searchParams.delete("highlight"); 91 window.history.replaceState({}, "", url); 92 93 // get individual terms from highlight string 94 const terms = highlight.toLowerCase().split(/\s+/).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)) return; 138 // bail with special keys 139 if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; 140 if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { 141 SphinxHighlight.hideSearchWords(); 142 event.preventDefault(); 143 } 144 }); 145 }, 146 }; 147 148 _ready(() => { 149 /* Do not call highlightSearchWords() when we are on the search page. 150 * It will highlight words from the *previous* search query. 151 */ 152 if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); 153 SphinxHighlight.initEscapeListener(); 154 });