AutoTOC

Automatically creates a table of contents for all HTML-headers on a web page.

当前为 2020-01-29 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name AutoTOC
  3. // @namespace http://runeskaug.com/greasemonkey
  4. // @description Automatically creates a table of contents for all HTML-headers on a web page.
  5. // @author Rune Skaug (greasemonkey@runeskaug.com), IzzySoft
  6. // @license CC BY (https://creativecommons.org/licenses/by/2.5/)
  7. // @include http://*
  8. // @include https://*
  9. // @version 1.8
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_addStyle
  12. // @homepageURL https://codeberg.org/izzy/userscripts
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. // text constants
  17. var fullTOCText = "Table of Contents";
  18. var hideBtnText = "\u00a0X\u00a0";
  19. var RXmatch = /^h[1-4]$/i; // regexp
  20. var XPmatch = "//h1|//h2|//h3|//h4"; // xpath
  21. //set the optional behaviour of the TOC box
  22. // if true, resets it to its initial state after you have selected a header - false does not reset it
  23. var resetSelect = true;
  24. // if true, shows a "Hide TOC"/close button on the left side of the bar
  25. var showHide = true;
  26. // if true, hides the TOC for all pages on current site
  27. var useCookie = true;
  28. // if true, adds menu item (Toggle TOC)
  29. var addMenuItem = true;
  30.  
  31. function f() {
  32. //only on (X)HTML pages containing at least one heading - excludes XML files, text files, plugins and images (displayed using minimal HTML)
  33. if (document.getElementsByTagName("html").length && ( document.getElementsByTagName('h1').length ||
  34. document.getElementsByTagName('h2').length ||
  35. document.getElementsByTagName('h3').length ||
  36. document.getElementsByTagName('h4').length )
  37. && (!useCookie || (useCookie && getCookie('autotoc_hide')!='true'))) {
  38. var aHs = getHTMLHeadings();
  39. if (aHs.length>1) { // HTML document, more than one heading.
  40. var body = document.getElementsByTagName('body')[0];
  41. body.style.marginBottom = "24px !important";
  42. // Browser sniff++ - due to rendering bug(s) in FF1.0
  43. var toc = document.createElement(window.opera||showHide?'tocdiv':'div');
  44. toc.id = 'js-toc';
  45. if (showHide) {
  46. var hideDiv = document.createElement('div');
  47. hideDiv.setAttribute('class','hideBtn');
  48. var hideLink = document.createElement('a');
  49. hideLink.setAttribute("href","#");
  50. hideLink.addEventListener("click",function(){if(useCookie){document.getElementById('js-toc').style.display='none';document.cookie='autotoc_hide=true; path=/';return false;}else{document.getElementById('js-toc').style.display='none';}});
  51. hideLink.appendChild(document.createTextNode(hideBtnText));
  52. hideDiv.appendChild(hideLink);
  53. toc.appendChild(hideDiv);
  54. }
  55. tocSelect = document.createElement('select');
  56. tocSelect.addEventListener("change", function(){gotoAnchor(this)});
  57. tocSelect.id = 'toc-select';
  58. tocEmptyOption = document.createElement('option');
  59. tocEmptyOption.setAttribute('value','');
  60. tocEmptyOption.appendChild(document.createTextNode(fullTOCText));
  61. tocSelect.appendChild(tocEmptyOption);
  62. toc.appendChild(tocSelect);
  63. document.body.appendChild(toc);
  64. for (var i=0,aH;aH=aHs[i];i++) {
  65. if (aH.offsetWidth) {
  66. op = document.createElement("option");
  67. op.appendChild(document.createTextNode(gs(aH.tagName)+getInnerText(aH).substring(0,100)));
  68. var refID = aH.id ? aH.id : aH.tagName+'-'+(i*1+1);
  69. op.setAttribute("value", refID);
  70. document.getElementById("toc-select").appendChild(op);
  71. aH.id = refID;
  72. }
  73. }
  74. GM_addStyle('@media print { #js-toc {display: none; visibility: hidden; }}\n'+
  75. '@media screen { #js-toc {position: fixed; left: 0; right: 0; top: auto; bottom: 0; width: 100%; display: block; border-top: 1px solid #777; background: #ddd; margin: 0; padding: 3px; z-index: 9999; }\n'+
  76. '#js-toc select { font: 8pt verdana, sans-serif; margin: 0; margin-left:5px; background: #fff; color: #000; float: left; padding: 0; vertical-align: bottom;}\n'+
  77. '#js-toc option { font: 8pt verdana, sans-serif; color: #000; }\n'+
  78. '#js-toc .hideBtn { font: bold 8pt verdana, sans-serif !important; float: left; margin-left: 2px; margin-right: 2px; padding: 1px; border: 1px solid #999; background: #e7e7e7; }\n'+
  79. '#js-toc .hideBtn a { color: #333; text-decoration: none; background: transparent;} #js-toc .hideBtn a:hover { color: #333; text-decoration: none; background: transparent;}\n'+
  80. '#js-toc:not(:hover) { height: 2px !important; width: 5px !important; border-radius: 5px !important; background-color: #00f !important; }'
  81. );
  82. }
  83. }
  84. };
  85.  
  86. function autoTOC_toggleDisplay() {
  87. if (document.getElementById('js-toc')) {
  88. // toc-bar exists
  89. if (document.getElementById('js-toc').style.display == 'none') {
  90. document.getElementById('js-toc').style.display = 'block';
  91. if (useCookie) {document.cookie = 'autotoc_hide=; path=/';}
  92. }
  93. else {
  94. document.getElementById('js-toc').style.display = 'none';
  95. if (useCookie) {document.cookie = 'autotoc_hide=true; path=/';}
  96. };
  97. } else {
  98. // toc-bar not created yet, clear hide-cookie and run main script
  99. if (useCookie) {document.cookie = 'autotoc_hide=; path=/';}
  100. f();
  101. }
  102. }
  103.  
  104. function flash(el,rep,delay) {
  105. for (var i=rep;i>0;i--) {
  106. window.setTimeout(function(){el.style.background="#ff7";},delay*i*2);
  107. window.setTimeout(function(){el.style.background=elbg;},delay*((i*2)+1));
  108. }
  109. }
  110. function gotoAnchor(selectEl) {
  111. if(selectEl) {
  112. el = document.getElementById(selectEl.value);
  113. elbg = el.style.background;
  114. location.href = '#' + selectEl.value;
  115. flash(el,5,100);
  116. if ( resetSelect ) { selectEl.selectedIndex = 0; }
  117. }
  118. }
  119.  
  120. function getHTMLHeadings() {
  121. function acceptNode(node) {
  122. if (node.tagName.match(RXmatch)) { if (node.value+''!='') { return NodeFilter.FILTER_ACCEPT; } }
  123. return NodeFilter.FILTER_SKIP;
  124. }
  125. outArray = new Array();
  126. // XPath
  127. if (document.evaluate) {
  128. var nodes = document.evaluate(XPmatch, document, null, XPathResult.ANY_TYPE, null);
  129. var thisHeading = nodes.iterateNext();
  130. var j = 0;
  131. while (thisHeading) {
  132. if (thisHeading.textContent+''!='') {
  133. outArray[j++] = thisHeading;
  134. }
  135. thisHeading = nodes.iterateNext();
  136. }
  137. }
  138. // document.getElementsByTagName - slow! :)
  139. else {
  140. var els = document.getElementsByTagName("*");
  141. var j = 0;
  142. for (var i=0,el;el=els[i];i++) {
  143. if (el.tagName.match(RXmatch)) outArray[j++] = el;
  144. }
  145. }
  146. return outArray;
  147. }
  148. function gs(s){
  149. s = s.toLowerCase();
  150. var ret = "";
  151. for (var i=1; i<(s.substring(1)*1);i++) {
  152. ret = ret + "\u00a0 \u00a0 ";
  153. }
  154. return ret;
  155. }
  156. function getInnerText(el) {
  157. var s='';
  158. for (var i=0,node; node=el.childNodes[i]; i++) {
  159. if (node.nodeType == 1) s += getInnerText(node);
  160. else if (node.nodeType == 3) s += node.nodeValue;
  161. }
  162. return s;
  163. }
  164. function getCookie(cname)
  165. {
  166. var namesep = cname + "=";
  167. var ca = document.cookie.split(';');
  168. for(var i=0, c; c=ca[i]; i++)
  169. {
  170. c = c.replace(/^\s*|\s*$/g,"");
  171. if (c.indexOf(namesep) == 0) {
  172. return c.substring(namesep.length,c.length);
  173. }
  174. }
  175. return null;
  176. }
  177. // main()
  178. if (!window.opera && addMenuItem) {
  179. GM_registerMenuCommand('AutoTOC: Toggle display', autoTOC_toggleDisplay);
  180. }
  181. f();
  182. })();