stream-markdown-parser 0.0.85 → 0.0.87

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
@@ -6308,7 +6308,7 @@ function replaceUnsafeChar(ch) {
6308
6308
  /**
6309
6309
  * Escape HTML characters to prevent XSS
6310
6310
  */
6311
- function escapeHtml(str) {
6311
+ function escapeHtml$2(str) {
6312
6312
  if (str.length === 0) return "";
6313
6313
  if (str.length < 32) {
6314
6314
  if (HTML_ESCAPE_TEST_RE.test(str)) return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar);
@@ -6362,16 +6362,16 @@ function renderAttrName(name) {
6362
6362
  case "style":
6363
6363
  case "target":
6364
6364
  case "title": return name;
6365
- default: return escapeHtml(name);
6365
+ default: return escapeHtml$2(name);
6366
6366
  }
6367
6367
  }
6368
6368
  function renderAttrsFromList(attrs) {
6369
6369
  if (!attrs || attrs.length === 0) return "";
6370
6370
  const firstAttr = attrs[0];
6371
- let result = ` ${renderAttrName(firstAttr[0])}="${escapeHtml(firstAttr[1])}"`;
6371
+ let result = ` ${renderAttrName(firstAttr[0])}="${escapeHtml$2(firstAttr[1])}"`;
6372
6372
  for (let i = 1; i < attrs.length; i++) {
6373
6373
  const attr = attrs[i];
6374
- result += ` ${renderAttrName(attr[0])}="${escapeHtml(attr[1])}"`;
6374
+ result += ` ${renderAttrName(attr[0])}="${escapeHtml$2(attr[1])}"`;
6375
6375
  }
6376
6376
  return result;
6377
6377
  }
@@ -6404,7 +6404,7 @@ function parseFenceInfo(info) {
6404
6404
  function renderFence(token, highlighted, info, langName, options) {
6405
6405
  if (highlighted.indexOf("<pre") === 0) return `${highlighted}\n`;
6406
6406
  if (info) {
6407
- if (!token.attrs || token.attrs.length === 0) return `<pre><code class="${escapeHtml(`${options.langPrefix ?? "language-"}${langName}`)}">${highlighted}</code></pre>\n`;
6407
+ if (!token.attrs || token.attrs.length === 0) return `<pre><code class="${escapeHtml$2(`${options.langPrefix ?? "language-"}${langName}`)}">${highlighted}</code></pre>\n`;
6408
6408
  const classIndex = token.attrIndex("class");
6409
6409
  const tmpAttrs = token.attrs ? token.attrs.slice() : [];
6410
6410
  const langClass = `${options.langPrefix ?? "language-"}${langName}`;
@@ -6418,11 +6418,11 @@ function renderFence(token, highlighted, info, langName, options) {
6418
6418
  return `<pre><code${renderAttrsFromList(token.attrs)}>${highlighted}</code></pre>\n`;
6419
6419
  }
6420
6420
  function renderCodeInlineToken(token) {
6421
- if (!token.attrs || token.attrs.length === 0) return `<code>${escapeHtml(token.content)}</code>`;
6422
- return `<code${renderAttrsFromList(token.attrs)}>${escapeHtml(token.content)}</code>`;
6421
+ if (!token.attrs || token.attrs.length === 0) return `<code>${escapeHtml$2(token.content)}</code>`;
6422
+ return `<code${renderAttrsFromList(token.attrs)}>${escapeHtml$2(token.content)}</code>`;
6423
6423
  }
6424
6424
  function renderCodeBlockToken(token) {
6425
- const content = escapeHtml(token.content);
6425
+ const content = escapeHtml$2(token.content);
6426
6426
  if (!token.attrs) return `<pre><code>${content}</code></pre>\n`;
6427
6427
  return `<pre${renderAttrsFromList(token.attrs)}><code>${content}</code></pre>\n`;
6428
6428
  }
@@ -6436,16 +6436,16 @@ function renderFastBlockOpenWithInline(token, prefix) {
6436
6436
  default: return null;
6437
6437
  }
6438
6438
  if (attrs.length === 1 && attrs[0][0] === "style") {
6439
- if (token.type === "td_open") return `${prefix}<td style="${escapeHtml(attrs[0][1])}">`;
6440
- if (token.type === "th_open") return `${prefix}<th style="${escapeHtml(attrs[0][1])}">`;
6439
+ if (token.type === "td_open") return `${prefix}<td style="${escapeHtml$2(attrs[0][1])}">`;
6440
+ if (token.type === "th_open") return `${prefix}<th style="${escapeHtml$2(attrs[0][1])}">`;
6441
6441
  }
6442
6442
  return null;
6443
6443
  }
6444
6444
  function renderLinkOpenToken(token) {
6445
6445
  const attrs = token.attrs;
6446
6446
  if (!attrs || attrs.length === 0) return "<a>";
6447
- if (attrs.length === 1) return `<a ${renderAttrName(attrs[0][0])}="${escapeHtml(attrs[0][1])}">`;
6448
- if (attrs.length === 2) return `<a ${renderAttrName(attrs[0][0])}="${escapeHtml(attrs[0][1])}" ${renderAttrName(attrs[1][0])}="${escapeHtml(attrs[1][1])}">`;
6447
+ if (attrs.length === 1) return `<a ${renderAttrName(attrs[0][0])}="${escapeHtml$2(attrs[0][1])}">`;
6448
+ if (attrs.length === 2) return `<a ${renderAttrName(attrs[0][0])}="${escapeHtml$2(attrs[0][1])}" ${renderAttrName(attrs[1][0])}="${escapeHtml$2(attrs[1][1])}">`;
6449
6449
  return `<a${renderAttrsFromList(attrs)}>`;
6450
6450
  }
6451
6451
  function canUseLinkInlineFastPath(token) {
@@ -6502,7 +6502,7 @@ const defaultRules = {
6502
6502
  const info = token.info ? unescapeAll(token.info).trim() : "";
6503
6503
  const { langName, langAttrs } = parseFenceInfo(info);
6504
6504
  const highlight = options.highlight;
6505
- const fallback = escapeHtml(token.content);
6505
+ const fallback = escapeHtml$2(token.content);
6506
6506
  if (!highlight) return renderFence(token, fallback, info, langName, options);
6507
6507
  const highlighted = highlight(token.content, langName, langAttrs);
6508
6508
  if (isPromiseLike(highlighted)) return highlighted.then((res) => renderFence(token, res || fallback, info, langName, options));
@@ -6524,10 +6524,10 @@ const defaultRules = {
6524
6524
  return options.breaks ? options.xhtmlOut ? "<br />\n" : "<br>\n" : "\n";
6525
6525
  },
6526
6526
  text(tokens, idx) {
6527
- return escapeHtml(tokens[idx].content);
6527
+ return escapeHtml$2(tokens[idx].content);
6528
6528
  },
6529
6529
  text_special(tokens, idx) {
6530
- return escapeHtml(tokens[idx].content);
6530
+ return escapeHtml$2(tokens[idx].content);
6531
6531
  },
6532
6532
  html_block(tokens, idx) {
6533
6533
  return tokens[idx].content;
@@ -6540,7 +6540,7 @@ function renderFenceSyncToken(token, options, _self) {
6540
6540
  const info = token.info ? unescapeAll(token.info).trim() : "";
6541
6541
  const { langName, langAttrs } = parseFenceInfo(info);
6542
6542
  const highlight = options.highlight;
6543
- const fallback = escapeHtml(token.content);
6543
+ const fallback = escapeHtml$2(token.content);
6544
6544
  if (!highlight) return renderFence(token, fallback, info, langName, options);
6545
6545
  const highlighted = highlight(token.content, langName, langAttrs);
6546
6546
  if (isPromiseLike(highlighted)) throw new TypeError("Renderer rule \"fence\" returned a Promise. Use renderAsync() instead.");
@@ -6550,10 +6550,10 @@ function renderSingleInlineTokenSync(tokens, options, env, self, rules, inlineBr
6550
6550
  const token = tokens[0];
6551
6551
  switch (token.type) {
6552
6552
  case "text":
6553
- if (rules.text === defaultRules.text) return token.content.length === 0 ? "" : escapeHtml(token.content);
6553
+ if (rules.text === defaultRules.text) return token.content.length === 0 ? "" : escapeHtml$2(token.content);
6554
6554
  break;
6555
6555
  case "text_special":
6556
- if (rules.text_special === defaultRules.text_special) return token.content.length === 0 ? "" : escapeHtml(token.content);
6556
+ if (rules.text_special === defaultRules.text_special) return token.content.length === 0 ? "" : escapeHtml$2(token.content);
6557
6557
  break;
6558
6558
  case "softbreak":
6559
6559
  if (rules.softbreak === defaultRules.softbreak) return softbreak;
@@ -6653,13 +6653,13 @@ var Renderer = class {
6653
6653
  switch (child.type) {
6654
6654
  case "text":
6655
6655
  if (textRule === defaultRules.text) {
6656
- result += escapeHtml(child.content);
6656
+ result += escapeHtml$2(child.content);
6657
6657
  continue;
6658
6658
  }
6659
6659
  break;
6660
6660
  case "text_special":
6661
6661
  if (textSpecialRule === defaultRules.text_special) {
6662
- result += escapeHtml(child.content);
6662
+ result += escapeHtml$2(child.content);
6663
6663
  continue;
6664
6664
  }
6665
6665
  break;
@@ -6780,15 +6780,15 @@ var Renderer = class {
6780
6780
  else if (attrs.length === 1) {
6781
6781
  const attr = attrs[0];
6782
6782
  if (type === "ordered_list_open" && attr[0] === "start") {
6783
- result += `${prefix}<ol start="${escapeHtml(attr[1])}">\n`;
6783
+ result += `${prefix}<ol start="${escapeHtml$2(attr[1])}">\n`;
6784
6784
  continue;
6785
6785
  }
6786
6786
  if (type === "td_open" && attr[0] === "style") {
6787
- result += `${prefix}<td style="${escapeHtml(attr[1])}">`;
6787
+ result += `${prefix}<td style="${escapeHtml$2(attr[1])}">`;
6788
6788
  continue;
6789
6789
  }
6790
6790
  if (type === "th_open" && attr[0] === "style") {
6791
- result += `${prefix}<th style="${escapeHtml(attr[1])}">`;
6791
+ result += `${prefix}<th style="${escapeHtml$2(attr[1])}">`;
6792
6792
  continue;
6793
6793
  }
6794
6794
  }
@@ -6987,7 +6987,7 @@ var Renderer = class {
6987
6987
  switch (token.type) {
6988
6988
  case "text":
6989
6989
  if (textRule === defaultRules.text) {
6990
- const escaped = token.content.length === 0 ? "" : escapeHtml(token.content);
6990
+ const escaped = token.content.length === 0 ? "" : escapeHtml$2(token.content);
6991
6991
  if (htmlInlineRule === defaultRules.html_inline && i + 1 < tokens.length && tokens[i + 1].type === "html_inline") {
6992
6992
  result += escaped + tokens[++i].content;
6993
6993
  while (i + 1 < tokens.length && tokens[i + 1].type === "html_inline") result += tokens[++i].content;
@@ -6999,7 +6999,7 @@ var Renderer = class {
6999
6999
  break;
7000
7000
  case "text_special":
7001
7001
  if (textSpecialRule === defaultRules.text_special) {
7002
- if (token.content.length !== 0) result += escapeHtml(token.content);
7002
+ if (token.content.length !== 0) result += escapeHtml$2(token.content);
7003
7003
  continue;
7004
7004
  }
7005
7005
  break;
@@ -12848,6 +12848,13 @@ function parseLinkToken(tokens, startIndex, options) {
12848
12848
  i++;
12849
12849
  }
12850
12850
  if (tokens[i]?.type === "link_close") loading = false;
12851
+ const lastLinkToken = linkTokens[linkTokens.length - 1];
12852
+ if (options?.__insideStrong && lastLinkToken?.type === "text" && String(lastLinkToken.content ?? "").endsWith("**") && !linkTokens.some((token) => token.type === "strong_open")) {
12853
+ const originalContent = String(lastLinkToken.content ?? "");
12854
+ const originalRaw = String(lastLinkToken.raw ?? originalContent);
12855
+ lastLinkToken.content = originalContent.slice(0, -2);
12856
+ lastLinkToken.raw = originalRaw.replace(/\*\*$/, "");
12857
+ }
12851
12858
  const children = parseInlineTokens(linkTokens, void 0, void 0, options);
12852
12859
  const linkText = children.map((node) => {
12853
12860
  const nodeAny = node;
@@ -12942,7 +12949,10 @@ function parseStrongToken(tokens, startIndex, raw, options) {
12942
12949
  innerTokens.push(tokens[i]);
12943
12950
  i++;
12944
12951
  }
12945
- children.push(...parseInlineTokens(innerTokens, resolveInnerRaw(raw, strongText), void 0, options));
12952
+ children.push(...parseInlineTokens(innerTokens, resolveInnerRaw(raw, strongText), void 0, {
12953
+ ...options,
12954
+ __insideStrong: true
12955
+ }));
12946
12956
  return {
12947
12957
  node: {
12948
12958
  type: "strong",
@@ -13022,7 +13032,6 @@ function parseTextToken(token) {
13022
13032
 
13023
13033
  //#endregion
13024
13034
  //#region src/parser/inline-parsers/index.ts
13025
- const STRONG_PAIR_RE = /\*\*([\s\S]*?)\*\*/;
13026
13035
  const STRIKETHROUGH_RE = /[^~]*~{2,}[^~]+/;
13027
13036
  const HAS_STRONG_RE = /\*\*/;
13028
13037
  const INLINE_REPARSE_MARKER_RE = /[[_*^~]/;
@@ -13041,6 +13050,9 @@ const ESCAPABLE_PUNCTUATION = new Set([
13041
13050
  "-",
13042
13051
  "!"
13043
13052
  ]);
13053
+ const WHITESPACE_RE = /\s/u;
13054
+ const ASCII_PUNCTUATION_RE = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/;
13055
+ const UNICODE_PUNCTUATION_RE = /\p{P}/u;
13044
13056
  const AUTOLINK_PROTOCOL_RE = /^(?:https?:\/\/|mailto:|ftp:\/\/)/i;
13045
13057
  const AUTOLINK_GENERIC_RE = /:\/\//;
13046
13058
  function countUnescapedAsterisks(str) {
@@ -13077,6 +13089,72 @@ function findNextUnescapedAsterisk(rawContent, startContentIndex = 0) {
13077
13089
  }
13078
13090
  return -1;
13079
13091
  }
13092
+ function isWhitespaceChar(ch) {
13093
+ return !!ch && WHITESPACE_RE.test(ch);
13094
+ }
13095
+ function isPunctuationChar(ch) {
13096
+ return !!ch && (ASCII_PUNCTUATION_RE.test(ch) || UNICODE_PUNCTUATION_RE.test(ch));
13097
+ }
13098
+ function isEmphasisOpenDelimiter(content, index) {
13099
+ const prev = index > 0 ? content[index - 1] : void 0;
13100
+ const next = content[index + 1];
13101
+ if (!next || isWhitespaceChar(next)) return false;
13102
+ return !(isPunctuationChar(next) && !!prev && !isWhitespaceChar(prev) && !isPunctuationChar(prev));
13103
+ }
13104
+ function isEmphasisCloseDelimiter(content, index) {
13105
+ const prev = index > 0 ? content[index - 1] : void 0;
13106
+ const next = content[index + 1];
13107
+ if (!prev || isWhitespaceChar(prev)) return false;
13108
+ return !(isPunctuationChar(prev) && !!next && !isWhitespaceChar(next) && !isPunctuationChar(next));
13109
+ }
13110
+ function findNextUnescapedEmphasisClose(rawContent, content, startContentIndex = 0) {
13111
+ let searchIndex = startContentIndex;
13112
+ let sawInvalidClose = false;
13113
+ while (searchIndex < content.length) {
13114
+ const closeIndex = rawContent ? findNextUnescapedAsterisk(rawContent, searchIndex) : content.indexOf("*", searchIndex);
13115
+ if (closeIndex === -1) break;
13116
+ if (isEmphasisCloseDelimiter(content, closeIndex)) return {
13117
+ index: closeIndex,
13118
+ sawInvalidClose
13119
+ };
13120
+ sawInvalidClose = true;
13121
+ searchIndex = closeIndex + 1;
13122
+ }
13123
+ return {
13124
+ index: -1,
13125
+ sawInvalidClose
13126
+ };
13127
+ }
13128
+ function isStrongOpenDelimiter(content, index) {
13129
+ const prev = index > 0 ? content[index - 1] : void 0;
13130
+ const next = content[index + 2];
13131
+ if (!next || isWhitespaceChar(next)) return false;
13132
+ return !(isPunctuationChar(next) && !!prev && !isWhitespaceChar(prev) && !isPunctuationChar(prev));
13133
+ }
13134
+ function isStrongCloseDelimiter(content, index) {
13135
+ const prev = index > 0 ? content[index - 1] : void 0;
13136
+ const next = content[index + 2];
13137
+ if (!prev || isWhitespaceChar(prev)) return false;
13138
+ return !(isPunctuationChar(prev) && !!next && !isWhitespaceChar(next) && !isPunctuationChar(next));
13139
+ }
13140
+ function findNextStrongClose(content, startContentIndex = 0) {
13141
+ let searchIndex = startContentIndex;
13142
+ let sawInvalidClose = false;
13143
+ while (searchIndex < content.length) {
13144
+ const closeIndex = content.indexOf("**", searchIndex);
13145
+ if (closeIndex === -1) break;
13146
+ if (isStrongCloseDelimiter(content, closeIndex)) return {
13147
+ index: closeIndex,
13148
+ sawInvalidClose
13149
+ };
13150
+ sawInvalidClose = true;
13151
+ searchIndex = closeIndex + 2;
13152
+ }
13153
+ return {
13154
+ index: -1,
13155
+ sawInvalidClose
13156
+ };
13157
+ }
13080
13158
  function decodeVisibleTextFromRaw(rawText) {
13081
13159
  let output = "";
13082
13160
  let index = 0;
@@ -13148,6 +13226,42 @@ function getAsteriskRunInfo(content, start) {
13148
13226
  intraword: isWordChar(prev) && isWordChar(next)
13149
13227
  };
13150
13228
  }
13229
+ function findLiteralIntrawordAsteriskRunPairEnd(content) {
13230
+ const runs = [];
13231
+ for (let index = 0; index < content.length;) {
13232
+ if (content[index] !== "*") {
13233
+ index++;
13234
+ continue;
13235
+ }
13236
+ const info = getAsteriskRunInfo(content, index);
13237
+ const end = index + info.len;
13238
+ if (info.len >= 2 && info.intraword) runs.push({
13239
+ start: index,
13240
+ end
13241
+ });
13242
+ index = end;
13243
+ }
13244
+ for (let index = 0; index < runs.length - 1; index++) {
13245
+ const current = runs[index];
13246
+ const next = runs[index + 1];
13247
+ if (!isWordOnly(content.slice(current.end, next.start))) return next.end;
13248
+ }
13249
+ return -1;
13250
+ }
13251
+ function isTripleAsteriskInnerText(text$1) {
13252
+ return !!text$1 && text$1.trim() === text$1 && /^[\p{L}\p{N}\s]+$/u.test(text$1);
13253
+ }
13254
+ function findTripleAsteriskClose(content, start) {
13255
+ let searchIndex = start;
13256
+ while (searchIndex < content.length) {
13257
+ const index = content.indexOf("***", searchIndex);
13258
+ if (index === -1) return -1;
13259
+ const info = getAsteriskRunInfo(content, index);
13260
+ if (info.len >= 3) return index;
13261
+ searchIndex = index + info.len;
13262
+ }
13263
+ return -1;
13264
+ }
13151
13265
  function isLikelyUrl(href) {
13152
13266
  if (!href) return false;
13153
13267
  return AUTOLINK_PROTOCOL_RE.test(href) || AUTOLINK_GENERIC_RE.test(href);
@@ -13170,6 +13284,21 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
13170
13284
  function handleEmphasisAndStrikethrough(content, token) {
13171
13285
  const rawSource = tokens.length === 1 ? raw : String(token.content ?? "");
13172
13286
  const markerCandidates = [];
13287
+ const literalIntrawordRunPairEnd = findLiteralIntrawordAsteriskRunPairEnd(content);
13288
+ if (literalIntrawordRunPairEnd !== -1) {
13289
+ pushText(content.slice(0, literalIntrawordRunPairEnd), content.slice(0, literalIntrawordRunPairEnd));
13290
+ const afterContent = content.slice(literalIntrawordRunPairEnd);
13291
+ if (afterContent) {
13292
+ handleToken({
13293
+ type: "text",
13294
+ content: afterContent,
13295
+ raw: afterContent
13296
+ });
13297
+ i--;
13298
+ }
13299
+ i++;
13300
+ return true;
13301
+ }
13173
13302
  if (STRIKETHROUGH_RE.test(content)) {
13174
13303
  const idx = content.indexOf("~~");
13175
13304
  if (idx !== -1) markerCandidates.push({
@@ -13287,13 +13416,92 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
13287
13416
  }
13288
13417
  }
13289
13418
  const runInfo = getAsteriskRunInfo(content, openIdx);
13290
- const exec = STRONG_PAIR_RE.exec(content);
13419
+ if (runInfo.len >= 3) {
13420
+ const closeIndex = findTripleAsteriskClose(content, openIdx + runInfo.len);
13421
+ if (closeIndex !== -1) {
13422
+ const inner$1 = content.slice(openIdx + runInfo.len, closeIndex);
13423
+ if (isTripleAsteriskInnerText(inner$1)) {
13424
+ const { node: node$1 } = parseStrongToken([
13425
+ {
13426
+ type: "strong_open",
13427
+ tag: "strong",
13428
+ content: "",
13429
+ markup: "**",
13430
+ info: "",
13431
+ meta: null
13432
+ },
13433
+ {
13434
+ type: "em_open",
13435
+ tag: "em",
13436
+ content: "",
13437
+ markup: "*",
13438
+ info: "",
13439
+ meta: null
13440
+ },
13441
+ {
13442
+ type: "text",
13443
+ tag: "",
13444
+ content: inner$1,
13445
+ markup: "",
13446
+ info: "",
13447
+ meta: null
13448
+ },
13449
+ {
13450
+ type: "em_close",
13451
+ tag: "em",
13452
+ content: "",
13453
+ markup: "*",
13454
+ info: "",
13455
+ meta: null
13456
+ },
13457
+ {
13458
+ type: "strong_close",
13459
+ tag: "strong",
13460
+ content: "",
13461
+ markup: "**",
13462
+ info: "",
13463
+ meta: null
13464
+ }
13465
+ ], 0, raw, options);
13466
+ resetCurrentTextNode();
13467
+ pushNode(node$1);
13468
+ const afterContent = content.slice(closeIndex + 3);
13469
+ if (afterContent) {
13470
+ handleToken({
13471
+ type: "text",
13472
+ content: afterContent,
13473
+ raw: afterContent
13474
+ });
13475
+ i--;
13476
+ }
13477
+ i++;
13478
+ return true;
13479
+ }
13480
+ }
13481
+ }
13482
+ if (!isStrongOpenDelimiter(content, openIdx)) {
13483
+ const literalRun = content.slice(openIdx, openIdx + runInfo.len);
13484
+ pushText(literalRun, literalRun);
13485
+ const afterContent = content.slice(openIdx + runInfo.len);
13486
+ if (afterContent) {
13487
+ handleToken({
13488
+ type: "text",
13489
+ content: afterContent,
13490
+ raw: afterContent
13491
+ });
13492
+ i--;
13493
+ }
13494
+ i++;
13495
+ return true;
13496
+ }
13497
+ const close = findNextStrongClose(content, openIdx + 2);
13291
13498
  let inner = "";
13292
13499
  let after = "";
13293
- if (exec && typeof exec.index === "number") {
13294
- inner = exec[1];
13295
- after = content.slice(exec.index + exec[0].length);
13296
- const closeRunInfo = getAsteriskRunInfo(content, exec.index + exec[0].length - 2);
13500
+ if (close.index !== -1) {
13501
+ inner = content.slice(openIdx + 2, close.index);
13502
+ after = content.slice(close.index + 2);
13503
+ const closeIdx = close.index;
13504
+ const closeRunInfo = getAsteriskRunInfo(content, closeIdx);
13297
13505
  if (runInfo.intraword && closeRunInfo.intraword && !isWordOnly(inner)) {
13298
13506
  pushText(content.slice(beforeText.length), content.slice(beforeText.length));
13299
13507
  i++;
@@ -13305,7 +13513,7 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
13305
13513
  return true;
13306
13514
  }
13307
13515
  } else {
13308
- if (requireClosingStrong) {
13516
+ if (requireClosingStrong || close.sawInvalidClose) {
13309
13517
  pushText(content.slice(beforeText.length), content.slice(beforeText.length));
13310
13518
  i++;
13311
13519
  return true;
@@ -13367,15 +13575,30 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
13367
13575
  if (idx === -1) idx = 0;
13368
13576
  const _text = content.slice(0, idx);
13369
13577
  if (_text) pushText(_text, _text);
13578
+ if (!isEmphasisOpenDelimiter(content, idx)) {
13579
+ pushText(content[idx], content[idx]);
13580
+ const afterContent = content.slice(idx + 1);
13581
+ if (afterContent) {
13582
+ handleToken({
13583
+ type: "text",
13584
+ content: afterContent,
13585
+ raw: afterContent
13586
+ });
13587
+ i--;
13588
+ }
13589
+ i++;
13590
+ return true;
13591
+ }
13370
13592
  const runInfo = getAsteriskRunInfo(content, idx);
13371
- const closeIndex = rawSource ? findNextUnescapedAsterisk(rawSource, idx + 1) : content.indexOf("*", idx + 1);
13593
+ const close = findNextUnescapedEmphasisClose(rawSource, content, idx + 1);
13594
+ const closeIndex = close.index;
13372
13595
  const nextInlineToken = tokens[i + 1];
13373
13596
  if (options?.final && nextInlineToken?.type === "em_open" && closeIndex !== -1 && content.slice(idx + 1, closeIndex).trim() !== content.slice(idx + 1, closeIndex)) {
13374
13597
  pushText(content.slice(idx), content.slice(idx));
13375
13598
  i++;
13376
13599
  return true;
13377
13600
  }
13378
- if (closeIndex === -1 && (options?.final || runInfo.intraword || !isWordChar(content[idx + 1]))) {
13601
+ if (closeIndex === -1 && (close.sawInvalidClose || options?.final || runInfo.intraword || !isWordChar(content[idx + 1]))) {
13379
13602
  pushText(content.slice(idx), content.slice(idx));
13380
13603
  i++;
13381
13604
  return true;
@@ -13392,7 +13615,7 @@ function parseInlineTokens(tokens, raw, pPreToken, options) {
13392
13615
  {
13393
13616
  type: "text",
13394
13617
  tag: "",
13395
- content: content.slice(idx, closeIndex > -1 ? closeIndex + 1 : void 0).replace(/\*/g, ""),
13618
+ content: closeIndex > -1 ? content.slice(idx + 1, closeIndex) : content.slice(idx + 1),
13396
13619
  markup: "",
13397
13620
  info: "",
13398
13621
  meta: null
@@ -15300,7 +15523,7 @@ function normalizeIndentedSourceForLookup(value) {
15300
15523
  }
15301
15524
  function canFindNodeRawAfterSourceIndex(source, startIndex, nodeRaw) {
15302
15525
  if (!nodeRaw) return false;
15303
- if (source.indexOf(nodeRaw, startIndex) !== -1) return true;
15526
+ if (source.includes(nodeRaw, startIndex)) return true;
15304
15527
  return normalizeIndentedSourceForLookup(source.slice(Math.max(0, startIndex))).includes(normalizeIndentedSourceForLookup(nodeRaw));
15305
15528
  }
15306
15529
  function extendHtmlBlockCloseToLineEnding(source, startIndex) {
@@ -15352,7 +15575,7 @@ const STRUCTURED_HTML_WRAPPER_BLOCK_TYPES = new Set([
15352
15575
  "table",
15353
15576
  "thematic_break"
15354
15577
  ]);
15355
- const STRUCTURED_HTML_WRAPPER_MARKER_RE = /(?:^|\n)(?:\s{0,3}(?:#{1,6}\s+\S|[-+*]\s+\S|\d+[.)]\s+\S|>\s*\S|(?:`{3,}|~{3,})|(?:\*{3,}|-{3,}|_{3,})(?:\s|$)|\|.*\|))/m;
15578
+ const STRUCTURED_HTML_WRAPPER_MARKER_RE = /(?:^|\n)\s{0,3}(?:#{1,6}\s+\S|[-+*]\s+\S|\d+[.)]\s+\S|>\s*\S|`{3,}|~{3,}|(?:\*{3,}|-{3,}|_{3,})(?:\s|$)|\|.*\|)/m;
15356
15579
  function hasStructuredHtmlWrapperMarkers(fragment) {
15357
15580
  return /\n\s*\n/.test(fragment) || STRUCTURED_HTML_WRAPPER_MARKER_RE.test(fragment);
15358
15581
  }
@@ -16501,6 +16724,38 @@ const CUSTOM_TAG_REGEX = /<([a-z][a-z0-9-]*)\b[^>]*>/gi;
16501
16724
  function hasOwn(obj, key) {
16502
16725
  return Object.prototype.hasOwnProperty.call(obj, key);
16503
16726
  }
16727
+ function getString(value) {
16728
+ return typeof value === "string" ? value : value == null ? "" : String(value);
16729
+ }
16730
+ function isSafeAttrName(value) {
16731
+ return /^[^\s"'<>`=]+$/.test(value) && !/^on/i.test(value);
16732
+ }
16733
+ function escapeHtml(value) {
16734
+ return getString(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
16735
+ }
16736
+ function escapeAttr(value) {
16737
+ return escapeHtml(value).replace(/`/g, "&#96;");
16738
+ }
16739
+ function normalizeTagName(tagName) {
16740
+ return String(tagName ?? "").trim().toLowerCase();
16741
+ }
16742
+ function serializeAttrs(attrs) {
16743
+ const pairs = Object.entries(attrs);
16744
+ if (pairs.length === 0) return "";
16745
+ return pairs.map(([name, value]) => value === "" ? ` ${name}` : ` ${name}="${escapeAttr(value)}"`).join("");
16746
+ }
16747
+ function sanitizeHtmlContentAttrs(attrs) {
16748
+ const clean = {};
16749
+ for (const [key, value] of Object.entries(attrs)) {
16750
+ const safeName = key.trim();
16751
+ const lowerKey = safeName.toLowerCase();
16752
+ if (!safeName || !isSafeAttrName(safeName)) continue;
16753
+ if (DANGEROUS_HTML_ATTRS.has(lowerKey)) continue;
16754
+ if (URL_HTML_ATTRS.has(lowerKey) && value && isUnsafeHtmlUrl(value)) continue;
16755
+ clean[safeName] = value;
16756
+ }
16757
+ return clean;
16758
+ }
16504
16759
  function isCustomHtmlComponentTag(tagName, customComponents) {
16505
16760
  const lowerTag = tagName.toLowerCase();
16506
16761
  if (EXTENDED_STANDARD_HTML_TAGS.has(lowerTag)) return false;
@@ -16654,6 +16909,95 @@ function tokenizeHtml(html) {
16654
16909
  }
16655
16910
  return tokens;
16656
16911
  }
16912
+ function tokenizeHtmlPreservingText(html) {
16913
+ const tokens = [];
16914
+ let pos = 0;
16915
+ while (pos < html.length) {
16916
+ if (html.startsWith("<!--", pos)) {
16917
+ const commentEnd = html.indexOf("-->", pos);
16918
+ if (commentEnd !== -1) {
16919
+ pos = commentEnd + 3;
16920
+ continue;
16921
+ }
16922
+ break;
16923
+ }
16924
+ const tagStart = html.indexOf("<", pos);
16925
+ if (tagStart === -1) {
16926
+ if (pos < html.length) tokens.push({
16927
+ type: "text",
16928
+ content: html.slice(pos)
16929
+ });
16930
+ break;
16931
+ }
16932
+ if (tagStart > pos) tokens.push({
16933
+ type: "text",
16934
+ content: html.slice(pos, tagStart)
16935
+ });
16936
+ if (html.startsWith("![CDATA[", tagStart + 1)) {
16937
+ const cdataEnd = html.indexOf("]]>", tagStart);
16938
+ if (cdataEnd !== -1) {
16939
+ tokens.push({
16940
+ type: "text",
16941
+ content: html.slice(tagStart, cdataEnd + 3)
16942
+ });
16943
+ pos = cdataEnd + 3;
16944
+ continue;
16945
+ }
16946
+ break;
16947
+ }
16948
+ if (html.startsWith("!", tagStart + 1)) {
16949
+ const specialEnd = html.indexOf(">", tagStart);
16950
+ if (specialEnd !== -1) {
16951
+ pos = specialEnd + 1;
16952
+ continue;
16953
+ }
16954
+ break;
16955
+ }
16956
+ const tagEnd = html.indexOf(">", tagStart);
16957
+ if (tagEnd === -1) break;
16958
+ const tagContent = html.slice(tagStart + 1, tagEnd).trim();
16959
+ if (!tagContent) {
16960
+ pos = tagEnd + 1;
16961
+ continue;
16962
+ }
16963
+ const isClosingTag$1 = tagContent.startsWith("/");
16964
+ const isSelfClosing$1 = tagContent.endsWith("/");
16965
+ if (isClosingTag$1) {
16966
+ const tagName$1 = tagContent.slice(1).trim();
16967
+ tokens.push({
16968
+ type: "tag_close",
16969
+ tagName: tagName$1
16970
+ });
16971
+ pos = tagEnd + 1;
16972
+ continue;
16973
+ }
16974
+ const spaceIndex = tagContent.indexOf(" ");
16975
+ let tagName = "";
16976
+ let attrsStr = "";
16977
+ if (spaceIndex === -1) tagName = isSelfClosing$1 ? tagContent.slice(0, -1).trim() : tagContent.trim();
16978
+ else {
16979
+ tagName = tagContent.slice(0, spaceIndex).trim();
16980
+ attrsStr = tagContent.slice(spaceIndex + 1);
16981
+ }
16982
+ const attrs = {};
16983
+ if (attrsStr) {
16984
+ const attrRegex = /([^\s=]+)(?:=(?:"([^"]*)"|'([^']*)'|(\S*)))?/g;
16985
+ let attrMatch;
16986
+ while ((attrMatch = attrRegex.exec(attrsStr)) !== null) {
16987
+ const name = attrMatch[1];
16988
+ const value = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
16989
+ if (name && !name.endsWith("/")) attrs[name] = value;
16990
+ }
16991
+ }
16992
+ tokens.push({
16993
+ type: isSelfClosing$1 || VOID_HTML_TAGS.has(tagName.toLowerCase()) ? "self_closing" : "tag_open",
16994
+ tagName,
16995
+ attrs
16996
+ });
16997
+ pos = tagEnd + 1;
16998
+ }
16999
+ return tokens;
17000
+ }
16657
17001
  function hasCustomHtmlComponents(content, customComponents) {
16658
17002
  if (!content || !content.includes("<")) return false;
16659
17003
  if (!customComponents || Object.keys(customComponents).length === 0) return false;
@@ -16662,6 +17006,49 @@ function hasCustomHtmlComponents(content, customComponents) {
16662
17006
  while ((match = CUSTOM_TAG_REGEX.exec(content)) !== null) if (isCustomHtmlComponentTag(match[1], customComponents)) return true;
16663
17007
  return false;
16664
17008
  }
17009
+ function sanitizeHtmlContent(content) {
17010
+ if (!content) return "";
17011
+ const tokens = tokenizeHtmlPreservingText(content);
17012
+ const stack = [];
17013
+ const output = [];
17014
+ let blockedDepth = 0;
17015
+ for (const token of tokens) {
17016
+ if (token.type === "text") {
17017
+ if (blockedDepth === 0) output.push(escapeHtml(token.content ?? ""));
17018
+ continue;
17019
+ }
17020
+ const tagName = normalizeTagName(token.tagName);
17021
+ if (!tagName) continue;
17022
+ if (BLOCKED_HTML_TAGS.has(tagName)) {
17023
+ if (token.type === "tag_open") blockedDepth += 1;
17024
+ else if (token.type === "tag_close" && blockedDepth > 0) blockedDepth -= 1;
17025
+ continue;
17026
+ }
17027
+ if (blockedDepth > 0) continue;
17028
+ if (token.type === "self_closing") {
17029
+ output.push(`<${tagName}${serializeAttrs(sanitizeHtmlContentAttrs(token.attrs ?? {}))}>`);
17030
+ continue;
17031
+ }
17032
+ if (token.type === "tag_open") {
17033
+ output.push(`<${tagName}${serializeAttrs(sanitizeHtmlContentAttrs(token.attrs ?? {}))}>`);
17034
+ if (!VOID_HTML_TAGS.has(tagName)) stack.push(tagName);
17035
+ continue;
17036
+ }
17037
+ const matchedIndex = stack.lastIndexOf(tagName);
17038
+ if (matchedIndex === -1) continue;
17039
+ while (stack.length > matchedIndex + 1) {
17040
+ const danglingTag = stack.pop();
17041
+ if (danglingTag) output.push(`</${danglingTag}>`);
17042
+ }
17043
+ const closingTag = stack.pop();
17044
+ if (closingTag) output.push(`</${closingTag}>`);
17045
+ }
17046
+ while (stack.length > 0) {
17047
+ const danglingTag = stack.pop();
17048
+ if (danglingTag) output.push(`</${danglingTag}>`);
17049
+ }
17050
+ return output.join("");
17051
+ }
16665
17052
 
16666
17053
  //#endregion
16667
17054
  //#region src/index.ts
@@ -16804,5 +17191,5 @@ function getMarkdown(msgId = `editor-${Date.now()}`, options = {}) {
16804
17191
  }
16805
17192
 
16806
17193
  //#endregion
16807
- 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, NON_STRUCTURING_HTML_TAGS, NON_STRUCTURING_HTML_TAG_NAMES, 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, convertHtmlAttrsToProps, convertHtmlPropValue, findMatchingClose, getHtmlTagFromContent, getMarkdown, hasCompleteHtmlTagContent, hasCustomHtmlComponents, isCustomHtmlComponentTag, isHtmlLikeTagName, isMathLike, isUnsafeHtmlUrl, mergeCustomHtmlTags, normalizeCustomHtmlTagName, normalizeCustomHtmlTags, normalizeStandaloneBackslashT, parseFenceToken, parseInlineTokens, parseMarkdownToStructure, processTokens, registerMarkdownPlugin, resolveCustomHtmlTags, sanitizeHtmlAttrs, sanitizeHtmlTokenAttrs, setDefaultMathOptions, shouldRenderUnknownHtmlTagAsText, stripCustomHtmlWrapper, stripHtmlControlAndWhitespace, tokenAttrsToRecord, tokenizeHtml };
17194
+ 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, NON_STRUCTURING_HTML_TAGS, NON_STRUCTURING_HTML_TAG_NAMES, 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, convertHtmlAttrsToProps, convertHtmlPropValue, findMatchingClose, getHtmlTagFromContent, getMarkdown, hasCompleteHtmlTagContent, hasCustomHtmlComponents, isCustomHtmlComponentTag, isHtmlLikeTagName, isMathLike, isUnsafeHtmlUrl, mergeCustomHtmlTags, normalizeCustomHtmlTagName, normalizeCustomHtmlTags, normalizeStandaloneBackslashT, parseFenceToken, parseInlineTokens, parseMarkdownToStructure, processTokens, registerMarkdownPlugin, resolveCustomHtmlTags, sanitizeHtmlAttrs, sanitizeHtmlContent, sanitizeHtmlTokenAttrs, setDefaultMathOptions, shouldRenderUnknownHtmlTagAsText, stripCustomHtmlWrapper, stripHtmlControlAndWhitespace, tokenAttrsToRecord, tokenizeHtml };
16808
17195
  //# sourceMappingURL=index.js.map