sphinx_highlight.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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. parent.insertBefore(
  29. span,
  30. parent.insertBefore(
  31. document.createTextNode(val.substr(pos + text.length)),
  32. node.nextSibling
  33. )
  34. );
  35. node.nodeValue = val.substr(0, pos);
  36. if (isInSVG) {
  37. const rect = document.createElementNS(
  38. "http://www.w3.org/2000/svg",
  39. "rect"
  40. );
  41. const bbox = parent.getBBox();
  42. rect.x.baseVal.value = bbox.x;
  43. rect.y.baseVal.value = bbox.y;
  44. rect.width.baseVal.value = bbox.width;
  45. rect.height.baseVal.value = bbox.height;
  46. rect.setAttribute("class", className);
  47. addItems.push({ parent: parent, target: rect });
  48. }
  49. }
  50. } else if (node.matches && !node.matches("button, select, textarea")) {
  51. node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
  52. }
  53. };
  54. const _highlightText = (thisNode, text, className) => {
  55. let addItems = [];
  56. _highlight(thisNode, addItems, text, className);
  57. addItems.forEach((obj) =>
  58. obj.parent.insertAdjacentElement("beforebegin", obj.target)
  59. );
  60. };
  61. /**
  62. * Small JavaScript module for the documentation.
  63. */
  64. const SphinxHighlight = {
  65. /**
  66. * highlight the search words provided in localstorage in the text
  67. */
  68. highlightSearchWords: () => {
  69. if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
  70. // get and clear terms from localstorage
  71. const url = new URL(window.location);
  72. const highlight =
  73. localStorage.getItem("sphinx_highlight_terms")
  74. || url.searchParams.get("highlight")
  75. || "";
  76. localStorage.removeItem("sphinx_highlight_terms")
  77. url.searchParams.delete("highlight");
  78. window.history.replaceState({}, "", url);
  79. // get individual terms from highlight string
  80. const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
  81. if (terms.length === 0) return; // nothing to do
  82. // There should never be more than one element matching "div.body"
  83. const divBody = document.querySelectorAll("div.body");
  84. const body = divBody.length ? divBody[0] : document.querySelector("body");
  85. window.setTimeout(() => {
  86. terms.forEach((term) => _highlightText(body, term, "highlighted"));
  87. }, 10);
  88. const searchBox = document.getElementById("searchbox");
  89. if (searchBox === null) return;
  90. searchBox.appendChild(
  91. document
  92. .createRange()
  93. .createContextualFragment(
  94. '<p class="highlight-link">' +
  95. '<a href="javascript:SphinxHighlight.hideSearchWords()">' +
  96. _("Hide Search Matches") +
  97. "</a></p>"
  98. )
  99. );
  100. },
  101. /**
  102. * helper function to hide the search marks again
  103. */
  104. hideSearchWords: () => {
  105. document
  106. .querySelectorAll("#searchbox .highlight-link")
  107. .forEach((el) => el.remove());
  108. document
  109. .querySelectorAll("span.highlighted")
  110. .forEach((el) => el.classList.remove("highlighted"));
  111. localStorage.removeItem("sphinx_highlight_terms")
  112. },
  113. initEscapeListener: () => {
  114. // only install a listener if it is really needed
  115. if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
  116. document.addEventListener("keydown", (event) => {
  117. // bail for input elements
  118. if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
  119. // bail with special keys
  120. if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
  121. if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
  122. SphinxHighlight.hideSearchWords();
  123. event.preventDefault();
  124. }
  125. });
  126. },
  127. };
  128. _ready(SphinxHighlight.highlightSearchWords);
  129. _ready(SphinxHighlight.initEscapeListener);