Having a table of contents (TOC) makes an article more readable. Our goal is to automatically generate TOC out of the headings in an article, for a site based on Jekyll and hosted on GitHub Pages, which is the setup of this site.

avonlea-jewelry-2TmFSvM8d0Q-unsplash

Generating the TOC

The site generated by Jekyll is basically a set of static HTML pages. It is therefore possible to collect the HTML heading elements (h1, h2, etc.) and generate a TOC using JavaScript, as these libraries do. However, those libraries do not work well, if the heading elements are not only used in the article, but also used in the header or footer of the site, which is the case for this site. For example, the “Memory Spills” site title in the footer is wrapped in a <h2> tag by Jekyll. That means “Memory Spills” would appear in the TOC, which is not nice.

Fortunately, rendering of table of contents (TOC) is supported by kramdown, which is Jekyll 3.0.0’s default Markdown processor, and, as a side note, the only supported Markdown processor on GitHub Pages. Adding the following code snippet directly after the front matter will do the trick:

---
My front matter
---

* TOC
{:toc}
My first paragraph.

Note that no empty lines shall be present between the {:toc} tag and the first paragraph of the article (My first paragraph. in the code example), or else the latter will not be included in the post.excerpt variable, which is used for multiple purposes on this site. First, the post.excerpt variable is used in the home layout to show an excerpt of the post content for each post, in addition to its post title. That way the post content can be previewed on the home page(s), before a reader decides to click the read more button which links to the complete post content. Second, the post.excerpt variable is also used to populate the description meta tags important for SEO.

Adding the TOC heading

The rendered TOC is just a list of article headings. It would be nice if it has the TOC heading “Contents” or “Table of contents”. It turns out this is a bit tricky.

With Markdown

To insert such a TOC heading, one can use the following markdown code:

---
My front matter
---

**Contents**
* TOC
{:toc}
My first paragraph.

This will insert a bold “Contents” line as the heading before the TOC, which is nice, but the word “Contents” becomes a part of the post.excerpt variable, and is consequently shown as a part of the preview, which is not so nice. Therefore, I had to cut off the extra Contents string as follows:

{{ post.excerpt | strip_html | replace_first: 'Contents', '' | lstrip | truncatewords: 40, "" }}<a href="{{ post.url | relative_url }}">&hellip; read more &raquo;</a>

Remember the ‘post.excerpt’ variable is also used to populate the description meta tags. There I found no easy way to get rid of the extra Contents string, which is not nice.

With JavaScript

Therefore, I removed the **Contents** heading from the markdown, and came up with a JavaScript hack.

function domReady() {
    var toc = document.getElementById('markdown-toc');
    if (toc) {
        toc.insertAdjacentHTML('beforebegin', '<p><strong>Table of contents</strong></p>');
    }
}

if ( document.addEventListener ) { // Mozilla, Opera, Webkit
    document.addEventListener( 'DOMContentLoaded', function() {
        document.removeEventListener( 'DOMContentLoaded', arguments.callee, false);
        domReady();
    }, false );
} else if ( document.attachEvent ) { // If IE event model is used
    // ensure firing before onload
    document.attachEvent('onreadystatechange', function() {
        if ( document.readyState === 'complete' ) {
            document.detachEvent( 'onreadystatechange', arguments.callee );
            domReady();
        }
    });
}

The above code can be included ({ % include javascript.html %}) as the last line in _includes/footer.html, so that the <script> tag appears immediately before the closing </body> tag.

This works well without poluting the post.excerpt variable. But is it possible to do this without the JavaScript hack?

With CSS

It turns out arbitrary text can be inserted using CSS, e.g.,

#my-id::before {
  content: "my text before the element with the id my-id";
}

The complete solution is adding the following CSS code to this file:

#markdown-toc::before {
  content: "Contents";
  margin-left: -$spacing-unit;
  line-height: 200%;
  font-weight: bold;
  @include relative-font-size(1.25);
  @include media-query($on-laptop) {
    @include relative-font-size(1.125);
  }
}

This pure CSS solution touches only one file, and, as with the JavaScript solution, it does not polute the post.excerpt variable.

However, I am not sure how to deal with i18n, e.g., for an article written in Chinese, one would prefer to add “目录” instead of “Contents”. That could be easier with the JavaScript solution. If you have a good solution, please leave a comment. Thanks!