stream-markdown-parser 1.0.0-rc.0 → 1.0.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.js CHANGED
@@ -1621,14 +1621,14 @@ const hasOwn$2 = Object.prototype.hasOwnProperty;
1621
1621
  function isGlobalMarkdownStateReason(value) {
1622
1622
  return value === "reference-definition" || value === "footnote-definition" || value === "abbreviation-definition";
1623
1623
  }
1624
- function isPlainObject(value) {
1624
+ function isPlainObject$1(value) {
1625
1625
  if (!value || typeof value !== "object") return false;
1626
1626
  const proto = Object.getPrototypeOf(value);
1627
1627
  return proto === Object.prototype || proto === null;
1628
1628
  }
1629
1629
  function cloneSnapshotValue(value) {
1630
1630
  if (Array.isArray(value)) return value.map((item) => cloneSnapshotValue(item));
1631
- if (isPlainObject(value)) {
1631
+ if (isPlainObject$1(value)) {
1632
1632
  const out = {};
1633
1633
  for (const key of Object.keys(value)) out[key] = cloneSnapshotValue(value[key]);
1634
1634
  return out;
@@ -1637,7 +1637,7 @@ function cloneSnapshotValue(value) {
1637
1637
  }
1638
1638
  function ownKeys(value) {
1639
1639
  if (Array.isArray(value)) return value.map((_, index) => String(index));
1640
- if (isPlainObject(value)) return Object.keys(value);
1640
+ if (isPlainObject$1(value)) return Object.keys(value);
1641
1641
  return [];
1642
1642
  }
1643
1643
  function snapshotValueEquals(left, right) {
@@ -1647,8 +1647,8 @@ function snapshotValueEquals(left, right) {
1647
1647
  for (let i = 0; i < left.length; i++) if (!snapshotValueEquals(left[i], right[i])) return false;
1648
1648
  return true;
1649
1649
  }
1650
- if (isPlainObject(left) || isPlainObject(right)) {
1651
- if (!isPlainObject(left) || !isPlainObject(right)) return false;
1650
+ if (isPlainObject$1(left) || isPlainObject$1(right)) {
1651
+ if (!isPlainObject$1(left) || !isPlainObject$1(right)) return false;
1652
1652
  const leftKeys = Object.keys(left);
1653
1653
  const rightKeys = Object.keys(right);
1654
1654
  if (leftKeys.length !== rightKeys.length) return false;
@@ -1659,7 +1659,7 @@ function snapshotValueEquals(left, right) {
1659
1659
  }
1660
1660
  function getSnapshotKeyValue(value, key) {
1661
1661
  if (Array.isArray(value)) return value[Number(key)];
1662
- if (isPlainObject(value)) return value[key];
1662
+ if (isPlainObject$1(value)) return value[key];
1663
1663
  }
1664
1664
  function restoreSnapshotValue(target, snapshot) {
1665
1665
  if (Array.isArray(target) && Array.isArray(snapshot)) {
@@ -1667,7 +1667,7 @@ function restoreSnapshotValue(target, snapshot) {
1667
1667
  for (let i = 0; i < snapshot.length; i++) target[i] = cloneSnapshotValue(snapshot[i]);
1668
1668
  return target;
1669
1669
  }
1670
- if (isPlainObject(target) && isPlainObject(snapshot)) {
1670
+ if (isPlainObject$1(target) && isPlainObject$1(snapshot)) {
1671
1671
  for (const key of Object.keys(target)) if (!hasOwn$2.call(snapshot, key)) delete target[key];
1672
1672
  for (const key of Object.keys(snapshot)) target[key] = cloneSnapshotValue(snapshot[key]);
1673
1673
  return target;
@@ -1677,7 +1677,7 @@ function restoreSnapshotValue(target, snapshot) {
1677
1677
  function resetOwnedSnapshotValue(env, key, entry) {
1678
1678
  const ownedKeys = entry.ownedKeys ?? [];
1679
1679
  const target = env[key];
1680
- if (isPlainObject(target) || Array.isArray(target)) {
1680
+ if (isPlainObject$1(target) || Array.isArray(target)) {
1681
1681
  const snapshotKeys = new Set(ownKeys(entry.value));
1682
1682
  for (const ownedKey of ownedKeys) if (entry.existed && snapshotKeys.has(ownedKey)) target[ownedKey] = cloneSnapshotValue(getSnapshotKeyValue(entry.value, ownedKey));
1683
1683
  else delete target[ownedKey];
@@ -1749,7 +1749,7 @@ function finalizeKnownGlobalMarkdownState(env) {
1749
1749
  if (!entry) continue;
1750
1750
  entry.ownedKeys = [];
1751
1751
  const after = env[key];
1752
- if (!isPlainObject(after) && !Array.isArray(after)) continue;
1752
+ if (!isPlainObject$1(after) && !Array.isArray(after)) continue;
1753
1753
  const beforeKeys = new Set(ownKeys(entry.existed ? entry.value : void 0));
1754
1754
  entry.ownedKeys = ownKeys(after).filter((name) => {
1755
1755
  if (!beforeKeys.has(name)) return true;
@@ -10507,6 +10507,134 @@ function tokenToRaw$1(token) {
10507
10507
  const shape = token;
10508
10508
  return String(shape.raw ?? shape.content ?? shape.markup ?? "");
10509
10509
  }
10510
+ function getMutableMeta(token) {
10511
+ const target = token;
10512
+ if (!target.meta) target.meta = {};
10513
+ return target.meta;
10514
+ }
10515
+ function setCustomHtmlSourceMeta(token, raw, inner) {
10516
+ const meta = getMutableMeta(token);
10517
+ meta.markstreamCustomHtmlRaw = raw;
10518
+ meta.markstreamCustomHtmlInner = inner;
10519
+ }
10520
+ function attachCustomHtmlSourceMeta(tokens, customTagSet) {
10521
+ if (!customTagSet.size) return;
10522
+ const customTagOpenRes = Array.from(customTagSet, (tag) => new RegExp(String.raw`<\s*${escapeTagForRegExp(tag)}(?=[\s>/])`, "i"));
10523
+ const stack = [];
10524
+ let needsTopLevelSeparator = false;
10525
+ const mayOpenCustomTag = (source) => {
10526
+ if (!source) return false;
10527
+ return customTagOpenRes.some((re) => re.test(source));
10528
+ };
10529
+ const appendToOpenFrames = (raw) => {
10530
+ if (!raw || !stack.length) return;
10531
+ for (const frame of stack) {
10532
+ frame.raw += raw;
10533
+ frame.inner += raw;
10534
+ }
10535
+ };
10536
+ const appendTopLevelSeparator = () => {
10537
+ if (!stack.length || !needsTopLevelSeparator) return;
10538
+ appendToOpenFrames("\n");
10539
+ needsTopLevelSeparator = false;
10540
+ };
10541
+ const appendSourceGap = (gap) => {
10542
+ appendToOpenFrames(gap);
10543
+ };
10544
+ const closeTopFrameWithRaw = (raw) => {
10545
+ for (let i = 0; i < stack.length; i++) {
10546
+ stack[i].raw += raw;
10547
+ if (i < stack.length - 1) stack[i].inner += raw;
10548
+ }
10549
+ const frame = stack.pop();
10550
+ setCustomHtmlSourceMeta(frame.token, frame.raw, frame.inner);
10551
+ };
10552
+ const getTopFrameClosePrefix = (raw) => {
10553
+ const tag = stack[stack.length - 1]?.tag;
10554
+ if (!tag) return null;
10555
+ const closePrefixRe = new RegExp(String.raw`^\s*<\s*\/\s*${escapeTagForRegExp(tag)}\s*>`, "i");
10556
+ return raw.match(closePrefixRe)?.[0] ?? null;
10557
+ };
10558
+ const startsWithTopFrameClose = (source) => {
10559
+ return !!getTopFrameClosePrefix(source);
10560
+ };
10561
+ const handleToken = (child, raw, knownTag) => {
10562
+ const tag = knownTag ?? (child.type === "html_inline" ? getHtmlInlineTagName(raw) : "");
10563
+ if (!(tag && customTagSet.has(tag))) {
10564
+ appendToOpenFrames(raw);
10565
+ return;
10566
+ }
10567
+ const closing = isHtmlInlineClosingTag(raw);
10568
+ const selfClosing = !closing && isSelfClosingHtmlInline(raw, tag);
10569
+ if (closing) {
10570
+ if (!stack.length || stack[stack.length - 1].tag !== tag) {
10571
+ appendToOpenFrames(raw);
10572
+ return;
10573
+ }
10574
+ closeTopFrameWithRaw(raw);
10575
+ return;
10576
+ }
10577
+ appendToOpenFrames(raw);
10578
+ if (selfClosing) {
10579
+ setCustomHtmlSourceMeta(child, raw, "");
10580
+ return;
10581
+ }
10582
+ stack.push({
10583
+ tag,
10584
+ token: child,
10585
+ raw,
10586
+ inner: ""
10587
+ });
10588
+ };
10589
+ for (const token of tokens) {
10590
+ if (token.type === "inline" && Array.isArray(token.children)) {
10591
+ const source = String(token.content ?? "");
10592
+ if (startsWithTopFrameClose(source)) needsTopLevelSeparator = false;
10593
+ else appendTopLevelSeparator();
10594
+ if (!stack.length && !mayOpenCustomTag(source)) {
10595
+ needsTopLevelSeparator = false;
10596
+ continue;
10597
+ }
10598
+ let cursor = 0;
10599
+ let sourceReliable = true;
10600
+ for (const child of token.children) {
10601
+ const childRaw = tokenToRaw$1(child);
10602
+ const tag = child.type === "html_inline" ? getHtmlInlineTagName(childRaw) : "";
10603
+ const isCustomTag = tag && customTagSet.has(tag);
10604
+ let raw = childRaw;
10605
+ if (sourceReliable && source && childRaw && (stack.length || isCustomTag)) {
10606
+ const index = source.indexOf(childRaw, cursor);
10607
+ if (index !== -1) {
10608
+ appendSourceGap(source.slice(cursor, index));
10609
+ raw = source.slice(index, index + childRaw.length);
10610
+ cursor = index + childRaw.length;
10611
+ } else {
10612
+ if (stack.length && !isCustomTag) continue;
10613
+ sourceReliable = false;
10614
+ }
10615
+ }
10616
+ handleToken(child, raw, tag);
10617
+ }
10618
+ if (sourceReliable && source && cursor < source.length && stack.length) appendSourceGap(source.slice(cursor));
10619
+ needsTopLevelSeparator = stack.length > 0;
10620
+ continue;
10621
+ }
10622
+ if (stack.length && typeof token.content === "string") {
10623
+ const raw = tokenToRaw$1(token);
10624
+ const closePrefix = token.type === "html_block" ? getTopFrameClosePrefix(raw) : null;
10625
+ if (closePrefix) {
10626
+ closeTopFrameWithRaw(`${needsTopLevelSeparator ? "\n" : ""}${closePrefix}`);
10627
+ needsTopLevelSeparator = stack.length > 0;
10628
+ continue;
10629
+ }
10630
+ if (!token.content) continue;
10631
+ appendTopLevelSeparator();
10632
+ appendToOpenFrames(token.content);
10633
+ needsTopLevelSeparator = true;
10634
+ }
10635
+ }
10636
+ for (const frame of stack) setCustomHtmlSourceMeta(frame.token, frame.raw, frame.inner);
10637
+ }
10510
10638
  function isNonElementHtmlBlock(content) {
10511
10639
  return /^\s*<\s*[!?]/.test(content);
10512
10640
  }
@@ -10693,25 +10821,41 @@ function fixStreamingHtmlInlineChildren(children, tagSet) {
10693
10821
  pendingBuffer: pendingAtEnd ?? void 0
10694
10822
  };
10695
10823
  }
10824
+ const BASE_AUTO_CLOSE_INLINE_TAGS = [
10825
+ "a",
10826
+ "span",
10827
+ "strong",
10828
+ "em",
10829
+ "b",
10830
+ "i",
10831
+ "u"
10832
+ ];
10696
10833
  function applyFixHtmlInlineTokens(md, options = {}) {
10697
- const commonHtmlTags = buildCommonHtmlTagSet(options.customHtmlTags);
10698
- const autoCloseInlineTagSet = new Set([
10699
- "a",
10700
- "span",
10701
- "strong",
10702
- "em",
10703
- "b",
10704
- "i",
10705
- "u"
10706
- ]);
10707
- const customTagSet = /* @__PURE__ */ new Set();
10834
+ const configuredCustomTagSet = /* @__PURE__ */ new Set();
10708
10835
  if (options.customHtmlTags?.length) for (const t of options.customHtmlTags) {
10709
10836
  const name = normalizeCustomHtmlTagName(t);
10710
10837
  if (!name) continue;
10711
- customTagSet.add(name);
10712
- autoCloseInlineTagSet.add(name);
10838
+ configuredCustomTagSet.add(name);
10713
10839
  }
10714
- const shouldMergeHtmlBlockTag = (tag) => customTagSet.has(tag) || !commonHtmlTags.has(tag) || BLOCK_LEVEL_HTML_TAGS.has(tag);
10840
+ const getRuleContext = (state) => {
10841
+ const s = state;
10842
+ const customTagSet = new Set(configuredCustomTagSet);
10843
+ const envTags = Array.isArray(s.env?.__markstreamCustomHtmlTags) ? s.env.__markstreamCustomHtmlTags : [];
10844
+ for (const t of envTags) {
10845
+ const name = normalizeCustomHtmlTagName(String(t ?? ""));
10846
+ if (name) customTagSet.add(name);
10847
+ }
10848
+ const commonHtmlTags = buildCommonHtmlTagSet(Array.from(customTagSet));
10849
+ const autoCloseInlineTagSet = new Set(BASE_AUTO_CLOSE_INLINE_TAGS);
10850
+ for (const tag of customTagSet) autoCloseInlineTagSet.add(tag);
10851
+ const shouldMergeHtmlBlockTag = (tag) => customTagSet.has(tag) || !commonHtmlTags.has(tag) || BLOCK_LEVEL_HTML_TAGS.has(tag);
10852
+ return {
10853
+ autoCloseInlineTagSet,
10854
+ commonHtmlTags,
10855
+ customTagSet,
10856
+ shouldMergeHtmlBlockTag
10857
+ };
10858
+ };
10715
10859
  const getHtmlBlockCarrierContent = (token) => {
10716
10860
  if (token.type === "html_block") return String(token.content ?? "");
10717
10861
  if (token.type !== "inline" || !Array.isArray(token.children) || token.children.length !== 1) return "";
@@ -10725,8 +10869,71 @@ function applyFixHtmlInlineTokens(md, options = {}) {
10725
10869
  token.raw = content;
10726
10870
  token.children = [];
10727
10871
  };
10872
+ const stripLeadingLineSeparators = (content) => content.replace(/^(?:\r?\n)+/, "");
10873
+ const isIndentedCodeTrailingContent = (content) => /^(?: {4}|\t)/.test(content);
10874
+ const normalizeIndentedCodeTrailingContent = (content) => content.replace(/^(?: {4}|\t)/gm, "");
10875
+ const createTrailingContentTokens = (content, textMode) => {
10876
+ const source = stripLeadingLineSeparators(content);
10877
+ if (!/\S/.test(source)) return [];
10878
+ if (isIndentedCodeTrailingContent(source)) return [{
10879
+ type: "code_block",
10880
+ content: normalizeIndentedCodeTrailingContent(source),
10881
+ raw: source
10882
+ }];
10883
+ const text$1 = source.replace(/^[\t ]+/, "");
10884
+ if (!text$1) return [];
10885
+ if (text$1.startsWith("<")) return [{
10886
+ type: "html_block",
10887
+ content: text$1
10888
+ }];
10889
+ const inlineToken = {
10890
+ type: "inline",
10891
+ tag: "",
10892
+ nesting: 0,
10893
+ content: text$1,
10894
+ children: [{
10895
+ type: "text",
10896
+ content: text$1,
10897
+ raw: text$1
10898
+ }]
10899
+ };
10900
+ if (textMode === "paragraph") return [
10901
+ {
10902
+ type: "paragraph_open",
10903
+ tag: "p",
10904
+ nesting: 1
10905
+ },
10906
+ inlineToken,
10907
+ {
10908
+ type: "paragraph_close",
10909
+ tag: "p",
10910
+ nesting: -1
10911
+ }
10912
+ ];
10913
+ if (textMode === "text") return [{
10914
+ type: "text",
10915
+ content: text$1,
10916
+ raw: text$1
10917
+ }];
10918
+ return [inlineToken];
10919
+ };
10920
+ const getTrailingContentTextMode = (tokens, index, fallback) => {
10921
+ return tokens[index - 1]?.type === "paragraph_open" && tokens[index + 1]?.type === "paragraph_close" ? "inline" : fallback;
10922
+ };
10923
+ const appendTrailingInlineContent = (token, content) => {
10924
+ const source = stripLeadingLineSeparators(content);
10925
+ if (!/\S/.test(source) || token.type !== "inline" || !Array.isArray(token.children)) return false;
10926
+ token.content = `${String(token.content ?? "")}${source}`;
10927
+ token.children.push({
10928
+ type: "text",
10929
+ content: source,
10930
+ raw: source
10931
+ });
10932
+ return true;
10933
+ };
10728
10934
  md.core.ruler.after("inline", "fix_html_inline_streaming", (state) => {
10729
10935
  const toks = state.tokens ?? [];
10936
+ const { commonHtmlTags, customTagSet } = getRuleContext(state);
10730
10937
  for (const t of toks) {
10731
10938
  const tok = t;
10732
10939
  if (tok.type !== "inline" || !Array.isArray(tok.children)) continue;
@@ -10752,9 +10959,11 @@ function applyFixHtmlInlineTokens(md, options = {}) {
10752
10959
  console.error("[applyFixHtmlInlineTokens] failed to fix streaming html inline", e);
10753
10960
  }
10754
10961
  }
10962
+ attachCustomHtmlSourceMeta(toks, customTagSet);
10755
10963
  });
10756
10964
  md.core.ruler.push("fix_html_inline_tokens", (state) => {
10757
10965
  const toks = state.tokens ?? [];
10966
+ const { autoCloseInlineTagSet, customTagSet, shouldMergeHtmlBlockTag } = getRuleContext(state);
10758
10967
  const tagStack = [];
10759
10968
  for (let i = 0; i < toks.length; i++) {
10760
10969
  const t = toks[i];
@@ -10777,21 +10986,10 @@ function applyFixHtmlInlineTokens(md, options = {}) {
10777
10986
  const after = mergedContent.slice(closeRange.end);
10778
10987
  openToken.content = before;
10779
10988
  openToken.loading = false;
10780
- const afterTrimmed = after.replace(/^\s+/, "");
10781
10989
  toks.splice(i, 1);
10782
10990
  tagStack.pop();
10783
- if (afterTrimmed) toks.splice(i, 0, afterTrimmed.startsWith("<") ? {
10784
- type: "html_block",
10785
- content: afterTrimmed
10786
- } : {
10787
- type: "inline",
10788
- content: afterTrimmed,
10789
- children: [{
10790
- type: "text",
10791
- content: afterTrimmed,
10792
- raw: afterTrimmed
10793
- }]
10794
- });
10991
+ const replacement = appendTrailingInlineContent(openToken, after) ? [] : createTrailingContentTokens(after, getTrailingContentTextMode(toks, i, "paragraph"));
10992
+ if (replacement.length) toks.splice(i, 0, ...replacement);
10795
10993
  i--;
10796
10994
  continue;
10797
10995
  }
@@ -10869,16 +11067,24 @@ function applyFixHtmlInlineTokens(md, options = {}) {
10869
11067
  if (stack.length > 0) {
10870
11068
  const top = stack[stack.length - 1];
10871
11069
  const openTok = toks[top.index];
10872
- if (tok.type === "html_block" && getCloseRe(top.tag).test(content)) {
10873
- openTok.content = `${String(openTok.content ?? "")}\n${content}`;
11070
+ const htmlBlockCloseMatch = tok.type === "html_block" ? getCloseRe(top.tag).exec(content) : null;
11071
+ if (htmlBlockCloseMatch) {
11072
+ const closeEnd = htmlBlockCloseMatch.index + htmlBlockCloseMatch[0].length;
11073
+ const closeContent = content.slice(0, closeEnd);
11074
+ const afterContent = content.slice(closeEnd);
11075
+ openTok.content = `${String(openTok.content ?? "")}\n${closeContent}`;
10874
11076
  if (Array.isArray(openTok.children)) openTok.children.push({
10875
11077
  type: "html_inline",
10876
11078
  content: `</${top.tag}>`,
10877
11079
  raw: `</${top.tag}>`
10878
11080
  });
10879
- toks.splice(i, 1);
10880
- i--;
10881
11081
  stack.pop();
11082
+ const replacement = appendTrailingInlineContent(openTok, afterContent) ? [] : createTrailingContentTokens(afterContent, getTrailingContentTextMode(toks, i, "paragraph"));
11083
+ if (replacement.length) toks.splice(i, 1, ...replacement);
11084
+ else {
11085
+ toks.splice(i, 1);
11086
+ i--;
11087
+ }
10882
11088
  continue;
10883
11089
  }
10884
11090
  if (tok.type !== "inline") continue;
@@ -10894,29 +11100,17 @@ function applyFixHtmlInlineTokens(md, options = {}) {
10894
11100
  const afterText = afterChildren.map((c) => String(c.content ?? c.raw ?? "")).join("");
10895
11101
  if (afterText.trim()) {
10896
11102
  const trimmed = afterText.replace(/^\s+/, "");
10897
- if (trimmed.startsWith("<")) toks.splice(i, 1, {
11103
+ if (appendTrailingInlineContent(openTok, afterText)) {
11104
+ toks.splice(i, 1);
11105
+ i--;
11106
+ } else if (trimmed.startsWith("<")) toks.splice(i, 1, {
10898
11107
  type: "html_block",
10899
11108
  content: trimmed
10900
11109
  });
10901
- else toks.splice(i, 1, {
10902
- type: "paragraph_open",
10903
- tag: "p",
10904
- nesting: 1
10905
- }, {
10906
- type: "inline",
10907
- tag: "",
10908
- nesting: 0,
10909
- content: afterText,
10910
- children: [{
10911
- type: "text",
10912
- content: afterText,
10913
- raw: afterText
10914
- }]
10915
- }, {
10916
- type: "paragraph_close",
10917
- tag: "p",
10918
- nesting: -1
10919
- });
11110
+ else {
11111
+ const replacement = createTrailingContentTokens(afterText, getTrailingContentTextMode(toks, i, "paragraph"));
11112
+ toks.splice(i, 1, ...replacement);
11113
+ }
10920
11114
  } else {
10921
11115
  toks.splice(i, 1);
10922
11116
  i--;
@@ -10989,15 +11183,8 @@ function applyFixHtmlInlineTokens(md, options = {}) {
10989
11183
  }];
10990
11184
  t.content = rawForNode;
10991
11185
  t.raw = rawForNode;
10992
- const afterTrimmed = (raw$2.slice(endTagIndex$1 + closeLen$1) || "").replace(/^\s+/, "");
10993
- if (afterTrimmed) toks.splice(i + 1, 0, afterTrimmed.startsWith("<") ? {
10994
- type: "html_block",
10995
- content: afterTrimmed
10996
- } : {
10997
- type: "text",
10998
- content: afterTrimmed,
10999
- raw: afterTrimmed
11000
- });
11186
+ const replacement = createTrailingContentTokens(raw$2.slice(endTagIndex$1 + closeLen$1) || "", "text");
11187
+ if (replacement.length) toks.splice(i + 1, 0, ...replacement);
11001
11188
  } else t.children = [{
11002
11189
  type: tag,
11003
11190
  content: "",
@@ -11194,7 +11381,7 @@ function applyFixIndentedCodeBlock(md, options = {}) {
11194
11381
 
11195
11382
  //#endregion
11196
11383
  //#region src/parser/linkifyHeuristics.ts
11197
- const FILENAMEISH_EXTENSION_RE = /\.([a-z0-9]{1,10})$/i;
11384
+ const FILENAMEISH_EXTENSION_RE = /\.([a-z0-9]{1,15})$/i;
11198
11385
  const FILENAMEISH_SEGMENT_RE = /[_()[\]{}<>]/u;
11199
11386
  const URL_PREFIX_HINT_RE = /^(?:https?:\/\/|ftp:\/\/|mailto:|www\.)/i;
11200
11387
  const URL_QUERY_OR_AUTH_HINT_RE = /[?#@]/u;
@@ -11202,7 +11389,11 @@ const PATH_SEPARATOR_RE = /[\\/]/u;
11202
11389
  const DOMAINISH_TEXT_RE = /^[\p{L}\p{N}./\\-]+$/u;
11203
11390
  const DOMAIN_LABEL_RE = /^[A-Za-z0-9-]{1,63}$/u;
11204
11391
  const PUNYCODE_TLD_RE = /^xn--[a-z0-9-]{2,59}$/i;
11205
- const NUMBERED_FILENAME_SEGMENT_RE = /(?:^|[._-])\d+|\d+[._-]/u;
11392
+ const MARKET_TICKER_SYMBOL_RE = /^(?:[A-Z]{1,6}|\d{1,8})$/u;
11393
+ const MARKET_TICKER_CONTEXT_SYMBOL_RE = /^(?=.{1,12}$)[A-Z0-9]+(?:[-.][A-Z0-9]+)*$/iu;
11394
+ const EXPLICIT_FILENAME_CONTEXT_RE = /文件名\s*[::]?|附件\s*[::]?|路径\s*[::]?|路徑\s*[::]?|文件列表\s*[::]?|文档列表\s*[::]?|文檔列表\s*[::]?|\bfile\s*names?\b\s*[::]?|\battachments?\b\s*[::]?|\bpaths?\b\s*[::]?|\bfile\s+lists?\b\s*[::]?|\bdocument\s+lists?\b\s*[::]?/iu;
11395
+ const FILENAME_CONTEXT_RE = /文件名\s*[::]?|文件\s*[::]?|附件\s*[::]?|档案\s*[::]?|檔案\s*[::]?|文档\s*[::]?|文檔\s*[::]?|资料\s*[::]?|資料\s*[::]?|路径\s*[::]?|路徑\s*[::]?|\bfile\s*name\b\s*[::]?|\battachments?\b\s*[::]?|\bfiles?\b\s*[::]?|\bdocuments?\b\s*[::]?|\bdocs?\b\s*[::]?|\bpaths?\b\s*[::]?/iu;
11396
+ const MARKET_TICKER_CONTEXT_RE = /股票代码|股票代碼|证券代码|證券代碼|(?:代码|代碼|交易所|后缀|後綴|市场|市場)(?=$|[\s::/|,,、()()])|\btickers?\b|\bsymbols?\b|\bexchanges?\b/iu;
11206
11397
  const AMBIGUOUS_BARE_DOMAIN_EXTENSIONS = new Set([
11207
11398
  "ai",
11208
11399
  "md",
@@ -11211,6 +11402,57 @@ const AMBIGUOUS_BARE_DOMAIN_EXTENSIONS = new Set([
11211
11402
  "sh",
11212
11403
  "zip"
11213
11404
  ]);
11405
+ const MARKET_TICKER_SUFFIXES = new Set([
11406
+ "as",
11407
+ "bj",
11408
+ "de",
11409
+ "hk",
11410
+ "l",
11411
+ "ln",
11412
+ "ny",
11413
+ "pa",
11414
+ "sh",
11415
+ "ss",
11416
+ "sz",
11417
+ "t",
11418
+ "us"
11419
+ ]);
11420
+ const MARKET_TICKER_CONTEXT_SUFFIXES = new Set([
11421
+ ...MARKET_TICKER_SUFFIXES,
11422
+ "at",
11423
+ "ax",
11424
+ "cn",
11425
+ "co",
11426
+ "it",
11427
+ "jp",
11428
+ "ks",
11429
+ "mc",
11430
+ "mx",
11431
+ "nz",
11432
+ "pl",
11433
+ "sa",
11434
+ "si",
11435
+ "to",
11436
+ "tw"
11437
+ ]);
11438
+ const EXPLICIT_FILENAME_CONTEXT_ONLY_EXTENSIONS = new Set([
11439
+ "com",
11440
+ "dev",
11441
+ "io",
11442
+ "page",
11443
+ "site"
11444
+ ]);
11445
+ const FILENAME_CONTEXT_ONLY_EXTENSIONS = new Set([
11446
+ "app",
11447
+ "apk",
11448
+ "dmg",
11449
+ "exe",
11450
+ "ipa",
11451
+ "lock",
11452
+ "log",
11453
+ "markdown",
11454
+ "webmanifest"
11455
+ ]);
11214
11456
  const FILENAMEISH_LINK_EXTENSIONS = new Set([
11215
11457
  "7z",
11216
11458
  "ai",
@@ -11278,6 +11520,65 @@ const FILENAMEISH_LINK_EXTENSIONS = new Set([
11278
11520
  "zip",
11279
11521
  "zsh"
11280
11522
  ]);
11523
+ function hasLinkifyDemotionContext(context) {
11524
+ return context?.filename === true || context?.explicitFilename === true || context?.marketTicker === true;
11525
+ }
11526
+ function mergeLinkifyDemotionContext(left, right) {
11527
+ const merged = {
11528
+ filename: left?.filename || right?.filename,
11529
+ explicitFilename: left?.explicitFilename || right?.explicitFilename,
11530
+ marketTicker: left?.marketTicker || right?.marketTicker
11531
+ };
11532
+ return hasLinkifyDemotionContext(merged) ? merged : void 0;
11533
+ }
11534
+ function withLinkifyDemotionContext(options, context) {
11535
+ if (!hasLinkifyDemotionContext(context)) return options;
11536
+ const inheritedContext = options?.__linkifyDemotionContext;
11537
+ return {
11538
+ ...options,
11539
+ __linkifyDemotionContext: {
11540
+ filename: inheritedContext?.filename || context?.filename,
11541
+ explicitFilename: inheritedContext?.explicitFilename || context?.explicitFilename,
11542
+ marketTicker: inheritedContext?.marketTicker || context?.marketTicker
11543
+ }
11544
+ };
11545
+ }
11546
+ function inferNextBlockLinkifyContext(raw) {
11547
+ const context = inferLinkifyDemotionContext(raw);
11548
+ return hasLinkifyDemotionContext(context) ? context : void 0;
11549
+ }
11550
+ function normalizeStandaloneContinuationText(text$1) {
11551
+ return text$1.replace(/^[\s>*_`[\]((【《"'“‘]+/u, "").replace(/[\s<*_`\]))】》"'.。;;,,、::!?!?]+$/u, "");
11552
+ }
11553
+ function inferContinuationLinkifyContext(raw, inherited) {
11554
+ if (!hasLinkifyDemotionContext(inherited)) return void 0;
11555
+ const parts = String(raw ?? "").trim().split(/\s+/u).map(normalizeStandaloneContinuationText).filter(Boolean);
11556
+ if (parts.length === 0) return void 0;
11557
+ const continuation = {};
11558
+ if (inherited?.filename && parts.every((part) => shouldDemoteFilenameLikeLinkify(part, {
11559
+ filename: true,
11560
+ explicitFilename: inherited.explicitFilename
11561
+ }))) continuation.filename = true;
11562
+ if (inherited?.explicitFilename && continuation.filename) continuation.explicitFilename = true;
11563
+ if (inherited?.marketTicker && parts.every((part) => shouldDemoteFilenameLikeLinkify(part, { marketTicker: true }))) continuation.marketTicker = true;
11564
+ return hasLinkifyDemotionContext(continuation) ? continuation : void 0;
11565
+ }
11566
+ function createLinkifyDemotionContextTracker(options, sticky = false) {
11567
+ let context;
11568
+ return {
11569
+ options(raw) {
11570
+ if (sticky || raw == null) return withLinkifyDemotionContext(options, context);
11571
+ return withLinkifyDemotionContext(options, mergeLinkifyDemotionContext(inferNextBlockLinkifyContext(raw), inferContinuationLinkifyContext(raw, context)));
11572
+ },
11573
+ remember(raw) {
11574
+ const nextContext = inferNextBlockLinkifyContext(raw);
11575
+ context = sticky ? mergeLinkifyDemotionContext(context, nextContext) : mergeLinkifyDemotionContext(nextContext, inferContinuationLinkifyContext(raw, context));
11576
+ },
11577
+ reset() {
11578
+ context = void 0;
11579
+ }
11580
+ };
11581
+ }
11281
11582
  function isValidDomainLabel(label) {
11282
11583
  return DOMAIN_LABEL_RE.test(label) && !label.startsWith("-") && !label.endsWith("-");
11283
11584
  }
@@ -11288,6 +11589,27 @@ function isPlausibleBareDomain(text$1) {
11288
11589
  if (!(isValidDomainLabel(tld) || PUNYCODE_TLD_RE.test(tld))) return false;
11289
11590
  return labels.every(isValidDomainLabel);
11290
11591
  }
11592
+ function hasNonAsciiText(input) {
11593
+ return Array.from(input).some((char) => char.charCodeAt(0) > 127);
11594
+ }
11595
+ function getHrefAuthority(href) {
11596
+ return href.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").split(/[/?#]/, 1)[0] ?? "";
11597
+ }
11598
+ function hasPunycodeAuthorityLabel(authority) {
11599
+ return authority.split(".").some((label) => label.toLowerCase().startsWith("xn--"));
11600
+ }
11601
+ function isDecodedFromRawPunycode(linkText, href, raw) {
11602
+ const authority = getHrefAuthority(href);
11603
+ return hasNonAsciiText(linkText) && hasPunycodeAuthorityLabel(authority) && String(raw ?? "").toLowerCase().includes(authority.toLowerCase());
11604
+ }
11605
+ function inferLinkifyDemotionContext(contextText) {
11606
+ const text$1 = String(contextText ?? "");
11607
+ return {
11608
+ explicitFilename: EXPLICIT_FILENAME_CONTEXT_RE.test(text$1),
11609
+ filename: FILENAME_CONTEXT_RE.test(text$1),
11610
+ marketTicker: MARKET_TICKER_CONTEXT_RE.test(text$1)
11611
+ };
11612
+ }
11291
11613
  function hasDomainAuthorityPrefix(text$1) {
11292
11614
  return isPlausibleBareDomain(text$1.split(/[\\/]/)[0] ?? "");
11293
11615
  }
@@ -11300,16 +11622,28 @@ function hasStrongFilenameSignals(linkText) {
11300
11622
  if (!DOMAINISH_TEXT_RE.test(linkText)) return true;
11301
11623
  if (PATH_SEPARATOR_RE.test(linkText)) return !hasDomainAuthorityPrefix(linkText);
11302
11624
  const extensionless = linkText.replace(FILENAMEISH_EXTENSION_RE, "");
11303
- if (Array.from(extensionless).some((char) => char.charCodeAt(0) > 127) && NUMBERED_FILENAME_SEGMENT_RE.test(extensionless)) return true;
11625
+ if (hasNonAsciiText(extensionless)) return true;
11304
11626
  return extensionless.split(".").filter(Boolean).some(isUppercaseFilenameSegment);
11305
11627
  }
11306
- function shouldDemoteFilenameLikeLinkify(linkText) {
11628
+ function isMarketTickerLikeText(linkText, extension, hasMarketTickerContext) {
11629
+ if (!(hasMarketTickerContext ? MARKET_TICKER_CONTEXT_SUFFIXES : MARKET_TICKER_SUFFIXES).has(extension)) return false;
11630
+ const symbol = linkText.slice(0, -(extension.length + 1));
11631
+ if (symbol === "") return linkText.startsWith(".");
11632
+ return (hasMarketTickerContext ? MARKET_TICKER_CONTEXT_SYMBOL_RE : MARKET_TICKER_SYMBOL_RE).test(symbol);
11633
+ }
11634
+ function shouldDemoteFilenameLikeLinkify(linkText, context = {}) {
11307
11635
  if (!linkText || URL_PREFIX_HINT_RE.test(linkText) || URL_QUERY_OR_AUTH_HINT_RE.test(linkText)) return false;
11308
11636
  const extensionMatch = linkText.match(FILENAMEISH_EXTENSION_RE);
11309
11637
  if (!extensionMatch) return false;
11310
11638
  const extension = String(extensionMatch[1] ?? "").toLowerCase();
11311
- if (!FILENAMEISH_LINK_EXTENSIONS.has(extension)) return false;
11639
+ if (isMarketTickerLikeText(linkText, extension, context.marketTicker === true)) return true;
11640
+ if (!FILENAMEISH_LINK_EXTENSIONS.has(extension)) {
11641
+ if (context.explicitFilename && EXPLICIT_FILENAME_CONTEXT_ONLY_EXTENSIONS.has(extension)) return true;
11642
+ if (context.filename && FILENAME_CONTEXT_ONLY_EXTENSIONS.has(extension)) return true;
11643
+ return false;
11644
+ }
11312
11645
  if (!AMBIGUOUS_BARE_DOMAIN_EXTENSIONS.has(extension)) return true;
11646
+ if (context.filename) return true;
11313
11647
  return hasStrongFilenameSignals(linkText);
11314
11648
  }
11315
11649
 
@@ -11439,16 +11773,17 @@ function applyFixLinkTokens(md) {
11439
11773
  for (let i = 0; i < toks.length; i++) {
11440
11774
  const t = toks[i];
11441
11775
  if (t && t.type === "inline" && Array.isArray(t.children)) try {
11442
- t.children = fixLinkToken(t.children);
11776
+ t.children = fixLinkToken(t.children, typeof t.content === "string" ? t.content : void 0);
11443
11777
  } catch (e) {
11444
11778
  console.error("[applyFixLinkTokens] failed to fix inline children", e);
11445
11779
  }
11446
11780
  }
11447
11781
  });
11448
11782
  }
11449
- function fixLinkToken(tokens) {
11783
+ function fixLinkToken(tokens, raw) {
11450
11784
  if (tokens.length < 3) return tokens;
11451
11785
  if (tokens.some((token) => token.type === "code_inline")) return tokens;
11786
+ const linkifyDemotionContext = inferLinkifyDemotionContext(raw);
11452
11787
  for (let i = 0; i <= tokens.length - 1; i++) {
11453
11788
  if (i < 0) i = 0;
11454
11789
  const curToken = tokens[i];
@@ -11461,11 +11796,11 @@ function fixLinkToken(tokens) {
11461
11796
  }
11462
11797
  if (closeIdx !== -1) {
11463
11798
  const linkText = collectLinkifyText(tokens, i, closeIdx);
11464
- if (curToken.markup === "linkify" && linkText && shouldDemoteFilenameLikeLinkify(linkText)) {
11799
+ const href = getHrefFromLinkOpen(curToken);
11800
+ if (curToken.markup === "linkify" && linkText && !isDecodedFromRawPunycode(linkText, href, raw) && shouldDemoteFilenameLikeLinkify(linkText, linkifyDemotionContext)) {
11465
11801
  tokens.splice(i, closeIdx - i + 1, textToken(linkText));
11466
11802
  continue;
11467
11803
  }
11468
- const href = getHrefFromLinkOpen(curToken);
11469
11804
  const hrefStop = firstIndexOfAny(href, LINKIFY_HARD_STOP_CHARS);
11470
11805
  for (let j = i + 1; j < closeIdx; j++) {
11471
11806
  const t = tokens[j];
@@ -13111,6 +13446,7 @@ function applyMath(md, mathOpts) {
13111
13446
  let content = "";
13112
13447
  let found = false;
13113
13448
  const firstLineContent = lineText === openDelim ? "" : lineText.slice(openDelim.length);
13449
+ const fallbackPlainBracketClose = !strict && openDelim === "\\[" ? "]" : "";
13114
13450
  if (firstLineContent.includes(closeDelim)) {
13115
13451
  const endIndex = firstLineContent.indexOf(closeDelim);
13116
13452
  content = firstLineContent.slice(0, endIndex);
@@ -13122,6 +13458,11 @@ function applyMath(md, mathOpts) {
13122
13458
  const lineStart = s.bMarks[nextLine] + s.tShift[nextLine];
13123
13459
  const lineEnd = s.eMarks[nextLine];
13124
13460
  const currentLine = s.src.slice(lineStart, lineEnd);
13461
+ if (fallbackPlainBracketClose && currentLine.trim() === fallbackPlainBracketClose) {
13462
+ closeDelim = fallbackPlainBracketClose;
13463
+ found = true;
13464
+ break;
13465
+ }
13125
13466
  if (currentLine.trim() === closeDelim) {
13126
13467
  found = true;
13127
13468
  break;
@@ -13288,6 +13629,11 @@ const DIFF_HEADER_PREFIXES = [
13288
13629
  "@@ "
13289
13630
  ];
13290
13631
  const NEWLINE_RE = /\r?\n/;
13632
+ function isPotentialDiffMetadataTail(line) {
13633
+ const value = String(line ?? "");
13634
+ if (!value) return false;
13635
+ return DIFF_HEADER_PREFIXES.some((prefix) => prefix.startsWith(value) || value.startsWith(prefix));
13636
+ }
13291
13637
  function flushPendingDiffHunk(orig, updated, pendingOrig, pendingUpdated) {
13292
13638
  if (pendingOrig.length > 0) orig.push(...pendingOrig);
13293
13639
  if (pendingUpdated.length > 0) updated.push(...pendingUpdated);
@@ -13300,7 +13646,7 @@ function splitUnifiedDiff(content, closed) {
13300
13646
  const pendingOrig = [];
13301
13647
  const pendingUpdated = [];
13302
13648
  const lines = content.split(NEWLINE_RE);
13303
- const stableLineCount = Math.max(0, lines.length - 1);
13649
+ const endsWithNewline = /\r?\n$/.test(content);
13304
13650
  const hasUnifiedDiffHeaders = lines.some((line) => line.startsWith("diff ") || line.startsWith("--- ") || line.startsWith("+++ ") || line.startsWith("@@ "));
13305
13651
  const processLine = (rawLine) => {
13306
13652
  const line = rawLine;
@@ -13318,18 +13664,25 @@ function splitUnifiedDiff(content, closed) {
13318
13664
  updated.push(contextLine);
13319
13665
  }
13320
13666
  };
13321
- for (let index = 0; index < stableLineCount; index++) processLine(lines[index] ?? "");
13322
- if (closed && stableLineCount < lines.length) processLine(lines[lines.length - 1] ?? "");
13323
- if (closed || pendingOrig.length > 0 && pendingUpdated.length > 0) flushPendingDiffHunk(orig, updated, pendingOrig, pendingUpdated);
13667
+ const lineCountToProcess = endsWithNewline ? Math.max(0, lines.length - 1) : lines.length;
13668
+ for (let index = 0; index < lineCountToProcess; index++) {
13669
+ const line = lines[index] ?? "";
13670
+ if (!closed && !endsWithNewline && index === lineCountToProcess - 1 && isPotentialDiffMetadataTail(line)) continue;
13671
+ processLine(line);
13672
+ }
13673
+ if (closed || pendingOrig.length > 0 || pendingUpdated.length > 0) flushPendingDiffHunk(orig, updated, pendingOrig, pendingUpdated);
13674
+ const originalCode = orig.join("\n");
13675
+ const updatedCode = updated.join("\n");
13324
13676
  return {
13325
- original: orig.join("\n"),
13326
- updated: updated.join("\n")
13677
+ original: closed && endsWithNewline && originalCode ? `${originalCode}\n` : originalCode,
13678
+ updated: closed && endsWithNewline && updatedCode ? `${updatedCode}\n` : updatedCode
13327
13679
  };
13328
13680
  }
13329
13681
  function parseFenceToken(token) {
13330
13682
  const hasMap = Array.isArray(token.map) && token.map.length === 2;
13331
13683
  const tokenMeta = token.meta ?? {};
13332
- const closed = typeof tokenMeta.closed === "boolean" ? tokenMeta.closed : void 0;
13684
+ const metaClosed = typeof tokenMeta.closed === "boolean" ? tokenMeta.closed : void 0;
13685
+ const closed = metaClosed === true || metaClosed !== false && hasMap;
13333
13686
  const info = String(token.info ?? "");
13334
13687
  const diff = info.startsWith("diff");
13335
13688
  const language = diff ? (() => {
@@ -13347,7 +13700,7 @@ function parseFenceToken(token) {
13347
13700
  code: String(updated ?? ""),
13348
13701
  raw: String(content ?? ""),
13349
13702
  diff,
13350
- loading: closed === true ? false : closed === false ? true : !hasMap,
13703
+ loading: metaClosed === true ? false : metaClosed === false ? true : !hasMap,
13351
13704
  originalCode: original,
13352
13705
  updatedCode: updated
13353
13706
  };
@@ -13358,7 +13711,7 @@ function parseFenceToken(token) {
13358
13711
  code: String(content ?? ""),
13359
13712
  raw: String(content ?? ""),
13360
13713
  diff,
13361
- loading: closed === true ? false : closed === false ? true : !hasMap
13714
+ loading: metaClosed === true ? false : metaClosed === false ? true : !hasMap
13362
13715
  };
13363
13716
  }
13364
13717
 
@@ -13448,6 +13801,15 @@ function tokenToRaw(token) {
13448
13801
  const raw = shape.raw ?? shape.content ?? shape.markup ?? "";
13449
13802
  return String(raw ?? "");
13450
13803
  }
13804
+ function getCustomHtmlSourceMeta(token) {
13805
+ const meta = token.meta;
13806
+ const raw = meta?.markstreamCustomHtmlRaw;
13807
+ const inner = meta?.markstreamCustomHtmlInner;
13808
+ return typeof raw === "string" && typeof inner === "string" ? {
13809
+ raw,
13810
+ inner
13811
+ } : null;
13812
+ }
13451
13813
  function getAttrValue$1(attrs, name) {
13452
13814
  const lowerName = name.toLowerCase();
13453
13815
  for (let i = attrs.length - 1; i >= 0; i--) {
@@ -13632,16 +13994,19 @@ function parseHtmlInlineCodeToken(token, tokens, i, parseInlineTokens$1, raw, pP
13632
13994
  const attrValue = match[2] || match[3] || match[4] || "";
13633
13995
  attrs.push([attrName, attrValue]);
13634
13996
  }
13635
- if (customTagSet?.has(tag)) return [{
13636
- type: tag,
13637
- tag,
13638
- attrs,
13639
- content: fragment.innerTokens.length ? stringifyTokens(fragment.innerTokens) : "",
13640
- children: fragment.innerTokens.length ? parseInlineTokens$1(fragment.innerTokens, raw, pPreToken, options) : [],
13641
- raw: content,
13642
- loading: token.loading || loading,
13643
- autoClosed
13644
- }, fragment.nextIndex];
13997
+ if (customTagSet?.has(tag)) {
13998
+ const sourceMeta = getCustomHtmlSourceMeta(token);
13999
+ return [{
14000
+ type: tag,
14001
+ tag,
14002
+ attrs,
14003
+ content: sourceMeta ? sourceMeta.inner : fragment.innerTokens.length ? stringifyTokens(fragment.innerTokens) : "",
14004
+ children: fragment.innerTokens.length ? parseInlineTokens$1(fragment.innerTokens, raw, pPreToken, options) : [],
14005
+ raw: sourceMeta?.raw ?? content,
14006
+ loading: token.loading || loading,
14007
+ autoClosed
14008
+ }, fragment.nextIndex];
14009
+ }
13645
14010
  return [{
13646
14011
  type: "html_inline",
13647
14012
  tag,
@@ -14189,6 +14554,17 @@ function recoverTrailingMarkdownLinkLabel(raw, href) {
14189
14554
  }
14190
14555
  function parseInlineTokens(tokens, raw, pPreToken, options) {
14191
14556
  if (!tokens || tokens.length === 0) return [];
14557
+ const inheritedContext = options?.__linkifyDemotionContext;
14558
+ const inferredContext = inferLinkifyDemotionContext(raw);
14559
+ const linkifyDemotionContext = {
14560
+ filename: inheritedContext?.filename || inferredContext.filename,
14561
+ explicitFilename: inheritedContext?.explicitFilename || inferredContext.explicitFilename,
14562
+ marketTicker: inheritedContext?.marketTicker || inferredContext.marketTicker
14563
+ };
14564
+ if (linkifyDemotionContext.filename || linkifyDemotionContext.explicitFilename || linkifyDemotionContext.marketTicker) options = {
14565
+ ...options,
14566
+ __linkifyDemotionContext: linkifyDemotionContext
14567
+ };
14192
14568
  const internalOptions = options;
14193
14569
  const result = [];
14194
14570
  let currentTextNode = null;
@@ -14981,8 +15357,9 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
14981
15357
  resetCurrentTextNode();
14982
15358
  const { node, nextIndex } = parseLinkToken(tokens, i, options);
14983
15359
  i = nextIndex;
14984
- if (token.markup === "linkify" && shouldDemoteFilenameLikeLinkify(node.text || node.href || "")) {
14985
- pushText(node.text || node.href || "", node.text || node.href || "");
15360
+ const linkText = node.text || node.href || "";
15361
+ if (token.markup === "linkify" && !isDecodedFromRawPunycode(linkText, node.href, raw) && shouldDemoteFilenameLikeLinkify(linkText, internalOptions?.__linkifyDemotionContext)) {
15362
+ pushText(linkText, linkText);
14986
15363
  return;
14987
15364
  }
14988
15365
  const hasSingleTextChild = node.children.length === 1 && node.children[0]?.type === "text";
@@ -15454,6 +15831,7 @@ function stripLeakedOrderedListMarkerSuffix(token) {
15454
15831
  function parseList(tokens, index, options) {
15455
15832
  const token = tokens[index];
15456
15833
  const listItems = [];
15834
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15457
15835
  let j = index + 1;
15458
15836
  while (j < tokens.length && tokens[j].type !== "bullet_list_close" && tokens[j].type !== "ordered_list_close") if (tokens[j].type === "list_item_open") {
15459
15837
  const itemChildren = [];
@@ -15465,22 +15843,26 @@ function parseList(tokens, index, options) {
15465
15843
  trimInlineTokenTail(contentToken);
15466
15844
  itemChildren.push({
15467
15845
  type: "paragraph",
15468
- children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), preToken, options),
15846
+ children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), preToken, linkifyContext.options()),
15469
15847
  raw: String(contentToken.content ?? "")
15470
15848
  });
15849
+ linkifyContext.remember(String(contentToken.content ?? ""));
15471
15850
  k += 3;
15472
15851
  } else if (tokens[k].type === "blockquote_open") {
15473
- const [blockquoteNode, newIndex] = parseBlockquote(tokens, k, options);
15852
+ const [blockquoteNode, newIndex] = parseBlockquote(tokens, k, linkifyContext.options());
15474
15853
  itemChildren.push(blockquoteNode);
15854
+ linkifyContext.remember(blockquoteNode.raw);
15475
15855
  k = newIndex;
15476
15856
  } else if (tokens[k].type === "bullet_list_open" || tokens[k].type === "ordered_list_open") {
15477
- const [nestedListNode, newIndex] = parseList(tokens, k, options);
15857
+ const [nestedListNode, newIndex] = parseList(tokens, k, linkifyContext.options());
15478
15858
  itemChildren.push(nestedListNode);
15859
+ linkifyContext.remember(nestedListNode.raw);
15479
15860
  k = newIndex;
15480
15861
  } else {
15481
- const handled = parseCommonBlockToken(tokens, k, options, containerTokenHandlers);
15862
+ const handled = parseCommonBlockToken(tokens, k, linkifyContext.options(), containerTokenHandlers);
15482
15863
  if (handled) {
15483
15864
  itemChildren.push(handled[0]);
15865
+ linkifyContext.remember(handled[0].raw);
15484
15866
  k = handled[1];
15485
15867
  } else k += 1;
15486
15868
  }
@@ -15514,27 +15896,35 @@ function parseAdmonition(tokens, index, match, options) {
15514
15896
  const kind = String(match[1] ?? "note");
15515
15897
  const title = String(match[2] ?? kind.charAt(0).toUpperCase() + kind.slice(1));
15516
15898
  const admonitionChildren = [];
15899
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15517
15900
  let j = index + 1;
15518
15901
  while (j < tokens.length && tokens[j].type !== "container_close") if (tokens[j].type === "paragraph_open") {
15519
15902
  const contentToken = tokens[j + 1];
15520
- if (contentToken) admonitionChildren.push({
15521
- type: "paragraph",
15522
- children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), void 0, options),
15523
- raw: String(contentToken.content ?? "")
15524
- });
15903
+ if (contentToken) {
15904
+ const paragraphNode = {
15905
+ type: "paragraph",
15906
+ children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), void 0, linkifyContext.options()),
15907
+ raw: String(contentToken.content ?? "")
15908
+ };
15909
+ admonitionChildren.push(paragraphNode);
15910
+ linkifyContext.remember(paragraphNode.raw);
15911
+ }
15525
15912
  j += 3;
15526
15913
  } else if (tokens[j].type === "bullet_list_open" || tokens[j].type === "ordered_list_open") {
15527
- const [listNode, newIndex] = parseList(tokens, j, options);
15914
+ const [listNode, newIndex] = parseList(tokens, j, linkifyContext.options());
15528
15915
  admonitionChildren.push(listNode);
15916
+ linkifyContext.remember(listNode.raw);
15529
15917
  j = newIndex;
15530
15918
  } else if (tokens[j].type === "blockquote_open") {
15531
- const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, options);
15919
+ const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, linkifyContext.options());
15532
15920
  admonitionChildren.push(blockquoteNode);
15921
+ linkifyContext.remember(blockquoteNode.raw);
15533
15922
  j = newIndex;
15534
15923
  } else {
15535
- const handled = parseBasicBlockToken(tokens, j, options);
15924
+ const handled = parseBasicBlockToken(tokens, j, linkifyContext.options());
15536
15925
  if (handled) {
15537
15926
  admonitionChildren.push(handled[0]);
15927
+ linkifyContext.remember(handled[0].raw);
15538
15928
  j = handled[1];
15539
15929
  } else j++;
15540
15930
  }
@@ -15594,6 +15984,7 @@ function parseContainer(tokens, index, options) {
15594
15984
  }
15595
15985
  if (!title) title = kind.charAt(0).toUpperCase() + kind.slice(1);
15596
15986
  const children = [];
15987
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15597
15988
  let j = index + 1;
15598
15989
  const closeType = /* @__PURE__ */ new RegExp(`^container_${kind}_close$`);
15599
15990
  while (j < tokens.length && tokens[j].type !== "container_close" && !closeType.test(tokens[j].type)) if (tokens[j].type === "paragraph_open") {
@@ -15608,26 +15999,30 @@ function parseContainer(tokens, index, options) {
15608
15999
  break;
15609
16000
  }
15610
16001
  }
15611
- const _children = i !== -1 ? childrenArr.slice(0, i) : childrenArr;
15612
- children.push({
16002
+ const paragraphNode = {
15613
16003
  type: "paragraph",
15614
- children: parseInlineTokens(_children || [], void 0, void 0, options),
16004
+ children: parseInlineTokens((i !== -1 ? childrenArr.slice(0, i) : childrenArr) || [], void 0, void 0, linkifyContext.options()),
15615
16005
  raw: String(contentToken.content ?? "").replace(/\n:+$/, "").replace(/\n\s*:::\s*$/, "")
15616
- });
16006
+ };
16007
+ children.push(paragraphNode);
16008
+ linkifyContext.remember(paragraphNode.raw);
15617
16009
  }
15618
16010
  j += 3;
15619
16011
  } else if (tokens[j].type === "bullet_list_open" || tokens[j].type === "ordered_list_open") {
15620
- const [listNode, newIndex] = parseList(tokens, j, options);
16012
+ const [listNode, newIndex] = parseList(tokens, j, linkifyContext.options());
15621
16013
  children.push(listNode);
16014
+ linkifyContext.remember(listNode.raw);
15622
16015
  j = newIndex;
15623
16016
  } else if (tokens[j].type === "blockquote_open") {
15624
- const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, options);
16017
+ const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, linkifyContext.options());
15625
16018
  children.push(blockquoteNode);
16019
+ linkifyContext.remember(blockquoteNode.raw);
15626
16020
  j = newIndex;
15627
16021
  } else {
15628
- const handled = parseBasicBlockToken(tokens, j, options);
16022
+ const handled = parseBasicBlockToken(tokens, j, linkifyContext.options());
15629
16023
  if (handled) {
15630
16024
  children.push(handled[0]);
16025
+ linkifyContext.remember(handled[0].raw);
15631
16026
  j = handled[1];
15632
16027
  } else j++;
15633
16028
  }
@@ -15659,35 +16054,41 @@ const containerTokenHandlers = {
15659
16054
  //#region src/parser/node-parsers/blockquote-parser.ts
15660
16055
  function parseBlockquote(tokens, index, options) {
15661
16056
  const blockquoteChildren = [];
16057
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15662
16058
  let j = index + 1;
15663
16059
  while (j < tokens.length && tokens[j].type !== "blockquote_close") switch (tokens[j].type) {
15664
16060
  case "paragraph_open": {
15665
16061
  const contentToken = tokens[j + 1];
15666
- blockquoteChildren.push({
16062
+ const paragraphNode = {
15667
16063
  type: "paragraph",
15668
- children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), void 0, options),
16064
+ children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), void 0, linkifyContext.options()),
15669
16065
  raw: String(contentToken.content ?? "")
15670
- });
16066
+ };
16067
+ blockquoteChildren.push(paragraphNode);
16068
+ linkifyContext.remember(paragraphNode.raw);
15671
16069
  j += 3;
15672
16070
  break;
15673
16071
  }
15674
16072
  case "bullet_list_open":
15675
16073
  case "ordered_list_open": {
15676
- const [listNode, newIndex] = parseList(tokens, j, options);
16074
+ const [listNode, newIndex] = parseList(tokens, j, linkifyContext.options());
15677
16075
  blockquoteChildren.push(listNode);
16076
+ linkifyContext.remember(listNode.raw);
15678
16077
  j = newIndex;
15679
16078
  break;
15680
16079
  }
15681
16080
  case "blockquote_open": {
15682
- const [nestedBlockquote, newIndex] = parseBlockquote(tokens, j, options);
16081
+ const [nestedBlockquote, newIndex] = parseBlockquote(tokens, j, linkifyContext.options());
15683
16082
  blockquoteChildren.push(nestedBlockquote);
16083
+ linkifyContext.remember(nestedBlockquote.raw);
15684
16084
  j = newIndex;
15685
16085
  break;
15686
16086
  }
15687
16087
  default: {
15688
- const handled = parseCommonBlockToken(tokens, j, options, containerTokenHandlers);
16088
+ const handled = parseCommonBlockToken(tokens, j, linkifyContext.options(), containerTokenHandlers);
15689
16089
  if (handled) {
15690
16090
  blockquoteChildren.push(handled[0]);
16091
+ linkifyContext.remember(handled[0].raw);
15691
16092
  j = handled[1];
15692
16093
  } else j++;
15693
16094
  break;
@@ -15724,9 +16125,11 @@ function parseDefinitionList(tokens, index, options) {
15724
16125
  let j = index + 1;
15725
16126
  let termNodes = [];
15726
16127
  let definitionNodes = [];
16128
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15727
16129
  while (j < tokens.length && tokens[j].type !== "dl_close") if (tokens[j].type === "dt_open") {
15728
16130
  const termToken = tokens[j + 1];
15729
- termNodes = parseInlineTokens(termToken.children || [], void 0, void 0, options);
16131
+ termNodes = parseInlineTokens(termToken.children || [], void 0, void 0, linkifyContext.options());
16132
+ linkifyContext.remember(termNodes.map((term) => term.raw).join(""));
15730
16133
  j += 3;
15731
16134
  } else if (tokens[j].type === "dd_open") {
15732
16135
  let k = j + 1;
@@ -15735,9 +16138,10 @@ function parseDefinitionList(tokens, index, options) {
15735
16138
  const contentToken = tokens[k + 1];
15736
16139
  definitionNodes.push({
15737
16140
  type: "paragraph",
15738
- children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), void 0, options),
16141
+ children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), void 0, linkifyContext.options()),
15739
16142
  raw: String(contentToken.content ?? "")
15740
16143
  });
16144
+ linkifyContext.remember(String(contentToken.content ?? ""));
15741
16145
  k += 3;
15742
16146
  } else k++;
15743
16147
  if (termNodes.length > 0) {
@@ -15764,16 +16168,19 @@ function parseFootnote(tokens, index, options) {
15764
16168
  const meta = tokens[index].meta ?? {};
15765
16169
  const id = String(meta?.label ?? "0");
15766
16170
  const footnoteChildren = [];
16171
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15767
16172
  let j = index + 1;
15768
16173
  while (j < tokens.length && tokens[j].type !== "footnote_close") if (tokens[j].type === "paragraph_open") {
15769
16174
  const contentToken = tokens[j + 1];
15770
16175
  const children = contentToken.children ? [...contentToken.children] : [];
15771
16176
  if (tokens[j + 2].type === "footnote_anchor") children.push(tokens[j + 2]);
15772
- footnoteChildren.push({
16177
+ const paragraphNode = {
15773
16178
  type: "paragraph",
15774
- children: parseInlineTokens(children, String(contentToken.content ?? ""), void 0, options),
16179
+ children: parseInlineTokens(children, String(contentToken.content ?? ""), void 0, linkifyContext.options()),
15775
16180
  raw: String(contentToken.content ?? "")
15776
- });
16181
+ };
16182
+ footnoteChildren.push(paragraphNode);
16183
+ linkifyContext.remember(paragraphNode.raw);
15777
16184
  j += 3;
15778
16185
  } else j++;
15779
16186
  return [{
@@ -15900,6 +16307,30 @@ function extractAlign(attrs) {
15900
16307
  }
15901
16308
  return "left";
15902
16309
  }
16310
+ function hasTableCellContext(context) {
16311
+ return context?.filename === true || context?.explicitFilename === true || context?.marketTicker === true;
16312
+ }
16313
+ function mergeTableCellContext(left, right) {
16314
+ const merged = {
16315
+ filename: left?.filename || right?.filename,
16316
+ explicitFilename: left?.explicitFilename || right?.explicitFilename,
16317
+ marketTicker: left?.marketTicker || right?.marketTicker
16318
+ };
16319
+ return hasTableCellContext(merged) ? merged : void 0;
16320
+ }
16321
+ function parseOptionsForTableCell(options, headerRaw, rowContext) {
16322
+ const cellContext = mergeTableCellContext(inferLinkifyDemotionContext(headerRaw), rowContext);
16323
+ if (!hasTableCellContext(cellContext)) return options;
16324
+ const inheritedContext = options?.__linkifyDemotionContext;
16325
+ return {
16326
+ ...options,
16327
+ __linkifyDemotionContext: {
16328
+ filename: inheritedContext?.filename || cellContext?.filename,
16329
+ explicitFilename: inheritedContext?.explicitFilename || cellContext?.explicitFilename,
16330
+ marketTicker: inheritedContext?.marketTicker || cellContext?.marketTicker
16331
+ }
16332
+ };
16333
+ }
15903
16334
  function parseTable(tokens, index, options) {
15904
16335
  let j = index + 1;
15905
16336
  let headerRow = null;
@@ -15915,18 +16346,23 @@ function parseTable(tokens, index, options) {
15915
16346
  else if (tokens[j].type === "tr_open") {
15916
16347
  const cells = [];
15917
16348
  let k = j + 1;
16349
+ let rowContext;
15918
16350
  while (k < tokens.length && tokens[k].type !== "tr_close") if (tokens[k].type === "th_open" || tokens[k].type === "td_open") {
15919
16351
  const isHeaderCell = tokens[k].type === "th_open";
15920
16352
  const contentToken = tokens[k + 1];
15921
16353
  const content = String(contentToken.content ?? "");
15922
16354
  const align = extractAlign(tokens[k].attrs);
16355
+ const cellIndex = cells.length;
16356
+ const isBodyCell = !isHeaderCell && !isHeader;
16357
+ const headerRaw = isBodyCell ? headerRow?.cells[cellIndex]?.raw : void 0;
15923
16358
  cells.push({
15924
16359
  type: "table_cell",
15925
16360
  header: isHeaderCell || isHeader,
15926
- children: parseInlineTokens(contentToken.children || [], content, void 0, options),
16361
+ children: parseInlineTokens(contentToken.children || [], content, void 0, parseOptionsForTableCell(options, headerRaw, isBodyCell ? rowContext : void 0)),
15927
16362
  raw: content,
15928
16363
  align
15929
16364
  });
16365
+ if (isBodyCell) rowContext = mergeTableCellContext(rowContext, inferLinkifyDemotionContext(content));
15930
16366
  k += 3;
15931
16367
  } else k++;
15932
16368
  const rowNode = {
@@ -15943,11 +16379,12 @@ function parseTable(tokens, index, options) {
15943
16379
  cells: [],
15944
16380
  raw: ""
15945
16381
  };
16382
+ const tokenLoading = tokens[index].loading === true;
15946
16383
  return [{
15947
16384
  type: "table",
15948
16385
  header: headerRow,
15949
16386
  rows,
15950
- loading: tokens[index].loading ?? false,
16387
+ loading: tokenLoading && !options?.final && rows.length === 0,
15951
16388
  raw: [headerRow, ...rows].map((row) => row.raw).join("\n")
15952
16389
  }, j + 1];
15953
16390
  }
@@ -16008,30 +16445,35 @@ function parseVmrContainer(tokens, index, options) {
16008
16445
  }
16009
16446
  }
16010
16447
  const children = [];
16448
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
16011
16449
  let j = index + 1;
16012
16450
  while (j < tokens.length && tokens[j].type !== "vmr_container_close") if (tokens[j].type === "paragraph_open") {
16013
16451
  const contentToken = tokens[j + 1];
16014
16452
  if (contentToken) {
16015
- const childrenArr = contentToken.children || [];
16016
- children.push({
16453
+ const paragraphNode = {
16017
16454
  type: "paragraph",
16018
- children: parseInlineTokens(childrenArr || [], void 0, void 0, options),
16455
+ children: parseInlineTokens(contentToken.children || [], void 0, void 0, linkifyContext.options()),
16019
16456
  raw: String(contentToken.content ?? "")
16020
- });
16457
+ };
16458
+ children.push(paragraphNode);
16459
+ linkifyContext.remember(paragraphNode.raw);
16021
16460
  }
16022
16461
  j += 3;
16023
16462
  } else if (tokens[j].type === "bullet_list_open" || tokens[j].type === "ordered_list_open") {
16024
- const [listNode, newIndex] = parseList(tokens, j, options);
16463
+ const [listNode, newIndex] = parseList(tokens, j, linkifyContext.options());
16025
16464
  children.push(listNode);
16465
+ linkifyContext.remember(listNode.raw);
16026
16466
  j = newIndex;
16027
16467
  } else if (tokens[j].type === "blockquote_open") {
16028
- const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, options);
16468
+ const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, linkifyContext.options());
16029
16469
  children.push(blockquoteNode);
16470
+ linkifyContext.remember(blockquoteNode.raw);
16030
16471
  j = newIndex;
16031
16472
  } else {
16032
- const handled = parseBasicBlockToken(tokens, j, options);
16473
+ const handled = parseBasicBlockToken(tokens, j, linkifyContext.options());
16033
16474
  if (handled) {
16034
16475
  children.push(handled[0]);
16476
+ linkifyContext.remember(handled[0].raw);
16035
16477
  j = handled[1];
16036
16478
  } else j++;
16037
16479
  }
@@ -16297,15 +16739,207 @@ function parseParagraph(tokens, index, options) {
16297
16739
 
16298
16740
  //#endregion
16299
16741
  //#region src/parser/index.ts
16742
+ const streamParseEnvCache = /* @__PURE__ */ new WeakMap();
16300
16743
  function getNodeFields(node) {
16301
16744
  return node;
16302
16745
  }
16746
+ function getParserNow() {
16747
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
16748
+ }
16749
+ function addTiming(metrics, key, value) {
16750
+ if (!metrics) return;
16751
+ metrics[key] = (metrics[key] ?? 0) + value;
16752
+ }
16753
+ function getParseTiming(options) {
16754
+ return options.__timing;
16755
+ }
16756
+ function finishTimedParse(result, timing, startedAt) {
16757
+ if (timing) addTiming(timing, "parseMarkdownToStructureTotalMs", getParserNow() - startedAt);
16758
+ return result;
16759
+ }
16760
+ function processTokensWithTiming(tokens, options, timing) {
16761
+ if (!timing) return processTokens(tokens, options);
16762
+ const startedAt = getParserNow();
16763
+ const result = processTokens(tokens, options);
16764
+ addTiming(timing, "processTokensMs", getParserNow() - startedAt);
16765
+ return result;
16766
+ }
16303
16767
  function getCustomHtmlTagSet(options) {
16304
16768
  const custom = options?.customHtmlTags;
16305
16769
  if (!Array.isArray(custom) || custom.length === 0) return null;
16306
16770
  const normalized = normalizeCustomHtmlTags(custom);
16307
16771
  return normalized.length ? new Set(normalized) : null;
16308
16772
  }
16773
+ function getStableStreamEnv(md, env) {
16774
+ const mdKey = md;
16775
+ let byMode = streamParseEnvCache.get(mdKey);
16776
+ if (!byMode) {
16777
+ byMode = /* @__PURE__ */ new Map();
16778
+ streamParseEnvCache.set(mdKey, byMode);
16779
+ }
16780
+ const modeKey = env.__markstreamFinal === true ? "final" : "streaming";
16781
+ let stableEnv = byMode.get(modeKey);
16782
+ if (!stableEnv) {
16783
+ stableEnv = {};
16784
+ byMode.set(modeKey, stableEnv);
16785
+ }
16786
+ for (const key of Object.keys(stableEnv)) if (!Object.prototype.hasOwnProperty.call(env, key)) delete stableEnv[key];
16787
+ Object.assign(stableEnv, env);
16788
+ return stableEnv;
16789
+ }
16790
+ function isPlainObject(value) {
16791
+ if (!value || typeof value !== "object") return false;
16792
+ const proto = Object.getPrototypeOf(value);
16793
+ return proto === Object.prototype || proto === null;
16794
+ }
16795
+ function copyCloneableOwnDataProperties(source, target, seen) {
16796
+ for (const key of Reflect.ownKeys(source)) {
16797
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
16798
+ if (!descriptor || !("value" in descriptor)) continue;
16799
+ const targetDescriptor = Object.getOwnPropertyDescriptor(target, key);
16800
+ if (targetDescriptor && (!("value" in targetDescriptor) || targetDescriptor.writable === false)) continue;
16801
+ target[key] = safeCloneTokenField(descriptor.value, seen);
16802
+ }
16803
+ }
16804
+ function safeCloneTokenField(value, seen = /* @__PURE__ */ new WeakMap()) {
16805
+ if (!value || typeof value !== "object") return value;
16806
+ const object = value;
16807
+ const existing = seen.get(object);
16808
+ if (existing) return existing;
16809
+ if (Array.isArray(value)) {
16810
+ const cloned$1 = [];
16811
+ seen.set(object, cloned$1);
16812
+ for (const item of value) cloned$1.push(safeCloneTokenField(item, seen));
16813
+ return cloned$1;
16814
+ }
16815
+ if (value instanceof Map) {
16816
+ const cloned$1 = /* @__PURE__ */ new Map();
16817
+ seen.set(object, cloned$1);
16818
+ for (const [key, item] of value) cloned$1.set(safeCloneTokenField(key, seen), safeCloneTokenField(item, seen));
16819
+ return cloned$1;
16820
+ }
16821
+ if (value instanceof Set) {
16822
+ const cloned$1 = /* @__PURE__ */ new Set();
16823
+ seen.set(object, cloned$1);
16824
+ for (const item of value) cloned$1.add(safeCloneTokenField(item, seen));
16825
+ return cloned$1;
16826
+ }
16827
+ if (value instanceof Date) {
16828
+ const cloned$1 = new Date(value.getTime());
16829
+ seen.set(object, cloned$1);
16830
+ return cloned$1;
16831
+ }
16832
+ if (value instanceof RegExp) {
16833
+ const cloned$1 = new RegExp(value.source, value.flags);
16834
+ cloned$1.lastIndex = value.lastIndex;
16835
+ seen.set(object, cloned$1);
16836
+ return cloned$1;
16837
+ }
16838
+ if (typeof URL !== "undefined" && value instanceof URL) {
16839
+ const cloned$1 = new URL(value.href);
16840
+ seen.set(object, cloned$1);
16841
+ copyCloneableOwnDataProperties(object, cloned$1, seen);
16842
+ return cloned$1;
16843
+ }
16844
+ if (typeof URLSearchParams !== "undefined" && value instanceof URLSearchParams) {
16845
+ const cloned$1 = new URLSearchParams(value.toString());
16846
+ seen.set(object, cloned$1);
16847
+ copyCloneableOwnDataProperties(object, cloned$1, seen);
16848
+ return cloned$1;
16849
+ }
16850
+ if (value instanceof Error) {
16851
+ let cloned$1;
16852
+ const ErrorCtor = value.constructor;
16853
+ try {
16854
+ cloned$1 = new ErrorCtor(value.message);
16855
+ } catch {
16856
+ cloned$1 = new Error(value.message);
16857
+ }
16858
+ Object.setPrototypeOf(cloned$1, Object.getPrototypeOf(value));
16859
+ seen.set(object, cloned$1);
16860
+ copyCloneableOwnDataProperties(object, cloned$1, seen);
16861
+ return cloned$1;
16862
+ }
16863
+ if (typeof Promise !== "undefined" && value instanceof Promise) {
16864
+ seen.set(object, value);
16865
+ return value;
16866
+ }
16867
+ if (typeof Node !== "undefined" && value instanceof Node) {
16868
+ seen.set(object, value);
16869
+ return value;
16870
+ }
16871
+ if (!isPlainObject(value)) {
16872
+ const cloned$1 = Object.create(Object.getPrototypeOf(value));
16873
+ seen.set(object, cloned$1);
16874
+ copyCloneableOwnDataProperties(object, cloned$1, seen);
16875
+ return cloned$1;
16876
+ }
16877
+ const cloned = {};
16878
+ seen.set(object, cloned);
16879
+ const record = value;
16880
+ for (const key of Object.keys(record)) cloned[key] = safeCloneTokenField(record[key], seen);
16881
+ return cloned;
16882
+ }
16883
+ function cloneMarkdownToken(token, cloneObjectFields = true) {
16884
+ if (!cloneObjectFields) {
16885
+ const cloned$1 = Object.assign(Object.create(Object.getPrototypeOf(token)), token);
16886
+ if (Array.isArray(token.attrs)) cloned$1.attrs = token.attrs.map((attr) => [...attr]);
16887
+ if (Array.isArray(token.map)) cloned$1.map = [...token.map];
16888
+ if (Array.isArray(token.children)) cloned$1.children = token.children.map((child) => cloneMarkdownToken(child, cloneObjectFields));
16889
+ return cloned$1;
16890
+ }
16891
+ const cloned = Object.create(Object.getPrototypeOf(token));
16892
+ const seen = /* @__PURE__ */ new WeakMap();
16893
+ for (const key of Reflect.ownKeys(token)) {
16894
+ const descriptor = Object.getOwnPropertyDescriptor(token, key);
16895
+ if (!descriptor) continue;
16896
+ if (!("value" in descriptor)) {
16897
+ Object.defineProperty(cloned, key, descriptor);
16898
+ continue;
16899
+ }
16900
+ const value = descriptor.value;
16901
+ let clonedValue = value;
16902
+ if (key === "attrs" && Array.isArray(value)) clonedValue = value.map((attr) => [...attr]);
16903
+ else if (key === "map" && Array.isArray(value)) clonedValue = [...value];
16904
+ else if (key === "children" && Array.isArray(value)) clonedValue = value.map((child) => cloneMarkdownToken(child, cloneObjectFields));
16905
+ else if (cloneObjectFields && value && typeof value === "object") clonedValue = safeCloneTokenField(value, seen);
16906
+ Object.defineProperty(cloned, key, {
16907
+ ...descriptor,
16908
+ value: clonedValue
16909
+ });
16910
+ }
16911
+ return cloned;
16912
+ }
16913
+ function cloneMarkdownTokens(tokens, cloneObjectFields = true) {
16914
+ return tokens.map((token) => cloneMarkdownToken(token, cloneObjectFields));
16915
+ }
16916
+ function shouldUseTopLevelStreamParse(md, options) {
16917
+ const internalOptions = options;
16918
+ const stream = md.stream;
16919
+ const streamParse = options.streamParse ?? "auto";
16920
+ return internalOptions.__disableStreamParse !== true && (streamParse === true || streamParse === "auto" && options.final !== true) && stream?.enabled === true && typeof stream.parse === "function";
16921
+ }
16922
+ function shouldResetTopLevelStreamCacheForFinalAutoParse(md, options) {
16923
+ const internalOptions = options;
16924
+ const streamParse = options.streamParse ?? "auto";
16925
+ const stream = md.stream;
16926
+ return options.final === true && streamParse === "auto" && internalOptions.__disableStreamParse !== true && stream?.enabled === true && typeof stream.reset === "function";
16927
+ }
16928
+ function shouldCloneTopLevelStreamTokenObjectFields(options) {
16929
+ return typeof options.preTransformTokens === "function" || typeof options.postTransformTokens === "function";
16930
+ }
16931
+ function parseTopLevelTokens(md, source, env, options) {
16932
+ if (options.customHtmlTags?.length) env.__markstreamCustomHtmlTags = options.customHtmlTags;
16933
+ if (!shouldUseTopLevelStreamParse(md, options)) return md.parse(source, env);
16934
+ const tokens = md.stream.parse(source, getStableStreamEnv(md, env));
16935
+ const cloneObjectFields = shouldCloneTopLevelStreamTokenObjectFields(options);
16936
+ const timing = getParseTiming(options);
16937
+ if (!timing) return cloneMarkdownTokens(tokens, cloneObjectFields);
16938
+ const startedAt = getParserNow();
16939
+ const cloned = cloneMarkdownTokens(tokens, cloneObjectFields);
16940
+ addTiming(timing, "tokenCloneMs", getParserNow() - startedAt);
16941
+ return cloned;
16942
+ }
16309
16943
  function buildAllowedHtmlTagSet(options) {
16310
16944
  const custom = options?.customHtmlTags;
16311
16945
  if (!Array.isArray(custom) || custom.length === 0) return STANDARD_HTML_TAGS;
@@ -16511,6 +17145,7 @@ function findLastClosingTagStart(raw, tag) {
16511
17145
  function buildDetailsChildParseOptions(options, final) {
16512
17146
  return {
16513
17147
  final,
17148
+ __disableStreamParse: true,
16514
17149
  requireClosingStrong: options.requireClosingStrong,
16515
17150
  customHtmlTags: options.customHtmlTags,
16516
17151
  validateLink: options.validateLink
@@ -16568,7 +17203,10 @@ function structureGenericHtmlBlockChildren(nodes, md, options, final) {
16568
17203
  }
16569
17204
  function parseDetailsFragmentChildren(fragment, md, options) {
16570
17205
  if (!fragment.trim()) return [];
16571
- return parseMarkdownToStructure(fragment, md, options);
17206
+ return parseMarkdownToStructure(fragment, md, {
17207
+ ...options,
17208
+ __disableStreamParse: true
17209
+ });
16572
17210
  }
16573
17211
  function parseSummaryChildren(fragment, md, options) {
16574
17212
  const children = parseDetailsFragmentChildren(fragment, md, options);
@@ -17495,8 +18133,11 @@ function ensureBlankLineBeforeCustomHtmlBlocks(markdown, tags) {
17495
18133
  return out;
17496
18134
  }
17497
18135
  function parseMarkdownToStructure(markdown, md, options = {}) {
18136
+ const timing = getParseTiming(options);
18137
+ const parseStartedAt = timing ? getParserNow() : 0;
17498
18138
  const isFinal = !!options.final;
17499
18139
  let safeMarkdown = (markdown ?? "").toString().replace(/([^\\])\r(ight|ho)/g, "$1\\r$2").replace(/([^\\])\n(abla|eq|ot|exists)/g, "$1\\n$2");
18140
+ if (shouldResetTopLevelStreamCacheForFinalAutoParse(md, options)) md.stream.reset();
17500
18141
  if (!isFinal) {
17501
18142
  if (safeMarkdown.endsWith("- *")) safeMarkdown = safeMarkdown.replace(/- \*$/, "- \\*");
17502
18143
  if (/(?:^|\n)\s*-\s*$/.test(safeMarkdown)) safeMarkdown = safeMarkdown.replace(/(?:^|\n)\s*-\s*$/, (m) => {
@@ -17537,15 +18178,15 @@ function parseMarkdownToStructure(markdown, md, options = {}) {
17537
18178
  if (standaloneHtmlDocument) {
17538
18179
  const preHook = options.preTransformTokens;
17539
18180
  const postHook = options.postTransformTokens;
17540
- if (typeof preHook === "function" || typeof postHook === "function") {
17541
- const rawTokens = md.parse(safeMarkdown, { __markstreamFinal: isFinal });
18181
+ if (shouldUseTopLevelStreamParse(md, options) || typeof preHook === "function" || typeof postHook === "function") {
18182
+ const rawTokens = parseTopLevelTokens(md, safeMarkdown, { __markstreamFinal: isFinal }, options);
17542
18183
  const hookedTokens = typeof preHook === "function" ? preHook(rawTokens) || rawTokens : rawTokens;
17543
18184
  if (typeof postHook === "function") postHook(hookedTokens);
17544
18185
  }
17545
- return standaloneHtmlDocument;
18186
+ return finishTimedParse(standaloneHtmlDocument, timing, parseStartedAt);
17546
18187
  }
17547
- const tokens = md.parse(safeMarkdown, { __markstreamFinal: isFinal });
17548
- if (!tokens || !Array.isArray(tokens)) return [];
18188
+ const tokens = parseTopLevelTokens(md, safeMarkdown, { __markstreamFinal: isFinal }, options);
18189
+ if (!tokens || !Array.isArray(tokens)) return finishTimedParse([], timing, parseStartedAt);
17549
18190
  const pre = options.preTransformTokens;
17550
18191
  const post = options.postTransformTokens;
17551
18192
  let transformedTokens = tokens;
@@ -17559,13 +18200,13 @@ function parseMarkdownToStructure(markdown, md, options = {}) {
17559
18200
  __sourceMarkdown: safeMarkdown,
17560
18201
  __customHtmlBlockCursor: 0
17561
18202
  };
17562
- let result = processTokens(transformedTokens, internalOptions);
18203
+ let result = processTokensWithTiming(transformedTokens, internalOptions, timing);
17563
18204
  if (post && typeof post === "function") {
17564
18205
  const postResult = post(transformedTokens);
17565
18206
  if (Array.isArray(postResult)) {
17566
18207
  const first = postResult[0];
17567
18208
  const firstType = first?.type;
17568
- if (first && typeof firstType === "string") result = processTokens(postResult);
18209
+ if (first && typeof firstType === "string") result = processTokensWithTiming(postResult, void 0, timing);
17569
18210
  else result = postResult;
17570
18211
  }
17571
18212
  }
@@ -17589,39 +18230,45 @@ function parseMarkdownToStructure(markdown, md, options = {}) {
17589
18230
  finalizeHtmlBlockLoading(result);
17590
18231
  }
17591
18232
  if (options.debug) console.log("Parsed Markdown Tree Structure:", result);
17592
- return result;
18233
+ return finishTimedParse(result, timing, parseStartedAt);
17593
18234
  }
17594
18235
  function processTokens(tokens, options) {
17595
18236
  if (!tokens || !Array.isArray(tokens)) return [];
17596
18237
  const result = [];
18238
+ const linkifyContext = createLinkifyDemotionContextTracker(options);
17597
18239
  let i = 0;
17598
18240
  while (i < tokens.length) {
17599
- const handled = parseCommonBlockToken(tokens, i, options, containerTokenHandlers);
18241
+ const handled = parseCommonBlockToken(tokens, i, linkifyContext.options(), containerTokenHandlers);
17600
18242
  if (handled) {
17601
18243
  result.push(handled[0]);
18244
+ linkifyContext.remember(handled[0].raw);
17602
18245
  i = handled[1];
17603
18246
  continue;
17604
18247
  }
17605
18248
  const token = tokens[i];
17606
18249
  switch (token.type) {
17607
18250
  case "paragraph_open": {
17608
- const paragraphNode = parseParagraph(tokens, i, options);
18251
+ const paragraphRaw = String(tokens[i + 1]?.content ?? "");
18252
+ const paragraphNode = parseParagraph(tokens, i, linkifyContext.options(paragraphRaw));
17609
18253
  const promoted = maybePromoteCustomNodeFromParagraph(paragraphNode, options);
17610
18254
  if (promoted) result.push(...promoted);
17611
18255
  else result.push(paragraphNode);
18256
+ linkifyContext.remember(paragraphNode.raw);
17612
18257
  i += 3;
17613
18258
  break;
17614
18259
  }
17615
18260
  case "bullet_list_open":
17616
18261
  case "ordered_list_open": {
17617
- const [listNode, newIndex] = parseList(tokens, i, options);
18262
+ const [listNode, newIndex] = parseList(tokens, i, linkifyContext.options());
17618
18263
  result.push(listNode);
18264
+ linkifyContext.remember(listNode.raw);
17619
18265
  i = newIndex;
17620
18266
  break;
17621
18267
  }
17622
18268
  case "blockquote_open": {
17623
- const [blockquoteNode, newIndex] = parseBlockquote(tokens, i, options);
18269
+ const [blockquoteNode, newIndex] = parseBlockquote(tokens, i, linkifyContext.options());
17624
18270
  result.push(blockquoteNode);
18271
+ linkifyContext.remember(blockquoteNode.raw);
17625
18272
  i = newIndex;
17626
18273
  break;
17627
18274
  }
@@ -17633,11 +18280,13 @@ function processTokens(tokens, options) {
17633
18280
  id,
17634
18281
  raw: String(token.content ?? "")
17635
18282
  });
18283
+ linkifyContext.remember(String(token.content ?? ""));
17636
18284
  i++;
17637
18285
  break;
17638
18286
  }
17639
18287
  case "hardbreak":
17640
18288
  result.push(parseHardBreak());
18289
+ linkifyContext.reset();
17641
18290
  i++;
17642
18291
  break;
17643
18292
  case "text": {
@@ -17651,23 +18300,26 @@ function processTokens(tokens, options) {
17651
18300
  raw: content
17652
18301
  }] : []
17653
18302
  });
18303
+ linkifyContext.remember(content);
17654
18304
  i++;
17655
18305
  break;
17656
18306
  }
17657
18307
  case "inline":
17658
18308
  {
17659
- const parsed = parseInlineTokens(token.children || [], String(token.content ?? ""), void 0, options);
18309
+ const raw = String(token.content ?? "");
18310
+ const parsed = parseInlineTokens(token.children || [], raw, void 0, linkifyContext.options(raw));
17660
18311
  if (parsed.length === 0) {} else if (parsed.every((n) => n.type === "html_block")) result.push(...parsed);
17661
18312
  else {
17662
18313
  const paragraphNode = {
17663
18314
  type: "paragraph",
17664
- raw: String(token.content ?? ""),
18315
+ raw,
17665
18316
  children: parsed
17666
18317
  };
17667
18318
  const promoted = maybePromoteCustomNodeFromParagraph(paragraphNode, options);
17668
18319
  if (promoted) result.push(...promoted);
17669
18320
  else result.push(paragraphNode);
17670
18321
  }
18322
+ linkifyContext.remember(raw);
17671
18323
  }
17672
18324
  i += 1;
17673
18325
  break;
@@ -18554,7 +19206,8 @@ function getMarkdown(msgId = `editor-${Date.now()}`, options = {}) {
18554
19206
  md.use(sub_plugin);
18555
19207
  md.use(sup_plugin);
18556
19208
  md.use(ins_plugin$1);
18557
- const markdownItCheckboxPlugin = import_markdown_it_task_checkbox.default ?? import_markdown_it_task_checkbox;
19209
+ const checkboxModule = import_markdown_it_task_checkbox;
19210
+ const markdownItCheckboxPlugin = checkboxModule.default ?? checkboxModule;
18558
19211
  md.use(markdownItCheckboxPlugin);
18559
19212
  md.use(ins_plugin);
18560
19213
  md.use(footnote_plugin);