const ROOT = document.getElementById("root"); const sidebar = document.createElement("div"); sidebar.classList.add("sidebar"); let contentsEl = document.createElement("ul"); sidebar.appendChild(contentsEl); const contentsHeadings = []; let cDepth = 1; for (const el of ROOT.querySelectorAll(["h1", "h2", "h3", "h4"])) { if (el.id.length == 0) { el.id = el.innerText .toLowerCase() .replace(/[^a-zA-Z]+/g, " ") .trim() .replace(/ /g, "-"); } let newLi = document.createElement("li"); const newA = document.createElement("a"); newA.setAttribute("href", "#" + el.id); newA.innerHTML = el.innerHTML; newLi.appendChild(newA); contentsHeadings.push([el, newLi]); const depth = parseInt(el.tagName[1]); // We're in too deep while (cDepth > depth) { contentsEl = contentsEl.parentElement.parentElement; cDepth--; } // We're _way_ too shallow if (cDepth < depth - 1) { while (cDepth < depth) { const tempLi = document.createElement("li"); contentsEl.appendChild(tempLi); const newUl = document.createElement("ul"); tempLi.appendChild(newUl); contentsEl = newUl; cDepth++; } } // We're only one level too shallow else if (cDepth < depth) { const newUl = document.createElement("ul"); if (depth == 0) { newLi.appendChild(newUl); } else { contentsEl.childNodes[contentsEl.childNodes.length - 1].appendChild(newUl); } contentsEl = newUl; cDepth++; } contentsEl.appendChild(newLi); } document.body.appendChild(sidebar); contentsHeadings.reverse(); const onScroll = () => { for (const [hEl, sbLi] of contentsHeadings) { sbLi.classList.remove("active"); } let set = false; for (const [hEl, sbLi] of contentsHeadings) { if (hEl.offsetTop <= window.scrollY + 32) { sbLi.classList.add("active"); set = true; break; } } if (!set && contentsHeadings.length != 0) { contentsHeadings[contentsHeadings.length - 1][1].classList.add("active"); } }; document.addEventListener("scroll", onScroll); document.addEventListener("resize", onScroll); onScroll(); for (const el of ROOT.querySelectorAll("[id]")) { if (el.tagName === "marker") continue; el.classList.add("haspara"); const pilcrow = document.createElement("a"); pilcrow.className = "pilcrow"; pilcrow.href = "#" + el.id; pilcrow.innerHTML = "¶"; el.prepend(pilcrow); } const foldable = (root, children) => { let state = true; root.addEventListener("click", (e) => { if (e.target.classList.contains("pilcrow")) state = true; else state = !state; if (state) children.classList.remove("closed"); else children.classList.add("closed"); if (state) root.classList.remove("closed"); else root.classList.add("closed"); }); root.classList.add("toggle-root"); }; const make_foldable = (root) => { const child_stacks = new Array(10).fill(null).map(() => ({ children: [], root: null })); const flush_header = (this_level, sibling) => { for (let level = 9; level >= this_level; level--) { const stack = child_stacks[level]; if (!stack.root) continue; const new_e = document.createElement("div"); new_e.classList.add("toggle-section"); for (const old_e of stack.children) { old_e.remove(); new_e.appendChild(old_e); } if (stack.root.tagName !== "H1") foldable(stack.root, new_e); let parent_level; for (parent_level = level - 1; parent_level > 0; parent_level--) if (child_stacks[parent_level].root) break; if (parent_level === -1) { if (sibling) root.insertBefore(new_e, sibling); else root.appendChild(new_e); } else { stack.root.remove(); child_stacks[parent_level].children.push(stack.root); child_stacks[parent_level].children.push(new_e); } stack.root = null; stack.children.length = 0; } }; let end = null; for (const child of [...root.children]) { if (child.tagName === "FOOTER") { end = child; break; } if (/^H\d$/.test(child.tagName)) { const this_level = parseInt(child.tagName[1]) - 1; flush_header(this_level, child); child_stacks[this_level].root = child; continue; } for (let level = 9; level >= 0; level--) { if (child_stacks[level].root) { child_stacks[level].children.push(child); break; } } } for (let level = 9; level >= 0; level--) { flush_header(level, end); } }; make_foldable(ROOT);