sphinx_highlight.js 5.0 KB

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