@peaceroad/markdown-it-footnote-here 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +33 -0
- package/README.md +35 -1
- package/index.js +174 -58
- package/package.json +1 -1
package/AGENTS.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Workflow for Updating markdown-it-footnote-here
|
|
2
|
+
|
|
3
|
+
This document captures the current implementation workflow, especially around footnotes/endnotes.
|
|
4
|
+
|
|
5
|
+
## Code overview
|
|
6
|
+
- `index.js`:
|
|
7
|
+
- Rendering helpers: `render_footnote_ref`, `render_footnote_open/close`, `render_footnote_anchor`, and `createAfterBackLinkToken`.
|
|
8
|
+
- Parsing: custom block rule `footnote_def`, inline rule `footnote_ref`, core rule `footnote_anchor`, and `endnotes_move` to append endnotes at the end.
|
|
9
|
+
- Endnote handling: labels starting with `endnotesPrefix` (default `en-`) are endnotes; DOM ids/classes use fixed `en`.
|
|
10
|
+
- Options include backlink placement, label customization, and endnotes section attributes.
|
|
11
|
+
|
|
12
|
+
## Adding features / making changes
|
|
13
|
+
1) Review options and defaults in `index.js` (`opt` object).
|
|
14
|
+
2) Keep DOM prefixes stable:
|
|
15
|
+
- Footnotes use `fn`; endnotes use `en` for ids/classes (`en1`, `en-ref1`, etc.).
|
|
16
|
+
3) Parsing flow:
|
|
17
|
+
- `footnote_def` registers notes into `env.footnotes` or `env.endnotes` based on `endnotesPrefix`.
|
|
18
|
+
- `footnote_ref` resolves references via `selectNoteEnv` and tags tokens with `isEndnote`.
|
|
19
|
+
4) Rendering flow:
|
|
20
|
+
- `footnote_anchor` injects backlinks/labels into footnote content.
|
|
21
|
+
- `endnotes_move` removes endnote blocks from inline positions and appends a `<section>` (attributes ordered aria-label → id → class → role).
|
|
22
|
+
5) When adding options:
|
|
23
|
+
- Update `README.md` and add fixtures under `test/`.
|
|
24
|
+
- Extend `test/test.js` to load the new fixtures.
|
|
25
|
+
|
|
26
|
+
## Testing
|
|
27
|
+
- Run `npm test` (uses `test/test.js` and fixtures under `test/`).
|
|
28
|
+
- Fixtures format: alternating `[Markdown]` and `[HTML]` blocks.
|
|
29
|
+
|
|
30
|
+
## Notes
|
|
31
|
+
- `labelSupTag` applies to both footnotes and endnotes.
|
|
32
|
+
- If `endnotesPrefix` is empty, endnotes are disabled.
|
|
33
|
+
- `endnotesUseHeading` true: render `<h2>` with `endnotesSectionAriaLabel`; false: use `aria-label` without heading.
|
package/README.md
CHANGED
|
@@ -28,6 +28,34 @@ HTML:
|
|
|
28
28
|
|
|
29
29
|
Notice. When multiple instances of the same footnote number appear in the main content, the default behavior is that the backlink from the footnote will refer to the first instance.
|
|
30
30
|
|
|
31
|
+
## Endnotes
|
|
32
|
+
|
|
33
|
+
When a footnote label starts with the endnote prefix (default: `en-`), it is collected at the end of the document and rendered as endnotes. The reference/backlink label for endnotes is prefixed by `endnotesLabelPrefix` (default: `E`), so endnotes appear as `[E1]`, `[E2]`, ...
|
|
34
|
+
|
|
35
|
+
Markdown:
|
|
36
|
+
|
|
37
|
+
```md
|
|
38
|
+
A paragraph.[^en-1]
|
|
39
|
+
|
|
40
|
+
[^en-1]: A endnote.
|
|
41
|
+
|
|
42
|
+
A paragraph.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
HTML:
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<p>A paragraph.<a href="#en1" id="en-ref1" class="en-noteref" role="doc-noteref">[E1]</a></p>
|
|
49
|
+
<p>A paragraph.</p>
|
|
50
|
+
<section aria-label="Notes" id="endnotes" role="doc-endnotes">
|
|
51
|
+
<ol>
|
|
52
|
+
<li id="en1">
|
|
53
|
+
<p><a href="#en-ref1" class="en-backlink" role="doc-backlink">[E1]</a> A endnote.</p>
|
|
54
|
+
</li>
|
|
55
|
+
</ol>
|
|
56
|
+
</section>
|
|
57
|
+
```
|
|
58
|
+
|
|
31
59
|
## Use
|
|
32
60
|
|
|
33
61
|
```js
|
|
@@ -53,7 +81,13 @@ npm install @peaceroad/markdown-it-footnote-here
|
|
|
53
81
|
- afterBacklinkSuffixArabicNumerals (boolean): If true, backlink suffix uses numbers (1, 2, ...) instead of letters (a, b, ...).
|
|
54
82
|
- afterBacklinkdAriaLabelPrefix (string): Prefix for aria-label of backlink (default: 'Back to reference ').
|
|
55
83
|
- labelBra (string): Bracket to use before footnote number (default: '[').
|
|
56
|
-
- labelKet (string): Bracket to use after footnote number (default: ']').
|
|
84
|
+
- labelKet (string): Bracket to use after footnote number (default: ']').
|
|
57
85
|
- labelSupTag (boolean): If true, wraps footnote reference in `<sup>` tag.
|
|
58
86
|
- backLabelBra (string): Bracket to use before backlink number (default: '[').
|
|
59
87
|
- backLabelKet (string): Bracket to use after backlink number (default: ']').
|
|
88
|
+
- endnotesPrefix (string): Prefix that marks a footnote as an endnote (default: `'en-'`).
|
|
89
|
+
- endnotesLabelPrefix (string): Label prefix for endnote refs/backlinks (default: `'E'`, e.g., `[E1]`).
|
|
90
|
+
- endnotesSectionId (string): `id` attribute for the endnotes section wrapper; omitted when empty (default: `'endnotes'`).
|
|
91
|
+
- endnotesSectionClass (string): `class` attribute for the endnotes section wrapper; omitted when empty (default: `''`).
|
|
92
|
+
- endnotesSectionAriaLabel (string): Used as `aria-label` when `endnotesUseHeading` is false. When `endnotesUseHeading` is true, this value becomes the heading text (default: `'Notes'`).
|
|
93
|
+
- endnotesUseHeading (boolean): If true, render `<h2>{endnotesSectionAriaLabel}</h2>` and omit `aria-label`. If false (default), omit the heading and set `aria-label` when provided.
|
package/index.js
CHANGED
|
@@ -8,71 +8,124 @@ const render_footnote_anchor_name = (tokens, idx, opt, env) => {
|
|
|
8
8
|
return prefix + n
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
const isEndnoteLabel = (label, opt) => {
|
|
12
|
+
if (!opt.endnotesPrefix) return false
|
|
13
|
+
return label.startsWith(opt.endnotesPrefix)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ensureNotesEnv = (env, key) => {
|
|
17
|
+
if (!env[key]) {
|
|
18
|
+
env[key] = { length: 0, refs: {}, positions: [] }
|
|
19
|
+
}
|
|
20
|
+
return env[key]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ENDNOTE_DOM_PREFIX = 'en'
|
|
24
|
+
|
|
25
|
+
const getDomPrefix = (isEndnote) => (isEndnote ? ENDNOTE_DOM_PREFIX : 'fn')
|
|
26
|
+
const getDisplayPrefix = (isEndnote, opt) => (isEndnote ? opt.endnotesLabelPrefix : '')
|
|
27
|
+
|
|
28
|
+
const getNotesMeta = (token, env) => {
|
|
29
|
+
if (token.meta && token.meta.isEndnote) return env.endnotes
|
|
30
|
+
return env.footnotes
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const selectNoteEnv = (label, env, preferEndnote) => {
|
|
34
|
+
const endRefs = env.endnotes && env.endnotes.refs
|
|
35
|
+
const footRefs = env.footnotes && env.footnotes.refs
|
|
36
|
+
const endId = endRefs && endRefs[':' + label]
|
|
37
|
+
const footId = footRefs && footRefs[':' + label]
|
|
38
|
+
|
|
39
|
+
if (preferEndnote && endId !== undefined) {
|
|
40
|
+
return { env: env.endnotes, id: endId, isEndnote: true }
|
|
41
|
+
}
|
|
42
|
+
if (footId !== undefined) {
|
|
43
|
+
return { env: env.footnotes, id: footId, isEndnote: false }
|
|
44
|
+
}
|
|
45
|
+
if (endId !== undefined) {
|
|
46
|
+
return { env: env.endnotes, id: endId, isEndnote: true }
|
|
47
|
+
}
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
11
51
|
const render_footnote_ref = (tokens, idx, opt, env) => {
|
|
12
52
|
const token = tokens[idx]
|
|
13
53
|
const id = token.meta.id
|
|
14
54
|
const n = id + 1
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
55
|
+
const notes = getNotesMeta(token, env)
|
|
56
|
+
const isEndnote = !!token.meta.isEndnote
|
|
57
|
+
const noteDomPrefix = getDomPrefix(isEndnote)
|
|
58
|
+
const displayPrefix = getDisplayPrefix(isEndnote, opt)
|
|
59
|
+
notes._refCount = notes._refCount || {}
|
|
60
|
+
let refIdx = (notes._refCount[id] = (notes._refCount[id] || 0) + 1)
|
|
18
61
|
if (!opt.afterBacklinkSuffixArabicNumerals) {
|
|
19
62
|
refIdx = String.fromCharCode(96 + refIdx)
|
|
20
63
|
}
|
|
21
64
|
let suffix = ''
|
|
22
|
-
let label = `${opt.labelBra}${n}${opt.labelKet}`
|
|
23
|
-
if (
|
|
65
|
+
let label = `${opt.labelBra}${displayPrefix}${n}${opt.labelKet}`
|
|
66
|
+
if (notes.totalCounts && notes.totalCounts[id] > 1) {
|
|
24
67
|
suffix = '-' + refIdx
|
|
25
68
|
if (opt.beforeSameBacklink) {
|
|
26
|
-
label = `${opt.labelBra}${n}${suffix}${opt.labelKet}`
|
|
69
|
+
label = `${opt.labelBra}${displayPrefix}${n}${suffix}${opt.labelKet}`
|
|
27
70
|
}
|
|
28
71
|
}
|
|
29
|
-
|
|
30
|
-
|
|
72
|
+
const href = `${noteDomPrefix}${n}`
|
|
73
|
+
let refCont = `<a href="#${href}" id="${noteDomPrefix}-ref${n}${suffix}" class="${noteDomPrefix}-noteref" role="doc-noteref">${label}</a>`
|
|
74
|
+
if (opt.labelSupTag) refCont = `<sup class="${noteDomPrefix}-noteref-wrapper">${refCont}</sup>`
|
|
31
75
|
return refCont
|
|
32
76
|
}
|
|
33
77
|
|
|
34
78
|
const render_footnote_open = (tokens, idx, opt, env, slf) => {
|
|
35
79
|
const id = slf.rules.footnote_anchor_name(tokens, idx, opt, env, slf)
|
|
80
|
+
const isEndnote = tokens[idx].meta && tokens[idx].meta.isEndnote
|
|
81
|
+
if (isEndnote) return `<li id="${ENDNOTE_DOM_PREFIX}${id}">\n`
|
|
36
82
|
return `<aside id="fn${id}" class="fn" role="doc-footnote">\n`
|
|
37
83
|
}
|
|
38
84
|
|
|
39
|
-
const render_footnote_close = () => {
|
|
85
|
+
const render_footnote_close = (tokens, idx) => {
|
|
86
|
+
const isEndnote = tokens[idx].meta && tokens[idx].meta.isEndnote
|
|
87
|
+
if (isEndnote) return `</li>\n`
|
|
40
88
|
return `</aside>\n`
|
|
41
89
|
}
|
|
42
90
|
|
|
43
91
|
const render_footnote_anchor = (tokens, idx, opt, env) => {
|
|
44
92
|
const idNum = tokens[idx].meta.id
|
|
45
93
|
const n = idNum + 1
|
|
46
|
-
const
|
|
47
|
-
const
|
|
94
|
+
const isEndnote = tokens[idx].meta && tokens[idx].meta.isEndnote
|
|
95
|
+
const notes = getNotesMeta(tokens[idx], env)
|
|
96
|
+
const counts = notes && notes.totalCounts
|
|
97
|
+
const noteDomPrefix = getDomPrefix(!!isEndnote)
|
|
98
|
+
const displayPrefix = getDisplayPrefix(!!isEndnote, opt)
|
|
99
|
+
|
|
48
100
|
if (opt.beforeSameBacklink && counts && counts[idNum] > 1) {
|
|
49
101
|
let links = ''
|
|
50
102
|
for (let i = 1; i <= counts[idNum]; i++) {
|
|
51
103
|
const suffix = '-' + String.fromCharCode(96 + i); // a, b, c ...
|
|
52
|
-
links += `<a href="
|
|
104
|
+
links += `<a href="#${noteDomPrefix}-ref${n}${suffix}" class="${noteDomPrefix}-backlink" role="doc-backlink">${opt.backLabelBra}${displayPrefix}${n}${suffix}${opt.backLabelKet}</a>`
|
|
53
105
|
}
|
|
54
106
|
return links + ' '
|
|
55
107
|
}
|
|
56
108
|
|
|
57
109
|
if (opt.afterBacklink) {
|
|
58
|
-
return `<span class="
|
|
110
|
+
return `<span class="${noteDomPrefix}-label">${opt.backLabelBra}${displayPrefix}${n}${opt.backLabelKet}</span> `
|
|
59
111
|
}
|
|
60
112
|
|
|
61
113
|
if (counts && counts[idNum] > 1) {
|
|
62
|
-
return `<a href="
|
|
114
|
+
return `<a href="#${noteDomPrefix}-ref${n}-a" class="${noteDomPrefix}-backlink" role="doc-backlink">${opt.backLabelBra}${displayPrefix}${n}${opt.backLabelKet}</a> `
|
|
63
115
|
}
|
|
64
116
|
|
|
65
|
-
return `<a href="
|
|
117
|
+
return `<a href="#${noteDomPrefix}-ref${n}" class="${noteDomPrefix}-backlink" role="doc-backlink">${opt.backLabelBra}${displayPrefix}${n}${opt.backLabelKet}</a> `
|
|
66
118
|
}
|
|
67
119
|
|
|
68
|
-
function createAfterBackLinkToken(state, counts, n, opt) {
|
|
120
|
+
function createAfterBackLinkToken(state, counts, n, opt, noteDomPrefix, isEndnote) {
|
|
121
|
+
const displayPrefix = isEndnote ? opt.endnotesLabelPrefix : ''
|
|
69
122
|
let html = ' '
|
|
70
123
|
if (counts && counts > 1) {
|
|
71
124
|
for (let i = 1; i <= counts; i++) {
|
|
72
125
|
const suffixChar = opt.afterBacklinkSuffixArabicNumerals ? i : String.fromCharCode(96 + i)
|
|
73
126
|
const suffix = '-' + suffixChar
|
|
74
|
-
html += `<a href="
|
|
75
|
-
if (opt.afterBacklinkdAriaLabelPrefix) html += ` aria-label="${opt.afterBacklinkdAriaLabelPrefix}${n}${suffix}"`
|
|
127
|
+
html += `<a href="#${noteDomPrefix}-ref${n}${suffix}" class="${noteDomPrefix}-backlink" role="doc-backlink"`
|
|
128
|
+
if (opt.afterBacklinkdAriaLabelPrefix) html += ` aria-label="${opt.afterBacklinkdAriaLabelPrefix}${displayPrefix}${n}${suffix}"`
|
|
76
129
|
html += `>${opt.afterBacklinkContent}`
|
|
77
130
|
if (opt.afterBacklinkWithNumber) {
|
|
78
131
|
html += `<sup>${suffixChar}</sup>`
|
|
@@ -80,8 +133,8 @@ function createAfterBackLinkToken(state, counts, n, opt) {
|
|
|
80
133
|
html += `</a>`
|
|
81
134
|
}
|
|
82
135
|
} else {
|
|
83
|
-
html += `<a href="
|
|
84
|
-
if (opt.afterBacklinkdAriaLabelPrefix) html += ` aria-label="${opt.afterBacklinkdAriaLabelPrefix}${n}"`
|
|
136
|
+
html += `<a href="#${noteDomPrefix}-ref${n}" class="${noteDomPrefix}-backlink" role="doc-backlink"`
|
|
137
|
+
if (opt.afterBacklinkdAriaLabelPrefix) html += ` aria-label="${opt.afterBacklinkdAriaLabelPrefix}${displayPrefix}${n}"`
|
|
85
138
|
html += `>${opt.afterBacklinkContent}</a>`
|
|
86
139
|
}
|
|
87
140
|
const token = new state.Token('html_inline', '', 0)
|
|
@@ -102,16 +155,22 @@ const footnote_plugin = (md, option) =>{
|
|
|
102
155
|
afterBacklinkWithNumber: false,
|
|
103
156
|
afterBacklinkSuffixArabicNumerals: false,
|
|
104
157
|
afterBacklinkdAriaLabelPrefix: 'Back to reference ', /* 戻る:本文参照 */
|
|
158
|
+
endnotesPrefix: 'en-',
|
|
159
|
+
endnotesLabelPrefix: 'E',
|
|
160
|
+
endnotesSectionId: 'endnotes',
|
|
161
|
+
endnotesSectionClass: '',
|
|
162
|
+
endnotesSectionAriaLabel: 'Notes',
|
|
163
|
+
endnotesUseHeading: false,
|
|
105
164
|
}
|
|
106
165
|
if (option) Object.assign(opt, option)
|
|
107
166
|
|
|
108
167
|
const isSpace = md.utils.isSpace
|
|
109
168
|
|
|
110
169
|
md.renderer.rules.footnote_ref = (tokens, idx, _options, env) => render_footnote_ref(tokens, idx, opt, env)
|
|
111
|
-
md.renderer.rules.footnote_open = render_footnote_open
|
|
112
|
-
md.renderer.rules.footnote_close = render_footnote_close
|
|
170
|
+
md.renderer.rules.footnote_open = (tokens, idx, _options, env, slf) => render_footnote_open(tokens, idx, opt, env, slf)
|
|
171
|
+
md.renderer.rules.footnote_close = (tokens, idx, _options, env, slf) => render_footnote_close(tokens, idx, opt, env, slf)
|
|
113
172
|
md.renderer.rules.footnote_anchor = (tokens, idx, _options, env, slf) => render_footnote_anchor(tokens, idx, opt, env, slf)
|
|
114
|
-
md.renderer.rules.footnote_anchor_name = render_footnote_anchor_name
|
|
173
|
+
md.renderer.rules.footnote_anchor_name = (tokens, idx, _options, env, slf) => render_footnote_anchor_name(tokens, idx, opt, env, slf)
|
|
115
174
|
|
|
116
175
|
// Process footnote block definition
|
|
117
176
|
const footnote_def = (state, startLine, endLine, silent) => {
|
|
@@ -134,16 +193,13 @@ const footnote_plugin = (md, option) =>{
|
|
|
134
193
|
|
|
135
194
|
if (silent) { return true; }
|
|
136
195
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
state.env.footnotes = { length: 0, refs: {}, positions: [] }
|
|
140
|
-
}
|
|
141
|
-
const fn = state.env.footnotes
|
|
196
|
+
const isEndnote = isEndnoteLabel(label, opt)
|
|
197
|
+
const fn = ensureNotesEnv(state.env, isEndnote ? 'endnotes' : 'footnotes')
|
|
142
198
|
const id = fn.length++
|
|
143
199
|
fn.refs[':' + label] = id
|
|
144
200
|
|
|
145
201
|
const token = new state.Token('footnote_open', '', 1)
|
|
146
|
-
token.meta = { id, label }
|
|
202
|
+
token.meta = { id, label, isEndnote }
|
|
147
203
|
token.level = state.level++
|
|
148
204
|
state.tokens.push(token)
|
|
149
205
|
fn.positions.push(state.tokens.length - 1)
|
|
@@ -193,6 +249,7 @@ const footnote_plugin = (md, option) =>{
|
|
|
193
249
|
|
|
194
250
|
const closeToken = new state.Token('footnote_close', '', -1)
|
|
195
251
|
closeToken.level = --state.level
|
|
252
|
+
closeToken.meta = { isEndnote }
|
|
196
253
|
state.tokens.push(closeToken)
|
|
197
254
|
|
|
198
255
|
return true
|
|
@@ -208,9 +265,6 @@ const footnote_plugin = (md, option) =>{
|
|
|
208
265
|
return false
|
|
209
266
|
}
|
|
210
267
|
|
|
211
|
-
const env = state.env
|
|
212
|
-
if (!env.footnotes || !env.footnotes.refs) { return false; }
|
|
213
|
-
|
|
214
268
|
let pos = start + 2
|
|
215
269
|
let found = false
|
|
216
270
|
for (; pos < posMax && !found; pos++) {
|
|
@@ -223,12 +277,13 @@ const footnote_plugin = (md, option) =>{
|
|
|
223
277
|
pos++; // pos set next ']' position.
|
|
224
278
|
|
|
225
279
|
const label = src.slice(start + 2, pos - 1)
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
280
|
+
const env = state.env
|
|
281
|
+
const preferEndnote = isEndnoteLabel(label, opt)
|
|
282
|
+
const resolved = selectNoteEnv(label, env, preferEndnote)
|
|
229
283
|
|
|
284
|
+
if (!resolved) { return false; }
|
|
230
285
|
if (!silent) {
|
|
231
|
-
const fn = env
|
|
286
|
+
const fn = resolved.env
|
|
232
287
|
|
|
233
288
|
if (!fn.list) { fn.list = []; }
|
|
234
289
|
|
|
@@ -236,10 +291,10 @@ const footnote_plugin = (md, option) =>{
|
|
|
236
291
|
fn.list[footnoteId] = { label, count: 0 }
|
|
237
292
|
|
|
238
293
|
fn.totalCounts = fn.totalCounts || {}
|
|
239
|
-
fn.totalCounts[id] = (fn.totalCounts[id] || 0) + 1
|
|
294
|
+
fn.totalCounts[resolved.id] = (fn.totalCounts[resolved.id] || 0) + 1
|
|
240
295
|
|
|
241
296
|
const token = state.push('footnote_ref', '', 0)
|
|
242
|
-
token.meta = { id, label }
|
|
297
|
+
token.meta = { id: resolved.id, label, isEndnote: resolved.isEndnote }
|
|
243
298
|
}
|
|
244
299
|
|
|
245
300
|
state.pos = pos
|
|
@@ -247,42 +302,103 @@ const footnote_plugin = (md, option) =>{
|
|
|
247
302
|
}
|
|
248
303
|
|
|
249
304
|
const footnote_anchor = (state) => {
|
|
305
|
+
if (!state.env.footnotes && !state.env.endnotes) return
|
|
250
306
|
const tokens = state.tokens
|
|
251
|
-
const
|
|
252
|
-
const positions = fn && fn.positions
|
|
253
|
-
if (!positions || positions.length === 0) { return; }
|
|
254
|
-
|
|
255
|
-
const createAnchorToken = (id) => {
|
|
307
|
+
const createAnchorToken = (id, isEndnote) => {
|
|
256
308
|
const aToken = new state.Token('footnote_anchor', '', 0)
|
|
257
|
-
aToken.meta = { id, label: id + 1 }
|
|
309
|
+
aToken.meta = { id, label: id + 1, isEndnote }
|
|
258
310
|
return aToken
|
|
259
311
|
}
|
|
260
312
|
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
313
|
+
const injectAnchors = (notes, isEndnote) => {
|
|
314
|
+
const positions = notes && notes.positions
|
|
315
|
+
if (!positions || positions.length === 0) { return; }
|
|
316
|
+
|
|
317
|
+
for (let j = 0, len = positions.length; j < len; ++j) {
|
|
318
|
+
const posOpen = positions[j]
|
|
319
|
+
if (posOpen + 2 >= tokens.length) continue
|
|
264
320
|
|
|
265
|
-
|
|
266
|
-
|
|
321
|
+
const t1 = tokens[posOpen + 1]
|
|
322
|
+
if (t1.type !== 'paragraph_open') continue
|
|
267
323
|
|
|
268
|
-
|
|
269
|
-
|
|
324
|
+
const t2 = tokens[posOpen + 2]
|
|
325
|
+
if (t2.type !== 'inline') continue
|
|
270
326
|
|
|
271
|
-
|
|
272
|
-
|
|
327
|
+
const t0 = tokens[posOpen]
|
|
328
|
+
const id = t0.meta.id
|
|
329
|
+
const noteDomPrefix = isEndnote ? ENDNOTE_DOM_PREFIX : 'fn'
|
|
273
330
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
331
|
+
t2.children.unshift(createAnchorToken(id, isEndnote))
|
|
332
|
+
if (opt.afterBacklink) {
|
|
333
|
+
const n = id + 1
|
|
334
|
+
const counts = notes.totalCounts && notes.totalCounts[id]
|
|
335
|
+
t2.children.push(createAfterBackLinkToken(state, counts, n, opt, noteDomPrefix, isEndnote))
|
|
336
|
+
}
|
|
279
337
|
}
|
|
280
338
|
}
|
|
339
|
+
|
|
340
|
+
injectAnchors(state.env.footnotes, false)
|
|
341
|
+
injectAnchors(state.env.endnotes, true)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const move_endnotes_to_section = (state) => {
|
|
345
|
+
if (!opt.endnotesPrefix) return
|
|
346
|
+
if (!state.env.endnotes || !state.env.endnotes.positions || state.env.endnotes.positions.length === 0) {
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const tokens = state.tokens
|
|
351
|
+
const endnoteBlocks = []
|
|
352
|
+
|
|
353
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
354
|
+
const token = tokens[i]
|
|
355
|
+
if (token.type !== 'footnote_open' || !token.meta || !token.meta.isEndnote) continue
|
|
356
|
+
|
|
357
|
+
let j = i + 1
|
|
358
|
+
while (j < tokens.length) {
|
|
359
|
+
const t = tokens[j]
|
|
360
|
+
if (t.type === 'footnote_close' && t.meta && t.meta.isEndnote) {
|
|
361
|
+
j++
|
|
362
|
+
break
|
|
363
|
+
}
|
|
364
|
+
j++
|
|
365
|
+
}
|
|
366
|
+
endnoteBlocks.push(tokens.slice(i, j))
|
|
367
|
+
tokens.splice(i, j - i)
|
|
368
|
+
i--
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (endnoteBlocks.length === 0) return
|
|
372
|
+
|
|
373
|
+
const sectionOpen = new state.Token('html_block', '', 0)
|
|
374
|
+
const attrs = []
|
|
375
|
+
if (!opt.endnotesUseHeading && opt.endnotesSectionAriaLabel) {
|
|
376
|
+
attrs.push(`aria-label="${opt.endnotesSectionAriaLabel}"`)
|
|
377
|
+
}
|
|
378
|
+
if (opt.endnotesSectionId) attrs.push(`id="${opt.endnotesSectionId}"`)
|
|
379
|
+
if (opt.endnotesSectionClass) attrs.push(`class="${opt.endnotesSectionClass}"`)
|
|
380
|
+
attrs.push('role="doc-endnotes"')
|
|
381
|
+
let sectionContent = `<section ${attrs.join(' ')}>\n`
|
|
382
|
+
if (opt.endnotesUseHeading && opt.endnotesSectionAriaLabel) {
|
|
383
|
+
sectionContent += `<h2>${opt.endnotesSectionAriaLabel}</h2>\n`
|
|
384
|
+
}
|
|
385
|
+
sectionContent += '<ol>\n'
|
|
386
|
+
sectionOpen.content = sectionContent
|
|
387
|
+
|
|
388
|
+
const sectionClose = new state.Token('html_block', '', 0)
|
|
389
|
+
sectionClose.content = '</ol>\n</section>\n'
|
|
390
|
+
|
|
391
|
+
tokens.push(sectionOpen)
|
|
392
|
+
endnoteBlocks.forEach(block => {
|
|
393
|
+
tokens.push(...block)
|
|
394
|
+
})
|
|
395
|
+
tokens.push(sectionClose)
|
|
281
396
|
}
|
|
282
397
|
|
|
283
398
|
md.block.ruler.before('reference', 'footnote_def', footnote_def, { alt: [ 'paragraph', 'reference' ] })
|
|
284
399
|
md.inline.ruler.after('image', 'footnote_ref', footnote_ref)
|
|
285
400
|
md.core.ruler.after('inline', 'footnote_anchor', footnote_anchor)
|
|
401
|
+
md.core.ruler.after('footnote_anchor', 'endnotes_move', move_endnotes_to_section)
|
|
286
402
|
}
|
|
287
403
|
|
|
288
404
|
export default footnote_plugin
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peaceroad/markdown-it-footnote-here",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A markdown-it plugin. This generate aside[role|doc-footnote] element just below the footnote reference paragraph.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type":"module",
|