@ai-react-markdown/engine 2.4.0 → 2.4.1

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,
@@ -717,7 +718,6 @@ function processConfirmedLine(cp, ln, text) {
717
718
  cp.unresolvedRefs.push({ offset: ln.start + m.index, label, footnote });
718
719
  }
719
720
  }
720
- const rawOpenAtStart = cp.piOpen || cp.declOpen || cp.cdataOpen;
721
721
  const rawSpans = [];
722
722
  let pos = 0;
723
723
  const poisonRawDivergence = () => {
@@ -788,14 +788,17 @@ function processConfirmedLine(cp, ln, text) {
788
788
  pos = decl + 2;
789
789
  }
790
790
  }
791
- const rawOpenAtEnd = cp.piOpen || cp.declOpen || cp.cdataOpen;
792
- if (!rawOpenAtStart && !rawOpenAtEnd) {
791
+ let tagText = scanText;
792
+ for (const [from, to] of rawSpans) {
793
+ tagText = tagText.slice(0, from) + " ".repeat(to - from) + tagText.slice(to);
794
+ }
795
+ {
793
796
  TAG_OR_COMMENT_RE.lastIndex = 0;
794
797
  let m;
795
798
  let lastCommentOpenerIdx = -1;
796
- while ((m = TAG_OR_COMMENT_RE.exec(scanText)) !== null) {
799
+ while ((m = TAG_OR_COMMENT_RE.exec(tagText)) !== null) {
797
800
  if (m[0] === "<!--") {
798
- const next = scanText.slice(m.index + 4, m.index + 6);
801
+ const next = tagText.slice(m.index + 4, m.index + 6);
799
802
  if (cp.commentOpen) {
800
803
  if (next.startsWith(">") || next === "->") cp.commentOpen = false;
801
804
  else if (next === "!>" || next === "-!") poisonRawDivergence();
@@ -824,34 +827,56 @@ function processConfirmedLine(cp, ln, text) {
824
827
  applyTag(tag, closing);
825
828
  }
826
829
  if (cp.commentOpen && lastCommentOpenerIdx !== -1 && !inRawText) {
827
- if (scanText.slice(0, lastCommentOpenerIdx).trim() !== "") {
830
+ if (tagText.slice(0, lastCommentOpenerIdx).trim() !== "") {
828
831
  cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastCommentOpenerIdx);
829
832
  }
830
833
  }
831
- if (cp.pendingTruncatedTags.length > 0 && scanText.includes(">")) {
834
+ if (masked !== null && masked !== ln.text) {
835
+ const inRaw = (i) => rawSpans.some(([from, to]) => i >= from && i < to);
836
+ TAG_OR_COMMENT_RE.lastIndex = 0;
837
+ let mr;
838
+ while ((mr = TAG_OR_COMMENT_RE.exec(ln.text)) !== null) {
839
+ if (mr[0] === "<!--" || mr[0] === "-->" || mr[0] === "--!>") continue;
840
+ const startMasked = masked[mr.index] !== ln.text[mr.index];
841
+ const wholeVisible = masked.slice(mr.index, mr.index + mr[0].length) === mr[0];
842
+ if (startMasked || wholeVisible || inRaw(mr.index) || cp.commentOpen) continue;
843
+ const closing = mr[1] === "/";
844
+ const tag = mr[2].toLowerCase();
845
+ const selfClosing = mr[3] !== void 0 && /\/\s*$/.test(mr[3]);
846
+ if (VOID_TAGS.has(tag) || selfClosing) continue;
847
+ applyTag(tag, closing);
848
+ }
849
+ }
850
+ if (cp.pendingTruncatedTags.length > 0 && ln.text.includes(">")) {
832
851
  cp.pendingTruncatedTags = [];
833
852
  }
834
853
  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));
854
+ const lastLt = tagText.lastIndexOf("<");
855
+ if (lastLt !== -1 && !tagText.includes(">", lastLt)) {
856
+ const m2 = TRUNCATED_TAG_RE.exec(tagText.slice(lastLt));
838
857
  if (m2) {
839
858
  const closing = m2[1] === "/";
840
859
  const tag = m2[2].toLowerCase();
841
860
  if (!VOID_TAGS.has(tag)) {
842
861
  applyTag(tag, closing);
843
- if (!closing && !inRawText) cp.pendingTruncatedTags.push(tag);
862
+ const rawLastLt = ln.text.lastIndexOf("<");
863
+ const rawTruncated = rawLastLt !== -1 && !ln.text.includes(">", rawLastLt);
864
+ if (!closing && !inRawText && rawTruncated) cp.pendingTruncatedTags.push(tag);
844
865
  }
845
866
  }
846
867
  }
847
868
  }
848
869
  }
849
- if ((inRawText || rawFlowStart) && cp.openTotal === 0) {
850
- let masked2 = scanText;
870
+ const effectiveOpen = cp.openTotal - cp.pendingTruncatedTags.length;
871
+ if ((inRawText || rawFlowStart) && effectiveOpen <= 0) {
872
+ let masked2 = "";
873
+ let cursor = 0;
851
874
  for (const [from, to] of rawSpans) {
852
- masked2 = masked2.slice(0, from) + " ".repeat(to - from) + masked2.slice(to);
875
+ masked2 += scanText.slice(cursor, from);
876
+ cursor = to;
853
877
  }
854
- if (floatingResidue(masked2, commentOpenAtLineStart).trim() !== "") {
878
+ masked2 += scanText.slice(cursor);
879
+ if (floatingResidue(masked2, commentOpenAtLineStart).length > 0) {
855
880
  cp.htmlSeamPending = true;
856
881
  }
857
882
  }
@@ -914,7 +939,7 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
914
939
  if (last && last.kind === "refs") last.tokens.push(token);
915
940
  else events.push({ kind: "refs", tokens: [token] });
916
941
  };
917
- const visit6 = (node, nested) => {
942
+ const visit7 = (node, nested) => {
918
943
  const type = node.type;
919
944
  if (type === "definition") {
920
945
  const start = node.position?.start?.offset;
@@ -955,7 +980,7 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
955
980
  }
956
981
  const children = node.children;
957
982
  if (children) {
958
- for (const child of children) visit6(child, true);
983
+ for (const child of children) visit7(child, true);
959
984
  }
960
985
  };
961
986
  let lastStart = -1;
@@ -968,11 +993,11 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
968
993
  if (start === void 0) {
969
994
  if (resumeAt > 0) return collectPrefixInjection(mdast, content, boundary, null);
970
995
  cacheable = false;
971
- visit6(child, false);
996
+ visit7(child, false);
972
997
  continue;
973
998
  }
974
999
  if (start >= boundary || start < resumeAt) continue;
975
- visit6(child, false);
1000
+ visit7(child, false);
976
1001
  }
977
1002
  return { events, uninjectable, cacheable };
978
1003
  }
@@ -985,7 +1010,7 @@ function cloneEventsForAppend(events) {
985
1010
  var TERMINATOR_LABEL = "__aimd_injection_terminator__";
986
1011
  var INJECTION_TERMINATOR = `[${TERMINATOR_LABEL}]: __aimd_sentinel_link__`;
987
1012
  function tailMentionsTerminator(tailSource) {
988
- return (0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(tailSource).includes(`[${(0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(TERMINATOR_LABEL)}`);
1013
+ return (0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(tailSource).replace(/\[ /g, "[").includes(`[${(0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(TERMINATOR_LABEL)}`);
989
1014
  }
990
1015
  function buildInjectionPrefix(events) {
991
1016
  if (events.length === 0) return { text: "", segments: [] };
@@ -1065,12 +1090,25 @@ function spliceTrees(input) {
1065
1090
  if (isTrailingLiteralText(node2)) {
1066
1091
  const prev = i > 0 ? prevHast.children[i - 1] : void 0;
1067
1092
  if (!prev || prev.type !== "element" || prev.position === void 0) return null;
1093
+ if (!ownsTrailingLiteral(prev, node2, prefixMdast))
1094
+ return null;
1068
1095
  }
1069
1096
  cutRegion.push(node2);
1070
1097
  continue;
1071
1098
  }
1072
1099
  const node = prevHast.children[i];
1073
- if (i > 0 && attrs[i - 1] < boundary && prevHast.children[i - 1].type === "element" && isTrailingLiteralText(node)) {
1100
+ 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
1101
+ // raw literal can only trail an `html` mdast node. A position-less
1102
+ // text after a `<p>` is the NEXT block's remnant — a stray end tag
1103
+ // (`</t>\na`) parse5 dropped, whose text merged with the wrap
1104
+ // separator — owned by the tail, which re-parses it; freezing it
1105
+ // here duplicated it (v2.4.0 review P3). Falls through to the
1106
+ // remnant look-ahead below, which bails to a full parse. And the
1107
+ // literal must really be THAT block's trailing text: the block's raw
1108
+ // source ends with it. A dropped-tag block right after a frozen html
1109
+ // element (`</details>\n\n</t>\ntext`) puts its remnant in the same
1110
+ // position, and freezing it duplicated it (release soak of the fix).
1111
+ ownsTrailingLiteral(prevHast.children[i - 1], node, prefixMdast)) {
1074
1112
  cutRegion.push(node);
1075
1113
  break;
1076
1114
  }
@@ -1285,8 +1323,16 @@ function stripInjectedHast(tailMdast, tailHast, injectedLen, tailWrapVisible) {
1285
1323
  }
1286
1324
  function tailLeadingTextIsHoist(tailMdastChildren, tailHastChildren) {
1287
1325
  const firstText = tailHastChildren[0];
1288
- if (!firstText || !isSeparatorText(firstText)) return false;
1326
+ if (!firstText) return false;
1289
1327
  const firstVisible = tailMdastChildren.find((c) => !isWrapInvisible(c));
1328
+ if (!isSeparatorText(firstText)) {
1329
+ if (firstText.type === "text" && firstText.position === void 0 && firstVisible?.type === "html") {
1330
+ if (/^\s*<\/[A-Za-z][A-Za-z0-9-]*\s*>/.test(firstVisible.value)) return true;
1331
+ if (isCompleteRawConstruct(firstVisible.value)) return false;
1332
+ return null;
1333
+ }
1334
+ return false;
1335
+ }
1290
1336
  if (!firstVisible) return false;
1291
1337
  const firstContent = tailHastChildren.find((c) => !isSeparatorText(c));
1292
1338
  if (firstContent) {
@@ -1308,6 +1354,14 @@ function isCompleteRawConstruct(value) {
1308
1354
  function isSeparatorText(node) {
1309
1355
  return node.type === "text" && node.position === void 0 && node.value.trim() === "";
1310
1356
  }
1357
+ function ownsTrailingLiteral(el, literal, prefixMdast) {
1358
+ const start = el.position?.start?.offset;
1359
+ if (start === void 0) return false;
1360
+ const owner = prefixMdast.find((c) => c.type === "html" && c.position?.start?.offset === start);
1361
+ if (!owner || owner.type !== "html") return false;
1362
+ const text = literal.value.trim();
1363
+ return text !== "" && owner.value.trimEnd().endsWith(text);
1364
+ }
1311
1365
  function isTrailingLiteralText(node) {
1312
1366
  return node.type === "text" && node.position === void 0 && node.value.trim() !== "";
1313
1367
  }
@@ -1542,7 +1596,11 @@ function createDefLabelScanner(parse = collectDefLabels) {
1542
1596
 
1543
1597
  // src/components/extractDefBodiesFromHast.ts
1544
1598
  var import_unist_util_visit2 = require("unist-util-visit");
1599
+ var import_micromark_util_sanitize_uri = require("micromark-util-sanitize-uri");
1545
1600
  var FN_LI_ID_RE = /(?:^|-)user-content-fn-(.+)$/;
1601
+ function footnoteSafeId(identifier) {
1602
+ return (0, import_micromark_util_sanitize_uri.normalizeUri)(identifier.toLowerCase());
1603
+ }
1546
1604
  function sourceIdFromFootnoteLiId(idProp, clobberPrefix) {
1547
1605
  let raw = null;
1548
1606
  if (clobberPrefix !== void 0) {
@@ -1699,10 +1757,10 @@ function phantomSuffixCloser(content) {
1699
1757
  const confirmed = endsWithNewline ? content : content + "\n";
1700
1758
  const { checkpoint } = computeFreezeBoundary(confirmed, { defListEnabled: false, referenceTaint: false });
1701
1759
  if (checkpoint.phasePoisonedAt !== Infinity) return "";
1760
+ if (checkpoint.openIndent !== 0) return "";
1702
1761
  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)}`;
1762
+ if (checkpoint.inFence) return `${nl}${checkpoint.fenceChar.repeat(checkpoint.fenceLen)}`;
1763
+ if (checkpoint.inMath) return `${nl}${"$".repeat(checkpoint.mathFenceLen)}`;
1706
1764
  return "";
1707
1765
  }
1708
1766
 
@@ -1941,6 +1999,41 @@ function createRegistry(onEmpty) {
1941
1999
  var import_rehype_katex = __toESM(require("rehype-katex"), 1);
1942
2000
  var import_rehype_raw = __toESM(require("rehype-raw"), 1);
1943
2001
  var import_rehype_unwrap_images = __toESM(require("rehype-unwrap-images"), 1);
2002
+
2003
+ // src/components/rehypeUnwrapCrossChunkImages.ts
2004
+ var import_unist_util_visit4 = require("unist-util-visit");
2005
+ var IMAGE_TAGS = /* @__PURE__ */ new Set(["img", "cross-chunk-image"]);
2006
+ var LINK_TAGS = /* @__PURE__ */ new Set(["a", "cross-chunk-link"]);
2007
+ function applicable(node, inLink) {
2008
+ let image = 0 /* Unknown */;
2009
+ for (const child of node.children) {
2010
+ if (child.type === "text" && /^\s*$/.test(child.value)) continue;
2011
+ if (child.type === "element" && IMAGE_TAGS.has(child.tagName)) {
2012
+ image = 1 /* ContainsImage */;
2013
+ } else if (!inLink && child.type === "element" && LINK_TAGS.has(child.tagName)) {
2014
+ const inner = applicable(child, true);
2015
+ if (inner === 2 /* ContainsOther */) return 2 /* ContainsOther */;
2016
+ if (inner === 1 /* ContainsImage */) image = 1 /* ContainsImage */;
2017
+ } else {
2018
+ return 2 /* ContainsOther */;
2019
+ }
2020
+ }
2021
+ return image;
2022
+ }
2023
+ function rehypeUnwrapCrossChunkImages() {
2024
+ return function transform(tree) {
2025
+ (0, import_unist_util_visit4.visit)(tree, "element", (node, index, parent) => {
2026
+ if (node.tagName === "p" && parent && typeof index === "number" && applicable(node, false) === 1 /* ContainsImage */ && // Only paragraphs that actually hold a placeholder — plain <img>
2027
+ // paragraphs were already unwrapped by rehype-unwrap-images.
2028
+ JSON.stringify(node.children).includes('"cross-chunk-image"')) {
2029
+ parent.children.splice(index, 1, ...node.children);
2030
+ return [import_unist_util_visit4.SKIP, index];
2031
+ }
2032
+ });
2033
+ };
2034
+ }
2035
+
2036
+ // src/components/pluginChain.ts
1944
2037
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
1945
2038
  var import_remark_breaks = __toESM(require("remark-breaks"), 1);
1946
2039
  var import_remark_cjk_friendly = __toESM(require("remark-cjk-friendly"), 1);
@@ -1956,13 +2049,13 @@ var import_remark_pangu = __toESM(require("remark-pangu"), 1);
1956
2049
  var import_remark_remove_comments = __toESM(require("remark-remove-comments"), 1);
1957
2050
 
1958
2051
  // src/components/rehypeRebaseHashLinks.ts
1959
- var import_unist_util_visit4 = require("unist-util-visit");
2052
+ var import_unist_util_visit5 = require("unist-util-visit");
1960
2053
  var DEFAULT_PREFIX = "user-content-";
1961
2054
  var rehypeRebaseHashLinks = (options) => {
1962
2055
  const prefix = options?.prefix ?? DEFAULT_PREFIX;
1963
2056
  const hashPrefix = "#" + prefix;
1964
2057
  return (tree) => {
1965
- (0, import_unist_util_visit4.visit)(tree, "element", (node) => {
2058
+ (0, import_unist_util_visit5.visit)(tree, "element", (node) => {
1966
2059
  if (node.tagName !== "a") return;
1967
2060
  const href = node.properties?.href;
1968
2061
  if (typeof href !== "string" || !href.startsWith("#")) return;
@@ -1974,7 +2067,7 @@ var rehypeRebaseHashLinks = (options) => {
1974
2067
  var rehypeRebaseHashLinks_default = rehypeRebaseHashLinks;
1975
2068
 
1976
2069
  // src/components/rehypeFooterAdorn.ts
1977
- var import_unist_util_visit5 = require("unist-util-visit");
2070
+ var import_unist_util_visit6 = require("unist-util-visit");
1978
2071
  var FOOTNOTE_LABEL_ID_RE = /(?:^|-)footnote-label$/;
1979
2072
  function isFootnoteLabelH2(node) {
1980
2073
  if (node.type !== "element") return false;
@@ -1989,7 +2082,7 @@ function isHr(node) {
1989
2082
  }
1990
2083
  function rehypeFooterAdorn() {
1991
2084
  return (tree) => {
1992
- (0, import_unist_util_visit5.visit)(tree, "element", (n) => {
2085
+ (0, import_unist_util_visit6.visit)(tree, "element", (n) => {
1993
2086
  const el = n;
1994
2087
  if (el.tagName !== "section") return;
1995
2088
  if (!(el.properties && "dataFootnotes" in el.properties)) return;
@@ -2067,7 +2160,10 @@ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix) {
2067
2160
  // above.
2068
2161
  [rehypeRebaseHashLinks_default, { prefix: clobberPrefix }],
2069
2162
  import_rehype_katex.default,
2070
- import_rehype_unwrap_images.default
2163
+ import_rehype_unwrap_images.default,
2164
+ // Same unwrap for `<cross-chunk-image>` placeholders (coordinated mode);
2165
+ // no-op on standalone documents.
2166
+ rehypeUnwrapCrossChunkImages
2071
2167
  ];
2072
2168
  }
2073
2169
  function buildCoreRemarkRehypeOptions(enableDefinitionList) {
@@ -2186,6 +2282,9 @@ function buildCrossChunkHandlers() {
2186
2282
  };
2187
2283
  }
2188
2284
 
2285
+ // src/components/crossChunkUrlSanitize.ts
2286
+ var import_micromark_util_sanitize_uri2 = require("micromark-util-sanitize-uri");
2287
+
2189
2288
  // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_listCacheClear.js
2190
2289
  function listCacheClear() {
2191
2290
  this.__data__ = [];
@@ -3413,6 +3512,7 @@ function isProtocolAllowed(url, allowed) {
3413
3512
  return allowed.some((p) => p === protocol);
3414
3513
  }
3415
3514
  function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
3515
+ rawUrl = (0, import_micromark_util_sanitize_uri2.normalizeUri)(rawUrl);
3416
3516
  const transformed = urlTransform(rawUrl, key, fakeElement(tagName, key, rawUrl));
3417
3517
  if (transformed == null) return "";
3418
3518
  const stringUrl = String(transformed);
@@ -4275,6 +4375,7 @@ function createRemendPreprocessor(options) {
4275
4375
  extendSanitizeSchema,
4276
4376
  extractContributions,
4277
4377
  extractDefBodiesFromHast,
4378
+ footnoteSafeId,
4278
4379
  getEnginePluginInternals,
4279
4380
  hasLoneSurrogate,
4280
4381
  highlight,