@readme/markdown 14.1.1 → 14.1.3

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/main.node.js CHANGED
@@ -107805,7 +107805,12 @@ const tocHastToMdx = (toc, components, variables) => {
107805
107805
 
107806
107806
 
107807
107807
  const sanitizeSchema = cjs_default()(defaultSchema, {
107808
- protocols: ['doc', 'ref', 'blog', 'changelog', 'page'],
107808
+ attributes: {
107809
+ a: ['target'],
107810
+ },
107811
+ protocols: {
107812
+ href: ['doc', 'ref', 'blog', 'changelog', 'page'],
107813
+ },
107809
107814
  });
107810
107815
  const compile_compile = (text, { components = {}, missingComponents, copyButtons, useTailwind, ...opts } = {}) => {
107811
107816
  // Destructure at runtime to avoid circular dependency issues
@@ -117396,17 +117401,46 @@ function mdxComponent() {
117396
117401
 
117397
117402
 
117398
117403
 
117404
+
117405
+
117406
+
117407
+
117408
+ const buildInlineMdProcessor = (safeMode) => {
117409
+ const micromarkExts = [mdxComponent(), syntax_gemoji(), legacyVariable(), magicBlock()];
117410
+ const fromMarkdownExts = [
117411
+ mdxComponentFromMarkdown(),
117412
+ gemojiFromMarkdown(),
117413
+ legacyVariableFromMarkdown(),
117414
+ emptyTaskListItemFromMarkdown(),
117415
+ magicBlockFromMarkdown(),
117416
+ ];
117417
+ // Since evaluating expressions can be dangerous, do so only when safeMode is off
117418
+ if (!safeMode) {
117419
+ const mdxExprExt = mdxExpression({ allowEmpty: true });
117420
+ micromarkExts.push({ text: mdxExprExt.text });
117421
+ fromMarkdownExts.push(mdxExpressionFromMarkdown());
117422
+ }
117423
+ return unified()
117424
+ .data('micromarkExtensions', micromarkExts)
117425
+ .data('fromMarkdownExtensions', fromMarkdownExts)
117426
+ .use(remarkParse)
117427
+ .use(remarkGfm);
117428
+ };
117429
+ const processorCache = new Map();
117399
117430
  /**
117400
- * Shared unified processor for re-parsing the body of an MDX-ish component.
117401
- * Used by both the block and inline-block transformers so a nested component
117402
- * (e.g. `<Anchor>text with <b>bold</b></Anchor>`) goes through the same
117403
- * tokenizer chain as the top-level parse.
117431
+ * Unified processor for re-parsing the body of an MDX component
117432
+ * Memoized based on the argument value so we don't pay the construction cost on every parse
117433
+ * Currently the argument is only safeMode, but we could add more arguments in the future,
117434
+ * in which case the key would need to be extend to include the new arguments.
117404
117435
  */
117405
- const inlineMdProcessor = unified()
117406
- .data('micromarkExtensions', [mdxComponent(), legacyVariable(), magicBlock()])
117407
- .data('fromMarkdownExtensions', [mdxComponentFromMarkdown(), legacyVariableFromMarkdown(), emptyTaskListItemFromMarkdown(), magicBlockFromMarkdown()])
117408
- .use(remarkParse)
117409
- .use(remarkGfm);
117436
+ const getInlineMdProcessor = ({ safeMode = false } = {}) => {
117437
+ let processor = processorCache.get(safeMode);
117438
+ if (!processor) {
117439
+ processor = buildInlineMdProcessor(safeMode);
117440
+ processorCache.set(safeMode, processor);
117441
+ }
117442
+ return processor;
117443
+ };
117410
117444
  /**
117411
117445
  * True when a tag name starts with an uppercase letter — ReadMe's marker for
117412
117446
  * a custom MDX component (vs a lowercase HTML tag).
@@ -117447,11 +117481,11 @@ const toMdxJsxTextElement = (name, attributes, children, position) => ({
117447
117481
  * rejects line endings), so the paragraph-flatten path covers every case we
117448
117482
  * actually produce; other shapes fall back to empty children.
117449
117483
  */
117450
- const parsePhrasingChildren = (value) => {
117484
+ const parsePhrasingChildren = (value, safeMode) => {
117451
117485
  const trimmed = value.trim();
117452
117486
  if (!trimmed)
117453
117487
  return [];
117454
- const [first] = inlineMdProcessor.parse(trimmed).children;
117488
+ const [first] = getInlineMdProcessor({ safeMode }).parse(trimmed).children;
117455
117489
  return first?.type === 'paragraph' ? first.children : [];
117456
117490
  };
117457
117491
  /**
@@ -117464,7 +117498,7 @@ const parsePhrasingChildren = (value) => {
117464
117498
  *
117465
117499
  * Returns the original node unchanged when it doesn't qualify.
117466
117500
  */
117467
- const promoteInlineHtml = (node, parseOpts) => {
117501
+ const promoteInlineHtml = (node, parseOpts, safeMode) => {
117468
117502
  const parsed = parseTag((node.value ?? '').trim(), parseOpts);
117469
117503
  if (!parsed)
117470
117504
  return node;
@@ -117489,7 +117523,7 @@ const promoteInlineHtml = (node, parseOpts) => {
117489
117523
  if (closeIdx < 0)
117490
117524
  return node;
117491
117525
  const inner = contentAfterTag.slice(0, closeIdx);
117492
- return toMdxJsxTextElement(tag, attributes, parsePhrasingChildren(inner), node.position);
117526
+ return toMdxJsxTextElement(tag, attributes, parsePhrasingChildren(inner, safeMode), node.position);
117493
117527
  };
117494
117528
  /**
117495
117529
  * Transform inline html nodes with expression attributes
@@ -117508,9 +117542,10 @@ const promoteInlineHtml = (node, parseOpts) => {
117508
117542
  * `<a href="x">` stays as an html node for rehype-raw.
117509
117543
  */
117510
117544
  const mdxishInlineMdxHtmlBlocks = (opts = {}) => tree => {
117511
- const parseOpts = { preserveExpressionsAsText: !!opts.safeMode };
117545
+ const safeMode = !!opts.safeMode;
117546
+ const parseOpts = { preserveExpressionsAsText: safeMode };
117512
117547
  visit(tree, 'paragraph', (paragraph) => {
117513
- paragraph.children = paragraph.children.map(child => child.type === 'html' ? promoteInlineHtml(child, parseOpts) : child);
117548
+ paragraph.children = paragraph.children.map(child => child.type === 'html' ? promoteInlineHtml(child, parseOpts, safeMode) : child);
117514
117549
  });
117515
117550
  };
117516
117551
  /* harmony default export */ const inline_html = (mdxishInlineMdxHtmlBlocks);
@@ -117593,15 +117628,15 @@ function safeDeindent(text) {
117593
117628
  * Dedents the content first to prevent indented component content
117594
117629
  * (from nested components) from being treated as code blocks.
117595
117630
  */
117596
- const parseMdChildren = (value) => {
117597
- const parsed = inlineMdProcessor.parse(safeDeindent(value).trim());
117631
+ const parseMdChildren = (value, safeMode) => {
117632
+ const parsed = getInlineMdProcessor({ safeMode }).parse(safeDeindent(value).trim());
117598
117633
  return parsed.children || [];
117599
117634
  };
117600
117635
  /**
117601
117636
  * Parse substring content of a node and update the parent's children to include the new nodes.
117602
117637
  */
117603
- const parseSibling = (stack, parent, index, sibling) => {
117604
- const siblingNodes = parseMdChildren(sibling);
117638
+ const parseSibling = (stack, parent, index, sibling, safeMode) => {
117639
+ const siblingNodes = parseMdChildren(sibling, safeMode);
117605
117640
  // The new sibling nodes might contain new components to be processed
117606
117641
  if (siblingNodes.length > 0) {
117607
117642
  parent.children.splice(index + 1, 0, ...siblingNodes);
@@ -117653,7 +117688,8 @@ const substituteNodeWithMdxNode = (parent, index, mdxNode) => {
117653
117688
  */
117654
117689
  const mdxishMdxComponentBlocks = (opts = {}) => tree => {
117655
117690
  const stack = [tree];
117656
- const parseOpts = { preserveExpressionsAsText: !!opts.safeMode };
117691
+ const safeMode = !!opts.safeMode;
117692
+ const parseOpts = { preserveExpressionsAsText: safeMode };
117657
117693
  const processChildNode = (parent, index) => {
117658
117694
  const node = parent.children[index];
117659
117695
  if (!node)
@@ -117699,7 +117735,7 @@ const mdxishMdxComponentBlocks = (opts = {}) => tree => {
117699
117735
  // Check and parse if there's relevant content after the current closing tag
117700
117736
  const remainingContent = contentAfterTag.trim();
117701
117737
  if (remainingContent) {
117702
- parseSibling(stack, parent, index, remainingContent);
117738
+ parseSibling(stack, parent, index, remainingContent, safeMode);
117703
117739
  }
117704
117740
  return;
117705
117741
  }
@@ -117712,7 +117748,7 @@ const mdxishMdxComponentBlocks = (opts = {}) => tree => {
117712
117748
  const componentInnerContent = contentAfterTag.substring(0, closingTagIndex);
117713
117749
  const contentAfterClose = contentAfterTag.substring(closingTagIndex + closingTagStr.length).trim();
117714
117750
  let parsedChildren = componentInnerContent.trim()
117715
- ? parseMdChildren(componentInnerContent)
117751
+ ? parseMdChildren(componentInnerContent, safeMode)
117716
117752
  : [];
117717
117753
  // Lowercase HTML tags are usually inline (e.g. <a>, <span>). Remark wraps
117718
117754
  // bare text in a paragraph; unwrap when there's exactly one paragraph so
@@ -117729,7 +117765,7 @@ const mdxishMdxComponentBlocks = (opts = {}) => tree => {
117729
117765
  substituteNodeWithMdxNode(parent, index, componentNode);
117730
117766
  // After the closing tag, there might be more content to be processed
117731
117767
  if (contentAfterClose) {
117732
- parseSibling(stack, parent, index, contentAfterClose);
117768
+ parseSibling(stack, parent, index, contentAfterClose, safeMode);
117733
117769
  }
117734
117770
  else if (componentNode.children.length > 0) {
117735
117771
  // The content inside the component block might contain new components to be processed
@@ -119639,11 +119675,17 @@ const blockTypes = [
119639
119675
  * Check if a node is a block-level node (cannot be inside a paragraph)
119640
119676
  */
119641
119677
  const isBlockNode = (node) => blockTypes.includes(node.type);
119678
+ const isParagraph = (node) => node.type === 'paragraph';
119679
+ /**
119680
+ * True for phrasing content that contributes only whitespace at render time
119681
+ * (a soft `break` node or a text node with no non-whitespace characters).
119682
+ */
119683
+ const isWhitespacePhrasing = (node) => node.type === 'break' || (node.type === 'text' && !node.value.trim());
119642
119684
  /**
119643
119685
  * Unified plugin that transforms magicBlock nodes into final MDAST nodes.
119644
119686
  */
119645
119687
  const magicBlockTransformer = (options = {}) => tree => {
119646
- const replacements = [];
119688
+ const lifts = [];
119647
119689
  visitParents(tree, 'magicBlock', (node, ancestors) => {
119648
119690
  const parent = ancestors[ancestors.length - 1]; // direct parent of the current node
119649
119691
  const index = parent.children.indexOf(node);
@@ -119657,51 +119699,60 @@ const magicBlockTransformer = (options = {}) => tree => {
119657
119699
  parent.children.splice(index, 1);
119658
119700
  return;
119659
119701
  }
119660
- // If parent is a paragraph and we're inserting block nodes (which must not be in paragraphs), lift them out
119661
- if (parent.type === 'paragraph' && children.some(child => isBlockNode(child))) {
119702
+ // If parent is a paragraph and we're inserting block nodes (which must not be in paragraphs)
119703
+ // it means we need to lift them out
119704
+ if (isParagraph(parent) && children.some(isBlockNode)) {
119662
119705
  const blockNodes = [];
119663
- const inlineNodes = [];
119664
119706
  children.forEach(child => {
119665
- (isBlockNode(child) ? blockNodes : inlineNodes).push(child);
119707
+ if (isBlockNode(child)) {
119708
+ blockNodes.push(child);
119709
+ }
119666
119710
  });
119667
- replacements.push({
119668
- container: ancestors[ancestors.length - 2] || tree, // grandparent of the current node
119711
+ lifts.push({
119712
+ childrenBlockNodes: blockNodes,
119713
+ grandparent: ancestors[ancestors.length - 2] || tree, // grandparent of the current node
119714
+ node,
119669
119715
  parent,
119670
- blockNodes,
119671
- inlineNodes,
119672
- before: parent.children.slice(0, index),
119673
- after: parent.children.slice(index + 1),
119674
119716
  });
119675
119717
  }
119676
119718
  else {
119677
119719
  parent.children.splice(index, 1, ...children);
119678
119720
  }
119679
119721
  });
119680
- // Second pass: apply replacements that require lifting block nodes out of paragraphs
119681
- // Process in reverse order to maintain correct indices
119682
- for (let i = replacements.length - 1; i >= 0; i -= 1) {
119683
- const { after, before, blockNodes, container, inlineNodes, parent } = replacements[i];
119684
- const containerChildren = container.children;
119685
- const paraIndex = containerChildren.indexOf(parent);
119686
- if (paraIndex === -1) {
119687
- parent.children.splice(before.length, 1, ...blockNodes, ...inlineNodes);
119722
+ // Second pass: apply lifts that move block nodes, and the content after them, out of paragraphs.
119723
+ // Operate on live state (find each node's current index now) and process in reverse so
119724
+ // that container insertions don't disturb earlier paragraphs' positions.
119725
+ for (let i = lifts.length - 1; i >= 0; i -= 1) {
119726
+ const { childrenBlockNodes: blockNodes, grandparent, node, parent: parentParagraph } = lifts[i];
119727
+ const nodePosition = parentParagraph.children.indexOf(node);
119728
+ const parentPosition = grandparent.children.indexOf(parentParagraph);
119729
+ if (nodePosition === -1 || parentPosition === -1) {
119688
119730
  // eslint-disable-next-line no-continue
119689
119731
  continue;
119690
119732
  }
119691
- if (inlineNodes.length > 0) {
119692
- parent.children = [...before, ...inlineNodes, ...after];
119693
- if (blockNodes.length > 0) {
119694
- containerChildren.splice(paraIndex + 1, 0, ...blockNodes);
119695
- }
119696
- }
119697
- else if (before.length === 0 && after.length === 0) {
119698
- containerChildren.splice(paraIndex, 1, ...blockNodes);
119733
+ // Snapshot live siblings to reconstruct the parent paragraph around the lifted node.
119734
+ const parentSiblingsBefore = parentParagraph.children.slice(0, nodePosition);
119735
+ const parentSiblingsAfter = parentParagraph.children.slice(nodePosition + 1);
119736
+ // Remove split-edge whitespace so lifted blocks do not render with extra blank lines.
119737
+ while (parentSiblingsBefore.length > 0 && isWhitespacePhrasing(parentSiblingsBefore[parentSiblingsBefore.length - 1]))
119738
+ parentSiblingsBefore.pop();
119739
+ while (parentSiblingsAfter.length > 0 && isWhitespacePhrasing(parentSiblingsAfter[0]))
119740
+ parentSiblingsAfter.shift();
119741
+ const splitOffContentFromParent = [...blockNodes];
119742
+ if (parentSiblingsAfter.length > 0) {
119743
+ // Keep trailing inline content grouped under a paragraph sibling as it might be an inline node
119744
+ // Even if it contains a block node, it will be hoisted out during its turn in the loop
119745
+ const trailingParagraph = { type: 'paragraph', children: parentSiblingsAfter };
119746
+ splitOffContentFromParent.push(trailingParagraph);
119747
+ }
119748
+ parentParagraph.children = [...parentSiblingsBefore];
119749
+ // If the parent paragraph is empty, just replace it with the lifted block nodes
119750
+ const shouldReplaceParent = parentParagraph.children.length === 0;
119751
+ if (shouldReplaceParent) {
119752
+ grandparent.children.splice(parentPosition, 1, ...splitOffContentFromParent);
119699
119753
  }
119700
119754
  else {
119701
- parent.children = [...before, ...after];
119702
- if (blockNodes.length > 0) {
119703
- containerChildren.splice(paraIndex + 1, 0, ...blockNodes);
119704
- }
119755
+ grandparent.children.splice(parentPosition + 1, 0, ...splitOffContentFromParent);
119705
119756
  }
119706
119757
  }
119707
119758
  };
@@ -119771,29 +119822,56 @@ function protectHTMLBlockContent(content) {
119771
119822
  function removeJSXComments(content) {
119772
119823
  return content.replace(JSX_COMMENT_REGEX, '');
119773
119824
  }
119825
+ const HTML_ELEM_PLACEHOLDER_PREFIX = '___MDXISH_HTML_ELEM_';
119826
+ const HTML_ELEM_PLACEHOLDER = new RegExp(`${HTML_ELEM_PLACEHOLDER_PREFIX}(\\d+)___`, 'g');
119827
+ // Matches an HTML element that starts at a line boundary and ends at a line boundary.
119828
+ // Allows optional leading indentation and lazily matches until the same closing tag.
119829
+ const BLOCK_HTML_RE = /(?<=^|\n)[ \t]*<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>[ \t]*(?=\n|$)/g;
119774
119830
  /**
119775
- * Escapes problematic braces in content to prevent MDX expression parsing errors.
119776
- * Handles unbalanced braces and paragraph-spanning expressions. Skips HTML elements
119777
- * so backslashes don't leak into rendered output via rehypeRaw.
119831
+ * Hides line-anchored HTML elements from the brace-escaping pass so we don't leak `\{`
119832
+ * into rendered output (rehypeRaw renders the `\` literally, e.g. `<div>{foo</div>`).
119833
+ *
119834
+ * One carve-out: if an interior line at column 0 has bare text containing `{`, mdxish
119835
+ * parses that line as a paragraph and the mdxExpression step would throw without an
119836
+ * escape — so we leave that case to the brace balancer.
119778
119837
  */
119779
- function escapeProblematicBraces(content) {
119780
- // Skip HTML elements — their content should never be escaped because
119781
- // rehypeRaw parses them into hast elements, making `\` literal text in output
119838
+ function protectHTMLElements(content) {
119782
119839
  const htmlElements = [];
119783
- const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
119784
- const idx = htmlElements.length;
119840
+ const protectedContent = content.replace(BLOCK_HTML_RE, match => {
119841
+ // Look at the lines between the open and close tags. If any of them starts
119842
+ // at column 0 with bare text (not whitespace, not another tag) and contains
119843
+ // `{`, mdxish will parse that line as a paragraph and the brace as an MDX
119844
+ // expression, which would throw an error. So we let the brace balancer escape it.
119845
+ // Otherwise, we need to extract the sequence to protect it from the brace escaping.
119846
+ const interior = match.split('\n').slice(1, -1);
119847
+ const hazard = interior.some(line => line.length > 0 && line[0] !== ' ' && line[0] !== '\t' && line[0] !== '<' && line.includes('{'));
119848
+ if (hazard)
119849
+ return match;
119785
119850
  htmlElements.push(match);
119786
- return `___HTML_ELEM_${idx}___`;
119851
+ return `${HTML_ELEM_PLACEHOLDER_PREFIX}${htmlElements.length - 1}___`;
119787
119852
  });
119788
- const toEscape = new Set();
119789
- // Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
119790
- const chars = Array.from(safe);
119853
+ return { htmlElements, protectedContent };
119854
+ }
119855
+ function restoreHTMLElements(content, htmlElements) {
119856
+ if (htmlElements.length === 0)
119857
+ return content;
119858
+ return content.replace(HTML_ELEM_PLACEHOLDER, (_m, idx) => htmlElements[parseInt(idx, 10)]);
119859
+ }
119860
+ /**
119861
+ * Escapes unbalanced and paragraph-spanning braces so MDX doesn't trip on them.
119862
+ */
119863
+ function escapeProblematicBraces(content) {
119864
+ const { htmlElements, protectedContent } = protectHTMLElements(content);
119791
119865
  let strDelim = null;
119792
119866
  let strEscaped = false;
119793
- // Stack of open braces with their state
119794
- const openStack = [];
119795
119867
  // Track position of last newline (outside strings) to detect blank lines
119796
- let lastNewlinePos = -2; // -2 means no recent newline
119868
+ // -2 means no recent newline
119869
+ let lastNewlinePos = -2;
119870
+ // Character state machine trackers
119871
+ const toEscape = new Set();
119872
+ // Convert to array of Unicode code points so that emojis and multi-byte characters are correctly tracked
119873
+ const chars = Array.from(protectedContent);
119874
+ const openStack = [];
119797
119875
  for (let i = 0; i < chars.length; i += 1) {
119798
119876
  const ch = chars[i];
119799
119877
  // Track string delimiters inside expressions to ignore braces within them
@@ -119813,22 +119891,17 @@ function escapeProblematicBraces(content) {
119813
119891
  // eslint-disable-next-line no-continue
119814
119892
  continue;
119815
119893
  }
119816
- // Track newlines to detect blank lines (paragraph boundaries)
119817
119894
  if (ch === '\n') {
119818
- // Check if this newline creates a blank line (only whitespace since last newline)
119819
119895
  if (lastNewlinePos >= 0) {
119820
119896
  const between = chars.slice(lastNewlinePos + 1, i).join('');
119821
119897
  if (/^[ \t]*$/.test(between)) {
119822
- // This is a blank line - mark all open expressions as paragraph-spanning
119823
- openStack.forEach(entry => {
119824
- entry.hasBlankLine = true;
119825
- });
119898
+ openStack.forEach(entry => { entry.hasBlankLine = true; });
119826
119899
  }
119827
119900
  }
119828
119901
  lastNewlinePos = i;
119829
119902
  }
119830
119903
  }
119831
- // Skip already-escaped braces (count preceding backslashes)
119904
+ // Skip already-escaped braces (odd run of preceding backslashes).
119832
119905
  if (ch === '{' || ch === '}') {
119833
119906
  let bs = 0;
119834
119907
  for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
@@ -119839,10 +119912,9 @@ function escapeProblematicBraces(content) {
119839
119912
  }
119840
119913
  }
119841
119914
  if (ch === '{') {
119842
- // If preceded by `=` (ignoring whitespace), this is a JSX attribute
119843
- // expression (e.g. `data={[...]}`). The mdxComponent tokenizer captures
119844
- // the entire component block, so blank lines inside attribute values
119845
- // won't split paragraphs — skip the blank-line check for these.
119915
+ // `=` (after whitespace) before `{` JSX attribute expression. The
119916
+ // mdxComponent tokenizer captures the whole component, so blank lines
119917
+ // inside attribute values are harmless. Nested `{` inherits the flag.
119846
119918
  let isAttrExpr = false;
119847
119919
  for (let j = i - 1; j >= 0; j -= 1) {
119848
119920
  const pc = chars[j];
@@ -119856,27 +119928,17 @@ function escapeProblematicBraces(content) {
119856
119928
  // Nested `{ ... }` inside an attribute value (e.g. `data={[{ ... }]}` or
119857
119929
  // `data={{ a: { b: 1 } }}`) must inherit the same exemption; only the
119858
119930
  // outer `{` is directly after `=`.
119859
- if (!isAttrExpr && openStack.length > 0) {
119860
- const parent = openStack[openStack.length - 1];
119861
- if (parent.isAttrExpr) {
119862
- isAttrExpr = true;
119863
- }
119931
+ if (!isAttrExpr && openStack.length > 0 && openStack[openStack.length - 1].isAttrExpr) {
119932
+ isAttrExpr = true;
119864
119933
  }
119865
119934
  openStack.push({ pos: i, hasBlankLine: false, isAttrExpr });
119866
- lastNewlinePos = -2; // Reset newline tracking for new expression
119935
+ lastNewlinePos = -2;
119867
119936
  }
119868
119937
  else if (ch === '}') {
119869
119938
  if (openStack.length > 0) {
119870
119939
  const entry = openStack.pop();
119871
- // Don't escape pure JSX comments, the `jsxComment` tokenizer downstream
119872
- // already knows how to swallow a whole `{/* ... */}` block in one go,
119873
- // even if the body has blank lines in it. If we escape the braces here
119874
- // the tokenizer never gets a shot at it.
119875
- //
119876
- // "Pure" means the braces open with `{/*` and close with `*/}` right
119877
- // next to each other. Something like `{/* c */ expr\n\nmore}` is just
119878
- // a regular expression that happens to start with a comment, so it
119879
- // still needs the normal blank-line protection.
119940
+ // Pure `{/* ... */}` comments are handled downstream by the jsxComment
119941
+ // tokenizer escaping their braces would prevent it from running.
119880
119942
  const isPureJsxComment = chars[entry.pos + 1] === '/' &&
119881
119943
  chars[entry.pos + 2] === '*' &&
119882
119944
  chars[i - 1] === '/' &&
@@ -119887,21 +119949,15 @@ function escapeProblematicBraces(content) {
119887
119949
  }
119888
119950
  }
119889
119951
  else {
119890
- // Unbalanced closing brace (no matching open)
119891
119952
  toEscape.add(i);
119892
119953
  }
119893
119954
  }
119894
119955
  }
119895
- // Any remaining open braces are unbalanced
119956
+ // Anything still open is unbalanced.
119896
119957
  openStack.forEach(entry => toEscape.add(entry.pos));
119897
- // If there are no problematic braces, return safe content as-is;
119898
- // otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
119899
- let result = toEscape.size === 0 ? safe : chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
119900
- // Restore HTML elements
119901
- if (htmlElements.length > 0) {
119902
- result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
119903
- }
119904
- return result;
119958
+ // Reconstruct the content with the escaped braces.
119959
+ const escapedContent = toEscape.size === 0 ? protectedContent : chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
119960
+ return restoreHTMLElements(escapedContent, htmlElements);
119905
119961
  }
119906
119962
  /**
119907
119963
  * Preprocesses JSX-like markdown content before parsing.
@@ -122047,15 +122103,18 @@ function mdxishAstProcessor(mdContent, opts = {}) {
122047
122103
  .use(remarkParse)
122048
122104
  .use(remarkFrontmatter)
122049
122105
  .use(normalize_malformed_md_syntax)
122050
- .use(magic_block_transformer)
122051
- .use(transform_images, { isMdxish: true })
122052
- .use(defaultTransformers)
122053
122106
  .use(self_closing_blocks)
122054
122107
  .use(mdx_blocks, { safeMode })
122055
122108
  .use(inline_html, { safeMode })
122056
122109
  .use(restore_snake_case_component_name, { mapping: snakeCaseMapping })
122057
122110
  .use(mdxish_tables)
122058
122111
  .use(mdxish_html_blocks)
122112
+ // The next few transformers must appear after mdxishMdxComponentBlocks
122113
+ // so nodes produced by the inline re-parse of component bodies
122114
+ // (e.g. code/image/embed inside <Tabs>) get visited too
122115
+ .use(magic_block_transformer)
122116
+ .use(transform_images, { isMdxish: true })
122117
+ .use(defaultTransformers)
122059
122118
  .use(newEditorTypes ? inline_mdx_blocks : undefined) // Merge inline html components (e.g. <Anchor>) into MDAST nodes
122060
122119
  .use(newEditorTypes ? mdxish_jsx_to_mdast : undefined) // Convert block JSX elements to MDAST types
122061
122120
  .use(variables_text) // Parse {user.*} patterns from text nodes