@incremark/core 0.2.5 → 0.2.7

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/index.js CHANGED
@@ -4,6 +4,8 @@ import { gfm } from 'micromark-extension-gfm';
4
4
  import { gfmFootnoteFromMarkdown } from 'mdast-util-gfm-footnote';
5
5
  import { math } from 'micromark-extension-math';
6
6
  import { mathFromMarkdown } from 'mdast-util-math';
7
+ import { directive } from 'micromark-extension-directive';
8
+ import { directiveFromMarkdown } from 'mdast-util-directive';
7
9
  import { codes, constants, types } from 'micromark-util-symbol';
8
10
  import { markdownLineEndingOrSpace } from 'micromark-util-character';
9
11
  import { factoryDestination } from 'micromark-factory-destination';
@@ -12,10 +14,699 @@ import { factoryLabel } from 'micromark-factory-label';
12
14
  import { factoryWhitespace } from 'micromark-factory-whitespace';
13
15
  import { gfmFootnote } from 'micromark-extension-gfm-footnote';
14
16
  import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
15
- import { directive } from 'micromark-extension-directive';
16
- import { directiveFromMarkdown } from 'mdast-util-directive';
17
17
 
18
- // src/parser/IncremarkParser.ts
18
+ // src/detector/index.ts
19
+ var RE_FENCE_START = /^(\s*)((`{3,})|(~{3,}))/;
20
+ var RE_EMPTY_LINE = /^\s*$/;
21
+ var RE_HEADING = /^#{1,6}\s/;
22
+ var RE_THEMATIC_BREAK = /^(\*{3,}|-{3,}|_{3,})\s*$/;
23
+ var RE_BLOCKQUOTE = /^\s{0,3}>/;
24
+ var RE_ESCAPE_SPECIAL = /[.*+?^${}()|[\]\\]/g;
25
+ var RE_FOOTNOTE_DEFINITION = /^\[\^([^\]]+)\]:\s/;
26
+ var RE_FOOTNOTE_CONTINUATION = /^(?: |\t)/;
27
+ var fenceEndPatternCache = /* @__PURE__ */ new Map();
28
+ var containerPatternCache = /* @__PURE__ */ new Map();
29
+ function detectFenceStart(line) {
30
+ const match = line.match(RE_FENCE_START);
31
+ if (match) {
32
+ const fence = match[2];
33
+ const char = fence[0];
34
+ return { char, length: fence.length };
35
+ }
36
+ return null;
37
+ }
38
+ function detectFenceEnd(line, context) {
39
+ if (!context.inFencedCode || !context.fenceChar || !context.fenceLength) {
40
+ return false;
41
+ }
42
+ const cacheKey = `${context.fenceChar}-${context.fenceLength}`;
43
+ let pattern = fenceEndPatternCache.get(cacheKey);
44
+ if (!pattern) {
45
+ pattern = new RegExp(`^\\s{0,3}${context.fenceChar}{${context.fenceLength},}\\s*$`);
46
+ fenceEndPatternCache.set(cacheKey, pattern);
47
+ }
48
+ return pattern.test(line);
49
+ }
50
+ function isEmptyLine(line) {
51
+ return RE_EMPTY_LINE.test(line);
52
+ }
53
+ function isSetextHeadingUnderline(line, prevLine) {
54
+ const trimmed = line.trim();
55
+ if (!/^={3,}$|^-{3,}$/.test(trimmed)) {
56
+ return false;
57
+ }
58
+ if (!prevLine) {
59
+ return false;
60
+ }
61
+ const trimmedPrev = prevLine.trim();
62
+ if (trimmedPrev === "") {
63
+ return false;
64
+ }
65
+ if (/^#{1,6}\s/.test(trimmedPrev) || /^(\*{3,}|-{3,}|_{3,})\s*$/.test(trimmedPrev)) {
66
+ return false;
67
+ }
68
+ if (/^(\s*)([-*+])\s/.test(trimmedPrev) || /^(\s*)(\d{1,9})[.)]\s/.test(trimmedPrev)) {
69
+ return false;
70
+ }
71
+ if (/^\s{0,3}>/.test(trimmedPrev)) {
72
+ return false;
73
+ }
74
+ if (/^(\s*)(`{3,}|~{3,})/.test(trimmedPrev)) {
75
+ return false;
76
+ }
77
+ const underlineIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
78
+ if (underlineIndent > 3) {
79
+ return false;
80
+ }
81
+ const contentIndent = prevLine.match(/^(\s*)/)?.[1].length ?? 0;
82
+ if (contentIndent > 3) {
83
+ return false;
84
+ }
85
+ return true;
86
+ }
87
+ function isHeading(line) {
88
+ return RE_HEADING.test(line);
89
+ }
90
+ function isThematicBreak(line) {
91
+ return RE_THEMATIC_BREAK.test(line.trim());
92
+ }
93
+ function isListItemStart(line) {
94
+ const hasListMarker = /^(\s*)([-*+]|\d{1,9}[.)])/.test(line);
95
+ if (!hasListMarker) {
96
+ return null;
97
+ }
98
+ const match = line.match(/^(\s*)([-*+]|\d{1,9}[.)])(.*)/);
99
+ if (match) {
100
+ const indent = match[1].length;
101
+ const marker = match[2];
102
+ const rest = match[3];
103
+ if (rest.trim()) {
104
+ const isOrdered = /^\d{1,9}[.)]/.test(marker);
105
+ return { ordered: isOrdered, indent };
106
+ }
107
+ if (/^\s+$/.test(rest)) {
108
+ const isOrdered = /^\d{1,9}[.)]/.test(marker);
109
+ return { ordered: isOrdered, indent };
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+ function isBlockquoteStart(line) {
115
+ return RE_BLOCKQUOTE.test(line);
116
+ }
117
+ function isFootnoteDefinitionStart(line) {
118
+ return RE_FOOTNOTE_DEFINITION.test(line);
119
+ }
120
+ function isFootnoteContinuation(line) {
121
+ return RE_FOOTNOTE_CONTINUATION.test(line);
122
+ }
123
+ function detectContainer(line, config) {
124
+ const marker = config?.marker || ":";
125
+ const minLength = config?.minMarkerLength || 3;
126
+ const cacheKey = `${marker}-${minLength}`;
127
+ let pattern = containerPatternCache.get(cacheKey);
128
+ if (!pattern) {
129
+ const escapedMarker = marker.replace(RE_ESCAPE_SPECIAL, "\\$&");
130
+ pattern = new RegExp(
131
+ `^(\\s*)(${escapedMarker}{${minLength},})(?:\\s*(\\w[\\w-]*))?(?:\\{[^}]*\\})?(?:\\s+(.*))?\\s*$`
132
+ );
133
+ containerPatternCache.set(cacheKey, pattern);
134
+ }
135
+ const match = line.match(pattern);
136
+ if (!match) {
137
+ return null;
138
+ }
139
+ const markerLength = match[2].length;
140
+ const name = match[3] || "";
141
+ const isEnd = !name && !match[4];
142
+ if (!isEnd && config?.allowedNames && config.allowedNames.length > 0) {
143
+ if (!config.allowedNames.includes(name)) {
144
+ return null;
145
+ }
146
+ }
147
+ return { name, markerLength, isEnd };
148
+ }
149
+ function detectContainerEnd(line, context, config) {
150
+ if (!context.inContainer || !context.containerMarkerLength) {
151
+ return false;
152
+ }
153
+ const result = detectContainer(line, config);
154
+ if (!result) {
155
+ return false;
156
+ }
157
+ return result.isEnd && result.markerLength >= context.containerMarkerLength;
158
+ }
159
+ var CodeContextUpdater = class {
160
+ update(line, context) {
161
+ const newContext = { ...context };
162
+ if (context.inFencedCode) {
163
+ if (detectFenceEnd(line, context)) {
164
+ newContext.inFencedCode = false;
165
+ newContext.fenceChar = void 0;
166
+ newContext.fenceLength = void 0;
167
+ return newContext;
168
+ }
169
+ return null;
170
+ }
171
+ const fence = detectFenceStart(line);
172
+ if (fence) {
173
+ newContext.inFencedCode = true;
174
+ newContext.fenceChar = fence.char;
175
+ newContext.fenceLength = fence.length;
176
+ return newContext;
177
+ }
178
+ return null;
179
+ }
180
+ };
181
+ var ContainerContextUpdater = class {
182
+ update(line, context, config) {
183
+ if (config === void 0) {
184
+ return null;
185
+ }
186
+ const newContext = { ...context };
187
+ if (context.inContainer) {
188
+ if (detectContainerEnd(line, context, config)) {
189
+ newContext.containerDepth = context.containerDepth - 1;
190
+ if (newContext.containerDepth === 0) {
191
+ newContext.inContainer = false;
192
+ newContext.containerMarkerLength = void 0;
193
+ newContext.containerName = void 0;
194
+ }
195
+ return newContext;
196
+ }
197
+ const nested = detectContainer(line, config);
198
+ if (nested && !nested.isEnd) {
199
+ newContext.containerDepth = context.containerDepth + 1;
200
+ return newContext;
201
+ }
202
+ return newContext;
203
+ } else {
204
+ const container = detectContainer(line, config);
205
+ if (container && !container.isEnd) {
206
+ newContext.inContainer = true;
207
+ newContext.containerMarkerLength = container.markerLength;
208
+ newContext.containerName = container.name;
209
+ newContext.containerDepth = 1;
210
+ return newContext;
211
+ }
212
+ }
213
+ return null;
214
+ }
215
+ };
216
+ var FootnoteContextUpdater = class {
217
+ update(line, context) {
218
+ const newContext = { ...context };
219
+ if (!context.inFootnote && isFootnoteDefinitionStart(line)) {
220
+ const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
221
+ newContext.inFootnote = true;
222
+ newContext.footnoteIdentifier = identifier;
223
+ return newContext;
224
+ }
225
+ if (context.inFootnote) {
226
+ if (isFootnoteDefinitionStart(line)) {
227
+ const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
228
+ newContext.footnoteIdentifier = identifier;
229
+ return newContext;
230
+ }
231
+ if (isEmptyLine(line)) {
232
+ return { ...context };
233
+ }
234
+ const listItem = isListItemStart(line);
235
+ if (listItem) {
236
+ if (listItem.indent === 0) {
237
+ newContext.inFootnote = false;
238
+ newContext.footnoteIdentifier = void 0;
239
+ } else {
240
+ return { ...context };
241
+ }
242
+ return null;
243
+ }
244
+ if (isHeading(line) || detectFenceStart(line) || isBlockquoteStart(line)) {
245
+ newContext.inFootnote = false;
246
+ newContext.footnoteIdentifier = void 0;
247
+ return newContext;
248
+ }
249
+ if (isFootnoteContinuation(line)) {
250
+ return { ...context };
251
+ }
252
+ newContext.inFootnote = false;
253
+ newContext.footnoteIdentifier = void 0;
254
+ return newContext;
255
+ }
256
+ return null;
257
+ }
258
+ };
259
+ var ListContextUpdater = class {
260
+ /**
261
+ * 检测是否是列表项的延续内容(缩进内容或空行)
262
+ */
263
+ isListContinuation(line, listIndent) {
264
+ if (isEmptyLine(line)) {
265
+ return true;
266
+ }
267
+ const contentIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
268
+ return contentIndent > listIndent;
269
+ }
270
+ update(line, context) {
271
+ const newContext = { ...context };
272
+ const listItem = isListItemStart(line);
273
+ if (context.inList) {
274
+ if (context.listMayEnd) {
275
+ if (listItem) {
276
+ if (listItem.ordered === context.listOrdered && listItem.indent === context.listIndent) {
277
+ newContext.listMayEnd = false;
278
+ return newContext;
279
+ }
280
+ newContext.listOrdered = listItem.ordered;
281
+ newContext.listIndent = listItem.indent;
282
+ newContext.listMayEnd = false;
283
+ return newContext;
284
+ } else if (this.isListContinuation(line, context.listIndent ?? 0)) {
285
+ newContext.listMayEnd = isEmptyLine(line);
286
+ return newContext;
287
+ } else {
288
+ newContext.inList = false;
289
+ newContext.listOrdered = void 0;
290
+ newContext.listIndent = void 0;
291
+ newContext.listMayEnd = false;
292
+ return newContext;
293
+ }
294
+ } else {
295
+ if (listItem) {
296
+ return null;
297
+ } else if (isEmptyLine(line)) {
298
+ newContext.listMayEnd = true;
299
+ return newContext;
300
+ } else if (this.isListContinuation(line, context.listIndent ?? 0)) {
301
+ return null;
302
+ } else {
303
+ newContext.inList = false;
304
+ newContext.listOrdered = void 0;
305
+ newContext.listIndent = void 0;
306
+ newContext.listMayEnd = false;
307
+ return newContext;
308
+ }
309
+ }
310
+ } else {
311
+ if (listItem) {
312
+ newContext.inList = true;
313
+ newContext.listOrdered = listItem.ordered;
314
+ newContext.listIndent = listItem.indent;
315
+ newContext.listMayEnd = false;
316
+ return newContext;
317
+ }
318
+ }
319
+ return null;
320
+ }
321
+ };
322
+ function createInitialContext() {
323
+ return {
324
+ inFencedCode: false,
325
+ listDepth: 0,
326
+ blockquoteDepth: 0,
327
+ inContainer: false,
328
+ containerDepth: 0,
329
+ inList: false,
330
+ inFootnote: false,
331
+ footnoteIdentifier: void 0
332
+ };
333
+ }
334
+ var ContextManager = class {
335
+ updaters = [
336
+ new CodeContextUpdater(),
337
+ new ContainerContextUpdater(),
338
+ new FootnoteContextUpdater(),
339
+ new ListContextUpdater()
340
+ ];
341
+ /**
342
+ * 更新上下文(处理一行后)
343
+ *
344
+ * @param line 当前行
345
+ * @param context 当前上下文
346
+ * @param containerConfig 容器配置
347
+ * @returns 更新后的上下文
348
+ */
349
+ update(line, context, containerConfig) {
350
+ const config = containerConfig === true ? {} : containerConfig === false ? void 0 : containerConfig;
351
+ for (const updater of this.updaters) {
352
+ const result = updater.update(line, context, config);
353
+ if (result !== null) {
354
+ return result;
355
+ }
356
+ }
357
+ return { ...context };
358
+ }
359
+ };
360
+ var contextManager = new ContextManager();
361
+ function updateContext(line, context, containerConfig) {
362
+ return contextManager.update(line, context, containerConfig);
363
+ }
364
+
365
+ // src/parser/boundary/BoundaryDetector.ts
366
+ var ContainerBoundaryChecker = class {
367
+ constructor(containerConfig) {
368
+ this.containerConfig = containerConfig;
369
+ }
370
+ check(lineIndex, context, lines) {
371
+ const line = lines[lineIndex];
372
+ if (!context.inContainer) {
373
+ return -1;
374
+ }
375
+ if (this.containerConfig !== void 0) {
376
+ const containerEnd = detectContainerEnd(line, context, this.containerConfig);
377
+ if (containerEnd) {
378
+ return lineIndex - 1;
379
+ }
380
+ }
381
+ return -1;
382
+ }
383
+ };
384
+ var ListBoundaryChecker = class {
385
+ check(lineIndex, context, lines) {
386
+ if (!context.inList) {
387
+ return -1;
388
+ }
389
+ if (!context.listMayEnd) {
390
+ return -1;
391
+ }
392
+ const line = lines[lineIndex];
393
+ const listItem = isListItemStart(line);
394
+ const contentIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
395
+ const isListContent = contentIndent > (context.listIndent ?? 0);
396
+ if (!listItem && !isListContent && !isEmptyLine(line)) {
397
+ return lineIndex - 1;
398
+ }
399
+ return -1;
400
+ }
401
+ };
402
+ var FootnoteBoundaryChecker = class {
403
+ check(lineIndex, context, lines) {
404
+ const line = lines[lineIndex];
405
+ const prevLine = lines[lineIndex - 1];
406
+ if (isFootnoteDefinitionStart(prevLine)) {
407
+ if (isEmptyLine(line) || isFootnoteContinuation(line)) {
408
+ return -1;
409
+ }
410
+ if (isFootnoteDefinitionStart(line)) {
411
+ return lineIndex - 1;
412
+ }
413
+ }
414
+ if (!isEmptyLine(prevLine) && isFootnoteContinuation(prevLine)) {
415
+ return -1;
416
+ }
417
+ if (!isEmptyLine(prevLine) && isFootnoteDefinitionStart(line) && !isFootnoteDefinitionStart(prevLine)) {
418
+ return lineIndex - 1;
419
+ }
420
+ return -1;
421
+ }
422
+ };
423
+ var NewBlockBoundaryChecker = class {
424
+ check(lineIndex, context, lines) {
425
+ const line = lines[lineIndex];
426
+ const prevLine = lines[lineIndex - 1];
427
+ if (isEmptyLine(prevLine)) {
428
+ return -1;
429
+ }
430
+ if (isSetextHeadingUnderline(line, prevLine)) {
431
+ return lineIndex - 1;
432
+ }
433
+ if (isHeading(line)) {
434
+ return lineIndex - 1;
435
+ }
436
+ if (detectFenceStart(line)) {
437
+ return lineIndex - 1;
438
+ }
439
+ if (isBlockquoteStart(line) && !isBlockquoteStart(prevLine)) {
440
+ return lineIndex - 1;
441
+ }
442
+ if (!context.inList && isListItemStart(line) && !isListItemStart(prevLine)) {
443
+ return lineIndex - 1;
444
+ }
445
+ return -1;
446
+ }
447
+ };
448
+ var EmptyLineBoundaryChecker = class {
449
+ check(lineIndex, context, lines) {
450
+ const line = lines[lineIndex];
451
+ const prevLine = lines[lineIndex - 1];
452
+ if (isEmptyLine(line) && !isEmptyLine(prevLine) && !context.inList) {
453
+ return lineIndex;
454
+ }
455
+ return -1;
456
+ }
457
+ };
458
+ var BoundaryDetector = class {
459
+ containerConfig;
460
+ checkers;
461
+ constructor(config = {}) {
462
+ this.containerConfig = config.containers;
463
+ this.checkers = [
464
+ new ContainerBoundaryChecker(this.containerConfig),
465
+ new ListBoundaryChecker(),
466
+ new FootnoteBoundaryChecker(),
467
+ new NewBlockBoundaryChecker(),
468
+ new EmptyLineBoundaryChecker()
469
+ ];
470
+ }
471
+ /**
472
+ * 查找稳定边界
473
+ * 返回稳定边界行号和该行对应的上下文(用于后续更新,避免重复计算)
474
+ *
475
+ * @param lines 所有行
476
+ * @param startLine 起始行
477
+ * @param context 当前上下文
478
+ * @returns 稳定边界结果
479
+ */
480
+ findStableBoundary(lines, startLine, context) {
481
+ let stableLine = -1;
482
+ let stableContext = context;
483
+ let tempContext = { ...context };
484
+ for (let i = startLine; i < lines.length; i++) {
485
+ const line = lines[i];
486
+ const wasInFencedCode = tempContext.inFencedCode;
487
+ const wasInContainer = tempContext.inContainer;
488
+ const wasContainerDepth = tempContext.containerDepth;
489
+ tempContext = updateContext(line, tempContext, this.containerConfig);
490
+ if (wasInFencedCode && !tempContext.inFencedCode) {
491
+ if (i < lines.length - 1) {
492
+ stableLine = i;
493
+ stableContext = { ...tempContext };
494
+ }
495
+ continue;
496
+ }
497
+ if (tempContext.inFencedCode) {
498
+ continue;
499
+ }
500
+ if (wasInContainer && wasContainerDepth === 1 && !tempContext.inContainer) {
501
+ if (i < lines.length - 1) {
502
+ stableLine = i;
503
+ stableContext = { ...tempContext };
504
+ }
505
+ continue;
506
+ }
507
+ if (tempContext.inContainer) {
508
+ continue;
509
+ }
510
+ const stablePoint = this.checkStability(i, tempContext, lines);
511
+ if (stablePoint >= 0) {
512
+ stableLine = stablePoint;
513
+ stableContext = { ...tempContext };
514
+ }
515
+ }
516
+ return { line: stableLine, context: stableContext };
517
+ }
518
+ /**
519
+ * 检查指定行是否是稳定边界
520
+ * 使用责任链模式,依次调用各个检查器
521
+ *
522
+ * @param lineIndex 行索引
523
+ * @param context 当前上下文
524
+ * @param lines 所有行
525
+ * @returns 稳定边界行号,如果不是稳定边界返回 -1
526
+ */
527
+ checkStability(lineIndex, context, lines) {
528
+ if (lineIndex === 0) {
529
+ return -1;
530
+ }
531
+ const line = lines[lineIndex];
532
+ const prevLine = lines[lineIndex - 1];
533
+ if (context.inFootnote) {
534
+ if (isFootnoteDefinitionStart(prevLine) && !isEmptyLine(line)) {
535
+ if (isFootnoteContinuation(line)) {
536
+ return -1;
537
+ }
538
+ return lineIndex - 1;
539
+ }
540
+ if (isEmptyLine(prevLine) && (isEmptyLine(line) || isFootnoteContinuation(line))) {
541
+ return -1;
542
+ }
543
+ return -1;
544
+ }
545
+ if (isHeading(prevLine) || isThematicBreak(prevLine)) {
546
+ return lineIndex - 1;
547
+ }
548
+ if (isSetextHeadingUnderline(prevLine, lines[lineIndex - 2])) {
549
+ return lineIndex - 1;
550
+ }
551
+ if (lineIndex >= lines.length - 1) {
552
+ return -1;
553
+ }
554
+ for (const checker of this.checkers) {
555
+ const stablePoint = checker.check(lineIndex, context, lines);
556
+ if (stablePoint >= 0) {
557
+ return stablePoint;
558
+ }
559
+ }
560
+ return -1;
561
+ }
562
+ };
563
+
564
+ // src/utils/index.ts
565
+ function isDefinitionNode(node) {
566
+ return node.type === "definition";
567
+ }
568
+ function isFootnoteDefinitionNode(node) {
569
+ return node.type === "footnoteDefinition";
570
+ }
571
+ function traverseAst(node, visitor) {
572
+ const stopEarly = visitor(node);
573
+ if (stopEarly === true) {
574
+ return;
575
+ }
576
+ if ("children" in node && Array.isArray(node.children)) {
577
+ for (const child of node.children) {
578
+ traverseAst(child, visitor);
579
+ }
580
+ }
581
+ }
582
+ function collectAstNodes(node, predicate) {
583
+ const results = [];
584
+ traverseAst(node, (node2) => {
585
+ if (predicate(node2)) {
586
+ results.push(node2);
587
+ }
588
+ });
589
+ return results;
590
+ }
591
+
592
+ // src/parser/manager/DefinitionManager.ts
593
+ var DefinitionManager = class {
594
+ definitions = {};
595
+ /**
596
+ * 从已完成的 blocks 中提取 definitions
597
+ *
598
+ * @param blocks 已完成的块
599
+ */
600
+ extractFromBlocks(blocks) {
601
+ for (const block of blocks) {
602
+ const newDefinitions = this.findDefinitions(block);
603
+ this.definitions = {
604
+ ...this.definitions,
605
+ ...newDefinitions
606
+ };
607
+ }
608
+ }
609
+ /**
610
+ * 从 block 中提取 definitions
611
+ *
612
+ * @param block 解析块
613
+ * @returns Definition 映射表
614
+ */
615
+ findDefinitions(block) {
616
+ const definitions = collectAstNodes(block.node, isDefinitionNode);
617
+ return definitions.reduce((acc, node) => {
618
+ acc[node.identifier] = node;
619
+ return acc;
620
+ }, {});
621
+ }
622
+ /**
623
+ * 获取所有 definitions
624
+ *
625
+ * @returns Definition 映射表
626
+ */
627
+ getAll() {
628
+ return this.definitions;
629
+ }
630
+ /**
631
+ * 清空所有 definitions
632
+ */
633
+ clear() {
634
+ this.definitions = {};
635
+ }
636
+ };
637
+
638
+ // src/parser/manager/FootnoteManager.ts
639
+ var FootnoteManager = class {
640
+ definitions = {};
641
+ referenceOrder = [];
642
+ /**
643
+ * 从已完成的 blocks 中提取 footnote definitions
644
+ *
645
+ * @param blocks 已完成的块
646
+ */
647
+ extractDefinitionsFromBlocks(blocks) {
648
+ for (const block of blocks) {
649
+ const newDefinitions = this.findFootnoteDefinitions(block);
650
+ this.definitions = {
651
+ ...this.definitions,
652
+ ...newDefinitions
653
+ };
654
+ }
655
+ }
656
+ /**
657
+ * 从 block 中提取 footnote definitions
658
+ *
659
+ * @param block 解析块
660
+ * @returns Footnote Definition 映射表
661
+ */
662
+ findFootnoteDefinitions(block) {
663
+ const definitions = collectAstNodes(block.node, isFootnoteDefinitionNode);
664
+ return definitions.reduce((acc, node) => {
665
+ acc[node.identifier] = node;
666
+ return acc;
667
+ }, {});
668
+ }
669
+ /**
670
+ * 收集 AST 中的脚注引用(按出现顺序)
671
+ *
672
+ * @param nodes AST 节点列表
673
+ */
674
+ collectReferences(nodes) {
675
+ nodes.forEach((node) => {
676
+ traverseAst(node, (n) => {
677
+ if (n.type === "footnoteReference") {
678
+ const identifier = n.identifier;
679
+ if (!this.referenceOrder.includes(identifier)) {
680
+ this.referenceOrder.push(identifier);
681
+ }
682
+ }
683
+ });
684
+ });
685
+ }
686
+ /**
687
+ * 获取所有 footnote definitions
688
+ *
689
+ * @returns Footnote Definition 映射表
690
+ */
691
+ getDefinitions() {
692
+ return this.definitions;
693
+ }
694
+ /**
695
+ * 获取脚注引用顺序
696
+ *
697
+ * @returns 脚注引用顺序
698
+ */
699
+ getReferenceOrder() {
700
+ return this.referenceOrder;
701
+ }
702
+ /**
703
+ * 清空所有 footnote definitions 和引用顺序
704
+ */
705
+ clear() {
706
+ this.definitions = {};
707
+ this.referenceOrder = [];
708
+ }
709
+ };
19
710
 
