stream-markdown-parser 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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];
@@ -12570,23 +12905,25 @@ function findRangeAt(ranges, index) {
12570
12905
  for (const range of ranges) if (index >= range[0] && index < range[1]) return range;
12571
12906
  return null;
12572
12907
  }
12573
- function buildImageRanges(src) {
12908
+ function buildImageRanges(src, allowIncomplete = false) {
12574
12909
  const ranges = [];
12575
12910
  let i = 0;
12576
12911
  while (i < src.length - 1) {
12577
12912
  if (src[i] === "!" && src[i + 1] === "[") {
12578
12913
  const start = i;
12579
12914
  let j = i + 2;
12580
- while (j < src.length) {
12915
+ let labelDepth = 1;
12916
+ while (j < src.length && labelDepth > 0) {
12581
12917
  if (src[j] === "\\" && j + 1 < src.length) {
12582
12918
  j += 2;
12583
12919
  continue;
12584
12920
  }
12585
- if (src[j] === "]") break;
12921
+ if (src[j] === "[") labelDepth++;
12922
+ else if (src[j] === "]") labelDepth--;
12586
12923
  j++;
12587
12924
  }
12588
- if (j < src.length && src[j] === "]" && j + 1 < src.length && src[j + 1] === "(") {
12589
- let k = j + 2;
12925
+ if (labelDepth === 0 && j < src.length && src[j] === "(") {
12926
+ let k = j + 1;
12590
12927
  let depth = 1;
12591
12928
  while (k < src.length && depth > 0) {
12592
12929
  if (src[k] === "\\" && k + 1 < src.length) {
@@ -12602,6 +12939,11 @@ function buildImageRanges(src) {
12602
12939
  i = k;
12603
12940
  continue;
12604
12941
  }
12942
+ if (allowIncomplete) {
12943
+ ranges.push([start, src.length]);
12944
+ i = src.length;
12945
+ continue;
12946
+ }
12605
12947
  }
12606
12948
  }
12607
12949
  i++;
@@ -12691,7 +13033,7 @@ function applyMath(md, mathOpts) {
12691
13033
  for (const [open, close] of delimiters) {
12692
13034
  const src = s.src;
12693
13035
  const codeSpanRanges = buildCodeSpanRanges(src);
12694
- const imageRanges = buildImageRanges(src);
13036
+ const imageRanges = buildImageRanges(src, allowLoading);
12695
13037
  let foundAny = false;
12696
13038
  if (open === "$$" && searchPos !== initialPos) searchPos = initialPos;
12697
13039
  let lastIndex = -1;
@@ -13111,6 +13453,7 @@ function applyMath(md, mathOpts) {
13111
13453
  let content = "";
13112
13454
  let found = false;
13113
13455
  const firstLineContent = lineText === openDelim ? "" : lineText.slice(openDelim.length);
13456
+ const fallbackPlainBracketClose = !strict && openDelim === "\\[" ? "]" : "";
13114
13457
  if (firstLineContent.includes(closeDelim)) {
13115
13458
  const endIndex = firstLineContent.indexOf(closeDelim);
13116
13459
  content = firstLineContent.slice(0, endIndex);
@@ -13122,6 +13465,11 @@ function applyMath(md, mathOpts) {
13122
13465
  const lineStart = s.bMarks[nextLine] + s.tShift[nextLine];
13123
13466
  const lineEnd = s.eMarks[nextLine];
13124
13467
  const currentLine = s.src.slice(lineStart, lineEnd);
13468
+ if (fallbackPlainBracketClose && currentLine.trim() === fallbackPlainBracketClose) {
13469
+ closeDelim = fallbackPlainBracketClose;
13470
+ found = true;
13471
+ break;
13472
+ }
13125
13473
  if (currentLine.trim() === closeDelim) {
13126
13474
  found = true;
13127
13475
  break;
@@ -13220,6 +13568,16 @@ function factory(opts = {}) {
13220
13568
  return md;
13221
13569
  }
13222
13570
 
13571
+ //#endregion
13572
+ //#region src/parser/token-copy.ts
13573
+ function cloneTokenWithMutableChildren(token) {
13574
+ const copy = Object.assign(Object.create(Object.getPrototypeOf(token)), token);
13575
+ if (Array.isArray(token.attrs)) copy.attrs = token.attrs.map((attr) => [...attr]);
13576
+ if (Array.isArray(token.map)) copy.map = [...token.map];
13577
+ if (Array.isArray(token.children)) copy.children = token.children.map((child) => cloneTokenWithMutableChildren(child));
13578
+ return copy;
13579
+ }
13580
+
13223
13581
  //#endregion
13224
13582
  //#region src/parser/inline-parsers/checkbox-parser.ts
13225
13583
  function parseCheckboxToken(token) {
@@ -13288,6 +13646,11 @@ const DIFF_HEADER_PREFIXES = [
13288
13646
  "@@ "
13289
13647
  ];
13290
13648
  const NEWLINE_RE = /\r?\n/;
13649
+ function isPotentialDiffMetadataTail(line) {
13650
+ const value = String(line ?? "");
13651
+ if (!value) return false;
13652
+ return DIFF_HEADER_PREFIXES.some((prefix) => prefix.startsWith(value) || value.startsWith(prefix));
13653
+ }
13291
13654
  function flushPendingDiffHunk(orig, updated, pendingOrig, pendingUpdated) {
13292
13655
  if (pendingOrig.length > 0) orig.push(...pendingOrig);
13293
13656
  if (pendingUpdated.length > 0) updated.push(...pendingUpdated);
@@ -13300,7 +13663,7 @@ function splitUnifiedDiff(content, closed) {
13300
13663
  const pendingOrig = [];
13301
13664
  const pendingUpdated = [];
13302
13665
  const lines = content.split(NEWLINE_RE);
13303
- const stableLineCount = Math.max(0, lines.length - 1);
13666
+ const endsWithNewline = /\r?\n$/.test(content);
13304
13667
  const hasUnifiedDiffHeaders = lines.some((line) => line.startsWith("diff ") || line.startsWith("--- ") || line.startsWith("+++ ") || line.startsWith("@@ "));
13305
13668
  const processLine = (rawLine) => {
13306
13669
  const line = rawLine;
@@ -13318,18 +13681,25 @@ function splitUnifiedDiff(content, closed) {
13318
13681
  updated.push(contextLine);
13319
13682
  }
13320
13683
  };
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);
13684
+ const lineCountToProcess = endsWithNewline ? Math.max(0, lines.length - 1) : lines.length;
13685
+ for (let index = 0; index < lineCountToProcess; index++) {
13686
+ const line = lines[index] ?? "";
13687
+ if (!closed && !endsWithNewline && index === lineCountToProcess - 1 && isPotentialDiffMetadataTail(line)) continue;
13688
+ processLine(line);
13689
+ }
13690
+ if (closed || pendingOrig.length > 0 || pendingUpdated.length > 0) flushPendingDiffHunk(orig, updated, pendingOrig, pendingUpdated);
13691
+ const originalCode = orig.join("\n");
13692
+ const updatedCode = updated.join("\n");
13324
13693
  return {
13325
- original: orig.join("\n"),
13326
- updated: updated.join("\n")
13694
+ original: closed && endsWithNewline && originalCode ? `${originalCode}\n` : originalCode,
13695
+ updated: closed && endsWithNewline && updatedCode ? `${updatedCode}\n` : updatedCode
13327
13696
  };
13328
13697
  }
13329
13698
  function parseFenceToken(token) {
13330
13699
  const hasMap = Array.isArray(token.map) && token.map.length === 2;
13331
13700
  const tokenMeta = token.meta ?? {};
13332
- const closed = typeof tokenMeta.closed === "boolean" ? tokenMeta.closed : void 0;
13701
+ const metaClosed = typeof tokenMeta.closed === "boolean" ? tokenMeta.closed : void 0;
13702
+ const closed = metaClosed === true || metaClosed !== false && hasMap;
13333
13703
  const info = String(token.info ?? "");
13334
13704
  const diff = info.startsWith("diff");
13335
13705
  const language = diff ? (() => {
@@ -13347,7 +13717,7 @@ function parseFenceToken(token) {
13347
13717
  code: String(updated ?? ""),
13348
13718
  raw: String(content ?? ""),
13349
13719
  diff,
13350
- loading: closed === true ? false : closed === false ? true : !hasMap,
13720
+ loading: metaClosed === true ? false : metaClosed === false ? true : !hasMap,
13351
13721
  originalCode: original,
13352
13722
  updatedCode: updated
13353
13723
  };
@@ -13358,7 +13728,7 @@ function parseFenceToken(token) {
13358
13728
  code: String(content ?? ""),
13359
13729
  raw: String(content ?? ""),
13360
13730
  diff,
13361
- loading: closed === true ? false : closed === false ? true : !hasMap
13731
+ loading: metaClosed === true ? false : metaClosed === false ? true : !hasMap
13362
13732
  };
13363
13733
  }
13364
13734
 
@@ -13448,6 +13818,15 @@ function tokenToRaw(token) {
13448
13818
  const raw = shape.raw ?? shape.content ?? shape.markup ?? "";
13449
13819
  return String(raw ?? "");
13450
13820
  }
13821
+ function getCustomHtmlSourceMeta(token) {
13822
+ const meta = token.meta;
13823
+ const raw = meta?.markstreamCustomHtmlRaw;
13824
+ const inner = meta?.markstreamCustomHtmlInner;
13825
+ return typeof raw === "string" && typeof inner === "string" ? {
13826
+ raw,
13827
+ inner
13828
+ } : null;
13829
+ }
13451
13830
  function getAttrValue$1(attrs, name) {
13452
13831
  const lowerName = name.toLowerCase();
13453
13832
  for (let i = attrs.length - 1; i >= 0; i--) {
@@ -13632,16 +14011,19 @@ function parseHtmlInlineCodeToken(token, tokens, i, parseInlineTokens$1, raw, pP
13632
14011
  const attrValue = match[2] || match[3] || match[4] || "";
13633
14012
  attrs.push([attrName, attrValue]);
13634
14013
  }
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];
14014
+ if (customTagSet?.has(tag)) {
14015
+ const sourceMeta = getCustomHtmlSourceMeta(token);
14016
+ return [{
14017
+ type: tag,
14018
+ tag,
14019
+ attrs,
14020
+ content: sourceMeta ? sourceMeta.inner : fragment.innerTokens.length ? stringifyTokens(fragment.innerTokens) : "",
14021
+ children: fragment.innerTokens.length ? parseInlineTokens$1(fragment.innerTokens, raw, pPreToken, options) : [],
14022
+ raw: sourceMeta?.raw ?? content,
14023
+ loading: token.loading || loading,
14024
+ autoClosed
14025
+ }, fragment.nextIndex];
14026
+ }
13645
14027
  return [{
13646
14028
  type: "html_inline",
13647
14029
  tag,
@@ -13656,6 +14038,19 @@ function parseHtmlInlineCodeToken(token, tokens, i, parseInlineTokens$1, raw, pP
13656
14038
 
13657
14039
  //#endregion
13658
14040
  //#region src/parser/inline-parsers/image-parser.ts
14041
+ function stringifyAltToken(token) {
14042
+ if (token.type === "math_inline") {
14043
+ if (token.raw) return String(token.raw);
14044
+ const markup = token.markup === "$$" ? "$$" : "$";
14045
+ return `${markup}${String(token.content ?? "")}${markup}`;
14046
+ }
14047
+ if (Array.isArray(token.children) && token.children.length > 0) return token.children.map((child) => stringifyAltToken(child)).join("");
14048
+ return String(token.content ?? "");
14049
+ }
14050
+ function getAltFromTokenChildren(token) {
14051
+ if (!token || !Array.isArray(token.children) || token.children.length === 0) return "";
14052
+ return token.children.map((child) => stringifyAltToken(child)).join("");
14053
+ }
13659
14054
  function parseImageToken(token, loading = false) {
13660
14055
  let attrs = token.attrs ?? [];
13661
14056
  let childWithAttrs = null;
@@ -13669,8 +14064,10 @@ function parseImageToken(token, loading = false) {
13669
14064
  }
13670
14065
  const src = String(attrs.find((attr) => attr[0] === "src")?.[1] ?? "");
13671
14066
  const altAttr = attrs.find((attr) => attr[0] === "alt")?.[1];
14067
+ const childAlt = getAltFromTokenChildren(childWithAttrs ?? token);
13672
14068
  let alt = "";
13673
- if (altAttr != null && String(altAttr).length > 0) alt = String(altAttr);
14069
+ if (childAlt) alt = childAlt;
14070
+ else if (altAttr != null && String(altAttr).length > 0) alt = String(altAttr);
13674
14071
  else if (childWithAttrs?.content != null && String(childWithAttrs.content).length > 0) alt = String(childWithAttrs.content);
13675
14072
  else if (Array.isArray(childWithAttrs?.children) && childWithAttrs.children[0]?.content) alt = String(childWithAttrs.children[0].content);
13676
14073
  else if (Array.isArray(token.children) && token.children[0]?.content) alt = String(token.children[0].content);
@@ -13762,14 +14159,18 @@ function parseLinkToken(tokens, startIndex, options) {
13762
14159
  i++;
13763
14160
  }
13764
14161
  if (tokens[i]?.type === "link_close") loading = false;
14162
+ let childTokens = linkTokens;
13765
14163
  const lastLinkToken = linkTokens[linkTokens.length - 1];
13766
14164
  if (options?.__insideStrong && lastLinkToken?.type === "text" && String(lastLinkToken.content ?? "").endsWith("**") && !linkTokens.some((token) => token.type === "strong_open")) {
13767
14165
  const originalContent = String(lastLinkToken.content ?? "");
13768
14166
  const originalRaw = String(lastLinkToken.raw ?? originalContent);
13769
- lastLinkToken.content = originalContent.slice(0, -2);
13770
- lastLinkToken.raw = originalRaw.replace(/\*\*$/, "");
14167
+ const adjustedLastLinkToken = cloneTokenWithMutableChildren(lastLinkToken);
14168
+ adjustedLastLinkToken.content = originalContent.slice(0, -2);
14169
+ adjustedLastLinkToken.raw = originalRaw.replace(/\*\*$/, "");
14170
+ childTokens = linkTokens.slice();
14171
+ childTokens[childTokens.length - 1] = adjustedLastLinkToken;
13771
14172
  }
13772
- const children = parseInlineTokens(linkTokens, void 0, void 0, options);
14173
+ const children = parseInlineTokens(childTokens, void 0, void 0, options);
13773
14174
  const linkText = children.map((node) => {
13774
14175
  const nodeAny = node;
13775
14176
  if ("content" in node) return String(nodeAny.content ?? "");
@@ -14189,11 +14590,27 @@ function recoverTrailingMarkdownLinkLabel(raw, href) {
14189
14590
  }
14190
14591
  function parseInlineTokens(tokens, raw, pPreToken, options) {
14191
14592
  if (!tokens || tokens.length === 0) return [];
14593
+ const inheritedContext = options?.__linkifyDemotionContext;
14594
+ const inferredContext = inferLinkifyDemotionContext(raw);
14595
+ const linkifyDemotionContext = {
14596
+ filename: inheritedContext?.filename || inferredContext.filename,
14597
+ explicitFilename: inheritedContext?.explicitFilename || inferredContext.explicitFilename,
14598
+ marketTicker: inheritedContext?.marketTicker || inferredContext.marketTicker
14599
+ };
14600
+ if (linkifyDemotionContext.filename || linkifyDemotionContext.explicitFilename || linkifyDemotionContext.marketTicker) options = {
14601
+ ...options,
14602
+ __linkifyDemotionContext: linkifyDemotionContext
14603
+ };
14192
14604
  const internalOptions = options;
14193
14605
  const result = [];
14194
14606
  let currentTextNode = null;
14195
14607
  let i = 0;
14196
14608
  const requireClosingStrong = options?.requireClosingStrong;
14609
+ const originalTokens = tokens;
14610
+ function ensureWorkingTokens() {
14611
+ if (tokens === originalTokens) tokens = tokens.slice();
14612
+ return tokens;
14613
+ }
14197
14614
  function resetCurrentTextNode() {
14198
14615
  currentTextNode = null;
14199
14616
  }
@@ -14644,7 +15061,8 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
14644
15061
  }
14645
15062
  function pushToken(token) {
14646
15063
  resetCurrentTextNode();
14647
- result.push(token);
15064
+ const node = cloneTokenWithMutableChildren(token);
15065
+ result.push(node);
14648
15066
  }
14649
15067
  function pushNode(node) {
14650
15068
  pushParsed(node);
@@ -14880,6 +15298,7 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
14880
15298
  pushText(displayText, displayText);
14881
15299
  i++;
14882
15300
  } else if (recoverOuterImageLinkFromSyntheticLinkToken(token)) i++;
15301
+ else if (recoverMarkdownImageFromLoadingImageTail(token)) i++;
14883
15302
  else if (recoverMarkdownImageFromTrailingBang(token)) i++;
14884
15303
  else if (recoverMarkdownLinkFromTrailingText(token)) i++;
14885
15304
  else {
@@ -14930,10 +15349,6 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
14930
15349
  result.length = trailingTextStart;
14931
15350
  } else currentTextNode = result[result.length - 1];
14932
15351
  const nextToken = tokens[i + 1];
14933
- if (pPreToken?.type === "list_item_open" && /^\d$/.test(content)) {
14934
- i++;
14935
- return;
14936
- }
14937
15352
  if ((content === "`" || content === "|" || content === "$") && !hasEscapedMarkup(token, `\\${content}`) || /^\*+$/.test(content) && !hasEscapedMarkup(token, "\\*")) {
14938
15353
  i++;
14939
15354
  return;
@@ -14971,6 +15386,7 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
14971
15386
  i++;
14972
15387
  }
14973
15388
  function handleLinkOpen(token) {
15389
+ if (recoverMarkdownImageFromLoadingImageTailLinkOpen(token)) return;
14974
15390
  if (shouldTreatLinkOpenAsTextInEscapedOuterImageTail()) {
14975
15391
  const { node: node$1, nextIndex: nextIndex$1 } = parseLinkToken(tokens, i, options);
14976
15392
  const text$1 = String(node$1.text || node$1.href || "");
@@ -14981,8 +15397,9 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
14981
15397
  resetCurrentTextNode();
14982
15398
  const { node, nextIndex } = parseLinkToken(tokens, i, options);
14983
15399
  i = nextIndex;
14984
- if (token.markup === "linkify" && shouldDemoteFilenameLikeLinkify(node.text || node.href || "")) {
14985
- pushText(node.text || node.href || "", node.text || node.href || "");
15400
+ const linkText = node.text || node.href || "";
15401
+ if (token.markup === "linkify" && !isDecodedFromRawPunycode(linkText, node.href, raw) && shouldDemoteFilenameLikeLinkify(linkText, internalOptions?.__linkifyDemotionContext)) {
15402
+ pushText(linkText, linkText);
14986
15403
  return;
14987
15404
  }
14988
15405
  const hasSingleTextChild = node.children.length === 1 && node.children[0]?.type === "text";
@@ -15017,6 +15434,13 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
15017
15434
  if (recoverMarkdownLinkFromTrailingText(node)) return;
15018
15435
  pushParsed(node);
15019
15436
  }
15437
+ function recoverMarkdownImageFromLoadingImageTailLinkOpen(token) {
15438
+ if (token.markup !== "linkify") return false;
15439
+ const { node, nextIndex } = parseLinkToken(tokens, i, options);
15440
+ if (!recoverMarkdownImageFromLoadingImageTailLink(node, nextIndex)) return false;
15441
+ i = nextIndex;
15442
+ return true;
15443
+ }
15020
15444
  function handleReference(token) {
15021
15445
  resetCurrentTextNode();
15022
15446
  pushNode(parseReferenceToken(token));
@@ -15051,6 +15475,41 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
15051
15475
  });
15052
15476
  return true;
15053
15477
  }
15478
+ function recoverMarkdownImageFromLoadingImageTail(token) {
15479
+ if (token.type !== "link") return false;
15480
+ const linkToken = token;
15481
+ const href = String(linkToken.href ?? "");
15482
+ if (!href) return false;
15483
+ return recoverMarkdownImageFromLoadingImageTailLink({
15484
+ href,
15485
+ title: linkToken.title == null || linkToken.title === "" ? null : String(linkToken.title),
15486
+ loading: Boolean(linkToken.loading)
15487
+ }, i + 1);
15488
+ }
15489
+ function recoverMarkdownImageFromLoadingImageTailLink(link$1, nextIndex) {
15490
+ const previous = result[result.length - 1];
15491
+ if (previous?.type !== "image" || previous.src || !previous.loading || !String(previous.raw ?? "").endsWith("](")) return false;
15492
+ const nextToken = tokens[nextIndex];
15493
+ const nextContent = String(nextToken?.content ?? "");
15494
+ if (nextToken?.type !== "text" || !nextContent.startsWith(")")) return false;
15495
+ result.pop();
15496
+ currentTextNode = null;
15497
+ const alt = String(previous.alt ?? "");
15498
+ pushParsed({
15499
+ type: "image",
15500
+ src: link$1.href,
15501
+ alt,
15502
+ title: link$1.title,
15503
+ raw: String(`![${alt}](${link$1.href}${link$1.title ? ` "${link$1.title}"` : ""})`),
15504
+ loading: Boolean(link$1.loading)
15505
+ });
15506
+ const trailing = nextContent.slice(1);
15507
+ const adjustedNext = cloneTokenWithMutableChildren(nextToken);
15508
+ adjustedNext.content = trailing;
15509
+ adjustedNext.raw = trailing;
15510
+ ensureWorkingTokens()[nextIndex] = adjustedNext;
15511
+ return true;
15512
+ }
15054
15513
  function recoverMarkdownImageFromTrailingBang(token) {
15055
15514
  if (token.type !== "link") return false;
15056
15515
  const previous = result[result.length - 1];
@@ -15218,12 +15677,14 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
15218
15677
  const lastContent = String(last.content ?? "");
15219
15678
  if (lastContent.startsWith(")")) {
15220
15679
  loading$1 = false;
15221
- const trailing = lastContent.slice(1);
15222
- if (trailing) {
15223
- last.content = trailing;
15224
- last.raw = trailing;
15680
+ const trailingAfterClose = lastContent.slice(1);
15681
+ if (trailingAfterClose) {
15682
+ const trailingToken = cloneTokenWithMutableChildren(last);
15683
+ trailingToken.content = trailingAfterClose;
15684
+ trailingToken.raw = trailingAfterClose;
15685
+ ensureWorkingTokens()[i + 4] = trailingToken;
15225
15686
  } else index++;
15226
- } else if (lastContent === ".") i++;
15687
+ } else if (lastContent === ".") index++;
15227
15688
  }
15228
15689
  pushInlineTextContent(textNodeContent, _token);
15229
15690
  const hrefFromToken = String(textToken$1.content ?? "");
@@ -15451,36 +15912,49 @@ function stripLeakedOrderedListMarkerSuffix(token) {
15451
15912
  break;
15452
15913
  }
15453
15914
  }
15915
+ function needsListParagraphTokenPatch(token) {
15916
+ const rawContent = String(token.content ?? "");
15917
+ return /[ \t\r\n]+$/.test(rawContent) || /\r?\n\s*\d+[.)]?\s*$/.test(rawContent);
15918
+ }
15454
15919
  function parseList(tokens, index, options) {
15455
15920
  const token = tokens[index];
15456
15921
  const listItems = [];
15922
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15457
15923
  let j = index + 1;
15458
15924
  while (j < tokens.length && tokens[j].type !== "bullet_list_close" && tokens[j].type !== "ordered_list_close") if (tokens[j].type === "list_item_open") {
15459
15925
  const itemChildren = [];
15460
15926
  let k = j + 1;
15461
15927
  while (k < tokens.length && tokens[k].type !== "list_item_close") if (tokens[k].type === "paragraph_open") {
15462
- const contentToken = tokens[k + 1];
15928
+ const originalContentToken = tokens[k + 1];
15929
+ const contentToken = needsListParagraphTokenPatch(originalContentToken) ? cloneTokenWithMutableChildren(originalContentToken) : originalContentToken;
15463
15930
  const preToken = tokens[k - 1];
15464
- stripLeakedOrderedListMarkerSuffix(contentToken);
15465
- trimInlineTokenTail(contentToken);
15931
+ if (contentToken !== originalContentToken) {
15932
+ stripLeakedOrderedListMarkerSuffix(contentToken);
15933
+ trimInlineTokenTail(contentToken);
15934
+ }
15935
+ const paragraphRaw = String(contentToken.content ?? "");
15466
15936
  itemChildren.push({
15467
15937
  type: "paragraph",
15468
- children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), preToken, options),
15469
- raw: String(contentToken.content ?? "")
15938
+ children: parseInlineTokens(contentToken.children || [], paragraphRaw, preToken, linkifyContext.options()),
15939
+ raw: paragraphRaw
15470
15940
  });
15941
+ linkifyContext.remember(paragraphRaw);
15471
15942
  k += 3;
15472
15943
  } else if (tokens[k].type === "blockquote_open") {
15473
- const [blockquoteNode, newIndex] = parseBlockquote(tokens, k, options);
15944
+ const [blockquoteNode, newIndex] = parseBlockquote(tokens, k, linkifyContext.options());
15474
15945
  itemChildren.push(blockquoteNode);
15946
+ linkifyContext.remember(blockquoteNode.raw);
15475
15947
  k = newIndex;
15476
15948
  } else if (tokens[k].type === "bullet_list_open" || tokens[k].type === "ordered_list_open") {
15477
- const [nestedListNode, newIndex] = parseList(tokens, k, options);
15949
+ const [nestedListNode, newIndex] = parseList(tokens, k, linkifyContext.options());
15478
15950
  itemChildren.push(nestedListNode);
15951
+ linkifyContext.remember(nestedListNode.raw);
15479
15952
  k = newIndex;
15480
15953
  } else {
15481
- const handled = parseCommonBlockToken(tokens, k, options, containerTokenHandlers);
15954
+ const handled = parseCommonBlockToken(tokens, k, linkifyContext.options(), containerTokenHandlers);
15482
15955
  if (handled) {
15483
15956
  itemChildren.push(handled[0]);
15957
+ linkifyContext.remember(handled[0].raw);
15484
15958
  k = handled[1];
15485
15959
  } else k += 1;
15486
15960
  }
@@ -15514,27 +15988,35 @@ function parseAdmonition(tokens, index, match, options) {
15514
15988
  const kind = String(match[1] ?? "note");
15515
15989
  const title = String(match[2] ?? kind.charAt(0).toUpperCase() + kind.slice(1));
15516
15990
  const admonitionChildren = [];
15991
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15517
15992
  let j = index + 1;
15518
15993
  while (j < tokens.length && tokens[j].type !== "container_close") if (tokens[j].type === "paragraph_open") {
15519
15994
  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
- });
15995
+ if (contentToken) {
15996
+ const paragraphNode = {
15997
+ type: "paragraph",
15998
+ children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), void 0, linkifyContext.options()),
15999
+ raw: String(contentToken.content ?? "")
16000
+ };
16001
+ admonitionChildren.push(paragraphNode);
16002
+ linkifyContext.remember(paragraphNode.raw);
16003
+ }
15525
16004
  j += 3;
15526
16005
  } else if (tokens[j].type === "bullet_list_open" || tokens[j].type === "ordered_list_open") {
15527
- const [listNode, newIndex] = parseList(tokens, j, options);
16006
+ const [listNode, newIndex] = parseList(tokens, j, linkifyContext.options());
15528
16007
  admonitionChildren.push(listNode);
16008
+ linkifyContext.remember(listNode.raw);
15529
16009
  j = newIndex;
15530
16010
  } else if (tokens[j].type === "blockquote_open") {
15531
- const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, options);
16011
+ const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, linkifyContext.options());
15532
16012
  admonitionChildren.push(blockquoteNode);
16013
+ linkifyContext.remember(blockquoteNode.raw);
15533
16014
  j = newIndex;
15534
16015
  } else {
15535
- const handled = parseBasicBlockToken(tokens, j, options);
16016
+ const handled = parseBasicBlockToken(tokens, j, linkifyContext.options());
15536
16017
  if (handled) {
15537
16018
  admonitionChildren.push(handled[0]);
16019
+ linkifyContext.remember(handled[0].raw);
15538
16020
  j = handled[1];
15539
16021
  } else j++;
15540
16022
  }
@@ -15594,6 +16076,7 @@ function parseContainer(tokens, index, options) {
15594
16076
  }
15595
16077
  if (!title) title = kind.charAt(0).toUpperCase() + kind.slice(1);
15596
16078
  const children = [];
16079
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15597
16080
  let j = index + 1;
15598
16081
  const closeType = /* @__PURE__ */ new RegExp(`^container_${kind}_close$`);
15599
16082
  while (j < tokens.length && tokens[j].type !== "container_close" && !closeType.test(tokens[j].type)) if (tokens[j].type === "paragraph_open") {
@@ -15608,26 +16091,30 @@ function parseContainer(tokens, index, options) {
15608
16091
  break;
15609
16092
  }
15610
16093
  }
15611
- const _children = i !== -1 ? childrenArr.slice(0, i) : childrenArr;
15612
- children.push({
16094
+ const paragraphNode = {
15613
16095
  type: "paragraph",
15614
- children: parseInlineTokens(_children || [], void 0, void 0, options),
16096
+ children: parseInlineTokens((i !== -1 ? childrenArr.slice(0, i) : childrenArr) || [], void 0, void 0, linkifyContext.options()),
15615
16097
  raw: String(contentToken.content ?? "").replace(/\n:+$/, "").replace(/\n\s*:::\s*$/, "")
15616
- });
16098
+ };
16099
+ children.push(paragraphNode);
16100
+ linkifyContext.remember(paragraphNode.raw);
15617
16101
  }
15618
16102
  j += 3;
15619
16103
  } else if (tokens[j].type === "bullet_list_open" || tokens[j].type === "ordered_list_open") {
15620
- const [listNode, newIndex] = parseList(tokens, j, options);
16104
+ const [listNode, newIndex] = parseList(tokens, j, linkifyContext.options());
15621
16105
  children.push(listNode);
16106
+ linkifyContext.remember(listNode.raw);
15622
16107
  j = newIndex;
15623
16108
  } else if (tokens[j].type === "blockquote_open") {
15624
- const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, options);
16109
+ const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, linkifyContext.options());
15625
16110
  children.push(blockquoteNode);
16111
+ linkifyContext.remember(blockquoteNode.raw);
15626
16112
  j = newIndex;
15627
16113
  } else {
15628
- const handled = parseBasicBlockToken(tokens, j, options);
16114
+ const handled = parseBasicBlockToken(tokens, j, linkifyContext.options());
15629
16115
  if (handled) {
15630
16116
  children.push(handled[0]);
16117
+ linkifyContext.remember(handled[0].raw);
15631
16118
  j = handled[1];
15632
16119
  } else j++;
15633
16120
  }
@@ -15659,35 +16146,41 @@ const containerTokenHandlers = {
15659
16146
  //#region src/parser/node-parsers/blockquote-parser.ts
15660
16147
  function parseBlockquote(tokens, index, options) {
15661
16148
  const blockquoteChildren = [];
16149
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15662
16150
  let j = index + 1;
15663
16151
  while (j < tokens.length && tokens[j].type !== "blockquote_close") switch (tokens[j].type) {
15664
16152
  case "paragraph_open": {
15665
16153
  const contentToken = tokens[j + 1];
15666
- blockquoteChildren.push({
16154
+ const paragraphNode = {
15667
16155
  type: "paragraph",
15668
- children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), void 0, options),
16156
+ children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), void 0, linkifyContext.options()),
15669
16157
  raw: String(contentToken.content ?? "")
15670
- });
16158
+ };
16159
+ blockquoteChildren.push(paragraphNode);
16160
+ linkifyContext.remember(paragraphNode.raw);
15671
16161
  j += 3;
15672
16162
  break;
15673
16163
  }
15674
16164
  case "bullet_list_open":
15675
16165
  case "ordered_list_open": {
15676
- const [listNode, newIndex] = parseList(tokens, j, options);
16166
+ const [listNode, newIndex] = parseList(tokens, j, linkifyContext.options());
15677
16167
  blockquoteChildren.push(listNode);
16168
+ linkifyContext.remember(listNode.raw);
15678
16169
  j = newIndex;
15679
16170
  break;
15680
16171
  }
15681
16172
  case "blockquote_open": {
15682
- const [nestedBlockquote, newIndex] = parseBlockquote(tokens, j, options);
16173
+ const [nestedBlockquote, newIndex] = parseBlockquote(tokens, j, linkifyContext.options());
15683
16174
  blockquoteChildren.push(nestedBlockquote);
16175
+ linkifyContext.remember(nestedBlockquote.raw);
15684
16176
  j = newIndex;
15685
16177
  break;
15686
16178
  }
15687
16179
  default: {
15688
- const handled = parseCommonBlockToken(tokens, j, options, containerTokenHandlers);
16180
+ const handled = parseCommonBlockToken(tokens, j, linkifyContext.options(), containerTokenHandlers);
15689
16181
  if (handled) {
15690
16182
  blockquoteChildren.push(handled[0]);
16183
+ linkifyContext.remember(handled[0].raw);
15691
16184
  j = handled[1];
15692
16185
  } else j++;
15693
16186
  break;
@@ -15706,13 +16199,14 @@ function parseCodeBlock(token) {
15706
16199
  if (token.info?.startsWith("diff")) return parseFenceToken(token);
15707
16200
  const contentStr = String(token.content ?? "");
15708
16201
  const match = contentStr.match(/ type="application\/vnd\.ant\.([^"]+)"/);
15709
- if (match?.[1]) token.content = contentStr.replace(/<antArtifact[^>]*>/g, "").replace(/<\/antArtifact>/g, "");
16202
+ let code$1 = contentStr;
16203
+ if (match?.[1]) code$1 = contentStr.replace(/<antArtifact[^>]*>/g, "").replace(/<\/antArtifact>/g, "");
15710
16204
  const hasMap = Array.isArray(token.map) && token.map.length === 2;
15711
16205
  return {
15712
16206
  type: "code_block",
15713
16207
  language: match ? match[1] : String(token.info ?? ""),
15714
- code: String(token.content ?? ""),
15715
- raw: String(token.content ?? ""),
16208
+ code: code$1,
16209
+ raw: code$1,
15716
16210
  loading: !hasMap
15717
16211
  };
15718
16212
  }
@@ -15724,9 +16218,11 @@ function parseDefinitionList(tokens, index, options) {
15724
16218
  let j = index + 1;
15725
16219
  let termNodes = [];
15726
16220
  let definitionNodes = [];
16221
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15727
16222
  while (j < tokens.length && tokens[j].type !== "dl_close") if (tokens[j].type === "dt_open") {
15728
16223
  const termToken = tokens[j + 1];
15729
- termNodes = parseInlineTokens(termToken.children || [], void 0, void 0, options);
16224
+ termNodes = parseInlineTokens(termToken.children || [], void 0, void 0, linkifyContext.options());
16225
+ linkifyContext.remember(termNodes.map((term) => term.raw).join(""));
15730
16226
  j += 3;
15731
16227
  } else if (tokens[j].type === "dd_open") {
15732
16228
  let k = j + 1;
@@ -15735,9 +16231,10 @@ function parseDefinitionList(tokens, index, options) {
15735
16231
  const contentToken = tokens[k + 1];
15736
16232
  definitionNodes.push({
15737
16233
  type: "paragraph",
15738
- children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), void 0, options),
16234
+ children: parseInlineTokens(contentToken.children || [], String(contentToken.content ?? ""), void 0, linkifyContext.options()),
15739
16235
  raw: String(contentToken.content ?? "")
15740
16236
  });
16237
+ linkifyContext.remember(String(contentToken.content ?? ""));
15741
16238
  k += 3;
15742
16239
  } else k++;
15743
16240
  if (termNodes.length > 0) {
@@ -15764,16 +16261,19 @@ function parseFootnote(tokens, index, options) {
15764
16261
  const meta = tokens[index].meta ?? {};
15765
16262
  const id = String(meta?.label ?? "0");
15766
16263
  const footnoteChildren = [];
16264
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
15767
16265
  let j = index + 1;
15768
16266
  while (j < tokens.length && tokens[j].type !== "footnote_close") if (tokens[j].type === "paragraph_open") {
15769
16267
  const contentToken = tokens[j + 1];
15770
16268
  const children = contentToken.children ? [...contentToken.children] : [];
15771
16269
  if (tokens[j + 2].type === "footnote_anchor") children.push(tokens[j + 2]);
15772
- footnoteChildren.push({
16270
+ const paragraphNode = {
15773
16271
  type: "paragraph",
15774
- children: parseInlineTokens(children, String(contentToken.content ?? ""), void 0, options),
16272
+ children: parseInlineTokens(children, String(contentToken.content ?? ""), void 0, linkifyContext.options()),
15775
16273
  raw: String(contentToken.content ?? "")
15776
- });
16274
+ };
16275
+ footnoteChildren.push(paragraphNode);
16276
+ linkifyContext.remember(paragraphNode.raw);
15777
16277
  j += 3;
15778
16278
  } else j++;
15779
16279
  return [{
@@ -15900,6 +16400,30 @@ function extractAlign(attrs) {
15900
16400
  }
15901
16401
  return "left";
15902
16402
  }
16403
+ function hasTableCellContext(context) {
16404
+ return context?.filename === true || context?.explicitFilename === true || context?.marketTicker === true;
16405
+ }
16406
+ function mergeTableCellContext(left, right) {
16407
+ const merged = {
16408
+ filename: left?.filename || right?.filename,
16409
+ explicitFilename: left?.explicitFilename || right?.explicitFilename,
16410
+ marketTicker: left?.marketTicker || right?.marketTicker
16411
+ };
16412
+ return hasTableCellContext(merged) ? merged : void 0;
16413
+ }
16414
+ function parseOptionsForTableCell(options, headerRaw, rowContext) {
16415
+ const cellContext = mergeTableCellContext(inferLinkifyDemotionContext(headerRaw), rowContext);
16416
+ if (!hasTableCellContext(cellContext)) return options;
16417
+ const inheritedContext = options?.__linkifyDemotionContext;
16418
+ return {
16419
+ ...options,
16420
+ __linkifyDemotionContext: {
16421
+ filename: inheritedContext?.filename || cellContext?.filename,
16422
+ explicitFilename: inheritedContext?.explicitFilename || cellContext?.explicitFilename,
16423
+ marketTicker: inheritedContext?.marketTicker || cellContext?.marketTicker
16424
+ }
16425
+ };
16426
+ }
15903
16427
  function parseTable(tokens, index, options) {
15904
16428
  let j = index + 1;
15905
16429
  let headerRow = null;
@@ -15915,18 +16439,23 @@ function parseTable(tokens, index, options) {
15915
16439
  else if (tokens[j].type === "tr_open") {
15916
16440
  const cells = [];
15917
16441
  let k = j + 1;
16442
+ let rowContext;
15918
16443
  while (k < tokens.length && tokens[k].type !== "tr_close") if (tokens[k].type === "th_open" || tokens[k].type === "td_open") {
15919
16444
  const isHeaderCell = tokens[k].type === "th_open";
15920
16445
  const contentToken = tokens[k + 1];
15921
16446
  const content = String(contentToken.content ?? "");
15922
16447
  const align = extractAlign(tokens[k].attrs);
16448
+ const cellIndex = cells.length;
16449
+ const isBodyCell = !isHeaderCell && !isHeader;
16450
+ const headerRaw = isBodyCell ? headerRow?.cells[cellIndex]?.raw : void 0;
15923
16451
  cells.push({
15924
16452
  type: "table_cell",
15925
16453
  header: isHeaderCell || isHeader,
15926
- children: parseInlineTokens(contentToken.children || [], content, void 0, options),
16454
+ children: parseInlineTokens(contentToken.children || [], content, void 0, parseOptionsForTableCell(options, headerRaw, isBodyCell ? rowContext : void 0)),
15927
16455
  raw: content,
15928
16456
  align
15929
16457
  });
16458
+ if (isBodyCell) rowContext = mergeTableCellContext(rowContext, inferLinkifyDemotionContext(content));
15930
16459
  k += 3;
15931
16460
  } else k++;
15932
16461
  const rowNode = {
@@ -15943,11 +16472,12 @@ function parseTable(tokens, index, options) {
15943
16472
  cells: [],
15944
16473
  raw: ""
15945
16474
  };
16475
+ const tokenLoading = tokens[index].loading === true;
15946
16476
  return [{
15947
16477
  type: "table",
15948
16478
  header: headerRow,
15949
16479
  rows,
15950
- loading: tokens[index].loading ?? false,
16480
+ loading: tokenLoading && !options?.final && rows.length === 0,
15951
16481
  raw: [headerRow, ...rows].map((row) => row.raw).join("\n")
15952
16482
  }, j + 1];
15953
16483
  }
@@ -16008,30 +16538,35 @@ function parseVmrContainer(tokens, index, options) {
16008
16538
  }
16009
16539
  }
16010
16540
  const children = [];
16541
+ const linkifyContext = createLinkifyDemotionContextTracker(options, true);
16011
16542
  let j = index + 1;
16012
16543
  while (j < tokens.length && tokens[j].type !== "vmr_container_close") if (tokens[j].type === "paragraph_open") {
16013
16544
  const contentToken = tokens[j + 1];
16014
16545
  if (contentToken) {
16015
- const childrenArr = contentToken.children || [];
16016
- children.push({
16546
+ const paragraphNode = {
16017
16547
  type: "paragraph",
16018
- children: parseInlineTokens(childrenArr || [], void 0, void 0, options),
16548
+ children: parseInlineTokens(contentToken.children || [], void 0, void 0, linkifyContext.options()),
16019
16549
  raw: String(contentToken.content ?? "")
16020
- });
16550
+ };
16551
+ children.push(paragraphNode);
16552
+ linkifyContext.remember(paragraphNode.raw);
16021
16553
  }
16022
16554
  j += 3;
16023
16555
  } else if (tokens[j].type === "bullet_list_open" || tokens[j].type === "ordered_list_open") {
16024
- const [listNode, newIndex] = parseList(tokens, j, options);
16556
+ const [listNode, newIndex] = parseList(tokens, j, linkifyContext.options());
16025
16557
  children.push(listNode);
16558
+ linkifyContext.remember(listNode.raw);
16026
16559
  j = newIndex;
16027
16560
  } else if (tokens[j].type === "blockquote_open") {
16028
- const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, options);
16561
+ const [blockquoteNode, newIndex] = parseBlockquote(tokens, j, linkifyContext.options());
16029
16562
  children.push(blockquoteNode);
16563
+ linkifyContext.remember(blockquoteNode.raw);
16030
16564
  j = newIndex;
16031
16565
  } else {
16032
- const handled = parseBasicBlockToken(tokens, j, options);
16566
+ const handled = parseBasicBlockToken(tokens, j, linkifyContext.options());
16033
16567
  if (handled) {
16034
16568
  children.push(handled[0]);
16569
+ linkifyContext.remember(handled[0].raw);
16035
16570
  j = handled[1];
16036
16571
  } else j++;
16037
16572
  }
@@ -16297,15 +16832,201 @@ function parseParagraph(tokens, index, options) {
16297
16832
 
16298
16833
  //#endregion
16299
16834
  //#region src/parser/index.ts
16835
+ const streamParseEnvCache = /* @__PURE__ */ new WeakMap();
16300
16836
  function getNodeFields(node) {
16301
16837
  return node;
16302
16838
  }
16839
+ function getParserNow() {
16840
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
16841
+ }
16842
+ function addTiming(metrics, key, value) {
16843
+ if (!metrics) return;
16844
+ metrics[key] = (metrics[key] ?? 0) + value;
16845
+ }
16846
+ function getParseTiming(options) {
16847
+ return options.__timing;
16848
+ }
16849
+ function finishTimedParse(result, timing, startedAt) {
16850
+ if (timing) addTiming(timing, "parseMarkdownToStructureTotalMs", getParserNow() - startedAt);
16851
+ return result;
16852
+ }
16853
+ function processTokensWithTiming(tokens, options, timing) {
16854
+ if (!timing) return processTokens(tokens, options);
16855
+ const startedAt = getParserNow();
16856
+ const result = processTokens(tokens, options);
16857
+ addTiming(timing, "processTokensMs", getParserNow() - startedAt);
16858
+ return result;
16859
+ }
16303
16860
  function getCustomHtmlTagSet(options) {
16304
16861
  const custom = options?.customHtmlTags;
16305
16862
  if (!Array.isArray(custom) || custom.length === 0) return null;
16306
16863
  const normalized = normalizeCustomHtmlTags(custom);
16307
16864
  return normalized.length ? new Set(normalized) : null;
16308
16865
  }
16866
+ function getStableStreamEnv(md, env) {
16867
+ const mdKey = md;
16868
+ let byMode = streamParseEnvCache.get(mdKey);
16869
+ if (!byMode) {
16870
+ byMode = /* @__PURE__ */ new Map();
16871
+ streamParseEnvCache.set(mdKey, byMode);
16872
+ }
16873
+ const modeKey = env.__markstreamFinal === true ? "final" : "streaming";
16874
+ let stableEnv = byMode.get(modeKey);
16875
+ if (!stableEnv) {
16876
+ stableEnv = {};
16877
+ byMode.set(modeKey, stableEnv);
16878
+ }
16879
+ for (const key of Object.keys(stableEnv)) if (!Object.prototype.hasOwnProperty.call(env, key)) delete stableEnv[key];
16880
+ Object.assign(stableEnv, env);
16881
+ return stableEnv;
16882
+ }
16883
+ function isPlainObject(value) {
16884
+ if (!value || typeof value !== "object") return false;
16885
+ const proto = Object.getPrototypeOf(value);
16886
+ return proto === Object.prototype || proto === null;
16887
+ }
16888
+ function copyCloneableOwnDataProperties(source, target, seen) {
16889
+ for (const key of Reflect.ownKeys(source)) {
16890
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
16891
+ if (!descriptor || !("value" in descriptor)) continue;
16892
+ const targetDescriptor = Object.getOwnPropertyDescriptor(target, key);
16893
+ if (targetDescriptor && (!("value" in targetDescriptor) || targetDescriptor.writable === false)) continue;
16894
+ target[key] = safeCloneTokenField(descriptor.value, seen);
16895
+ }
16896
+ }
16897
+ function safeCloneTokenField(value, seen = /* @__PURE__ */ new WeakMap()) {
16898
+ if (!value || typeof value !== "object") return value;
16899
+ const object = value;
16900
+ const existing = seen.get(object);
16901
+ if (existing) return existing;
16902
+ if (Array.isArray(value)) {
16903
+ const cloned$1 = [];
16904
+ seen.set(object, cloned$1);
16905
+ for (const item of value) cloned$1.push(safeCloneTokenField(item, seen));
16906
+ return cloned$1;
16907
+ }
16908
+ if (value instanceof Map) {
16909
+ const cloned$1 = /* @__PURE__ */ new Map();
16910
+ seen.set(object, cloned$1);
16911
+ for (const [key, item] of value) cloned$1.set(safeCloneTokenField(key, seen), safeCloneTokenField(item, seen));
16912
+ return cloned$1;
16913
+ }
16914
+ if (value instanceof Set) {
16915
+ const cloned$1 = /* @__PURE__ */ new Set();
16916
+ seen.set(object, cloned$1);
16917
+ for (const item of value) cloned$1.add(safeCloneTokenField(item, seen));
16918
+ return cloned$1;
16919
+ }
16920
+ if (value instanceof Date) {
16921
+ const cloned$1 = new Date(value.getTime());
16922
+ seen.set(object, cloned$1);
16923
+ return cloned$1;
16924
+ }
16925
+ if (value instanceof RegExp) {
16926
+ const cloned$1 = new RegExp(value.source, value.flags);
16927
+ cloned$1.lastIndex = value.lastIndex;
16928
+ seen.set(object, cloned$1);
16929
+ return cloned$1;
16930
+ }
16931
+ if (typeof URL !== "undefined" && value instanceof URL) {
16932
+ const cloned$1 = new URL(value.href);
16933
+ seen.set(object, cloned$1);
16934
+ copyCloneableOwnDataProperties(object, cloned$1, seen);
16935
+ return cloned$1;
16936
+ }
16937
+ if (typeof URLSearchParams !== "undefined" && value instanceof URLSearchParams) {
16938
+ const cloned$1 = new URLSearchParams(value.toString());
16939
+ seen.set(object, cloned$1);
16940
+ copyCloneableOwnDataProperties(object, cloned$1, seen);
16941
+ return cloned$1;
16942
+ }
16943
+ if (value instanceof Error) {
16944
+ let cloned$1;
16945
+ const ErrorCtor = value.constructor;
16946
+ try {
16947
+ cloned$1 = new ErrorCtor(value.message);
16948
+ } catch {
16949
+ cloned$1 = new Error(value.message);
16950
+ }
16951
+ Object.setPrototypeOf(cloned$1, Object.getPrototypeOf(value));
16952
+ seen.set(object, cloned$1);
16953
+ copyCloneableOwnDataProperties(object, cloned$1, seen);
16954
+ return cloned$1;
16955
+ }
16956
+ if (typeof Promise !== "undefined" && value instanceof Promise) {
16957
+ seen.set(object, value);
16958
+ return value;
16959
+ }
16960
+ if (typeof Node !== "undefined" && value instanceof Node) {
16961
+ seen.set(object, value);
16962
+ return value;
16963
+ }
16964
+ if (!isPlainObject(value)) {
16965
+ const cloned$1 = Object.create(Object.getPrototypeOf(value));
16966
+ seen.set(object, cloned$1);
16967
+ copyCloneableOwnDataProperties(object, cloned$1, seen);
16968
+ return cloned$1;
16969
+ }
16970
+ const cloned = {};
16971
+ seen.set(object, cloned);
16972
+ const record = value;
16973
+ for (const key of Object.keys(record)) cloned[key] = safeCloneTokenField(record[key], seen);
16974
+ return cloned;
16975
+ }
16976
+ function cloneMarkdownToken(token, cloneObjectFields = true) {
16977
+ if (!cloneObjectFields) return cloneTokenWithMutableChildren(token);
16978
+ const cloned = Object.create(Object.getPrototypeOf(token));
16979
+ const seen = /* @__PURE__ */ new WeakMap();
16980
+ for (const key of Reflect.ownKeys(token)) {
16981
+ const descriptor = Object.getOwnPropertyDescriptor(token, key);
16982
+ if (!descriptor) continue;
16983
+ if (!("value" in descriptor)) {
16984
+ Object.defineProperty(cloned, key, descriptor);
16985
+ continue;
16986
+ }
16987
+ const value = descriptor.value;
16988
+ let clonedValue = value;
16989
+ if (key === "attrs" && Array.isArray(value)) clonedValue = value.map((attr) => [...attr]);
16990
+ else if (key === "map" && Array.isArray(value)) clonedValue = [...value];
16991
+ else if (key === "children" && Array.isArray(value)) clonedValue = value.map((child) => cloneMarkdownToken(child, cloneObjectFields));
16992
+ else if (cloneObjectFields && value && typeof value === "object") clonedValue = safeCloneTokenField(value, seen);
16993
+ Object.defineProperty(cloned, key, {
16994
+ ...descriptor,
16995
+ value: clonedValue
16996
+ });
16997
+ }
16998
+ return cloned;
16999
+ }
17000
+ function cloneMarkdownTokens(tokens, cloneObjectFields = true) {
17001
+ return tokens.map((token) => cloneMarkdownToken(token, cloneObjectFields));
17002
+ }
17003
+ function shouldUseTopLevelStreamParse(md, options) {
17004
+ const internalOptions = options;
17005
+ const stream = md.stream;
17006
+ const streamParse = options.streamParse ?? "auto";
17007
+ return internalOptions.__disableStreamParse !== true && (streamParse === true || streamParse === "auto" && options.final !== true) && stream?.enabled === true && typeof stream.parse === "function";
17008
+ }
17009
+ function shouldResetTopLevelStreamCacheForFinalAutoParse(md, options) {
17010
+ const internalOptions = options;
17011
+ const streamParse = options.streamParse ?? "auto";
17012
+ const stream = md.stream;
17013
+ return options.final === true && streamParse === "auto" && internalOptions.__disableStreamParse !== true && stream?.enabled === true && typeof stream.reset === "function";
17014
+ }
17015
+ function shouldCloneTopLevelStreamTokens(options) {
17016
+ return typeof options.preTransformTokens === "function" || typeof options.postTransformTokens === "function";
17017
+ }
17018
+ function parseTopLevelTokens(md, source, env, options) {
17019
+ if (options.customHtmlTags?.length) env.__markstreamCustomHtmlTags = options.customHtmlTags;
17020
+ if (!shouldUseTopLevelStreamParse(md, options)) return md.parse(source, env);
17021
+ const tokens = md.stream.parse(source, getStableStreamEnv(md, env));
17022
+ if (!shouldCloneTopLevelStreamTokens(options)) return tokens;
17023
+ const timing = getParseTiming(options);
17024
+ if (!timing) return cloneMarkdownTokens(tokens, true);
17025
+ const startedAt = getParserNow();
17026
+ const cloned = cloneMarkdownTokens(tokens, true);
17027
+ addTiming(timing, "tokenCloneMs", getParserNow() - startedAt);
17028
+ return cloned;
17029
+ }
16309
17030
  function buildAllowedHtmlTagSet(options) {
16310
17031
  const custom = options?.customHtmlTags;
16311
17032
  if (!Array.isArray(custom) || custom.length === 0) return STANDARD_HTML_TAGS;
@@ -16511,6 +17232,7 @@ function findLastClosingTagStart(raw, tag) {
16511
17232
  function buildDetailsChildParseOptions(options, final) {
16512
17233
  return {
16513
17234
  final,
17235
+ __disableStreamParse: true,
16514
17236
  requireClosingStrong: options.requireClosingStrong,
16515
17237
  customHtmlTags: options.customHtmlTags,
16516
17238
  validateLink: options.validateLink
@@ -16568,7 +17290,10 @@ function structureGenericHtmlBlockChildren(nodes, md, options, final) {
16568
17290
  }
16569
17291
  function parseDetailsFragmentChildren(fragment, md, options) {
16570
17292
  if (!fragment.trim()) return [];
16571
- return parseMarkdownToStructure(fragment, md, options);
17293
+ return parseMarkdownToStructure(fragment, md, {
17294
+ ...options,
17295
+ __disableStreamParse: true
17296
+ });
16572
17297
  }
16573
17298
  function parseSummaryChildren(fragment, md, options) {
16574
17299
  const children = parseDetailsFragmentChildren(fragment, md, options);
@@ -17495,8 +18220,11 @@ function ensureBlankLineBeforeCustomHtmlBlocks(markdown, tags) {
17495
18220
  return out;
17496
18221
  }
17497
18222
  function parseMarkdownToStructure(markdown, md, options = {}) {
18223
+ const timing = getParseTiming(options);
18224
+ const parseStartedAt = timing ? getParserNow() : 0;
17498
18225
  const isFinal = !!options.final;
17499
18226
  let safeMarkdown = (markdown ?? "").toString().replace(/([^\\])\r(ight|ho)/g, "$1\\r$2").replace(/([^\\])\n(abla|eq|ot|exists)/g, "$1\\n$2");
18227
+ if (shouldResetTopLevelStreamCacheForFinalAutoParse(md, options)) md.stream.reset();
17500
18228
  if (!isFinal) {
17501
18229
  if (safeMarkdown.endsWith("- *")) safeMarkdown = safeMarkdown.replace(/- \*$/, "- \\*");
17502
18230
  if (/(?:^|\n)\s*-\s*$/.test(safeMarkdown)) safeMarkdown = safeMarkdown.replace(/(?:^|\n)\s*-\s*$/, (m) => {
@@ -17537,15 +18265,15 @@ function parseMarkdownToStructure(markdown, md, options = {}) {
17537
18265
  if (standaloneHtmlDocument) {
17538
18266
  const preHook = options.preTransformTokens;
17539
18267
  const postHook = options.postTransformTokens;
17540
- if (typeof preHook === "function" || typeof postHook === "function") {
17541
- const rawTokens = md.parse(safeMarkdown, { __markstreamFinal: isFinal });
18268
+ if (shouldUseTopLevelStreamParse(md, options) || typeof preHook === "function" || typeof postHook === "function") {
18269
+ const rawTokens = parseTopLevelTokens(md, safeMarkdown, { __markstreamFinal: isFinal }, options);
17542
18270
  const hookedTokens = typeof preHook === "function" ? preHook(rawTokens) || rawTokens : rawTokens;
17543
18271
  if (typeof postHook === "function") postHook(hookedTokens);
17544
18272
  }
17545
- return standaloneHtmlDocument;
18273
+ return finishTimedParse(standaloneHtmlDocument, timing, parseStartedAt);
17546
18274
  }
17547
- const tokens = md.parse(safeMarkdown, { __markstreamFinal: isFinal });
17548
- if (!tokens || !Array.isArray(tokens)) return [];
18275
+ const tokens = parseTopLevelTokens(md, safeMarkdown, { __markstreamFinal: isFinal }, options);
18276
+ if (!tokens || !Array.isArray(tokens)) return finishTimedParse([], timing, parseStartedAt);
17549
18277
  const pre = options.preTransformTokens;
17550
18278
  const post = options.postTransformTokens;
17551
18279
  let transformedTokens = tokens;
@@ -17559,13 +18287,13 @@ function parseMarkdownToStructure(markdown, md, options = {}) {
17559
18287
  __sourceMarkdown: safeMarkdown,
17560
18288
  __customHtmlBlockCursor: 0
17561
18289
  };
17562
- let result = processTokens(transformedTokens, internalOptions);
18290
+ let result = processTokensWithTiming(transformedTokens, internalOptions, timing);
17563
18291
  if (post && typeof post === "function") {
17564
18292
  const postResult = post(transformedTokens);
17565
18293
  if (Array.isArray(postResult)) {
17566
18294
  const first = postResult[0];
17567
18295
  const firstType = first?.type;
17568
- if (first && typeof firstType === "string") result = processTokens(postResult);
18296
+ if (first && typeof firstType === "string") result = processTokensWithTiming(postResult, void 0, timing);
17569
18297
  else result = postResult;
17570
18298
  }
17571
18299
  }
@@ -17589,39 +18317,45 @@ function parseMarkdownToStructure(markdown, md, options = {}) {
17589
18317
  finalizeHtmlBlockLoading(result);
17590
18318
  }
17591
18319
  if (options.debug) console.log("Parsed Markdown Tree Structure:", result);
17592
- return result;
18320
+ return finishTimedParse(result, timing, parseStartedAt);
17593
18321
  }
17594
18322
  function processTokens(tokens, options) {
17595
18323
  if (!tokens || !Array.isArray(tokens)) return [];
17596
18324
  const result = [];
18325
+ const linkifyContext = createLinkifyDemotionContextTracker(options);
17597
18326
  let i = 0;
17598
18327
  while (i < tokens.length) {
17599
- const handled = parseCommonBlockToken(tokens, i, options, containerTokenHandlers);
18328
+ const handled = parseCommonBlockToken(tokens, i, linkifyContext.options(), containerTokenHandlers);
17600
18329
  if (handled) {
17601
18330
  result.push(handled[0]);
18331
+ linkifyContext.remember(handled[0].raw);
17602
18332
  i = handled[1];
17603
18333
  continue;
17604
18334
  }
17605
18335
  const token = tokens[i];
17606
18336
  switch (token.type) {
17607
18337
  case "paragraph_open": {
17608
- const paragraphNode = parseParagraph(tokens, i, options);
18338
+ const paragraphRaw = String(tokens[i + 1]?.content ?? "");
18339
+ const paragraphNode = parseParagraph(tokens, i, linkifyContext.options(paragraphRaw));
17609
18340
  const promoted = maybePromoteCustomNodeFromParagraph(paragraphNode, options);
17610
18341
  if (promoted) result.push(...promoted);
17611
18342
  else result.push(paragraphNode);
18343
+ linkifyContext.remember(paragraphNode.raw);
17612
18344
  i += 3;
17613
18345
  break;
17614
18346
  }
17615
18347
  case "bullet_list_open":
17616
18348
  case "ordered_list_open": {
17617
- const [listNode, newIndex] = parseList(tokens, i, options);
18349
+ const [listNode, newIndex] = parseList(tokens, i, linkifyContext.options());
17618
18350
  result.push(listNode);
18351
+ linkifyContext.remember(listNode.raw);
17619
18352
  i = newIndex;
17620
18353
  break;
17621
18354
  }
17622
18355
  case "blockquote_open": {
17623
- const [blockquoteNode, newIndex] = parseBlockquote(tokens, i, options);
18356
+ const [blockquoteNode, newIndex] = parseBlockquote(tokens, i, linkifyContext.options());
17624
18357
  result.push(blockquoteNode);
18358
+ linkifyContext.remember(blockquoteNode.raw);
17625
18359
  i = newIndex;
17626
18360
  break;
17627
18361
  }
@@ -17633,11 +18367,13 @@ function processTokens(tokens, options) {
17633
18367
  id,
17634
18368
  raw: String(token.content ?? "")
17635
18369
  });
18370
+ linkifyContext.remember(String(token.content ?? ""));
17636
18371
  i++;
17637
18372
  break;
17638
18373
  }
17639
18374
  case "hardbreak":
17640
18375
  result.push(parseHardBreak());
18376
+ linkifyContext.reset();
17641
18377
  i++;
17642
18378
  break;
17643
18379
  case "text": {
@@ -17651,23 +18387,26 @@ function processTokens(tokens, options) {
17651
18387
  raw: content
17652
18388
  }] : []
17653
18389
  });
18390
+ linkifyContext.remember(content);
17654
18391
  i++;
17655
18392
  break;
17656
18393
  }
17657
18394
  case "inline":
17658
18395
  {
17659
- const parsed = parseInlineTokens(token.children || [], String(token.content ?? ""), void 0, options);
18396
+ const raw = String(token.content ?? "");
18397
+ const parsed = parseInlineTokens(token.children || [], raw, void 0, linkifyContext.options(raw));
17660
18398
  if (parsed.length === 0) {} else if (parsed.every((n) => n.type === "html_block")) result.push(...parsed);
17661
18399
  else {
17662
18400
  const paragraphNode = {
17663
18401
  type: "paragraph",
17664
- raw: String(token.content ?? ""),
18402
+ raw,
17665
18403
  children: parsed
17666
18404
  };
17667
18405
  const promoted = maybePromoteCustomNodeFromParagraph(paragraphNode, options);
17668
18406
  if (promoted) result.push(...promoted);
17669
18407
  else result.push(paragraphNode);
17670
18408
  }
18409
+ linkifyContext.remember(raw);
17671
18410
  }
17672
18411
  i += 1;
17673
18412
  break;
@@ -18554,7 +19293,8 @@ function getMarkdown(msgId = `editor-${Date.now()}`, options = {}) {
18554
19293
  md.use(sub_plugin);
18555
19294
  md.use(sup_plugin);
18556
19295
  md.use(ins_plugin$1);
18557
- const markdownItCheckboxPlugin = import_markdown_it_task_checkbox.default ?? import_markdown_it_task_checkbox;
19296
+ const checkboxModule = import_markdown_it_task_checkbox;
19297
+ const markdownItCheckboxPlugin = checkboxModule.default ?? checkboxModule;
18558
19298
  md.use(markdownItCheckboxPlugin);
18559
19299
  md.use(ins_plugin);
18560
19300
  md.use(footnote_plugin);