55 * @typedef {import('./state.js').State } State
66 */
77
8+ /**
9+ * @callback FootnoteBackContentTemplate
10+ * Generate content for the backreference dynamically.
11+ *
12+ * For the following markdown:
13+ *
14+ * ```markdown
15+ * Alpha[^micromark], bravo[^micromark], and charlie[^remark].
16+ *
17+ * [^remark]: things about remark
18+ * [^micromark]: things about micromark
19+ * ```
20+ *
21+ * This function will be called with:
22+ *
23+ * * `0` and `0` for the backreference from `things about micromark` to
24+ * `alpha`, as it is the first used definition, and the first call to it
25+ * * `0` and `1` for the backreference from `things about micromark` to
26+ * `bravo`, as it is the first used definition, and the second call to it
27+ * * `1` and `0` for the backreference from `things about remark` to
28+ * `charlie`, as it is the second used definition
29+ * @param {number } referenceIndex
30+ * Index of the definition in the order that they are first referenced,
31+ * 0-indexed.
32+ * @param {number } rereferenceIndex
33+ * Index of calls to the same definition, 0-indexed.
34+ * @returns {Array<ElementContent> | ElementContent | string }
35+ * Content for the backreference when linking back from definitions to their
36+ * reference.
37+ *
38+ * @callback FootnoteBackLabelTemplate
39+ * Generate a back label dynamically.
40+ *
41+ * For the following markdown:
42+ *
43+ * ```markdown
44+ * Alpha[^micromark], bravo[^micromark], and charlie[^remark].
45+ *
46+ * [^remark]: things about remark
47+ * [^micromark]: things about micromark
48+ * ```
49+ *
50+ * This function will be called with:
51+ *
52+ * * `0` and `0` for the backreference from `things about micromark` to
53+ * `alpha`, as it is the first used definition, and the first call to it
54+ * * `0` and `1` for the backreference from `things about micromark` to
55+ * `bravo`, as it is the first used definition, and the second call to it
56+ * * `1` and `0` for the backreference from `things about remark` to
57+ * `charlie`, as it is the second used definition
58+ * @param {number } referenceIndex
59+ * Index of the definition in the order that they are first referenced,
60+ * 0-indexed.
61+ * @param {number } rereferenceIndex
62+ * Index of calls to the same definition, 0-indexed.
63+ * @returns {string }
64+ * Back label to use when linking back from definitions to their reference.
65+ */
66+
867import structuredClone from '@ungap/structured-clone'
968import { normalizeUri } from 'micromark-util-sanitize-uri'
1069
70+ /**
71+ * Generate the default content that GitHub uses on backreferences.
72+ *
73+ * @param {number } _
74+ * Index of the definition in the order that they are first referenced,
75+ * 0-indexed.
76+ * @param {number } rereferenceIndex
77+ * Index of calls to the same definition, 0-indexed.
78+ * @returns {Array<ElementContent> }
79+ * Content.
80+ */
81+ export function defaultFootnoteBackContent ( _ , rereferenceIndex ) {
82+ /** @type {Array<ElementContent> } */
83+ const result = [ { type : 'text' , value : '↩' } ]
84+
85+ if ( rereferenceIndex > 1 ) {
86+ result . push ( {
87+ type : 'element' ,
88+ tagName : 'sup' ,
89+ properties : { } ,
90+ children : [ { type : 'text' , value : String ( rereferenceIndex ) } ]
91+ } )
92+ }
93+
94+ return result
95+ }
96+
97+ /**
98+ * Generate the default label that GitHub uses on backreferences.
99+ *
100+ * @param {number } referenceIndex
101+ * Index of the definition in the order that they are first referenced,
102+ * 0-indexed.
103+ * @param {number } rereferenceIndex
104+ * Index of calls to the same definition, 0-indexed.
105+ * @returns {string }
106+ * Label.
107+ */
108+ export function defaultFootnoteBackLabel ( referenceIndex , rereferenceIndex ) {
109+ return (
110+ 'Back to reference ' +
111+ ( referenceIndex + 1 ) +
112+ ( rereferenceIndex > 1 ? '-' + rereferenceIndex : '' )
113+ )
114+ }
115+
11116/**
12117 * Generate a hast footer for called footnote definitions.
13118 *
@@ -16,23 +121,27 @@ import {normalizeUri} from 'micromark-util-sanitize-uri'
16121 * @returns {Element | undefined }
17122 * `section` element or `undefined`.
18123 */
124+ // eslint-disable-next-line complexity
19125export function footer ( state ) {
20126 const clobberPrefix =
21127 typeof state . options . clobberPrefix === 'string'
22128 ? state . options . clobberPrefix
23129 : 'user-content-'
24- const footnoteBackLabel = state . options . footnoteBackLabel || 'Back to content'
130+ const footnoteBackContent =
131+ state . options . footnoteBackContent || defaultFootnoteBackContent
132+ const footnoteBackLabel =
133+ state . options . footnoteBackLabel || defaultFootnoteBackLabel
25134 const footnoteLabel = state . options . footnoteLabel || 'Footnotes'
26135 const footnoteLabelTagName = state . options . footnoteLabelTagName || 'h2'
27136 const footnoteLabelProperties = state . options . footnoteLabelProperties || {
28137 className : [ 'sr-only' ]
29138 }
30139 /** @type {Array<ElementContent> } */
31140 const listItems = [ ]
32- let index = - 1
141+ let referenceIndex = - 1
33142
34- while ( ++ index < state . footnoteOrder . length ) {
35- const def = state . footnoteById . get ( state . footnoteOrder [ index ] )
143+ while ( ++ referenceIndex < state . footnoteOrder . length ) {
144+ const def = state . footnoteById . get ( state . footnoteOrder [ referenceIndex ] )
36145
37146 if ( ! def ) {
38147 continue
@@ -41,15 +150,27 @@ export function footer(state) {
41150 const content = state . all ( def )
42151 const id = String ( def . identifier ) . toUpperCase ( )
43152 const safeId = normalizeUri ( id . toLowerCase ( ) )
44- let referenceIndex = 0
153+ let rereferenceIndex = 0
45154 /** @type {Array<ElementContent> } */
46155 const backReferences = [ ]
47156 const counts = state . footnoteCounts . get ( id )
48157
49158 // eslint-disable-next-line no-unmodified-loop-condition
50- while ( counts !== undefined && ++ referenceIndex <= counts ) {
51- /** @type {Element } */
52- const backReference = {
159+ while ( counts !== undefined && ++ rereferenceIndex <= counts ) {
160+ if ( backReferences . length > 0 ) {
161+ backReferences . push ( { type : 'text' , value : ' ' } )
162+ }
163+
164+ let children =
165+ typeof footnoteBackContent === 'string'
166+ ? footnoteBackContent
167+ : footnoteBackContent ( referenceIndex , rereferenceIndex )
168+
169+ if ( typeof children === 'string' ) {
170+ children = { type : 'text' , value : children }
171+ }
172+
173+ backReferences . push ( {
53174 type : 'element' ,
54175 tagName : 'a' ,
55176 properties : {
@@ -58,28 +179,16 @@ export function footer(state) {
58179 clobberPrefix +
59180 'fnref-' +
60181 safeId +
61- ( referenceIndex > 1 ? '-' + referenceIndex : '' ) ,
62- dataFootnoteBackref : true ,
63- className : [ 'data-footnote-backref' ] ,
64- ariaLabel : footnoteBackLabel
182+ ( rereferenceIndex > 1 ? '-' + rereferenceIndex : '' ) ,
183+ dataFootnoteBackref : '' ,
184+ ariaLabel :
185+ typeof footnoteBackLabel === 'string'
186+ ? footnoteBackLabel
187+ : footnoteBackLabel ( referenceIndex , rereferenceIndex ) ,
188+ className : [ 'data-footnote-backref' ]
65189 } ,
66- children : [ { type : 'text' , value : '↩' } ]
67- }
68-
69- if ( referenceIndex > 1 ) {
70- backReference . children . push ( {
71- type : 'element' ,
72- tagName : 'sup' ,
73- properties : { } ,
74- children : [ { type : 'text' , value : String ( referenceIndex ) } ]
75- } )
76- }
77-
78- if ( backReferences . length > 0 ) {
79- backReferences . push ( { type : 'text' , value : ' ' } )
80- }
81-
82- backReferences . push ( backReference )
190+ children : Array . isArray ( children ) ? children : [ children ]
191+ } )
83192 }
84193
85194 const tail = content [ content . length - 1 ]
0 commit comments