| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- /**
- * @import {ElementContent, Element} from 'hast'
- * @import {State} from './state.js'
- */
- /**
- * @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> | 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<ElementContent>}
- * Content.
- */
- export function defaultFootnoteBackContent(_, rereferenceIndex) {
- /** @type {Array<ElementContent>} */
- 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.
- *
- * @param {State} state
- * Info passed around.
- * @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 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 || {
- className: ['sr-only']
- }
- /** @type {Array<ElementContent>} */
- const listItems = []
- let referenceIndex = -1
- while (++referenceIndex < state.footnoteOrder.length) {
- const definition = state.footnoteById.get(
- state.footnoteOrder[referenceIndex]
- )
- if (!definition) {
- continue
- }
- const content = state.all(definition)
- const id = String(definition.identifier).toUpperCase()
- const safeId = normalizeUri(id.toLowerCase())
- let rereferenceIndex = 0
- /** @type {Array<ElementContent>} */
- const backReferences = []
- const counts = state.footnoteCounts.get(id)
- // eslint-disable-next-line no-unmodified-loop-condition
- 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: {
- href:
- '#' +
- clobberPrefix +
- 'fnref-' +
- safeId +
- (rereferenceIndex > 1 ? '-' + rereferenceIndex : ''),
- dataFootnoteBackref: '',
- ariaLabel:
- typeof footnoteBackLabel === 'string'
- ? footnoteBackLabel
- : footnoteBackLabel(referenceIndex, rereferenceIndex),
- className: ['data-footnote-backref']
- },
- children: Array.isArray(children) ? children : [children]
- })
- }
- const tail = content[content.length - 1]
- if (tail && tail.type === 'element' && tail.tagName === 'p') {
- const tailTail = tail.children[tail.children.length - 1]
- if (tailTail && tailTail.type === 'text') {
- tailTail.value += ' '
- } else {
- tail.children.push({type: 'text', value: ' '})
- }
- tail.children.push(...backReferences)
- } else {
- content.push(...backReferences)
- }
- /** @type {Element} */
- const listItem = {
- type: 'element',
- tagName: 'li',
- properties: {id: clobberPrefix + 'fn-' + safeId},
- children: state.wrap(content, true)
- }
- state.patch(definition, listItem)
- listItems.push(listItem)
- }
- if (listItems.length === 0) {
- return
- }
- return {
- type: 'element',
- tagName: 'section',
- properties: {dataFootnotes: true, className: ['footnotes']},
- children: [
- {
- type: 'element',
- tagName: footnoteLabelTagName,
- properties: {
- ...structuredClone(footnoteLabelProperties),
- id: 'footnote-label'
- },
- children: [{type: 'text', value: footnoteLabel}]
- },
- {type: 'text', value: '\n'},
- {
- type: 'element',
- tagName: 'ol',
- properties: {},
- children: state.wrap(listItems, true)
- },
- {type: 'text', value: '\n'}
- ]
- }
- }
|