@peaceroad/markdown-it-footnote-here 0.3.0 → 0.3.1
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/index.js +95 -81
- package/package.json +6 -1
- package/AGENTS.md +0 -33
package/index.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
const render_footnote_anchor_name = (tokens, idx,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
let prefix = ''
|
|
5
|
-
if (typeof env.docId === 'string') {
|
|
6
|
-
prefix = '-' + env.docId + '-'
|
|
7
|
-
}
|
|
1
|
+
const render_footnote_anchor_name = (tokens, idx, _opt, env) => {
|
|
2
|
+
const n = tokens[idx].meta.id + 1
|
|
3
|
+
const prefix = typeof env.docId === 'string' ? `-${env.docId}-` : ''
|
|
8
4
|
return prefix + n
|
|
9
5
|
}
|
|
10
6
|
|
|
@@ -22,28 +18,29 @@ const ensureNotesEnv = (env, key) => {
|
|
|
22
18
|
|
|
23
19
|
const ENDNOTE_DOM_PREFIX = 'en'
|
|
24
20
|
|
|
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
21
|
const selectNoteEnv = (label, env, preferEndnote) => {
|
|
34
|
-
const endRefs = env.endnotes && env.endnotes.refs
|
|
35
22
|
const footRefs = env.footnotes && env.footnotes.refs
|
|
36
|
-
const
|
|
37
|
-
|
|
23
|
+
const endRefs = env.endnotes && env.endnotes.refs
|
|
24
|
+
if (!footRefs && !endRefs) return null
|
|
25
|
+
const key = ':' + label
|
|
38
26
|
|
|
39
|
-
if (preferEndnote &&
|
|
40
|
-
|
|
27
|
+
if (preferEndnote && endRefs) {
|
|
28
|
+
const endId = endRefs[key]
|
|
29
|
+
if (endId !== undefined) {
|
|
30
|
+
return { env: env.endnotes, id: endId, isEndnote: true }
|
|
31
|
+
}
|
|
41
32
|
}
|
|
42
|
-
if (
|
|
43
|
-
|
|
33
|
+
if (footRefs) {
|
|
34
|
+
const footId = footRefs[key]
|
|
35
|
+
if (footId !== undefined) {
|
|
36
|
+
return { env: env.footnotes, id: footId, isEndnote: false }
|
|
37
|
+
}
|
|
44
38
|
}
|
|
45
|
-
if (
|
|
46
|
-
|
|
39
|
+
if (!preferEndnote && endRefs) {
|
|
40
|
+
const endId = endRefs[key]
|
|
41
|
+
if (endId !== undefined) {
|
|
42
|
+
return { env: env.endnotes, id: endId, isEndnote: true }
|
|
43
|
+
}
|
|
47
44
|
}
|
|
48
45
|
return null
|
|
49
46
|
}
|
|
@@ -52,18 +49,19 @@ const render_footnote_ref = (tokens, idx, opt, env) => {
|
|
|
52
49
|
const token = tokens[idx]
|
|
53
50
|
const id = token.meta.id
|
|
54
51
|
const n = id + 1
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const noteDomPrefix =
|
|
58
|
-
const displayPrefix =
|
|
59
|
-
notes.
|
|
60
|
-
let refIdx = (notes._refCount[id] = (notes._refCount[id] || 0) + 1)
|
|
61
|
-
if (!opt.afterBacklinkSuffixArabicNumerals) {
|
|
62
|
-
refIdx = String.fromCharCode(96 + refIdx)
|
|
63
|
-
}
|
|
52
|
+
const isEndnote = token.meta.isEndnote
|
|
53
|
+
const notes = isEndnote ? env.endnotes : env.footnotes
|
|
54
|
+
const noteDomPrefix = isEndnote ? ENDNOTE_DOM_PREFIX : 'fn'
|
|
55
|
+
const displayPrefix = isEndnote ? opt.endnotesLabelPrefix : ''
|
|
56
|
+
const totalCounts = notes.totalCounts ? notes.totalCounts[id] || 0 : 0
|
|
64
57
|
let suffix = ''
|
|
65
58
|
let label = `${opt.labelBra}${displayPrefix}${n}${opt.labelKet}`
|
|
66
|
-
if (
|
|
59
|
+
if (totalCounts > 1) {
|
|
60
|
+
const refCount = notes._refCount || (notes._refCount = [])
|
|
61
|
+
let refIdx = (refCount[id] = (refCount[id] || 0) + 1)
|
|
62
|
+
if (!opt.afterBacklinkSuffixArabicNumerals) {
|
|
63
|
+
refIdx = String.fromCharCode(96 + refIdx)
|
|
64
|
+
}
|
|
67
65
|
suffix = '-' + refIdx
|
|
68
66
|
if (opt.beforeSameBacklink) {
|
|
69
67
|
label = `${opt.labelBra}${displayPrefix}${n}${suffix}${opt.labelKet}`
|
|
@@ -77,13 +75,13 @@ const render_footnote_ref = (tokens, idx, opt, env) => {
|
|
|
77
75
|
|
|
78
76
|
const render_footnote_open = (tokens, idx, opt, env, slf) => {
|
|
79
77
|
const id = slf.rules.footnote_anchor_name(tokens, idx, opt, env, slf)
|
|
80
|
-
const isEndnote = tokens[idx].meta
|
|
78
|
+
const isEndnote = tokens[idx].meta.isEndnote
|
|
81
79
|
if (isEndnote) return `<li id="${ENDNOTE_DOM_PREFIX}${id}">\n`
|
|
82
80
|
return `<aside id="fn${id}" class="fn" role="doc-footnote">\n`
|
|
83
81
|
}
|
|
84
82
|
|
|
85
83
|
const render_footnote_close = (tokens, idx) => {
|
|
86
|
-
const isEndnote = tokens[idx].meta
|
|
84
|
+
const isEndnote = tokens[idx].meta.isEndnote
|
|
87
85
|
if (isEndnote) return `</li>\n`
|
|
88
86
|
return `</aside>\n`
|
|
89
87
|
}
|
|
@@ -91,15 +89,16 @@ const render_footnote_close = (tokens, idx) => {
|
|
|
91
89
|
const render_footnote_anchor = (tokens, idx, opt, env) => {
|
|
92
90
|
const idNum = tokens[idx].meta.id
|
|
93
91
|
const n = idNum + 1
|
|
94
|
-
const isEndnote = tokens[idx].meta
|
|
95
|
-
const notes =
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const
|
|
92
|
+
const isEndnote = tokens[idx].meta.isEndnote
|
|
93
|
+
const notes = isEndnote ? env.endnotes : env.footnotes
|
|
94
|
+
const totalCounts = notes.totalCounts
|
|
95
|
+
const count = totalCounts ? totalCounts[idNum] || 0 : 0
|
|
96
|
+
const noteDomPrefix = isEndnote ? ENDNOTE_DOM_PREFIX : 'fn'
|
|
97
|
+
const displayPrefix = isEndnote ? opt.endnotesLabelPrefix : ''
|
|
99
98
|
|
|
100
|
-
if (opt.beforeSameBacklink &&
|
|
99
|
+
if (opt.beforeSameBacklink && count > 1) {
|
|
101
100
|
let links = ''
|
|
102
|
-
for (let i = 1; i <=
|
|
101
|
+
for (let i = 1; i <= count; i++) {
|
|
103
102
|
const suffix = '-' + String.fromCharCode(96 + i); // a, b, c ...
|
|
104
103
|
links += `<a href="#${noteDomPrefix}-ref${n}${suffix}" class="${noteDomPrefix}-backlink" role="doc-backlink">${opt.backLabelBra}${displayPrefix}${n}${suffix}${opt.backLabelKet}</a>`
|
|
105
104
|
}
|
|
@@ -110,7 +109,7 @@ const render_footnote_anchor = (tokens, idx, opt, env) => {
|
|
|
110
109
|
return `<span class="${noteDomPrefix}-label">${opt.backLabelBra}${displayPrefix}${n}${opt.backLabelKet}</span> `
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
if (
|
|
112
|
+
if (count > 1) {
|
|
114
113
|
return `<a href="#${noteDomPrefix}-ref${n}-a" class="${noteDomPrefix}-backlink" role="doc-backlink">${opt.backLabelBra}${displayPrefix}${n}${opt.backLabelKet}</a> `
|
|
115
114
|
}
|
|
116
115
|
|
|
@@ -199,7 +198,7 @@ const footnote_plugin = (md, option) =>{
|
|
|
199
198
|
fn.refs[':' + label] = id
|
|
200
199
|
|
|
201
200
|
const token = new state.Token('footnote_open', '', 1)
|
|
202
|
-
token.meta = { id,
|
|
201
|
+
token.meta = { id, isEndnote }
|
|
203
202
|
token.level = state.level++
|
|
204
203
|
state.tokens.push(token)
|
|
205
204
|
fn.positions.push(state.tokens.length - 1)
|
|
@@ -266,14 +265,13 @@ const footnote_plugin = (md, option) =>{
|
|
|
266
265
|
}
|
|
267
266
|
|
|
268
267
|
let pos = start + 2
|
|
269
|
-
|
|
270
|
-
for (; pos < posMax && !found; pos++) {
|
|
268
|
+
for (; pos < posMax; pos++) {
|
|
271
269
|
const ch = src.charCodeAt(pos)
|
|
270
|
+
if (ch === 0x5D /* ] */) break
|
|
272
271
|
if (ch === 0x20 || ch === 0x0A) { return false; } // space or linebreak
|
|
273
|
-
if (ch === 0x5D /* ] */) { found = true; break; }
|
|
274
272
|
}
|
|
275
273
|
|
|
276
|
-
if (
|
|
274
|
+
if (pos >= posMax || pos === start + 2) { return false; }
|
|
277
275
|
pos++; // pos set next ']' position.
|
|
278
276
|
|
|
279
277
|
const label = src.slice(start + 2, pos - 1)
|
|
@@ -285,16 +283,11 @@ const footnote_plugin = (md, option) =>{
|
|
|
285
283
|
if (!silent) {
|
|
286
284
|
const fn = resolved.env
|
|
287
285
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const footnoteId = fn.list.length
|
|
291
|
-
fn.list[footnoteId] = { label, count: 0 }
|
|
292
|
-
|
|
293
|
-
fn.totalCounts = fn.totalCounts || {}
|
|
286
|
+
fn.totalCounts = fn.totalCounts || []
|
|
294
287
|
fn.totalCounts[resolved.id] = (fn.totalCounts[resolved.id] || 0) + 1
|
|
295
288
|
|
|
296
289
|
const token = state.push('footnote_ref', '', 0)
|
|
297
|
-
token.meta = { id: resolved.id,
|
|
290
|
+
token.meta = { id: resolved.id, isEndnote: resolved.isEndnote }
|
|
298
291
|
}
|
|
299
292
|
|
|
300
293
|
state.pos = pos
|
|
@@ -306,7 +299,7 @@ const footnote_plugin = (md, option) =>{
|
|
|
306
299
|
const tokens = state.tokens
|
|
307
300
|
const createAnchorToken = (id, isEndnote) => {
|
|
308
301
|
const aToken = new state.Token('footnote_anchor', '', 0)
|
|
309
|
-
aToken.meta = { id,
|
|
302
|
+
aToken.meta = { id, isEndnote }
|
|
310
303
|
return aToken
|
|
311
304
|
}
|
|
312
305
|
|
|
@@ -314,6 +307,30 @@ const footnote_plugin = (md, option) =>{
|
|
|
314
307
|
const positions = notes && notes.positions
|
|
315
308
|
if (!positions || positions.length === 0) { return; }
|
|
316
309
|
|
|
310
|
+
if (opt.afterBacklink) {
|
|
311
|
+
const noteDomPrefix = isEndnote ? ENDNOTE_DOM_PREFIX : 'fn'
|
|
312
|
+
const totalCounts = notes.totalCounts
|
|
313
|
+
for (let j = 0, len = positions.length; j < len; ++j) {
|
|
314
|
+
const posOpen = positions[j]
|
|
315
|
+
if (posOpen + 2 >= tokens.length) continue
|
|
316
|
+
|
|
317
|
+
const t1 = tokens[posOpen + 1]
|
|
318
|
+
if (t1.type !== 'paragraph_open') continue
|
|
319
|
+
|
|
320
|
+
const t2 = tokens[posOpen + 2]
|
|
321
|
+
if (t2.type !== 'inline') continue
|
|
322
|
+
|
|
323
|
+
const t0 = tokens[posOpen]
|
|
324
|
+
const id = t0.meta.id
|
|
325
|
+
|
|
326
|
+
t2.children.unshift(createAnchorToken(id, isEndnote))
|
|
327
|
+
const n = id + 1
|
|
328
|
+
const counts = totalCounts && totalCounts[id]
|
|
329
|
+
t2.children.push(createAfterBackLinkToken(state, counts, n, opt, noteDomPrefix, isEndnote))
|
|
330
|
+
}
|
|
331
|
+
return
|
|
332
|
+
}
|
|
333
|
+
|
|
317
334
|
for (let j = 0, len = positions.length; j < len; ++j) {
|
|
318
335
|
const posOpen = positions[j]
|
|
319
336
|
if (posOpen + 2 >= tokens.length) continue
|
|
@@ -326,14 +343,8 @@ const footnote_plugin = (md, option) =>{
|
|
|
326
343
|
|
|
327
344
|
const t0 = tokens[posOpen]
|
|
328
345
|
const id = t0.meta.id
|
|
329
|
-
const noteDomPrefix = isEndnote ? ENDNOTE_DOM_PREFIX : 'fn'
|
|
330
346
|
|
|
331
347
|
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
|
-
}
|
|
337
348
|
}
|
|
338
349
|
}
|
|
339
350
|
|
|
@@ -348,27 +359,31 @@ const footnote_plugin = (md, option) =>{
|
|
|
348
359
|
}
|
|
349
360
|
|
|
350
361
|
const tokens = state.tokens
|
|
351
|
-
const
|
|
362
|
+
const endnoteTokens = []
|
|
352
363
|
|
|
353
|
-
|
|
364
|
+
let write = 0
|
|
365
|
+
let i = 0
|
|
366
|
+
while (i < tokens.length) {
|
|
354
367
|
const token = tokens[i]
|
|
355
|
-
if (token.type
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
368
|
+
if (token.type === 'footnote_open' && token.meta && token.meta.isEndnote) {
|
|
369
|
+
endnoteTokens.push(token)
|
|
370
|
+
i++
|
|
371
|
+
while (i < tokens.length) {
|
|
372
|
+
const t = tokens[i]
|
|
373
|
+
endnoteTokens.push(t)
|
|
374
|
+
if (t.type === 'footnote_close' && t.meta && t.meta.isEndnote) {
|
|
375
|
+
i++
|
|
376
|
+
break
|
|
377
|
+
}
|
|
378
|
+
i++
|
|
363
379
|
}
|
|
364
|
-
|
|
380
|
+
continue
|
|
365
381
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
i--
|
|
382
|
+
tokens[write++] = token
|
|
383
|
+
i++
|
|
369
384
|
}
|
|
370
385
|
|
|
371
|
-
if (
|
|
386
|
+
if (endnoteTokens.length === 0) return
|
|
372
387
|
|
|
373
388
|
const sectionOpen = new state.Token('html_block', '', 0)
|
|
374
389
|
const attrs = []
|
|
@@ -388,10 +403,9 @@ const footnote_plugin = (md, option) =>{
|
|
|
388
403
|
const sectionClose = new state.Token('html_block', '', 0)
|
|
389
404
|
sectionClose.content = '</ol>\n</section>\n'
|
|
390
405
|
|
|
406
|
+
tokens.length = write
|
|
391
407
|
tokens.push(sectionOpen)
|
|
392
|
-
|
|
393
|
-
tokens.push(...block)
|
|
394
|
-
})
|
|
408
|
+
for (let j = 0; j < endnoteTokens.length; j++) tokens.push(endnoteTokens[j])
|
|
395
409
|
tokens.push(sectionClose)
|
|
396
410
|
}
|
|
397
411
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peaceroad/markdown-it-footnote-here",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
7
|
+
"files" : [
|
|
8
|
+
"index.js",
|
|
9
|
+
"LICENSE",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
7
12
|
"author": "peaceroad <peaceroad@gmail.com>",
|
|
8
13
|
"homepage": "https://github.com/peaceroad/markdown-it-footnote-here#readme",
|
|
9
14
|
"repository": {
|
package/AGENTS.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
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.
|