From 56c88e45690be138fad9f0bf367b939d09816863 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 17 Jul 2023 12:25:25 +0200 Subject: [PATCH] Fix to match GH for HTML generated for backreferences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GH previously generated HTML for backreferences to repeated references that was not accessible, as it failed [WCAG 2.1 SC 2.4.4 — Link Purpose (In Context)](https://www.w3.org/TR/WCAG21/#link-purpose-in-context) * GH changed the text content they use in their backreferences from `Back to content` to `Back to reference i`, where `i` is either `x` or `x-y`, of which `x` is the reference index, and `y` the rereference index This commit changes all HTML output for users that relied on the defaults, so that it matches GH again, exactly. The default handling is exposed as `defaultFootnoteBackLabel`. Users who set `footnoteBackLabel` are not affected. But these users can now provide a function instead of a `string`, to also solve the WCAG issue. The type for this function is exposed as `FootnoteBackLabelTemplate`. Additionally, you can now pass `footnoteBackContent` to set the *content* of the backreference. Related-to: github/cmark-gfm#307. Closes remarkjs/remark-rehype#32. --- index.d.ts | 8 ++ index.js | 4 + lib/footer.js | 167 +++++++++++++++++++++++++++++------- lib/state.js | 155 ++++++++++++++++++++++++++------- readme.md | 164 ++++++++++++++++++++++++++++++++--- test/core.js | 2 + test/footnote-definition.js | 12 +-- test/footnote.js | 130 +++++++++++++++++++++++++--- 8 files changed, 548 insertions(+), 94 deletions(-) diff --git a/index.d.ts b/index.d.ts index 0e0afab..7fc5df3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,10 +1,18 @@ import type {Data, ElementContent, Literal, Properties} from 'hast' // Expose types. +export type { + FootnoteBackContentTemplate, + FootnoteBackLabelTemplate +} from './lib/footer.js' export type {Handler, Handlers, Options, State} from './lib/state.js' // Expose JS API. export {handlers as defaultHandlers} from './lib/handlers/index.js' +export { + defaultFootnoteBackContent, + defaultFootnoteBackLabel +} from './lib/footer.js' export {toHast} from './lib/index.js' /** diff --git a/index.js b/index.js index ee692b5..5e88898 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,7 @@ // Note: types exposed from `index.d.ts`. export {handlers as defaultHandlers} from './lib/handlers/index.js' export {toHast} from './lib/index.js' +export { + defaultFootnoteBackContent, + defaultFootnoteBackLabel +} from './lib/footer.js' diff --git a/lib/footer.js b/lib/footer.js index 35997fb..5af1ef0 100644 --- a/lib/footer.js +++ b/lib/footer.js @@ -5,9 +5,114 @@ * @typedef {import('./state.js').State} State */ +/** + * @callback FootnoteBackContentTemplate + * Generate content for the backreference dynamically. + * + * For the following markdown: + * + * ```markdown + * Alpha[^micromark], bravo[^micromark], and charlie[^remark]. + * + * [^remark]: things about remark + * [^micromark]: things about micromark + * ``` + * + * This function will be called with: + * + * * `0` and `0` for the backreference from `things about micromark` to + * `alpha`, as it is the first used definition, and the first call to it + * * `0` and `1` for the backreference from `things about micromark` to + * `bravo`, as it is the first used definition, and the second call to it + * * `1` and `0` for the backreference from `things about remark` to + * `charlie`, as it is the second used definition + * @param {number} referenceIndex + * Index of the definition in the order that they are first referenced, + * 0-indexed. + * @param {number} rereferenceIndex + * Index of calls to the same definition, 0-indexed. + * @returns {Array | ElementContent | string} + * Content for the backreference when linking back from definitions to their + * reference. + * + * @callback FootnoteBackLabelTemplate + * Generate a back label dynamically. + * + * For the following markdown: + * + * ```markdown + * Alpha[^micromark], bravo[^micromark], and charlie[^remark]. + * + * [^remark]: things about remark + * [^micromark]: things about micromark + * ``` + * + * This function will be called with: + * + * * `0` and `0` for the backreference from `things about micromark` to + * `alpha`, as it is the first used definition, and the first call to it + * * `0` and `1` for the backreference from `things about micromark` to + * `bravo`, as it is the first used definition, and the second call to it + * * `1` and `0` for the backreference from `things about remark` to + * `charlie`, as it is the second used definition + * @param {number} referenceIndex + * Index of the definition in the order that they are first referenced, + * 0-indexed. + * @param {number} rereferenceIndex + * Index of calls to the same definition, 0-indexed. + * @returns {string} + * Back label to use when linking back from definitions to their reference. + */ + import structuredClone from '@ungap/structured-clone' import {normalizeUri} from 'micromark-util-sanitize-uri' +/** + * Generate the default content that GitHub uses on backreferences. + * + * @param {number} _ + * Index of the definition in the order that they are first referenced, + * 0-indexed. + * @param {number} rereferenceIndex + * Index of calls to the same definition, 0-indexed. + * @returns {Array} + * Content. + */ +export function defaultFootnoteBackContent(_, rereferenceIndex) { + /** @type {Array} */ + const result = [{type: 'text', value: '↩'}] + + if (rereferenceIndex > 1) { + result.push({ + type: 'element', + tagName: 'sup', + properties: {}, + children: [{type: 'text', value: String(rereferenceIndex)}] + }) + } + + return result +} + +/** + * Generate the default label that GitHub uses on backreferences. + * + * @param {number} referenceIndex + * Index of the definition in the order that they are first referenced, + * 0-indexed. + * @param {number} rereferenceIndex + * Index of calls to the same definition, 0-indexed. + * @returns {string} + * Label. + */ +export function defaultFootnoteBackLabel(referenceIndex, rereferenceIndex) { + return ( + 'Back to reference ' + + (referenceIndex + 1) + + (rereferenceIndex > 1 ? '-' + rereferenceIndex : '') + ) +} + /** * Generate a hast footer for called footnote definitions. * @@ -16,12 +121,16 @@ import {normalizeUri} from 'micromark-util-sanitize-uri' * @returns {Element | undefined} * `section` element or `undefined`. */ +// eslint-disable-next-line complexity 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 footnoteBackContent = + state.options.footnoteBackContent || defaultFootnoteBackContent + const footnoteBackLabel = + state.options.footnoteBackLabel || defaultFootnoteBackLabel const footnoteLabel = state.options.footnoteLabel || 'Footnotes' const footnoteLabelTagName = state.options.footnoteLabelTagName || 'h2' const footnoteLabelProperties = state.options.footnoteLabelProperties || { @@ -29,10 +138,10 @@ export function footer(state) { } /** @type {Array} */ const listItems = [] - let index = -1 + let referenceIndex = -1 - while (++index < state.footnoteOrder.length) { - const def = state.footnoteById.get(state.footnoteOrder[index]) + while (++referenceIndex < state.footnoteOrder.length) { + const def = state.footnoteById.get(state.footnoteOrder[referenceIndex]) if (!def) { continue @@ -41,15 +150,27 @@ export function footer(state) { const content = state.all(def) const id = String(def.identifier).toUpperCase() const safeId = normalizeUri(id.toLowerCase()) - let referenceIndex = 0 + let rereferenceIndex = 0 /** @type {Array} */ const backReferences = [] const counts = state.footnoteCounts.get(id) // eslint-disable-next-line no-unmodified-loop-condition - while (counts !== undefined && ++referenceIndex <= counts) { - /** @type {Element} */ - const backReference = { + while (counts !== undefined && ++rereferenceIndex <= counts) { + if (backReferences.length > 0) { + backReferences.push({type: 'text', value: ' '}) + } + + let children = + typeof footnoteBackContent === 'string' + ? footnoteBackContent + : footnoteBackContent(referenceIndex, rereferenceIndex) + + if (typeof children === 'string') { + children = {type: 'text', value: children} + } + + backReferences.push({ type: 'element', tagName: 'a', properties: { @@ -58,28 +179,16 @@ export function footer(state) { clobberPrefix + 'fnref-' + safeId + - (referenceIndex > 1 ? '-' + referenceIndex : ''), - dataFootnoteBackref: true, - className: ['data-footnote-backref'], - ariaLabel: footnoteBackLabel + (rereferenceIndex > 1 ? '-' + rereferenceIndex : ''), + dataFootnoteBackref: '', + ariaLabel: + typeof footnoteBackLabel === 'string' + ? footnoteBackLabel + : footnoteBackLabel(referenceIndex, rereferenceIndex), + className: ['data-footnote-backref'] }, - children: [{type: 'text', value: '↩'}] - } - - if (referenceIndex > 1) { - backReference.children.push({ - type: 'element', - tagName: 'sup', - properties: {}, - children: [{type: 'text', value: String(referenceIndex)}] - }) - } - - if (backReferences.length > 0) { - backReferences.push({type: 'text', value: ' '}) - } - - backReferences.push(backReference) + children: Array.isArray(children) ? children : [children] + }) } const tail = content[content.length - 1] diff --git a/lib/state.js b/lib/state.js index e911f04..bce9ea2 100644 --- a/lib/state.js +++ b/lib/state.js @@ -10,6 +10,9 @@ * @typedef {import('mdast').FootnoteDefinition} MdastFootnoteDefinition * @typedef {import('mdast').Nodes} MdastNodes * @typedef {import('mdast').Parents} MdastParents + * + * @typedef {import('./footer.js').FootnoteBackContentTemplate} FootnoteBackContentTemplate + * @typedef {import('./footer.js').FootnoteBackLabelTemplate} FootnoteBackLabelTemplate */ /** @@ -24,6 +27,121 @@ * @returns {Array | HastElementContent | undefined} * hast node. * + * @typedef {Record} Handlers + * Handle nodes. + * + * @typedef Options + * Configuration (optional). + * @property {boolean | null | undefined} [allowDangerousHtml=false] + * Whether to persist raw HTML in markdown in the hast tree (default: + * `false`). + * @property {string | null | undefined} [clobberPrefix='user-content-'] + * Prefix to use before the `id` property on footnotes to prevent them from + * *clobbering* (default: `'user-content-'`). + * + * Pass `''` for trusted markdown and when you are careful with + * polyfilling. + * You could pass a different prefix. + * + * DOM clobbering is this: + * + * ```html + *

+ * + * ``` + * + * The above example shows that elements are made available by browsers, by + * their ID, on the `window` object. + * This is a security risk because you might be expecting some other variable + * at that place. + * It can also break polyfills. + * Using a prefix solves these problems. + * @property {FootnoteBackContentTemplate | string | null | undefined} [footnoteBackContent] + * Content of the backreference back to references (default: `defaultFootnoteBackContent`). + * + * The default value is: + * + * ```js + * function defaultFootnoteBackContent(_, rereferenceIndex) { + * const result = [{type: 'text', value: '↩'}] + * + * if (rereferenceIndex > 1) { + * result.push({ + * type: 'element', + * tagName: 'sup', + * properties: {}, + * children: [{type: 'text', value: String(rereferenceIndex)}] + * }) + * } + * + * return result + * } + * ``` + * + * This content is used in the `a` element of each backreference (the `↩` + * links). + * @property {FootnoteBackLabelTemplate | string | null | undefined} [footnoteBackLabel] + * Label to describe the backreference back to references (default: + * `defaultFootnoteBackLabel`). + * + * The default value is: + * + * ```js + * function defaultFootnoteBackLabel(referenceIndex, rereferenceIndex) { + * return ( + * 'Back to reference ' + + * (referenceIndex + 1) + + * (rereferenceIndex > 1 ? '-' + rereferenceIndex : '') + * ) + * } + * ``` + * + * Change it when the markdown is not in English. + * + * This label is used in the `ariaLabel` property on each backreference + * (the `↩` links). + * It affects users of assistive technology. + * @property {string | null | undefined} [footnoteLabel='Footnotes'] + * Textual label to use for the footnotes section (default: `'Footnotes'`). + * + * Change it when the markdown is not in English. + * + * This label is typically hidden visually (assuming a `sr-only` CSS class + * is defined that does that) and so affects screen readers only. + * If you do have such a class, but want to show this section to everyone, + * pass different properties with the `footnoteLabelProperties` option. + * @property {HastProperties | null | undefined} [footnoteLabelProperties={className: ['sr-only']}] + * Properties to use on the footnote label (default: `{className: + * ['sr-only']}`). + * + * Change it to show the label and add other properties. + * + * This label is typically hidden visually (assuming an `sr-only` CSS class + * is defined that does that) and so affects screen readers only. + * If you do have such a class, but want to show this section to everyone, + * pass an empty string. + * You can also add different properties. + * + * > 👉 **Note**: `id: 'footnote-label'` is always added, because footnote + * > calls use it with `aria-describedby` to provide an accessible label. + * @property {string | null | undefined} [footnoteLabelTagName='h2'] + * HTML tag name to use for the footnote label element (default: `'h2'`). + * + * Change it to match your document structure. + * + * This label is typically hidden visually (assuming a `sr-only` CSS class + * is defined that does that) and so affects screen readers only. + * If you do have such a class, but want to show this section to everyone, + * pass different properties with the `footnoteLabelProperties` option. + * @property {Handlers | null | undefined} [handlers] + * Extra handlers for nodes (optional). + * @property {Array | null | undefined} [passThrough] + * List of custom mdast node types to pass through (keep) in hast (note that + * the node itself is passed, but eventual children are transformed) + * (optional). + * @property {Handler | null | undefined} [unknownHandler] + * Handler for all unknown nodes (optional). + * * @typedef State * Info passed around. * @property {(node: MdastNodes) => Array} all @@ -48,38 +166,6 @@ * Copy a node’s positional info. * @property {(nodes: Array, loose?: boolean | undefined) => Array} wrap * Wrap `nodes` with line endings between each node, adds initial/final line endings when `loose`. - * - * @typedef Options - * Configuration (optional). - * @property {boolean | null | undefined} [allowDangerousHtml=false] - * Whether to persist raw HTML in markdown in the hast tree (default: - * `false`). - * @property {string | null | undefined} [clobberPrefix='user-content-'] - * Prefix to use before the `id` attribute on footnotes to prevent it from - * *clobbering*(default: `'user-content-'`). - * @property {string | null | undefined} [footnoteBackLabel='Back to content'] - * Label to use from backreferences back to their footnote call (affects - * screen readers) (default: `'Back to content'`). - * @property {string | null | undefined} [footnoteLabel='Footnotes'] - * Label to use for the footnotes section (affects screen readers) (default: - * `'Footnotes'`). - * @property {HastProperties | null | undefined} [footnoteLabelProperties={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) (default: `{className: ['sr-only']}`). - * @property {string | null | undefined} [footnoteLabelTagName='h2'] - * Tag name to use for the footnote label (default: `'h2'`). - * @property {Handlers | null | undefined} [handlers] - * Extra handlers for nodes (optional). - * @property {Array | null | undefined} [passThrough] - * List of custom mdast node types to pass through (keep) in hast (note that - * the node itself is passed, but eventual children are transformed) - * (optional). - * @property {Handler | null | undefined} [unknownHandler] - * Handler for all unknown nodes (optional). - * - * @typedef {Record} Handlers - * Handle nodes. */ import {visit} from 'unist-util-visit' @@ -88,6 +174,9 @@ import {handlers as defaultHandlers} from './handlers/index.js' const own = {}.hasOwnProperty +/** @type {Options} */ +const emptyOptions = {} + /** * Create `state` from an mdast tree. * @@ -99,7 +188,7 @@ const own = {}.hasOwnProperty * `state` function. */ export function createState(tree, options) { - const settings = options || {} + const settings = options || emptyOptions /** @type {Map} */ const definitionById = new Map() /** @type {Map} */ diff --git a/readme.md b/readme.md index fdf65c0..ad10bb2 100644 --- a/readme.md +++ b/readme.md @@ -17,8 +17,12 @@ * [Install](#install) * [Use](#use) * [API](#api) + * [`defaultFootnoteBackContent(referenceIndex, rereferenceIndex)`](#defaultfootnotebackcontentreferenceindex-rereferenceindex) + * [`defaultFootnoteBackLabel(referenceIndex, rereferenceIndex)`](#defaultfootnotebacklabelreferenceindex-rereferenceindex) * [`defaultHandlers`](#defaulthandlers) * [`toHast(tree[, options])`](#tohasttree-options) + * [`FootnoteBackContentTemplate`](#footnotebackcontenttemplate) + * [`FootnoteBackLabelTemplate`](#footnotebacklabeltemplate) * [`Handler`](#handler) * [`Handlers`](#handlers) * [`Options`](#options) @@ -114,10 +118,45 @@ console.log(html) ## API -This package exports the identifiers [`defaultHandlers`][api-default-handlers] -and [`toHast`][api-to-hast]. +This package exports the identifiers +[`defaultFootnoteBackContent`][api-default-footnote-back-content], +[`defaultFootnoteBackLabel`][api-default-footnote-back-label], +[`defaultHandlers`][api-default-handlers], and +[`toHast`][api-to-hast]. There is no default export. +### `defaultFootnoteBackContent(referenceIndex, rereferenceIndex)` + +Generate the default content that GitHub uses on backreferences. + +###### Parameters + +* `referenceIndex` (`number`) + — index of the definition in the order that they are first referenced, + 0-indexed +* `rereferenceIndex` (`number`) + — index of calls to the same definition, 0-indexed + +###### Returns + +Content (`Array`). + +### `defaultFootnoteBackLabel(referenceIndex, rereferenceIndex)` + +Generate the default label that GitHub uses on backreferences. + +###### Parameters + +* `referenceIndex` (`number`) + — index of the definition in the order that they are first referenced, + 0-indexed +* `rereferenceIndex` (`number`) + — index of calls to the same definition, 0-indexed + +###### Returns + +Label (`string`). + ### `defaultHandlers` Default handlers for nodes ([`Handlers`][api-handlers]). @@ -204,6 +243,76 @@ The default behavior for unknown nodes is: This behavior can be changed by passing an `unknownHandler`. +### `FootnoteBackContentTemplate` + +Generate content for the backreference dynamically. + +For the following markdown: + +```markdown +Alpha[^micromark], bravo[^micromark], and charlie[^remark]. + +[^remark]: things about remark +[^micromark]: things about micromark +``` + +This function will be called with: + +* `0` and `0` for the backreference from `things about micromark` to + `alpha`, as it is the first used definition, and the first call to it +* `0` and `1` for the backreference from `things about micromark` to + `bravo`, as it is the first used definition, and the second call to it +* `1` and `0` for the backreference from `things about remark` to + `charlie`, as it is the second used definition + +###### Parameters + +* `referenceIndex` (`number`) + — index of the definition in the order that they are first referenced, + 0-indexed +* `rereferenceIndex` (`number`) + — index of calls to the same definition, 0-indexed + +###### Returns + +Content for the backreference when linking back from definitions to their +reference (`Array`, `ElementContent`, or `string`). + +### `FootnoteBackLabelTemplate` + +Generate a back label dynamically. + +For the following markdown: + +```markdown +Alpha[^micromark], bravo[^micromark], and charlie[^remark]. + +[^remark]: things about remark +[^micromark]: things about micromark +``` + +This function will be called with: + +* `0` and `0` for the backreference from `things about micromark` to + `alpha`, as it is the first used definition, and the first call to it +* `0` and `1` for the backreference from `things about micromark` to + `bravo`, as it is the first used definition, and the second call to it +* `1` and `0` for the backreference from `things about remark` to + `charlie`, as it is the second used definition + +###### Parameters + +* `referenceIndex` (`number`) + — index of the definition in the order that they are first referenced, + 0-indexed +* `rereferenceIndex` (`number`) + — index of calls to the same definition, 0-indexed + +###### Returns + +Back label to use when linking back from definitions to their reference +(`string`). + ### `Handler` Handle a node (TypeScript). @@ -240,11 +349,18 @@ Configuration (TypeScript). * `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 + — prefix to use before the `id` property on footnotes to prevent them from *clobbering* -* `footnoteBackLabel` (`string`, default: `'Back to content'`) - — label to use from backreferences back to their footnote call (affects - screen readers) +* `footnoteBackContent` + ([`FootnoteBackContentTemplate`][api-footnote-back-content-template] + or `string`, default: + [`defaultFootnoteBackContent`][api-default-footnote-back-content]) + — content of the backreference back to references +* `footnoteBackLabel` + ([`FootnoteBackLabelTemplate`][api-footnote-back-label-template] + or `string`, default: + [`defaultFootnoteBackLabel`][api-default-footnote-back-label]) + — label to describe the backreference back to references * `footnoteLabel` (`string`, default: `'Footnotes'`) — label to use for the footnotes section (affects screen readers) * `footnoteLabelProperties` @@ -408,7 +524,7 @@ console.log(html)

Footnotes

  1. -

    Monde!

    +

    Monde!

@@ -420,14 +536,20 @@ In that case, it’s important to translate and define the labels relating to footnotes so that screen reader users can properly pronounce the page: ```diff -@@ -9,7 +9,10 @@ const mdast = fromMarkdown(markdown, { +@@ -9,7 +9,16 @@ const mdast = fromMarkdown(markdown, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] }) -const hast = toHast(mdast) +const hast = toHast(mdast, { + footnoteLabel: 'Notes de bas de page', -+ footnoteBackLabel: 'Arrière' ++ footnoteBackLabel(referenceIndex, rereferenceIndex) { ++ return ( ++ 'Retour à la référence ' + ++ (referenceIndex + 1) + ++ (rereferenceIndex > 1 ? '-' + rereferenceIndex : '') ++ ) ++ } +}) const html = toHtml(hast) @@ -443,8 +565,8 @@ footnotes so that screen reader users can properly pronounce the page: +

Notes de bas de page

  1. --

    Monde!

    -+

    Monde!

    +-

    Monde!

    ++

    Monde!

@@ -1344,8 +1466,14 @@ Raw nodes are typically ignored but are handled by ## Types This package is fully typed with [TypeScript][]. -It exports the [`Handler`][api-handler], [`Handlers`][api-handlers], -[`Options`][api-options], [`Raw`][api-raw], and [`State`][api-state] types. +It exports the +[`FootnoteBackContentTemplate`][api-footnote-back-content-template], +[`FootnoteBackLabelTemplate`][api-footnote-back-label-template], +[`Handler`][api-handler], +[`Handlers`][api-handlers], +[`Options`][api-options], +[`Raw`][api-raw], and +[`State`][api-state] types. It also registers the `Raw` node type with `@types/hast`. If you’re working with the syntax tree (and you pass @@ -1567,9 +1695,15 @@ abide by its terms. [dfn-literal]: https://github.com/syntax-tree/hast#literal +[api-default-footnote-back-content]: #defaultfootnotebackcontentreferenceindex-rereferenceindex + +[api-default-footnote-back-label]: #defaultfootnotebacklabelreferenceindex-rereferenceindex + [api-default-handlers]: #defaulthandlers -[api-to-hast]: #tohasttree-options +[api-footnote-back-content-template]: #footnotebackcontenttemplate + +[api-footnote-back-label-template]: #footnotebacklabeltemplate [api-handler]: #handler @@ -1580,3 +1714,5 @@ abide by its terms. [api-raw]: #raw [api-state]: #state + +[api-to-hast]: #tohasttree-options diff --git a/test/core.js b/test/core.js index ef1a0b8..776011f 100644 --- a/test/core.js +++ b/test/core.js @@ -12,6 +12,8 @@ import {toHast} from '../index.js' test('toHast', async function (t) { await t.test('should expose the public api', async function () { assert.deepEqual(Object.keys(await import('../index.js')).sort(), [ + 'defaultFootnoteBackContent', + 'defaultFootnoteBackLabel', 'defaultHandlers', 'toHast' ]) diff --git a/test/footnote-definition.js b/test/footnote-definition.js index e44d289..5e9df1d 100644 --- a/test/footnote-definition.js +++ b/test/footnote-definition.js @@ -63,9 +63,9 @@ test('footnoteDefinition', async function (t) { 'a', { href: '#user-content-fnref-charlie', - dataFootnoteBackref: true, - className: ['data-footnote-backref'], - ariaLabel: 'Back to content' + dataFootnoteBackref: '', + ariaLabel: 'Back to reference 1', + className: ['data-footnote-backref'] }, '↩' ) @@ -138,9 +138,9 @@ test('footnoteDefinition', async function (t) { 'a', { href: '#user-content-fnref-echo', - dataFootnoteBackref: true, - className: ['data-footnote-backref'], - ariaLabel: 'Back to content' + dataFootnoteBackref: '', + ariaLabel: 'Back to reference 1', + className: ['data-footnote-backref'] }, '↩' ) diff --git a/test/footnote.js b/test/footnote.js index 952b221..84a9be3 100644 --- a/test/footnote.js +++ b/test/footnote.js @@ -1,3 +1,7 @@ +/** + * @typedef {import('hast').ElementContent} ElementContent + */ + import assert from 'node:assert/strict' import test from 'node:test' import {toHtml} from 'hast-util-to-html' @@ -40,7 +44,7 @@ test('footnote', async function (t) {

delta

- + ` @@ -82,10 +86,10 @@ test('footnote', async function (t) {

Footnotes

  1. -

    a

    +

    a

  2. -

    b

    +

    b

` @@ -118,10 +122,10 @@ test('footnote', async function (t) {

Footnotes

  1. -

    a

    +

    a

  2. -

    b

    +

    b

` @@ -145,7 +149,7 @@ test('footnote', async function (t) {

Footnotes

  1. -

    Recursion11 2 3 4

    +

    Recursion11 2 3 4

` @@ -171,7 +175,109 @@ test('footnote', async function (t) {

Voetnoten

  1. -

    a

    +

    a

    +
  2. +
+
` + ) + } + ) + + await t.test( + 'should support `footnoteBackLabel` as a function', + async function () { + assert.equal( + toHtml( + // @ts-expect-error: to do: remove when `to-html` is released. + toHast( + fromMarkdown('[^1]\n[^1]: a', { + extensions: [gfm()], + mdastExtensions: [gfmFromMarkdown()] + }), + { + footnoteBackLabel(referenceIndex, rereferenceIndex) { + return ( + 'Terug naar referentie ' + + (referenceIndex + 1) + + (rereferenceIndex > 1 ? '-' + rereferenceIndex : '') + ) + } + } + ) + ), + `

1

+

Footnotes

+
    +
  1. +

    a

    +
  2. +
+
` + ) + } + ) + + await t.test( + 'should support `footnoteBackContent` as `string`', + async function () { + assert.equal( + toHtml( + // @ts-expect-error: to do: remove when `to-html` is released. + toHast( + fromMarkdown('[^1]\n[^1]: a', { + extensions: [gfm()], + mdastExtensions: [gfmFromMarkdown()] + }), + {footnoteBackContent: '⬆️'} + ) + ), + `

1

+

Footnotes

+
    +
  1. +

    a ⬆️

    +
  2. +
+
` + ) + } + ) + + await t.test( + 'should support `footnoteBackContent` as a function`', + async function () { + assert.equal( + toHtml( + // @ts-expect-error: to do: remove when `to-html` is released. + toHast( + fromMarkdown('[^1]\n[^1]: a', { + extensions: [gfm()], + mdastExtensions: [gfmFromMarkdown()] + }), + { + footnoteBackContent(_, rereferenceIndex) { + /** @type {Array} */ + const result = [{type: 'text', value: '⬆️'}] + + if (rereferenceIndex > 1) { + result.push({ + type: 'element', + tagName: 'sup', + properties: {}, + children: [{type: 'text', value: String(rereferenceIndex)}] + }) + } + + return result + } + } + ) + ), + `

1

+

Footnotes

+
    +
  1. +

    a ⬆️

` @@ -195,7 +301,7 @@ test('footnote', async function (t) {

Footnotes

  1. -

    a

    +

    a

` @@ -218,7 +324,7 @@ test('footnote', async function (t) {

Footnotes

  1. -

    a

    +

    a

` @@ -241,7 +347,7 @@ test('footnote', async function (t) {

Footnotes

  1. -

    a

    +

    a

` @@ -266,10 +372,10 @@ test('footnote', async function (t) {

Footnotes

  1. -

    d 2

    +

    d 2

  2. -

    e

    +

    e

`