Heading Links
The comark/plugins/heading-links plugin links each heading to itself, using the id the parser gives every heading. Readers can click a heading to get a link to that section.
The links are added to the document tree, so every renderer outputs them without a custom heading component. Headings inside components are linked too.
Usage
import { parseMarkdown } from 'comark'
import headingLinks from 'comark/plugins/heading-links'
const result = await parseMarkdown('## Setup', {
plugins: [headingLinks()]
})<h2 id="setup"><a href="#setup">Setup</a></h2>API
headingLinks(options?)
Returns a ComarkPlugin that adds a link to each matching heading.
Parameters:
options?- Optional configuration, see Options
Returns: ComarkPlugin
Headings without an id are left unchanged. With the 'wrap' behavior, headings that already contain a link are also left unchanged, since links cannot be nested.
Added links are marked in the tree with $.permalink: 'wrap' for a link around the heading text, 'separate' for a link before or after it. Renderers don't output $, and the marker has two uses:
- Markdown output leaves the links out, so a tree parsed with this plugin can be saved back to Markdown unchanged.
tocandtitle-descriptionleave a'separate'link out of heading text. Code of your own that reads heading text can do the same.
ProseH2. The plugin can't see those components, so each heading would end up with two nested links.Options
| Option | Type | Default | Description |
|---|---|---|---|
tags | string[] | ['h2', 'h3', 'h4'] | Heading tags that get a link |
behavior | 'wrap' | 'prepend' | 'append' | 'wrap' | Where the link goes |
content | Node[] | ['#'] | Link content for 'prepend' and 'append' |
props | Record<string, unknown> | {} | Extra props for the link |
tags
Heading tags that get a link.
headingLinks({ tags: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] }) // link every heading levelDefault: ['h2', 'h3', 'h4']
behavior
Where the link goes:
'wrap': the link wraps the heading content.'prepend': a separate link is inserted before the heading content.'append': a separate link is inserted after the heading content.
headingLinks({ behavior: 'prepend' })<h2 id="setup"><a href="#setup">#</a>Setup</h2>Default: 'wrap'
content
Content of the link for 'prepend' and 'append', as Comark nodes. Ignored for 'wrap'.
headingLinks({
behavior: 'append',
content: [['span', { class: 'icon-link' }]],
props: { 'aria-label': 'Link to this section' },
})<h2 id="setup">Setup<a aria-label="Link to this section" href="#setup"><span class="icon-link"></span></a></h2>Default: ['#']
props
Extra props for the link. href is always set from the heading id.
headingLinks({ behavior: 'prepend', props: { class: 'heading-link' } })Default: {}
Examples
Show the link on hover
Add a class to a separate # link, then show it only when the heading is hovered or the link has keyboard focus:
headingLinks({ behavior: 'prepend', props: { class: 'heading-link' } }).heading-link {
margin-inline-end: 0.25em;
opacity: 0;
}
:is(h2, h3, h4):hover .heading-link,
.heading-link:focus-visible {
opacity: 1;
}With the table of contents
toc and title-description leave a separate prepend or append link out of heading text, so the plugins work in any order:
import headingLinks from 'comark/plugins/heading-links'
import toc from 'comark/plugins/toc'
const result = await parseMarkdown(content, {
plugins: [headingLinks(), toc()]
})