@incremark/core 0.1.1 → 0.2.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/dist/detector/index.d.ts +1 -1
- package/dist/detector/index.js +9 -1
- package/dist/detector/index.js.map +1 -1
- package/dist/index-3rgnFbip.d.ts +396 -0
- package/dist/index.d.ts +53 -2
- package/dist/index.js +1564 -35
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +6 -1
- package/dist/utils/index.js +7 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +7 -1
- package/src/__tests__/footnote.test.ts +214 -0
- package/src/detector/index.ts +30 -0
- package/src/extensions/html-extension/index.test.ts +409 -0
- package/src/extensions/html-extension/index.ts +792 -0
- package/src/extensions/micromark-gfm-footnote-incremental.ts +275 -0
- package/src/extensions/micromark-reference-extension.ts +724 -0
- package/src/index.ts +33 -0
- package/src/parser/IncremarkParser.footnote.test.ts +334 -0
- package/src/parser/IncremarkParser.ts +374 -14
- package/src/types/index.ts +29 -1
- package/src/utils/index.ts +9 -0
- package/dist/index-ChNeZ1wr.d.ts +0 -217
package/dist/index.js
CHANGED
|
@@ -1,9 +1,1229 @@
|
|
|
1
1
|
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
2
2
|
import { gfmFromMarkdown } from 'mdast-util-gfm';
|
|
3
3
|
import { gfm } from 'micromark-extension-gfm';
|
|
4
|
+
import { codes, constants, types } from 'micromark-util-symbol';
|
|
5
|
+
import { markdownLineEndingOrSpace, markdownSpace, markdownLineEnding } from 'micromark-util-character';
|
|
6
|
+
import { factoryDestination } from 'micromark-factory-destination';
|
|
7
|
+
import { factoryTitle } from 'micromark-factory-title';
|
|
8
|
+
import { factoryLabel } from 'micromark-factory-label';
|
|
9
|
+
import { factoryWhitespace } from 'micromark-factory-whitespace';
|
|
4
10
|
|
|
5
11
|
// src/parser/IncremarkParser.ts
|
|
6
12
|
|
|
13
|
+
// ../../node_modules/devlop/lib/default.js
|
|
14
|
+
function ok() {
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ../../node_modules/micromark-util-normalize-identifier/index.js
|
|
18
|
+
function normalizeIdentifier(value) {
|
|
19
|
+
return value.replace(/[\t\n\r ]+/g, " ").replace(/^ | $/g, "").toLowerCase().toUpperCase();
|
|
20
|
+
}
|
|
21
|
+
function enterFootnoteCallString() {
|
|
22
|
+
this.buffer();
|
|
23
|
+
}
|
|
24
|
+
function enterFootnoteCall(token) {
|
|
25
|
+
this.enter({ type: "footnoteReference", identifier: "", label: "" }, token);
|
|
26
|
+
}
|
|
27
|
+
function enterFootnoteDefinitionLabelString() {
|
|
28
|
+
this.buffer();
|
|
29
|
+
}
|
|
30
|
+
function enterFootnoteDefinition(token) {
|
|
31
|
+
this.enter(
|
|
32
|
+
{ type: "footnoteDefinition", identifier: "", label: "", children: [] },
|
|
33
|
+
token
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function exitFootnoteCallString(token) {
|
|
37
|
+
const label = this.resume();
|
|
38
|
+
const node = this.stack[this.stack.length - 1];
|
|
39
|
+
ok(node.type === "footnoteReference");
|
|
40
|
+
node.identifier = normalizeIdentifier(
|
|
41
|
+
this.sliceSerialize(token)
|
|
42
|
+
).toLowerCase();
|
|
43
|
+
node.label = label;
|
|
44
|
+
}
|
|
45
|
+
function exitFootnoteCall(token) {
|
|
46
|
+
this.exit(token);
|
|
47
|
+
}
|
|
48
|
+
function exitFootnoteDefinitionLabelString(token) {
|
|
49
|
+
const label = this.resume();
|
|
50
|
+
const node = this.stack[this.stack.length - 1];
|
|
51
|
+
ok(node.type === "footnoteDefinition");
|
|
52
|
+
node.identifier = normalizeIdentifier(
|
|
53
|
+
this.sliceSerialize(token)
|
|
54
|
+
).toLowerCase();
|
|
55
|
+
node.label = label;
|
|
56
|
+
}
|
|
57
|
+
function exitFootnoteDefinition(token) {
|
|
58
|
+
this.exit(token);
|
|
59
|
+
}
|
|
60
|
+
function gfmFootnoteFromMarkdown() {
|
|
61
|
+
return {
|
|
62
|
+
enter: {
|
|
63
|
+
gfmFootnoteCallString: enterFootnoteCallString,
|
|
64
|
+
gfmFootnoteCall: enterFootnoteCall,
|
|
65
|
+
gfmFootnoteDefinitionLabelString: enterFootnoteDefinitionLabelString,
|
|
66
|
+
gfmFootnoteDefinition: enterFootnoteDefinition
|
|
67
|
+
},
|
|
68
|
+
exit: {
|
|
69
|
+
gfmFootnoteCallString: exitFootnoteCallString,
|
|
70
|
+
gfmFootnoteCall: exitFootnoteCall,
|
|
71
|
+
gfmFootnoteDefinitionLabelString: exitFootnoteDefinitionLabelString,
|
|
72
|
+
gfmFootnoteDefinition: exitFootnoteDefinition
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/extensions/html-extension/index.ts
|
|
78
|
+
var DEFAULT_TAG_BLACKLIST = [
|
|
79
|
+
"script",
|
|
80
|
+
"style",
|
|
81
|
+
"iframe",
|
|
82
|
+
"object",
|
|
83
|
+
"embed",
|
|
84
|
+
"form",
|
|
85
|
+
"input",
|
|
86
|
+
"button",
|
|
87
|
+
"textarea",
|
|
88
|
+
"select",
|
|
89
|
+
"meta",
|
|
90
|
+
"link",
|
|
91
|
+
"base",
|
|
92
|
+
"frame",
|
|
93
|
+
"frameset",
|
|
94
|
+
"applet",
|
|
95
|
+
"noscript",
|
|
96
|
+
"template"
|
|
97
|
+
];
|
|
98
|
+
var DEFAULT_ATTR_BLACKLIST = [
|
|
99
|
+
// 事件属性通过正则匹配
|
|
100
|
+
"formaction",
|
|
101
|
+
"xlink:href",
|
|
102
|
+
"xmlns",
|
|
103
|
+
"srcdoc"
|
|
104
|
+
];
|
|
105
|
+
var DEFAULT_PROTOCOL_BLACKLIST = [
|
|
106
|
+
"javascript:",
|
|
107
|
+
"vbscript:",
|
|
108
|
+
"data:"
|
|
109
|
+
// 注意:data:image/ 会被特殊处理允许
|
|
110
|
+
];
|
|
111
|
+
var URL_ATTRS = ["href", "src", "action", "formaction", "poster", "background"];
|
|
112
|
+
var VOID_ELEMENTS = ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"];
|
|
113
|
+
function detectHtmlContentType(html) {
|
|
114
|
+
const trimmed = html.trim();
|
|
115
|
+
if (!trimmed) return "unknown";
|
|
116
|
+
if (!trimmed.startsWith("<")) return "unknown";
|
|
117
|
+
const closingMatch = trimmed.match(/^<\/([a-zA-Z][a-zA-Z0-9-]*)\s*>$/);
|
|
118
|
+
if (closingMatch) {
|
|
119
|
+
return "closing";
|
|
120
|
+
}
|
|
121
|
+
const singleTagMatch = trimmed.match(/^<([a-zA-Z][a-zA-Z0-9-]*)(\s[^]*?)?(\/?)>$/);
|
|
122
|
+
if (singleTagMatch) {
|
|
123
|
+
const [fullMatch, tagName, attrsString, selfClosingSlash] = singleTagMatch;
|
|
124
|
+
if (attrsString) {
|
|
125
|
+
let inQuote = "";
|
|
126
|
+
let hasUnquotedBracket = false;
|
|
127
|
+
for (let i = 0; i < attrsString.length; i++) {
|
|
128
|
+
const char = attrsString[i];
|
|
129
|
+
if (inQuote) {
|
|
130
|
+
if (char === inQuote) inQuote = "";
|
|
131
|
+
} else {
|
|
132
|
+
if (char === '"' || char === "'") inQuote = char;
|
|
133
|
+
else if (char === "<") {
|
|
134
|
+
hasUnquotedBracket = true;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (hasUnquotedBracket) {
|
|
140
|
+
return "fragment";
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const isSelfClosing = selfClosingSlash === "/" || VOID_ELEMENTS.includes(tagName.toLowerCase());
|
|
144
|
+
return isSelfClosing ? "self-closing" : "opening";
|
|
145
|
+
}
|
|
146
|
+
let bracketCount = 0;
|
|
147
|
+
for (const char of trimmed) {
|
|
148
|
+
if (char === "<") bracketCount++;
|
|
149
|
+
}
|
|
150
|
+
if (bracketCount > 1) {
|
|
151
|
+
return "fragment";
|
|
152
|
+
}
|
|
153
|
+
return "unknown";
|
|
154
|
+
}
|
|
155
|
+
function parseHtmlTag(html) {
|
|
156
|
+
const trimmed = html.trim();
|
|
157
|
+
const contentType = detectHtmlContentType(trimmed);
|
|
158
|
+
if (contentType !== "opening" && contentType !== "closing" && contentType !== "self-closing") {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (contentType === "closing") {
|
|
162
|
+
const match2 = trimmed.match(/^<\/([a-zA-Z][a-zA-Z0-9-]*)\s*>$/);
|
|
163
|
+
if (!match2) return null;
|
|
164
|
+
return {
|
|
165
|
+
tagName: match2[1].toLowerCase(),
|
|
166
|
+
attrs: {},
|
|
167
|
+
isClosing: true,
|
|
168
|
+
isSelfClosing: false,
|
|
169
|
+
rawHtml: html
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const match = trimmed.match(/^<([a-zA-Z][a-zA-Z0-9-]*)(\s[^]*?)?(\/?)>$/);
|
|
173
|
+
if (!match) return null;
|
|
174
|
+
const [, tagName, attrsString, selfClosingSlash] = match;
|
|
175
|
+
const isSelfClosing = selfClosingSlash === "/" || VOID_ELEMENTS.includes(tagName.toLowerCase());
|
|
176
|
+
const attrs = {};
|
|
177
|
+
if (attrsString) {
|
|
178
|
+
const attrRegex = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
179
|
+
let attrMatch;
|
|
180
|
+
while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
|
|
181
|
+
const [, name, doubleQuoted, singleQuoted, unquoted] = attrMatch;
|
|
182
|
+
const value = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
|
|
183
|
+
attrs[name.toLowerCase()] = decodeHtmlEntities(value);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
tagName: tagName.toLowerCase(),
|
|
188
|
+
attrs,
|
|
189
|
+
isClosing: false,
|
|
190
|
+
isSelfClosing,
|
|
191
|
+
rawHtml: html
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function decodeHtmlEntities(text) {
|
|
195
|
+
const entities = {
|
|
196
|
+
"&": "&",
|
|
197
|
+
"<": "<",
|
|
198
|
+
">": ">",
|
|
199
|
+
""": '"',
|
|
200
|
+
"'": "'",
|
|
201
|
+
"'": "'",
|
|
202
|
+
" ": " "
|
|
203
|
+
};
|
|
204
|
+
return text.replace(/&(?:#(\d+)|#x([a-fA-F0-9]+)|([a-zA-Z]+));/g, (match, dec, hex, name) => {
|
|
205
|
+
if (dec) return String.fromCharCode(parseInt(dec, 10));
|
|
206
|
+
if (hex) return String.fromCharCode(parseInt(hex, 16));
|
|
207
|
+
return entities[`&${name};`] || match;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function parseTagDirect(tag) {
|
|
211
|
+
const trimmed = tag.trim();
|
|
212
|
+
const closingMatch = trimmed.match(/^<\/([a-zA-Z][a-zA-Z0-9-]*)\s*>$/);
|
|
213
|
+
if (closingMatch) {
|
|
214
|
+
return {
|
|
215
|
+
tagName: closingMatch[1].toLowerCase(),
|
|
216
|
+
attrs: {},
|
|
217
|
+
isClosing: true,
|
|
218
|
+
isSelfClosing: false,
|
|
219
|
+
rawHtml: tag
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const openMatch = trimmed.match(/^<([a-zA-Z][a-zA-Z0-9-]*)([\s\S]*?)(\/?)>$/);
|
|
223
|
+
if (!openMatch) return null;
|
|
224
|
+
const [, tagName, attrsString, selfClosingSlash] = openMatch;
|
|
225
|
+
const isSelfClosing = selfClosingSlash === "/" || VOID_ELEMENTS.includes(tagName.toLowerCase());
|
|
226
|
+
const attrs = {};
|
|
227
|
+
if (attrsString) {
|
|
228
|
+
const attrRegex = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
229
|
+
let attrMatch;
|
|
230
|
+
while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
|
|
231
|
+
const [, name, doubleQuoted, singleQuoted, unquoted] = attrMatch;
|
|
232
|
+
const value = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
|
|
233
|
+
attrs[name.toLowerCase()] = decodeHtmlEntities(value);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
tagName: tagName.toLowerCase(),
|
|
238
|
+
attrs,
|
|
239
|
+
isClosing: false,
|
|
240
|
+
isSelfClosing,
|
|
241
|
+
rawHtml: tag
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function parseHtmlFragment(html, options = {}) {
|
|
245
|
+
const result = [];
|
|
246
|
+
const stack = [];
|
|
247
|
+
const tokenRegex = /(<\/?[a-zA-Z][^>]*>)|([^<]+)/g;
|
|
248
|
+
let match;
|
|
249
|
+
while ((match = tokenRegex.exec(html)) !== null) {
|
|
250
|
+
const [, tag, text] = match;
|
|
251
|
+
if (tag) {
|
|
252
|
+
const parsed = parseTagDirect(tag);
|
|
253
|
+
if (!parsed) continue;
|
|
254
|
+
if (isTagBlacklisted(parsed.tagName, options)) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (parsed.isClosing) {
|
|
258
|
+
let found = false;
|
|
259
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
260
|
+
if (stack[i].tagName === parsed.tagName) {
|
|
261
|
+
const node = stack.pop();
|
|
262
|
+
if (stack.length > 0) {
|
|
263
|
+
stack[stack.length - 1].children.push(node);
|
|
264
|
+
} else {
|
|
265
|
+
result.push(node);
|
|
266
|
+
}
|
|
267
|
+
found = true;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (!found) continue;
|
|
272
|
+
} else {
|
|
273
|
+
const sanitizedAttrs = sanitizeAttrs(parsed.attrs, options);
|
|
274
|
+
const node = {
|
|
275
|
+
type: "htmlElement",
|
|
276
|
+
tagName: parsed.tagName,
|
|
277
|
+
attrs: sanitizedAttrs,
|
|
278
|
+
children: [],
|
|
279
|
+
data: options.preserveRawHtml !== false ? {
|
|
280
|
+
rawHtml: tag,
|
|
281
|
+
parsed: true
|
|
282
|
+
} : void 0
|
|
283
|
+
};
|
|
284
|
+
if (parsed.isSelfClosing) {
|
|
285
|
+
if (stack.length > 0) {
|
|
286
|
+
stack[stack.length - 1].children.push(node);
|
|
287
|
+
} else {
|
|
288
|
+
result.push(node);
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
stack.push(node);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} else if (text && text.trim()) {
|
|
295
|
+
const textNode = {
|
|
296
|
+
type: "text",
|
|
297
|
+
value: text
|
|
298
|
+
};
|
|
299
|
+
if (stack.length > 0) {
|
|
300
|
+
stack[stack.length - 1].children.push(textNode);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
while (stack.length > 0) {
|
|
305
|
+
const node = stack.pop();
|
|
306
|
+
if (stack.length > 0) {
|
|
307
|
+
stack[stack.length - 1].children.push(node);
|
|
308
|
+
} else {
|
|
309
|
+
result.push(node);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
function isTagBlacklisted(tagName, options) {
|
|
315
|
+
const blacklist = options.tagBlacklist ?? DEFAULT_TAG_BLACKLIST;
|
|
316
|
+
return blacklist.includes(tagName.toLowerCase());
|
|
317
|
+
}
|
|
318
|
+
function isAttrBlacklisted(attrName, options) {
|
|
319
|
+
const name = attrName.toLowerCase();
|
|
320
|
+
const blacklist = options.attrBlacklist ?? DEFAULT_ATTR_BLACKLIST;
|
|
321
|
+
if (name.startsWith("on")) return true;
|
|
322
|
+
return blacklist.includes(name);
|
|
323
|
+
}
|
|
324
|
+
function isProtocolDangerous(url, options) {
|
|
325
|
+
const protocolBlacklist = options.protocolBlacklist ?? DEFAULT_PROTOCOL_BLACKLIST;
|
|
326
|
+
const normalizedUrl = url.trim().toLowerCase();
|
|
327
|
+
for (const protocol of protocolBlacklist) {
|
|
328
|
+
if (normalizedUrl.startsWith(protocol)) {
|
|
329
|
+
if (protocol === "data:" && normalizedUrl.startsWith("data:image/")) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
function sanitizeAttrs(attrs, options) {
|
|
338
|
+
const result = {};
|
|
339
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
340
|
+
if (isAttrBlacklisted(name, options)) continue;
|
|
341
|
+
if (URL_ATTRS.includes(name.toLowerCase())) {
|
|
342
|
+
if (isProtocolDangerous(value, options)) continue;
|
|
343
|
+
}
|
|
344
|
+
result[name] = value;
|
|
345
|
+
}
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
function isHtmlNode(node) {
|
|
349
|
+
return node.type === "html";
|
|
350
|
+
}
|
|
351
|
+
function hasChildren(node) {
|
|
352
|
+
return "children" in node && Array.isArray(node.children);
|
|
353
|
+
}
|
|
354
|
+
function processHtmlNodesInArray(nodes, options) {
|
|
355
|
+
const result = [];
|
|
356
|
+
let i = 0;
|
|
357
|
+
while (i < nodes.length) {
|
|
358
|
+
const node = nodes[i];
|
|
359
|
+
if (isHtmlNode(node)) {
|
|
360
|
+
const contentType = detectHtmlContentType(node.value);
|
|
361
|
+
if (contentType === "fragment") {
|
|
362
|
+
const fragmentNodes = parseHtmlFragment(node.value, options);
|
|
363
|
+
if (fragmentNodes.length > 0) {
|
|
364
|
+
result.push(...fragmentNodes);
|
|
365
|
+
} else {
|
|
366
|
+
result.push(node);
|
|
367
|
+
}
|
|
368
|
+
i++;
|
|
369
|
+
} else if (contentType === "self-closing") {
|
|
370
|
+
const parsed = parseHtmlTag(node.value);
|
|
371
|
+
if (parsed && !isTagBlacklisted(parsed.tagName, options)) {
|
|
372
|
+
const elementNode = {
|
|
373
|
+
type: "htmlElement",
|
|
374
|
+
tagName: parsed.tagName,
|
|
375
|
+
attrs: sanitizeAttrs(parsed.attrs, options),
|
|
376
|
+
children: [],
|
|
377
|
+
data: options.preserveRawHtml !== false ? {
|
|
378
|
+
rawHtml: node.value,
|
|
379
|
+
parsed: true,
|
|
380
|
+
originalType: "html"
|
|
381
|
+
} : void 0
|
|
382
|
+
};
|
|
383
|
+
result.push(elementNode);
|
|
384
|
+
}
|
|
385
|
+
i++;
|
|
386
|
+
} else if (contentType === "closing") {
|
|
387
|
+
i++;
|
|
388
|
+
} else if (contentType === "opening") {
|
|
389
|
+
const parsed = parseHtmlTag(node.value);
|
|
390
|
+
if (!parsed || isTagBlacklisted(parsed.tagName, options)) {
|
|
391
|
+
i++;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const tagName = parsed.tagName;
|
|
395
|
+
const contentNodes = [];
|
|
396
|
+
let depth = 1;
|
|
397
|
+
let j = i + 1;
|
|
398
|
+
let foundClosing = false;
|
|
399
|
+
while (j < nodes.length && depth > 0) {
|
|
400
|
+
const nextNode = nodes[j];
|
|
401
|
+
if (isHtmlNode(nextNode)) {
|
|
402
|
+
const nextType = detectHtmlContentType(nextNode.value);
|
|
403
|
+
if (nextType === "closing") {
|
|
404
|
+
const nextParsed = parseHtmlTag(nextNode.value);
|
|
405
|
+
if (nextParsed && nextParsed.tagName === tagName) {
|
|
406
|
+
depth--;
|
|
407
|
+
if (depth === 0) {
|
|
408
|
+
foundClosing = true;
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
} else if (nextType === "opening") {
|
|
413
|
+
const nextParsed = parseHtmlTag(nextNode.value);
|
|
414
|
+
if (nextParsed && nextParsed.tagName === tagName) {
|
|
415
|
+
depth++;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
contentNodes.push(nextNode);
|
|
420
|
+
j++;
|
|
421
|
+
}
|
|
422
|
+
const elementNode = {
|
|
423
|
+
type: "htmlElement",
|
|
424
|
+
tagName: parsed.tagName,
|
|
425
|
+
attrs: sanitizeAttrs(parsed.attrs, options),
|
|
426
|
+
children: processHtmlNodesInArray(contentNodes, options),
|
|
427
|
+
data: options.preserveRawHtml !== false ? {
|
|
428
|
+
rawHtml: node.value,
|
|
429
|
+
parsed: true,
|
|
430
|
+
originalType: "html"
|
|
431
|
+
} : void 0
|
|
432
|
+
};
|
|
433
|
+
result.push(elementNode);
|
|
434
|
+
i = foundClosing ? j + 1 : j;
|
|
435
|
+
} else {
|
|
436
|
+
result.push(node);
|
|
437
|
+
i++;
|
|
438
|
+
}
|
|
439
|
+
} else {
|
|
440
|
+
if (hasChildren(node)) {
|
|
441
|
+
const processed = processHtmlNodesInArray(
|
|
442
|
+
node.children,
|
|
443
|
+
options
|
|
444
|
+
);
|
|
445
|
+
result.push({
|
|
446
|
+
...node,
|
|
447
|
+
children: processed
|
|
448
|
+
});
|
|
449
|
+
} else {
|
|
450
|
+
result.push(node);
|
|
451
|
+
}
|
|
452
|
+
i++;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
function transformHtmlNodes(ast, options = {}) {
|
|
458
|
+
return {
|
|
459
|
+
...ast,
|
|
460
|
+
children: processHtmlNodesInArray(ast.children, options)
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function createHtmlTreeTransformer(options = {}) {
|
|
464
|
+
return function transformer(tree) {
|
|
465
|
+
return transformHtmlNodes(tree, options);
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
var htmlTreeExtension = {
|
|
469
|
+
enter: {},
|
|
470
|
+
exit: {}
|
|
471
|
+
};
|
|
472
|
+
function isHtmlElementNode(node) {
|
|
473
|
+
return node.type === "htmlElement";
|
|
474
|
+
}
|
|
475
|
+
function walkHtmlElements(node, callback, parent = null) {
|
|
476
|
+
if (isHtmlElementNode(node)) {
|
|
477
|
+
callback(node, parent);
|
|
478
|
+
}
|
|
479
|
+
if (hasChildren(node) || node.type === "root") {
|
|
480
|
+
const children = node.children;
|
|
481
|
+
for (const child of children) {
|
|
482
|
+
walkHtmlElements(child, callback, node);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function findHtmlElementsByTag(root, tagName) {
|
|
487
|
+
const result = [];
|
|
488
|
+
walkHtmlElements(root, (node) => {
|
|
489
|
+
if (node.tagName === tagName.toLowerCase()) {
|
|
490
|
+
result.push(node);
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
function htmlElementToString(node) {
|
|
496
|
+
const { tagName, attrs, children } = node;
|
|
497
|
+
const attrsStr = Object.entries(attrs).map(([name, value]) => {
|
|
498
|
+
if (value === "") return name;
|
|
499
|
+
return `${name}="${escapeHtml(value)}"`;
|
|
500
|
+
}).join(" ");
|
|
501
|
+
const openTag = attrsStr ? `<${tagName} ${attrsStr}>` : `<${tagName}>`;
|
|
502
|
+
if (children.length === 0 && isSelfClosingTag(tagName)) {
|
|
503
|
+
return attrsStr ? `<${tagName} ${attrsStr} />` : `<${tagName} />`;
|
|
504
|
+
}
|
|
505
|
+
const childrenStr = children.map((child) => {
|
|
506
|
+
if (child.type === "text") {
|
|
507
|
+
return child.value;
|
|
508
|
+
}
|
|
509
|
+
if (isHtmlElementNode(child)) {
|
|
510
|
+
return htmlElementToString(child);
|
|
511
|
+
}
|
|
512
|
+
return "";
|
|
513
|
+
}).join("");
|
|
514
|
+
return `${openTag}${childrenStr}</${tagName}>`;
|
|
515
|
+
}
|
|
516
|
+
function isSelfClosingTag(tagName) {
|
|
517
|
+
return ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"].includes(tagName.toLowerCase());
|
|
518
|
+
}
|
|
519
|
+
function escapeHtml(text) {
|
|
520
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
521
|
+
}
|
|
522
|
+
function micromarkReferenceExtension() {
|
|
523
|
+
return {
|
|
524
|
+
// 在 text 中使用 codes.rightSquareBracket 键覆盖 labelEnd
|
|
525
|
+
text: {
|
|
526
|
+
[codes.rightSquareBracket]: {
|
|
527
|
+
name: "labelEnd",
|
|
528
|
+
resolveAll: resolveAllLabelEnd,
|
|
529
|
+
resolveTo: resolveToLabelEnd,
|
|
530
|
+
tokenize: tokenizeLabelEnd,
|
|
531
|
+
// 添加 add: 'before' 确保先被尝试
|
|
532
|
+
add: "before"
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function resolveAllLabelEnd(events) {
|
|
538
|
+
let index = -1;
|
|
539
|
+
const newEvents = [];
|
|
540
|
+
while (++index < events.length) {
|
|
541
|
+
const token = events[index][1];
|
|
542
|
+
newEvents.push(events[index]);
|
|
543
|
+
if (token.type === types.labelImage || token.type === types.labelLink || token.type === types.labelEnd) {
|
|
544
|
+
const offset = token.type === types.labelImage ? 4 : 2;
|
|
545
|
+
token.type = types.data;
|
|
546
|
+
index += offset;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (events.length !== newEvents.length) {
|
|
550
|
+
events.length = 0;
|
|
551
|
+
events.push(...newEvents);
|
|
552
|
+
}
|
|
553
|
+
return events;
|
|
554
|
+
}
|
|
555
|
+
function resolveToLabelEnd(events, context) {
|
|
556
|
+
let index = events.length;
|
|
557
|
+
let offset = 0;
|
|
558
|
+
let token;
|
|
559
|
+
let open;
|
|
560
|
+
let close;
|
|
561
|
+
let media;
|
|
562
|
+
while (index--) {
|
|
563
|
+
token = events[index][1];
|
|
564
|
+
if (open !== void 0) {
|
|
565
|
+
if (token.type === types.link || token.type === types.labelLink && token._inactive) {
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
if (events[index][0] === "enter" && token.type === types.labelLink) {
|
|
569
|
+
token._inactive = true;
|
|
570
|
+
}
|
|
571
|
+
} else if (close !== void 0) {
|
|
572
|
+
if (events[index][0] === "enter" && (token.type === types.labelImage || token.type === types.labelLink) && !token._balanced) {
|
|
573
|
+
open = index;
|
|
574
|
+
if (token.type !== types.labelLink) {
|
|
575
|
+
offset = 2;
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
} else if (token.type === types.labelEnd) {
|
|
580
|
+
close = index;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (open === void 0 || close === void 0) {
|
|
584
|
+
return events;
|
|
585
|
+
}
|
|
586
|
+
const group = {
|
|
587
|
+
type: events[open][1].type === types.labelLink ? types.link : types.image,
|
|
588
|
+
start: { ...events[open][1].start },
|
|
589
|
+
end: { ...events[events.length - 1][1].end }
|
|
590
|
+
};
|
|
591
|
+
const label = {
|
|
592
|
+
type: types.label,
|
|
593
|
+
start: { ...events[open][1].start },
|
|
594
|
+
end: { ...events[close][1].end }
|
|
595
|
+
};
|
|
596
|
+
const text = {
|
|
597
|
+
type: types.labelText,
|
|
598
|
+
start: { ...events[open + offset + 2][1].end },
|
|
599
|
+
end: { ...events[close - 2][1].start }
|
|
600
|
+
};
|
|
601
|
+
media = [
|
|
602
|
+
["enter", group, context],
|
|
603
|
+
["enter", label, context]
|
|
604
|
+
];
|
|
605
|
+
media.push(...events.slice(open + 1, open + offset + 3));
|
|
606
|
+
media.push(["enter", text, context]);
|
|
607
|
+
media.push(...events.slice(open + offset + 4, close - 3));
|
|
608
|
+
media.push(
|
|
609
|
+
["exit", text, context],
|
|
610
|
+
events[close - 2],
|
|
611
|
+
events[close - 1],
|
|
612
|
+
["exit", label, context]
|
|
613
|
+
);
|
|
614
|
+
media.push(...events.slice(close + 1));
|
|
615
|
+
media.push(["exit", group, context]);
|
|
616
|
+
events.splice(open, events.length - open, ...media);
|
|
617
|
+
return events;
|
|
618
|
+
}
|
|
619
|
+
function tokenizeLabelEnd(effects, ok2, nok) {
|
|
620
|
+
const self = this;
|
|
621
|
+
let index = self.events.length;
|
|
622
|
+
let labelStart;
|
|
623
|
+
while (index--) {
|
|
624
|
+
if ((self.events[index][1].type === types.labelImage || self.events[index][1].type === types.labelLink) && !self.events[index][1]._balanced) {
|
|
625
|
+
labelStart = self.events[index][1];
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return start;
|
|
630
|
+
function start(code) {
|
|
631
|
+
if (!labelStart) {
|
|
632
|
+
return nok(code);
|
|
633
|
+
}
|
|
634
|
+
if (labelStart._inactive) {
|
|
635
|
+
return labelEndNok(code);
|
|
636
|
+
}
|
|
637
|
+
if (labelStart.type === types.labelLink) {
|
|
638
|
+
const labelText = self.sliceSerialize({ start: labelStart.end, end: self.now() });
|
|
639
|
+
if (labelText.startsWith("^")) {
|
|
640
|
+
return nok(code);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
effects.enter(types.labelEnd);
|
|
644
|
+
effects.enter(types.labelMarker);
|
|
645
|
+
effects.consume(code);
|
|
646
|
+
effects.exit(types.labelMarker);
|
|
647
|
+
effects.exit(types.labelEnd);
|
|
648
|
+
return after;
|
|
649
|
+
}
|
|
650
|
+
function after(code) {
|
|
651
|
+
if (code === codes.leftParenthesis) {
|
|
652
|
+
return effects.attempt(
|
|
653
|
+
{
|
|
654
|
+
tokenize: tokenizeResource,
|
|
655
|
+
partial: false
|
|
656
|
+
},
|
|
657
|
+
labelEndOk,
|
|
658
|
+
labelEndNok
|
|
659
|
+
// 修复:resource 解析失败时返回 nok
|
|
660
|
+
)(code);
|
|
661
|
+
}
|
|
662
|
+
if (code === codes.leftSquareBracket) {
|
|
663
|
+
return effects.attempt(
|
|
664
|
+
{
|
|
665
|
+
tokenize: tokenizeReferenceFull,
|
|
666
|
+
partial: false
|
|
667
|
+
},
|
|
668
|
+
labelEndOk,
|
|
669
|
+
referenceNotFull
|
|
670
|
+
// 修改:即使不是 full reference,也尝试 collapsed
|
|
671
|
+
)(code);
|
|
672
|
+
}
|
|
673
|
+
return labelEndOk(code);
|
|
674
|
+
}
|
|
675
|
+
function referenceNotFull(code) {
|
|
676
|
+
return effects.attempt(
|
|
677
|
+
{
|
|
678
|
+
tokenize: tokenizeReferenceCollapsed,
|
|
679
|
+
partial: false
|
|
680
|
+
},
|
|
681
|
+
labelEndOk,
|
|
682
|
+
labelEndOk
|
|
683
|
+
// 修改:即使失败也返回 ok
|
|
684
|
+
)(code);
|
|
685
|
+
}
|
|
686
|
+
function labelEndOk(code) {
|
|
687
|
+
return ok2(code);
|
|
688
|
+
}
|
|
689
|
+
function labelEndNok(code) {
|
|
690
|
+
labelStart._balanced = true;
|
|
691
|
+
return nok(code);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function tokenizeResource(effects, ok2, nok) {
|
|
695
|
+
return resourceStart;
|
|
696
|
+
function resourceStart(code) {
|
|
697
|
+
if (code !== codes.leftParenthesis) {
|
|
698
|
+
return nok(code);
|
|
699
|
+
}
|
|
700
|
+
effects.enter(types.resource);
|
|
701
|
+
effects.enter(types.resourceMarker);
|
|
702
|
+
effects.consume(code);
|
|
703
|
+
effects.exit(types.resourceMarker);
|
|
704
|
+
return resourceBefore;
|
|
705
|
+
}
|
|
706
|
+
function resourceBefore(code) {
|
|
707
|
+
return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceOpen)(code) : resourceOpen(code);
|
|
708
|
+
}
|
|
709
|
+
function resourceOpen(code) {
|
|
710
|
+
if (code === codes.rightParenthesis) {
|
|
711
|
+
return resourceEnd(code);
|
|
712
|
+
}
|
|
713
|
+
return factoryDestination(
|
|
714
|
+
effects,
|
|
715
|
+
resourceDestinationAfter,
|
|
716
|
+
resourceDestinationMissing,
|
|
717
|
+
types.resourceDestination,
|
|
718
|
+
types.resourceDestinationLiteral,
|
|
719
|
+
types.resourceDestinationLiteralMarker,
|
|
720
|
+
types.resourceDestinationRaw,
|
|
721
|
+
types.resourceDestinationString,
|
|
722
|
+
constants.linkResourceDestinationBalanceMax
|
|
723
|
+
)(code);
|
|
724
|
+
}
|
|
725
|
+
function resourceDestinationAfter(code) {
|
|
726
|
+
return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceBetween)(code) : resourceEnd(code);
|
|
727
|
+
}
|
|
728
|
+
function resourceDestinationMissing(code) {
|
|
729
|
+
return nok(code);
|
|
730
|
+
}
|
|
731
|
+
function resourceBetween(code) {
|
|
732
|
+
if (code === codes.quotationMark || code === codes.apostrophe || code === codes.leftParenthesis) {
|
|
733
|
+
return factoryTitle(
|
|
734
|
+
effects,
|
|
735
|
+
resourceTitleAfter,
|
|
736
|
+
nok,
|
|
737
|
+
types.resourceTitle,
|
|
738
|
+
types.resourceTitleMarker,
|
|
739
|
+
types.resourceTitleString
|
|
740
|
+
)(code);
|
|
741
|
+
}
|
|
742
|
+
return resourceEnd(code);
|
|
743
|
+
}
|
|
744
|
+
function resourceTitleAfter(code) {
|
|
745
|
+
return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceEnd)(code) : resourceEnd(code);
|
|
746
|
+
}
|
|
747
|
+
function resourceEnd(code) {
|
|
748
|
+
if (code === codes.rightParenthesis) {
|
|
749
|
+
effects.enter(types.resourceMarker);
|
|
750
|
+
effects.consume(code);
|
|
751
|
+
effects.exit(types.resourceMarker);
|
|
752
|
+
effects.exit(types.resource);
|
|
753
|
+
return ok2;
|
|
754
|
+
}
|
|
755
|
+
return nok(code);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function tokenizeReferenceFull(effects, ok2, nok) {
|
|
759
|
+
const self = this;
|
|
760
|
+
return referenceFull;
|
|
761
|
+
function referenceFull(code) {
|
|
762
|
+
if (code !== codes.leftSquareBracket) {
|
|
763
|
+
return nok(code);
|
|
764
|
+
}
|
|
765
|
+
return factoryLabel.call(
|
|
766
|
+
self,
|
|
767
|
+
effects,
|
|
768
|
+
referenceFullAfter,
|
|
769
|
+
referenceFullMissing,
|
|
770
|
+
types.reference,
|
|
771
|
+
types.referenceMarker,
|
|
772
|
+
types.referenceString
|
|
773
|
+
)(code);
|
|
774
|
+
}
|
|
775
|
+
function referenceFullAfter(code) {
|
|
776
|
+
return ok2(code);
|
|
777
|
+
}
|
|
778
|
+
function referenceFullMissing(code) {
|
|
779
|
+
return nok(code);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function tokenizeReferenceCollapsed(effects, ok2, nok) {
|
|
783
|
+
return referenceCollapsedStart;
|
|
784
|
+
function referenceCollapsedStart(code) {
|
|
785
|
+
if (code !== codes.leftSquareBracket) {
|
|
786
|
+
return nok(code);
|
|
787
|
+
}
|
|
788
|
+
effects.enter(types.reference);
|
|
789
|
+
effects.enter(types.referenceMarker);
|
|
790
|
+
effects.consume(code);
|
|
791
|
+
effects.exit(types.referenceMarker);
|
|
792
|
+
return referenceCollapsedOpen;
|
|
793
|
+
}
|
|
794
|
+
function referenceCollapsedOpen(code) {
|
|
795
|
+
if (code === codes.rightSquareBracket) {
|
|
796
|
+
effects.enter(types.referenceMarker);
|
|
797
|
+
effects.consume(code);
|
|
798
|
+
effects.exit(types.referenceMarker);
|
|
799
|
+
effects.exit(types.reference);
|
|
800
|
+
return ok2;
|
|
801
|
+
}
|
|
802
|
+
return nok(code);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function factorySpace(effects, ok2, type, max) {
|
|
806
|
+
const limit = max ? max - 1 : Number.POSITIVE_INFINITY;
|
|
807
|
+
let size = 0;
|
|
808
|
+
return start;
|
|
809
|
+
function start(code) {
|
|
810
|
+
if (markdownSpace(code)) {
|
|
811
|
+
effects.enter(type);
|
|
812
|
+
return prefix(code);
|
|
813
|
+
}
|
|
814
|
+
return ok2(code);
|
|
815
|
+
}
|
|
816
|
+
function prefix(code) {
|
|
817
|
+
if (markdownSpace(code) && size++ < limit) {
|
|
818
|
+
effects.consume(code);
|
|
819
|
+
return prefix;
|
|
820
|
+
}
|
|
821
|
+
effects.exit(type);
|
|
822
|
+
return ok2(code);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
var blankLine = {
|
|
826
|
+
partial: true,
|
|
827
|
+
tokenize: tokenizeBlankLine
|
|
828
|
+
};
|
|
829
|
+
function tokenizeBlankLine(effects, ok2, nok) {
|
|
830
|
+
return start;
|
|
831
|
+
function start(code) {
|
|
832
|
+
return markdownSpace(code) ? factorySpace(effects, after, "linePrefix")(code) : after(code);
|
|
833
|
+
}
|
|
834
|
+
function after(code) {
|
|
835
|
+
return code === null || markdownLineEnding(code) ? ok2(code) : nok(code);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
var indent = {
|
|
839
|
+
tokenize: tokenizeIndent,
|
|
840
|
+
partial: true
|
|
841
|
+
};
|
|
842
|
+
function gfmFootnote() {
|
|
843
|
+
return {
|
|
844
|
+
document: {
|
|
845
|
+
[91]: {
|
|
846
|
+
name: "gfmFootnoteDefinition",
|
|
847
|
+
tokenize: tokenizeDefinitionStart,
|
|
848
|
+
continuation: {
|
|
849
|
+
tokenize: tokenizeDefinitionContinuation
|
|
850
|
+
},
|
|
851
|
+
exit: gfmFootnoteDefinitionEnd
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
text: {
|
|
855
|
+
[91]: {
|
|
856
|
+
name: "gfmFootnoteCall",
|
|
857
|
+
tokenize: tokenizeGfmFootnoteCall
|
|
858
|
+
},
|
|
859
|
+
[93]: {
|
|
860
|
+
name: "gfmPotentialFootnoteCall",
|
|
861
|
+
add: "after",
|
|
862
|
+
tokenize: tokenizePotentialGfmFootnoteCall,
|
|
863
|
+
resolveTo: resolveToPotentialGfmFootnoteCall
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
function tokenizePotentialGfmFootnoteCall(effects, ok2, nok) {
|
|
869
|
+
const self = this;
|
|
870
|
+
let index = self.events.length;
|
|
871
|
+
const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
|
|
872
|
+
let labelStart;
|
|
873
|
+
while (index--) {
|
|
874
|
+
const token = self.events[index][1];
|
|
875
|
+
if (token.type === "labelImage") {
|
|
876
|
+
labelStart = token;
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
if (token.type === "gfmFootnoteCall" || token.type === "labelLink" || token.type === "label" || token.type === "image" || token.type === "link") {
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return start;
|
|
884
|
+
function start(code) {
|
|
885
|
+
if (!labelStart || !labelStart._balanced) {
|
|
886
|
+
return nok(code);
|
|
887
|
+
}
|
|
888
|
+
const id = normalizeIdentifier(self.sliceSerialize({
|
|
889
|
+
start: labelStart.end,
|
|
890
|
+
end: self.now()
|
|
891
|
+
}));
|
|
892
|
+
if (id.codePointAt(0) !== 94 || !defined.includes(id.slice(1))) {
|
|
893
|
+
return nok(code);
|
|
894
|
+
}
|
|
895
|
+
effects.enter("gfmFootnoteCallLabelMarker");
|
|
896
|
+
effects.consume(code);
|
|
897
|
+
effects.exit("gfmFootnoteCallLabelMarker");
|
|
898
|
+
return ok2(code);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
function resolveToPotentialGfmFootnoteCall(events, context) {
|
|
902
|
+
let index = events.length;
|
|
903
|
+
while (index--) {
|
|
904
|
+
if (events[index][1].type === "labelImage" && events[index][0] === "enter") {
|
|
905
|
+
events[index][1];
|
|
906
|
+
break;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
events[index + 1][1].type = "data";
|
|
910
|
+
events[index + 3][1].type = "gfmFootnoteCallLabelMarker";
|
|
911
|
+
const call = {
|
|
912
|
+
type: "gfmFootnoteCall",
|
|
913
|
+
start: Object.assign({}, events[index + 3][1].start),
|
|
914
|
+
end: Object.assign({}, events[events.length - 1][1].end)
|
|
915
|
+
};
|
|
916
|
+
const marker = {
|
|
917
|
+
type: "gfmFootnoteCallMarker",
|
|
918
|
+
start: Object.assign({}, events[index + 3][1].end),
|
|
919
|
+
end: Object.assign({}, events[index + 3][1].end)
|
|
920
|
+
};
|
|
921
|
+
marker.end.column++;
|
|
922
|
+
marker.end.offset++;
|
|
923
|
+
marker.end._bufferIndex++;
|
|
924
|
+
const string = {
|
|
925
|
+
type: "gfmFootnoteCallString",
|
|
926
|
+
start: Object.assign({}, marker.end),
|
|
927
|
+
end: Object.assign({}, events[events.length - 1][1].start)
|
|
928
|
+
};
|
|
929
|
+
const chunk = {
|
|
930
|
+
type: "chunkString",
|
|
931
|
+
contentType: "string",
|
|
932
|
+
start: Object.assign({}, string.start),
|
|
933
|
+
end: Object.assign({}, string.end)
|
|
934
|
+
};
|
|
935
|
+
const replacement = [
|
|
936
|
+
// Take the `labelImageMarker` (now `data`, the `!`)
|
|
937
|
+
events[index + 1],
|
|
938
|
+
events[index + 2],
|
|
939
|
+
["enter", call, context],
|
|
940
|
+
// The `[`
|
|
941
|
+
events[index + 3],
|
|
942
|
+
events[index + 4],
|
|
943
|
+
// The `^`.
|
|
944
|
+
["enter", marker, context],
|
|
945
|
+
["exit", marker, context],
|
|
946
|
+
// Everything in between.
|
|
947
|
+
["enter", string, context],
|
|
948
|
+
["enter", chunk, context],
|
|
949
|
+
["exit", chunk, context],
|
|
950
|
+
["exit", string, context],
|
|
951
|
+
// The ending (`]`, properly parsed and labelled).
|
|
952
|
+
events[events.length - 2],
|
|
953
|
+
events[events.length - 1],
|
|
954
|
+
["exit", call, context]
|
|
955
|
+
];
|
|
956
|
+
events.splice(index, events.length - index + 1, ...replacement);
|
|
957
|
+
return events;
|
|
958
|
+
}
|
|
959
|
+
function tokenizeGfmFootnoteCall(effects, ok2, nok) {
|
|
960
|
+
const self = this;
|
|
961
|
+
const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
|
|
962
|
+
let size = 0;
|
|
963
|
+
let data;
|
|
964
|
+
return start;
|
|
965
|
+
function start(code) {
|
|
966
|
+
effects.enter("gfmFootnoteCall");
|
|
967
|
+
effects.enter("gfmFootnoteCallLabelMarker");
|
|
968
|
+
effects.consume(code);
|
|
969
|
+
effects.exit("gfmFootnoteCallLabelMarker");
|
|
970
|
+
return callStart;
|
|
971
|
+
}
|
|
972
|
+
function callStart(code) {
|
|
973
|
+
if (code !== 94) return nok(code);
|
|
974
|
+
effects.enter("gfmFootnoteCallMarker");
|
|
975
|
+
effects.consume(code);
|
|
976
|
+
effects.exit("gfmFootnoteCallMarker");
|
|
977
|
+
effects.enter("gfmFootnoteCallString");
|
|
978
|
+
effects.enter("chunkString").contentType = "string";
|
|
979
|
+
return callData;
|
|
980
|
+
}
|
|
981
|
+
function callData(code) {
|
|
982
|
+
if (
|
|
983
|
+
// Too long.
|
|
984
|
+
size > 999 || // Closing brace with nothing.
|
|
985
|
+
code === 93 && !data || // Space or tab is not supported by GFM for some reason.
|
|
986
|
+
// `\n` and `[` not being supported makes sense.
|
|
987
|
+
code === null || code === 91 || markdownLineEndingOrSpace(code)
|
|
988
|
+
) {
|
|
989
|
+
return nok(code);
|
|
990
|
+
}
|
|
991
|
+
if (code === 93) {
|
|
992
|
+
effects.exit("chunkString");
|
|
993
|
+
const token = effects.exit("gfmFootnoteCallString");
|
|
994
|
+
if (!defined.includes(normalizeIdentifier(self.sliceSerialize(token)))) {
|
|
995
|
+
return nok(code);
|
|
996
|
+
}
|
|
997
|
+
effects.enter("gfmFootnoteCallLabelMarker");
|
|
998
|
+
effects.consume(code);
|
|
999
|
+
effects.exit("gfmFootnoteCallLabelMarker");
|
|
1000
|
+
effects.exit("gfmFootnoteCall");
|
|
1001
|
+
return ok2;
|
|
1002
|
+
}
|
|
1003
|
+
if (!markdownLineEndingOrSpace(code)) {
|
|
1004
|
+
data = true;
|
|
1005
|
+
}
|
|
1006
|
+
size++;
|
|
1007
|
+
effects.consume(code);
|
|
1008
|
+
return code === 92 ? callEscape : callData;
|
|
1009
|
+
}
|
|
1010
|
+
function callEscape(code) {
|
|
1011
|
+
if (code === 91 || code === 92 || code === 93) {
|
|
1012
|
+
effects.consume(code);
|
|
1013
|
+
size++;
|
|
1014
|
+
return callData;
|
|
1015
|
+
}
|
|
1016
|
+
return callData(code);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
function tokenizeDefinitionStart(effects, ok2, nok) {
|
|
1020
|
+
const self = this;
|
|
1021
|
+
const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
|
|
1022
|
+
let identifier;
|
|
1023
|
+
let size = 0;
|
|
1024
|
+
let data;
|
|
1025
|
+
return start;
|
|
1026
|
+
function start(code) {
|
|
1027
|
+
effects.enter("gfmFootnoteDefinition")._container = true;
|
|
1028
|
+
effects.enter("gfmFootnoteDefinitionLabel");
|
|
1029
|
+
effects.enter("gfmFootnoteDefinitionLabelMarker");
|
|
1030
|
+
effects.consume(code);
|
|
1031
|
+
effects.exit("gfmFootnoteDefinitionLabelMarker");
|
|
1032
|
+
return labelAtMarker;
|
|
1033
|
+
}
|
|
1034
|
+
function labelAtMarker(code) {
|
|
1035
|
+
if (code === 94) {
|
|
1036
|
+
effects.enter("gfmFootnoteDefinitionMarker");
|
|
1037
|
+
effects.consume(code);
|
|
1038
|
+
effects.exit("gfmFootnoteDefinitionMarker");
|
|
1039
|
+
effects.enter("gfmFootnoteDefinitionLabelString");
|
|
1040
|
+
effects.enter("chunkString").contentType = "string";
|
|
1041
|
+
return labelInside;
|
|
1042
|
+
}
|
|
1043
|
+
return nok(code);
|
|
1044
|
+
}
|
|
1045
|
+
function labelInside(code) {
|
|
1046
|
+
if (
|
|
1047
|
+
// Too long.
|
|
1048
|
+
size > 999 || // Closing brace with nothing.
|
|
1049
|
+
code === 93 && !data || // Space or tab is not supported by GFM for some reason.
|
|
1050
|
+
// `\n` and `[` not being supported makes sense.
|
|
1051
|
+
code === null || code === 91 || markdownLineEndingOrSpace(code)
|
|
1052
|
+
) {
|
|
1053
|
+
return nok(code);
|
|
1054
|
+
}
|
|
1055
|
+
if (code === 93) {
|
|
1056
|
+
effects.exit("chunkString");
|
|
1057
|
+
const token = effects.exit("gfmFootnoteDefinitionLabelString");
|
|
1058
|
+
identifier = normalizeIdentifier(self.sliceSerialize(token));
|
|
1059
|
+
effects.enter("gfmFootnoteDefinitionLabelMarker");
|
|
1060
|
+
effects.consume(code);
|
|
1061
|
+
effects.exit("gfmFootnoteDefinitionLabelMarker");
|
|
1062
|
+
effects.exit("gfmFootnoteDefinitionLabel");
|
|
1063
|
+
return labelAfter;
|
|
1064
|
+
}
|
|
1065
|
+
if (!markdownLineEndingOrSpace(code)) {
|
|
1066
|
+
data = true;
|
|
1067
|
+
}
|
|
1068
|
+
size++;
|
|
1069
|
+
effects.consume(code);
|
|
1070
|
+
return code === 92 ? labelEscape : labelInside;
|
|
1071
|
+
}
|
|
1072
|
+
function labelEscape(code) {
|
|
1073
|
+
if (code === 91 || code === 92 || code === 93) {
|
|
1074
|
+
effects.consume(code);
|
|
1075
|
+
size++;
|
|
1076
|
+
return labelInside;
|
|
1077
|
+
}
|
|
1078
|
+
return labelInside(code);
|
|
1079
|
+
}
|
|
1080
|
+
function labelAfter(code) {
|
|
1081
|
+
if (code === 58) {
|
|
1082
|
+
effects.enter("definitionMarker");
|
|
1083
|
+
effects.consume(code);
|
|
1084
|
+
effects.exit("definitionMarker");
|
|
1085
|
+
if (!defined.includes(identifier)) {
|
|
1086
|
+
defined.push(identifier);
|
|
1087
|
+
}
|
|
1088
|
+
return factorySpace(effects, whitespaceAfter, "gfmFootnoteDefinitionWhitespace");
|
|
1089
|
+
}
|
|
1090
|
+
return nok(code);
|
|
1091
|
+
}
|
|
1092
|
+
function whitespaceAfter(code) {
|
|
1093
|
+
return ok2(code);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
function tokenizeDefinitionContinuation(effects, ok2, nok) {
|
|
1097
|
+
return effects.check(blankLine, ok2, effects.attempt(indent, ok2, nok));
|
|
1098
|
+
}
|
|
1099
|
+
function gfmFootnoteDefinitionEnd(effects) {
|
|
1100
|
+
effects.exit("gfmFootnoteDefinition");
|
|
1101
|
+
}
|
|
1102
|
+
function tokenizeIndent(effects, ok2, nok) {
|
|
1103
|
+
const self = this;
|
|
1104
|
+
return factorySpace(effects, afterPrefix, "gfmFootnoteDefinitionIndent", 4 + 1);
|
|
1105
|
+
function afterPrefix(code) {
|
|
1106
|
+
const tail = self.events[self.events.length - 1];
|
|
1107
|
+
return tail && tail[1].type === "gfmFootnoteDefinitionIndent" && tail[2].sliceSerialize(tail[1], true).length === 4 ? ok2(code) : nok(code);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function gfmFootnoteIncremental() {
|
|
1111
|
+
const original = gfmFootnote();
|
|
1112
|
+
return {
|
|
1113
|
+
...original,
|
|
1114
|
+
text: {
|
|
1115
|
+
...original.text,
|
|
1116
|
+
// 覆盖 text[91] (`[` 的处理) - 这是脚注引用解析的起点
|
|
1117
|
+
[codes.leftSquareBracket]: {
|
|
1118
|
+
...original.text[codes.leftSquareBracket],
|
|
1119
|
+
tokenize: tokenizeGfmFootnoteCallIncremental
|
|
1120
|
+
},
|
|
1121
|
+
// 覆盖 text[93] (`]` 的处理) - 用于处理 ![^1] 这样的情况
|
|
1122
|
+
[codes.rightSquareBracket]: {
|
|
1123
|
+
...original.text[codes.rightSquareBracket],
|
|
1124
|
+
tokenize: tokenizePotentialGfmFootnoteCallIncremental
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
function tokenizeGfmFootnoteCallIncremental(effects, ok2, nok) {
|
|
1130
|
+
let size = 0;
|
|
1131
|
+
let data = false;
|
|
1132
|
+
return start;
|
|
1133
|
+
function start(code) {
|
|
1134
|
+
if (code !== codes.leftSquareBracket) {
|
|
1135
|
+
return nok(code);
|
|
1136
|
+
}
|
|
1137
|
+
effects.enter("gfmFootnoteCall");
|
|
1138
|
+
effects.enter("gfmFootnoteCallLabelMarker");
|
|
1139
|
+
effects.consume(code);
|
|
1140
|
+
effects.exit("gfmFootnoteCallLabelMarker");
|
|
1141
|
+
return callStart;
|
|
1142
|
+
}
|
|
1143
|
+
function callStart(code) {
|
|
1144
|
+
if (code !== codes.caret) {
|
|
1145
|
+
return nok(code);
|
|
1146
|
+
}
|
|
1147
|
+
effects.enter("gfmFootnoteCallMarker");
|
|
1148
|
+
effects.consume(code);
|
|
1149
|
+
effects.exit("gfmFootnoteCallMarker");
|
|
1150
|
+
effects.enter("gfmFootnoteCallString");
|
|
1151
|
+
const token = effects.enter("chunkString");
|
|
1152
|
+
token.contentType = "string";
|
|
1153
|
+
return callData;
|
|
1154
|
+
}
|
|
1155
|
+
function callData(code) {
|
|
1156
|
+
if (
|
|
1157
|
+
// 太长
|
|
1158
|
+
size > constants.linkReferenceSizeMax || // 右括号但没有数据
|
|
1159
|
+
code === codes.rightSquareBracket && !data || // EOF、换行、空格、制表符、左括号不支持
|
|
1160
|
+
code === codes.eof || code === codes.leftSquareBracket || markdownLineEndingOrSpace(code)
|
|
1161
|
+
) {
|
|
1162
|
+
return nok(code);
|
|
1163
|
+
}
|
|
1164
|
+
if (code === codes.rightSquareBracket) {
|
|
1165
|
+
effects.exit("chunkString");
|
|
1166
|
+
effects.exit("gfmFootnoteCallString");
|
|
1167
|
+
effects.enter("gfmFootnoteCallLabelMarker");
|
|
1168
|
+
effects.consume(code);
|
|
1169
|
+
effects.exit("gfmFootnoteCallLabelMarker");
|
|
1170
|
+
effects.exit("gfmFootnoteCall");
|
|
1171
|
+
return ok2;
|
|
1172
|
+
}
|
|
1173
|
+
if (!markdownLineEndingOrSpace(code)) {
|
|
1174
|
+
data = true;
|
|
1175
|
+
}
|
|
1176
|
+
size++;
|
|
1177
|
+
effects.consume(code);
|
|
1178
|
+
return code === codes.backslash ? callEscape : callData;
|
|
1179
|
+
}
|
|
1180
|
+
function callEscape(code) {
|
|
1181
|
+
if (code === codes.leftSquareBracket || code === codes.backslash || code === codes.rightSquareBracket) {
|
|
1182
|
+
effects.consume(code);
|
|
1183
|
+
size++;
|
|
1184
|
+
return callData;
|
|
1185
|
+
}
|
|
1186
|
+
return callData(code);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
function tokenizePotentialGfmFootnoteCallIncremental(effects, ok2, nok) {
|
|
1190
|
+
const self = this;
|
|
1191
|
+
let index = self.events.length;
|
|
1192
|
+
let labelStart;
|
|
1193
|
+
while (index--) {
|
|
1194
|
+
const token = self.events[index][1];
|
|
1195
|
+
if (token.type === "labelImage") {
|
|
1196
|
+
labelStart = token;
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
1199
|
+
if (token.type === "gfmFootnoteCall" || token.type === "labelLink" || token.type === "label" || token.type === "image" || token.type === "link") {
|
|
1200
|
+
break;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
return start;
|
|
1204
|
+
function start(code) {
|
|
1205
|
+
if (code !== codes.rightSquareBracket) {
|
|
1206
|
+
return nok(code);
|
|
1207
|
+
}
|
|
1208
|
+
if (!labelStart || !labelStart._balanced) {
|
|
1209
|
+
return nok(code);
|
|
1210
|
+
}
|
|
1211
|
+
const id = normalizeIdentifier(
|
|
1212
|
+
self.sliceSerialize({
|
|
1213
|
+
start: labelStart.end,
|
|
1214
|
+
end: self.now()
|
|
1215
|
+
})
|
|
1216
|
+
);
|
|
1217
|
+
if (id.codePointAt(0) !== codes.caret) {
|
|
1218
|
+
return nok(code);
|
|
1219
|
+
}
|
|
1220
|
+
effects.enter("gfmFootnoteCallLabelMarker");
|
|
1221
|
+
effects.consume(code);
|
|
1222
|
+
effects.exit("gfmFootnoteCallLabelMarker");
|
|
1223
|
+
return ok2(code);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
7
1227
|
// src/detector/index.ts
|
|
8
1228
|
var RE_FENCE_START = /^(\s*)((`{3,})|(~{3,}))/;
|
|
9
1229
|
var RE_EMPTY_LINE = /^\s*$/;
|
|
@@ -16,6 +1236,8 @@ var RE_HTML_BLOCK_1 = /^\s{0,3}<(script|pre|style|textarea|!--|!DOCTYPE|\?|!\[CD
|
|
|
16
1236
|
var RE_HTML_BLOCK_2 = /^\s{0,3}<\/?[a-zA-Z][a-zA-Z0-9-]*(\s|>|$)/;
|
|
17
1237
|
var RE_TABLE_DELIMITER = /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?$/;
|
|
18
1238
|
var RE_ESCAPE_SPECIAL = /[.*+?^${}()|[\]\\]/g;
|
|
1239
|
+
var RE_FOOTNOTE_DEFINITION = /^\[\^[^\]]+\]:\s/;
|
|
1240
|
+
var RE_FOOTNOTE_CONTINUATION = /^(?: |\t)/;
|
|
19
1241
|
var fenceEndPatternCache = /* @__PURE__ */ new Map();
|
|
20
1242
|
var containerPatternCache = /* @__PURE__ */ new Map();
|
|
21
1243
|
function detectFenceStart(line) {
|
|
@@ -68,6 +1290,12 @@ function isHtmlBlock(line) {
|
|
|
68
1290
|
function isTableDelimiter(line) {
|
|
69
1291
|
return RE_TABLE_DELIMITER.test(line.trim());
|
|
70
1292
|
}
|
|
1293
|
+
function isFootnoteDefinitionStart(line) {
|
|
1294
|
+
return RE_FOOTNOTE_DEFINITION.test(line);
|
|
1295
|
+
}
|
|
1296
|
+
function isFootnoteContinuation(line) {
|
|
1297
|
+
return RE_FOOTNOTE_CONTINUATION.test(line);
|
|
1298
|
+
}
|
|
71
1299
|
function detectContainer(line, config) {
|
|
72
1300
|
const marker = config?.marker || ":";
|
|
73
1301
|
const minLength = config?.minMarkerLength || 3;
|
|
@@ -179,6 +1407,34 @@ function updateContext(line, context, containerConfig) {
|
|
|
179
1407
|
return newContext;
|
|
180
1408
|
}
|
|
181
1409
|
|
|
1410
|
+
// src/utils/index.ts
|
|
1411
|
+
var idCounter = 0;
|
|
1412
|
+
function generateId(prefix = "block") {
|
|
1413
|
+
return `${prefix}-${++idCounter}`;
|
|
1414
|
+
}
|
|
1415
|
+
function resetIdCounter() {
|
|
1416
|
+
idCounter = 0;
|
|
1417
|
+
}
|
|
1418
|
+
function calculateLineOffset(lines, lineIndex) {
|
|
1419
|
+
let offset = 0;
|
|
1420
|
+
for (let i = 0; i < lineIndex && i < lines.length; i++) {
|
|
1421
|
+
offset += lines[i].length + 1;
|
|
1422
|
+
}
|
|
1423
|
+
return offset;
|
|
1424
|
+
}
|
|
1425
|
+
function splitLines(text) {
|
|
1426
|
+
return text.split("\n");
|
|
1427
|
+
}
|
|
1428
|
+
function joinLines(lines, start, end) {
|
|
1429
|
+
return lines.slice(start, end + 1).join("\n");
|
|
1430
|
+
}
|
|
1431
|
+
function isDefinitionNode(node) {
|
|
1432
|
+
return node.type === "definition";
|
|
1433
|
+
}
|
|
1434
|
+
function isFootnoteDefinitionNode(node) {
|
|
1435
|
+
return node.type === "footnoteDefinition";
|
|
1436
|
+
}
|
|
1437
|
+
|
|
182
1438
|
// src/parser/IncremarkParser.ts
|
|
183
1439
|
var IncremarkParser = class {
|
|
184
1440
|
buffer = "";
|
|
@@ -192,8 +1448,16 @@ var IncremarkParser = class {
|
|
|
192
1448
|
options;
|
|
193
1449
|
/** 缓存的容器配置,避免重复计算 */
|
|
194
1450
|
containerConfig;
|
|
1451
|
+
/** 缓存的 HTML 树配置,避免重复计算 */
|
|
1452
|
+
htmlTreeConfig;
|
|
195
1453
|
/** 上次 append 返回的 pending blocks,用于 getAst 复用 */
|
|
196
1454
|
lastPendingBlocks = [];
|
|
1455
|
+
/** Definition 映射表(用于引用式图片和链接) */
|
|
1456
|
+
definitionMap = {};
|
|
1457
|
+
/** Footnote Definition 映射表 */
|
|
1458
|
+
footnoteDefinitionMap = {};
|
|
1459
|
+
/** Footnote Reference 出现顺序(按引用在文档中的顺序) */
|
|
1460
|
+
footnoteReferenceOrder = [];
|
|
197
1461
|
constructor(options = {}) {
|
|
198
1462
|
this.options = {
|
|
199
1463
|
gfm: true,
|
|
@@ -201,6 +1465,7 @@ var IncremarkParser = class {
|
|
|
201
1465
|
};
|
|
202
1466
|
this.context = createInitialContext();
|
|
203
1467
|
this.containerConfig = this.computeContainerConfig();
|
|
1468
|
+
this.htmlTreeConfig = this.computeHtmlTreeConfig();
|
|
204
1469
|
}
|
|
205
1470
|
generateBlockId() {
|
|
206
1471
|
return `block-${++this.blockIdCounter}`;
|
|
@@ -210,12 +1475,82 @@ var IncremarkParser = class {
|
|
|
210
1475
|
if (!containers) return void 0;
|
|
211
1476
|
return containers === true ? {} : containers;
|
|
212
1477
|
}
|
|
1478
|
+
computeHtmlTreeConfig() {
|
|
1479
|
+
const htmlTree = this.options.htmlTree;
|
|
1480
|
+
if (!htmlTree) return void 0;
|
|
1481
|
+
return htmlTree === true ? {} : htmlTree;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* 将 HTML 节点转换为纯文本
|
|
1485
|
+
* 递归处理 AST 中所有 html 类型的节点
|
|
1486
|
+
* - 块级 HTML 节点 → 转换为 paragraph 包含 text
|
|
1487
|
+
* - 内联 HTML 节点(在段落内部)→ 转换为 text 节点
|
|
1488
|
+
*/
|
|
1489
|
+
convertHtmlToText(ast) {
|
|
1490
|
+
const processInlineChildren = (children) => {
|
|
1491
|
+
return children.map((node) => {
|
|
1492
|
+
const n = node;
|
|
1493
|
+
if (n.type === "html") {
|
|
1494
|
+
const htmlNode = n;
|
|
1495
|
+
const textNode = {
|
|
1496
|
+
type: "text",
|
|
1497
|
+
value: htmlNode.value,
|
|
1498
|
+
position: htmlNode.position
|
|
1499
|
+
};
|
|
1500
|
+
return textNode;
|
|
1501
|
+
}
|
|
1502
|
+
if ("children" in n && Array.isArray(n.children)) {
|
|
1503
|
+
const parent = n;
|
|
1504
|
+
return {
|
|
1505
|
+
...parent,
|
|
1506
|
+
children: processInlineChildren(parent.children)
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
return n;
|
|
1510
|
+
});
|
|
1511
|
+
};
|
|
1512
|
+
const processBlockChildren = (children) => {
|
|
1513
|
+
return children.map((node) => {
|
|
1514
|
+
if (node.type === "html") {
|
|
1515
|
+
const htmlNode = node;
|
|
1516
|
+
const textNode = {
|
|
1517
|
+
type: "text",
|
|
1518
|
+
value: htmlNode.value
|
|
1519
|
+
};
|
|
1520
|
+
const paragraphNode = {
|
|
1521
|
+
type: "paragraph",
|
|
1522
|
+
children: [textNode],
|
|
1523
|
+
position: htmlNode.position
|
|
1524
|
+
};
|
|
1525
|
+
return paragraphNode;
|
|
1526
|
+
}
|
|
1527
|
+
if ("children" in node && Array.isArray(node.children)) {
|
|
1528
|
+
const parent = node;
|
|
1529
|
+
if (node.type === "paragraph" || node.type === "heading" || node.type === "tableCell" || node.type === "delete" || node.type === "emphasis" || node.type === "strong" || node.type === "link" || node.type === "linkReference") {
|
|
1530
|
+
return {
|
|
1531
|
+
...parent,
|
|
1532
|
+
children: processInlineChildren(parent.children)
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
return {
|
|
1536
|
+
...parent,
|
|
1537
|
+
children: processBlockChildren(parent.children)
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
return node;
|
|
1541
|
+
});
|
|
1542
|
+
};
|
|
1543
|
+
return {
|
|
1544
|
+
...ast,
|
|
1545
|
+
children: processBlockChildren(ast.children)
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
213
1548
|
parse(text) {
|
|
214
1549
|
const extensions = [];
|
|
215
1550
|
const mdastExtensions = [];
|
|
216
1551
|
if (this.options.gfm) {
|
|
217
1552
|
extensions.push(gfm());
|
|
218
|
-
mdastExtensions.push(...gfmFromMarkdown());
|
|
1553
|
+
mdastExtensions.push(...gfmFromMarkdown(), gfmFootnoteFromMarkdown());
|
|
219
1554
|
}
|
|
220
1555
|
if (this.options.extensions) {
|
|
221
1556
|
extensions.push(...this.options.extensions);
|
|
@@ -223,7 +1558,79 @@ var IncremarkParser = class {
|
|
|
223
1558
|
if (this.options.mdastExtensions) {
|
|
224
1559
|
mdastExtensions.push(...this.options.mdastExtensions);
|
|
225
1560
|
}
|
|
226
|
-
|
|
1561
|
+
if (this.options.gfm) {
|
|
1562
|
+
extensions.push(gfmFootnoteIncremental());
|
|
1563
|
+
}
|
|
1564
|
+
extensions.push(micromarkReferenceExtension());
|
|
1565
|
+
let ast = fromMarkdown(text, { extensions, mdastExtensions });
|
|
1566
|
+
if (this.htmlTreeConfig) {
|
|
1567
|
+
ast = transformHtmlNodes(ast, this.htmlTreeConfig);
|
|
1568
|
+
} else {
|
|
1569
|
+
ast = this.convertHtmlToText(ast);
|
|
1570
|
+
}
|
|
1571
|
+
return ast;
|
|
1572
|
+
}
|
|
1573
|
+
updateDefinationsFromComplatedBlocks(blocks) {
|
|
1574
|
+
for (const block of blocks) {
|
|
1575
|
+
this.definitionMap = {
|
|
1576
|
+
...this.definitionMap,
|
|
1577
|
+
...this.findDefinition(block)
|
|
1578
|
+
};
|
|
1579
|
+
this.footnoteDefinitionMap = {
|
|
1580
|
+
...this.footnoteDefinitionMap,
|
|
1581
|
+
...this.findFootnoteDefinition(block)
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
findDefinition(block) {
|
|
1586
|
+
const definitions = [];
|
|
1587
|
+
function findDefination(node) {
|
|
1588
|
+
if (isDefinitionNode(node)) {
|
|
1589
|
+
definitions.push(node);
|
|
1590
|
+
}
|
|
1591
|
+
if ("children" in node && Array.isArray(node.children)) {
|
|
1592
|
+
for (const child of node.children) {
|
|
1593
|
+
findDefination(child);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
findDefination(block.node);
|
|
1598
|
+
return definitions.reduce((acc, node) => {
|
|
1599
|
+
acc[node.identifier] = node;
|
|
1600
|
+
return acc;
|
|
1601
|
+
}, {});
|
|
1602
|
+
}
|
|
1603
|
+
findFootnoteDefinition(block) {
|
|
1604
|
+
const footnoteDefinitions = [];
|
|
1605
|
+
function findFootnoteDefinition(node) {
|
|
1606
|
+
if (isFootnoteDefinitionNode(node)) {
|
|
1607
|
+
footnoteDefinitions.push(node);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
findFootnoteDefinition(block.node);
|
|
1611
|
+
return footnoteDefinitions.reduce((acc, node) => {
|
|
1612
|
+
acc[node.identifier] = node;
|
|
1613
|
+
return acc;
|
|
1614
|
+
}, {});
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* 收集 AST 中的脚注引用(按出现顺序)
|
|
1618
|
+
* 用于确定脚注的显示顺序
|
|
1619
|
+
*/
|
|
1620
|
+
collectFootnoteReferences(nodes) {
|
|
1621
|
+
const visitNode = (node) => {
|
|
1622
|
+
if (!node) return;
|
|
1623
|
+
if (node.type === "footnoteReference") {
|
|
1624
|
+
const identifier = node.identifier;
|
|
1625
|
+
if (!this.footnoteReferenceOrder.includes(identifier)) {
|
|
1626
|
+
this.footnoteReferenceOrder.push(identifier);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
if (node.children && Array.isArray(node.children)) {
|
|
1630
|
+
node.children.forEach(visitNode);
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
nodes.forEach(visitNode);
|
|
227
1634
|
}
|
|
228
1635
|
/**
|
|
229
1636
|
* 增量更新 lines 和 lineOffsets
|
|
@@ -310,7 +1717,30 @@ var IncremarkParser = class {
|
|
|
310
1717
|
if (lineIndex >= this.lines.length - 1) {
|
|
311
1718
|
return -1;
|
|
312
1719
|
}
|
|
1720
|
+
if (isFootnoteDefinitionStart(prevLine)) {
|
|
1721
|
+
if (isEmptyLine(line) || isFootnoteContinuation(line)) {
|
|
1722
|
+
return -1;
|
|
1723
|
+
}
|
|
1724
|
+
if (isFootnoteDefinitionStart(line)) {
|
|
1725
|
+
return lineIndex - 1;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
if (!isEmptyLine(prevLine) && isFootnoteContinuation(prevLine)) {
|
|
1729
|
+
const footnoteStartLine = this.findFootnoteStart(lineIndex - 1);
|
|
1730
|
+
if (footnoteStartLine >= 0) {
|
|
1731
|
+
if (isEmptyLine(line) || isFootnoteContinuation(line)) {
|
|
1732
|
+
return -1;
|
|
1733
|
+
}
|
|
1734
|
+
if (isFootnoteDefinitionStart(line)) {
|
|
1735
|
+
return lineIndex - 1;
|
|
1736
|
+
}
|
|
1737
|
+
return lineIndex - 1;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
313
1740
|
if (!isEmptyLine(prevLine)) {
|
|
1741
|
+
if (isFootnoteDefinitionStart(line) && !isFootnoteDefinitionStart(prevLine)) {
|
|
1742
|
+
return lineIndex - 1;
|
|
1743
|
+
}
|
|
314
1744
|
if (isHeading(line)) {
|
|
315
1745
|
return lineIndex - 1;
|
|
316
1746
|
}
|
|
@@ -338,6 +1768,37 @@ var IncremarkParser = class {
|
|
|
338
1768
|
}
|
|
339
1769
|
return -1;
|
|
340
1770
|
}
|
|
1771
|
+
/**
|
|
1772
|
+
* 从指定行向上查找脚注定义的起始行
|
|
1773
|
+
*
|
|
1774
|
+
* @param fromLine 开始查找的行索引
|
|
1775
|
+
* @returns 脚注起始行索引,如果不属于脚注返回 -1
|
|
1776
|
+
*
|
|
1777
|
+
* @example
|
|
1778
|
+
* // 假设 lines 为:
|
|
1779
|
+
* // 0: "[^1]: 第一行"
|
|
1780
|
+
* // 1: " 第二行"
|
|
1781
|
+
* // 2: " 第三行"
|
|
1782
|
+
* findFootnoteStart(2) // 返回 0
|
|
1783
|
+
* findFootnoteStart(1) // 返回 0
|
|
1784
|
+
*/
|
|
1785
|
+
findFootnoteStart(fromLine) {
|
|
1786
|
+
const maxLookback = 20;
|
|
1787
|
+
const startLine = Math.max(0, fromLine - maxLookback);
|
|
1788
|
+
for (let i = fromLine; i >= startLine; i--) {
|
|
1789
|
+
const line = this.lines[i];
|
|
1790
|
+
if (isFootnoteDefinitionStart(line)) {
|
|
1791
|
+
return i;
|
|
1792
|
+
}
|
|
1793
|
+
if (isEmptyLine(line)) {
|
|
1794
|
+
continue;
|
|
1795
|
+
}
|
|
1796
|
+
if (!isFootnoteContinuation(line)) {
|
|
1797
|
+
return -1;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return -1;
|
|
1801
|
+
}
|
|
341
1802
|
nodesToBlocks(nodes, startOffset, rawText, status) {
|
|
342
1803
|
const blocks = [];
|
|
343
1804
|
let currentOffset = startOffset;
|
|
@@ -368,7 +1829,10 @@ var IncremarkParser = class {
|
|
|
368
1829
|
completed: [],
|
|
369
1830
|
updated: [],
|
|
370
1831
|
pending: [],
|
|
371
|
-
ast: { type: "root", children: [] }
|
|
1832
|
+
ast: { type: "root", children: [] },
|
|
1833
|
+
definitions: {},
|
|
1834
|
+
footnoteDefinitions: {},
|
|
1835
|
+
footnoteReferenceOrder: []
|
|
372
1836
|
};
|
|
373
1837
|
if (stableBoundary >= this.pendingStartLine && stableBoundary >= 0) {
|
|
374
1838
|
const stableText = this.lines.slice(this.pendingStartLine, stableBoundary + 1).join("\n");
|
|
@@ -377,6 +1841,7 @@ var IncremarkParser = class {
|
|
|
377
1841
|
const newBlocks = this.nodesToBlocks(ast.children, stableOffset, stableText, "completed");
|
|
378
1842
|
this.completedBlocks.push(...newBlocks);
|
|
379
1843
|
update.completed = newBlocks;
|
|
1844
|
+
this.updateDefinationsFromComplatedBlocks(newBlocks);
|
|
380
1845
|
this.context = contextAtLine;
|
|
381
1846
|
this.pendingStartLine = stableBoundary + 1;
|
|
382
1847
|
}
|
|
@@ -393,6 +1858,10 @@ var IncremarkParser = class {
|
|
|
393
1858
|
type: "root",
|
|
394
1859
|
children: [...this.completedBlocks.map((b) => b.node), ...update.pending.map((b) => b.node)]
|
|
395
1860
|
};
|
|
1861
|
+
this.collectFootnoteReferences(update.ast.children);
|
|
1862
|
+
update.definitions = this.getDefinitionMap();
|
|
1863
|
+
update.footnoteDefinitions = this.getFootnoteDefinitionMap();
|
|
1864
|
+
update.footnoteReferenceOrder = this.getFootnoteReferenceOrder();
|
|
396
1865
|
this.emitChange(update.pending);
|
|
397
1866
|
return update;
|
|
398
1867
|
}
|
|
@@ -401,7 +1870,7 @@ var IncremarkParser = class {
|
|
|
401
1870
|
*/
|
|
402
1871
|
emitChange(pendingBlocks = []) {
|
|
403
1872
|
if (this.options.onChange) {
|
|
404
|
-
|
|
1873
|
+
const state = {
|
|
405
1874
|
completedBlocks: this.completedBlocks,
|
|
406
1875
|
pendingBlocks,
|
|
407
1876
|
markdown: this.buffer,
|
|
@@ -411,8 +1880,11 @@ var IncremarkParser = class {
|
|
|
411
1880
|
...this.completedBlocks.map((b) => b.node),
|
|
412
1881
|
...pendingBlocks.map((b) => b.node)
|
|
413
1882
|
]
|
|
414
|
-
}
|
|
415
|
-
|
|
1883
|
+
},
|
|
1884
|
+
definitions: { ...this.definitionMap },
|
|
1885
|
+
footnoteDefinitions: { ...this.footnoteDefinitionMap }
|
|
1886
|
+
};
|
|
1887
|
+
this.options.onChange(state);
|
|
416
1888
|
}
|
|
417
1889
|
}
|
|
418
1890
|
/**
|
|
@@ -424,7 +1896,10 @@ var IncremarkParser = class {
|
|
|
424
1896
|
completed: [],
|
|
425
1897
|
updated: [],
|
|
426
1898
|
pending: [],
|
|
427
|
-
ast: { type: "root", children: [] }
|
|
1899
|
+
ast: { type: "root", children: [] },
|
|
1900
|
+
definitions: {},
|
|
1901
|
+
footnoteDefinitions: {},
|
|
1902
|
+
footnoteReferenceOrder: []
|
|
428
1903
|
};
|
|
429
1904
|
if (this.pendingStartLine < this.lines.length) {
|
|
430
1905
|
const remainingText = this.lines.slice(this.pendingStartLine).join("\n");
|
|
@@ -439,6 +1914,7 @@ var IncremarkParser = class {
|
|
|
439
1914
|
);
|
|
440
1915
|
this.completedBlocks.push(...finalBlocks);
|
|
441
1916
|
update.completed = finalBlocks;
|
|
1917
|
+
this.updateDefinationsFromComplatedBlocks(finalBlocks);
|
|
442
1918
|
}
|
|
443
1919
|
}
|
|
444
1920
|
this.lastPendingBlocks = [];
|
|
@@ -447,6 +1923,10 @@ var IncremarkParser = class {
|
|
|
447
1923
|
type: "root",
|
|
448
1924
|
children: this.completedBlocks.map((b) => b.node)
|
|
449
1925
|
};
|
|
1926
|
+
this.collectFootnoteReferences(update.ast.children);
|
|
1927
|
+
update.definitions = this.getDefinitionMap();
|
|
1928
|
+
update.footnoteDefinitions = this.getFootnoteDefinitionMap();
|
|
1929
|
+
update.footnoteReferenceOrder = this.getFootnoteReferenceOrder();
|
|
450
1930
|
this.emitChange([]);
|
|
451
1931
|
return update;
|
|
452
1932
|
}
|
|
@@ -462,12 +1942,14 @@ var IncremarkParser = class {
|
|
|
462
1942
|
* 复用上次 append 的 pending 结果,避免重复解析
|
|
463
1943
|
*/
|
|
464
1944
|
getAst() {
|
|
1945
|
+
const children = [
|
|
1946
|
+
...this.completedBlocks.map((b) => b.node),
|
|
1947
|
+
...this.lastPendingBlocks.map((b) => b.node)
|
|
1948
|
+
];
|
|
1949
|
+
this.collectFootnoteReferences(children);
|
|
465
1950
|
return {
|
|
466
1951
|
type: "root",
|
|
467
|
-
children
|
|
468
|
-
...this.completedBlocks.map((b) => b.node),
|
|
469
|
-
...this.lastPendingBlocks.map((b) => b.node)
|
|
470
|
-
]
|
|
1952
|
+
children
|
|
471
1953
|
};
|
|
472
1954
|
}
|
|
473
1955
|
/**
|
|
@@ -482,11 +1964,33 @@ var IncremarkParser = class {
|
|
|
482
1964
|
getBuffer() {
|
|
483
1965
|
return this.buffer;
|
|
484
1966
|
}
|
|
1967
|
+
/**
|
|
1968
|
+
* 获取 Definition 映射表(用于引用式图片和链接)
|
|
1969
|
+
*/
|
|
1970
|
+
getDefinitionMap() {
|
|
1971
|
+
return { ...this.definitionMap };
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* 获取 Footnote Definition 映射表
|
|
1975
|
+
*/
|
|
1976
|
+
getFootnoteDefinitionMap() {
|
|
1977
|
+
return { ...this.footnoteDefinitionMap };
|
|
1978
|
+
}
|
|
1979
|
+
/**
|
|
1980
|
+
* 获取脚注引用的出现顺序
|
|
1981
|
+
*/
|
|
1982
|
+
getFootnoteReferenceOrder() {
|
|
1983
|
+
return [...this.footnoteReferenceOrder];
|
|
1984
|
+
}
|
|
485
1985
|
/**
|
|
486
1986
|
* 设置状态变化回调(用于 DevTools 等)
|
|
487
1987
|
*/
|
|
488
1988
|
setOnChange(callback) {
|
|
489
|
-
this.options.onChange
|
|
1989
|
+
const originalOnChange = this.options.onChange;
|
|
1990
|
+
this.options.onChange = (state) => {
|
|
1991
|
+
originalOnChange?.(state);
|
|
1992
|
+
callback?.(state);
|
|
1993
|
+
};
|
|
490
1994
|
}
|
|
491
1995
|
/**
|
|
492
1996
|
* 重置解析器状态
|
|
@@ -500,6 +2004,9 @@ var IncremarkParser = class {
|
|
|
500
2004
|
this.blockIdCounter = 0;
|
|
501
2005
|
this.context = createInitialContext();
|
|
502
2006
|
this.lastPendingBlocks = [];
|
|
2007
|
+
this.definitionMap = {};
|
|
2008
|
+
this.footnoteDefinitionMap = {};
|
|
2009
|
+
this.footnoteReferenceOrder = [];
|
|
503
2010
|
this.emitChange([]);
|
|
504
2011
|
}
|
|
505
2012
|
/**
|
|
@@ -517,28 +2024,6 @@ function createIncremarkParser(options) {
|
|
|
517
2024
|
return new IncremarkParser(options);
|
|
518
2025
|
}
|
|
519
2026
|
|
|
520
|
-
// src/utils/index.ts
|
|
521
|
-
var idCounter = 0;
|
|
522
|
-
function generateId(prefix = "block") {
|
|
523
|
-
return `${prefix}-${++idCounter}`;
|
|
524
|
-
}
|
|
525
|
-
function resetIdCounter() {
|
|
526
|
-
idCounter = 0;
|
|
527
|
-
}
|
|
528
|
-
function calculateLineOffset(lines, lineIndex) {
|
|
529
|
-
let offset = 0;
|
|
530
|
-
for (let i = 0; i < lineIndex && i < lines.length; i++) {
|
|
531
|
-
offset += lines[i].length + 1;
|
|
532
|
-
}
|
|
533
|
-
return offset;
|
|
534
|
-
}
|
|
535
|
-
function splitLines(text) {
|
|
536
|
-
return text.split("\n");
|
|
537
|
-
}
|
|
538
|
-
function joinLines(lines, start, end) {
|
|
539
|
-
return lines.slice(start, end + 1).join("\n");
|
|
540
|
-
}
|
|
541
|
-
|
|
542
2027
|
// src/transformer/utils.ts
|
|
543
2028
|
function countChars(node) {
|
|
544
2029
|
return countCharsInNode(node);
|
|
@@ -1256,7 +2741,51 @@ function createPlugin(name, matcher, options = {}) {
|
|
|
1256
2741
|
...options
|
|
1257
2742
|
};
|
|
1258
2743
|
}
|
|
2744
|
+
/**
|
|
2745
|
+
* @file Micromark 扩展:支持增量解析的 Reference 语法
|
|
2746
|
+
*
|
|
2747
|
+
* @description
|
|
2748
|
+
* 在增量解析场景中,引用式图片/链接(如 `![Alt][id]`)可能在定义(`[id]: url`)之前出现。
|
|
2749
|
+
* 标准 micromark 会检查 parser.defined,如果 id 未定义就解析为文本。
|
|
2750
|
+
*
|
|
2751
|
+
* 本扩展通过覆盖 labelEnd 构造,移除 parser.defined 检查,
|
|
2752
|
+
* 使得 reference 语法总是被解析为 reference token,
|
|
2753
|
+
* 由渲染层根据实际的 definitionMap 决定如何渲染。
|
|
2754
|
+
*
|
|
2755
|
+
* @module micromark-reference-extension
|
|
2756
|
+
*
|
|
2757
|
+
* @features
|
|
2758
|
+
* - ✅ 支持所有 resource 语法(带 title 的图片/链接)
|
|
2759
|
+
* - ✅ 支持所有 reference 语法(full, collapsed, shortcut)
|
|
2760
|
+
* - ✅ 延迟验证:解析时不检查定义是否存在
|
|
2761
|
+
* - ✅ 使用官方 factory 函数,保证与 CommonMark 标准一致
|
|
2762
|
+
*
|
|
2763
|
+
* @dependencies
|
|
2764
|
+
* - micromark-factory-destination: 解析 URL(支持尖括号、括号平衡)
|
|
2765
|
+
* - micromark-factory-title: 解析 title(支持三种引号,支持多行)
|
|
2766
|
+
* - micromark-factory-label: 解析 label(支持转义、长度限制)
|
|
2767
|
+
* - micromark-factory-whitespace: 解析空白符(正确生成 lineEnding/linePrefix token)
|
|
2768
|
+
* - micromark-util-character: 字符判断工具
|
|
2769
|
+
* - micromark-util-symbol: 常量(codes, types, constants)
|
|
2770
|
+
* - micromark-util-types: TypeScript 类型定义
|
|
2771
|
+
*
|
|
2772
|
+
* @see {@link https://github.com/micromark/micromark} - micromark 官方文档
|
|
2773
|
+
* @see {@link https://spec.commonmark.org/0.30/#images} - CommonMark 图片规范
|
|
2774
|
+
* @see {@link https://spec.commonmark.org/0.30/#links} - CommonMark 链接规范
|
|
2775
|
+
*
|
|
2776
|
+
* @example
|
|
2777
|
+
* ```typescript
|
|
2778
|
+
* import { micromarkReferenceExtension } from './micromark-reference-extension'
|
|
2779
|
+
* import { fromMarkdown } from 'mdast-util-from-markdown'
|
|
2780
|
+
*
|
|
2781
|
+
* const extensions = [micromarkReferenceExtension()]
|
|
2782
|
+
* const ast = fromMarkdown(text, { extensions })
|
|
2783
|
+
* ```
|
|
2784
|
+
*
|
|
2785
|
+
* @author Incremark Team
|
|
2786
|
+
* @license MIT
|
|
2787
|
+
*/
|
|
1259
2788
|
|
|
1260
|
-
export { BlockTransformer, IncremarkParser, allPlugins, calculateLineOffset, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createIncremarkParser, createInitialContext, createPlugin, defaultPlugins, detectContainer, detectContainerEnd, detectFenceEnd, detectFenceStart, generateId, imagePlugin, isBlockBoundary, isBlockquoteStart, isEmptyLine, isHeading, isHtmlBlock, isListItemStart, isTableDelimiter, isThematicBreak, joinLines, mathPlugin, mermaidPlugin, resetIdCounter, sliceAst, splitLines, thematicBreakPlugin, updateContext };
|
|
2789
|
+
export { BlockTransformer, DEFAULT_ATTR_BLACKLIST as HTML_ATTR_BLACKLIST, DEFAULT_PROTOCOL_BLACKLIST as HTML_PROTOCOL_BLACKLIST, DEFAULT_TAG_BLACKLIST as HTML_TAG_BLACKLIST, IncremarkParser, allPlugins, calculateLineOffset, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createHtmlTreeTransformer, createIncremarkParser, createInitialContext, createPlugin, defaultPlugins, detectContainer, detectContainerEnd, detectFenceEnd, detectFenceStart, detectHtmlContentType, findHtmlElementsByTag, generateId, htmlElementToString, htmlTreeExtension, imagePlugin, isBlockBoundary, isBlockquoteStart, isEmptyLine, isHeading, isHtmlBlock, isHtmlElementNode, isListItemStart, isTableDelimiter, isThematicBreak, joinLines, mathPlugin, mermaidPlugin, parseHtmlFragment, parseHtmlTag, resetIdCounter, sliceAst, splitLines, thematicBreakPlugin, transformHtmlNodes, updateContext, walkHtmlElements };
|
|
1261
2790
|
//# sourceMappingURL=index.js.map
|
|
1262
2791
|
//# sourceMappingURL=index.js.map
|