@wdprlib/parser 5.3.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/index.cjs +2429 -1199
  2. package/dist/index.d.cts +95 -8
  3. package/dist/index.d.ts +95 -8
  4. package/dist/index.js +2385 -1152
  5. package/package.json +3 -2
  6. package/src/build-info.generated.ts +2 -2
  7. package/src/index.ts +3 -0
  8. package/src/lexer/lexer.ts +34 -5
  9. package/src/lexer/quoted-string.ts +4 -4
  10. package/src/lexer/syntax-actions.ts +6 -1
  11. package/src/lexer/text-actions.ts +15 -1
  12. package/src/lexer/url-schemes.ts +78 -0
  13. package/src/parser/constants.ts +4 -0
  14. package/src/parser/parse/block.ts +1 -1
  15. package/src/parser/postprocess/divAdjacentParagraph.ts +23 -21
  16. package/src/parser/postprocess/spanStrip/merge.ts +8 -2
  17. package/src/parser/preprocess/typography.ts +25 -3
  18. package/src/parser/preprocess/utils/raw-regions.ts +59 -14
  19. package/src/parser/preprocess/whitespace/index.ts +8 -1
  20. package/src/parser/rules/block/bibliography/entry-content.ts +1 -1
  21. package/src/parser/rules/block/block-list/bare-content.ts +3 -1
  22. package/src/parser/rules/block/block-list/bare-paragraph.ts +7 -2
  23. package/src/parser/rules/block/block-list/item-content.ts +7 -3
  24. package/src/parser/rules/block/block-list/li-content.ts +8 -3
  25. package/src/parser/rules/block/block-list/li-item.ts +1 -1
  26. package/src/parser/rules/block/blockquote/build.ts +1 -1
  27. package/src/parser/rules/block/code/boundary.ts +76 -0
  28. package/src/parser/rules/block/code/content.ts +11 -40
  29. package/src/parser/rules/block/code/index.ts +8 -31
  30. package/src/parser/rules/block/code/open.ts +46 -0
  31. package/src/parser/rules/block/definition-list/item-key.ts +1 -1
  32. package/src/parser/rules/block/definition-list/item-value.ts +1 -1
  33. package/src/parser/rules/block/div/failed.ts +2 -0
  34. package/src/parser/rules/block/index.ts +3 -0
  35. package/src/parser/rules/block/list/line.ts +6 -3
  36. package/src/parser/rules/block/math/boundary.ts +104 -0
  37. package/src/parser/rules/block/math/index.ts +17 -57
  38. package/src/parser/rules/block/module/index.ts +3 -2
  39. package/src/parser/rules/block/module/listpages/extraction/listpages.ts +5 -0
  40. package/src/parser/rules/block/module/listpages/extraction/query.ts +1 -0
  41. package/src/parser/rules/block/module/listpages/extraction/template.ts +16 -0
  42. package/src/parser/rules/block/module/listpages/extraction/variables.ts +1 -0
  43. package/src/parser/rules/block/module/listpages/index.ts +1 -0
  44. package/src/parser/rules/block/module/listpages/normalization/order-parent.ts +9 -1
  45. package/src/parser/rules/block/module/listpages/normalize.ts +1 -0
  46. package/src/parser/rules/block/module/listpages/parser.ts +1 -0
  47. package/src/parser/rules/block/module/listpages/resolve.ts +4 -2
  48. package/src/parser/rules/block/module/listpages/template/excerpt.ts +63 -0
  49. package/src/parser/rules/block/module/listpages/template/format/content.ts +0 -32
  50. package/src/parser/rules/block/module/listpages/template/format/index.ts +1 -1
  51. package/src/parser/rules/block/module/listpages/template/getters/index.ts +14 -0
  52. package/src/parser/rules/block/module/listpages/template/getters/parameterized.ts +3 -1
  53. package/src/parser/rules/block/module/listpages/template/getters/registered.ts +52 -0
  54. package/src/parser/rules/block/module/listpages/template/getters/simple.ts +15 -5
  55. package/src/parser/rules/block/module/listpages/template/literal.ts +6 -0
  56. package/src/parser/rules/block/module/listpages/template/syntax.ts +10 -1
  57. package/src/parser/rules/block/module/listpages/types/data-requirements.ts +6 -0
  58. package/src/parser/rules/block/module/listpages/types/external-data.ts +36 -2
  59. package/src/parser/rules/block/module/listpages/types/normalized-query.ts +14 -4
  60. package/src/parser/rules/block/module/listpages/types/query.ts +2 -0
  61. package/src/parser/rules/block/module/listpages/types/variables.ts +5 -0
  62. package/src/parser/rules/block/module/listpages/url-resolution/fields.ts +2 -0
  63. package/src/parser/rules/block/module/listusers/resolve.ts +2 -1
  64. package/src/parser/rules/block/module/mapping.ts +2 -1
  65. package/src/parser/rules/block/module/rate/index.ts +16 -19
  66. package/src/parser/rules/block/module/rate/resolve.ts +108 -0
  67. package/src/parser/rules/block/module/rate/types.ts +6 -16
  68. package/src/parser/rules/block/module/resolution/resolve-async.ts +2 -1
  69. package/src/parser/rules/block/module/resolve.ts +10 -8
  70. package/src/parser/rules/block/module/types-common.ts +4 -0
  71. package/src/parser/rules/block/note/boundary.ts +88 -0
  72. package/src/parser/rules/block/note/index.ts +37 -0
  73. package/src/parser/rules/block/paragraph/index.ts +12 -4
  74. package/src/parser/rules/block/paragraph/normalize.ts +6 -1
  75. package/src/parser/rules/block/parsing/block-item.ts +1 -1
  76. package/src/parser/rules/block/parsing/content.ts +1 -1
  77. package/src/parser/rules/block/parsing/inline-content.ts +6 -3
  78. package/src/parser/rules/block/table/pipe/cell.ts +3 -1
  79. package/src/parser/rules/block/table-block/cell-content/index.ts +1 -1
  80. package/src/parser/rules/block/table-block/cell-content/segments.ts +5 -2
  81. package/src/parser/rules/block/table-block/cell-newline.ts +2 -1
  82. package/src/parser/rules/contracts/rule.ts +4 -2
  83. package/src/parser/rules/contracts/scope.ts +2 -0
  84. package/src/parser/rules/inline/anchor/child.ts +8 -2
  85. package/src/parser/rules/inline/anchor/content.ts +3 -1
  86. package/src/parser/rules/inline/anchor/index.ts +4 -1
  87. package/src/parser/rules/inline/anchor/newline.ts +2 -1
  88. package/src/parser/rules/inline/autolink.ts +153 -0
  89. package/src/parser/rules/inline/button/attributes.ts +17 -0
  90. package/src/parser/rules/inline/button/index.ts +17 -0
  91. package/src/parser/rules/inline/button/syntax.ts +56 -0
  92. package/src/parser/rules/inline/date/index.ts +17 -0
  93. package/src/parser/rules/inline/date/syntax.ts +46 -0
  94. package/src/parser/rules/inline/email/candidates.ts +134 -0
  95. package/src/parser/rules/inline/email/index.ts +36 -0
  96. package/src/parser/rules/inline/email/scan.ts +76 -0
  97. package/src/parser/rules/inline/expr/branch.ts +3 -1
  98. package/src/parser/rules/inline/footnote/boundary.ts +56 -0
  99. package/src/parser/rules/inline/footnote/content.ts +29 -41
  100. package/src/parser/rules/inline/footnote/elements.ts +9 -34
  101. package/src/parser/rules/inline/footnote/index.ts +4 -1
  102. package/src/parser/rules/inline/formatting/close.ts +12 -0
  103. package/src/parser/rules/inline/index.ts +14 -0
  104. package/src/parser/rules/inline/line-break/newline.ts +8 -1
  105. package/src/parser/rules/inline/link-bracket/direct-url.ts +11 -3
  106. package/src/parser/rules/inline/link-bracket/parsed.ts +9 -4
  107. package/src/parser/rules/inline/link-bracket/parts.ts +14 -36
  108. package/src/parser/rules/inline/link-bracket/special-target.ts +9 -0
  109. package/src/parser/rules/inline/link-single.ts +9 -7
  110. package/src/parser/rules/inline/link-triple/index.ts +1 -0
  111. package/src/parser/rules/inline/link-triple/label.ts +7 -1
  112. package/src/parser/rules/inline/parsing/automatic-line-break.ts +35 -0
  113. package/src/parser/rules/inline/parsing/block-boundary.ts +2 -0
  114. package/src/parser/rules/inline/parsing/block-start-predicates.ts +10 -0
  115. package/src/parser/rules/inline/parsing/inline-content.ts +46 -7
  116. package/src/parser/rules/inline/parsing/plain-text.ts +7 -2
  117. package/src/parser/rules/inline/parsing/preserved-line-break.ts +13 -0
  118. package/src/parser/rules/inline/parsing/raw-tag.ts +19 -0
  119. package/src/parser/rules/inline/raw/angle.ts +2 -1
  120. package/src/parser/rules/inline/raw/end.ts +21 -1
  121. package/src/parser/rules/inline/size/content.ts +32 -5
  122. package/src/parser/rules/inline/size/value.ts +11 -0
  123. package/src/parser/rules/inline/social/index.ts +17 -0
  124. package/src/parser/rules/inline/social/syntax.ts +40 -0
  125. package/src/parser/rules/inline/span/content.ts +3 -1
  126. package/src/parser/rules/inline/span/newline.ts +2 -1
  127. package/src/parser/rules/opaque-probe.ts +58 -0
  128. package/src/pipeline/process.ts +13 -0
  129. package/src/pipeline/types.ts +19 -0
  130. package/src/parser/rules/block/math/content.ts +0 -54
  131. package/src/parser/rules/block/math/name.ts +0 -35
  132. package/src/parser/rules/inline/footnote/child.ts +0 -22
  133. package/src/parser/rules/inline/footnote/newline.ts +0 -27
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // packages/parser/src/build-info.generated.ts
2
2
  var buildInfo = Object.freeze({
3
- version: "5.3.0",
4
- sha: "b71b84c705e05717f5ea12fd89684a91b669dbff",
3
+ version: "6.0.0",
4
+ sha: "705ff97e22abd7bfb49d7f8f30087e6f09654c91",
5
5
  dirty: false
6
6
  });
7
7
 
@@ -205,13 +205,13 @@ function isValidAnchorNameChar(code) {
205
205
  }
206
206
 
207
207
  // packages/parser/src/lexer/quoted-string.ts
