toc.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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 hash = basename (url.pathname, /[.]x?html/) + url.hash.slice (1);
  34. return config.INDEX_NAME + "#" + hash;
  35. }
  36. }
  37. /* Keep children but remove grandchildren (Exception: don't remove
  38. anything on the current page; however, that's not a problem in the
  39. Kawa manual). */
  40. function
  41. hide_grand_child_nodes (ul)
  42. {
  43. for (let li = ul.firstElementChild; li; li = li.nextElementSibling)
  44. {
  45. let a = li.firstElementChild;
  46. let li$ = a && a.nextElementSibling;
  47. /* Never remove Overall-Index. */
  48. if (li$ && a.getAttribute ("href") !== config.OVERALL_INDEX_NAME)
  49. li$.setAttribute ("toc-detail", "yes");
  50. }
  51. }
  52. /** Scan ToC entries to see which should be hidden. */
  53. export function
  54. scan_toc (node, filename)
  55. {
  56. var current = with_sidebar_query (filename);
  57. var ul = node.querySelector ("ul");
  58. if (filename === config.INDEX_NAME)
  59. hide_grand_child_nodes (ul);
  60. else
  61. scan_toc1 (ul, current);
  62. }
  63. /* Scan ToC entries to see which should be hidden. Return "current" if node
  64. matches current, "ancestor" if node is ancestor of current, else 'null'. */
  65. function
  66. scan_toc1 (node, current)
  67. {
  68. if (node.matches ("a"))
  69. {
  70. if (current === node.getAttribute ("href"))
  71. {
  72. node.setAttribute ("toc-current", "yes");
  73. var ul = node.nextElementSibling;
  74. if (ul && ul.matches ("ul"))
  75. hide_grand_child_nodes (ul);
  76. return "current";
  77. }
  78. }
  79. var ancestor = null;
  80. for (var child = node.firstElementChild; child;
  81. child = child.nextElementSibling)
  82. {
  83. if (scan_toc1 (child, current) !== null)
  84. {
  85. ancestor = child;
  86. break;
  87. }
  88. }
  89. if (ancestor && ancestor.parentNode && ancestor.parentNode.parentNode)
  90. {
  91. var pparent = ancestor.parentNode.parentNode;
  92. for (var sib = pparent.firstElementChild; sib;
  93. sib = sib.nextElementSibling)
  94. {
  95. if (sib !== ancestor.parentNode
  96. && sib.firstElementChild
  97. && sib.firstElementChild.nextElementSibling)
  98. {
  99. sib.firstElementChild
  100. .nextElementSibling
  101. .setAttribute ("toc-detail", "yes");
  102. }
  103. }
  104. }
  105. return ancestor ? "ancestor" : null;
  106. }
  107. /** Reset what is done by 'scan_toc' and 'hide_grand_child_nodes'. */
  108. export function
  109. clear_toc_styles (node)
  110. {
  111. function
  112. do_clear (node$)
  113. {
  114. if (node$.matches ("ul"))
  115. node$.removeAttribute ("toc-detail");
  116. else if (node$.matches ("a"))
  117. node$.removeAttribute ("toc-current");
  118. }
  119. depth_first_walk (node, do_clear, Node.ELEMENT_NODE);
  120. }
  121. /** Build the global dictionary containing navigation links from NAV. NAV
  122. must be an 'ul' DOM element containing the table of content of the
  123. manual. */
  124. export function
  125. create_link_dict (nav)
  126. {
  127. let prev_id = config.INDEX_ID;
  128. let links = {};
  129. function
  130. add_link (elem)
  131. {
  132. if (elem.matches ("a") && elem.hasAttribute ("href"))
  133. {
  134. let id = href_hash (elem.getAttribute ("href"));
  135. links[prev_id] = Object.assign ({}, links[prev_id], { forward: id });
  136. links[id] = Object.assign ({}, links[id], { backward: prev_id });
  137. prev_id = id;
  138. }
  139. }
  140. depth_first_walk (nav, add_link, Node.ELEMENT_NODE);
  141. return links;
  142. }
  143. /** Modify LINKS to handle the iframe based navigation properly. Relative
  144. links will be opened inside the corresponding iframe and absolute links
  145. will be opened in a new tab. LINKS must be an array or a collection of
  146. nodes. */
  147. export function
  148. fix_links (links)
  149. {
  150. for (let i = 0; i < links.length; i += 1)
  151. {
  152. let link = links[i];
  153. let href = link.getAttribute ("href");
  154. if (href)
  155. {
  156. if (absolute_url_p (href))
  157. link.setAttribute ("target", "_blank");
  158. else
  159. link.setAttribute ("href", with_sidebar_query (href));
  160. }
  161. }
  162. }