AutoTOC

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

目前為 2022-01-26 提交的版本,檢視 最新版本

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