20
711
  // src/extensions/html-extension/index.ts
21
712
  var DEFAULT_TAG_BLACKLIST = [
@@ -403,65 +1094,6 @@ function transformHtmlNodes(ast, options = {}) {
403
1094
  children: processHtmlNodesInArray(ast.children, options)
404
1095
  };
405
1096
  }
406
- function createHtmlTreeTransformer(options = {}) {
407
- return function transformer(tree) {
408
- return transformHtmlNodes(tree, options);
409
- };
410
- }
411
- var htmlTreeExtension = {
412
- enter: {},
413
- exit: {}
414
- };
415
- function isHtmlElementNode(node) {
416
- return node.type === "htmlElement";
417
- }
418
- function walkHtmlElements(node, callback, parent = null) {
419
- if (isHtmlElementNode(node)) {
420
- callback(node, parent);
421
- }
422
- if (hasChildren(node) || node.type === "root") {
423
- const children = node.children;
424
- for (const child of children) {
425
- walkHtmlElements(child, callback, node);
426
- }
427
- }
428
- }
429
- function findHtmlElementsByTag(root, tagName) {
430
- const result = [];
431
- walkHtmlElements(root, (node) => {
432
- if (node.tagName === tagName.toLowerCase()) {
433
- result.push(node);
434
- }
435
- });
436
- return result;
437
- }
438
- function htmlElementToString(node) {
439
- const { tagName, attrs, children } = node;
440
- const attrsStr = Object.entries(attrs).map(([name, value]) => {
441
- if (value === "") return name;
442
- return `${name}="${escapeHtml(value)}"`;
443
- }).join(" ");
444
- const openTag = attrsStr ? `<${tagName} ${attrsStr}>` : `<${tagName}>`;
445
- if (children.length === 0 && isSelfClosingTag(tagName)) {
446
- return attrsStr ? `<${tagName} ${attrsStr} />` : `<${tagName} />`;
447
- }
448
- const childrenStr = children.map((child) => {
449
- if (child.type === "text") {
450
- return child.value;
451
- }
452
- if (isHtmlElementNode(child)) {
453
- return htmlElementToString(child);
454
- }
455
- return "";
456
- }).join("");
457
- return `${openTag}${childrenStr}</${tagName}>`;
458
- }
459
- function isSelfClosingTag(tagName) {
460
- return ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"].includes(tagName.toLowerCase());
461
- }
462
- function escapeHtml(text) {
463
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
464
- }
465
1097
  function micromarkReferenceExtension() {
466
1098
  return {
467
1099
  // 在 text 中使用 codes.rightSquareBracket 键覆盖 labelEnd
@@ -862,384 +1494,51 @@ function tokenizePotentialGfmFootnoteCallIncremental(effects, ok, nok) {
862
1494
  }
863
1495
  }
864
1496
 
865
- // src/detector/index.ts
866
- var RE_FENCE_START = /^(\s*)((`{3,})|(~{3,}))/;
867
- var RE_EMPTY_LINE = /^\s*$/;
868
- var RE_HEADING = /^#{1,6}\s/;
869
- var RE_THEMATIC_BREAK = /^(\*{3,}|-{3,}|_{3,})\s*$/;
870
- var RE_UNORDERED_LIST = /^(\s*)([-*+])\s/;
871
- var RE_ORDERED_LIST = /^(\s*)(\d{1,9})[.)]\s/;
872
- var RE_BLOCKQUOTE = /^\s{0,3}>/;
873
- var RE_HTML_BLOCK_1 = /^\s{0,3}<(script|pre|style|textarea|!--|!DOCTYPE|\?|!\[CDATA\[)/i;
874
- var RE_HTML_BLOCK_2 = /^\s{0,3}<\/?[a-zA-Z][a-zA-Z0-9-]*(\s|>|$)/;
875
- var RE_TABLE_DELIMITER = /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?$/;
876
- var RE_ESCAPE_SPECIAL = /[.*+?^${}()|[\]\\]/g;
877
- var RE_FOOTNOTE_DEFINITION = /^\[\^[^\]]+\]:\s/;
878
- var RE_FOOTNOTE_CONTINUATION = /^(?: |\t)/;
879
- var fenceEndPatternCache = /* @__PURE__ */ new Map();
880
- var containerPatternCache = /* @__PURE__ */ new Map();
881
- function detectFenceStart(line) {
882
- const match = line.match(RE_FENCE_START);
883
- if (match) {
884
- const fence = match[2];
885
- const char = fence[0];
886
- return { char, length: fence.length };
887
- }
888
- return null;
889
- }
890
- function detectFenceEnd(line, context) {
891
- if (!context.inFencedCode || !context.fenceChar || !context.fenceLength) {
892
- return false;
893
- }
894
- const cacheKey = `${context.fenceChar}-${context.fenceLength}`;
895
- let pattern = fenceEndPatternCache.get(cacheKey);
896
- if (!pattern) {
897
- pattern = new RegExp(`^\\s{0,3}${context.fenceChar}{${context.fenceLength},}\\s*$`);
898
- fenceEndPatternCache.set(cacheKey, pattern);
899
- }
900
- return pattern.test(line);
901
- }
902
- function isEmptyLine(line) {
903
- return RE_EMPTY_LINE.test(line);
904
- }
905
- function isHeading(line) {
906
- return RE_HEADING.test(line);
907
- }
908
- function isThematicBreak(line) {
909
- return RE_THEMATIC_BREAK.test(line.trim());
910
- }
911
- function isListItemStart(line) {
912
- const unordered = line.match(RE_UNORDERED_LIST);
913
- if (unordered) {
914
- return { ordered: false, indent: unordered[1].length };
915
- }
916
- const ordered = line.match(RE_ORDERED_LIST);
917
- if (ordered) {
918
- return { ordered: true, indent: ordered[1].length };
919
- }
920
- return null;
921
- }
922
- function isBlockquoteStart(line) {
923
- return RE_BLOCKQUOTE.test(line);
924
- }
925
- function isHtmlBlock(line) {
926
- return RE_HTML_BLOCK_1.test(line) || RE_HTML_BLOCK_2.test(line);
927
- }
928
- function isTableDelimiter(line) {
929
- return RE_TABLE_DELIMITER.test(line.trim());
930
- }
931
- function isFootnoteDefinitionStart(line) {
932
- return RE_FOOTNOTE_DEFINITION.test(line);
933
- }
934
- function isFootnoteContinuation(line) {
935
- return RE_FOOTNOTE_CONTINUATION.test(line);
936
- }
937
- function detectContainer(line, config) {
938
- const marker = config?.marker || ":";
939
- const minLength = config?.minMarkerLength || 3;
940
- const cacheKey = `${marker}-${minLength}`;
941
- let pattern = containerPatternCache.get(cacheKey);
942
- if (!pattern) {
943
- const escapedMarker = marker.replace(RE_ESCAPE_SPECIAL, "\\$&");
944
- pattern = new RegExp(
945
- `^(\\s*)(${escapedMarker}{${minLength},})(?:\\s*(\\w[\\w-]*))?(?:\\{[^}]*\\})?(?:\\s+(.*))?\\s*$`
946
- );
947
- containerPatternCache.set(cacheKey, pattern);
948
- }
949
- const match = line.match(pattern);
950
- if (!match) {
951
- return null;
952
- }
953
- const markerLength = match[2].length;
954
- const name = match[3] || "";
955
- const isEnd = !name && !match[4];
956
- if (!isEnd && config?.allowedNames && config.allowedNames.length > 0) {
957
- if (!config.allowedNames.includes(name)) {
958
- return null;
959
- }
960
- }
961
- return { name, markerLength, isEnd };
962
- }
963
- function detectContainerEnd(line, context, config) {
964
- if (!context.inContainer || !context.containerMarkerLength) {
965
- return false;
966
- }
967
- const result = detectContainer(line, config);
968
- if (!result) {
969
- return false;
970
- }
971
- return result.isEnd && result.markerLength >= context.containerMarkerLength;
972
- }
973
- function isBlockBoundary(prevLine, currentLine, context) {
974
- if (context.inFencedCode) {
975
- return detectFenceEnd(currentLine, context);
976
- }
977
- if (isEmptyLine(prevLine) && !isEmptyLine(currentLine)) {
978
- return true;
979
- }
980
- if (isHeading(currentLine) && !isEmptyLine(prevLine)) {
981
- return true;
982
- }
983
- if (isThematicBreak(currentLine)) {
984
- return true;
985
- }
986
- if (detectFenceStart(currentLine)) {
987
- return true;
988
- }
989
- return false;
990
- }
991
- function createInitialContext() {
992
- return {
993
- inFencedCode: false,
994
- listDepth: 0,
995
- blockquoteDepth: 0,
996
- inContainer: false,
997
- containerDepth: 0,
998
- inList: false
999
- };
1000
- }
1001
- function isListContinuation(line, listIndent) {
1002
- if (isEmptyLine(line)) {
1003
- return true;
1004
- }
1005
- const contentIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
1006
- return contentIndent > listIndent;
1007
- }
1008
- function updateContext(line, context, containerConfig) {
1009
- const newContext = { ...context };
1010
- const containerCfg = containerConfig === true ? {} : containerConfig === false ? void 0 : containerConfig;
1011
- if (context.inFencedCode) {
1012
- if (detectFenceEnd(line, context)) {
1013
- newContext.inFencedCode = false;
1014
- newContext.fenceChar = void 0;
1015
- newContext.fenceLength = void 0;
1016
- }
1017
- return newContext;
1018
- }
1019
- const fence = detectFenceStart(line);
1020
- if (fence) {
1021
- newContext.inFencedCode = true;
1022
- newContext.fenceChar = fence.char;
1023
- newContext.fenceLength = fence.length;
1024
- return newContext;
1025
- }
1026
- if (containerCfg !== void 0) {
1027
- if (context.inContainer) {
1028
- if (detectContainerEnd(line, context, containerCfg)) {
1029
- newContext.containerDepth = context.containerDepth - 1;
1030
- if (newContext.containerDepth === 0) {
1031
- newContext.inContainer = false;
1032
- newContext.containerMarkerLength = void 0;
1033
- newContext.containerName = void 0;
1034
- }
1035
- return newContext;
1036
- }
1037
- const nested = detectContainer(line, containerCfg);
1038
- if (nested && !nested.isEnd) {
1039
- newContext.containerDepth = context.containerDepth + 1;
1040
- return newContext;
1041
- }
1042
- return newContext;
1043
- } else {
1044
- const container = detectContainer(line, containerCfg);
1045
- if (container && !container.isEnd) {
1046
- newContext.inContainer = true;
1047
- newContext.containerMarkerLength = container.markerLength;
1048
- newContext.containerName = container.name;
1049
- newContext.containerDepth = 1;
1050
- return newContext;
1051
- }
1052
- }
1053
- }
1054
- const listItem = isListItemStart(line);
1055
- if (context.inList) {
1056
- if (context.listMayEnd) {
1057
- if (listItem) {
1058
- if (listItem.ordered === context.listOrdered && listItem.indent === context.listIndent) {
1059
- newContext.listMayEnd = false;
1060
- return newContext;
1061
- }
1062
- newContext.inList = true;
1063
- newContext.listOrdered = listItem.ordered;
1064
- newContext.listIndent = listItem.indent;
1065
- newContext.listMayEnd = false;
1066
- return newContext;
1067
- } else if (isListContinuation(line, context.listIndent ?? 0)) {
1068
- newContext.listMayEnd = isEmptyLine(line);
1069
- return newContext;
1070
- } else {
1071
- newContext.inList = false;
1072
- newContext.listOrdered = void 0;
1073
- newContext.listIndent = void 0;
1074
- newContext.listMayEnd = false;
1075
- return newContext;
1076
- }
1077
- } else {
1078
- if (listItem) {
1079
- return newContext;
1080
- } else if (isEmptyLine(line)) {
1081
- newContext.listMayEnd = true;
1082
- return newContext;
1083
- } else if (isListContinuation(line, context.listIndent ?? 0)) {
1084
- return newContext;
1085
- } else {
1086
- newContext.inList = false;
1087
- newContext.listOrdered = void 0;
1088
- newContext.listIndent = void 0;
1089
- newContext.listMayEnd = false;
1090
- return newContext;
1091
- }
1092
- }
1093
- } else {
1094
- if (listItem) {
1095
- newContext.inList = true;
1096
- newContext.listOrdered = listItem.ordered;
1097
- newContext.listIndent = listItem.indent;
1098
- newContext.listMayEnd = false;
1099
- return newContext;
1100
- }
1101
- }
1102
- return newContext;
1103
- }
1104
-
1105
- // src/utils/index.ts
1106
- var idCounter = 0;
1107
- function generateId(prefix = "block") {
1108
- return `${prefix}-${++idCounter}`;
1109
- }
1110
- function resetIdCounter() {
1111
- idCounter = 0;
1112
- }
1113
- function calculateLineOffset(lines, lineIndex) {
1114
- let offset = 0;
1115
- for (let i = 0; i < lineIndex && i < lines.length; i++) {
1116
- offset += lines[i].length + 1;
1117
- }
1118
- return offset;
1119
- }
1120
- function splitLines(text) {
1121
- return text.split("\n");
1122
- }
1123
- function joinLines(lines, start, end) {
1124
- return lines.slice(start, end + 1).join("\n");
1125
- }
1126
- function isDefinitionNode(node) {
1127
- return node.type === "definition";
1128
- }
1129
- function isFootnoteDefinitionNode(node) {
1130
- return node.type === "footnoteDefinition";
1497
+ // src/parser/ast/AstBuilder.ts
1498
+ var INLINE_CONTAINER_TYPES = [
1499
+ "paragraph",
1500
+ "heading",
1501
+ "tableCell",
1502
+ "delete",
1503
+ "emphasis",
1504
+ "strong",
1505
+ "link",
1506
+ "linkReference"
1507
+ ];
1508
+ function isInlineContainer(node) {
1509
+ return INLINE_CONTAINER_TYPES.includes(node.type);
1131
1510
  }
1132
-
1133
- // src/parser/IncremarkParser.ts
1134
- var IncremarkParser = class {
1135
- buffer = "";
1136
- lines = [];
1137
- /** 行偏移量前缀和:lineOffsets[i] = 第i行起始位置的偏移量 */
1138
- lineOffsets = [0];
1139
- completedBlocks = [];
1140
- pendingStartLine = 0;
1141
- blockIdCounter = 0;
1142
- context;
1511
+ var AstBuilder = class {
1143
1512
  options;
1144
- /** 缓存的容器配置,避免重复计算 */
1145
1513
  containerConfig;
1146
- /** 缓存的 HTML 树配置,避免重复计算 */
1147
- htmlTreeConfig;
1148
- /** 上次 append 返回的 pending blocks,用于 getAst 复用 */
1149
- lastPendingBlocks = [];
1150
- /** Definition 映射表(用于引用式图片和链接) */
1151
- definitionMap = {};
1152
- /** Footnote Definition 映射表 */
1153
- footnoteDefinitionMap = {};
1154
- /** Footnote Reference 出现顺序(按引用在文档中的顺序) */
1155
- footnoteReferenceOrder = [];
1514
+ htmlTreeConfig;
1156
1515
  constructor(options = {}) {
1157
- this.options = {
1158
- gfm: true,
1159
- ...options
1160
- };
1161
- this.context = createInitialContext();
1162
- this.containerConfig = this.computeContainerConfig();
1163
- this.htmlTreeConfig = this.computeHtmlTreeConfig();
1164
- }
1165
- generateBlockId() {
1166
- return `block-${++this.blockIdCounter}`;
1516
+ this.options = options;
1517
+ this.containerConfig = this.computeContainerConfig(options);
1518
+ this.htmlTreeConfig = this.computeHtmlTreeConfig(options);
1167
1519
  }
1168
- computeContainerConfig() {
1169
- const containers = this.options.containers;
1520
+ /**
1521
+ * 计算容器配置
1522
+ */
1523
+ computeContainerConfig(options) {
1524
+ const containers = options.containers;
1170
1525
  if (!containers) return void 0;
1171
1526
  return containers === true ? {} : containers;
1172
1527
  }
1173
- computeHtmlTreeConfig() {
1174
- const htmlTree = this.options.htmlTree;
1528
+ /**
1529
+ * 计算 HTML 树配置
1530
+ */
1531
+ computeHtmlTreeConfig(options) {
1532
+ const htmlTree = options.htmlTree;
1175
1533
  if (!htmlTree) return void 0;
1176
1534
  return htmlTree === true ? {} : htmlTree;
1177
1535
  }
1178
1536
  /**
1179
- * HTML 节点转换为纯文本
1180
- * 递归处理 AST 中所有 html 类型的节点
1181
- * - 块级 HTML 节点 → 转换为 paragraph 包含 text
1182
- * - 内联 HTML 节点(在段落内部)→ 转换为 text 节点
1537
+ * 解析文本为 AST
1538
+ *
1539
+ * @param text Markdown 文本
1540
+ * @returns AST
1183
1541
  */
1184
- convertHtmlToText(ast) {
1185
- const processInlineChildren = (children) => {
1186
- return children.map((node) => {
1187
- const n = node;
1188
- if (n.type === "html") {
1189
- const htmlNode = n;
1190
- const textNode = {
1191
- type: "text",
1192
- value: htmlNode.value,
1193
- position: htmlNode.position
1194
- };
1195
- return textNode;
1196
- }
1197
- if ("children" in n && Array.isArray(n.children)) {
1198
- const parent = n;
1199
- return {
1200
- ...parent,
1201
- children: processInlineChildren(parent.children)
1202
- };
1203
- }
1204
- return n;
1205
- });
1206
- };
1207
- const processBlockChildren = (children) => {
1208
- return children.map((node) => {
1209
- if (node.type === "html") {
1210
- const htmlNode = node;
1211
- const textNode = {
1212
- type: "text",
1213
- value: htmlNode.value
1214
- };
1215
- const paragraphNode = {
1216
- type: "paragraph",
1217
- children: [textNode],
1218
- position: htmlNode.position
1219
- };
1220
- return paragraphNode;
1221
- }
1222
- if ("children" in node && Array.isArray(node.children)) {
1223
- const parent = node;
1224
- 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") {
1225
- return {
1226
- ...parent,
1227
- children: processInlineChildren(parent.children)
1228
- };
1229
- }
1230
- return {
1231
- ...parent,
1232
- children: processBlockChildren(parent.children)
1233
- };
1234
- }
1235
- return node;
1236
- });
1237
- };
1238
- return {
1239
- ...ast,
1240
- children: processBlockChildren(ast.children)
1241
- };
1242
- }
1243
1542
  parse(text) {
1244
1543
  const extensions = [];
1245
1544
  const mdastExtensions = [];
@@ -1273,67 +1572,160 @@ var IncremarkParser = class {
1273
1572
  }
1274
1573
  return ast;
1275
1574
  }
1276
- updateDefinitionsFromCompletedBlocks(blocks) {
1277
- for (const block of blocks) {
1278
- this.definitionMap = {
1279
- ...this.definitionMap,
1280
- ...this.findDefinition(block)
1281
- };
1282
- this.footnoteDefinitionMap = {
1283
- ...this.footnoteDefinitionMap,
1284
- ...this.findFootnoteDefinition(block)
1285
- };
1286
- }
1575
+ /**
1576
+ * HTML 节点转换为纯文本(当未启用 HTML 树转换时)
1577
+ *
1578
+ * @param ast AST
1579
+ * @returns 转换后的 AST
1580
+ */
1581
+ convertHtmlToText(ast) {
1582
+ return {
1583
+ ...ast,
1584
+ children: this.processBlockChildren(ast.children)
1585
+ };
1287
1586
  }
1288
- findDefinition(block) {
1289
- const definitions = [];
1290
- function findDefinitionRecursive(node) {
1291
- if (isDefinitionNode(node)) {
1292
- definitions.push(node);
1587
+ /**
1588
+ * 处理块级节点
1589
+ */
1590
+ processBlockChildren(children) {
1591
+ return children.map((node) => {
1592
+ if (node.type === "html") {
1593
+ return this.convertBlockHtmlToParagraph(node);
1293
1594
  }
1294
1595
  if ("children" in node && Array.isArray(node.children)) {
1295
- for (const child of node.children) {
1296
- findDefinitionRecursive(child);
1297
- }
1596
+ const parent = node;
1597
+ const children2 = isInlineContainer(node) ? this.processInlineChildren(parent.children) : this.processBlockChildren(parent.children);
1598
+ return {
1599
+ ...parent,
1600
+ children: children2
1601
+ };
1298
1602
  }
1299
- }
1300
- findDefinitionRecursive(block.node);
1301
- return definitions.reduce((acc, node) => {
1302
- acc[node.identifier] = node;
1303
- return acc;
1304
- }, {});
1603
+ return node;
1604
+ });
1305
1605
  }
1306
- findFootnoteDefinition(block) {
1307
- const footnoteDefinitions = [];
1308
- function findFootnoteDefinition(node) {
1309
- if (isFootnoteDefinitionNode(node)) {
1310
- footnoteDefinitions.push(node);
1606
+ /**
1607
+ * 处理内联节点
1608
+ */
1609
+ processInlineChildren(children) {
1610
+ return children.map((node) => {
1611
+ const n = node;
1612
+ if (n.type === "html") {
1613
+ return this.convertInlineHtmlToText(n);
1614
+ }
1615
+ if ("children" in n && Array.isArray(n.children)) {
1616
+ const parent = n;
1617
+ return {
1618
+ ...parent,
1619
+ children: this.processInlineChildren(parent.children)
1620
+ };
1311
1621
  }
1622
+ return n;
1623
+ });
1624
+ }
1625
+ /**
1626
+ * 将块级 HTML 节点转换为段落
1627
+ */
1628
+ convertBlockHtmlToParagraph(htmlNode) {
1629
+ const textNode = {
1630
+ type: "text",
1631
+ value: htmlNode.value
1632
+ };
1633
+ const paragraphNode = {
1634
+ type: "paragraph",
1635
+ children: [textNode],
1636
+ position: htmlNode.position
1637
+ };
1638
+ return paragraphNode;
1639
+ }
1640
+ /**
1641
+ * 将内联 HTML 节点转换为纯文本节点
1642
+ */
1643
+ convertInlineHtmlToText(htmlNode) {
1644
+ return {
1645
+ type: "text",
1646
+ value: htmlNode.value,
1647
+ position: htmlNode.position
1648
+ };
1649
+ }
1650
+ /**
1651
+ * 将 AST 节点转换为 ParsedBlock
1652
+ *
1653
+ * @param nodes AST 节点列表
1654
+ * @param startOffset 起始偏移量
1655
+ * @param rawText 原始文本
1656
+ * @param status 块状态
1657
+ * @param generateBlockId 生成块 ID 的函数
1658
+ * @returns ParsedBlock 列表
1659
+ */
1660
+ nodesToBlocks(nodes, startOffset, rawText, status, generateBlockId) {
1661
+ const blocks = [];
1662
+ let currentOffset = startOffset;
1663
+ for (const node of nodes) {
1664
+ const nodeStart = node.position?.start?.offset ?? currentOffset;
1665
+ const nodeEnd = node.position?.end?.offset ?? currentOffset + 1;
1666
+ const nodeText = rawText.substring(nodeStart - startOffset, nodeEnd - startOffset);
1667
+ blocks.push({
1668
+ id: generateBlockId(),
1669
+ status,
1670
+ node,
1671
+ startOffset: nodeStart,
1672
+ endOffset: nodeEnd,
1673
+ rawText: nodeText
1674
+ });
1675
+ currentOffset = nodeEnd;
1312
1676
  }
1313
- findFootnoteDefinition(block.node);
1314
- return footnoteDefinitions.reduce((acc, node) => {
1315
- acc[node.identifier] = node;
1316
- return acc;
1317
- }, {});
1677
+ return blocks;
1678
+ }
1679
+ };
1680
+
1681
+ // src/parser/IncremarkParser.ts
1682
+ var IncremarkParser = class {
1683
+ buffer = "";
1684
+ lines = [];
1685
+ /** 行偏移量前缀和:lineOffsets[i] = 第i行起始位置的偏移量 */
1686
+ lineOffsets = [0];
1687
+ completedBlocks = [];
1688
+ pendingStartLine = 0;
1689
+ blockIdCounter = 0;
1690
+ context;
1691
+ options;
1692
+ /** 边界检测器 */
1693
+ boundaryDetector;
1694
+ /** AST 构建器 */
1695
+ astBuilder;
1696
+ /** Definition 管理器 */
1697
+ definitionManager;
1698
+ /** Footnote 管理器 */
1699
+ footnoteManager;
1700
+ /** 上次 append 返回的 pending blocks,用于 getAst 复用 */
1701
+ lastPendingBlocks = [];
1702
+ constructor(options = {}) {
1703
+ this.options = {
1704
+ gfm: true,
1705
+ ...options
1706
+ };
1707
+ this.context = createInitialContext();
1708
+ this.astBuilder = new AstBuilder(this.options);
1709
+ this.boundaryDetector = new BoundaryDetector({ containers: this.astBuilder.containerConfig });
1710
+ this.definitionManager = new DefinitionManager();
1711
+ this.footnoteManager = new FootnoteManager();
1712
+ }
1713
+ generateBlockId() {
1714
+ return `block-${++this.blockIdCounter}`;
1715
+ }
1716
+ /**
1717
+ * 更新已完成的 blocks 中的 definitions 和 footnote definitions
1718
+ */
1719
+ updateDefinitionsFromCompletedBlocks(blocks) {
1720
+ this.definitionManager.extractFromBlocks(blocks);
1721
+ this.footnoteManager.extractDefinitionsFromBlocks(blocks);
1318
1722
  }
1319
1723
  /**
1320
1724
  * 收集 AST 中的脚注引用(按出现顺序)
1321
1725
  * 用于确定脚注的显示顺序
1322
1726
  */
1323
1727
  collectFootnoteReferences(nodes) {
1324
- const visitNode = (node) => {
1325
- if (!node) return;
1326
- if (node.type === "footnoteReference") {
1327
- const identifier = node.identifier;
1328
- if (!this.footnoteReferenceOrder.includes(identifier)) {
1329
- this.footnoteReferenceOrder.push(identifier);
1330
- }
1331
- }
1332
- if (node.children && Array.isArray(node.children)) {
1333
- node.children.forEach(visitNode);
1334
- }
1335
- };
1336
- nodes.forEach(visitNode);
1728
+ this.footnoteManager.collectReferences(nodes);
1337
1729
  }
1338
1730
  /**
1339
1731
  * 增量更新 lines 和 lineOffsets
@@ -1371,169 +1763,12 @@ var IncremarkParser = class {
1371
1763
  * 返回稳定边界行号和该行对应的上下文(用于后续更新,避免重复计算)
1372
1764
  */
1373
1765
  findStableBoundary() {
1374
- let stableLine = -1;
1375
- let stableContext = this.context;
1376
- let tempContext = { ...this.context };
1377
- for (let i = this.pendingStartLine; i < this.lines.length; i++) {
1378
- const line = this.lines[i];
1379
- const wasInFencedCode = tempContext.inFencedCode;
1380
- const wasInContainer = tempContext.inContainer;
1381
- const wasContainerDepth = tempContext.containerDepth;
1382
- tempContext = updateContext(line, tempContext, this.containerConfig);
1383
- if (wasInFencedCode && !tempContext.inFencedCode) {
1384
- if (i < this.lines.length - 1) {
1385
- stableLine = i;
1386
- stableContext = { ...tempContext };
1387
- }
1388
- continue;
1389
- }
1390
- if (tempContext.inFencedCode) {
1391
- continue;
1392
- }
1393
- if (wasInContainer && wasContainerDepth === 1 && !tempContext.inContainer) {
1394
- if (i < this.lines.length - 1) {
1395
- stableLine = i;
1396
- stableContext = { ...tempContext };
1397
- }
1398
- continue;
1399
- }
1400
- if (tempContext.inContainer) {
1401
- continue;
1402
- }
1403
- const stablePoint = this.checkStability(i, tempContext);
1404
- if (stablePoint >= 0) {
1405
- stableLine = stablePoint;
1406
- stableContext = { ...tempContext };
1407
- }
1408
- }
1409
- return { line: stableLine, contextAtLine: stableContext };
1410
- }
1411
- checkStability(lineIndex, context) {
1412
- if (lineIndex === 0) {
1413
- return -1;
1414
- }
1415
- const line = this.lines[lineIndex];
1416
- const prevLine = this.lines[lineIndex - 1];
1417
- if (context.inContainer) {
1418
- if (this.containerConfig !== void 0) {
1419
- const containerEnd = detectContainerEnd(line, context, this.containerConfig);
1420
- if (containerEnd) {
1421
- return lineIndex - 1;
1422
- }
1423
- }
1424
- return -1;
1425
- }
1426
- if (context.inList) {
1427
- if (!context.listMayEnd) {
1428
- return -1;
1429
- }
1430
- }
1431
- if (isHeading(prevLine) || isThematicBreak(prevLine)) {
1432
- return lineIndex - 1;
1433
- }
1434
- if (lineIndex >= this.lines.length - 1) {
1435
- return -1;
1436
- }
1437
- if (isFootnoteDefinitionStart(prevLine)) {
1438
- if (isEmptyLine(line) || isFootnoteContinuation(line)) {
1439
- return -1;
1440
- }
1441
- if (isFootnoteDefinitionStart(line)) {
1442
- return lineIndex - 1;
1443
- }
1444
- }
1445
- if (!isEmptyLine(prevLine) && isFootnoteContinuation(prevLine)) {
1446
- const footnoteStartLine = this.findFootnoteStart(lineIndex - 1);
1447
- if (footnoteStartLine >= 0) {
1448
- if (isEmptyLine(line) || isFootnoteContinuation(line)) {
1449
- return -1;
1450
- }
1451
- if (isFootnoteDefinitionStart(line)) {
1452
- return lineIndex - 1;
1453
- }
1454
- return lineIndex - 1;
1455
- }
1456
- }
1457
- if (!isEmptyLine(prevLine)) {
1458
- if (isFootnoteDefinitionStart(line) && !isFootnoteDefinitionStart(prevLine)) {
1459
- return lineIndex - 1;
1460
- }
1461
- if (isHeading(line)) {
1462
- return lineIndex - 1;
1463
- }
1464
- if (detectFenceStart(line)) {
1465
- return lineIndex - 1;
1466
- }
1467
- if (isBlockquoteStart(line) && !isBlockquoteStart(prevLine)) {
1468
- return lineIndex - 1;
1469
- }
1470
- if (!context.inList && isListItemStart(line) && !isListItemStart(prevLine)) {
1471
- return lineIndex - 1;
1472
- }
1473
- if (this.containerConfig !== void 0) {
1474
- const container = detectContainer(line, this.containerConfig);
1475
- if (container && !container.isEnd) {
1476
- const prevContainer = detectContainer(prevLine, this.containerConfig);
1477
- if (!prevContainer || prevContainer.isEnd) {
1478
- return lineIndex - 1;
1479
- }
1480
- }
1481
- }
1482
- }
1483
- if (isEmptyLine(line) && !isEmptyLine(prevLine) && !context.inList) {
1484
- return lineIndex;
1485
- }
1486
- return -1;
1487
- }
1488
- /**
1489
- * 从指定行向上查找脚注定义的起始行
1490
- *
1491
- * @param fromLine 开始查找的行索引
1492
- * @returns 脚注起始行索引,如果不属于脚注返回 -1
1493
- *
1494
- * @example
1495
- * // 假设 lines 为:
1496
- * // 0: "[^1]: 第一行"
1497
- * // 1: " 第二行"
1498
- * // 2: " 第三行"
1499
- * findFootnoteStart(2) // 返回 0
1500
- * findFootnoteStart(1) // 返回 0
1501
- */
1502
- findFootnoteStart(fromLine) {
1503
- const maxLookback = 20;
1504
- const startLine = Math.max(0, fromLine - maxLookback);
1505
- for (let i = fromLine; i >= startLine; i--) {
1506
- const line = this.lines[i];
1507
- if (isFootnoteDefinitionStart(line)) {
1508
- return i;
1509
- }
1510
- if (isEmptyLine(line)) {
1511
- continue;
1512
- }
1513
- if (!isFootnoteContinuation(line)) {
1514
- return -1;
1515
- }
1516
- }
1517
- return -1;
1518
- }
1519
- nodesToBlocks(nodes, startOffset, rawText, status) {
1520
- const blocks = [];
1521
- let currentOffset = startOffset;
1522
- for (const node of nodes) {
1523
- const nodeStart = node.position?.start?.offset ?? currentOffset;
1524
- const nodeEnd = node.position?.end?.offset ?? currentOffset + 1;
1525
- const nodeText = rawText.substring(nodeStart - startOffset, nodeEnd - startOffset);
1526
- blocks.push({
1527
- id: this.generateBlockId(),
1528
- status,
1529
- node,
1530
- startOffset: nodeStart,
1531
- endOffset: nodeEnd,
1532
- rawText: nodeText
1533
- });
1534
- currentOffset = nodeEnd;
1535
- }
1536
- return blocks;
1766
+ const result = this.boundaryDetector.findStableBoundary(
1767
+ this.lines,
1768
+ this.pendingStartLine,
1769
+ this.context
1770
+ );
1771
+ return { line: result.line, contextAtLine: result.context };
1537
1772
  }
1538
1773
  /**
1539
1774
  * 追加新的 chunk 并返回增量更新
@@ -1554,8 +1789,8 @@ var IncremarkParser = class {
1554
1789
  if (stableBoundary >= this.pendingStartLine && stableBoundary >= 0) {
1555
1790
  const stableText = this.lines.slice(this.pendingStartLine, stableBoundary + 1).join("\n");
1556
1791
  const stableOffset = this.getLineOffset(this.pendingStartLine);
1557
- const ast = this.parse(stableText);
1558
- const newBlocks = this.nodesToBlocks(ast.children, stableOffset, stableText, "completed");
1792
+ const ast = this.astBuilder.parse(stableText);
1793
+ const newBlocks = this.astBuilder.nodesToBlocks(ast.children, stableOffset, stableText, "completed", () => this.generateBlockId());
1559
1794
  this.completedBlocks.push(...newBlocks);
1560
1795
  update.completed = newBlocks;
1561
1796
  this.updateDefinitionsFromCompletedBlocks(newBlocks);
@@ -1566,8 +1801,8 @@ var IncremarkParser = class {
1566
1801
  const pendingText = this.lines.slice(this.pendingStartLine).join("\n");
1567
1802
  if (pendingText.trim()) {
1568
1803
  const pendingOffset = this.getLineOffset(this.pendingStartLine);
1569
- const ast = this.parse(pendingText);
1570
- update.pending = this.nodesToBlocks(ast.children, pendingOffset, pendingText, "pending");
1804
+ const ast = this.astBuilder.parse(pendingText);
1805
+ update.pending = this.astBuilder.nodesToBlocks(ast.children, pendingOffset, pendingText, "pending", () => this.generateBlockId());
1571
1806
  }
1572
1807
  }
1573
1808
  this.lastPendingBlocks = update.pending;
@@ -1598,8 +1833,8 @@ var IncremarkParser = class {
1598
1833
  ...pendingBlocks.map((b) => b.node)
1599
1834
  ]
1600
1835
  },
1601
- definitions: { ...this.definitionMap },
1602
- footnoteDefinitions: { ...this.footnoteDefinitionMap }
1836
+ definitions: { ...this.getDefinitionMap() },
1837
+ footnoteDefinitions: { ...this.getFootnoteDefinitionMap() }
1603
1838
  };
1604
1839
  this.options.onChange(state);
1605
1840
  }
@@ -1614,20 +1849,21 @@ var IncremarkParser = class {
1614
1849
  updated: [],
1615
1850
  pending: [],
1616
1851
  ast: { type: "root", children: [] },
1617
- definitions: {},
1618
- footnoteDefinitions: {},
1619
- footnoteReferenceOrder: []
1852
+ definitions: this.getDefinitionMap(),
1853
+ footnoteDefinitions: this.getFootnoteDefinitionMap(),
1854
+ footnoteReferenceOrder: this.getFootnoteReferenceOrder()
1620
1855
  };
1621
1856
  if (this.pendingStartLine < this.lines.length) {
1622
1857
  const remainingText = this.lines.slice(this.pendingStartLine).join("\n");
1623
1858
  if (remainingText.trim()) {
1624
1859
  const remainingOffset = this.getLineOffset(this.pendingStartLine);
1625
- const ast = this.parse(remainingText);
1626
- const finalBlocks = this.nodesToBlocks(
1860
+ const ast = this.astBuilder.parse(remainingText);
1861
+ const finalBlocks = this.astBuilder.nodesToBlocks(
1627
1862
  ast.children,
1628
1863
  remainingOffset,
1629
1864
  remainingText,
1630
- "completed"
1865
+ "completed",
1866
+ () => this.generateBlockId()
1631
1867
  );
1632
1868
  this.completedBlocks.push(...finalBlocks);
1633
1869
  update.completed = finalBlocks;
@@ -1685,19 +1921,19 @@ var IncremarkParser = class {
1685
1921
  * 获取 Definition 映射表(用于引用式图片和链接)
1686
1922
  */
1687
1923
  getDefinitionMap() {
1688
- return { ...this.definitionMap };
1924
+ return this.definitionManager.getAll();
1689
1925
  }
1690
1926
  /**
1691
1927
  * 获取 Footnote Definition 映射表
1692
1928
  */
1693
1929
  getFootnoteDefinitionMap() {
1694
- return { ...this.footnoteDefinitionMap };
1930
+ return this.footnoteManager.getDefinitions();
1695
1931
  }
1696
1932
  /**
1697
1933
  * 获取脚注引用的出现顺序
1698
1934
  */
1699
1935
  getFootnoteReferenceOrder() {
1700
- return [...this.footnoteReferenceOrder];
1936
+ return this.footnoteManager.getReferenceOrder();
1701
1937
  }
1702
1938
  /**
1703
1939
  * 设置状态变化回调(用于 DevTools 等)
@@ -1721,9 +1957,8 @@ var IncremarkParser = class {
1721
1957
  this.blockIdCounter = 0;
1722
1958
  this.context = createInitialContext();
1723
1959
  this.lastPendingBlocks = [];
1724
- this.definitionMap = {};
1725
- this.footnoteDefinitionMap = {};
1726
- this.footnoteReferenceOrder = [];
1960
+ this.definitionManager.clear();
1961
+ this.footnoteManager.clear();
1727
1962
  this.emitChange([]);
1728
1963
  }
1729
1964
  /**
@@ -1985,19 +2220,8 @@ var BlockTransformer = class {
1985
2220
  if (this.state.currentBlock) {
1986
2221
  const updated = blocks.find((b) => b.id === this.state.currentBlock.id);
1987
2222
  if (updated && updated.node !== this.state.currentBlock.node) {
1988
- const oldTotal = this.cachedTotalChars ?? this.countChars(this.state.currentBlock.node);
1989
- const newTotal = this.countChars(updated.node);
1990
- if (newTotal < oldTotal || newTotal < this.state.currentProgress) {
1991
- this.state.currentProgress = Math.min(this.state.currentProgress, newTotal);
1992
- this.chunks = [];
1993
- }
1994
- this.clearCache();
2223
+ this.handleContentChange(this.state.currentBlock.node, updated.node, true);
1995
2224
  this.state.currentBlock = updated;
1996
- if (!this.rafId && !this.isPaused) {
1997
- if (this.state.currentProgress < newTotal) {
1998
- this.startIfNeeded();
1999
- }
2000
- }
2001
2225
  }
2002
2226
  }
2003
2227
  }
@@ -2006,19 +2230,8 @@ var BlockTransformer = class {
2006
2230
  */
2007
2231
  update(block) {
2008
2232
  if (this.state.currentBlock?.id === block.id) {
2009
- const oldTotal = this.cachedTotalChars ?? this.countChars(this.state.currentBlock.node);
2010
- const newTotal = this.countChars(block.node);
2011
- if (newTotal !== oldTotal) {
2012
- this.clearCache();
2013
- }
2014
- if (newTotal < oldTotal || newTotal < this.state.currentProgress) {
2015
- this.state.currentProgress = Math.min(this.state.currentProgress, newTotal);
2016
- this.chunks = [];
2017
- }
2233
+ this.handleContentChange(this.state.currentBlock.node, block.node, false);
2018
2234
  this.state.currentBlock = block;
2019
- if (newTotal > oldTotal && !this.rafId && !this.isPaused && this.state.currentProgress >= oldTotal) {
2020
- this.startIfNeeded();
2021
- }
2022
2235
  }
2023
2236
  }
2024
2237
  /**
@@ -2165,6 +2378,30 @@ var BlockTransformer = class {
2165
2378
  this.removeVisibilityHandler();
2166
2379
  }
2167
2380
  // ============ 私有方法 ============
2381
+ /**
2382
+ * 处理 block 内容更新时的字符数变化和进度调整
2383
+ * 统一 push 和 update 方法中的重复逻辑
2384
+ */
2385
+ handleContentChange(oldNode, newNode, isUpdateFromPush) {
2386
+ const oldTotal = this.cachedTotalChars ?? this.countChars(oldNode);
2387
+ const newTotal = this.countChars(newNode);
2388
+ if (newTotal < oldTotal || newTotal < this.state.currentProgress) {
2389
+ this.state.currentProgress = Math.min(this.state.currentProgress, newTotal);
2390
+ this.chunks = [];
2391
+ }
2392
+ this.clearCache();
2393
+ if (isUpdateFromPush) {
2394
+ if (!this.rafId && !this.isPaused) {
2395
+ if (this.state.currentProgress < newTotal) {
2396
+ this.startIfNeeded();
2397
+ }
2398
+ }
2399
+ } else {
2400
+ if (newTotal > oldTotal && !this.rafId && !this.isPaused && this.state.currentProgress >= oldTotal) {
2401
+ this.startIfNeeded();
2402
+ }
2403
+ }
2404
+ }
2168
2405
  getAllBlockIds() {
2169
2406
  return new Set([
2170
2407
  ...this.state.completedBlocks.map((b) => b.id),
@@ -2251,8 +2488,21 @@ var BlockTransformer = class {
2251
2488
  }
2252
2489
  /**
2253
2490
  * 从 AST 节点中提取指定范围的文本
2491
+ *
2492
+ * 优化说明:
2493
+ * - 提前终止:当 charIndex >= end 时立即返回,避免不必要的遍历
2494
+ * - 局部更新:charIndex 只在需要时更新,减少计算
2495
+ * - 早期返回:发现足够的文本后可以提前退出(当前未实现,可作为未来优化)
2496
+ *
2497
+ * @param node 要提取文本的 AST 节点
2498
+ * @param start 起始字符索引(包含)
2499
+ * @param end 结束字符索引(不包含)
2500
+ * @returns 提取的文本
2254
2501
  */
2255
2502
  extractText(node, start, end) {
2503
+ if (start >= end) {
2504
+ return "";
2505
+ }
2256
2506
  let result = "";
2257
2507
  let charIndex = 0;
2258
2508
  function traverse(n) {
@@ -2260,12 +2510,12 @@ var BlockTransformer = class {
2260
2510
  if (n.value && typeof n.value === "string") {
2261
2511
  const nodeStart = charIndex;
2262
2512
  const nodeEnd = charIndex + n.value.length;
2263
- charIndex = nodeEnd;
2264
2513
  const overlapStart = Math.max(start, nodeStart);
2265
2514
  const overlapEnd = Math.min(end, nodeEnd);
2266
2515
  if (overlapStart < overlapEnd) {
2267
2516
  result += n.value.slice(overlapStart - nodeStart, overlapEnd - nodeStart);
2268
2517
  }
2518
+ charIndex = nodeEnd;
2269
2519
  return charIndex < end;
2270
2520
  }
2271
2521
  if (n.children && Array.isArray(n.children)) {
@@ -2383,12 +2633,21 @@ var BlockTransformer = class {
2383
2633
  }
2384
2634
  /**
2385
2635
  * 获取总字符数(带缓存)
2636
+ *
2637
+ * 缓存策略:
2638
+ * - 首次调用时计算并缓存
2639
+ * - 内容更新时通过 clearCache() 清除缓存,下次重新计算
2640
+ * - 切换到新 block 时也会清除缓存
2386
2641
  */
2387
2642
  getTotalChars() {
2388
- if (this.cachedTotalChars === null && this.state.currentBlock) {
2643
+ if (!this.state.currentBlock) {
2644
+ this.cachedTotalChars = null;
2645
+ return 0;
2646
+ }
2647
+ if (this.cachedTotalChars === null) {
2389
2648
  this.cachedTotalChars = this.countChars(this.state.currentBlock.node);
2390
2649
  }
2391
- return this.cachedTotalChars ?? 0;
2650
+ return this.cachedTotalChars;
2392
2651
  }
2393
2652
  /**
2394
2653
  * 清除缓存(当 block 切换或内容更新时)
@@ -2519,6 +2778,6 @@ function createPlugin(name, matcher, options = {}) {
2519
2778
  * @license MIT
2520
2779
  */
2521
2780
 
2522
- 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 };
2781
+ export { BlockTransformer, IncremarkParser, allPlugins, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createIncremarkParser, createPlugin, defaultPlugins, imagePlugin, mathPlugin, mermaidPlugin, sliceAst, thematicBreakPlugin };
2523
2782
  //# sourceMappingURL=index.js.map
2524
2783
  //# sourceMappingURL=index.js.map