stream-markdown-parser 0.0.43 → 0.0.45

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
@@ -6819,6 +6819,7 @@ const BASE_COMMON_HTML_TAGS = new Set([
6819
6819
  "del",
6820
6820
  "dfn",
6821
6821
  "em",
6822
+ "font",
6822
6823
  "i",
6823
6824
  "img",
6824
6825
  "input",
@@ -6874,7 +6875,7 @@ const BASE_COMMON_HTML_TAGS = new Set([
6874
6875
  const OPEN_TAG_RE = /<([A-Z][\w-]*)(?=[\s/>]|$)/gi;
6875
6876
  const CLOSE_TAG_RE = /<\/\s*([A-Z][\w-]*)(?=[\s/>]|$)/gi;
6876
6877
  const TAG_NAME_AT_START_RE = /^<\s*(?:\/\s*)?([A-Z][\w-]*)/i;
6877
- function findTagCloseIndexOutsideQuotes(html) {
6878
+ function findTagCloseIndexOutsideQuotes$1(html) {
6878
6879
  let inSingle = false;
6879
6880
  let inDouble = false;
6880
6881
  for (let i = 0; i < html.length; i++) {
@@ -6917,8 +6918,8 @@ function findFirstIncompleteTag(content, tagSet) {
6917
6918
  const idx = m.index ?? -1;
6918
6919
  if (idx < 0) continue;
6919
6920
  const tag = (m[1] ?? "").toLowerCase();
6920
- if (!tagSet.has(tag)) continue;
6921
- if (findTagCloseIndexOutsideQuotes(content.slice(idx)) !== -1) continue;
6921
+ if (!isCommonHtmlTagOrPrefix(tag, tagSet)) continue;
6922
+ if (findTagCloseIndexOutsideQuotes$1(content.slice(idx)) !== -1) continue;
6922
6923
  if (!first || idx < first.index) first = {
6923
6924
  index: idx,
6924
6925
  tag,
@@ -6930,7 +6931,7 @@ function findFirstIncompleteTag(content, tagSet) {
6930
6931
  if (idx < 0) continue;
6931
6932
  const tag = (m[1] ?? "").toLowerCase();
6932
6933
  if (!isCommonHtmlTagOrPrefix(tag, tagSet)) continue;
6933
- if (findTagCloseIndexOutsideQuotes(content.slice(idx)) !== -1) continue;
6934
+ if (findTagCloseIndexOutsideQuotes$1(content.slice(idx)) !== -1) continue;
6934
6935
  if (!first || idx < first.index) first = {
6935
6936
  index: idx,
6936
6937
  tag,
@@ -6996,7 +6997,7 @@ function fixStreamingHtmlInlineChildren(children, tagSet) {
6996
6997
  cursor = lt + 1;
6997
6998
  continue;
6998
6999
  }
6999
- const closeIdx = findTagCloseIndexOutsideQuotes(sub);
7000
+ const closeIdx = findTagCloseIndexOutsideQuotes$1(sub);
7000
7001
  if (closeIdx === -1) {
7001
7002
  pushTextPart("<", baseToken);
7002
7003
  cursor = lt + 1;
@@ -7034,7 +7035,7 @@ function fixStreamingHtmlInlineChildren(children, tagSet) {
7034
7035
  if (pending) {
7035
7036
  pending.buffer += tokenToRaw$1(child);
7036
7037
  pendingAtEnd = pending.buffer;
7037
- const closeIdx = findTagCloseIndexOutsideQuotes(pending.buffer);
7038
+ const closeIdx = findTagCloseIndexOutsideQuotes$1(pending.buffer);
7038
7039
  if (closeIdx === -1) continue;
7039
7040
  const tagChunk = pending.buffer.slice(0, closeIdx + 1);
7040
7041
  const afterChunk = pending.buffer.slice(closeIdx + 1);
@@ -7052,7 +7053,7 @@ function fixStreamingHtmlInlineChildren(children, tagSet) {
7052
7053
  if (child.type === "html_inline") {
7053
7054
  const content = tokenToRaw$1(child);
7054
7055
  const tagName = (content.match(TAG_NAME_AT_START_RE)?.[1] ?? "").toLowerCase();
7055
- if (tagName && tagSet.has(tagName) && findTagCloseIndexOutsideQuotes(content) === -1) {
7056
+ if (tagName && tagSet.has(tagName) && findTagCloseIndexOutsideQuotes$1(content) === -1) {
7056
7057
  pending = {
7057
7058
  tag: tagName,
7058
7059
  buffer: content,
@@ -7132,10 +7133,69 @@ function applyFixHtmlInlineTokens(md, options = {}) {
7132
7133
  const tagStack = [];
7133
7134
  for (let i = 0; i < toks.length; i++) {
7134
7135
  const t = toks[i];
7136
+ if (tagStack.length > 0) {
7137
+ const [openTag, openIndex] = tagStack[tagStack.length - 1];
7138
+ if (i !== openIndex) {
7139
+ if (t.type === "paragraph_open" || t.type === "paragraph_close") {
7140
+ toks.splice(i, 1);
7141
+ i--;
7142
+ continue;
7143
+ }
7144
+ const chunk = String(t.content ?? t.raw ?? "");
7145
+ const closeRe = new RegExp(`<\\s*\\/\\s*${openTag}\\s*>`, "i");
7146
+ const closeMatch = chunk ? closeRe.exec(chunk) : null;
7147
+ const isClosingTag$1 = !!closeMatch;
7148
+ if (chunk) {
7149
+ const openToken = toks[openIndex];
7150
+ if (closeMatch && typeof closeMatch.index === "number") {
7151
+ const end = closeMatch.index + String(closeMatch[0] ?? "").length;
7152
+ const before = chunk.slice(0, end);
7153
+ const after = chunk.slice(end);
7154
+ openToken.content = `${String(openToken.content || "")}\n${before}`;
7155
+ openToken.loading = false;
7156
+ const afterTrimmed = after.replace(/^\s+/, "");
7157
+ toks.splice(i, 1);
7158
+ tagStack.pop();
7159
+ if (afterTrimmed) toks.splice(i, 0, afterTrimmed.startsWith("<") ? {
7160
+ type: "html_block",
7161
+ content: afterTrimmed
7162
+ } : {
7163
+ type: "inline",
7164
+ content: afterTrimmed,
7165
+ children: [{
7166
+ type: "text",
7167
+ content: afterTrimmed,
7168
+ raw: afterTrimmed
7169
+ }]
7170
+ });
7171
+ i--;
7172
+ continue;
7173
+ }
7174
+ openToken.content = `${String(openToken.content || "")}\n${chunk}`;
7175
+ if (openToken.loading !== false) openToken.loading = !isClosingTag$1;
7176
+ }
7177
+ toks.splice(i, 1);
7178
+ i--;
7179
+ if (isClosingTag$1) tagStack.pop();
7180
+ continue;
7181
+ }
7182
+ }
7135
7183
  if (t.type === "html_block") {
7136
- const tag = (t.content?.match(/<([^\s>/]+)/)?.[1] ?? "").toLowerCase();
7137
- if (!/<\s*\/\s*[^\s>]+\s*>/.test(t.content || "")) tagStack.push([tag, i]);
7138
- else if (tagStack.length > 0 && tagStack[tagStack.length - 1][0] === tag) tagStack.pop();
7184
+ const rawContent = String(t.content || "");
7185
+ const tag = (rawContent.match(/<\s*(?:\/\s*)?([^\s>/]+)/)?.[1] ?? "").toLowerCase();
7186
+ if (!/^\s*<\s*\//.test(rawContent)) {
7187
+ if (tag) {
7188
+ if (!new RegExp(`<\\s*\\/\\s*${tag}\\s*>`, "i").test(rawContent)) tagStack.push([tag, i]);
7189
+ }
7190
+ } else if (tagStack.length > 0 && tag && tagStack[tagStack.length - 1][0] === tag) {
7191
+ const [, openIndex] = tagStack[tagStack.length - 1];
7192
+ const openToken = toks[openIndex];
7193
+ openToken.content = `${String(openToken.content || "")}\n${rawContent}`;
7194
+ openToken.loading = false;
7195
+ tagStack.pop();
7196
+ toks.splice(i, 1);
7197
+ i--;
7198
+ }
7139
7199
  continue;
7140
7200
  } else if (tagStack.length > 0) {
7141
7201
  if (t.type === "paragraph_open" || t.type === "paragraph_close") {
@@ -7156,10 +7216,153 @@ function applyFixHtmlInlineTokens(md, options = {}) {
7156
7216
  i--;
7157
7217
  } else continue;
7158
7218
  }
7219
+ if (customTagSet.size > 0) {
7220
+ const openReCache = /* @__PURE__ */ new Map();
7221
+ const closeReCache = /* @__PURE__ */ new Map();
7222
+ const getOpenRe = (tag) => {
7223
+ let r = openReCache.get(tag);
7224
+ if (!r) {
7225
+ r = new RegExp(`<\\s*${tag}\\b`, "i");
7226
+ openReCache.set(tag, r);
7227
+ }
7228
+ return r;
7229
+ };
7230
+ const getCloseRe = (tag) => {
7231
+ let r = closeReCache.get(tag);
7232
+ if (!r) {
7233
+ r = new RegExp(`<\\s*\\/\\s*${tag}\\s*>`, "i");
7234
+ closeReCache.set(tag, r);
7235
+ }
7236
+ return r;
7237
+ };
7238
+ const stack = [];
7239
+ for (let i = 0; i < toks.length; i++) {
7240
+ const tok = toks[i];
7241
+ const content = String(tok.content ?? "");
7242
+ if (stack.length > 0) {
7243
+ const top = stack[stack.length - 1];
7244
+ const openTok = toks[top.index];
7245
+ if (tok.type === "html_block" && getCloseRe(top.tag).test(content)) {
7246
+ openTok.content = `${String(openTok.content ?? "")}\n${content}`;
7247
+ if (Array.isArray(openTok.children)) openTok.children.push({
7248
+ type: "html_inline",
7249
+ content: `</${top.tag}>`,
7250
+ raw: `</${top.tag}>`
7251
+ });
7252
+ toks.splice(i, 1);
7253
+ i--;
7254
+ stack.pop();
7255
+ continue;
7256
+ }
7257
+ if (tok.type !== "inline") continue;
7258
+ const children = Array.isArray(tok.children) ? tok.children : [];
7259
+ const closeChildIndex = children.findIndex((c) => {
7260
+ if (!c || c.type !== "html_inline") return false;
7261
+ const cContent = String(c.content ?? "");
7262
+ return /^\s*<\s*\//.test(cContent) && cContent.toLowerCase().includes(top.tag);
7263
+ });
7264
+ if (closeChildIndex !== -1) {
7265
+ const beforeChildren = children.slice(0, closeChildIndex + 1);
7266
+ const afterChildren = children.slice(closeChildIndex + 1);
7267
+ const beforeText = beforeChildren.map((c) => String(c?.content ?? c?.raw ?? "")).join("");
7268
+ openTok.content = `${String(openTok.content ?? "")}\n${beforeText}`;
7269
+ if (Array.isArray(openTok.children)) openTok.children.push(...beforeChildren);
7270
+ if (afterChildren.length) {
7271
+ const afterText = afterChildren.map((c) => String(c.content ?? c.raw ?? "")).join("");
7272
+ if (afterText.trim()) {
7273
+ const trimmed = afterText.replace(/^\s+/, "");
7274
+ if (trimmed.startsWith("<")) toks.splice(i, 1, {
7275
+ type: "html_block",
7276
+ content: trimmed
7277
+ });
7278
+ else toks.splice(i, 1, {
7279
+ type: "paragraph_open",
7280
+ tag: "p",
7281
+ nesting: 1
7282
+ }, {
7283
+ type: "inline",
7284
+ tag: "",
7285
+ nesting: 0,
7286
+ content: afterText,
7287
+ children: [{
7288
+ type: "text",
7289
+ content: afterText,
7290
+ raw: afterText
7291
+ }]
7292
+ }, {
7293
+ type: "paragraph_close",
7294
+ tag: "p",
7295
+ nesting: -1
7296
+ });
7297
+ } else {
7298
+ toks.splice(i, 1);
7299
+ i--;
7300
+ }
7301
+ } else {
7302
+ toks.splice(i, 1);
7303
+ i--;
7304
+ }
7305
+ stack.pop();
7306
+ continue;
7307
+ }
7308
+ openTok.content = `${String(openTok.content ?? "")}\n${content}`;
7309
+ if (Array.isArray(openTok.children)) openTok.children.push(...children);
7310
+ toks.splice(i, 1);
7311
+ i--;
7312
+ continue;
7313
+ }
7314
+ if (tok.type !== "inline") continue;
7315
+ for (const tag of customTagSet) if (getOpenRe(tag).test(content) && !getCloseRe(tag).test(content)) {
7316
+ stack.push({
7317
+ tag,
7318
+ index: i
7319
+ });
7320
+ break;
7321
+ }
7322
+ }
7323
+ }
7324
+ {
7325
+ let depth = 0;
7326
+ for (let i = 0; i < toks.length; i++) {
7327
+ const t = toks[i];
7328
+ if (t.type === "paragraph_open") {
7329
+ depth++;
7330
+ continue;
7331
+ }
7332
+ if (t.type === "paragraph_close") if (depth > 0) depth--;
7333
+ else {
7334
+ toks.splice(i, 1);
7335
+ i--;
7336
+ }
7337
+ }
7338
+ }
7159
7339
  for (let i = 0; i < toks.length; i++) {
7160
7340
  const t = toks[i];
7161
7341
  if (t.type === "html_block") {
7162
7342
  const tag = (t.content?.match(/<([^\s>/]+)/)?.[1] ?? "").toLowerCase();
7343
+ if (customTagSet.has(tag)) {
7344
+ const raw$1 = String(t.content ?? "");
7345
+ const closeRe = new RegExp(`<\\/\\s*${tag}\\s*>`, "i");
7346
+ t.loading = closeRe.test(raw$1) ? false : t.loading !== void 0 ? t.loading : true;
7347
+ const closeMatch = closeRe.exec(raw$1);
7348
+ const endTagIndex = closeMatch ? closeMatch.index : -1;
7349
+ const closeLen = closeMatch ? closeMatch[0].length : 0;
7350
+ if (endTagIndex !== -1) {
7351
+ const rawForNode = raw$1.slice(0, endTagIndex + closeLen);
7352
+ t.content = rawForNode;
7353
+ t.raw = rawForNode;
7354
+ const afterTrimmed = (raw$1.slice(endTagIndex + closeLen) || "").replace(/^\s+/, "");
7355
+ if (afterTrimmed) toks.splice(i + 1, 0, afterTrimmed.startsWith("<") ? {
7356
+ type: "html_block",
7357
+ content: afterTrimmed
7358
+ } : {
7359
+ type: "text",
7360
+ content: afterTrimmed,
7361
+ raw: afterTrimmed
7362
+ });
7363
+ }
7364
+ continue;
7365
+ }
7163
7366
  if (tag.startsWith("!") || tag.startsWith("?")) {
7164
7367
  t.loading = false;
7165
7368
  continue;
@@ -7177,7 +7380,7 @@ function applyFixHtmlInlineTokens(md, options = {}) {
7177
7380
  "li"
7178
7381
  ].includes(tag)) continue;
7179
7382
  t.type = "inline";
7180
- const loading = t.content?.toLowerCase().includes(`</${tag}>`) ? false : t.loading !== void 0 ? t.loading : true;
7383
+ const loading = new RegExp(`<\\/\\s*${tag}\\s*>`, "i").test(String(t.content ?? "")) ? false : t.loading !== void 0 ? t.loading : true;
7181
7384
  const attrs = [];
7182
7385
  const attrRegex = /\s([\w:-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g;
7183
7386
  let match;
@@ -7187,24 +7390,36 @@ function applyFixHtmlInlineTokens(md, options = {}) {
7187
7390
  attrs.push([attrName, attrValue]);
7188
7391
  }
7189
7392
  if (customTagSet.has(tag)) {
7190
- const contentMatch = t.content?.match(new RegExp(`<\\s*${tag}[^>]*>([\\s\\S]*)`, "i"));
7191
- const raw$1 = t.content;
7192
- const endTagRegex = new RegExp(`</\\s*${tag}\\s*>`, "i");
7193
- const endTagIndex = t.content?.toLowerCase().indexOf(`</${tag}>`) ?? -1;
7393
+ const raw$1 = String(t.content ?? "");
7394
+ const closeMatch = new RegExp(`<\\/\\s*${tag}\\s*>`, "i").exec(raw$1);
7395
+ const endTagIndex = closeMatch ? closeMatch.index : -1;
7396
+ const closeLen = closeMatch ? closeMatch[0].length : 0;
7397
+ const rawForNode = endTagIndex !== -1 ? raw$1.slice(0, endTagIndex + closeLen) : raw$1;
7398
+ let inner = "";
7399
+ const openEnd = findTagCloseIndexOutsideQuotes$1(raw$1);
7400
+ if (openEnd !== -1) {
7401
+ if (endTagIndex !== -1 && openEnd < endTagIndex) inner = raw$1.slice(openEnd + 1, endTagIndex);
7402
+ else if (endTagIndex === -1) inner = raw$1.slice(openEnd + 1).replace(/<.*$/, "");
7403
+ }
7194
7404
  t.children = [{
7195
7405
  type: tag,
7196
- content: endTagIndex !== -1 ? contentMatch[1].split(endTagRegex)[0] ? contentMatch ? contentMatch[1] : "" : "" : contentMatch ? contentMatch[1].replace(/<.*$/, "") : "",
7197
- raw: raw$1,
7406
+ content: inner,
7407
+ raw: rawForNode,
7198
7408
  attrs,
7199
7409
  tag,
7200
7410
  loading
7201
7411
  }];
7202
7412
  if (endTagIndex !== -1) {
7203
- const afterContent = t.content?.slice(endTagIndex + tag.length + 3) || "";
7204
- if (afterContent.trim()) toks.splice(i + 1, 0, {
7413
+ t.content = rawForNode;
7414
+ t.raw = rawForNode;
7415
+ const afterTrimmed = (raw$1.slice(endTagIndex + closeLen) || "").replace(/^\s+/, "");
7416
+ if (afterTrimmed) toks.splice(i + 1, 0, afterTrimmed.startsWith("<") ? {
7417
+ type: "html_block",
7418
+ content: afterTrimmed
7419
+ } : {
7205
7420
  type: "text",
7206
- content: afterContent,
7207
- raw: afterContent
7421
+ content: afterTrimmed,
7422
+ raw: afterTrimmed
7208
7423
  });
7209
7424
  }
7210
7425
  } else t.children = [{
@@ -7961,7 +8176,7 @@ function fixTableTokens(tokens) {
7961
8176
  if (token.type === "inline") {
7962
8177
  const tcontent = String(token.content ?? "");
7963
8178
  const childContent = String(token.children?.[0]?.content ?? "");
7964
- if (/^\|(?:[^|\n]+\|?)+/.test(tcontent)) {
8179
+ if (!tcontent.includes("\n") && /^\|(?:[^|\n]+\|?)+/.test(tcontent)) {
7965
8180
  const body = childContent.slice(1).split("|").map((i$1) => i$1.trim()).filter(Boolean).flatMap((i$1) => createTh(i$1));
7966
8181
  const insert = [
7967
8182
  ...createStart(),
@@ -8213,6 +8428,16 @@ function countUnescapedStrong(s) {
8213
8428
  while (re.exec(s) !== null) c++;
8214
8429
  return c;
8215
8430
  }
8431
+ function findLastUnescapedStrongMarker(s) {
8432
+ const re = /(^|[^\\])(__|\*\*)/g;
8433
+ let m;
8434
+ let last = null;
8435
+ while ((m = re.exec(s)) !== null) last = {
8436
+ marker: m[2],
8437
+ index: m.index + (m[1]?.length ?? 0)
8438
+ };
8439
+ return last;
8440
+ }
8216
8441
  function normalizeStandaloneBackslashT(s, opts) {
8217
8442
  const commands = opts?.commands ?? KATEX_COMMANDS;
8218
8443
  const escapeExclamation = opts?.escapeExclamation ?? true;
@@ -8297,7 +8522,8 @@ function applyMath(md, mathOpts) {
8297
8522
  foundAny = true;
8298
8523
  if (!silent) {
8299
8524
  s.pending = "";
8300
- const isStrongPrefix = countUnescapedStrong(preMathPos ? src.slice(preMathPos, searchPos) : src.slice(0, searchPos)) % 2 === 1;
8525
+ const toPushBefore = preMathPos ? src.slice(preMathPos, searchPos) : src.slice(0, searchPos);
8526
+ const isStrongPrefix = countUnescapedStrong(toPushBefore) % 2 === 1;
8301
8527
  if (preMathPos) pushText(src.slice(preMathPos, searchPos));
8302
8528
  else {
8303
8529
  let text$1 = src.slice(0, searchPos);
@@ -8305,8 +8531,9 @@ function applyMath(md, mathOpts) {
8305
8531
  pushText(text$1);
8306
8532
  }
8307
8533
  if (isStrongPrefix) {
8534
+ const strongMarker = findLastUnescapedStrongMarker(toPushBefore)?.marker ?? "**";
8308
8535
  const strongToken = s.push("strong_open", "", 0);
8309
- strongToken.markup = src.slice(0, index + 2);
8536
+ strongToken.markup = strongMarker;
8310
8537
  const token = s.push("math_inline", "math", 0);
8311
8538
  token.content = normalizeStandaloneBackslashT(content$1, mathOpts);
8312
8539
  token.markup = open === "$$" ? "$$" : open === "\\(" ? "\\(\\)" : open === "$" ? "$" : "()";
@@ -8343,29 +8570,31 @@ function applyMath(md, mathOpts) {
8343
8570
  let toPushBefore = src.slice(0, searchPos) ? src.slice(preMathPos, index) : before;
8344
8571
  const isStrongPrefix = countUnescapedStrong(toPushBefore) % 2 === 1;
8345
8572
  if (index !== s.pos && isStrongPrefix) toPushBefore = s.pending + src.slice(s.pos, index);
8573
+ const strongMarkerInfo = isStrongPrefix ? findLastUnescapedStrongMarker(toPushBefore) : null;
8574
+ const strongMarker = strongMarkerInfo?.marker ?? "**";
8346
8575
  if (s.pending !== toPushBefore) {
8347
8576
  s.pending = "";
8348
- if (isStrongPrefix) {
8349
- const _match = toPushBefore.match(/(\*+)/);
8350
- const after = toPushBefore.slice(_match.index + _match[0].length);
8351
- pushText(toPushBefore.slice(0, _match.index));
8577
+ if (isStrongPrefix) if (strongMarkerInfo) {
8578
+ const after = toPushBefore.slice(strongMarkerInfo.index + strongMarker.length);
8579
+ pushText(toPushBefore.slice(0, strongMarkerInfo.index));
8352
8580
  const strongToken = s.push("strong_open", "", 0);
8353
- strongToken.markup = _match[0];
8581
+ strongToken.markup = strongMarker;
8354
8582
  const textToken$1 = s.push("text", "", 0);
8355
8583
  textToken$1.content = after;
8356
8584
  s.push("strong_close", "", 0);
8357
8585
  } else pushText(toPushBefore);
8586
+ else pushText(toPushBefore);
8358
8587
  }
8359
8588
  if (isStrongPrefix) {
8360
8589
  const strongToken = s.push("strong_open", "", 0);
8361
- strongToken.markup = "**";
8590
+ strongToken.markup = strongMarker;
8362
8591
  const token = s.push("math_inline", "math", 0);
8363
8592
  token.content = normalizeStandaloneBackslashT(content, mathOpts);
8364
8593
  token.markup = open === "$$" ? "$$" : open === "\\(" ? "\\(\\)" : open === "$" ? "$" : "()";
8365
8594
  token.raw = `${open}${content}${close}`;
8366
8595
  token.loading = false;
8367
8596
  const raw = src.slice(endIdx + close.length);
8368
- const isBeforeClose = raw.startsWith("*");
8597
+ const isBeforeClose = raw.startsWith(strongMarker);
8369
8598
  if (isBeforeClose) s.push("strong_close", "", 0);
8370
8599
  if (raw) {
8371
8600
  s.pos = endIdx + close.length;
@@ -8908,6 +9137,7 @@ function parseHtmlInlineCodeToken(token, tokens, i, parseInlineTokens$1, raw, pP
8908
9137
  tag,
8909
9138
  attrs,
8910
9139
  content: fragment.innerTokens.length ? stringifyTokens(fragment.innerTokens) : "",
9140
+ children: fragment.innerTokens.length ? parseInlineTokens$1(fragment.innerTokens, raw, pPreToken, options) : [],
8911
9141
  raw: content,
8912
9142
  loading: token.loading || loading,
8913
9143
  autoClosed
@@ -9255,7 +9485,7 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
9255
9485
  i++;
9256
9486
  return true;
9257
9487
  }
9258
- if (/\*\*/.test(content) && !content.endsWith("**")) {
9488
+ if (/\*\*/.test(content)) {
9259
9489
  const openIdx = content.indexOf("**");
9260
9490
  const beforeText = openIdx > -1 ? content.slice(0, openIdx) : "";
9261
9491
  if (beforeText) pushText(beforeText, beforeText);
@@ -10169,7 +10399,7 @@ function parseHtmlBlock(token) {
10169
10399
  const isVoid = VOID_TAGS.has(tag);
10170
10400
  let closeRe = CLOSE_TAG_RE_CACHE.get(tag);
10171
10401
  if (!closeRe) {
10172
- closeRe = new RegExp(`<\\/\\s*${tag}\\b`, "i");
10402
+ closeRe = new RegExp(`<\\s*\\/\\s*${tag}\\s*>`, "i");
10173
10403
  CLOSE_TAG_RE_CACHE.set(tag, closeRe);
10174
10404
  }
10175
10405
  const hasClosing = closeRe.test(raw);
@@ -10278,6 +10508,98 @@ function parseThematicBreak() {
10278
10508
 
10279
10509
  //#endregion
10280
10510
  //#region src/parser/node-parsers/block-token-parser.ts
10511
+ function findTagCloseIndexOutsideQuotes(input) {
10512
+ let inSingle = false;
10513
+ let inDouble = false;
10514
+ for (let i = 0; i < input.length; i++) {
10515
+ const ch = input[i];
10516
+ if (ch === "\\") {
10517
+ i++;
10518
+ continue;
10519
+ }
10520
+ if (!inDouble && ch === "'") {
10521
+ inSingle = !inSingle;
10522
+ continue;
10523
+ }
10524
+ if (!inSingle && ch === "\"") {
10525
+ inDouble = !inDouble;
10526
+ continue;
10527
+ }
10528
+ if (!inSingle && !inDouble && ch === ">") return i;
10529
+ }
10530
+ return -1;
10531
+ }
10532
+ function stripWrapperNewlines(s) {
10533
+ return s.replace(/^\r?\n/, "").replace(/\r?\n$/, "");
10534
+ }
10535
+ function stripTrailingPartialClosingTag(inner, tag) {
10536
+ if (!inner || !tag) return inner;
10537
+ const re = new RegExp(String.raw`[\t ]*<\s*\/\s*${tag}[^>]*$`, "i");
10538
+ return inner.replace(re, "");
10539
+ }
10540
+ function findNextCustomHtmlBlockFromSource(source, tag, startIndex) {
10541
+ if (!source || !tag) return null;
10542
+ const lowerTag = tag.toLowerCase();
10543
+ const openRe = new RegExp(String.raw`<\s*${lowerTag}(?=\s|>|/)`, "gi");
10544
+ openRe.lastIndex = Math.max(0, startIndex || 0);
10545
+ const openMatch = openRe.exec(source);
10546
+ if (!openMatch || openMatch.index == null) return null;
10547
+ const openStart = openMatch.index;
10548
+ const openSlice = source.slice(openStart);
10549
+ const openEndRel = findTagCloseIndexOutsideQuotes(openSlice);
10550
+ if (openEndRel === -1) return null;
10551
+ const openEnd = openStart + openEndRel;
10552
+ if (/\/\s*>\s*$/.test(openSlice.slice(0, openEndRel + 1))) {
10553
+ const end = openEnd + 1;
10554
+ return {
10555
+ raw: source.slice(openStart, end),
10556
+ end
10557
+ };
10558
+ }
10559
+ let depth = 1;
10560
+ let i = openEnd + 1;
10561
+ const isOpenAt = (pos) => {
10562
+ const s = source.slice(pos);
10563
+ return new RegExp(String.raw`^<\s*${lowerTag}(?=\s|>|/)`, "i").test(s);
10564
+ };
10565
+ const isCloseAt = (pos) => {
10566
+ const s = source.slice(pos);
10567
+ return new RegExp(String.raw`^<\s*\/\s*${lowerTag}(?=\s|>)`, "i").test(s);
10568
+ };
10569
+ while (i < source.length) {
10570
+ const lt = source.indexOf("<", i);
10571
+ if (lt === -1) return {
10572
+ raw: source.slice(openStart),
10573
+ end: source.length
10574
+ };
10575
+ if (isCloseAt(lt)) {
10576
+ const gt = source.indexOf(">", lt);
10577
+ if (gt === -1) return null;
10578
+ depth--;
10579
+ if (depth === 0) {
10580
+ const end = gt + 1;
10581
+ return {
10582
+ raw: source.slice(openStart, end),
10583
+ end
10584
+ };
10585
+ }
10586
+ i = gt + 1;
10587
+ continue;
10588
+ }
10589
+ if (isOpenAt(lt)) {
10590
+ const rel = findTagCloseIndexOutsideQuotes(source.slice(lt));
10591
+ if (rel === -1) return null;
10592
+ depth++;
10593
+ i = lt + rel + 1;
10594
+ continue;
10595
+ }
10596
+ i = lt + 1;
10597
+ }
10598
+ return {
10599
+ raw: source.slice(openStart),
10600
+ end: source.length
10601
+ };
10602
+ }
10281
10603
  function parseBasicBlockToken(tokens, index, options) {
10282
10604
  const token = tokens[index];
10283
10605
  switch (token.type) {
@@ -10291,10 +10613,37 @@ function parseBasicBlockToken(tokens, index, options) {
10291
10613
  if (new Set(options.customHtmlTags.map((t) => {
10292
10614
  const m = String(t ?? "").trim().match(/^[<\s/]*([A-Z][\w-]*)/i);
10293
10615
  return m ? m[1].toLowerCase() : "";
10294
- }).filter(Boolean)).has(htmlBlockNode.tag)) return [{
10295
- ...htmlBlockNode,
10296
- type: htmlBlockNode.tag
10297
- }, index + 1];
10616
+ }).filter(Boolean)).has(htmlBlockNode.tag)) {
10617
+ const tag = htmlBlockNode.tag;
10618
+ const fromSource = findNextCustomHtmlBlockFromSource(String(options?.__sourceMarkdown ?? ""), tag, Number(options?.__customHtmlBlockCursor ?? 0));
10619
+ if (fromSource) options.__customHtmlBlockCursor = fromSource.end;
10620
+ const rawHtml = String(fromSource?.raw ?? htmlBlockNode.content ?? "");
10621
+ const openEnd = findTagCloseIndexOutsideQuotes(rawHtml);
10622
+ const closeMatch = new RegExp(`<\\s*\\/\\s*${tag}\\s*>`, "i").exec(rawHtml);
10623
+ const closeIndex = closeMatch ? closeMatch.index : -1;
10624
+ let inner = "";
10625
+ if (openEnd !== -1) if (closeIndex !== -1 && openEnd < closeIndex) inner = rawHtml.slice(openEnd + 1, closeIndex);
10626
+ else inner = rawHtml.slice(openEnd + 1);
10627
+ if (closeIndex === -1) inner = stripTrailingPartialClosingTag(inner, tag);
10628
+ const attrs = [];
10629
+ const openTag = openEnd !== -1 ? rawHtml.slice(0, openEnd + 1) : rawHtml;
10630
+ const attrRegex = /\s([\w:-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g;
10631
+ let m;
10632
+ while ((m = attrRegex.exec(openTag)) !== null) {
10633
+ const name = m[1];
10634
+ if (!name || name.toLowerCase() === tag) continue;
10635
+ const value = m[2] || m[3] || m[4] || "";
10636
+ attrs.push([name, value]);
10637
+ }
10638
+ return [{
10639
+ type: tag,
10640
+ tag,
10641
+ content: stripWrapperNewlines(inner),
10642
+ raw: String(fromSource?.raw ?? htmlBlockNode.raw ?? rawHtml),
10643
+ loading: htmlBlockNode.loading,
10644
+ attrs: attrs.length ? attrs : void 0
10645
+ }, index + 1];
10646
+ }
10298
10647
  }
10299
10648
  return [htmlBlockNode, index + 1];
10300
10649
  }
@@ -10608,18 +10957,51 @@ function parseParagraph(tokens, index, options) {
10608
10957
 
10609
10958
  //#endregion
10610
10959
  //#region src/parser/index.ts
10960
+ function stripDanglingHtmlLikeTail(markdown) {
10961
+ const s = String(markdown ?? "");
10962
+ const lastLt = s.lastIndexOf("<");
10963
+ if (lastLt === -1) return s;
10964
+ const tail = s.slice(lastLt);
10965
+ if (tail.includes(">")) return s;
10966
+ if (!/^<\s*(?:\/\s*)?[A-Z!][\s\S]*$/i.test(tail)) return s;
10967
+ return s.slice(0, lastLt);
10968
+ }
10611
10969
  function parseMarkdownToStructure(markdown, md, options = {}) {
10612
10970
  let safeMarkdown = (markdown ?? "").toString().replace(/([^\\])\r(ight|ho)/g, "$1\\r$2").replace(/([^\\])\n(abla|eq|ot|exists)/g, "$1\\n$2");
10613
10971
  if (safeMarkdown.endsWith("- *")) safeMarkdown = safeMarkdown.replace(/- \*$/, "- \\*");
10614
- if (/\n\s*-\s*$/.test(safeMarkdown)) safeMarkdown = safeMarkdown.replace(/\n\s*-\s*$/, "\n");
10972
+ if (/(?:^|\n)\s*-\s*$/.test(safeMarkdown)) safeMarkdown = safeMarkdown.replace(/(?:^|\n)\s*-\s*$/, (m) => {
10973
+ return m.startsWith("\n") ? "\n" : "";
10974
+ });
10975
+ else if (/(?:^|\n)\s*--\s*$/.test(safeMarkdown)) safeMarkdown = safeMarkdown.replace(/(?:^|\n)\s*--\s*$/, (m) => {
10976
+ return m.startsWith("\n") ? "\n" : "";
10977
+ });
10978
+ else if (/(?:^|\n)\s*>\s*$/.test(safeMarkdown)) safeMarkdown = safeMarkdown.replace(/(?:^|\n)\s*>\s*$/, (m) => {
10979
+ return m.startsWith("\n") ? "\n" : "";
10980
+ });
10981
+ else if (/\n\s*[*+]\s*$/.test(safeMarkdown)) safeMarkdown = safeMarkdown.replace(/\n\s*[*+]\s*$/, "\n");
10615
10982
  else if (/\n[[(]\n*$/.test(safeMarkdown)) safeMarkdown = safeMarkdown.replace(/(\n\[|\n\()+\n*$/g, "\n");
10983
+ if (options.customHtmlTags?.length) {
10984
+ const tags = options.customHtmlTags.map((t) => String(t ?? "").trim()).filter(Boolean).map((t) => {
10985
+ return (t.match(/^[<\s/]*([A-Z][\w-]*)/i)?.[1] ?? "").toLowerCase();
10986
+ }).filter(Boolean);
10987
+ if (tags.length) if (!safeMarkdown.includes("</")) {} else for (const tag of tags) {
10988
+ const re = new RegExp(String.raw`(^[\t ]*<\s*\/\s*${tag}\s*>[\t ]*)(\r?\n)(?![\t ]*\r?\n|$)`, "gim");
10989
+ safeMarkdown = safeMarkdown.replace(re, "$1$2$2");
10990
+ }
10991
+ }
10992
+ safeMarkdown = stripDanglingHtmlLikeTail(safeMarkdown);
10616
10993
  const tokens = md.parse(safeMarkdown, {});
10617
10994
  if (!tokens || !Array.isArray(tokens)) return [];
10618
10995
  const pre = options.preTransformTokens;
10619
10996
  const post = options.postTransformTokens;
10620
10997
  let transformedTokens = tokens;
10621
10998
  if (pre && typeof pre === "function") transformedTokens = pre(transformedTokens) || transformedTokens;
10622
- let result = processTokens(transformedTokens, options);
10999
+ const internalOptions = {
11000
+ ...options,
11001
+ __sourceMarkdown: safeMarkdown,
11002
+ __customHtmlBlockCursor: 0
11003
+ };
11004
+ let result = processTokens(transformedTokens, internalOptions);
10623
11005
  if (post && typeof post === "function") {
10624
11006
  const postResult = post(transformedTokens);
10625
11007
  if (Array.isArray(postResult)) {
@@ -10677,6 +11059,20 @@ function processTokens(tokens, options) {
10677
11059
  result.push(parseHardBreak());
10678
11060
  i++;
10679
11061
  break;
11062
+ case "text": {
11063
+ const content = String(token.content ?? "");
11064
+ result.push({
11065
+ type: "paragraph",
11066
+ raw: content,
11067
+ children: content ? [{
11068
+ type: "text",
11069
+ content,
11070
+ raw: content
11071
+ }] : []
11072
+ });
11073
+ i++;
11074
+ break;
11075
+ }
10680
11076
  case "inline":
10681
11077
  result.push(...parseInlineTokens(token.children || [], String(token.content ?? ""), void 0, {
10682
11078
  requireClosingStrong: options?.requireClosingStrong,