155 lines
5.0 KiB
JavaScript
155 lines
5.0 KiB
JavaScript
/* Highlighting utilities for Sphinx HTML documentation. */
|
|
"use strict";
|
|
|
|
const SPHINX_HIGHLIGHT_ENABLED = true
|
|
|
|
/**
|
|
* highlight a given string on a node by wrapping it in
|
|
* span elements with the given class name.
|
|
*/
|
|
const _highlight = (node, addItems, text, className) => {
|
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
const val = node.nodeValue;
|
|
const parent = node.parentNode;
|
|
const pos = val.toLowerCase().indexOf(text);
|
|
if (
|
|
pos >= 0 &&
|
|
!parent.classList.contains(className) &&
|
|
!parent.classList.contains("nohighlight")
|
|
) {
|
|
let span;
|
|
|
|
const closestNode = parent.closest("body, svg, foreignObject");
|
|
const isInSVG = closestNode && closestNode.matches("svg");
|
|
if (isInSVG) {
|
|
span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
|
|
} else {
|
|
span = document.createElement("span");
|
|
span.classList.add(className);
|
|
}
|
|
|
|
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
|
|
const rest = document.createTextNode(val.substr(pos + text.length));
|
|
parent.insertBefore(
|
|
span,
|
|
parent.insertBefore(
|
|
rest,
|
|
node.nextSibling
|
|
)
|
|
);
|
|
node.nodeValue = val.substr(0, pos);
|
|
/* There may be more occurrences of search term in this node. So call this
|
|
* function recursively on the remaining fragment.
|
|
*/
|
|
_highlight(rest, addItems, text, className);
|
|
|
|
if (isInSVG) {
|
|
const rect = document.createElementNS(
|
|
"http://www.w3.org/2000/svg",
|
|
"rect"
|
|
);
|
|
const bbox = parent.getBBox();
|
|
rect.x.baseVal.value = bbox.x;
|
|
rect.y.baseVal.value = bbox.y;
|
|
rect.width.baseVal.value = bbox.width;
|
|
rect.height.baseVal.value = bbox.height;
|
|
rect.setAttribute("class", className);
|
|
addItems.push({ parent: parent, target: rect });
|
|
}
|
|
}
|
|
} else if (node.matches && !node.matches("button, select, textarea")) {
|
|
node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
|
|
}
|
|
};
|
|
const _highlightText = (thisNode, text, className) => {
|
|
let addItems = [];
|
|
_highlight(thisNode, addItems, text, className);
|
|
addItems.forEach((obj) =>
|
|
obj.parent.insertAdjacentElement("beforebegin", obj.target)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Small JavaScript module for the documentation.
|
|
*/
|
|
const SphinxHighlight = {
|
|
|
|
/**
|
|
* highlight the search words provided in localstorage in the text
|
|
*/
|
|
highlightSearchWords: () => {
|
|
if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
|
|
|
|
// get and clear terms from localstorage
|
|
const url = new URL(window.location);
|
|
const highlight =
|
|
localStorage.getItem("sphinx_highlight_terms")
|
|
|| url.searchParams.get("highlight")
|
|
|| "";
|
|
localStorage.removeItem("sphinx_highlight_terms")
|
|
url.searchParams.delete("highlight");
|
|
window.history.replaceState({}, "", url);
|
|
|
|
// get individual terms from highlight string
|
|
const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
|
|
if (terms.length === 0) return; // nothing to do
|
|
|
|
// There should never be more than one element matching "div.body"
|
|
const divBody = document.querySelectorAll("div.body");
|
|
const body = divBody.length ? divBody[0] : document.querySelector("body");
|
|
window.setTimeout(() => {
|
|
terms.forEach((term) => _highlightText(body, term, "highlighted"));
|
|
}, 10);
|
|
|
|
const searchBox = document.getElementById("searchbox");
|
|
if (searchBox === null) return;
|
|
searchBox.appendChild(
|
|
document
|
|
.createRange()
|
|
.createContextualFragment(
|
|
'<p class="highlight-link">' +
|
|
'<a href="javascript:SphinxHighlight.hideSearchWords()">' +
|
|
_("Hide Search Matches") +
|
|
"</a></p>"
|
|
)
|
|
);
|
|
},
|
|
|
|
/**
|
|
* helper function to hide the search marks again
|
|
*/
|
|
hideSearchWords: () => {
|
|
document
|
|
.querySelectorAll("#searchbox .highlight-link")
|
|
.forEach((el) => el.remove());
|
|
document
|
|
.querySelectorAll("span.highlighted")
|
|
.forEach((el) => el.classList.remove("highlighted"));
|
|
localStorage.removeItem("sphinx_highlight_terms")
|
|
},
|
|
|
|
initEscapeListener: () => {
|
|
// only install a listener if it is really needed
|
|
if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
|
|
|
|
document.addEventListener("keydown", (event) => {
|
|
// bail for input elements
|
|
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
|
|
// bail with special keys
|
|
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
|
|
SphinxHighlight.hideSearchWords();
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
},
|
|
};
|
|
|
|
_ready(() => {
|
|
/* Do not call highlightSearchWords() when we are on the search page.
|
|
* It will highlight words from the *previous* search query.
|
|
*/
|
|
if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords();
|
|
SphinxHighlight.initEscapeListener();
|
|
});
|