Today I implemented a feature I was thinking about: A ToC on the top of all the articles.
Originally, my idea was to have pelican generate it statically. That would enable the ToC for subscribers of the Atom News Feed too. But it was harder than including a piece of JavaScript in the HTML template and some feed readers might already show their own ToC.
So now let's see how I went about it and create a lot of headings in the process so the generator creates a lot of output.
My quick development process
I want to see the first three levels
Level based approach
My first idea was to have
const toc_root = { level: 1, children: [], parent: undefined, };
Pros
- Would be able to detect the highest level
- Very long titles would wrap correctly
Cons
- Hard to implement
- Coming back to JavaScript from Rust knowing what is a copy vs. what is a borrow/reference is hard and lead me down a very strange path
CSS based approach
Pros
- Simple to implement
Cons
- Long titles could give a reader the wrong impression of the level
The Code
This section will now show up in the ToC:
JavaScript
(function() {
//! Render a TOC on the top of Blog Articles
if (is_article() == false) {
return
}
const entry_content = document.querySelector("div.entry-content");
let toc = '';
let id = 0;
for (let heading of entry_content.querySelectorAll("h1,h2,h3")) {
id += 1;
const title = heading.innerText;
const id_string = `${id}`;
const depth = parseInt(heading.nodeName.substr(1));
const a = document.createElement("a");
a.name = id_string;
heading.appendChild(a);
toc += ` <a class="toc_level${depth}" href="#${id_string}">${title}</a>\n`;
}
if (id === 0) { return }
toc = `<div class="toc_frame">\n <h1>Table of Contents:</h1>\n <div class="toc_entries">\n${toc} </div>\n</div>`;
entry_content.innerHTML = toc + entry_content.innerHTML;
function is_article() {
const parts = location.pathname.split("/");
const first = parts[1] || '';
if (first.length !== 4) { return false }
const year = parseInt(first, 10);
return year > 2000 && year < 2100;
}
})()
CSS
.toc_frame h1 { margin-top: 0; font-size: 1.2rem; }
.toc_frame .toc_entries a { display: block; }
.toc_frame .toc_entries a.toc_level1 { margin-left: 0em; }
.toc_frame .toc_entries a.toc_level2 { margin-left: 1em; }
.toc_frame .toc_entries a.toc_level3 { margin-left: 2em; }