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);