function toc(options = {}) {
if (typeof options === "string") {
options = {
headers: options
};
}
const {
headers = "h2,h3,h4,h5,h6",
hideStartingFrom = null,
title = "Table of contents"
} = options;
return Generators.observe(notify => {
let previousHeadings = [];
let renderedEmptyToC = false;
function observed() {
const currentHeadings = Array.from(document.querySelectorAll(headers));
if (!currentHeadings.length) {
if (!renderedEmptyToC) {
notify(html`Unable to generate ToC: no headings found`);
renderedEmptyToC = true;
}
return;
}
if (
currentHeadings.length !== previousHeadings.length ||
currentHeadings.some((h, i) => previousHeadings[i] !== h)
) {
renderedEmptyToC = false;
let currentIndentation;
let startIndentation = headers
.split(',')
.map(h => parseInt(h.replace(/h/g, '')))
.sort()[0];
previousHeadings = currentHeadings;
const entries = [];
for (const h of Array.from(previousHeadings)) {
if (hideStartingFrom && h.textContent === hideStartingFrom) {
break;
}
let nodeIndentiation = parseInt(h.tagName[1], 10);
if (typeof currentIndentation === 'undefined') {
currentIndentation = startIndentation = nodeIndentiation;
} else {
while (currentIndentation < nodeIndentiation) {
entries.push('<ul>');
currentIndentation++;
}
while (currentIndentation > nodeIndentiation) {
entries.push('</ul>');
currentIndentation--;
}
}
entries.push(
Object.assign(
html`<li><a href=#${h.id}>${DOM.text(h.textContent)}`,
{ onclick: e => (e.preventDefault(), h.scrollIntoView()) }
)
);
}
while (currentIndentation > startIndentation) {
entries.push('</ul>');
currentIndentation--;
}
let content;
if (typeof title === "string") {
content = html`<b>${title}</b><ul>${entries}`;
} else {
content = html`<ul>${entries}`;
}
notify(content);
}
}
const observer = new MutationObserver(observed);
observer.observe(document.body, { childList: true, subtree: true });
observed();
return () => observer.disconnect();
});
}