@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.cjs CHANGED
@@ -30,7 +30,7 @@ var __export = (target, all) => {
30
30
  var exports_src = {};
31
31
  __export(exports_src, {
32
32
  tokenize: () => tokenize,
33
- text: () => import_ast9.text,
33
+ text: () => import_ast12.text,
34
34
  resolveTagCloud: () => resolveTagCloud,
35
35
  resolveModules: () => resolveModules,
36
36
  resolveListUsers: () => resolveListUsers,
@@ -47,49 +47,49 @@ __export(exports_src, {
47
47
  parseDateSelector: () => parseDateSelector,
48
48
  parseCategory: () => parseCategory,
49
49
  parse: () => parse,
50
- paragraph: () => import_ast9.paragraph,
50
+ paragraph: () => import_ast12.paragraph,
51
51
  normalizeQuery: () => normalizeQuery,
52
52
  matchesListPagesSelectors: () => matchesListPagesSelectors,
53
- listItemSubList: () => import_ast9.listItemSubList,
54
- listItemElements: () => import_ast9.listItemElements,
55
- list: () => import_ast9.list,
56
- link: () => import_ast9.link,
57
- lineBreak: () => import_ast9.lineBreak,
58
- italics: () => import_ast9.italics,
53
+ listItemSubList: () => import_ast12.listItemSubList,
54
+ listItemElements: () => import_ast12.listItemElements,
55
+ list: () => import_ast12.list,
56
+ link: () => import_ast12.link,
57
+ lineBreak: () => import_ast12.lineBreak,
58
+ italics: () => import_ast12.italics,
59
59
  isTagCloudModule: () => isTagCloudModule,
60
60
  isListUsersModule: () => isListUsersModule2,
61
- horizontalRule: () => import_ast9.horizontalRule,
62
- heading: () => import_ast9.heading,
61
+ horizontalRule: () => import_ast12.horizontalRule,
62
+ heading: () => import_ast12.heading,
63
63
  extractListUsersVariables: () => extractListUsersVariables,
64
64
  extractIncludeReferences: () => extractIncludeReferences,
65
65
  extractDataRequirements: () => extractDataRequirements,
66
66
  definePageData: () => definePageData,
67
67
  createToken: () => createToken,
68
- createSettings: () => import_ast10.createSettings,
69
- createPosition: () => import_ast9.createPosition,
70
- createPoint: () => import_ast9.createPoint,
71
- container: () => import_ast9.container,
68
+ createSettings: () => import_ast13.createSettings,
69
+ createPosition: () => import_ast12.createPosition,
70
+ createPoint: () => import_ast12.createPoint,
71
+ container: () => import_ast12.container,
72
72
  compileTemplate: () => compileTemplate,
73
73
  compileListUsersTemplate: () => compileListUsersTemplate,
74
74
  buildInfo: () => buildInfo,
75
- bold: () => import_ast9.bold,
76
- STYLE_SLOT_PREFIX: () => import_ast4.STYLE_SLOT_PREFIX,
75
+ bold: () => import_ast12.bold,
76
+ STYLE_SLOT_PREFIX: () => import_ast7.STYLE_SLOT_PREFIX,
77
77
  Parser: () => Parser,
78
78
  Lexer: () => Lexer,
79
- DEFAULT_SETTINGS: () => import_ast10.DEFAULT_SETTINGS
79
+ DEFAULT_SETTINGS: () => import_ast13.DEFAULT_SETTINGS
80
80
  });
81
81
  module.exports = __toCommonJS(exports_src);
82
82
 
83
83
  // packages/parser/src/build-info.generated.ts
84
84
  var buildInfo = Object.freeze({
85
- version: "5.3.0",
86
- sha: "b71b84c705e05717f5ea12fd89684a91b669dbff",
85
+ version: "6.0.0",
86
+ sha: "705ff97e22abd7bfb49d7f8f30087e6f09654c91",
87
87
  dirty: false
88
88
  });
89
89
 
90
90
  // packages/parser/src/index.ts
91
- var import_ast9 = require("@wdprlib/ast");
92
- var import_ast10 = require("@wdprlib/ast");
91
+ var import_ast12 = require("@wdprlib/ast");
92
+ var import_ast13 = require("@wdprlib/ast");
93
93
 
94
94
  // packages/parser/src/lexer/tokens.ts
95
95
  function createToken(type, value, position, lineStart = false) {
@@ -272,13 +272,13 @@ function isValidAnchorNameChar(code) {
272
272
  }
273
273
 
274
274
  // packages/parser/src/lexer/quoted-string.ts
275
- function scanQuotedString(state) {
275
+ function scanQuotedString(state, end = state.source.length) {
276
276
  let quoted = advance(state);
277
- while (!isAtEnd(state) && current(state) !== '"' && current(state) !== `
277
+ while (state.pos < end && current(state) !== '"' && current(state) !== `
278
278
  `) {
279
279
  quoted += advance(state);
280
280
  }
281
- if (current(state) === '"') {
281
+ if (state.pos < end && current(state) === '"') {
282
282
  quoted += advance(state);
283
283
  }
284
284
  return quoted;
@@ -363,6 +363,26 @@ function isCompactPlainTextBoundary(code) {
363
363
  }
364
364
  }
365
365
 
366
+ // packages/parser/src/lexer/url-schemes.ts
367
+ var URL_SCHEME_NAMES = new Set([
368
+ "http",
369
+ "https",
370
+ "ftp",
371
+ "gopher",
372
+ "news",
373
+ "mailto",
374
+ "mms"
375
+ ]);
376
+ var TRAILING_URL_SCHEME = /(?:^|[^A-Za-z0-9])(https?|ftp|gopher|news|mailto|mms)$/;
377
+ var URL_SCHEME_PREFIX = /^(?:(?:https?|ftp|gopher|news|mms):\/\/|mailto:)/;
378
+ var URL_PATTERN = /^(?:(?:https?|ftp|gopher|news|mms):\/\/|mailto:)(?:[^ \\/"']*\/)*[^ \t\n\\/"']*[A-Za-z%0-9/?=&~_]/;
379
+ function startsWithUrlScheme(src, pos) {
380
+ return URL_SCHEME_PREFIX.test(src.slice(pos, pos + 10));
381
+ }
382
+ function startsWithUrl(src, pos) {
383
+ return URL_PATTERN.test(src.slice(pos, pos + 2048));
384
+ }
385
+
366
386
  // packages/parser/src/lexer/syntax-actions.ts
367
387
  function scanSimpleSyntaxToken(src, pos, isLineStart) {
368
388
  switch (src[pos]) {
@@ -399,7 +419,7 @@ function scanSimpleSyntaxToken(src, pos, isLineStart) {
399
419
  }
400
420
  }
401
421
  function scanStarToken(src, pos, isLineStart) {
402
- if (src[pos + 1] === "*") {
422
+ if (src[pos + 1] === "*" && !startsWithUrl(src, pos + 2)) {
403
423
  return token("BOLD_MARKER", "**");
404
424
  }
405
425
  return isLineStart ? token("LIST_BULLET", "*") : token("STAR", "*");
@@ -585,7 +605,13 @@ function scanTextToken(src, pos) {
585
605
  return token3("TEXT", char);
586
606
  }
587
607
  function scanCompactTextToken(src, pos) {
588
- const end = findCompactPlainTextRunEnd(src, pos);
608
+ let end = findCompactPlainTextRunEnd(src, pos);
609
+ if (end > pos && src[end] === ":") {
610
+ const match = TRAILING_URL_SCHEME.exec(src.slice(pos, end));
611
+ if (match) {
612
+ end -= match[1].length;
613
+ }
614
+ }
589
615
  return end > pos ? runToken3(src, pos, end, "TEXT") : null;
590
616
  }
591
617
  function token3(type, value) {
@@ -631,6 +657,8 @@ class Lexer {
631
657
  splitBlockClosePositions = new Set;
632
658
  lastNonWhitespaceType = null;
633
659
  blockOpenerDepth = 0;
660
+ rawTagBounds = null;
661
+ rawClosesExhausted = false;
634
662
  constructor(source, options = {}) {
635
663
  this.options = {
636
664
  trackPositions: options.trackPositions ?? true,
@@ -651,8 +679,8 @@ class Lexer {
651
679
  current() {
652
680
  return current(this.state);
653
681
  }
654
- findInvalidAnchorNameEnd() {
655
- return findInvalidAnchorNameEnd(this.state.source, this.state.pos);
682
+ findInvalidAnchorNameEnd(source) {
683
+ return findInvalidAnchorNameEnd(source, this.state.pos);
656
684
  }
657
685
  advance(n = 1) {
658
686
  return advance(this.state, n);
@@ -664,6 +692,18 @@ class Lexer {
664
692
  this.state.tokens.push(createLexerToken(this.state, type, value, this.options.trackPositions));
665
693
  this.lastNonWhitespaceType = updateLastNonWhitespaceType(this.lastNonWhitespaceType, type);
666
694
  this.blockOpenerDepth = nextBlockOpenerDepth(this.blockOpenerDepth, type);
695
+ if (this.rawTagBounds === null && !this.rawClosesExhausted && (value.toLowerCase() === "button" || value.toLowerCase() === "social") && this.state.tokens.at(-2)?.type === "BLOCK_OPEN" && /\s/.test(this.current())) {
696
+ const close = this.state.source.indexOf("]]", this.state.pos);
697
+ if (close < 0) {
698
+ this.rawClosesExhausted = true;
699
+ } else {
700
+ this.rawTagBounds = {
701
+ source: this.state.source.slice(0, close),
702
+ close,
703
+ outerDepth: this.blockOpenerDepth - 1
704
+ };
705
+ }
706
+ }
667
707
  }
668
708
  emitTokenAction(action) {
669
709
  advanceByToken(this.state, action.type, action.length, action.value);
@@ -681,7 +721,14 @@ class Lexer {
681
721
  scanToken() {
682
722
  const char = this.current();
683
723
  const isLineStart = isSyntaxLineStart(this.state);
684
- const src = this.state.source;
724
+ const bounds = this.rawTagBounds;
725
+ const src = bounds?.source ?? this.state.source;
726
+ if (bounds && this.state.pos === bounds.close) {
727
+ this.emitTokenAction({ type: "BLOCK_CLOSE", value: "]]", length: 2 });
728
+ this.blockOpenerDepth = bounds.outerDepth;
729
+ this.rawTagBounds = null;
730
+ return;
731
+ }
685
732
  const spacingAction = scanSpacingToken(src, this.state.pos);
686
733
  if (spacingAction) {
687
734
  this.emitTokenAction(limitBlockquotePrefixSpace(spacingAction, this.state.tokens.at(-1)));
@@ -694,7 +741,7 @@ class Lexer {
694
741
  lineStart: isLineStart,
695
742
  physicalLineStart: this.state.lineStart,
696
743
  splitBlockClose: this.splitBlockClosePositions.has(this.state.pos),
697
- findInvalidAnchorNameEnd: () => this.findInvalidAnchorNameEnd()
744
+ findInvalidAnchorNameEnd: () => this.findInvalidAnchorNameEnd(src)
698
745
  });
699
746
  if (punctuation.handled) {
700
747
  if (punctuation.clearSplitBlockCloseAt !== undefined) {
@@ -709,7 +756,7 @@ class Lexer {
709
756
  if (char === '"') {
710
757
  const lastNonWs = this.lastNonWhitespaceTokenType();
711
758
  if (this.blockOpenerDepth > 0 && lastNonWs === "EQUALS") {
712
- this.addToken("QUOTED_STRING", scanQuotedString(this.state));
759
+ this.addToken("QUOTED_STRING", scanQuotedString(this.state, src.length));
713
760
  return;
714
761
  }
715
762
  this.advance();
@@ -897,7 +944,7 @@ function parseBlockItem(ctx) {
897
944
  }
898
945
  }
899
946
  const fallback = ctx.blockFallbackRule.parse(ctx);
900
- if (fallback.success && fallback.elements.length > 0) {
947
+ if (fallback.success) {
901
948
  return { elements: fallback.elements, consumed: fallback.consumed };
902
949
  }
903
950
  return { elements: [], consumed: 1 };
@@ -945,7 +992,8 @@ function parseBlocksUntil(ctx, closeCondition, options) {
945
992
  blockCtx.pos = pos;
946
993
  blockCtx.scope = blockScope;
947
994
  const result = parseBlockItem(blockCtx);
948
- elements.push(...result.elements);
995
+ for (const element of result.elements)
996
+ elements.push(element);
949
997
  consumed += result.consumed;
950
998
  pos += result.consumed;
951
999
  }
@@ -972,6 +1020,42 @@ function getExcludedBlockRules(blockRules, excluded) {
972
1020
  byExcluded.set(excluded, filtered);
973
1021
  return filtered;
974
1022
  }
1023
+ // packages/parser/src/parser/rules/inline/parsing/automatic-line-break.ts
1024
+ var origins = new WeakMap;
1025
+ function createAutomaticLineBreak(token5) {
1026
+ const element = { element: "line-break" };
1027
+ origins.set(element, token5);
1028
+ return element;
1029
+ }
1030
+ function precedingSingleNewline(ctx) {
1031
+ let pos = ctx.pos - 1;
1032
+ while (ctx.tokens[pos]?.type === "WHITESPACE")
1033
+ pos--;
1034
+ const newline = ctx.tokens[pos];
1035
+ if (newline?.type !== "NEWLINE")
1036
+ return;
1037
+ pos--;
1038
+ while (ctx.tokens[pos]?.type === "WHITESPACE")
1039
+ pos--;
1040
+ if (ctx.tokens[pos]?.type === "NEWLINE")
1041
+ return;
1042
+ return newline;
1043
+ }
1044
+ function stripAutomaticLineBreak(elements, token5) {
1045
+ if (!token5)
1046
+ return;
1047
+ let index = elements.length - 1;
1048
+ while (index >= 0) {
1049
+ const element2 = elements[index];
1050
+ if (element2?.element !== "text" || !/^[ \t]*$/.test(element2.data))
1051
+ break;
1052
+ index--;
1053
+ }
1054
+ const element = elements[index];
1055
+ if (element && origins.get(element) === token5)
1056
+ elements.length = index;
1057
+ }
1058
+
975
1059
  // packages/parser/src/parser/rules/inline/parsing/rules.ts
976
1060
  function canApplyInlineRule(rule, token5) {
977
1061
  if (rule.startTokens.length === 0) {
@@ -994,18 +1078,479 @@ function getCandidateInlineRules(inlineRules, tokenType) {
994
1078
  byType.set(tokenType, candidates);
995
1079
  return candidates;
996
1080
  }
1081
+ // packages/parser/src/parser/rules/inline/parsing/raw-tag.ts
1082
+ var unclosedRanges = new WeakMap;
1083
+ function findRawTagClose(tokens, start, end) {
1084
+ const unclosed = unclosedRanges.get(tokens);
1085
+ if (unclosed && start >= unclosed.start && end <= unclosed.end)
1086
+ return null;
1087
+ for (let close = start;close < end; close++) {
1088
+ if (tokens[close]?.type === "BLOCK_CLOSE")
1089
+ return close;
1090
+ if (!tokens[close] || tokens[close]?.type === "EOF")
1091
+ break;
1092
+ }
1093
+ unclosedRanges.set(tokens, { start, end });
1094
+ return null;
1095
+ }
1096
+
1097
+ // packages/parser/src/parser/rules/inline/button/attributes.ts
1098
+ function parseButtonAttributes(source) {
1099
+ const sections = source.trim().split('="');
1100
+ const attrs = {};
1101
+ let key = sections[0].trim();
1102
+ for (const section of sections.slice(1)) {
1103
+ const quote = section.lastIndexOf('"');
1104
+ if (quote < 0)
1105
+ continue;
1106
+ if (key === "text" || key === "class" || key === "style") {
1107
+ attrs[key] = section.slice(0, quote).replace(/\\([\s\S]|$)/g, (_match, char) => char === "0" ? "\x00" : char);
1108
+ }
1109
+ key = section.slice(quote + 1).trim();
1110
+ }
1111
+ return attrs;
1112
+ }
1113
+
1114
+ // packages/parser/src/parser/rules/inline/button/syntax.ts
1115
+ function parseButtonSyntax(ctx, start, end) {
1116
+ const tokens = ctx.tokens;
1117
+ if (tokens[start]?.type !== "BLOCK_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "button")
1118
+ return null;
1119
+ let pos = start + 2;
1120
+ const skipSpace = () => {
1121
+ const before = pos;
1122
+ while (pos < end && /^\s+$/.test(tokens[pos]?.value ?? ""))
1123
+ pos++;
1124
+ return pos > before;
1125
+ };
1126
+ if (!skipSpace())
1127
+ return null;
1128
+ let action = "";
1129
+ while (pos < end && tokens[pos]?.type !== "BLOCK_CLOSE" && !/^\s+$/.test(tokens[pos]?.value ?? "")) {
1130
+ const part = tokens[pos]?.value ?? "";
1131
+ if (!/^[a-z0-9_-]+$/i.test(part))
1132
+ return null;
1133
+ action += part;
1134
+ pos++;
1135
+ }
1136
+ if (!action)
1137
+ return null;
1138
+ action = action.replaceAll("_", "-");
1139
+ skipSpace();
1140
+ const close = findRawTagClose(tokens, pos, end);
1141
+ if (close === null)
1142
+ return null;
1143
+ const attrs = parseButtonAttributes(tokens.slice(pos, close).map((token5) => token5.value).join(""));
1144
+ const value = (key) => attrs[key] && attrs[key] !== "0" ? attrs[key] : null;
1145
+ const attributes = {};
1146
+ for (const name of ["class", "style"]) {
1147
+ const attr = value(name);
1148
+ if (attr !== null)
1149
+ attributes[name] = attr;
1150
+ }
1151
+ return { data: { action, text: value("text"), attributes }, end: close + 1 };
1152
+ }
1153
+
1154
+ // packages/parser/src/parser/rules/inline/social/syntax.ts
1155
+ var invalidBodies = new WeakMap;
1156
+ function parseSocialSyntax(ctx, start, end) {
1157
+ const tokens = ctx.tokens;
1158
+ if (tokens[start]?.type !== "BLOCK_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "social")
1159
+ return null;
1160
+ const bodyStart = start + 2;
1161
+ if (tokens[bodyStart]?.type !== "BLOCK_CLOSE" && !/^\s+$/.test(tokens[bodyStart]?.value ?? ""))
1162
+ return null;
1163
+ const invalid = invalidBodies.get(tokens);
1164
+ if (invalid && bodyStart >= invalid.start && bodyStart <= invalid.bracket)
1165
+ return null;
1166
+ const close = findRawTagClose(tokens, bodyStart, end);
1167
+ if (close === null)
1168
+ return null;
1169
+ for (let pos = bodyStart;pos < close; pos++) {
1170
+ if (tokens[pos].value.includes("]")) {
1171
+ invalidBodies.set(tokens, { start: bodyStart, bracket: pos });
1172
+ return null;
1173
+ }
1174
+ }
1175
+ const body = tokens.slice(bodyStart, close).map((token5) => token5.value).join("");
1176
+ const sites = body.trim() ? body.split(",").map((site) => site.trim().toLowerCase()).filter(Boolean) : null;
1177
+ return { data: { sites }, end: close + 1 };
1178
+ }
1179
+
1180
+ // packages/parser/src/parser/rules/block/parsing/attributes/names.ts
1181
+ function consumeAttributeName(ctx, startPos, startConsumed, startName, options) {
1182
+ if (startName === "_" && isAttributeWordToken(ctx.tokens[startPos])) {
1183
+ startName += ctx.tokens[startPos]?.value ?? "";
1184
+ startPos++;
1185
+ startConsumed++;
1186
+ }
1187
+ return options.strikeHyphens ? consumeRawNameSuffix(ctx, startPos, startConsumed, startName, options.hyphenatedNames) : consumeSafeNameSuffix(ctx, startPos, startConsumed, startName);
1188
+ }
1189
+ function isAttributeNameToken(token5) {
1190
+ return isAttributeWordToken(token5) || token5?.type === "UNDERSCORE";
1191
+ }
1192
+ function consumeSafeNameSuffix(ctx, startPos, startConsumed, startName) {
1193
+ let name = startName;
1194
+ let pos = startPos;
1195
+ let consumed = startConsumed;
1196
+ while (isAttributeNameSeparator(ctx.tokens[pos]) && isAttributeWordToken(ctx.tokens[pos + 1])) {
1197
+ name += ctx.tokens[pos]?.value ?? "";
1198
+ pos++;
1199
+ consumed++;
1200
+ name += ctx.tokens[pos]?.value ?? "";
1201
+ pos++;
1202
+ consumed++;
1203
+ }
1204
+ return { name, pos, consumed };
1205
+ }
1206
+ function consumeRawNameSuffix(ctx, startPos, startConsumed, startName, hyphenatedNames) {
1207
+ let name = startName;
1208
+ let pos = startPos;
1209
+ let consumed = startConsumed;
1210
+ while (isAttributeNameSeparator(ctx.tokens[pos])) {
1211
+ while (isAttributeNameSeparator(ctx.tokens[pos])) {
1212
+ if (hyphenatedNames || ctx.tokens[pos]?.type === "UNDERSCORE") {
1213
+ name += ctx.tokens[pos]?.value ?? "-";
1214
+ }
1215
+ pos++;
1216
+ consumed++;
1217
+ }
1218
+ if (!isAttributeWordToken(ctx.tokens[pos])) {
1219
+ break;
1220
+ }
1221
+ if (hyphenatedNames || name.endsWith("_")) {
1222
+ name += ctx.tokens[pos]?.value ?? "";
1223
+ }
1224
+ pos++;
1225
+ consumed++;
1226
+ }
1227
+ return { name, pos, consumed };
1228
+ }
1229
+ function isAttributeWordToken(token5) {
1230
+ return token5?.type === "TEXT" || token5?.type === "IDENTIFIER";
1231
+ }
1232
+ function isAttributeNameSeparator(token5) {
1233
+ return token5?.type === "TEXT" && token5.value === "-" || token5?.type === "STRIKE_MARKER" || token5?.type === "UNDERSCORE";
1234
+ }
1235
+
1236
+ // packages/parser/src/parser/rules/block/parsing/attributes/values.ts
1237
+ function consumeAttributeValue(token5) {
1238
+ if (!token5) {
1239
+ return null;
1240
+ }
1241
+ if (token5.type === "QUOTED_STRING") {
1242
+ return { value: stripQuotes(token5.value) };
1243
+ }
1244
+ if (isAttributeNameToken(token5)) {
1245
+ return { value: token5.value };
1246
+ }
1247
+ return null;
1248
+ }
1249
+ function stripQuotes(value) {
1250
+ return value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
1251
+ }
1252
+
1253
+ // packages/parser/src/parser/rules/block/parsing/attributes/scanner.ts
1254
+ function scanAttributes(ctx, startPos, options) {
1255
+ const attrs = {};
1256
+ let pos = startPos;
1257
+ let consumed = 0;
1258
+ while (pos < ctx.tokens.length) {
1259
+ const token5 = ctx.tokens[pos];
1260
+ if (!token5 || isAttributeTerminator(token5)) {
1261
+ break;
1262
+ }
1263
+ if (token5.type === "WHITESPACE") {
1264
+ pos++;
1265
+ consumed++;
1266
+ continue;
1267
+ }
1268
+ if (!isAttributeNameToken(token5)) {
1269
+ pos++;
1270
+ consumed++;
1271
+ continue;
1272
+ }
1273
+ let name = token5.value;
1274
+ pos++;
1275
+ consumed++;
1276
+ const nameResult = consumeAttributeName(ctx, pos, consumed, name, options);
1277
+ pos = nameResult.pos;
1278
+ consumed = nameResult.consumed;
1279
+ name = nameResult.name.toLowerCase();
1280
+ if (ctx.tokens[pos]?.type !== "EQUALS") {
1281
+ attrs[name] = "true";
1282
+ continue;
1283
+ }
1284
+ pos++;
1285
+ consumed++;
1286
+ const valueResult = consumeAttributeValue(ctx.tokens[pos]);
1287
+ if (!valueResult) {
1288
+ continue;
1289
+ }
1290
+ attrs[name] = valueResult.value;
1291
+ pos++;
1292
+ consumed++;
1293
+ }
1294
+ return { attrs, consumed };
1295
+ }
1296
+ function isAttributeTerminator(token5) {
1297
+ return token5.type === "BLOCK_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF";
1298
+ }
1299
+
1300
+ // packages/parser/src/parser/rules/block/parsing/attributes/index.ts
1301
+ function parseAttributes(ctx, startPos) {
1302
+ const result = scanAttributes(ctx, startPos, { hyphenatedNames: true, strikeHyphens: false });
1303
+ return { attrs: filterUnsafeAttributes(result.attrs), consumed: result.consumed };
1304
+ }
1305
+ function parseAttributesRaw(ctx, startPos, hyphenatedNames = true) {
1306
+ return scanAttributes(ctx, startPos, { hyphenatedNames, strikeHyphens: true });
1307
+ }
1308
+
1309
+ // packages/parser/src/parser/rules/inline/date/syntax.ts
1310
+ function parseDateSyntax(ctx, start, end) {
1311
+ const tokens = ctx.tokens;
1312
+ if (tokens[start]?.type !== "BLOCK_OPEN" || tokens[start + 1]?.value !== "date")
1313
+ return null;
1314
+ let pos = start + 2;
1315
+ const skipSpace = () => {
1316
+ const before = pos;
1317
+ while (pos < end && /^(?:\s+)$/.test(tokens[pos]?.value ?? ""))
1318
+ pos++;
1319
+ return pos > before;
1320
+ };
1321
+ if (!skipSpace())
1322
+ return null;
1323
+ const value = tokens[pos]?.value ?? "";
1324
+ if (pos >= end || !/^\d+$/.test(value))
1325
+ return null;
1326
+ const timestamp = Number(value);
1327
+ if (!Number.isSafeInteger(timestamp) || timestamp > 8640000000000)
1328
+ return null;
1329
+ pos++;
1330
+ const separator = skipSpace();
1331
+ if (tokens[pos]?.type !== "BLOCK_CLOSE" && !separator)
1332
+ return null;
1333
+ let close = pos;
1334
+ for (;close < end; close++) {
1335
+ const token5 = tokens[close];
1336
+ if (!token5 || token5.type === "EOF" || token5.type === "BLOCK_OPEN" || /[\r\n]/.test(token5.value))
1337
+ return null;
1338
+ if (token5.type === "BLOCK_CLOSE")
1339
+ break;
1340
+ }
1341
+ if (close >= end)
1342
+ return null;
1343
+ const attributes = parseAttributesRaw(ctx, pos).attrs;
1344
+ const format = attributes.format || null;
1345
+ return {
1346
+ data: {
1347
+ value: { timestamp, timezone: "UTC" },
1348
+ format,
1349
+ hover: format?.split("|").slice(1).includes("agohover") ?? false
1350
+ },
1351
+ end: close + 1
1352
+ };
1353
+ }
1354
+
1355
+ // packages/parser/src/parser/rules/inline/email/scan.ts
1356
+ function isDomainChar(code) {
1357
+ return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 45;
1358
+ }
1359
+ function isLocalChar(code) {
1360
+ return isDomainChar(code) || code === 95;
1361
+ }
1362
+ function scanEmail(source, start, commentEnd) {
1363
+ const comments = [];
1364
+ let pos = start;
1365
+ const skipComments = () => {
1366
+ let end2 = commentEnd(pos);
1367
+ while (end2 > pos) {
1368
+ comments.push({ start: pos, end: end2 });
1369
+ pos = end2;
1370
+ end2 = commentEnd(pos);
1371
+ }
1372
+ };
1373
+ let localPart = false;
1374
+ while (pos < source.length) {
1375
+ skipComments();
1376
+ if (isLocalChar(source.charCodeAt(pos))) {
1377
+ localPart = true;
1378
+ pos++;
1379
+ } else if (source[pos] === "." && localPart) {
1380
+ localPart = false;
1381
+ pos++;
1382
+ } else
1383
+ break;
1384
+ }
1385
+ const localEnd = pos;
1386
+ if (!localPart || source[pos] !== "@")
1387
+ return { localEnd, comments };
1388
+ pos++;
1389
+ let domainPart = false;
1390
+ let dots = 0;
1391
+ let end = -1;
1392
+ while (pos < source.length) {
1393
+ skipComments();
1394
+ if (isDomainChar(source.charCodeAt(pos))) {
1395
+ domainPart = true;
1396
+ pos++;
1397
+ if (dots > 0)
1398
+ end = pos;
1399
+ } else if (source[pos] === "." && domainPart) {
1400
+ domainPart = false;
1401
+ dots++;
1402
+ pos++;
1403
+ } else
1404
+ break;
1405
+ }
1406
+ if (end === -1)
1407
+ return { localEnd, comments };
1408
+ const parts = [];
1409
+ let copied = start;
1410
+ for (const comment of comments) {
1411
+ if (comment.start >= end)
1412
+ break;
1413
+ parts.push(source.slice(copied, comment.start));
1414
+ copied = comment.end;
1415
+ }
1416
+ parts.push(source.slice(copied, end));
1417
+ return { end, address: parts.join(""), localEnd, comments };
1418
+ }
1419
+
1420
+ // packages/parser/src/parser/rules/inline/email/candidates.ts
1421
+ var caches = new WeakMap;
1422
+ var EMAIL_START_TOKENS = [
1423
+ "TEXT",
1424
+ "IDENTIFIER",
1425
+ "UNDERSCORE",
1426
+ "UNDERLINE_MARKER",
1427
+ "STRIKE_MARKER"
1428
+ ];
1429
+ function getCache(tokens) {
1430
+ let cache = caches.get(tokens);
1431
+ if (cache)
1432
+ return cache;
1433
+ const offsets = [0];
1434
+ const values = [];
1435
+ for (const token5 of tokens) {
1436
+ values.push(token5.value);
1437
+ offsets.push(offsets.at(-1) + token5.value.length);
1438
+ }
1439
+ cache = {
1440
+ source: values.join(""),
1441
+ offsets,
1442
+ candidates: new Map,
1443
+ commentEnds: new Map,
1444
+ unclosedComment: Infinity
1445
+ };
1446
+ caches.set(tokens, cache);
1447
+ return cache;
1448
+ }
1449
+ function commentEnd(cache, pos) {
1450
+ const opener = cache.source[pos] === `
1451
+ ` ? pos + 1 : pos;
1452
+ if (!cache.source.startsWith("[!--", opener))
1453
+ return pos;
1454
+ const cached = cache.commentEnds.get(pos);
1455
+ if (cached !== undefined)
1456
+ return cached;
1457
+ const close = opener >= cache.unclosedComment ? -1 : cache.source.indexOf("--]", opener + 4);
1458
+ if (close === -1)
1459
+ cache.unclosedComment = Math.min(cache.unclosedComment, opener);
1460
+ const end = close === -1 ? pos : close + 3;
1461
+ cache.commentEnds.set(pos, end);
1462
+ return end;
1463
+ }
1464
+ function getEmailCandidate(tokens, index) {
1465
+ const token5 = tokens[index];
1466
+ if (!token5 || !EMAIL_START_TOKENS.includes(token5.type))
1467
+ return null;
1468
+ const cache = getCache(tokens);
1469
+ if (cache.candidates.has(index))
1470
+ return cache.candidates.get(index);
1471
+ const value = tokens[index]?.value ?? "";
1472
+ let suffix = value.length;
1473
+ while (suffix > 0 && (isLocalChar(value.charCodeAt(suffix - 1)) || value[suffix - 1] === "."))
1474
+ suffix--;
1475
+ const doubled = value.lastIndexOf("..");
1476
+ if (doubled >= suffix)
1477
+ suffix = doubled + 2;
1478
+ while (value[suffix] === ".")
1479
+ suffix++;
1480
+ if (suffix >= value.length || !isLocalChar(value.charCodeAt(suffix))) {
1481
+ cache.candidates.set(index, null);
1482
+ return null;
1483
+ }
1484
+ const start = cache.offsets[index] + suffix;
1485
+ const scanned = scanEmail(cache.source, start, (pos) => commentEnd(cache, pos));
1486
+ if (scanned.end === undefined) {
1487
+ cache.candidates.set(index, null);
1488
+ let comment = 0;
1489
+ for (let i = index + 1;i < tokens.length && cache.offsets[i + 1] <= scanned.localEnd; i++) {
1490
+ const offset = cache.offsets[i];
1491
+ while (scanned.comments[comment] && scanned.comments[comment].end <= offset)
1492
+ comment++;
1493
+ const region = scanned.comments[comment];
1494
+ if (!region || offset < region.start)
1495
+ cache.candidates.set(i, null);
1496
+ }
1497
+ return null;
1498
+ }
1499
+ let endToken = index;
1500
+ while (cache.offsets[endToken + 1] < scanned.end)
1501
+ endToken++;
1502
+ const candidate = { start, end: scanned.end, endToken, address: scanned.address };
1503
+ cache.candidates.set(index, candidate);
1504
+ return candidate;
1505
+ }
1506
+ function getEmailGroup(tokens, index, limit) {
1507
+ const first = getEmailCandidate(tokens, index);
1508
+ if (!first || first.endToken >= limit)
1509
+ return null;
1510
+ const candidates = [first];
1511
+ let last = first;
1512
+ while (last.endToken > index) {
1513
+ const next = getEmailCandidate(tokens, last.endToken);
1514
+ if (!next || next.start < last.end || next.endToken >= limit)
1515
+ break;
1516
+ candidates.push(next);
1517
+ last = next;
1518
+ }
1519
+ const cache = getCache(tokens);
1520
+ return {
1521
+ candidates,
1522
+ source: cache.source,
1523
+ start: cache.offsets[index],
1524
+ end: cache.offsets[last.endToken + 1],
1525
+ endToken: last.endToken + 1
1526
+ };
1527
+ }
1528
+ function emailRegionEnd(tokens, index, limit) {
1529
+ return getEmailGroup(tokens, index, limit)?.endToken ?? index;
1530
+ }
1531
+
997
1532
  // packages/parser/src/parser/rules/inline/raw/end.ts
1533
+ var unclosedAngleRanges = new WeakMap;
998
1534
  function rawRegionEnd(tokens, start, end) {
999
1535
  const type = tokens[start]?.type;
1000
1536
  const close = type === "RAW_OPEN" ? "RAW_OPEN" : type === "RAW_BLOCK_OPEN" ? "RAW_BLOCK_CLOSE" : null;
1001
1537
  if (!close)
1002
1538
  return start;
1539
+ const cached = type === "RAW_BLOCK_OPEN" ? unclosedAngleRanges.get(tokens) : undefined;
1540
+ if (cached && start >= cached.start && start < cached.end && (cached.lineEnd || end <= cached.end)) {
1541
+ return start;
1542
+ }
1003
1543
  for (let pos = start + 1;pos < end; pos++) {
1004
- if (tokens[pos]?.type === "NEWLINE" || tokens[pos]?.type === "EOF")
1005
- break;
1544
+ if (tokens[pos]?.type === "NEWLINE" || tokens[pos]?.type === "EOF") {
1545
+ if (type === "RAW_BLOCK_OPEN")
1546
+ unclosedAngleRanges.set(tokens, { start, end: pos, lineEnd: true });
1547
+ return start;
1548
+ }
1006
1549
  if (tokens[pos]?.type === close)
1007
1550
  return pos + 1;
1008
1551
  }
1552
+ if (type === "RAW_BLOCK_OPEN")
1553
+ unclosedAngleRanges.set(tokens, { start, end, lineEnd: false });
1009
1554
  return start;
1010
1555
  }
1011
1556
  function protectedInlineRegionEnd(tokens, start, end) {
@@ -1024,7 +1569,7 @@ var MIN_INLINE_TEXT_RUN_LENGTH = 32;
1024
1569
  var MIN_INLINE_TEXT_RUN_DOCUMENT_TOKENS = 1e5;
1025
1570
  function collectLongPlainTextRun(ctx, startPos, endType) {
1026
1571
  const firstToken = ctx.tokens[startPos];
1027
- if (firstToken?.type === "TEXT" && firstToken.value.length >= MIN_INLINE_TEXT_RUN_LENGTH && firstToken.value !== "(") {
1572
+ if (!getEmailCandidate(ctx.tokens, startPos) && firstToken?.type === "TEXT" && firstToken.value.length >= MIN_INLINE_TEXT_RUN_LENGTH && firstToken.value !== "(") {
1028
1573
  return { value: firstToken.value, consumed: 1 };
1029
1574
  }
1030
1575
  let pos = startPos;
@@ -1046,10 +1591,10 @@ function collectLongPlainTextRun(ctx, startPos, endType) {
1046
1591
  }
1047
1592
  function isPlainTextRunToken(ctx, pos) {
1048
1593
  const token5 = ctx.tokens[pos];
1049
- if (!token5)
1594
+ if (!token5 || getEmailCandidate(ctx.tokens, pos))
1050
1595
  return false;
1051
1596
  if (token5.type === "IDENTIFIER") {
1052
- return true;
1597
+ return !(URL_SCHEME_NAMES.has(token5.value) && ctx.tokens[pos + 1]?.type === "COLON");
1053
1598
  }
1054
1599
  if (token5.type === "WHITESPACE") {
1055
1600
  const next = ctx.tokens[pos + 1];
@@ -1198,6 +1743,10 @@ var BLOCK_START_TOKENS = [
1198
1743
  "HEADING_MARKER",
1199
1744
  "HR_MARKER",
1200
1745
  "TABLE_MARKER",
1746
+ "TABLE_HEADER",
1747
+ "TABLE_LEFT",
1748
+ "TABLE_CENTER",
1749
+ "TABLE_RIGHT",
1201
1750
  "COLON",
1202
1751
  "BLOCK_OPEN",
1203
1752
  "BLOCK_END_OPEN",
@@ -1275,12 +1824,725 @@ var INDENT_ACCEPTING_BLOCK_NAMES = new Set([
1275
1824
  "tabs"
1276
1825
  ]);
1277
1826
 
1278
- // packages/parser/src/parser/rules/inline/parsing/block-boundary.ts
1279
- function isExcludedBlockToken(ctx, tokenPos) {
1280
- const excluded = ctx.scope.excludedBlockNames;
1281
- if (!excluded?.size)
1282
- return false;
1283
- const token5 = ctx.tokens[tokenPos];
1827
+ // packages/parser/src/parser/rules/block/code/open.ts
1828
+ var attributeRanges = new WeakMap;
1829
+ function findAttributesEnd(tokens, start) {
1830
+ const cached = attributeRanges.get(tokens);
1831
+ if (cached && start >= cached.start && start <= cached.end)
1832
+ return cached.end;
1833
+ let end = start;
1834
+ while (end < tokens.length) {
1835
+ const type = tokens[end]?.type;
1836
+ if (type === "BLOCK_CLOSE" || type === "NEWLINE" || type === "EOF")
1837
+ break;
1838
+ end++;
1839
+ }
1840
+ attributeRanges.set(tokens, { start, end });
1841
+ return end;
1842
+ }
1843
+ function findCodeOpen(tokens, start) {
1844
+ const name = tokens[start + 1];
1845
+ if (tokens[start]?.type !== "BLOCK_OPEN" || name?.type !== "IDENTIFIER" && name?.type !== "TEXT" || name.value.toLowerCase() !== "code" || tokens[start + 2]?.type === "UNDERSCORE")
1846
+ return null;
1847
+ const attributesEnd = findAttributesEnd(tokens, start + 2);
1848
+ const repaired = tokens[attributesEnd]?.type !== "BLOCK_CLOSE";
1849
+ const last = tokens[attributesEnd - 1];
1850
+ if (repaired && (last?.type !== "QUOTED_STRING" || !last.value.includes("]]")))
1851
+ return null;
1852
+ let bodyStart = attributesEnd + (repaired ? 0 : 1);
1853
+ if (tokens[bodyStart]?.type === "NEWLINE")
1854
+ bodyStart++;
1855
+ return {
1856
+ attributesEnd,
1857
+ bodyStart,
1858
+ repaired,
1859
+ closingSwallowed: repaired && last.value.includes("[[/code]]")
1860
+ };
1861
+ }
1862
+
1863
+ // packages/parser/src/parser/rules/block/code/boundary.ts
1864
+ var caches2 = new WeakMap;
1865
+ function codeCloseEnd(tokens, start) {
1866
+ if (tokens[start]?.type !== "BLOCK_END_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "code")
1867
+ return start;
1868
+ let end = start + 2;
1869
+ while (tokens[end]?.type === "WHITESPACE")
1870
+ end++;
1871
+ return tokens[end]?.type === "BLOCK_CLOSE" ? end + 1 : start;
1872
+ }
1873
+ function nestedCodeBodyStart(tokens, start) {
1874
+ if (tokens[start]?.type !== "BLOCK_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "code")
1875
+ return start;
1876
+ for (let pos = start + 2;pos < tokens.length; pos++) {
1877
+ const type = tokens[pos]?.type;
1878
+ if (type === "NEWLINE" || type === "EOF" || type === "BLOCK_OPEN")
1879
+ return start;
1880
+ if (type === "BLOCK_CLOSE") {
1881
+ const open = findCodeOpen(tokens, start);
1882
+ return open && !open.repaired ? open.bodyStart : start;
1883
+ }
1884
+ }
1885
+ return start;
1886
+ }
1887
+ function findCodeBodyBounds(tokens, start) {
1888
+ let cache = caches2.get(tokens);
1889
+ if (!cache) {
1890
+ cache = new Map;
1891
+ caches2.set(tokens, cache);
1892
+ }
1893
+ const cached = cache.get(start);
1894
+ if (cached)
1895
+ return cached;
1896
+ const stack = [start];
1897
+ let pos = start;
1898
+ while (pos < tokens.length && tokens[pos]?.type !== "EOF") {
1899
+ const rawEnd = rawRegionEnd(tokens, pos, tokens.length);
1900
+ if (rawEnd > pos) {
1901
+ pos = rawEnd;
1902
+ continue;
1903
+ }
1904
+ const end = codeCloseEnd(tokens, pos);
1905
+ if (end > pos) {
1906
+ const result2 = { closeStart: pos, end, foundClose: true };
1907
+ cache.set(stack.pop(), result2);
1908
+ if (stack.length === 0)
1909
+ return result2;
1910
+ pos = end;
1911
+ continue;
1912
+ }
1913
+ const bodyStart = nestedCodeBodyStart(tokens, pos);
1914
+ if (bodyStart > pos) {
1915
+ const child = cache.get(bodyStart);
1916
+ if (child) {
1917
+ pos = child.end;
1918
+ if (!child.foundClose)
1919
+ break;
1920
+ } else {
1921
+ stack.push(bodyStart);
1922
+ pos = bodyStart;
1923
+ }
1924
+ continue;
1925
+ }
1926
+ pos++;
1927
+ }
1928
+ const result = { closeStart: pos, end: pos, foundClose: false };
1929
+ for (const bodyStart of stack)
1930
+ cache.set(bodyStart, result);
1931
+ return result;
1932
+ }
1933
+
1934
+ // packages/parser/src/parser/rules/block/math/boundary.ts
1935
+ var nameRanges = new WeakMap;
1936
+ var bodyCaches = new WeakMap;
1937
+ function findNameEnd(tokens, start) {
1938
+ const cached = nameRanges.get(tokens);
1939
+ if (cached && start >= cached.start && start <= cached.end)
1940
+ return cached.end;
1941
+ let end = start;
1942
+ while (end < tokens.length) {
1943
+ const type = tokens[end]?.type;
1944
+ if (type === "BLOCK_CLOSE" || type === "WHITESPACE" || type === "NEWLINE" || type === "EOF")
1945
+ break;
1946
+ end++;
1947
+ }
1948
+ nameRanges.set(tokens, { start, end });
1949
+ return end;
1950
+ }
1951
+ function findMathOpen(tokens, start) {
1952
+ const name = tokens[start + 1];
1953
+ if (tokens[start]?.type !== "BLOCK_OPEN" || name?.type !== "IDENTIFIER" && name?.type !== "TEXT" || name.value.toLowerCase() !== "math" || tokens[start + 2]?.type === "UNDERSCORE")
1954
+ return null;
1955
+ let pos = start + 2;
1956
+ while (tokens[pos]?.type === "WHITESPACE")
1957
+ pos++;
1958
+ const nameStart = pos;
1959
+ if (tokens[pos]?.type === "IDENTIFIER" || tokens[pos]?.type === "TEXT") {
1960
+ pos = findNameEnd(tokens, pos);
1961
+ }
1962
+ const nameEnd = pos;
1963
+ while (tokens[pos]?.type === "WHITESPACE")
1964
+ pos++;
1965
+ if (tokens[pos]?.type !== "BLOCK_CLOSE")
1966
+ return null;
1967
+ pos++;
1968
+ if (tokens[pos]?.type === "NEWLINE")
1969
+ pos++;
1970
+ return { nameStart, nameEnd, bodyStart: pos };
1971
+ }
1972
+ function mathCloseEnd(tokens, start) {
1973
+ if (tokens[start]?.type !== "BLOCK_END_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "math" || tokens[start + 2]?.type !== "BLOCK_CLOSE")
1974
+ return start;
1975
+ const end = start + 3;
1976
+ return tokens[end]?.type === "NEWLINE" ? end + 1 : end;
1977
+ }
1978
+ function findMathBodyBounds(tokens, start) {
1979
+ let cache = bodyCaches.get(tokens);
1980
+ if (!cache) {
1981
+ cache = new Map;
1982
+ bodyCaches.set(tokens, cache);
1983
+ }
1984
+ const visited = [];
1985
+ let result = {
1986
+ closeStart: tokens.length,
1987
+ end: tokens.length,
1988
+ foundClose: false,
1989
+ hasContent: false
1990
+ };
1991
+ for (let pos = start;pos < tokens.length; pos++) {
1992
+ const cached = cache.get(pos);
1993
+ if (cached) {
1994
+ result = cached;
1995
+ break;
1996
+ }
1997
+ const end = mathCloseEnd(tokens, pos);
1998
+ if (end > pos) {
1999
+ result = { closeStart: pos, end, foundClose: true, hasContent: false };
2000
+ cache.set(pos, result);
2001
+ break;
2002
+ }
2003
+ visited.push(pos);
2004
+ }
2005
+ for (let i = visited.length - 1;i >= 0; i--) {
2006
+ const pos = visited[i];
2007
+ const token5 = tokens[pos];
2008
+ if (!result.hasContent && (token5.type === "BACKSLASH_BREAK" || token5.value.trim() !== "")) {
2009
+ result = { ...result, hasContent: true };
2010
+ }
2011
+ cache.set(pos, result);
2012
+ }
2013
+ return result;
2014
+ }
2015
+
2016
+ // packages/parser/src/parser/rules/opaque-probe.ts
2017
+ function createOpaqueProbe(ctx) {
2018
+ return {
2019
+ ...ctx,
2020
+ diagnostics: [],
2021
+ footnotes: [],
2022
+ tocEntries: [],
2023
+ codeBlocks: [],
2024
+ htmlBlocks: [],
2025
+ bibcites: [],
2026
+ scope: {
2027
+ ...ctx.scope,
2028
+ inlineEnd: undefined,
2029
+ tableFormatting: undefined,
2030
+ blockCloseCondition: () => true
2031
+ }
2032
+ };
2033
+ }
2034
+ function opaqueRuleEnd(probe, pos, rules, requireClosed = false) {
2035
+ const token5 = probe.tokens[pos];
2036
+ if (!token5)
2037
+ return pos;
2038
+ probe.pos = pos;
2039
+ for (const rule of rules) {
2040
+ if (!rule.startTokens.includes(token5.type))
2041
+ continue;
2042
+ if (rule.name === "code") {
2043
+ const open = findCodeOpen(probe.tokens, pos);
2044
+ if (!open)
2045
+ continue;
2046
+ if (open.closingSwallowed) {
2047
+ if (!requireClosed)
2048
+ return open.bodyStart;
2049
+ continue;
2050
+ }
2051
+ const bounds = findCodeBodyBounds(probe.tokens, open.bodyStart);
2052
+ if (!requireClosed || bounds.foundClose)
2053
+ return bounds.end;
2054
+ continue;
2055
+ }
2056
+ if (rule.name === "math") {
2057
+ const open = findMathOpen(probe.tokens, pos);
2058
+ if (!open)
2059
+ continue;
2060
+ const bounds = findMathBodyBounds(probe.tokens, open.bodyStart);
2061
+ if (bounds.hasContent && (!requireClosed || bounds.foundClose))
2062
+ return bounds.end;
2063
+ continue;
2064
+ }
2065
+ const result = rule.parse(probe);
2066
+ if (result.success)
2067
+ return pos + result.consumed;
2068
+ }
2069
+ return pos;
2070
+ }
2071
+
2072
+ // packages/parser/src/parser/rules/tokens.ts
2073
+ function currentToken(ctx) {
2074
+ return ctx.tokens[ctx.pos] ?? eofToken();
2075
+ }
2076
+ function eofToken() {
2077
+ return {
2078
+ type: "EOF",
2079
+ value: "",
2080
+ position: { start: { line: 0, column: 0, offset: 0 }, end: { line: 0, column: 0, offset: 0 } },
2081
+ lineStart: false
2082
+ };
2083
+ }
2084
+ function hasClosingMarkerBeforeNewline(ctx, markerType, markerValue) {
2085
+ let pos = ctx.pos;
2086
+ while (pos < (ctx.scope.inlineEnd ?? ctx.tokens.length)) {
2087
+ const token5 = ctx.tokens[pos];
2088
+ if (!token5 || token5.type === "NEWLINE" || token5.type === "EOF") {
2089
+ return false;
2090
+ }
2091
+ if (token5.type === markerType) {
2092
+ if (markerValue === undefined || token5.value === markerValue) {
2093
+ return true;
2094
+ }
2095
+ }
2096
+ pos++;
2097
+ }
2098
+ return false;
2099
+ }
2100
+ // packages/parser/src/parser/rules/block/code/attributes.ts
2101
+ function repairSwallowedCodeClose(ctx, pos, attrs) {
2102
+ const prevToken = ctx.tokens[pos - 1];
2103
+ if (prevToken?.type !== "QUOTED_STRING" || !prevToken.value.includes("]]")) {
2104
+ return null;
2105
+ }
2106
+ const rawValue = prevToken.value;
2107
+ const bracketIdx = rawValue.indexOf("]]");
2108
+ const truncatedValue = rawValue.startsWith('"') ? rawValue.slice(1, bracketIdx) : rawValue.slice(0, bracketIdx);
2109
+ for (const key of Object.keys(attrs)) {
2110
+ const stored = attrs[key];
2111
+ if (stored === rawValue || stored === rawValue.slice(1, -1) || stored === rawValue.slice(1)) {
2112
+ attrs[key] = truncatedValue;
2113
+ break;
2114
+ }
2115
+ }
2116
+ return {
2117
+ closingSwallowed: rawValue.includes("[[/code]]")
2118
+ };
2119
+ }
2120
+
2121
+ // packages/parser/src/parser/rules/block/paragraph/span-markers.ts
2122
+ function processCloseSpanMarkers(elements) {
2123
+ let result = null;
2124
+ for (let i = 0;i < elements.length; i++) {
2125
+ const elem = elements[i];
2126
+ if (!elem)
2127
+ continue;
2128
+ if (isCloseSpanMarker(elem)) {
2129
+ if (result === null) {
2130
+ result = elements.slice(0, i);
2131
+ }
2132
+ if (result.length > 0) {
2133
+ const spanContent = [...result];
2134
+ result.length = 0;
2135
+ result.push({
2136
+ element: "container",
2137
+ data: {
2138
+ type: "span",
2139
+ attributes: {},
2140
+ elements: spanContent
2141
+ }
2142
+ });
2143
+ }
2144
+ } else {
2145
+ result?.push(elem);
2146
+ }
2147
+ }
2148
+ return result ?? elements;
2149
+ }
2150
+ function isCloseSpanMarker(elem) {
2151
+ 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;
2152
+ }
2153
+
2154
+ // packages/parser/src/parser/rules/inline/parsing/preserved-line-break.ts
2155
+ function createPreservedTrailingLineBreak() {
2156
+ const lineBreak = { element: "line-break" };
2157
+ lineBreak._preservedTrailingBreak = true;
2158
+ return lineBreak;
2159
+ }
2160
+ function createPreservedLeadingLineBreak() {
2161
+ const lineBreak = { element: "line-break", _preservedLeadingBreak: true };
2162
+ return lineBreak;
2163
+ }
2164
+ function isPreservedLeadingLineBreak(element) {
2165
+ return element?.element === "line-break" && element._preservedLeadingBreak === true;
2166
+ }
2167
+
2168
+ // packages/parser/src/parser/rules/block/paragraph/normalize.ts
2169
+ function normalizeParagraphElements(source) {
2170
+ let elements = processCloseSpanMarkers(source);
2171
+ removeTrailingLineBreaks(elements);
2172
+ removeTrailingWhitespaceText(elements);
2173
+ elements = removeLeadingLineBreaks(elements);
2174
+ return elements;
2175
+ }
2176
+ function removeTrailingLineBreaks(elements) {
2177
+ while (elements.length > 0 && elements[elements.length - 1]?.element === "line-break") {
2178
+ const lastEl = elements[elements.length - 1];
2179
+ if (lastEl._preservedTrailingBreak) {
2180
+ delete lastEl._preservedTrailingBreak;
2181
+ break;
2182
+ }
2183
+ elements.pop();
2184
+ }
2185
+ }
2186
+ function removeTrailingWhitespaceText(elements) {
2187
+ while (elements.length > 0) {
2188
+ const last = elements[elements.length - 1];
2189
+ if (last?.element === "text" && "data" in last && typeof last.data === "string" && last.data.trim() === "") {
2190
+ elements.pop();
2191
+ } else {
2192
+ break;
2193
+ }
2194
+ }
2195
+ }
2196
+ function removeLeadingLineBreaks(elements) {
2197
+ let first = 0;
2198
+ while (first < elements.length && elements[first]?.element === "line-break" && !isPreservedLeadingLineBreak(elements[first])) {
2199
+ first++;
2200
+ }
2201
+ return first > 0 ? elements.slice(first) : elements;
2202
+ }
2203
+
2204
+ // packages/parser/src/parser/rules/block/code/content.ts
2205
+ function collectCodeContent(ctx, startPos, closingSwallowed) {
2206
+ if (closingSwallowed)
2207
+ return { contents: "", consumed: 0, foundClose: true };
2208
+ const bounds = findCodeBodyBounds(ctx.tokens, startPos);
2209
+ return {
2210
+ contents: ctx.tokens.slice(startPos, bounds.closeStart).map((token5) => token5.value).join(""),
2211
+ consumed: bounds.end - startPos,
2212
+ foundClose: bounds.foundClose
2213
+ };
2214
+ }
2215
+
2216
+ // packages/parser/src/parser/rules/block/code/index.ts
2217
+ var codeBlockRule = {
2218
+ name: "code",
2219
+ startTokens: ["BLOCK_OPEN"],
2220
+ requiresLineStart: false,
2221
+ parse(ctx) {
2222
+ const openToken = currentToken(ctx);
2223
+ if (openToken.type !== "BLOCK_OPEN") {
2224
+ return { success: false };
2225
+ }
2226
+ const open = findCodeOpen(ctx.tokens, ctx.pos);
2227
+ if (!open)
2228
+ return { success: false };
2229
+ const attrResult = parseAttributesRaw(ctx, ctx.pos + 2);
2230
+ if (open.repaired)
2231
+ repairSwallowedCodeClose(ctx, open.attributesEnd, attrResult.attrs);
2232
+ let pos = open.bodyStart;
2233
+ let consumed = pos - ctx.pos;
2234
+ const contentResult = collectCodeContent(ctx, pos, open.closingSwallowed);
2235
+ let codeContent = contentResult.contents.replace(/\n$/, "");
2236
+ consumed += contentResult.consumed;
2237
+ pos += contentResult.consumed;
2238
+ if (!contentResult.foundClose) {
2239
+ ctx.diagnostics.push({
2240
+ severity: "warning",
2241
+ code: "unclosed-block",
2242
+ message: "Missing closing tag [[/code]] for [[code]]",
2243
+ position: openToken.position
2244
+ });
2245
+ }
2246
+ const codeBlockData = {
2247
+ contents: codeContent,
2248
+ language: attrResult.attrs.type ?? null,
2249
+ name: attrResult.attrs.name ?? null
2250
+ };
2251
+ ctx.codeBlocks.push(codeBlockData);
2252
+ const elements = [{ element: "code", data: codeBlockData }];
2253
+ if (ctx.tokens[pos]?.type === "NEWLINE" && !getParagraphNewlineBoundary(ctx, pos, false).shouldBreak && !ctx.scope.blockCloseCondition?.({ ...ctx, pos: pos + 1 })) {
2254
+ const after = parseInlineUntil({ ...ctx, pos: pos + 1 }, "PARAGRAPH_BREAK");
2255
+ elements.push({ element: "line-break" }, ...normalizeParagraphElements(after.elements));
2256
+ consumed += 1 + after.consumed;
2257
+ }
2258
+ return { success: true, elements, consumed };
2259
+ }
2260
+ };
2261
+
2262
+ // packages/parser/src/parser/rules/block/math/index.ts
2263
+ var mathBlockRule = {
2264
+ name: "math",
2265
+ startTokens: ["BLOCK_OPEN"],
2266
+ requiresLineStart: false,
2267
+ parse(ctx) {
2268
+ const openToken = currentToken(ctx);
2269
+ const open = findMathOpen(ctx.tokens, ctx.pos);
2270
+ if (!open)
2271
+ return { success: false };
2272
+ const bounds = findMathBodyBounds(ctx.tokens, open.bodyStart);
2273
+ if (!bounds.foundClose) {
2274
+ ctx.diagnostics.push({
2275
+ severity: "warning",
2276
+ code: "unclosed-block",
2277
+ message: "Missing closing tag [[/math]] for [[math]]",
2278
+ position: openToken.position
2279
+ });
2280
+ }
2281
+ if (!bounds.hasContent) {
2282
+ return { success: false };
2283
+ }
2284
+ const latexSource = ctx.tokens.slice(open.bodyStart, bounds.closeStart).map((token5) => token5.type === "BACKSLASH_BREAK" ? "\\\n" : token5.value).join("").trim();
2285
+ const name = ctx.tokens.slice(open.nameStart, open.nameEnd).map((token5) => token5.value).join("");
2286
+ return {
2287
+ success: true,
2288
+ elements: [
2289
+ {
2290
+ element: "math",
2291
+ data: {
2292
+ name: name || null,
2293
+ "latex-source": latexSource
2294
+ }
2295
+ }
2296
+ ],
2297
+ consumed: bounds.end - ctx.pos
2298
+ };
2299
+ }
2300
+ };
2301
+
2302
+ // packages/parser/src/parser/rules/inline/link-triple/fallback.ts
2303
+ function literalOpenLink(value) {
2304
+ return {
2305
+ success: true,
2306
+ elements: [{ element: "text", data: value }],
2307
+ consumed: 1
2308
+ };
2309
+ }
2310
+
2311
+ // packages/parser/src/parser/rules/inline/link-triple/label.ts
2312
+ function buildTripleLinkLabel(args) {
2313
+ if (args.isPage && args.foundPipe && !args.labelText.trim())
2314
+ return "page";
2315
+ return {
2316
+ text: getTripleLinkDisplayText({
2317
+ ...args,
2318
+ originalTarget: args.isPage && !args.foundPipe ? args.originalTarget.split("#")[0] : args.originalTarget
2319
+ })
2320
+ };
2321
+ }
2322
+ function getTripleLinkDisplayText(args) {
2323
+ const trimmedLabel = args.labelText.trim();
2324
+ if (args.foundPipe) {
2325
+ return trimmedLabel || args.finalTarget;
2326
+ }
2327
+ const colonIdx = args.originalTarget.indexOf(":");
2328
+ if (colonIdx !== -1 && !args.originalTarget.startsWith("http") && !args.originalTarget.startsWith("*")) {
2329
+ return args.originalTarget.slice(colonIdx + 1).trim();
2330
+ }
2331
+ return args.originalTarget;
2332
+ }
2333
+
2334
+ // packages/parser/src/parser/rules/inline/link-triple/syntax.ts
2335
+ function hasClosingLinkMarker(ctx, startPos) {
2336
+ let pos = startPos;
2337
+ while (pos < ctx.tokens.length) {
2338
+ const token5 = ctx.tokens[pos];
2339
+ if (!token5 || token5.type === "EOF") {
2340
+ return false;
2341
+ }
2342
+ if (token5.type === "LINK_CLOSE") {
2343
+ return true;
2344
+ }
2345
+ if (token5.type === "NEWLINE") {
2346
+ const next = ctx.tokens[pos + 1];
2347
+ if (next?.type === "NEWLINE" || next?.type === "LINK_CLOSE") {
2348
+ return false;
2349
+ }
2350
+ }
2351
+ pos++;
2352
+ }
2353
+ return false;
2354
+ }
2355
+ function collectTripleLinkParts(ctx, startPos) {
2356
+ let target = "";
2357
+ let labelText = "";
2358
+ let foundPipe = false;
2359
+ let consumed = 1;
2360
+ let pos = startPos;
2361
+ while (pos < ctx.tokens.length) {
2362
+ const token5 = ctx.tokens[pos];
2363
+ if (!token5 || token5.type === "LINK_CLOSE" || token5.type === "EOF") {
2364
+ break;
2365
+ }
2366
+ if (token5.type === "NEWLINE") {
2367
+ if (foundPipe) {
2368
+ labelText += " ";
2369
+ } else {
2370
+ target += " ";
2371
+ }
2372
+ consumed++;
2373
+ pos++;
2374
+ continue;
2375
+ }
2376
+ if (token5.type === "PIPE" && !foundPipe) {
2377
+ foundPipe = true;
2378
+ } else if (foundPipe) {
2379
+ labelText += token5.value;
2380
+ } else {
2381
+ target += token5.value;
2382
+ }
2383
+ consumed++;
2384
+ pos++;
2385
+ }
2386
+ if (ctx.tokens[pos]?.type === "LINK_CLOSE") {
2387
+ consumed++;
2388
+ }
2389
+ return { target, labelText, foundPipe, consumed };
2390
+ }
2391
+
2392
+ // packages/parser/src/parser/rules/inline/link-triple/interwiki.ts
2393
+ var INTERWIKI_PREFIXES = new Set(["wikipedia", "google", "dictionary", "wikidot"]);
2394
+ function isInterwikiTarget(target) {
2395
+ const colonIdx = target.indexOf(":");
2396
+ if (colonIdx <= 0 || target.includes("/")) {
2397
+ return false;
2398
+ }
2399
+ const prefix = target.slice(0, colonIdx).toLowerCase();
2400
+ return INTERWIKI_PREFIXES.has(prefix);
2401
+ }
2402
+
2403
+ // packages/parser/src/parser/rules/inline/link-triple/target.ts
2404
+ function normalizeTripleLinkTarget(trimmedTarget) {
2405
+ if (trimmedTarget.startsWith("*")) {
2406
+ return { target: trimmedTarget.slice(1), hasStar: true };
2407
+ }
2408
+ return { target: trimmedTarget, hasStar: false };
2409
+ }
2410
+ function isInvalidTripleLinkTarget(trimmedTarget, foundPipe) {
2411
+ return trimmedTarget === "" && foundPipe || /#{2,}/.test(trimmedTarget);
2412
+ }
2413
+ function determineLinkTypeAndLocation(target) {
2414
+ if (target.startsWith("#")) {
2415
+ return { linkType: "anchor", link: target };
2416
+ }
2417
+ if (target.startsWith("http://") || target.startsWith("https://")) {
2418
+ return { linkType: "direct", link: target };
2419
+ }
2420
+ if (isInterwikiTarget(target)) {
2421
+ return { linkType: "interwiki", link: target };
2422
+ }
2423
+ return { linkType: "page", link: { site: null, page: target } };
2424
+ }
2425
+
2426
+ // packages/parser/src/parser/rules/inline/link-triple/index.ts
2427
+ var linkTripleRule = {
2428
+ name: "linkTriple",
2429
+ startTokens: ["LINK_OPEN"],
2430
+ parse(ctx) {
2431
+ const startToken = currentToken(ctx);
2432
+ if (!hasClosingLinkMarker(ctx, ctx.pos + 1)) {
2433
+ return literalOpenLink(startToken.value);
2434
+ }
2435
+ const parts = collectTripleLinkParts(ctx, ctx.pos + 1);
2436
+ const trimmedTarget = parts.target.trim();
2437
+ if (isInvalidTripleLinkTarget(trimmedTarget, parts.foundPipe)) {
2438
+ return literalOpenLink(startToken.value);
2439
+ }
2440
+ const normalized = normalizeTripleLinkTarget(trimmedTarget);
2441
+ const { linkType, link } = determineLinkTypeAndLocation(normalized.target);
2442
+ const label = buildTripleLinkLabel({
2443
+ isPage: linkType === "page",
2444
+ foundPipe: parts.foundPipe,
2445
+ labelText: parts.labelText,
2446
+ finalTarget: normalized.target,
2447
+ originalTarget: trimmedTarget
2448
+ });
2449
+ return {
2450
+ success: true,
2451
+ elements: [
2452
+ {
2453
+ element: "link",
2454
+ data: {
2455
+ type: linkType,
2456
+ link,
2457
+ extra: null,
2458
+ label,
2459
+ target: normalized.hasStar && linkType === "direct" ? "new-tab" : null
2460
+ }
2461
+ }
2462
+ ],
2463
+ consumed: parts.consumed
2464
+ };
2465
+ }
2466
+ };
2467
+
2468
+ // packages/parser/src/parser/rules/block/note/boundary.ts
2469
+ var opaqueRules = [codeBlockRule, mathBlockRule, linkTripleRule];
2470
+ var boundsCache = new WeakMap;
2471
+ var closeCache = new WeakMap;
2472
+ function tagEnd(tokens, pos, name, close = false) {
2473
+ 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;
2474
+ }
2475
+ function findClose(probe, start, name) {
2476
+ let byName = closeCache.get(probe.tokens);
2477
+ if (!byName) {
2478
+ byName = new Map;
2479
+ closeCache.set(probe.tokens, byName);
2480
+ }
2481
+ let cache = byName.get(name);
2482
+ if (!cache) {
2483
+ cache = new Map;
2484
+ byName.set(name, cache);
2485
+ }
2486
+ if (cache.has(start))
2487
+ return cache.get(start);
2488
+ const visited = [];
2489
+ let result = -1;
2490
+ for (let pos = start;pos < probe.tokens.length; pos++) {
2491
+ if (cache.has(pos)) {
2492
+ result = cache.get(pos);
2493
+ break;
2494
+ }
2495
+ visited.push(pos);
2496
+ const protectedEnd = protectedInlineRegionEnd(probe.tokens, pos, probe.tokens.length);
2497
+ if (protectedEnd > pos) {
2498
+ pos = protectedEnd - 1;
2499
+ continue;
2500
+ }
2501
+ if (tagEnd(probe.tokens, pos, name, true) > pos) {
2502
+ result = pos;
2503
+ break;
2504
+ }
2505
+ const opaqueEnd = opaqueRuleEnd(probe, pos, opaqueRules, true);
2506
+ if (opaqueEnd > pos) {
2507
+ pos = opaqueEnd - 1;
2508
+ continue;
2509
+ }
2510
+ if (name === "note") {
2511
+ const footnoteStart = tagEnd(probe.tokens, pos, "footnote");
2512
+ if (footnoteStart > pos) {
2513
+ const end = findClose(probe, footnoteStart, "footnote");
2514
+ if (end >= 0)
2515
+ pos = end + 2;
2516
+ }
2517
+ }
2518
+ }
2519
+ for (const pos of visited)
2520
+ cache.set(pos, result);
2521
+ return result;
2522
+ }
2523
+ function findNoteBounds(ctx, start = ctx.pos) {
2524
+ const openEnd = tagEnd(ctx.tokens, start, "note");
2525
+ if (openEnd === start || ctx.tokens[openEnd]?.type !== "NEWLINE")
2526
+ return null;
2527
+ let cache = boundsCache.get(ctx.tokens);
2528
+ if (!cache) {
2529
+ cache = new Map;
2530
+ boundsCache.set(ctx.tokens, cache);
2531
+ }
2532
+ if (cache.has(start))
2533
+ return cache.get(start);
2534
+ const close = findClose(createOpaqueProbe(ctx), openEnd + 1, "note");
2535
+ const result = close < 0 ? null : { bodyStart: openEnd + 1, close, end: close + 3 };
2536
+ cache.set(start, result);
2537
+ return result;
2538
+ }
2539
+
2540
+ // packages/parser/src/parser/rules/inline/parsing/block-boundary.ts
2541
+ function isExcludedBlockToken(ctx, tokenPos) {
2542
+ const excluded = ctx.scope.excludedBlockNames;
2543
+ if (!excluded?.size)
2544
+ return false;
2545
+ const token5 = ctx.tokens[tokenPos];
1284
2546
  if (token5?.type !== "BLOCK_OPEN" && token5?.type !== "BLOCK_END_OPEN")
1285
2547
  return false;
1286
2548
  const nameResult = parseBlockName(ctx, tokenPos + 1);
@@ -1297,6 +2559,8 @@ function isUnknownBlockToken(ctx, tokenPos) {
1297
2559
  }
1298
2560
  return true;
1299
2561
  }
2562
+ if (nameResult.name === "note")
2563
+ return findNoteBounds(ctx, tokenPos) === null;
1300
2564
  return !KNOWN_BLOCK_NAMES.has(nameResult.name);
1301
2565
  }
1302
2566
  function isIndentAcceptingBlock(ctx, tokenPos) {
@@ -1320,7 +2584,13 @@ function isParagraphBreakingBlockStart(ctx, newlinePos, lookAhead) {
1320
2584
  if (!nextMeaningfulToken.lineStart && !isIndentedBlockOpener) {
1321
2585
  return false;
1322
2586
  }
1323
- return !parseImageOpen({ ...ctx, pos: nextPos }) && !isOrphanCloseSpan(ctx, nextPos) && !isAnchorName(ctx, nextPos) && !isInvalidBlockOpen(ctx, nextPos) && !isInvalidHeading(ctx, nextPos) && !isExcludedBlockStart(ctx, nextPos) && !isUnknownBlockStart(ctx, nextPos);
2587
+ 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);
2588
+ }
2589
+ function isInvalidListMarker(ctx, markerPos) {
2590
+ const marker = ctx.tokens[markerPos];
2591
+ if (marker?.type !== "LIST_BULLET" && marker?.type !== "LIST_NUMBER")
2592
+ return false;
2593
+ return ctx.tokens[markerPos + 1]?.type !== "WHITESPACE";
1324
2594
  }
1325
2595
  function isOrphanCloseSpan(ctx, blockEndOpenPos) {
1326
2596
  const token5 = ctx.tokens[blockEndOpenPos];
@@ -1406,13 +2676,6 @@ function skipWhitespaceAfterNewline(ctx, newlinePos) {
1406
2676
  return lookAhead;
1407
2677
  }
1408
2678
 
1409
- // packages/parser/src/parser/rules/inline/parsing/preserved-line-break.ts
1410
- function createPreservedTrailingLineBreak() {
1411
- const lineBreak = { element: "line-break" };
1412
- lineBreak._preservedTrailingBreak = true;
1413
- return lineBreak;
1414
- }
1415
-
1416
2679
  // packages/parser/src/parser/rules/inline/parsing/simple-token.ts
1417
2680
  function parseSimpleInlineToken(token5, nextToken) {
1418
2681
  if (token5.type === "TEXT" && token5.value !== "(") {
@@ -1429,12 +2692,14 @@ function parseInlineUntil(ctx, endType) {
1429
2692
  const nodes = [];
1430
2693
  let consumed = 0;
1431
2694
  let pos = ctx.pos;
2695
+ let consumedEmptyRaw = false;
1432
2696
  const paragraphMode = endType === "PARAGRAPH_BREAK";
1433
- const multiline = paragraphMode || FORMATTING_CLOSE_TOKENS.has(endType);
2697
+ const preserveNewlines = endType === "EOF";
2698
+ const multiline = paragraphMode || preserveNewlines || FORMATTING_CLOSE_TOKENS.has(endType);
1434
2699
  let inlineEnd = ctx.scope.inlineEnd ?? ctx.tokens.length;
1435
2700
  if (!multiline) {
1436
2701
  for (let end = ctx.pos;end < inlineEnd; end++) {
1437
- const protectedEnd = protectedInlineRegionEnd(ctx.tokens, end, inlineEnd);
2702
+ 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));
1438
2703
  if (protectedEnd > end) {
1439
2704
  end = protectedEnd - 1;
1440
2705
  continue;
@@ -1468,6 +2733,12 @@ function parseInlineUntil(ctx, endType) {
1468
2733
  if (!multiline && token5.type === "NEWLINE") {
1469
2734
  break;
1470
2735
  }
2736
+ if (preserveNewlines && token5.type === "NEWLINE") {
2737
+ nodes.push({ element: "text", data: token5.value });
2738
+ pos++;
2739
+ consumed++;
2740
+ continue;
2741
+ }
1471
2742
  if (multiline && token5.type === "NEWLINE" && !ctx.scope.tableFormatting) {
1472
2743
  const boundary = getParagraphNewlineBoundary(ctx, pos, nodes.length > 0);
1473
2744
  if (boundary.shouldBreak) {
@@ -1483,10 +2754,11 @@ function parseInlineUntil(ctx, endType) {
1483
2754
  consumed++;
1484
2755
  continue;
1485
2756
  }
1486
- if (token5.type === endType) {
2757
+ const hasEmail = emailRegionEnd(ctx.tokens, pos, inlineEnd) > pos;
2758
+ if (token5.type === endType && !hasEmail) {
1487
2759
  break;
1488
2760
  }
1489
- if (canCollectLongPlainTextRuns) {
2761
+ if (canCollectLongPlainTextRuns && !hasEmail) {
1490
2762
  const plainTextRun = collectLongPlainTextRun(ctx, pos, endType);
1491
2763
  if (plainTextRun) {
1492
2764
  nodes.push({ element: "text", data: plainTextRun.value });
@@ -1496,7 +2768,7 @@ function parseInlineUntil(ctx, endType) {
1496
2768
  }
1497
2769
  }
1498
2770
  const simpleToken = parseSimpleInlineToken(token5, ctx.tokens[pos + 1]);
1499
- if (simpleToken) {
2771
+ if (simpleToken && !hasEmail) {
1500
2772
  nodes.push(simpleToken.element);
1501
2773
  consumed += simpleToken.consumed;
1502
2774
  pos += simpleToken.consumed;
@@ -1507,6 +2779,10 @@ function parseInlineUntil(ctx, endType) {
1507
2779
  for (const rule of getCandidateInlineRules(inlineRules, token5.type)) {
1508
2780
  const result = rule.parse(inlineCtx);
1509
2781
  if (result.success) {
2782
+ stripAutomaticLineBreak(nodes, result.stripLeadingLineBreak);
2783
+ if (rule.name === "raw" && result.elements.length === 0 && nodes.length === 0) {
2784
+ consumedEmptyRaw = true;
2785
+ }
1510
2786
  if (rule.name === "comment") {
1511
2787
  let after = pos + result.consumed;
1512
2788
  while (ctx.tokens[after]?.type === "WHITESPACE")
@@ -1525,7 +2801,9 @@ function parseInlineUntil(ctx, endType) {
1525
2801
  nodes.pop();
1526
2802
  }
1527
2803
  }
1528
- nodes.push(...result.elements);
2804
+ for (const element of result.elements) {
2805
+ nodes.push(paragraphMode && consumedEmptyRaw && nodes.length === 0 && token5.type === "NEWLINE" && element.element === "line-break" ? createPreservedLeadingLineBreak() : element);
2806
+ }
1529
2807
  consumed += result.consumed;
1530
2808
  pos += result.consumed;
1531
2809
  matched = true;
@@ -1550,138 +2828,10 @@ var FORMATTING_CLOSE_TOKENS = new Set([
1550
2828
  "MONO_CLOSE",
1551
2829
  "COLOR_MARKER"
1552
2830
  ]);
1553
- // packages/parser/src/parser/rules/block/parsing/attributes/names.ts
1554
- function consumeAttributeName(ctx, startPos, startConsumed, startName, options) {
1555
- if (startName === "_" && isAttributeWordToken(ctx.tokens[startPos])) {
1556
- startName += ctx.tokens[startPos]?.value ?? "";
1557
- startPos++;
1558
- startConsumed++;
1559
- }
1560
- return options.strikeHyphens ? consumeRawNameSuffix(ctx, startPos, startConsumed, startName, options.hyphenatedNames) : consumeSafeNameSuffix(ctx, startPos, startConsumed, startName);
1561
- }
1562
- function isAttributeNameToken(token5) {
1563
- return isAttributeWordToken(token5) || token5?.type === "UNDERSCORE";
1564
- }
1565
- function consumeSafeNameSuffix(ctx, startPos, startConsumed, startName) {
1566
- let name = startName;
1567
- let pos = startPos;
1568
- let consumed = startConsumed;
1569
- while (isAttributeNameSeparator(ctx.tokens[pos]) && isAttributeWordToken(ctx.tokens[pos + 1])) {
1570
- name += ctx.tokens[pos]?.value ?? "";
1571
- pos++;
1572
- consumed++;
1573
- name += ctx.tokens[pos]?.value ?? "";
1574
- pos++;
1575
- consumed++;
1576
- }
1577
- return { name, pos, consumed };
1578
- }
1579
- function consumeRawNameSuffix(ctx, startPos, startConsumed, startName, hyphenatedNames) {
1580
- let name = startName;
1581
- let pos = startPos;
1582
- let consumed = startConsumed;
1583
- while (isAttributeNameSeparator(ctx.tokens[pos])) {
1584
- while (isAttributeNameSeparator(ctx.tokens[pos])) {
1585
- if (hyphenatedNames || ctx.tokens[pos]?.type === "UNDERSCORE") {
1586
- name += ctx.tokens[pos]?.value ?? "-";
1587
- }
1588
- pos++;
1589
- consumed++;
1590
- }
1591
- if (!isAttributeWordToken(ctx.tokens[pos])) {
1592
- break;
1593
- }
1594
- if (hyphenatedNames || name.endsWith("_")) {
1595
- name += ctx.tokens[pos]?.value ?? "";
1596
- }
1597
- pos++;
1598
- consumed++;
1599
- }
1600
- return { name, pos, consumed };
1601
- }
1602
- function isAttributeWordToken(token5) {
1603
- return token5?.type === "TEXT" || token5?.type === "IDENTIFIER";
1604
- }
1605
- function isAttributeNameSeparator(token5) {
1606
- return token5?.type === "TEXT" && token5.value === "-" || token5?.type === "STRIKE_MARKER" || token5?.type === "UNDERSCORE";
1607
- }
1608
-
1609
- // packages/parser/src/parser/rules/block/parsing/attributes/values.ts
1610
- function consumeAttributeValue(token5) {
1611
- if (!token5) {
1612
- return null;
1613
- }
1614
- if (token5.type === "QUOTED_STRING") {
1615
- return { value: stripQuotes(token5.value) };
1616
- }
1617
- if (isAttributeNameToken(token5)) {
1618
- return { value: token5.value };
1619
- }
1620
- return null;
1621
- }
1622
- function stripQuotes(value) {
1623
- return value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
1624
- }
1625
-
1626
- // packages/parser/src/parser/rules/block/parsing/attributes/scanner.ts
1627
- function scanAttributes(ctx, startPos, options) {
1628
- const attrs = {};
1629
- let pos = startPos;
1630
- let consumed = 0;
1631
- while (pos < ctx.tokens.length) {
1632
- const token5 = ctx.tokens[pos];
1633
- if (!token5 || isAttributeTerminator(token5)) {
1634
- break;
1635
- }
1636
- if (token5.type === "WHITESPACE") {
1637
- pos++;
1638
- consumed++;
1639
- continue;
1640
- }
1641
- if (!isAttributeNameToken(token5)) {
1642
- pos++;
1643
- consumed++;
1644
- continue;
1645
- }
1646
- let name = token5.value;
1647
- pos++;
1648
- consumed++;
1649
- const nameResult = consumeAttributeName(ctx, pos, consumed, name, options);
1650
- pos = nameResult.pos;
1651
- consumed = nameResult.consumed;
1652
- name = nameResult.name.toLowerCase();
1653
- if (ctx.tokens[pos]?.type !== "EQUALS") {
1654
- attrs[name] = "true";
1655
- continue;
1656
- }
1657
- pos++;
1658
- consumed++;
1659
- const valueResult = consumeAttributeValue(ctx.tokens[pos]);
1660
- if (!valueResult) {
1661
- continue;
1662
- }
1663
- attrs[name] = valueResult.value;
1664
- pos++;
1665
- consumed++;
1666
- }
1667
- return { attrs, consumed };
1668
- }
1669
- function isAttributeTerminator(token5) {
1670
- return token5.type === "BLOCK_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF";
1671
- }
1672
-
1673
- // packages/parser/src/parser/rules/block/parsing/attributes/index.ts
1674
- function parseAttributes(ctx, startPos) {
1675
- const result = scanAttributes(ctx, startPos, { hyphenatedNames: true, strikeHyphens: false });
1676
- return { attrs: filterUnsafeAttributes(result.attrs), consumed: result.consumed };
1677
- }
1678
- function parseAttributesRaw(ctx, startPos, hyphenatedNames = true) {
1679
- return scanAttributes(ctx, startPos, { hyphenatedNames, strikeHyphens: true });
1680
- }
1681
2831
  // packages/parser/src/parser/parse/block.ts
1682
- function parseNextBlock(ctx, skipWhitespace2, isAtEnd2) {
2832
+ function parseNextBlock(ctx, skipWhitespace2, isAtEnd3) {
1683
2833
  skipWhitespace2();
1684
- if (isAtEnd2()) {
2834
+ if (isAtEnd3()) {
1685
2835
  return [];
1686
2836
  }
1687
2837
  const token5 = ctx.tokens[ctx.pos];
@@ -1700,7 +2850,7 @@ function parseNextBlock(ctx, skipWhitespace2, isAtEnd2) {
1700
2850
  }
1701
2851
  }
1702
2852
  const result = ctx.blockFallbackRule.parse(ctx);
1703
- if (result.success && result.elements.length > 0) {
2853
+ if (result.success) {
1704
2854
  ctx.pos += result.consumed;
1705
2855
  return result.elements;
1706
2856
  }
@@ -1709,36 +2859,7 @@ function parseNextBlock(ctx, skipWhitespace2, isAtEnd2) {
1709
2859
  }
1710
2860
 
1711
2861
  // packages/parser/src/parser/parse/context.ts
1712
- var import_ast6 = require("@wdprlib/ast");
1713
-
1714
- // packages/parser/src/parser/rules/tokens.ts
1715
- function currentToken(ctx) {
1716
- return ctx.tokens[ctx.pos] ?? eofToken();
1717
- }
1718
- function eofToken() {
1719
- return {
1720
- type: "EOF",
1721
- value: "",
1722
- position: { start: { line: 0, column: 0, offset: 0 }, end: { line: 0, column: 0, offset: 0 } },
1723
- lineStart: false
1724
- };
1725
- }
1726
- function hasClosingMarkerBeforeNewline(ctx, markerType, markerValue) {
1727
- let pos = ctx.pos;
1728
- while (pos < (ctx.scope.inlineEnd ?? ctx.tokens.length)) {
1729
- const token5 = ctx.tokens[pos];
1730
- if (!token5 || token5.type === "NEWLINE" || token5.type === "EOF") {
1731
- return false;
1732
- }
1733
- if (token5.type === markerType) {
1734
- if (markerValue === undefined || token5.value === markerValue) {
1735
- return true;
1736
- }
1737
- }
1738
- pos++;
1739
- }
1740
- return false;
1741
- }
2862
+ var import_ast9 = require("@wdprlib/ast");
1742
2863
  // packages/parser/src/parser/rules/block/heading/open.ts
1743
2864
  function parseHeadingOpen(ctx) {
1744
2865
  const marker = ctx.tokens[ctx.pos];
@@ -1984,10 +3105,11 @@ function parseNativeListLine(ctx, startPos) {
1984
3105
  const ltype = markerToken.type === "LIST_BULLET" ? "bullet" : "numbered";
1985
3106
  pos++;
1986
3107
  consumed++;
1987
- if (ctx.tokens[pos]?.type === "WHITESPACE") {
1988
- pos++;
1989
- consumed++;
3108
+ if (ctx.tokens[pos]?.type !== "WHITESPACE") {
3109
+ return { kind: "stop" };
1990
3110
  }
3111
+ pos++;
3112
+ consumed++;
1991
3113
  const inlineCtx = { ...ctx, pos };
1992
3114
  const inlineResult = parseInlineUntil(inlineCtx, "NEWLINE");
1993
3115
  consumed += inlineResult.consumed;
@@ -2097,9 +3219,9 @@ function appendBareParagraphElements(state, elements) {
2097
3219
  function appendBareParagraphText(state, text) {
2098
3220
  state.current.push({ element: "text", data: text });
2099
3221
  }
2100
- function appendBareParagraphLineBreakIfNeeded(state) {
3222
+ function appendBareParagraphLineBreakIfNeeded(state, token5) {
2101
3223
  if (state.current.length > 0) {
2102
- state.current.push({ element: "line-break" });
3224
+ state.current.push(createAutomaticLineBreak(token5));
2103
3225
  }
2104
3226
  }
2105
3227
  function flushBareParagraph(state) {
@@ -2204,7 +3326,7 @@ function parseBareListContent(ctx, startPos, listType) {
2204
3326
  if (consecutiveNewlines >= 2) {
2205
3327
  flushBareParagraph(paragraphState);
2206
3328
  } else {
2207
- appendBareParagraphLineBreakIfNeeded(paragraphState);
3329
+ appendBareParagraphLineBreakIfNeeded(paragraphState, token5);
2208
3330
  }
2209
3331
  continue;
2210
3332
  }
@@ -2216,6 +3338,7 @@ function parseBareListContent(ctx, startPos, listType) {
2216
3338
  for (const rule of getCandidateInlineRules(ctx.inlineRules, token5.type)) {
2217
3339
  const result = rule.parse(inlineCtx);
2218
3340
  if (result.success) {
3341
+ stripAutomaticLineBreak(paragraphState.current, result.stripLeadingLineBreak);
2219
3342
  appendBareParagraphElements(paragraphState, result.elements);
2220
3343
  consumed += result.consumed;
2221
3344
  pos += result.consumed;
@@ -2254,7 +3377,7 @@ function parseListItemBlockContent(ctx, pos, token5) {
2254
3377
  for (const rule of getCandidateBlockRules(filteredBlockRules, token5)) {
2255
3378
  const result = rule.parse(blockCtx);
2256
3379
  if (result.success) {
2257
- return { matched: true, elements: result.elements, consumed: result.consumed };
3380
+ return { matched: true, ...result };
2258
3381
  }
2259
3382
  }
2260
3383
  return { matched: false, elements: [], consumed: 0 };
@@ -2273,7 +3396,7 @@ function parseListItemInlineContent(ctx, pos, tokenType) {
2273
3396
  for (const rule of getCandidateInlineRules(ctx.inlineRules, tokenType)) {
2274
3397
  const result = rule.parse(inlineCtx);
2275
3398
  if (result.success) {
2276
- return { matched: true, elements: result.elements, consumed: result.consumed };
3399
+ return { matched: true, ...result };
2277
3400
  }
2278
3401
  }
2279
3402
  return { matched: false, elements: [], consumed: 0 };
@@ -2315,7 +3438,9 @@ function collectPostLiTrailingContent(ctx, startPos, listType) {
2315
3438
  }
2316
3439
  const inlineResult = parseListItemInlineContent(ctx, pos, token5.type);
2317
3440
  if (inlineResult.matched) {
2318
- elements.push(...inlineResult.elements);
3441
+ stripAutomaticLineBreak(elements, inlineResult.stripLeadingLineBreak);
3442
+ for (const element of inlineResult.elements)
3443
+ elements.push(element);
2319
3444
  consumed += inlineResult.consumed;
2320
3445
  pos += inlineResult.consumed;
2321
3446
  continue;
@@ -2360,7 +3485,7 @@ function collectLiItemContent(ctx, startPos, listType, parseNestedList) {
2360
3485
  if (token5.type === "NEWLINE") {
2361
3486
  const newlineResult = consumeLiItemNewlines(ctx, pos, elements.length > 0);
2362
3487
  if (newlineResult.addLineBreak) {
2363
- elements.push({ element: "line-break" });
3488
+ elements.push(createAutomaticLineBreak(token5));
2364
3489
  }
2365
3490
  pos += newlineResult.consumed;
2366
3491
  consumed += newlineResult.consumed;
@@ -2368,14 +3493,17 @@ function collectLiItemContent(ctx, startPos, listType, parseNestedList) {
2368
3493
  }
2369
3494
  const blockResult = parseListItemBlockContent(ctx, pos, token5);
2370
3495
  if (blockResult.matched) {
2371
- elements.push(...blockResult.elements);
3496
+ for (const element of blockResult.elements)
3497
+ elements.push(element);
2372
3498
  consumed += blockResult.consumed;
2373
3499
  pos += blockResult.consumed;
2374
3500
  continue;
2375
3501
  }
2376
3502
  const inlineResult = parseListItemInlineContent(ctx, pos, token5.type);
2377
3503
  if (inlineResult.matched) {
2378
- elements.push(...inlineResult.elements);
3504
+ stripAutomaticLineBreak(elements, inlineResult.stripLeadingLineBreak);
3505
+ for (const element of inlineResult.elements)
3506
+ elements.push(element);
2379
3507
  consumed += inlineResult.consumed;
2380
3508
  pos += inlineResult.consumed;
2381
3509
  continue;
@@ -2446,7 +3574,8 @@ function parseLiItem(ctx, startPos, listType, parseNestedList) {
2446
3574
  consumed += closeConsumed;
2447
3575
  pos += closeConsumed;
2448
3576
  const trailingResult = collectPostLiTrailingContent(ctx, pos, listType);
2449
- contentElements.push(...trailingResult.elements);
3577
+ for (const element of trailingResult.elements)
3578
+ contentElements.push(element);
2450
3579
  consumed += trailingResult.consumed;
2451
3580
  }
2452
3581
  return {
@@ -2706,7 +3835,7 @@ function sliceLineTokens(ctx, lines) {
2706
3835
  for (const { start, end } of lines) {
2707
3836
  for (let pos = start;pos < end; pos++) {
2708
3837
  const token5 = ctx.tokens[pos];
2709
- if (token5) {
3838
+ if (token5 && token5.type !== "BACKSLASH_BREAK") {
2710
3839
  tokens.push(token5);
2711
3840
  }
2712
3841
  }
@@ -2876,7 +4005,8 @@ function parseDefinitionItemKey(ctx, startPos) {
2876
4005
  const inlineCtx = { ...ctx, pos };
2877
4006
  const result = parseInlineUntil(inlineCtx, "COLON");
2878
4007
  if (result.elements.length > 0) {
2879
- key.push(...result.elements);
4008
+ for (const element of result.elements)
4009
+ key.push(element);
2880
4010
  for (let i = 0;i < result.consumed; i++) {
2881
4011
  const t = ctx.tokens[pos + i];
2882
4012
  if (t)
@@ -2932,7 +4062,8 @@ function parseDefinitionItemValue(ctx, startPos) {
2932
4062
  const inlineCtx = { ...ctx, pos };
2933
4063
  const result = parseInlineUntil(inlineCtx, "NEWLINE");
2934
4064
  if (result.elements.length > 0) {
2935
- value.push(...result.elements);
4065
+ for (const element of result.elements)
4066
+ value.push(element);
2936
4067
  pos += result.consumed;
2937
4068
  consumed += result.consumed;
2938
4069
  } else {
@@ -3005,87 +4136,102 @@ var definitionListRule = {
3005
4136
  if (result.items.length === 0) {
3006
4137
  return { success: false };
3007
4138
  }
3008
- const items = toDefinitionListItems(result.items.filter((item) => item.value.length > 0));
3009
- return {
3010
- success: true,
3011
- elements: items.length > 0 ? [{ element: "definition-list", data: items }] : [],
3012
- consumed: result.consumed
3013
- };
4139
+ const items = toDefinitionListItems(result.items.filter((item) => item.value.length > 0));
4140
+ return {
4141
+ success: true,
4142
+ elements: items.length > 0 ? [{ element: "definition-list", data: items }] : [],
4143
+ consumed: result.consumed
4144
+ };
4145
+ }
4146
+ };
4147
+
4148
+ // packages/parser/src/parser/postprocess/divAdjacentParagraph.ts
4149
+ var unparsedDivStarts = new WeakSet;
4150
+ function markUnparsedDivStart(elements) {
4151
+ const first = elements[0];
4152
+ const text = first?.element === "container" && first.data.type === "paragraph" ? first.data.elements[0] : first;
4153
+ if (text?.element === "text")
4154
+ unparsedDivStarts.add(text);
4155
+ }
4156
+ function isUnparsedDivParagraph(el) {
4157
+ if (!el || el.element !== "container")
4158
+ return false;
4159
+ return el.data.type === "paragraph" && el.data.elements.some((child) => unparsedDivStarts.has(child));
4160
+ }
4161
+ function isDivContainer(el) {
4162
+ if (!el || el.element !== "container")
4163
+ return false;
4164
+ return el.data.type === "div";
4165
+ }
4166
+ function suppressAtLevel(elements) {
4167
+ if (elements.length <= 1)
4168
+ return elements;
4169
+ const unwrap = Array.from({ length: elements.length }, () => false);
4170
+ for (let i = 0;i < elements.length; i++) {
4171
+ if (!isUnparsedDivParagraph(elements[i]))
4172
+ continue;
4173
+ const prevIsDiv = i > 0 && isDivContainer(elements[i - 1]);
4174
+ const nextIsDiv = i < elements.length - 1 && isDivContainer(elements[i + 1]);
4175
+ if (prevIsDiv || nextIsDiv) {
4176
+ unwrap[i] = true;
4177
+ }
3014
4178
  }
3015
- };
3016
-
3017
- // packages/parser/src/parser/rules/block/paragraph/content.ts
3018
- function parseInlineContent(ctx) {
3019
- return parseInlineUntil(ctx, "PARAGRAPH_BREAK");
3020
- }
3021
-
3022
- // packages/parser/src/parser/rules/block/paragraph/span-markers.ts
3023
- function processCloseSpanMarkers(elements) {
3024
- let result = null;
4179
+ const result = [];
3025
4180
  for (let i = 0;i < elements.length; i++) {
3026
- const elem = elements[i];
3027
- if (!elem)
4181
+ const el = elements[i];
4182
+ if (!el)
3028
4183
  continue;
3029
- if (isCloseSpanMarker(elem)) {
3030
- if (result === null) {
3031
- result = elements.slice(0, i);
3032
- }
3033
- if (result.length > 0) {
3034
- const spanContent = [...result];
3035
- result.length = 0;
3036
- result.push({
3037
- element: "container",
3038
- data: {
3039
- type: "span",
3040
- attributes: {},
3041
- elements: spanContent
3042
- }
3043
- });
4184
+ if (unwrap[i] && el.element === "container") {
4185
+ const inner = el.data.elements;
4186
+ if (i > 0 && isDivContainer(elements[i - 1])) {
4187
+ result.push({ element: "line-break" });
3044
4188
  }
4189
+ result.push(...inner);
3045
4190
  } else {
3046
- result?.push(elem);
4191
+ result.push(el);
3047
4192
  }
3048
4193
  }
3049
- return result ?? elements;
4194
+ return result;
3050
4195
  }
3051
- function isCloseSpanMarker(elem) {
3052
- 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;
4196
+ function suppressDivAdjacentParagraphs(elements) {
4197
+ return suppressAtLevel(elements);
3053
4198
  }
3054
4199
 
3055
- // packages/parser/src/parser/rules/block/paragraph/normalize.ts
3056
- function normalizeParagraphElements(source) {
3057
- let elements = processCloseSpanMarkers(source);
3058
- removeTrailingLineBreaks2(elements);
3059
- removeTrailingWhitespaceText(elements);
3060
- elements = removeLeadingLineBreaks(elements);
3061
- return elements;
4200
+ // packages/parser/src/parser/rules/block/div/close.ts
4201
+ function isDivClose(ctx) {
4202
+ const token5 = ctx.tokens[ctx.pos];
4203
+ if (token5?.type !== "BLOCK_END_OPEN")
4204
+ return false;
4205
+ const closeNameResult = parseBlockName(ctx, ctx.pos + 1);
4206
+ return closeNameResult?.name === "div";
3062
4207
  }
3063
- function removeTrailingLineBreaks2(elements) {
3064
- while (elements.length > 0 && elements[elements.length - 1]?.element === "line-break") {
3065
- const lastEl = elements[elements.length - 1];
3066
- if (lastEl._preservedTrailingBreak) {
3067
- delete lastEl._preservedTrailingBreak;
3068
- break;
3069
- }
3070
- elements.pop();
4208
+ function consumeDivClose(ctx, startPos) {
4209
+ let pos = startPos;
4210
+ let consumed = 0;
4211
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
4212
+ return { pos, consumed };
3071
4213
  }
3072
- }
3073
- function removeTrailingWhitespaceText(elements) {
3074
- while (elements.length > 0) {
3075
- const last = elements[elements.length - 1];
3076
- if (last?.element === "text" && "data" in last && typeof last.data === "string" && last.data.trim() === "") {
3077
- elements.pop();
3078
- } else {
3079
- break;
3080
- }
4214
+ pos++;
4215
+ consumed++;
4216
+ const closeNameResult = parseBlockName(ctx, pos);
4217
+ if (closeNameResult) {
4218
+ pos += closeNameResult.consumed;
4219
+ consumed += closeNameResult.consumed;
3081
4220
  }
3082
- }
3083
- function removeLeadingLineBreaks(elements) {
3084
- let first = 0;
3085
- while (first < elements.length && elements[first]?.element === "line-break") {
3086
- first++;
4221
+ if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
4222
+ pos++;
4223
+ consumed++;
3087
4224
  }
3088
- return first > 0 ? elements.slice(first) : elements;
4225
+ if (ctx.tokens[pos]?.type === "NEWLINE") {
4226
+ pos++;
4227
+ consumed++;
4228
+ }
4229
+ return { pos, consumed };
4230
+ }
4231
+
4232
+ // packages/parser/src/parser/rules/block/paragraph/content.ts
4233
+ function parseInlineContent(ctx) {
4234
+ return parseInlineUntil(ctx, "PARAGRAPH_BREAK");
3089
4235
  }
3090
4236
 
3091
4237
  // packages/parser/src/parser/rules/block/paragraph/index.ts
@@ -3095,25 +4241,30 @@ var paragraphRule = {
3095
4241
  requiresLineStart: false,
3096
4242
  parse(ctx) {
3097
4243
  const result = parseInlineContent(ctx);
3098
- if (result.elements.length === 0) {
4244
+ if (result.consumed === 0) {
3099
4245
  return { success: false };
3100
4246
  }
3101
4247
  const elements = normalizeParagraphElements(result.elements);
3102
4248
  if (elements.length === 0) {
3103
- return { success: false };
4249
+ return { success: true, elements: [], consumed: result.consumed };
3104
4250
  }
3105
4251
  const nextPos = ctx.pos + result.consumed;
3106
4252
  const nextToken = ctx.tokens[nextPos];
3107
4253
  if (nextToken?.type === "COLON" && nextToken.lineStart) {
4254
+ if (isPreservedLeadingLineBreak(elements[0]))
4255
+ elements[0] = { element: "line-break" };
3108
4256
  return {
3109
4257
  success: true,
3110
4258
  elements: [...elements, { element: "line-break" }],
3111
4259
  consumed: result.consumed
3112
4260
  };
3113
4261
  }
4262
+ const wrapped = wrapParagraphElements(elements);
4263
+ if (isDivClose(ctx))
4264
+ markUnparsedDivStart(wrapped);
3114
4265
  return {
3115
4266
  success: true,
3116
- elements: wrapParagraphElements(elements),
4267
+ elements: wrapped,
3117
4268
  consumed: result.consumed
3118
4269
  };
3119
4270
  }
@@ -3124,7 +4275,7 @@ function wrapParagraphElements(elements) {
3124
4275
  let bare = false;
3125
4276
  const flush = (trimBreaks = false) => {
3126
4277
  const content = trimBreaks ? normalizeParagraphElements(group) : group;
3127
- while (content[0]?.element === "line-break")
4278
+ while (content[0]?.element === "line-break" && !isPreservedLeadingLineBreak(content[0]))
3128
4279
  content.shift();
3129
4280
  while (content.length) {
3130
4281
  const last = content.at(-1);
@@ -3136,6 +4287,8 @@ function wrapParagraphElements(elements) {
3136
4287
  content.shift();
3137
4288
  if (content[0]?.element === "text")
3138
4289
  content[0] = { element: "text", data: content[0].data.trimStart() };
4290
+ if (isPreservedLeadingLineBreak(content[0]))
4291
+ content[0] = { element: "line-break" };
3139
4292
  if (content.length)
3140
4293
  output.push(...bare || content.some((el) => el.element === "image") ? content : [
3141
4294
  {
@@ -3157,37 +4310,36 @@ function wrapParagraphElements(elements) {
3157
4310
  return output;
3158
4311
  }
3159
4312
 
3160
- // packages/parser/src/parser/rules/block/div/close.ts
3161
- function isDivClose(ctx) {
3162
- const token5 = ctx.tokens[ctx.pos];
3163
- if (token5?.type !== "BLOCK_END_OPEN")
3164
- return false;
3165
- const closeNameResult = parseBlockName(ctx, ctx.pos + 1);
3166
- return closeNameResult?.name === "div";
3167
- }
3168
- function consumeDivClose(ctx, startPos) {
3169
- let pos = startPos;
3170
- let consumed = 0;
3171
- if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
3172
- return { pos, consumed };
3173
- }
3174
- pos++;
3175
- consumed++;
3176
- const closeNameResult = parseBlockName(ctx, pos);
3177
- if (closeNameResult) {
3178
- pos += closeNameResult.consumed;
3179
- consumed += closeNameResult.consumed;
3180
- }
3181
- if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
3182
- pos++;
3183
- consumed++;
3184
- }
3185
- if (ctx.tokens[pos]?.type === "NEWLINE") {
3186
- pos++;
3187
- consumed++;
4313
+ // packages/parser/src/parser/rules/block/note/index.ts
4314
+ var excludedBlockNames = new Set(["note"]);
4315
+ var noteRule = {
4316
+ name: "note",
4317
+ startTokens: ["BLOCK_OPEN"],
4318
+ requiresLineStart: false,
4319
+ parse(ctx) {
4320
+ const bounds = findNoteBounds(ctx);
4321
+ if (!bounds)
4322
+ return { success: false };
4323
+ const body = parseBlocksUntil({
4324
+ ...ctx,
4325
+ tokens: ctx.tokens.slice(bounds.bodyStart, bounds.close),
4326
+ pos: 0,
4327
+ scope: {
4328
+ ...ctx.scope,
4329
+ inlineEnd: undefined,
4330
+ tableFormatting: undefined,
4331
+ blockCloseCondition: undefined
4332
+ }
4333
+ }, () => false, { excludedBlockNames });
4334
+ return {
4335
+ success: true,
4336
+ consumed: bounds.end - ctx.pos,
4337
+ elements: [
4338
+ { element: "container", data: { type: "note", attributes: {}, elements: body.elements } }
4339
+ ]
4340
+ };
3188
4341
  }
3189
- return { pos, consumed };
3190
- }
4342
+ };
3191
4343
 
3192
4344
  // packages/parser/src/parser/rules/block/div/nesting.ts
3193
4345
  var divCloseCountCache = new WeakMap;
@@ -3356,6 +4508,7 @@ function consumeFailedDiv(ctx) {
3356
4508
  pos++;
3357
4509
  consumed++;
3358
4510
  }
4511
+ markUnparsedDivStart(elements);
3359
4512
  return {
3360
4513
  success: true,
3361
4514
  elements: [
@@ -3467,126 +4620,6 @@ var divRule = {
3467
4620
  }
3468
4621
  };
3469
4622
 
3470
- // packages/parser/src/parser/rules/block/code/attributes.ts
3471
- function repairSwallowedCodeClose(ctx, pos, attrs) {
3472
- const prevToken = ctx.tokens[pos - 1];
3473
- if (prevToken?.type !== "QUOTED_STRING" || !prevToken.value.includes("]]")) {
3474
- return null;
3475
- }
3476
- const rawValue = prevToken.value;
3477
- const bracketIdx = rawValue.indexOf("]]");
3478
- const truncatedValue = rawValue.startsWith('"') ? rawValue.slice(1, bracketIdx) : rawValue.slice(0, bracketIdx);
3479
- for (const key of Object.keys(attrs)) {
3480
- const stored = attrs[key];
3481
- if (stored === rawValue || stored === rawValue.slice(1, -1) || stored === rawValue.slice(1)) {
3482
- attrs[key] = truncatedValue;
3483
- break;
3484
- }
3485
- }
3486
- return {
3487
- closingSwallowed: rawValue.includes("[[/code]]")
3488
- };
3489
- }
3490
-
3491
- // packages/parser/src/parser/rules/block/code/content.ts
3492
- function collectCodeContent(ctx, startPos, closingSwallowed) {
3493
- const contentParts = [];
3494
- let pos = startPos;
3495
- let consumed = 0;
3496
- let foundClose = closingSwallowed;
3497
- while (!closingSwallowed && pos < ctx.tokens.length) {
3498
- const token5 = ctx.tokens[pos];
3499
- if (!token5 || token5.type === "EOF") {
3500
- break;
3501
- }
3502
- if (token5.type === "BLOCK_END_OPEN") {
3503
- const closeNameResult = parseBlockName(ctx, pos + 1);
3504
- if (closeNameResult?.name === "code") {
3505
- foundClose = true;
3506
- const closeConsumed = consumeCodeClose(ctx, pos, closeNameResult.consumed);
3507
- consumed += closeConsumed;
3508
- break;
3509
- }
3510
- }
3511
- contentParts.push(token5.value);
3512
- pos++;
3513
- consumed++;
3514
- }
3515
- return { contents: contentParts.join(""), consumed, foundClose };
3516
- }
3517
- function consumeCodeClose(ctx, startPos, closeNameConsumed) {
3518
- let pos = startPos + 1 + closeNameConsumed;
3519
- let consumed = 1 + closeNameConsumed;
3520
- if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
3521
- pos++;
3522
- consumed++;
3523
- }
3524
- return consumed;
3525
- }
3526
-
3527
- // packages/parser/src/parser/rules/block/code/index.ts
3528
- var codeBlockRule = {
3529
- name: "code",
3530
- startTokens: ["BLOCK_OPEN"],
3531
- requiresLineStart: false,
3532
- parse(ctx) {
3533
- const openToken = currentToken(ctx);
3534
- if (openToken.type !== "BLOCK_OPEN") {
3535
- return { success: false };
3536
- }
3537
- let pos = ctx.pos + 1;
3538
- let consumed = 1;
3539
- const nameResult = parseBlockName(ctx, pos);
3540
- if (!nameResult || nameResult.name !== "code") {
3541
- return { success: false };
3542
- }
3543
- pos += nameResult.consumed;
3544
- consumed += nameResult.consumed;
3545
- const attrResult = parseAttributesRaw(ctx, pos);
3546
- pos += attrResult.consumed;
3547
- consumed += attrResult.consumed;
3548
- let closingSwallowed = false;
3549
- if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
3550
- const repaired = repairSwallowedCodeClose(ctx, pos, attrResult.attrs);
3551
- if (!repaired)
3552
- return { success: false };
3553
- closingSwallowed = repaired.closingSwallowed;
3554
- } else {
3555
- pos++;
3556
- consumed++;
3557
- }
3558
- if (ctx.tokens[pos]?.type === "NEWLINE") {
3559
- pos++;
3560
- consumed++;
3561
- }
3562
- const contentResult = collectCodeContent(ctx, pos, closingSwallowed);
3563
- let codeContent = contentResult.contents.replace(/\n$/, "");
3564
- consumed += contentResult.consumed;
3565
- pos += contentResult.consumed;
3566
- if (!contentResult.foundClose) {
3567
- ctx.diagnostics.push({
3568
- severity: "warning",
3569
- code: "unclosed-block",
3570
- message: "Missing closing tag [[/code]] for [[code]]",
3571
- position: openToken.position
3572
- });
3573
- }
3574
- const codeBlockData = {
3575
- contents: codeContent,
3576
- language: attrResult.attrs.type ?? null,
3577
- name: attrResult.attrs.name ?? null
3578
- };
3579
- ctx.codeBlocks.push(codeBlockData);
3580
- const elements = [{ element: "code", data: codeBlockData }];
3581
- if (ctx.tokens[pos]?.type === "NEWLINE" && !getParagraphNewlineBoundary(ctx, pos, false).shouldBreak && !ctx.scope.blockCloseCondition?.({ ...ctx, pos: pos + 1 })) {
3582
- const after = parseInlineUntil({ ...ctx, pos: pos + 1 }, "PARAGRAPH_BREAK");
3583
- elements.push({ element: "line-break" }, ...normalizeParagraphElements(after.elements));
3584
- consumed += 1 + after.consumed;
3585
- }
3586
- return { success: true, elements, consumed };
3587
- }
3588
- };
3589
-
3590
4623
  // packages/parser/src/parser/rules/block/collapsible/attributes.ts
3591
4624
  function parseMultilineAttributes(ctx, startPos) {
3592
4625
  const attrs = {};
@@ -4015,7 +5048,9 @@ function parseTableCell(ctx, startPos, cellStart) {
4015
5048
  for (const rule of getCandidateInlineRules(inlineRules, token5.type)) {
4016
5049
  const result = rule.parse(inlineCtx);
4017
5050
  if (result.success) {
4018
- children.push(...result.elements);
5051
+ stripAutomaticLineBreak(children, result.stripLeadingLineBreak);
5052
+ for (const element of result.elements)
5053
+ children.push(element);
4019
5054
  consumed += result.consumed;
4020
5055
  pos += result.consumed;
4021
5056
  matched = true;
@@ -4282,7 +5317,8 @@ function createCellContentAccumulator() {
4282
5317
  addInline(element) {
4283
5318
  currentSegment.push(element);
4284
5319
  },
4285
- addInlineElements(nextElements) {
5320
+ addInlineElements(nextElements, stripLeadingLineBreak) {
5321
+ stripAutomaticLineBreak(currentSegment, stripLeadingLineBreak);
4286
5322
  currentSegment.push(...nextElements);
4287
5323
  },
4288
5324
  addBlockElements(blockElements) {
@@ -4382,7 +5418,7 @@ function consumeCellContentNewline(ctx, startPos, content) {
4382
5418
  return { consumed };
4383
5419
  }
4384
5420
  if (!content.isEmpty()) {
4385
- content.addInline({ element: "line-break" });
5421
+ content.addInline(createAutomaticLineBreak(ctx.tokens[startPos]));
4386
5422
  }
4387
5423
  return { consumed };
4388
5424
  }
@@ -4433,7 +5469,7 @@ function parseCellContent(ctx, closeCondition) {
4433
5469
  for (const rule of getCandidateInlineRules(ctx.inlineRules, token5.type)) {
4434
5470
  const result = rule.parse(inlineCtx);
4435
5471
  if (result.success) {
4436
- content.addInlineElements(result.elements);
5472
+ content.addInlineElements(result.elements, result.stripLeadingLineBreak);
4437
5473
  consumed += result.consumed;
4438
5474
  pos += result.consumed;
4439
5475
  matched = true;
@@ -4735,8 +5771,18 @@ var rateModuleRule = {
4735
5771
  name: "module-rate",
4736
5772
  acceptsNames: ["rate"],
4737
5773
  hasBody: false,
4738
- parse() {
4739
- return { module: "rate" };
5774
+ parse(_ctx, _pos, args) {
5775
+ return { module: "rate", ref: Object.keys(args).length === 0 ? { kind: "main" } : null };
5776
+ }
5777
+ };
5778
+ var customRateModuleRule = {
5779
+ name: "module-custom-rate",
5780
+ acceptsNames: ["customrate"],
5781
+ hasBody: false,
5782
+ parse(_ctx, _pos, args) {
5783
+ const key = args.key;
5784
+ const valid = key !== undefined && key !== "" && Object.keys(args).every((name) => name === "key");
5785
+ return { module: "custom-rate", ref: valid ? { kind: "custom", axisKey: key } : null };
4740
5786
  }
4741
5787
  };
4742
5788
 
@@ -4871,6 +5917,7 @@ var listPagesModuleRule = {
4871
5917
  "created-at": createdAt,
4872
5918
  "updated-at": updatedAt,
4873
5919
  rating,
5920
+ "rating-axis": args["rating-axis"],
4874
5921
  votes,
4875
5922
  name,
4876
5923
  fullname,
@@ -5014,6 +6061,7 @@ var tagCloudModuleRule = {
5014
6061
  // packages/parser/src/parser/rules/block/module/mapping.ts
5015
6062
  var MODULE_RULES = [
5016
6063
  rateModuleRule,
6064
+ customRateModuleRule,
5017
6065
  cssModuleRule,
5018
6066
  backlinksModuleRule,
5019
6067
  categoriesModuleRule,
@@ -5584,22 +6632,6 @@ function getDayOfYear(date) {
5584
6632
  function splitContentSections(content) {
5585
6633
  return content.split(/^={4,}$/m).map((section) => section.trim());
5586
6634
  }
5587
- function getSummary(page) {
5588
- if (page.content) {
5589
- const sections = splitContentSections(page.content);
5590
- if (sections.length > 1) {
5591
- return sections[0]?.trim() ?? "";
5592
- }
5593
- }
5594
- return getFirstParagraph(page.content);
5595
- }
5596
- function getFirstParagraph(content) {
5597
- if (!content)
5598
- return "";
5599
- const stripped = content.replace(/^(\+{1,6}) (.*)/gm, "").replace(/^\[\[toc(\s[^\]]+)?\]\]/gim, "").replace(/^\[\[\/?div(\s[^\]]+)?\]\]/gim, "").replace(/^\[\[\/?module(\s[^\]]+)?\]\]/gim, "").trim();
5600
- const paragraphs = stripped.split(/\n{2,}/);
5601
- return paragraphs[0]?.trim() ?? "";
5602
- }
5603
6635
  // packages/parser/src/parser/rules/block/module/listpages/template/format/tags.ts
5604
6636
  function formatTagsLinked(tags, prefix) {
5605
6637
  if (tags.length === 0)
@@ -5612,6 +6644,16 @@ function formatUserLinked(user) {
5612
6644
  return "Anonymous";
5613
6645
  return `[[*user ${user.name}]]`;
5614
6646
  }
6647
+ // packages/parser/src/parser/rules/block/module/listpages/template/getters/parameterized.ts
6648
+ var import_ast2 = require("@wdprlib/ast");
6649
+
6650
+ // packages/parser/src/parser/rules/block/module/listpages/template/literal.ts
6651
+ function literalWikitext(value) {
6652
+ if (value === "")
6653
+ return "";
6654
+ return `@<${Array.from(value, (char) => `&#${char.codePointAt(0)};`).join("")}>@`;
6655
+ }
6656
+
5615
6657
  // packages/parser/src/parser/rules/block/module/listpages/template/getters/parameterized.ts
5616
6658
  function createBraceParamGetter(name, param) {
5617
6659
  switch (name) {
@@ -5640,7 +6682,7 @@ function createParenParamGetter(name, param) {
5640
6682
  if (name !== "preview")
5641
6683
  return null;
5642
6684
  const len = Number(param);
5643
- return (ctx) => (ctx.page.content ?? "").slice(0, len);
6685
+ return (ctx) => literalWikitext(import_ast2.excerptText(ctx.page.readableText ?? "", { maxLength: len }));
5644
6686
  }
5645
6687
  function createFormattedGetter(name, format) {
5646
6688
  switch (name) {
@@ -5667,6 +6709,7 @@ function createTagsLinkedGetter(name, format) {
5667
6709
  }
5668
6710
 
5669
6711
  // packages/parser/src/parser/rules/block/module/listpages/template/getters/simple.ts
6712
+ var import_ast3 = require("@wdprlib/ast");
5670
6713
  var DEFAULT_PREVIEW_LENGTH = 200;
5671
6714
  var SIMPLE_GETTERS = {
5672
6715
  created_at: (ctx) => formatDate(ctx.page.createdAt),
@@ -5696,14 +6739,14 @@ var SIMPLE_GETTERS = {
5696
6739
  parent_title: (ctx) => ctx.page.parentTitle ?? "",
5697
6740
  parent_title_linked: (ctx) => ctx.page.parentFullname ? `[[[${ctx.page.parentFullname} | ${ctx.page.parentTitle}]]]` : "",
5698
6741
  content: (ctx) => ctx.page.content ?? "",
5699
- preview: (ctx) => (ctx.page.content ?? "").slice(0, DEFAULT_PREVIEW_LENGTH),
5700
- summary: (ctx) => getSummary(ctx.page),
5701
- first_paragraph: (ctx) => getFirstParagraph(ctx.page.content),
6742
+ preview: (ctx) => literalWikitext(import_ast3.excerptText(ctx.page.readableText ?? "", { maxLength: DEFAULT_PREVIEW_LENGTH })),
6743
+ summary: (ctx) => literalWikitext(ctx.page.firstParagraph ?? ""),
6744
+ first_paragraph: (ctx) => literalWikitext(ctx.page.firstParagraph ?? ""),
5702
6745
  tags: (ctx) => ctx.page.tags.join(" "),
5703
6746
  _tags: (ctx) => ctx.page.hiddenTags.join(" "),
5704
6747
  children: (ctx) => String(ctx.page.children),
5705
6748
  comments: (ctx) => String(ctx.page.comments),
5706
- size: (ctx) => String(ctx.page.size),
6749
+ size: (ctx) => ctx.page.readableText !== undefined ? String(import_ast3.countCharacters(ctx.page.readableText)) : ctx.page.size !== undefined ? String(ctx.page.size) : "",
5707
6750
  rating: (ctx) => String(ctx.page.rating),
5708
6751
  rating_votes: (ctx) => String(ctx.page.ratingVotes),
5709
6752
  rating_percent: (ctx) => String(ctx.page.ratingPercent ?? 0),
@@ -5717,11 +6760,136 @@ var SIMPLE_GETTERS = {
5717
6760
  site_domain: (ctx) => ctx.site.domain
5718
6761
  };
5719
6762
 
6763
+ // packages/parser/src/parser/rules/block/module/listpages/template/getters/registered.ts
6764
+ function createRegisteredGetter(name, key, format) {
6765
+ if (name === "metadata")
6766
+ return (ctx) => {
6767
+ const metadata = ctx.page.metadata;
6768
+ const entry = metadata && Object.hasOwn(metadata, key) ? metadata[key] : null;
6769
+ if (!entry)
6770
+ return "";
6771
+ let value;
6772
+ switch (entry.type) {
6773
+ case "text":
6774
+ value = entry.value;
6775
+ break;
6776
+ case "number":
6777
+ value = String(entry.value);
6778
+ break;
6779
+ case "date":
6780
+ value = formatDate(entry.value, format);
6781
+ break;
6782
+ case "user":
6783
+ value = format === "id" ? String(entry.value.id) : format === "unix" ? entry.value.unixName : entry.value.name;
6784
+ break;
6785
+ }
6786
+ return literalWikitext(value);
6787
+ };
6788
+ if (name === "customrate" || name === "customrate_votes" || name === "customrate_percent")
6789
+ return (ctx) => {
6790
+ const ratings = ctx.page.customRates;
6791
+ const entry = ratings && Object.hasOwn(ratings, key) ? ratings[key] : null;
6792
+ if (!entry)
6793
+ return "";
6794
+ return String(name === "customrate" ? entry.points : name === "customrate_votes" ? entry.votes : entry.percent);
6795
+ };
6796
+ return null;
6797
+ }
6798
+
6799
+ // packages/parser/src/parser/rules/block/module/listpages/template/getters/index.ts
6800
+ var import_ast4 = require("@wdprlib/ast");
6801
+
6802
+ // packages/parser/src/parser/rules/block/module/listpages/template/excerpt.ts
6803
+ function findExcerptEnd(template, contentStart) {
6804
+ let quote;
6805
+ for (let cursor = contentStart + "excerpt{".length;cursor < template.length; cursor++) {
6806
+ const char = template[cursor];
6807
+ if (quote) {
6808
+ if (char === "\\")
6809
+ cursor++;
6810
+ else if (char === quote)
6811
+ quote = undefined;
6812
+ } else if (char === '"' || char === "'") {
6813
+ quote = char;
6814
+ } else if (char === "}") {
6815
+ return template.startsWith("%%", cursor + 1) ? cursor + 1 : -1;
6816
+ }
6817
+ }
6818
+ return -1;
6819
+ }
6820
+ function parseExcerptOptions(input) {
6821
+ if (input.length > 8192)
6822
+ return null;
6823
+ const attributes = new Map;
6824
+ let cursor = 0;
6825
+ while (cursor < input.length) {
6826
+ while (cursor < input.length && /\s/.test(input[cursor]))
6827
+ cursor++;
6828
+ if (cursor === input.length)
6829
+ break;
6830
+ const start = cursor;
6831
+ while (cursor < input.length && /[a-z]/.test(input[cursor]))
6832
+ cursor++;
6833
+ const name = input.slice(start, cursor);
6834
+ if (!["pattern", "flags", "group", "match", "max"].includes(name) || attributes.has(name))
6835
+ return null;
6836
+ while (cursor < input.length && /\s/.test(input[cursor]))
6837
+ cursor++;
6838
+ if (input[cursor++] !== "=")
6839
+ return null;
6840
+ while (cursor < input.length && /\s/.test(input[cursor]))
6841
+ cursor++;
6842
+ const quote = input[cursor++];
6843
+ if (quote !== '"' && quote !== "'")
6844
+ return null;
6845
+ let value = "";
6846
+ while (cursor < input.length && input[cursor] !== quote) {
6847
+ if (input[cursor] === "\\" && (input[cursor + 1] === quote || input[cursor + 1] === "\\")) {
6848
+ value += input[cursor + 1] === quote ? quote : "\\\\";
6849
+ cursor += 2;
6850
+ } else
6851
+ value += input[cursor++];
6852
+ }
6853
+ if (input[cursor++] !== quote)
6854
+ return null;
6855
+ if (cursor < input.length && !/\s/.test(input[cursor]))
6856
+ return null;
6857
+ attributes.set(name, value);
6858
+ }
6859
+ const pattern = attributes.get("pattern");
6860
+ if (pattern === undefined)
6861
+ return null;
6862
+ const group = attributes.get("group");
6863
+ const match = attributes.get("match");
6864
+ const max = attributes.get("max");
6865
+ if (match !== undefined && !/^\d+$/.test(match) || max !== undefined && !/^\d+$/.test(max))
6866
+ return null;
6867
+ return {
6868
+ pattern,
6869
+ flags: attributes.get("flags"),
6870
+ group: group === undefined ? undefined : /^\d+$/.test(group) ? Number(group) : group,
6871
+ match: match === undefined ? undefined : Number(match),
6872
+ maxLength: max === undefined ? undefined : Number(max)
6873
+ };
6874
+ }
6875
+
5720
6876
  // packages/parser/src/parser/rules/block/module/listpages/template/getters/index.ts
5721
6877
  function createVariableGetter(name, braceParam, parenParam, format) {
6878
+ if (name === "excerpt") {
6879
+ const options = braceParam === undefined ? null : parseExcerptOptions(braceParam);
6880
+ if (!options || parenParam !== undefined || format !== undefined)
6881
+ return () => "";
6882
+ const extract = import_ast4.compileTextExcerpt(options);
6883
+ return (ctx) => literalWikitext(extract(ctx.page.readableText ?? ""));
6884
+ }
6885
+ const registeredGetter = braceParam !== undefined ? createRegisteredGetter(name, braceParam, format) : null;
6886
+ if (registeredGetter)
6887
+ return registeredGetter;
5722
6888
  const braceGetter = braceParam !== undefined ? createBraceParamGetter(name, braceParam) : null;
5723
6889
  if (braceGetter)
5724
6890
  return braceGetter;
6891
+ if (braceParam !== undefined)
6892
+ return () => "";
5725
6893
  const parenGetter = parenParam !== undefined ? createParenParamGetter(name, parenParam) : null;
5726
6894
  if (parenGetter)
5727
6895
  return parenGetter;
@@ -5743,7 +6911,7 @@ function scanTemplateVariables(template) {
5743
6911
  if (start === -1)
5744
6912
  break;
5745
6913
  const contentStart = start + 2;
5746
- const end = template.indexOf("%%", contentStart);
6914
+ const end = template.slice(contentStart, contentStart + 8).toLowerCase() === "excerpt{" ? findExcerptEnd(template, contentStart) : template.indexOf("%%", contentStart);
5747
6915
  if (end === -1)
5748
6916
  break;
5749
6917
  const parsed = parseTemplateVariableContent(template.slice(contentStart, end));
@@ -5766,6 +6934,11 @@ function parseTemplateVariableContent(content) {
5766
6934
  if (cursor === 0)
5767
6935
  return null;
5768
6936
  const name = content.slice(0, cursor);
6937
+ if (name.toLowerCase() === "excerpt" && content[cursor] === "{") {
6938
+ if (!content.endsWith("}"))
6939
+ return null;
6940
+ return { name, braceParam: content.slice(cursor + 1, -1) };
6941
+ }
5769
6942
  let braceParam;
5770
6943
  let parenParam;
5771
6944
  let format;
@@ -5845,6 +7018,7 @@ function buildQuery(module2) {
5845
7018
  updatedAt: module2["updated-at"],
5846
7019
  createdBy: module2["created-by"],
5847
7020
  rating: module2.rating,
7021
+ ratingAxis: module2["rating-axis"],
5848
7022
  votes: module2.votes,
5849
7023
  name: module2.name,
5850
7024
  fullname: module2.fullname,
@@ -5899,6 +7073,7 @@ var KNOWN_VARIABLES = new Set([
5899
7073
  "parent_title_linked",
5900
7074
  "content",
5901
7075
  "preview",
7076
+ "excerpt",
5902
7077
  "summary",
5903
7078
  "first_paragraph",
5904
7079
  "tags",
@@ -5936,12 +7111,24 @@ function extractVariablesFromTemplate(template) {
5936
7111
  const contentIndices = new Set;
5937
7112
  const previewLengths = new Set;
5938
7113
  const formFields = new Set;
7114
+ const metadataKeys = new Set;
7115
+ const customRateKeys = new Set;
5939
7116
  let tagsLinkPrefix;
5940
7117
  let hiddenTagsLinkPrefix;
5941
7118
  for (const match of scanTemplateVariables(template)) {
5942
7119
  const varName = match.name.toLowerCase();
5943
7120
  if (match.braceParam !== undefined) {
5944
7121
  switch (varName) {
7122
+ case "metadata":
7123
+ metadataKeys.add(match.braceParam);
7124
+ variables.add("metadata");
7125
+ continue;
7126
+ case "customrate":
7127
+ case "customrate_votes":
7128
+ case "customrate_percent":
7129
+ customRateKeys.add(match.braceParam);
7130
+ variables.add(varName);
7131
+ continue;
5945
7132
  case "content":
5946
7133
  contentIndices.add(Number(match.braceParam));
5947
7134
  variables.add("content_n");
@@ -5991,6 +7178,8 @@ function extractVariablesFromTemplate(template) {
5991
7178
  contentIndices: Array.from(contentIndices).sort((a, b) => a - b),
5992
7179
  previewLengths: Array.from(previewLengths).sort((a, b) => a - b),
5993
7180
  formFields: Array.from(formFields).sort(),
7181
+ metadataKeys: [...metadataKeys].sort(),
7182
+ customRateKeys: [...customRateKeys].sort(),
5994
7183
  tagsLinkPrefix,
5995
7184
  hiddenTagsLinkPrefix
5996
7185
  };
@@ -6012,6 +7201,9 @@ function extractListPagesModule(listPages, state, result) {
6012
7201
  contentSectionIndices: extraction.contentIndices,
6013
7202
  previewLengths: extraction.previewLengths,
6014
7203
  formFields: extraction.formFields,
7204
+ metadataKeys: extraction.metadataKeys,
7205
+ customRateKeys: extraction.customRateKeys,
7206
+ needsReadableText: extraction.variables.some((name) => ["preview", "preview_n", "summary", "first_paragraph", "excerpt"].includes(name)),
6015
7207
  tagsLinkPrefix: extraction.tagsLinkPrefix,
6016
7208
  hiddenTagsLinkPrefix: extraction.hiddenTagsLinkPrefix,
6017
7209
  urlAttrPrefix: listPages["url-attr-prefix"],
@@ -6344,13 +7536,101 @@ function wrapListPagesResult(module2, items, parse, data) {
6344
7536
  return result;
6345
7537
  }
6346
7538
 
7539
+ // packages/parser/src/parser/rules/block/module/rate/resolve.ts
7540
+ function refKey(ref) {
7541
+ return ref.kind === "main" ? "main" : `custom:${ref.axisKey}`;
7542
+ }
7543
+ async function resolveRatings(ast, fetchRatings) {
7544
+ const refs = new Map;
7545
+ mapDocument(ast, (element) => {
7546
+ if (isRating(element) && element.data.ref)
7547
+ refs.set(refKey(element.data.ref), element.data.ref);
7548
+ return element;
7549
+ });
7550
+ const states = new Map;
7551
+ if (fetchRatings && refs.size > 0) {
7552
+ for (const state of await fetchRatings([...refs.values()])) {
7553
+ const key = refKey(state.ref);
7554
+ if (refs.has(key))
7555
+ states.set(key, state);
7556
+ }
7557
+ }
7558
+ return mapDocument(ast, (element) => {
7559
+ if (!isRating(element))
7560
+ return element;
7561
+ const { state: _previous, ...data } = element.data;
7562
+ const state = data.ref ? states.get(refKey(data.ref)) : undefined;
7563
+ return { ...element, data: state ? { ...data, state } : data };
7564
+ });
7565
+ }
7566
+ function suppressModuleRatings(result) {
7567
+ const ast = mapDocument(getModuleParseAst(result), (element) => isRating(element) ? null : element);
7568
+ return "ast" in result ? { ...result, ast } : ast;
7569
+ }
7570
+ function isRating(element) {
7571
+ return element.element === "module" && (element.data.module === "rate" || element.data.module === "custom-rate");
7572
+ }
7573
+ function mapDocument(ast, transform) {
7574
+ const map = (elements) => elements.flatMap((original) => {
7575
+ const element = transform(original);
7576
+ if (!element)
7577
+ return [];
7578
+ if (element.element === "if") {
7579
+ return [
7580
+ {
7581
+ ...element,
7582
+ data: {
7583
+ ...element.data,
7584
+ then: map(element.data.then),
7585
+ else: map(element.data.else)
7586
+ }
7587
+ }
7588
+ ];
7589
+ }
7590
+ if (element.element === "ifexpr") {
7591
+ return [
7592
+ {
7593
+ ...element,
7594
+ data: {
7595
+ ...element.data,
7596
+ then: map(element.data.then),
7597
+ else: map(element.data.else)
7598
+ }
7599
+ }
7600
+ ];
7601
+ }
7602
+ if (element.element === "bibliography-block") {
7603
+ return [
7604
+ {
7605
+ ...element,
7606
+ data: {
7607
+ ...element.data,
7608
+ entries: element.data.entries.map((entry) => ({
7609
+ ...entry,
7610
+ key: map(entry.key),
7611
+ value: map(entry.value)
7612
+ }))
7613
+ }
7614
+ }
7615
+ ];
7616
+ }
7617
+ return [mapElementChildren(element, map)];
7618
+ });
7619
+ return {
7620
+ ...ast,
7621
+ elements: map(ast.elements),
7622
+ ...ast.footnotes ? { footnotes: ast.footnotes.map(map) } : {}
7623
+ };
7624
+ }
7625
+
6347
7626
  // packages/parser/src/parser/rules/block/module/listpages/resolve.ts
6348
7627
  function isListPagesModule2(module2) {
6349
7628
  return module2.module === "list-pages";
6350
7629
  }
6351
7630
  function resolveListPages(module2, data, compiledTemplate, parse) {
6352
- const items = renderListPagesItems(module2, data, compiledTemplate, parse);
6353
- return wrapListPagesResult(module2, items, parse, data);
7631
+ const parseFragment = (source) => suppressModuleRatings(parse(source));
7632
+ const items = renderListPagesItems(module2, data, compiledTemplate, parseFragment);
7633
+ return wrapListPagesResult(module2, items, parseFragment, data);
6354
7634
  }
6355
7635
  // packages/parser/src/parser/rules/block/module/listpages/types/external-data.ts
6356
7636
  function definePageData(input) {
@@ -6364,7 +7644,6 @@ function definePageData(input) {
6364
7644
  hiddenTags: input.hiddenTags ?? [],
6365
7645
  children: input.children ?? 0,
6366
7646
  comments: input.comments ?? 0,
6367
- size: input.size ?? 0,
6368
7647
  rating: input.rating ?? 0,
6369
7648
  ratingVotes: input.ratingVotes ?? 0,
6370
7649
  revisions: input.revisions ?? 0
@@ -6442,6 +7721,7 @@ var URL_RESOLVABLE_FIELDS = [
6442
7721
  aliases: ["createdby", "created_by"]
6443
7722
  },
6444
7723
  { attr: "rating", queryKey: "rating", type: "string" },
7724
+ { attr: "rating-axis", queryKey: "ratingAxis", type: "string" },
6445
7725
  { attr: "votes", queryKey: "votes", type: "string" },
6446
7726
  { attr: "reverse", queryKey: "reverse", type: "boolean" }
6447
7727
  ];
@@ -6543,6 +7823,7 @@ var ORDER_FIELD_MAP = {
6543
7823
  votes: "votes",
6544
7824
  revisions: "revisions",
6545
7825
  comments: "comments",
7826
+ commented_at: "commented_at",
6546
7827
  pagelength: "size",
6547
7828
  size: "size",
6548
7829
  random: "random",
@@ -6550,6 +7831,13 @@ var ORDER_FIELD_MAP = {
6550
7831
  updated_at: "updated_at"
6551
7832
  };
6552
7833
  function parseOrder(value) {
7834
+ const metadata = /^metadata\{([^}]*)\}(?:\s+(asc|desc))?$/i.exec(value.trim());
7835
+ if (metadata)
7836
+ return {
7837
+ field: "metadata",
7838
+ key: metadata[1],
7839
+ direction: metadata[2]?.toLowerCase() === "asc" ? "asc" : "desc"
7840
+ };
6553
7841
  const defaultOrder = { field: "created_at", direction: "desc" };
6554
7842
  const trimmed = value.trim().toLowerCase();
6555
7843
  if (!trimmed)
@@ -6669,6 +7957,8 @@ function normalizeQuery(query) {
6669
7957
  result.linkTo = query.linkTo;
6670
7958
  if (query.createdBy)
6671
7959
  result.createdBy = query.createdBy;
7960
+ if (query.ratingAxis !== undefined)
7961
+ result.ratingAxis = query.ratingAxis;
6672
7962
  if (query.name)
6673
7963
  result.name = query.name;
6674
7964
  if (query.fullname)
@@ -6853,7 +8143,7 @@ function resolveIfTags(data, pageTags) {
6853
8143
  // packages/parser/src/parser/preprocess/utils/raw-regions.ts
6854
8144
  var BASE_PLACEHOLDER_OPEN = "";
6855
8145
  var BASE_PLACEHOLDER_CLOSE = "";
6856
- var RAW_BLOCK_OPEN_PATTERN = /\[\[\s*(code|html)\b[^\]]*\]\]/iy;
8146
+ var RAW_BLOCK_OPEN_PATTERN = /\[\[html\b[^\]]*\]\]/iy;
6857
8147
  function makeUniqueSentinels(source) {
6858
8148
  let openRun = 0;
6859
8149
  let closeRun = 0;
@@ -6872,10 +8162,21 @@ function makeUniqueSentinels(source) {
6872
8162
  }
6873
8163
  function maskRawRegions(source, sentinels) {
6874
8164
  const placeholders = [];
8165
+ let tokens;
8166
+ const getTokens = () => tokens ??= tokenize(source);
6875
8167
  let masked = "";
6876
8168
  let i = 0;
6877
8169
  while (i < source.length) {
6878
- const rawBlock = tryMaskRawBlock(source, i, placeholders, sentinels);
8170
+ if (source.startsWith("[!--", i)) {
8171
+ const close = source.indexOf("--]", i + 4);
8172
+ if (close !== -1) {
8173
+ const end = close + 3;
8174
+ masked += source.slice(i, end);
8175
+ i = end;
8176
+ continue;
8177
+ }
8178
+ }
8179
+ const rawBlock = tryMaskRawBlock(source, i, placeholders, sentinels, getTokens);
6879
8180
  if (rawBlock) {
6880
8181
  masked += rawBlock.placeholder;
6881
8182
  i = rawBlock.end;
@@ -6896,16 +8197,25 @@ function restorePlaceholders(source, placeholders, sentinels) {
6896
8197
  const pattern = new RegExp(`${escapeRegex(sentinels.open)}(\\d+)${escapeRegex(sentinels.close)}`, "g");
6897
8198
  return source.replace(pattern, (_, idx) => placeholders[Number(idx)] ?? "");
6898
8199
  }
6899
- function tryMaskRawBlock(source, pos, placeholders, sentinels) {
8200
+ function tryMaskRawBlock(source, pos, placeholders, sentinels, getTokens) {
6900
8201
  if (source[pos] !== "[" || source[pos + 1] !== "[")
6901
8202
  return null;
8203
+ if (source.slice(pos, pos + 6).toLowerCase() === "[[code") {
8204
+ const tokens = getTokens();
8205
+ const start = tokenAtOffset(tokens, pos);
8206
+ const open = findCodeOpen(tokens, start);
8207
+ if (!open)
8208
+ return null;
8209
+ const bounds = open.closingSwallowed ? null : findCodeBodyBounds(tokens, open.bodyStart);
8210
+ const end = open.closingSwallowed ? tokens[open.attributesEnd - 1].position.end.offset : bounds.foundClose ? tokens[bounds.end - 1].position.end.offset : source.length;
8211
+ return { placeholder: pushPlaceholder(placeholders, source.slice(pos, end), sentinels), end };
8212
+ }
6902
8213
  RAW_BLOCK_OPEN_PATTERN.lastIndex = pos;
6903
8214
  const openMatch = RAW_BLOCK_OPEN_PATTERN.exec(source);
6904
8215
  if (!openMatch)
6905
8216
  return null;
6906
- const name = openMatch[1].toLowerCase();
6907
8217
  const openLen = openMatch[0].length;
6908
- const closePattern = new RegExp(`\\[\\[\\/\\s*${name}\\s*\\]\\]`, "ig");
8218
+ const closePattern = /\[\[\/\s*html\s*\]\]/gi;
6909
8219
  closePattern.lastIndex = pos + openLen;
6910
8220
  const closeMatch = closePattern.exec(source);
6911
8221
  if (closeMatch) {
@@ -6915,12 +8225,19 @@ function tryMaskRawBlock(source, pos, placeholders, sentinels) {
6915
8225
  end
6916
8226
  };
6917
8227
  }
6918
- if (name !== "code")
6919
- return null;
6920
- return {
6921
- placeholder: pushPlaceholder(placeholders, source.slice(pos), sentinels),
6922
- end: source.length
6923
- };
8228
+ return null;
8229
+ }
8230
+ function tokenAtOffset(tokens, offset) {
8231
+ let low = 0;
8232
+ let high = tokens.length;
8233
+ while (low < high) {
8234
+ const middle = Math.floor((low + high) / 2);
8235
+ if (tokens[middle].position.start.offset < offset)
8236
+ low = middle + 1;
8237
+ else
8238
+ high = middle;
8239
+ }
8240
+ return tokens[low]?.position.start.offset === offset ? low : tokens.length;
6924
8241
  }
6925
8242
  function tryMaskRawInline(source, pos, placeholders, sentinels) {
6926
8243
  if (source[pos] === "@" && source[pos + 1] === "<") {
@@ -6932,17 +8249,22 @@ function tryMaskRawInline(source, pos, placeholders, sentinels) {
6932
8249
  return null;
6933
8250
  }
6934
8251
  function tryMaskSingleLineRaw(source, pos, openerLength, close, placeholders, sentinels) {
6935
- const closePos = source.indexOf(close, pos + openerLength);
6936
- const newline = source.indexOf(`
6937
- `, pos + openerLength);
6938
- if (closePos === -1 || newline !== -1 && newline < closePos)
8252
+ const end = singleLineRawEnd(source, pos, openerLength, close);
8253
+ if (end === pos)
6939
8254
  return null;
6940
- const end = closePos + close.length;
6941
8255
  return {
6942
8256
  placeholder: pushPlaceholder(placeholders, source.slice(pos, end), sentinels),
6943
8257
  end
6944
8258
  };
6945
8259
  }
8260
+ function singleLineRawEnd(source, pos, openerLength, close) {
8261
+ const closePos = source.indexOf(close, pos + openerLength);
8262
+ const newline = source.indexOf(`
8263
+ `, pos + openerLength);
8264
+ if (closePos === -1 || newline !== -1 && newline < closePos)
8265
+ return pos;
8266
+ return closePos + close.length;
8267
+ }
6946
8268
  function pushPlaceholder(placeholders, text, sentinels) {
6947
8269
  const idx = placeholders.length;
6948
8270
  placeholders.push(text);
@@ -7544,7 +8866,7 @@ function isListUsersModule2(module2) {
7544
8866
  function resolveListUsers(_module, data, compiledTemplate, parse) {
7545
8867
  const ctx = { user: data.user };
7546
8868
  const substituted = compiledTemplate(ctx);
7547
- const itemAst = getModuleParseAst(parse(substituted));
8869
+ const itemAst = getModuleParseAst(suppressModuleRatings(parse(substituted)));
7548
8870
  return itemAst.elements;
7549
8871
  }
7550
8872
  // packages/parser/src/parser/rules/block/module/resolution/data-maps.ts
@@ -7746,7 +9068,7 @@ function countDynamicModules(elements) {
7746
9068
  }
7747
9069
 
7748
9070
  // packages/parser/src/parser/rules/block/module/resolution/styles.ts
7749
- var import_ast2 = require("@wdprlib/ast");
9071
+ var import_ast5 = require("@wdprlib/ast");
7750
9072
  function collectStyles(elements, ignoredAnchors = new WeakSet) {
7751
9073
  const styles = [];
7752
9074
  const anchoredStyles = [];
@@ -7774,10 +9096,10 @@ function mergeCollectedStyles(existing, collected, resolvedSlots = new Map, anch
7774
9096
  return merged;
7775
9097
  }
7776
9098
  function isStyleSlotMarker(style) {
7777
- return style.startsWith(import_ast2.STYLE_SLOT_PREFIX);
9099
+ return style.startsWith(import_ast5.STYLE_SLOT_PREFIX);
7778
9100
  }
7779
9101
  function createStyleSlotMarker(slotId) {
7780
- return `${import_ast2.STYLE_SLOT_PREFIX}${slotId}`;
9102
+ return `${import_ast5.STYLE_SLOT_PREFIX}${slotId}`;
7781
9103
  }
7782
9104
  function getStyleSlotId(data) {
7783
9105
  const slotId = data._styleSlot;
@@ -7789,7 +9111,7 @@ function removeFirst(values, value) {
7789
9111
  values.splice(index, 1);
7790
9112
  }
7791
9113
  function appendStyleSlot(target, marker, resolvedSlots) {
7792
- const slotId = Number(marker.slice(import_ast2.STYLE_SLOT_PREFIX.length));
9114
+ const slotId = Number(marker.slice(import_ast5.STYLE_SLOT_PREFIX.length));
7793
9115
  const replacement = resolvedSlots.get(slotId);
7794
9116
  if (replacement)
7795
9117
  target.push(...replacement);
@@ -7819,24 +9141,24 @@ function collectStylesFromElements(elements, styles, anchoredStyles, anchors, ig
7819
9141
  for (const element of elements) {
7820
9142
  if (element.element === "style") {
7821
9143
  const css = element.data;
7822
- if (css.startsWith(import_ast2.STYLE_SLOT_PREFIX)) {
9144
+ if (css.startsWith(import_ast5.STYLE_SLOT_PREFIX)) {
7823
9145
  styles.push(css);
7824
9146
  continue;
7825
9147
  }
7826
- if (css.startsWith(import_ast2.STYLE_ANCHOR_PREFIX)) {
9148
+ if (css.startsWith(import_ast5.STYLE_ANCHOR_PREFIX)) {
7827
9149
  anchors.push(element);
7828
9150
  if (ignoredAnchors.has(element)) {
7829
9151
  result.push(element);
7830
9152
  continue;
7831
9153
  }
7832
- const anchoredCss = css.slice(import_ast2.STYLE_ANCHOR_PREFIX.length);
9154
+ const anchoredCss = css.slice(import_ast5.STYLE_ANCHOR_PREFIX.length);
7833
9155
  styles.push(anchoredCss);
7834
9156
  anchoredStyles.push(anchoredCss);
7835
9157
  result.push(element);
7836
9158
  continue;
7837
9159
  }
7838
9160
  styles.push(css);
7839
- const anchor = { element: "style", data: `${import_ast2.STYLE_ANCHOR_PREFIX}${css}` };
9161
+ const anchor = { element: "style", data: `${import_ast5.STYLE_ANCHOR_PREFIX}${css}` };
7840
9162
  anchors.push(anchor);
7841
9163
  result.push(anchor);
7842
9164
  continue;
@@ -7959,7 +9281,7 @@ function walkAndResolve(elements, ctx) {
7959
9281
  }
7960
9282
 
7961
9283
  // packages/parser/src/parser/rules/block/module/resolution/document.ts
7962
- var import_ast3 = require("@wdprlib/ast");
9284
+ var import_ast6 = require("@wdprlib/ast");
7963
9285
 
7964
9286
  // packages/parser/src/parser/toc.ts
7965
9287
  class TocIndexer {
@@ -8221,11 +9543,11 @@ function walkRenderOrder(elements, pageTags, callback) {
8221
9543
  for (const element of elements) {
8222
9544
  callback(element);
8223
9545
  if (element.element === "if") {
8224
- walkRenderOrder(import_ast3.isTruthy(element.data.condition) ? element.data.then : element.data.else, pageTags, callback);
9546
+ walkRenderOrder(import_ast6.isTruthy(element.data.condition) ? element.data.then : element.data.else, pageTags, callback);
8225
9547
  continue;
8226
9548
  }
8227
9549
  if (element.element === "ifexpr") {
8228
- const evaluated = import_ast3.evaluateExpression(element.data.expression);
9550
+ const evaluated = import_ast6.evaluateExpression(element.data.expression);
8229
9551
  if (evaluated.success) {
8230
9552
  walkRenderOrder(evaluated.value !== 0 ? element.data.then : element.data.else, pageTags, callback);
8231
9553
  }
@@ -8337,11 +9659,11 @@ async function resolveModules(ast, dataProvider, options) {
8337
9659
  if (mergedStyles.length > 0)
8338
9660
  result.styles = mergedStyles;
8339
9661
  options.onDiagnostics?.(registry.diagnostics);
8340
- return registry.finalize(result, finalElements, pageTags, containsSyntaxFootnoteBlock(ast.elements));
9662
+ return resolveRatings(registry.finalize(result, finalElements, pageTags, containsSyntaxFootnoteBlock(ast.elements)), dataProvider.fetchRatings);
8341
9663
  }
8342
9664
  function createModuleParseFunction(options, dataProvider, registry) {
8343
9665
  const transform = createModuleSourceTransform(options, dataProvider);
8344
- return (source) => registry.register(options.parse(transform ? transform(source) : source), {
9666
+ return (source) => registry.register(suppressModuleRatings(options.parse(transform ? transform(source) : source)), {
8345
9667
  stripLegacyImplicitFootnoteBlock: true
8346
9668
  });
8347
9669
  }
@@ -8358,7 +9680,7 @@ function createModuleSourceTransform(options, dataProvider) {
8358
9680
  }
8359
9681
 
8360
9682
  // packages/parser/src/parser/rules/block/module/index.ts
8361
- var import_ast4 = require("@wdprlib/ast");
9683
+ var import_ast7 = require("@wdprlib/ast");
8362
9684
 
8363
9685
  // packages/parser/src/parser/rules/block/footnoteblock/attributes.ts
8364
9686
  function parseFootnoteBlockAttributes(ctx, startPos) {
@@ -9048,7 +10370,7 @@ var tabviewRule = {
9048
10370
  };
9049
10371
 
9050
10372
  // packages/parser/src/parser/rules/block/include/index.ts
9051
- var import_ast5 = require("@wdprlib/ast");
10373
+ var import_ast8 = require("@wdprlib/ast");
9052
10374
 
9053
10375
  // packages/parser/src/parser/rules/block/include/arguments.ts
9054
10376
  function collectIncludeArguments(ctx, startPos) {
@@ -9099,177 +10421,42 @@ function parsePageRef(target) {
9099
10421
  };
9100
10422
  }
9101
10423
  }
9102
- return { site: null, page: target };
9103
- }
9104
-
9105
- // packages/parser/src/parser/rules/block/include/variables.ts
9106
- function parseVariables(tokens) {
9107
- const vars = {};
9108
- let current2 = "";
9109
- for (const token5 of tokens) {
9110
- if (token5 === "|") {
9111
- assignVariable(vars, current2);
9112
- current2 = "";
9113
- } else {
9114
- current2 += token5;
9115
- }
9116
- }
9117
- assignVariable(vars, current2);
9118
- return vars;
9119
- }
9120
- function assignVariable(vars, source) {
9121
- if (!source.trim()) {
9122
- return;
9123
- }
9124
- const eqIndex = source.indexOf("=");
9125
- if (eqIndex === -1) {
9126
- return;
9127
- }
9128
- const key = source.slice(0, eqIndex).trim();
9129
- if (!key) {
9130
- return;
9131
- }
9132
- vars[key] = source.slice(eqIndex + 1).trim();
9133
- }
9134
-
9135
- // packages/parser/src/parser/rules/block/include/index.ts
9136
- var includeRule = {
9137
- name: "include",
9138
- startTokens: ["BLOCK_OPEN"],
9139
- requiresLineStart: false,
9140
- parse(ctx) {
9141
- const openToken = currentToken(ctx);
9142
- if (openToken.type !== "BLOCK_OPEN") {
9143
- return { success: false };
9144
- }
9145
- let pos = ctx.pos + 1;
9146
- let consumed = 1;
9147
- const nameResult = parseBlockName(ctx, pos);
9148
- if (!nameResult || nameResult.name.toLowerCase() !== "include") {
9149
- return { success: false };
9150
- }
9151
- if (!ctx.settings.enablePageSyntax) {
9152
- return { success: false };
9153
- }
9154
- pos += nameResult.consumed;
9155
- consumed += nameResult.consumed;
9156
- while (ctx.tokens[pos]?.type === "WHITESPACE") {
9157
- pos++;
9158
- consumed++;
9159
- }
9160
- const args = collectIncludeArguments(ctx, pos);
9161
- pos += args.consumed;
9162
- consumed += args.consumed;
9163
- if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
9164
- return { success: false };
9165
- }
9166
- pos++;
9167
- consumed++;
9168
- const directiveEnd = pos;
9169
- if (ctx.tokens[pos]?.type === "NEWLINE") {
9170
- pos++;
9171
- consumed++;
9172
- }
9173
- if (!args.target) {
9174
- return { success: false };
9175
- }
9176
- const location = parsePageRef(args.target);
9177
- if (ctx.deferInclude?.(location)) {
9178
- const source = ctx.tokens.slice(ctx.pos, directiveEnd).map((token5) => token5.value).join("");
9179
- const elements = [];
9180
- const lines = source.split(`
9181
- `);
9182
- for (let index = 0;index < lines.length; index++) {
9183
- if (index > 0)
9184
- elements.push(import_ast5.lineBreak());
9185
- if (lines[index] !== "")
9186
- elements.push(import_ast5.text(lines[index]));
9187
- }
9188
- return { success: true, elements: [import_ast5.paragraph(elements)], consumed };
9189
- }
9190
- return {
9191
- success: true,
9192
- elements: [
9193
- {
9194
- element: "include",
9195
- data: {
9196
- "paragraph-safe": false,
9197
- variables: parseVariables(args.argumentTokens),
9198
- location,
9199
- elements: []
9200
- }
9201
- }
9202
- ],
9203
- consumed
9204
- };
9205
- }
9206
- };
9207
-
9208
- // packages/parser/src/parser/rules/block/math/content.ts
9209
- function collectMathContent(ctx, startPos) {
9210
- let latexSource = "";
9211
- let pos = startPos;
9212
- let consumed = 0;
9213
- let foundClose = false;
9214
- while (pos < ctx.tokens.length) {
9215
- const token5 = ctx.tokens[pos];
9216
- if (!token5)
9217
- break;
9218
- if (token5.type === "BLOCK_END_OPEN") {
9219
- const closeNameResult = parseBlockName(ctx, pos + 1);
9220
- if (closeNameResult?.name === "math") {
9221
- foundClose = true;
9222
- break;
9223
- }
9224
- }
9225
- latexSource += token5.type === "BACKSLASH_BREAK" ? "\\\n" : token5.value;
9226
- pos++;
9227
- consumed++;
9228
- }
9229
- return { latexSource, consumed, foundClose };
9230
- }
9231
- function consumeMathClose(ctx, startPos) {
9232
- let pos = startPos + 1;
9233
- let consumed = 1;
9234
- const closeNameResult = parseBlockName(ctx, pos);
9235
- if (closeNameResult) {
9236
- pos += closeNameResult.consumed;
9237
- consumed += closeNameResult.consumed;
9238
- }
9239
- if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
9240
- pos++;
9241
- consumed++;
9242
- }
9243
- if (ctx.tokens[pos]?.type === "NEWLINE") {
9244
- consumed++;
9245
- }
9246
- return consumed;
10424
+ return { site: null, page: target };
9247
10425
  }
9248
10426
 
9249
- // packages/parser/src/parser/rules/block/math/name.ts
9250
- function parseMathName(ctx, startPos) {
9251
- let pos = startPos;
9252
- let consumed = 0;
9253
- let name = "";
9254
- const first = ctx.tokens[pos];
9255
- if (first?.type !== "IDENTIFIER" && first?.type !== "TEXT") {
9256
- return { name: null, consumed: 0 };
9257
- }
9258
- while (pos < ctx.tokens.length) {
9259
- const token5 = ctx.tokens[pos];
9260
- if (!token5 || token5.type === "BLOCK_CLOSE" || token5.type === "WHITESPACE" || token5.type === "NEWLINE") {
9261
- break;
10427
+ // packages/parser/src/parser/rules/block/include/variables.ts
10428
+ function parseVariables(tokens) {
10429
+ const vars = {};
10430
+ let current2 = "";
10431
+ for (const token5 of tokens) {
10432
+ if (token5 === "|") {
10433
+ assignVariable(vars, current2);
10434
+ current2 = "";
10435
+ } else {
10436
+ current2 += token5;
9262
10437
  }
9263
- name += token5.value;
9264
- pos++;
9265
- consumed++;
9266
10438
  }
9267
- return { name: name === "" ? null : name, consumed };
10439
+ assignVariable(vars, current2);
10440
+ return vars;
10441
+ }
10442
+ function assignVariable(vars, source) {
10443
+ if (!source.trim()) {
10444
+ return;
10445
+ }
10446
+ const eqIndex = source.indexOf("=");
10447
+ if (eqIndex === -1) {
10448
+ return;
10449
+ }
10450
+ const key = source.slice(0, eqIndex).trim();
10451
+ if (!key) {
10452
+ return;
10453
+ }
10454
+ vars[key] = source.slice(eqIndex + 1).trim();
9268
10455
  }
9269
10456
 
9270
- // packages/parser/src/parser/rules/block/math/index.ts
9271
- var mathBlockRule = {
9272
- name: "math",
10457
+ // packages/parser/src/parser/rules/block/include/index.ts
10458
+ var includeRule = {
10459
+ name: "include",
9273
10460
  startTokens: ["BLOCK_OPEN"],
9274
10461
  requiresLineStart: false,
9275
10462
  parse(ctx) {
@@ -9280,7 +10467,10 @@ var mathBlockRule = {
9280
10467
  let pos = ctx.pos + 1;
9281
10468
  let consumed = 1;
9282
10469
  const nameResult = parseBlockName(ctx, pos);
9283
- if (!nameResult || nameResult.name !== "math") {
10470
+ if (!nameResult || nameResult.name.toLowerCase() !== "include") {
10471
+ return { success: false };
10472
+ }
10473
+ if (!ctx.settings.enablePageSyntax) {
9284
10474
  return { success: false };
9285
10475
  }
9286
10476
  pos += nameResult.consumed;
@@ -9289,49 +10479,46 @@ var mathBlockRule = {
9289
10479
  pos++;
9290
10480
  consumed++;
9291
10481
  }
9292
- const mathName = parseMathName(ctx, pos);
9293
- pos += mathName.consumed;
9294
- consumed += mathName.consumed;
9295
- while (ctx.tokens[pos]?.type === "WHITESPACE") {
9296
- pos++;
9297
- consumed++;
9298
- }
10482
+ const args = collectIncludeArguments(ctx, pos);
10483
+ pos += args.consumed;
10484
+ consumed += args.consumed;
9299
10485
  if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
9300
10486
  return { success: false };
9301
10487
  }
9302
10488
  pos++;
9303
10489
  consumed++;
10490
+ const directiveEnd = pos;
9304
10491
  if (ctx.tokens[pos]?.type === "NEWLINE") {
9305
10492
  pos++;
9306
10493
  consumed++;
9307
10494
  }
9308
- const contentResult = collectMathContent(ctx, pos);
9309
- const latexSource = contentResult.latexSource.trim();
9310
- consumed += contentResult.consumed;
9311
- pos += contentResult.consumed;
9312
- if (!contentResult.foundClose) {
9313
- ctx.diagnostics.push({
9314
- severity: "warning",
9315
- code: "unclosed-block",
9316
- message: "Missing closing tag [[/math]] for [[math]]",
9317
- position: openToken.position
9318
- });
9319
- } else {
9320
- const closeConsumed = consumeMathClose(ctx, pos);
9321
- pos += closeConsumed;
9322
- consumed += closeConsumed;
9323
- }
9324
- if (!latexSource) {
10495
+ if (!args.target) {
9325
10496
  return { success: false };
9326
10497
  }
10498
+ const location = parsePageRef(args.target);
10499
+ if (ctx.deferInclude?.(location)) {
10500
+ const source = ctx.tokens.slice(ctx.pos, directiveEnd).map((token5) => token5.value).join("");
10501
+ const elements = [];
10502
+ const lines = source.split(`
10503
+ `);
10504
+ for (let index = 0;index < lines.length; index++) {
10505
+ if (index > 0)
10506
+ elements.push(import_ast8.lineBreak());
10507
+ if (lines[index] !== "")
10508
+ elements.push(import_ast8.text(lines[index]));
10509
+ }
10510
+ return { success: true, elements: [import_ast8.paragraph(elements)], consumed };
10511
+ }
9327
10512
  return {
9328
10513
  success: true,
9329
10514
  elements: [
9330
10515
  {
9331
- element: "math",
10516
+ element: "include",
9332
10517
  data: {
9333
- name: mathName.name,
9334
- "latex-source": latexSource
10518
+ "paragraph-safe": false,
10519
+ variables: parseVariables(args.argumentTokens),
10520
+ location,
10521
+ elements: []
9335
10522
  }
9336
10523
  }
9337
10524
  ],
@@ -10201,7 +11388,8 @@ function parseBibliographyContent(ctx, startPos) {
10201
11388
  const inlineCtx = { ...ctx, pos };
10202
11389
  const result = parseInlineUntil(inlineCtx, "NEWLINE");
10203
11390
  if (result.elements.length > 0) {
10204
- content.push(...result.elements);
11391
+ for (const element of result.elements)
11392
+ content.push(element);
10205
11393
  pos += result.consumed;
10206
11394
  consumed += result.consumed;
10207
11395
  } else {
@@ -10630,6 +11818,7 @@ var blockRules = [
10630
11818
  iftagsRule,
10631
11819
  bibliographyRule,
10632
11820
  galleryRule,
11821
+ noteRule,
10633
11822
  divRule
10634
11823
  ];
10635
11824
  // packages/parser/src/parser/rules/inline/formatting/close.ts
@@ -10642,6 +11831,11 @@ function findFormattingClose(ctx, start, marker) {
10642
11831
  return null;
10643
11832
  if (!table && token5.type === "NEWLINE" && getParagraphNewlineBoundary(ctx, pos, true).shouldBreak)
10644
11833
  return null;
11834
+ const inlineRegionEnd = Math.max(emailRegionEnd(ctx.tokens, pos, end), parseButtonSyntax(ctx, pos, end)?.end ?? pos, parseSocialSyntax(ctx, pos, end)?.end ?? pos);
11835
+ if (inlineRegionEnd > pos) {
11836
+ pos = inlineRegionEnd - 1;
11837
+ continue;
11838
+ }
10645
11839
  if (token5.type === marker && !table?.suppressedClosers.has(pos))
10646
11840
  return pos;
10647
11841
  const protectedEnd = protectedInlineRegionEnd(ctx.tokens, pos, end);
@@ -10750,190 +11944,30 @@ var strikethroughRule = {
10750
11944
 
10751
11945
  // packages/parser/src/parser/rules/inline/superscript.ts
10752
11946
  var superscriptRule = {
10753
- name: "superscript",
10754
- startTokens: ["SUPER_MARKER"],
10755
- parse(ctx) {
10756
- return parseDelimitedContainer(ctx, "SUPER_MARKER", "superscript", {
10757
- discardEmpty: true
10758
- });
10759
- }
10760
- };
10761
-
10762
- // packages/parser/src/parser/rules/inline/subscript.ts
10763
- var subscriptRule = {
10764
- name: "subscript",
10765
- startTokens: ["SUB_MARKER"],
10766
- parse(ctx) {
10767
- return parseDelimitedContainer(ctx, "SUB_MARKER", "subscript", { discardEmpty: true });
10768
- }
10769
- };
10770
-
10771
- // packages/parser/src/parser/rules/inline/monospace.ts
10772
- var monospaceRule = {
10773
- name: "monospace",
10774
- startTokens: ["MONO_MARKER"],
10775
- parse(ctx) {
10776
- return parseDelimitedContainer(ctx, "MONO_CLOSE", "monospace");
10777
- }
10778
- };
10779
-
10780
- // packages/parser/src/parser/rules/inline/link-triple/fallback.ts
10781
- function literalOpenLink(value) {
10782
- return {
10783
- success: true,
10784
- elements: [{ element: "text", data: value }],
10785
- consumed: 1
10786
- };
10787
- }
10788
-
10789
- // packages/parser/src/parser/rules/inline/link-triple/label.ts
10790
- function buildTripleLinkLabel(args) {
10791
- return {
10792
- text: getTripleLinkDisplayText(args)
10793
- };
10794
- }
10795
- function getTripleLinkDisplayText(args) {
10796
- const trimmedLabel = args.labelText.trim();
10797
- if (args.foundPipe) {
10798
- return trimmedLabel || args.finalTarget;
10799
- }
10800
- const colonIdx = args.originalTarget.indexOf(":");
10801
- if (colonIdx !== -1 && !args.originalTarget.startsWith("http") && !args.originalTarget.startsWith("*")) {
10802
- return args.originalTarget.slice(colonIdx + 1).trim();
10803
- }
10804
- return args.originalTarget;
10805
- }
10806
-
10807
- // packages/parser/src/parser/rules/inline/link-triple/syntax.ts
10808
- function hasClosingLinkMarker(ctx, startPos) {
10809
- let pos = startPos;
10810
- while (pos < ctx.tokens.length) {
10811
- const token5 = ctx.tokens[pos];
10812
- if (!token5 || token5.type === "EOF") {
10813
- return false;
10814
- }
10815
- if (token5.type === "LINK_CLOSE") {
10816
- return true;
10817
- }
10818
- if (token5.type === "NEWLINE") {
10819
- const next = ctx.tokens[pos + 1];
10820
- if (next?.type === "NEWLINE" || next?.type === "LINK_CLOSE") {
10821
- return false;
10822
- }
10823
- }
10824
- pos++;
10825
- }
10826
- return false;
10827
- }
10828
- function collectTripleLinkParts(ctx, startPos) {
10829
- let target = "";
10830
- let labelText = "";
10831
- let foundPipe = false;
10832
- let consumed = 1;
10833
- let pos = startPos;
10834
- while (pos < ctx.tokens.length) {
10835
- const token5 = ctx.tokens[pos];
10836
- if (!token5 || token5.type === "LINK_CLOSE" || token5.type === "EOF") {
10837
- break;
10838
- }
10839
- if (token5.type === "NEWLINE") {
10840
- if (foundPipe) {
10841
- labelText += " ";
10842
- } else {
10843
- target += " ";
10844
- }
10845
- consumed++;
10846
- pos++;
10847
- continue;
10848
- }
10849
- if (token5.type === "PIPE" && !foundPipe) {
10850
- foundPipe = true;
10851
- } else if (foundPipe) {
10852
- labelText += token5.value;
10853
- } else {
10854
- target += token5.value;
10855
- }
10856
- consumed++;
10857
- pos++;
10858
- }
10859
- if (ctx.tokens[pos]?.type === "LINK_CLOSE") {
10860
- consumed++;
10861
- }
10862
- return { target, labelText, foundPipe, consumed };
10863
- }
10864
-
10865
- // packages/parser/src/parser/rules/inline/link-triple/interwiki.ts
10866
- var INTERWIKI_PREFIXES = new Set(["wikipedia", "google", "dictionary", "wikidot"]);
10867
- function isInterwikiTarget(target) {
10868
- const colonIdx = target.indexOf(":");
10869
- if (colonIdx <= 0 || target.includes("/")) {
10870
- return false;
10871
- }
10872
- const prefix = target.slice(0, colonIdx).toLowerCase();
10873
- return INTERWIKI_PREFIXES.has(prefix);
10874
- }
10875
-
10876
- // packages/parser/src/parser/rules/inline/link-triple/target.ts
10877
- function normalizeTripleLinkTarget(trimmedTarget) {
10878
- if (trimmedTarget.startsWith("*")) {
10879
- return { target: trimmedTarget.slice(1), hasStar: true };
10880
- }
10881
- return { target: trimmedTarget, hasStar: false };
10882
- }
10883
- function isInvalidTripleLinkTarget(trimmedTarget, foundPipe) {
10884
- return trimmedTarget === "" && foundPipe || /#{2,}/.test(trimmedTarget);
10885
- }
10886
- function determineLinkTypeAndLocation(target) {
10887
- if (target.startsWith("#")) {
10888
- return { linkType: "anchor", link: target };
10889
- }
10890
- if (target.startsWith("http://") || target.startsWith("https://")) {
10891
- return { linkType: "direct", link: target };
10892
- }
10893
- if (isInterwikiTarget(target)) {
10894
- return { linkType: "interwiki", link: target };
10895
- }
10896
- return { linkType: "page", link: { site: null, page: target } };
10897
- }
10898
-
10899
- // packages/parser/src/parser/rules/inline/link-triple/index.ts
10900
- var linkTripleRule = {
10901
- name: "linkTriple",
10902
- startTokens: ["LINK_OPEN"],
10903
- parse(ctx) {
10904
- const startToken = currentToken(ctx);
10905
- if (!hasClosingLinkMarker(ctx, ctx.pos + 1)) {
10906
- return literalOpenLink(startToken.value);
10907
- }
10908
- const parts = collectTripleLinkParts(ctx, ctx.pos + 1);
10909
- const trimmedTarget = parts.target.trim();
10910
- if (isInvalidTripleLinkTarget(trimmedTarget, parts.foundPipe)) {
10911
- return literalOpenLink(startToken.value);
10912
- }
10913
- const normalized = normalizeTripleLinkTarget(trimmedTarget);
10914
- const { linkType, link } = determineLinkTypeAndLocation(normalized.target);
10915
- const label = buildTripleLinkLabel({
10916
- foundPipe: parts.foundPipe,
10917
- labelText: parts.labelText,
10918
- finalTarget: normalized.target,
10919
- originalTarget: trimmedTarget
11947
+ name: "superscript",
11948
+ startTokens: ["SUPER_MARKER"],
11949
+ parse(ctx) {
11950
+ return parseDelimitedContainer(ctx, "SUPER_MARKER", "superscript", {
11951
+ discardEmpty: true
10920
11952
  });
10921
- return {
10922
- success: true,
10923
- elements: [
10924
- {
10925
- element: "link",
10926
- data: {
10927
- type: linkType,
10928
- link,
10929
- extra: null,
10930
- label,
10931
- target: normalized.hasStar && linkType === "direct" ? "new-tab" : null
10932
- }
10933
- }
10934
- ],
10935
- consumed: parts.consumed
10936
- };
11953
+ }
11954
+ };
11955
+
11956
+ // packages/parser/src/parser/rules/inline/subscript.ts
11957
+ var subscriptRule = {
11958
+ name: "subscript",
11959
+ startTokens: ["SUB_MARKER"],
11960
+ parse(ctx) {
11961
+ return parseDelimitedContainer(ctx, "SUB_MARKER", "subscript", { discardEmpty: true });
11962
+ }
11963
+ };
11964
+
11965
+ // packages/parser/src/parser/rules/inline/monospace.ts
11966
+ var monospaceRule = {
11967
+ name: "monospace",
11968
+ startTokens: ["MONO_MARKER"],
11969
+ parse(ctx) {
11970
+ return parseDelimitedContainer(ctx, "MONO_CLOSE", "monospace");
10937
11971
  }
10938
11972
  };
10939
11973
 
@@ -10944,7 +11978,7 @@ function normalizeAnchor(anchor) {
10944
11978
 
10945
11979
  // packages/parser/src/parser/rules/inline/link-bracket/direct-url.ts
10946
11980
  function isDirectBracketUrl(url) {
10947
- return url !== "" && (url.startsWith("/") || url.startsWith("http://") || url.startsWith("https://"));
11981
+ return url !== "" && (url.startsWith("/") || startsWithUrlScheme(url, 0));
10948
11982
  }
10949
11983
 
10950
11984
  // packages/parser/src/parser/rules/inline/link-bracket/parts.ts
@@ -10953,35 +11987,24 @@ function collectBracketLinkParts(ctx, startPos) {
10953
11987
  return null;
10954
11988
  }
10955
11989
  let pos = startPos;
10956
- let consumed = 0;
10957
- let first = "";
10958
- while (pos < ctx.tokens.length) {
10959
- const token5 = ctx.tokens[pos];
10960
- if (!token5 || token5.type === "WHITESPACE" || token5.type === "BRACKET_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF") {
10961
- break;
10962
- }
10963
- first += token5.value;
10964
- pos++;
10965
- consumed++;
10966
- }
10967
- while (ctx.tokens[pos]?.type === "WHITESPACE") {
10968
- pos++;
10969
- consumed++;
10970
- }
10971
- let label = "";
10972
- while (pos < ctx.tokens.length) {
11990
+ const end = Math.min(ctx.scope.inlineEnd ?? ctx.tokens.length, ctx.tokens.length);
11991
+ const values = [];
11992
+ while (pos < end) {
10973
11993
  const token5 = ctx.tokens[pos];
10974
- if (!token5 || token5.type === "BRACKET_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF") {
11994
+ if (!token5 || token5.type === "BRACKET_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF")
10975
11995
  break;
10976
- }
10977
- label += token5.value;
11996
+ values.push(token5.value);
10978
11997
  pos++;
10979
- consumed++;
10980
11998
  }
10981
- if (ctx.tokens[pos]?.type !== "BRACKET_CLOSE") {
11999
+ if (pos >= end || ctx.tokens[pos]?.type !== "BRACKET_CLOSE")
10982
12000
  return null;
10983
- }
10984
- return { first, label, consumed: consumed + 1 };
12001
+ const content = values.join("");
12002
+ const separator = content.search(/[ \t]/);
12003
+ return {
12004
+ first: separator === -1 ? content : content.slice(0, separator),
12005
+ label: separator === -1 ? "" : content.slice(separator).trimStart(),
12006
+ consumed: pos - startPos + 1
12007
+ };
10985
12008
  }
10986
12009
 
10987
12010
  // packages/parser/src/parser/rules/inline/link-bracket/prefix.ts
@@ -10992,6 +12015,14 @@ function parseBracketLinkPrefix(ctx, startPos) {
10992
12015
  return { target: null, bodyStart: startPos, consumed: 0 };
10993
12016
  }
10994
12017
 
12018
+ // packages/parser/src/parser/rules/inline/link-bracket/special-target.ts
12019
+ function isBracketEmail(target) {
12020
+ return /^[_a-z0-9-]+(?:\.[_a-z0-9-]+)*@[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i.test(target);
12021
+ }
12022
+ function wikipediaPage(target) {
12023
+ return /^wikipedia:((?!:)[A-Za-z0-9_/=&~#.:;+-]+)$/.exec(target)?.[1] ?? null;
12024
+ }
12025
+
10995
12026
  // packages/parser/src/parser/rules/inline/link-bracket/parsed.ts
10996
12027
  function parseSingleBracketLink(ctx) {
10997
12028
  const prefix = parseBracketLinkPrefix(ctx, ctx.pos + 1);
@@ -11000,17 +12031,20 @@ function parseSingleBracketLink(ctx) {
11000
12031
  return null;
11001
12032
  }
11002
12033
  const link = parts.first.trim();
11003
- if (!isDirectBracketUrl(link)) {
12034
+ const email = isBracketEmail(link);
12035
+ const wikiPage = wikipediaPage(link);
12036
+ if (!email && wikiPage === null && !isDirectBracketUrl(link)) {
11004
12037
  return null;
11005
12038
  }
11006
- const labelText = parts.label.trim();
12039
+ const labelText = parts.label.trim() || wikiPage;
11007
12040
  if (!labelText) {
11008
12041
  return null;
11009
12042
  }
11010
12043
  return {
11011
- link,
12044
+ link: email ? `mailto:${link}` : link,
12045
+ interwiki: wikiPage !== null,
11012
12046
  labelText,
11013
- target: prefix.target,
12047
+ target: wikiPage !== null ? "new-tab" : prefix.target,
11014
12048
  consumed: 1 + prefix.consumed + parts.consumed
11015
12049
  };
11016
12050
  }
@@ -11063,7 +12097,7 @@ var linkSingleRule = {
11063
12097
  {
11064
12098
  element: "link",
11065
12099
  data: {
11066
- type: "direct",
12100
+ type: parsed.interwiki ? "interwiki" : "direct",
11067
12101
  link: parsed.link,
11068
12102
  extra: null,
11069
12103
  label: linkLabel,
@@ -11132,6 +12166,134 @@ var linkStarRule = {
11132
12166
  }
11133
12167
  };
11134
12168
 
12169
+ // packages/parser/src/parser/rules/inline/autolink.ts
12170
+ var URL_BOUNDARY_TOKENS = new Set([
12171
+ "WHITESPACE",
12172
+ "NEWLINE",
12173
+ "EOF",
12174
+ "BLOCK_OPEN",
12175
+ "BLOCK_END_OPEN",
12176
+ "BLOCK_CLOSE",
12177
+ "LINK_OPEN",
12178
+ "LINK_CLOSE",
12179
+ "COMMENT_OPEN",
12180
+ "BACKSLASH_BREAK"
12181
+ ]);
12182
+ var autolinkRule = {
12183
+ name: "autolink",
12184
+ startTokens: ["IDENTIFIER", "STAR", "LIST_BULLET"],
12185
+ parse(ctx) {
12186
+ let pos = ctx.pos;
12187
+ let target = null;
12188
+ const first = ctx.tokens[pos];
12189
+ if (!first) {
12190
+ return { success: false };
12191
+ }
12192
+ if (first.type === "STAR" || first.type === "LIST_BULLET") {
12193
+ if (first.value !== "*") {
12194
+ return { success: false };
12195
+ }
12196
+ target = "new-tab";
12197
+ pos++;
12198
+ }
12199
+ const scheme = ctx.tokens[pos];
12200
+ if (scheme?.type !== "IDENTIFIER" || !URL_SCHEME_NAMES.has(scheme.value)) {
12201
+ return { success: false };
12202
+ }
12203
+ if (ctx.tokens[pos + 1]?.type !== "COLON") {
12204
+ return { success: false };
12205
+ }
12206
+ const prev = ctx.tokens[ctx.pos - 1];
12207
+ if (prev && !first.lineStart) {
12208
+ const lastChar = prev.value[prev.value.length - 1] ?? "";
12209
+ if (/[A-Za-z]/.test(lastChar)) {
12210
+ return { success: false };
12211
+ }
12212
+ }
12213
+ const values = [];
12214
+ let end = pos;
12215
+ const inlineEnd = ctx.scope.inlineEnd ?? ctx.tokens.length;
12216
+ while (end < inlineEnd) {
12217
+ const token5 = ctx.tokens[end];
12218
+ if (!token5 || URL_BOUNDARY_TOKENS.has(token5.type)) {
12219
+ break;
12220
+ }
12221
+ if (token5.type === "RAW_OPEN" || token5.type === "RAW_BLOCK_OPEN") {
12222
+ if (rawRegionEnd(ctx.tokens, end, inlineEnd) > end)
12223
+ break;
12224
+ }
12225
+ values.push(token5.value);
12226
+ end++;
12227
+ if (/[ \t\n\\"']/.test(token5.value))
12228
+ break;
12229
+ }
12230
+ const candidate = values.join("");
12231
+ const match = URL_PATTERN.exec(candidate);
12232
+ if (!match) {
12233
+ return { success: false };
12234
+ }
12235
+ const url = match[0];
12236
+ let length = 0;
12237
+ let count = 0;
12238
+ while (length < url.length) {
12239
+ length += values[count]?.length ?? 0;
12240
+ count++;
12241
+ }
12242
+ const rest = candidate.slice(url.length, length);
12243
+ const elements = [
12244
+ {
12245
+ element: "link",
12246
+ data: {
12247
+ type: "direct",
12248
+ link: url,
12249
+ extra: null,
12250
+ label: { text: url },
12251
+ target
12252
+ }
12253
+ }
12254
+ ];
12255
+ if (rest !== "") {
12256
+ elements.push({ element: "text", data: rest });
12257
+ }
12258
+ return {
12259
+ success: true,
12260
+ elements,
12261
+ consumed: pos - ctx.pos + count
12262
+ };
12263
+ }
12264
+ };
12265
+
12266
+ // packages/parser/src/parser/rules/inline/email/index.ts
12267
+ var emailRule = {
12268
+ name: "email",
12269
+ startTokens: EMAIL_START_TOKENS,
12270
+ parse(ctx) {
12271
+ const group = getEmailGroup(ctx.tokens, ctx.pos, ctx.scope.inlineEnd ?? ctx.tokens.length);
12272
+ if (!group)
12273
+ return { success: false };
12274
+ const elements = [];
12275
+ let copied = group.start;
12276
+ for (const candidate of group.candidates) {
12277
+ if (copied < candidate.start)
12278
+ elements.push({ element: "text", data: group.source.slice(copied, candidate.start) });
12279
+ elements.push(ctx.scope.suppressEmailLinks ? { element: "text", data: candidate.address } : {
12280
+ element: "link",
12281
+ data: {
12282
+ type: "direct",
12283
+ link: `mailto:${candidate.address}`,
12284
+ label: { text: candidate.address },
12285
+ target: null,
12286
+ extra: null
12287
+ }
12288
+ });
12289
+ copied = candidate.end;
12290
+ }
12291
+ if (copied < group.end)
12292
+ elements.push({ element: "text", data: group.source.slice(copied, group.end) });
12293
+ return { success: true, elements, consumed: group.endToken - ctx.pos };
12294
+ }
12295
+ };
12296
+
11135
12297
  // packages/parser/src/parser/rules/inline/color/syntax.ts
11136
12298
  function parseColorContent(ctx) {
11137
12299
  const close = findFormattingClose(ctx, ctx.pos + 1, "COLOR_MARKER");
@@ -11218,7 +12380,7 @@ var newlineLineBreakRule = {
11218
12380
  }
11219
12381
  return {
11220
12382
  success: true,
11221
- elements: [{ element: "line-break" }],
12383
+ elements: [createAutomaticLineBreak(currentTok)],
11222
12384
  consumed: 1
11223
12385
  };
11224
12386
  }
@@ -11241,6 +12403,9 @@ function isValidBlockStartAfterNewline(ctx, tokenPos) {
11241
12403
  if (!isBlockStartToken(token5?.type) || !token5?.lineStart) {
11242
12404
  return false;
11243
12405
  }
12406
+ if (token5.type === "LIST_BULLET" || token5.type === "LIST_NUMBER") {
12407
+ return ctx.tokens[tokenPos + 1]?.type === "WHITESPACE";
12408
+ }
11244
12409
  if (token5.type !== "HEADING_MARKER") {
11245
12410
  return true;
11246
12411
  }
@@ -11498,6 +12663,9 @@ var htmlInlineRule = {
11498
12663
  }
11499
12664
  };
11500
12665
 
12666
+ // packages/parser/src/parser/rules/inline/raw/angle.ts
12667
+ var import_entities = require("entities");
12668
+
11501
12669
  // packages/parser/src/parser/rules/inline/raw/result.ts
11502
12670
  function rawElement(value, consumed) {
11503
12671
  return {
@@ -11545,7 +12713,7 @@ function parseAngleRaw(ctx) {
11545
12713
  if (ctx.tokens[pos]?.type === "RAW_BLOCK_CLOSE") {
11546
12714
  consumed++;
11547
12715
  }
11548
- return rawElement(value, consumed);
12716
+ return rawElement(import_entities.decodeHTML(value), consumed);
11549
12717
  }
11550
12718
 
11551
12719
  // packages/parser/src/parser/rules/inline/raw/double-at.ts
@@ -11768,7 +12936,7 @@ function consumeSpanNewline(ctx, pos, paragraphStrip, afterBlankLine, children,
11768
12936
  return { consumed: consumed2, afterBlankLine: paragraphStrip };
11769
12937
  }
11770
12938
  const targetChildren = paragraphStrip && afterBlankLine ? escapedChildren : children;
11771
- targetChildren.push({ element: "line-break" });
12939
+ targetChildren.push(createAutomaticLineBreak(ctx.tokens[pos]));
11772
12940
  let consumed = 1;
11773
12941
  let nextPos = pos + 1;
11774
12942
  while (ctx.tokens[nextPos]?.type === "WHITESPACE" && ctx.tokens[nextPos]?.lineStart) {
@@ -11856,7 +13024,9 @@ function parseOneSpanChild(ctx, pos, targetChildren) {
11856
13024
  for (const rule of getCandidateInlineRules(inlineRules, token5.type)) {
11857
13025
  const result = rule.parse(inlineCtx);
11858
13026
  if (result.success) {
11859
- targetChildren.push(...result.elements);
13027
+ stripAutomaticLineBreak(targetChildren, result.stripLeadingLineBreak);
13028
+ for (const element of result.elements)
13029
+ targetChildren.push(element);
11860
13030
  return { consumed: result.consumed };
11861
13031
  }
11862
13032
  }
@@ -11924,56 +13094,60 @@ var closeSpanRule = {
11924
13094
  }
11925
13095
  };
11926
13096
 
11927
- // packages/parser/src/parser/rules/inline/size/content.ts
11928
- function parseSizeContent(ctx, startPos) {
11929
- const children = [];
11930
- let pos = startPos;
11931
- let consumed = 0;
11932
- while (pos < ctx.tokens.length) {
11933
- const token5 = ctx.tokens[pos];
11934
- if (!token5 || token5.type === "EOF") {
11935
- break;
11936
- }
11937
- const closeResult = tryConsumeSizeClose(ctx, pos);
11938
- if (closeResult) {
11939
- return {
11940
- children,
11941
- consumed: consumed + closeResult.consumed,
11942
- foundClose: true
11943
- };
11944
- }
11945
- const inlineCtx = { ...ctx, pos };
11946
- const inlineResult = parseInlineUntil(inlineCtx, "BLOCK_END_OPEN");
11947
- if (inlineResult.elements.length > 0) {
11948
- children.push(...inlineResult.elements);
11949
- pos += inlineResult.consumed;
11950
- consumed += inlineResult.consumed;
11951
- } else {
11952
- children.push({ element: "text", data: token5.value });
11953
- pos++;
11954
- consumed++;
11955
- }
11956
- }
11957
- return { children, consumed, foundClose: false };
11958
- }
11959
- function tryConsumeSizeClose(ctx, pos) {
11960
- if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
11961
- return null;
13097
+ // packages/parser/src/parser/rules/inline/button/index.ts
13098
+ var buttonRule = {
13099
+ name: "button",
13100
+ startTokens: ["BLOCK_OPEN"],
13101
+ parse(ctx) {
13102
+ const result = parseButtonSyntax(ctx, ctx.pos, ctx.scope.inlineEnd ?? ctx.tokens.length);
13103
+ return result ? {
13104
+ success: true,
13105
+ consumed: result.end - ctx.pos,
13106
+ elements: [{ element: "button", data: result.data }]
13107
+ } : { success: false };
11962
13108
  }
11963
- const closeNameResult = parseBlockName(ctx, pos + 1);
11964
- if (!closeNameResult || closeNameResult.name !== "size") {
11965
- return null;
13109
+ };
13110
+
13111
+ // packages/parser/src/parser/rules/inline/social/index.ts
13112
+ var socialRule = {
13113
+ name: "social",
13114
+ startTokens: ["BLOCK_OPEN"],
13115
+ parse(ctx) {
13116
+ const result = parseSocialSyntax(ctx, ctx.pos, ctx.scope.inlineEnd ?? ctx.tokens.length);
13117
+ return result ? {
13118
+ success: true,
13119
+ consumed: result.end - ctx.pos,
13120
+ elements: [{ element: "social", data: result.data }]
13121
+ } : { success: false };
11966
13122
  }
11967
- let closePos = pos + 1 + closeNameResult.consumed;
11968
- let consumed = 1 + closeNameResult.consumed;
11969
- if (ctx.tokens[closePos]?.type === "BLOCK_CLOSE") {
11970
- consumed++;
13123
+ };
13124
+
13125
+ // packages/parser/src/parser/rules/inline/date/index.ts
13126
+ var dateRule = {
13127
+ name: "date",
13128
+ startTokens: ["BLOCK_OPEN"],
13129
+ parse(ctx) {
13130
+ const parsed = parseDateSyntax(ctx, ctx.pos, ctx.scope.inlineEnd ?? ctx.tokens.length);
13131
+ return parsed ? {
13132
+ success: true,
13133
+ consumed: parsed.end - ctx.pos,
13134
+ elements: [{ element: "date", data: parsed.data }]
13135
+ } : { success: false };
11971
13136
  }
11972
- return { consumed };
11973
- }
13137
+ };
11974
13138
 
11975
13139
  // packages/parser/src/parser/rules/inline/size/value.ts
11976
13140
  var VALID_SIZE_UNITS = ["px", "em", "rem", "ex", "%", "cm", "mm", "in", "pc"];
13141
+ var VALID_SIZE_KEYWORDS = [
13142
+ "smaller",
13143
+ "larger",
13144
+ "xx-small",
13145
+ "x-small",
13146
+ "small",
13147
+ "large",
13148
+ "x-large",
13149
+ "xx-large"
13150
+ ];
11977
13151
  function parseSizeValue(ctx, startPos) {
11978
13152
  let pos = startPos;
11979
13153
  let consumed = 0;
@@ -11998,6 +13172,8 @@ function parseSizeValue(ctx, startPos) {
11998
13172
  return isValidSizeValue(size) ? { size, consumed } : null;
11999
13173
  }
12000
13174
  function isValidSizeValue(size) {
13175
+ if (VALID_SIZE_KEYWORDS.includes(size))
13176
+ return true;
12001
13177
  const unitPattern = VALID_SIZE_UNITS.join("|");
12002
13178
  return new RegExp(`^(\\d+(?:\\.\\d+)?)(${unitPattern})$`, "i").test(size);
12003
13179
  }
@@ -12029,6 +13205,81 @@ function parseSizeOpen(ctx) {
12029
13205
  return { size: sizeResult.size, bodyStart: pos, consumed };
12030
13206
  }
12031
13207
 
13208
+ // packages/parser/src/parser/rules/inline/size/content.ts
13209
+ function parseSizeContent(ctx, startPos) {
13210
+ const children = [];
13211
+ let pos = startPos;
13212
+ let consumed = 0;
13213
+ const inlineEnd = findSizeClose(ctx, startPos);
13214
+ while (pos < (ctx.scope.inlineEnd ?? ctx.tokens.length)) {
13215
+ const token5 = ctx.tokens[pos];
13216
+ if (!token5 || token5.type === "EOF") {
13217
+ break;
13218
+ }
13219
+ const closeResult = tryConsumeSizeClose(ctx, pos);
13220
+ if (closeResult) {
13221
+ return {
13222
+ children,
13223
+ consumed: consumed + closeResult.consumed,
13224
+ foundClose: true
13225
+ };
13226
+ }
13227
+ const inlineCtx = { ...ctx, pos, scope: { ...ctx.scope, inlineEnd } };
13228
+ const inlineResult = parseInlineUntil(inlineCtx, "EOF");
13229
+ if (inlineResult.consumed > 0) {
13230
+ for (const element of inlineResult.elements)
13231
+ children.push(element);
13232
+ pos += inlineResult.consumed;
13233
+ consumed += inlineResult.consumed;
13234
+ } else {
13235
+ children.push({ element: "text", data: token5.value });
13236
+ pos++;
13237
+ consumed++;
13238
+ }
13239
+ }
13240
+ return { children, consumed, foundClose: false };
13241
+ }
13242
+ function findSizeClose(ctx, startPos) {
13243
+ const end = ctx.scope.inlineEnd ?? ctx.tokens.length;
13244
+ let depth = 0;
13245
+ for (let pos = startPos;pos < end; pos++) {
13246
+ const protectedEnd = protectedInlineRegionEnd(ctx.tokens, pos, end);
13247
+ if (protectedEnd > pos) {
13248
+ pos = protectedEnd - 1;
13249
+ continue;
13250
+ }
13251
+ const open = ctx.tokens[pos]?.type === "BLOCK_OPEN" ? parseSizeOpen({ ...ctx, pos }) : null;
13252
+ if (open) {
13253
+ depth++;
13254
+ pos = open.bodyStart - 1;
13255
+ continue;
13256
+ }
13257
+ const close = tryConsumeSizeClose(ctx, pos);
13258
+ if (!close)
13259
+ continue;
13260
+ if (depth === 0)
13261
+ return pos;
13262
+ depth--;
13263
+ pos += close.consumed - 1;
13264
+ }
13265
+ return end;
13266
+ }
13267
+ function tryConsumeSizeClose(ctx, pos) {
13268
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
13269
+ return null;
13270
+ }
13271
+ const closeNameResult = parseBlockName(ctx, pos + 1);
13272
+ if (!closeNameResult || closeNameResult.name !== "size") {
13273
+ return null;
13274
+ }
13275
+ let closePos = pos + 1 + closeNameResult.consumed;
13276
+ let consumed = 1 + closeNameResult.consumed;
13277
+ if (ctx.tokens[closePos]?.type === "BLOCK_CLOSE") {
13278
+ consumed++;
13279
+ }
13280
+ return { consumed };
13281
+ }
13282
+
12032
13283
  // packages/parser/src/parser/rules/inline/size/index.ts
12033
13284
  var sizeRule = {
12034
13285
  name: "size",
@@ -12069,19 +13320,6 @@ var sizeRule = {
12069
13320
  }
12070
13321
  };
12071
13322
 
12072
- // packages/parser/src/parser/rules/inline/footnote/child.ts
12073
- function parseFootnoteChild(ctx, pos) {
12074
- const token5 = ctx.tokens[pos];
12075
- if (!token5) {
12076
- return { elements: [], consumed: 0 };
12077
- }
12078
- const inlineResult = parseInlineUntil({ ...ctx, pos }, "BLOCK_END_OPEN");
12079
- if (inlineResult.elements.length > 0) {
12080
- return { elements: inlineResult.elements, consumed: inlineResult.consumed };
12081
- }
12082
- return { elements: [{ element: "text", data: token5.value }], consumed: 1 };
12083
- }
12084
-
12085
13323
  // packages/parser/src/parser/rules/inline/footnote/close.ts
12086
13324
  function tryConsumeFootnoteClose(ctx, pos) {
12087
13325
  if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
@@ -12103,96 +13341,73 @@ function tryConsumeFootnoteClose(ctx, pos) {
12103
13341
  return { consumed };
12104
13342
  }
12105
13343
 
12106
- // packages/parser/src/parser/rules/inline/footnote/newline.ts
12107
- function consumeFootnoteNewline(ctx, pos) {
12108
- let nextPos = pos + 1;
12109
- let consumed = 1;
12110
- while (ctx.tokens[nextPos]?.type === "WHITESPACE") {
12111
- nextPos++;
12112
- consumed++;
12113
- }
12114
- if (ctx.tokens[nextPos]?.type !== "NEWLINE") {
12115
- return { consumed, paragraphBreak: false };
12116
- }
12117
- while (ctx.tokens[nextPos]?.type === "NEWLINE") {
12118
- nextPos++;
12119
- consumed++;
12120
- }
12121
- return { consumed, paragraphBreak: true };
12122
- }
12123
-
12124
- // packages/parser/src/parser/rules/inline/footnote/content.ts
12125
- function parseFootnoteContent(ctx, startPos) {
12126
- const paragraphs = [[]];
12127
- let currentParagraph = 0;
13344
+ // packages/parser/src/parser/rules/inline/math-inline/syntax.ts
13345
+ function parseInlineMathSource(ctx, startPos) {
12128
13346
  let pos = startPos;
12129
13347
  let consumed = 0;
13348
+ if (ctx.tokens[pos]?.type !== "TEXT" || ctx.tokens[pos]?.value !== "$") {
13349
+ return null;
13350
+ }
13351
+ pos++;
13352
+ consumed++;
13353
+ while (ctx.tokens[pos]?.type === "WHITESPACE") {
13354
+ pos++;
13355
+ consumed++;
13356
+ }
13357
+ let latexSource = "";
12130
13358
  while (pos < ctx.tokens.length) {
12131
13359
  const token5 = ctx.tokens[pos];
12132
- if (!token5 || token5.type === "EOF") {
12133
- break;
12134
- }
12135
- const closeResult = tryConsumeFootnoteClose(ctx, pos);
12136
- if (closeResult) {
12137
- return {
12138
- paragraphs,
12139
- consumed: consumed + closeResult.consumed,
12140
- foundClose: true
12141
- };
13360
+ if (!token5 || token5.type === "NEWLINE") {
13361
+ return null;
12142
13362
  }
12143
- if (token5.type === "NEWLINE") {
12144
- const newlineResult = consumeFootnoteNewline(ctx, pos);
12145
- pos += newlineResult.consumed;
12146
- consumed += newlineResult.consumed;
12147
- if (newlineResult.paragraphBreak) {
12148
- currentParagraph++;
12149
- paragraphs[currentParagraph] = [];
12150
- } else {
12151
- paragraphs[currentParagraph].push({ element: "line-break" });
12152
- }
12153
- continue;
13363
+ if (token5.type === "TEXT" && token5.value === "$" && ctx.tokens[pos + 1]?.type === "BLOCK_CLOSE") {
13364
+ break;
12154
13365
  }
12155
- const child = parseFootnoteChild(ctx, pos);
12156
- paragraphs[currentParagraph].push(...child.elements);
12157
- pos += child.consumed;
12158
- consumed += child.consumed;
13366
+ latexSource += token5.value;
13367
+ pos++;
13368
+ consumed++;
13369
+ }
13370
+ if (ctx.tokens[pos]?.type !== "TEXT" || ctx.tokens[pos]?.value !== "$") {
13371
+ return null;
13372
+ }
13373
+ pos++;
13374
+ consumed++;
13375
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
13376
+ return null;
12159
13377
  }
12160
- return { paragraphs, consumed, foundClose: false };
13378
+ return {
13379
+ latexSource: latexSource.trim(),
13380
+ consumed: consumed + 1
13381
+ };
12161
13382
  }
12162
13383
 
12163
- // packages/parser/src/parser/rules/inline/footnote/elements.ts
12164
- function buildFootnoteChildren(paragraphs) {
12165
- const children = [];
12166
- for (let i = 0;i < paragraphs.length; i++) {
12167
- const para = trimLineBreaks(paragraphs[i] ?? []);
12168
- if (para.length === 0) {
12169
- continue;
13384
+ // packages/parser/src/parser/rules/inline/math-inline/index.ts
13385
+ var mathInlineRule = {
13386
+ name: "math-inline",
13387
+ startTokens: ["BLOCK_OPEN"],
13388
+ parse(ctx) {
13389
+ const openToken = currentToken(ctx);
13390
+ if (openToken.type !== "BLOCK_OPEN") {
13391
+ return { success: false };
12170
13392
  }
12171
- if (i === 0) {
12172
- children.push(...para);
12173
- } else {
12174
- children.push({
12175
- element: "container",
12176
- data: {
12177
- type: "paragraph",
12178
- attributes: {},
12179
- elements: para
12180
- }
12181
- });
13393
+ const parsed = parseInlineMathSource(ctx, ctx.pos + 1);
13394
+ if (!parsed) {
13395
+ return { success: false };
12182
13396
  }
13397
+ return {
13398
+ success: true,
13399
+ elements: [
13400
+ {
13401
+ element: "math-inline",
13402
+ data: {
13403
+ "latex-source": parsed.latexSource
13404
+ }
13405
+ }
13406
+ ],
13407
+ consumed: 1 + parsed.consumed
13408
+ };
12183
13409
  }
12184
- return children;
12185
- }
12186
- function trimLineBreaks(elements) {
12187
- const result = [...elements];
12188
- while (result.length > 0 && result[0]?.element === "line-break") {
12189
- result.shift();
12190
- }
12191
- while (result.length > 0 && result[result.length - 1]?.element === "line-break") {
12192
- result.pop();
12193
- }
12194
- return result;
12195
- }
13410
+ };
12196
13411
 
12197
13412
  // packages/parser/src/parser/rules/inline/footnote/open.ts
12198
13413
  function parseFootnoteOpen(ctx) {
@@ -12219,6 +13434,91 @@ function parseFootnoteOpen(ctx) {
12219
13434
  return { bodyStart: pos, consumed };
12220
13435
  }
12221
13436
 
13437
+ // packages/parser/src/parser/rules/inline/footnote/boundary.ts
13438
+ var opaqueRules2 = [
13439
+ codeBlockRule,
13440
+ htmlBlockRule,
13441
+ mathBlockRule,
13442
+ mathInlineRule,
13443
+ linkTripleRule,
13444
+ linkSingleRule,
13445
+ linkStarRule,
13446
+ linkAnchorRule
13447
+ ];
13448
+ function findFootnoteEnd(ctx, start) {
13449
+ const probe = createOpaqueProbe(ctx);
13450
+ let depth = 0;
13451
+ for (let pos = start;pos < ctx.tokens.length; pos++) {
13452
+ if (ctx.tokens[pos]?.type === "EOF")
13453
+ return pos;
13454
+ const protectedEnd = protectedInlineRegionEnd(ctx.tokens, pos, ctx.tokens.length);
13455
+ if (protectedEnd > pos) {
13456
+ pos = protectedEnd - 1;
13457
+ continue;
13458
+ }
13459
+ probe.pos = pos;
13460
+ const close = tryConsumeFootnoteClose(probe, pos);
13461
+ if (close) {
13462
+ if (depth === 0)
13463
+ return pos;
13464
+ depth--;
13465
+ pos += close.consumed - 1;
13466
+ continue;
13467
+ }
13468
+ const open = parseFootnoteOpen(probe);
13469
+ if (open) {
13470
+ depth++;
13471
+ pos += open.consumed - 1;
13472
+ continue;
13473
+ }
13474
+ const opaqueEnd = opaqueRuleEnd(probe, pos, opaqueRules2);
13475
+ if (opaqueEnd > pos)
13476
+ pos = opaqueEnd - 1;
13477
+ }
13478
+ return ctx.tokens.length;
13479
+ }
13480
+
13481
+ // packages/parser/src/parser/rules/inline/footnote/content.ts
13482
+ function parseFootnoteContent(ctx, startPos) {
13483
+ const end = findFootnoteEnd(ctx, startPos);
13484
+ let leadingNewlines = 0;
13485
+ for (let pos = startPos;pos < end; pos++) {
13486
+ const type = ctx.tokens[pos]?.type;
13487
+ if (type === "NEWLINE")
13488
+ leadingNewlines++;
13489
+ else if (type !== "WHITESPACE")
13490
+ break;
13491
+ }
13492
+ const bodyCtx = {
13493
+ ...ctx,
13494
+ tokens: ctx.tokens.slice(startPos, end),
13495
+ pos: 0,
13496
+ scope: {
13497
+ ...ctx.scope,
13498
+ inlineEnd: undefined,
13499
+ tableFormatting: undefined,
13500
+ blockCloseCondition: undefined
13501
+ }
13502
+ };
13503
+ const result = parseBlocksUntil(bodyCtx, () => false);
13504
+ const close = tryConsumeFootnoteClose(ctx, end);
13505
+ return {
13506
+ elements: result.elements,
13507
+ consumed: end - startPos + (close?.consumed ?? 0),
13508
+ foundClose: close !== null,
13509
+ leadingParagraphBreak: leadingNewlines >= 2
13510
+ };
13511
+ }
13512
+
13513
+ // packages/parser/src/parser/rules/inline/footnote/elements.ts
13514
+ function buildFootnoteChildren(elements, leadingParagraphBreak) {
13515
+ const first = elements[0];
13516
+ if (!leadingParagraphBreak && first?.element === "container" && first.data.type === "paragraph") {
13517
+ return [...first.data.elements, ...elements.slice(1)];
13518
+ }
13519
+ return elements;
13520
+ }
13521
+
12222
13522
  // packages/parser/src/parser/rules/inline/footnote/index.ts
12223
13523
  var footnoteRule = {
12224
13524
  name: "footnote",
@@ -12234,7 +13534,7 @@ var footnoteRule = {
12234
13534
  }
12235
13535
  const contentResult = parseFootnoteContent(ctx, openResult.bodyStart);
12236
13536
  const consumed = openResult.consumed + contentResult.consumed;
12237
- const children = buildFootnoteChildren(contentResult.paragraphs);
13537
+ const children = buildFootnoteChildren(contentResult.elements, contentResult.leadingParagraphBreak);
12238
13538
  if (!contentResult.foundClose) {
12239
13539
  ctx.diagnostics.push({
12240
13540
  severity: "warning",
@@ -12594,11 +13894,15 @@ function parseAnchorChild(ctx, pos) {
12594
13894
  if (!token5) {
12595
13895
  return { elements: [], consumed: 0 };
12596
13896
  }
12597
- const inlineCtx = { ...ctx, pos };
13897
+ const inlineCtx = {
13898
+ ...ctx,
13899
+ pos,
13900
+ scope: { ...ctx.scope, suppressEmailLinks: true }
13901
+ };
12598
13902
  for (const rule of getCandidateInlineRules(inlineRules, token5.type)) {
12599
13903
  const result = rule.parse(inlineCtx);
12600
13904
  if (result.success) {
12601
- return { elements: result.elements, consumed: result.consumed };
13905
+ return result;
12602
13906
  }
12603
13907
  }
12604
13908
  return { elements: [{ element: "text", data: token5.value }], consumed: 1 };
@@ -12658,7 +13962,7 @@ function consumeAnchorNewline(ctx, pos, paragraphStrip, children) {
12658
13962
  let consumed = 1;
12659
13963
  let nextPos = pos + 1;
12660
13964
  if (!paragraphStrip) {
12661
- children.push({ element: "line-break" });
13965
+ children.push(createAutomaticLineBreak(ctx.tokens[pos]));
12662
13966
  while (ctx.tokens[nextPos]?.type === "WHITESPACE" && ctx.tokens[nextPos]?.lineStart) {
12663
13967
  nextPos++;
12664
13968
  consumed++;
@@ -12711,7 +14015,9 @@ function parseAnchorContent(ctx, startPos, paragraphStrip) {
12711
14015
  continue;
12712
14016
  }
12713
14017
  const child = parseAnchorChild(ctx, pos);
12714
- children.push(...child.elements);
14018
+ stripAutomaticLineBreak(children, child.stripLeadingLineBreak);
14019
+ for (const element of child.elements)
14020
+ children.push(element);
12715
14021
  pos += child.consumed;
12716
14022
  consumed += child.consumed;
12717
14023
  }
@@ -12785,75 +14091,8 @@ var anchorRule = {
12785
14091
  }
12786
14092
  }
12787
14093
  ],
12788
- consumed
12789
- };
12790
- }
12791
- };
12792
-
12793
- // packages/parser/src/parser/rules/inline/math-inline/syntax.ts
12794
- function parseInlineMathSource(ctx, startPos) {
12795
- let pos = startPos;
12796
- let consumed = 0;
12797
- if (ctx.tokens[pos]?.type !== "TEXT" || ctx.tokens[pos]?.value !== "$") {
12798
- return null;
12799
- }
12800
- pos++;
12801
- consumed++;
12802
- while (ctx.tokens[pos]?.type === "WHITESPACE") {
12803
- pos++;
12804
- consumed++;
12805
- }
12806
- let latexSource = "";
12807
- while (pos < ctx.tokens.length) {
12808
- const token5 = ctx.tokens[pos];
12809
- if (!token5 || token5.type === "NEWLINE") {
12810
- return null;
12811
- }
12812
- if (token5.type === "TEXT" && token5.value === "$" && ctx.tokens[pos + 1]?.type === "BLOCK_CLOSE") {
12813
- break;
12814
- }
12815
- latexSource += token5.value;
12816
- pos++;
12817
- consumed++;
12818
- }
12819
- if (ctx.tokens[pos]?.type !== "TEXT" || ctx.tokens[pos]?.value !== "$") {
12820
- return null;
12821
- }
12822
- pos++;
12823
- consumed++;
12824
- if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
12825
- return null;
12826
- }
12827
- return {
12828
- latexSource: latexSource.trim(),
12829
- consumed: consumed + 1
12830
- };
12831
- }
12832
-
12833
- // packages/parser/src/parser/rules/inline/math-inline/index.ts
12834
- var mathInlineRule = {
12835
- name: "math-inline",
12836
- startTokens: ["BLOCK_OPEN"],
12837
- parse(ctx) {
12838
- const openToken = currentToken(ctx);
12839
- if (openToken.type !== "BLOCK_OPEN") {
12840
- return { success: false };
12841
- }
12842
- const parsed = parseInlineMathSource(ctx, ctx.pos + 1);
12843
- if (!parsed) {
12844
- return { success: false };
12845
- }
12846
- return {
12847
- success: true,
12848
- elements: [
12849
- {
12850
- element: "math-inline",
12851
- data: {
12852
- "latex-source": parsed.latexSource
12853
- }
12854
- }
12855
- ],
12856
- consumed: 1 + parsed.consumed
14094
+ consumed,
14095
+ stripLeadingLineBreak: openResult.paragraphStrip ? precedingSingleNewline(ctx) : undefined
12857
14096
  };
12858
14097
  }
12859
14098
  };
@@ -12964,7 +14203,9 @@ function parseInlineBranch(ctx, startPos) {
12964
14203
  }
12965
14204
  const result = rule.parse(inlineCtx);
12966
14205
  if (result.success) {
12967
- elements.push(...result.elements);
14206
+ stripAutomaticLineBreak(elements, result.stripLeadingLineBreak);
14207
+ for (const element of result.elements)
14208
+ elements.push(element);
12968
14209
  consumed += result.consumed;
12969
14210
  pos += result.consumed;
12970
14211
  matched = true;
@@ -13287,6 +14528,7 @@ var textRule = {
13287
14528
 
13288
14529
  // packages/parser/src/parser/rules/inline/index.ts
13289
14530
  var inlineRules = [
14531
+ emailRule,
13290
14532
  boldRule,
13291
14533
  italicRule,
13292
14534
  underlineRule,
@@ -13298,6 +14540,7 @@ var inlineRules = [
13298
14540
  linkSingleRule,
13299
14541
  linkAnchorRule,
13300
14542
  linkStarRule,
14543
+ autolinkRule,
13301
14544
  colorRule,
13302
14545
  backslashLineBreakRule,
13303
14546
  underscoreLineBreakRule,
@@ -13307,6 +14550,9 @@ var inlineRules = [
13307
14550
  rawRule,
13308
14551
  imageRule,
13309
14552
  embedBlockRule,
14553
+ buttonRule,
14554
+ socialRule,
14555
+ dateRule,
13310
14556
  sizeRule,
13311
14557
  footnoteRule,
13312
14558
  spanRule,
@@ -13330,7 +14576,7 @@ function createParseContext(tokens, options = {}, deferInclude) {
13330
14576
  pos: 0,
13331
14577
  version: options.version ?? "wikidot",
13332
14578
  trackPositions: options.trackPositions ?? true,
13333
- settings: options.settings ?? import_ast6.DEFAULT_SETTINGS,
14579
+ settings: options.settings ?? import_ast9.DEFAULT_SETTINGS,
13334
14580
  appendImplicitFootnoteBlock: options.appendImplicitFootnoteBlock ?? true,
13335
14581
  deferInclude,
13336
14582
  footnotes: [],
@@ -13562,6 +14808,8 @@ function mergeSpanStripParagraphs(children) {
13562
14808
  i++;
13563
14809
  continue;
13564
14810
  }
14811
+ const firstMeaningful = paraData.elements.find((child) => child.element !== "line-break" && !isWhitespaceText(child));
14812
+ const keepParagraph = firstMeaningful && !isSpanStripMarker(firstMeaningful);
13565
14813
  const mergedChildren = [...paraData.elements];
13566
14814
  i++;
13567
14815
  while (i < expandedChildren.length) {
@@ -13574,7 +14822,8 @@ function mergeSpanStripParagraphs(children) {
13574
14822
  break;
13575
14823
  }
13576
14824
  const hasSpanStrip = hasParagraphStripSpan(nextPara);
13577
- mergedChildren.push(...nextParaData.elements);
14825
+ for (const element of nextParaData.elements)
14826
+ mergedChildren.push(element);
13578
14827
  i++;
13579
14828
  if (!hasSpanStrip) {
13580
14829
  const peekNext = expandedChildren[i];
@@ -13585,7 +14834,7 @@ function mergeSpanStripParagraphs(children) {
13585
14834
  }
13586
14835
  const escapedSpans = extractEscapedSpans(mergedChildren);
13587
14836
  removeLineBreaksAroundSpanStrip(mergedChildren);
13588
- if (escapedSpans.length > 0) {
14837
+ if (escapedSpans.length > 0 || keepParagraph) {
13589
14838
  if (mergedChildren.length > 0) {
13590
14839
  result.push(paragraphElement(mergedChildren));
13591
14840
  }
@@ -13806,50 +15055,6 @@ function cleanInternalFlags(elements) {
13806
15055
  }
13807
15056
  return removeEmptySpansAndAdjacentWhitespace(cleaned ?? elements);
13808
15057
  }
13809
- // packages/parser/src/parser/postprocess/divAdjacentParagraph.ts
13810
- function isParagraphContainer2(el) {
13811
- if (!el || el.element !== "container")
13812
- return false;
13813
- return el.data.type === "paragraph";
13814
- }
13815
- function isDivContainer(el) {
13816
- if (!el || el.element !== "container")
13817
- return false;
13818
- return el.data.type === "div";
13819
- }
13820
- function suppressAtLevel(elements) {
13821
- if (elements.length <= 1)
13822
- return elements;
13823
- const unwrap = Array.from({ length: elements.length }, () => false);
13824
- for (let i = 0;i < elements.length; i++) {
13825
- if (!isParagraphContainer2(elements[i]))
13826
- continue;
13827
- const prevIsDiv = i > 0 && isDivContainer(elements[i - 1]);
13828
- const nextIsDiv = i < elements.length - 1 && isDivContainer(elements[i + 1]);
13829
- if (prevIsDiv || nextIsDiv) {
13830
- unwrap[i] = true;
13831
- }
13832
- }
13833
- const result = [];
13834
- for (let i = 0;i < elements.length; i++) {
13835
- const el = elements[i];
13836
- if (!el)
13837
- continue;
13838
- if (unwrap[i] && el.element === "container") {
13839
- const inner = el.data.elements;
13840
- if (i > 0 && isDivContainer(elements[i - 1])) {
13841
- result.push({ element: "line-break" });
13842
- }
13843
- result.push(...inner);
13844
- } else {
13845
- result.push(el);
13846
- }
13847
- }
13848
- return result;
13849
- }
13850
- function suppressDivAdjacentParagraphs(elements) {
13851
- return suppressAtLevel(elements);
13852
- }
13853
15058
  // packages/parser/src/parser/parse/footnotes.ts
13854
15059
  function containsFootnoteBlock(elements) {
13855
15060
  let found = false;
@@ -14127,7 +15332,9 @@ function substitute(text2) {
14127
15332
  result = result.replace(WHITESPACE_ONLY_LINE, "");
14128
15333
  }
14129
15334
  if (result.indexOf("\\\n") !== -1) {
14130
- result = result.replace(CONCAT_LINES, String.fromCharCode(57344));
15335
+ const sentinels = makeUniqueSentinels(result);
15336
+ const { masked, placeholders } = maskRawRegions(result, sentinels);
15337
+ result = restorePlaceholders(masked.replace(CONCAT_LINES, String.fromCharCode(57344)), placeholders, sentinels);
14131
15338
  }
14132
15339
  if (result.indexOf("\t") !== -1) {
14133
15340
  result = result.replace(TABS, " ");
@@ -14194,14 +15401,26 @@ function replaceDelimitedTypography(text2, opener, closer, leftQuote, rightQuote
14194
15401
  let searchFrom = 0;
14195
15402
  let result = "";
14196
15403
  let lastCopied = 0;
15404
+ let closeIndex = -1;
15405
+ let newlineIndex = -1;
14197
15406
  while (searchFrom < text2.length) {
14198
15407
  const openIndex = text2.indexOf(opener, searchFrom);
14199
15408
  if (openIndex === -1)
14200
15409
  break;
14201
15410
  const contentStart = openIndex + opener.length;
14202
- const closeIndex = text2.indexOf(closer, contentStart);
15411
+ if (closeIndex < contentStart)
15412
+ closeIndex = text2.indexOf(closer, contentStart);
14203
15413
  if (closeIndex === -1)
14204
15414
  break;
15415
+ if (newlineIndex < contentStart) {
15416
+ const nextNewline = text2.indexOf(`
15417
+ `, contentStart);
15418
+ newlineIndex = nextNewline === -1 ? text2.length : nextNewline;
15419
+ }
15420
+ if (newlineIndex < closeIndex) {
15421
+ searchFrom = newlineIndex + 1;
15422
+ continue;
15423
+ }
14205
15424
  result += text2.slice(lastCopied, openIndex);
14206
15425
  result += leftQuote;
14207
15426
  result += text2.slice(contentStart, closeIndex);
@@ -14212,7 +15431,12 @@ function replaceDelimitedTypography(text2, opener, closer, leftQuote, rightQuote
14212
15431
  return lastCopied === 0 ? text2 : result + text2.slice(lastCopied);
14213
15432
  }
14214
15433
  function substitute2(text2) {
14215
- let result = text2;
15434
+ if (!text2.includes("`") && !text2.includes(",,") && !text2.includes("...") && !text2.includes(". . .")) {
15435
+ return text2;
15436
+ }
15437
+ const sentinels = makeUniqueSentinels(text2);
15438
+ const { masked, placeholders } = maskRawRegions(text2, sentinels);
15439
+ let result = masked;
14216
15440
  if (result.includes("``") && result.includes("''")) {
14217
15441
  result = replaceDelimitedTypography(result, "``", "''", LEFT_DOUBLE_QUOTE, RIGHT_DOUBLE_QUOTE);
14218
15442
  }
@@ -14228,7 +15452,7 @@ function substitute2(text2) {
14228
15452
  if (result.includes(". . .")) {
14229
15453
  result = replaceExactEllipsisPattern(result, ". . .");
14230
15454
  }
14231
- return result;
15455
+ return restorePlaceholders(result, placeholders, sentinels);
14232
15456
  }
14233
15457
 
14234
15458
  // packages/parser/src/parser/preprocess/index.ts
@@ -14268,12 +15492,12 @@ function matchDirectiveKind(source, i) {
14268
15492
  }
14269
15493
 
14270
15494
  // packages/parser/src/parser/preprocess/expr/evaluate.ts
14271
- var import_ast7 = require("@wdprlib/ast");
15495
+ var import_ast10 = require("@wdprlib/ast");
14272
15496
  function evaluateDirective(kind, match) {
14273
15497
  if (kind === "expr") {
14274
- const result2 = import_ast7.evaluateExpression(match.head);
15498
+ const result2 = import_ast10.evaluateExpression(match.head);
14275
15499
  if (result2.success)
14276
- return import_ast7.formatExprValue(result2.value);
15500
+ return import_ast10.formatExprValue(result2.value);
14277
15501
  if (result2.error === "empty expression")
14278
15502
  return "";
14279
15503
  return "ERROR";
@@ -14281,11 +15505,11 @@ function evaluateDirective(kind, match) {
14281
15505
  if (kind === "if") {
14282
15506
  if (!match.hasPipe)
14283
15507
  return "";
14284
- return import_ast7.isTruthy(match.head) ? match.thenText : match.elseText;
15508
+ return import_ast10.isTruthy(match.head) ? match.thenText : match.elseText;
14285
15509
  }
14286
15510
  if (!match.hasPipe)
14287
15511
  return "";
14288
- const result = import_ast7.evaluateExpression(match.head);
15512
+ const result = import_ast10.evaluateExpression(match.head);
14289
15513
  if (!result.success)
14290
15514
  return "ERROR";
14291
15515
  return result.value !== 0 && !Number.isNaN(result.value) ? match.thenText : match.elseText;
@@ -14467,13 +15691,13 @@ function parseSource(source, options, deferInclude) {
14467
15691
  return deferInclude ? parseTokensWithIncludeDeferral(tokens, options ?? {}, deferInclude) : new Parser(tokens, options).parse();
14468
15692
  }
14469
15693
  // packages/parser/src/pipeline/process.ts
14470
- var import_ast8 = require("@wdprlib/ast");
15694
+ var import_ast11 = require("@wdprlib/ast");
14471
15695
 
14472
15696
  // packages/parser/src/parser/rules/block/module/resolution/resolve-async.ts
14473
15697
  async function resolveModulesWithAsyncParse(ast, dataProvider, options) {
14474
15698
  const registry = new ModuleDocumentRegistry;
14475
15699
  registry.register(ast);
14476
- const parse2 = async (source) => registry.register(await options.parse(source));
15700
+ const parse2 = async (source) => registry.register(suppressModuleRatings(await options.parse(source)));
14477
15701
  const [listPagesData, listUsersData, tagCloudData] = await Promise.all([
14478
15702
  buildListPagesDataMap(dataProvider, options.requirements.listPages ?? [], options.urlPath, options.paginationState),
14479
15703
  buildListUsersDataMap(dataProvider, options.requirements.listUsers ?? []),
@@ -14650,7 +15874,7 @@ var PIPELINE_DIAGNOSTIC_POSITION = {
14650
15874
  end: { line: 1, column: 1, offset: 0 }
14651
15875
  };
14652
15876
  async function processWikitext(source, options) {
14653
- const settings = options.settings ?? import_ast8.DEFAULT_SETTINGS;
15877
+ const settings = options.settings ?? import_ast11.DEFAULT_SETTINGS;
14654
15878
  const callbackContext = {
14655
15879
  page: options.page,
14656
15880
  settings
@@ -14709,8 +15933,13 @@ async function processWikitext(source, options) {
14709
15933
  if (hasResolvableRequirements(extractDataRequirements(ast).requirements, options.dataProvider)) {
14710
15934
  diagnostics.push(createLimitDiagnostic("module-resolution-limit", `Module resolution stopped after ${DEFAULT_MODULE_MAX_PASSES} passes.`));
14711
15935
  }
15936
+ ast = await resolveRatings(ast, dataProvider.fetchRatings);
15937
+ const readableText = import_ast11.extractReadableText(ast, options.readableText);
14712
15938
  return {
14713
15939
  ast,
15940
+ readableText,
15941
+ firstParagraph: import_ast11.extractFirstParagraph(ast, options.readableText),
15942
+ characterCount: import_ast11.countCharacters(readableText),
14714
15943
  page: options.page,
14715
15944
  settings,
14716
15945
  diagnostics,
@@ -14739,6 +15968,7 @@ function createLimitDiagnostic(code, message) {
14739
15968
  function createModuleDataProvider(options, context) {
14740
15969
  const provider = options.dataProvider;
14741
15970
  return {
15971
+ fetchRatings: provider?.fetchRatings ? (refs) => provider.fetchRatings(refs, context) : undefined,
14742
15972
  fetchListPages: provider?.fetchListPages ? (query, requirement) => provider.fetchListPages(query, requirement, context) : undefined,
14743
15973
  fetchListUsers: provider?.fetchListUsers ? (requirement) => provider.fetchListUsers(requirement, context) : undefined,
14744
15974
  fetchTagCloud: provider?.fetchTagCloud ? (requirement) => provider.fetchTagCloud(requirement, context) : undefined,