rehype-autolink-headings
Advanced tools
Comparing version 6.1.1 to 7.0.0
@@ -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; |
276
lib/index.js
/** | ||
* @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 | ||
} | ||
} |
379
readme.md
@@ -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 & 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 & 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 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
28537
6
11
314
493
+ Added@types/hast@3.0.4(transitive)
+ Added@types/unist@3.0.3(transitive)
+ Added@ungap/structured-clone@1.2.1(transitive)
+ Addeddequal@2.0.3(transitive)
+ Addeddevlop@1.1.0(transitive)
+ Addedhast-util-heading-rank@3.0.0(transitive)
+ Addedhast-util-is-element@3.0.0(transitive)
+ Addedunified@11.0.5(transitive)
+ Addedunist-util-is@6.0.0(transitive)
+ Addedunist-util-stringify-position@4.0.0(transitive)
+ Addedunist-util-visit@5.0.0(transitive)
+ Addedunist-util-visit-parents@6.0.1(transitive)
+ Addedvfile@6.0.3(transitive)
+ Addedvfile-message@4.0.2(transitive)
- Removedextend@^3.0.0
- Removedhast-util-has-property@^2.0.0
- Removed@types/hast@2.3.10(transitive)
- Removed@types/unist@2.0.11(transitive)
- Removedhast-util-has-property@2.0.1(transitive)
- Removedhast-util-heading-rank@2.1.1(transitive)
- Removedhast-util-is-element@2.1.3(transitive)
- Removedis-buffer@2.0.5(transitive)
- Removedunified@10.1.2(transitive)
- Removedunist-util-is@5.2.1(transitive)
- Removedunist-util-stringify-position@3.0.3(transitive)
- Removedunist-util-visit@4.1.2(transitive)
- Removedunist-util-visit-parents@5.1.3(transitive)
- Removedvfile@5.3.7(transitive)
- Removedvfile-message@3.1.4(transitive)
Updated@types/hast@^3.0.0
Updatedhast-util-is-element@^3.0.0
Updatedunified@^11.0.0
Updatedunist-util-visit@^5.0.0