comark-footnotes 0.0.0-stage → 0.7.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/LICENSE +41 -0
- package/README.md +46 -2
- package/dist/index.d.mts +23 -0
- package/dist/index.mjs +306 -0
- package/package.json +47 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 onmax
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
The block and inline rules in src/index.ts are ported from markdown-it-footnote,
|
|
26
|
+
https://github.com/markdown-it/markdown-it-footnote, under its license:
|
|
27
|
+
|
|
28
|
+
Copyright (c) 2014-2015 Vitaly Puzrin, Alex Kocharin.
|
|
29
|
+
|
|
30
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
31
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
|
32
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
33
|
+
persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
34
|
+
|
|
35
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
|
36
|
+
Software.
|
|
37
|
+
|
|
38
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
39
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
40
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
41
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# comark-footnotes
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://npmx.dev/package/comark-footnotes)
|
|
4
|
+
[](https://npmx.dev/package/comark-footnotes)
|
|
5
|
+
[](https://npmx.dev/package/comark-footnotes)
|
|
6
|
+
[](https://comark-ui.onmax.me/footnotes/)
|
|
7
|
+
|
|
8
|
+
Footnotes in [Comark](https://comark.dev), in place of `comark/plugins/footnotes`: `[^1]` references, `[^1]:` definitions that hold any Markdown over several lines and blocks, and `^[inline notes]`. A parse plugin with no components, so it works in any renderer.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
pnpm add comark comark-footnotes
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { parseMarkdown } from "comark";
|
|
16
|
+
import footnotes from "comark-footnotes";
|
|
17
|
+
|
|
18
|
+
const document = await parseMarkdown(markdown, {
|
|
19
|
+
plugins: [footnotes()],
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```md
|
|
24
|
+
The deploy waits when the roof holds more than 12 pigeons[^count], unless it's a hotfix^[Two approvals.].
|
|
25
|
+
|
|
26
|
+
[^count]: Counted once, from the window,
|
|
27
|
+
at 9:00.
|
|
28
|
+
|
|
29
|
+
Pigeons that fly off mid-count still count.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Notes are numbered in the order they're first referenced and listed in a section at the end. It is named `footnotes`, like Comark's plugin, and takes the same options (`label`, `hr`, `backRef`) and writes the same nodes, so it replaces it. When a plugin list holds both, Comark keeps the first.
|
|
33
|
+
|
|
34
|
+
Compared with Comark's plugin, a definition inside fenced code stays in the code, Markdown in a note renders, and a note can run over several lines and blocks. A reference used twice gets its own id, `fnref-label-2`.
|
|
35
|
+
|
|
36
|
+
## Issues
|
|
37
|
+
|
|
38
|
+
A reference with no definition stays as text, and a definition nothing references is dropped. Each adds a sentence to `meta.issues`:
|
|
39
|
+
|
|
40
|
+
```txt
|
|
41
|
+
footnotes: [^gone] has no definition; add a "[^gone]: …" line, or remove the reference.
|
|
42
|
+
footnotes: [^spare]: is never referenced; add [^spare] where it applies, or remove the definition.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Credits
|
|
46
|
+
|
|
47
|
+
The parser is ported from [markdown-it-footnote](https://github.com/markdown-it/markdown-it-footnote) by Vitaly Puzrin and Alex Kocharin, MIT.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
export type FootnotesMeta = {
|
|
3
|
+
/** One readable sentence per problem with a footnote. Empty when the document has none. Other plugins add theirs too. */
|
|
4
|
+
issues: string[];
|
|
5
|
+
/** The comark-linter rule of each issue, by its sentence, so the linter reports it under its own code. */
|
|
6
|
+
issueRules: Record<string, string>;
|
|
7
|
+
};
|
|
8
|
+
export interface FootnotesOptions {
|
|
9
|
+
/** The section's heading. Default `"Footnotes"`; an empty string leaves the heading out. */
|
|
10
|
+
label?: string;
|
|
11
|
+
/** Whether a horizontal rule comes before the section. Default `true`. */
|
|
12
|
+
hr?: boolean;
|
|
13
|
+
/** The link from a note back to its reference. Default `"↩"`. */
|
|
14
|
+
backRef?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Footnotes: `[^label]` references with `[^label]: …` definitions, which can run over indented lines and hold any
|
|
18
|
+
* blocks, and `^[inline notes]`. They're numbered in the order they're first referenced and listed at the end.
|
|
19
|
+
* It is named `footnotes`, so it takes the place of `comark/plugins/footnotes`, with the same nodes.
|
|
20
|
+
*/
|
|
21
|
+
declare const _default: import("comark").ComarkPluginFactory<FootnotesOptions, FootnotesMeta, {}>;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { _default as default };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { defineComarkPlugin } from "comark";
|
|
2
|
+
//#region ../../shared/markdown-it.ts
|
|
3
|
+
/**
|
|
4
|
+
* An inline element and a block element, by the prefix of their `_open` and `_close` types. Not `mdc_block_slot` for
|
|
5
|
+
* blocks: directly inside a `::component` Comark reads those as named slots and splits the block apart.
|
|
6
|
+
*/
|
|
7
|
+
const SPAN = "mdc_inline_span";
|
|
8
|
+
const BLOCK = "blockquote";
|
|
9
|
+
/**
|
|
10
|
+
* Adds a rule before or after the first of `anchors` the parser has, else last. Comark's own rules exist only with
|
|
11
|
+
* its default plugins, and markdown-exit throws on a missing anchor, which would make every parse throw.
|
|
12
|
+
*/
|
|
13
|
+
function addRule(ruler, place, anchors, name, fn, options) {
|
|
14
|
+
for (const anchor of anchors) try {
|
|
15
|
+
ruler[place](anchor, name, fn, options);
|
|
16
|
+
return;
|
|
17
|
+
} catch {}
|
|
18
|
+
ruler.push(name, fn, options);
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/index.ts
|
|
22
|
+
const DEF = "cfn-def:";
|
|
23
|
+
const REF = "cfn-ref:";
|
|
24
|
+
const NOTE = "cfn-note";
|
|
25
|
+
/** `[^label]: text`, with the lines indented under it, as a block that holds the definition's own blocks. */
|
|
26
|
+
function definition(state, startLine, endLine, silent) {
|
|
27
|
+
const start = (state.bMarks[startLine] ?? 0) + (state.tShift[startLine] ?? 0);
|
|
28
|
+
const max = state.eMarks[startLine] ?? 0;
|
|
29
|
+
if (start + 4 > max) return false;
|
|
30
|
+
if (state.src.charCodeAt(start) !== 91 || state.src.charCodeAt(start + 1) !== 94) return false;
|
|
31
|
+
let pos = start + 2;
|
|
32
|
+
for (; pos < max; pos++) {
|
|
33
|
+
const ch = state.src.charCodeAt(pos);
|
|
34
|
+
if (ch === 32) return false;
|
|
35
|
+
if (ch === 93) break;
|
|
36
|
+
}
|
|
37
|
+
if (pos === start + 2) return false;
|
|
38
|
+
if (pos + 1 >= max || state.src.charCodeAt(++pos) !== 58) return false;
|
|
39
|
+
if (silent) return true;
|
|
40
|
+
pos++;
|
|
41
|
+
const label = state.src.slice(start + 2, pos - 2);
|
|
42
|
+
const open = state.push(`${BLOCK}_open`, DEF + label, 1);
|
|
43
|
+
open.map = [startLine, 0];
|
|
44
|
+
const saved = {
|
|
45
|
+
bMark: state.bMarks[startLine] ?? 0,
|
|
46
|
+
tShift: state.tShift[startLine] ?? 0,
|
|
47
|
+
sCount: state.sCount[startLine] ?? 0,
|
|
48
|
+
parentType: state.parentType
|
|
49
|
+
};
|
|
50
|
+
const afterColon = pos;
|
|
51
|
+
const initial = saved.sCount + pos - (saved.bMark + saved.tShift);
|
|
52
|
+
let offset = initial;
|
|
53
|
+
while (pos < max) {
|
|
54
|
+
const ch = state.src.charCodeAt(pos);
|
|
55
|
+
if (ch === 9) offset += 4 - offset % 4;
|
|
56
|
+
else if (ch === 32) offset++;
|
|
57
|
+
else break;
|
|
58
|
+
pos++;
|
|
59
|
+
}
|
|
60
|
+
state.tShift[startLine] = pos - afterColon;
|
|
61
|
+
state.sCount[startLine] = offset - initial;
|
|
62
|
+
state.bMarks[startLine] = afterColon;
|
|
63
|
+
state.blkIndent += 4;
|
|
64
|
+
state.parentType = "footnote";
|
|
65
|
+
if ((state.sCount[startLine] ?? 0) < state.blkIndent) state.sCount[startLine] = (state.sCount[startLine] ?? 0) + state.blkIndent;
|
|
66
|
+
state.md.block.tokenize(state, startLine, endLine);
|
|
67
|
+
state.parentType = saved.parentType;
|
|
68
|
+
state.blkIndent -= 4;
|
|
69
|
+
state.tShift[startLine] = saved.tShift;
|
|
70
|
+
state.sCount[startLine] = saved.sCount;
|
|
71
|
+
state.bMarks[startLine] = saved.bMark;
|
|
72
|
+
open.map[1] = state.line;
|
|
73
|
+
state.push(`${BLOCK}_close`, DEF + label, -1);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
/** `[^label]`, as an empty span with the label. Whether a definition exists is decided in `post`, which reports it. */
|
|
77
|
+
function reference(state, silent) {
|
|
78
|
+
const start = state.pos;
|
|
79
|
+
const max = state.posMax;
|
|
80
|
+
if (start + 3 > max) return false;
|
|
81
|
+
if (state.src.charCodeAt(start) !== 91 || state.src.charCodeAt(start + 1) !== 94) return false;
|
|
82
|
+
let pos = start + 2;
|
|
83
|
+
for (; pos < max; pos++) {
|
|
84
|
+
const ch = state.src.charCodeAt(pos);
|
|
85
|
+
if (ch === 32 || ch === 10) return false;
|
|
86
|
+
if (ch === 93) break;
|
|
87
|
+
}
|
|
88
|
+
if (pos === start + 2 || pos >= max) return false;
|
|
89
|
+
const end = skipIncomplete(state, pos + 1);
|
|
90
|
+
const next = state.src.charCodeAt(end);
|
|
91
|
+
if (end === pos + 1 && (next === 40 || next === 91)) return false;
|
|
92
|
+
if (!silent) {
|
|
93
|
+
const tag = REF + state.src.slice(start + 2, pos);
|
|
94
|
+
state.push(`${SPAN}_open`, tag, 1);
|
|
95
|
+
state.push(`${SPAN}_close`, tag, -1);
|
|
96
|
+
}
|
|
97
|
+
state.pos = end;
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
/** `^[text]`, a note written where it applies, as a span holding the note's inline content. */
|
|
101
|
+
function inlineNote(state, silent) {
|
|
102
|
+
const start = state.pos;
|
|
103
|
+
const max = state.posMax;
|
|
104
|
+
if (start + 2 >= max) return false;
|
|
105
|
+
if (state.src.charCodeAt(start) !== 94 || state.src.charCodeAt(start + 1) !== 91) return false;
|
|
106
|
+
const end = state.md.helpers.parseLinkLabel(state, start + 1);
|
|
107
|
+
if (end < 0) return false;
|
|
108
|
+
if (!silent) {
|
|
109
|
+
state.push(`${SPAN}_open`, NOTE, 1);
|
|
110
|
+
state.pos = start + 2;
|
|
111
|
+
state.posMax = end;
|
|
112
|
+
state.md.inline.tokenize(state);
|
|
113
|
+
state.push(`${SPAN}_close`, NOTE, -1);
|
|
114
|
+
}
|
|
115
|
+
state.pos = skipIncomplete(state, end + 1);
|
|
116
|
+
state.posMax = max;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
const INCOMPLETE = "(comark:incomplete-link)";
|
|
120
|
+
const skipIncomplete = (state, pos) => state.src.startsWith(INCOMPLETE, pos) ? pos + 24 : pos;
|
|
121
|
+
const markdownIt = (md) => {
|
|
122
|
+
addRule(md.block.ruler, "before", ["reference"], "footnote_def", definition, { alt: ["paragraph", "reference"] });
|
|
123
|
+
addRule(md.inline.ruler, "before", ["comark_inline_span", "link"], "footnote_inline", inlineNote);
|
|
124
|
+
addRule(md.inline.ruler, "before", ["comark_inline_span", "link"], "footnote_ref", reference);
|
|
125
|
+
};
|
|
126
|
+
const sanitizeLabel = (label) => label.replace(/[^a-zA-Z0-9_-]/g, (char) => `-${char.charCodeAt(0).toString(16)}-`);
|
|
127
|
+
const isElement = (node) => Array.isArray(node) && node[0] !== null;
|
|
128
|
+
/** The label after a marker's tag, or nothing for another element. */
|
|
129
|
+
const labelAfter = (marker, [tag]) => tag.startsWith(marker) ? tag.slice(marker.length) : void 0;
|
|
130
|
+
const childrenOf = ([, , ...children]) => children;
|
|
131
|
+
const built = /* @__PURE__ */ new WeakMap();
|
|
132
|
+
const pending = /* @__PURE__ */ new WeakMap();
|
|
133
|
+
/** Takes every definition out of the tree, wherever it sits, keeping the first for each label and noting the repeats. */
|
|
134
|
+
function takeDefinitions(nodes, start, found, repeated) {
|
|
135
|
+
for (let index = nodes.length - 1; index >= start; index--) {
|
|
136
|
+
const node = nodes[index];
|
|
137
|
+
if (!isElement(node)) continue;
|
|
138
|
+
const label = labelAfter(DEF, node);
|
|
139
|
+
if (label !== void 0) {
|
|
140
|
+
nodes.splice(index, 1);
|
|
141
|
+
const content = childrenOf(node);
|
|
142
|
+
takeDefinitions(content, 0, found, repeated);
|
|
143
|
+
if (found.has(label)) repeated.add(label);
|
|
144
|
+
found.set(label, content);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
takeDefinitions(node, 2, found, repeated);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Footnotes: `[^label]` references with `[^label]: …` definitions, which can run over indented lines and hold any
|
|
152
|
+
* blocks, and `^[inline notes]`. They're numbered in the order they're first referenced and listed at the end.
|
|
153
|
+
* It is named `footnotes`, so it takes the place of `comark/plugins/footnotes`, with the same nodes.
|
|
154
|
+
*/
|
|
155
|
+
var src_default = defineComarkPlugin((options) => {
|
|
156
|
+
const { label = "Footnotes", hr = true, backRef = "↩" } = options ?? {};
|
|
157
|
+
return {
|
|
158
|
+
name: "footnotes",
|
|
159
|
+
markdownItPlugins: [markdownIt],
|
|
160
|
+
post(state) {
|
|
161
|
+
const { tree } = state;
|
|
162
|
+
const issues = [];
|
|
163
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
164
|
+
const repeated = /* @__PURE__ */ new Set();
|
|
165
|
+
takeDefinitions(tree.nodes, 0, definitions, repeated);
|
|
166
|
+
const notes = /* @__PURE__ */ new Map();
|
|
167
|
+
const missing = /* @__PURE__ */ new Set();
|
|
168
|
+
const ids = new Set([...definitions.keys()].map(sanitizeLabel));
|
|
169
|
+
let inline = 0;
|
|
170
|
+
const autoLabel = () => {
|
|
171
|
+
let next;
|
|
172
|
+
do
|
|
173
|
+
next = `note-${++inline}`;
|
|
174
|
+
while (definitions.has(next) || notes.has(next) || ids.has(sanitizeLabel(next)));
|
|
175
|
+
ids.add(sanitizeLabel(next));
|
|
176
|
+
return next;
|
|
177
|
+
};
|
|
178
|
+
const refTo = (key, content) => {
|
|
179
|
+
let note = notes.get(key);
|
|
180
|
+
if (!note) {
|
|
181
|
+
note = {
|
|
182
|
+
label: key,
|
|
183
|
+
id: sanitizeLabel(key),
|
|
184
|
+
content: content(),
|
|
185
|
+
refs: 0
|
|
186
|
+
};
|
|
187
|
+
notes.set(key, note);
|
|
188
|
+
}
|
|
189
|
+
const count = ++note.refs;
|
|
190
|
+
const sup = [
|
|
191
|
+
"sup",
|
|
192
|
+
{ class: "footnote-ref" },
|
|
193
|
+
[
|
|
194
|
+
"a",
|
|
195
|
+
{
|
|
196
|
+
href: `#fn-${note.id}`,
|
|
197
|
+
id: count === 1 ? `fnref-${note.id}` : `fnref-${note.id}-${count}`
|
|
198
|
+
},
|
|
199
|
+
`[${[...notes.keys()].indexOf(key) + 1}]`
|
|
200
|
+
]
|
|
201
|
+
];
|
|
202
|
+
built.set(sup, {
|
|
203
|
+
label: key,
|
|
204
|
+
content: note.content
|
|
205
|
+
});
|
|
206
|
+
return sup;
|
|
207
|
+
};
|
|
208
|
+
const replace = (nodes, start) => {
|
|
209
|
+
for (let index = start; index < nodes.length; index++) {
|
|
210
|
+
const node = nodes[index];
|
|
211
|
+
const waiting = pending.get(nodes)?.get(index);
|
|
212
|
+
if (waiting !== void 0 && node === `[^${waiting}]`) {
|
|
213
|
+
const content = definitions.get(waiting);
|
|
214
|
+
if (content) {
|
|
215
|
+
nodes[index] = refTo(waiting, () => content);
|
|
216
|
+
pending.get(nodes)?.delete(index);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
missing.add(waiting);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (!isElement(node)) continue;
|
|
223
|
+
const [tag] = node;
|
|
224
|
+
const key = labelAfter(REF, node);
|
|
225
|
+
const previous = tag === "sup" ? built.get(node) : void 0;
|
|
226
|
+
if (key !== void 0) {
|
|
227
|
+
const content = definitions.get(key);
|
|
228
|
+
if (content) nodes[index] = refTo(key, () => content);
|
|
229
|
+
else {
|
|
230
|
+
nodes[index] = `[^${key}]`;
|
|
231
|
+
let entries = pending.get(nodes);
|
|
232
|
+
if (!entries) pending.set(nodes, entries = /* @__PURE__ */ new Map());
|
|
233
|
+
entries.set(index, key);
|
|
234
|
+
missing.add(key);
|
|
235
|
+
}
|
|
236
|
+
} else if (tag === NOTE) {
|
|
237
|
+
const content = childrenOf(node);
|
|
238
|
+
nodes[index] = refTo(autoLabel(), () => content);
|
|
239
|
+
} else if (previous) nodes[index] = refTo(previous.label, () => previous.content);
|
|
240
|
+
if (nodes[index] !== node) continue;
|
|
241
|
+
replace(node, 2);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
replace(tree.nodes, 0);
|
|
245
|
+
for (const note of notes.values()) replace(note.content, 0);
|
|
246
|
+
for (const key of missing) issues.push({
|
|
247
|
+
rule: "comark/footnotes/missing-definition",
|
|
248
|
+
message: `footnotes: [^${key}] has no definition; add a "[^${key}]: …" line, or remove the reference.`
|
|
249
|
+
});
|
|
250
|
+
for (const key of definitions.keys()) if (!notes.has(key)) issues.push({
|
|
251
|
+
rule: "comark/footnotes/unused-definition",
|
|
252
|
+
message: `footnotes: [^${key}]: is never referenced; add [^${key}] where it applies, or remove the definition.`
|
|
253
|
+
});
|
|
254
|
+
for (const key of repeated) issues.push({
|
|
255
|
+
rule: "comark/footnotes/duplicate-definition",
|
|
256
|
+
message: `footnotes: [^${key}]: is defined more than once, and only the first shows; give the others their own labels, or remove them.`
|
|
257
|
+
});
|
|
258
|
+
if (issues.length) {
|
|
259
|
+
tree.meta.issues = [...tree.meta.issues ?? [], ...issues.map((issue) => issue.message)];
|
|
260
|
+
tree.meta.issueRules = {
|
|
261
|
+
...tree.meta.issueRules,
|
|
262
|
+
...Object.fromEntries(issues.map((issue) => [issue.message, issue.rule]))
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (!notes.size) return;
|
|
266
|
+
const items = [...notes.values()].map((note) => {
|
|
267
|
+
const backrefs = Array.from({ length: note.refs }, (_, index) => [" ", [
|
|
268
|
+
"a",
|
|
269
|
+
{
|
|
270
|
+
href: index ? `#fnref-${note.id}-${index + 1}` : `#fnref-${note.id}`,
|
|
271
|
+
class: "footnote-backref"
|
|
272
|
+
},
|
|
273
|
+
backRef
|
|
274
|
+
]]).flat();
|
|
275
|
+
const content = [...note.content];
|
|
276
|
+
const last = content.at(-1);
|
|
277
|
+
if (isElement(last) && last[0] === "p") content[content.length - 1] = [...last, ...backrefs];
|
|
278
|
+
else content.push(...backrefs);
|
|
279
|
+
return [
|
|
280
|
+
"li",
|
|
281
|
+
{ id: `fn-${note.id}` },
|
|
282
|
+
...content
|
|
283
|
+
];
|
|
284
|
+
});
|
|
285
|
+
const rule = hr ? [["hr", {}]] : [];
|
|
286
|
+
const heading = label ? [[
|
|
287
|
+
"h2",
|
|
288
|
+
{ id: "footnotes" },
|
|
289
|
+
label
|
|
290
|
+
]] : [];
|
|
291
|
+
tree.nodes.push([
|
|
292
|
+
"section",
|
|
293
|
+
{ class: "footnotes" },
|
|
294
|
+
...rule,
|
|
295
|
+
...heading,
|
|
296
|
+
[
|
|
297
|
+
"ol",
|
|
298
|
+
{ class: "footnotes-list" },
|
|
299
|
+
...items
|
|
300
|
+
]
|
|
301
|
+
]);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
//#endregion
|
|
306
|
+
export { src_default as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "comark-footnotes",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.7.1",
|
|
4
|
+
"description": "Footnotes in Comark: [^1] references, [^1]: definitions over several lines and blocks, and ^[inline notes], in place of comark/plugins/footnotes.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"comark",
|
|
7
|
+
"endnotes",
|
|
8
|
+
"footnotes",
|
|
9
|
+
"markdown",
|
|
10
|
+
"mdc",
|
|
11
|
+
"plugin"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://comark-ui.onmax.me/footnotes",
|
|
14
|
+
"bugs": "https://github.com/onmax/comark-ui/issues",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "onmax",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/onmax/comark-ui.git",
|
|
20
|
+
"directory": "packages/footnotes"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"exports": {
|
|
28
|
+
".": "./dist/index.mjs",
|
|
29
|
+
"./package.json": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"provenance": true
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@comark/html": "^0.8.1",
|
|
37
|
+
"comark": "^0.8.1"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"comark": "^0.8.0"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=22.12.0"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsdown",
|
|
47
|
+
"dev": "tsdown --watch",
|
|
48
|
+
"doctor": "vite-doctor . --no-cache --max-warnings 0 --format text"
|
|
49
|
+
}
|
|
6
50
|
}
|