Example: Generating a table-of-content from your HTML via JavaScript
So I couldn't get the "Build a Table of Contents from your HTML" example working easily.
Instead of trying to overcome the problems, I wasted three days to create my own solution...
I thought, maybe others would benefit from it, so I'll post it below.
So first, you have to add a placeholder to your document – for example:
<section class='no-margin-content prose prose-lg'>
<h1 class="toc-exclude">Inhalt</h1>
<div id="table-of-content"></div>
</section>
The only requirement here, is an element with the ID "table-of-content", which is the element where the table-of-content will be inserted.
I'm using Tailwind CSS and the Typography plugin of Tailwind. If you don't want to use Tailwind, you'd have to adapt my code (more information below). prose prose-lg
are classes from the Typography plugin.
For no-margin-content
(as the name implies, a page layout with margins, but without any generated CSS content), you can add something like the following to your CSS:
@page noMarginContent {
margin-top: theme('spacing.20');
margin-bottom: theme('spacing.24');
margin-left: theme('spacing.20');
margin-right: theme('spacing.20');
@top-right { content: none; }
@bottom-left { content: none; }
@bottom-right { content: none; }
}
.no-margin-content {
page: noMarginContent;
The theme
function comes from Tailwind via PostCSS. You could just define values via vanilla CSS (e.g. 30pt
).
The only other CSS that's required, is the generated CSS content for the page numbers:
.toc-page-placeholder::after {
content: target-counter(attr(data-href), page);
}
The last part is, to add the following JavaScript (somewhere after the paged.js
polyfill):
// Generate Table-of-Content
class handlers extends Paged.Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
}
beforeParsed(content) {
generate_table_of_content(content)
}
}
Paged.registerHandlers(handlers);
// Generate a table-of-content from headings in the document:
function generate_table_of_content(content) {
content.querySelector("#table-of-content").innerHTML = `<nav></nav>`
const toc = content.querySelector("#table-of-content nav")
// Feel free, to add or remove heading levels:
const headlines = content.querySelectorAll("h1, h2, h3, h4")
let level // Indention level of TOC entry
// Current heading level:
let h1 = 0
let h2 = 0
let h3 = 0
let h4 = 0
let hl_path // Current heading level path (eg. '1.7.3')
// Dummy element to create elements via `innerHTML` using strings
const div = document.createElement("div");
for (hl of headlines.values()) {
// Exclude headings that are marked with the "toc-exclude" class:
if (
hl.className.split(" ").includes("toc-exclude")
// Or have a parent with .toc-exclude
|| hl.closest(".toc-exclude") !== null
) {
continue
}
const tag = hl.nodeName
if (tag === "H1") {
level = "pl-0"
h1 = h1 + 1
// Reset all child heading levels:
h2 = 0
h3 = 0
h4 = 0
hl_path = `${h1}`
// set ID so we can link to the heading
hl.id = `hl-${hl_path}`
} else if (tag === "H2") {
level = "pl-6"
h2 = h2 + 1
h3 = 0
h4 = 0
hl_path = `${h1}.${h2}`
hl.id = `hl-${hl_path}`
} else if (tag === "H3") {
level = "pl-16 -ml-0.5"
h3 = h3 + 1
h4 = 0
hl_path = `${h1}.${h2}.${h3}`
hl.id = `hl-${hl_path}`
} else if (tag === "H4") {
level = "pl-32 -ml-3.5"
h4 = h4 + 1
hl_path = `${h1}.${h2}.${h3}.${h4}`
hl.id = `hl-${hl_path}`
} else {
throw "No headline element matched"
}
// Create TOC entry:
div.innerHTML = `<div class="flex relative leading-relaxed mb-4">
<span class="absolute bottom-0 -z-10 overflow-hidden max-w-full tracking-widest text-gray-700">.........................................................................................................................................................................</span>
<div class="${level} inline-block pr-2 bg-white font-black flex-shrink-0">
${hl_path}.
</div>
<div class="inline-block flex-grow">
<a href='#${hl.id}' class="no-underline bg-white pr-1">${hl.innerHTML}</a>
</div>
<div class="pl-1 bg-white inline-block font-black flex-shrink-0 self-end">
<span class="toc-page-placeholder" data-href="#${hl.id}"></span>
</div>
</div>`.trim()
// Add generated TOC entry:
toc.append(div.firstChild)
}
}
Again, I'm using TailwindCSS, so you'd have to adapt the above, if you don't want to use TailwindCSS. Basically, you'd go to the documentation of Tailwind, and paste every CSS class into the search box at the top. If it isn't found, then you don't need to do anything (these are non-Tailwind classes). If it is found, you add the CSS of the class to your CSS file.
You can exclude headings from the table-of-content by adding the class 'toc-exclude' to them, or by wrapping them in a parent with that class (I use this, for example, to exclude sub-headings of the section with legal information, or to exclude the heading of the TOC itself).