contents.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. /*
  2. Inno Setup
  3. Copyright (C) 1997-2025 Jordan Russell
  4. Portions by Martijn Laan
  5. For conditions of distribution and use, see LICENSE.TXT.
  6. JavaScript code used by contents.htm
  7. */
  8. function get_absolute_top(obj)
  9. {
  10. var y = obj.offsetTop;
  11. while ((obj = obj.offsetParent)) {
  12. y += obj.offsetTop;
  13. }
  14. return y;
  15. }
  16. function get_viewport_element()
  17. {
  18. // On IE 6 in Standards mode & Firefox 1.5, properties like
  19. // scrollTop and clientHeight are set on document.documentElement.
  20. // On IE 5, they're on document.body; the ones on documentElement
  21. // are zero.
  22. if (document.documentElement.clientHeight) {
  23. return document.documentElement;
  24. } else {
  25. return document.body;
  26. }
  27. }
  28. function is_element_displayed(element)
  29. {
  30. do {
  31. if (element.hidden) return false;
  32. } while ((element = element.parentElement));
  33. return true;
  34. }
  35. function ensure_elements_visible(elementTop, elementBottom)
  36. {
  37. if (!is_element_displayed(elementTop) || !is_element_displayed(elementBottom)) {
  38. return;
  39. }
  40. const scrollerElement = document.getElementById("tabbody-contents");
  41. // Inflate by 2 pixels to ensure that focus rectangles are fully visible
  42. let yTop = get_absolute_top(elementTop) - 2;
  43. let yBottom = get_absolute_top(elementBottom) + elementBottom.offsetHeight + 2;
  44. // Make yTop and yBottom relative to the top of the visible client area
  45. const scrollerTop = get_absolute_top(scrollerElement) + scrollerElement.scrollTop;
  46. yTop -= scrollerTop;
  47. yBottom -= scrollerTop;
  48. if (yTop < 0) {
  49. // Scroll up to make the top of elementTop visible
  50. scrollerElement.scrollBy(0, yTop);
  51. } else if (yBottom > scrollerElement.clientHeight) {
  52. // How far do we have to scroll down for elementBottom to be entirely visible?
  53. let delta = yBottom - scrollerElement.clientHeight;
  54. // Don't allow any part of elementTop to be scrolled off the top
  55. if (delta > yTop) delta = yTop;
  56. if (delta > 0) scrollerElement.scrollBy(0, delta);
  57. }
  58. }
  59. function toggle_node(id)
  60. {
  61. const contentElement = document.getElementById("nodecontent_" + id);
  62. const itemElement = contentElement.parentElement;
  63. const linkElement = itemElement.querySelector(":scope > a");
  64. const imageElement = linkElement.querySelector(":scope > img");
  65. const expanding = !!contentElement.hidden;
  66. contentElement.hidden = !expanding;
  67. linkElement.setAttribute("aria-expanded", expanding);
  68. imageElement.src = expanding ? "images/contentsheadopen.svg" : "images/contentsheadclosed.svg";
  69. imageElement.alt = expanding ? "\u25BC " : "\u25B6 ";
  70. if (expanding) {
  71. // Scroll expanded items into view. This is similar to calling scrollIntoView() but
  72. // doesn't do any scrolling if the items are already fully visible.
  73. ensure_elements_visible(itemElement, itemElement);
  74. }
  75. }
  76. function init_contents(toggleNode)
  77. {
  78. var i;
  79. if (toggleNode == 0) {
  80. for (i = 1; document.getElementById("nodecontent_" + i) != null; i++) {
  81. toggle_node(i);
  82. }
  83. } else {
  84. toggle_node(toggleNode);
  85. }
  86. }
  87. var curSelectedNode = null;
  88. function set_selected_node(newSel)
  89. {
  90. if (curSelectedNode == newSel) return;
  91. if (curSelectedNode) {
  92. curSelectedNode.removeAttribute("aria-selected");
  93. }
  94. curSelectedNode = newSel;
  95. if (curSelectedNode) {
  96. curSelectedNode.setAttribute("aria-selected", true);
  97. // Expand parent nodes (may scroll)
  98. let p = curSelectedNode;
  99. while ((p = p.parentElement) && p.id !== "tabbody-contents") {
  100. if (p.id && p.id.startsWith("nodecontent_") && p.hidden) {
  101. toggle_node(p.id.substring(12));
  102. }
  103. }
  104. // Then scroll the node's A element into view
  105. ensure_elements_visible(curSelectedNode, curSelectedNode);
  106. // If the focus is currently inside the Contents tab panel (and not inside the
  107. // topic body frame), then ensure the new selected node is focused. This matters
  108. // when Back is clicked in the browser; we want the selection and the focus
  109. // rectangle to both move back to the previous node.
  110. if (document.getElementById("tabbody-contents").contains(document.activeElement)) {
  111. curSelectedNode.focus();
  112. }
  113. }
  114. }
  115. var topic_name_regexp = /(?:^|[/\\])topic_([a-z0-9_\-]+)\.htm$/;
  116. function topic_name_from_path(path)
  117. {
  118. var matches = path.match(topic_name_regexp);
  119. return matches ? matches[1] : "";
  120. }
  121. function sync_contents(bodyTopic)
  122. {
  123. if (!bodyTopic) return;
  124. const elements = document.getElementById("tabbody-contents").getElementsByTagName("a");
  125. for (let i = 0; i < elements.length; i++) {
  126. if (topic_name_from_path(elements[i].getAttribute("href")) === bodyTopic) {
  127. set_selected_node(elements[i]);
  128. break;
  129. }
  130. }
  131. }
  132. function select_tab(newTab)
  133. {
  134. const tabs = ["contents", "index"];
  135. for (let i = 0; i < tabs.length; i++) {
  136. if (tabs[i] != newTab) {
  137. document.getElementById("tab-" + tabs[i]).setAttribute("aria-selected", false);
  138. document.getElementById("tabbody-" + tabs[i]).hidden = true;
  139. }
  140. }
  141. document.getElementById("tab-" + newTab).setAttribute("aria-selected", true);
  142. document.getElementById("tabbody-" + newTab).hidden = false;
  143. if (newTab == "index") init_index_tab();
  144. }
  145. var indexTabInited = false;
  146. function init_index_tab()
  147. {
  148. if (indexTabInited) return;
  149. indexTabInited = true;
  150. var script = document.createElement("script");
  151. script.src = "contentsindex.js";
  152. script.type = "text/javascript";
  153. document.getElementsByTagName("head")[0].appendChild(script);
  154. // contentsindex.js calls init_index_tab_elements()
  155. }
  156. function init_index_tab_elements()
  157. {
  158. var html = "ERROR!";
  159. if (contentsIndexData) {
  160. var len = contentsIndexData.length;
  161. var htmlArray = new Array(len);
  162. var i, matches;
  163. var re = /^([^#:]+)(#[^:]+)?:(.+)$/
  164. for (i = 0; i < len; i++) {
  165. matches = contentsIndexData[i].match(re);
  166. if (!matches) break;
  167. htmlArray[i] = '<a href="topic_' + matches[1] + ".htm" +
  168. ((matches[2] !== undefined) ? matches[2] : "") +
  169. '" target="bodyframe">' + matches[3] + "</a><br />";
  170. }
  171. // Note: On IE6, joining an array is ~5x faster than using "html +=" to build a long string
  172. if (i == len) { // were all processed?
  173. html = htmlArray.join("");
  174. }
  175. }
  176. document.getElementById("tabbody-index").innerHTML = html;
  177. }
  178. window.addEventListener("message", (event) => {
  179. //console.log("contents.js message received:", event.data);
  180. if (typeof event.data === "string" && event.data.startsWith("ishelp_sync_contents:")) {
  181. sync_contents(event.data.substring(21));
  182. }
  183. });