
I wrote about 5 quick hacks for your Ghost theme last year, after switching to Ghost as a blogging platform. The last hack I mentioned was generating a "table of contents" using a handlebars script I'd found. Ghost was still considered beta at the time, and when 1.0 was released, the script stopped working correctly. I never bothered going back to figure out why.
But a table of contents is nice to have, and convenient for your visitors, so I wrote a new script that should work for any html page (with minor adjustments). You can get it from GitHub too.
/**
* For displaying a table of contents - pass the entire document (DOM) to writeTocToDocument
*/
function getHeaderLevel(header) {
return Number(header.nodeName.slice(-1));
}
function createTocMarkup(headers) {
var prevLevel = 1;
var output = "";
headers.forEach(function(h) {
var currLevel = getHeaderLevel(h);
if (currLevel > prevLevel) {
var ranOnce = false;
while (currLevel > prevLevel) {
if (ranOnce) {
output += " ";
}
output += "<ol style=\"margin-bottom:0px\"><li>";
prevLevel += 1;
ranOnce = true;
}
} else if (currLevel == prevLevel) {
output += "</li><li>";
} else if (currLevel < prevLevel) {
while (currLevel < prevLevel) {
output += "</li></ol>";
prevLevel -= 1;
}
output += "<li>";
}
output += `<a href="#${h.id}">${h.innerText}</a>`;
});
if (output != "") {
// Change 2 to the max header level you want in the TOC; in my case, H2
while (prevLevel >= 2) {
output += "</li></ol>";
prevLevel -= 1;
}
output = `<h2 class="widget-title">Table of Contents</h2><div style="margin-left:-10px">${output}</div>`;
}
return output;
}
function getTocMarkup(document) {
// I was only interested in the headers within the element that had the .post-content class,
// which is specific to the Ghost blog. If you're using this elsewhere, or are interested in
// the entire document, delete this line and use document.querySelectorAll(...) on the next line.
var body = document.getElementsByClassName('post-content')[0];
// Add or remove header tags you do (or don't) want to include in the TOC
var headers = body.querySelectorAll('h2, h3, h4, h5, h6');
// Change the number to 1 if you want headers no matter what.
// Or if you want at least 3 headers before generating a TOC, change it to 3.
if (headers.length >= 2) {
return createTocMarkup(headers);
} else {
return "";
}
}
General Usage
Just call the function and write the return value out to the page.
<script type="text/javascript">
document.write(getTocMarkup(document));
</script>
Usage in Ghost
Here's how I've got it displayed in the side bar in Ghost.
-
Copy the above script into a file named
toc.js
, and drop it in theassets/js
directory. -
Reference the file from
default.hbs
, somewhere between the<head></head>
tags so it's available as the page loads.<script type="text/javascript" src="{{asset "js/toc.js"}}"></script>
-
Call it from wherever you want to display it.
Those steps are generic to Ghost. Here's how I got it to work with the Wildbird theme.
-
Modify
post.hbs
, near the bottom where it inserts the sidebar, so that it also passes a reference to the current page into the sidebar.{{!-- The tag below includes the theme sidebar - partials/sidebar.hbs --}} {{> sidebar this}}
-
Modify the
sidebar.hbs
file so that, if the current page is a "post", it'll insert a new section containing your table of contents.{{!-- Table of Contents --}} {{#is "post"}} <section class="widget widget-text"> {{#post}} <script type="text/javascript"> document.write(getTocMarkup(document)); </script> {{/post}} </section><!-- .widget --> {{/is}}
Snapshots
Here's how it looks when rendered.
A single-layer of headers:
Two layers of headers:
Multiple layers of headers:
It somewhat handles omitted headers, like going right from H2 to H5, but not really nicely.
Some styling applied to remove the numbers and indent on linewrap
