Background (TL;DR)
While setting up the new version of Staticman for my demo GitLab pages, I’ve read developers’ documentations, setup guide and some community blog posts so as to come up with my own guide. It’s originated and inspired from a variety of sources, and refined according to hours of testing. Consequently, despite the original intention to keep things simple, I’ve finally come up with a post with over a thousand words.
To pass my ideas in this post to visitors, it’s better that they have an overview of the contents before actually looking into the details. Therefore, a table of contents is nice-to-have feature for this blog.
Problem
Hugo provides a convenient function {{ .TableOfContents }}
for this. It
converts the headings of the page into an unordered list of anchors. However,
the post’s title and subtitle, which are represented as <h1>
and <h2>
, are
taken into account. As a result, the generated table of contents looks
awkward.
-
- Section 1
- subsection 1.1
- subsection 1.2
- Section 2
- Section 1
-
This explains Hugo’s issue #1778.
Goal
To generate a table of contents like the one above.
First attempt
In spite of the eleven likes for Micheal Blum’s snippet from
layouts/partials/table-of-contents.html
, GitLab’s CD informed me of
the failure of job 98224023 through email.
From the message, I opened this blog’s bash command list to test the TOC. Surprisingly, the generated links pointed to my post on laptop fan cleaning.
I tried looking at the related HTML template files, from which I couldn’t understand. As I’m not supposed to do testing testing after the term has started, I abandoned this approach.
Second attempt
Ryan Parman rewrote the aforementioned HTML template file and published it as gist a796d66f. However, when I performed the same test again, the same error was reproduced.
Final solution
Thanks to Yihui Xie’s simple JavaScript, I managed to get this fixed, at least, in runtime level.
Single page testing in browser
To understand what his script actually did, before including this in my theme’s footer, I ran some of the lines in the web developer’s console in the browser.
- ✓:
toc = document.getElementById('TableOfContents')
- ✗:
var toc = document.getElementById('TableOfContents')
What does Xie’s script do?
- Extract the element with id
TableOfContents
. - Set variable
ul
to be the unordered list directly under#TableOfContents
. - Proceed only when
ul
has more than one child. (A TOC usually contains more than one section, so only the top levelul
containing one single item will get “peeled off”.) - Verify if
ul
exists, exit if it doesn’t. - Set variable
li
to be the first child. It’s supposed to be the first list item. - Run a check if
li
really represents an<li>
. - Extract the only list element’s internal HTML use this to replace its parent’s external HTML.
Xie’s script enabled me to move one step forward toward the real solution.
-
- Section 1
- subsection 1.1
- subsection 1.2
- Section 2
- Section 1
The leftmost dot was still there. Nonetheless, the above verbal analysis of his code allowed me to decouple the logic and to adapt it for other blog themes.
An ugly hard code solution is to copy the above lines and rename the second
instance of the unordered list as ul2
in the JavaScript—that’s too hard to
swallow. I would definitely go for a loop when doing repetitive tasks—that’s
what machines are made for.
Adaptations to Xie’s script to Beautiful Hugo
Since ul
and li
are changing elements during this tag unwrapping process, we
wrap the lines containing them with a loop. As the first iteration is run
unconditionally, a do-while loop is chosen for this task. Failing any of the
if statements would end the process, so we replace return
with break
.
Finally, just put any condition that allows the loop to run.
// Copyright (c) 2017 Yihui Xie & 2018 Vincent Tam under MIT
(function() {
var toc = document.getElementById('TableOfContents');
if (!toc) return;
do {
var li, ul = toc.querySelector('ul');
if (ul.childElementCount !== 1) break;
li = ul.firstElementChild;
if (li.tagName !== 'LI') break;
// remove <ul><li></li></ul> where only <ul> only contains one <li>
ul.outerHTML = li.innerHTML;
} while (toc.childElementCount >= 1);
})();
Whole site regenerated
After local testing with hugo server -D
, I published my changes to the site’s
theme. After updating the theme, the problem’s now fixed.
To instantly view the changes on the site’s theme, I have to modify the copy of
the theme’s files under the local repo for the site. However, it’s better to
commit the changes to another separate repo ~/beautifulhugo
so as to make my
theme clean. It will be laborious and prone to errors to type out the
whole path by hand. A more efficient solution will be posted in
the next post.