Creating a TOC for my Ghost Blog

When I write huge articles, I tend to add more sections in my blog post. It's always a tedious work, when it comes to getting the right markup (or markdown) for the Table of Contents. It had been pretty hectic for these days when I had to check all the lines starting with # and make sure I get the right representation of the id of the tag and get it working as a link.

A good thing about my writing style is that it includes only headings starting with <h2> tag and maximum it goes till <h4>, with usage of <h4> being the least of all. As usual, being a lazy developer, I switched to automate it instead of doing it manually, after a few pretty solid tests.

I did a nifty JavaScript / jQuery script that can run on the console to generate the markdown for me:

var headings = [];  
$(".c-content").find("h2, h3, h4, h5").each(function () {
  var spaces = "";
  if (this.localName == "h2") spaces = "";
  if (this.localName == "h3") spaces = "  ";
  if (this.localName == "h4") spaces = "    ";
  if (this.localName == "h5") spaces = "      ";
  headings.push(`${spaces}1. [${this.innerHTML}](#${this.id})`);
});
headings.join("\n");  

Working of the Script above

Since JavaScript / jQuery process line by line, I leveraged that to create the ordered Table of Contents for my markdown. Well, this is how it works.

Starting with an empty array to hold all my headings, and at the end, I would like to combine everything with a new line. So, I created this:

var headings = [];  
headings.join("\n");  

Okay, so what's going to be inside the array? I wanted to loop through the headings. I am definitely sure I won't go beyond <h5> for any reason. Trusting that, I skipped it for <h6>. Well, a good article wouldn't have much deeper sections. What do you think?

Moving on, I create the loop this way:

$(".c-content").find("h2, h3, h4, h5").each(function () {

});

I am using Ghost, The Professional Publishing Platform to write my blog. I have my own instance running in my own server at My Adventures. Alright, the reason I said this is, unlike the very vulnerable WordPress, Ghost has a unique way of handling themes and posts.

I decided to create my own custom theme with the help of my friends from Egypt, and like how WordPress has the default .entry-content class for the contents, I decided to use .c-content for the same (Just making sure that there's no copy of WordPress vulnerabilities even in CSS). This way, all the above said tags, i.e., <h2>, <h3>, <h4>, <h5> reside inside that class.

This is the reason for the selector this way:

$(".c-content").find("h2, h3, h4, h5")

I have used jQuery's .each() for iterating over the tags and getting the information. Using console.log(this) I was able to learn there's a way to get the current tag name.

this.localName; // Gives you the current tag in lowercase. Eg: "h2"  

Thanks to JavaScript. We need to do indentation in markdown based on the section weight. What I meant was, when it's <h2>, it's the parent and <h3> becomes the child and so on. I wrote a quick JavaScript if ... else statement for this:

  var spaces = "";
  if (this.localName == "h2") spaces = "";
  if (this.localName == "h3") spaces = "  ";
  if (this.localName == "h4") spaces = "    ";
  if (this.localName == "h5") spaces = "      ";

That was pretty useful for me to generate the spaces. Since I was using arrays at the first instant, I was using functions like .push() and .join() instead of crappy string concatenation. Yes, array functions are more performant than string concatenation (but I don't care, as it's going to be less than 15 nodes and I am on my new shiny MacBook Pro 2015 Retina).

headings.push(`${spaces}1. [${this.innerHTML}](#${this.id})`);  

The above code pushes the contents this way:

1. [This is heading 2.](#linkslugforheading)  
1. [This is heading 2.](#linkslugforheading)  
  1. [This is heading 3.](#linkslugforheading)
  1. [This is heading 3.](#linkslugforheading)
  1. [This is heading 3.](#linkslugforheading)
1. [This is heading 2.](#linkslugforheading)  
1. [This is heading 2.](#linkslugforheading)  
  1. [This is heading 3.](#linkslugforheading)
1. [This is heading 2.](#linkslugforheading)  

I don't mind if all the lines are starting with 1. as markdown is smart enough to understand that it's an ordered list and just uses <li> accordingly with <ol> as its parent. The link slug is automatically generated by Ghost and I have to make sure I don't put the same heading for two.

The id is generated by stripping off all the crazy characters and converting them to lowercase. I could probably guess the RegEx for the slugs are [A-Za-z0-9] and that's it. No spaces or special characters (like me) are allowed.

So with those above stuff, I have got my code ready:

var headings = [];  
$(".c-content").find("h2, h3, h4, h5").each(function () {
  var spaces = "";
  if (this.localName == "h2") spaces = "";
  if (this.localName == "h3") spaces = "  ";
  if (this.localName == "h4") spaces = "    ";
  if (this.localName == "h5") spaces = "      ";
  headings.push(`${spaces}1. [${this.innerHTML}](#${this.id})`);
});
headings.join("\n");  

I hope you would have enjoyed learning this nifty stuff along with me.



comments powered by Disqus