208
- function scanQuotedString(state) {
208
+ function scanQuotedString(state, end = state.source.length) {
209
209
  let quoted = advance(state);
210
- while (!isAtEnd(state) && current(state) !== '"' && current(state) !== `
210
+ while (state.pos < end && current(state) !== '"' && current(state) !== `
211
211
  `) {
212
212
  quoted += advance(state);
213
213
  }
214
- if (current(state) === '"') {
214
+ if (state.pos < end && current(state) === '"') {
215
215
  quoted += advance(state);
216
216
  }
217
217
  return quoted;
@@ -296,6 +296,26 @@ function isCompactPlainTextBoundary(code) {
296
296
  }
297
297
  }
298
298
 
299
+ // packages/parser/src/lexer/url-schemes.ts
300
+ var URL_SCHEME_NAMES = new Set([
301
+ "http",
302
+ "https",
303
+ "ftp",
304
+ "gopher",
305
+ "news",
306
+ "mailto",
307
+ "mms"
308
+ ]);
309
+ var TRAILING_URL_SCHEME = /(?:^|[^A-Za-z0-9])(https?|ftp|gopher|news|mailto|mms)$/;
310
+ var URL_SCHEME_PREFIX = /^(?:(?:https?|ftp|gopher|news|mms):\/\/|mailto:)/;
311
+ var URL_PATTERN = /^(?:(?:https?|ftp|gopher|news|mms):\/\/|mailto:)(?:[^ \\/"']*\/)*[^ \t\n\\/"']*[A-Za-z%0-9/?=&~_]/;
312
+ function startsWithUrlScheme(src, pos) {
313
+ return URL_SCHEME_PREFIX.test(src.slice(pos, pos + 10));
314
+ }
315
+ function startsWithUrl(src, pos) {
316
+ return URL_PATTERN.test(src.slice(pos, pos + 2048));
317
+ }
318
+
299
319
  // packages/parser/src/lexer/syntax-actions.ts
300
320
  function scanSimpleSyntaxToken(src, pos, isLineStart) {
301
321
  switch (src[pos]) {
@@ -332,7 +352,7 @@ function scanSimpleSyntaxToken(src, pos, isLineStart) {
332
352
  }
333
353
  }
334
354
  function scanStarToken(src, pos, isLineStart) {
335
- if (src[pos + 1] === "*") {
355
+ if (src[pos + 1] === "*" && !startsWithUrl(src, pos + 2)) {
336
356
  return token("BOLD_MARKER", "**");
337
357
  }
338
358
  return isLineStart ? token("LIST_BULLET", "*") : token("STAR", "*");
@@ -518,7 +538,13 @@ function scanTextToken(src, pos) {
518
538
  return token3("TEXT", char);
519
539
  }
520
540
  function scanCompactTextToken(src, pos) {
521
- const end = findCompactPlainTextRunEnd(src, pos);
541
+ let end = findCompactPlainTextRunEnd(src, pos);
542
+ if (end > pos && src[end] === ":") {
543
+ const match = TRAILING_URL_SCHEME.exec(src.slice(pos, end));
544
+ if (match) {
545
+ end -= match[1].length;
546
+ }
547
+ }
522
548
  return end > pos ? runToken3(src, pos, end, "TEXT") : null;
523
549
  }
524
550
  function token3(type, value) {
@@ -564,6 +590,8 @@ class Lexer {
564
590
  splitBlockClosePositions = new Set;
565
591
  lastNonWhitespaceType = null;
566
592
  blockOpenerDepth = 0;
593
+ rawTagBounds = null;
594
+ rawClosesExhausted = false;
567
595
  constructor(source, options = {}) {
568
596
  this.options = {
569
597
  trackPositions: options.trackPositions ?? true,
@@ -584,8 +612,8 @@ class Lexer {
584
612
  current() {
585
613
  return current(this.state);
586
614
  }
587
- findInvalidAnchorNameEnd() {
588
- return findInvalidAnchorNameEnd(this.state.source, this.state.pos);
615
+ findInvalidAnchorNameEnd(source) {
616
+ return findInvalidAnchorNameEnd(source, this.state.pos);
589
617
  }
590
618
  advance(n = 1) {
591
619
  return advance(this.state, n);
@@ -597,6 +625,18 @@ class Lexer {
597
625
  this.state.tokens.push(createLexerToken(this.state, type, value, this.options.trackPositions));
598
626
  this.lastNonWhitespaceType = updateLastNonWhitespaceType(this.lastNonWhitespaceType, type);
599
627
  this.blockOpenerDepth = nextBlockOpenerDepth(this.blockOpenerDepth, type);
628
+ if (this.rawTagBounds === null && !this.rawClosesExhausted && (value.toLowerCase() === "button" || value.toLowerCase() === "social") && this.state.tokens.at(-2)?.type === "BLOCK_OPEN" && /\s/.test(this.current())) {
629
+ const close = this.state.source.indexOf("]]", this.state.pos);
630
+ if (close < 0) {
631
+ this.rawClosesExhausted = true;
632
+ } else {
633
+ this.rawTagBounds = {
634
+ source: this.state.source.slice(0, close),
635
+ close,
636
+ outerDepth: this.blockOpenerDepth - 1
637
+ };
638
+ }
639
+ }
600
640
  }
601
641
  emitTokenAction(action) {
602
642
  advanceByToken(this.state, action.type, action.length, action.value);
@@ -614,7 +654,14 @@ class Lexer {
614
654
  scanToken() {
615
655
  const char = this.current();
616
656
  const isLineStart = isSyntaxLineStart(this.state);
617
- const src = this.state.source;
657
+ const bounds = this.rawTagBounds;
658
+ const src = bounds?.source ?? this.state.source;
659
+ if (bounds && this.state.pos === bounds.close) {
660
+ this.emitTokenAction({ type: "BLOCK_CLOSE", value: "]]", length: 2 });
661
+ this.blockOpenerDepth = bounds.outerDepth;
662
+ this.rawTagBounds = null;
663
+ return;
664
+ }
618
665
  const spacingAction = scanSpacingToken(src, this.state.pos);
619
666
  if (spacingAction) {
620
667
  this.emitTokenAction(limitBlockquotePrefixSpace(spacingAction, this.state.tokens.at(-1)));
@@ -627,7 +674,7 @@ class Lexer {
627
674
  lineStart: isLineStart,
628
675
  physicalLineStart: this.state.lineStart,
629
676
  splitBlockClose: this.splitBlockClosePositions.has(this.state.pos),
630
- findInvalidAnchorNameEnd: () => this.findInvalidAnchorNameEnd()
677
+ findInvalidAnchorNameEnd: () => this.findInvalidAnchorNameEnd(src)
631
678
  });
632
679
  if (punctuation.handled) {
633
680
  if (punctuation.clearSplitBlockCloseAt !== undefined) {
@@ -642,7 +689,7 @@ class Lexer {
642
689
  if (char === '"') {
643
690
  const lastNonWs = this.lastNonWhitespaceTokenType();
644
691
  if (this.blockOpenerDepth > 0 && lastNonWs === "EQUALS") {
645
- this.addToken("QUOTED_STRING", scanQuotedString(this.state));
692
+ this.addToken("QUOTED_STRING", scanQuotedString(this.state, src.length));
646
693
  return;
647
694
  }
648
695
  this.advance();
@@ -830,7 +877,7 @@ function parseBlockItem(ctx) {
830
877
  }
831
878
  }
832
879
  const fallback = ctx.blockFallbackRule.parse(ctx);
833
- if (fallback.success && fallback.elements.length > 0) {
880
+ if (fallback.success) {
834
881
  return { elements: fallback.elements, consumed: fallback.consumed };
835
882
  }
836
883
  return { elements: [], consumed: 1 };
@@ -878,7 +925,8 @@ function parseBlocksUntil(ctx, closeCondition, options) {
878
925
  blockCtx.pos = pos;
879
926
  blockCtx.scope = blockScope;
880
927
  const result = parseBlockItem(blockCtx);
881
- elements.push(...result.elements);
928
+ for (const element of result.elements)
929
+ elements.push(element);
882
930
  consumed += result.consumed;
883
931
  pos += result.consumed;
884
932
  }
@@ -905,6 +953,42 @@ function getExcludedBlockRules(blockRules, excluded) {
905
953
  byExcluded.set(excluded, filtered);
906
954
  return filtered;
907
955
  }
956
+ // packages/parser/src/parser/rules/inline/parsing/automatic-line-break.ts
957
+ var origins = new WeakMap;
958
+ function createAutomaticLineBreak(token5) {
959
+ const element = { element: "line-break" };
960
+ origins.set(element, token5);
961
+ return element;
962
+ }
963
+ function precedingSingleNewline(ctx) {
964
+ let pos = ctx.pos - 1;
965
+ while (ctx.tokens[pos]?.type === "WHITESPACE")
966
+ pos--;
967
+ const newline = ctx.tokens[pos];
968
+ if (newline?.type !== "NEWLINE")
969
+ return;
970
+ pos--;
971
+ while (ctx.tokens[pos]?.type === "WHITESPACE")
972
+ pos--;
973
+ if (ctx.tokens[pos]?.type === "NEWLINE")
974
+ return;
975
+ return newline;
976
+ }
977
+ function stripAutomaticLineBreak(elements, token5) {
978
+ if (!token5)
979
+ return;
980
+ let index = elements.length - 1;
981
+ while (index >= 0) {
982
+ const element2 = elements[index];
983
+ if (element2?.element !== "text" || !/^[ \t]*$/.test(element2.data))
984
+ break;
985
+ index--;
986
+ }
987
+ const element = elements[index];
988
+ if (element && origins.get(element) === token5)
989
+ elements.length = index;
990
+ }
991
+
908
992
  // packages/parser/src/parser/rules/inline/parsing/rules.ts
909
993
  function canApplyInlineRule(rule, token5) {
910
994
  if (rule.startTokens.length === 0) {
@@ -927,18 +1011,479 @@ function getCandidateInlineRules(inlineRules, tokenType) {
927
1011
  byType.set(tokenType, candidates);
928
1012
  return candidates;
929
1013
  }
1014
+ // packages/parser/src/parser/rules/inline/parsing/raw-tag.ts
1015
+ var unclosedRanges = new WeakMap;
1016
+ function findRawTagClose(tokens, start, end) {
1017
+ const unclosed = unclosedRanges.get(tokens);
1018
+ if (unclosed && start >= unclosed.start && end <= unclosed.end)
1019
+ return null;
1020
+ for (let close = start;close < end; close++) {
1021
+ if (tokens[close]?.type === "BLOCK_CLOSE")
1022
+ return close;
1023
+ if (!tokens[close] || tokens[close]?.type === "EOF")
1024
+ break;
1025
+ }
1026
+ unclosedRanges.set(tokens, { start, end });
1027
+ return null;
1028
+ }
1029
+
1030
+ // packages/parser/src/parser/rules/inline/button/attributes.ts
1031
+ function parseButtonAttributes(source) {
1032
+ const sections = source.trim().split('="');
1033
+ const attrs = {};
1034
+ let key = sections[0].trim();
1035
+ for (const section of sections.slice(1)) {
1036
+ const quote = section.lastIndexOf('"');
1037
+ if (quote < 0)
1038
+ continue;
1039
+ if (key === "text" || key === "class" || key === "style") {
1040
+ attrs[key] = section.slice(0, quote).replace(/\\([\s\S]|$)/g, (_match, char) => char === "0" ? "\x00" : char);
1041
+ }
1042
+ key = section.slice(quote + 1).trim();
1043
+ }
1044
+ return attrs;
1045
+ }
1046
+
1047
+ // packages/parser/src/parser/rules/inline/button/syntax.ts
1048
+ function parseButtonSyntax(ctx, start, end) {
1049
+ const tokens = ctx.tokens;
1050
+ if (tokens[start]?.type !== "BLOCK_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "button")
1051
+ return null;
1052
+ let pos = start + 2;
1053
+ const skipSpace = () => {
1054
+ const before = pos;
1055
+ while (pos < end && /^\s+$/.test(tokens[pos]?.value ?? ""))
1056
+ pos++;
1057
+ return pos > before;
1058
+ };
1059
+ if (!skipSpace())
1060
+ return null;
1061
+ let action = "";
1062
+ while (pos < end && tokens[pos]?.type !== "BLOCK_CLOSE" && !/^\s+$/.test(tokens[pos]?.value ?? "")) {
1063
+ const part = tokens[pos]?.value ?? "";
1064
+ if (!/^[a-z0-9_-]+$/i.test(part))
1065
+ return null;
1066
+ action += part;
1067
+ pos++;
1068
+ }
1069
+ if (!action)
1070
+ return null;
1071
+ action = action.replaceAll("_", "-");
1072
+ skipSpace();
1073
+ const close = findRawTagClose(tokens, pos, end);
1074
+ if (close === null)
1075
+ return null;
1076
+ const attrs = parseButtonAttributes(tokens.slice(pos, close).map((token5) => token5.value).join(""));
1077
+ const value = (key) => attrs[key] && attrs[key] !== "0" ? attrs[key] : null;
1078
+ const attributes = {};
1079
+ for (const name of ["class", "style"]) {
1080
+ const attr = value(name);
1081
+ if (attr !== null)
1082
+ attributes[name] = attr;
1083
+ }
1084
+ return { data: { action, text: value("text"), attributes }, end: close + 1 };
1085
+ }
1086
+
1087
+ // packages/parser/src/parser/rules/inline/social/syntax.ts
1088
+ var invalidBodies = new WeakMap;
1089
+ function parseSocialSyntax(ctx, start, end) {
1090
+ const tokens = ctx.tokens;
1091
+ if (tokens[start]?.type !== "BLOCK_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "social")
1092
+ return null;
1093
+ const bodyStart = start + 2;
1094
+ if (tokens[bodyStart]?.type !== "BLOCK_CLOSE" && !/^\s+$/.test(tokens[bodyStart]?.value ?? ""))
1095
+ return null;
1096
+ const invalid = invalidBodies.get(tokens);
1097
+ if (invalid && bodyStart >= invalid.start && bodyStart <= invalid.bracket)
1098
+ return null;
1099
+ const close = findRawTagClose(tokens, bodyStart, end);
1100
+ if (close === null)
1101
+ return null;
1102
+ for (let pos = bodyStart;pos < close; pos++) {
1103
+ if (tokens[pos].value.includes("]")) {
1104
+ invalidBodies.set(tokens, { start: bodyStart, bracket: pos });
1105
+ return null;
1106
+ }
1107
+ }
1108
+ const body = tokens.slice(bodyStart, close).map((token5) => token5.value).join("");
1109
+ const sites = body.trim() ? body.split(",").map((site) => site.trim().toLowerCase()).filter(Boolean) : null;
1110
+ return { data: { sites }, end: close + 1 };
1111
+ }
1112
+
1113
+ // packages/parser/src/parser/rules/block/parsing/attributes/names.ts
1114
+ function consumeAttributeName(ctx, startPos, startConsumed, startName, options) {
1115
+ if (startName === "_" && isAttributeWordToken(ctx.tokens[startPos])) {
1116
+ startName += ctx.tokens[startPos]?.value ?? "";
1117
+ startPos++;
1118
+ startConsumed++;
1119
+ }
1120
+ return options.strikeHyphens ? consumeRawNameSuffix(ctx, startPos, startConsumed, startName, options.hyphenatedNames) : consumeSafeNameSuffix(ctx, startPos, startConsumed, startName);
1121
+ }
1122
+ function isAttributeNameToken(token5) {
1123
+ return isAttributeWordToken(token5) || token5?.type === "UNDERSCORE";
1124
+ }
1125
+ function consumeSafeNameSuffix(ctx, startPos, startConsumed, startName) {
1126
+ let name = startName;
1127
+ let pos = startPos;
1128
+ let consumed = startConsumed;
1129
+ while (isAttributeNameSeparator(ctx.tokens[pos]) && isAttributeWordToken(ctx.tokens[pos + 1])) {
1130
+ name += ctx.tokens[pos]?.value ?? "";
1131
+ pos++;
1132
+ consumed++;
1133
+ name += ctx.tokens[pos]?.value ?? "";
1134
+ pos++;
1135
+ consumed++;
1136
+ }
1137
+ return { name, pos, consumed };
1138
+ }
1139
+ function consumeRawNameSuffix(ctx, startPos, startConsumed, startName, hyphenatedNames) {
1140
+ let name = startName;
1141
+ let pos = startPos;
1142
+ let consumed = startConsumed;
1143
+ while (isAttributeNameSeparator(ctx.tokens[pos])) {
1144
+ while (isAttributeNameSeparator(ctx.tokens[pos])) {
1145
+ if (hyphenatedNames || ctx.tokens[pos]?.type === "UNDERSCORE") {
1146
+ name += ctx.tokens[pos]?.value ?? "-";
1147
+ }
1148
+ pos++;
1149
+ consumed++;
1150
+ }
1151
+ if (!isAttributeWordToken(ctx.tokens[pos])) {
1152
+ break;
1153
+ }
1154
+ if (hyphenatedNames || name.endsWith("_")) {
1155
+ name += ctx.tokens[pos]?.value ?? "";
1156
+ }
1157
+ pos++;
1158
+ consumed++;
1159
+ }
1160
+ return { name, pos, consumed };
1161
+ }
1162
+ function isAttributeWordToken(token5) {
1163
+ return token5?.type === "TEXT" || token5?.type === "IDENTIFIER";
1164
+ }
1165
+ function isAttributeNameSeparator(token5) {
1166
+ return token5?.type === "TEXT" && token5.value === "-" || token5?.type === "STRIKE_MARKER" || token5?.type === "UNDERSCORE";
1167
+ }
1168
+
1169
+ // packages/parser/src/parser/rules/block/parsing/attributes/values.ts
1170
+ function consumeAttributeValue(token5) {
1171
+ if (!token5) {
1172
+ return null;
1173
+ }
1174
+ if (token5.type === "QUOTED_STRING") {
1175
+ return { value: stripQuotes(token5.value) };
1176
+ }
1177
+ if (isAttributeNameToken(token5)) {
1178
+ return { value: token5.value };
1179
+ }
1180
+ return null;
1181
+ }
1182
+ function stripQuotes(value) {
1183
+ return value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
1184
+ }
1185
+
1186
+ // packages/parser/src/parser/rules/block/parsing/attributes/scanner.ts
1187
+ function scanAttributes(ctx, startPos, options) {
1188
+ const attrs = {};
1189
+ let pos = startPos;
1190
+ let consumed = 0;
1191
+ while (pos < ctx.tokens.length) {
1192
+ const token5 = ctx.tokens[pos];
1193
+ if (!token5 || isAttributeTerminator(token5)) {
1194
+ break;
1195
+ }
1196
+ if (token5.type === "WHITESPACE") {
1197
+ pos++;
1198
+ consumed++;
1199
+ continue;
1200
+ }
1201
+ if (!isAttributeNameToken(token5)) {
1202
+ pos++;
1203
+ consumed++;
1204
+ continue;
1205
+ }
1206
+ let name = token5.value;
1207
+ pos++;
1208
+ consumed++;
1209
+ const nameResult = consumeAttributeName(ctx, pos, consumed, name, options);
1210
+ pos = nameResult.pos;
1211
+ consumed = nameResult.consumed;
1212
+ name = nameResult.name.toLowerCase();
1213
+ if (ctx.tokens[pos]?.type !== "EQUALS") {
1214
+ attrs[name] = "true";
1215
+ continue;
1216
+ }
1217
+ pos++;
1218
+ consumed++;
1219
+ const valueResult = consumeAttributeValue(ctx.tokens[pos]);
1220
+ if (!valueResult) {
1221
+ continue;
1222
+ }
1223
+ attrs[name] = valueResult.value;
1224
+ pos++;
1225
+ consumed++;
1226
+ }
1227
+ return { attrs, consumed };
1228
+ }
1229
+ function isAttributeTerminator(token5) {
1230
+ return token5.type === "BLOCK_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF";
1231
+ }
1232
+
1233
+ // packages/parser/src/parser/rules/block/parsing/attributes/index.ts
1234
+ function parseAttributes(ctx, startPos) {
1235
+ const result = scanAttributes(ctx, startPos, { hyphenatedNames: true, strikeHyphens: false });
1236
+ return { attrs: filterUnsafeAttributes(result.attrs), consumed: result.consumed };
1237
+ }
1238
+ function parseAttributesRaw(ctx, startPos, hyphenatedNames = true) {
1239
+ return scanAttributes(ctx, startPos, { hyphenatedNames, strikeHyphens: true });
1240
+ }
1241
+
1242
+ // packages/parser/src/parser/rules/inline/date/syntax.ts
1243
+ function parseDateSyntax(ctx, start, end) {
1244
+ const tokens = ctx.tokens;
1245
+ if (tokens[start]?.type !== "BLOCK_OPEN" || tokens[start + 1]?.value !== "date")
1246
+ return null;
1247
+ let pos = start + 2;
1248
+ const skipSpace = () => {
1249
+ const before = pos;
1250
+ while (pos < end && /^(?:\s+)$/.test(tokens[pos]?.value ?? ""))
1251
+ pos++;
1252
+ return pos > before;
1253
+ };
1254
+ if (!skipSpace())
1255
+ return null;
1256
+ const value = tokens[pos]?.value ?? "";
1257
+ if (pos >= end || !/^\d+$/.test(value))
1258
+ return null;
1259
+ const timestamp = Number(value);
1260
+ if (!Number.isSafeInteger(timestamp) || timestamp > 8640000000000)
1261
+ return null;
1262
+ pos++;
1263
+ const separator = skipSpace();
1264
+ if (tokens[pos]?.type !== "BLOCK_CLOSE" && !separator)
1265
+ return null;
1266
+ let close = pos;
1267
+ for (;close < end; close++) {
1268
+ const token5 = tokens[close];
1269
+ if (!token5 || token5.type === "EOF" || token5.type === "BLOCK_OPEN" || /[\r\n]/.test(token5.value))
1270
+ return null;
1271
+ if (token5.type === "BLOCK_CLOSE")
1272
+ break;
1273
+ }
1274
+ if (close >= end)
1275
+ return null;
1276
+ const attributes = parseAttributesRaw(ctx, pos).attrs;
1277
+ const format = attributes.format || null;
1278
+ return {
1279
+ data: {
1280
+ value: { timestamp, timezone: "UTC" },
1281
+ format,
1282
+ hover: format?.split("|").slice(1).includes("agohover") ?? false
1283
+ },
1284
+ end: close + 1
1285
+ };
1286
+ }
1287
+
1288
+ // packages/parser/src/parser/rules/inline/email/scan.ts
1289
+ function isDomainChar(code) {
1290
+ return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 45;
1291
+ }
1292
+ function isLocalChar(code) {
1293
+ return isDomainChar(code) || code === 95;
1294
+ }
1295
+ function scanEmail(source, start, commentEnd) {
1296
+ const comments = [];
1297
+ let pos = start;
1298
+ const skipComments = () => {
1299
+ let end2 = commentEnd(pos);
1300
+ while (end2 > pos) {
1301
+ comments.push({ start: pos, end: end2 });
1302
+ pos = end2;
1303
+ end2 = commentEnd(pos);
1304
+ }
1305
+ };
1306
+ let localPart = false;
1307
+ while (pos < source.length) {
1308
+ skipComments();
1309
+ if (isLocalChar(source.charCodeAt(pos))) {
1310
+ localPart = true;
1311
+ pos++;
1312
+ } else if (source[pos] === "." && localPart) {
1313
+ localPart = false;
1314
+ pos++;
1315
+ } else
1316
+ break;
1317
+ }
1318
+ const localEnd = pos;
1319
+ if (!localPart || source[pos] !== "@")
1320
+ return { localEnd, comments };
1321
+ pos++;
1322
+ let domainPart = false;
1323
+ let dots = 0;
1324
+ let end = -1;
1325
+ while (pos < source.length) {
1326
+ skipComments();
1327
+ if (isDomainChar(source.charCodeAt(pos))) {
1328
+ domainPart = true;
1329
+ pos++;
1330
+ if (dots > 0)
1331
+ end = pos;
1332
+ } else if (source[pos] === "." && domainPart) {
1333
+ domainPart = false;
1334
+ dots++;
1335
+ pos++;
1336
+ } else
1337
+ break;
1338
+ }
1339
+ if (end === -1)
1340
+ return { localEnd, comments };
1341
+ const parts = [];
1342
+ let copied = start;
1343
+ for (const comment of comments) {
1344
+ if (comment.start >= end)
1345
+ break;
1346
+ parts.push(source.slice(copied, comment.start));
1347
+ copied = comment.end;
1348
+ }
1349
+ parts.push(source.slice(copied, end));
1350
+ return { end, address: parts.join(""), localEnd, comments };
1351
+ }
1352
+
1353
+ // packages/parser/src/parser/rules/inline/email/candidates.ts
1354
+ var caches = new WeakMap;
1355
+ var EMAIL_START_TOKENS = [
1356
+ "TEXT",
1357
+ "IDENTIFIER",
1358
+ "UNDERSCORE",
1359
+ "UNDERLINE_MARKER",
1360
+ "STRIKE_MARKER"
1361
+ ];
1362
+ function getCache(tokens) {
1363
+ let cache = caches.get(tokens);
1364
+ if (cache)
1365
+ return cache;
1366
+ const offsets = [0];
1367
+ const values = [];
1368
+ for (const token5 of tokens) {
1369
+ values.push(token5.value);
1370
+ offsets.push(offsets.at(-1) + token5.value.length);
1371
+ }
1372
+ cache = {
1373
+ source: values.join(""),
1374
+ offsets,
1375
+ candidates: new Map,
1376
+ commentEnds: new Map,
1377
+ unclosedComment: Infinity
1378
+ };
1379
+ caches.set(tokens, cache);
1380
+ return cache;
1381
+ }
1382
+ function commentEnd(cache, pos) {
1383
+ const opener = cache.source[pos] === `
1384
+ ` ? pos + 1 : pos;
1385
+ if (!cache.source.startsWith("[!--", opener))
1386
+ return pos;
1387
+ const cached = cache.commentEnds.get(pos);
1388
+ if (cached !== undefined)
1389
+ return cached;
1390
+ const close = opener >= cache.unclosedComment ? -1 : cache.source.indexOf("--]", opener + 4);
1391
+ if (close === -1)
1392
+ cache.unclosedComment = Math.min(cache.unclosedComment, opener);
1393
+ const end = close === -1 ? pos : close + 3;
1394
+ cache.commentEnds.set(pos, end);
1395
+ return end;
1396
+ }
1397
+ function getEmailCandidate(tokens, index) {
1398
+ const token5 = tokens[index];
1399
+ if (!token5 || !EMAIL_START_TOKENS.includes(token5.type))
1400
+ return null;
1401
+ const cache = getCache(tokens);
1402
+ if (cache.candidates.has(index))
1403
+ return cache.candidates.get(index);
1404
+ const value = tokens[index]?.value ?? "";
1405
+ let suffix = value.length;
1406
+ while (suffix > 0 && (isLocalChar(value.charCodeAt(suffix - 1)) || value[suffix - 1] === "."))
1407
+ suffix--;
1408
+ const doubled = value.lastIndexOf("..");
1409
+ if (doubled >= suffix)
1410
+ suffix = doubled + 2;
1411
+ while (value[suffix] === ".")
1412
+ suffix++;
1413
+ if (suffix >= value.length || !isLocalChar(value.charCodeAt(suffix))) {
1414
+ cache.candidates.set(index, null);
1415
+ return null;
1416
+ }
1417
+ const start = cache.offsets[index] + suffix;
1418
+ const scanned = scanEmail(cache.source, start, (pos) => commentEnd(cache, pos));
1419
+ if (scanned.end === undefined) {
1420
+ cache.candidates.set(index, null);
1421
+ let comment = 0;
1422
+ for (let i = index + 1;i < tokens.length && cache.offsets[i + 1] <= scanned.localEnd; i++) {
1423
+ const offset = cache.offsets[i];
1424
+ while (scanned.comments[comment] && scanned.comments[comment].end <= offset)
1425
+ comment++;
1426
+ const region = scanned.comments[comment];
1427
+ if (!region || offset < region.start)
1428
+ cache.candidates.set(i, null);
1429
+ }
1430
+ return null;
1431
+ }
1432
+ let endToken = index;
1433
+ while (cache.offsets[endToken + 1] < scanned.end)
1434
+ endToken++;
1435
+ const candidate = { start, end: scanned.end, endToken, address: scanned.address };
1436
+ cache.candidates.set(index, candidate);
1437
+ return candidate;
1438
+ }
1439
+ function getEmailGroup(tokens, index, limit) {
1440
+ const first = getEmailCandidate(tokens, index);
1441
+ if (!first || first.endToken >= limit)
1442
+ return null;
1443
+ const candidates = [first];
1444
+ let last = first;
1445
+ while (last.endToken > index) {
1446
+ const next = getEmailCandidate(tokens, last.endToken);
1447
+ if (!next || next.start < last.end || next.endToken >= limit)
1448
+ break;
1449
+ candidates.push(next);
1450
+ last = next;
1451
+ }
1452
+ const cache = getCache(tokens);
1453
+ return {
1454
+ candidates,
1455
+ source: cache.source,
1456
+ start: cache.offsets[index],
1457
+ end: cache.offsets[last.endToken + 1],
1458
+ endToken: last.endToken + 1
1459
+ };
1460
+ }
1461
+ function emailRegionEnd(tokens, index, limit) {
1462
+ return getEmailGroup(tokens, index, limit)?.endToken ?? index;
1463
+ }
1464
+
930
1465
  // packages/parser/src/parser/rules/inline/raw/end.ts
1466
+ var unclosedAngleRanges = new WeakMap;
931
1467
  function rawRegionEnd(tokens, start, end) {
932
1468
  const type = tokens[start]?.type;
933
1469
  const close = type === "RAW_OPEN" ? "RAW_OPEN" : type === "RAW_BLOCK_OPEN" ? "RAW_BLOCK_CLOSE" : null;
934
1470
  if (!close)
935
1471
  return start;
1472
+ const cached = type === "RAW_BLOCK_OPEN" ? unclosedAngleRanges.get(tokens) : undefined;
1473
+ if (cached && start >= cached.start && start < cached.end && (cached.lineEnd || end <= cached.end)) {
1474
+ return start;
1475
+ }
936
1476
  for (let pos = start + 1;pos < end; pos++) {
937
- if (tokens[pos]?.type === "NEWLINE" || tokens[pos]?.type === "EOF")
938
- break;
1477
+ if (tokens[pos]?.type === "NEWLINE" || tokens[pos]?.type === "EOF") {
1478
+ if (type === "RAW_BLOCK_OPEN")
1479
+ unclosedAngleRanges.set(tokens, { start, end: pos, lineEnd: true });
1480
+ return start;
1481
+ }
939
1482
  if (tokens[pos]?.type === close)
940
1483
  return pos + 1;
941
1484
  }
1485
+ if (type === "RAW_BLOCK_OPEN")
1486
+ unclosedAngleRanges.set(tokens, { start, end, lineEnd: false });
942
1487
  return start;
943
1488
  }
944
1489
  function protectedInlineRegionEnd(tokens, start, end) {
@@ -957,7 +1502,7 @@ var MIN_INLINE_TEXT_RUN_LENGTH = 32;
957
1502
  var MIN_INLINE_TEXT_RUN_DOCUMENT_TOKENS = 1e5;
958
1503
  function collectLongPlainTextRun(ctx, startPos, endType) {
959
1504
  const firstToken = ctx.tokens[startPos];
960
- if (firstToken?.type === "TEXT" && firstToken.value.length >= MIN_INLINE_TEXT_RUN_LENGTH && firstToken.value !== "(") {
1505
+ if (!getEmailCandidate(ctx.tokens, startPos) && firstToken?.type === "TEXT" && firstToken.value.length >= MIN_INLINE_TEXT_RUN_LENGTH && firstToken.value !== "(") {
961
1506
  return { value: firstToken.value, consumed: 1 };
962
1507
  }
963
1508
  let pos = startPos;
@@ -979,10 +1524,10 @@ function collectLongPlainTextRun(ctx, startPos, endType) {
979
1524
  }
980
1525
  function isPlainTextRunToken(ctx, pos) {
981
1526
  const token5 = ctx.tokens[pos];
982
- if (!token5)
1527
+ if (!token5 || getEmailCandidate(ctx.tokens, pos))
983
1528
  return false;
984
1529
  if (token5.type === "IDENTIFIER") {
985
- return true;
1530
+ return !(URL_SCHEME_NAMES.has(token5.value) && ctx.tokens[pos + 1]?.type === "COLON");
986
1531
  }
987
1532
  if (token5.type === "WHITESPACE") {
988
1533
  const next = ctx.tokens[pos + 1];
@@ -1131,6 +1676,10 @@ var BLOCK_START_TOKENS = [
1131
1676
  "HEADING_MARKER",
1132
1677
  "HR_MARKER",
1133
1678
  "TABLE_MARKER",
1679
+ "TABLE_HEADER",
1680
+ "TABLE_LEFT",
1681
+ "TABLE_CENTER",
1682
+ "TABLE_RIGHT",
1134
1683
  "COLON",
1135
1684
  "BLOCK_OPEN",
1136
1685
  "BLOCK_END_OPEN",
@@ -1208,14 +1757,727 @@ var INDENT_ACCEPTING_BLOCK_NAMES = new Set([
1208
1757
  "tabs"
1209
1758
  ]);
1210
1759
 
1211
- // packages/parser/src/parser/rules/inline/parsing/block-boundary.ts
1212
- function isExcludedBlockToken(ctx, tokenPos) {
1213
- const excluded = ctx.scope.excludedBlockNames;
1214
- if (!excluded?.size)
1215
- return false;
1216
- const token5 = ctx.tokens[tokenPos];
1217
- if (token5?.type !== "BLOCK_OPEN" && token5?.type !== "BLOCK_END_OPEN")
1218
- return false;
1760
+ // packages/parser/src/parser/rules/block/code/open.ts
1761
+ var attributeRanges = new WeakMap;
1762
+ function findAttributesEnd(tokens, start) {
1763
+ const cached = attributeRanges.get(tokens);
1764
+ if (cached && start >= cached.start && start <= cached.end)
1765
+ return cached.end;
1766
+ let end = start;
1767
+ while (end < tokens.length) {
1768
+ const type = tokens[end]?.type;
1769
+ if (type === "BLOCK_CLOSE" || type === "NEWLINE" || type === "EOF")
1770
+ break;
1771
+ end++;
1772
+ }
1773
+ attributeRanges.set(tokens, { start, end });
1774
+ return end;
1775
+ }
1776
+ function findCodeOpen(tokens, start) {
1777
+ const name = tokens[start + 1];
1778
+ if (tokens[start]?.type !== "BLOCK_OPEN" || name?.type !== "IDENTIFIER" && name?.type !== "TEXT" || name.value.toLowerCase() !== "code" || tokens[start + 2]?.type === "UNDERSCORE")
1779
+ return null;
1780
+ const attributesEnd = findAttributesEnd(tokens, start + 2);
1781
+ const repaired = tokens[attributesEnd]?.type !== "BLOCK_CLOSE";
1782
+ const last = tokens[attributesEnd - 1];
1783
+ if (repaired && (last?.type !== "QUOTED_STRING" || !last.value.includes("]]")))
1784
+ return null;
1785
+ let bodyStart = attributesEnd + (repaired ? 0 : 1);
1786
+ if (tokens[bodyStart]?.type === "NEWLINE")
1787
+ bodyStart++;
1788
+ return {
1789
+ attributesEnd,
1790
+ bodyStart,
1791
+ repaired,
1792
+ closingSwallowed: repaired && last.value.includes("[[/code]]")
1793
+ };
1794
+ }
1795
+
1796
+ // packages/parser/src/parser/rules/block/code/boundary.ts
1797
+ var caches2 = new WeakMap;
1798
+ function codeCloseEnd(tokens, start) {
1799
+ if (tokens[start]?.type !== "BLOCK_END_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "code")
1800
+ return start;
1801
+ let end = start + 2;
1802
+ while (tokens[end]?.type === "WHITESPACE")
1803
+ end++;
1804
+ return tokens[end]?.type === "BLOCK_CLOSE" ? end + 1 : start;
1805
+ }
1806
+ function nestedCodeBodyStart(tokens, start) {
1807
+ if (tokens[start]?.type !== "BLOCK_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "code")
1808
+ return start;
1809
+ for (let pos = start + 2;pos < tokens.length; pos++) {
1810
+ const type = tokens[pos]?.type;
1811
+ if (type === "NEWLINE" || type === "EOF" || type === "BLOCK_OPEN")
1812
+ return start;
1813
+ if (type === "BLOCK_CLOSE") {
1814
+ const open = findCodeOpen(tokens, start);
1815
+ return open && !open.repaired ? open.bodyStart : start;
1816
+ }
1817
+ }
1818
+ return start;
1819
+ }
1820
+ function findCodeBodyBounds(tokens, start) {
1821
+ let cache = caches2.get(tokens);
1822
+ if (!cache) {
1823
+ cache = new Map;
1824
+ caches2.set(tokens, cache);
1825
+ }
1826
+ const cached = cache.get(start);
1827
+ if (cached)
1828
+ return cached;
1829
+ const stack = [start];
1830
+ let pos = start;
1831
+ while (pos < tokens.length && tokens[pos]?.type !== "EOF") {
1832
+ const rawEnd = rawRegionEnd(tokens, pos, tokens.length);
1833
+ if (rawEnd > pos) {
1834
+ pos = rawEnd;
1835
+ continue;
1836
+ }
1837
+ const end = codeCloseEnd(tokens, pos);
1838
+ if (end > pos) {
1839
+ const result2 = { closeStart: pos, end, foundClose: true };
1840
+ cache.set(stack.pop(), result2);
1841
+ if (stack.length === 0)
1842
+ return result2;
1843
+ pos = end;
1844
+ continue;
1845
+ }
1846
+ const bodyStart = nestedCodeBodyStart(tokens, pos);
1847
+ if (bodyStart > pos) {
1848
+ const child = cache.get(bodyStart);
1849
+ if (child) {
1850
+ pos = child.end;
1851
+ if (!child.foundClose)
1852
+ break;
1853
+ } else {
1854
+ stack.push(bodyStart);
1855
+ pos = bodyStart;
1856
+ }
1857
+ continue;
1858
+ }
1859
+ pos++;
1860
+ }
1861
+ const result = { closeStart: pos, end: pos, foundClose: false };
1862
+ for (const bodyStart of stack)
1863
+ cache.set(bodyStart, result);
1864
+ return result;
1865
+ }
1866
+
1867
+ // packages/parser/src/parser/rules/block/math/boundary.ts
1868
+ var nameRanges = new WeakMap;
1869
+ var bodyCaches = new WeakMap;
1870
+ function findNameEnd(tokens, start) {
1871
+ const cached = nameRanges.get(tokens);
1872
+ if (cached && start >= cached.start && start <= cached.end)
1873
+ return cached.end;
1874
+ let end = start;
1875
+ while (end < tokens.length) {
1876
+ const type = tokens[end]?.type;
1877
+ if (type === "BLOCK_CLOSE" || type === "WHITESPACE" || type === "NEWLINE" || type === "EOF")
1878
+ break;
1879
+ end++;
1880
+ }
1881
+ nameRanges.set(tokens, { start, end });
1882
+ return end;
1883
+ }
1884
+ function findMathOpen(tokens, start) {
1885
+ const name = tokens[start + 1];
1886
+ if (tokens[start]?.type !== "BLOCK_OPEN" || name?.type !== "IDENTIFIER" && name?.type !== "TEXT" || name.value.toLowerCase() !== "math" || tokens[start + 2]?.type === "UNDERSCORE")
1887
+ return null;
1888
+ let pos = start + 2;
1889
+ while (tokens[pos]?.type === "WHITESPACE")
1890
+ pos++;
1891
+ const nameStart = pos;
1892
+ if (tokens[pos]?.type === "IDENTIFIER" || tokens[pos]?.type === "TEXT") {
1893
+ pos = findNameEnd(tokens, pos);
1894
+ }
1895
+ const nameEnd = pos;
1896
+ while (tokens[pos]?.type === "WHITESPACE")
1897
+ pos++;
1898
+ if (tokens[pos]?.type !== "BLOCK_CLOSE")
1899
+ return null;
1900
+ pos++;
1901
+ if (tokens[pos]?.type === "NEWLINE")
1902
+ pos++;
1903
+ return { nameStart, nameEnd, bodyStart: pos };
1904
+ }
1905
+ function mathCloseEnd(tokens, start) {
1906
+ if (tokens[start]?.type !== "BLOCK_END_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "math" || tokens[start + 2]?.type !== "BLOCK_CLOSE")
1907
+ return start;
1908
+ const end = start + 3;
1909
+ return tokens[end]?.type === "NEWLINE" ? end + 1 : end;
1910
+ }
1911
+ function findMathBodyBounds(tokens, start) {
1912
+ let cache = bodyCaches.get(tokens);
1913
+ if (!cache) {
1914
+ cache = new Map;
1915
+ bodyCaches.set(tokens, cache);
1916
+ }
1917
+ const visited = [];
1918
+ let result = {
1919
+ closeStart: tokens.length,
1920
+ end: tokens.length,
1921
+ foundClose: false,
1922
+ hasContent: false
1923
+ };
1924
+ for (let pos = start;pos < tokens.length; pos++) {
1925
+ const cached = cache.get(pos);
1926
+ if (cached) {
1927
+ result = cached;
1928
+ break;
1929
+ }
1930
+ const end = mathCloseEnd(tokens, pos);
1931
+ if (end > pos) {
1932
+ result = { closeStart: pos, end, foundClose: true, hasContent: false };
1933
+ cache.set(pos, result);
1934
+ break;
1935
+ }
1936
+ visited.push(pos);
1937
+ }
1938
+ for (let i = visited.length - 1;i >= 0; i--) {
1939
+ const pos = visited[i];
1940
+ const token5 = tokens[pos];
1941
+ if (!result.hasContent && (token5.type === "BACKSLASH_BREAK" || token5.value.trim() !== "")) {
1942
+ result = { ...result, hasContent: true };
1943
+ }
1944
+ cache.set(pos, result);
1945
+ }
1946
+ return result;
1947
+ }
1948
+
1949
+ // packages/parser/src/parser/rules/opaque-probe.ts
1950
+ function createOpaqueProbe(ctx) {
1951
+ return {
1952
+ ...ctx,
1953
+ diagnostics: [],
1954
+ footnotes: [],
1955
+ tocEntries: [],
1956
+ codeBlocks: [],
1957
+ htmlBlocks: [],
1958
+ bibcites: [],
1959
+ scope: {
1960
+ ...ctx.scope,
1961
+ inlineEnd: undefined,
1962
+ tableFormatting: undefined,
1963
+ blockCloseCondition: () => true
1964
+ }
1965
+ };
1966
+ }
1967
+ function opaqueRuleEnd(probe, pos, rules, requireClosed = false) {
1968
+ const token5 = probe.tokens[pos];
1969
+ if (!token5)
1970
+ return pos;
1971
+ probe.pos = pos;
1972
+ for (const rule of rules) {
1973
+ if (!rule.startTokens.includes(token5.type))
1974
+ continue;
1975
+ if (rule.name === "code") {
1976
+ const open = findCodeOpen(probe.tokens, pos);
1977
+ if (!open)
1978
+ continue;
1979
+ if (open.closingSwallowed) {
1980
+ if (!requireClosed)
1981
+ return open.bodyStart;
1982
+ continue;
1983
+ }
1984
+ const bounds = findCodeBodyBounds(probe.tokens, open.bodyStart);
1985
+ if (!requireClosed || bounds.foundClose)
1986
+ return bounds.end;
1987
+ continue;
1988
+ }
1989
+ if (rule.name === "math") {
1990
+ const open = findMathOpen(probe.tokens, pos);
1991
+ if (!open)
1992
+ continue;
1993
+ const bounds = findMathBodyBounds(probe.tokens, open.bodyStart);
1994
+ if (bounds.hasContent && (!requireClosed || bounds.foundClose))
1995
+ return bounds.end;
1996
+ continue;
1997
+ }
1998
+ const result = rule.parse(probe);
1999
+ if (result.success)
2000
+ return pos + result.consumed;
2001
+ }
2002
+ return pos;
2003
+ }
2004
+
2005
+ // packages/parser/src/parser/rules/tokens.ts
2006
+ function currentToken(ctx) {
2007
+ return ctx.tokens[ctx.pos] ?? eofToken();
2008
+ }
2009
+ function eofToken() {
2010
+ return {
2011
+ type: "EOF",
2012
+ value: "",
2013
+ position: { start: { line: 0, column: 0, offset: 0 }, end: { line: 0, column: 0, offset: 0 } },
2014
+ lineStart: false
2015
+ };
2016
+ }
2017
+ function hasClosingMarkerBeforeNewline(ctx, markerType, markerValue) {
2018
+ let pos = ctx.pos;
2019
+ while (pos < (ctx.scope.inlineEnd ?? ctx.tokens.length)) {
2020
+ const token5 = ctx.tokens[pos];
2021
+ if (!token5 || token5.type === "NEWLINE" || token5.type === "EOF") {
2022
+ return false;
2023
+ }
2024
+ if (token5.type === markerType) {
2025
+ if (markerValue === undefined || token5.value === markerValue) {
2026
+ return true;
2027
+ }
2028
+ }
2029
+ pos++;
2030
+ }
2031
+ return false;
2032
+ }
2033
+ // packages/parser/src/parser/rules/block/code/attributes.ts
2034
+ function repairSwallowedCodeClose(ctx, pos, attrs) {
2035
+ const prevToken = ctx.tokens[pos - 1];
2036
+ if (prevToken?.type !== "QUOTED_STRING" || !prevToken.value.includes("]]")) {
2037
+ return null;
2038
+ }
2039
+ const rawValue = prevToken.value;
2040
+ const bracketIdx = rawValue.indexOf("]]");
2041
+ const truncatedValue = rawValue.startsWith('"') ? rawValue.slice(1, bracketIdx) : rawValue.slice(0, bracketIdx);
2042
+ for (const key of Object.keys(attrs)) {
2043
+ const stored = attrs[key];
2044
+ if (stored === rawValue || stored === rawValue.slice(1, -1) || stored === rawValue.slice(1)) {
2045
+ attrs[key] = truncatedValue;
2046
+ break;
2047
+ }
2048
+ }
2049
+ return {
2050
+ closingSwallowed: rawValue.includes("[[/code]]")
2051
+ };
2052
+ }
2053
+
2054
+ // packages/parser/src/parser/rules/block/paragraph/span-markers.ts
2055
+ function processCloseSpanMarkers(elements) {
2056
+ let result = null;
2057
+ for (let i = 0;i < elements.length; i++) {
2058
+ const elem = elements[i];
2059
+ if (!elem)
2060
+ continue;
2061
+ if (isCloseSpanMarker(elem)) {
2062
+ if (result === null) {
2063
+ result = elements.slice(0, i);
2064
+ }
2065
+ if (result.length > 0) {
2066
+ const spanContent = [...result];
2067
+ result.length = 0;
2068
+ result.push({
2069
+ element: "container",
2070
+ data: {
2071
+ type: "span",
2072
+ attributes: {},
2073
+ elements: spanContent
2074
+ }
2075
+ });
2076
+ }
2077
+ } else {
2078
+ result?.push(elem);
2079
+ }
2080
+ }
2081
+ return result ?? elements;
2082
+ }
2083
+ function isCloseSpanMarker(elem) {
2084
+ return elem.element === "container" && elem.data && typeof elem.data === "object" && "type" in elem.data && elem.data.type === "span" && "attributes" in elem.data && typeof elem.data.attributes === "object" && elem.data.attributes !== null && "_closeSpan" in elem.data.attributes;
2085
+ }
2086
+
2087
+ // packages/parser/src/parser/rules/inline/parsing/preserved-line-break.ts
2088
+ function createPreservedTrailingLineBreak() {
2089
+ const lineBreak = { element: "line-break" };
2090
+ lineBreak._preservedTrailingBreak = true;
2091
+ return lineBreak;
2092
+ }
2093
+ function createPreservedLeadingLineBreak() {
2094
+ const lineBreak = { element: "line-break", _preservedLeadingBreak: true };
2095
+ return lineBreak;
2096
+ }
2097
+ function isPreservedLeadingLineBreak(element) {
2098
+ return element?.element === "line-break" && element._preservedLeadingBreak === true;
2099
+ }
2100
+
2101
+ // packages/parser/src/parser/rules/block/paragraph/normalize.ts
2102
+ function normalizeParagraphElements(source) {
2103
+ let elements = processCloseSpanMarkers(source);
2104
+ removeTrailingLineBreaks(elements);
2105
+ removeTrailingWhitespaceText(elements);
2106
+ elements = removeLeadingLineBreaks(elements);
2107
+ return elements;
2108
+ }
2109
+ function removeTrailingLineBreaks(elements) {
2110
+ while (elements.length > 0 && elements[elements.length - 1]?.element === "line-break") {
2111
+ const lastEl = elements[elements.length - 1];
2112
+ if (lastEl._preservedTrailingBreak) {
2113
+ delete lastEl._preservedTrailingBreak;
2114
+ break;
2115
+ }
2116
+ elements.pop();
2117
+ }
2118
+ }
2119
+ function removeTrailingWhitespaceText(elements) {
2120
+ while (elements.length > 0) {
2121
+ const last = elements[elements.length - 1];
2122
+ if (last?.element === "text" && "data" in last && typeof last.data === "string" && last.data.trim() === "") {
2123
+ elements.pop();
2124
+ } else {
2125
+ break;
2126
+ }
2127
+ }
2128
+ }
2129
+ function removeLeadingLineBreaks(elements) {
2130
+ let first = 0;
2131
+ while (first < elements.length && elements[first]?.element === "line-break" && !isPreservedLeadingLineBreak(elements[first])) {
2132
+ first++;
2133
+ }
2134
+ return first > 0 ? elements.slice(first) : elements;
2135
+ }
2136
+
2137
+ // packages/parser/src/parser/rules/block/code/content.ts
2138
+ function collectCodeContent(ctx, startPos, closingSwallowed) {
2139
+ if (closingSwallowed)
2140
+ return { contents: "", consumed: 0, foundClose: true };
2141
+ const bounds = findCodeBodyBounds(ctx.tokens, startPos);
2142
+ return {
2143
+ contents: ctx.tokens.slice(startPos, bounds.closeStart).map((token5) => token5.value).join(""),
2144
+ consumed: bounds.end - startPos,
2145
+ foundClose: bounds.foundClose
2146
+ };
2147
+ }
2148
+
2149
+ // packages/parser/src/parser/rules/block/code/index.ts
2150
+ var codeBlockRule = {
2151
+ name: "code",
2152
+ startTokens: ["BLOCK_OPEN"],
2153
+ requiresLineStart: false,
2154
+ parse(ctx) {
2155
+ const openToken = currentToken(ctx);
2156
+ if (openToken.type !== "BLOCK_OPEN") {
2157
+ return { success: false };
2158
+ }
2159
+ const open = findCodeOpen(ctx.tokens, ctx.pos);
2160
+ if (!open)
2161
+ return { success: false };
2162
+ const attrResult = parseAttributesRaw(ctx, ctx.pos + 2);
2163
+ if (open.repaired)
2164
+ repairSwallowedCodeClose(ctx, open.attributesEnd, attrResult.attrs);
2165
+ let pos = open.bodyStart;
2166
+ let consumed = pos - ctx.pos;
2167
+ const contentResult = collectCodeContent(ctx, pos, open.closingSwallowed);
2168
+ let codeContent = contentResult.contents.replace(/\n$/, "");
2169
+ consumed += contentResult.consumed;
2170
+ pos += contentResult.consumed;
2171
+ if (!contentResult.foundClose) {
2172
+ ctx.diagnostics.push({
2173
+ severity: "warning",
2174
+ code: "unclosed-block",
2175
+ message: "Missing closing tag [[/code]] for [[code]]",
2176
+ position: openToken.position
2177
+ });
2178
+ }
2179
+ const codeBlockData = {
2180
+ contents: codeContent,
2181
+ language: attrResult.attrs.type ?? null,
2182
+ name: attrResult.attrs.name ?? null
2183
+ };
2184
+ ctx.codeBlocks.push(codeBlockData);
2185
+ const elements = [{ element: "code", data: codeBlockData }];
2186
+ if (ctx.tokens[pos]?.type === "NEWLINE" && !getParagraphNewlineBoundary(ctx, pos, false).shouldBreak && !ctx.scope.blockCloseCondition?.({ ...ctx, pos: pos + 1 })) {
2187
+ const after = parseInlineUntil({ ...ctx, pos: pos + 1 }, "PARAGRAPH_BREAK");
2188
+ elements.push({ element: "line-break" }, ...normalizeParagraphElements(after.elements));
2189
+ consumed += 1 + after.consumed;
2190
+ }
2191
+ return { success: true, elements, consumed };
2192
+ }
2193
+ };
2194
+
2195
+ // packages/parser/src/parser/rules/block/math/index.ts
2196
+ var mathBlockRule = {
2197
+ name: "math",
2198
+ startTokens: ["BLOCK_OPEN"],
2199
+ requiresLineStart: false,
2200
+ parse(ctx) {
2201
+ const openToken = currentToken(ctx);
2202
+ const open = findMathOpen(ctx.tokens, ctx.pos);
2203
+ if (!open)
2204
+ return { success: false };
2205
+ const bounds = findMathBodyBounds(ctx.tokens, open.bodyStart);
2206
+ if (!bounds.foundClose) {
2207
+ ctx.diagnostics.push({
2208
+ severity: "warning",
2209
+ code: "unclosed-block",
2210
+ message: "Missing closing tag [[/math]] for [[math]]",
2211
+ position: openToken.position
2212
+ });
2213
+ }
2214
+ if (!bounds.hasContent) {
2215
+ return { success: false };
2216
+ }
2217
+ const latexSource = ctx.tokens.slice(open.bodyStart, bounds.closeStart).map((token5) => token5.type === "BACKSLASH_BREAK" ? "\\\n" : token5.value).join("").trim();
2218
+ const name = ctx.tokens.slice(open.nameStart, open.nameEnd).map((token5) => token5.value).join("");
2219
+ return {
2220
+ success: true,
2221
+ elements: [
2222
+ {
2223
+ element: "math",
2224
+ data: {
2225
+ name: name || null,
2226
+ "latex-source": latexSource
2227
+ }
2228
+ }
2229
+ ],
2230
+ consumed: bounds.end - ctx.pos
2231
+ };
2232
+ }
2233
+ };
2234
+
2235
+ // packages/parser/src/parser/rules/inline/link-triple/fallback.ts
2236
+ function literalOpenLink(value) {
2237
+ return {
2238
+ success: true,
2239
+ elements: [{ element: "text", data: value }],
2240
+ consumed: 1
2241
+ };
2242
+ }
2243
+
2244
+ // packages/parser/src/parser/rules/inline/link-triple/label.ts
2245
+ function buildTripleLinkLabel(args) {
2246
+ if (args.isPage && args.foundPipe && !args.labelText.trim())
2247
+ return "page";
2248
+ return {
2249
+ text: getTripleLinkDisplayText({
2250
+ ...args,
2251
+ originalTarget: args.isPage && !args.foundPipe ? args.originalTarget.split("#")[0] : args.originalTarget
2252
+ })
2253
+ };
2254
+ }
2255
+ function getTripleLinkDisplayText(args) {
2256
+ const trimmedLabel = args.labelText.trim();
2257
+ if (args.foundPipe) {
2258
+ return trimmedLabel || args.finalTarget;
2259
+ }
2260
+ const colonIdx = args.originalTarget.indexOf(":");
2261
+ if (colonIdx !== -1 && !args.originalTarget.startsWith("http") && !args.originalTarget.startsWith("*")) {
2262
+ return args.originalTarget.slice(colonIdx + 1).trim();
2263
+ }
2264
+ return args.originalTarget;
2265
+ }
2266
+
2267
+ // packages/parser/src/parser/rules/inline/link-triple/syntax.ts
2268
+ function hasClosingLinkMarker(ctx, startPos) {
2269
+ let pos = startPos;
2270
+ while (pos < ctx.tokens.length) {
2271
+ const token5 = ctx.tokens[pos];
2272
+ if (!token5 || token5.type === "EOF") {
2273
+ return false;
2274
+ }
2275
+ if (token5.type === "LINK_CLOSE") {
2276
+ return true;
2277
+ }
2278
+ if (token5.type === "NEWLINE") {
2279
+ const next = ctx.tokens[pos + 1];
2280
+ if (next?.type === "NEWLINE" || next?.type === "LINK_CLOSE") {
2281
+ return false;
2282
+ }
2283
+ }
2284
+ pos++;
2285
+ }
2286
+ return false;
2287
+ }
2288
+ function collectTripleLinkParts(ctx, startPos) {
2289
+ let target = "";
2290
+ let labelText = "";
2291
+ let foundPipe = false;
2292
+ let consumed = 1;
2293
+ let pos = startPos;
2294
+ while (pos < ctx.tokens.length) {
2295
+ const token5 = ctx.tokens[pos];
2296
+ if (!token5 || token5.type === "LINK_CLOSE" || token5.type === "EOF") {
2297
+ break;
2298
+ }
2299
+ if (token5.type === "NEWLINE") {
2300
+ if (foundPipe) {
2301
+ labelText += " ";
2302
+ } else {
2303
+ target += " ";
2304
+ }
2305
+ consumed++;
2306
+ pos++;
2307
+ continue;
2308
+ }
2309
+ if (token5.type === "PIPE" && !foundPipe) {
2310
+ foundPipe = true;
2311
+ } else if (foundPipe) {
2312
+ labelText += token5.value;
2313
+ } else {
2314
+ target += token5.value;
2315
+ }
2316
+ consumed++;
2317
+ pos++;
2318
+ }
2319
+ if (ctx.tokens[pos]?.type === "LINK_CLOSE") {
2320
+ consumed++;
2321
+ }
2322
+ return { target, labelText, foundPipe, consumed };
2323
+ }
2324
+
2325
+ // packages/parser/src/parser/rules/inline/link-triple/interwiki.ts
2326
+ var INTERWIKI_PREFIXES = new Set(["wikipedia", "google", "dictionary", "wikidot"]);
2327
+ function isInterwikiTarget(target) {
2328
+ const colonIdx = target.indexOf(":");
2329
+ if (colonIdx <= 0 || target.includes("/")) {
2330
+ return false;
2331
+ }
2332
+ const prefix = target.slice(0, colonIdx).toLowerCase();
2333
+ return INTERWIKI_PREFIXES.has(prefix);
2334
+ }
2335
+
2336
+ // packages/parser/src/parser/rules/inline/link-triple/target.ts
2337
+ function normalizeTripleLinkTarget(trimmedTarget) {
2338
+ if (trimmedTarget.startsWith("*")) {
2339
+ return { target: trimmedTarget.slice(1), hasStar: true };
2340
+ }
2341
+ return { target: trimmedTarget, hasStar: false };
2342
+ }
2343
+ function isInvalidTripleLinkTarget(trimmedTarget, foundPipe) {
2344
+ return trimmedTarget === "" && foundPipe || /#{2,}/.test(trimmedTarget);
2345
+ }
2346
+ function determineLinkTypeAndLocation(target) {
2347
+ if (target.startsWith("#")) {
2348
+ return { linkType: "anchor", link: target };
2349
+ }
2350
+ if (target.startsWith("http://") || target.startsWith("https://")) {
2351
+ return { linkType: "direct", link: target };
2352
+ }
2353
+ if (isInterwikiTarget(target)) {
2354
+ return { linkType: "interwiki", link: target };
2355
+ }
2356
+ return { linkType: "page", link: { site: null, page: target } };
2357
+ }
2358
+
2359
+ // packages/parser/src/parser/rules/inline/link-triple/index.ts
2360
+ var linkTripleRule = {
2361
+ name: "linkTriple",
2362
+ startTokens: ["LINK_OPEN"],
2363
+ parse(ctx) {
2364
+ const startToken = currentToken(ctx);
2365
+ if (!hasClosingLinkMarker(ctx, ctx.pos + 1)) {
2366
+ return literalOpenLink(startToken.value);
2367
+ }
2368
+ const parts = collectTripleLinkParts(ctx, ctx.pos + 1);
2369
+ const trimmedTarget = parts.target.trim();
2370
+ if (isInvalidTripleLinkTarget(trimmedTarget, parts.foundPipe)) {
2371
+ return literalOpenLink(startToken.value);
2372
+ }
2373
+ const normalized = normalizeTripleLinkTarget(trimmedTarget);
2374
+ const { linkType, link } = determineLinkTypeAndLocation(normalized.target);
2375
+ const label = buildTripleLinkLabel({
2376
+ isPage: linkType === "page",
2377
+ foundPipe: parts.foundPipe,
2378
+ labelText: parts.labelText,
2379
+ finalTarget: normalized.target,
2380
+ originalTarget: trimmedTarget
2381
+ });
2382
+ return {
2383
+ success: true,
2384
+ elements: [
2385
+ {
2386
+ element: "link",
2387
+ data: {
2388
+ type: linkType,
2389
+ link,
2390
+ extra: null,
2391
+ label,
2392
+ target: normalized.hasStar && linkType === "direct" ? "new-tab" : null
2393
+ }
2394
+ }
2395
+ ],
2396
+ consumed: parts.consumed
2397
+ };
2398
+ }
2399
+ };
2400
+
2401
+ // packages/parser/src/parser/rules/block/note/boundary.ts
2402
+ var opaqueRules = [codeBlockRule, mathBlockRule, linkTripleRule];
2403
+ var boundsCache = new WeakMap;
2404
+ var closeCache = new WeakMap;
2405
+ function tagEnd(tokens, pos, name, close = false) {
2406
+ return tokens[pos]?.type === (close ? "BLOCK_END_OPEN" : "BLOCK_OPEN") && tokens[pos + 1]?.value.toLowerCase() === name && tokens[pos + 2]?.type === "BLOCK_CLOSE" ? pos + 3 : pos;
2407
+ }
2408
+ function findClose(probe, start, name) {
2409
+ let byName = closeCache.get(probe.tokens);
2410
+ if (!byName) {
2411
+ byName = new Map;
2412
+ closeCache.set(probe.tokens, byName);
2413
+ }
2414
+ let cache = byName.get(name);
2415
+ if (!cache) {
2416
+ cache = new Map;
2417
+ byName.set(name, cache);
2418
+ }
2419
+ if (cache.has(start))
2420
+ return cache.get(start);
2421
+ const visited = [];
2422
+ let result = -1;
2423
+ for (let pos = start;pos < probe.tokens.length; pos++) {
2424
+ if (cache.has(pos)) {
2425
+ result = cache.get(pos);
2426
+ break;
2427
+ }
2428
+ visited.push(pos);
2429
+ const protectedEnd = protectedInlineRegionEnd(probe.tokens, pos, probe.tokens.length);
2430
+ if (protectedEnd > pos) {
2431
+ pos = protectedEnd - 1;
2432
+ continue;
2433
+ }
2434
+ if (tagEnd(probe.tokens, pos, name, true) > pos) {
2435
+ result = pos;
2436
+ break;
2437
+ }
2438
+ const opaqueEnd = opaqueRuleEnd(probe, pos, opaqueRules, true);
2439
+ if (opaqueEnd > pos) {
2440
+ pos = opaqueEnd - 1;
2441
+ continue;
2442
+ }
2443
+ if (name === "note") {
2444
+ const footnoteStart = tagEnd(probe.tokens, pos, "footnote");
2445
+ if (footnoteStart > pos) {
2446
+ const end = findClose(probe, footnoteStart, "footnote");
2447
+ if (end >= 0)
2448
+ pos = end + 2;
2449
+ }
2450
+ }
2451
+ }
2452
+ for (const pos of visited)
2453
+ cache.set(pos, result);
2454
+ return result;
2455
+ }
2456
+ function findNoteBounds(ctx, start = ctx.pos) {
2457
+ const openEnd = tagEnd(ctx.tokens, start, "note");
2458
+ if (openEnd === start || ctx.tokens[openEnd]?.type !== "NEWLINE")
2459
+ return null;
2460
+ let cache = boundsCache.get(ctx.tokens);
2461
+ if (!cache) {
2462
+ cache = new Map;
2463
+ boundsCache.set(ctx.tokens, cache);
2464
+ }
2465
+ if (cache.has(start))
2466
+ return cache.get(start);
2467
+ const close = findClose(createOpaqueProbe(ctx), openEnd + 1, "note");
2468
+ const result = close < 0 ? null : { bodyStart: openEnd + 1, close, end: close + 3 };
2469
+ cache.set(start, result);
2470
+ return result;
2471
+ }
2472
+
2473
+ // packages/parser/src/parser/rules/inline/parsing/block-boundary.ts
2474
+ function isExcludedBlockToken(ctx, tokenPos) {
2475
+ const excluded = ctx.scope.excludedBlockNames;
2476
+ if (!excluded?.size)
2477
+ return false;
2478
+ const token5 = ctx.tokens[tokenPos];
2479
+ if (token5?.type !== "BLOCK_OPEN" && token5?.type !== "BLOCK_END_OPEN")
2480
+ return false;
1219
2481
  const nameResult = parseBlockName(ctx, tokenPos + 1);
1220
2482
  return nameResult !== null && excluded.has(nameResult.name);
1221
2483
  }
@@ -1230,6 +2492,8 @@ function isUnknownBlockToken(ctx, tokenPos) {
1230
2492
  }
1231
2493
  return true;
1232
2494
  }
2495
+ if (nameResult.name === "note")
2496
+ return findNoteBounds(ctx, tokenPos) === null;
1233
2497
  return !KNOWN_BLOCK_NAMES.has(nameResult.name);
1234
2498
  }
1235
2499
  function isIndentAcceptingBlock(ctx, tokenPos) {
@@ -1253,7 +2517,13 @@ function isParagraphBreakingBlockStart(ctx, newlinePos, lookAhead) {
1253
2517
  if (!nextMeaningfulToken.lineStart && !isIndentedBlockOpener) {
1254
2518
  return false;
1255
2519
  }
1256
- return !parseImageOpen({ ...ctx, pos: nextPos }) && !isOrphanCloseSpan(ctx, nextPos) && !isAnchorName(ctx, nextPos) && !isInvalidBlockOpen(ctx, nextPos) && !isInvalidHeading(ctx, nextPos) && !isExcludedBlockStart(ctx, nextPos) && !isUnknownBlockStart(ctx, nextPos);
2520
+ return !parseImageOpen({ ...ctx, pos: nextPos }) && !isOrphanCloseSpan(ctx, nextPos) && !isAnchorName(ctx, nextPos) && !isInvalidBlockOpen(ctx, nextPos) && !isInvalidHeading(ctx, nextPos) && !isInvalidListMarker(ctx, nextPos) && !isExcludedBlockStart(ctx, nextPos) && !isUnknownBlockStart(ctx, nextPos);
2521
+ }
2522
+ function isInvalidListMarker(ctx, markerPos) {
2523
+ const marker = ctx.tokens[markerPos];
2524
+ if (marker?.type !== "LIST_BULLET" && marker?.type !== "LIST_NUMBER")
2525
+ return false;
2526
+ return ctx.tokens[markerPos + 1]?.type !== "WHITESPACE";
1257
2527
  }
1258
2528
  function isOrphanCloseSpan(ctx, blockEndOpenPos) {
1259
2529
  const token5 = ctx.tokens[blockEndOpenPos];
@@ -1339,13 +2609,6 @@ function skipWhitespaceAfterNewline(ctx, newlinePos) {
1339
2609
  return lookAhead;
1340
2610
  }
1341
2611
 
1342
- // packages/parser/src/parser/rules/inline/parsing/preserved-line-break.ts
1343
- function createPreservedTrailingLineBreak() {
1344
- const lineBreak = { element: "line-break" };
1345
- lineBreak._preservedTrailingBreak = true;
1346
- return lineBreak;
1347
- }
1348
-
1349
2612
  // packages/parser/src/parser/rules/inline/parsing/simple-token.ts
1350
2613
  function parseSimpleInlineToken(token5, nextToken) {
1351
2614
  if (token5.type === "TEXT" && token5.value !== "(") {
@@ -1362,12 +2625,14 @@ function parseInlineUntil(ctx, endType) {
1362
2625
  const nodes = [];
1363
2626
  let consumed = 0;
1364
2627
  let pos = ctx.pos;
2628
+ let consumedEmptyRaw = false;
1365
2629
  const paragraphMode = endType === "PARAGRAPH_BREAK";
1366
- const multiline = paragraphMode || FORMATTING_CLOSE_TOKENS.has(endType);
2630
+ const preserveNewlines = endType === "EOF";
2631
+ const multiline = paragraphMode || preserveNewlines || FORMATTING_CLOSE_TOKENS.has(endType);
1367
2632
  let inlineEnd = ctx.scope.inlineEnd ?? ctx.tokens.length;
1368
2633
  if (!multiline) {
1369
2634
  for (let end = ctx.pos;end < inlineEnd; end++) {
1370
- const protectedEnd = protectedInlineRegionEnd(ctx.tokens, end, inlineEnd);
2635
+ const protectedEnd = Math.max(parseButtonSyntax(ctx, end, inlineEnd)?.end ?? end, parseSocialSyntax(ctx, end, inlineEnd)?.end ?? end, parseDateSyntax(ctx, end, inlineEnd)?.end ?? end, emailRegionEnd(ctx.tokens, end, inlineEnd), protectedInlineRegionEnd(ctx.tokens, end, inlineEnd));
1371
2636
  if (protectedEnd > end) {
1372
2637
  end = protectedEnd - 1;
1373
2638
  continue;
@@ -1389,232 +2654,117 @@ function parseInlineUntil(ctx, endType) {
1389
2654
  const canCollectLongPlainTextRuns = ctx.tokens.length >= MIN_INLINE_TEXT_RUN_DOCUMENT_TOKENS;
1390
2655
  while (pos < inlineEnd) {
1391
2656
  const token5 = ctx.tokens[pos];
1392
- if (!token5 || token5.type === "EOF") {
1393
- break;
1394
- }
1395
- if (ctx.scope.blockCloseCondition) {
1396
- const checkCtx = { ...ctx, pos };
1397
- if (ctx.scope.blockCloseCondition(checkCtx)) {
1398
- break;
1399
- }
1400
- }
1401
- if (!multiline && token5.type === "NEWLINE") {
1402
- break;
1403
- }
1404
- if (multiline && token5.type === "NEWLINE" && !ctx.scope.tableFormatting) {
1405
- const boundary = getParagraphNewlineBoundary(ctx, pos, nodes.length > 0);
1406
- if (boundary.shouldBreak) {
1407
- if (boundary.preservePrecedingLineBreak) {
1408
- nodes.push(createPreservedTrailingLineBreak());
1409
- }
1410
- consumed += boundary.consumed;
1411
- break;
1412
- }
1413
- }
1414
- if (ctx.scope.tableFormatting?.suppressedClosers.has(pos)) {
1415
- pos++;
1416
- consumed++;
1417
- continue;
1418
- }
1419
- if (token5.type === endType) {
1420
- break;
1421
- }
1422
- if (canCollectLongPlainTextRuns) {
1423
- const plainTextRun = collectLongPlainTextRun(ctx, pos, endType);
1424
- if (plainTextRun) {
1425
- nodes.push({ element: "text", data: plainTextRun.value });
1426
- consumed += plainTextRun.consumed;
1427
- pos += plainTextRun.consumed;
1428
- continue;
1429
- }
1430
- }
1431
- const simpleToken = parseSimpleInlineToken(token5, ctx.tokens[pos + 1]);
1432
- if (simpleToken) {
1433
- nodes.push(simpleToken.element);
1434
- consumed += simpleToken.consumed;
1435
- pos += simpleToken.consumed;
1436
- continue;
1437
- }
1438
- inlineCtx.pos = pos;
1439
- let matched = false;
1440
- for (const rule of getCandidateInlineRules(inlineRules, token5.type)) {
1441
- const result = rule.parse(inlineCtx);
1442
- if (result.success) {
1443
- if (rule.name === "comment") {
1444
- let after = pos + result.consumed;
1445
- while (ctx.tokens[after]?.type === "WHITESPACE")
1446
- after++;
1447
- if (ctx.tokens[after]?.type === "NEWLINE" || ctx.tokens[after]?.type === "EOF") {
1448
- while (nodes.at(-1)?.element === "text") {
1449
- const last = nodes.at(-1);
1450
- if (last.element !== "text")
1451
- break;
1452
- last.data = last.data.trimEnd();
1453
- if (last.data)
1454
- break;
1455
- nodes.pop();
1456
- }
1457
- if (nodes.at(-1)?.element === "line-break")
1458
- nodes.pop();
1459
- }
1460
- }
1461
- nodes.push(...result.elements);
1462
- consumed += result.consumed;
1463
- pos += result.consumed;
1464
- matched = true;
1465
- break;
1466
- }
1467
- }
1468
- if (!matched) {
1469
- nodes.push({ element: "text", data: token5.value });
1470
- consumed++;
1471
- pos++;
1472
- }
1473
- }
1474
- return { elements: nodes, consumed };
1475
- }
1476
- var FORMATTING_CLOSE_TOKENS = new Set([
1477
- "BOLD_MARKER",
1478
- "ITALIC_MARKER",
1479
- "UNDERLINE_MARKER",
1480
- "STRIKE_MARKER",
1481
- "SUPER_MARKER",
1482
- "SUB_MARKER",
1483
- "MONO_CLOSE",
1484
- "COLOR_MARKER"
1485
- ]);
1486
- // packages/parser/src/parser/rules/block/parsing/attributes/names.ts
1487
- function consumeAttributeName(ctx, startPos, startConsumed, startName, options) {
1488
- if (startName === "_" && isAttributeWordToken(ctx.tokens[startPos])) {
1489
- startName += ctx.tokens[startPos]?.value ?? "";
1490
- startPos++;
1491
- startConsumed++;
1492
- }
1493
- return options.strikeHyphens ? consumeRawNameSuffix(ctx, startPos, startConsumed, startName, options.hyphenatedNames) : consumeSafeNameSuffix(ctx, startPos, startConsumed, startName);
1494
- }
1495
- function isAttributeNameToken(token5) {
1496
- return isAttributeWordToken(token5) || token5?.type === "UNDERSCORE";
1497
- }
1498
- function consumeSafeNameSuffix(ctx, startPos, startConsumed, startName) {
1499
- let name = startName;
1500
- let pos = startPos;
1501
- let consumed = startConsumed;
1502
- while (isAttributeNameSeparator(ctx.tokens[pos]) && isAttributeWordToken(ctx.tokens[pos + 1])) {
1503
- name += ctx.tokens[pos]?.value ?? "";
1504
- pos++;
1505
- consumed++;
1506
- name += ctx.tokens[pos]?.value ?? "";
1507
- pos++;
1508
- consumed++;
1509
- }
1510
- return { name, pos, consumed };
1511
- }
1512
- function consumeRawNameSuffix(ctx, startPos, startConsumed, startName, hyphenatedNames) {
1513
- let name = startName;
1514
- let pos = startPos;
1515
- let consumed = startConsumed;
1516
- while (isAttributeNameSeparator(ctx.tokens[pos])) {
1517
- while (isAttributeNameSeparator(ctx.tokens[pos])) {
1518
- if (hyphenatedNames || ctx.tokens[pos]?.type === "UNDERSCORE") {
1519
- name += ctx.tokens[pos]?.value ?? "-";
1520
- }
1521
- pos++;
1522
- consumed++;
1523
- }
1524
- if (!isAttributeWordToken(ctx.tokens[pos])) {
1525
- break;
1526
- }
1527
- if (hyphenatedNames || name.endsWith("_")) {
1528
- name += ctx.tokens[pos]?.value ?? "";
1529
- }
1530
- pos++;
1531
- consumed++;
1532
- }
1533
- return { name, pos, consumed };
1534
- }
1535
- function isAttributeWordToken(token5) {
1536
- return token5?.type === "TEXT" || token5?.type === "IDENTIFIER";
1537
- }
1538
- function isAttributeNameSeparator(token5) {
1539
- return token5?.type === "TEXT" && token5.value === "-" || token5?.type === "STRIKE_MARKER" || token5?.type === "UNDERSCORE";
1540
- }
1541
-
1542
- // packages/parser/src/parser/rules/block/parsing/attributes/values.ts
1543
- function consumeAttributeValue(token5) {
1544
- if (!token5) {
1545
- return null;
1546
- }
1547
- if (token5.type === "QUOTED_STRING") {
1548
- return { value: stripQuotes(token5.value) };
1549
- }
1550
- if (isAttributeNameToken(token5)) {
1551
- return { value: token5.value };
1552
- }
1553
- return null;
1554
- }
1555
- function stripQuotes(value) {
1556
- return value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
1557
- }
1558
-
1559
- // packages/parser/src/parser/rules/block/parsing/attributes/scanner.ts
1560
- function scanAttributes(ctx, startPos, options) {
1561
- const attrs = {};
1562
- let pos = startPos;
1563
- let consumed = 0;
1564
- while (pos < ctx.tokens.length) {
1565
- const token5 = ctx.tokens[pos];
1566
- if (!token5 || isAttributeTerminator(token5)) {
2657
+ if (!token5 || token5.type === "EOF") {
1567
2658
  break;
1568
2659
  }
1569
- if (token5.type === "WHITESPACE") {
2660
+ if (ctx.scope.blockCloseCondition) {
2661
+ const checkCtx = { ...ctx, pos };
2662
+ if (ctx.scope.blockCloseCondition(checkCtx)) {
2663
+ break;
2664
+ }
2665
+ }
2666
+ if (!multiline && token5.type === "NEWLINE") {
2667
+ break;
2668
+ }
2669
+ if (preserveNewlines && token5.type === "NEWLINE") {
2670
+ nodes.push({ element: "text", data: token5.value });
1570
2671
  pos++;
1571
2672
  consumed++;
1572
2673
  continue;
1573
2674
  }
1574
- if (!isAttributeNameToken(token5)) {
2675
+ if (multiline && token5.type === "NEWLINE" && !ctx.scope.tableFormatting) {
2676
+ const boundary = getParagraphNewlineBoundary(ctx, pos, nodes.length > 0);
2677
+ if (boundary.shouldBreak) {
2678
+ if (boundary.preservePrecedingLineBreak) {
2679
+ nodes.push(createPreservedTrailingLineBreak());
2680
+ }
2681
+ consumed += boundary.consumed;
2682
+ break;
2683
+ }
2684
+ }
2685
+ if (ctx.scope.tableFormatting?.suppressedClosers.has(pos)) {
1575
2686
  pos++;
1576
2687
  consumed++;
1577
2688
  continue;
1578
2689
  }
1579
- let name = token5.value;
1580
- pos++;
1581
- consumed++;
1582
- const nameResult = consumeAttributeName(ctx, pos, consumed, name, options);
1583
- pos = nameResult.pos;
1584
- consumed = nameResult.consumed;
1585
- name = nameResult.name.toLowerCase();
1586
- if (ctx.tokens[pos]?.type !== "EQUALS") {
1587
- attrs[name] = "true";
1588
- continue;
2690
+ const hasEmail = emailRegionEnd(ctx.tokens, pos, inlineEnd) > pos;
2691
+ if (token5.type === endType && !hasEmail) {
2692
+ break;
1589
2693
  }
1590
- pos++;
1591
- consumed++;
1592
- const valueResult = consumeAttributeValue(ctx.tokens[pos]);
1593
- if (!valueResult) {
2694
+ if (canCollectLongPlainTextRuns && !hasEmail) {
2695
+ const plainTextRun = collectLongPlainTextRun(ctx, pos, endType);
2696
+ if (plainTextRun) {
2697
+ nodes.push({ element: "text", data: plainTextRun.value });
2698
+ consumed += plainTextRun.consumed;
2699
+ pos += plainTextRun.consumed;
2700
+ continue;
2701
+ }
2702
+ }
2703
+ const simpleToken = parseSimpleInlineToken(token5, ctx.tokens[pos + 1]);
2704
+ if (simpleToken && !hasEmail) {
2705
+ nodes.push(simpleToken.element);
2706
+ consumed += simpleToken.consumed;
2707
+ pos += simpleToken.consumed;
1594
2708
  continue;
1595
2709
  }
1596
- attrs[name] = valueResult.value;
1597
- pos++;
1598
- consumed++;
2710
+ inlineCtx.pos = pos;
2711
+ let matched = false;
2712
+ for (const rule of getCandidateInlineRules(inlineRules, token5.type)) {
2713
+ const result = rule.parse(inlineCtx);
2714
+ if (result.success) {
2715
+ stripAutomaticLineBreak(nodes, result.stripLeadingLineBreak);
2716
+ if (rule.name === "raw" && result.elements.length === 0 && nodes.length === 0) {
2717
+ consumedEmptyRaw = true;
2718
+ }
2719
+ if (rule.name === "comment") {
2720
+ let after = pos + result.consumed;
2721
+ while (ctx.tokens[after]?.type === "WHITESPACE")
2722
+ after++;
2723
+ if (ctx.tokens[after]?.type === "NEWLINE" || ctx.tokens[after]?.type === "EOF") {
2724
+ while (nodes.at(-1)?.element === "text") {
2725
+ const last = nodes.at(-1);
2726
+ if (last.element !== "text")
2727
+ break;
2728
+ last.data = last.data.trimEnd();
2729
+ if (last.data)
2730
+ break;
2731
+ nodes.pop();
2732
+ }
2733
+ if (nodes.at(-1)?.element === "line-break")
2734
+ nodes.pop();
2735
+ }
2736
+ }
2737
+ for (const element of result.elements) {
2738
+ nodes.push(paragraphMode && consumedEmptyRaw && nodes.length === 0 && token5.type === "NEWLINE" && element.element === "line-break" ? createPreservedLeadingLineBreak() : element);
2739
+ }
2740
+ consumed += result.consumed;
2741
+ pos += result.consumed;
2742
+ matched = true;
2743
+ break;
2744
+ }
2745
+ }
2746
+ if (!matched) {
2747
+ nodes.push({ element: "text", data: token5.value });
2748
+ consumed++;
2749
+ pos++;
2750
+ }
1599
2751
  }
1600
- return { attrs, consumed };
1601
- }
1602
- function isAttributeTerminator(token5) {
1603
- return token5.type === "BLOCK_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF";
1604
- }
1605
-
1606
- // packages/parser/src/parser/rules/block/parsing/attributes/index.ts
1607
- function parseAttributes(ctx, startPos) {
1608
- const result = scanAttributes(ctx, startPos, { hyphenatedNames: true, strikeHyphens: false });
1609
- return { attrs: filterUnsafeAttributes(result.attrs), consumed: result.consumed };
1610
- }
1611
- function parseAttributesRaw(ctx, startPos, hyphenatedNames = true) {
1612
- return scanAttributes(ctx, startPos, { hyphenatedNames, strikeHyphens: true });
2752
+ return { elements: nodes, consumed };
1613
2753
  }
2754
+ var FORMATTING_CLOSE_TOKENS = new Set([
2755
+ "BOLD_MARKER",
2756
+ "ITALIC_MARKER",
2757
+ "UNDERLINE_MARKER",
2758
+ "STRIKE_MARKER",
2759
+ "SUPER_MARKER",
2760
+ "SUB_MARKER",
2761
+ "MONO_CLOSE",
2762
+ "COLOR_MARKER"
2763
+ ]);
1614
2764
  // packages/parser/src/parser/parse/block.ts
1615
- function parseNextBlock(ctx, skipWhitespace2, isAtEnd2) {
2765
+ function parseNextBlock(ctx, skipWhitespace2, isAtEnd3) {
1616
2766
  skipWhitespace2();
1617
- if (isAtEnd2()) {
2767
+ if (isAtEnd3()) {
1618
2768
  return [];
1619
2769
  }
1620
2770
  const token5 = ctx.tokens[ctx.pos];
@@ -1633,7 +2783,7 @@ function parseNextBlock(ctx, skipWhitespace2, isAtEnd2) {
1633
2783
  }
1634
2784
  }
1635
2785
  const result = ctx.blockFallbackRule.parse(ctx);
1636
- if (result.success && result.elements.length > 0) {
2786
+ if (result.success) {
1637
2787
  ctx.pos += result.consumed;
1638
2788
  return result.elements;
1639
2789
  }
@@ -1643,35 +2793,6 @@ function parseNextBlock(ctx, skipWhitespace2, isAtEnd2) {
1643
2793
 
1644
2794
  // packages/parser/src/parser/parse/context.ts
1645
2795
  import { DEFAULT_SETTINGS } from "@wdprlib/ast";
1646
-
1647
- // packages/parser/src/parser/rules/tokens.ts
1648
- function currentToken(ctx) {
1649
- return ctx.tokens[ctx.pos] ?? eofToken();
1650
- }
1651
- function eofToken() {
1652
- return {
1653
- type: "EOF",
1654
- value: "",
1655
- position: { start: { line: 0, column: 0, offset: 0 }, end: { line: 0, column: 0, offset: 0 } },
1656
- lineStart: false
1657
- };
1658
- }
1659
- function hasClosingMarkerBeforeNewline(ctx, markerType, markerValue) {
1660
- let pos = ctx.pos;
1661
- while (pos < (ctx.scope.inlineEnd ?? ctx.tokens.length)) {
1662
- const token5 = ctx.tokens[pos];
1663
- if (!token5 || token5.type === "NEWLINE" || token5.type === "EOF") {
1664
- return false;
1665
- }
1666
- if (token5.type === markerType) {
1667
- if (markerValue === undefined || token5.value === markerValue) {
1668
- return true;
1669
- }
1670
- }
1671
- pos++;
1672
- }
1673
- return false;
1674
- }
1675
2796
  // packages/parser/src/parser/rules/block/heading/open.ts
1676
2797
  function parseHeadingOpen(ctx) {
1677
2798
  const marker = ctx.tokens[ctx.pos];
@@ -1917,10 +3038,11 @@ function parseNativeListLine(ctx, startPos) {
1917
3038
  const ltype = markerToken.type === "LIST_BULLET" ? "bullet" : "numbered";
1918
3039
  pos++;
1919
3040
  consumed++;
1920
- if (ctx.tokens[pos]?.type === "WHITESPACE") {
1921
- pos++;
1922
- consumed++;
3041
+ if (ctx.tokens[pos]?.type !== "WHITESPACE") {
3042
+ return { kind: "stop" };
1923
3043
  }
3044
+ pos++;
3045
+ consumed++;
1924
3046
  const inlineCtx = { ...ctx, pos };
1925
3047
  const inlineResult = parseInlineUntil(inlineCtx, "NEWLINE");
1926
3048
  consumed += inlineResult.consumed;
@@ -2030,9 +3152,9 @@ function appendBareParagraphElements(state, elements) {
2030
3152
  function appendBareParagraphText(state, text) {
2031
3153
  state.current.push({ element: "text", data: text });
2032
3154
  }
2033
- function appendBareParagraphLineBreakIfNeeded(state) {
3155
+ function appendBareParagraphLineBreakIfNeeded(state, token5) {
2034
3156
  if (state.current.length > 0) {
2035
- state.current.push({ element: "line-break" });
3157
+ state.current.push(createAutomaticLineBreak(token5));
2036
3158
  }
2037
3159
  }
2038
3160
  function flushBareParagraph(state) {
@@ -2137,7 +3259,7 @@ function parseBareListContent(ctx, startPos, listType) {
2137
3259
  if (consecutiveNewlines >= 2) {
2138
3260
  flushBareParagraph(paragraphState);
2139
3261
  } else {
2140
- appendBareParagraphLineBreakIfNeeded(paragraphState);
3262
+ appendBareParagraphLineBreakIfNeeded(paragraphState, token5);
2141
3263
  }
2142
3264
  continue;
2143
3265
  }
@@ -2149,6 +3271,7 @@ function parseBareListContent(ctx, startPos, listType) {
2149
3271
  for (const rule of getCandidateInlineRules(ctx.inlineRules, token5.type)) {
2150
3272
  const result = rule.parse(inlineCtx);
2151
3273
  if (result.success) {
3274
+ stripAutomaticLineBreak(paragraphState.current, result.stripLeadingLineBreak);
2152
3275
  appendBareParagraphElements(paragraphState, result.elements);
2153
3276
  consumed += result.consumed;
2154
3277
  pos += result.consumed;
@@ -2187,7 +3310,7 @@ function parseListItemBlockContent(ctx, pos, token5) {
2187
3310
  for (const rule of getCandidateBlockRules(filteredBlockRules, token5)) {
2188
3311
  const result = rule.parse(blockCtx);
2189
3312
  if (result.success) {
2190
- return { matched: true, elements: result.elements, consumed: result.consumed };
3313
+ return { matched: true, ...result };
2191
3314
  }
2192
3315
  }
2193
3316
  return { matched: false, elements: [], consumed: 0 };
@@ -2206,7 +3329,7 @@ function parseListItemInlineContent(ctx, pos, tokenType) {
2206
3329
  for (const rule of getCandidateInlineRules(ctx.inlineRules, tokenType)) {
2207
3330
  const result = rule.parse(inlineCtx);
2208
3331
  if (result.success) {
2209
- return { matched: true, elements: result.elements, consumed: result.consumed };
3332
+ return { matched: true, ...result };
2210
3333
  }
2211
3334
  }
2212
3335
  return { matched: false, elements: [], consumed: 0 };
@@ -2248,7 +3371,9 @@ function collectPostLiTrailingContent(ctx, startPos, listType) {
2248
3371
  }
2249
3372
  const inlineResult = parseListItemInlineContent(ctx, pos, token5.type);
2250
3373
  if (inlineResult.matched) {
2251
- elements.push(...inlineResult.elements);
3374
+ stripAutomaticLineBreak(elements, inlineResult.stripLeadingLineBreak);
3375
+ for (const element of inlineResult.elements)
3376
+ elements.push(element);
2252
3377
  consumed += inlineResult.consumed;
2253
3378
  pos += inlineResult.consumed;
2254
3379
  continue;
@@ -2293,7 +3418,7 @@ function collectLiItemContent(ctx, startPos, listType, parseNestedList) {
2293
3418
  if (token5.type === "NEWLINE") {
2294
3419
  const newlineResult = consumeLiItemNewlines(ctx, pos, elements.length > 0);
2295
3420
  if (newlineResult.addLineBreak) {
2296
- elements.push({ element: "line-break" });
3421
+ elements.push(createAutomaticLineBreak(token5));
2297
3422
  }
2298
3423
  pos += newlineResult.consumed;
2299
3424
  consumed += newlineResult.consumed;
@@ -2301,14 +3426,17 @@ function collectLiItemContent(ctx, startPos, listType, parseNestedList) {
2301
3426
  }
2302
3427
  const blockResult = parseListItemBlockContent(ctx, pos, token5);
2303
3428
  if (blockResult.matched) {
2304
- elements.push(...blockResult.elements);
3429
+ for (const element of blockResult.elements)
3430
+ elements.push(element);
2305
3431
  consumed += blockResult.consumed;
2306
3432
  pos += blockResult.consumed;
2307
3433
  continue;
2308
3434
  }
2309
3435
  const inlineResult = parseListItemInlineContent(ctx, pos, token5.type);
2310
3436
  if (inlineResult.matched) {
2311
- elements.push(...inlineResult.elements);
3437
+ stripAutomaticLineBreak(elements, inlineResult.stripLeadingLineBreak);
3438
+ for (const element of inlineResult.elements)
3439
+ elements.push(element);
2312
3440
  consumed += inlineResult.consumed;
2313
3441
  pos += inlineResult.consumed;
2314
3442
  continue;
@@ -2379,7 +3507,8 @@ function parseLiItem(ctx, startPos, listType, parseNestedList) {
2379
3507
  consumed += closeConsumed;
2380
3508
  pos += closeConsumed;
2381
3509
  const trailingResult = collectPostLiTrailingContent(ctx, pos, listType);
2382
- contentElements.push(...trailingResult.elements);
3510
+ for (const element of trailingResult.elements)
3511
+ contentElements.push(element);
2383
3512
  consumed += trailingResult.consumed;
2384
3513
  }
2385
3514
  return {
@@ -2639,7 +3768,7 @@ function sliceLineTokens(ctx, lines) {
2639
3768
  for (const { start, end } of lines) {
2640
3769
  for (let pos = start;pos < end; pos++) {
2641
3770
  const token5 = ctx.tokens[pos];
2642
- if (token5) {
3771
+ if (token5 && token5.type !== "BACKSLASH_BREAK") {
2643
3772
  tokens.push(token5);
2644
3773
  }
2645
3774
  }
@@ -2809,7 +3938,8 @@ function parseDefinitionItemKey(ctx, startPos) {
2809
3938
  const inlineCtx = { ...ctx, pos };
2810
3939
  const result = parseInlineUntil(inlineCtx, "COLON");
2811
3940
  if (result.elements.length > 0) {
2812
- key.push(...result.elements);
3941
+ for (const element of result.elements)
3942
+ key.push(element);
2813
3943
  for (let i = 0;i < result.consumed; i++) {
2814
3944
  const t = ctx.tokens[pos + i];
2815
3945
  if (t)
@@ -2865,7 +3995,8 @@ function parseDefinitionItemValue(ctx, startPos) {
2865
3995
  const inlineCtx = { ...ctx, pos };
2866
3996
  const result = parseInlineUntil(inlineCtx, "NEWLINE");
2867
3997
  if (result.elements.length > 0) {
2868
- value.push(...result.elements);
3998
+ for (const element of result.elements)
3999
+ value.push(element);
2869
4000
  pos += result.consumed;
2870
4001
  consumed += result.consumed;
2871
4002
  } else {
@@ -2947,78 +4078,93 @@ var definitionListRule = {
2947
4078
  }
2948
4079
  };
2949
4080
 
2950
- // packages/parser/src/parser/rules/block/paragraph/content.ts
2951
- function parseInlineContent(ctx) {
2952
- return parseInlineUntil(ctx, "PARAGRAPH_BREAK");
4081
+ // packages/parser/src/parser/postprocess/divAdjacentParagraph.ts
4082
+ var unparsedDivStarts = new WeakSet;
4083
+ function markUnparsedDivStart(elements) {
4084
+ const first = elements[0];
4085
+ const text = first?.element === "container" && first.data.type === "paragraph" ? first.data.elements[0] : first;
4086
+ if (text?.element === "text")
4087
+ unparsedDivStarts.add(text);
2953
4088
  }
2954
-
2955
- // packages/parser/src/parser/rules/block/paragraph/span-markers.ts
2956
- function processCloseSpanMarkers(elements) {
2957
- let result = null;
4089
+ function isUnparsedDivParagraph(el) {
4090
+ if (!el || el.element !== "container")
4091
+ return false;
4092
+ return el.data.type === "paragraph" && el.data.elements.some((child) => unparsedDivStarts.has(child));
4093
+ }
4094
+ function isDivContainer(el) {
4095
+ if (!el || el.element !== "container")
4096
+ return false;
4097
+ return el.data.type === "div";
4098
+ }
4099
+ function suppressAtLevel(elements) {
4100
+ if (elements.length <= 1)
4101
+ return elements;
4102
+ const unwrap = Array.from({ length: elements.length }, () => false);
2958
4103
  for (let i = 0;i < elements.length; i++) {
2959
- const elem = elements[i];
2960
- if (!elem)
4104
+ if (!isUnparsedDivParagraph(elements[i]))
2961
4105
  continue;
2962
- if (isCloseSpanMarker(elem)) {
2963
- if (result === null) {
2964
- result = elements.slice(0, i);
2965
- }
2966
- if (result.length > 0) {
2967
- const spanContent = [...result];
2968
- result.length = 0;
2969
- result.push({
2970
- element: "container",
2971
- data: {
2972
- type: "span",
2973
- attributes: {},
2974
- elements: spanContent
2975
- }
2976
- });
4106
+ const prevIsDiv = i > 0 && isDivContainer(elements[i - 1]);
4107
+ const nextIsDiv = i < elements.length - 1 && isDivContainer(elements[i + 1]);
4108
+ if (prevIsDiv || nextIsDiv) {
4109
+ unwrap[i] = true;
4110
+ }
4111
+ }
4112
+ const result = [];
4113
+ for (let i = 0;i < elements.length; i++) {
4114
+ const el = elements[i];
4115
+ if (!el)
4116
+ continue;
4117
+ if (unwrap[i] && el.element === "container") {
4118
+ const inner = el.data.elements;
4119
+ if (i > 0 && isDivContainer(elements[i - 1])) {
4120
+ result.push({ element: "line-break" });
2977
4121
  }
4122
+ result.push(...inner);
2978
4123
  } else {
2979
- result?.push(elem);
4124
+ result.push(el);
2980
4125
  }
2981
4126
  }
2982
- return result ?? elements;
4127
+ return result;
2983
4128
  }
2984
- function isCloseSpanMarker(elem) {
2985
- return elem.element === "container" && elem.data && typeof elem.data === "object" && "type" in elem.data && elem.data.type === "span" && "attributes" in elem.data && typeof elem.data.attributes === "object" && elem.data.attributes !== null && "_closeSpan" in elem.data.attributes;
4129
+ function suppressDivAdjacentParagraphs(elements) {
4130
+ return suppressAtLevel(elements);
2986
4131
  }
2987
4132
 
2988
- // packages/parser/src/parser/rules/block/paragraph/normalize.ts
2989
- function normalizeParagraphElements(source) {
2990
- let elements = processCloseSpanMarkers(source);
2991
- removeTrailingLineBreaks2(elements);
2992
- removeTrailingWhitespaceText(elements);
2993
- elements = removeLeadingLineBreaks(elements);
2994
- return elements;
4133
+ // packages/parser/src/parser/rules/block/div/close.ts
4134
+ function isDivClose(ctx) {
4135
+ const token5 = ctx.tokens[ctx.pos];
4136
+ if (token5?.type !== "BLOCK_END_OPEN")
4137
+ return false;
4138
+ const closeNameResult = parseBlockName(ctx, ctx.pos + 1);
4139
+ return closeNameResult?.name === "div";
2995
4140
  }
2996
- function removeTrailingLineBreaks2(elements) {
2997
- while (elements.length > 0 && elements[elements.length - 1]?.element === "line-break") {
2998
- const lastEl = elements[elements.length - 1];
2999
- if (lastEl._preservedTrailingBreak) {
3000
- delete lastEl._preservedTrailingBreak;
3001
- break;
3002
- }
3003
- elements.pop();
4141
+ function consumeDivClose(ctx, startPos) {
4142
+ let pos = startPos;
4143
+ let consumed = 0;
4144
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
4145
+ return { pos, consumed };
3004
4146
  }
3005
- }
3006
- function removeTrailingWhitespaceText(elements) {
3007
- while (elements.length > 0) {
3008
- const last = elements[elements.length - 1];
3009
- if (last?.element === "text" && "data" in last && typeof last.data === "string" && last.data.trim() === "") {
3010
- elements.pop();
3011
- } else {
3012
- break;
3013
- }
4147
+ pos++;
4148
+ consumed++;
4149
+ const closeNameResult = parseBlockName(ctx, pos);
4150
+ if (closeNameResult) {
4151
+ pos += closeNameResult.consumed;
4152
+ consumed += closeNameResult.consumed;
4153
+ }
4154
+ if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
4155
+ pos++;
4156
+ consumed++;
3014
4157
  }
3015
- }
3016
- function removeLeadingLineBreaks(elements) {
3017
- let first = 0;
3018
- while (first < elements.length && elements[first]?.element === "line-break") {
3019
- first++;
4158
+ if (ctx.tokens[pos]?.type === "NEWLINE") {
4159
+ pos++;
4160
+ consumed++;
3020
4161
  }
3021
- return first > 0 ? elements.slice(first) : elements;
4162
+ return { pos, consumed };
4163
+ }
4164
+
4165
+ // packages/parser/src/parser/rules/block/paragraph/content.ts
4166
+ function parseInlineContent(ctx) {
4167
+ return parseInlineUntil(ctx, "PARAGRAPH_BREAK");
3022
4168
  }
3023
4169
 
3024
4170
  // packages/parser/src/parser/rules/block/paragraph/index.ts
@@ -3028,25 +4174,30 @@ var paragraphRule = {
3028
4174
  requiresLineStart: false,
3029
4175
  parse(ctx) {
3030
4176
  const result = parseInlineContent(ctx);
3031
- if (result.elements.length === 0) {
4177
+ if (result.consumed === 0) {
3032
4178
  return { success: false };
3033
4179
  }
3034
4180
  const elements = normalizeParagraphElements(result.elements);
3035
4181
  if (elements.length === 0) {
3036
- return { success: false };
4182
+ return { success: true, elements: [], consumed: result.consumed };
3037
4183
  }
3038
4184
  const nextPos = ctx.pos + result.consumed;
3039
4185
  const nextToken = ctx.tokens[nextPos];
3040
4186
  if (nextToken?.type === "COLON" && nextToken.lineStart) {
4187
+ if (isPreservedLeadingLineBreak(elements[0]))
4188
+ elements[0] = { element: "line-break" };
3041
4189
  return {
3042
4190
  success: true,
3043
4191
  elements: [...elements, { element: "line-break" }],
3044
4192
  consumed: result.consumed
3045
4193
  };
3046
4194
  }
4195
+ const wrapped = wrapParagraphElements(elements);
4196
+ if (isDivClose(ctx))
4197
+ markUnparsedDivStart(wrapped);
3047
4198
  return {
3048
4199
  success: true,
3049
- elements: wrapParagraphElements(elements),
4200
+ elements: wrapped,
3050
4201
  consumed: result.consumed
3051
4202
  };
3052
4203
  }
@@ -3057,7 +4208,7 @@ function wrapParagraphElements(elements) {
3057
4208
  let bare = false;
3058
4209
  const flush = (trimBreaks = false) => {
3059
4210
  const content = trimBreaks ? normalizeParagraphElements(group) : group;
3060
- while (content[0]?.element === "line-break")
4211
+ while (content[0]?.element === "line-break" && !isPreservedLeadingLineBreak(content[0]))
3061
4212
  content.shift();
3062
4213
  while (content.length) {
3063
4214
  const last = content.at(-1);
@@ -3069,6 +4220,8 @@ function wrapParagraphElements(elements) {
3069
4220
  content.shift();
3070
4221
  if (content[0]?.element === "text")
3071
4222
  content[0] = { element: "text", data: content[0].data.trimStart() };
4223
+ if (isPreservedLeadingLineBreak(content[0]))
4224
+ content[0] = { element: "line-break" };
3072
4225
  if (content.length)
3073
4226
  output.push(...bare || content.some((el) => el.element === "image") ? content : [
3074
4227
  {
@@ -3090,37 +4243,36 @@ function wrapParagraphElements(elements) {
3090
4243
  return output;
3091
4244
  }
3092
4245
 
3093
- // packages/parser/src/parser/rules/block/div/close.ts
3094
- function isDivClose(ctx) {
3095
- const token5 = ctx.tokens[ctx.pos];
3096
- if (token5?.type !== "BLOCK_END_OPEN")
3097
- return false;
3098
- const closeNameResult = parseBlockName(ctx, ctx.pos + 1);
3099
- return closeNameResult?.name === "div";
3100
- }
3101
- function consumeDivClose(ctx, startPos) {
3102
- let pos = startPos;
3103
- let consumed = 0;
3104
- if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
3105
- return { pos, consumed };
3106
- }
3107
- pos++;
3108
- consumed++;
3109
- const closeNameResult = parseBlockName(ctx, pos);
3110
- if (closeNameResult) {
3111
- pos += closeNameResult.consumed;
3112
- consumed += closeNameResult.consumed;
3113
- }
3114
- if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
3115
- pos++;
3116
- consumed++;
3117
- }
3118
- if (ctx.tokens[pos]?.type === "NEWLINE") {
3119
- pos++;
3120
- consumed++;
4246
+ // packages/parser/src/parser/rules/block/note/index.ts
4247
+ var excludedBlockNames = new Set(["note"]);
4248
+ var noteRule = {
4249
+ name: "note",
4250
+ startTokens: ["BLOCK_OPEN"],
4251
+ requiresLineStart: false,
4252
+ parse(ctx) {
4253
+ const bounds = findNoteBounds(ctx);
4254
+ if (!bounds)
4255
+ return { success: false };
4256
+ const body = parseBlocksUntil({
4257
+ ...ctx,
4258
+ tokens: ctx.tokens.slice(bounds.bodyStart, bounds.close),
4259
+ pos: 0,
4260
+ scope: {
4261
+ ...ctx.scope,
4262
+ inlineEnd: undefined,
4263
+ tableFormatting: undefined,
4264
+ blockCloseCondition: undefined
4265
+ }
4266
+ }, () => false, { excludedBlockNames });
4267
+ return {
4268
+ success: true,
4269
+ consumed: bounds.end - ctx.pos,
4270
+ elements: [
4271
+ { element: "container", data: { type: "note", attributes: {}, elements: body.elements } }
4272
+ ]
4273
+ };
3121
4274
  }
3122
- return { pos, consumed };
3123
- }
4275
+ };
3124
4276
 
3125
4277
  // packages/parser/src/parser/rules/block/div/nesting.ts
3126
4278
  var divCloseCountCache = new WeakMap;
@@ -3289,6 +4441,7 @@ function consumeFailedDiv(ctx) {
3289
4441
  pos++;
3290
4442
  consumed++;
3291
4443
  }
4444
+ markUnparsedDivStart(elements);
3292
4445
  return {
3293
4446
  success: true,
3294
4447
  elements: [
@@ -3400,126 +4553,6 @@ var divRule = {
3400
4553
  }
3401
4554
  };
3402
4555
 
3403
- // packages/parser/src/parser/rules/block/code/attributes.ts
3404
- function repairSwallowedCodeClose(ctx, pos, attrs) {
3405
- const prevToken = ctx.tokens[pos - 1];
3406
- if (prevToken?.type !== "QUOTED_STRING" || !prevToken.value.includes("]]")) {
3407
- return null;
3408
- }
3409
- const rawValue = prevToken.value;
3410
- const bracketIdx = rawValue.indexOf("]]");
3411
- const truncatedValue = rawValue.startsWith('"') ? rawValue.slice(1, bracketIdx) : rawValue.slice(0, bracketIdx);
3412
- for (const key of Object.keys(attrs)) {
3413
- const stored = attrs[key];
3414
- if (stored === rawValue || stored === rawValue.slice(1, -1) || stored === rawValue.slice(1)) {
3415
- attrs[key] = truncatedValue;
3416
- break;
3417
- }
3418
- }
3419
- return {
3420
- closingSwallowed: rawValue.includes("[[/code]]")
3421
- };
3422
- }
3423
-
3424
- // packages/parser/src/parser/rules/block/code/content.ts
3425
- function collectCodeContent(ctx, startPos, closingSwallowed) {
3426
- const contentParts = [];
3427
- let pos = startPos;
3428
- let consumed = 0;
3429
- let foundClose = closingSwallowed;
3430
- while (!closingSwallowed && pos < ctx.tokens.length) {
3431
- const token5 = ctx.tokens[pos];
3432
- if (!token5 || token5.type === "EOF") {
3433
- break;
3434
- }
3435
- if (token5.type === "BLOCK_END_OPEN") {
3436
- const closeNameResult = parseBlockName(ctx, pos + 1);
3437
- if (closeNameResult?.name === "code") {
3438
- foundClose = true;
3439
- const closeConsumed = consumeCodeClose(ctx, pos, closeNameResult.consumed);
3440
- consumed += closeConsumed;
3441
- break;
3442
- }
3443
- }
3444
- contentParts.push(token5.value);
3445
- pos++;
3446
- consumed++;
3447
- }
3448
- return { contents: contentParts.join(""), consumed, foundClose };
3449
- }
3450
- function consumeCodeClose(ctx, startPos, closeNameConsumed) {
3451
- let pos = startPos + 1 + closeNameConsumed;
3452
- let consumed = 1 + closeNameConsumed;
3453
- if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
3454
- pos++;
3455
- consumed++;
3456
- }
3457
- return consumed;
3458
- }
3459
-
3460
- // packages/parser/src/parser/rules/block/code/index.ts
3461
- var codeBlockRule = {
3462
- name: "code",
3463
- startTokens: ["BLOCK_OPEN"],
3464
- requiresLineStart: false,
3465
- parse(ctx) {
3466
- const openToken = currentToken(ctx);
3467
- if (openToken.type !== "BLOCK_OPEN") {
3468
- return { success: false };
3469
- }
3470
- let pos = ctx.pos + 1;
3471
- let consumed = 1;
3472
- const nameResult = parseBlockName(ctx, pos);
3473
- if (!nameResult || nameResult.name !== "code") {
3474
- return { success: false };
3475
- }
3476
- pos += nameResult.consumed;
3477
- consumed += nameResult.consumed;
3478
- const attrResult = parseAttributesRaw(ctx, pos);
3479
- pos += attrResult.consumed;
3480
- consumed += attrResult.consumed;
3481
- let closingSwallowed = false;
3482
- if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
3483
- const repaired = repairSwallowedCodeClose(ctx, pos, attrResult.attrs);
3484
- if (!repaired)
3485
- return { success: false };
3486
- closingSwallowed = repaired.closingSwallowed;
3487
- } else {
3488
- pos++;
3489
- consumed++;
3490
- }
3491
- if (ctx.tokens[pos]?.type === "NEWLINE") {
3492
- pos++;
3493
- consumed++;
3494
- }
3495
- const contentResult = collectCodeContent(ctx, pos, closingSwallowed);
3496
- let codeContent = contentResult.contents.replace(/\n$/, "");
3497
- consumed += contentResult.consumed;
3498
- pos += contentResult.consumed;
3499
- if (!contentResult.foundClose) {
3500
- ctx.diagnostics.push({
3501
- severity: "warning",
3502
- code: "unclosed-block",
3503
- message: "Missing closing tag [[/code]] for [[code]]",
3504
- position: openToken.position
3505
- });
3506
- }
3507
- const codeBlockData = {
3508
- contents: codeContent,
3509
- language: attrResult.attrs.type ?? null,
3510
- name: attrResult.attrs.name ?? null
3511
- };
3512
- ctx.codeBlocks.push(codeBlockData);
3513
- const elements = [{ element: "code", data: codeBlockData }];
3514
- if (ctx.tokens[pos]?.type === "NEWLINE" && !getParagraphNewlineBoundary(ctx, pos, false).shouldBreak && !ctx.scope.blockCloseCondition?.({ ...ctx, pos: pos + 1 })) {
3515
- const after = parseInlineUntil({ ...ctx, pos: pos + 1 }, "PARAGRAPH_BREAK");
3516
- elements.push({ element: "line-break" }, ...normalizeParagraphElements(after.elements));
3517
- consumed += 1 + after.consumed;
3518
- }
3519
- return { success: true, elements, consumed };
3520
- }
3521
- };
3522
-
3523
4556
  // packages/parser/src/parser/rules/block/collapsible/attributes.ts
3524
4557
  function parseMultilineAttributes(ctx, startPos) {
3525
4558
  const attrs = {};
@@ -3948,7 +4981,9 @@ function parseTableCell(ctx, startPos, cellStart) {
3948
4981
  for (const rule of getCandidateInlineRules(inlineRules, token5.type)) {
3949
4982
  const result = rule.parse(inlineCtx);
3950
4983
  if (result.success) {
3951
- children.push(...result.elements);
4984
+ stripAutomaticLineBreak(children, result.stripLeadingLineBreak);
4985
+ for (const element of result.elements)
4986
+ children.push(element);
3952
4987
  consumed += result.consumed;
3953
4988
  pos += result.consumed;
3954
4989
  matched = true;
@@ -4215,7 +5250,8 @@ function createCellContentAccumulator() {
4215
5250
  addInline(element) {
4216
5251
  currentSegment.push(element);
4217
5252
  },
4218
- addInlineElements(nextElements) {
5253
+ addInlineElements(nextElements, stripLeadingLineBreak) {
5254
+ stripAutomaticLineBreak(currentSegment, stripLeadingLineBreak);
4219
5255
  currentSegment.push(...nextElements);
4220
5256
  },
4221
5257
  addBlockElements(blockElements) {
@@ -4315,7 +5351,7 @@ function consumeCellContentNewline(ctx, startPos, content) {
4315
5351
  return { consumed };
4316
5352
  }
4317
5353
  if (!content.isEmpty()) {
4318
- content.addInline({ element: "line-break" });
5354
+ content.addInline(createAutomaticLineBreak(ctx.tokens[startPos]));
4319
5355
  }
4320
5356
  return { consumed };
4321
5357
  }
@@ -4366,7 +5402,7 @@ function parseCellContent(ctx, closeCondition) {
4366
5402
  for (const rule of getCandidateInlineRules(ctx.inlineRules, token5.type)) {
4367
5403
  const result = rule.parse(inlineCtx);
4368
5404
  if (result.success) {
4369
- content.addInlineElements(result.elements);
5405
+ content.addInlineElements(result.elements, result.stripLeadingLineBreak);
4370
5406
  consumed += result.consumed;
4371
5407
  pos += result.consumed;
4372
5408
  matched = true;
@@ -4668,8 +5704,18 @@ var rateModuleRule = {
4668
5704
  name: "module-rate",
4669
5705
  acceptsNames: ["rate"],
4670
5706
  hasBody: false,
4671
- parse() {
4672
- return { module: "rate" };
5707
+ parse(_ctx, _pos, args) {
5708
+ return { module: "rate", ref: Object.keys(args).length === 0 ? { kind: "main" } : null };
5709
+ }
5710
+ };
5711
+ var customRateModuleRule = {
5712
+ name: "module-custom-rate",
5713
+ acceptsNames: ["customrate"],
5714
+ hasBody: false,
5715
+ parse(_ctx, _pos, args) {
5716
+ const key = args.key;
5717
+ const valid = key !== undefined && key !== "" && Object.keys(args).every((name) => name === "key");
5718
+ return { module: "custom-rate", ref: valid ? { kind: "custom", axisKey: key } : null };
4673
5719
  }
4674
5720
  };
4675
5721
 
@@ -4804,6 +5850,7 @@ var listPagesModuleRule = {
4804
5850
  "created-at": createdAt,
4805
5851
  "updated-at": updatedAt,
4806
5852
  rating,
5853
+ "rating-axis": args["rating-axis"],
4807
5854
  votes,
4808
5855
  name,
4809
5856
  fullname,
@@ -4947,6 +5994,7 @@ var tagCloudModuleRule = {
4947
5994
  // packages/parser/src/parser/rules/block/module/mapping.ts
4948
5995
  var MODULE_RULES = [
4949
5996
  rateModuleRule,
5997
+ customRateModuleRule,
4950
5998
  cssModuleRule,
4951
5999
  backlinksModuleRule,
4952
6000
  categoriesModuleRule,
@@ -5517,22 +6565,6 @@ function getDayOfYear(date) {
5517
6565
  function splitContentSections(content) {
5518
6566
  return content.split(/^={4,}$/m).map((section) => section.trim());
5519
6567
  }
5520
- function getSummary(page) {
5521
- if (page.content) {
5522
- const sections = splitContentSections(page.content);
5523
- if (sections.length > 1) {
5524
- return sections[0]?.trim() ?? "";
5525
- }
5526
- }
5527
- return getFirstParagraph(page.content);
5528
- }
5529
- function getFirstParagraph(content) {
5530
- if (!content)
5531
- return "";
5532
- const stripped = content.replace(/^(\+{1,6}) (.*)/gm, "").replace(/^\[\[toc(\s[^\]]+)?\]\]/gim, "").replace(/^\[\[\/?div(\s[^\]]+)?\]\]/gim, "").replace(/^\[\[\/?module(\s[^\]]+)?\]\]/gim, "").trim();
5533
- const paragraphs = stripped.split(/\n{2,}/);
5534
- return paragraphs[0]?.trim() ?? "";
5535
- }
5536
6568
  // packages/parser/src/parser/rules/block/module/listpages/template/format/tags.ts
5537
6569
  function formatTagsLinked(tags, prefix) {
5538
6570
  if (tags.length === 0)
@@ -5545,6 +6577,16 @@ function formatUserLinked(user) {
5545
6577
  return "Anonymous";
5546
6578
  return `[[*user ${user.name}]]`;
5547
6579
  }
6580
+ // packages/parser/src/parser/rules/block/module/listpages/template/getters/parameterized.ts
6581
+ import { excerptText } from "@wdprlib/ast";
6582
+
6583
+ // packages/parser/src/parser/rules/block/module/listpages/template/literal.ts
6584
+ function literalWikitext(value) {
6585
+ if (value === "")
6586
+ return "";
6587
+ return `@<${Array.from(value, (char) => `&#${char.codePointAt(0)};`).join("")}>@`;
6588
+ }
6589
+
5548
6590
  // packages/parser/src/parser/rules/block/module/listpages/template/getters/parameterized.ts
5549
6591
  function createBraceParamGetter(name, param) {
5550
6592
  switch (name) {
@@ -5573,7 +6615,7 @@ function createParenParamGetter(name, param) {
5573
6615
  if (name !== "preview")
5574
6616
  return null;
5575
6617
  const len = Number(param);
5576
- return (ctx) => (ctx.page.content ?? "").slice(0, len);
6618
+ return (ctx) => literalWikitext(excerptText(ctx.page.readableText ?? "", { maxLength: len }));
5577
6619
  }
5578
6620
  function createFormattedGetter(name, format) {
5579
6621
  switch (name) {
@@ -5600,6 +6642,7 @@ function createTagsLinkedGetter(name, format) {
5600
6642
  }
5601
6643
 
5602
6644
  // packages/parser/src/parser/rules/block/module/listpages/template/getters/simple.ts
6645
+ import { countCharacters, excerptText as excerptText2 } from "@wdprlib/ast";
5603
6646
  var DEFAULT_PREVIEW_LENGTH = 200;
5604
6647
  var SIMPLE_GETTERS = {
5605
6648
  created_at: (ctx) => formatDate(ctx.page.createdAt),
@@ -5629,14 +6672,14 @@ var SIMPLE_GETTERS = {
5629
6672
  parent_title: (ctx) => ctx.page.parentTitle ?? "",
5630
6673
  parent_title_linked: (ctx) => ctx.page.parentFullname ? `[[[${ctx.page.parentFullname} | ${ctx.page.parentTitle}]]]` : "",
5631
6674
  content: (ctx) => ctx.page.content ?? "",
5632
- preview: (ctx) => (ctx.page.content ?? "").slice(0, DEFAULT_PREVIEW_LENGTH),
5633
- summary: (ctx) => getSummary(ctx.page),
5634
- first_paragraph: (ctx) => getFirstParagraph(ctx.page.content),
6675
+ preview: (ctx) => literalWikitext(excerptText2(ctx.page.readableText ?? "", { maxLength: DEFAULT_PREVIEW_LENGTH })),
6676
+ summary: (ctx) => literalWikitext(ctx.page.firstParagraph ?? ""),
6677
+ first_paragraph: (ctx) => literalWikitext(ctx.page.firstParagraph ?? ""),
5635
6678
  tags: (ctx) => ctx.page.tags.join(" "),
5636
6679
  _tags: (ctx) => ctx.page.hiddenTags.join(" "),
5637
6680
  children: (ctx) => String(ctx.page.children),
5638
6681
  comments: (ctx) => String(ctx.page.comments),
5639
- size: (ctx) => String(ctx.page.size),
6682
+ size: (ctx) => ctx.page.readableText !== undefined ? String(countCharacters(ctx.page.readableText)) : ctx.page.size !== undefined ? String(ctx.page.size) : "",
5640
6683
  rating: (ctx) => String(ctx.page.rating),
5641
6684
  rating_votes: (ctx) => String(ctx.page.ratingVotes),
5642
6685
  rating_percent: (ctx) => String(ctx.page.ratingPercent ?? 0),
@@ -5650,11 +6693,136 @@ var SIMPLE_GETTERS = {
5650
6693
  site_domain: (ctx) => ctx.site.domain
5651
6694
  };
5652
6695
 
6696
+ // packages/parser/src/parser/rules/block/module/listpages/template/getters/registered.ts
6697
+ function createRegisteredGetter(name, key, format) {
6698
+ if (name === "metadata")
6699
+ return (ctx) => {
6700
+ const metadata = ctx.page.metadata;
6701
+ const entry = metadata && Object.hasOwn(metadata, key) ? metadata[key] : null;
6702
+ if (!entry)
6703
+ return "";
6704
+ let value;
6705
+ switch (entry.type) {
6706
+ case "text":
6707
+ value = entry.value;
6708
+ break;
6709
+ case "number":
6710
+ value = String(entry.value);
6711
+ break;
6712
+ case "date":
6713
+ value = formatDate(entry.value, format);
6714
+ break;
6715
+ case "user":
6716
+ value = format === "id" ? String(entry.value.id) : format === "unix" ? entry.value.unixName : entry.value.name;
6717
+ break;
6718
+ }
6719
+ return literalWikitext(value);
6720
+ };
6721
+ if (name === "customrate" || name === "customrate_votes" || name === "customrate_percent")
6722
+ return (ctx) => {
6723
+ const ratings = ctx.page.customRates;
6724
+ const entry = ratings && Object.hasOwn(ratings, key) ? ratings[key] : null;
6725
+ if (!entry)
6726
+ return "";
6727
+ return String(name === "customrate" ? entry.points : name === "customrate_votes" ? entry.votes : entry.percent);
6728
+ };
6729
+ return null;
6730
+ }
6731
+
6732
+ // packages/parser/src/parser/rules/block/module/listpages/template/getters/index.ts
6733
+ import { compileTextExcerpt } from "@wdprlib/ast";
6734
+
6735
+ // packages/parser/src/parser/rules/block/module/listpages/template/excerpt.ts
6736
+ function findExcerptEnd(template, contentStart) {
6737
+ let quote;
6738
+ for (let cursor = contentStart + "excerpt{".length;cursor < template.length; cursor++) {
6739
+ const char = template[cursor];
6740
+ if (quote) {
6741
+ if (char === "\\")
6742
+ cursor++;
6743
+ else if (char === quote)
6744
+ quote = undefined;
6745
+ } else if (char === '"' || char === "'") {
6746
+ quote = char;
6747
+ } else if (char === "}") {
6748
+ return template.startsWith("%%", cursor + 1) ? cursor + 1 : -1;
6749
+ }
6750
+ }
6751
+ return -1;
6752
+ }
6753
+ function parseExcerptOptions(input) {
6754
+ if (input.length > 8192)
6755
+ return null;
6756
+ const attributes = new Map;
6757
+ let cursor = 0;
6758
+ while (cursor < input.length) {
6759
+ while (cursor < input.length && /\s/.test(input[cursor]))
6760
+ cursor++;
6761
+ if (cursor === input.length)
6762
+ break;
6763
+ const start = cursor;
6764
+ while (cursor < input.length && /[a-z]/.test(input[cursor]))
6765
+ cursor++;
6766
+ const name = input.slice(start, cursor);
6767
+ if (!["pattern", "flags", "group", "match", "max"].includes(name) || attributes.has(name))
6768
+ return null;
6769
+ while (cursor < input.length && /\s/.test(input[cursor]))
6770
+ cursor++;
6771
+ if (input[cursor++] !== "=")
6772
+ return null;
6773
+ while (cursor < input.length && /\s/.test(input[cursor]))
6774
+ cursor++;
6775
+ const quote = input[cursor++];
6776
+ if (quote !== '"' && quote !== "'")
6777
+ return null;
6778
+ let value = "";
6779
+ while (cursor < input.length && input[cursor] !== quote) {
6780
+ if (input[cursor] === "\\" && (input[cursor + 1] === quote || input[cursor + 1] === "\\")) {
6781
+ value += input[cursor + 1] === quote ? quote : "\\\\";
6782
+ cursor += 2;
6783
+ } else
6784
+ value += input[cursor++];
6785
+ }
6786
+ if (input[cursor++] !== quote)
6787
+ return null;
6788
+ if (cursor < input.length && !/\s/.test(input[cursor]))
6789
+ return null;
6790
+ attributes.set(name, value);
6791
+ }
6792
+ const pattern = attributes.get("pattern");
6793
+ if (pattern === undefined)
6794
+ return null;
6795
+ const group = attributes.get("group");
6796
+ const match = attributes.get("match");
6797
+ const max = attributes.get("max");
6798
+ if (match !== undefined && !/^\d+$/.test(match) || max !== undefined && !/^\d+$/.test(max))
6799
+ return null;
6800
+ return {
6801
+ pattern,
6802
+ flags: attributes.get("flags"),
6803
+ group: group === undefined ? undefined : /^\d+$/.test(group) ? Number(group) : group,
6804
+ match: match === undefined ? undefined : Number(match),
6805
+ maxLength: max === undefined ? undefined : Number(max)
6806
+ };
6807
+ }
6808
+
5653
6809
  // packages/parser/src/parser/rules/block/module/listpages/template/getters/index.ts
5654
6810
  function createVariableGetter(name, braceParam, parenParam, format) {
6811
+ if (name === "excerpt") {
6812
+ const options = braceParam === undefined ? null : parseExcerptOptions(braceParam);
6813
+ if (!options || parenParam !== undefined || format !== undefined)
6814
+ return () => "";
6815
+ const extract = compileTextExcerpt(options);
6816
+ return (ctx) => literalWikitext(extract(ctx.page.readableText ?? ""));
6817
+ }
6818
+ const registeredGetter = braceParam !== undefined ? createRegisteredGetter(name, braceParam, format) : null;
6819
+ if (registeredGetter)
6820
+ return registeredGetter;
5655
6821
  const braceGetter = braceParam !== undefined ? createBraceParamGetter(name, braceParam) : null;
5656
6822
  if (braceGetter)
5657
6823
  return braceGetter;
6824
+ if (braceParam !== undefined)
6825
+ return () => "";
5658
6826
  const parenGetter = parenParam !== undefined ? createParenParamGetter(name, parenParam) : null;
5659
6827
  if (parenGetter)
5660
6828
  return parenGetter;
@@ -5676,7 +6844,7 @@ function scanTemplateVariables(template) {
5676
6844
  if (start === -1)
5677
6845
  break;
5678
6846
  const contentStart = start + 2;
5679
- const end = template.indexOf("%%", contentStart);
6847
+ const end = template.slice(contentStart, contentStart + 8).toLowerCase() === "excerpt{" ? findExcerptEnd(template, contentStart) : template.indexOf("%%", contentStart);
5680
6848
  if (end === -1)
5681
6849
  break;
5682
6850
  const parsed = parseTemplateVariableContent(template.slice(contentStart, end));
@@ -5699,6 +6867,11 @@ function parseTemplateVariableContent(content) {
5699
6867
  if (cursor === 0)
5700
6868
  return null;
5701
6869
  const name = content.slice(0, cursor);
6870
+ if (name.toLowerCase() === "excerpt" && content[cursor] === "{") {
6871
+ if (!content.endsWith("}"))
6872
+ return null;
6873
+ return { name, braceParam: content.slice(cursor + 1, -1) };
6874
+ }
5702
6875
  let braceParam;
5703
6876
  let parenParam;
5704
6877
  let format;
@@ -5778,6 +6951,7 @@ function buildQuery(module) {
5778
6951
  updatedAt: module["updated-at"],
5779
6952
  createdBy: module["created-by"],
5780
6953
  rating: module.rating,
6954
+ ratingAxis: module["rating-axis"],
5781
6955
  votes: module.votes,
5782
6956
  name: module.name,
5783
6957
  fullname: module.fullname,
@@ -5832,6 +7006,7 @@ var KNOWN_VARIABLES = new Set([
5832
7006
  "parent_title_linked",
5833
7007
  "content",
5834
7008
  "preview",
7009
+ "excerpt",
5835
7010
  "summary",
5836
7011
  "first_paragraph",
5837
7012
  "tags",
@@ -5869,12 +7044,24 @@ function extractVariablesFromTemplate(template) {
5869
7044
  const contentIndices = new Set;
5870
7045
  const previewLengths = new Set;
5871
7046
  const formFields = new Set;
7047
+ const metadataKeys = new Set;
7048
+ const customRateKeys = new Set;
5872
7049
  let tagsLinkPrefix;
5873
7050
  let hiddenTagsLinkPrefix;
5874
7051
  for (const match of scanTemplateVariables(template)) {
5875
7052
  const varName = match.name.toLowerCase();
5876
7053
  if (match.braceParam !== undefined) {
5877
7054
  switch (varName) {
7055
+ case "metadata":
7056
+ metadataKeys.add(match.braceParam);
7057
+ variables.add("metadata");
7058
+ continue;
7059
+ case "customrate":
7060
+ case "customrate_votes":
7061
+ case "customrate_percent":
7062
+ customRateKeys.add(match.braceParam);
7063
+ variables.add(varName);
7064
+ continue;
5878
7065
  case "content":
5879
7066
  contentIndices.add(Number(match.braceParam));
5880
7067
  variables.add("content_n");
@@ -5924,6 +7111,8 @@ function extractVariablesFromTemplate(template) {
5924
7111
  contentIndices: Array.from(contentIndices).sort((a, b) => a - b),
5925
7112
  previewLengths: Array.from(previewLengths).sort((a, b) => a - b),
5926
7113
  formFields: Array.from(formFields).sort(),
7114
+ metadataKeys: [...metadataKeys].sort(),
7115
+ customRateKeys: [...customRateKeys].sort(),
5927
7116
  tagsLinkPrefix,
5928
7117
  hiddenTagsLinkPrefix
5929
7118
  };
@@ -5945,6 +7134,9 @@ function extractListPagesModule(listPages, state, result) {
5945
7134
  contentSectionIndices: extraction.contentIndices,
5946
7135
  previewLengths: extraction.previewLengths,
5947
7136
  formFields: extraction.formFields,
7137
+ metadataKeys: extraction.metadataKeys,
7138
+ customRateKeys: extraction.customRateKeys,
7139
+ needsReadableText: extraction.variables.some((name) => ["preview", "preview_n", "summary", "first_paragraph", "excerpt"].includes(name)),
5948
7140
  tagsLinkPrefix: extraction.tagsLinkPrefix,
5949
7141
  hiddenTagsLinkPrefix: extraction.hiddenTagsLinkPrefix,
5950
7142
  urlAttrPrefix: listPages["url-attr-prefix"],
@@ -6277,13 +7469,101 @@ function wrapListPagesResult(module, items, parse, data) {
6277
7469
  return result;
6278
7470
  }
6279
7471
 
7472
+ // packages/parser/src/parser/rules/block/module/rate/resolve.ts
7473
+ function refKey(ref) {
7474
+ return ref.kind === "main" ? "main" : `custom:${ref.axisKey}`;
7475
+ }
7476
+ async function resolveRatings(ast, fetchRatings) {
7477
+ const refs = new Map;
7478
+ mapDocument(ast, (element) => {
7479
+ if (isRating(element) && element.data.ref)
7480
+ refs.set(refKey(element.data.ref), element.data.ref);
7481
+ return element;
7482
+ });
7483
+ const states = new Map;
7484
+ if (fetchRatings && refs.size > 0) {
7485
+ for (const state of await fetchRatings([...refs.values()])) {
7486
+ const key = refKey(state.ref);
7487
+ if (refs.has(key))
7488
+ states.set(key, state);
7489
+ }
7490
+ }
7491
+ return mapDocument(ast, (element) => {
7492
+ if (!isRating(element))
7493
+ return element;
7494
+ const { state: _previous, ...data } = element.data;
7495
+ const state = data.ref ? states.get(refKey(data.ref)) : undefined;
7496
+ return { ...element, data: state ? { ...data, state } : data };
7497
+ });
7498
+ }
7499
+ function suppressModuleRatings(result) {
7500
+ const ast = mapDocument(getModuleParseAst(result), (element) => isRating(element) ? null : element);
7501
+ return "ast" in result ? { ...result, ast } : ast;
7502
+ }
7503
+ function isRating(element) {
7504
+ return element.element === "module" && (element.data.module === "rate" || element.data.module === "custom-rate");
7505
+ }
7506
+ function mapDocument(ast, transform) {
7507
+ const map = (elements) => elements.flatMap((original) => {
7508
+ const element = transform(original);
7509
+ if (!element)
7510
+ return [];
7511
+ if (element.element === "if") {
7512
+ return [
7513
+ {
7514
+ ...element,
7515
+ data: {
7516
+ ...element.data,
7517
+ then: map(element.data.then),
7518
+ else: map(element.data.else)
7519
+ }
7520
+ }
7521
+ ];
7522
+ }
7523
+ if (element.element === "ifexpr") {
7524
+ return [
7525
+ {
7526
+ ...element,
7527
+ data: {
7528
+ ...element.data,
7529
+ then: map(element.data.then),
7530
+ else: map(element.data.else)
7531
+ }
7532
+ }
7533
+ ];
7534
+ }
7535
+ if (element.element === "bibliography-block") {
7536
+ return [
7537
+ {
7538
+ ...element,
7539
+ data: {
7540
+ ...element.data,
7541
+ entries: element.data.entries.map((entry) => ({
7542
+ ...entry,
7543
+ key: map(entry.key),
7544
+ value: map(entry.value)
7545
+ }))
7546
+ }
7547
+ }
7548
+ ];
7549
+ }
7550
+ return [mapElementChildren(element, map)];
7551
+ });
7552
+ return {
7553
+ ...ast,
7554
+ elements: map(ast.elements),
7555
+ ...ast.footnotes ? { footnotes: ast.footnotes.map(map) } : {}
7556
+ };
7557
+ }
7558
+
6280
7559
  // packages/parser/src/parser/rules/block/module/listpages/resolve.ts
6281
7560
  function isListPagesModule2(module) {
6282
7561
  return module.module === "list-pages";
6283
7562
  }
6284
7563
  function resolveListPages(module, data, compiledTemplate, parse) {
6285
- const items = renderListPagesItems(module, data, compiledTemplate, parse);
6286
- return wrapListPagesResult(module, items, parse, data);
7564
+ const parseFragment = (source) => suppressModuleRatings(parse(source));
7565
+ const items = renderListPagesItems(module, data, compiledTemplate, parseFragment);
7566
+ return wrapListPagesResult(module, items, parseFragment, data);
6287
7567
  }
6288
7568
  // packages/parser/src/parser/rules/block/module/listpages/types/external-data.ts
6289
7569
  function definePageData(input) {
@@ -6297,7 +7577,6 @@ function definePageData(input) {
6297
7577
  hiddenTags: input.hiddenTags ?? [],
6298
7578
  children: input.children ?? 0,
6299
7579
  comments: input.comments ?? 0,
6300
- size: input.size ?? 0,
6301
7580
  rating: input.rating ?? 0,
6302
7581
  ratingVotes: input.ratingVotes ?? 0,
6303
7582
  revisions: input.revisions ?? 0
@@ -6375,6 +7654,7 @@ var URL_RESOLVABLE_FIELDS = [
6375
7654
  aliases: ["createdby", "created_by"]
6376
7655
  },
6377
7656
  { attr: "rating", queryKey: "rating", type: "string" },
7657
+ { attr: "rating-axis", queryKey: "ratingAxis", type: "string" },
6378
7658
  { attr: "votes", queryKey: "votes", type: "string" },
6379
7659
  { attr: "reverse", queryKey: "reverse", type: "boolean" }
6380
7660
  ];
@@ -6476,6 +7756,7 @@ var ORDER_FIELD_MAP = {
6476
7756
  votes: "votes",
6477
7757
  revisions: "revisions",
6478
7758
  comments: "comments",
7759
+ commented_at: "commented_at",
6479
7760
  pagelength: "size",
6480
7761
  size: "size",
6481
7762
  random: "random",
@@ -6483,6 +7764,13 @@ var ORDER_FIELD_MAP = {
6483
7764
  updated_at: "updated_at"
6484
7765
  };
6485
7766
  function parseOrder(value) {
7767
+ const metadata = /^metadata\{([^}]*)\}(?:\s+(asc|desc))?$/i.exec(value.trim());
7768
+ if (metadata)
7769
+ return {
7770
+ field: "metadata",
7771
+ key: metadata[1],
7772
+ direction: metadata[2]?.toLowerCase() === "asc" ? "asc" : "desc"
7773
+ };
6486
7774
  const defaultOrder = { field: "created_at", direction: "desc" };
6487
7775
  const trimmed = value.trim().toLowerCase();
6488
7776
  if (!trimmed)
@@ -6602,6 +7890,8 @@ function normalizeQuery(query) {
6602
7890
  result.linkTo = query.linkTo;
6603
7891
  if (query.createdBy)
6604
7892
  result.createdBy = query.createdBy;
7893
+ if (query.ratingAxis !== undefined)
7894
+ result.ratingAxis = query.ratingAxis;
6605
7895
  if (query.name)
6606
7896
  result.name = query.name;
6607
7897
  if (query.fullname)
@@ -6786,7 +8076,7 @@ function resolveIfTags(data, pageTags) {
6786
8076
  // packages/parser/src/parser/preprocess/utils/raw-regions.ts
6787
8077
  var BASE_PLACEHOLDER_OPEN = "";
6788
8078
  var BASE_PLACEHOLDER_CLOSE = "";
6789
- var RAW_BLOCK_OPEN_PATTERN = /\[\[\s*(code|html)\b[^\]]*\]\]/iy;
8079
+ var RAW_BLOCK_OPEN_PATTERN = /\[\[html\b[^\]]*\]\]/iy;
6790
8080
  function makeUniqueSentinels(source) {
6791
8081
  let openRun = 0;
6792
8082
  let closeRun = 0;
@@ -6805,10 +8095,21 @@ function makeUniqueSentinels(source) {
6805
8095
  }
6806
8096
  function maskRawRegions(source, sentinels) {
6807
8097
  const placeholders = [];
8098
+ let tokens;
8099
+ const getTokens = () => tokens ??= tokenize(source);
6808
8100
  let masked = "";
6809
8101
  let i = 0;
6810
8102
  while (i < source.length) {
6811
- const rawBlock = tryMaskRawBlock(source, i, placeholders, sentinels);
8103
+ if (source.startsWith("[!--", i)) {
8104
+ const close = source.indexOf("--]", i + 4);
8105
+ if (close !== -1) {
8106
+ const end = close + 3;
8107
+ masked += source.slice(i, end);
8108
+ i = end;
8109
+ continue;
8110
+ }
8111
+ }
8112
+ const rawBlock = tryMaskRawBlock(source, i, placeholders, sentinels, getTokens);
6812
8113
  if (rawBlock) {
6813
8114
  masked += rawBlock.placeholder;
6814
8115
  i = rawBlock.end;
@@ -6829,16 +8130,25 @@ function restorePlaceholders(source, placeholders, sentinels) {
6829
8130
  const pattern = new RegExp(`${escapeRegex(sentinels.open)}(\\d+)${escapeRegex(sentinels.close)}`, "g");
6830
8131
  return source.replace(pattern, (_, idx) => placeholders[Number(idx)] ?? "");
6831
8132
  }
6832
- function tryMaskRawBlock(source, pos, placeholders, sentinels) {
8133
+ function tryMaskRawBlock(source, pos, placeholders, sentinels, getTokens) {
6833
8134
  if (source[pos] !== "[" || source[pos + 1] !== "[")
6834
8135
  return null;
8136
+ if (source.slice(pos, pos + 6).toLowerCase() === "[[code") {
8137
+ const tokens = getTokens();
8138
+ const start = tokenAtOffset(tokens, pos);
8139
+ const open = findCodeOpen(tokens, start);
8140
+ if (!open)
8141
+ return null;
8142
+ const bounds = open.closingSwallowed ? null : findCodeBodyBounds(tokens, open.bodyStart);
8143
+ const end = open.closingSwallowed ? tokens[open.attributesEnd - 1].position.end.offset : bounds.foundClose ? tokens[bounds.end - 1].position.end.offset : source.length;
8144
+ return { placeholder: pushPlaceholder(placeholders, source.slice(pos, end), sentinels), end };
8145
+ }
6835
8146
  RAW_BLOCK_OPEN_PATTERN.lastIndex = pos;
6836
8147
  const openMatch = RAW_BLOCK_OPEN_PATTERN.exec(source);
6837
8148
  if (!openMatch)
6838
8149
  return null;
6839
- const name = openMatch[1].toLowerCase();
6840
8150
  const openLen = openMatch[0].length;
6841
- const closePattern = new RegExp(`\\[\\[\\/\\s*${name}\\s*\\]\\]`, "ig");
8151
+ const closePattern = /\[\[\/\s*html\s*\]\]/gi;
6842
8152
  closePattern.lastIndex = pos + openLen;
6843
8153
  const closeMatch = closePattern.exec(source);
6844
8154
  if (closeMatch) {
@@ -6848,12 +8158,19 @@ function tryMaskRawBlock(source, pos, placeholders, sentinels) {
6848
8158
  end
6849
8159
  };
6850
8160
  }
6851
- if (name !== "code")
6852
- return null;
6853
- return {
6854
- placeholder: pushPlaceholder(placeholders, source.slice(pos), sentinels),
6855
- end: source.length
6856
- };
8161
+ return null;
8162
+ }
8163
+ function tokenAtOffset(tokens, offset) {
8164
+ let low = 0;
8165
+ let high = tokens.length;
8166
+ while (low < high) {
8167
+ const middle = Math.floor((low + high) / 2);
8168
+ if (tokens[middle].position.start.offset < offset)
8169
+ low = middle + 1;
8170
+ else
8171
+ high = middle;
8172
+ }
8173
+ return tokens[low]?.position.start.offset === offset ? low : tokens.length;
6857
8174
  }
6858
8175
  function tryMaskRawInline(source, pos, placeholders, sentinels) {
6859
8176
  if (source[pos] === "@" && source[pos + 1] === "<") {
@@ -6865,17 +8182,22 @@ function tryMaskRawInline(source, pos, placeholders, sentinels) {
6865
8182
  return null;
6866
8183
  }
6867
8184
  function tryMaskSingleLineRaw(source, pos, openerLength, close, placeholders, sentinels) {
6868
- const closePos = source.indexOf(close, pos + openerLength);
6869
- const newline = source.indexOf(`
6870
- `, pos + openerLength);
6871
- if (closePos === -1 || newline !== -1 && newline < closePos)
8185
+ const end = singleLineRawEnd(source, pos, openerLength, close);
8186
+ if (end === pos)
6872
8187
  return null;
6873
- const end = closePos + close.length;
6874
8188
  return {
6875
8189
  placeholder: pushPlaceholder(placeholders, source.slice(pos, end), sentinels),
6876
8190
  end
6877
8191
  };
6878
8192
  }
8193
+ function singleLineRawEnd(source, pos, openerLength, close) {
8194
+ const closePos = source.indexOf(close, pos + openerLength);
8195
+ const newline = source.indexOf(`
8196
+ `, pos + openerLength);
8197
+ if (closePos === -1 || newline !== -1 && newline < closePos)
8198
+ return pos;
8199
+ return closePos + close.length;
8200
+ }
6879
8201
  function pushPlaceholder(placeholders, text, sentinels) {
6880
8202
  const idx = placeholders.length;
6881
8203
  placeholders.push(text);
@@ -7477,7 +8799,7 @@ function isListUsersModule2(module) {
7477
8799
  function resolveListUsers(_module, data, compiledTemplate, parse) {
7478
8800
  const ctx = { user: data.user };
7479
8801
  const substituted = compiledTemplate(ctx);
7480
- const itemAst = getModuleParseAst(parse(substituted));
8802
+ const itemAst = getModuleParseAst(suppressModuleRatings(parse(substituted)));
7481
8803
  return itemAst.elements;
7482
8804
  }
7483
8805
  // packages/parser/src/parser/rules/block/module/resolution/data-maps.ts
@@ -8273,11 +9595,11 @@ async function resolveModules(ast, dataProvider, options) {
8273
9595
  if (mergedStyles.length > 0)
8274
9596
  result.styles = mergedStyles;
8275
9597
  options.onDiagnostics?.(registry.diagnostics);
8276
- return registry.finalize(result, finalElements, pageTags, containsSyntaxFootnoteBlock(ast.elements));
9598
+ return resolveRatings(registry.finalize(result, finalElements, pageTags, containsSyntaxFootnoteBlock(ast.elements)), dataProvider.fetchRatings);
8277
9599
  }
8278
9600
  function createModuleParseFunction(options, dataProvider, registry) {
8279
9601
  const transform = createModuleSourceTransform(options, dataProvider);
8280
- return (source) => registry.register(options.parse(transform ? transform(source) : source), {
9602
+ return (source) => registry.register(suppressModuleRatings(options.parse(transform ? transform(source) : source)), {
8281
9603
  stripLegacyImplicitFootnoteBlock: true
8282
9604
  });
8283
9605
  }
@@ -9141,141 +10463,6 @@ var includeRule = {
9141
10463
  }
9142
10464
  };
9143
10465
 
9144
- // packages/parser/src/parser/rules/block/math/content.ts
9145
- function collectMathContent(ctx, startPos) {
9146
- let latexSource = "";
9147
- let pos = startPos;
9148
- let consumed = 0;
9149
- let foundClose = false;
9150
- while (pos < ctx.tokens.length) {
9151
- const token5 = ctx.tokens[pos];
9152
- if (!token5)
9153
- break;
9154
- if (token5.type === "BLOCK_END_OPEN") {
9155
- const closeNameResult = parseBlockName(ctx, pos + 1);
9156
- if (closeNameResult?.name === "math") {
9157
- foundClose = true;
9158
- break;
9159
- }
9160
- }
9161
- latexSource += token5.type === "BACKSLASH_BREAK" ? "\\\n" : token5.value;
9162
- pos++;
9163
- consumed++;
9164
- }
9165
- return { latexSource, consumed, foundClose };
9166
- }
9167
- function consumeMathClose(ctx, startPos) {
9168
- let pos = startPos + 1;
9169
- let consumed = 1;
9170
- const closeNameResult = parseBlockName(ctx, pos);
9171
- if (closeNameResult) {
9172
- pos += closeNameResult.consumed;
9173
- consumed += closeNameResult.consumed;
9174
- }
9175
- if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
9176
- pos++;
9177
- consumed++;
9178
- }
9179
- if (ctx.tokens[pos]?.type === "NEWLINE") {
9180
- consumed++;
9181
- }
9182
- return consumed;
9183
- }
9184
-
9185
- // packages/parser/src/parser/rules/block/math/name.ts
9186
- function parseMathName(ctx, startPos) {
9187
- let pos = startPos;
9188
- let consumed = 0;
9189
- let name = "";
9190
- const first = ctx.tokens[pos];
9191
- if (first?.type !== "IDENTIFIER" && first?.type !== "TEXT") {
9192
- return { name: null, consumed: 0 };
9193
- }
9194
- while (pos < ctx.tokens.length) {
9195
- const token5 = ctx.tokens[pos];
9196
- if (!token5 || token5.type === "BLOCK_CLOSE" || token5.type === "WHITESPACE" || token5.type === "NEWLINE") {
9197
- break;
9198
- }
9199
- name += token5.value;
9200
- pos++;
9201
- consumed++;
9202
- }
9203
- return { name: name === "" ? null : name, consumed };
9204
- }
9205
-
9206
- // packages/parser/src/parser/rules/block/math/index.ts
9207
- var mathBlockRule = {
9208
- name: "math",
9209
- startTokens: ["BLOCK_OPEN"],
9210
- requiresLineStart: false,
9211
- parse(ctx) {
9212
- const openToken = currentToken(ctx);
9213
- if (openToken.type !== "BLOCK_OPEN") {
9214
- return { success: false };
9215
- }
9216
- let pos = ctx.pos + 1;
9217
- let consumed = 1;
9218
- const nameResult = parseBlockName(ctx, pos);
9219
- if (!nameResult || nameResult.name !== "math") {
9220
- return { success: false };
9221
- }
9222
- pos += nameResult.consumed;
9223
- consumed += nameResult.consumed;
9224
- while (ctx.tokens[pos]?.type === "WHITESPACE") {
9225
- pos++;
9226
- consumed++;
9227
- }
9228
- const mathName = parseMathName(ctx, pos);
9229
- pos += mathName.consumed;
9230
- consumed += mathName.consumed;
9231
- while (ctx.tokens[pos]?.type === "WHITESPACE") {
9232
- pos++;
9233
- consumed++;
9234
- }
9235
- if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
9236
- return { success: false };
9237
- }
9238
- pos++;
9239
- consumed++;
9240
- if (ctx.tokens[pos]?.type === "NEWLINE") {
9241
- pos++;
9242
- consumed++;
9243
- }
9244
- const contentResult = collectMathContent(ctx, pos);
9245
- const latexSource = contentResult.latexSource.trim();
9246
- consumed += contentResult.consumed;
9247
- pos += contentResult.consumed;
9248
- if (!contentResult.foundClose) {
9249
- ctx.diagnostics.push({
9250
- severity: "warning",
9251
- code: "unclosed-block",
9252
- message: "Missing closing tag [[/math]] for [[math]]",
9253
- position: openToken.position
9254
- });
9255
- } else {
9256
- const closeConsumed = consumeMathClose(ctx, pos);
9257
- pos += closeConsumed;
9258
- consumed += closeConsumed;
9259
- }
9260
- if (!latexSource) {
9261
- return { success: false };
9262
- }
9263
- return {
9264
- success: true,
9265
- elements: [
9266
- {
9267
- element: "math",
9268
- data: {
9269
- name: mathName.name,
9270
- "latex-source": latexSource
9271
- }
9272
- }
9273
- ],
9274
- consumed
9275
- };
9276
- }
9277
- };
9278
-
9279
10466
  // packages/parser/src/parser/rules/block/html/body.ts
9280
10467
  function lookaheadHasHtmlClose(ctx, from) {
9281
10468
  for (let i = from;i < ctx.tokens.length; i++) {
@@ -10137,7 +11324,8 @@ function parseBibliographyContent(ctx, startPos) {
10137
11324
  const inlineCtx = { ...ctx, pos };
10138
11325
  const result = parseInlineUntil(inlineCtx, "NEWLINE");
10139
11326
  if (result.elements.length > 0) {
10140
- content.push(...result.elements);
11327
+ for (const element of result.elements)
11328
+ content.push(element);
10141
11329
  pos += result.consumed;
10142
11330
  consumed += result.consumed;
10143
11331
  } else {
@@ -10566,6 +11754,7 @@ var blockRules = [
10566
11754
  iftagsRule,
10567
11755
  bibliographyRule,
10568
11756
  galleryRule,
11757
+ noteRule,
10569
11758
  divRule
10570
11759
  ];
10571
11760
  // packages/parser/src/parser/rules/inline/formatting/close.ts
@@ -10578,6 +11767,11 @@ function findFormattingClose(ctx, start, marker) {
10578
11767
  return null;
10579
11768
  if (!table && token5.type === "NEWLINE" && getParagraphNewlineBoundary(ctx, pos, true).shouldBreak)
10580
11769
  return null;
11770
+ const inlineRegionEnd = Math.max(emailRegionEnd(ctx.tokens, pos, end), parseButtonSyntax(ctx, pos, end)?.end ?? pos, parseSocialSyntax(ctx, pos, end)?.end ?? pos);
11771
+ if (inlineRegionEnd > pos) {
11772
+ pos = inlineRegionEnd - 1;
11773
+ continue;
11774
+ }
10581
11775
  if (token5.type === marker && !table?.suppressedClosers.has(pos))
10582
11776
  return pos;
10583
11777
  const protectedEnd = protectedInlineRegionEnd(ctx.tokens, pos, end);
@@ -10701,175 +11895,15 @@ var subscriptRule = {
10701
11895
  startTokens: ["SUB_MARKER"],
10702
11896
  parse(ctx) {
10703
11897
  return parseDelimitedContainer(ctx, "SUB_MARKER", "subscript", { discardEmpty: true });
10704
- }
10705
- };
10706
-
10707
- // packages/parser/src/parser/rules/inline/monospace.ts
10708
- var monospaceRule = {
10709
- name: "monospace",
10710
- startTokens: ["MONO_MARKER"],
10711
- parse(ctx) {
10712
- return parseDelimitedContainer(ctx, "MONO_CLOSE", "monospace");
10713
- }
10714
- };
10715
-
10716
- // packages/parser/src/parser/rules/inline/link-triple/fallback.ts
10717
- function literalOpenLink(value) {
10718
- return {
10719
- success: true,
10720
- elements: [{ element: "text", data: value }],
10721
- consumed: 1
10722
- };
10723
- }
10724
-
10725
- // packages/parser/src/parser/rules/inline/link-triple/label.ts
10726
- function buildTripleLinkLabel(args) {
10727
- return {
10728
- text: getTripleLinkDisplayText(args)
10729
- };
10730
- }
10731
- function getTripleLinkDisplayText(args) {
10732
- const trimmedLabel = args.labelText.trim();
10733
- if (args.foundPipe) {
10734
- return trimmedLabel || args.finalTarget;
10735
- }
10736
- const colonIdx = args.originalTarget.indexOf(":");
10737
- if (colonIdx !== -1 && !args.originalTarget.startsWith("http") && !args.originalTarget.startsWith("*")) {
10738
- return args.originalTarget.slice(colonIdx + 1).trim();
10739
- }
10740
- return args.originalTarget;
10741
- }
10742
-
10743
- // packages/parser/src/parser/rules/inline/link-triple/syntax.ts
10744
- function hasClosingLinkMarker(ctx, startPos) {
10745
- let pos = startPos;
10746
- while (pos < ctx.tokens.length) {
10747
- const token5 = ctx.tokens[pos];
10748
- if (!token5 || token5.type === "EOF") {
10749
- return false;
10750
- }
10751
- if (token5.type === "LINK_CLOSE") {
10752
- return true;
10753
- }
10754
- if (token5.type === "NEWLINE") {
10755
- const next = ctx.tokens[pos + 1];
10756
- if (next?.type === "NEWLINE" || next?.type === "LINK_CLOSE") {
10757
- return false;
10758
- }
10759
- }
10760
- pos++;
10761
- }
10762
- return false;
10763
- }
10764
- function collectTripleLinkParts(ctx, startPos) {
10765
- let target = "";
10766
- let labelText = "";
10767
- let foundPipe = false;
10768
- let consumed = 1;
10769
- let pos = startPos;
10770
- while (pos < ctx.tokens.length) {
10771
- const token5 = ctx.tokens[pos];
10772
- if (!token5 || token5.type === "LINK_CLOSE" || token5.type === "EOF") {
10773
- break;
10774
- }
10775
- if (token5.type === "NEWLINE") {
10776
- if (foundPipe) {
10777
- labelText += " ";
10778
- } else {
10779
- target += " ";
10780
- }
10781
- consumed++;
10782
- pos++;
10783
- continue;
10784
- }
10785
- if (token5.type === "PIPE" && !foundPipe) {
10786
- foundPipe = true;
10787
- } else if (foundPipe) {
10788
- labelText += token5.value;
10789
- } else {
10790
- target += token5.value;
10791
- }
10792
- consumed++;
10793
- pos++;
10794
- }
10795
- if (ctx.tokens[pos]?.type === "LINK_CLOSE") {
10796
- consumed++;
10797
- }
10798
- return { target, labelText, foundPipe, consumed };
10799
- }
10800
-
10801
- // packages/parser/src/parser/rules/inline/link-triple/interwiki.ts
10802
- var INTERWIKI_PREFIXES = new Set(["wikipedia", "google", "dictionary", "wikidot"]);
10803
- function isInterwikiTarget(target) {
10804
- const colonIdx = target.indexOf(":");
10805
- if (colonIdx <= 0 || target.includes("/")) {
10806
- return false;
10807
- }
10808
- const prefix = target.slice(0, colonIdx).toLowerCase();
10809
- return INTERWIKI_PREFIXES.has(prefix);
10810
- }
10811
-
10812
- // packages/parser/src/parser/rules/inline/link-triple/target.ts
10813
- function normalizeTripleLinkTarget(trimmedTarget) {
10814
- if (trimmedTarget.startsWith("*")) {
10815
- return { target: trimmedTarget.slice(1), hasStar: true };
10816
- }
10817
- return { target: trimmedTarget, hasStar: false };
10818
- }
10819
- function isInvalidTripleLinkTarget(trimmedTarget, foundPipe) {
10820
- return trimmedTarget === "" && foundPipe || /#{2,}/.test(trimmedTarget);
10821
- }
10822
- function determineLinkTypeAndLocation(target) {
10823
- if (target.startsWith("#")) {
10824
- return { linkType: "anchor", link: target };
10825
- }
10826
- if (target.startsWith("http://") || target.startsWith("https://")) {
10827
- return { linkType: "direct", link: target };
10828
- }
10829
- if (isInterwikiTarget(target)) {
10830
- return { linkType: "interwiki", link: target };
10831
- }
10832
- return { linkType: "page", link: { site: null, page: target } };
10833
- }
11898
+ }
11899
+ };
10834
11900
 
10835
- // packages/parser/src/parser/rules/inline/link-triple/index.ts
10836
- var linkTripleRule = {
10837
- name: "linkTriple",
10838
- startTokens: ["LINK_OPEN"],
11901
+ // packages/parser/src/parser/rules/inline/monospace.ts
11902
+ var monospaceRule = {
11903
+ name: "monospace",
11904
+ startTokens: ["MONO_MARKER"],
10839
11905
  parse(ctx) {
10840
- const startToken = currentToken(ctx);
10841
- if (!hasClosingLinkMarker(ctx, ctx.pos + 1)) {
10842
- return literalOpenLink(startToken.value);
10843
- }
10844
- const parts = collectTripleLinkParts(ctx, ctx.pos + 1);
10845
- const trimmedTarget = parts.target.trim();
10846
- if (isInvalidTripleLinkTarget(trimmedTarget, parts.foundPipe)) {
10847
- return literalOpenLink(startToken.value);
10848
- }
10849
- const normalized = normalizeTripleLinkTarget(trimmedTarget);
10850
- const { linkType, link } = determineLinkTypeAndLocation(normalized.target);
10851
- const label = buildTripleLinkLabel({
10852
- foundPipe: parts.foundPipe,
10853
- labelText: parts.labelText,
10854
- finalTarget: normalized.target,
10855
- originalTarget: trimmedTarget
10856
- });
10857
- return {
10858
- success: true,
10859
- elements: [
10860
- {
10861
- element: "link",
10862
- data: {
10863
- type: linkType,
10864
- link,
10865
- extra: null,
10866
- label,
10867
- target: normalized.hasStar && linkType === "direct" ? "new-tab" : null
10868
- }
10869
- }
10870
- ],
10871
- consumed: parts.consumed
10872
- };
11906
+ return parseDelimitedContainer(ctx, "MONO_CLOSE", "monospace");
10873
11907
  }
10874
11908
  };
10875
11909
 
@@ -10880,7 +11914,7 @@ function normalizeAnchor(anchor) {
10880
11914
 
10881
11915
  // packages/parser/src/parser/rules/inline/link-bracket/direct-url.ts
10882
11916
  function isDirectBracketUrl(url) {
10883
- return url !== "" && (url.startsWith("/") || url.startsWith("http://") || url.startsWith("https://"));
11917
+ return url !== "" && (url.startsWith("/") || startsWithUrlScheme(url, 0));
10884
11918
  }
10885
11919
 
10886
11920
  // packages/parser/src/parser/rules/inline/link-bracket/parts.ts
@@ -10889,35 +11923,24 @@ function collectBracketLinkParts(ctx, startPos) {
10889
11923
  return null;
10890
11924
  }
10891
11925
  let pos = startPos;
10892
- let consumed = 0;
10893
- let first = "";
10894
- while (pos < ctx.tokens.length) {
10895
- const token5 = ctx.tokens[pos];
10896
- if (!token5 || token5.type === "WHITESPACE" || token5.type === "BRACKET_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF") {
10897
- break;
10898
- }
10899
- first += token5.value;
10900
- pos++;
10901
- consumed++;
10902
- }
10903
- while (ctx.tokens[pos]?.type === "WHITESPACE") {
10904
- pos++;
10905
- consumed++;
10906
- }
10907
- let label = "";
10908
- while (pos < ctx.tokens.length) {
11926
+ const end = Math.min(ctx.scope.inlineEnd ?? ctx.tokens.length, ctx.tokens.length);
11927
+ const values = [];
11928
+ while (pos < end) {
10909
11929
  const token5 = ctx.tokens[pos];
10910
- if (!token5 || token5.type === "BRACKET_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF") {
11930
+ if (!token5 || token5.type === "BRACKET_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF")
10911
11931
  break;
10912
- }
10913
- label += token5.value;
11932
+ values.push(token5.value);
10914
11933
  pos++;
10915
- consumed++;
10916
11934
  }
10917
- if (ctx.tokens[pos]?.type !== "BRACKET_CLOSE") {
11935
+ if (pos >= end || ctx.tokens[pos]?.type !== "BRACKET_CLOSE")
10918
11936
  return null;
10919
- }
10920
- return { first, label, consumed: consumed + 1 };
11937
+ const content = values.join("");
11938
+ const separator = content.search(/[ \t]/);
11939
+ return {
11940
+ first: separator === -1 ? content : content.slice(0, separator),
11941
+ label: separator === -1 ? "" : content.slice(separator).trimStart(),
11942
+ consumed: pos - startPos + 1
11943
+ };
10921
11944
  }
10922
11945
 
10923
11946
  // packages/parser/src/parser/rules/inline/link-bracket/prefix.ts
@@ -10928,6 +11951,14 @@ function parseBracketLinkPrefix(ctx, startPos) {
10928
11951
  return { target: null, bodyStart: startPos, consumed: 0 };
10929
11952
  }
10930
11953
 
11954
+ // packages/parser/src/parser/rules/inline/link-bracket/special-target.ts
11955
+ function isBracketEmail(target) {
11956
+ return /^[_a-z0-9-]+(?:\.[_a-z0-9-]+)*@[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i.test(target);
11957
+ }
11958
+ function wikipediaPage(target) {
11959
+ return /^wikipedia:((?!:)[A-Za-z0-9_/=&~#.:;+-]+)$/.exec(target)?.[1] ?? null;
11960
+ }
11961
+
10931
11962
  // packages/parser/src/parser/rules/inline/link-bracket/parsed.ts
10932
11963
  function parseSingleBracketLink(ctx) {
10933
11964
  const prefix = parseBracketLinkPrefix(ctx, ctx.pos + 1);
@@ -10936,17 +11967,20 @@ function parseSingleBracketLink(ctx) {
10936
11967
  return null;
10937
11968
  }
10938
11969
  const link = parts.first.trim();
10939
- if (!isDirectBracketUrl(link)) {
11970
+ const email = isBracketEmail(link);
11971
+ const wikiPage = wikipediaPage(link);
11972
+ if (!email && wikiPage === null && !isDirectBracketUrl(link)) {
10940
11973
  return null;
10941
11974
  }
10942
- const labelText = parts.label.trim();
11975
+ const labelText = parts.label.trim() || wikiPage;
10943
11976
  if (!labelText) {
10944
11977
  return null;
10945
11978
  }
10946
11979
  return {
10947
- link,
11980
+ link: email ? `mailto:${link}` : link,
11981
+ interwiki: wikiPage !== null,
10948
11982
  labelText,
10949
- target: prefix.target,
11983
+ target: wikiPage !== null ? "new-tab" : prefix.target,
10950
11984
  consumed: 1 + prefix.consumed + parts.consumed
10951
11985
  };
10952
11986
  }
@@ -10999,7 +12033,7 @@ var linkSingleRule = {
10999
12033
  {
11000
12034
  element: "link",
11001
12035
  data: {
11002
- type: "direct",
12036
+ type: parsed.interwiki ? "interwiki" : "direct",
11003
12037
  link: parsed.link,
11004
12038
  extra: null,
11005
12039
  label: linkLabel,
@@ -11068,6 +12102,134 @@ var linkStarRule = {
11068
12102
  }
11069
12103
  };
11070
12104
 
12105
+ // packages/parser/src/parser/rules/inline/autolink.ts
12106
+ var URL_BOUNDARY_TOKENS = new Set([
12107
+ "WHITESPACE",
12108
+ "NEWLINE",
12109
+ "EOF",
12110
+ "BLOCK_OPEN",
12111
+ "BLOCK_END_OPEN",
12112
+ "BLOCK_CLOSE",
12113
+ "LINK_OPEN",
12114
+ "LINK_CLOSE",
12115
+ "COMMENT_OPEN",
12116
+ "BACKSLASH_BREAK"
12117
+ ]);
12118
+ var autolinkRule = {
12119
+ name: "autolink",
12120
+ startTokens: ["IDENTIFIER", "STAR", "LIST_BULLET"],
12121
+ parse(ctx) {
12122
+ let pos = ctx.pos;
12123
+ let target = null;
12124
+ const first = ctx.tokens[pos];
12125
+ if (!first) {
12126
+ return { success: false };
12127
+ }
12128
+ if (first.type === "STAR" || first.type === "LIST_BULLET") {
12129
+ if (first.value !== "*") {
12130
+ return { success: false };
12131
+ }
12132
+ target = "new-tab";
12133
+ pos++;
12134
+ }
12135
+ const scheme = ctx.tokens[pos];
12136
+ if (scheme?.type !== "IDENTIFIER" || !URL_SCHEME_NAMES.has(scheme.value)) {
12137
+ return { success: false };
12138
+ }
12139
+ if (ctx.tokens[pos + 1]?.type !== "COLON") {
12140
+ return { success: false };
12141
+ }
12142
+ const prev = ctx.tokens[ctx.pos - 1];
12143
+ if (prev && !first.lineStart) {
12144
+ const lastChar = prev.value[prev.value.length - 1] ?? "";
12145
+ if (/[A-Za-z]/.test(lastChar)) {
12146
+ return { success: false };
12147
+ }
12148
+ }
12149
+ const values = [];
12150
+ let end = pos;
12151
+ const inlineEnd = ctx.scope.inlineEnd ?? ctx.tokens.length;
12152
+ while (end < inlineEnd) {
12153
+ const token5 = ctx.tokens[end];
12154
+ if (!token5 || URL_BOUNDARY_TOKENS.has(token5.type)) {
12155
+ break;
12156
+ }
12157
+ if (token5.type === "RAW_OPEN" || token5.type === "RAW_BLOCK_OPEN") {
12158
+ if (rawRegionEnd(ctx.tokens, end, inlineEnd) > end)
12159
+ break;
12160
+ }
12161
+ values.push(token5.value);
12162
+ end++;
12163
+ if (/[ \t\n\\"']/.test(token5.value))
12164
+ break;
12165
+ }
12166
+ const candidate = values.join("");
12167
+ const match = URL_PATTERN.exec(candidate);
12168
+ if (!match) {
12169
+ return { success: false };
12170
+ }
12171
+ const url = match[0];
12172
+ let length = 0;
12173
+ let count = 0;
12174
+ while (length < url.length) {
12175
+ length += values[count]?.length ?? 0;
12176
+ count++;
12177
+ }
12178
+ const rest = candidate.slice(url.length, length);
12179
+ const elements = [
12180
+ {
12181
+ element: "link",
12182
+ data: {
12183
+ type: "direct",
12184
+ link: url,
12185
+ extra: null,
12186
+ label: { text: url },
12187
+ target
12188
+ }
12189
+ }
12190
+ ];
12191
+ if (rest !== "") {
12192
+ elements.push({ element: "text", data: rest });
12193
+ }
12194
+ return {
12195
+ success: true,
12196
+ elements,
12197
+ consumed: pos - ctx.pos + count
12198
+ };
12199
+ }
12200
+ };
12201
+
12202
+ // packages/parser/src/parser/rules/inline/email/index.ts
12203
+ var emailRule = {
12204
+ name: "email",
12205
+ startTokens: EMAIL_START_TOKENS,
12206
+ parse(ctx) {
12207
+ const group = getEmailGroup(ctx.tokens, ctx.pos, ctx.scope.inlineEnd ?? ctx.tokens.length);
12208
+ if (!group)
12209
+ return { success: false };
12210
+ const elements = [];
12211
+ let copied = group.start;
12212
+ for (const candidate of group.candidates) {
12213
+ if (copied < candidate.start)
12214
+ elements.push({ element: "text", data: group.source.slice(copied, candidate.start) });
12215
+ elements.push(ctx.scope.suppressEmailLinks ? { element: "text", data: candidate.address } : {
12216
+ element: "link",
12217
+ data: {
12218
+ type: "direct",
12219
+ link: `mailto:${candidate.address}`,
12220
+ label: { text: candidate.address },
12221
+ target: null,
12222
+ extra: null
12223
+ }
12224
+ });
12225
+ copied = candidate.end;
12226
+ }
12227
+ if (copied < group.end)
12228
+ elements.push({ element: "text", data: group.source.slice(copied, group.end) });
12229
+ return { success: true, elements, consumed: group.endToken - ctx.pos };
12230
+ }
12231
+ };
12232
+
11071
12233
  // packages/parser/src/parser/rules/inline/color/syntax.ts
11072
12234
  function parseColorContent(ctx) {
11073
12235
  const close = findFormattingClose(ctx, ctx.pos + 1, "COLOR_MARKER");
@@ -11154,7 +12316,7 @@ var newlineLineBreakRule = {
11154
12316
  }
11155
12317
  return {
11156
12318
  success: true,
11157
- elements: [{ element: "line-break" }],
12319
+ elements: [createAutomaticLineBreak(currentTok)],
11158
12320
  consumed: 1
11159
12321
  };
11160
12322
  }
@@ -11177,6 +12339,9 @@ function isValidBlockStartAfterNewline(ctx, tokenPos) {
11177
12339
  if (!isBlockStartToken(token5?.type) || !token5?.lineStart) {
11178
12340
  return false;
11179
12341
  }
12342
+ if (token5.type === "LIST_BULLET" || token5.type === "LIST_NUMBER") {
12343
+ return ctx.tokens[tokenPos + 1]?.type === "WHITESPACE";
12344
+ }
11180
12345
  if (token5.type !== "HEADING_MARKER") {
11181
12346
  return true;
11182
12347
  }
@@ -11434,6 +12599,9 @@ var htmlInlineRule = {
11434
12599
  }
11435
12600
  };
11436
12601
 
12602
+ // packages/parser/src/parser/rules/inline/raw/angle.ts
12603
+ import { decodeHTML } from "entities";
12604
+
11437
12605
  // packages/parser/src/parser/rules/inline/raw/result.ts
11438
12606
  function rawElement(value, consumed) {
11439
12607
  return {
@@ -11481,7 +12649,7 @@ function parseAngleRaw(ctx) {
11481
12649
  if (ctx.tokens[pos]?.type === "RAW_BLOCK_CLOSE") {
11482
12650
  consumed++;
11483
12651
  }
11484
- return rawElement(value, consumed);
12652
+ return rawElement(decodeHTML(value), consumed);
11485
12653
  }
11486
12654
 
11487
12655
  // packages/parser/src/parser/rules/inline/raw/double-at.ts
@@ -11704,7 +12872,7 @@ function consumeSpanNewline(ctx, pos, paragraphStrip, afterBlankLine, children,
11704
12872
  return { consumed: consumed2, afterBlankLine: paragraphStrip };
11705
12873
  }
11706
12874
  const targetChildren = paragraphStrip && afterBlankLine ? escapedChildren : children;
11707
- targetChildren.push({ element: "line-break" });
12875
+ targetChildren.push(createAutomaticLineBreak(ctx.tokens[pos]));
11708
12876
  let consumed = 1;
11709
12877
  let nextPos = pos + 1;
11710
12878
  while (ctx.tokens[nextPos]?.type === "WHITESPACE" && ctx.tokens[nextPos]?.lineStart) {
@@ -11792,7 +12960,9 @@ function parseOneSpanChild(ctx, pos, targetChildren) {
11792
12960
  for (const rule of getCandidateInlineRules(inlineRules, token5.type)) {
11793
12961
  const result = rule.parse(inlineCtx);
11794
12962
  if (result.success) {
11795
- targetChildren.push(...result.elements);
12963
+ stripAutomaticLineBreak(targetChildren, result.stripLeadingLineBreak);
12964
+ for (const element of result.elements)
12965
+ targetChildren.push(element);
11796
12966
  return { consumed: result.consumed };
11797
12967
  }
11798
12968
  }
@@ -11860,56 +13030,60 @@ var closeSpanRule = {
11860
13030
  }
11861
13031
  };
11862
13032
 
11863
- // packages/parser/src/parser/rules/inline/size/content.ts
11864
- function parseSizeContent(ctx, startPos) {
11865
- const children = [];
11866
- let pos = startPos;
11867
- let consumed = 0;
11868
- while (pos < ctx.tokens.length) {
11869
- const token5 = ctx.tokens[pos];
11870
- if (!token5 || token5.type === "EOF") {
11871
- break;
11872
- }
11873
- const closeResult = tryConsumeSizeClose(ctx, pos);
11874
- if (closeResult) {
11875
- return {
11876
- children,
11877
- consumed: consumed + closeResult.consumed,
11878
- foundClose: true
11879
- };
11880
- }
11881
- const inlineCtx = { ...ctx, pos };
11882
- const inlineResult = parseInlineUntil(inlineCtx, "BLOCK_END_OPEN");
11883
- if (inlineResult.elements.length > 0) {
11884
- children.push(...inlineResult.elements);
11885
- pos += inlineResult.consumed;
11886
- consumed += inlineResult.consumed;
11887
- } else {
11888
- children.push({ element: "text", data: token5.value });
11889
- pos++;
11890
- consumed++;
11891
- }
11892
- }
11893
- return { children, consumed, foundClose: false };
11894
- }
11895
- function tryConsumeSizeClose(ctx, pos) {
11896
- if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
11897
- return null;
13033
+ // packages/parser/src/parser/rules/inline/button/index.ts
13034
+ var buttonRule = {
13035
+ name: "button",
13036
+ startTokens: ["BLOCK_OPEN"],
13037
+ parse(ctx) {
13038
+ const result = parseButtonSyntax(ctx, ctx.pos, ctx.scope.inlineEnd ?? ctx.tokens.length);
13039
+ return result ? {
13040
+ success: true,
13041
+ consumed: result.end - ctx.pos,
13042
+ elements: [{ element: "button", data: result.data }]
13043
+ } : { success: false };
11898
13044
  }
11899
- const closeNameResult = parseBlockName(ctx, pos + 1);
11900
- if (!closeNameResult || closeNameResult.name !== "size") {
11901
- return null;
13045
+ };
13046
+
13047
+ // packages/parser/src/parser/rules/inline/social/index.ts
13048
+ var socialRule = {
13049
+ name: "social",
13050
+ startTokens: ["BLOCK_OPEN"],
13051
+ parse(ctx) {
13052
+ const result = parseSocialSyntax(ctx, ctx.pos, ctx.scope.inlineEnd ?? ctx.tokens.length);
13053
+ return result ? {
13054
+ success: true,
13055
+ consumed: result.end - ctx.pos,
13056
+ elements: [{ element: "social", data: result.data }]
13057
+ } : { success: false };
11902
13058
  }
11903
- let closePos = pos + 1 + closeNameResult.consumed;
11904
- let consumed = 1 + closeNameResult.consumed;
11905
- if (ctx.tokens[closePos]?.type === "BLOCK_CLOSE") {
11906
- consumed++;
13059
+ };
13060
+
13061
+ // packages/parser/src/parser/rules/inline/date/index.ts
13062
+ var dateRule = {
13063
+ name: "date",
13064
+ startTokens: ["BLOCK_OPEN"],
13065
+ parse(ctx) {
13066
+ const parsed = parseDateSyntax(ctx, ctx.pos, ctx.scope.inlineEnd ?? ctx.tokens.length);
13067
+ return parsed ? {
13068
+ success: true,
13069
+ consumed: parsed.end - ctx.pos,
13070
+ elements: [{ element: "date", data: parsed.data }]
13071
+ } : { success: false };
11907
13072
  }
11908
- return { consumed };
11909
- }
13073
+ };
11910
13074
 
11911
13075
  // packages/parser/src/parser/rules/inline/size/value.ts
11912
13076
  var VALID_SIZE_UNITS = ["px", "em", "rem", "ex", "%", "cm", "mm", "in", "pc"];
13077
+ var VALID_SIZE_KEYWORDS = [
13078
+ "smaller",
13079
+ "larger",
13080
+ "xx-small",
13081
+ "x-small",
13082
+ "small",
13083
+ "large",
13084
+ "x-large",
13085
+ "xx-large"
13086
+ ];
11913
13087
  function parseSizeValue(ctx, startPos) {
11914
13088
  let pos = startPos;
11915
13089
  let consumed = 0;
@@ -11934,6 +13108,8 @@ function parseSizeValue(ctx, startPos) {
11934
13108
  return isValidSizeValue(size) ? { size, consumed } : null;
11935
13109
  }
11936
13110
  function isValidSizeValue(size) {
13111
+ if (VALID_SIZE_KEYWORDS.includes(size))
13112
+ return true;
11937
13113
  const unitPattern = VALID_SIZE_UNITS.join("|");
11938
13114
  return new RegExp(`^(\\d+(?:\\.\\d+)?)(${unitPattern})$`, "i").test(size);
11939
13115
  }
@@ -11965,6 +13141,81 @@ function parseSizeOpen(ctx) {
11965
13141
  return { size: sizeResult.size, bodyStart: pos, consumed };
11966
13142
  }
11967
13143
 
13144
+ // packages/parser/src/parser/rules/inline/size/content.ts
13145
+ function parseSizeContent(ctx, startPos) {
13146
+ const children = [];
13147
+ let pos = startPos;
13148
+ let consumed = 0;
13149
+ const inlineEnd = findSizeClose(ctx, startPos);
13150
+ while (pos < (ctx.scope.inlineEnd ?? ctx.tokens.length)) {
13151
+ const token5 = ctx.tokens[pos];
13152
+ if (!token5 || token5.type === "EOF") {
13153
+ break;
13154
+ }
13155
+ const closeResult = tryConsumeSizeClose(ctx, pos);
13156
+ if (closeResult) {
13157
+ return {
13158
+ children,
13159
+ consumed: consumed + closeResult.consumed,
13160
+ foundClose: true
13161
+ };
13162
+ }
13163
+ const inlineCtx = { ...ctx, pos, scope: { ...ctx.scope, inlineEnd } };
13164
+ const inlineResult = parseInlineUntil(inlineCtx, "EOF");
13165
+ if (inlineResult.consumed > 0) {
13166
+ for (const element of inlineResult.elements)
13167
+ children.push(element);
13168
+ pos += inlineResult.consumed;
13169
+ consumed += inlineResult.consumed;
13170
+ } else {
13171
+ children.push({ element: "text", data: token5.value });
13172
+ pos++;
13173
+ consumed++;
13174
+ }
13175
+ }
13176
+ return { children, consumed, foundClose: false };
13177
+ }
13178
+ function findSizeClose(ctx, startPos) {
13179
+ const end = ctx.scope.inlineEnd ?? ctx.tokens.length;
13180
+ let depth = 0;
13181
+ for (let pos = startPos;pos < end; pos++) {
13182
+ const protectedEnd = protectedInlineRegionEnd(ctx.tokens, pos, end);
13183
+ if (protectedEnd > pos) {
13184
+ pos = protectedEnd - 1;
13185
+ continue;
13186
+ }
13187
+ const open = ctx.tokens[pos]?.type === "BLOCK_OPEN" ? parseSizeOpen({ ...ctx, pos }) : null;
13188
+ if (open) {
13189
+ depth++;
13190
+ pos = open.bodyStart - 1;
13191
+ continue;
13192
+ }
13193
+ const close = tryConsumeSizeClose(ctx, pos);
13194
+ if (!close)
13195
+ continue;
13196
+ if (depth === 0)
13197
+ return pos;
13198
+ depth--;
13199
+ pos += close.consumed - 1;
13200
+ }
13201
+ return end;
13202
+ }
13203
+ function tryConsumeSizeClose(ctx, pos) {
13204
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
13205
+ return null;
13206
+ }
13207
+ const closeNameResult = parseBlockName(ctx, pos + 1);
13208
+ if (!closeNameResult || closeNameResult.name !== "size") {
13209
+ return null;
13210
+ }
13211
+ let closePos = pos + 1 + closeNameResult.consumed;
13212
+ let consumed = 1 + closeNameResult.consumed;
13213
+ if (ctx.tokens[closePos]?.type === "BLOCK_CLOSE") {
13214
+ consumed++;
13215
+ }
13216
+ return { consumed };
13217
+ }
13218
+
11968
13219
  // packages/parser/src/parser/rules/inline/size/index.ts
11969
13220
  var sizeRule = {
11970
13221
  name: "size",
@@ -12005,19 +13256,6 @@ var sizeRule = {
12005
13256
  }
12006
13257
  };
12007
13258
 
12008
- // packages/parser/src/parser/rules/inline/footnote/child.ts
12009
- function parseFootnoteChild(ctx, pos) {
12010
- const token5 = ctx.tokens[pos];
12011
- if (!token5) {
12012
- return { elements: [], consumed: 0 };
12013
- }
12014
- const inlineResult = parseInlineUntil({ ...ctx, pos }, "BLOCK_END_OPEN");
12015
- if (inlineResult.elements.length > 0) {
12016
- return { elements: inlineResult.elements, consumed: inlineResult.consumed };
12017
- }
12018
- return { elements: [{ element: "text", data: token5.value }], consumed: 1 };
12019
- }
12020
-
12021
13259
  // packages/parser/src/parser/rules/inline/footnote/close.ts
12022
13260
  function tryConsumeFootnoteClose(ctx, pos) {
12023
13261
  if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
@@ -12039,96 +13277,73 @@ function tryConsumeFootnoteClose(ctx, pos) {
12039
13277
  return { consumed };
12040
13278
  }
12041
13279
 
12042
- // packages/parser/src/parser/rules/inline/footnote/newline.ts
12043
- function consumeFootnoteNewline(ctx, pos) {
12044
- let nextPos = pos + 1;
12045
- let consumed = 1;
12046
- while (ctx.tokens[nextPos]?.type === "WHITESPACE") {
12047
- nextPos++;
12048
- consumed++;
12049
- }
12050
- if (ctx.tokens[nextPos]?.type !== "NEWLINE") {
12051
- return { consumed, paragraphBreak: false };
13280
+ // packages/parser/src/parser/rules/inline/math-inline/syntax.ts
13281
+ function parseInlineMathSource(ctx, startPos) {
13282
+ let pos = startPos;
13283
+ let consumed = 0;
13284
+ if (ctx.tokens[pos]?.type !== "TEXT" || ctx.tokens[pos]?.value !== "$") {
13285
+ return null;
12052
13286
  }
12053
- while (ctx.tokens[nextPos]?.type === "NEWLINE") {
12054
- nextPos++;
13287
+ pos++;
13288
+ consumed++;
13289
+ while (ctx.tokens[pos]?.type === "WHITESPACE") {
13290
+ pos++;
12055
13291
  consumed++;
12056
13292
  }
12057
- return { consumed, paragraphBreak: true };
12058
- }
12059
-
12060
- // packages/parser/src/parser/rules/inline/footnote/content.ts
12061
- function parseFootnoteContent(ctx, startPos) {
12062
- const paragraphs = [[]];
12063
- let currentParagraph = 0;
12064
- let pos = startPos;
12065
- let consumed = 0;
13293
+ let latexSource = "";
12066
13294
  while (pos < ctx.tokens.length) {
12067
13295
  const token5 = ctx.tokens[pos];
12068
- if (!token5 || token5.type === "EOF") {
12069
- break;
12070
- }
12071
- const closeResult = tryConsumeFootnoteClose(ctx, pos);
12072
- if (closeResult) {
12073
- return {
12074
- paragraphs,
12075
- consumed: consumed + closeResult.consumed,
12076
- foundClose: true
12077
- };
13296
+ if (!token5 || token5.type === "NEWLINE") {
13297
+ return null;
12078
13298
  }
12079
- if (token5.type === "NEWLINE") {
12080
- const newlineResult = consumeFootnoteNewline(ctx, pos);
12081
- pos += newlineResult.consumed;
12082
- consumed += newlineResult.consumed;
12083
- if (newlineResult.paragraphBreak) {
12084
- currentParagraph++;
12085
- paragraphs[currentParagraph] = [];
12086
- } else {
12087
- paragraphs[currentParagraph].push({ element: "line-break" });
12088
- }
12089
- continue;
13299
+ if (token5.type === "TEXT" && token5.value === "$" && ctx.tokens[pos + 1]?.type === "BLOCK_CLOSE") {
13300
+ break;
12090
13301
  }
12091
- const child = parseFootnoteChild(ctx, pos);
12092
- paragraphs[currentParagraph].push(...child.elements);
12093
- pos += child.consumed;
12094
- consumed += child.consumed;
13302
+ latexSource += token5.value;
13303
+ pos++;
13304
+ consumed++;
13305
+ }
13306
+ if (ctx.tokens[pos]?.type !== "TEXT" || ctx.tokens[pos]?.value !== "$") {
13307
+ return null;
13308
+ }
13309
+ pos++;
13310
+ consumed++;
13311
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
13312
+ return null;
12095
13313
  }
12096
- return { paragraphs, consumed, foundClose: false };
13314
+ return {
13315
+ latexSource: latexSource.trim(),
13316
+ consumed: consumed + 1
13317
+ };
12097
13318
  }
12098
13319
 
12099
- // packages/parser/src/parser/rules/inline/footnote/elements.ts
12100
- function buildFootnoteChildren(paragraphs) {
12101
- const children = [];
12102
- for (let i = 0;i < paragraphs.length; i++) {
12103
- const para = trimLineBreaks(paragraphs[i] ?? []);
12104
- if (para.length === 0) {
12105
- continue;
13320
+ // packages/parser/src/parser/rules/inline/math-inline/index.ts
13321
+ var mathInlineRule = {
13322
+ name: "math-inline",
13323
+ startTokens: ["BLOCK_OPEN"],
13324
+ parse(ctx) {
13325
+ const openToken = currentToken(ctx);
13326
+ if (openToken.type !== "BLOCK_OPEN") {
13327
+ return { success: false };
12106
13328
  }
12107
- if (i === 0) {
12108
- children.push(...para);
12109
- } else {
12110
- children.push({
12111
- element: "container",
12112
- data: {
12113
- type: "paragraph",
12114
- attributes: {},
12115
- elements: para
12116
- }
12117
- });
13329
+ const parsed = parseInlineMathSource(ctx, ctx.pos + 1);
13330
+ if (!parsed) {
13331
+ return { success: false };
12118
13332
  }
13333
+ return {
13334
+ success: true,
13335
+ elements: [
13336
+ {
13337
+ element: "math-inline",
13338
+ data: {
13339
+ "latex-source": parsed.latexSource
13340
+ }
13341
+ }
13342
+ ],
13343
+ consumed: 1 + parsed.consumed
13344
+ };
12119
13345
  }
12120
- return children;
12121
- }
12122
- function trimLineBreaks(elements) {
12123
- const result = [...elements];
12124
- while (result.length > 0 && result[0]?.element === "line-break") {
12125
- result.shift();
12126
- }
12127
- while (result.length > 0 && result[result.length - 1]?.element === "line-break") {
12128
- result.pop();
12129
- }
12130
- return result;
12131
- }
13346
+ };
12132
13347
 
12133
13348
  // packages/parser/src/parser/rules/inline/footnote/open.ts
12134
13349
  function parseFootnoteOpen(ctx) {
@@ -12155,6 +13370,91 @@ function parseFootnoteOpen(ctx) {
12155
13370
  return { bodyStart: pos, consumed };
12156
13371
  }
12157
13372
 
13373
+ // packages/parser/src/parser/rules/inline/footnote/boundary.ts
13374
+ var opaqueRules2 = [
13375
+ codeBlockRule,
13376
+ htmlBlockRule,
13377
+ mathBlockRule,
13378
+ mathInlineRule,
13379
+ linkTripleRule,
13380
+ linkSingleRule,
13381
+ linkStarRule,
13382
+ linkAnchorRule
13383
+ ];
13384
+ function findFootnoteEnd(ctx, start) {
13385
+ const probe = createOpaqueProbe(ctx);
13386
+ let depth = 0;
13387
+ for (let pos = start;pos < ctx.tokens.length; pos++) {
13388
+ if (ctx.tokens[pos]?.type === "EOF")
13389
+ return pos;
13390
+ const protectedEnd = protectedInlineRegionEnd(ctx.tokens, pos, ctx.tokens.length);
13391
+ if (protectedEnd > pos) {
13392
+ pos = protectedEnd - 1;
13393
+ continue;
13394
+ }
13395
+ probe.pos = pos;
13396
+ const close = tryConsumeFootnoteClose(probe, pos);
13397
+ if (close) {
13398
+ if (depth === 0)
13399
+ return pos;
13400
+ depth--;
13401
+ pos += close.consumed - 1;
13402
+ continue;
13403
+ }
13404
+ const open = parseFootnoteOpen(probe);
13405
+ if (open) {
13406
+ depth++;
13407
+ pos += open.consumed - 1;
13408
+ continue;
13409
+ }
13410
+ const opaqueEnd = opaqueRuleEnd(probe, pos, opaqueRules2);
13411
+ if (opaqueEnd > pos)
13412
+ pos = opaqueEnd - 1;
13413
+ }
13414
+ return ctx.tokens.length;
13415
+ }
13416
+
13417
+ // packages/parser/src/parser/rules/inline/footnote/content.ts
13418
+ function parseFootnoteContent(ctx, startPos) {
13419
+ const end = findFootnoteEnd(ctx, startPos);
13420
+ let leadingNewlines = 0;
13421
+ for (let pos = startPos;pos < end; pos++) {
13422
+ const type = ctx.tokens[pos]?.type;
13423
+ if (type === "NEWLINE")
13424
+ leadingNewlines++;
13425
+ else if (type !== "WHITESPACE")
13426
+ break;
13427
+ }
13428
+ const bodyCtx = {
13429
+ ...ctx,
13430
+ tokens: ctx.tokens.slice(startPos, end),
13431
+ pos: 0,
13432
+ scope: {
13433
+ ...ctx.scope,
13434
+ inlineEnd: undefined,
13435
+ tableFormatting: undefined,
13436
+ blockCloseCondition: undefined
13437
+ }
13438
+ };
13439
+ const result = parseBlocksUntil(bodyCtx, () => false);
13440
+ const close = tryConsumeFootnoteClose(ctx, end);
13441
+ return {
13442
+ elements: result.elements,
13443
+ consumed: end - startPos + (close?.consumed ?? 0),
13444
+ foundClose: close !== null,
13445
+ leadingParagraphBreak: leadingNewlines >= 2
13446
+ };
13447
+ }
13448
+
13449
+ // packages/parser/src/parser/rules/inline/footnote/elements.ts
13450
+ function buildFootnoteChildren(elements, leadingParagraphBreak) {
13451
+ const first = elements[0];
13452
+ if (!leadingParagraphBreak && first?.element === "container" && first.data.type === "paragraph") {
13453
+ return [...first.data.elements, ...elements.slice(1)];
13454
+ }
13455
+ return elements;
13456
+ }
13457
+
12158
13458
  // packages/parser/src/parser/rules/inline/footnote/index.ts
12159
13459
  var footnoteRule = {
12160
13460
  name: "footnote",
@@ -12170,7 +13470,7 @@ var footnoteRule = {
12170
13470
  }
12171
13471
  const contentResult = parseFootnoteContent(ctx, openResult.bodyStart);
12172
13472
  const consumed = openResult.consumed + contentResult.consumed;
12173
- const children = buildFootnoteChildren(contentResult.paragraphs);
13473
+ const children = buildFootnoteChildren(contentResult.elements, contentResult.leadingParagraphBreak);
12174
13474
  if (!contentResult.foundClose) {
12175
13475
  ctx.diagnostics.push({
12176
13476
  severity: "warning",
@@ -12530,11 +13830,15 @@ function parseAnchorChild(ctx, pos) {
12530
13830
  if (!token5) {
12531
13831
  return { elements: [], consumed: 0 };
12532
13832
  }
12533
- const inlineCtx = { ...ctx, pos };
13833
+ const inlineCtx = {
13834
+ ...ctx,
13835
+ pos,
13836
+ scope: { ...ctx.scope, suppressEmailLinks: true }
13837
+ };
12534
13838
  for (const rule of getCandidateInlineRules(inlineRules, token5.type)) {
12535
13839
  const result = rule.parse(inlineCtx);
12536
13840
  if (result.success) {
12537
- return { elements: result.elements, consumed: result.consumed };
13841
+ return result;
12538
13842
  }
12539
13843
  }
12540
13844
  return { elements: [{ element: "text", data: token5.value }], consumed: 1 };
@@ -12594,7 +13898,7 @@ function consumeAnchorNewline(ctx, pos, paragraphStrip, children) {
12594
13898
  let consumed = 1;
12595
13899
  let nextPos = pos + 1;
12596
13900
  if (!paragraphStrip) {
12597
- children.push({ element: "line-break" });
13901
+ children.push(createAutomaticLineBreak(ctx.tokens[pos]));
12598
13902
  while (ctx.tokens[nextPos]?.type === "WHITESPACE" && ctx.tokens[nextPos]?.lineStart) {
12599
13903
  nextPos++;
12600
13904
  consumed++;
@@ -12647,7 +13951,9 @@ function parseAnchorContent(ctx, startPos, paragraphStrip) {
12647
13951
  continue;
12648
13952
  }
12649
13953
  const child = parseAnchorChild(ctx, pos);
12650
- children.push(...child.elements);
13954
+ stripAutomaticLineBreak(children, child.stripLeadingLineBreak);
13955
+ for (const element of child.elements)
13956
+ children.push(element);
12651
13957
  pos += child.consumed;
12652
13958
  consumed += child.consumed;
12653
13959
  }
@@ -12721,75 +14027,8 @@ var anchorRule = {
12721
14027
  }
12722
14028
  }
12723
14029
  ],
12724
- consumed
12725
- };
12726
- }
12727
- };
12728
-
12729
- // packages/parser/src/parser/rules/inline/math-inline/syntax.ts
12730
- function parseInlineMathSource(ctx, startPos) {
12731
- let pos = startPos;
12732
- let consumed = 0;
12733
- if (ctx.tokens[pos]?.type !== "TEXT" || ctx.tokens[pos]?.value !== "$") {
12734
- return null;
12735
- }
12736
- pos++;
12737
- consumed++;
12738
- while (ctx.tokens[pos]?.type === "WHITESPACE") {
12739
- pos++;
12740
- consumed++;
12741
- }
12742
- let latexSource = "";
12743
- while (pos < ctx.tokens.length) {
12744
- const token5 = ctx.tokens[pos];
12745
- if (!token5 || token5.type === "NEWLINE") {
12746
- return null;
12747
- }
12748
- if (token5.type === "TEXT" && token5.value === "$" && ctx.tokens[pos + 1]?.type === "BLOCK_CLOSE") {
12749
- break;
12750
- }
12751
- latexSource += token5.value;
12752
- pos++;
12753
- consumed++;
12754
- }
12755
- if (ctx.tokens[pos]?.type !== "TEXT" || ctx.tokens[pos]?.value !== "$") {
12756
- return null;
12757
- }
12758
- pos++;
12759
- consumed++;
12760
- if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
12761
- return null;
12762
- }
12763
- return {
12764
- latexSource: latexSource.trim(),
12765
- consumed: consumed + 1
12766
- };
12767
- }
12768
-
12769
- // packages/parser/src/parser/rules/inline/math-inline/index.ts
12770
- var mathInlineRule = {
12771
- name: "math-inline",
12772
- startTokens: ["BLOCK_OPEN"],
12773
- parse(ctx) {
12774
- const openToken = currentToken(ctx);
12775
- if (openToken.type !== "BLOCK_OPEN") {
12776
- return { success: false };
12777
- }
12778
- const parsed = parseInlineMathSource(ctx, ctx.pos + 1);
12779
- if (!parsed) {
12780
- return { success: false };
12781
- }
12782
- return {
12783
- success: true,
12784
- elements: [
12785
- {
12786
- element: "math-inline",
12787
- data: {
12788
- "latex-source": parsed.latexSource
12789
- }
12790
- }
12791
- ],
12792
- consumed: 1 + parsed.consumed
14030
+ consumed,
14031
+ stripLeadingLineBreak: openResult.paragraphStrip ? precedingSingleNewline(ctx) : undefined
12793
14032
  };
12794
14033
  }
12795
14034
  };
@@ -12900,7 +14139,9 @@ function parseInlineBranch(ctx, startPos) {
12900
14139
  }
12901
14140
  const result = rule.parse(inlineCtx);
12902
14141
  if (result.success) {
12903
- elements.push(...result.elements);
14142
+ stripAutomaticLineBreak(elements, result.stripLeadingLineBreak);
14143
+ for (const element of result.elements)
14144
+ elements.push(element);
12904
14145
  consumed += result.consumed;
12905
14146
  pos += result.consumed;
12906
14147
  matched = true;
@@ -13223,6 +14464,7 @@ var textRule = {
13223
14464
 
13224
14465
  // packages/parser/src/parser/rules/inline/index.ts
13225
14466
  var inlineRules = [
14467
+ emailRule,
13226
14468
  boldRule,
13227
14469
  italicRule,
13228
14470
  underlineRule,
@@ -13234,6 +14476,7 @@ var inlineRules = [
13234
14476
  linkSingleRule,
13235
14477
  linkAnchorRule,
13236
14478
  linkStarRule,
14479
+ autolinkRule,
13237
14480
  colorRule,
13238
14481
  backslashLineBreakRule,
13239
14482
  underscoreLineBreakRule,
@@ -13243,6 +14486,9 @@ var inlineRules = [
13243
14486
  rawRule,
13244
14487
  imageRule,
13245
14488
  embedBlockRule,
14489
+ buttonRule,
14490
+ socialRule,
14491
+ dateRule,
13246
14492
  sizeRule,
13247
14493
  footnoteRule,
13248
14494
  spanRule,
@@ -13498,6 +14744,8 @@ function mergeSpanStripParagraphs(children) {
13498
14744
  i++;
13499
14745
  continue;
13500
14746
  }
14747
+ const firstMeaningful = paraData.elements.find((child) => child.element !== "line-break" && !isWhitespaceText(child));
14748
+ const keepParagraph = firstMeaningful && !isSpanStripMarker(firstMeaningful);
13501
14749
  const mergedChildren = [...paraData.elements];
13502
14750
  i++;
13503
14751
  while (i < expandedChildren.length) {
@@ -13510,7 +14758,8 @@ function mergeSpanStripParagraphs(children) {
13510
14758
  break;
13511
14759
  }
13512
14760
  const hasSpanStrip = hasParagraphStripSpan(nextPara);
13513
- mergedChildren.push(...nextParaData.elements);
14761
+ for (const element of nextParaData.elements)
14762
+ mergedChildren.push(element);
13514
14763
  i++;
13515
14764
  if (!hasSpanStrip) {
13516
14765
  const peekNext = expandedChildren[i];
@@ -13521,7 +14770,7 @@ function mergeSpanStripParagraphs(children) {
13521
14770
  }
13522
14771
  const escapedSpans = extractEscapedSpans(mergedChildren);
13523
14772
  removeLineBreaksAroundSpanStrip(mergedChildren);
13524
- if (escapedSpans.length > 0) {
14773
+ if (escapedSpans.length > 0 || keepParagraph) {
13525
14774
  if (mergedChildren.length > 0) {
13526
14775
  result.push(paragraphElement(mergedChildren));
13527
14776
  }
@@ -13742,50 +14991,6 @@ function cleanInternalFlags(elements) {
13742
14991
  }
13743
14992
  return removeEmptySpansAndAdjacentWhitespace(cleaned ?? elements);
13744
14993
  }
13745
- // packages/parser/src/parser/postprocess/divAdjacentParagraph.ts
13746
- function isParagraphContainer2(el) {
13747
- if (!el || el.element !== "container")
13748
- return false;
13749
- return el.data.type === "paragraph";
13750
- }
13751
- function isDivContainer(el) {
13752
- if (!el || el.element !== "container")
13753
- return false;
13754
- return el.data.type === "div";
13755
- }
13756
- function suppressAtLevel(elements) {
13757
- if (elements.length <= 1)
13758
- return elements;
13759
- const unwrap = Array.from({ length: elements.length }, () => false);
13760
- for (let i = 0;i < elements.length; i++) {
13761
- if (!isParagraphContainer2(elements[i]))
13762
- continue;
13763
- const prevIsDiv = i > 0 && isDivContainer(elements[i - 1]);
13764
- const nextIsDiv = i < elements.length - 1 && isDivContainer(elements[i + 1]);
13765
- if (prevIsDiv || nextIsDiv) {
13766
- unwrap[i] = true;
13767
- }
13768
- }
13769
- const result = [];
13770
- for (let i = 0;i < elements.length; i++) {
13771
- const el = elements[i];
13772
- if (!el)
13773
- continue;
13774
- if (unwrap[i] && el.element === "container") {
13775
- const inner = el.data.elements;
13776
- if (i > 0 && isDivContainer(elements[i - 1])) {
13777
- result.push({ element: "line-break" });
13778
- }
13779
- result.push(...inner);
13780
- } else {
13781
- result.push(el);
13782
- }
13783
- }
13784
- return result;
13785
- }
13786
- function suppressDivAdjacentParagraphs(elements) {
13787
- return suppressAtLevel(elements);
13788
- }
13789
14994
  // packages/parser/src/parser/parse/footnotes.ts
13790
14995
  function containsFootnoteBlock(elements) {
13791
14996
  let found = false;
@@ -14063,7 +15268,9 @@ function substitute(text2) {
14063
15268
  result = result.replace(WHITESPACE_ONLY_LINE, "");
14064
15269
  }
14065
15270
  if (result.indexOf("\\\n") !== -1) {
14066
- result = result.replace(CONCAT_LINES, String.fromCharCode(57344));
15271
+ const sentinels = makeUniqueSentinels(result);
15272
+ const { masked, placeholders } = maskRawRegions(result, sentinels);
15273
+ result = restorePlaceholders(masked.replace(CONCAT_LINES, String.fromCharCode(57344)), placeholders, sentinels);
14067
15274
  }
14068
15275
  if (result.indexOf("\t") !== -1) {
14069
15276
  result = result.replace(TABS, " ");
@@ -14130,14 +15337,26 @@ function replaceDelimitedTypography(text2, opener, closer, leftQuote, rightQuote
14130
15337
  let searchFrom = 0;
14131
15338
  let result = "";
14132
15339
  let lastCopied = 0;
15340
+ let closeIndex = -1;
15341
+ let newlineIndex = -1;
14133
15342
  while (searchFrom < text2.length) {
14134
15343
  const openIndex = text2.indexOf(opener, searchFrom);
14135
15344
  if (openIndex === -1)
14136
15345
  break;
14137
15346
  const contentStart = openIndex + opener.length;
14138
- const closeIndex = text2.indexOf(closer, contentStart);
15347
+ if (closeIndex < contentStart)
15348
+ closeIndex = text2.indexOf(closer, contentStart);
14139
15349
  if (closeIndex === -1)
14140
15350
  break;
15351
+ if (newlineIndex < contentStart) {
15352
+ const nextNewline = text2.indexOf(`
15353
+ `, contentStart);
15354
+ newlineIndex = nextNewline === -1 ? text2.length : nextNewline;
15355
+ }
15356
+ if (newlineIndex < closeIndex) {
15357
+ searchFrom = newlineIndex + 1;
15358
+ continue;
15359
+ }
14141
15360
  result += text2.slice(lastCopied, openIndex);
14142
15361
  result += leftQuote;
14143
15362
  result += text2.slice(contentStart, closeIndex);
@@ -14148,7 +15367,12 @@ function replaceDelimitedTypography(text2, opener, closer, leftQuote, rightQuote
14148
15367
  return lastCopied === 0 ? text2 : result + text2.slice(lastCopied);
14149
15368
  }
14150
15369
  function substitute2(text2) {
14151
- let result = text2;
15370
+ if (!text2.includes("`") && !text2.includes(",,") && !text2.includes("...") && !text2.includes(". . .")) {
15371
+ return text2;
15372
+ }
15373
+ const sentinels = makeUniqueSentinels(text2);
15374
+ const { masked, placeholders } = maskRawRegions(text2, sentinels);
15375
+ let result = masked;
14152
15376
  if (result.includes("``") && result.includes("''")) {
14153
15377
  result = replaceDelimitedTypography(result, "``", "''", LEFT_DOUBLE_QUOTE, RIGHT_DOUBLE_QUOTE);
14154
15378
  }
@@ -14164,7 +15388,7 @@ function substitute2(text2) {
14164
15388
  if (result.includes(". . .")) {
14165
15389
  result = replaceExactEllipsisPattern(result, ". . .");
14166
15390
  }
14167
- return result;
15391
+ return restorePlaceholders(result, placeholders, sentinels);
14168
15392
  }
14169
15393
 
14170
15394
  // packages/parser/src/parser/preprocess/index.ts
@@ -14404,14 +15628,17 @@ function parseSource(source, options, deferInclude) {
14404
15628
  }
14405
15629
  // packages/parser/src/pipeline/process.ts
14406
15630
  import {
14407
- DEFAULT_SETTINGS as DEFAULT_SETTINGS2
15631
+ DEFAULT_SETTINGS as DEFAULT_SETTINGS2,
15632
+ extractReadableText,
15633
+ extractFirstParagraph,
15634
+ countCharacters as countCharacters2
14408
15635
  } from "@wdprlib/ast";
14409
15636
 
14410
15637
  // packages/parser/src/parser/rules/block/module/resolution/resolve-async.ts
14411
15638
  async function resolveModulesWithAsyncParse(ast, dataProvider, options) {
14412
15639
  const registry = new ModuleDocumentRegistry;
14413
15640
  registry.register(ast);
14414
- const parse2 = async (source) => registry.register(await options.parse(source));
15641
+ const parse2 = async (source) => registry.register(suppressModuleRatings(await options.parse(source)));
14415
15642
  const [listPagesData, listUsersData, tagCloudData] = await Promise.all([
14416
15643
  buildListPagesDataMap(dataProvider, options.requirements.listPages ?? [], options.urlPath, options.paginationState),
14417
15644
  buildListUsersDataMap(dataProvider, options.requirements.listUsers ?? []),
@@ -14647,8 +15874,13 @@ async function processWikitext(source, options) {
14647
15874
  if (hasResolvableRequirements(extractDataRequirements(ast).requirements, options.dataProvider)) {
14648
15875
  diagnostics.push(createLimitDiagnostic("module-resolution-limit", `Module resolution stopped after ${DEFAULT_MODULE_MAX_PASSES} passes.`));
14649
15876
  }
15877
+ ast = await resolveRatings(ast, dataProvider.fetchRatings);
15878
+ const readableText = extractReadableText(ast, options.readableText);
14650
15879
  return {
14651
15880
  ast,
15881
+ readableText,
15882
+ firstParagraph: extractFirstParagraph(ast, options.readableText),
15883
+ characterCount: countCharacters2(readableText),
14652
15884
  page: options.page,
14653
15885
  settings,
14654
15886
  diagnostics,
@@ -14677,6 +15909,7 @@ function createLimitDiagnostic(code, message) {
14677
15909
  function createModuleDataProvider(options, context) {
14678
15910
  const provider = options.dataProvider;
14679
15911
  return {
15912
+ fetchRatings: provider?.fetchRatings ? (refs) => provider.fetchRatings(refs, context) : undefined,
14680
15913
  fetchListPages: provider?.fetchListPages ? (query, requirement) => provider.fetchListPages(query, requirement, context) : undefined,
14681
15914
  fetchListUsers: provider?.fetchListUsers ? (requirement) => provider.fetchListUsers(requirement, context) : undefined,
14682
15915
  fetchTagCloud: provider?.fetchTagCloud ? (requirement) => provider.fetchTagCloud(requirement, context) : undefined,