Skip to content

Commit

Permalink
Change to remove function form of State, use plain object
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Jul 16, 2023
1 parent 127a488 commit b328aa9
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 159 deletions.
22 changes: 16 additions & 6 deletions lib/footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ import {normalizeUri} from 'micromark-util-sanitize-uri'
* `section` element or `undefined`.
*/
export function footer(state) {
const clobberPrefix =
typeof state.options.clobberPrefix === 'string'
? state.options.clobberPrefix
: 'user-content-'
const footnoteBackLabel = state.options.footnoteBackLabel || 'Back to content'
const footnoteLabel = state.options.footnoteLabel || 'Footnotes'
const footnoteLabelTagName = state.options.footnoteLabelTagName || 'h2'
const footnoteLabelProperties = state.options.footnoteLabelProperties || {
className: ['sr-only']
}
/** @type {Array<ElementContent>} */
const listItems = []
let index = -1
Expand All @@ -43,13 +53,13 @@ export function footer(state) {
properties: {
href:
'#' +
state.clobberPrefix +
clobberPrefix +
'fnref-' +
safeId +
(referenceIndex > 1 ? '-' + referenceIndex : ''),
dataFootnoteBackref: true,
className: ['data-footnote-backref'],
ariaLabel: state.footnoteBackLabel
ariaLabel: footnoteBackLabel
},
children: [{type: 'text', value: '↩'}]
}
Expand Down Expand Up @@ -89,7 +99,7 @@ export function footer(state) {
const listItem = {
type: 'element',
tagName: 'li',
properties: {id: state.clobberPrefix + 'fn-' + safeId},
properties: {id: clobberPrefix + 'fn-' + safeId},
children: state.wrap(content, true)
}

Expand All @@ -109,12 +119,12 @@ export function footer(state) {
children: [
{
type: 'element',
tagName: state.footnoteLabelTagName,
tagName: footnoteLabelTagName,
properties: {
...structuredClone(state.footnoteLabelProperties),
...structuredClone(footnoteLabelProperties),
id: 'footnote-label'
},
children: [{type: 'text', value: state.footnoteLabel}]
children: [{type: 'text', value: footnoteLabel}]
},
{type: 'text', value: '\n'},
{
Expand Down
8 changes: 6 additions & 2 deletions lib/handlers/footnote-reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import {normalizeUri} from 'micromark-util-sanitize-uri'
* hast node.
*/
export function footnoteReference(state, node) {
const clobberPrefix =
typeof state.options.clobberPrefix === 'string'
? state.options.clobberPrefix
: 'user-content-'
const id = String(node.identifier).toUpperCase()
const safeId = normalizeUri(id.toLowerCase())
const index = state.footnoteOrder.indexOf(id)
Expand All @@ -39,9 +43,9 @@ export function footnoteReference(state, node) {
type: 'element',
tagName: 'a',
properties: {
href: '#' + state.clobberPrefix + 'fn-' + safeId,
href: '#' + clobberPrefix + 'fn-' + safeId,
id:
state.clobberPrefix +
clobberPrefix +
'fnref-' +
safeId +
(reuseCounter > 1 ? '-' + reuseCounter : ''),
Expand Down
2 changes: 1 addition & 1 deletion lib/handlers/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* hast node.
*/
export function html(state, node) {
if (state.dangerous) {
if (state.options.allowDangerousHtml) {
/** @type {Raw} */
const result = {type: 'raw', value: node.value}
state.patch(node, result)
Expand Down
181 changes: 45 additions & 136 deletions lib/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,71 +61,33 @@
* @returns {Array<HastElementContent> | HastElementContent | null | undefined}
* hast node.
*
* @callback HFunctionProps
* Signature of `state` for when props are passed.
* @param {MdastNodes | PositionLike | null | undefined} node
* mdast node or unist position.
* @param {string} tagName
* HTML tag name.
* @param {HastProperties} props
* Properties.
* @param {Array<HastElementContent> | null | undefined} [children]
* hast content.
* @returns {HastElement}
* Compiled element.
*
* @callback HFunctionNoProps
* Signature of `state` for when no props are passed.
* @param {MdastNodes | PositionLike | null | undefined} node
* mdast node or unist position.
* @param {string} tagName
* HTML tag name.
* @param {Array<HastElementContent> | null | undefined} [children]
* hast content.
* @returns {HastElement}
* Compiled element.
*
* @typedef HFields
* Info on `state`.
* @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 {HastProperties} footnoteLabelProperties
* Properties on the HTML tag used for the footnote label.
* @property {string} footnoteBackLabel
* Label to use from backreferences back to their footnote call.
* @property {(identifier: string) => MdastDefinition | undefined} definition
* Definition cache.
* @typedef State
* Info passed around.
* @property {(node: MdastNodes) => Array<HastElementContent>} all
* Transform the children of an mdast parent to hast.
* @property {<Type extends HastNodes>(from: MdastNodes, to: Type) => HastElement | Type} applyData
* Honor the `data` of `from`, and generate an element instead of `node`.
* @property {Record<string, MdastFootnoteDefinition>} footnoteById
* Footnote definitions by their identifier.
* @property {Array<string>} footnoteOrder
* Identifiers of order when footnote calls first appear in tree order.
* @property {Record<string, number>} footnoteCounts
* Counts for how often the same footnote was called.
* @property {Array<string>} footnoteOrder
* Identifiers of order when footnote calls first appear in tree order.
* @property {Handlers} handlers
* Applied handlers.
* @property {Handler} unknownHandler
* Handler for any none not in `passThrough` or otherwise handled.
* @property {(from: MdastNodes, node: HastNodes) => void} patch
* Copy a node’s positional info.
* @property {<Type extends HastNodes>(from: MdastNodes, to: Type) => HastElement | Type} applyData
* Honor the `data` of `from`, and generate an element instead of `node`.
* @property {(node: MdastNodes, parent: MdastParents | null | undefined) => Array<HastElementContent> | HastElementContent | null | undefined} one
* Transform an mdast node to hast.
* @property {(node: MdastNodes) => Array<HastElementContent>} all
* Transform the children of an mdast parent to hast.
* @property {Options} options
* Configuration.
* @property {(from: MdastNodes, node: HastNodes) => void} patch
* Copy a node’s positional info.
* @property {<Type extends HastRootContent>(nodes: Array<Type>, loose?: boolean | null | undefined) => Array<HastText | Type>} wrap
* Wrap `nodes` with line endings between each node, adds initial/final line endings when `loose`.
* @property {(left: MdastNodeWithData | PositionLike | null | undefined, right: HastElementContent) => HastElementContent} augment
* Like `state` but lower-level and usable on non-elements.
* Deprecated: use `patch` and `applyData`.
* @property {Array<string>} passThrough
* List of node types to pass through untouched (except for their children).
*
* @property {(identifier: string) => MdastDefinition | undefined} definition
* Definition cache.
*
* To do: expose map, document.
*
* @typedef Options
* Configuration (optional).
Expand Down Expand Up @@ -158,9 +120,6 @@
*
* @typedef {Record<string, Handler>} Handlers
* Handle nodes.
*
* @typedef {HFields & HFunctionNoProps & HFunctionProps} State
* Info passed around.
*/

import {visit} from 'unist-util-visit'
Expand All @@ -182,55 +141,34 @@ const own = {}.hasOwnProperty
*/
export function createState(tree, options) {
const settings = options || {}
const dangerous = settings.allowDangerousHtml || false
/** @type {Record<string, MdastFootnoteDefinition>} */
const footnoteById = {}

// To do: next major: add `options` to state, remove:
// `dangerous`, `clobberPrefix`, `footnoteLabel`, `footnoteLabelTagName`,
// `footnoteLabelProperties`, `footnoteBackLabel`, `passThrough`,
// `unknownHandler`.

// To do: next major: move to `state.options.allowDangerousHtml`.
state.dangerous = dangerous
// To do: next major: move to `state.options`.
state.clobberPrefix =
settings.clobberPrefix === null || settings.clobberPrefix === undefined
? 'user-content-'
: settings.clobberPrefix
// To do: next major: move to `state.options`.
state.footnoteLabel = settings.footnoteLabel || 'Footnotes'
// To do: next major: move to `state.options`.
state.footnoteLabelTagName = settings.footnoteLabelTagName || 'h2'
// To do: next major: move to `state.options`.
state.footnoteLabelProperties = settings.footnoteLabelProperties || {
className: ['sr-only']
/** @type {State} */
const state = {
options: settings,
// @ts-expect-error: fix `null` handling?
handlers: {...handlers, ...settings.handlers},

// To do: next major: replace utility with `definitionById` object, so we
// only walk once (as we need footnotes too).
definition: definitions(tree),
footnoteById,
/** @type {Array<string>} */
footnoteOrder: [],
/** @type {Record<string, number>} */
footnoteCounts: {},

patch,
applyData,
// @ts-expect-error: fix `null` handling.
one: oneBound,
all: allBound,
// @ts-expect-error: fix `null` handling.
wrap,
// To do: next major: remove `augment`.
augment
}
// To do: next major: move to `state.options`.
state.footnoteBackLabel = settings.footnoteBackLabel || 'Back to content'
// To do: next major: move to `state.options`.
state.unknownHandler = settings.unknownHandler
// To do: next major: move to `state.options`.
state.passThrough = settings.passThrough

state.handlers = {...handlers, ...settings.handlers}

// To do: next major: replace utility with `definitionById` object, so we
// only walk once (as we need footnotes too).
state.definition = definitions(tree)
state.footnoteById = footnoteById
/** @type {Array<string>} */
state.footnoteOrder = []
/** @type {Record<string, number>} */
state.footnoteCounts = {}

state.patch = patch
state.applyData = applyData
state.one = oneBound
state.all = allBound
state.wrap = wrap
// To do: next major: remove `augment`.
state.augment = augment

visit(tree, 'footnoteDefinition', function (definition) {
const id = String(definition.identifier).toUpperCase()
Expand All @@ -242,8 +180,6 @@ export function createState(tree, options) {
}
})

// @ts-expect-error Hush, it’s fine!
// To do: a cleaned state should allow this?
return state

/**
Expand Down Expand Up @@ -299,29 +235,6 @@ export function createState(tree, options) {
}
/* c8 ignore stop */

/**
* Create an element for `node`.
*
* @type {HFunctionProps}
*/
/* c8 ignore start */
// To do: next major: remove.
function state(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 || []
})
}
/* c8 ignore stop */

/**
* Transform an mdast node into a hast node.
*
Expand All @@ -333,7 +246,6 @@ export function createState(tree, options) {
* Resulting hast node.
*/
function oneBound(node, parent) {
// @ts-expect-error: that’s a state :)
return one(state, node, parent)
}

Expand All @@ -346,7 +258,6 @@ export function createState(tree, options) {
* Resulting hast nodes.
*/
function allBound(parent) {
// @ts-expect-error: that’s a state :)
return all(state, parent)
}
}
Expand Down Expand Up @@ -449,23 +360,21 @@ function applyData(from, to) {
*/
// To do: next major: do not expose, keep bound.
export function one(state, node, parent) {
const type = node && node.type
const type = node.type

if (own.call(state.handlers, type)) {
return state.handlers[type](state, node, parent)
}

if (state.passThrough && state.passThrough.includes(type)) {
if (state.options.passThrough && state.options.passThrough.includes(type)) {
// To do: next major: deep clone.
// @ts-expect-error: types of passed through nodes are expected to be added manually.
return 'children' in node ? {...node, children: all(state, node)} : node
}

if (state.unknownHandler) {
return state.unknownHandler(state, node, parent)
}
const unknown = state.options.unknownHandler || defaultUnknownHandler

return defaultUnknownHandler(state, node)
return unknown(state, node, parent)
}

/**
Expand Down
28 changes: 14 additions & 14 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,27 +282,27 @@ Info passed around about the current state (TypeScript type).

###### Fields

<!-- To do: add `options`, alternative to `definition`. -->

* `patch` (`(from: MdastNode, to: HastNode) => undefined`)
— copy a node’s positional info
* `all` (`(node: MdastNode) => Array<HastNode>`)
— transform the children of an mdast parent to hast
* `applyData` (`<Type extends HastNode>(from: MdastNode, to: Type) => Type | HastElement`)
— honor the `data` of `from` and maybe generate an element instead of `to`
* `footnoteById` (`Record<string, MdastFootnoteDefinition>`)
— footnote definitions by their uppercased identifier
* `footnoteCounts` (`Record<string, number>`)
— counts for how often the same footnote was called
* `footnoteOrder` (`Array<string>`)
— identifiers of order when footnote calls first appear in tree order
* `handlers` ([`Handlers`][api-handlers])
— applied node handlers
* `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
* `options` ([`Options`][api-options])
— configuration
* `patch` (`(from: MdastNode, to: HastNode) => undefined`)
— copy a node’s positional info
* `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

## Examples

Expand Down

0 comments on commit b328aa9

Please sign in to comment.