September 16, 2018

How to add a Ghost Blog Table of Contents automatically (without jQuery)

Add an automatic dynamic table of contents to a Ghost blog using Javascript.

How to add a Ghost Blog Table of Contents automatically (without jQuery)

Updated December 2018! Improvements:

  • Automatically places the table of contents before the first heading
  • Works on multi-level headings
  • Changed it to pure JavaScript to avoid having to load jQuery

When I published the Professional Australian Glossary I decided to add a table of contents. It was a large article. But I thought... there's got to be a way of automating this. And if there isn't, I'll build one!

I pieced this together from around the web, but had to redo it a bit to make it work in Ghost.

Introduction

Tables of contents are almost mandatory to making a blog post easier to read. There are plugins for WordPress, but no easy way to do it on a Ghost blog.

Sub-heading

This is a lower-level nested block, to make sure it works with h3 and below headings.

How to install

Put this code at the bottom in an HTML block. This looks through the code and finds all the headings (tagged with h2... did you use headings?) and creates the contents.

Notes:

  • Your theme might use h1 instead of h2. Adjust the code below accordingly.
  • You don't want to include the heading for Table of Contents in the table of contents. So I included code to add that title in, and then also to look out for that heading, so as not to index it.

You have two options. First is jQuery. It's effective and cleaner. But there's pure JavaScript below.

jQuery version

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha256-3edrmyuQ0w65f8gfBsqowzjJe2iM6n0nKciPUp8y+7E=" crossorigin="anonymous"></script>
<script>
    var contentsTitle = "Table of Contents"; // Set your title here, to avoid making a heading for it later
	var ToC = "<h2>"+contentsTitle+"</h2>";
    ToC += "<nav role='navigation' class='table-of-contents'><ul>";
    var first = false;
	$("h2,h3").each(function() {
		var el = $(this);
        if (first === false) {
        	first = true;
            $('<span id="dynamictoc"></span>').insertBefore(el);
        }
        var title = el.text();
		var link = "#" + el.attr("id");
        if (el.is("h2")) {
			ToC += "<li><a href='" + link + "'>" + title + "</a></li>";
        } else if (el.is("h3")) {
        	ToC += "<li style='margin-left:2em'><a href='" + link + "'>" + title + "</a></li>";
        }
	});
	ToC += "</ul></nav>";
    console.log(ToC);
	$("#dynamictoc").html(ToC);
</script>

Pure JavaScript version

I wrote this version because I felt like loading extra resources was lazy, and Ghost doesn't use it (and I wouldn't want to rely on whatever version they were using anyway). Secondly, this is pretty basic DOM manipulation and I thought: why not just do it in pure JS anyway? ES6 is amazing, anyway.

<script>    
   var contentsTitle = "Table of Contents"; // Set your title here, to avoid making a heading for it later
	var ToC = "<h2>"+contentsTitle+"</h2>";
    ToC += "<nav role='navigation' class='table-of-contents'><ul>";
    var first = false;
 
    document.querySelectorAll('h2,h3').forEach(function(el,index) {
        if (first === false) {
        	first = true;
            var newSpan = document.createElement("SPAN");
            newSpan.id="dynamictocnative";
            el.parentNode.insertBefore(newSpan, el);
        }       
		var title = el.textContent;
		var link = "#" + el.id;
        if (el.nodeName === "H2") {
			ToC += "<li><a href='" + link + "'>" + title + "</a></li>";
        } else if (el.nodeName === "H3"){
        	ToC += "<li style='margin-left:2em'><a href='" + link + "'>" + title + "</a></li>";
        }
    });
	
	ToC += "</ul></nav>";
    var tocDiv = document.getElementById('dynamictocnative');
    tocDiv.innerHTML = ToC;
</script>

Next steps

Things I want to make this do—coming soon, but help out if you can!

  • Make this a js plugin so I can embed it in a website, and if there are more than 2 headings it makes a table of contents
  • Make it work without using jQuery - done!
  • Nested headings (h1, h2, h3 and so on) - done!
  • Automatic placement before the first heading (other than the title) - done!