5
5
* @typedef {import('./state.js').State } State
6
6
*/
7
7
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
+
8
67
import structuredClone from '@ungap/structured-clone'
9
68
import { normalizeUri } from 'micromark-util-sanitize-uri'
10
69
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
+
11
116
/**
12
117
* Generate a hast footer for called footnote definitions.
13
118
*
@@ -16,23 +121,27 @@ import {normalizeUri} from 'micromark-util-sanitize-uri'
16
121
* @returns {Element | undefined }
17
122
* `section` element or `undefined`.
18
123
*/
124
+ // eslint-disable-next-line complexity
19
125
export function footer ( state ) {
20
126
const clobberPrefix =
21
127
typeof state . options . clobberPrefix === 'string'
22
128
? state . options . clobberPrefix
23
129
: '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
25
134
const footnoteLabel = state . options . footnoteLabel || 'Footnotes'
26
135
const footnoteLabelTagName = state . options . footnoteLabelTagName || 'h2'
27
136
const footnoteLabelProperties = state . options . footnoteLabelProperties || {
28
137
className : [ 'sr-only' ]
29
138
}
30
139
/** @type {Array<ElementContent> } */
31
140
const listItems = [ ]
32
- let index = - 1
141
+ let referenceIndex = - 1
33
142
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 ] )
36
145
37
146
if ( ! def ) {
38
147
continue
@@ -41,15 +150,27 @@ export function footer(state) {
41
150
const content = state . all ( def )
42
151
const id = String ( def . identifier ) . toUpperCase ( )
43
152
const safeId = normalizeUri ( id . toLowerCase ( ) )
44
- let referenceIndex = 0
153
+ let rereferenceIndex = 0
45
154
/** @type {Array<ElementContent> } */
46
155
const backReferences = [ ]
47
156
const counts = state . footnoteCounts . get ( id )
48
157
49
158
// 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 ( {
53
174
type : 'element' ,
54
175
tagName : 'a' ,
55
176
properties : {
@@ -58,28 +179,16 @@ export function footer(state) {
58
179
clobberPrefix +
59
180
'fnref-' +
60
181
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' ]
65
189
} ,
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
+ } )
83
192
}
84
193
85
194
const tail = content [ content . length - 1 ]
0 commit comments