Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More β†’
Socket
Sign inDemoInstall
Socket

rehype-autolink-headings

Package Overview
Dependencies
Maintainers
2
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rehype-autolink-headings - npm Package Compare versions

Comparing version 6.1.1 to 7.0.0

7

index.d.ts

@@ -1,2 +0,5 @@

export {default} from './lib/index.js'
export type Options = import('./lib/index.js').Options
export { default } from "./lib/index.js";
export type Behavior = import('./lib/index.js').Behavior;
export type Build = import('./lib/index.js').Build;
export type BuildProperties = import('./lib/index.js').BuildProperties;
export type Options = import('./lib/index.js').Options;
/**
* @typedef {import('./lib/index.js').Behavior} Behavior
* @typedef {import('./lib/index.js').Build} Build
* @typedef {import('./lib/index.js').BuildProperties} BuildProperties
* @typedef {import('./lib/index.js').Options} Options

@@ -3,0 +6,0 @@ */

/**
* Plugin to automatically add links to headings (h1-h6).
* Add links from headings back to themselves.
*
* @type {import('unified').Plugin<[Options?]|void[], Root>}
* ###### 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
*
* @param {Readonly<Options> | null | undefined} [options]
* Configuration (optional).
* @returns
* Transform.
*/
export default function rehypeAutolinkHeadings(
options?: void | Options | undefined
):
| void
| import('unified').Transformer<import('hast').Root, import('hast').Root>
export type Root = import('hast').Root
export type Parent = import('hast').Parent
export type Element = import('hast').Element
export type ElementChild = Element['children'][number]
export type Properties = import('hast').Properties
export type Test = import('hast-util-is-element').Test
export type Behavior = 'prepend' | 'append' | 'wrap' | 'before' | 'after'
export type Build = (node: Element) => ElementChild | ElementChild[]
export default function rehypeAutolinkHeadings(options?: Readonly<Options> | null | undefined): (tree: Root) => undefined;
export type Element = import('hast').Element;
export type ElementContent = import('hast').ElementContent;
export type Properties = import('hast').Properties;
export type Root = import('hast').Root;
export type Test = import('hast-util-is-element').Test;
/**
* Behavior.
*/
export type Behavior = 'after' | 'append' | 'before' | 'prepend' | 'wrap';
/**
* Generate content.
*/
export type Build = (element: Readonly<Element>) => Array<ElementContent> | ElementContent;
/**
* Generate properties.
*/
export type BuildProperties = (element: Readonly<Element>) => Properties;
/**
* Configuration.
*/
export type Options = {
/**
* How to create links.
*/
behavior?: Behavior | undefined
/**
* Please use `behavior` instead
*/
behaviour?: Behavior | undefined
/**
* Extra properties to set on the link when injecting.
* Defaults to `{ariaHidden: true, tabIndex: -1}` when `'prepend'` or
* `'append'`.
*/
properties?: import('hast').Properties | undefined
/**
* hast nodes to insert in the link.
*/
content?:
| import('hast').ElementContent
| import('hast').ElementContent[]
| Build
| undefined
/**
* hast node to wrap the heading and link with, if `behavior` is `'before'` or
* `'after'`.
* There is no default.
*/
group?:
| import('hast').ElementContent
| import('hast').ElementContent[]
| Build
| undefined
/**
* Test to define which heading elements are linked.
* Any test that can be given to `hast-util-is-element` is supported.
* The default (no test) is to link all headings.
* Can be used to link only h1-h3, or for example all except h1.
*/
test?: Test
}
/**
* How to create links (default: `'prepend'`).
*/
behavior?: Behavior | null | undefined;
/**
* Content to insert in the link (default: `<span class="icon icon-link"></span>`), if `behavior` is not `'wrap'`.
*/
content?: Readonly<ElementContent> | ReadonlyArray<ElementContent> | Build | null | undefined;
/**
* Content to wrap the heading and link with, if `behavior` is `'after'` or
* `'before'` (optional).
*/
group?: Readonly<ElementContent> | ReadonlyArray<ElementContent> | Build | null | undefined;
/**
* Extra properties to set on the link when injecting (default:
* `{ariaHidden: true, tabIndex: -1}` if `'append'` or `'prepend'`, otherwise
* `undefined`).
*/
properties?: Readonly<Properties> | BuildProperties | null | undefined;
/**
* Extra test for which headings are linked (optional).
*/
test?: Test | null | undefined;
};
/**
* Deep clone.
*
* See: <https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1237#issuecomment-1345515448>
*/
export type Cloneable<T> = T extends Record<any, any> ? { -readonly [k in keyof T]: Cloneable<T[k]>; } : T;
/**
* @typedef {import('hast').Root} Root
* @typedef {import('hast').Parent} Parent
* @typedef {import('hast').Element} Element
* @typedef {Element['children'][number]} ElementChild
* @typedef {import('hast').ElementContent} ElementContent
* @typedef {import('hast').Properties} Properties
* @typedef {import('hast').Root} Root
*
* @typedef {import('hast-util-is-element').Test} Test
*/
/**
* @typedef {'after' | 'append' | 'before' | 'prepend' | 'wrap'} Behavior
* Behavior.
*
* @typedef {'prepend'|'append'|'wrap'|'before'|'after'} Behavior
*
* @callback Build
* @param {Element} node
* @returns {ElementChild|ElementChild[]}
* Generate content.
* @param {Readonly<Element>} element
* Current heading.
* @returns {Array<ElementContent> | ElementContent}
* Content.
*
* @callback BuildProperties
* Generate properties.
* @param {Readonly<Element>} element
* Current heading.
* @returns {Properties}
* Properties.
*
* @typedef Options
* Configuration.
* @property {Behavior} [behavior='prepend']
* How to create links.
* @property {Behavior} [behaviour]
* Please use `behavior` instead
* @property {Properties} [properties]
* Extra properties to set on the link when injecting.
* Defaults to `{ariaHidden: true, tabIndex: -1}` when `'prepend'` or
* `'append'`.
* @property {ElementChild|ElementChild[]|Build} [content={type: 'element', tagName: 'span', properties: {className: ['icon', 'icon-link']}, children: []}]
* hast nodes to insert in the link.
* @property {ElementChild|ElementChild[]|Build} [group]
* hast node to wrap the heading and link with, if `behavior` is `'before'` or
* `'after'`.
* There is no default.
* @property {Test} [test]
* Test to define which heading elements are linked.
* Any test that can be given to `hast-util-is-element` is supported.
* The default (no test) is to link all headings.
* Can be used to link only h1-h3, or for example all except h1.
* @property {Behavior | null | undefined} [behavior='prepend']
* How to create links (default: `'prepend'`).
* @property {Readonly<ElementContent> | ReadonlyArray<ElementContent> | Build | null | undefined} [content={type: 'element', tagName: 'span', properties: {className: ['icon', 'icon-link']}, children: []}]
* Content to insert in the link (default: `<span class="icon icon-link"></span>`), if `behavior` is not `'wrap'`.
* @property {Readonly<ElementContent> | ReadonlyArray<ElementContent> | Build | null | undefined} [group]
* Content to wrap the heading and link with, if `behavior` is `'after'` or
* `'before'` (optional).
* @property {Readonly<Properties> | BuildProperties | null | undefined} [properties]
* Extra properties to set on the link when injecting (default:
* `{ariaHidden: true, tabIndex: -1}` if `'append'` or `'prepend'`, otherwise
* `undefined`).
* @property {Test | null | undefined} [test]
* Extra test for which headings are linked (optional).
*/
import extend from 'extend'
import {hasProperty} from 'hast-util-has-property'
/**
* @template T
* Kind.
* @typedef {(
* T extends Record<any, any>
* ? {-readonly [k in keyof T]: Cloneable<T[k]>}
* : T
* )} Cloneable
* Deep clone.
*
* See: <https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1237#issuecomment-1345515448>
*/
import structuredClone from '@ungap/structured-clone'
import {headingRank} from 'hast-util-heading-rank'
import {convertElement} from 'hast-util-is-element'
import {visit, SKIP} from 'unist-util-visit'
import {SKIP, visit} from 'unist-util-visit'

