@incremark/core 0.2.4 → 0.2.6

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
@@ -2,6 +2,8 @@ import { fromMarkdown } from 'mdast-util-from-markdown';
2
2
  import { gfmFromMarkdown } from 'mdast-util-gfm';
3
3
  import { gfm } from 'micromark-extension-gfm';
4
4
  import { gfmFootnoteFromMarkdown } from 'mdast-util-gfm-footnote';
5
+ import { math } from 'micromark-extension-math';
6
+ import { mathFromMarkdown } from 'mdast-util-math';
5
7
  import { codes, constants, types } from 'micromark-util-symbol';
6
8
  import { markdownLineEndingOrSpace } from 'micromark-util-character';
7
9
  import { factoryDestination } from 'micromark-factory-destination';
@@ -865,14 +867,12 @@ var RE_FENCE_START = /^(\s*)((`{3,})|(~{3,}))/;
865
867
  var RE_EMPTY_LINE = /^\s*$/;
866
868
  var RE_HEADING = /^#{1,6}\s/;
867
869
  var RE_THEMATIC_BREAK = /^(\*{3,}|-{3,}|_{3,})\s*$/;
868
- var RE_UNORDERED_LIST = /^(\s*)([-*+])\s/;
869
- var RE_ORDERED_LIST = /^(\s*)(\d{1,9})[.)]\s/;
870
870
  var RE_BLOCKQUOTE = /^\s{0,3}>/;
871
871
  var RE_HTML_BLOCK_1 = /^\s{0,3}<(script|pre|style|textarea|!--|!DOCTYPE|\?|!\[CDATA\[)/i;
872
872
  var RE_HTML_BLOCK_2 = /^\s{0,3}<\/?[a-zA-Z][a-zA-Z0-9-]*(\s|>|$)/;
873
873
  var RE_TABLE_DELIMITER = /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?$/;
874
874
  var RE_ESCAPE_SPECIAL = /[.*+?^${}()|[\]\\]/g;
875
- var RE_FOOTNOTE_DEFINITION = /^\[\^[^\]]+\]:\s/;
875
+ var RE_FOOTNOTE_DEFINITION = /^\[\^([^\]]+)\]:\s/;
876
876
  var RE_FOOTNOTE_CONTINUATION = /^(?: |\t)/;
877
877
  var fenceEndPatternCache = /* @__PURE__ */ new Map();
878
878
  var containerPatternCache = /* @__PURE__ */ new Map();
@@ -907,13 +907,23 @@ function isThematicBreak(line) {
907
907
  return RE_THEMATIC_BREAK.test(line.trim());
908
908
  }
909
909
  function isListItemStart(line) {
910
- const unordered = line.match(RE_UNORDERED_LIST);
911
- if (unordered) {
912
- return { ordered: false, indent: unordered[1].length };
910
+ const hasListMarker = /^(\s*)([-*+]|\d{1,9}[.)])/.test(line);
911
+ if (!hasListMarker) {
912
+ return null;
913
913
  }
914
- const ordered = line.match(RE_ORDERED_LIST);
915
- if (ordered) {
916
- return { ordered: true, indent: ordered[1].length };
914
+ const match = line.match(/^(\s*)([-*+]|\d{1,9}[.)])(.*)/);
915
+ if (match) {
916
+ const indent = match[1].length;
917
+ const marker = match[2];
918
+ const rest = match[3];
919
+ if (rest.trim()) {
920
+ const isOrdered = /^\d{1,9}[.)]/.test(marker);
921
+ return { ordered: isOrdered, indent };
922
+ }
923
+ if (/^\s+$/.test(rest)) {
924
+ const isOrdered = /^\d{1,9}[.)]/.test(marker);
925
+ return { ordered: isOrdered, indent };
926
+ }
917
927
  }
918
928
  return null;
919
929
  }
@@ -993,7 +1003,9 @@ function createInitialContext() {
993
1003
  blockquoteDepth: 0,
994
1004
  inContainer: false,
995
1005
  containerDepth: 0,
996
- inList: false
1006
+ inList: false,
1007
+ inFootnote: false,
1008
+ footnoteIdentifier: void 0
997
1009
  };
998
1010
  }
999
1011
  function isListContinuation(line, listIndent) {
@@ -1049,6 +1061,17 @@ function updateContext(line, context, containerConfig) {
1049
1061
  }
1050
1062
  }
1051
1063
  }
1064
+ if (context.inFootnote && isFootnoteDefinitionStart(line)) {
1065
+ const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
1066
+ newContext.footnoteIdentifier = identifier;
1067
+ return newContext;
1068
+ }
1069
+ if (!context.inFootnote && isFootnoteDefinitionStart(line)) {
1070
+ const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
1071
+ newContext.inFootnote = true;
1072
+ newContext.footnoteIdentifier = identifier;
1073
+ return newContext;
1074
+ }
1052
1075
  const listItem = isListItemStart(line);
1053
1076
  if (context.inList) {
1054
1077
  if (context.listMayEnd) {
@@ -1097,6 +1120,41 @@ function updateContext(line, context, containerConfig) {
1097
1120
  return newContext;
1098
1121
  }
1099
1122
  }
1123
+ if (context.inFootnote) {
1124
+ if (isEmptyLine(line)) {
1125
+ return newContext;
1126
+ } else if (isListItemStart(line)) {
1127
+ const listItemInfo = isListItemStart(line);
1128
+ if (listItemInfo.indent === 0) {
1129
+ newContext.inFootnote = false;
1130
+ newContext.footnoteIdentifier = void 0;
1131
+ } else {
1132
+ return newContext;
1133
+ }
1134
+ } else if (isHeading(line)) {
1135
+ newContext.inFootnote = false;
1136
+ newContext.footnoteIdentifier = void 0;
1137
+ return newContext;
1138
+ } else if (detectFenceStart(line)) {
1139
+ newContext.inFootnote = false;
1140
+ newContext.footnoteIdentifier = void 0;
1141
+ return newContext;
1142
+ } else if (isBlockquoteStart(line)) {
1143
+ newContext.inFootnote = false;
1144
+ newContext.footnoteIdentifier = void 0;
1145
+ return newContext;
1146
+ } else if (isFootnoteContinuation(line)) {
1147
+ return newContext;
1148
+ } else if (isFootnoteDefinitionStart(line)) {
1149
+ const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
1150
+ newContext.footnoteIdentifier = identifier;
1151
+ return newContext;
1152
+ } else {
1153
+ newContext.inFootnote = false;
1154
+ newContext.footnoteIdentifier = void 0;
1155
+ return newContext;
1156
+ }
1157
+ }
1100
1158
  return newContext;
1101
1159
  }
1102
1160
 
@@ -1245,6 +1303,10 @@ var IncremarkParser = class {
1245
1303
  extensions.push(gfm());
1246
1304
  mdastExtensions.push(...gfmFromMarkdown(), gfmFootnoteFromMarkdown());
1247
1305
  }
1306
+ if (this.options.math) {
1307
+ extensions.push(math());
1308
+ mdastExtensions.push(mathFromMarkdown());
1309
+ }
1248
1310
  if (this.containerConfig !== void 0) {
1249
1311
  extensions.push(directive());
1250
1312
  mdastExtensions.push(directiveFromMarkdown());
@@ -1267,7 +1329,7 @@ var IncremarkParser = class {
1267
1329
  }
1268
1330
  return ast;
1269
1331
  }
1270
- updateDefinationsFromComplatedBlocks(blocks) {
1332
+ updateDefinitionsFromCompletedBlocks(blocks) {
1271
1333
  for (const block of blocks) {
1272
1334
  this.definitionMap = {
1273
1335
  ...this.definitionMap,
@@ -1281,17 +1343,17 @@ var IncremarkParser = class {
1281
1343
  }
1282
1344
  findDefinition(block) {
1283
1345
  const definitions = [];
1284
- function findDefination(node) {
1346
+ function findDefinitionRecursive(node) {
1285
1347
  if (isDefinitionNode(node)) {
1286
1348
  definitions.push(node);
1287
1349
  }
1288
1350
  if ("children" in node && Array.isArray(node.children)) {
1289
1351
  for (const child of node.children) {
1290
- findDefination(child);
1352
+ findDefinitionRecursive(child);
1291
1353
  }
1292
1354
  }
1293
1355
  }
1294
- findDefination(block.node);
1356
+ findDefinitionRecursive(block.node);
1295
1357
  return definitions.reduce((acc, node) => {
1296
1358
  acc[node.identifier] = node;
1297
1359
  return acc;
@@ -1552,7 +1614,7 @@ var IncremarkParser = class {
1552
1614
  const newBlocks = this.nodesToBlocks(ast.children, stableOffset, stableText, "completed");
1553
1615
  this.completedBlocks.push(...newBlocks);
1554
1616
  update.completed = newBlocks;
1555
- this.updateDefinationsFromComplatedBlocks(newBlocks);
1617
+ this.updateDefinitionsFromCompletedBlocks(newBlocks);
1556
1618
  this.context = contextAtLine;
1557
1619
  this.pendingStartLine = stableBoundary + 1;
1558
1620
  }
@@ -1625,7 +1687,7 @@ var IncremarkParser = class {
1625
1687
  );
1626
1688
  this.completedBlocks.push(...finalBlocks);
1627
1689
  update.completed = finalBlocks;
1628
- this.updateDefinationsFromComplatedBlocks(finalBlocks);
1690
+ this.updateDefinitionsFromCompletedBlocks(finalBlocks);
1629
1691
  }
1630
1692
  }
1631
1693
  this.lastPendingBlocks = [];
@@ -1643,7 +1705,7 @@ var IncremarkParser = class {
1643
1705
  }
1644
1706
  /**
1645
1707
  * 强制中断解析,将所有待处理内容标记为完成
1646
- * 语义上等同于 finalize(),但名称更清晰
1708
+ * @deprecated 请使用 finalize() 代替,功能完全相同
1647
1709
  */
1648
1710
  abort() {
1649
1711
  return this.finalize();
@@ -1852,54 +1914,57 @@ function appendToAst(baseNode, sourceNode, startChars, endChars, accumulatedChun
1852
1914
  if (endChars <= startChars) {
1853
1915
  return baseNode;
1854
1916
  }
1855
- const newPart = sliceAst(sourceNode, endChars, accumulatedChunks, startChars);
1856
- if (!newPart) {
1917
+ const fullSlice = sliceAst(sourceNode, endChars, accumulatedChunks);
1918
+ if (!fullSlice) {
1857
1919
  return baseNode;
1858
1920
  }
1859
- return mergeAstNodes(baseNode, newPart);
1921
+ return smartMergeAst(baseNode, fullSlice);
1860
1922
  }
1861
- function mergeAstNodes(baseNode, newPart) {
1862
- if (baseNode.type !== newPart.type) {
1863
- return baseNode;
1923
+ function smartMergeAst(baseNode, fullSlice) {
1924
+ if (baseNode.type !== fullSlice.type) {
1925
+ return fullSlice;
1864
1926
  }
1865
1927
  const base = baseNode;
1866
- const part = newPart;
1867
- if (base.value && typeof base.value === "string" && part.value && typeof part.value === "string") {
1868
- const baseChunks = base.chunks || [];
1869
- const partChunks = part.chunks || [];
1870
- const mergedChunks = [...baseChunks, ...partChunks];
1871
- const mergedValue = base.value + part.value;
1872
- const baseStableLength = base.stableLength ?? 0;
1873
- const result = {
1874
- ...base,
1875
- value: mergedValue,
1876
- stableLength: mergedChunks.length > 0 ? baseStableLength : void 0,
1877
- chunks: mergedChunks.length > 0 ? mergedChunks : void 0
1878
- };
1879
- return result;
1928
+ const full = fullSlice;
1929
+ if (full.value !== void 0) {
1930
+ return fullSlice;
1880
1931
  }
1881
- if (base.children && Array.isArray(base.children) && part.children && Array.isArray(part.children)) {
1882
- if (base.children.length > 0 && part.children.length > 0) {
1883
- const lastBaseChild = base.children[base.children.length - 1];
1884
- const firstPartChild = part.children[0];
1885
- if (lastBaseChild.type === firstPartChild.type) {
1886
- const merged = mergeAstNodes(lastBaseChild, firstPartChild);
1887
- return {
1888
- ...base,
1889
- children: [
1890
- ...base.children.slice(0, -1),
1891
- merged,
1892
- ...part.children.slice(1)
1893
- ]
1894
- };
1932
+ if (base.children && Array.isArray(base.children) && full.children && Array.isArray(full.children)) {
1933
+ if (full.children.length < base.children.length) {
1934
+ return fullSlice;
1935
+ }
1936
+ if (full.children.length === base.children.length) {
1937
+ if (base.children.length === 0) {
1938
+ return fullSlice;
1895
1939
  }
1940
+ const lastIndex = base.children.length - 1;
1941
+ const mergedLast2 = smartMergeAst(
1942
+ base.children[lastIndex],
1943
+ full.children[lastIndex]
1944
+ );
1945
+ return {
1946
+ ...full,
1947
+ children: [
1948
+ ...base.children.slice(0, lastIndex),
1949
+ mergedLast2
1950
+ ]
1951
+ };
1896
1952
  }
1953
+ const baseLastIndex = base.children.length - 1;
1954
+ const mergedLast = smartMergeAst(
1955
+ base.children[baseLastIndex],
1956
+ full.children[baseLastIndex]
1957
+ );
1897
1958
  return {
1898
- ...base,
1899
- children: [...base.children, ...part.children]
1959
+ ...full,
1960
+ children: [
1961
+ ...base.children.slice(0, baseLastIndex),
1962
+ mergedLast,
1963
+ ...full.children.slice(base.children.length)
1964
+ ]
1900
1965
  };
1901
1966
  }
1902
- return baseNode;
1967
+ return fullSlice;
1903
1968
  }
1904
1969
  function cloneNode(node) {
1905
1970
  if (typeof structuredClone === "function") {
@@ -1949,6 +2014,7 @@ var BlockTransformer = class {
1949
2014
  plugins: options.plugins ?? [],
1950
2015
  onChange: options.onChange ?? (() => {
1951
2016
  }),
2017
+ onAllComplete: options.onAllComplete ?? null,
1952
2018
  pauseOnHidden: options.pauseOnHidden ?? true
1953
2019
  };
1954
2020
  this.state = {
@@ -1975,18 +2041,8 @@ var BlockTransformer = class {
1975
2041
  if (this.state.currentBlock) {
1976
2042
  const updated = blocks.find((b) => b.id === this.state.currentBlock.id);
1977
2043
  if (updated && updated.node !== this.state.currentBlock.node) {
1978
- this.clearCache();
1979
- const oldTotal = this.cachedTotalChars ?? this.countChars(this.state.currentBlock.node);
1980
- const newTotal = this.countChars(updated.node);
1981
- if (newTotal < oldTotal || newTotal < this.state.currentProgress) {
1982
- this.state.currentProgress = Math.min(this.state.currentProgress, newTotal);
1983
- }
2044
+ this.handleContentChange(this.state.currentBlock.node, updated.node, true);
1984
2045
  this.state.currentBlock = updated;
1985
- if (!this.rafId && !this.isPaused) {
1986
- if (this.state.currentProgress < newTotal) {
1987
- this.startIfNeeded();
1988
- }
1989
- }
1990
2046
  }
1991
2047
  }
1992
2048
  }
@@ -1995,13 +2051,8 @@ var BlockTransformer = class {
1995
2051
  */
1996
2052
  update(block) {
1997
2053
  if (this.state.currentBlock?.id === block.id) {
1998
- const oldTotal = this.cachedTotalChars ?? this.countChars(this.state.currentBlock.node);
1999
- const newTotal = this.countChars(block.node);
2054
+ this.handleContentChange(this.state.currentBlock.node, block.node, false);
2000
2055
  this.state.currentBlock = block;
2001
- if (newTotal > oldTotal && !this.rafId && !this.isPaused && this.state.currentProgress >= oldTotal) {
2002
- this.clearCache();
2003
- this.startIfNeeded();
2004
- }
2005
2056
  }
2006
2057
  }
2007
2058
  /**
@@ -2023,6 +2074,7 @@ var BlockTransformer = class {
2023
2074
  this.chunks = [];
2024
2075
  this.clearCache();
2025
2076
  this.emit();
2077
+ this.options.onAllComplete?.();
2026
2078
  }
2027
2079
  /**
2028
2080
  * 重置状态
@@ -2147,6 +2199,30 @@ var BlockTransformer = class {
2147
2199
  this.removeVisibilityHandler();
2148
2200
  }
2149
2201
  // ============ 私有方法 ============
2202
+ /**
2203
+ * 处理 block 内容更新时的字符数变化和进度调整
2204
+ * 统一 push 和 update 方法中的重复逻辑
2205
+ */
2206
+ handleContentChange(oldNode, newNode, isUpdateFromPush) {
2207
+ const oldTotal = this.cachedTotalChars ?? this.countChars(oldNode);
2208
+ const newTotal = this.countChars(newNode);
2209
+ if (newTotal < oldTotal || newTotal < this.state.currentProgress) {
2210
+ this.state.currentProgress = Math.min(this.state.currentProgress, newTotal);
2211
+ this.chunks = [];
2212
+ }
2213
+ this.clearCache();
2214
+ if (isUpdateFromPush) {
2215
+ if (!this.rafId && !this.isPaused) {
2216
+ if (this.state.currentProgress < newTotal) {
2217
+ this.startIfNeeded();
2218
+ }
2219
+ }
2220
+ } else {
2221
+ if (newTotal > oldTotal && !this.rafId && !this.isPaused && this.state.currentProgress >= oldTotal) {
2222
+ this.startIfNeeded();
2223
+ }
2224
+ }
2225
+ }
2150
2226
  getAllBlockIds() {
2151
2227
  return new Set([
2152
2228
  ...this.state.completedBlocks.map((b) => b.id),
@@ -2233,8 +2309,21 @@ var BlockTransformer = class {
2233
2309
  }
2234
2310
  /**
2235
2311
  * 从 AST 节点中提取指定范围的文本
2312
+ *
2313
+ * 优化说明:
2314
+ * - 提前终止:当 charIndex >= end 时立即返回,避免不必要的遍历
2315
+ * - 局部更新:charIndex 只在需要时更新,减少计算
2316
+ * - 早期返回:发现足够的文本后可以提前退出(当前未实现,可作为未来优化)
2317
+ *
2318
+ * @param node 要提取文本的 AST 节点
2319
+ * @param start 起始字符索引(包含)
2320
+ * @param end 结束字符索引(不包含)
2321
+ * @returns 提取的文本
2236
2322
  */
2237
2323
  extractText(node, start, end) {
2324
+ if (start >= end) {
2325
+ return "";
2326
+ }
2238
2327
  let result = "";
2239
2328
  let charIndex = 0;
2240
2329
  function traverse(n) {
@@ -2242,12 +2331,12 @@ var BlockTransformer = class {
2242
2331
  if (n.value && typeof n.value === "string") {
2243
2332
  const nodeStart = charIndex;
2244
2333
  const nodeEnd = charIndex + n.value.length;
2245
- charIndex = nodeEnd;
2246
2334
  const overlapStart = Math.max(start, nodeStart);
2247
2335
  const overlapEnd = Math.min(end, nodeEnd);
2248
2336
  if (overlapStart < overlapEnd) {
2249
2337
  result += n.value.slice(overlapStart - nodeStart, overlapEnd - nodeStart);
2250
2338
  }
2339
+ charIndex = nodeEnd;
2251
2340
  return charIndex < end;
2252
2341
  }
2253
2342
  if (n.children && Array.isArray(n.children)) {
@@ -2279,6 +2368,7 @@ var BlockTransformer = class {
2279
2368
  this.isRunning = false;
2280
2369
  this.cancelRaf();
2281
2370
  this.emit();
2371
+ this.options.onAllComplete?.();
2282
2372
  }
2283
2373
  }
2284
2374
  cancelRaf() {
@@ -2364,12 +2454,21 @@ var BlockTransformer = class {
2364
2454
  }
2365
2455
  /**
2366
2456
  * 获取总字符数(带缓存)
2457
+ *
2458
+ * 缓存策略:
2459
+ * - 首次调用时计算并缓存
2460
+ * - 内容更新时通过 clearCache() 清除缓存,下次重新计算
2461
+ * - 切换到新 block 时也会清除缓存
2367
2462
  */
2368
2463
  getTotalChars() {
2369
- if (this.cachedTotalChars === null && this.state.currentBlock) {
2464
+ if (!this.state.currentBlock) {
2465
+ this.cachedTotalChars = null;
2466
+ return 0;
2467
+ }
2468
+ if (this.cachedTotalChars === null) {
2370
2469
  this.cachedTotalChars = this.countChars(this.state.currentBlock.node);
2371
2470
  }
2372
- return this.cachedTotalChars ?? 0;
2471
+ return this.cachedTotalChars;
2373
2472
  }
2374
2473
  /**
2375
2474
  * 清除缓存(当 block 切换或内容更新时)
@@ -2381,10 +2480,13 @@ var BlockTransformer = class {
2381
2480
  }
2382
2481
  /**
2383
2482
  * 获取累积的 chunks(用于 fade-in 效果)
2483
+ * stableChars 表示在 chunks 之前的稳定字符数
2384
2484
  */
2385
2485
  getAccumulatedChunks() {
2386
2486
  if (this.options.effect === "fade-in" && this.chunks.length > 0) {
2387
- return { stableChars: 0, chunks: this.chunks };
2487
+ const chunksLength = this.chunks.reduce((sum, c) => sum + c.text.length, 0);
2488
+ const stableChars = this.state.currentProgress - chunksLength;
2489
+ return { stableChars: Math.max(0, stableChars), chunks: this.chunks };
2388
2490
  }
2389
2491
  return void 0;
2390
2492
  }