Skip to content

A Metalsmith plugin to auto-generate table of contents from headings

License

Notifications You must be signed in to change notification settings

metalsmith/table-of-contents

Repository files navigation

@metalsmith/table-of-contents

A metalsmith plugin to generate table of contents.

metalsmith: core plugin npm version ci: build code coverage license: MIT

@metalsmith/table-of-contents generates tables of contents for all files with a toc: true key in their frontmatter, and attaches a table-of-contents tree to the file's metadata.

Installation

NPM:

npm install @metalsmith/table-of-contents

Yarn:

yarn add @metalsmith/table-of-contents

Usage

Add @metalsmith/table-of-contents to your metalsmith build:

const metalsmith = require('metalsmith')
const toc = require('@metalsmith/table-of-contents')
const layouts = require('@metalsmith/layouts')

metalsmith(__dirname).use(toc()) // defaults
metalsmith(__dirname)
  .use(
    toc({
      // explicit defaults
      levels: [2, 3, 4, 5, 6],
      anchor: 'add',
      root: null
    })
  )
  .use(layouts()) // use layouts/in-place for custom HTML rendering
  .build()

Specify toc: true in the file's frontmatter:

---
title: Hello World!
toc: true
---

<h2>First subtitle</h2>
<h2>Second subtitle</h2>

...which will be transformed in JS to:

{
  title: 'Hello World',
  toc: {
    level: 1,
    items: [
      {
        level: 2,
        anchor: 'first-subtitle',
        items: []
      },
      {
        level: 2,
        anchor: 'second-subtitle',
        items: []
      },
    ]
  },
  contents: Buffer.from('....')
}

Options

All options are optional.

  • levels: (number[]) - specify an array of numbers from 1-6 (matching h1-6 tags). Default is [2,3,4,5,6]
  • anchor: ('add'|'keep'|'overwrite'|Function) - a strategy for handling heading ID's and anchor links:
    • 'add' (default) will use existing id's, and add new id's to elements without id
    • 'keep': will only use existing id's
    • 'overwrite': will overwrite existing id's, and add new id's to elements without id
    • Function: you can specify a custom callback which gets the cheerio element as parameter, e.g. ($el) => $el.attr('id')
  • root: (string) - Optional root selector to search for headings. Useful if you want to target headings in a specific element, e.g. article.main-content.

Rendering

By default @metalsmith/table-of-contents can be toString()ed in templates. Eg. with Handlebars you could just do {{{ toc }}}. If the templating language executes in a JS context (like underscore templates), you can simply call toString: <%= toc.toString() %>. This will render a default HTML like:

<ol class="toc">
  <li class="toc-item">
    <a class="toc-link" href="#first-title">First title</a>
  </li>
</ol>

Custom rendering

If you need to customize the rendered HTML, you can always use @metalsmith/layouts or @metalsmith/in-place with a custom template partial. Below is an example with an inline Handlebars partial:

{{#*inline "renderToc" }}
<ol class="toc">
  {{#each .}}
  <li data-level="{{ level }}">
    <a href="#{{{ anchor }}}">{{ text }}</a>
    {{#if items.length }} {{> renderToc items }} {{/if}}
  </li>
  {{/each}}
</ol>
{{/inline}} {{> renderToc toc.items }}

For each TOC item you have access to the properties text,anchor,level,items,parent.

Compatibility with <a name=""> anchors

Sometimes you need to re-use existing <a name=""> tags as anchors instead of the headings themselves (eg when using @metalsmith/markdown or a plugin like jsdoc-to-md. In that case you can use a custom function for the anchor option like so:

metalsmith.use(
  tableOfContents({
    anchor($el) {
      const $anchor = $el.prev()
      return $anchor.length ? $anchor.attr('name') : null
    }
  })
)

Debug

To enable debug logs, set the DEBUG environment variable to @metalsmith/table-of-contents:

Linux/Mac:

DEBUG=@metalsmith/table-of-contents

Windows:

set "DEBUG=@metalsmith/table-of-contents"

Alternatively you can set DEBUG to @metalsmith/* to debug all Metalsmith core plugins.

CLI Usage

To use this plugin with the Metalsmith CLI, add @metalsmith/table-of-contents to the plugins key in your metalsmith.json file:

{
  "plugins": [
    {
      "@metalsmith/table-of-contents": {
        "levels": [2, 3, 4, 5, 6],
        "anchor": "add",
        "root": null
      }
    }
  ]
}

Caveat

According to the HTML specification a <section><h1>Title</h1></section> will be recognized as a <h2>. This plugin does not take section roots into account, so you should explicitly use h1-h6 tags.

Credits

Credit goes to anatoo for creating the original metalsmith-autotoc, on which this plugin is based.

License

MIT