toc.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. /* toc.js - Generate and manage table of content */
  2. import { absolute_url_p, basename, depth_first_walk } from "./utils";
  3. import config from "./config";
  4. /* Adapt HREF to refer to an anchor in index file. HREF must be a string
  5. representing an absolute or relative URL.
  6. For example "foo/bar.html" will be replaced by "config.INDEX_NAME#bar". */
  7. var with_sidebar_query = (function () {
  8. /* DOM element used to access the HTMLAnchorElement interface. */
  9. let node = document.createElement ("a");
  10. return function (href) {
  11. node.href = href;
  12. var node_name = basename (node.pathname, /[.]x?html/);
  13. if (href == config.INDEX_NAME || node_name == "start")
  14. return config.INDEX_NAME;
  15. else
  16. return config.INDEX_NAME + "#" + node_name + node.hash.slice (1);
  17. };
  18. } ());
  19. /* Keep children but remove grandchildren (Exception: don't remove
  20. anything on the current page; however, that's not a problem in the
  21. Kawa manual). */
  22. function
  23. hide_grand_child_nodes (ul)
  24. {
  25. for (let li = ul.firstElementChild; li; li = li.nextElementSibling)
  26. {
  27. let a = li.firstElementChild;
  28. let li$ = a && a.nextElementSibling;
  29. /* Never remove Overall-Index. */
  30. if (li$ && a.getAttribute ("href") != config.OVERALL_INDEX_NAME)
  31. li$.setAttribute ("toc-detail", "yes");
  32. }
  33. }
  34. /** Scan ToC entries to see which should be hidden. */
  35. export function
  36. scan_toc (node, filename)
  37. {
  38. var current = with_sidebar_query (filename);
  39. var ul = node.querySelector ("ul");
  40. if (filename == config.INDEX_NAME)
  41. hide_grand_child_nodes (ul);
  42. else
  43. scan_toc1 (ul, current);
  44. }
  45. /* Scan ToC entries to see which should be hidden. Return "current" if node
  46. matches current, "ancestor" if node is ancestor of current, else 'null'. */
  47. function
  48. scan_toc1 (node, current)
  49. {
  50. if (node.matches ("a"))
  51. {
  52. if (current == node.getAttribute ("href"))
  53. {
  54. node.setAttribute ("toc-current", "yes");
  55. var ul = node.nextElementSibling;
  56. if (ul && ul.matches ("ul"))
  57. hide_grand_child_nodes (ul);
  58. return "current";
  59. }
  60. }
  61. var ancestor = null;
  62. for (var child = node.firstElementChild; child;
  63. child = child.nextElementSibling)
  64. {
  65. if (scan_toc1 (child, current) !== null)
  66. {
  67. ancestor = child;
  68. break;
  69. }
  70. }
  71. if (ancestor && ancestor.parentNode && ancestor.parentNode.parentNode)
  72. {
  73. var pparent = ancestor.parentNode.parentNode;
  74. for (var sib = pparent.firstElementChild; sib;
  75. sib = sib.nextElementSibling)
  76. {
  77. if (sib != ancestor.parentNode
  78. && sib.firstElementChild
  79. && sib.firstElementChild.nextElementSibling)
  80. {
  81. sib.firstElementChild
  82. .nextElementSibling
  83. .setAttribute ("toc-detail", "yes");
  84. }
  85. }
  86. }
  87. return ancestor ? "ancestor" : null;
  88. }
  89. /** Reset what is done by 'scan_toc' and 'hide_grand_child_nodes'. */
  90. export function
  91. clear_toc_styles (node)
  92. {
  93. function
  94. do_clear (node$)
  95. {
  96. if (node$.matches ("ul"))
  97. node$.removeAttribute ("toc-detail");
  98. else if (node$.matches ("a"))
  99. node$.removeAttribute ("toc-current");
  100. }
  101. depth_first_walk (node, do_clear, Node.ELEMENT_NODE);
  102. }
  103. /** Build the global dictionary containing navigation links from NAV. NAV
  104. must be an 'ul' DOM element containing the table of content of the
  105. manual. */
  106. export function
  107. create_link_dict (nav)
  108. {
  109. let prev_id = config.INDEX_ID;
  110. let links = {};
  111. function
  112. add_link (elem)
  113. {
  114. if (elem.matches ("a"))
  115. {
  116. let id = elem.getAttribute ("href").replace (/.*#/, "");
  117. links[prev_id] = Object.assign ({}, links[prev_id], { forward: id });
  118. links[id] = Object.assign ({}, links[id], { backward: prev_id });
  119. prev_id = id;
  120. }
  121. }
  122. depth_first_walk (nav, add_link, Node.ELEMENT_NODE);
  123. return links;
  124. }
  125. /** Modify LINKS to handle the iframe based navigation properly. Relative
  126. links will be opened inside the corresponding iframe and absolute links
  127. will be opened in a new tab. LINKS must be an array or a collection of
  128. nodes. */
  129. export function
  130. fix_links (links)
  131. {
  132. function
  133. fix_link (link, href)
  134. {
  135. if (absolute_url_p (href))
  136. link.setAttribute ("target", "_blank");
  137. else
  138. link.setAttribute ("href", with_sidebar_query (href));
  139. }
  140. for (let i = 0; i < links.length; i += 1)
  141. {
  142. let href = links[i].getAttribute ("href");
  143. if (href)
  144. fix_link (links[i], href);
  145. }
  146. }