@metalsmith/table-of-contents
A metalsmith plugin to generate table of contents.
@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 (matchingh1-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
.
<a name="">
anchors
Compatibility with 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.