Adding a table of contents
Tags: webdev, gatsby, ghost, cms
Published: 2020-07-31 19:40:55

---
title: Adding a table of contents
published: true
date: 2020-04-21 07:00:00 UTC
tags: #webdev #gatsby #ghost #cms 
canonical_url: https://www.jamify.org/2020/04/21/adding-a-table-of-contents/
---

Learn how you can add an auto-generated, interactive Tables of Contents pane within seconds. That's why I have plenty of time to delve into more advanced customization techniques. Learn how to write your own plugin for chapter numberings and make use of component shadowing to customize styles.

 ![Adding a table of contents](https://images.unsplash.com/photo-1470225620780-dba8ba36b745?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ)

A table of contents section, commonly abbreviated as ToC, gives your readers a quick overview of the topics you touch upon and makes it easier to browse through your article.

In electronic publications, a ToC can be made interactive, so chapters of interest are just one click away. While a random blog article may not need a ToC, tutorials, guides and documentation articles of all kinds, benefit a lot from it.

## Jamify starter

Let's start jamming by downloading a fresh starter project from Github:

```bash
$ git clone https://github.com/styxlab/gatsby-starter-try-ghost.git jamify-toc
```

and change into the work directory:

```bash
$ cd jamify-toc
```

## Installing the ToC

Here you can fully leverage Jamify's plugin approach. As is the case for many Gatsby projects, new functionality can be integrated by installing a plugin thereby harnessing the power of the javascript plugin eco-system. Install the following plugin to integrate a ToC box into your site's user interface:

```bash
$ yarn add gatsby-theme-ghost-toc
```

The plugin must be registered in `gatsby-config.js`, put it right after the `gatsby-transformer-rehype` plugin:

```javascript
// In your gatsby-config.js
plugins: [
    {
        resolve: `gatsby-theme-ghost-toc`,
    },
]
```

> Note that the ToC is auto-generated from the headlines of each article. This is done by the plugin `gatsby-transformer-rehype` which is already included in the starter. That's why you do not need to install this dependency.

Fire up the development build:

```bash
[jamify-toc]$ yarn develop
```

And visit your site at `http://localhost:8000` as usual. Switch to an article containing headlines in order to see the ToC in action:

 ![Adding a table of contents](https://cms.jamify.org/content/images/2020/05/jamify-toc-small-2.png)

 ![Adding a table of contents](https://cms.jamify.org/content/images/2020/05/jamify-toc-wide-2.png)

For small screens, a ToC is added after the feature image and right before the main content area. For larger screens, the ToC is moved to the right hand side and is made sticky, so it is always visible when you scroll down.

The current document position is highlighted in color, giving your readers a visual indication of the current location within the document. Clicking on ToC items brings you directly to the chapter. If you have wide content such as full screen gallery pictures, the ToC box slides under your images:

![Adding a table of contents](https://cms.jamify.org/content/images/2020/05/jamify-toc-img-overlap.png)

Depending on the headline level, ToC items are indented. You can see this in the picture above, where the `h3` heading `Image sizes` is moved to the right, visualizing the relationship to its `h2` parent `Working with images in posts`.

## Configuration Options

If you are missing a configuration option in the `gatsby-theme-ghost-toc` plugin, tell us and we might implement it in a future release. For the time being, there is just one option available:

```javascript
// In your gatsby-config.js
plugins: [
    {
        resolve: `gatsby-theme-ghost-toc`,
        options: {
            maxDepth: 2,
        }
    },
]
```

The `maxDepth` option restricts the number of shown headline levels. With the default value of 2 you get a primary heading and one sub-heading. You can increase this number up to 6 levels, but if your heading levels get too convoluted it doesn't look great on your page. A value between one and three should be fine for most use cases.

## Developer friendliness

From a user's perspective, this is all you need to know and the given options may be exactly what you have been looking for.

However, you may have different needs. If you are designing a blog for a customer, you may want to know how you can further customize the style of the ToC. As an application developer, you may wish to understand how the automatic ToC generation is working.

> Jamify is all about enabling you to achieve your publishing goals, giving you a framework that is easy to extend and fun to work with.

Let's delve into these more advanced topics that are connected to the underlying framework before making specific customizations to the ToC.

## Automatic ToC Generation

I mentioned earlier that the ToC tree structure is generated in the transformer plugin that comes pre-installed with the starter. So, what is the `gatsby-transformer-rehype` plugin actually doing?

It's main task is to take an `html` fragment as input, make some transformations to it and spit out the changed `html` again. The special property of the transformation is that it uses an intermediate transformation step:

```text
HTML -> htmlAST -> htmlAST -> HTML
        └── plugin1: mutating htmlAST / └── -> ToC 
                              └── plugin2: mutating /    
```

The HTML is never changed directly, it is first transformed to a so called syntax tree, here it is an html syntax tree called `htmlAST`.  The cool thing about the syntax tree is that it's really easy to manipulate it and that you can use standard libraries to do so.

Another important concept is that the transformer plugin usually does not manipulate the `htmlAST` itself, it delegates this work to sub-plugins. All sub-plugins to `gatsby-transformer-rehype` follow a naming convention, so they start with `gatsby-rehype-*` which indicates that they perform a specific manipulation task on the `htmlAST`.

What does this have to do with the automatic ToC generation? Well, `gatsby-transformer-rehype` generates a ToC from the last `htmlAST`, after all sub-plugins have completed their transformations. This ToC is then put into the GraphQL schema and you can query it from your React components as usual:

```graphql
{
  allHtmlRehype {
    edges {
      node {
        html
        tableOfContents
      }
    }
  }
}
```

This is exactly how the `gatsby-theme-ghost-toc` plugin is retrieving the `tableOfContents` data, using it to make a great user interface out of it.

Armed with this information about the Jamify framework, you can now make changes to the ToC data.

## Chapter numbering

If you want to publish a thesis or a book, your chapters and subsections typically follow a numbering scheme. How can you automatically generate chapter numbers, add them to the headings and make sure they also show up in your ToC?

As there is currently no standard plugin available for this use case, you can easily write your own. Before doing so, let's think about how this requirement can be accomplished with Jamify. From what you have learned in the previous chapter, it's quite obvious that you can use a `gatsby-rehype-*` plugin: For every post, you get an `htmlAst` from the transformer that you can manipulate.

So, the idea is to create a plugin named `gatsby-rehype-chapter`, where you take the `htmlAst`, visit each heading and add the chapter number to it. As the mutated `htmlAst` ist automatically transformed back to `html` by the transformer, your changes will then show up both in your headings and in your ToC. The difficult part is to write a function that generates correct numberings, but that's not hard either.

### Your first rehype plugin

When starting a new plugin it's always a good idea to implement and test it locally. Gatsby provides an extremely convenient way to do that. You simply place your plugin in a folder called `plugins` and it will be resolved like external plugins during build time:

```bash
[jamify-toc]$ mkdir -p plugins/gatsby-rehype-chapters
[jamify-toc]$ cd plugins/gatsby-rehype-chapters
```

Every plugin must contain a `package.json` file. We can create it with `npm init`:

```bash
[gatsby-rehype-chapters]$ npm init
```

Answer the upcoming questions as follows:

```json
{
  "name": "gatsby-rehype-chapters",
  "version": "1.0.0",
  "description": "Add chapter numbering to headings",
  "main": "gatsby-node.js",
  "author": "",
  "license": "MIT"
}

```

It's import that you type `gatsby-node.js` as your entry point instead of the default `index.js`. This is the file where we implement the plugin functionality. Create and open a new `gatsby-node.js` file in your favourite editor and paste the following code into it:

```javascript
const visit = require(`unist-util-visit`)

module.exports = ({
  htmlAst,
  generateTableOfContents,
  htmlNode,
  getNode,
  reporter
}) => {

    const map = []

    // recursive walk to generate numbering
    const numberingOfChapters = (toc, numbering = ``, depth = 1, chapter = 1) => {
        toc.forEach((node) => {
            map.push({ id: node.id, numbering: `${numbering}${chapter}.` })
            if (node.items && node.items.length > 0 && depth < 7) {
                numberingOfChapters(node.items, `${numbering}${chapter}.`, depth + 1)
            }
            chapter = chapter + 1
        })
    }
    numberingOfChapters(generateTableOfContents(htmlAst))

    const tags = [`h1`,`h2`,`h3`,`h4`,`h5`,`h6`]
    const headings = node => tags.includes(node.tagName)

    visit(htmlAst, headings, node => {
        const id = node.properties && node.properties.id || `error-missing-id`
        const [child] = node.children
        if (child.type === `text`) {
            const { numbering } = map.find(node => node.id === id)
            child.value = numbering + ` ` + child.value
        }
    })

    return htmlAst
}

```

This is a lot of code, but I walk you through it. You export one function, which needs to be called by the transformer plugin. That's why the function must conform to the transformer's requirements: It's got a specific list of input arguments (most notably `htmlAst` and some others) and it must return the changed `htmlAst`. All `gatsby-rehype-*` plugins are of this form, taking `htmlAst` as input, manipulate it and give it back again. It may look trivial at first, but it's a powerful concept.

The function body performs two tasks. The first part generates the numbering within function `numberingOfChapters`. The second part utilizes the `visit` function which is used here to find the headings within the `htmlAst`. Once a heading is found, it's previously generated numbering is looked up from the `map` and then prefixed to the heading.

The `numberingOfChapters` is quite interesting. It uses recursion for generating all sub-levels and it is called with a tree structure that was generated with the function `generateTableOfContents(htmlAst)`. So, we can leverage the on-demand computed ToC structure to generate the numberings!

> You may ask yourself how the ToC can contain the numbering, if the plugin only mutates the headings. This magic is possible due to a clever design of the transformer: a final ToC is always generated based on the latest mutation!

### Register your plugin

There is only one final step missing. You need to register your plugin in your `gatsby-config.js`. Don't forget to change to the root of your working directory and make the following changes:

```javascript
// gatsby-config.js
{
    resolve: `gatsby-transformer-rehype`,
    options: {
        filter: node => (
            node.internal.type === `GhostPost` ||
            node.internal.type === `GhostPage`
        ) && node.slug !== `data-schema`,
        plugins: [
            {
                resolve: `gatsby-rehype-ghost-links`,
            },
            {
                resolve: `gatsby-rehype-prismjs`,
            },
            {
                resolve: `gatsby-rehype-chapters`,
            },
        ],
    },
},
```

The `gatsby-rehype-chapters` plugin must be registered as a sub-plugin of `gatsby-transformer-rehype`. As the other two `gatsby-rehype-*` plugins are doing completely different things, the order within the sub-plugins does not matter here.

### Numbering in action

It's time to inspect the results. Rebuild the project in development mode with `yarn develop` and go to one of the posts:

![Adding a table of contents](https://cms.jamify.org/content/images/2020/05/jamify-toc-chapters-2.png)

As you can see, numberings have been added to the titles and the Table of Contents side pane.

> The data generation and user interface ToC pane is entirely done during build time, effectively offloading computational heavy work from the point in time where you site is served to your visitors. This is the crucial concept behind making your sites flaring fast.

## Customize Styling

So far you have worked with making changes to the underlying data: the headings and the ToC that is generated from it. Now, it's time to make changes to the style. We could look at the code of plugin `gatsby-theme-ghost-toc` and make changes directly there.

However, if you only intend to make a small change, there is a much better way to customize `gatsby-theme-ghost-toc`: through component shadowing. The basic idea is that you only rewrite a small portion of the plugin that is relevant to you. You just need to put a matching file into a place where Gatsby can replace the original.

Let's use component shadowing to change the highlighting color of the ToC from green to blue. As a first step, you need to locate the code where the ToC is styled. The relevant code block can be found in file`TableOfContentStyles.js`, at the end:

```javascript
export const TocLink = styled(Link)`
    && {
        height: 100%;
        box-shadow: none;
        color: ${props => (props.state.isActive ? `#54BC4B` : `inherit`)} !important;
        border-bottom: ${props => (props.state.isActive ? `1px solid #54BC4B` : `none`)};
        text-decoration: none;
        &:hover {
            color: #54BC4B !important;
            border-bottom: 1px solid #54BC4B;
            text-decoration: none;
            box-shadow: none;
        }
        &::before {
            background-color: #EEE;
            content:' ';
            display: inline-block;
            height: inherit;
            left: 0;
            position:absolute;
            width: 2px;
            margin-left: 1px;
        }
        &::before {
            background-color: ${props => (props.state.isActive ? `#54BC4B` : `#EEE`)};
        }
    }
`
```

There are five occurrences of the green color `#54BC4B` that we want to replace with the blue color `#30B4F9` on a copy of that file. The copy must be put into a replicated folder structure under the `src/` folder:

```bash
[jamify-toc]$ mkdir -p src/gatsby-theme-ghost-toc/components/common
[jamify-toc]$ cp node_modules/gatsby-theme-ghost-toc/components/common/TableOfContentStyles.js src/gatsby-theme-ghost-toc/components/common/
```

Take note on how the directory structure matches the original:

```text
src/gatsby-theme-ghost-toc/
└── components
    └── common
        └── TableOfContentStyles.js

```

Finally you can replace the color with this one-liner:

```bash
[jamify-toc]$ sed -i "s|#54BC4B|#30B4F9|g" src/gatsby-theme-ghost-toc/components/common/TableOfContentStyles.js
```

After rebuilding the project with `yarn develop`, you should see the color change in the ToC highlight color.

![Adding a table of contents](https://cms.jamify.org/content/images/2020/05/jamify-toc-style-1.png)

Note that you only changed one file of the `gatsby-theme-ghost-toc` plugin, so your shadowed file will most likely work even after a new plugin version has been published. That's another benefit of component shadowing: you still get the improvements that are published to the original plugin.

## Summary

I hope this tutorial conveyed that adding a ToC to you Jamify site is dead simple. This pre-configured ToC already contains a lot of cool features: auto-generation of ToC from existing headers, moving the ToC box to the side for large screens, jumping to a headline by clicking a ToC item, highlighting of ToC items as you scroll through the document and an configuration option to control the headline levels.

While this is great, you can do even more! You learned how to write a rehype plugin that let's you generate and add chapter numberings to you titles and ToC items. Once you come to the realization that you can use exactly the same approach to make changes to _anything within your blog content,_ you'll start feeling the power in your own hands. Use it with care!

While the plugin approach is best suited for content changes or computations based on content, customizations to the presentation layer such as style changes are usually best accomplished with component shadowing. This is another super powerful concept that you learned in this tutorial. You used it to change the highlighting color of ToC items, but it can be used to change any functionality of every plugin that you use in your project. Even more power at your hands!

> [Do you want early access to Blogody, the brand new blogging platform that I am creating? Just sign-up on the new Blogody landing page and be among the first to get notified!](https://www.blogody.com/landing)

***

This post was originally published at [jamify.org](https://www.jamify.org/2020/04/21/adding-a-table-of-contents/) on April 21, 2020.