DevDocs TOC

add table of content.

  1. // ==UserScript==
  2. // @name DevDocs TOC
  3. // @namespace what.ever
  4. // @match https://devdocs.io/*
  5. // @run-at document-idle
  6. // @grant none
  7. // @version 0.3
  8. // @author sleazy-su
  9. // @description add table of content.
  10. // ==/UserScript==
  11.  
  12. let itv = -1;
  13. function debounce(func, interval){
  14. if(itv == -1){
  15. itv = setTimeout(()=>{
  16. itv = -1;
  17. func();
  18. }, interval);
  19. }else{
  20. clearInterval(itv);
  21. itv = -1;
  22. debounce(func, interval);
  23. }
  24. }
  25.  
  26. addToc();
  27. const observer = new MutationObserver(()=>{debounce(addToc, 800)});
  28. observer.observe(document.querySelector('._container'), { childList: true, subtree: true });
  29.  
  30. function addToc() {
  31. console.log('updating toc...');
  32.  
  33. document.querySelector('#toc-container')?.remove();
  34. const container = document.createElement('div');
  35. container.id = 'toc-container';
  36. container.innerText = 'TOC';
  37. container.dataset.tagName = container.tagName;
  38. container.innerHTML = `<style>
  39. #toc-container{ position: fixed; top: 0; right: 0; height: 100vh; overflow: auto; color: var(--textColor); }
  40. .toc-item{ cursor: pointer; margin: 0; }
  41. .toc-item.hide-children::before { content: '+ '; }
  42. .toc-item.hide-children *{ display: none; }
  43. </style>`;
  44.  
  45. const appWidth = document.querySelector('._app').clientWidth;
  46. const leftMargin = (document.body.clientWidth - appWidth) / 2 + appWidth;
  47. container.style.left = leftMargin + 'px';
  48.  
  49. const titleEls = document.querySelectorAll('h1, h2, h3');
  50. const level = { DIV: 0, H1: 1, H2: 2, H3: 3 };
  51.  
  52. let parentEl = container;
  53. const pairs = { titleEls: [], tocEls: [] };
  54. for (let titleEl of titleEls) {
  55. const lv = level[titleEl.tagName];
  56. while (lv <= level[parentEl.dataset.tagName]) {
  57. parentEl = parentEl.parentElement;
  58. }
  59. const tocEl = document.createElement('ul');
  60. tocEl.innerText = titleEl.innerText;
  61. tocEl.dataset.tagName = titleEl.tagName;
  62. tocEl.classList.add('toc-item');
  63. parentEl.append(tocEl);
  64. parentEl = tocEl;
  65. pairs['titleEls'].push(titleEl);
  66. pairs['tocEls'].push(tocEl);
  67. }
  68.  
  69. document.body.append(container);
  70. container.addEventListener('click', ev => {
  71. const index = pairs['tocEls'].indexOf(ev.target);
  72. pairs['titleEls'][index]?.scrollIntoView();
  73. });
  74. container.addEventListener('contextmenu', ev => {
  75. ev.preventDefault();
  76. if (ev.target.children.length > 0) {
  77. const clazz = ev.target.classList;
  78. const hideFlag = 'hide-children';
  79. if (clazz.contains(hideFlag)) {
  80. clazz.remove(hideFlag);
  81. } else {
  82. clazz.add(hideFlag);
  83. }
  84. }
  85. });
  86. }