You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
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

to
6.1.1

lib/index.d.ts

64

index.d.ts

@@ -1,62 +0,2 @@

/**
* Plugin to automatically add links to headings (h1-h6).
*
* @type {import('unified').Plugin<[Options?]|void[], Root>}
*/
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[]
/**
* 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
}
export {default} from './lib/index.js'
export type Options = import('./lib/index.js').Options
/**
* @typedef {import('hast').Root} Root
* @typedef {import('hast').Parent} Parent
* @typedef {import('hast').Element} Element
* @typedef {Element['children'][number]} ElementChild
* @typedef {import('hast').Properties} Properties
* @typedef {import('hast-util-is-element').Test} Test
*
* @typedef {'prepend'|'append'|'wrap'|'before'|'after'} Behavior
*
* @callback Build
* @param {Element} node
* @returns {ElementChild|ElementChild[]}
*
* @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.
* @typedef {import('./lib/index.js').Options} Options
*/
import extend from 'extend'
import {hasProperty} from 'hast-util-has-property'
import {headingRank} from 'hast-util-heading-rank'
import {convertElement} from 'hast-util-is-element'
import {visit, SKIP} from 'unist-util-visit'
/** @type {Element} */
const contentDefaults = {
type: 'element',
tagName: 'span',
properties: {className: ['icon', 'icon-link']},
children: []
}
/**
* Plugin to automatically add links to headings (h1-h6).
*
* @type {import('unified').Plugin<[Options?]|void[], Root>}
*/
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)
/** @type {import('unist-util-visit').Visitor<Element>} */
let method
if (behavior === 'wrap') {
method = wrap
} else if (behavior === 'before' || behavior === 'after') {
method = around
} else {
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)
) {
return method(node, index, parent)
}
})
}
/** @type {import('unist-util-visit').Visitor<Element>} */
function inject(node) {
node.children[behavior === 'prepend' ? 'unshift' : 'push'](
create(node, extend(true, {}, props), toChildren(content, node))
)
return [SKIP]
}
/** @type {import('unist-util-visit').Visitor<Element>} */
function around(node, index, parent) {
// Uncommon.
/* c8 ignore next */
if (typeof index !== 'number' || !parent) return
const link = create(
node,
extend(true, {}, props),
toChildren(content, node)
)
let nodes = behavior === 'before' ? [link, node] : [node, link]
if (group) {
const grouping = toNode(group, node)
if (grouping && !Array.isArray(grouping) && grouping.type === 'element') {
grouping.children = nodes
nodes = [grouping]
}
}
parent.children.splice(index, 1, ...nodes)
return [SKIP, index + nodes.length]
}
/** @type {import('unist-util-visit').Visitor<Element>} */
function wrap(node) {
node.children = [create(node, extend(true, {}, props), 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]
}
/**
* @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)
}
/**
* @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
}
}
}
export {default} from './lib/index.js'
{
"name": "rehype-autolink-headings",
"version": "6.1.0",
"version": "6.1.1",
"description": "rehype plugin to add links to headings",

@@ -30,2 +30,3 @@ "license": "MIT",

"files": [
"lib/",
"index.d.ts",

@@ -44,3 +45,3 @@ "index.js"

"devDependencies": {
"@types/extend": "^3.0.1",
"@types/extend": "^3.0.0",
"@types/tape": "^4.0.0",

@@ -58,7 +59,7 @@ "bail": "^2.0.0",

"type-coverage": "^2.0.0",
"typescript": "^4.0.0",
"xo": "^0.44.0"
"typescript": "~4.4.0",
"xo": "^0.47.0"
},
"scripts": {
"build": "rimraf \"*.d.ts\" \"test/**/*.d.ts\" && tsc && type-coverage",
"build": "rimraf \"lib/**/*.d.ts\" \"test/**/*.d.ts\" \"*.d.ts\" && tsc && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",

@@ -65,0 +66,0 @@ "test-api": "node --conditions development test/index.js",

@@ -11,12 +11,58 @@ # rehype-autolink-headings

[**rehype**][rehype] plugin to automatically add links to headings (h1-h6) that
already have an ID.
**[rehype][]** plugin to add links to headings with `id`s 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)
* [Examples](#examples)
* [Example: different behaviors](#example-different-behaviors)
* [Example: content](#example-content)
* [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` attributes,
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`
attributes 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 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](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c):
Node 12+ is needed to use it and it must be `import`ed instead of `require`d.
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][]:
[npm][]:
```sh

@@ -26,9 +72,23 @@ npm install rehype-autolink-headings

In Deno with [Skypack][]:
```js
import rehypeAutolinkHeadings from 'https://cdn.skypack.dev/rehype-autolink-headings@6?dts'
```
In browsers with [Skypack][]:
```html
<script type="module">
import rehypeAutolinkHeadings from 'https://cdn.skypack.dev/rehype-autolink-headings@6?min'
</script>
```
## Use
Say we have the following file, `fragment.html`:
Say we have the following file `example.html`:
```html
<h1>Lorem ipsum 😪</h1>
<h2>dolor—sit—amet</h2>
<h1 id=some-id>Lorem ipsum</h1>
<h2>Dolor sit amet 😪</h2>
<h3>consectetur &amp; adipisicing</h3>

@@ -39,32 +99,37 @@ <h4>elit</h4>

And our script, `example.js`, looks as follows:
And our module `example.js` looks as follows:
```js
import fs from 'node:fs'
import rehype from 'rehype'
import {read} from 'to-vfile'
import {rehype} from 'rehype'
import rehypeSlug from 'rehype-slug'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
const buf = fs.readFileSync('fragment.html')
main()
rehype()
.data('settings', {fragment: true})
.use(rehypeSlug)
.use(rehypeAutolinkHeadings)
.process(buf)
.then((file) => {
console.log(String(file))
})
async function main() {
const file = await rehype()
.data('settings', {fragment: true})
.use(rehypeSlug)
.use(rehypeAutolinkHeadings)
.process(await read('example.html'))
console.log(String(file))
}
```
Now, running `node example` yields:
Now, running `node example.js` yields:
```html
<h1 id="lorem-ipsum-"><a aria-hidden="true" href="#lorem-ipsum-"><span class="icon icon-link"></span></a>Lorem ipsum 😪</h1>
<h2 id="dolorsitamet"><a aria-hidden="true" href="#dolorsitamet"><span class="icon icon-link"></span></a>dolor—sit—amet</h2>
<h3 id="consectetur--adipisicing"><a aria-hidden="true" href="#consectetur--adipisicing"><span class="icon icon-link"></span></a>consectetur &#x26; adipisicing</h3>
<h4 id="elit"><a aria-hidden="true" href="#elit"><span class="icon icon-link"></span></a>elit</h4>
<h5 id="elit-1"><a aria-hidden="true" href="#elit-1"><span class="icon icon-link"></span></a>elit</h5>
<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>
```
> 👉 **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

@@ -77,9 +142,11 @@

Add links to headings (h1-h6) with an `id`.
Add links to headings with `id`s back to themselves.
**Note**: this plugin expects `id`s to already exist on headings.
One way to add those automatically is [`rehype-slug`][slug] (see example).
> 👉 **Note**: this plugin operates on headings that have `id` attributes.
> You can use `rehype-slug` to automatically generate `id`s for headings.
##### `options`
Configuration (optional).
###### `options.behavior`

@@ -95,76 +162,168 @@

Supplying `wrap` will ignore any value defined by the `content` option.
Supplying `prepend`, `append`, or `wrap` will ignore the `group` option.
> 👉 **Note**: `options.content` is ignored when the behavior is `wrap`,
> `options.group` is ignored when the behavior is `prepend`, `append`, or
> `wrap`.
###### `options.properties`
Extra properties to set on the link (`Object?`).
Attributes to set on the link ([`Properties`][properties], optional).
Defaults to `{ariaHidden: true, tabIndex: -1}` when in `'prepend'` or
`'append'` mode.
`'append'` mode, and `{}` otherwise.
###### `options.content`
[**hast**][hast] nodes to insert in the link (`Function|Node|Children`).
By default, the following is used:
**[hast][]** nodes to insert in the link (`Function|Node|Children`).
By default, a `<span class="icon icon-link"></span>` is used.
When a function is given, it’s called with the current heading (`Element`) and
should return one or more nodes.
> 👉 **Note**: this option is ignored when the behavior is `wrap`.
###### `options.group`
**[hast][]** node to wrap the heading and link with (`Function|Node`, optional).
There is no default.
When a function is given, it’s called with the current heading (`Element`) and
should return a hast node.
> 👉 **Note**: this option is ignored when the behavior is `prepend`, `append`,
> or `wrap`
###### `options.test`
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.
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.
## Examples
### Example: different behaviors
This example shows what each behavior generates by default.
```js
{
type: 'element',
tagName: 'span',
properties: {className: ['icon', 'icon-link']},
children: []
import {rehype} from 'rehype'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
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>')
)
)
}
}
```
If `behavior` is `wrap`, then `content` is ignored.
Yields:
If `content` is a function, it’s called with the current heading (`Element`) and
should return one or more nodes:
```html
<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
The following example passes `content` as a function, to generate an accessible
description of each link.
```js
import {rehype} from 'rehype'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import {toString} from 'hast-util-to-string'
import {h} from 'hastscript'
// …
main()
function content(node) {
return [
h('span.visually-hidden', 'Read the “', toString(node), '” section'),
h('span.icon.icon-link', {ariaHidden: true})
]
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))
}
```
###### `options.group`
Yields:
[**hast**][hast] node to wrap the heading and link with (`Function|Node`), if
`behavior` is `before` or `after`.
There is no default.
```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>
```
If `behavior` is `prepend`, `append`, or `wrap`, then `group` is ignored.
### Example: group
If `group` is a function, it’s called with the current heading (`Element`) and
should return a hast node.
The following example passes `group` as a function, to dynamically generate a
differing element that wraps the heading.
```js
import {rehype} from 'rehype'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import {h} from 'hastscript'
// …
main()
function group(node) {
return h('.heading-' + node.charAt(1) + '-group')
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))
}
```
###### `options.test`
Yields:
Test to define which heading elements are linked.
Any test that can be given to [`hast-util-is-element`][is] is supported.
The default (no test) is to link all headings.
```html
<div class="heading-1-group"><a href="#xxx"><span class="icon icon-link"></span></a><h1 id="xxx">xxx</h1></div>
```
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']`).
## Types
Functions can also be given to do more complex things!
This package is fully typed with [TypeScript][].
It exports an `Options` type, which specifies the interface of the accepted
options.
## Compatibility
Projects maintained by the unified collective are compatible with all 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.
This plugin works with `rehype-parse` version 1+, `rehype-stringify` version 1+,
`rehype` version 1+, and `unified` version 4+.
## Security

