@ai-react-markdown/engine 2.4.0 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -61,6 +61,7 @@ __export(src_exports, {
61
61
  extendSanitizeSchema: () => extendSanitizeSchema,
62
62
  extractContributions: () => extractContributions,
63
63
  extractDefBodiesFromHast: () => extractDefBodiesFromHast,
64
+ footnoteSafeId: () => footnoteSafeId,
64
65
  getEnginePluginInternals: () => getEnginePluginInternals,
65
66
  hasLoneSurrogate: () => hasLoneSurrogate,
66
67
  highlight: () => highlight,
@@ -294,6 +295,10 @@ var TAG_OR_COMMENT_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)(?=[\s/>])([^>]*)>|<!--|--
294
295
  var TRUNCATED_TAG_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)([^<>]*)$/;
295
296
  var REF_RE = /!?\[((?:[^[\]\\]|\\.)*)\]/g;
296
297
  var BACKTICK_RUN_RE = /`+/g;
298
+ var MD_BLANK_RE = /^[ \t\r]*$/;
299
+ var isMdBlank = (text) => MD_BLANK_RE.test(text);
300
+ var mdTrim = (text) => text.replace(/^[ \t\r]+|[ \t\r]+$/g, "");
301
+ var mdTrimStart = (text) => text.replace(/^[ \t\r]+/, "");
297
302
  function computeIndent(text) {
298
303
  let indent = 0;
299
304
  for (const ch of text) {
@@ -303,8 +308,25 @@ function computeIndent(text) {
303
308
  }
304
309
  return indent;
305
310
  }
311
+ function firstUnescaped(text, ch) {
312
+ for (let i = 0; i < text.length; i++) {
313
+ if (text[i] === "\\") i += 1;
314
+ else if (text[i] === ch) return i;
315
+ }
316
+ return -1;
317
+ }
318
+ function lastUnclosedBracket(text) {
319
+ let open = -1;
320
+ for (let i = 0; i < text.length; i++) {
321
+ const c = text[i];
322
+ if (c === "\\") i += 1;
323
+ else if (c === "[") open = i;
324
+ else if (c === "]") open = -1;
325
+ }
326
+ return open;
327
+ }
306
328
  function normalizeLabel(label) {
307
- const collapsed = label.trim().replace(/[ \t\r\n]+/g, " ");
329
+ const collapsed = label.replace(/[ \t\r\n]+/g, " ").replace(/^ | $/g, "");
308
330
  return collapsed ? (0, import_micromark_util_normalize_identifier.normalizeIdentifier)(collapsed) : "";
309
331
  }
310
332
  function canBecomeDdLine(text, confirmed) {
@@ -381,6 +403,7 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
381
403
  prevLineWasText: false,
382
404
  prevLineWasValidDef: false,
383
405
  paragraphHasUnpairedRun: false,
406
+ openBracket: null,
384
407
  htmlFlowSinceBlank: false,
385
408
  htmlSeamPending: false,
386
409
  phasePoisonedAt: Infinity,
@@ -388,11 +411,11 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
388
411
  };
389
412
  }
390
413
  function isPlausibleLinkDefRest(rest) {
391
- const t = rest.trim();
414
+ const t = mdTrim(rest);
392
415
  if (t === "") return false;
393
416
  const destEnd = linkDestinationEnd(t);
394
417
  if (destEnd === -1) return false;
395
- const after = t.slice(destEnd).trim();
418
+ const after = mdTrim(t.slice(destEnd));
396
419
  if (after === "") return true;
397
420
  const opener = after[0];
398
421
  if (opener !== '"' && opener !== "'" && opener !== "(") return false;
@@ -402,7 +425,7 @@ function isPlausibleLinkDefRest(rest) {
402
425
  i += 1;
403
426
  continue;
404
427
  }
405
- if (after[i] === closer) return after.slice(i + 1).trim() === "";
428
+ if (after[i] === closer) return isMdBlank(after.slice(i + 1));
406
429
  }
407
430
  return false;
408
431
  }
@@ -439,6 +462,36 @@ function linkDestinationEnd(t) {
439
462
  if (balance !== 0 || i === 0) return -1;
440
463
  return i;
441
464
  }
465
+ function inlineResourceEnd(text, openIdx) {
466
+ let i = openIdx + 1;
467
+ const skipWs = () => {
468
+ while (i < text.length && (text[i] === " " || text[i] === " ")) i += 1;
469
+ };
470
+ skipWs();
471
+ if (text[i] === ")") return i + 1;
472
+ const destEnd = linkDestinationEnd(text.slice(i));
473
+ if (destEnd === -1) return -1;
474
+ i += destEnd;
475
+ const beforeWs = i;
476
+ skipWs();
477
+ if (text[i] === ")") return i + 1;
478
+ if (i === beforeWs) return -1;
479
+ const opener = text[i];
480
+ if (opener !== '"' && opener !== "'" && opener !== "(") return -1;
481
+ const closer = opener === "(" ? ")" : opener;
482
+ for (i += 1; i < text.length; i++) {
483
+ if (text[i] === "\\") {
484
+ i += 1;
485
+ continue;
486
+ }
487
+ if (text[i] === closer) {
488
+ i += 1;
489
+ skipWs();
490
+ return text[i] === ")" ? i + 1 : -1;
491
+ }
492
+ }
493
+ return -1;
494
+ }
442
495
  function classifyBlockStart(text, indent, defListEnabled) {
443
496
  if (indent >= 4) return true;
444
497
  if (LIST_MARKER_RE.test(text) || FOOTNOTE_DEF_RE.test(text)) return true;
@@ -456,12 +509,13 @@ function computeFreezeBoundary(text, options, resume) {
456
509
  let end = text.indexOf("\n", start);
457
510
  if (end === -1) end = text.length;
458
511
  const confirmed = end < text.length;
459
- const lineText = text.slice(start, end);
512
+ const rawLine = text.slice(start, end);
513
+ const lineText = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
460
514
  const ln = {
461
515
  start,
462
516
  end,
463
517
  text: lineText,
464
- blank: confirmed && lineText.trim() === "",
518
+ blank: confirmed && isMdBlank(lineText),
465
519
  indent: computeIndent(lineText)
466
520
  };
467
521
  if (!confirmed) {
@@ -550,7 +604,7 @@ function processConfirmedLine(cp, ln, text) {
550
604
  const isBlockStart = cp.prevLineBlank;
551
605
  if (cp.htmlSeamPending && !ln.blank && !cp.htmlFlowSinceBlank && !(cp.commentOpen || cp.piOpen || cp.declOpen || cp.cdataOpen)) {
552
606
  const defShapedLine = DEF_RE.test(ln.text) || FOOTNOTE_DEF_RE.test(ln.text);
553
- const commentOnly = ln.text.replace(/<!--[\s\S]*?-->/g, " ").replace(/<!--[\s\S]*$/, " ").trim() === "";
607
+ const commentOnly = ln.text.replace(/<!--[\s\S]*?-->/g, " ").replace(/<!--[\s\S]*$/, " ").replace(/[ \t\r]/g, "") === "";
554
608
  if (!defShapedLine && !commentOnly) {
555
609
  cp.htmlSeamPending = false;
556
610
  }
@@ -571,13 +625,14 @@ function processConfirmedLine(cp, ln, text) {
571
625
  const rawOpenAtLineStart = cp.commentOpen || cp.piOpen || cp.declOpen || cp.cdataOpen;
572
626
  if (cp.inFence) {
573
627
  const close = FENCE_RE.exec(ln.text);
574
- if (close && close[1][0] === cp.fenceChar && close[1].length >= cp.fenceLen && ln.text.trim() === close[1]) {
628
+ if (close && close[1][0] === cp.fenceChar && close[1].length >= cp.fenceLen && isMdBlank(ln.text.slice(close[0].length))) {
575
629
  cp.inFence = false;
576
630
  cp.fenceChar = "";
577
631
  cp.fenceLen = 0;
578
632
  }
579
633
  cp.blankRun = 0;
580
634
  cp.paragraphHasUnpairedRun = false;
635
+ cp.openBracket = null;
581
636
  cp.prevLineBlank = false;
582
637
  cp.prevLineWasText = false;
583
638
  cp.prevLineWasValidDef = false;
@@ -599,6 +654,7 @@ function processConfirmedLine(cp, ln, text) {
599
654
  cp.openIndent = ln.indent;
600
655
  cp.blankRun = 0;
601
656
  cp.paragraphHasUnpairedRun = false;
657
+ cp.openBracket = null;
602
658
  cp.prevLineBlank = false;
603
659
  cp.prevLineWasText = false;
604
660
  cp.prevLineWasValidDef = false;
@@ -607,12 +663,13 @@ function processConfirmedLine(cp, ln, text) {
607
663
  }
608
664
  if (cp.inMath) {
609
665
  const close = MATH_RUN_RE.exec(ln.text);
610
- if (close && close[1].length >= cp.mathFenceLen && ln.text.trim() === close[1]) {
666
+ if (close && close[1].length >= cp.mathFenceLen && isMdBlank(ln.text.slice(close[0].length))) {
611
667
  cp.inMath = false;
612
668
  cp.mathFenceLen = 0;
613
669
  }
614
670
  cp.blankRun = 0;
615
671
  cp.paragraphHasUnpairedRun = false;
672
+ cp.openBracket = null;
616
673
  cp.prevLineBlank = false;
617
674
  cp.prevLineWasText = false;
618
675
  cp.prevLineWasValidDef = false;
@@ -634,6 +691,7 @@ function processConfirmedLine(cp, ln, text) {
634
691
  cp.openIndent = ln.indent;
635
692
  cp.blankRun = 0;
636
693
  cp.paragraphHasUnpairedRun = false;
694
+ cp.openBracket = null;
637
695
  cp.prevLineBlank = false;
638
696
  cp.prevLineWasText = false;
639
697
  cp.prevLineWasValidDef = false;
@@ -657,6 +715,7 @@ function processConfirmedLine(cp, ln, text) {
657
715
  defListSettled: null
658
716
  });
659
717
  cp.paragraphHasUnpairedRun = false;
718
+ cp.openBracket = null;
660
719
  cp.htmlFlowSinceBlank = false;
661
720
  cp.prevLineBlank = true;
662
721
  cp.prevLineWasText = false;
@@ -669,13 +728,13 @@ function processConfirmedLine(cp, ln, text) {
669
728
  } else if (LIST_MARKER_RE.test(ln.text) || FOOTNOTE_DEF_RE.test(ln.text) || cp.defListEnabled && DEF_LIST_DD_RE.test(ln.text) || ln.indent >= 4) {
670
729
  cp.hazardVerdict = true;
671
730
  }
672
- const tagStart = ln.indent <= 3 ? /^<\/?([A-Za-z][A-Za-z0-9-]*)/.exec(ln.text.trimStart()) : null;
731
+ const tagStart = ln.indent <= 3 ? /^<\/?([A-Za-z][A-Za-z0-9-]*)/.exec(mdTrimStart(ln.text)) : null;
673
732
  if (tagStart) {
674
733
  cp.htmlFlowSinceBlank = true;
675
734
  if (!TYPE6_NAMES.has(tagStart[1].toLowerCase())) cp.hazardVerdict = true;
676
735
  }
677
736
  const inRawText = cp.htmlFlowSinceBlank || rawOpenAtLineStart;
678
- const rawFlowStart = ln.indent <= 3 && /^<(?:!--|\?|![A-Za-z]|!\[CDATA\[)/.test(ln.text.trimStart());
737
+ const rawFlowStart = ln.indent <= 3 && /^<(?:!--|\?|![A-Za-z]|!\[CDATA\[)/.test(mdTrimStart(ln.text));
679
738
  const { masked, unpaired } = inRawText ? { masked: null, unpaired: false } : maskIntraLineCodeSpans(ln.text, cp.paragraphHasUnpairedRun);
680
739
  if (unpaired) cp.paragraphHasUnpairedRun = true;
681
740
  const scanText = masked ?? ln.text;
@@ -693,31 +752,52 @@ function processConfirmedLine(cp, ln, text) {
693
752
  if (key && !cp.defs.has(key)) cp.defs.set(key, ln.end);
694
753
  }
695
754
  }
696
- if (cp.referenceTaint && scanText.includes("[")) {
697
- const defBracket = validDef ? def.index + def[0].indexOf("[") : -1;
698
- REF_RE.lastIndex = 0;
699
- let m;
700
- while ((m = REF_RE.exec(scanText)) !== null) {
701
- const follow = scanText[m.index + m[0].length];
702
- if (follow === "(") continue;
703
- if (follow === ":" && m.index === defBracket) continue;
704
- const inner = m[1];
755
+ if (cp.referenceTaint) {
756
+ const pushRef = (offset, inner, followAt) => {
757
+ const follow = scanText[followAt];
758
+ if (follow === "(" && inlineResourceEnd(scanText, followAt) !== -1) return;
705
759
  let label;
706
760
  let footnote = false;
707
761
  if (inner.startsWith("^")) {
708
762
  footnote = true;
709
763
  label = normalizeLabel(inner.slice(1));
710
764
  } else if (follow === "[") {
711
- const explicit = /^\[((?:[^[\]\\]|\\.)*)\]/.exec(scanText.slice(m.index + m[0].length));
765
+ const explicit = /^\[((?:[^[\]\\]|\\.)*)\]/.exec(scanText.slice(followAt));
712
766
  label = normalizeLabel(explicit && explicit[1] ? explicit[1] : inner);
713
767
  } else {
714
768
  label = normalizeLabel(inner);
715
769
  }
716
- if (!label) continue;
717
- cp.unresolvedRefs.push({ offset: ln.start + m.index, label, footnote });
770
+ if (label) cp.unresolvedRefs.push({ offset, label, footnote });
771
+ };
772
+ const pending = cp.openBracket;
773
+ cp.openBracket = null;
774
+ if (pending) {
775
+ const close = firstUnescaped(scanText, "]");
776
+ const open = firstUnescaped(scanText, "[");
777
+ const cont = (t) => t.replace(/^ {0,3}>[ \t]?/, "");
778
+ if (close !== -1 && (open === -1 || close < open)) {
779
+ pushRef(pending.offset, `${pending.text}
780
+ ${cont(scanText.slice(0, close))}`, close + 1);
781
+ } else if (close === -1 && open === -1) {
782
+ cp.openBracket = { offset: pending.offset, text: `${pending.text}
783
+ ${cont(scanText)}` };
784
+ }
785
+ }
786
+ if (scanText.includes("[")) {
787
+ const defBracket = validDef ? def.index + def[0].indexOf("[") : -1;
788
+ REF_RE.lastIndex = 0;
789
+ let m;
790
+ while ((m = REF_RE.exec(scanText)) !== null) {
791
+ const followAt = m.index + m[0].length;
792
+ if (scanText[followAt] === ":" && m.index === defBracket) continue;
793
+ pushRef(ln.start + m.index, m[1], followAt);
794
+ }
795
+ const trailingOpen = lastUnclosedBracket(scanText);
796
+ if (trailingOpen !== -1) {
797
+ cp.openBracket = { offset: ln.start + trailingOpen, text: scanText.slice(trailingOpen + 1) };
798
+ }
718
799
  }
719
800
  }
720
- const rawOpenAtStart = cp.piOpen || cp.declOpen || cp.cdataOpen;
721
801
  const rawSpans = [];
722
802
  let pos = 0;
723
803
  const poisonRawDivergence = () => {
@@ -776,7 +856,7 @@ function processConfirmedLine(cp, ln, text) {
776
856
  if (scanText[pi + 2] === ">") {
777
857
  rawSpans.push([pi, pi + 3]);
778
858
  pos = pi + 3;
779
- if (scanText.slice(0, pi).trim() !== "" || ln.indent > 3) poisonRawDivergence();
859
+ if (!isMdBlank(scanText.slice(0, pi)) || ln.indent > 3) poisonRawDivergence();
780
860
  continue;
781
861
  }
782
862
  rawSpans.push([pi, pi + 2]);
@@ -788,14 +868,17 @@ function processConfirmedLine(cp, ln, text) {
788
868
  pos = decl + 2;
789
869
  }
790
870
  }
791
- const rawOpenAtEnd = cp.piOpen || cp.declOpen || cp.cdataOpen;
792
- if (!rawOpenAtStart && !rawOpenAtEnd) {
871
+ let tagText = scanText;
872
+ for (const [from, to] of rawSpans) {
873
+ tagText = tagText.slice(0, from) + " ".repeat(to - from) + tagText.slice(to);
874
+ }
875
+ {
793
876
  TAG_OR_COMMENT_RE.lastIndex = 0;
794
877
  let m;
795
878
  let lastCommentOpenerIdx = -1;
796
- while ((m = TAG_OR_COMMENT_RE.exec(scanText)) !== null) {
879
+ while ((m = TAG_OR_COMMENT_RE.exec(tagText)) !== null) {
797
880
  if (m[0] === "<!--") {
798
- const next = scanText.slice(m.index + 4, m.index + 6);
881
+ const next = tagText.slice(m.index + 4, m.index + 6);
799
882
  if (cp.commentOpen) {
800
883
  if (next.startsWith(">") || next === "->") cp.commentOpen = false;
801
884
  else if (next === "!>" || next === "-!") poisonRawDivergence();
@@ -824,34 +907,56 @@ function processConfirmedLine(cp, ln, text) {
824
907
  applyTag(tag, closing);
825
908
  }
826
909
  if (cp.commentOpen && lastCommentOpenerIdx !== -1 && !inRawText) {
827
- if (scanText.slice(0, lastCommentOpenerIdx).trim() !== "") {
910
+ if (!isMdBlank(tagText.slice(0, lastCommentOpenerIdx))) {
828
911
  cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastCommentOpenerIdx);
829
912
  }
830
913
  }
831
- if (cp.pendingTruncatedTags.length > 0 && scanText.includes(">")) {
914
+ if (masked !== null && masked !== ln.text) {
915
+ const inRaw = (i) => rawSpans.some(([from, to]) => i >= from && i < to);
916
+ TAG_OR_COMMENT_RE.lastIndex = 0;
917
+ let mr;
918
+ while ((mr = TAG_OR_COMMENT_RE.exec(ln.text)) !== null) {
919
+ if (mr[0] === "<!--" || mr[0] === "-->" || mr[0] === "--!>") continue;
920
+ const startMasked = masked[mr.index] !== ln.text[mr.index];
921
+ const wholeVisible = masked.slice(mr.index, mr.index + mr[0].length) === mr[0];
922
+ if (startMasked || wholeVisible || inRaw(mr.index) || cp.commentOpen) continue;
923
+ const closing = mr[1] === "/";
924
+ const tag = mr[2].toLowerCase();
925
+ const selfClosing = mr[3] !== void 0 && /\/\s*$/.test(mr[3]);
926
+ if (VOID_TAGS.has(tag) || selfClosing) continue;
927
+ applyTag(tag, closing);
928
+ }
929
+ }
930
+ if (cp.pendingTruncatedTags.length > 0 && ln.text.includes(">")) {
832
931
  cp.pendingTruncatedTags = [];
833
932
  }
834
933
  if (!cp.commentOpen) {
835
- const lastLt = scanText.lastIndexOf("<");
836
- if (lastLt !== -1 && !scanText.includes(">", lastLt)) {
837
- const m2 = TRUNCATED_TAG_RE.exec(scanText.slice(lastLt));
934
+ const lastLt = tagText.lastIndexOf("<");
935
+ if (lastLt !== -1 && !tagText.includes(">", lastLt)) {
936
+ const m2 = TRUNCATED_TAG_RE.exec(tagText.slice(lastLt));
838
937
  if (m2) {
839
938
  const closing = m2[1] === "/";
840
939
  const tag = m2[2].toLowerCase();
841
940
  if (!VOID_TAGS.has(tag)) {
842
941
  applyTag(tag, closing);
843
- if (!closing && !inRawText) cp.pendingTruncatedTags.push(tag);
942
+ const rawLastLt = ln.text.lastIndexOf("<");
943
+ const rawTruncated = rawLastLt !== -1 && !ln.text.includes(">", rawLastLt);
944
+ if (!closing && !inRawText && rawTruncated) cp.pendingTruncatedTags.push(tag);
844
945
  }
845
946
  }
846
947
  }
847
948
  }
848
949
  }
849
- if ((inRawText || rawFlowStart) && cp.openTotal === 0) {
850
- let masked2 = scanText;
950
+ const effectiveOpen = cp.openTotal - cp.pendingTruncatedTags.length;
951
+ if ((inRawText || rawFlowStart) && effectiveOpen <= 0) {
952
+ let masked2 = "";
953
+ let cursor = 0;
851
954
  for (const [from, to] of rawSpans) {
852
- masked2 = masked2.slice(0, from) + " ".repeat(to - from) + masked2.slice(to);
955
+ masked2 += scanText.slice(cursor, from);
956
+ cursor = to;
853
957
  }
854
- if (floatingResidue(masked2, commentOpenAtLineStart).trim() !== "") {
958
+ masked2 += scanText.slice(cursor);
959
+ if (floatingResidue(masked2, commentOpenAtLineStart).length > 0) {
855
960
  cp.htmlSeamPending = true;
856
961
  }
857
962
  }
@@ -914,7 +1019,7 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
914
1019
  if (last && last.kind === "refs") last.tokens.push(token);
915
1020
  else events.push({ kind: "refs", tokens: [token] });
916
1021
  };
917
- const visit6 = (node, nested) => {
1022
+ const visit7 = (node, nested) => {
918
1023
  const type = node.type;
919
1024
  if (type === "definition") {
920
1025
  const start = node.position?.start?.offset;
@@ -955,7 +1060,7 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
955
1060
  }
956
1061
  const children = node.children;
957
1062
  if (children) {
958
- for (const child of children) visit6(child, true);
1063
+ for (const child of children) visit7(child, true);
959
1064
  }
960
1065
  };
961
1066
  let lastStart = -1;
@@ -968,11 +1073,11 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
968
1073
  if (start === void 0) {
969
1074
  if (resumeAt > 0) return collectPrefixInjection(mdast, content, boundary, null);
970
1075
  cacheable = false;
971
- visit6(child, false);
1076
+ visit7(child, false);
972
1077
  continue;
973
1078
  }
974
1079
  if (start >= boundary || start < resumeAt) continue;
975
- visit6(child, false);
1080
+ visit7(child, false);
976
1081
  }
977
1082
  return { events, uninjectable, cacheable };
978
1083
  }
@@ -985,7 +1090,7 @@ function cloneEventsForAppend(events) {
985
1090
  var TERMINATOR_LABEL = "__aimd_injection_terminator__";
986
1091
  var INJECTION_TERMINATOR = `[${TERMINATOR_LABEL}]: __aimd_sentinel_link__`;
987
1092
  function tailMentionsTerminator(tailSource) {
988
- return (0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(tailSource).includes(`[${(0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(TERMINATOR_LABEL)}`);
1093
+ return (0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(tailSource).replace(/\[ /g, "[").includes(`[${(0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(TERMINATOR_LABEL)}`);
989
1094
  }
990
1095
  function buildInjectionPrefix(events) {
991
1096
  if (events.length === 0) return { text: "", segments: [] };
@@ -1028,13 +1133,14 @@ function rebaseDualWalk(node, segments, maxEnd, offsetDelta, lineDelta) {
1028
1133
  const position = node.position;
1029
1134
  if (position) {
1030
1135
  for (const point of [position.start, position.end]) {
1136
+ if (!point) continue;
1031
1137
  const seg = point.offset !== void 0 && point.offset <= maxEnd ? segments.find((s) => point.offset >= s.injStart && point.offset <= s.injEnd) : void 0;
1032
1138
  if (seg) {
1033
1139
  point.offset += seg.offsetDelta;
1034
- point.line += seg.lineDelta;
1140
+ if (typeof point.line === "number") point.line += seg.lineDelta;
1035
1141
  } else {
1036
1142
  if (point.offset !== void 0) point.offset += offsetDelta;
1037
- point.line += lineDelta;
1143
+ if (typeof point.line === "number") point.line += lineDelta;
1038
1144
  }
1039
1145
  }
1040
1146
  }
@@ -1065,12 +1171,25 @@ function spliceTrees(input) {
1065
1171
  if (isTrailingLiteralText(node2)) {
1066
1172
  const prev = i > 0 ? prevHast.children[i - 1] : void 0;
1067
1173
  if (!prev || prev.type !== "element" || prev.position === void 0) return null;
1174
+ if (!ownsTrailingLiteral(prev, node2, prefixMdast))
1175
+ return null;
1068
1176
  }
1069
1177
  cutRegion.push(node2);
1070
1178
  continue;
1071
1179
  }
1072
1180
  const node = prevHast.children[i];
1073
- if (i > 0 && attrs[i - 1] < boundary && prevHast.children[i - 1].type === "element" && isTrailingLiteralText(node)) {
1181
+ if (i > 0 && attrs[i - 1] < boundary && prevHast.children[i - 1].type === "element" && isTrailingLiteralText(node) && // …and only when that element really IS an html block's output: a
1182
+ // raw literal can only trail an `html` mdast node. A position-less
1183
+ // text after a `<p>` is the NEXT block's remnant — a stray end tag
1184
+ // (`</t>\na`) parse5 dropped, whose text merged with the wrap
1185
+ // separator — owned by the tail, which re-parses it; freezing it
1186
+ // here duplicated it (v2.4.0 review P3). Falls through to the
1187
+ // remnant look-ahead below, which bails to a full parse. And the
1188
+ // literal must really be THAT block's trailing text: the block's raw
1189
+ // source ends with it. A dropped-tag block right after a frozen html
1190
+ // element (`</details>\n\n</t>\ntext`) puts its remnant in the same
1191
+ // position, and freezing it duplicated it (release soak of the fix).
1192
+ ownsTrailingLiteral(prevHast.children[i - 1], node, prefixMdast)) {
1074
1193
  cutRegion.push(node);
1075
1194
  break;
1076
1195
  }
@@ -1188,6 +1307,9 @@ function alignPrefixCut(prefixMdast, cutRegion, tailWrapVisible) {
1188
1307
  nextIdx = pairIdx + 1 + stripped;
1189
1308
  const candidate = visibles[nextIdx];
1190
1309
  if (!candidate) return null;
1310
+ if (start === void 0 && sepBuffer.some((sep) => sep.type !== "text" || sep.value !== "\n")) {
1311
+ return null;
1312
+ }
1191
1313
  if (start !== void 0) {
1192
1314
  const cStart = candidate.position?.start?.offset;
1193
1315
  const cEnd = candidate.position?.end?.offset;
@@ -1204,6 +1326,9 @@ function alignPrefixCut(prefixMdast, cutRegion, tailWrapVisible) {
1204
1326
  const trailingGaps = sawContent ? trailingStripped : Math.max(0, visibles.length - 1);
1205
1327
  const seam = visibles.length > 0 && tailWrapVisible ? 1 : 0;
1206
1328
  const last = out[out.length - 1];
1329
+ if (last !== void 0 && last.type === "text" && last.position !== void 0 && last.value.trim() === "") {
1330
+ return null;
1331
+ }
1207
1332
  const lastIsLiteral = last !== void 0 && last.type === "text" && last.value.trim() !== "";
1208
1333
  const litOwnerEnd = pairIdx >= 0 ? visibles[pairIdx].position?.end?.offset : void 0;
1209
1334
  const litEnd = lastIsLiteral ? last.position?.end?.offset : void 0;
@@ -1241,8 +1366,16 @@ function alignPrefixCut(prefixMdast, cutRegion, tailWrapVisible) {
1241
1366
  const v = lastPaired.value;
1242
1367
  const lastLt = v.lastIndexOf("<");
1243
1368
  if (lastLt !== -1 && /^<[!?]/.test(v.slice(lastLt))) return null;
1369
+ const lastOut = out[out.length - 1];
1370
+ const outEnd = lastOut?.type === "element" ? lastOut.position?.end?.offset : void 0;
1371
+ const blockEnd = lastPaired.position?.end?.offset;
1372
+ if (outEnd !== void 0 && blockEnd !== void 0 && outEnd < blockEnd) return null;
1244
1373
  }
1245
1374
  if (sepBuffer.length !== trailingGaps && sepBuffer.length !== trailingGaps + 1) return null;
1375
+ for (let j = pairIdx + 1; j < visibles.length; j++) {
1376
+ const v = visibles[j];
1377
+ if (v.type === "html" && !/^\s*<[!?]/.test(v.value)) return null;
1378
+ }
1246
1379
  for (let i = 0; i < trailingGaps + seam; i++) {
1247
1380
  out.push({ type: "text", value: "\n" });
1248
1381
  }
@@ -1285,8 +1418,16 @@ function stripInjectedHast(tailMdast, tailHast, injectedLen, tailWrapVisible) {
1285
1418
  }
1286
1419
  function tailLeadingTextIsHoist(tailMdastChildren, tailHastChildren) {
1287
1420
  const firstText = tailHastChildren[0];
1288
- if (!firstText || !isSeparatorText(firstText)) return false;
1421
+ if (!firstText) return false;
1289
1422
  const firstVisible = tailMdastChildren.find((c) => !isWrapInvisible(c));
1423
+ if (!isSeparatorText(firstText)) {
1424
+ if (firstText.type === "text" && firstText.position === void 0 && firstVisible?.type === "html") {
1425
+ if (/^\s*<\/[A-Za-z][A-Za-z0-9-]*\s*>/.test(firstVisible.value)) return true;
1426
+ if (isCompleteRawConstruct(firstVisible.value)) return false;
1427
+ return null;
1428
+ }
1429
+ return false;
1430
+ }
1290
1431
  if (!firstVisible) return false;
1291
1432
  const firstContent = tailHastChildren.find((c) => !isSeparatorText(c));
1292
1433
  if (firstContent) {
@@ -1308,6 +1449,14 @@ function isCompleteRawConstruct(value) {
1308
1449
  function isSeparatorText(node) {
1309
1450
  return node.type === "text" && node.position === void 0 && node.value.trim() === "";
1310
1451
  }
1452
+ function ownsTrailingLiteral(el, literal, prefixMdast) {
1453
+ const start = el.position?.start?.offset;
1454
+ if (start === void 0) return false;
1455
+ const owner = prefixMdast.find((c) => c.type === "html" && c.position?.start?.offset === start);
1456
+ if (!owner || owner.type !== "html") return false;
1457
+ const text = literal.value.trim();
1458
+ return text !== "" && owner.value.trimEnd().endsWith(text);
1459
+ }
1311
1460
  function isTrailingLiteralText(node) {
1312
1461
  return node.type === "text" && node.position === void 0 && node.value.trim() !== "";
1313
1462
  }
@@ -1542,7 +1691,11 @@ function createDefLabelScanner(parse = collectDefLabels) {
1542
1691
 
1543
1692
  // src/components/extractDefBodiesFromHast.ts
1544
1693
  var import_unist_util_visit2 = require("unist-util-visit");
1694
+ var import_micromark_util_sanitize_uri = require("micromark-util-sanitize-uri");
1545
1695
  var FN_LI_ID_RE = /(?:^|-)user-content-fn-(.+)$/;
1696
+ function footnoteSafeId(identifier) {
1697
+ return (0, import_micromark_util_sanitize_uri.normalizeUri)(identifier.toLowerCase());
1698
+ }
1546
1699
  function sourceIdFromFootnoteLiId(idProp, clobberPrefix) {
1547
1700
  let raw = null;
1548
1701
  if (clobberPrefix !== void 0) {
@@ -1699,10 +1852,10 @@ function phantomSuffixCloser(content) {
1699
1852
  const confirmed = endsWithNewline ? content : content + "\n";
1700
1853
  const { checkpoint } = computeFreezeBoundary(confirmed, { defListEnabled: false, referenceTaint: false });
1701
1854
  if (checkpoint.phasePoisonedAt !== Infinity) return "";
1855
+ if (checkpoint.openIndent !== 0) return "";
1702
1856
  const nl = endsWithNewline ? "" : "\n";
1703
- const indent = " ".repeat(checkpoint.openIndent);
1704
- if (checkpoint.inFence) return `${nl}${indent}${checkpoint.fenceChar.repeat(checkpoint.fenceLen)}`;
1705
- if (checkpoint.inMath) return `${nl}${indent}${"$".repeat(checkpoint.mathFenceLen)}`;
1857
+ if (checkpoint.inFence) return `${nl}${checkpoint.fenceChar.repeat(checkpoint.fenceLen)}`;
1858
+ if (checkpoint.inMath) return `${nl}${"$".repeat(checkpoint.mathFenceLen)}`;
1706
1859
  return "";
1707
1860
  }
1708
1861
 
@@ -1817,6 +1970,7 @@ function createRegistry(onEmpty) {
1817
1970
  }
1818
1971
  },
1819
1972
  contributeLabels(symbol, footnotes, links) {
1973
+ if (!this.chunkOrder.includes(symbol)) return;
1820
1974
  const data = this.chunkData.get(symbol);
1821
1975
  if (data) {
1822
1976
  data.ownFootnoteLabels = footnotes;
@@ -1841,6 +1995,7 @@ function createRegistry(onEmpty) {
1841
1995
  this._notify();
1842
1996
  },
1843
1997
  contributeChunkData(symbol, data) {
1998
+ if (!this.chunkOrder.includes(symbol)) return;
1844
1999
  this.chunkData.set(symbol, data);
1845
2000
  this.labelSet.footnoteLabels = /* @__PURE__ */ new Set();
1846
2001
  this.labelSet.linkLabels = /* @__PURE__ */ new Set();
@@ -1941,6 +2096,43 @@ function createRegistry(onEmpty) {
1941
2096
  var import_rehype_katex = __toESM(require("rehype-katex"), 1);
1942
2097
  var import_rehype_raw = __toESM(require("rehype-raw"), 1);
1943
2098
  var import_rehype_unwrap_images = __toESM(require("rehype-unwrap-images"), 1);
2099
+
2100
+ // src/components/rehypeUnwrapCrossChunkImages.ts
2101
+ var import_unist_util_visit4 = require("unist-util-visit");
2102
+ var IMAGE_TAGS = /* @__PURE__ */ new Set(["img", "cross-chunk-image"]);
2103
+ var LINK_TAGS = /* @__PURE__ */ new Set(["a", "cross-chunk-link"]);
2104
+ function applicable(node, inLink, seen) {
2105
+ let image = 0 /* Unknown */;
2106
+ for (const child of node.children) {
2107
+ if (child.type === "text" && /^\s*$/.test(child.value)) continue;
2108
+ if (child.type === "element" && IMAGE_TAGS.has(child.tagName)) {
2109
+ if (child.tagName !== "img") seen.placeholder = true;
2110
+ image = 1 /* ContainsImage */;
2111
+ } else if (!inLink && child.type === "element" && LINK_TAGS.has(child.tagName)) {
2112
+ if (child.tagName !== "a") seen.placeholder = true;
2113
+ const inner = applicable(child, true, seen);
2114
+ if (inner === 2 /* ContainsOther */) return 2 /* ContainsOther */;
2115
+ if (inner === 1 /* ContainsImage */) image = 1 /* ContainsImage */;
2116
+ } else {
2117
+ return 2 /* ContainsOther */;
2118
+ }
2119
+ }
2120
+ return image;
2121
+ }
2122
+ function rehypeUnwrapCrossChunkImages() {
2123
+ return function transform(tree) {
2124
+ (0, import_unist_util_visit4.visit)(tree, "element", (node, index, parent) => {
2125
+ if (node.tagName !== "p" || !parent || typeof index !== "number") return;
2126
+ const seen = { placeholder: false };
2127
+ if (applicable(node, false, seen) === 1 /* ContainsImage */ && seen.placeholder) {
2128
+ parent.children.splice(index, 1, ...node.children);
2129
+ return [import_unist_util_visit4.SKIP, index];
2130
+ }
2131
+ });
2132
+ };
2133
+ }
2134
+
2135
+ // src/components/pluginChain.ts
1944
2136
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
1945
2137
  var import_remark_breaks = __toESM(require("remark-breaks"), 1);
1946
2138
  var import_remark_cjk_friendly = __toESM(require("remark-cjk-friendly"), 1);
@@ -1956,13 +2148,13 @@ var import_remark_pangu = __toESM(require("remark-pangu"), 1);
1956
2148
  var import_remark_remove_comments = __toESM(require("remark-remove-comments"), 1);
1957
2149
 
1958
2150
  // src/components/rehypeRebaseHashLinks.ts
1959
- var import_unist_util_visit4 = require("unist-util-visit");
2151
+ var import_unist_util_visit5 = require("unist-util-visit");
1960
2152
  var DEFAULT_PREFIX = "user-content-";
1961
2153
  var rehypeRebaseHashLinks = (options) => {
1962
2154
  const prefix = options?.prefix ?? DEFAULT_PREFIX;
1963
2155
  const hashPrefix = "#" + prefix;
1964
2156
  return (tree) => {
1965
- (0, import_unist_util_visit4.visit)(tree, "element", (node) => {
2157
+ (0, import_unist_util_visit5.visit)(tree, "element", (node) => {
1966
2158
  if (node.tagName !== "a") return;
1967
2159
  const href = node.properties?.href;
1968
2160
  if (typeof href !== "string" || !href.startsWith("#")) return;
@@ -1974,7 +2166,7 @@ var rehypeRebaseHashLinks = (options) => {
1974
2166
  var rehypeRebaseHashLinks_default = rehypeRebaseHashLinks;
1975
2167
 
1976
2168
  // src/components/rehypeFooterAdorn.ts
1977
- var import_unist_util_visit5 = require("unist-util-visit");
2169
+ var import_unist_util_visit6 = require("unist-util-visit");
1978
2170
  var FOOTNOTE_LABEL_ID_RE = /(?:^|-)footnote-label$/;
1979
2171
  function isFootnoteLabelH2(node) {
1980
2172
  if (node.type !== "element") return false;
@@ -1989,7 +2181,7 @@ function isHr(node) {
1989
2181
  }
1990
2182
  function rehypeFooterAdorn() {
1991
2183
  return (tree) => {
1992
- (0, import_unist_util_visit5.visit)(tree, "element", (n) => {
2184
+ (0, import_unist_util_visit6.visit)(tree, "element", (n) => {
1993
2185
  const el = n;
1994
2186
  if (el.tagName !== "section") return;
1995
2187
  if (!(el.properties && "dataFootnotes" in el.properties)) return;
@@ -2067,7 +2259,10 @@ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix) {
2067
2259
  // above.
2068
2260
  [rehypeRebaseHashLinks_default, { prefix: clobberPrefix }],
2069
2261
  import_rehype_katex.default,
2070
- import_rehype_unwrap_images.default
2262
+ import_rehype_unwrap_images.default,
2263
+ // Same unwrap for `<cross-chunk-image>` placeholders (coordinated mode);
2264
+ // no-op on standalone documents.
2265
+ rehypeUnwrapCrossChunkImages
2071
2266
  ];
2072
2267
  }
2073
2268
  function buildCoreRemarkRehypeOptions(enableDefinitionList) {
@@ -2186,6 +2381,9 @@ function buildCrossChunkHandlers() {
2186
2381
  };
2187
2382
  }
2188
2383
 
2384
+ // src/components/crossChunkUrlSanitize.ts
2385
+ var import_micromark_util_sanitize_uri2 = require("micromark-util-sanitize-uri");
2386
+
2189
2387
  // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_listCacheClear.js
2190
2388
  function listCacheClear() {
2191
2389
  this.__data__ = [];
@@ -3413,14 +3611,13 @@ function isProtocolAllowed(url, allowed) {
3413
3611
  return allowed.some((p) => p === protocol);
3414
3612
  }
3415
3613
  function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
3416
- const transformed = urlTransform(rawUrl, key, fakeElement(tagName, key, rawUrl));
3417
- if (transformed == null) return "";
3418
- const stringUrl = String(transformed);
3419
- if (stringUrl === "") return "";
3614
+ rawUrl = (0, import_micromark_util_sanitize_uri2.normalizeUri)(rawUrl);
3420
3615
  const callerProtocols = schema.protocols;
3421
3616
  const allowed = callerProtocols === void 0 || callerProtocols === null ? sanitizeSchema.protocols?.[key] : callerProtocols[key];
3422
- if (!allowed || allowed.length === 0) return stringUrl;
3423
- return isProtocolAllowed(stringUrl, allowed) ? stringUrl : "";
3617
+ if (allowed && allowed.length > 0 && !isProtocolAllowed(rawUrl, allowed)) return null;
3618
+ const transformed = urlTransform(rawUrl, key, fakeElement(tagName, key, rawUrl));
3619
+ if (transformed == null) return null;
3620
+ return String(transformed);
3424
3621
  }
3425
3622
 
3426
3623
  // src/plugins/defs.ts
@@ -3800,6 +3997,20 @@ var LITERAL_CONTENT_CLOSE_REGEX = {
3800
3997
  math: /<\/math\s*>/gi,
3801
3998
  svg: /<\/svg\s*>/gi
3802
3999
  };
4000
+ function restOfLineIsBlank(content, pos) {
4001
+ for (let i = pos; i < content.length; i++) {
4002
+ const c = content[i];
4003
+ if (c === "\n") return true;
4004
+ if (c !== " " && c !== " " && c !== "\r") return false;
4005
+ }
4006
+ return true;
4007
+ }
4008
+ function lineHasBacktick(content, pos) {
4009
+ for (let i = pos; i < content.length && content[i] !== "\n"; i++) {
4010
+ if (content[i] === "`") return true;
4011
+ }
4012
+ return false;
4013
+ }
3803
4014
  function isAtLineStart(content, pos) {
3804
4015
  let i = pos - 1;
3805
4016
  let spaces = 0;
@@ -3842,7 +4053,7 @@ function splitByProtectedRegions(content) {
3842
4053
  if (multilineStart !== -1) {
3843
4054
  if (char === multilineFenceMarker) {
3844
4055
  const runLen = getRepeatedMarkerLength(content, i, multilineFenceMarker);
3845
- if (runLen >= multilineFenceLength && isAtLineStart(content, i)) {
4056
+ if (runLen >= multilineFenceLength && isAtLineStart(content, i) && restOfLineIsBlank(content, i + runLen)) {
3846
4057
  pushProtected(multilineStart, i + runLen);
3847
4058
  multilineStart = -1;
3848
4059
  multilineFenceMarker = null;
@@ -3858,7 +4069,7 @@ function splitByProtectedRegions(content) {
3858
4069
  }
3859
4070
  if (char === "`" || char === "~") {
3860
4071
  const runLen = getRepeatedMarkerLength(content, i, char);
3861
- if (runLen >= 3 && isAtLineStart(content, i)) {
4072
+ if (runLen >= 3 && isAtLineStart(content, i) && !(char === "`" && lineHasBacktick(content, i + runLen))) {
3862
4073
  multilineStart = i;
3863
4074
  multilineFenceMarker = char;
3864
4075
  multilineFenceLength = runLen;
@@ -4275,6 +4486,7 @@ function createRemendPreprocessor(options) {
4275
4486
  extendSanitizeSchema,
4276
4487
  extractContributions,
4277
4488
  extractDefBodiesFromHast,
4489
+ footnoteSafeId,
4278
4490
  getEnginePluginInternals,
4279
4491
  hasLoneSurrogate,
4280
4492
  highlight,