mdast-util-to-hast
Advanced tools
Comparing version 12.2.6 to 12.3.0
@@ -1,18 +0,4 @@ | ||
import type {Literal} from 'hast' | ||
// To do: next major: remove this file. | ||
export type {Raw} from './index.js' | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions | ||
export interface Raw extends Literal { | ||
type: 'raw' | ||
} | ||
declare module 'hast' { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions | ||
interface RootContentMap { | ||
raw: Raw | ||
} | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions | ||
interface ElementContentMap { | ||
raw: Raw | ||
} | ||
} | ||
/// <reference types="./index.js" /> |
@@ -1,7 +0,48 @@ | ||
export type Options = import('./lib/index.js').Options | ||
export type Handler = import('./lib/index.js').Handler | ||
export type Handlers = import('./lib/index.js').Handlers | ||
export type H = import('./lib/index.js').H | ||
export type Raw = import('./complex-types.js').Raw | ||
export {one, all} from './lib/traverse.js' | ||
export {defaultHandlers, toHast} from './lib/index.js' | ||
import type {Literal} from 'hast' | ||
import type {State} from './lib/state.js' | ||
// Expose types. | ||
export type {State, Handler, Handlers, Options} from './lib/state.js' | ||
// To do: next major: remove. | ||
/** | ||
* Deprecated: use `State`. | ||
*/ | ||
export type H = State | ||
// Expose JS API. | ||
export {handlers as defaultHandlers} from './lib/handlers/index.js' | ||
// To do: next major: remove. | ||
export {one, all} from './lib/state.js' | ||
export {toHast} from './lib/index.js' | ||
// Expose node type. | ||
/** | ||
* Raw string of HTML embedded into HTML AST. | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions | ||
export interface Raw extends Literal { | ||
/** | ||
* Node type. | ||
*/ | ||
type: 'raw' | ||
} | ||
// Register nodes in content. | ||
declare module 'hast' { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions | ||
interface RootContentMap { | ||
/** | ||
* Raw string of HTML embedded into HTML AST. | ||
*/ | ||
raw: Raw | ||
} | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions | ||
interface ElementContentMap { | ||
/** | ||
* Raw string of HTML embedded into HTML AST. | ||
*/ | ||
raw: Raw | ||
} | ||
} |
15
index.js
@@ -1,10 +0,5 @@ | ||
/** | ||
* @typedef {import('./lib/index.js').Options} Options | ||
* @typedef {import('./lib/index.js').Handler} Handler | ||
* @typedef {import('./lib/index.js').Handlers} Handlers | ||
* @typedef {import('./lib/index.js').H} H | ||
* @typedef {import('./complex-types.js').Raw} Raw | ||
*/ | ||
export {one, all} from './lib/traverse.js' | ||
export {defaultHandlers, toHast} from './lib/index.js' | ||
// Note: types exposed from `index.d.ts`. | ||
export {handlers as defaultHandlers} from './lib/handlers/index.js' | ||
// To do: next major: remove. | ||
export {one, all} from './lib/state.js' | ||
export {toHast} from './lib/index.js' |
/** | ||
* @param {H} h | ||
* @returns {Element|null} | ||
* Generate a hast footer for called footnote definitions. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @returns {Element | undefined} | ||
* `section` element or `undefined`. | ||
*/ | ||
export function footer(h: H): Element | null | ||
export function footer(state: State): Element | undefined | ||
export type Element = import('hast').Element | ||
export type ElementContent = import('hast').ElementContent | ||
export type H = import('./index.js').H | ||
export type State = import('./state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').ElementContent} ElementContent | ||
* @typedef {import('./index.js').H} H | ||
* | ||
* @typedef {import('./state.js').State} State | ||
*/ | ||
import {normalizeUri} from 'micromark-util-sanitize-uri' | ||
import {u} from 'unist-builder' | ||
import {all} from './traverse.js' | ||
import {wrap} from './wrap.js' | ||
/** | ||
* @param {H} h | ||
* @returns {Element|null} | ||
* Generate a hast footer for called footnote definitions. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @returns {Element | undefined} | ||
* `section` element or `undefined`. | ||
*/ | ||
export function footer(h) { | ||
let index = -1 | ||
export function footer(state) { | ||
/** @type {Array<ElementContent>} */ | ||
const listItems = [] | ||
let index = -1 | ||
while (++index < h.footnoteOrder.length) { | ||
const def = h.footnoteById[h.footnoteOrder[index].toUpperCase()] | ||
while (++index < state.footnoteOrder.length) { | ||
const def = state.footnoteById[state.footnoteOrder[index]] | ||
@@ -28,4 +30,4 @@ if (!def) { | ||
const content = all(h, def) | ||
const id = String(def.identifier) | ||
const content = state.all(def) | ||
const id = String(def.identifier).toUpperCase() | ||
const safeId = normalizeUri(id.toLowerCase()) | ||
@@ -36,3 +38,3 @@ let referenceIndex = 0 | ||
while (++referenceIndex <= h.footnoteCounts[id]) { | ||
while (++referenceIndex <= state.footnoteCounts[id]) { | ||
/** @type {Element} */ | ||
@@ -45,3 +47,3 @@ const backReference = { | ||
'#' + | ||
h.clobberPrefix + | ||
state.clobberPrefix + | ||
'fnref-' + | ||
@@ -52,3 +54,3 @@ safeId + | ||
className: ['data-footnote-backref'], | ||
ariaLabel: h.footnoteBackLabel | ||
ariaLabel: state.footnoteBackLabel | ||
}, | ||
@@ -92,9 +94,7 @@ children: [{type: 'text', value: '↩'}] | ||
tagName: 'li', | ||
properties: {id: h.clobberPrefix + 'fn-' + safeId}, | ||
children: wrap(content, true) | ||
properties: {id: state.clobberPrefix + 'fn-' + safeId}, | ||
children: state.wrap(content, true) | ||
} | ||
if (def.position) { | ||
listItem.position = def.position | ||
} | ||
state.patch(def, listItem) | ||
@@ -105,3 +105,3 @@ listItems.push(listItem) | ||
if (listItems.length === 0) { | ||
return null | ||
return | ||
} | ||
@@ -116,8 +116,9 @@ | ||
type: 'element', | ||
tagName: h.footnoteLabelTagName, | ||
tagName: state.footnoteLabelTagName, | ||
properties: { | ||
...JSON.parse(JSON.stringify(h.footnoteLabelProperties)), | ||
// To do: use structured clone. | ||
...JSON.parse(JSON.stringify(state.footnoteLabelProperties)), | ||
id: 'footnote-label' | ||
}, | ||
children: [u('text', h.footnoteLabel)] | ||
children: [{type: 'text', value: state.footnoteLabel}] | ||
}, | ||
@@ -129,3 +130,3 @@ {type: 'text', value: '\n'}, | ||
properties: {}, | ||
children: wrap(listItems, true) | ||
children: state.wrap(listItems, true) | ||
}, | ||
@@ -132,0 +133,0 @@ {type: 'text', value: '\n'} |
/** | ||
* @param {H} h | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Blockquote} Blockquote | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* Turn an mdast `blockquote` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Blockquote} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function blockquote(h: H, node: Blockquote): import('hast').Element | ||
export function blockquote(state: State, node: Blockquote): Element | ||
export type Element = import('hast').Element | ||
export type Blockquote = import('mdast').Blockquote | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Blockquote} Blockquote | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {wrap} from '../wrap.js' | ||
import {all} from '../traverse.js' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `blockquote` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Blockquote} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function blockquote(h, node) { | ||
return h(node, 'blockquote', wrap(all(h, node), true)) | ||
export function blockquote(state, node) { | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: 'blockquote', | ||
properties: {}, | ||
children: state.wrap(state.all(node), true) | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').Text} Text | ||
* @typedef {import('mdast').Break} Break | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* Turn an mdast `break` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Break} node | ||
* @returns {Array<Element|Text>} | ||
* mdast node. | ||
* @returns {Array<Element | Text>} | ||
* hast element content. | ||
*/ | ||
export function hardBreak(h: H, node: Break): Array<Element | Text> | ||
export function hardBreak(state: State, node: Break): Array<Element | Text> | ||
export type Element = import('hast').Element | ||
export type Text = import('hast').Text | ||
export type Break = import('mdast').Break | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
@@ -5,14 +5,20 @@ /** | ||
* @typedef {import('mdast').Break} Break | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {u} from 'unist-builder' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `break` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Break} node | ||
* @returns {Array<Element|Text>} | ||
* mdast node. | ||
* @returns {Array<Element | Text>} | ||
* hast element content. | ||
*/ | ||
export function hardBreak(h, node) { | ||
return [h(node, 'br'), u('text', '\n')] | ||
export function hardBreak(state, node) { | ||
/** @type {Element} */ | ||
const result = {type: 'element', tagName: 'br', properties: {}, children: []} | ||
state.patch(node, result) | ||
return [state.applyData(node, result), {type: 'text', value: '\n'}] | ||
} |
/** | ||
* @param {H} h | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('mdast').Code} Code | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* Turn an mdast `code` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Code} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function code(h: H, node: Code): import('hast').Element | ||
export function code(state: State, node: Code): Element | ||
export type Element = import('hast').Element | ||
export type Properties = import('hast').Properties | ||
export type Code = import('mdast').Code | ||
export type Properties = import('hast').Properties | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('mdast').Code} Code | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {u} from 'unist-builder' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `code` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Code} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function code(h, node) { | ||
export function code(state, node) { | ||
const value = node.value ? node.value + '\n' : '' | ||
// To do: next major, use `node.lang` w/o regex, the splitting’s been going | ||
// on for years in remark now. | ||
const lang = node.lang && node.lang.match(/^[^ \t]+(?=[ \t]|$)/) | ||
const lang = node.lang ? node.lang.match(/^[^ \t]+(?=[ \t]|$)/) : null | ||
/** @type {Properties} */ | ||
const props = {} | ||
const properties = {} | ||
if (lang) { | ||
props.className = ['language-' + lang] | ||
properties.className = ['language-' + lang] | ||
} | ||
const code = h(node, 'code', props, [u('text', value)]) | ||
// Create `<code>`. | ||
/** @type {Element} */ | ||
let result = { | ||
type: 'element', | ||
tagName: 'code', | ||
properties, | ||
children: [{type: 'text', value}] | ||
} | ||
if (node.meta) { | ||
code.data = {meta: node.meta} | ||
result.data = {meta: node.meta} | ||
} | ||
return h(node.position, 'pre', [code]) | ||
state.patch(node, result) | ||
result = state.applyData(node, result) | ||
// Create `<pre>`. | ||
result = {type: 'element', tagName: 'pre', properties: {}, children: [result]} | ||
state.patch(node, result) | ||
return result | ||
} |
/** | ||
* @param {H} h | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Delete} Delete | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* Turn an mdast `delete` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Delete} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function strikethrough(h: H, node: Delete): import('hast').Element | ||
export function strikethrough(state: State, node: Delete): Element | ||
export type Element = import('hast').Element | ||
export type Delete = import('mdast').Delete | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Delete} Delete | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {all} from '../traverse.js' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `delete` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Delete} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function strikethrough(h, node) { | ||
return h(node, 'del', all(h, node)) | ||
export function strikethrough(state, node) { | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: 'del', | ||
properties: {}, | ||
children: state.all(node) | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Emphasis} Emphasis | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* Turn an mdast `emphasis` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Emphasis} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function emphasis(h: H, node: Emphasis): import('hast').Element | ||
export function emphasis(state: State, node: Emphasis): Element | ||
export type Element = import('hast').Element | ||
export type Emphasis = import('mdast').Emphasis | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Emphasis} Emphasis | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {all} from '../traverse.js' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `emphasis` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Emphasis} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function emphasis(h, node) { | ||
return h(node, 'em', all(h, node)) | ||
export function emphasis(state, node) { | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: 'em', | ||
properties: {}, | ||
children: state.all(node) | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* Turn an mdast `footnoteReference` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {FootnoteReference} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function footnoteReference( | ||
h: H, | ||
state: State, | ||
node: FootnoteReference | ||
): import('hast').Element | ||
): Element | ||
export type FootnoteReference = import('mdast').FootnoteReference | ||
export type H = import('../index.js').H | ||
export type Element = import('hast').Element | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('mdast').FootnoteReference} FootnoteReference | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {normalizeUri} from 'micromark-util-sanitize-uri' | ||
import {u} from 'unist-builder' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `footnoteReference` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {FootnoteReference} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function footnoteReference(h, node) { | ||
const id = String(node.identifier) | ||
export function footnoteReference(state, node) { | ||
const id = String(node.identifier).toUpperCase() | ||
const safeId = normalizeUri(id.toLowerCase()) | ||
const index = h.footnoteOrder.indexOf(id) | ||
const index = state.footnoteOrder.indexOf(id) | ||
/** @type {number} */ | ||
@@ -21,29 +27,39 @@ let counter | ||
if (index === -1) { | ||
h.footnoteOrder.push(id) | ||
h.footnoteCounts[id] = 1 | ||
counter = h.footnoteOrder.length | ||
state.footnoteOrder.push(id) | ||
state.footnoteCounts[id] = 1 | ||
counter = state.footnoteOrder.length | ||
} else { | ||
h.footnoteCounts[id]++ | ||
state.footnoteCounts[id]++ | ||
counter = index + 1 | ||
} | ||
const reuseCounter = h.footnoteCounts[id] | ||
const reuseCounter = state.footnoteCounts[id] | ||
return h(node, 'sup', [ | ||
h( | ||
node.position, | ||
'a', | ||
{ | ||
href: '#' + h.clobberPrefix + 'fn-' + safeId, | ||
id: | ||
h.clobberPrefix + | ||
'fnref-' + | ||
safeId + | ||
(reuseCounter > 1 ? '-' + reuseCounter : ''), | ||
dataFootnoteRef: true, | ||
ariaDescribedBy: 'footnote-label' | ||
}, | ||
[u('text', String(counter))] | ||
) | ||
]) | ||
/** @type {Element} */ | ||
const link = { | ||
type: 'element', | ||
tagName: 'a', | ||
properties: { | ||
href: '#' + state.clobberPrefix + 'fn-' + safeId, | ||
id: | ||
state.clobberPrefix + | ||
'fnref-' + | ||
safeId + | ||
(reuseCounter > 1 ? '-' + reuseCounter : ''), | ||
dataFootnoteRef: true, | ||
ariaDescribedBy: ['footnote-label'] | ||
}, | ||
children: [{type: 'text', value: String(counter)}] | ||
} | ||
state.patch(node, link) | ||
/** @type {Element} */ | ||
const sup = { | ||
type: 'element', | ||
tagName: 'sup', | ||
properties: {}, | ||
children: [link] | ||
} | ||
state.patch(node, sup) | ||
return state.applyData(node, sup) | ||
} |
/** | ||
* @param {H} h | ||
* Turn an mdast `footnote` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Footnote} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function footnote(h: H, node: Footnote): import('hast').Element | ||
export function footnote(state: State, node: Footnote): Element | ||
export type Element = import('hast').Element | ||
export type Footnote = import('mdast').Footnote | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Footnote} Footnote | ||
* @typedef {import('../index.js').H} H | ||
* | ||
* @todo | ||
* `footnote` (or “inline note”) are a pandoc footnotes feature (`^[a note]`) | ||
* that does not exist in GFM. | ||
* We still have support for it, so that things remain working with | ||
* `micromark-extension-footnote` and `mdast-util-footnote`, but in the future | ||
* we might be able to remove it? | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
@@ -15,8 +9,20 @@ | ||
// To do: when both: | ||
// * <https://github.com/micromark/micromark-extension-footnote> | ||
// * <https://github.com/syntax-tree/mdast-util-footnote> | ||
// …are archived, remove this (also from mdast). | ||
// These inline notes are not used in GFM. | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `footnote` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Footnote} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function footnote(h, node) { | ||
const footnoteById = h.footnoteById | ||
export function footnote(state, node) { | ||
const footnoteById = state.footnoteById | ||
let no = 1 | ||
@@ -35,3 +41,3 @@ | ||
return footnoteReference(h, { | ||
return footnoteReference(state, { | ||
type: 'footnoteReference', | ||
@@ -38,0 +44,0 @@ identifier, |
/** | ||
* @param {H} h | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Heading} Heading | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* Turn an mdast `heading` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Heading} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function heading(h: H, node: Heading): import('hast').Element | ||
export function heading(state: State, node: Heading): Element | ||
export type Element = import('hast').Element | ||
export type Heading = import('mdast').Heading | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Heading} Heading | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {all} from '../traverse.js' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `heading` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Heading} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function heading(h, node) { | ||
return h(node, 'h' + node.depth, all(h, node)) | ||
export function heading(state, node) { | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: 'h' + node.depth, | ||
properties: {}, | ||
children: state.all(node) | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* Return either a `raw` node in dangerous mode, otherwise nothing. | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').HTML} Html | ||
* @typedef {import('../state.js').State} State | ||
* @typedef {import('../../index.js').Raw} Raw | ||
*/ | ||
/** | ||
* Turn an mdast `html` node into hast (`raw` node in dangerous mode, otherwise | ||
* nothing). | ||
* | ||
* @param {H} h | ||
* @param {HTML} node | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Html} node | ||
* mdast node. | ||
* @returns {Raw | Element | null} | ||
* hast node. | ||
*/ | ||
export function html(h: H, node: HTML): import('hast').ElementContent | null | ||
export type HTML = import('mdast').HTML | ||
export type H = import('../index.js').H | ||
export function html(state: State, node: Html): Raw | Element | null | ||
export type Element = import('hast').Element | ||
export type Html = import('mdast').HTML | ||
export type State = import('../state.js').State | ||
export type Raw = import('../../index.js').Raw |
/** | ||
* @typedef {import('mdast').HTML} HTML | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').HTML} Html | ||
* @typedef {import('../state.js').State} State | ||
* @typedef {import('../../index.js').Raw} Raw | ||
*/ | ||
import {u} from 'unist-builder' | ||
/** | ||
* Return either a `raw` node in dangerous mode, otherwise nothing. | ||
* Turn an mdast `html` node into hast (`raw` node in dangerous mode, otherwise | ||
* nothing). | ||
* | ||
* @param {H} h | ||
* @param {HTML} node | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Html} node | ||
* mdast node. | ||
* @returns {Raw | Element | null} | ||
* hast node. | ||
*/ | ||
export function html(h, node) { | ||
return h.dangerous ? h.augment(node, u('raw', node.value)) : null | ||
export function html(state, node) { | ||
if (state.dangerous) { | ||
/** @type {Raw} */ | ||
const result = {type: 'raw', value: node.value} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} | ||
// To do: next major: return `undefined`. | ||
return null | ||
} |
/** | ||
* @param {H} h | ||
* Turn an mdast `imageReference` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {ImageReference} node | ||
* mdast node. | ||
* @returns {ElementContent | Array<ElementContent>} | ||
* hast node. | ||
*/ | ||
export function imageReference( | ||
h: H, | ||
state: State, | ||
node: ImageReference | ||
): import('hast').ElementContent | import('hast').ElementContent[] | ||
): ElementContent | Array<ElementContent> | ||
export type ElementContent = import('hast').ElementContent | ||
export type Element = import('hast').Element | ||
export type Properties = import('hast').Properties | ||
export type ImageReference = import('mdast').ImageReference | ||
export type Parent = import('mdast').Parent | ||
export type Properties = import('hast').Properties | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').ElementContent} ElementContent | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('mdast').ImageReference} ImageReference | ||
* @typedef {import('mdast').Parent} Parent | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
@@ -12,20 +13,29 @@ | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `imageReference` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {ImageReference} node | ||
* mdast node. | ||
* @returns {ElementContent | Array<ElementContent>} | ||
* hast node. | ||
*/ | ||
export function imageReference(h, node) { | ||
const def = h.definition(node.identifier) | ||
export function imageReference(state, node) { | ||
const def = state.definition(node.identifier) | ||
if (!def) { | ||
return revert(h, node) | ||
return revert(state, node) | ||
} | ||
/** @type {Properties} */ | ||
const props = {src: normalizeUri(def.url || ''), alt: node.alt} | ||
const properties = {src: normalizeUri(def.url || ''), alt: node.alt} | ||
if (def.title !== null && def.title !== undefined) { | ||
props.title = def.title | ||
properties.title = def.title | ||
} | ||
return h(node, 'img', props) | ||
/** @type {Element} */ | ||
const result = {type: 'element', tagName: 'img', properties, children: []} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* Turn an mdast `image` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Image} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function image(h: H, node: Image): import('hast').Element | ||
export function image(state: State, node: Image): Element | ||
export type Element = import('hast').Element | ||
export type Properties = import('hast').Properties | ||
export type Image = import('mdast').Image | ||
export type Properties = import('hast').Properties | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('mdast').Image} Image | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
@@ -10,14 +11,27 @@ | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `image` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Image} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function image(h, node) { | ||
export function image(state, node) { | ||
/** @type {Properties} */ | ||
const props = {src: normalizeUri(node.url), alt: node.alt} | ||
const properties = {src: normalizeUri(node.url)} | ||
if (node.alt !== null && node.alt !== undefined) { | ||
properties.alt = node.alt | ||
} | ||
if (node.title !== null && node.title !== undefined) { | ||
props.title = node.title | ||
properties.title = node.title | ||
} | ||
return h(node, 'img', props) | ||
/** @type {Element} */ | ||
const result = {type: 'element', tagName: 'img', properties, children: []} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
@@ -22,2 +22,4 @@ export namespace handlers { | ||
export {table} | ||
export {tableCell} | ||
export {tableRow} | ||
export {text} | ||
@@ -50,2 +52,4 @@ export {thematicBreak} | ||
import {table} from './table.js' | ||
import {tableCell} from './table-cell.js' | ||
import {tableRow} from './table-row.js' | ||
import {text} from './text.js' | ||
@@ -52,0 +56,0 @@ import {thematicBreak} from './thematic-break.js' |
@@ -21,5 +21,10 @@ import {blockquote} from './blockquote.js' | ||
import {table} from './table.js' | ||
import {tableRow} from './table-row.js' | ||
import {tableCell} from './table-cell.js' | ||
import {text} from './text.js' | ||
import {thematicBreak} from './thematic-break.js' | ||
/** | ||
* Default handlers for nodes. | ||
*/ | ||
export const handlers = { | ||
@@ -46,2 +51,4 @@ blockquote, | ||
table, | ||
tableCell, | ||
tableRow, | ||
text, | ||
@@ -57,3 +64,4 @@ thematicBreak, | ||
function ignore() { | ||
// To do: next major: return `undefined`. | ||
return null | ||
} |
/** | ||
* @param {H} h | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').Text} Text | ||
* @typedef {import('mdast').InlineCode} InlineCode | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* Turn an mdast `inlineCode` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {InlineCode} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function inlineCode(h: H, node: InlineCode): import('hast').Element | ||
export function inlineCode(state: State, node: InlineCode): Element | ||
export type Element = import('hast').Element | ||
export type Text = import('hast').Text | ||
export type InlineCode = import('mdast').InlineCode | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').Text} Text | ||
* @typedef {import('mdast').InlineCode} InlineCode | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {u} from 'unist-builder' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `inlineCode` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {InlineCode} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function inlineCode(h, node) { | ||
return h(node, 'code', [u('text', node.value.replace(/\r?\n|\r/g, ' '))]) | ||
export function inlineCode(state, node) { | ||
/** @type {Text} */ | ||
const text = {type: 'text', value: node.value.replace(/\r?\n|\r/g, ' ')} | ||
state.patch(node, text) | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: 'code', | ||
properties: {}, | ||
children: [text] | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* Turn an mdast `linkReference` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {LinkReference} node | ||
* mdast node. | ||
* @returns {ElementContent | Array<ElementContent>} | ||
* hast node. | ||
*/ | ||
export function linkReference( | ||
h: H, | ||
state: State, | ||
node: LinkReference | ||
): import('hast').ElementContent | import('hast').ElementContent[] | ||
): ElementContent | Array<ElementContent> | ||
export type Element = import('hast').Element | ||
export type ElementContent = import('hast').ElementContent | ||
export type Properties = import('hast').Properties | ||
export type LinkReference = import('mdast').LinkReference | ||
export type Properties = import('hast').Properties | ||
export type H = import('../index.js').H | ||
export type Parent = import('mdast').Parent | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').ElementContent} ElementContent | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('mdast').LinkReference} LinkReference | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('mdast').Parent} Parent | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
@@ -10,23 +11,36 @@ | ||
import {revert} from '../revert.js' | ||
import {all} from '../traverse.js' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `linkReference` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {LinkReference} node | ||
* mdast node. | ||
* @returns {ElementContent | Array<ElementContent>} | ||
* hast node. | ||
*/ | ||
export function linkReference(h, node) { | ||
const def = h.definition(node.identifier) | ||
export function linkReference(state, node) { | ||
const def = state.definition(node.identifier) | ||
if (!def) { | ||
return revert(h, node) | ||
return revert(state, node) | ||
} | ||
/** @type {Properties} */ | ||
const props = {href: normalizeUri(def.url || '')} | ||
const properties = {href: normalizeUri(def.url || '')} | ||
if (def.title !== null && def.title !== undefined) { | ||
props.title = def.title | ||
properties.title = def.title | ||
} | ||
return h(node, 'a', props, all(h, node)) | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: 'a', | ||
properties, | ||
children: state.all(node) | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* Turn an mdast `link` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Link} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function link(h: H, node: Link): import('hast').Element | ||
export function link(state: State, node: Link): Element | ||
export type Element = import('hast').Element | ||
export type Properties = import('hast').Properties | ||
export type Link = import('mdast').Link | ||
export type Properties = import('hast').Properties | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('mdast').Link} Link | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {normalizeUri} from 'micromark-util-sanitize-uri' | ||
import {all} from '../traverse.js' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `link` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Link} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function link(h, node) { | ||
export function link(state, node) { | ||
/** @type {Properties} */ | ||
const props = {href: normalizeUri(node.url)} | ||
const properties = {href: normalizeUri(node.url)} | ||
if (node.title !== null && node.title !== undefined) { | ||
props.title = node.title | ||
properties.title = node.title | ||
} | ||
return h(node, 'a', props, all(h, node)) | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: 'a', | ||
properties, | ||
children: state.all(node) | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').ElementContent} ElementContent | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('mdast').Content} Content | ||
* @typedef {import('mdast').ListItem} ListItem | ||
* @typedef {import('mdast').Parent} Parent | ||
* @typedef {import('mdast').Root} Root | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* @typedef {Root | Content} Nodes | ||
* @typedef {Extract<Nodes, Parent>} Parents | ||
*/ | ||
/** | ||
* Turn an mdast `listItem` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {ListItem} node | ||
* @param {List} parent | ||
* mdast node. | ||
* @param {Parents | null | undefined} parent | ||
* Parent of `node`. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function listItem( | ||
h: H, | ||
state: State, | ||
node: ListItem, | ||
parent: List | ||
): import('hast').Element | ||
parent: Parents | null | undefined | ||
): Element | ||
export type Element = import('hast').Element | ||
export type ElementContent = import('hast').ElementContent | ||
export type Properties = import('hast').Properties | ||
export type Content = import('mdast').Content | ||
export type ListItem = import('mdast').ListItem | ||
export type List = import('mdast').List | ||
export type Properties = import('hast').Properties | ||
export type Element = import('hast').Element | ||
export type H = import('../index.js').H | ||
export type Content = import('../index.js').Content | ||
export type Parent = import('mdast').Parent | ||
export type Root = import('mdast').Root | ||
export type State = import('../state.js').State | ||
export type Nodes = Root | Content | ||
export type Parents = Extract<Nodes, Parent> |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').ElementContent} ElementContent | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('mdast').Content} Content | ||
* @typedef {import('mdast').ListItem} ListItem | ||
* @typedef {import('mdast').List} List | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../index.js').Content} Content | ||
* @typedef {import('mdast').Parent} Parent | ||
* @typedef {import('mdast').Root} Root | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {u} from 'unist-builder' | ||
import {all} from '../traverse.js' | ||
/** | ||
* @typedef {Root | Content} Nodes | ||
* @typedef {Extract<Nodes, Parent>} Parents | ||
*/ | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `listItem` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {ListItem} node | ||
* @param {List} parent | ||
* mdast node. | ||
* @param {Parents | null | undefined} parent | ||
* Parent of `node`. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function listItem(h, node, parent) { | ||
const result = all(h, node) | ||
export function listItem(state, node, parent) { | ||
const results = state.all(node) | ||
const loose = parent ? listLoose(parent) : listItemLoose(node) | ||
/** @type {Properties} */ | ||
const props = {} | ||
/** @type {Array<Content>} */ | ||
const wrapped = [] | ||
const properties = {} | ||
/** @type {Array<ElementContent>} */ | ||
const children = [] | ||
if (typeof node.checked === 'boolean') { | ||
const head = results[0] | ||
/** @type {Element} */ | ||
let paragraph | ||
if ( | ||
result[0] && | ||
result[0].type === 'element' && | ||
result[0].tagName === 'p' | ||
) { | ||
paragraph = result[0] | ||
if (head && head.type === 'element' && head.tagName === 'p') { | ||
paragraph = head | ||
} else { | ||
paragraph = h(null, 'p', []) | ||
result.unshift(paragraph) | ||
paragraph = {type: 'element', tagName: 'p', properties: {}, children: []} | ||
results.unshift(paragraph) | ||
} | ||
if (paragraph.children.length > 0) { | ||
paragraph.children.unshift(u('text', ' ')) | ||
paragraph.children.unshift({type: 'text', value: ' '}) | ||
} | ||
paragraph.children.unshift( | ||
h(null, 'input', { | ||
type: 'checkbox', | ||
checked: node.checked, | ||
disabled: true | ||
}) | ||
) | ||
paragraph.children.unshift({ | ||
type: 'element', | ||
tagName: 'input', | ||
properties: {type: 'checkbox', checked: node.checked, disabled: true}, | ||
children: [] | ||
}) | ||
// According to github-markdown-css, this class hides bullet. | ||
// See: <https://github.com/sindresorhus/github-markdown-css>. | ||
props.className = ['task-list-item'] | ||
properties.className = ['task-list-item'] | ||
} | ||
@@ -60,4 +67,4 @@ | ||
while (++index < result.length) { | ||
const child = result[index] | ||
while (++index < results.length) { | ||
const child = results[index] | ||
@@ -71,36 +78,42 @@ // Add eols before nodes, except if this is a loose, first paragraph. | ||
) { | ||
wrapped.push(u('text', '\n')) | ||
children.push({type: 'text', value: '\n'}) | ||
} | ||
if (child.type === 'element' && child.tagName === 'p' && !loose) { | ||
wrapped.push(...child.children) | ||
children.push(...child.children) | ||
} else { | ||
wrapped.push(child) | ||
children.push(child) | ||
} | ||
} | ||
const tail = result[result.length - 1] | ||
const tail = results[results.length - 1] | ||
// Add a final eol. | ||
if (tail && (loose || !('tagName' in tail) || tail.tagName !== 'p')) { | ||
wrapped.push(u('text', '\n')) | ||
if (tail && (loose || tail.type !== 'element' || tail.tagName !== 'p')) { | ||
children.push({type: 'text', value: '\n'}) | ||
} | ||
return h(node, 'li', props, wrapped) | ||
/** @type {Element} */ | ||
const result = {type: 'element', tagName: 'li', properties, children} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} | ||
/** | ||
* @param {List} node | ||
* @param {Parents} node | ||
* @return {Boolean} | ||
*/ | ||
function listLoose(node) { | ||
let loose = node.spread | ||
const children = node.children | ||
let index = -1 | ||
let loose = false | ||
if (node.type === 'list') { | ||
loose = node.spread || false | ||
const children = node.children | ||
let index = -1 | ||
while (!loose && ++index < children.length) { | ||
loose = listItemLoose(children[index]) | ||
while (!loose && ++index < children.length) { | ||
loose = listItemLoose(children[index]) | ||
} | ||
} | ||
return Boolean(loose) | ||
return loose | ||
} | ||
@@ -107,0 +120,0 @@ |
/** | ||
* @param {H} h | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('mdast').List} List | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* Turn an mdast `list` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {List} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function list(h: H, node: List): import('hast').Element | ||
export type List = import('mdast').List | ||
export function list(state: State, node: List): Element | ||
export type Element = import('hast').Element | ||
export type Properties = import('hast').Properties | ||
export type H = import('../index.js').H | ||
export type List = import('mdast').List | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('mdast').List} List | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('mdast').List} List | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {wrap} from '../wrap.js' | ||
import {all} from '../traverse.js' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `list` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {List} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function list(h, node) { | ||
export function list(state, node) { | ||
/** @type {Properties} */ | ||
const props = {} | ||
const name = node.ordered ? 'ol' : 'ul' | ||
const items = all(h, node) | ||
const properties = {} | ||
const results = state.all(node) | ||
let index = -1 | ||
if (typeof node.start === 'number' && node.start !== 1) { | ||
props.start = node.start | ||
properties.start = node.start | ||
} | ||
// Like GitHub, add a class for custom styling. | ||
while (++index < items.length) { | ||
const item = items[index] | ||
while (++index < results.length) { | ||
const child = results[index] | ||
if ( | ||
item.type === 'element' && | ||
item.tagName === 'li' && | ||
item.properties && | ||
Array.isArray(item.properties.className) && | ||
item.properties.className.includes('task-list-item') | ||
child.type === 'element' && | ||
child.tagName === 'li' && | ||
child.properties && | ||
Array.isArray(child.properties.className) && | ||
child.properties.className.includes('task-list-item') | ||
) { | ||
props.className = ['contains-task-list'] | ||
properties.className = ['contains-task-list'] | ||
break | ||
@@ -42,3 +44,11 @@ } | ||
return h(node, name, props, wrap(items, true)) | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: node.ordered ? 'ol' : 'ul', | ||
properties, | ||
children: state.wrap(results, true) | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Paragraph} Paragraph | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* Turn an mdast `paragraph` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Paragraph} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function paragraph(h: H, node: Paragraph): import('hast').Element | ||
export function paragraph(state: State, node: Paragraph): Element | ||
export type Element = import('hast').Element | ||
export type Paragraph = import('mdast').Paragraph | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Paragraph} Paragraph | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {all} from '../traverse.js' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `paragraph` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Paragraph} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function paragraph(h, node) { | ||
return h(node, 'p', all(h, node)) | ||
export function paragraph(state, node) { | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: 'p', | ||
properties: {}, | ||
children: state.all(node) | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* @param {Root} node | ||
* @typedef {import('hast').Root} HastRoot | ||
* @typedef {import('hast').Element} HastElement | ||
* @typedef {import('mdast').Root} MdastRoot | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
export function root(h: H, node: Root): import('hast').ElementContent | ||
export type Root = import('mdast').Root | ||
export type H = import('../index.js').H | ||
/** | ||
* Turn an mdast `root` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {MdastRoot} node | ||
* mdast node. | ||
* @returns {HastRoot | HastElement} | ||
* hast node. | ||
*/ | ||
export function root(state: State, node: MdastRoot): HastRoot | HastElement | ||
export type HastRoot = import('hast').Root | ||
export type HastElement = import('hast').Element | ||
export type MdastRoot = import('mdast').Root | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('mdast').Root} Root | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('hast').Root} HastRoot | ||
* @typedef {import('hast').Element} HastElement | ||
* @typedef {import('mdast').Root} MdastRoot | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {u} from 'unist-builder' | ||
import {all} from '../traverse.js' | ||
import {wrap} from '../wrap.js' | ||
/** | ||
* @param {H} h | ||
* @param {Root} node | ||
* Turn an mdast `root` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {MdastRoot} node | ||
* mdast node. | ||
* @returns {HastRoot | HastElement} | ||
* hast node. | ||
*/ | ||
export function root(h, node) { | ||
// @ts-expect-error `root`s are also fine. | ||
return h.augment(node, u('root', wrap(all(h, node)))) | ||
export function root(state, node) { | ||
/** @type {HastRoot} */ | ||
const result = {type: 'root', children: state.wrap(state.all(node))} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Strong} Strong | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* Turn an mdast `strong` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Strong} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function strong(h: H, node: Strong): import('hast').Element | ||
export function strong(state: State, node: Strong): Element | ||
export type Element = import('hast').Element | ||
export type Strong = import('mdast').Strong | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Strong} Strong | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {all} from '../traverse.js' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `strong` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Strong} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function strong(h, node) { | ||
return h(node, 'strong', all(h, node)) | ||
export function strong(state, node) { | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: 'strong', | ||
properties: {}, | ||
children: state.all(node) | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* Turn an mdast `table` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Table} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function table(h: H, node: Table): import('hast').Element | ||
export function table(state: State, node: Table): Element | ||
export type Element = import('hast').Element | ||
export type Table = import('mdast').Table | ||
export type Element = import('hast').Element | ||
export type H = import('../index.js').H | ||
export type Content = import('../index.js').Content | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').Table} Table | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../index.js').Content} Content | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {pointStart, pointEnd} from 'unist-util-position' | ||
import {wrap} from '../wrap.js' | ||
import {all} from '../traverse.js' | ||
/** | ||
* @param {H} h | ||
* Turn an mdast `table` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {Table} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function table(h, node) { | ||
const rows = node.children | ||
let index = -1 | ||
const align = node.align || [] | ||
export function table(state, node) { | ||
const rows = state.all(node) | ||
const firstRow = rows.shift() | ||
/** @type {Array<Element>} */ | ||
const result = [] | ||
const tableContent = [] | ||
while (++index < rows.length) { | ||
const row = rows[index].children | ||
const name = index === 0 ? 'th' : 'td' | ||
/** @type {Array<Content>} */ | ||
const out = [] | ||
let cellIndex = -1 | ||
const length = node.align ? align.length : row.length | ||
if (firstRow) { | ||
/** @type {Element} */ | ||
const head = { | ||
type: 'element', | ||
tagName: 'thead', | ||
properties: {}, | ||
children: state.wrap([firstRow], true) | ||
} | ||
state.patch(node.children[0], head) | ||
tableContent.push(head) | ||
} | ||
while (++cellIndex < length) { | ||
const cell = row[cellIndex] | ||
out.push( | ||
h(cell, name, {align: align[cellIndex]}, cell ? all(h, cell) : []) | ||
) | ||
if (rows.length > 0) { | ||
/** @type {Element} */ | ||
const body = { | ||
type: 'element', | ||
tagName: 'tbody', | ||
properties: {}, | ||
children: state.wrap(rows, true) | ||
} | ||
result[index] = h(rows[index], 'tr', wrap(out, true)) | ||
const start = pointStart(node.children[1]) | ||
const end = pointEnd(node.children[node.children.length - 1]) | ||
if (start.line && end.line) body.position = {start, end} | ||
tableContent.push(body) | ||
} | ||
return h( | ||
node, | ||
'table', | ||
wrap( | ||
[h(result[0].position, 'thead', wrap([result[0]], true))].concat( | ||
result[1] | ||
? h( | ||
{ | ||
start: pointStart(result[1]), | ||
end: pointEnd(result[result.length - 1]) | ||
}, | ||
'tbody', | ||
wrap(result.slice(1), true) | ||
) | ||
: [] | ||
), | ||
true | ||
) | ||
) | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: 'table', | ||
properties: {}, | ||
children: state.wrap(tableContent, true) | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @param {H} h | ||
* @param {Text} node | ||
* Turn an mdast `text` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {MdastText} node | ||
* mdast node. | ||
* @returns {HastText | HastElement} | ||
* hast node. | ||
*/ | ||
export function text(h: H, node: Text): import('hast').ElementContent | ||
export type Text = import('mdast').Text | ||
export type H = import('../index.js').H | ||
export function text(state: State, node: MdastText): HastText | HastElement | ||
export type HastElement = import('hast').Element | ||
export type HastText = import('hast').Text | ||
export type MdastText = import('mdast').Text | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('mdast').Text} Text | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('hast').Element} HastElement | ||
* @typedef {import('hast').Text} HastText | ||
* @typedef {import('mdast').Text} MdastText | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
import {trimLines} from 'trim-lines' | ||
import {u} from 'unist-builder' | ||
/** | ||
* @param {H} h | ||
* @param {Text} node | ||
* Turn an mdast `text` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {MdastText} node | ||
* mdast node. | ||
* @returns {HastText | HastElement} | ||
* hast node. | ||
*/ | ||
export function text(h, node) { | ||
return h.augment(node, u('text', trimLines(String(node.value)))) | ||
export function text(state, node) { | ||
/** @type {HastText} */ | ||
const result = {type: 'text', value: trimLines(String(node.value))} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').ThematicBreak} ThematicBreak | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* @param {H} h | ||
* @param {ThematicBreak} [node] | ||
* Turn an mdast `thematicBreak` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {ThematicBreak} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function thematicBreak( | ||
h: H, | ||
node?: import('mdast').ThematicBreak | undefined | ||
): import('hast').Element | ||
export function thematicBreak(state: State, node: ThematicBreak): Element | ||
export type Element = import('hast').Element | ||
export type ThematicBreak = import('mdast').ThematicBreak | ||
export type Element = import('hast').Element | ||
export type H = import('../index.js').H | ||
export type State = import('../state.js').State |
/** | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('mdast').ThematicBreak} ThematicBreak | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('../index.js').H} H | ||
* @typedef {import('../state.js').State} State | ||
*/ | ||
/** | ||
* @param {H} h | ||
* @param {ThematicBreak} [node] | ||
* Turn an mdast `thematicBreak` node into hast. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {ThematicBreak} node | ||
* mdast node. | ||
* @returns {Element} | ||
* hast node. | ||
*/ | ||
export function thematicBreak(h, node) { | ||
return h(node, 'hr') | ||
export function thematicBreak(state, node) { | ||
/** @type {Element} */ | ||
const result = { | ||
type: 'element', | ||
tagName: 'hr', | ||
properties: {}, | ||
children: [] | ||
} | ||
state.patch(node, result) | ||
return state.applyData(node, result) | ||
} |
/** | ||
* Transform `tree` (an mdast node) to a hast node. | ||
* Transform mdast to hast. | ||
* | ||
* @param {MdastNode} tree mdast node | ||
* @param {Options} [options] Configuration | ||
* @returns {HastNode|null|undefined} hast node | ||
* ##### Notes | ||
* | ||
* ###### HTML | ||
* | ||
* Raw HTML is available in mdast as `html` nodes and can be embedded in hast | ||
* as semistandard `raw` nodes. | ||
* Most utilities ignore `raw` nodes but two notable ones don’t: | ||
* | ||
* * `hast-util-to-html` also has an option `allowDangerousHtml` which will | ||
* output the raw HTML. | ||
* This is typically discouraged as noted by the option name but is useful | ||
* if you completely trust authors | ||
* * `hast-util-raw` can handle the raw embedded HTML strings by parsing them | ||
* into standard hast nodes (`element`, `text`, etc). | ||
* This is a heavy task as it needs a full HTML parser, but it is the only | ||
* way to support untrusted content | ||
* | ||
* ###### Footnotes | ||
* | ||
* Many options supported here relate to footnotes. | ||
* Footnotes are not specified by CommonMark, which we follow by default. | ||
* They are supported by GitHub, so footnotes can be enabled in markdown with | ||
* `mdast-util-gfm`. | ||
* | ||
* The options `footnoteBackLabel` and `footnoteLabel` define natural language | ||
* that explains footnotes, which is hidden for sighted users but shown to | ||
* assistive technology. | ||
* When your page is not in English, you must define translated values. | ||
* | ||
* Back references use ARIA attributes, but the section label itself uses a | ||
* heading that is hidden with an `sr-only` class. | ||
* To show it to sighted users, define different attributes in | ||
* `footnoteLabelProperties`. | ||
* | ||
* ###### Clobbering | ||
* | ||
* Footnotes introduces a problem, as it links footnote calls to footnote | ||
* definitions on the page through `id` attributes generated from user content, | ||
* which results in DOM clobbering. | ||
* | ||
* DOM clobbering is this: | ||
* | ||
* ```html | ||
* <p id=x></p> | ||
* <script>alert(x) // `x` now refers to the DOM `p#x` element</script> | ||
* ``` | ||
* | ||
* Elements by their ID are made available by browsers on the `window` object, | ||
* which is a security risk. | ||
* Using a prefix solves this problem. | ||
* | ||
* More information on how to handle clobbering and the prefix is explained in | ||
* Example: headings (DOM clobbering) in `rehype-sanitize`. | ||
* | ||
* ###### Unknown nodes | ||
* | ||
* Unknown nodes are nodes with a type that isn’t in `handlers` or `passThrough`. | ||
* The default behavior for unknown nodes is: | ||
* | ||
* * when the node has a `value` (and doesn’t have `data.hName`, | ||
* `data.hProperties`, or `data.hChildren`, see later), create a hast `text` | ||
* node | ||
* * otherwise, create a `<div>` element (which could be changed with | ||
* `data.hName`), with its children mapped from mdast to hast as well | ||
* | ||
* This behavior can be changed by passing an `unknownHandler`. | ||
* | ||
* @param {MdastNodes} tree | ||
* mdast tree. | ||
* @param {Options | null | undefined} [options] | ||
* Configuration. | ||
* @returns {HastNodes | null | undefined} | ||
* hast tree. | ||
*/ | ||
export function toHast( | ||
tree: MdastNode, | ||
options?: Options | undefined | ||
): HastNode | null | undefined | ||
export {handlers as defaultHandlers} from './handlers/index.js' | ||
export type MdastNode = | ||
| import('mdast').Root | ||
| import('mdast').Parent['children'][number] | ||
export type HastNode = | ||
| import('hast').Root | ||
| import('hast').Parent['children'][number] | ||
export type Parent = import('mdast').Parent | ||
export type Definition = import('mdast').Definition | ||
export type FootnoteDefinition = import('mdast').FootnoteDefinition | ||
export type Properties = import('hast').Properties | ||
export type Element = import('hast').Element | ||
export type Content = import('hast').ElementContent | ||
export type EmbeddedHastFields = { | ||
/** | ||
* Defines the tag name of an element. | ||
*/ | ||
hName?: string | undefined | ||
/** | ||
* Defines the properties of an element. | ||
*/ | ||
hProperties?: import('hast').Properties | undefined | ||
/** | ||
* Defines the (hast) children of an element. | ||
*/ | ||
hChildren?: import('hast').ElementContent[] | undefined | ||
} | ||
/** | ||
* unist data with embedded hast fields. | ||
*/ | ||
export type Data = Record<string, unknown> & EmbeddedHastFields | ||
/** | ||
* unist node with embedded hast data. | ||
*/ | ||
export type NodeWithData = MdastNode & { | ||
data?: Data | ||
} | ||
export type PositionLike = { | ||
start?: PointLike | null | undefined | ||
end?: PointLike | null | undefined | ||
} | ||
export type PointLike = { | ||
line?: number | null | undefined | ||
column?: number | null | undefined | ||
offset?: number | null | undefined | ||
} | ||
/** | ||
* Handle a node. | ||
*/ | ||
export type Handler = ( | ||
h: H, | ||
node: any, | ||
parent: Parent | null | ||
) => Content | Array<Content> | null | undefined | ||
export type HFunctionProps = ( | ||
node: MdastNode | PositionLike | null | undefined, | ||
tagName: string, | ||
props: Properties, | ||
children?: import('hast').ElementContent[] | null | undefined | ||
) => Element | ||
export type HFunctionNoProps = ( | ||
node: MdastNode | PositionLike | null | undefined, | ||
tagName: string, | ||
children?: import('hast').ElementContent[] | null | undefined | ||
) => Element | ||
export type HFields = { | ||
/** | ||
* Whether HTML is allowed. | ||
*/ | ||
dangerous: boolean | ||
/** | ||
* Prefix to use to prevent DOM clobbering. | ||
*/ | ||
clobberPrefix: string | ||
/** | ||
* Label to use to introduce the footnote section. | ||
*/ | ||
footnoteLabel: string | ||
/** | ||
* HTML used for the footnote label. | ||
*/ | ||
footnoteLabelTagName: string | ||
/** | ||
* Properties on the HTML tag used for the footnote label. | ||
*/ | ||
footnoteLabelProperties: Properties | ||
/** | ||
* Label to use to go back to a footnote call from the footnote section. | ||
*/ | ||
footnoteBackLabel: string | ||
/** | ||
* Definition cache. | ||
*/ | ||
definition: (identifier: string) => Definition | null | ||
/** | ||
* Footnote cache. | ||
*/ | ||
footnoteById: Record<string, FootnoteDefinition> | ||
/** | ||
* Order in which footnotes occur. | ||
*/ | ||
footnoteOrder: Array<string> | ||
/** | ||
* Counts the same footnote was used. | ||
*/ | ||
footnoteCounts: Record<string, number> | ||
/** | ||
* Applied handlers. | ||
*/ | ||
handlers: Handlers | ||
/** | ||
* Handler for any none not in `passThrough` or otherwise handled. | ||
*/ | ||
unknownHandler: Handler | ||
/** | ||
* Like `h` but lower-level and usable on non-elements. | ||
*/ | ||
augment: ( | ||
left: NodeWithData | PositionLike | null | undefined, | ||
right: Content | ||
) => Content | ||
/** | ||
* List of node types to pass through untouched (except for their children). | ||
*/ | ||
passThrough: Array<string> | ||
} | ||
/** | ||
* Configuration (optional). | ||
*/ | ||
export type Options = { | ||
/** | ||
* Whether to allow `html` nodes and inject them as `raw` HTML. | ||
*/ | ||
allowDangerousHtml?: boolean | undefined | ||
/** | ||
* Prefix to use before the `id` attribute to prevent it from *clobbering*. | ||
* attributes. | ||
* DOM clobbering is this: | ||
* | ||
* ```html | ||
* <p id=x></p> | ||
* <script>alert(x)</script> | ||
* ``` | ||
* | ||
* Elements by their ID are made available in browsers on the `window` object. | ||
* Using a prefix prevents this from being a problem. | ||
*/ | ||
clobberPrefix?: string | undefined | ||
/** | ||
* Label to use for the footnotes section. | ||
* Affects screen reader users. | ||
* Change it if you’re authoring in a different language. | ||
*/ | ||
footnoteLabel?: string | undefined | ||
/** | ||
* HTML tag to use for the footnote label. | ||
* Can be changed to match your document structure and play well with your choice of css. | ||
*/ | ||
footnoteLabelTagName?: string | undefined | ||
/** | ||
* Properties to use on the footnote label. | ||
* A 'sr-only' class is added by default to hide this from sighted users. | ||
* Change it to make the label visible, or add classes for other purposes. | ||
*/ | ||
footnoteLabelProperties?: import('hast').Properties | undefined | ||
/** | ||
* Label to use from backreferences back to their footnote call. | ||
* Affects screen reader users. | ||
* Change it if you’re authoring in a different language. | ||
*/ | ||
footnoteBackLabel?: string | undefined | ||
/** | ||
* Object mapping mdast nodes to functions handling them | ||
*/ | ||
handlers?: Handlers | undefined | ||
/** | ||
* List of custom mdast node types to pass through (keep) in hast | ||
*/ | ||
passThrough?: string[] | undefined | ||
/** | ||
* Handler for all unknown nodes. | ||
*/ | ||
unknownHandler?: Handler | undefined | ||
} | ||
/** | ||
* Map of node types to handlers | ||
*/ | ||
export type Handlers = Record<string, Handler> | ||
/** | ||
* Handle context | ||
*/ | ||
export type H = HFunctionProps & HFunctionNoProps & HFields | ||
tree: MdastNodes, | ||
options?: Options | null | undefined | ||
): HastNodes | null | undefined | ||
export type HastContent = import('hast').Content | ||
export type HastRoot = import('hast').Root | ||
export type MdastContent = import('mdast').Content | ||
export type MdastRoot = import('mdast').Root | ||
export type Options = import('./state.js').Options | ||
export type HastNodes = HastRoot | HastContent | ||
export type MdastNodes = MdastRoot | MdastContent |
350
lib/index.js
/** | ||
* @typedef {import('mdast').Root|import('mdast').Parent['children'][number]} MdastNode | ||
* @typedef {import('hast').Root|import('hast').Parent['children'][number]} HastNode | ||
* @typedef {import('mdast').Parent} Parent | ||
* @typedef {import('mdast').Definition} Definition | ||
* @typedef {import('mdast').FootnoteDefinition} FootnoteDefinition | ||
* @typedef {import('hast').Properties} Properties | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').ElementContent} Content | ||
* @typedef {import('hast').Content} HastContent | ||
* @typedef {import('hast').Root} HastRoot | ||
* | ||
* @typedef EmbeddedHastFields | ||
* @property {string} [hName] | ||
* Defines the tag name of an element. | ||
* @property {Properties} [hProperties] | ||
* Defines the properties of an element. | ||
* @property {Array<Content>} [hChildren] | ||
* Defines the (hast) children of an element. | ||
* @typedef {import('mdast').Content} MdastContent | ||
* @typedef {import('mdast').Root} MdastRoot | ||
* | ||
* @typedef {Record<string, unknown> & EmbeddedHastFields} Data | ||
* unist data with embedded hast fields. | ||
* @typedef {import('./state.js').Options} Options | ||
*/ | ||
/** | ||
* @typedef {HastRoot | HastContent} HastNodes | ||
* @typedef {MdastRoot | MdastContent} MdastNodes | ||
*/ | ||
import {footer} from './footer.js' | ||
import {createState} from './state.js' | ||
/** | ||
* Transform mdast to hast. | ||
* | ||
* @typedef {MdastNode & {data?: Data}} NodeWithData | ||
* unist node with embedded hast data. | ||
* ##### Notes | ||
* | ||
* @typedef PositionLike | ||
* @property {PointLike | null | undefined} [start] | ||
* @property {PointLike | null | undefined} [end] | ||
* ###### HTML | ||
* | ||
* @typedef PointLike | ||
* @property {number | null | undefined} [line] | ||
* @property {number | null | undefined} [column] | ||
* @property {number | null | undefined} [offset] | ||
* Raw HTML is available in mdast as `html` nodes and can be embedded in hast | ||
* as semistandard `raw` nodes. | ||
* Most utilities ignore `raw` nodes but two notable ones don’t: | ||
* | ||
* @callback Handler | ||
* Handle a node. | ||
* @param {H} h | ||
* Handle context. | ||
* @param {any} node | ||
* mdast node to handle. | ||
* @param {Parent|null} parent | ||
* Parent of `node`. | ||
* @returns {Content|Array<Content>|null|undefined} | ||
* hast node. | ||
* * `hast-util-to-html` also has an option `allowDangerousHtml` which will | ||
* output the raw HTML. | ||
* This is typically discouraged as noted by the option name but is useful | ||
* if you completely trust authors | ||
* * `hast-util-raw` can handle the raw embedded HTML strings by parsing them | ||
* into standard hast nodes (`element`, `text`, etc). | ||
* This is a heavy task as it needs a full HTML parser, but it is the only | ||
* way to support untrusted content | ||
* | ||
* @callback HFunctionProps | ||
* @param {MdastNode|PositionLike|null|undefined} node | ||
* mdast node or unist position. | ||
* @param {string} tagName | ||
* HTML tag name. | ||
* @param {Properties} props | ||
* Properties. | ||
* @param {Array<Content>?} [children] | ||
* hast content. | ||
* @returns {Element} | ||
* Compiled element. | ||
* ###### Footnotes | ||
* | ||
* @callback HFunctionNoProps | ||
* @param {MdastNode|PositionLike|null|undefined} node | ||
* mdast node or unist position. | ||
* @param {string} tagName | ||
* HTML tag name. | ||
* @param {Array<Content>?} [children] | ||
* hast content | ||
* @returns {Element} | ||
* Compiled element. | ||
* Many options supported here relate to footnotes. | ||
* Footnotes are not specified by CommonMark, which we follow by default. | ||
* They are supported by GitHub, so footnotes can be enabled in markdown with | ||
* `mdast-util-gfm`. | ||
* | ||
* @typedef HFields | ||
* @property {boolean} dangerous | ||
* Whether HTML is allowed. | ||
* @property {string} clobberPrefix | ||
* Prefix to use to prevent DOM clobbering. | ||
* @property {string} footnoteLabel | ||
* Label to use to introduce the footnote section. | ||
* @property {string} footnoteLabelTagName | ||
* HTML used for the footnote label. | ||
* @property {Properties} footnoteLabelProperties | ||
* Properties on the HTML tag used for the footnote label. | ||
* @property {string} footnoteBackLabel | ||
* Label to use to go back to a footnote call from the footnote section. | ||
* @property {(identifier: string) => Definition|null} definition | ||
* Definition cache. | ||
* @property {Record<string, FootnoteDefinition>} footnoteById | ||
* Footnote cache. | ||
* @property {Array<string>} footnoteOrder | ||
* Order in which footnotes occur. | ||
* @property {Record<string, number>} footnoteCounts | ||
* Counts the same footnote was used. | ||
* @property {Handlers} handlers | ||
* Applied handlers. | ||
* @property {Handler} unknownHandler | ||
* Handler for any none not in `passThrough` or otherwise handled. | ||
* @property {(left: NodeWithData|PositionLike|null|undefined, right: Content) => Content} augment | ||
* Like `h` but lower-level and usable on non-elements. | ||
* @property {Array<string>} passThrough | ||
* List of node types to pass through untouched (except for their children). | ||
* The options `footnoteBackLabel` and `footnoteLabel` define natural language | ||
* that explains footnotes, which is hidden for sighted users but shown to | ||
* assistive technology. | ||
* When your page is not in English, you must define translated values. | ||
* | ||
* @typedef Options | ||
* Configuration (optional). | ||
* @property {boolean} [allowDangerousHtml=false] | ||
* Whether to allow `html` nodes and inject them as `raw` HTML. | ||
* @property {string} [clobberPrefix='user-content-'] | ||
* Prefix to use before the `id` attribute to prevent it from *clobbering*. | ||
* attributes. | ||
* DOM clobbering is this: | ||
* Back references use ARIA attributes, but the section label itself uses a | ||
* heading that is hidden with an `sr-only` class. | ||
* To show it to sighted users, define different attributes in | ||
* `footnoteLabelProperties`. | ||
* | ||
* ```html | ||
* <p id=x></p> | ||
* <script>alert(x)</script> | ||
* ``` | ||
* ###### Clobbering | ||
* | ||
* Elements by their ID are made available in browsers on the `window` object. | ||
* Using a prefix prevents this from being a problem. | ||
* @property {string} [footnoteLabel='Footnotes'] | ||
* Label to use for the footnotes section. | ||
* Affects screen reader users. | ||
* Change it if you’re authoring in a different language. | ||
* @property {string} [footnoteLabelTagName='h2'] | ||
* HTML tag to use for the footnote label. | ||
* Can be changed to match your document structure and play well with your choice of css. | ||
* @property {Properties} [footnoteLabelProperties={className: ['sr-only']}] | ||
* Properties to use on the footnote label. | ||
* A 'sr-only' class is added by default to hide this from sighted users. | ||
* Change it to make the label visible, or add classes for other purposes. | ||
* @property {string} [footnoteBackLabel='Back to content'] | ||
* Label to use from backreferences back to their footnote call. | ||
* Affects screen reader users. | ||
* Change it if you’re authoring in a different language. | ||
* @property {Handlers} [handlers] | ||
* Object mapping mdast nodes to functions handling them | ||
* @property {Array<string>} [passThrough] | ||
* List of custom mdast node types to pass through (keep) in hast | ||
* @property {Handler} [unknownHandler] | ||
* Handler for all unknown nodes. | ||
* Footnotes introduces a problem, as it links footnote calls to footnote | ||
* definitions on the page through `id` attributes generated from user content, | ||
* which results in DOM clobbering. | ||
* | ||
* @typedef {Record<string, Handler>} Handlers | ||
* Map of node types to handlers | ||
* @typedef {HFunctionProps & HFunctionNoProps & HFields} H | ||
* Handle context | ||
*/ | ||
import {u} from 'unist-builder' | ||
import {visit} from 'unist-util-visit' | ||
import {pointStart, pointEnd} from 'unist-util-position' | ||
import {generated} from 'unist-util-generated' | ||
import {definitions} from 'mdast-util-definitions' | ||
import {one} from './traverse.js' | ||
import {footer} from './footer.js' | ||
import {handlers} from './handlers/index.js' | ||
const own = {}.hasOwnProperty | ||
/** | ||
* Turn mdast into hast. | ||
* DOM clobbering is this: | ||
* | ||
* @param {MdastNode} tree | ||
* mdast node. | ||
* @param {Options} [options] | ||
* Configuration (optional). | ||
* @returns {H} | ||
* `h` function. | ||
*/ | ||
function factory(tree, options) { | ||
const settings = options || {} | ||
const dangerous = settings.allowDangerousHtml || false | ||
/** @type {Record<string, FootnoteDefinition>} */ | ||
const footnoteById = {} | ||
h.dangerous = dangerous | ||
h.clobberPrefix = | ||
settings.clobberPrefix === undefined || settings.clobberPrefix === null | ||
? 'user-content-' | ||
: settings.clobberPrefix | ||
h.footnoteLabel = settings.footnoteLabel || 'Footnotes' | ||
h.footnoteLabelTagName = settings.footnoteLabelTagName || 'h2' | ||
h.footnoteLabelProperties = settings.footnoteLabelProperties || { | ||
className: ['sr-only'] | ||
} | ||
h.footnoteBackLabel = settings.footnoteBackLabel || 'Back to content' | ||
h.definition = definitions(tree) | ||
h.footnoteById = footnoteById | ||
/** @type {Array<string>} */ | ||
h.footnoteOrder = [] | ||
/** @type {Record<string, number>} */ | ||
h.footnoteCounts = {} | ||
h.augment = augment | ||
h.handlers = {...handlers, ...settings.handlers} | ||
h.unknownHandler = settings.unknownHandler | ||
h.passThrough = settings.passThrough | ||
visit(tree, 'footnoteDefinition', (definition) => { | ||
const id = String(definition.identifier).toUpperCase() | ||
// Mimick CM behavior of link definitions. | ||
// See: <https://github.com/syntax-tree/mdast-util-definitions/blob/8290999/index.js#L26>. | ||
if (!own.call(footnoteById, id)) { | ||
footnoteById[id] = definition | ||
} | ||
}) | ||
// @ts-expect-error Hush, it’s fine! | ||
return h | ||
/** | ||
* Finalise the created `right`, a hast node, from `left`, an mdast node. | ||
* | ||
* @param {(NodeWithData|PositionLike)?} left | ||
* @param {Content} right | ||
* @returns {Content} | ||
*/ | ||
function augment(left, right) { | ||
// Handle `data.hName`, `data.hProperties, `data.hChildren`. | ||
if (left && 'data' in left && left.data) { | ||
/** @type {Data} */ | ||
const data = left.data | ||
if (data.hName) { | ||
if (right.type !== 'element') { | ||
right = { | ||
type: 'element', | ||
tagName: '', | ||
properties: {}, | ||
children: [] | ||
} | ||
} | ||
right.tagName = data.hName | ||
} | ||
if (right.type === 'element' && data.hProperties) { | ||
right.properties = {...right.properties, ...data.hProperties} | ||
} | ||
if ('children' in right && right.children && data.hChildren) { | ||
right.children = data.hChildren | ||
} | ||
} | ||
if (left) { | ||
const ctx = 'type' in left ? left : {position: left} | ||
if (!generated(ctx)) { | ||
// @ts-expect-error: fine. | ||
right.position = {start: pointStart(ctx), end: pointEnd(ctx)} | ||
} | ||
} | ||
return right | ||
} | ||
/** | ||
* Create an element for `node`. | ||
* | ||
* @type {HFunctionProps} | ||
*/ | ||
function h(node, tagName, props, children) { | ||
if (Array.isArray(props)) { | ||
children = props | ||
props = {} | ||
} | ||
// @ts-expect-error augmenting an element yields an element. | ||
return augment(node, { | ||
type: 'element', | ||
tagName, | ||
properties: props || {}, | ||
children: children || [] | ||
}) | ||
} | ||
} | ||
/** | ||
* Transform `tree` (an mdast node) to a hast node. | ||
* ```html | ||
* <p id=x></p> | ||
* <script>alert(x) // `x` now refers to the DOM `p#x` element</script> | ||
* ``` | ||
* | ||
* @param {MdastNode} tree mdast node | ||
* @param {Options} [options] Configuration | ||
* @returns {HastNode|null|undefined} hast node | ||
* Elements by their ID are made available by browsers on the `window` object, | ||
* which is a security risk. | ||
* Using a prefix solves this problem. | ||
* | ||
* More information on how to handle clobbering and the prefix is explained in | ||
* Example: headings (DOM clobbering) in `rehype-sanitize`. | ||
* | ||
* ###### Unknown nodes | ||
* | ||
* Unknown nodes are nodes with a type that isn’t in `handlers` or `passThrough`. | ||
* The default behavior for unknown nodes is: | ||
* | ||
* * when the node has a `value` (and doesn’t have `data.hName`, | ||
* `data.hProperties`, or `data.hChildren`, see later), create a hast `text` | ||
* node | ||
* * otherwise, create a `<div>` element (which could be changed with | ||
* `data.hName`), with its children mapped from mdast to hast as well | ||
* | ||
* This behavior can be changed by passing an `unknownHandler`. | ||
* | ||
* @param {MdastNodes} tree | ||
* mdast tree. | ||
* @param {Options | null | undefined} [options] | ||
* Configuration. | ||
* @returns {HastNodes | null | undefined} | ||
* hast tree. | ||
*/ | ||
// To do: next major: always return a single `root`. | ||
export function toHast(tree, options) { | ||
const h = factory(tree, options) | ||
const node = one(h, tree, null) | ||
const foot = footer(h) | ||
const state = createState(tree, options) | ||
const node = state.one(tree, null) | ||
const foot = footer(state) | ||
@@ -287,8 +106,7 @@ if (foot) { | ||
// So assume `node` is a parent node. | ||
node.children.push(u('text', '\n'), foot) | ||
node.children.push({type: 'text', value: '\n'}, foot) | ||
} | ||
// To do: next major: always return root? | ||
return Array.isArray(node) ? {type: 'root', children: node} : node | ||
} | ||
export {handlers as defaultHandlers} from './handlers/index.js' |
/** | ||
* @typedef {import('hast').ElementContent} ElementContent | ||
* | ||
* @typedef {import('mdast').Content} Content | ||
* @typedef {import('mdast').Reference} Reference | ||
* @typedef {import('mdast').Root} Root | ||
* | ||
* @typedef {import('./state.js').State} State | ||
*/ | ||
/** | ||
* @typedef {Root | Content} Nodes | ||
* @typedef {Extract<Nodes, Reference>} References | ||
*/ | ||
/** | ||
* Return the content of a reference without definition as plain text. | ||
* | ||
* @param {H} h | ||
* @param {ImageReference|LinkReference} node | ||
* @returns {Content|Array<Content>} | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {References} node | ||
* Reference node (image, link). | ||
* @returns {ElementContent | Array<ElementContent>} | ||
* hast content. | ||
*/ | ||
export function revert( | ||
h: H, | ||
node: ImageReference | LinkReference | ||
): Content | Array<Content> | ||
export type LinkReference = import('mdast').LinkReference | ||
export type ImageReference = import('mdast').ImageReference | ||
export type H = import('./index.js').H | ||
export type Content = import('./index.js').Content | ||
state: State, | ||
node: References | ||
): ElementContent | Array<ElementContent> | ||
export type ElementContent = import('hast').ElementContent | ||
export type Content = import('mdast').Content | ||
export type Reference = import('mdast').Reference | ||
export type Root = import('mdast').Root | ||
export type State = import('./state.js').State | ||
export type Nodes = Root | Content | ||
export type References = Extract<Nodes, Reference> |
/** | ||
* @typedef {import('mdast').LinkReference} LinkReference | ||
* @typedef {import('mdast').ImageReference} ImageReference | ||
* @typedef {import('./index.js').H} H | ||
* @typedef {import('./index.js').Content} Content | ||
* @typedef {import('hast').ElementContent} ElementContent | ||
* | ||
* @typedef {import('mdast').Content} Content | ||
* @typedef {import('mdast').Reference} Reference | ||
* @typedef {import('mdast').Root} Root | ||
* | ||
* @typedef {import('./state.js').State} State | ||
*/ | ||
import {u} from 'unist-builder' | ||
import {all} from './traverse.js' | ||
/** | ||
* @typedef {Root | Content} Nodes | ||
* @typedef {Extract<Nodes, Reference>} References | ||
*/ | ||
// To do: next major: always return array. | ||
/** | ||
* Return the content of a reference without definition as plain text. | ||
* | ||
* @param {H} h | ||
* @param {ImageReference|LinkReference} node | ||
* @returns {Content|Array<Content>} | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {References} node | ||
* Reference node (image, link). | ||
* @returns {ElementContent | Array<ElementContent>} | ||
* hast content. | ||
*/ | ||
export function revert(h, node) { | ||
export function revert(state, node) { | ||
const subtype = node.referenceType | ||
@@ -29,6 +39,6 @@ let suffix = ']' | ||
if (node.type === 'imageReference') { | ||
return u('text', '![' + node.alt + suffix) | ||
return {type: 'text', value: '![' + node.alt + suffix} | ||
} | ||
const contents = all(h, node) | ||
const contents = state.all(node) | ||
const head = contents[0] | ||
@@ -39,3 +49,3 @@ | ||
} else { | ||
contents.unshift(u('text', '[')) | ||
contents.unshift({type: 'text', value: '['}) | ||
} | ||
@@ -48,3 +58,3 @@ | ||
} else { | ||
contents.push(u('text', suffix)) | ||
contents.push({type: 'text', value: suffix}) | ||
} | ||
@@ -51,0 +61,0 @@ |
/** | ||
* @param {H} h | ||
* @param {MdastNode} node | ||
* @param {MdastParent | null} parent | ||
* Transform an mdast node into a hast node. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {MdastNodes} node | ||
* mdast node. | ||
* @param {MdastParents | null | undefined} [parent] | ||
* Parent of `node`. | ||
* @returns {HastElementContent | Array<HastElementContent> | null | undefined} | ||
* Resulting hast node. | ||
*/ | ||
export function one( | ||
h: H, | ||
node: MdastNode, | ||
parent: MdastParent | null | ||
): | ||
| import('hast').ElementContent | ||
| import('hast').ElementContent[] | ||
| null | ||
| undefined | ||
state: State, | ||
node: MdastNodes, | ||
parent?: MdastParents | null | undefined | ||
): HastElementContent | Array<HastElementContent> | null | undefined | ||
/** | ||
* @param {H} h | ||
* @param {MdastNode} parent | ||
* Transform the children of an mdast node into hast nodes. | ||
* | ||
* @param {State} state | ||
* Info passed around. | ||
* @param {MdastNodes} parent | ||
* mdast node to compile | ||
* @returns {Array<HastElementContent>} | ||
* Resulting hast nodes. | ||
*/ | ||
export function all(h: H, parent: MdastNode): import('hast').ElementContent[] | ||
export type MdastNode = import('mdast').Root | import('mdast').Content | ||
export type MdastParent = Extract<MdastNode, import('mdast').Parent> | ||
export type Handler = import('./index.js').Handler | ||
export type H = import('./index.js').H | ||
export type Content = import('./index.js').Content | ||
export function all(state: State, parent: MdastNodes): Array<HastElementContent> | ||
export type HastElementContent = import('hast').ElementContent | ||
export type HastElement = import('hast').Element | ||
export type HastText = import('hast').Text | ||
export type MdastContent = import('mdast').Content | ||
export type MdastParent = import('mdast').Parent | ||
export type MdastRoot = import('mdast').Root | ||
export type State = import('./state.js').State | ||
export type MdastNodes = MdastRoot | MdastContent | ||
export type MdastParents = Extract<MdastNodes, MdastParent> |
/** | ||
* Wrap `nodes` with line feeds between each entry. | ||
* Optionally adds line feeds at the start and end. | ||
* @typedef {import('hast').Content} Content | ||
* @typedef {import('hast').Text} Text | ||
*/ | ||
/** | ||
* Wrap `nodes` with line endings between each node. | ||
* | ||
* @param {Array<Content>} nodes | ||
* @param {boolean} [loose=false] | ||
* @returns {Array<Content>} | ||
* @template {Content} Type | ||
* Node type. | ||
* @param {Array<Type>} nodes | ||
* List of nodes to wrap. | ||
* @param {boolean | null | undefined} [loose=false] | ||
* Whether to add line endings at start and end. | ||
* @returns {Array<Type | Text>} | ||
* Wrapped nodes. | ||
*/ | ||
export function wrap( | ||
nodes: Array<Content>, | ||
loose?: boolean | undefined | ||
): Array<Content> | ||
export type Content = import('./index.js').Content | ||
export function wrap<Type extends import('hast').Content>( | ||
nodes: Type[], | ||
loose?: boolean | null | undefined | ||
): (import('hast').Text | Type)[] | ||
export type Content = import('hast').Content | ||
export type Text = import('hast').Text |
{ | ||
"name": "mdast-util-to-hast", | ||
"version": "12.2.6", | ||
"version": "12.3.0", | ||
"description": "mdast utility to transform to hast", | ||
@@ -43,3 +43,2 @@ "license": "MIT", | ||
"trim-lines": "^3.0.0", | ||
"unist-builder": "^3.0.0", | ||
"unist-util-generated": "^2.0.0", | ||
@@ -50,5 +49,6 @@ "unist-util-position": "^4.0.0", | ||
"devDependencies": { | ||
"@types/tape": "^4.0.0", | ||
"@types/node": "^18.0.0", | ||
"c8": "^7.0.0", | ||
"hast-util-to-html": "^8.0.0", | ||
"hast-util-to-html": "^8.0.4", | ||
"hastscript": "^7.0.0", | ||
"mdast-util-from-markdown": "^1.0.0", | ||
@@ -60,4 +60,2 @@ "mdast-util-gfm": "^2.0.0", | ||
"remark-preset-wooorm": "^9.0.0", | ||
"rimraf": "^3.0.0", | ||
"tape": "^5.0.0", | ||
"type-coverage": "^2.0.0", | ||
@@ -69,6 +67,6 @@ "typescript": "^4.0.0", | ||
"prepack": "npm run build && npm run format", | ||
"build": "rimraf \"{lib/**/**,test/**}*.d.ts\" \"index.d.ts\" && tsc && type-coverage", | ||
"format": "remark . -qfo && prettier -w . --loglevel warn && xo --fix", | ||
"test-api": "node test/index.js", | ||
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test/index.js", | ||
"build": "tsc --build --clean && tsc --build && type-coverage", | ||
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", | ||
"test-api": "node --conditions development test/index.js", | ||
"test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api", | ||
"test": "npm run build && npm run format && npm run test-coverage" | ||
@@ -106,5 +104,5 @@ }, | ||
"ignoreFiles": [ | ||
"lib/index.d.ts" | ||
"lib/state.d.ts" | ||
] | ||
} | ||
} |
319
readme.md
@@ -20,6 +20,9 @@ # mdast-util-to-hast | ||
* [API](#api) | ||
* [`toHast(node[, options])`](#tohastnode-options) | ||
* [`toHast(tree[, options])`](#tohasttree-options) | ||
* [`defaultHandlers`](#defaulthandlers) | ||
* [`all(h, parent)`](#allh-parent) | ||
* [`one(h, node, parent)`](#oneh-node-parent) | ||
* [`Handler`](#handler) | ||
* [`Handlers`](#handlers) | ||
* [`Options`](#options) | ||
* [`Raw`](#raw) | ||
* [`State`](#state) | ||
* [Examples](#examples) | ||
@@ -31,2 +34,4 @@ * [Example: supporting HTML in markdown naïvely](#example-supporting-html-in-markdown-naïvely) | ||
* [Algorithm](#algorithm) | ||
* [Default handling](#default-handling) | ||
* [Fields on nodes](#fields-on-nodes) | ||
* [CSS](#css) | ||
@@ -62,3 +67,3 @@ * [Syntax tree](#syntax-tree) | ||
This package is [ESM only][esm]. | ||
In Node.js (version 12.20+, 14.14+, or 16.0+), install with [npm][]: | ||
In Node.js (version 14.14+ and 16.0+), install with [npm][]: | ||
@@ -72,3 +77,3 @@ ```sh | ||
```js | ||
import {toHast} from "https://esm.sh/mdast-util-to-hast@12" | ||
import {toHast} from 'https://esm.sh/mdast-util-to-hast@12' | ||
``` | ||
@@ -80,3 +85,3 @@ | ||
<script type="module"> | ||
import {toHast} from "https://esm.sh/mdast-util-to-hast@12?bundle" | ||
import {toHast} from 'https://esm.sh/mdast-util-to-hast@12?bundle' | ||
</script> | ||
@@ -96,3 +101,3 @@ ``` | ||
```js | ||
import {promises as fs} from 'node:fs' | ||
import {fs} from 'node:fs/promises' | ||
import {fromMarkdown} from 'mdast-util-from-markdown' | ||
@@ -118,18 +123,25 @@ import {toHast} from 'mdast-util-to-hast' | ||
This package exports the identifiers `toHast`, `defaultHandlers`, `all`, and | ||
`one`. | ||
This package exports the identifiers [`defaultHandlers`][api-default-handlers] | ||
and [`toHast`][api-to-hast]. | ||
There is no default export. | ||
### `toHast(node[, options])` | ||
### `toHast(tree[, options])` | ||
[mdast][] utility to transform to [hast][]. | ||
Transform mdast to hast. | ||
##### `options` | ||
###### Parameters | ||
Configuration (optional). | ||
* `tree` ([`MdastNode`][mdast-node]) | ||
— mdast tree | ||
* `options` ([`Options`][api-options], optional) | ||
— configuration | ||
###### `options.allowDangerousHtml` | ||
###### Returns | ||
Whether to persist raw HTML in markdown in the hast tree (`boolean`, default: | ||
`false`). | ||
hast tree ([`HastNode | null | undefined`][hast-node]). | ||
##### Notes | ||
###### HTML | ||
Raw HTML is available in mdast as [`html`][mdast-html] nodes and can be embedded | ||
@@ -148,6 +160,25 @@ in hast as semistandard `raw` nodes. | ||
###### `options.clobberPrefix` | ||
###### Footnotes | ||
Prefix to use before the `id` attribute on footnotes to prevent it from | ||
*clobbering* (`string`, default: `'user-content-'`). | ||
Many options supported here relate to footnotes. | ||
Footnotes are not specified by CommonMark, which we follow by default. | ||
They are supported by GitHub, so footnotes can be enabled in markdown with | ||
[`mdast-util-gfm`][mdast-util-gfm]. | ||
The options `footnoteBackLabel` and `footnoteLabel` define natural language | ||
that explains footnotes, which is hidden for sighted users but shown to | ||
assistive technology. | ||
When your page is not in English, you must define translated values. | ||
Back references use ARIA attributes, but the section label itself uses a | ||
heading that is hidden with an `sr-only` class. | ||
To show it to sighted users, define different attributes in | ||
`footnoteLabelProperties`. | ||
###### Clobbering | ||
Footnotes introduces a problem, as it links footnote calls to footnote | ||
definitions on the page through `id` attributes generated from user content, | ||
which results in DOM clobbering. | ||
DOM clobbering is this: | ||
@@ -167,116 +198,119 @@ | ||
> 👉 **Note**: this option affects footnotes. | ||
> Footnotes are not specified by CommonMark. | ||
> They are supported by GitHub, so they can be enabled by using the utility | ||
> [`mdast-util-gfm`][mdast-util-gfm]. | ||
###### Unknown nodes | ||
###### `options.footnoteLabel` | ||
Unknown nodes are nodes with a type that isn’t in `handlers` or `passThrough`. | ||
The default behavior for unknown nodes is: | ||
Label to use for the footnotes section (`string`, default: `'Footnotes'`). | ||
Affects screen readers. | ||
Change it when the markdown is not in English. | ||
* when the node has a `value` (and doesn’t have `data.hName`, | ||
`data.hProperties`, or `data.hChildren`, see later), create a hast `text` | ||
node | ||
* otherwise, create a `<div>` element (which could be changed with | ||
`data.hName`), with its children mapped from mdast to hast as well | ||
> 👉 **Note**: this option affects footnotes. | ||
> Footnotes are not specified by CommonMark. | ||
> They are supported by GitHub, so they can be enabled by using the utility | ||
> [`mdast-util-gfm`][mdast-util-gfm]. | ||
This behavior can be changed by passing an `unknownHandler`. | ||
###### `options.footnoteLabelTagName` | ||
### `defaultHandlers` | ||
HTML tag to use for the footnote label (`string`, default: `h2`). | ||
Can be changed to match your document structure and play well with your CSS. | ||
Default handlers for nodes ([`Handlers`][api-handlers]). | ||
> 👉 **Note**: this option affects footnotes. | ||
> Footnotes are not specified by CommonMark. | ||
> They are supported by GitHub, so they can be enabled by using the utility | ||
> [`mdast-util-gfm`][mdast-util-gfm]. | ||
### `Handler` | ||
###### `options.footnoteLabelProperties` | ||
Handle a node (TypeScript). | ||
Properties to use on the footnote label (`object`, default: | ||
`{className: ['sr-only']}`). | ||
Importantly, `id: 'footnote-label'` is always added, because footnote calls use | ||
it with `aria-describedby` to provide an accessible label. | ||
A `sr-only` class is added by default to hide this from sighted users. | ||
Change it to make the label visible, or add classes for other purposes. | ||
###### Parameters | ||
> 👉 **Note**: this option affects footnotes. | ||
> Footnotes are not specified by CommonMark. | ||
> They are supported by GitHub, so they can be enabled by using the utility | ||
> [`mdast-util-gfm`][mdast-util-gfm]. | ||
* `state` ([`State`][api-state]) | ||
— info passed around | ||
* `node` ([`MdastNode`][mdast-node]) | ||
— node to handle | ||
* `parent` ([`MdastNode | null | undefined`][mdast-node]) | ||
— parent of `node` | ||
###### `options.footnoteBackLabel` | ||
###### Returns | ||
Label to use from backreferences back to their footnote call (`string`, default: | ||
`'Back to content'`). | ||
Affects screen readers. | ||
Change it when the markdown is not in English. | ||
Result ([`HastNode | Array<HastNode> | null | undefined`][mdast-node]). | ||
> 👉 **Note**: this option affects footnotes. | ||
> Footnotes are not specified by CommonMark. | ||
> They are supported by GitHub, so they can be enabled by using the utility | ||
> [`mdast-util-gfm`][mdast-util-gfm]. | ||
### `Handlers` | ||
###### `options.handlers` | ||
Handle nodes (TypeScript). | ||
Object mapping node types to functions handling the corresponding nodes. | ||
See [`lib/handlers/`][handlers] for examples. | ||
###### Type | ||
In a handler, you have access to `h`, which should be used to create hast nodes | ||
from mdast nodes. | ||
On `h`, there are several fields that may be of interest. | ||
```ts | ||
type Handlers = Record<string, Handler> | ||
``` | ||
###### `options.passThrough` | ||
### `Options` | ||
List of mdast node types to pass through (keep) in hast (`Array<string>`, | ||
default: `[]`). | ||
If the passed through nodes have children, those children are expected to be | ||
mdast and will be handled. | ||
Configuration (TypeScript). | ||
Similar functionality can be achieved with a custom handler. | ||
A `passThrough` of `['customNode']` is equivalent to: | ||
###### Fields | ||
```js | ||
toHast(/* … */, { | ||
handlers: { | ||
customNode(h, node) { | ||
return 'children' in node ? {...node, children: all(h, node)} : node | ||
} | ||
} | ||
}) | ||
``` | ||
* `allowDangerousHtml` (`boolean`, default: `false`) | ||
— whether to persist raw HTML in markdown in the hast tree | ||
* `clobberPrefix` (`string`, default: `'user-content-'`) | ||
— prefix to use before the `id` attribute on footnotes to prevent it from | ||
*clobbering* | ||
* `footnoteBackLabel` (`string`, default: `'Back to content'`) | ||
— label to use from backreferences back to their footnote call (affects | ||
screen readers) | ||
* `footnoteLabel` (`string`, default: `'Footnotes'`) | ||
— label to use for the footnotes section (affects screen readers) | ||
* `footnoteLabelProperties` | ||
([`Properties`][properties], default: `{className: ['sr-only']}`) | ||
— properties to use on the footnote label | ||
(note that `id: 'footnote-label'` is always added as footnote calls use it | ||
with `aria-describedby` to provide an accessible label) | ||
* `footnoteLabelTagName` (`string`, default: `h2`) | ||
— tag name to use for the footnote label | ||
* `handlers` ([`Handlers`][api-handlers], optional) | ||
— extra handlers for nodes | ||
* `passThrough` (`Array<string>`, optional) | ||
— list of custom mdast node types to pass through (keep) in hast (note that | ||
the node itself is passed, but eventual children are transformed) | ||
* `unknownHandler` ([`Handler`][api-handler], optional) | ||
— handle all unknown nodes | ||
###### `options.unknownHandler` | ||
### `Raw` | ||
Handler for unknown nodes (`Handler?`). | ||
Unknown nodes are nodes with a type that isn’t in `handlers` or `passThrough`. | ||
The default behavior for unknown nodes is: | ||
Raw string of HTML embedded into HTML AST (TypeScript). | ||
* when the node has a `value` (and doesn’t have `data.hName`, | ||
`data.hProperties`, or `data.hChildren`, see later), create a hast `text` | ||
node | ||
* otherwise, create a `<div>` element (which could be changed with | ||
`data.hName`), with its children mapped from mdast to hast as well | ||
###### Type | ||
##### Returns | ||
```ts | ||
import type {Literal} from 'hast' | ||
[`HastNode`][hast-node]. | ||
interface Raw extends Literal { | ||
type: 'raw' | ||
} | ||
``` | ||
### `defaultHandlers` | ||
### `State` | ||
Object mapping mdast node types to functions that can handle them. | ||
See [`lib/handlers/index.js`][default-handlers]. | ||
Info passed around about the current state (TypeScript type). | ||
### `all(h, parent)` | ||
###### Fields | ||
Helper function for writing custom handlers passed to `options.handlers`. | ||
Pass it `h` and a parent node (mdast) and it will turn the node’s children into | ||
an array of transformed nodes (hast). | ||
<!-- To do: add `options`, alternative to `definition`. --> | ||
### `one(h, node, parent)` | ||
* `patch` (`(from: MdastNode, to: HastNode) => void`) | ||
— copy a node’s positional info | ||
* `applyData` (`<Type extends HastNode>(from: MdastNode, to: Type) => Type | HastElement`) | ||
— honor the `data` of `from` and maybe generate an element instead of `to` | ||
* `one` (`(node: MdastNode, parent: MdastNode | undefined) => HastNode | Array<HastNode> | undefined`) | ||
— transform an mdast node to hast | ||
* `all` (`(node: MdastNode) => Array<HastNode>`) | ||
— transform the children of an mdast parent to hast | ||
* `wrap` (`<Type extends HastNode>(nodes: Array<Type>, loose?: boolean) => Array<Type | HastText>`) | ||
— wrap `nodes` with line endings between each node, adds initial/final line | ||
endings when `loose` | ||
* `handlers` ([`Handlers`][api-handlers]) | ||
— applied node handlers | ||
* `footnoteById` (`Record<string, MdastFootnoteDefinition>`) | ||
— footnote definitions by their uppercased identifier | ||
* `footnoteOrder` (`Array<string>`) | ||
— identifiers of order when footnote calls first appear in tree order | ||
* `footnoteCounts` (`Record<string, number>`) | ||
— counts for how often the same footnote was called | ||
Helper function for writing custom handlers passed to `options.handlers`. | ||
Pass it `h`, a `node`, and its `parent` (mdast) and it will turn `node` into | ||
hast content. | ||
## Examples | ||
@@ -435,3 +469,3 @@ | ||
```js | ||
import {toHast, all} from 'mdast-util-to-hast' | ||
import {toHast} from 'mdast-util-to-hast' | ||
import {toHtml} from 'hast-util-to-html' | ||
@@ -446,4 +480,9 @@ | ||
handlers: { | ||
mark(h, node) { | ||
return h(node, 'mark', all(h, node)) | ||
mark(state, node) { | ||
return { | ||
type: 'element', | ||
tagName: 'mark', | ||
properties: {}, | ||
children: state.all(node) | ||
} | ||
} | ||
@@ -485,3 +524,3 @@ } | ||
##### Default handling | ||
### Default handling | ||
@@ -1123,3 +1162,3 @@ The following table gives insight into what input turns into what output: | ||
##### Fields on nodes | ||
### Fields on nodes | ||
@@ -1141,5 +1180,5 @@ A frequent problem arises when having to turn one syntax tree into another. | ||
* `node.data.hName` configures the element’s tag name | ||
* `node.data.hProperties` is mixed into the element’s properties | ||
* `node.data.hChildren` configures the element’s children | ||
* `node.data.hName` — define the element’s tag name | ||
* `node.data.hProperties` — define extra properties to use | ||
* `node.data.hChildren` — define hast children to use | ||
@@ -1180,3 +1219,2 @@ ###### `hName` | ||
alt: 'Big red circle on a black background', | ||
title: null, | ||
data: {hProperties: {className: ['responsive']}} | ||
@@ -1255,3 +1293,3 @@ } | ||
Assuming you know how to use (semantic) HTML and CSS, then it should generally | ||
be straight forward to style the HTML produced by this plugin. | ||
be straightforward to style the HTML produced by this plugin. | ||
With CSS, you can get creative and style the results as you please. | ||
@@ -1309,3 +1347,3 @@ | ||
interface Raw <: Literal { | ||
type: "raw" | ||
type: 'raw' | ||
} | ||
@@ -1322,10 +1360,15 @@ ``` | ||
This package is fully typed with [TypeScript][]. | ||
It also exports `Options`, `Handler`, `Handlers`, `H`, and `Raw` types. | ||
It also exports [`Handler`][api-handler], [`Handlers`][api-handlers], | ||
[`Options`][api-options], [`Raw`][api-raw], and [`State`][api-state] types. | ||
If you’re working with raw nodes in the hast syntax tree (which are added when | ||
It also registers the `Raw` node type with `@types/mdast`. | ||
If you’re working with the syntax tree (and you pass | ||
`allowDangerousHtml: true`), make sure to import this utility somewhere in your | ||
types, as that registers the new node types in the tree. | ||
types, as that registers the new node type in the tree. | ||
```js | ||
/** @typedef {import('mdast-util-to-hast')} */ | ||
/** | ||
* @typedef {import('mdast-util-to-hast')} | ||
*/ | ||
import {visit} from 'unist-util-visit' | ||
@@ -1345,3 +1388,3 @@ | ||
versions of Node.js. | ||
As of now, that is Node.js 12.20+, 14.14+, and 16.0+. | ||
As of now, that is Node.js 14.14+ and 16.0+. | ||
Our projects sometimes work with older versions, but this is not guaranteed. | ||
@@ -1481,13 +1524,15 @@ | ||
[contributing]: https://github.com/syntax-tree/.github/blob/HEAD/contributing.md | ||
[contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md | ||
[support]: https://github.com/syntax-tree/.github/blob/HEAD/support.md | ||
[support]: https://github.com/syntax-tree/.github/blob/main/support.md | ||
[coc]: https://github.com/syntax-tree/.github/blob/HEAD/code-of-conduct.md | ||
[coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md | ||
[mdast]: https://github.com/syntax-tree/mdast | ||
[mdast-node]: https://github.com/syntax-tree/mdast#nodes | ||
[mdast-html]: https://github.com/syntax-tree/mdast#html | ||
[hast-util-table-cell-style]: https://github.com/mapbox/hast-util-table-cell-style | ||
[mdast-util-gfm]: https://github.com/syntax-tree/mdast-util-gfm | ||
@@ -1498,10 +1543,6 @@ [hast]: https://github.com/syntax-tree/hast | ||
[remark-rehype]: https://github.com/remarkjs/remark-rehype | ||
[properties]: https://github.com/syntax-tree/hast#properties | ||
[handlers]: lib/handlers | ||
[hast-util-table-cell-style]: https://github.com/mapbox/hast-util-table-cell-style | ||
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting | ||
[mdast-util-gfm]: https://github.com/syntax-tree/mdast-util-gfm | ||
[hast-util-to-mdast]: https://github.com/syntax-tree/hast-util-to-mdast | ||
@@ -1515,8 +1556,24 @@ | ||
[remark-rehype]: https://github.com/remarkjs/remark-rehype | ||
[clobber-example]: https://github.com/rehypejs/rehype-sanitize#example-headings-dom-clobbering | ||
[default-handlers]: lib/handlers/index.js | ||
[github-markdown-css]: https://github.com/sindresorhus/github-markdown-css | ||
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting | ||
[dfn-literal]: https://github.com/syntax-tree/hast#literal | ||
[api-default-handlers]: #defaulthandlers | ||
[api-to-hast]: #tohasttree-options | ||
[api-handler]: #handler | ||
[api-handlers]: #handlers | ||
[api-options]: #options | ||
[api-raw]: #raw | ||
[api-state]: #state |
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
121688
8
13
66
2832
1558
1
- Removedunist-builder@^3.0.0
- Removedunist-builder@3.0.1(transitive)