@@ -176,12 +335,12 @@

Always be wary of user input and use [`rehype-sanitize`][sanitize].
Always be wary of user input and use [`rehype-sanitize`][rehype-sanitize].
## Related
* [`rehype-slug`][slug]
— Add `id`s to headings
* [`rehype-slug`][rehype-slug]
— add `id`s to headings
* [`rehype-highlight`](https://github.com/rehypejs/rehype-highlight)
— Syntax highlight code blocks
— apply syntax highlighting to code blocks
* [`rehype-toc`](https://github.com/JS-DevTools/rehype-toc)
— Add a table of contents (TOC)
— add a table of contents (TOC)

@@ -232,2 +391,4 @@ ## Contribute

[skypack]: https://www.skypack.dev
[health]: https://github.com/rehypejs/.github

@@ -245,12 +406,18 @@

[hast]: https://github.com/syntax-tree/hast
[typescript]: https://www.typescriptlang.org
[unified]: https://github.com/unifiedjs/unified
[rehype]: https://github.com/rehypejs/rehype
[hast]: https://github.com/syntax-tree/hast
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
[sanitize]: https://github.com/rehypejs/rehype-sanitize
[rehype-sanitize]: https://github.com/rehypejs/rehype-sanitize
[slug]: https://github.com/rehypejs/rehype-slug
[rehype-slug]: https://github.com/rehypejs/rehype-slug
[is]: https://github.com/syntax-tree/hast-util-is-element
[hast-util-is-element]: https://github.com/syntax-tree/hast-util-is-element
[properties]: https://github.com/syntax-tree/hast#properties