toc.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /* toc.js - Generate and manage the table of content
  2. Copyright © 2017 Free Software Foundation, Inc.
  3. This file is part of GNU Texinfo.
  4. GNU Texinfo is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. GNU Texinfo is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with GNU Texinfo. If not, see <http://www.gnu.org/licenses/>. */
  14. import {
  15. absolute_url_p,
  16. basename,
  17. depth_first_walk,
  18. href_hash
  19. } from "./utils";
  20. import config from "./config";
  21. /** Return a relative URL corresponding to HREF, which refers to an anchor of
  22. 'config.INDEX_NAME'. URL must be a USVString representing an absolute or
  23. relative URL.
  24. For example "foo/bar.html" will return "config.INDEX_NAME#bar". */
  25. export function
  26. with_sidebar_query (href)
  27. {
  28. if (basename (href) === config.INDEX_NAME)
  29. return config.INDEX_NAME;
  30. else
  31. {
  32. let url = new window.URL (href, window.location);
  33. let new_hash = basename (url.pathname, /[.]x?html/);
  34. if (url.hash)
  35. new_hash += ("." + url.hash.slice (1));
  36. return config.INDEX_NAME + "#" + new_hash;
  37. }
  38. }
  39. /* Keep children but remove grandchildren (Exception: don't remove
  40. anything on the current page; however, that's not a problem in the
  41. Kawa manual). */
  42. function
  43. hide_grand_child_nodes (ul)
  44. {
  45. for (let li = ul.firstElementChild; li; li = li.nextElementSibling)
  46. {
  47. let a = li.firstElementChild;
  48. let li$ = a && a.nextElementSibling;
  49. /* Never remove Overall-Index. */
  50. if (li$ && a.getAttribute ("href") !== config.OVERALL_INDEX_NAME)
  51. li$.setAttribute ("toc-detail", "yes");
  52. }
  53. }
  54. /** Scan ToC entries to see which should be hidden. */
  55. export function
  56. scan_toc (node, filename)
  57. {
  58. var current = with_sidebar_query (filename);
  59. var ul = node.querySelector ("ul");
  60. if (filename === config.INDEX_NAME)
  61. hide_grand_child_nodes (ul);
  62. else
  63. scan_toc1 (ul, current);
  64. }
  65. /* Scan ToC entries to see which should be hidden. Return "current" if node
  66. matches current, "ancestor" if node is ancestor of current, else 'null'. */
  67. function
  68. scan_toc1 (node, current)
  69. {
  70. if (node.matches ("a"))
  71. {
  72. if (current === node.getAttribute ("href"))
  73. {
  74. node.setAttribute ("toc-current", "yes");
  75. var ul = node.nextElementSibling;
  76. if (ul && ul.matches ("ul"))
  77. hide_grand_child_nodes (ul);
  78. return "current";
  79. }
  80. }
  81. var ancestor = null;
  82. for (var child = node.firstElementChild; child;
  83. child = child.nextElementSibling)
  84. {
  85. if (scan_toc1 (child, current) !== null)
  86. {
  87. ancestor = child;
  88. break;
  89. }
  90. }
  91. if (ancestor && ancestor.parentNode && ancestor.parentNode.parentNode)
  92. {
  93. var pparent = ancestor.parentNode.parentNode;
  94. for (var sib = pparent.firstElementChild; sib;
  95. sib = sib.nextElementSibling)
  96. {
  97. if (sib !== ancestor.parentNode
  98. && sib.firstElementChild
  99. && sib.firstElementChild.nextElementSibling)
  100. {
  101. sib.firstElementChild
  102. .nextElementSibling
  103. .setAttribute ("toc-detail", "yes");
  104. }
  105. }
  106. }
  107. return ancestor ? "ancestor" : null;
  108. }
  109. /** Reset what is done by 'scan_toc' and 'hide_grand_child_nodes'. */
  110. export function
  111. clear_toc_styles (node)
  112. {
  113. function
  114. do_clear (node$)
  115. {
  116. if (node$.matches ("ul"))
  117. node$.removeAttribute ("toc-detail");
  118. else if (node$.matches ("a"))
  119. node$.removeAttribute ("toc-current");
  120. }
  121. depth_first_walk (node, do_clear, Node.ELEMENT_NODE);
  122. }
  123. /** Build the global dictionary containing navigation links from NAV. NAV
  124. must be an 'ul' DOM element containing the table of content of the
  125. manual. */
  126. export function
  127. create_link_dict (nav)
  128. {
  129. let prev_id = config.INDEX_ID;
  130. let links = {};
  131. function
  132. add_link (elem)
  133. {
  134. if (elem.matches ("a") && elem.hasAttribute ("href"))
  135. {
  136. let id = href_hash (elem.getAttribute ("href"));
  137. links[prev_id] = Object.assign ({}, links[prev_id], { forward: id });
  138. links[id] = Object.assign ({}, links[id], { backward: prev_id });
  139. prev_id = id;
  140. }
  141. }
  142. depth_first_walk (nav, add_link, Node.ELEMENT_NODE);
  143. /* Add a reference to the first and last node of the manual. */
  144. links["*TOP*"] = config.INDEX_ID;
  145. links["*END*"] = prev_id;
  146. return links;
  147. }
  148. /** Modify LINKS to handle the iframe based navigation properly. Relative
  149. links will be opened inside the corresponding iframe and absolute links
  150. will be opened in a new tab. LINKS must be an array or a collection of
  151. nodes. */
  152. export function
  153. fix_links (links)
  154. {
  155. for (let i = 0; i < links.length; i += 1)
  156. {
  157. let link = links[i];
  158. let href = link.getAttribute ("href");
  159. if (href)
  160. {
  161. if (absolute_url_p (href))
  162. link.setAttribute ("target", "_blank");
  163. else
  164. link.setAttribute ("href", with_sidebar_query (href));
  165. }
  166. }
  167. }