499 lines
15 KiB
Markdown
499 lines
15 KiB
Markdown
|
# rehype-autolink-headings
|
|||
|
|
|||
|
[![Build][build-badge]][build]
|
|||
|
[![Coverage][coverage-badge]][coverage]
|
|||
|
[![Downloads][downloads-badge]][downloads]
|
|||
|
[![Size][size-badge]][size]
|
|||
|
[![Sponsors][sponsors-badge]][collective]
|
|||
|
[![Backers][backers-badge]][collective]
|
|||
|
[![Chat][chat-badge]][chat]
|
|||
|
|
|||
|
**[rehype][]** plugin to add links from headings back to themselves.
|
|||
|
|
|||
|
## Contents
|
|||
|
|
|||
|
* [What is this?](#what-is-this)
|
|||
|
* [When should I use this?](#when-should-i-use-this)
|
|||
|
* [Install](#install)
|
|||
|
* [Use](#use)
|
|||
|
* [API](#api)
|
|||
|
* [`unified().use(rehypeAutolinkHeadings[, options])`](#unifieduserehypeautolinkheadings-options)
|
|||
|
* [`Behavior`](#behavior)
|
|||
|
* [`Build`](#build)
|
|||
|
* [`BuildProperties`](#buildproperties)
|
|||
|
* [`Options`](#options)
|
|||
|
* [Examples](#examples)
|
|||
|
* [Example: different behaviors](#example-different-behaviors)
|
|||
|
* [Example: building content with `hastscript`](#example-building-content-with-hastscript)
|
|||
|
* [Example: passing content from a string of HTML](#example-passing-content-from-a-string-of-html)
|
|||
|
* [Example: group](#example-group)
|
|||
|
* [Types](#types)
|
|||
|
* [Compatibility](#compatibility)
|
|||
|
* [Security](#security)
|
|||
|
* [Related](#related)
|
|||
|
* [Contribute](#contribute)
|
|||
|
* [License](#license)
|
|||
|
|
|||
|
## What is this?
|
|||
|
|
|||
|
This package is a [unified][] ([rehype][]) plugin to add links from headings
|
|||
|
back to themselves.
|
|||
|
It looks for headings (so `<h1>` through `<h6>`) that have `id` properties,
|
|||
|
and injects a link to themselves.
|
|||
|
Similar functionality is applied by many places that render markdown.
|
|||
|
For example, when browsing this readme on GitHub or npm, an anchor is added
|
|||
|
to headings, which you can share to point people to a particular place in a
|
|||
|
document.
|
|||
|
|
|||
|
**unified** is a project that transforms content with abstract syntax trees
|
|||
|
(ASTs).
|
|||
|
**rehype** adds support for HTML to unified.
|
|||
|
**hast** is the HTML AST that rehype uses.
|
|||
|
This is a rehype plugin that adds links to headings in the AST.
|
|||
|
|
|||
|
## When should I use this?
|
|||
|
|
|||
|
This plugin is useful when you have relatively long documents, where you want
|
|||
|
users to be able to link to particular sections, and you already have `id`
|
|||
|
properties set on all (or certain?) headings.
|
|||
|
|
|||
|
A different plugin, [`rehype-slug`][rehype-slug], adds `id`s to headings.
|
|||
|
When a heading doesn’t already have an `id` property, it creates a slug from
|
|||
|
it, and adds that as the `id` property.
|
|||
|
When using both plugins together, all headings (whether explicitly with a
|
|||
|
certain `id` or automatically with a generate one) will get a link back to
|
|||
|
themselves.
|
|||
|
|
|||
|
## Install
|
|||
|
|
|||
|
This package is [ESM only][esm].
|
|||
|
In Node.js (version 16+), install with [npm][]:
|
|||
|
|
|||
|
```sh
|
|||
|
npm install rehype-autolink-headings
|
|||
|
```
|
|||
|
|
|||
|
In Deno with [`esm.sh`][esmsh]:
|
|||
|
|
|||
|
```js
|
|||
|
import rehypeAutolinkHeadings from 'https://esm.sh/rehype-autolink-headings@7'
|
|||
|
```
|
|||
|
|
|||
|
In browsers with [`esm.sh`][esmsh]:
|
|||
|
|
|||
|
```html
|
|||
|
<script type="module">
|
|||
|
import rehypeAutolinkHeadings from 'https://esm.sh/rehype-autolink-headings@7?bundle'
|
|||
|
</script>
|
|||
|
```
|
|||
|
|
|||
|
## Use
|
|||
|
|
|||
|
Say we have the following file `example.html`:
|
|||
|
|
|||
|
```html
|
|||
|
<h1>Solar System</h1>
|
|||
|
<h2>Formation and evolution</h2>
|
|||
|
<h2>Structure and composition</h2>
|
|||
|
<h3>Orbits</h3>
|
|||
|
<h3>Composition</h3>
|
|||
|
<h3>Distances and scales</h3>
|
|||
|
<h3>Interplanetary environment</h3>
|
|||
|
<p>…</p>
|
|||
|
```
|
|||
|
|
|||
|
…and our module `example.js` contains:
|
|||
|
|
|||
|
```js
|
|||
|
import {rehype} from 'rehype'
|
|||
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
|||
|
import rehypeSlug from 'rehype-slug'
|
|||
|
import {read} from 'to-vfile'
|
|||
|
|
|||
|
const file = await rehype()
|
|||
|
.data('settings', {fragment: true})
|
|||
|
.use(rehypeSlug)
|
|||
|
.use(rehypeAutolinkHeadings)
|
|||
|
.process(await read('example.html'))
|
|||
|
|
|||
|
console.log(String(file))
|
|||
|
```
|
|||
|
|
|||
|
…then running `node example.js` yields:
|
|||
|
|
|||
|
```html
|
|||
|
<h1 id="solar-system"><a aria-hidden="true" tabindex="-1" href="#solar-system"><span class="icon icon-link"></span></a>Solar System</h1>
|
|||
|
<h2 id="formation-and-evolution"><a aria-hidden="true" tabindex="-1" href="#formation-and-evolution"><span class="icon icon-link"></span></a>Formation and evolution</h2>
|
|||
|
<h2 id="structure-and-composition"><a aria-hidden="true" tabindex="-1" href="#structure-and-composition"><span class="icon icon-link"></span></a>Structure and composition</h2>
|
|||
|
<h3 id="orbits"><a aria-hidden="true" tabindex="-1" href="#orbits"><span class="icon icon-link"></span></a>Orbits</h3>
|
|||
|
<h3 id="composition"><a aria-hidden="true" tabindex="-1" href="#composition"><span class="icon icon-link"></span></a>Composition</h3>
|
|||
|
<h3 id="distances-and-scales"><a aria-hidden="true" tabindex="-1" href="#distances-and-scales"><span class="icon icon-link"></span></a>Distances and scales</h3>
|
|||
|
<h3 id="interplanetary-environment"><a aria-hidden="true" tabindex="-1" href="#interplanetary-environment"><span class="icon icon-link"></span></a>Interplanetary environment</h3>
|
|||
|
<p>…</p>
|
|||
|
```
|
|||
|
|
|||
|
## API
|
|||
|
|
|||
|
This package exports no identifiers.
|
|||
|
The default export is [`rehypeAutolinkHeadings`][api-rehype-autolink-headings].
|
|||
|
|
|||
|
### `unified().use(rehypeAutolinkHeadings[, options])`
|
|||
|
|
|||
|
Add links from headings back to themselves.
|
|||
|
|
|||
|
###### Parameters
|
|||
|
|
|||
|
* `options` ([`Options`][api-options], optional)
|
|||
|
— configuration
|
|||
|
|
|||
|
###### Returns
|
|||
|
|
|||
|
Transform ([`Transformer`][unified-transformer]).
|
|||
|
|
|||
|
###### Notes
|
|||
|
|
|||
|
This plugin only applies to headings with `id`s.
|
|||
|
Use `rehype-slug` to generate `id`s for headings that don’t have them.
|
|||
|
|
|||
|
Several behaviors are supported:
|
|||
|
|
|||
|
* `'prepend'` (default) — inject link before the heading text
|
|||
|
* `'append'` — inject link after the heading text
|
|||
|
* `'wrap'` — wrap the whole heading text with the link
|
|||
|
* `'before'` — insert link before the heading
|
|||
|
* `'after'` — insert link after the heading
|
|||
|
|
|||
|
### `Behavior`
|
|||
|
|
|||
|
Behavior (TypeScript type).
|
|||
|
|
|||
|
###### Type
|
|||
|
|
|||
|
```ts
|
|||
|
type Behavior = 'after' | 'append' | 'before' | 'prepend' | 'wrap'
|
|||
|
```
|
|||
|
|
|||
|
### `Build`
|
|||
|
|
|||
|
Generate content (TypeScript type).
|
|||
|
|
|||
|
###### Parameters
|
|||
|
|
|||
|
* `element` ([`Element`][hast-element])
|
|||
|
— current heading
|
|||
|
|
|||
|
###### Returns
|
|||
|
|
|||
|
Content ([`Array<Node>`][hast-node] or `Node`).
|
|||
|
|
|||
|
### `BuildProperties`
|
|||
|
|
|||
|
Generate properties (TypeScript type).
|
|||
|
|
|||
|
###### Parameters
|
|||
|
|
|||
|
* `element` ([`Element`][hast-element])
|
|||
|
— current heading
|
|||
|
|
|||
|
###### Returns
|
|||
|
|
|||
|
Properties ([`Properties`][hast-properties]).
|
|||
|
|
|||
|
### `Options`
|
|||
|
|
|||
|
Configuration (TypeScript type).
|
|||
|
|
|||
|
###### Fields
|
|||
|
|
|||
|
* `behavior` ([`Behavior`][api-behavior], default: `'prepend'`)
|
|||
|
— how to create links
|
|||
|
* `content` ([`Array<Node>`][hast-node], `Node`, or [`Build`][api-build],
|
|||
|
default: if `'wrap'` then `undefined`, otherwise equivalent of
|
|||
|
`<span class="icon icon-link"></span>`)
|
|||
|
— content to insert in the link;
|
|||
|
if `behavior` is `'wrap'` and `Build` is passed, its result replaces the
|
|||
|
existing content, otherwise the content is added after existing content
|
|||
|
* `group` ([`Array<Node>`][hast-node], `Node`, or [`Build`][api-build],
|
|||
|
optional)
|
|||
|
— content to wrap the heading and link with, if `behavior` is `'after'` or
|
|||
|
`'before'`
|
|||
|
* `headingProperties` ([`BuildProperties`][api-build-properties] or
|
|||
|
[`Properties`][hast-properties], optional)
|
|||
|
— extra properties to set on the heading
|
|||
|
* `properties` ([`BuildProperties`][api-build-properties] or
|
|||
|
[`Properties`][hast-properties], default:
|
|||
|
`{ariaHidden: true, tabIndex: -1}` if `'append'` or `'prepend'`, otherwise
|
|||
|
`undefined`)
|
|||
|
— extra properties to set on the link when injecting
|
|||
|
* `test` ([`Test`][hast-util-is-element-test], optional)
|
|||
|
— extra test for which headings are linked
|
|||
|
|
|||
|
## Examples
|
|||
|
|
|||
|
### Example: different behaviors
|
|||
|
|
|||
|
This example shows what each behavior generates by default.
|
|||
|
|
|||
|
```js
|
|||
|
import {rehype} from 'rehype'
|
|||
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
|||
|
|
|||
|
const behaviors = ['after', 'append', 'before', 'prepend', 'wrap']
|
|||
|
let index = -1
|
|||
|
while (++index < behaviors.length) {
|
|||
|
const behavior = behaviors[index]
|
|||
|
console.log(
|
|||
|
String(
|
|||
|
await rehype()
|
|||
|
.data('settings', {fragment: true})
|
|||
|
.use(rehypeAutolinkHeadings, {behavior})
|
|||
|
.process('<h1 id="' + behavior + '">' + behavior + '</h1>')
|
|||
|
)
|
|||
|
)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Yields:
|
|||
|
|
|||
|
```html
|
|||
|
<h1 id="after">after</h1><a href="#after"><span class="icon icon-link"></span></a>
|
|||
|
<h1 id="append">append<a aria-hidden="true" tabindex="-1" href="#append"><span class="icon icon-link"></span></a></h1>
|
|||
|
<a href="#before"><span class="icon icon-link"></span></a><h1 id="before">before</h1>
|
|||
|
<h1 id="prepend"><a aria-hidden="true" tabindex="-1" href="#prepend"><span class="icon icon-link"></span></a>prepend</h1>
|
|||
|
<h1 id="wrap"><a href="#wrap">wrap</a></h1>
|
|||
|
```
|
|||
|
|
|||
|
### Example: building content with `hastscript`
|
|||
|
|
|||
|
The following example passes `options.content` as a function, to generate an
|
|||
|
accessible description specific to each link.
|
|||
|
It uses [`hastscript`][hastscript] to build nodes.
|
|||
|
|
|||
|
```js
|
|||
|
import {h} from 'hastscript'
|
|||
|
import {toString} from 'hast-util-to-string'
|
|||
|
import {rehype} from 'rehype'
|
|||
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
|||
|
|
|||
|
const file = await rehype()
|
|||
|
.data('settings', {fragment: true})
|
|||
|
.use(rehypeAutolinkHeadings, {
|
|||
|
content(node) {
|
|||
|
return [
|
|||
|
h('span.visually-hidden', 'Read the “', toString(node), '” section'),
|
|||
|
h('span.icon.icon-link', {ariaHidden: 'true'})
|
|||
|
]
|
|||
|
}
|
|||
|
})
|
|||
|
.process('<h1 id="pluto">Pluto</h1>')
|
|||
|
|
|||
|
console.log(String(file))
|
|||
|
```
|
|||
|
|
|||
|
Yields:
|
|||
|
|
|||
|
```html
|
|||
|
<h1 id="pluto"><a aria-hidden="true" tabindex="-1" href="#pluto"><span class="visually-hidden">Read the “Pluto” section</span><span class="icon icon-link" aria-hidden="true"></span></a>Pluto</h1>
|
|||
|
```
|
|||
|
|
|||
|
### Example: passing content from a string of HTML
|
|||
|
|
|||
|
The following example passes `content` as nodes.
|
|||
|
It uses [`hast-util-from-html-isomorphic`][hast-util-from-html-isomorphic] to
|
|||
|
build nodes from a string of HTML.
|
|||
|
|
|||
|
```js
|
|||
|
/**
|
|||
|
* @typedef {import('hast').ElementContent} ElementContent
|
|||
|
*/
|
|||
|
|
|||
|
import {fromHtmlIsomorphic} from 'hast-util-from-html-isomorphic'
|
|||
|
import {rehype} from 'rehype'
|
|||
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
|||
|
|
|||
|
const file = await rehype()
|
|||
|
.data('settings', {fragment: true})
|
|||
|
.use(rehypeAutolinkHeadings, {
|
|||
|
content: /** @type {Array<ElementContent>} */ (
|
|||
|
fromHtmlIsomorphic(
|
|||
|
'<svg height="10" width="10"><circle cx="5" cy="5" r="5" fill="black" /></svg>',
|
|||
|
{fragment: true}
|
|||
|
).children
|
|||
|
)
|
|||
|
})
|
|||
|
.process('<h1 id="makemake">Makemake</h1>')
|
|||
|
|
|||
|
console.log(String(file))
|
|||
|
```
|
|||
|
|
|||
|
Yields:
|
|||
|
|
|||
|
```html
|
|||
|
<h1 id="makemake"><a aria-hidden="true" tabindex="-1" href="#makemake"><svg height="10" width="10"><circle cx="5" cy="5" r="5" fill="black"></circle></svg></a>Makemake</h1>
|
|||
|
```
|
|||
|
|
|||
|
### Example: group
|
|||
|
|
|||
|
The following example passes `group` as a function, to dynamically generate a
|
|||
|
differing element that wraps the heading.
|
|||
|
It uses [`hastscript`][hastscript] to build nodes.
|
|||
|
|
|||
|
```js
|
|||
|
import {h} from 'hastscript'
|
|||
|
import {rehype} from 'rehype'
|
|||
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
|||
|
|
|||
|
const file = await rehype()
|
|||
|
.data('settings', {fragment: true})
|
|||
|
.use(rehypeAutolinkHeadings, {
|
|||
|
behavior: 'before',
|
|||
|
group(node) {
|
|||
|
return h('.heading-' + node.tagName.charAt(1) + '-group')
|
|||
|
}
|
|||
|
})
|
|||
|
.process('<h1 id="ceres">Ceres</h1>')
|
|||
|
|
|||
|
console.log(String(file))
|
|||
|
```
|
|||
|
|
|||
|
Yields:
|
|||
|
|
|||
|
```html
|
|||
|
<div class="heading-1-group"><a href="#ceres"><span class="icon icon-link"></span></a><h1 id="ceres">Ceres</h1></div>
|
|||
|
```
|
|||
|
|
|||
|
## Types
|
|||
|
|
|||
|
This package is fully typed with [TypeScript][].
|
|||
|
It exports the additional types
|
|||
|
[`Behavior`][api-behavior],
|
|||
|
[`Build`][api-build],
|
|||
|
[`BuildProperties`][api-build-properties], and
|
|||
|
[`Options`][api-options].
|
|||
|
|
|||
|
## Compatibility
|
|||
|
|
|||
|
Projects maintained by the unified collective are compatible with maintained
|
|||
|
versions of Node.js.
|
|||
|
|
|||
|
When we cut a new major release, we drop support for unmaintained versions of
|
|||
|
Node.
|
|||
|
This means we try to keep the current release line,
|
|||
|
`rehype-autolink-headings@^7`, compatible with Node.js 16.
|
|||
|
|
|||
|
This plugin works with `rehype-parse` version 1+, `rehype-stringify` version 1+,
|
|||
|
`rehype` version 1+, and `unified` version 4+.
|
|||
|
|
|||
|
## Security
|
|||
|
|
|||
|
Use of `rehype-autolink-headings` can open you up to a
|
|||
|
[cross-site scripting (XSS)][xss] attack if you pass user provided content in
|
|||
|
`content`, `group`, or `properties`.
|
|||
|
|
|||
|
Always be wary of user input and use [`rehype-sanitize`][rehype-sanitize].
|
|||
|
|
|||
|
## Related
|
|||
|
|
|||
|
* [`rehype-slug`][rehype-slug]
|
|||
|
— add `id`s to headings
|
|||
|
* [`rehype-highlight`](https://github.com/rehypejs/rehype-highlight)
|
|||
|
— apply syntax highlighting to code blocks
|
|||
|
* [`rehype-toc`](https://github.com/JS-DevTools/rehype-toc)
|
|||
|
— add a table of contents (TOC)
|
|||
|
|
|||
|
## Contribute
|
|||
|
|
|||
|
See [`contributing.md`][contributing] in [`rehypejs/.github`][health] for ways
|
|||
|
to get started.
|
|||
|
See [`support.md`][support] for ways to get help.
|
|||
|
|
|||
|
This project has a [code of conduct][coc].
|
|||
|
By interacting with this repository, organization, or community you agree to
|
|||
|
abide by its terms.
|
|||
|
|
|||
|
## License
|
|||
|
|
|||
|
[MIT][license] © [Titus Wormer][author]
|
|||
|
|
|||
|
<!-- Definitions -->
|
|||
|
|
|||
|
[build-badge]: https://github.com/rehypejs/rehype-autolink-headings/workflows/main/badge.svg
|
|||
|
|
|||
|
[build]: https://github.com/rehypejs/rehype-autolink-headings/actions
|
|||
|
|
|||
|
[coverage-badge]: https://img.shields.io/codecov/c/github/rehypejs/rehype-autolink-headings.svg
|
|||
|
|
|||
|
[coverage]: https://codecov.io/github/rehypejs/rehype-autolink-headings
|
|||
|
|
|||
|
[downloads-badge]: https://img.shields.io/npm/dm/rehype-autolink-headings.svg
|
|||
|
|
|||
|
[downloads]: https://www.npmjs.com/package/rehype-autolink-headings
|
|||
|
|
|||
|
[size-badge]: https://img.shields.io/bundlejs/size/rehype-autolink-headings
|
|||
|
|
|||
|
[size]: https://bundlejs.com/?q=rehype-autolink-headings
|
|||
|
|
|||
|
[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
|
|||
|
|
|||
|
[backers-badge]: https://opencollective.com/unified/backers/badge.svg
|
|||
|
|
|||
|
[collective]: https://opencollective.com/unified
|
|||
|
|
|||
|
[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg
|
|||
|
|
|||
|
[chat]: https://github.com/rehypejs/rehype/discussions
|
|||
|
|
|||
|
[npm]: https://docs.npmjs.com/cli/install
|
|||
|
|
|||
|
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
|||
|
|
|||
|
[esmsh]: https://esm.sh
|
|||
|
|
|||
|
[health]: https://github.com/rehypejs/.github
|
|||
|
|
|||
|
[contributing]: https://github.com/rehypejs/.github/blob/main/contributing.md
|
|||
|
|
|||
|
[support]: https://github.com/rehypejs/.github/blob/main/support.md
|
|||
|
|
|||
|
[coc]: https://github.com/rehypejs/.github/blob/main/code-of-conduct.md
|
|||
|
|
|||
|
[license]: license
|
|||
|
|
|||
|
[author]: https://wooorm.com
|
|||
|
|
|||
|
[hast-element]: https://github.com/syntax-tree/hast#element
|
|||
|
|
|||
|
[hast-node]: https://github.com/syntax-tree/hast#nodes
|
|||
|
|
|||
|
[hast-util-is-element-test]: https://github.com/syntax-tree/hast-util-is-element#test
|
|||
|
|
|||
|
[hast-properties]: https://github.com/syntax-tree/hast#properties
|
|||
|
|
|||
|
[hastscript]: https://github.com/syntax-tree/hastscript
|
|||
|
|
|||
|
[hast-util-from-html-isomorphic]: https://github.com/syntax-tree/hast-util-from-html-isomorphic
|
|||
|
|
|||
|
[rehype]: https://github.com/rehypejs/rehype
|
|||
|
|
|||
|
[rehype-sanitize]: https://github.com/rehypejs/rehype-sanitize
|
|||
|
|
|||
|
[typescript]: https://www.typescriptlang.org
|
|||
|
|
|||
|
[unified]: https://github.com/unifiedjs/unified
|
|||
|
|
|||
|
[unified-transformer]: https://github.com/unifiedjs/unified#transformer
|
|||
|
|
|||
|
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
|
|||
|
|
|||
|
[rehype-slug]: https://github.com/rehypejs/rehype-slug
|
|||
|
|
|||
|
[api-behavior]: #behavior
|
|||
|
|
|||
|
[api-build]: #build
|
|||
|
|
|||
|
[api-build-properties]: #buildproperties
|
|||
|
|
|||
|
[api-options]: #options
|
|||
|
|
|||
|
[api-rehype-autolink-headings]: #unifieduserehypeautolinkheadings-options
|