stream-markdown-parser 0.0.77 → 0.0.79

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
@@ -9409,8 +9409,8 @@ function applyContainers(md) {
9409
9409
  }
9410
9410
 
9411
9411
  //#endregion
9412
- //#region src/plugins/fixHtmlInline.ts
9413
- const VOID_TAGS$2 = new Set([
9412
+ //#region src/htmlTags.ts
9413
+ const VOID_HTML_TAG_NAMES = [
9414
9414
  "area",
9415
9415
  "base",
9416
9416
  "br",
@@ -9425,9 +9425,8 @@ const VOID_TAGS$2 = new Set([
9425
9425
  "source",
9426
9426
  "track",
9427
9427
  "wbr"
9428
- ]);
9429
- const BASE_COMMON_HTML_TAGS = new Set([
9430
- ...Array.from(VOID_TAGS$2),
9428
+ ];
9429
+ const INLINE_HTML_TAG_NAMES = [
9431
9430
  "a",
9432
9431
  "abbr",
9433
9432
  "b",
@@ -9442,8 +9441,6 @@ const BASE_COMMON_HTML_TAGS = new Set([
9442
9441
  "em",
9443
9442
  "font",
9444
9443
  "i",
9445
- "img",
9446
- "input",
9447
9444
  "ins",
9448
9445
  "kbd",
9449
9446
  "label",
@@ -9458,12 +9455,14 @@ const BASE_COMMON_HTML_TAGS = new Set([
9458
9455
  "sup",
9459
9456
  "time",
9460
9457
  "u",
9461
- "var",
9458
+ "var"
9459
+ ];
9460
+ const BLOCK_HTML_TAG_NAMES = [
9462
9461
  "article",
9463
9462
  "aside",
9464
9463
  "blockquote",
9465
- "div",
9466
9464
  "details",
9465
+ "div",
9467
9466
  "figcaption",
9468
9467
  "figure",
9469
9468
  "footer",
@@ -9488,11 +9487,127 @@ const BASE_COMMON_HTML_TAGS = new Set([
9488
9487
  "th",
9489
9488
  "thead",
9490
9489
  "tr",
9491
- "ul",
9490
+ "ul"
9491
+ ];
9492
+ const SVG_HTML_TAG_NAMES = [
9492
9493
  "svg",
9493
9494
  "g",
9494
9495
  "path"
9496
+ ];
9497
+ const EXTENDED_STANDARD_HTML_TAG_NAMES = [
9498
+ "address",
9499
+ "audio",
9500
+ "body",
9501
+ "canvas",
9502
+ "caption",
9503
+ "colgroup",
9504
+ "datalist",
9505
+ "dd",
9506
+ "dialog",
9507
+ "dl",
9508
+ "dt",
9509
+ "fieldset",
9510
+ "form",
9511
+ "head",
9512
+ "hgroup",
9513
+ "html",
9514
+ "iframe",
9515
+ "legend",
9516
+ "map",
9517
+ "menu",
9518
+ "meter",
9519
+ "noscript",
9520
+ "object",
9521
+ "optgroup",
9522
+ "option",
9523
+ "output",
9524
+ "picture",
9525
+ "progress",
9526
+ "rp",
9527
+ "rt",
9528
+ "ruby",
9529
+ "script",
9530
+ "select",
9531
+ "style",
9532
+ "template",
9533
+ "textarea",
9534
+ "tfoot",
9535
+ "title",
9536
+ "video"
9537
+ ];
9538
+ const DANGEROUS_HTML_ATTR_NAMES = [
9539
+ "onclick",
9540
+ "onerror",
9541
+ "onload",
9542
+ "onmouseover",
9543
+ "onmouseout",
9544
+ "onmousedown",
9545
+ "onmouseup",
9546
+ "onkeydown",
9547
+ "onkeyup",
9548
+ "onfocus",
9549
+ "onblur",
9550
+ "onsubmit",
9551
+ "onreset",
9552
+ "onchange",
9553
+ "onselect",
9554
+ "ondblclick",
9555
+ "ontouchstart",
9556
+ "ontouchend",
9557
+ "ontouchmove",
9558
+ "ontouchcancel",
9559
+ "onwheel",
9560
+ "onscroll",
9561
+ "oncopy",
9562
+ "oncut",
9563
+ "onpaste",
9564
+ "oninput",
9565
+ "oninvalid",
9566
+ "onsearch"
9567
+ ];
9568
+ const URL_HTML_ATTR_NAMES = [
9569
+ "href",
9570
+ "src",
9571
+ "srcset",
9572
+ "xlink:href",
9573
+ "formaction"
9574
+ ];
9575
+ const BLOCKED_HTML_TAG_NAMES = ["script"];
9576
+ const VOID_HTML_TAGS = new Set(VOID_HTML_TAG_NAMES);
9577
+ const STANDARD_BLOCK_HTML_TAGS = new Set(BLOCK_HTML_TAG_NAMES);
9578
+ const STANDARD_HTML_TAGS = new Set([
9579
+ ...VOID_HTML_TAG_NAMES,
9580
+ ...INLINE_HTML_TAG_NAMES,
9581
+ ...BLOCK_HTML_TAG_NAMES,
9582
+ ...SVG_HTML_TAG_NAMES
9495
9583
  ]);
9584
+ const EXTENDED_STANDARD_HTML_TAGS = new Set([...STANDARD_HTML_TAGS, ...EXTENDED_STANDARD_HTML_TAG_NAMES]);
9585
+ const DANGEROUS_HTML_ATTRS = new Set(DANGEROUS_HTML_ATTR_NAMES);
9586
+ const URL_HTML_ATTRS = new Set(URL_HTML_ATTR_NAMES);
9587
+ const BLOCKED_HTML_TAGS = new Set(BLOCKED_HTML_TAG_NAMES);
9588
+ function stripHtmlControlAndWhitespace(value) {
9589
+ let out = "";
9590
+ for (const ch of value) {
9591
+ const code$1 = ch.charCodeAt(0);
9592
+ if (code$1 <= 31 || code$1 === 127) continue;
9593
+ if (/\s/u.test(ch)) continue;
9594
+ out += ch;
9595
+ }
9596
+ return out;
9597
+ }
9598
+ function isUnsafeHtmlUrl(value) {
9599
+ const normalized = stripHtmlControlAndWhitespace(value).toLowerCase();
9600
+ if (normalized.startsWith("javascript:") || normalized.startsWith("vbscript:")) return true;
9601
+ if (normalized.startsWith("data:")) return !(normalized.startsWith("data:image/") || normalized.startsWith("data:video/") || normalized.startsWith("data:audio/"));
9602
+ return false;
9603
+ }
9604
+
9605
+ //#endregion
9606
+ //#region src/plugins/fixHtmlInline.ts
9607
+ const VOID_TAGS = VOID_HTML_TAGS;
9608
+ const BASE_COMMON_HTML_TAGS = STANDARD_HTML_TAGS;
9609
+ const BLOCK_LEVEL_HTML_TAGS = new Set(STANDARD_BLOCK_HTML_TAGS);
9610
+ BLOCK_LEVEL_HTML_TAGS.delete("details");
9496
9611
  const OPEN_TAG_RE = /<([A-Z][\w-]*)(?=[\s/>]|$)/gi;
9497
9612
  const CLOSE_TAG_RE = /<\/\s*([A-Z][\w-]*)(?=[\s/>]|$)/gi;
9498
9613
  const TAG_NAME_AT_START_RE = /^<\s*(?:\/\s*)?([A-Z][\w-]*)/i;
@@ -9504,9 +9619,9 @@ function isHtmlInlineClosingTag(content) {
9504
9619
  return /^\s*<\s*\//.test(content);
9505
9620
  }
9506
9621
  function isSelfClosingHtmlInline(content, tag) {
9507
- return VOID_TAGS$2.has(tag) || /\/\s*>\s*$/.test(content);
9622
+ return VOID_TAGS.has(tag) || /\/\s*>\s*$/.test(content);
9508
9623
  }
9509
- function escapeRegex$1(value) {
9624
+ function escapeRegex$2(value) {
9510
9625
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9511
9626
  }
9512
9627
  function findMatchingCloseChildIndex(children, tag) {
@@ -9542,7 +9657,7 @@ function getTrailingOpenDepth(children, tag) {
9542
9657
  return depth;
9543
9658
  }
9544
9659
  function findMatchingCloseRangeInHtml(content, tag, startIndex = 0) {
9545
- const tokenRe = new RegExp(String.raw`<\s*(\/?)\s*${escapeRegex$1(tag)}(?=[\s>/])[^>]*>`, "gi");
9660
+ const tokenRe = new RegExp(String.raw`<\s*(\/?)\s*${escapeRegex$2(tag)}(?=[\s>/])[^>]*>`, "gi");
9546
9661
  tokenRe.lastIndex = Math.max(0, startIndex);
9547
9662
  let depth = 0;
9548
9663
  let match;
@@ -9562,13 +9677,8 @@ function findMatchingCloseRangeInHtml(content, tag, startIndex = 0) {
9562
9677
  }
9563
9678
  return null;
9564
9679
  }
9565
- function findMatchingCloseOffsetInHtml(content, tag, startIndex = 0) {
9566
- const range = findMatchingCloseRangeInHtml(content, tag, startIndex);
9567
- if (!range) return -1;
9568
- return range.end;
9569
- }
9570
9680
  function getTrailingCustomTagDepthInHtml(content, tag) {
9571
- const tokenRe = new RegExp(String.raw`<\s*(\/?)\s*${escapeRegex$1(tag)}(?=[\s>/])[^>]*>`, "gi");
9681
+ const tokenRe = new RegExp(String.raw`<\s*(\/?)\s*${escapeRegex$2(tag)}(?=[\s>/])[^>]*>`, "gi");
9572
9682
  let depth = 0;
9573
9683
  let match;
9574
9684
  while ((match = tokenRe.exec(content)) !== null) {
@@ -9583,7 +9693,7 @@ function getTrailingCustomTagDepthInHtml(content, tag) {
9583
9693
  }
9584
9694
  return depth;
9585
9695
  }
9586
- function findTagCloseIndexOutsideQuotes$2(html) {
9696
+ function findTagCloseIndexOutsideQuotes$3(html) {
9587
9697
  let inSingle = false;
9588
9698
  let inDouble = false;
9589
9699
  for (let i = 0; i < html.length; i++) {
@@ -9627,7 +9737,7 @@ function findFirstIncompleteTag(content, tagSet) {
9627
9737
  if (idx < 0) continue;
9628
9738
  const tag = (m[1] ?? "").toLowerCase();
9629
9739
  if (!isCommonHtmlTagOrPrefix(tag, tagSet)) continue;
9630
- if (findTagCloseIndexOutsideQuotes$2(content.slice(idx)) !== -1) continue;
9740
+ if (findTagCloseIndexOutsideQuotes$3(content.slice(idx)) !== -1) continue;
9631
9741
  if (!first || idx < first.index) first = {
9632
9742
  index: idx,
9633
9743
  tag,
@@ -9639,7 +9749,7 @@ function findFirstIncompleteTag(content, tagSet) {
9639
9749
  if (idx < 0) continue;
9640
9750
  const tag = (m[1] ?? "").toLowerCase();
9641
9751
  if (!isCommonHtmlTagOrPrefix(tag, tagSet)) continue;
9642
- if (findTagCloseIndexOutsideQuotes$2(content.slice(idx)) !== -1) continue;
9752
+ if (findTagCloseIndexOutsideQuotes$3(content.slice(idx)) !== -1) continue;
9643
9753
  if (!first || idx < first.index) first = {
9644
9754
  index: idx,
9645
9755
  tag,
@@ -9705,7 +9815,7 @@ function fixStreamingHtmlInlineChildren(children, tagSet) {
9705
9815
  cursor = lt + 1;
9706
9816
  continue;
9707
9817
  }
9708
- const closeIdx = findTagCloseIndexOutsideQuotes$2(sub);
9818
+ const closeIdx = findTagCloseIndexOutsideQuotes$3(sub);
9709
9819
  if (closeIdx === -1) {
9710
9820
  pushTextPart("<", baseToken);
9711
9821
  cursor = lt + 1;
@@ -9743,7 +9853,7 @@ function fixStreamingHtmlInlineChildren(children, tagSet) {
9743
9853
  if (pending) {
9744
9854
  pending.buffer += tokenToRaw$1(child);
9745
9855
  pendingAtEnd = pending.buffer;
9746
- const closeIdx = findTagCloseIndexOutsideQuotes$2(pending.buffer);
9856
+ const closeIdx = findTagCloseIndexOutsideQuotes$3(pending.buffer);
9747
9857
  if (closeIdx === -1) continue;
9748
9858
  const tagChunk = pending.buffer.slice(0, closeIdx + 1);
9749
9859
  const afterChunk = pending.buffer.slice(closeIdx + 1);
@@ -9761,7 +9871,7 @@ function fixStreamingHtmlInlineChildren(children, tagSet) {
9761
9871
  if (child.type === "html_inline") {
9762
9872
  const content = tokenToRaw$1(child);
9763
9873
  const tagName = (content.match(TAG_NAME_AT_START_RE)?.[1] ?? "").toLowerCase();
9764
- if (tagName && tagSet.has(tagName) && findTagCloseIndexOutsideQuotes$2(content) === -1) {
9874
+ if (tagName && tagSet.has(tagName) && findTagCloseIndexOutsideQuotes$3(content) === -1) {
9765
9875
  pending = {
9766
9876
  tag: tagName,
9767
9877
  buffer: content,
@@ -9808,7 +9918,20 @@ function applyFixHtmlInlineTokens(md, options = {}) {
9808
9918
  customTagSet.add(name);
9809
9919
  autoCloseInlineTagSet.add(name);
9810
9920
  }
9811
- const shouldMergeHtmlBlockTag = (tag) => customTagSet.has(tag) || !commonHtmlTags.has(tag);
9921
+ const shouldMergeHtmlBlockTag = (tag) => customTagSet.has(tag) || !commonHtmlTags.has(tag) || BLOCK_LEVEL_HTML_TAGS.has(tag);
9922
+ const getHtmlBlockCarrierContent = (token) => {
9923
+ if (token.type === "html_block") return String(token.content ?? "");
9924
+ if (token.type !== "inline" || !Array.isArray(token.children) || token.children.length !== 1) return "";
9925
+ const onlyChild = token.children[0];
9926
+ if (onlyChild?.type !== "html_block") return "";
9927
+ return String(token.content ?? onlyChild.content ?? "");
9928
+ };
9929
+ const normalizeHtmlBlockCarrier = (token, content) => {
9930
+ token.type = "html_block";
9931
+ token.content = content;
9932
+ token.raw = content;
9933
+ token.children = [];
9934
+ };
9812
9935
  md.core.ruler.after("inline", "fix_html_inline_streaming", (state) => {
9813
9936
  const toks = state.tokens ?? [];
9814
9937
  for (const t of toks) {
@@ -9851,14 +9974,15 @@ function applyFixHtmlInlineTokens(md, options = {}) {
9851
9974
  continue;
9852
9975
  }
9853
9976
  const chunk = String(t.content ?? t.raw ?? "");
9854
- const closeEnd = chunk ? findMatchingCloseOffsetInHtml(chunk, openTag) : -1;
9855
- const isClosingTag$1 = closeEnd !== -1;
9856
9977
  if (chunk) {
9857
9978
  const openToken = toks[openIndex];
9858
- if (closeEnd !== -1) {
9859
- const before = chunk.slice(0, closeEnd);
9860
- const after = chunk.slice(closeEnd);
9861
- openToken.content = `${String(openToken.content || "")}\n${before}`;
9979
+ const mergedContent = `${String(openToken.content || "")}\n${chunk}`;
9980
+ const openEnd = findTagCloseIndexOutsideQuotes$3(mergedContent);
9981
+ const closeRange = openEnd === -1 ? null : findMatchingCloseRangeInHtml(mergedContent, openTag, openEnd + 1);
9982
+ if (closeRange) {
9983
+ const before = mergedContent.slice(0, closeRange.end);
9984
+ const after = mergedContent.slice(closeRange.end);
9985
+ openToken.content = before;
9862
9986
  openToken.loading = false;
9863
9987
  const afterTrimmed = after.replace(/^\s+/, "");
9864
9988
  toks.splice(i, 1);
@@ -9878,20 +10002,20 @@ function applyFixHtmlInlineTokens(md, options = {}) {
9878
10002
  i--;
9879
10003
  continue;
9880
10004
  }
9881
- openToken.content = `${String(openToken.content || "")}\n${chunk}`;
9882
- if (openToken.loading !== false) openToken.loading = !isClosingTag$1;
10005
+ openToken.content = mergedContent;
10006
+ if (openToken.loading !== false) openToken.loading = true;
9883
10007
  }
9884
10008
  toks.splice(i, 1);
9885
10009
  i--;
9886
- if (isClosingTag$1) tagStack.pop();
9887
10010
  continue;
9888
10011
  }
9889
10012
  }
9890
- if (t.type === "html_block") {
9891
- const rawContent = String(t.content || "");
10013
+ const rawContent = getHtmlBlockCarrierContent(t);
10014
+ if (rawContent) {
9892
10015
  const tag = (rawContent.match(/<\s*(?:\/\s*)?([^\s>/]+)/)?.[1] ?? "").toLowerCase();
9893
10016
  const isClosingTag$1 = /^\s*<\s*\//.test(rawContent);
9894
10017
  if (!tag || !shouldMergeHtmlBlockTag(tag)) continue;
10018
+ normalizeHtmlBlockCarrier(t, rawContent);
9895
10019
  if (!isClosingTag$1) {
9896
10020
  if (tag) {
9897
10021
  if (!new RegExp(`^\\s*<\\s*${tag}\\b[^>]*\\/\\s*>`, "i").test(rawContent) && getTrailingCustomTagDepthInHtml(rawContent, tag) > 0) tagStack.push([tag, i]);
@@ -10052,7 +10176,7 @@ function applyFixHtmlInlineTokens(md, options = {}) {
10052
10176
  }
10053
10177
  if (customTagSet.has(tag)) {
10054
10178
  const raw$2 = String(t.content ?? "");
10055
- const openEnd = findTagCloseIndexOutsideQuotes$2(raw$2);
10179
+ const openEnd = findTagCloseIndexOutsideQuotes$3(raw$2);
10056
10180
  const closeRange = openEnd === -1 ? null : findMatchingCloseRangeInHtml(raw$2, tag, openEnd + 1);
10057
10181
  t.loading = !!closeRange ? false : t.loading !== void 0 ? t.loading : true;
10058
10182
  const endTagIndex$1 = closeRange?.start ?? -1;
@@ -10185,7 +10309,7 @@ function applyFixHtmlInlineTokens(md, options = {}) {
10185
10309
  }
10186
10310
  const strictTagName = String(onlyChild.content ?? raw).match(STRICT_OPEN_TAG_NAME_AT_START_RE)?.[1]?.toLowerCase() ?? "";
10187
10311
  if (!strictTagName) continue;
10188
- if (/\/\s*>\s*$/.test(raw) || VOID_TAGS$2.has(strictTagName)) {
10312
+ if (/\/\s*>\s*$/.test(raw) || VOID_TAGS.has(strictTagName)) {
10189
10313
  htmlToken.children = [{
10190
10314
  type: "html_inline",
10191
10315
  content: raw
@@ -11091,6 +11215,35 @@ function createTh(text$1) {
11091
11215
  }
11092
11216
  ];
11093
11217
  }
11218
+ function getPipeRowCells(line, requireTrailingPipe) {
11219
+ if (!line.startsWith("|") || line.includes("\n")) return null;
11220
+ if (requireTrailingPipe && !line.endsWith("|")) return null;
11221
+ const cells = line.slice(1).split("|");
11222
+ if (cells.at(-1) === "") cells.pop();
11223
+ return cells.length > 0 && cells.every((cell) => cell.length > 0) ? cells : null;
11224
+ }
11225
+ function isSingleLineFallbackTable(line) {
11226
+ return getPipeRowCells(line, false) !== null;
11227
+ }
11228
+ function hasTrailingPipeHeaderRow(line) {
11229
+ return getPipeRowCells(line, true) !== null;
11230
+ }
11231
+ function isSeparatorCell(cell) {
11232
+ return /^:?-+:?$/.test(cell.trim());
11233
+ }
11234
+ function isTableSeparatorRow(line) {
11235
+ if (!line.startsWith("|")) return false;
11236
+ const cells = line.slice(1).split("|");
11237
+ if (cells.at(-1) === "") cells.pop();
11238
+ return cells.length > 0 && cells.every(isSeparatorCell);
11239
+ }
11240
+ function isTruncatedSeparatorRow(line) {
11241
+ return line === "|" || line === "|:";
11242
+ }
11243
+ function hasTrailingPipeHeaderRowWithoutColon(line) {
11244
+ const cells = getPipeRowCells(line, true);
11245
+ return cells !== null && cells.every((cell) => !cell.includes(":"));
11246
+ }
11094
11247
  function fixTableTokens(tokens) {
11095
11248
  const fixedTokens = [...tokens];
11096
11249
  if (tokens.length < 3) return fixedTokens;
@@ -11099,7 +11252,8 @@ function fixTableTokens(tokens) {
11099
11252
  if (token.type === "inline") {
11100
11253
  const tcontent = String(token.content ?? "");
11101
11254
  const headerContent = tcontent.split("\n")[0] ?? "";
11102
- if (!tcontent.includes("\n") && /^\|(?:[^|\n]+\|?)+/.test(tcontent)) {
11255
+ const [headerLine = "", separatorLine = "", ...rest] = tcontent.split("\n");
11256
+ if (!tcontent.includes("\n") && isSingleLineFallbackTable(tcontent)) {
11103
11257
  const body = headerContent.slice(1).split("|").map((i$1) => i$1.trim()).filter(Boolean).flatMap((i$1) => createTh(i$1));
11104
11258
  const insert = [
11105
11259
  ...createStart(),
@@ -11107,7 +11261,7 @@ function fixTableTokens(tokens) {
11107
11261
  ...createEnd()
11108
11262
  ];
11109
11263
  fixedTokens.splice(i - 1, 3, ...insert);
11110
- } else if (/^\|(?:[^|\n]+\|)+\n\|(?:\s*:?-+:?\s*\|?)+$/.test(tcontent)) {
11264
+ } else if (tcontent.includes("\n") && rest.length === 0 && hasTrailingPipeHeaderRow(headerLine) && isTableSeparatorRow(separatorLine)) {
11111
11265
  const body = headerContent.slice(1, -1).split("|").map((i$1) => i$1.trim()).flatMap((i$1) => createTh(i$1));
11112
11266
  const insert = [
11113
11267
  ...createStart(),
@@ -11115,7 +11269,7 @@ function fixTableTokens(tokens) {
11115
11269
  ...createEnd()
11116
11270
  ];
11117
11271
  fixedTokens.splice(i - 1, 3, ...insert);
11118
- } else if (/^\|(?:[^|\n:]+\|)+\n\|:?$/.test(tcontent)) {
11272
+ } else if (tcontent.includes("\n") && rest.length === 0 && hasTrailingPipeHeaderRowWithoutColon(headerLine) && isTruncatedSeparatorRow(separatorLine)) {
11119
11273
  token.content = tcontent.slice(0, -2);
11120
11274
  token.children.splice(2, 1);
11121
11275
  }
@@ -11201,24 +11355,27 @@ const TEX_BRACE_COMMANDS = [
11201
11355
  const ESCAPED_TEX_BRACE_COMMANDS = TEX_BRACE_COMMANDS.map((c) => c.replace(/[.*+?^${}()|[\\]"\]/g, "\\$&")).join("|");
11202
11356
  const TEX_CMD_RE = /\\[a-z]+/i;
11203
11357
  const PREFIX_CLASS = "(?:\\\\|\\u0008)";
11204
- const TEX_CMD_WITH_BRACES_RE = new RegExp(`${PREFIX_CLASS}(?:${ESCAPED_TEX_BRACE_COMMANDS})\\s*\\{[^}]+\\}`, "i");
11205
- const TEX_BRACE_CMD_START_RE = new RegExp(`(?:${PREFIX_CLASS})?(?:${ESCAPED_TEX_BRACE_COMMANDS})\s*\{`, "i");
11358
+ const TEX_CMD_WITH_BRACES_RE = new RegExp(String.raw`${PREFIX_CLASS}(?:${ESCAPED_TEX_BRACE_COMMANDS})\s*\{[^}]+\}`, "i");
11359
+ const TEX_BRACE_CMD_START_RE = new RegExp(String.raw`(?:${PREFIX_CLASS})?(?:${ESCAPED_TEX_BRACE_COMMANDS})\s*\{`, "i");
11206
11360
  const TEX_SPECIFIC_RE = /\\(?:text|frac|left|right|times)/;
11207
- const OPS_RE = /* @__PURE__ */ new RegExp("(?:^|[^+])\\+(?!\\+)|[=\\-*/^<>]|\\\\times|\\\\pm|\\\\cdot|\\\\le|\\\\ge|\\\\neq");
11361
+ const OPS_RE = /(?:^|[^+])\+(?!\+)|[=\-*/^<>]|\\times|\\pm|\\cdot|\\le|\\ge|\\neq/;
11208
11362
  const HYPHENATED_MULTIWORD_RE = /\b[A-Z]{2,}-[A-Z]{2,}\b/i;
11209
11363
  const FUNC_CALL_RE = /[A-Z]+\s*\([^)]+\)/i;
11210
11364
  const WORDS_RE = /\b(?:sin|cos|tan|log|ln|exp|sqrt|frac|sum|lim|int|prod)\b/;
11211
11365
  const DATE_TIME_RE = /\b\d{4}\/\d{1,2}\/\d{1,2}(?:[ T]\d{1,2}:\d{2}(?::\d{2})?)?\b/;
11366
+ const CONTROL_TEX_REPLACEMENTS = {
11367
+ [String.fromCharCode(8)]: "\\b",
11368
+ [String.fromCharCode(11)]: "\\v",
11369
+ [String.fromCharCode(12)]: "\\f"
11370
+ };
11371
+ function normalizeMathControlChars(value) {
11372
+ let result = "";
11373
+ for (const ch of value) result += CONTROL_TEX_REPLACEMENTS[ch] ?? ch;
11374
+ return result;
11375
+ }
11212
11376
  function isMathLike(s) {
11213
11377
  if (!s) return false;
11214
- const norm = s.replace(/[\u0008\v\f]/g, (ch) => {
11215
- switch (ch) {
11216
- case "\b": return "\\b";
11217
- case "\v": return "\\v";
11218
- case "\f": return "\\f";
11219
- default: return ch;
11220
- }
11221
- });
11378
+ const norm = normalizeMathControlChars(s);
11222
11379
  const stripped = norm.trim();
11223
11380
  if (DATE_TIME_RE.test(stripped)) return false;
11224
11381
  if (stripped.includes("**")) return false;
@@ -12164,22 +12321,6 @@ function parseHighlightToken(tokens, startIndex, options) {
12164
12321
 
12165
12322
  //#endregion
12166
12323
  //#region src/parser/inline-parsers/html-inline-code-parser.ts
12167
- const VOID_TAGS$1 = new Set([
12168
- "area",
12169
- "base",
12170
- "br",
12171
- "col",
12172
- "embed",
12173
- "hr",
12174
- "img",
12175
- "input",
12176
- "link",
12177
- "meta",
12178
- "param",
12179
- "source",
12180
- "track",
12181
- "wbr"
12182
- ]);
12183
12324
  let emptyTagSets = null;
12184
12325
  const TAG_SET_CACHE = /* @__PURE__ */ new WeakMap();
12185
12326
  function getEmptyTagSets() {
@@ -12197,7 +12338,7 @@ function isClosingTag(html) {
12197
12338
  return /^<\s*\//.test(html);
12198
12339
  }
12199
12340
  function isSelfClosing(tag, html) {
12200
- return /\/\s*>\s*$/.test(html) || VOID_TAGS$1.has(tag);
12341
+ return /\/\s*>\s*$/.test(html) || VOID_HTML_TAGS.has(tag);
12201
12342
  }
12202
12343
  function normalizeCustomTag$1(t) {
12203
12344
  const raw = String(t ?? "").trim();
@@ -12227,7 +12368,7 @@ function tokenToRaw(token) {
12227
12368
  const raw = shape.raw ?? shape.content ?? shape.markup ?? "";
12228
12369
  return String(raw ?? "");
12229
12370
  }
12230
- function parseTagAttrs(openTag) {
12371
+ function parseTagAttrs$1(openTag) {
12231
12372
  const attrs = [];
12232
12373
  const attrRegex = /\s([\w:-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g;
12233
12374
  let match;
@@ -12333,7 +12474,7 @@ function parseHtmlInlineCodeToken(token, tokens, i, parseInlineTokens$1, raw, pP
12333
12474
  }, i + 1];
12334
12475
  if (tag === "a") {
12335
12476
  const fragment$1 = collectHtmlFragment(tokens, i, tag);
12336
- const attrs$1 = parseTagAttrs(code$1);
12477
+ const attrs$1 = parseTagAttrs$1(code$1);
12337
12478
  const innerTokens = fragment$1.innerTokens;
12338
12479
  const href = String(getAttrValue$1(attrs$1, "href") ?? "");
12339
12480
  const titleAttr = getAttrValue$1(attrs$1, "title");
@@ -13866,6 +14007,28 @@ function parseAdmonition(tokens, index, match, options) {
13866
14007
 
13867
14008
  //#endregion
13868
14009
  //#region src/parser/node-parsers/container-parser.ts
14010
+ const CONTAINER_KINDS = new Set([
14011
+ "warning",
14012
+ "info",
14013
+ "note",
14014
+ "tip",
14015
+ "danger",
14016
+ "caution"
14017
+ ]);
14018
+ function parseContainerInfo(info) {
14019
+ let markerEnd = 0;
14020
+ while (markerEnd < info.length && markerEnd < 3 && info[markerEnd] === ":") markerEnd++;
14021
+ if (markerEnd === 0 || info[markerEnd] === ":") return null;
14022
+ const rest = info.slice(markerEnd).trimStart();
14023
+ if (!rest) return null;
14024
+ const firstWhitespace = rest.search(/\s/);
14025
+ const rawKind = (firstWhitespace === -1 ? rest : rest.slice(0, firstWhitespace)).toLowerCase();
14026
+ if (!CONTAINER_KINDS.has(rawKind)) return null;
14027
+ return {
14028
+ kind: rawKind,
14029
+ title: firstWhitespace === -1 ? "" : rest.slice(firstWhitespace).trim()
14030
+ };
14031
+ }
13869
14032
  function parseContainer(tokens, index, options) {
13870
14033
  const openToken = tokens[index];
13871
14034
  let kind = "note";
@@ -13875,15 +14038,16 @@ function parseContainer(tokens, index, options) {
13875
14038
  kind = typeMatch[1];
13876
14039
  const info = String(openToken.info ?? "").trim();
13877
14040
  if (info && !info.startsWith(":::")) {
13878
- const maybe = info.replace(/* @__PURE__ */ new RegExp(`^${kind}`), "").trim();
13879
- if (maybe) title = maybe;
14041
+ if (info.toLowerCase().startsWith(kind)) {
14042
+ const maybe = info.slice(kind.length).trim();
14043
+ if (maybe) title = maybe;
14044
+ }
13880
14045
  }
13881
14046
  } else {
13882
- const info = String(openToken.info ?? "").trim();
13883
- const match = /^:{1,3}\s*(warning|info|note|tip|danger|caution)\s*(.*)$/i.exec(info);
13884
- if (match) {
13885
- kind = match[1];
13886
- title = String(match[2] ?? "");
14047
+ const parsedInfo = parseContainerInfo(String(openToken.info ?? "").trim());
14048
+ if (parsedInfo) {
14049
+ kind = parsedInfo.kind;
14050
+ title = parsedInfo.title;
13887
14051
  }
13888
14052
  }
13889
14053
  if (!title) title = kind.charAt(0).toUpperCase() + kind.slice(1);
@@ -14100,23 +14264,7 @@ function parseHeading(tokens, index, options) {
14100
14264
 
14101
14265
  //#endregion
14102
14266
  //#region src/parser/node-parsers/html-block-parser.ts
14103
- const VOID_TAGS = new Set([
14104
- "area",
14105
- "base",
14106
- "br",
14107
- "col",
14108
- "embed",
14109
- "hr",
14110
- "img",
14111
- "input",
14112
- "link",
14113
- "meta",
14114
- "param",
14115
- "source",
14116
- "track",
14117
- "wbr"
14118
- ]);
14119
- function findTagCloseIndexOutsideQuotes$1(input) {
14267
+ function findTagCloseIndexOutsideQuotes$2(input) {
14120
14268
  let inSingle = false;
14121
14269
  let inDouble = false;
14122
14270
  for (let i = 0; i < input.length; i++) {
@@ -14148,7 +14296,7 @@ function findMatchingCloseTagEnd(rawHtml, tag, startIndex) {
14148
14296
  if (lt === -1) return -1;
14149
14297
  const slice = rawHtml.slice(lt);
14150
14298
  if (closeTagRe.test(slice)) {
14151
- const endRel = findTagCloseIndexOutsideQuotes$1(slice);
14299
+ const endRel = findTagCloseIndexOutsideQuotes$2(slice);
14152
14300
  if (endRel === -1) return -1;
14153
14301
  if (depth === 0) return lt + endRel + 1;
14154
14302
  depth--;
@@ -14156,7 +14304,7 @@ function findMatchingCloseTagEnd(rawHtml, tag, startIndex) {
14156
14304
  continue;
14157
14305
  }
14158
14306
  if (openTagRe.test(slice)) {
14159
- const endRel = findTagCloseIndexOutsideQuotes$1(slice);
14307
+ const endRel = findTagCloseIndexOutsideQuotes$2(slice);
14160
14308
  if (endRel === -1) return -1;
14161
14309
  const rawTag = slice.slice(0, endRel + 1);
14162
14310
  if (!/\/\s*>$/.test(rawTag)) depth++;
@@ -14167,6 +14315,18 @@ function findMatchingCloseTagEnd(rawHtml, tag, startIndex) {
14167
14315
  }
14168
14316
  return -1;
14169
14317
  }
14318
+ function parseTagAttrs(openTag) {
14319
+ const attrs = [];
14320
+ const attrRegex = /\s([\w:-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g;
14321
+ let match;
14322
+ while ((match = attrRegex.exec(openTag)) !== null) {
14323
+ const attrName = match[1];
14324
+ if (!attrName) continue;
14325
+ const attrValue = match[2] || match[3] || match[4] || "";
14326
+ attrs.push([attrName, attrValue]);
14327
+ }
14328
+ return attrs;
14329
+ }
14170
14330
  function parseHtmlBlock(token) {
14171
14331
  const raw = String(token.content ?? "");
14172
14332
  if (/^\s*<!--/.test(raw) || /^\s*<!/.test(raw) || /^\s*<\?/.test(raw)) return {
@@ -14184,10 +14344,11 @@ function parseHtmlBlock(token) {
14184
14344
  tag: "",
14185
14345
  loading: false
14186
14346
  };
14187
- const openEnd = findTagCloseIndexOutsideQuotes$1(raw);
14347
+ const openEnd = findTagCloseIndexOutsideQuotes$2(raw);
14188
14348
  const openTag = openEnd === -1 ? raw : raw.slice(0, openEnd + 1);
14189
14349
  const selfClosing = openEnd !== -1 && /\/\s*>$/.test(openTag);
14190
- const isVoid = VOID_TAGS.has(tag);
14350
+ const isVoid = VOID_HTML_TAGS.has(tag);
14351
+ const attrs = parseTagAttrs(openTag);
14191
14352
  const hasClosing = (openEnd === -1 ? -1 : findMatchingCloseTagEnd(raw, tag, openEnd + 1)) !== -1;
14192
14353
  const loading = !(isVoid || selfClosing || hasClosing);
14193
14354
  return {
@@ -14195,6 +14356,7 @@ function parseHtmlBlock(token) {
14195
14356
  content: loading ? `${raw.replace(/<[^>]*$/, "")}\n</${tag}>` : raw,
14196
14357
  raw,
14197
14358
  tag,
14359
+ attrs: attrs.length ? attrs : void 0,
14198
14360
  loading
14199
14361
  };
14200
14362
  }
@@ -14389,7 +14551,7 @@ function parseVmrContainer(tokens, index, options) {
14389
14551
  raw
14390
14552
  }, hasCloseToken ? j + 1 : j];
14391
14553
  }
14392
- function findTagCloseIndexOutsideQuotes(input) {
14554
+ function findTagCloseIndexOutsideQuotes$1(input) {
14393
14555
  let inSingle = false;
14394
14556
  let inDouble = false;
14395
14557
  for (let i = 0; i < input.length; i++) {
@@ -14418,14 +14580,14 @@ function stripTrailingPartialClosingTag(inner, tag) {
14418
14580
  const re = new RegExp(String.raw`[\t ]*<\s*\/\s*${tag}[^>]*$`, "i");
14419
14581
  return inner.replace(re, "");
14420
14582
  }
14421
- function escapeRegex(value) {
14583
+ function escapeRegex$1(value) {
14422
14584
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14423
14585
  }
14424
14586
  function findMatchingCloseTagRange(rawHtml, tag, startIndex) {
14425
14587
  if (!rawHtml || !tag) return null;
14426
14588
  const lowerTag = tag.toLowerCase();
14427
- const openTagRe = new RegExp(String.raw`^<\s*${escapeRegex(lowerTag)}(?=\s|>|/)`, "i");
14428
- const closeTagRe = new RegExp(String.raw`^<\s*\/\s*${escapeRegex(lowerTag)}(?=\s|>)`, "i");
14589
+ const openTagRe = new RegExp(String.raw`^<\s*${escapeRegex$1(lowerTag)}(?=\s|>|/)`, "i");
14590
+ const closeTagRe = new RegExp(String.raw`^<\s*\/\s*${escapeRegex$1(lowerTag)}(?=\s|>)`, "i");
14429
14591
  let depth = 0;
14430
14592
  let index = Math.max(0, startIndex);
14431
14593
  while (index < rawHtml.length) {
@@ -14433,7 +14595,7 @@ function findMatchingCloseTagRange(rawHtml, tag, startIndex) {
14433
14595
  if (lt === -1) break;
14434
14596
  const slice = rawHtml.slice(lt);
14435
14597
  if (closeTagRe.test(slice)) {
14436
- const endRel = findTagCloseIndexOutsideQuotes(slice);
14598
+ const endRel = findTagCloseIndexOutsideQuotes$1(slice);
14437
14599
  if (endRel === -1) return null;
14438
14600
  if (depth === 0) return {
14439
14601
  start: lt,
@@ -14444,7 +14606,7 @@ function findMatchingCloseTagRange(rawHtml, tag, startIndex) {
14444
14606
  continue;
14445
14607
  }
14446
14608
  if (openTagRe.test(slice)) {
14447
- const endRel = findTagCloseIndexOutsideQuotes(slice);
14609
+ const endRel = findTagCloseIndexOutsideQuotes$1(slice);
14448
14610
  if (endRel === -1) return null;
14449
14611
  const raw = slice.slice(0, endRel + 1);
14450
14612
  if (!/\/\s*>$/.test(raw)) depth++;
@@ -14464,7 +14626,7 @@ function findNextCustomHtmlBlockFromSource(source, tag, startIndex) {
14464
14626
  if (!openMatch || openMatch.index == null) return null;
14465
14627
  const openStart = openMatch.index;
14466
14628
  const openSlice = source.slice(openStart);
14467
- const openEndRel = findTagCloseIndexOutsideQuotes(openSlice);
14629
+ const openEndRel = findTagCloseIndexOutsideQuotes$1(openSlice);
14468
14630
  if (openEndRel === -1) return null;
14469
14631
  const openEnd = openStart + openEndRel;
14470
14632
  if (/\/\s*>\s*$/.test(openSlice.slice(0, openEndRel + 1))) {
@@ -14505,7 +14667,7 @@ function findNextCustomHtmlBlockFromSource(source, tag, startIndex) {
14505
14667
  continue;
14506
14668
  }
14507
14669
  if (isOpenAt(lt)) {
14508
- const rel = findTagCloseIndexOutsideQuotes(source.slice(lt));
14670
+ const rel = findTagCloseIndexOutsideQuotes$1(source.slice(lt));
14509
14671
  if (rel === -1) return null;
14510
14672
  depth++;
14511
14673
  i = lt + rel + 1;
@@ -14561,7 +14723,7 @@ function parseBasicBlockToken(tokens, index, options) {
14561
14723
  const fromSource = findNextCustomHtmlBlockFromSource(source, tag, Math.max(clampNonNegative(cursor), clampNonNegative(mappedLineStart)));
14562
14724
  if (fromSource) options.__customHtmlBlockCursor = fromSource.end;
14563
14725
  const rawHtml = String(fromSource?.raw ?? htmlBlockNode.raw ?? "");
14564
- const openEnd = findTagCloseIndexOutsideQuotes(rawHtml);
14726
+ const openEnd = findTagCloseIndexOutsideQuotes$1(rawHtml);
14565
14727
  const openTag = openEnd !== -1 ? rawHtml.slice(0, openEnd + 1) : rawHtml;
14566
14728
  const selfClosing = openEnd !== -1 && /\/\s*>\s*$/.test(openTag);
14567
14729
  const closeRange = openEnd === -1 ? null : findMatchingCloseTagRange(rawHtml, tag, openEnd + 1);
@@ -14656,84 +14818,6 @@ function parseParagraph(tokens, index, options) {
14656
14818
 
14657
14819
  //#endregion
14658
14820
  //#region src/parser/index.ts
14659
- const STANDARD_HTML_TAGS = new Set([
14660
- "area",
14661
- "base",
14662
- "br",
14663
- "col",
14664
- "embed",
14665
- "hr",
14666
- "img",
14667
- "input",
14668
- "link",
14669
- "meta",
14670
- "param",
14671
- "source",
14672
- "track",
14673
- "wbr",
14674
- "a",
14675
- "abbr",
14676
- "b",
14677
- "bdi",
14678
- "bdo",
14679
- "button",
14680
- "cite",
14681
- "code",
14682
- "data",
14683
- "del",
14684
- "dfn",
14685
- "em",
14686
- "font",
14687
- "i",
14688
- "ins",
14689
- "kbd",
14690
- "label",
14691
- "mark",
14692
- "q",
14693
- "s",
14694
- "samp",
14695
- "small",
14696
- "span",
14697
- "strong",
14698
- "sub",
14699
- "sup",
14700
- "time",
14701
- "u",
14702
- "var",
14703
- "article",
14704
- "aside",
14705
- "blockquote",
14706
- "div",
14707
- "details",
14708
- "figcaption",
14709
- "figure",
14710
- "footer",
14711
- "header",
14712
- "h1",
14713
- "h2",
14714
- "h3",
14715
- "h4",
14716
- "h5",
14717
- "h6",
14718
- "li",
14719
- "main",
14720
- "nav",
14721
- "ol",
14722
- "p",
14723
- "pre",
14724
- "section",
14725
- "summary",
14726
- "table",
14727
- "tbody",
14728
- "td",
14729
- "th",
14730
- "thead",
14731
- "tr",
14732
- "ul",
14733
- "svg",
14734
- "g",
14735
- "path"
14736
- ]);
14737
14821
  function normalizeTagName(t) {
14738
14822
  const raw = String(t ?? "").trim();
14739
14823
  if (!raw) return "";
@@ -14812,6 +14896,328 @@ function parseStandaloneHtmlDocument(markdown) {
14812
14896
  loading: false
14813
14897
  }];
14814
14898
  }
14899
+ function escapeRegex(value) {
14900
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14901
+ }
14902
+ function findTagCloseIndexOutsideQuotes(input) {
14903
+ let inSingle = false;
14904
+ let inDouble = false;
14905
+ for (let i = 0; i < input.length; i++) {
14906
+ const ch = input[i];
14907
+ if (ch === "\\") {
14908
+ i++;
14909
+ continue;
14910
+ }
14911
+ if (!inDouble && ch === "'") {
14912
+ inSingle = !inSingle;
14913
+ continue;
14914
+ }
14915
+ if (!inSingle && ch === "\"") {
14916
+ inDouble = !inDouble;
14917
+ continue;
14918
+ }
14919
+ if (!inSingle && !inDouble && ch === ">") return i;
14920
+ }
14921
+ return -1;
14922
+ }
14923
+ function getMergeableNodeRaw(node) {
14924
+ const raw = node?.raw;
14925
+ if (typeof raw === "string") return raw;
14926
+ const content = node?.content;
14927
+ if (typeof content === "string") return content;
14928
+ return "";
14929
+ }
14930
+ const SOURCE_EXACT_HTML_BLOCK_TAGS = new Set([
14931
+ "article",
14932
+ "aside",
14933
+ "blockquote",
14934
+ "div",
14935
+ "figcaption",
14936
+ "figure",
14937
+ "footer",
14938
+ "h1",
14939
+ "h2",
14940
+ "h3",
14941
+ "h4",
14942
+ "h5",
14943
+ "h6",
14944
+ "header",
14945
+ "li",
14946
+ "main",
14947
+ "nav",
14948
+ "ol",
14949
+ "p",
14950
+ "pre",
14951
+ "section",
14952
+ "summary",
14953
+ "table",
14954
+ "tbody",
14955
+ "td",
14956
+ "th",
14957
+ "thead",
14958
+ "tr",
14959
+ "ul"
14960
+ ]);
14961
+ function findNextHtmlBlockFromSource(source, tag, startIndex) {
14962
+ if (!source || !tag) return null;
14963
+ const lowerTag = tag.toLowerCase();
14964
+ const openRe = new RegExp(String.raw`<\s*${escapeRegex(lowerTag)}(?=\s|>|/)`, "gi");
14965
+ openRe.lastIndex = Math.max(0, startIndex);
14966
+ const openMatch = openRe.exec(source);
14967
+ if (!openMatch || openMatch.index == null) return null;
14968
+ const start = openMatch.index;
14969
+ const openEndRel = findTagCloseIndexOutsideQuotes(source.slice(start));
14970
+ if (openEndRel === -1) return null;
14971
+ const openEnd = start + openEndRel;
14972
+ const openTag = source.slice(start, openEnd + 1);
14973
+ if (VOID_HTML_TAGS.has(lowerTag) || /\/\s*>$/.test(openTag)) return {
14974
+ raw: openTag,
14975
+ start,
14976
+ end: openEnd + 1,
14977
+ closed: true
14978
+ };
14979
+ let depth = 1;
14980
+ let index = openEnd + 1;
14981
+ const isOpenAt = (pos) => {
14982
+ const slice = source.slice(pos);
14983
+ return new RegExp(String.raw`^<\s*${escapeRegex(lowerTag)}(?=\s|>|/)`, "i").test(slice);
14984
+ };
14985
+ const isCloseAt = (pos) => {
14986
+ const slice = source.slice(pos);
14987
+ return new RegExp(String.raw`^<\s*\/\s*${escapeRegex(lowerTag)}(?=\s|>)`, "i").test(slice);
14988
+ };
14989
+ while (index < source.length) {
14990
+ const lt = source.indexOf("<", index);
14991
+ if (lt === -1) return {
14992
+ raw: source.slice(start),
14993
+ start,
14994
+ end: source.length,
14995
+ closed: false
14996
+ };
14997
+ if (isCloseAt(lt)) {
14998
+ const endRel = findTagCloseIndexOutsideQuotes(source.slice(lt));
14999
+ if (endRel === -1) return null;
15000
+ depth--;
15001
+ const end = lt + endRel + 1;
15002
+ if (depth === 0) return {
15003
+ raw: source.slice(start, end),
15004
+ start,
15005
+ end,
15006
+ closed: true
15007
+ };
15008
+ index = end;
15009
+ continue;
15010
+ }
15011
+ if (isOpenAt(lt)) {
15012
+ const endRel = findTagCloseIndexOutsideQuotes(source.slice(lt));
15013
+ if (endRel === -1) return null;
15014
+ const raw = source.slice(lt, lt + endRel + 1);
15015
+ if (!/\/\s*>$/.test(raw)) depth++;
15016
+ index = lt + endRel + 1;
15017
+ continue;
15018
+ }
15019
+ index = lt + 1;
15020
+ }
15021
+ return {
15022
+ raw: source.slice(start),
15023
+ start,
15024
+ end: source.length,
15025
+ closed: false
15026
+ };
15027
+ }
15028
+ function findApproximateConsumedPrefixEnd(exact, approximate) {
15029
+ if (!approximate) return 0;
15030
+ let i = 0;
15031
+ let j = 0;
15032
+ while (i < exact.length && j < approximate.length) {
15033
+ if (exact[i] === approximate[j]) {
15034
+ i++;
15035
+ j++;
15036
+ continue;
15037
+ }
15038
+ if (exact[i] === "\r" || exact[i] === "\n") {
15039
+ i++;
15040
+ continue;
15041
+ }
15042
+ return -1;
15043
+ }
15044
+ return j === approximate.length ? i : -1;
15045
+ }
15046
+ function buildHtmlBlockContent(raw, tag, closed) {
15047
+ if (closed) return raw;
15048
+ return `${raw.replace(/<[^>]*$/, "")}\n</${tag}>`;
15049
+ }
15050
+ function extendHtmlBlockCloseToLineEnding(source, startIndex) {
15051
+ let end = Math.max(0, startIndex);
15052
+ while (end < source.length && (source[end] === " " || source[end] === " ")) end++;
15053
+ if (source[end] === "\r") {
15054
+ end++;
15055
+ if (source[end] === "\n") end++;
15056
+ return end;
15057
+ }
15058
+ if (source[end] === "\n") return end + 1;
15059
+ return startIndex;
15060
+ }
15061
+ function isDetailsOpenHtmlBlock(node) {
15062
+ if (node.type !== "html_block") return false;
15063
+ if (String(node.tag ?? "").toLowerCase() !== "details") return false;
15064
+ const raw = String(node.raw ?? node.content ?? "");
15065
+ return /^\s*<details\b/i.test(raw);
15066
+ }
15067
+ function isDetailsCloseHtmlBlock(node) {
15068
+ if (node.type !== "html_block") return false;
15069
+ const raw = String(node.raw ?? node.content ?? "");
15070
+ return /^\s*<\/details\b/i.test(raw);
15071
+ }
15072
+ function findLastClosingTagStart(raw, tag) {
15073
+ const closeRe = new RegExp(String.raw`<\s*\/\s*${escapeRegex(tag)}(?=\s|>)`, "gi");
15074
+ let last = -1;
15075
+ let match;
15076
+ while ((match = closeRe.exec(raw)) !== null) last = match.index;
15077
+ return last;
15078
+ }
15079
+ function buildDetailsChildParseOptions(options, final) {
15080
+ return {
15081
+ final,
15082
+ requireClosingStrong: options.requireClosingStrong,
15083
+ customHtmlTags: options.customHtmlTags,
15084
+ validateLink: options.validateLink
15085
+ };
15086
+ }
15087
+ function parseDetailsFragmentChildren(fragment, md, options) {
15088
+ if (!fragment.trim()) return [];
15089
+ return parseMarkdownToStructure(fragment, md, options);
15090
+ }
15091
+ function buildStructuredSummaryNode(summaryRaw, md, options) {
15092
+ const summaryNode = parseHtmlBlock({ content: summaryRaw });
15093
+ const openEnd = findTagCloseIndexOutsideQuotes(summaryRaw);
15094
+ const closeStart = findLastClosingTagStart(summaryRaw, "summary");
15095
+ if (openEnd !== -1 && closeStart !== -1 && closeStart >= openEnd + 1) {
15096
+ const children = parseDetailsFragmentChildren(summaryRaw.slice(openEnd + 1, closeStart), md, options);
15097
+ if (children.length > 0) summaryNode.children = children;
15098
+ }
15099
+ summaryNode.raw = summaryRaw;
15100
+ return summaryNode;
15101
+ }
15102
+ function buildDetailsPrefixChildren(openRaw, md, options) {
15103
+ const openEnd = findTagCloseIndexOutsideQuotes(openRaw);
15104
+ if (openEnd === -1) return [];
15105
+ const innerPrefix = openRaw.slice(openEnd + 1);
15106
+ if (!innerPrefix.trim()) return [];
15107
+ const summaryBlock = findNextHtmlBlockFromSource(innerPrefix, "summary", 0);
15108
+ if (!summaryBlock) return parseDetailsFragmentChildren(innerPrefix, md, options);
15109
+ const beforeSummary = innerPrefix.slice(0, summaryBlock.start);
15110
+ const afterSummary = innerPrefix.slice(summaryBlock.end);
15111
+ return [
15112
+ ...parseDetailsFragmentChildren(beforeSummary, md, options),
15113
+ buildStructuredSummaryNode(summaryBlock.raw, md, options),
15114
+ ...parseDetailsFragmentChildren(afterSummary, md, options)
15115
+ ];
15116
+ }
15117
+ function combineStructuredDetailsHtmlBlocks(nodes, source, md, options, final, sourceCursor = 0) {
15118
+ const merged = [];
15119
+ let cursor = sourceCursor;
15120
+ for (let i = 0; i < nodes.length; i++) {
15121
+ const node = nodes[i];
15122
+ const nodeRaw = getMergeableNodeRaw(node);
15123
+ let nodePos = -1;
15124
+ if (nodeRaw) {
15125
+ nodePos = source.indexOf(nodeRaw, cursor);
15126
+ if (nodePos !== -1) cursor = nodePos + nodeRaw.length;
15127
+ }
15128
+ if (!isDetailsOpenHtmlBlock(node)) {
15129
+ merged.push(node);
15130
+ continue;
15131
+ }
15132
+ const openRaw = String(node.raw ?? getMergeableNodeRaw(node) ?? "");
15133
+ const openStart = nodePos !== -1 ? nodePos : source.indexOf(openRaw, Math.max(0, cursor - openRaw.length));
15134
+ if (openStart === -1) {
15135
+ merged.push(node);
15136
+ continue;
15137
+ }
15138
+ let depth = 1;
15139
+ let closeIndex = -1;
15140
+ for (let j = i + 1; j < nodes.length; j++) {
15141
+ const current = nodes[j];
15142
+ if (isDetailsOpenHtmlBlock(current)) {
15143
+ depth++;
15144
+ continue;
15145
+ }
15146
+ if (!isDetailsCloseHtmlBlock(current)) continue;
15147
+ depth--;
15148
+ if (depth === 0) {
15149
+ closeIndex = j;
15150
+ break;
15151
+ }
15152
+ }
15153
+ const [children] = combineStructuredDetailsHtmlBlocks(closeIndex === -1 ? nodes.slice(i + 1) : nodes.slice(i + 1, closeIndex), source, md, options, final, openStart + openRaw.length);
15154
+ const prefixChildren = buildDetailsPrefixChildren(openRaw, md, buildDetailsChildParseOptions(options, final));
15155
+ const exact = findNextHtmlBlockFromSource(source, "details", openStart);
15156
+ const closeRaw = closeIndex === -1 ? "</details>" : String(nodes[closeIndex]?.raw ?? getMergeableNodeRaw(nodes[closeIndex]) ?? "</details>");
15157
+ const explicitClose = closeIndex !== -1 && exact?.closed === true;
15158
+ const trimmedCloseRaw = closeRaw.replace(/[\t\r\n ]+$/, "");
15159
+ const closeStart = explicitClose ? (() => {
15160
+ const closeOffset = exact.raw.lastIndexOf(trimmedCloseRaw);
15161
+ return closeOffset === -1 ? source.length : openStart + closeOffset;
15162
+ })() : source.length;
15163
+ const middleSource = source.slice(openStart + openRaw.length, closeStart === -1 ? source.length : closeStart);
15164
+ const middleTokens = md.parse(middleSource, { __markstreamFinal: final });
15165
+ const renderedMiddle = md.renderer.render(middleTokens, md.options, { __markstreamFinal: final });
15166
+ const closeMarkupEnd = closeStart + trimmedCloseRaw.length;
15167
+ const closeSliceEnd = explicitClose ? Math.max(closeStart + closeRaw.length, extendHtmlBlockCloseToLineEnding(source, closeMarkupEnd)) : source.length;
15168
+ const renderedCloseRaw = explicitClose ? source.slice(closeStart, closeSliceEnd) : closeRaw;
15169
+ const mergedRaw = explicitClose ? source.slice(openStart, closeSliceEnd) : source.slice(openStart);
15170
+ merged.push({
15171
+ ...node,
15172
+ tag: "details",
15173
+ attrs: parseTagAttrs(openRaw.slice(0, findTagCloseIndexOutsideQuotes(openRaw) + 1)),
15174
+ raw: mergedRaw,
15175
+ content: `${openRaw}${renderedMiddle}${renderedCloseRaw}`,
15176
+ children: [...prefixChildren, ...children],
15177
+ loading: !final && !explicitClose
15178
+ });
15179
+ cursor = explicitClose ? closeSliceEnd : source.length;
15180
+ if (closeIndex === -1) break;
15181
+ i = closeIndex;
15182
+ }
15183
+ return [merged, cursor];
15184
+ }
15185
+ function mergeSplitTopLevelHtmlBlocks(nodes, final, source) {
15186
+ if (!source) return nodes;
15187
+ const merged = nodes.slice();
15188
+ let sourceHtmlCursor = 0;
15189
+ for (let i = 0; i < merged.length; i++) {
15190
+ const node = merged[i];
15191
+ if (node?.type !== "html_block") continue;
15192
+ const tag = String(node?.tag ?? "").toLowerCase();
15193
+ if (!tag) continue;
15194
+ if (!SOURCE_EXACT_HTML_BLOCK_TAGS.has(tag)) continue;
15195
+ const exact = findNextHtmlBlockFromSource(source, tag, sourceHtmlCursor);
15196
+ if (!exact) continue;
15197
+ sourceHtmlCursor = exact.end;
15198
+ const currentContent = String(node?.content ?? getMergeableNodeRaw(node));
15199
+ const currentRaw = String(node?.raw ?? currentContent);
15200
+ const nextContent = buildHtmlBlockContent(exact.raw, tag, exact.closed);
15201
+ const desiredLoading = !final && !exact.closed;
15202
+ const needsExpansion = currentContent !== nextContent || currentRaw !== exact.raw || Boolean(node?.loading) !== desiredLoading;
15203
+ node.content = nextContent;
15204
+ node.raw = exact.raw;
15205
+ node.loading = desiredLoading;
15206
+ if (!needsExpansion) continue;
15207
+ let tailCursor = findApproximateConsumedPrefixEnd(exact.raw, currentContent);
15208
+ if (tailCursor === -1) tailCursor = 0;
15209
+ const j = i + 1;
15210
+ while (j < merged.length) {
15211
+ const nextRaw = getMergeableNodeRaw(merged[j]);
15212
+ if (!nextRaw) break;
15213
+ const nextPos = exact.raw.indexOf(nextRaw, tailCursor);
15214
+ if (nextPos === -1) break;
15215
+ tailCursor = nextPos + nextRaw.length;
15216
+ merged.splice(j, 1);
15217
+ }
15218
+ }
15219
+ return merged;
15220
+ }
14815
15221
  function stripDanglingHtmlLikeTail(markdown) {
14816
15222
  const isWs = (ch) => ch === " " || ch === " " || ch === "\n" || ch === "\r";
14817
15223
  const isLikelyHtmlTagPrefix = (tail$1) => {
@@ -14980,7 +15386,7 @@ function ensureBlankLineBeforeInlineMultilineCustomHtmlBlocks(markdown, tags) {
14980
15386
  }
14981
15387
  return false;
14982
15388
  };
14983
- const findTagCloseIndexOutsideQuotes$3 = (input) => {
15389
+ const findTagCloseIndexOutsideQuotes$4 = (input) => {
14984
15390
  let inSingle = false;
14985
15391
  let inDouble = false;
14986
15392
  for (let i = 0; i < input.length; i++) {
@@ -15029,7 +15435,7 @@ function ensureBlankLineBeforeInlineMultilineCustomHtmlBlocks(markdown, tags) {
15029
15435
  i++;
15030
15436
  continue;
15031
15437
  }
15032
- const closeIdxRel = findTagCloseIndexOutsideQuotes$3(line.slice(i));
15438
+ const closeIdxRel = findTagCloseIndexOutsideQuotes$4(line.slice(i));
15033
15439
  if (closeIdxRel === -1) {
15034
15440
  hasRenderablePrefix = true;
15035
15441
  i++;
@@ -15133,7 +15539,7 @@ function normalizeCustomHtmlOpeningTagSameLine(markdown, tags) {
15133
15539
  while (i < s.length && isIndentWs(s[i])) i++;
15134
15540
  return s.slice(i);
15135
15541
  };
15136
- const findTagCloseIndexOutsideQuotes$3 = (input) => {
15542
+ const findTagCloseIndexOutsideQuotes$4 = (input) => {
15137
15543
  let inSingle = false;
15138
15544
  let inDouble = false;
15139
15545
  for (let i = 0; i < input.length; i++) {
@@ -15205,7 +15611,7 @@ function normalizeCustomHtmlOpeningTagSameLine(markdown, tags) {
15205
15611
  if (i === nameStart) return line;
15206
15612
  const tagName = line.slice(nameStart, i).toLowerCase();
15207
15613
  if (!tagSet.has(tagName)) return line;
15208
- const gtRel = findTagCloseIndexOutsideQuotes$3(line.slice(i));
15614
+ const gtRel = findTagCloseIndexOutsideQuotes$4(line.slice(i));
15209
15615
  if (gtRel === -1) return line;
15210
15616
  const gt = i + gtRel;
15211
15617
  if (hasClosingTagOnLine(line, gt + 1, tagName)) return line;
@@ -15649,6 +16055,8 @@ function parseMarkdownToStructure(markdown, md, options = {}) {
15649
16055
  else result = postResult;
15650
16056
  }
15651
16057
  }
16058
+ result = mergeSplitTopLevelHtmlBlocks(result, isFinal, safeMarkdown);
16059
+ result = combineStructuredDetailsHtmlBlocks(result, safeMarkdown, md, options, isFinal)[0];
15652
16060
  if (isFinal) {
15653
16061
  const seen = /* @__PURE__ */ new WeakSet();
15654
16062
  const finalizeHtmlBlockLoading = (value) => {
@@ -15896,5 +16304,5 @@ function getMarkdown(msgId = `editor-${Date.now()}`, options = {}) {
15896
16304
  }
15897
16305
 
15898
16306
  //#endregion
15899
- export { ESCAPED_TEX_BRACE_COMMANDS, KATEX_COMMANDS, TEX_BRACE_COMMANDS, applyContainers, applyMath, clearRegisteredMarkdownPlugins, findMatchingClose, getMarkdown, isMathLike, normalizeStandaloneBackslashT, parseFenceToken, parseInlineTokens, parseMarkdownToStructure, processTokens, registerMarkdownPlugin, setDefaultMathOptions };
16307
+ export { BLOCKED_HTML_TAGS, BLOCKED_HTML_TAG_NAMES, BLOCK_HTML_TAG_NAMES, DANGEROUS_HTML_ATTRS, DANGEROUS_HTML_ATTR_NAMES, ESCAPED_TEX_BRACE_COMMANDS, EXTENDED_STANDARD_HTML_TAGS, EXTENDED_STANDARD_HTML_TAG_NAMES, INLINE_HTML_TAG_NAMES, KATEX_COMMANDS, STANDARD_BLOCK_HTML_TAGS, STANDARD_HTML_TAGS, SVG_HTML_TAG_NAMES, TEX_BRACE_COMMANDS, URL_HTML_ATTRS, URL_HTML_ATTR_NAMES, VOID_HTML_TAGS, VOID_HTML_TAG_NAMES, applyContainers, applyMath, clearRegisteredMarkdownPlugins, findMatchingClose, getMarkdown, isMathLike, isUnsafeHtmlUrl, normalizeStandaloneBackslashT, parseFenceToken, parseInlineTokens, parseMarkdownToStructure, processTokens, registerMarkdownPlugin, setDefaultMathOptions, stripHtmlControlAndWhitespace };
15900
16308
  //# sourceMappingURL=index.js.map