@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/detector/index.d.ts +118 -1
- package/dist/detector/index.js +216 -82
- package/dist/detector/index.js.map +1 -1
- package/dist/index-CfgnWMWh.d.ts +225 -0
- package/dist/index.d.ts +31 -40
- package/dist/index.js +943 -684
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +17 -1
- package/dist/utils/index.js +21 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/index-BfVDhalw.d.ts +0 -410
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/
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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/
|
|
866
|
-
var
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1159
|
-
|
|
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
|
-
|
|
1169
|
-
|
|
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
|
-
|
|
1174
|
-
|
|
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
|
-
*
|
|
1180
|
-
*
|
|
1181
|
-
*
|
|
1182
|
-
*
|
|
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
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
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
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
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
|
-
|
|
1296
|
-
|
|
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
|
-
|
|
1301
|
-
return definitions.reduce((acc, node) => {
|
|
1302
|
-
acc[node.identifier] = node;
|
|
1303
|
-
return acc;
|
|
1304
|
-
}, {});
|
|
1603
|
+
return node;
|
|
1604
|
+
});
|
|
1305
1605
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
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.
|
|
1602
|
-
footnoteDefinitions: { ...this.
|
|
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
|
|
1924
|
+
return this.definitionManager.getAll();
|
|
1689
1925
|
}
|
|
1690
1926
|
/**
|
|
1691
1927
|
* 获取 Footnote Definition 映射表
|
|
1692
1928
|
*/
|
|
1693
1929
|
getFootnoteDefinitionMap() {
|
|
1694
|
-
return
|
|
1930
|
+
return this.footnoteManager.getDefinitions();
|
|
1695
1931
|
}
|
|
1696
1932
|
/**
|
|
1697
1933
|
* 获取脚注引用的出现顺序
|
|
1698
1934
|
*/
|
|
1699
1935
|
getFootnoteReferenceOrder() {
|
|
1700
|
-
return
|
|
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.
|
|
1725
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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,
|
|
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
|