function renderTableOfContents() { const root = document.querySelector("table-of-contents"); if (root === null) { console.error("No element found, unable to render table of contents!"); return; } const list = document.createElement("ul"); const headers = document.querySelectorAll("h2,h3,h4,h5,h6"); const ancestors = []; for (const header of headers) { const depth = parseInt(header.tagName.slice(1)) - 1; while (ancestors.length >= depth) { ancestors.pop(); } const li = document.createElement("li"); const a = document.createElement("a"); if (header.querySelector("code") !== null) { const code = document.createElement("code"); code.textContent = header.textContent; a.append(code); } else { a.textContent = header.textContent; } a.href = `#${header.id}`; const ul = document.createElement("ul"); li.replaceChildren(a, ul); const mostRecent = ancestors[ancestors.length - 1]; if (mostRecent === undefined) { list.appendChild(li); } else { mostRecent.appendChild(li); } ancestors.push(ul); } const title = document.createElement("h2"); title.textContent = "Table of contents"; root.replaceChildren(title, list); } function groupHeadersInDiv(headerTag, index = 0) { const elements = document.querySelectorAll(headerTag); const firstElement = elements[index]; if (!firstElement) { return; } let container = document.createElement("div"); console.log(firstElement); firstElement.replaceWith(container); container.append(firstElement); while (true) { const sib = container.nextSibling; if (sib === null) { break; } if (sib.nodeName === headerTag.toUpperCase()) { container = document.createElement("div"); sib.replaceWith(container); index++; } container.append(sib); } if (index < elements.length) { groupHeadersInDiv(headerTag, index + 1); } } function slugify(elements) { return elements .map((element, i) => { if (i === 0) return null; return element.textContent.trim(); }) .filter((x) => x !== null) .join("-"); } function attachIdToHeaders() { const headers = document.querySelectorAll("h1,h2,h3,h4,h5,h6"); const ancestors = []; for (const header of headers) { const depth = parseInt(header.tagName.slice(1)); while (ancestors.length >= depth) { ancestors.pop(); } ancestors.push(header); if (depth > 1) header.id = slugify(ancestors); } } attachIdToHeaders(); renderTableOfContents(); for (let i = 2; i <= 6; ++i) { groupHeadersInDiv(`h${i}`); }