@@ -52,36 +71,60 @@ /** @type {Element} */

/** @type {Options} */
const emptyOptions = {}
/**
* Plugin to automatically add links to headings (h1-h6).
* Add links from headings back to themselves.
*
* @type {import('unified').Plugin<[Options?]|void[], Root>}
* ###### 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
*
* @param {Readonly<Options> | null | undefined} [options]
* Configuration (optional).
* @returns
* Transform.
*/
export default function rehypeAutolinkHeadings(options = {}) {
let props = options.properties
const behavior = options.behaviour || options.behavior || 'prepend'
const content = options.content || contentDefaults
const group = options.group
const is = convertElement(options.test)
export default function rehypeAutolinkHeadings(options) {
const settings = options || emptyOptions
let props = settings.properties
const behavior = settings.behavior || 'prepend'
const content = settings.content || contentDefaults
const group = settings.group
const is = convertElement(settings.test)
/** @type {import('unist-util-visit/complex-types').Visitor<Element>} */
/** @type {import('unist-util-visit').Visitor<Element>} */
let method
if (behavior === 'wrap') {
if (behavior === 'after' || behavior === 'before') {
method = around
} else if (behavior === 'wrap') {
method = wrap
} else if (behavior === 'before' || behavior === 'after') {
method = around
} else {
method = inject
if (!props) {
props = {ariaHidden: 'true', tabIndex: -1}
}
method = inject
}
return (tree) => {
visit(tree, 'element', (node, index, parent) => {
if (
headingRank(node) &&
hasProperty(node, 'id') &&
is(node, index, parent)
) {
/**
* Transform.
*
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
return function (tree) {
visit(tree, 'element', function (node, index, parent) {
if (headingRank(node) && node.properties.id && is(node, index, parent)) {
return method(node, index, parent)

@@ -92,6 +135,7 @@ }

/** @type {import('unist-util-visit/complex-types').Visitor<Element>} */
/** @type {import('unist-util-visit').Visitor<Element>} */
function inject(node) {
const children = toChildren(content, node)
node.children[behavior === 'prepend' ? 'unshift' : 'push'](
create(node, extend(true, {}, props), toChildren(content, node))
create(node, toProperties(props, node), children)
)

@@ -102,13 +146,9 @@

/** @type {import('unist-util-visit/complex-types').Visitor<Element>} */
/** @type {import('unist-util-visit').Visitor<Element>} */
function around(node, index, parent) {
// Uncommon.
/* c8 ignore next */
/* c8 ignore next -- uncommon */
if (typeof index !== 'number' || !parent) return
const link = create(
node,
extend(true, {}, props),
toChildren(content, node)
)
const children = toChildren(content, node)
const link = create(node, toProperties(props, node), children)
let nodes = behavior === 'before' ? [link, node] : [node, link]

@@ -130,46 +170,88 @@

/** @type {import('unist-util-visit/complex-types').Visitor<Element>} */
/** @type {import('unist-util-visit').Visitor<Element>} */
function wrap(node) {
node.children = [create(node, extend(true, {}, props), node.children)]
node.children = [create(node, toProperties(props, node), node.children)]
return [SKIP]
}
}
/**
* @param {ElementChild|ElementChild[]|Build} value
* @param {Element} node
* @returns {ElementChild[]}
*/
function toChildren(value, node) {
const result = toNode(value, node)
return Array.isArray(result) ? result : [result]
}
/**
* Deep clone.
*
* @template T
* Kind.
* @param {T} thing
* Thing to clone.
* @returns {Cloneable<T>}
* Cloned thing.
*/
function clone(thing) {
// Cast because it’s mutable now.
return /** @type {Cloneable<T>} */ (structuredClone(thing))
}
/**
* @param {ElementChild|ElementChild[]|Build} value
* @param {Element} node
* @returns {ElementChild|ElementChild[]}
*/
function toNode(value, node) {
if (typeof value === 'function') return value(node)
return extend(true, Array.isArray(value) ? [] : {}, value)
/**
* Create an `a`.
*
* @param {Readonly<Element>} node
* Related heading.
* @param {Properties | undefined} props
* Properties to set on the link.
* @param {Array<ElementContent>} children
* Content.
* @returns {Element}
* Link.
*/
function create(node, props, children) {
return {
type: 'element',
tagName: 'a',
properties: {...props, href: '#' + node.properties.id},
children
}
}
/**
* @param {Element} node
* @param {Properties} props
* @param {ElementChild[]} children
* @returns {Element}
*/
function create(node, props, children) {
return {
type: 'element',
tagName: 'a',
properties: Object.assign({}, props, {
// Fix hast types and make them required.
/* c8 ignore next */
href: '#' + (node.properties || {}).id
}),
children
}
}
/**
* Turn into children.
*
* @param {Readonly<ElementContent> | ReadonlyArray<ElementContent> | Build} value
* Content.
* @param {Readonly<Element>} node
* Related heading.
* @returns {Array<ElementContent>}
* Children.
*/
function toChildren(value, node) {
const result = toNode(value, node)
return Array.isArray(result) ? result : [result]
}
/**
* Turn into a node.
*
* @param {Readonly<ElementContent> | ReadonlyArray<ElementContent> | Build} value
* Content.
* @param {Readonly<Element>} node
* Related heading.
* @returns {Array<ElementContent> | ElementContent}
* Node.
*/
function toNode(value, node) {
if (typeof value === 'function') return value(node)
return clone(value)
}
/**
* Turn into properties.
*
* @param {Readonly<Properties> | BuildProperties | null | undefined} value
* Properties.
* @param {Readonly<Element>} node
* Related heading.
* @returns {Properties}
* Properties.
*/
function toProperties(value, node) {
if (typeof value === 'function') return value(node)
return value ? clone(value) : {}
}
{
"name": "rehype-autolink-headings",
"version": "6.1.1",
"version": "7.0.0",
"description": "rehype plugin to add links to headings",

@@ -27,4 +27,3 @@ "license": "MIT",

"type": "module",
"main": "index.js",
"types": "index.d.ts",
"exports": "./index.js",
"files": [

@@ -36,51 +35,41 @@ "lib/",

"dependencies": {
"@types/hast": "^2.0.0",
"extend": "^3.0.0",
"hast-util-has-property": "^2.0.0",
"hast-util-heading-rank": "^2.0.0",
"hast-util-is-element": "^2.0.0",
"unified": "^10.0.0",
"unist-util-visit": "^4.0.0"
"@types/hast": "^3.0.0",
"@ungap/structured-clone": "^1.0.0",
"hast-util-heading-rank": "^3.0.0",
"hast-util-is-element": "^3.0.0",
"unified": "^11.0.0",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@types/extend": "^3.0.0",
"@types/tape": "^4.0.0",
"bail": "^2.0.0",
"c8": "^7.0.0",
"@types/node": "^20.0.0",
"@types/ungap__structured-clone": "^0.3.0",
"c8": "^8.0.0",
"is-hidden": "^2.0.0",
"prettier": "^2.0.0",
"rehype": "^12.0.0",
"remark-cli": "^10.0.0",
"prettier": "^3.0.0",
"rehype": "^13.0.0",
"remark-cli": "^11.0.0",
"remark-preset-wooorm": "^9.0.0",
"rimraf": "^3.0.0",
"tape": "^5.0.0",
"to-vfile": "^7.0.0",
"type-coverage": "^2.0.0",
"typescript": "~4.4.0",
"xo": "^0.47.0"
"typescript": "^5.0.0",
"xo": "^0.56.0"
},
"scripts": {
"build": "rimraf \"lib/**/*.d.ts\" \"test/**/*.d.ts\" \"*.d.ts\" && tsc && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"build": "tsc --build --clean && tsc --build && type-coverage",
"format": "remark . --frail --output --quiet && prettier . --log-level warn --write && xo --fix",
"prepack": "npm run build && npm run format",
"test": "npm run build && npm run format && npm run test-coverage",
"test-api": "node --conditions development test/index.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
"test": "npm run build && npm run format && npm run test-coverage"
"test-coverage": "c8 --100 --check-coverage --reporter lcov npm run test-api"
},
"prettier": {
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": false,
"singleQuote": true,
"bracketSpacing": false,
"semi": false,
"trailingComma": "none"
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false
},
"xo": {
"prettier": true,
"ignores": [
"types/"
]
},
"remarkConfig": {
"plugins": [
"preset-wooorm"
"remark-preset-wooorm"
]

@@ -91,5 +80,18 @@ },

"detail": true,
"strict": true,
"ignoreCatch": true
"ignoreCatch": true,
"strict": true
},
"xo": {
"overrides": [
{
"files": [
"test/**/*.js"
],
"rules": {
"no-await-in-loop": "off"
}
}
],
"prettier": true
}
}

@@ -11,3 +11,3 @@ # rehype-autolink-headings

**[rehype][]** plugin to add links to headings with `id`s back to themselves.
**[rehype][]** plugin to add links from headings back to themselves.

@@ -22,5 +22,10 @@ ## Contents

* [`unified().use(rehypeAutolinkHeadings[, options])`](#unifieduserehypeautolinkheadings-options)
* [`Behavior`](#behavior)
* [`Build`](#build)
* [`BuildProperties`](#buildproperties)
* [`Options`](#options)
* [Examples](#examples)
* [Example: different behaviors](#example-different-behaviors)
* [Example: content](#example-content)
* [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)

@@ -38,3 +43,3 @@ * [Types](#types)

back to themselves.
It looks for headings (so `<h1>` through `<h6>`), that have `id` attributes,
It looks for headings (so `<h1>` through `<h6>`) that have `id` properties,
and injects a link to themselves.

@@ -56,7 +61,7 @@ Similar functionality is applied by many places that render markdown.

users to be able to link to particular sections, and you already have `id`
attributes set on all (or certain?) headings.
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` attribute, it creates a slug from
it, and adds that as the `id` attribute.
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

@@ -68,4 +73,4 @@ certain `id` or automatically with a generate one) will get a link back to

This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).
In Node.js (version 12.20+, 14.14+, or 16.0+), install with [npm][]:
This package is [ESM only][esm].
In Node.js (version 16+), install with [npm][]:

@@ -76,13 +81,13 @@ ```sh

In Deno with [Skypack][]:
In Deno with [`esm.sh`][esmsh]:
```js
import rehypeAutolinkHeadings from 'https://cdn.skypack.dev/rehype-autolink-headings@6?dts'
import rehypeAutolinkHeadings from 'https://esm.sh/rehype-autolink-headings@7'
```
In browsers with [Skypack][]:
In browsers with [`esm.sh`][esmsh]:
```html
<script type="module">
import rehypeAutolinkHeadings from 'https://cdn.skypack.dev/rehype-autolink-headings@6?min'
import rehypeAutolinkHeadings from 'https://esm.sh/rehype-autolink-headings@7?bundle'
</script>

@@ -96,65 +101,68 @@ ```

```html
<h1 id=some-id>Lorem ipsum</h1>
<h2>Dolor sit amet πŸ˜ͺ</h2>
<h3>consectetur &amp; adipisicing</h3>
<h4>elit</h4>
<h5>elit</h5>
<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` looks as follows:
…and our module `example.js` contains:
```js
import {read} from 'to-vfile'
import {rehype} from 'rehype'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypeSlug from 'rehype-slug'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import {read} from 'to-vfile'
main()
const file = await rehype()
.data('settings', {fragment: true})
.use(rehypeSlug)
.use(rehypeAutolinkHeadings)
.process(await read('example.html'))
async function main() {
const file = await rehype()
.data('settings', {fragment: true})
.use(rehypeSlug)
.use(rehypeAutolinkHeadings)
.process(await read('example.html'))
console.log(String(file))
}
console.log(String(file))
```
Now, running `node example.js` yields:
…then running `node example.js` yields:
```html
<h1 id="some-id"><a aria-hidden="true" tabindex="-1" href="#some-id"><span class="icon icon-link"></span></a>Lorem ipsum</h1>
<h2 id="dolor-sit-amet-"><a aria-hidden="true" tabindex="-1" href="#dolor-sit-amet-"><span class="icon icon-link"></span></a>Dolor sit amet πŸ˜ͺ</h2>
<h3 id="consectetur--adipisicing"><a aria-hidden="true" tabindex="-1" href="#consectetur--adipisicing"><span class="icon icon-link"></span></a>consectetur &#x26; adipisicing</h3>
<h4 id="elit"><a aria-hidden="true" tabindex="-1" href="#elit"><span class="icon icon-link"></span></a>elit</h4>
<h5 id="elit-1"><a aria-hidden="true" tabindex="-1" href="#elit-1"><span class="icon icon-link"></span></a>elit</h5>
<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>
```
> πŸ‘‰ **Note**: observe that from the `<h2>` on, automatic `id`s are generated.
> This is done by `rehype-slug`.
> Without `rehype-slug`, those headings would not be linked.
## API
This package exports no identifiers.
The default export is `rehypeAutolinkHeadings`.
The default export is [`rehypeAutolinkHeadings`][api-rehype-autolink-headings].
### `unified().use(rehypeAutolinkHeadings[, options])`
Add links to headings with `id`s back to themselves.
Add links from headings back to themselves.
> πŸ‘‰ **Note**: this plugin operates on headings that have `id` attributes.
> You can use `rehype-slug` to automatically generate `id`s for headings.
###### Parameters
##### `options`
* `options` ([`Options`][api-options], optional)
β€” configuration
Configuration (optional).
###### Returns
###### `options.behavior`
Transform ([`Transformer`][unified-transformer]).
How to create links (`string`, default: `'prepend'`).
###### Notes
* `'prepend'` β€” inject link before the heading text
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

@@ -165,44 +173,61 @@ * `'wrap'` β€” wrap the whole heading text with the link

> πŸ‘‰ **Note**: `options.content` is ignored when the behavior is `wrap`,
> `options.group` is ignored when the behavior is `prepend`, `append`, or
> `wrap`.
### `Behavior`
###### `options.properties`
Behavior (TypeScript type).
Attributes to set on the link ([`Properties`][properties], optional).
Defaults to `{ariaHidden: true, tabIndex: -1}` when in `'prepend'` or
`'append'` mode, and `{}` otherwise.
###### Type
###### `options.content`
```ts
type Behavior = 'after' | 'append' | 'before' | 'prepend' | 'wrap'
```
**[hast][]** nodes to insert in the link (`Function|Node|Children`).
By default, a `<span class="icon icon-link"></span>` is used.
### `Build`
When a function is given, it’s called with the current heading (`Element`) and
should return one or more nodes.
Generate content (TypeScript type).
> πŸ‘‰ **Note**: this option is ignored when the behavior is `wrap`.
###### Parameters
###### `options.group`
* `element` ([`Element`][hast-element])
β€” current heading
**[hast][]** node to wrap the heading and link with (`Function|Node`, optional).
There is no default.
###### Returns
When a function is given, it’s called with the current heading (`Element`) and
should return a hast node.
Content ([`Array<Node>`][hast-node] or `Node`).
> πŸ‘‰ **Note**: this option is ignored when the behavior is `prepend`, `append`,
> or `wrap`
### `BuildProperties`
###### `options.test`
Generate properties (TypeScript type).
Test to define which heading elements are linked.
Any test that can be given to [`hast-util-is-element`][hast-util-is-element] is
supported.
The default (no test) is to link all headings with an `id` attribute.
###### Parameters
Can be used to link only `<h1>` through `<h3>` (with `['h1', 'h2', 'h3']`), or
for example all except `<h1>` (with `['h2', 'h3', 'h4', 'h5', 'h6']`).
A function can be given to do more complex things.
* `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: equivalent of `<span class="icon icon-link"></span>`)
β€” content to insert in the link, if `behavior` is not `'wrap'`
* `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'`
* `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

@@ -218,18 +243,14 @@

main()
async function main() {
const behaviors = ['prepend', 'append', 'wrap', 'before', 'after']
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>')
)
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>')
)
}
)
}

@@ -241,43 +262,76 @@ ```

```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="append">append<a aria-hidden="true" tabindex="-1" href="#append"><span class="icon icon-link"></span></a></h1>
<h1 id="wrap"><a href="#wrap">wrap</a></h1>
<a href="#before"><span class="icon icon-link"></span></a><h1 id="before">before</h1>
<h1 id="after">after</h1><a href="#after"><span class="icon icon-link"></span></a>
```
### Example: content
### Example: building content with `hastscript`
The following example passes `content` as a function, to generate an accessible
description of each link.
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'
import {toString} from 'hast-util-to-string'
import {h} from 'hastscript'
main()
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>')
async function main() {
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="xxx">xxx</h1>')
console.log(String(file))
```
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="xxx"><a aria-hidden="true" tabindex="-1" href="#xxx"><span class="visually-hidden">Read the β€œxxx” section</span><span class="icon icon-link" aria-hidden="true"></span></a>xxx</h1>
<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>
```

@@ -289,23 +343,20 @@

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'
import {h} from 'hastscript'
main()
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>')
async function main() {
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="xxx">xxx</h1>')
console.log(String(file))
}
console.log(String(file))
```

@@ -316,3 +367,3 @@

```html
<div class="heading-1-group"><a href="#xxx"><span class="icon icon-link"></span></a><h1 id="xxx">xxx</h1></div>
<div class="heading-1-group"><a href="#ceres"><span class="icon icon-link"></span></a><h1 id="ceres">Ceres</h1></div>
```

@@ -323,12 +374,18 @@

This package is fully typed with [TypeScript][].
It exports an `Options` type, which specifies the interface of the accepted
options.
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 all maintained
Projects maintained by the unified collective are compatible with maintained
versions of Node.js.
As of now, that is Node.js 12.20+, 14.14+, and 16.0+.
Our projects sometimes work with older versions, but this is not guaranteed.
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+,

@@ -341,3 +398,3 @@ `rehype` version 1+, and `unified` version 4+.

[cross-site scripting (XSS)][xss] attack if you pass user provided content in
`properties` or `content`.
`content`, `group`, or `properties`.

@@ -383,5 +440,5 @@ Always be wary of user input and use [`rehype-sanitize`][rehype-sanitize].

[size-badge]: https://img.shields.io/bundlephobia/minzip/rehype-autolink-headings.svg
[size-badge]: https://img.shields.io/bundlejs/size/rehype-autolink-headings
[size]: https://bundlephobia.com/result?p=rehype-autolink-headings
[size]: https://bundlejs.com/?q=rehype-autolink-headings

@@ -400,11 +457,13 @@ [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg

[skypack]: https://www.skypack.dev
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
[esmsh]: https://esm.sh
[health]: https://github.com/rehypejs/.github
[contributing]: https://github.com/rehypejs/.github/blob/HEAD/contributing.md
[contributing]: https://github.com/rehypejs/.github/blob/main/contributing.md
[support]: https://github.com/rehypejs/.github/blob/HEAD/support.md
[support]: https://github.com/rehypejs/.github/blob/main/support.md
[coc]: https://github.com/rehypejs/.github/blob/HEAD/code-of-conduct.md
[coc]: https://github.com/rehypejs/.github/blob/main/code-of-conduct.md

@@ -415,2 +474,18 @@ [license]: license

[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

@@ -420,14 +495,16 @@

[rehype]: https://github.com/rehypejs/rehype
[unified-transformer]: https://github.com/unifiedjs/unified#transformer
[hast]: https://github.com/syntax-tree/hast
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
[rehype-sanitize]: https://github.com/rehypejs/rehype-sanitize
[rehype-slug]: https://github.com/rehypejs/rehype-slug
[hast-util-is-element]: https://github.com/syntax-tree/hast-util-is-element
[api-behavior]: #behavior
[properties]: https://github.com/syntax-tree/hast#properties
[api-build]: #build
[api-build-properties]: #buildproperties
[api-options]: #options
[api-rehype-autolink-headings]: #unifieduserehypeautolinkheadings-options
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚑️ by Socket Inc