docs/headers.js
2023-06-19 02:37:46 +01:00

171 lines
4.9 KiB
JavaScript

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 = "&para;";
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);