@readme/markdown 11.15.0 → 12.0.1

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
@@ -73147,12 +73147,45 @@ const plain = (node, opts = {}) => {
73147
73147
  };
73148
73148
  /* harmony default export */ const lib_plain = (plain);
73149
73149
 
73150
+ ;// ./processor/transform/extract-text.ts
73151
+ /**
73152
+ * Extracts text content from a single AST node recursively.
73153
+ * Works with both MDAST and HAST-like node structures.
73154
+ *
73155
+ * Placed this outside of the utils.ts file to avoid circular dependencies.
73156
+ *
73157
+ * @param node - The node to extract text from (can be MDAST Node or HAST-like structure)
73158
+ * @returns The concatenated text content
73159
+ */
73160
+ const extractText = (node) => {
73161
+ if (node.type === 'text' && typeof node.value === 'string') {
73162
+ return node.value;
73163
+ }
73164
+ // When a blockquote contains only an image (no text), treat it as having content
73165
+ // so the blockquote is no longer treated as empty and preserved correctly.
73166
+ if (node.type === 'image') {
73167
+ return typeof node.alt === 'string' && node.alt ? node.alt : '[image]';
73168
+ }
73169
+ if (node.children && Array.isArray(node.children)) {
73170
+ return node.children
73171
+ .map(child => {
73172
+ if (child && typeof child === 'object' && 'type' in child) {
73173
+ return extractText(child);
73174
+ }
73175
+ return '';
73176
+ })
73177
+ .join('');
73178
+ }
73179
+ return '';
73180
+ };
73181
+
73150
73182
  ;// ./processor/transform/callouts.ts
73151
73183
 
73152
73184
 
73153
73185
 
73154
73186
 
73155
73187
 
73188
+
73156
73189
  const callouts_regex = `^(${emoji_regex().source}|⚠)(\\s+|$)`;
73157
73190
  const findFirst = (node) => {
73158
73191
  if ('children' in node)
@@ -73173,42 +73206,76 @@ const wrapHeading = (node) => {
73173
73206
  },
73174
73207
  };
73175
73208
  };
73209
+ /**
73210
+ * Checks if a blockquote matches the expected callout structure:
73211
+ * blockquote > paragraph > text node
73212
+ */
73213
+ const isCalloutStructure = (node) => {
73214
+ const firstChild = node.children?.[0];
73215
+ if (!firstChild || firstChild.type !== 'paragraph')
73216
+ return false;
73217
+ if (!('children' in firstChild))
73218
+ return false;
73219
+ const firstTextChild = firstChild.children?.[0];
73220
+ return firstTextChild?.type === 'text';
73221
+ };
73222
+ const processBlockquote = (node, index, parent) => {
73223
+ if (!isCalloutStructure(node)) {
73224
+ // Only stringify empty blockquotes (no extractable text content)
73225
+ // Preserve blockquotes with actual content (e.g., headings, lists, etc.)
73226
+ const content = extractText(node);
73227
+ const isEmpty = !content || content.trim() === '';
73228
+ if (isEmpty && index !== undefined && parent) {
73229
+ const textNode = {
73230
+ type: 'text',
73231
+ value: '>',
73232
+ };
73233
+ const paragraphNode = {
73234
+ type: 'paragraph',
73235
+ children: [textNode],
73236
+ position: node.position,
73237
+ };
73238
+ parent.children.splice(index, 1, paragraphNode);
73239
+ }
73240
+ return;
73241
+ }
73242
+ // isCalloutStructure ensures node.children[0] is a Paragraph with children
73243
+ const firstParagraph = node.children[0];
73244
+ const startText = lib_plain(firstParagraph).toString();
73245
+ const [match, icon] = startText.match(callouts_regex) || [];
73246
+ if (icon && match) {
73247
+ const heading = startText.slice(match.length);
73248
+ const empty = !heading.length && firstParagraph.children.length === 1;
73249
+ const theme = themes[icon] || 'default';
73250
+ const firstChild = findFirst(node.children[0]);
73251
+ if (firstChild && 'value' in firstChild && typeof firstChild.value === 'string') {
73252
+ firstChild.value = firstChild.value.slice(match.length);
73253
+ }
73254
+ if (heading) {
73255
+ node.children[0] = wrapHeading(node);
73256
+ // @note: We add to the offset/column the length of the unicode
73257
+ // character that was stripped off, so that the start position of the
73258
+ // heading/text matches where it actually starts.
73259
+ node.children[0].position.start.offset += match.length;
73260
+ node.children[0].position.start.column += match.length;
73261
+ }
73262
+ Object.assign(node, {
73263
+ type: NodeTypes.callout,
73264
+ data: {
73265
+ hName: 'Callout',
73266
+ hProperties: {
73267
+ icon,
73268
+ ...(empty && { empty }),
73269
+ theme,
73270
+ },
73271
+ },
73272
+ });
73273
+ }
73274
+ };
73176
73275
  const calloutTransformer = () => {
73177
73276
  return (tree) => {
73178
- visit(tree, 'blockquote', (node) => {
73179
- if (!(node.children[0].type === 'paragraph' && node.children[0].children[0].type === 'text'))
73180
- return;
73181
- // @ts-expect-error -- @todo: update plain to accept mdast
73182
- const startText = lib_plain(node.children[0]).toString();
73183
- const [match, icon] = startText.match(callouts_regex) || [];
73184
- if (icon && match) {
73185
- const heading = startText.slice(match.length);
73186
- const empty = !heading.length && node.children[0].children.length === 1;
73187
- const theme = themes[icon] || 'default';
73188
- const firstChild = findFirst(node.children[0]);
73189
- if (firstChild && 'value' in firstChild && typeof firstChild.value === 'string') {
73190
- firstChild.value = firstChild.value.slice(match.length);
73191
- }
73192
- if (heading) {
73193
- node.children[0] = wrapHeading(node);
73194
- // @note: We add to the offset/column the length of the unicode
73195
- // character that was stripped off, so that the start position of the
73196
- // heading/text matches where it actually starts.
73197
- node.children[0].position.start.offset += match.length;
73198
- node.children[0].position.start.column += match.length;
73199
- }
73200
- Object.assign(node, {
73201
- type: NodeTypes.callout,
73202
- data: {
73203
- hName: 'Callout',
73204
- hProperties: {
73205
- icon,
73206
- ...(empty && { empty }),
73207
- theme,
73208
- },
73209
- },
73210
- });
73211
- }
73277
+ visit(tree, 'blockquote', (node, index, parent) => {
73278
+ processBlockquote(node, index, parent);
73212
73279
  });
73213
73280
  };
73214
73281
  };
@@ -90874,6 +90941,17 @@ const parseTag = (value) => {
90874
90941
  contentAfterTag,
90875
90942
  };
90876
90943
  };
90944
+ /**
90945
+ * Parse substring content of a node and update the parent's children to include the new nodes.
90946
+ */
90947
+ const parseSibling = (stack, parent, index, sibling) => {
90948
+ const siblingNodes = parseMdChildren(sibling);
90949
+ // The new sibling nodes might contain new components to be processed
90950
+ if (siblingNodes.length > 0) {
90951
+ parent.children.splice(index + 1, 0, ...siblingNodes);
90952
+ stack.push(parent);
90953
+ }
90954
+ };
90877
90955
  /**
90878
90956
  * Create an MdxJsxFlowElement node from component data.
90879
90957
  */
@@ -91016,11 +91094,12 @@ const mdxishComponentBlocks = () => tree => {
91016
91094
  if ('children' in node && Array.isArray(node.children)) {
91017
91095
  stack.push(node);
91018
91096
  }
91019
- // Only visit HTML nodes with an actual html tag
91097
+ // Only visit HTML nodes with an actual html tag,
91098
+ // which means a potential unparsed MDX component
91020
91099
  const value = node.value;
91021
91100
  if (node.type !== 'html' || typeof value !== 'string')
91022
91101
  return;
91023
- const parsed = parseTag(value);
91102
+ const parsed = parseTag(value.trim());
91024
91103
  if (!parsed)
91025
91104
  return;
91026
91105
  const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
@@ -91037,11 +91116,19 @@ const mdxishComponentBlocks = () => tree => {
91037
91116
  startPosition: node.position,
91038
91117
  });
91039
91118
  substituteNodeWithMdxNode(parent, index, componentNode);
91119
+ // Check and parse if there's relevant content after the current closing tag
91120
+ const remainingContent = contentAfterTag.trim();
91121
+ if (remainingContent) {
91122
+ parseSibling(stack, parent, index, remainingContent);
91123
+ }
91040
91124
  return;
91041
91125
  }
91042
91126
  // Case 2: Self-contained block (closing tag in content)
91043
91127
  if (contentAfterTag.includes(closingTagStr)) {
91044
- const componentInnerContent = contentAfterTag.substring(0, contentAfterTag.lastIndexOf(closingTagStr)).trim();
91128
+ // Find the first closing tag
91129
+ const closingTagIndex = contentAfterTag.indexOf(closingTagStr);
91130
+ const componentInnerContent = contentAfterTag.substring(0, closingTagIndex).trim();
91131
+ const contentAfterClose = contentAfterTag.substring(closingTagIndex + closingTagStr.length).trim();
91045
91132
  const componentNode = createComponentNode({
91046
91133
  tag,
91047
91134
  attributes,
@@ -91049,6 +91136,14 @@ const mdxishComponentBlocks = () => tree => {
91049
91136
  startPosition: node.position,
91050
91137
  });
91051
91138
  substituteNodeWithMdxNode(parent, index, componentNode);
91139
+ // After the closing tag, there might be more content to be processed
91140
+ if (contentAfterClose) {
91141
+ parseSibling(stack, parent, index, contentAfterClose);
91142
+ }
91143
+ else if (componentNode.children.length > 0) {
91144
+ // The content inside the component block might contain new components to be processed
91145
+ stack.push(componentNode);
91146
+ }
91052
91147
  return;
91053
91148
  }
91054
91149
  // Case 3: Multi-sibling component (closing tag in a following sibling)
@@ -91074,8 +91169,13 @@ const mdxishComponentBlocks = () => tree => {
91074
91169
  });
91075
91170
  // Remove all nodes from opening tag to closing tag (inclusive) and replace with component node
91076
91171
  parent.children.splice(index, closingIndex - index + 1, componentNode);
91172
+ // Since we might be merging sibling nodes together and combining content,
91173
+ // there might be new components to process
91174
+ if (componentNode.children.length > 0) {
91175
+ stack.push(componentNode);
91176
+ }
91077
91177
  };
91078
- // Travel the tree depth-first
91178
+ // Process the nodes with the components depth-first to maintain the correct order of the nodes
91079
91179
  while (stack.length) {
91080
91180
  const parent = stack.pop();
91081
91181
  if (parent?.children) {
@@ -91096,6 +91196,7 @@ const mdxishComponentBlocks = () => tree => {
91096
91196
 
91097
91197
 
91098
91198
 
91199
+
91099
91200
  const isTableCell = (node) => isMDXElement(node) && ['th', 'td'].includes(node.name);
91100
91201
  const tableTypes = {
91101
91202
  tr: 'tableRow',
@@ -91120,18 +91221,13 @@ const isTextOnly = (children) => {
91120
91221
  });
91121
91222
  };
91122
91223
  /**
91123
- * Extract text content from children nodes
91224
+ * Convenience wrapper that extracts text content from an array of children nodes.
91124
91225
  */
91125
- const extractText = (children) => {
91226
+ const extractTextFromChildren = (children) => {
91126
91227
  return children
91127
91228
  .map(child => {
91128
91229
  if (child && typeof child === 'object' && 'type' in child) {
91129
- if (child.type === 'text' && 'value' in child && typeof child.value === 'string') {
91130
- return child.value;
91131
- }
91132
- if (child.type === 'mdxJsxTextElement' && 'children' in child && Array.isArray(child.children)) {
91133
- return extractText(child.children);
91134
- }
91230
+ return extractText(child);
91135
91231
  }
91136
91232
  return '';
91137
91233
  })
@@ -91162,7 +91258,7 @@ const processTableNode = (node, index, parent) => {
91162
91258
  let parsedChildren = cellChildren;
91163
91259
  // If cell contains only text nodes, try to re-parse as markdown
91164
91260
  if (isTextOnly(cellChildren)) {
91165
- const textContent = extractText(cellChildren);
91261
+ const textContent = extractTextFromChildren(cellChildren);
91166
91262
  if (textContent.trim()) {
91167
91263
  try {
91168
91264
  const parsed = parseMarkdown(textContent);
@@ -106577,7 +106673,7 @@ const CUSTOM_PROP_BOUNDARIES = [
106577
106673
  /**
106578
106674
  * Tags that should be passed through and handled at runtime (not by the mdxish plugin)
106579
106675
  */
106580
- const RUNTIME_COMPONENT_TAGS = new Set(['Variable', 'variable']);
106676
+ const RUNTIME_COMPONENT_TAGS = new Set(['Variable', 'variable', 'rdme-pin']);
106581
106677
  /**
106582
106678
  * Standard HTML tags that should never be treated as custom components.
106583
106679
  * Uses the html-tags package, converted to a Set<string> for efficient lookups.
@@ -113208,6 +113304,33 @@ const INLINE_COMPONENT_TAGS = new Set(['anchor', 'glossary']);
113208
113304
  function isElementContentNode(node) {
113209
113305
  return node.type === 'element' || node.type === 'text' || node.type === 'comment';
113210
113306
  }
113307
+ /**
113308
+ * Components are assumed to be block-level, so whitespace between them can be removed
113309
+ * We want to remove them because it can be treated as actual children of the component
113310
+ * when it doesn't need to.
113311
+ *
113312
+ * It can be a problem because it can be passed as args to the components, like the ones
113313
+ * defined in /components, which can break the type assumptions of the components and cause
113314
+ * type errors, accessing properties that don't exist.
113315
+ */
113316
+ function areAllChildrenComponents(children) {
113317
+ return children.every(child => {
113318
+ // Whitespace-only text nodes don't affect the check
113319
+ if (child.type === 'text' && !child.value.trim())
113320
+ return true;
113321
+ // Text with actual content means we have mixed content
113322
+ if (child.type === 'text')
113323
+ return false;
113324
+ // Comments don't affect the check
113325
+ if (child.type === 'comment')
113326
+ return true;
113327
+ // Standard HTML tags are not considered components
113328
+ if (child.type === 'element' && 'tagName' in child) {
113329
+ return !STANDARD_HTML_TAGS.has(child.tagName.toLowerCase());
113330
+ }
113331
+ return false;
113332
+ });
113333
+ }
113211
113334
  /** Check if nodes represent a single paragraph with only text (no markdown formatting) */
113212
113335
  function isSingleParagraphTextNode(nodes) {
113213
113336
  return (nodes.length === 1 &&
@@ -113252,6 +113375,7 @@ function isActualHtmlTag(tagName, originalExcerpt) {
113252
113375
  function parseTextChildren(node, processMarkdown) {
113253
113376
  if (!node.children?.length)
113254
113377
  return;
113378
+ // First pass: Recursively process text children as they may contain stringified markdown / mdx content
113255
113379
  node.children = node.children.flatMap(child => {
113256
113380
  if (child.type !== 'text' || !child.value.trim())
113257
113381
  return [child];
@@ -113263,6 +113387,15 @@ function parseTextChildren(node, processMarkdown) {
113263
113387
  }
113264
113388
  return children;
113265
113389
  });
113390
+ // Post-processing: remove whitespace-only text nodes if all siblings are components
113391
+ // This prevents whitespace between component children from being counted as extra children
113392
+ if (areAllChildrenComponents(node.children)) {
113393
+ node.children = node.children.filter(child => {
113394
+ if (child.type === 'text' && !child.value.trim())
113395
+ return false;
113396
+ return true;
113397
+ });
113398
+ }
113266
113399
  }
113267
113400
  /** Convert node properties from kebab-case/lowercase to camelCase */
113268
113401
  function normalizeProperties(node) {
@@ -113325,6 +113458,15 @@ const mdxExpressionHandler = (_state, node) => ({
113325
113458
  type: 'text',
113326
113459
  value: node.value || '',
113327
113460
  });
113461
+ // Since we serialize component / html tag attributes
113462
+ function decodeHtmlEntities(value) {
113463
+ return value
113464
+ .replace(/&quot;/g, '"')
113465
+ .replace(/&lt;/g, '<')
113466
+ .replace(/&gt;/g, '>')
113467
+ .replace(/&#10;/g, '\n')
113468
+ .replace(/&amp;/g, '&');
113469
+ }
113328
113470
  // Convert MDX JSX elements to HAST elements, preserving attributes and children
113329
113471
  const mdxJsxElementHandler = (state, node) => {
113330
113472
  const { attributes = [], name } = node;
@@ -113336,7 +113478,7 @@ const mdxJsxElementHandler = (state, node) => {
113336
113478
  properties[attribute.name] = true;
113337
113479
  }
113338
113480
  else if (typeof attribute.value === 'string') {
113339
- properties[attribute.name] = attribute.value;
113481
+ properties[attribute.name] = decodeHtmlEntities(attribute.value);
113340
113482
  }
113341
113483
  else {
113342
113484
  properties[attribute.name] = attribute.value.value;
@@ -113398,6 +113540,17 @@ function base64Decode(str) {
113398
113540
  }
113399
113541
  return decodeURIComponent(escape(atob(str)));
113400
113542
  }
113543
+ function escapeHtmlAttribute(value) {
113544
+ return value
113545
+ .replace(/&/g, '&amp;')
113546
+ .replace(/"/g, '&quot;')
113547
+ .replace(/</g, '&lt;')
113548
+ .replace(/>/g, '&gt;')
113549
+ .replace(/\n/g, '&#10;');
113550
+ }
113551
+ // Marker prefix for JSON-serialized complex values (arrays/objects)
113552
+ // Using a prefix that won't conflict with regular string values
113553
+ const JSON_VALUE_MARKER = '__MDXISH_JSON__';
113401
113554
  // Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
113402
113555
  const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
113403
113556
  const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
@@ -113527,6 +113680,16 @@ function extractBalancedBraces(content, start) {
113527
113680
  return null;
113528
113681
  return { content: content.slice(start, pos - 1), end: pos };
113529
113682
  }
113683
+ function restoreInlineCode(content, protectedCode) {
113684
+ return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
113685
+ return protectedCode.inlineCode[parseInt(idx, 10)];
113686
+ });
113687
+ }
113688
+ function restoreCodeBlocks(content, protectedCode) {
113689
+ return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
113690
+ return protectedCode.codeBlocks[parseInt(idx, 10)];
113691
+ });
113692
+ }
113530
113693
  /**
113531
113694
  * Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
113532
113695
  * Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
@@ -113542,7 +113705,7 @@ function extractBalancedBraces(content, start) {
113542
113705
  * // Returns: '<a href="https://example.com">Link</a>'
113543
113706
  * ```
113544
113707
  */
113545
- function evaluateAttributeExpressions(content, context) {
113708
+ function evaluateAttributeExpressions(content, context, protectedCode) {
113546
113709
  const attrStartRegex = /(\w+)=\{/g;
113547
113710
  let result = '';
113548
113711
  let lastEnd = 0;
@@ -113552,7 +113715,14 @@ function evaluateAttributeExpressions(content, context) {
113552
113715
  const braceStart = match.index + match[0].length;
113553
113716
  const extracted = extractBalancedBraces(content, braceStart);
113554
113717
  if (extracted) {
113555
- const expression = extracted.content;
113718
+ // The expression might contain template literals in MDX component tag props
113719
+ // E.g. <Component greeting={`Hello World!`} />
113720
+ // that is marked as inline code. So we need to restore the inline codes
113721
+ // in the expression to evaluate it
113722
+ let expression = extracted.content;
113723
+ if (protectedCode) {
113724
+ expression = restoreInlineCode(expression, protectedCode);
113725
+ }
113556
113726
  const fullMatchEnd = extracted.end;
113557
113727
  result += content.slice(lastEnd, match.index);
113558
113728
  try {
@@ -113568,14 +113738,20 @@ function evaluateAttributeExpressions(content, context) {
113568
113738
  result += `style="${cssString}"`;
113569
113739
  }
113570
113740
  else {
113571
- result += `${attributeName}='${JSON.stringify(evalResult)}'`;
113741
+ // These are arrays / objects attribute values
113742
+ // Mark JSON-serialized values with a prefix so they can be parsed back correctly
113743
+ const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
113744
+ // Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
113745
+ result += `${attributeName}="${jsonValue}"`;
113572
113746
  }
113573
113747
  }
113574
113748
  else if (attributeName === 'className') {
113575
- result += `class="${evalResult}"`;
113749
+ // Escape special characters so that it doesn't break and split the attribute value to nodes
113750
+ // This will be restored later in the pipeline
113751
+ result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
113576
113752
  }
113577
113753
  else {
113578
- result += `${attributeName}="${evalResult}"`;
113754
+ result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
113579
113755
  }
113580
113756
  }
113581
113757
  catch (_error) {
@@ -113606,13 +113782,9 @@ function evaluateAttributeExpressions(content, context) {
113606
113782
  * // Returns: 'Text with `inline` and ```js\ncode\n```'
113607
113783
  * ```
113608
113784
  */
113609
- function restoreCodeBlocks(content, protectedCode) {
113610
- let restored = content.replace(/___CODE_BLOCK_(\d+)___/g, (_match, index) => {
113611
- return protectedCode.codeBlocks[parseInt(index, 10)];
113612
- });
113613
- restored = restored.replace(/___INLINE_CODE_(\d+)___/g, (_match, index) => {
113614
- return protectedCode.inlineCode[parseInt(index, 10)];
113615
- });
113785
+ function restoreProtectedCodes(content, protectedCode) {
113786
+ let restored = restoreCodeBlocks(content, protectedCode);
113787
+ restored = restoreInlineCode(restored, protectedCode);
113616
113788
  return restored;
113617
113789
  }
113618
113790
  /**
@@ -113633,9 +113805,9 @@ function preprocessJSXExpressions(content, context = {}) {
113633
113805
  // Step 3: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
113634
113806
  // For inline expressions, we use a library to parse the expression & evaluate it later
113635
113807
  // For attribute expressions, it was difficult to use a library to parse them, so do it manually
113636
- processed = evaluateAttributeExpressions(processed, context);
113808
+ processed = evaluateAttributeExpressions(processed, context, protectedCode);
113637
113809
  // Step 4: Restore protected code blocks
113638
- processed = restoreCodeBlocks(processed, protectedCode);
113810
+ processed = restoreProtectedCodes(processed, protectedCode);
113639
113811
  return processed;
113640
113812
  }
113641
113813
 
@@ -114047,7 +114219,7 @@ const wrapPinnedBlocks = (node, json) => {
114047
114219
  return node;
114048
114220
  return {
114049
114221
  children: [node],
114050
- data: { className: 'pin', hName: 'rdme-pin' },
114222
+ data: { hName: 'rdme-pin', hProperties: { className: 'pin' } },
114051
114223
  type: 'rdme-pin',
114052
114224
  };
114053
114225
  };
@@ -114135,15 +114307,16 @@ function parseMagicBlock(raw, options = {}) {
114135
114307
  type: 'code',
114136
114308
  value: obj.code.trim(),
114137
114309
  }));
114138
- // Single code block without a tab name renders as a plain code block
114310
+ // Single code block without a tab name (meta or language) renders as a plain code block
114311
+ // Otherwise, we want to render it as a code tabs block
114139
114312
  if (children.length === 1) {
114140
114313
  if (!children[0].value)
114141
114314
  return [];
114142
- if (children[0].meta)
114315
+ if (!(children[0].meta || children[0].lang))
114143
114316
  return [wrapPinnedBlocks(children[0], json)];
114144
114317
  }
114145
- // Multiple code blocks render as tabbed code blocks
114146
- return [wrapPinnedBlocks({ children, className: 'tabs', data: { hName: 'code-tabs' }, type: 'code-tabs' }, json)];
114318
+ // Multiple code blocks or a single code block with a tab name (meta or language) renders as a code tabs block
114319
+ return [wrapPinnedBlocks({ children, className: 'tabs', data: { hName: 'CodeTabs' }, type: 'code-tabs' }, json)];
114147
114320
  }
114148
114321
  // API header: renders as a heading element (h1-h6)
114149
114322
  case 'api-header': {
@@ -114344,11 +114517,31 @@ function parseMagicBlock(raw, options = {}) {
114344
114517
  }
114345
114518
  }
114346
114519
  /**
114347
- * Check if a child node is a flow element that needs unwrapping (mdxJsxFlowElement, etc.)
114520
+ * Block-level node types that cannot be nested inside paragraphs.
114348
114521
  */
114349
- const needsUnwrapping = (child) => {
114350
- return child.type === 'mdxJsxFlowElement';
114351
- };
114522
+ const blockTypes = [
114523
+ 'heading',
114524
+ 'code',
114525
+ 'code-tabs',
114526
+ 'paragraph',
114527
+ 'blockquote',
114528
+ 'list',
114529
+ 'table',
114530
+ 'thematicBreak',
114531
+ 'html',
114532
+ 'yaml',
114533
+ 'toml',
114534
+ 'rdme-pin',
114535
+ 'rdme-callout',
114536
+ 'html-block',
114537
+ 'embed',
114538
+ 'figure',
114539
+ 'mdxJsxFlowElement',
114540
+ ];
114541
+ /**
114542
+ * Check if a node is a block-level node (cannot be inside a paragraph)
114543
+ */
114544
+ const isBlockNode = (node) => blockTypes.includes(node.type);
114352
114545
  /**
114353
114546
  * Unified plugin that restores magic blocks from placeholder tokens.
114354
114547
  *
@@ -114361,8 +114554,9 @@ const magicBlockRestorer = ({ blocks }) => tree => {
114361
114554
  return;
114362
114555
  // Map: key → original raw magic block content
114363
114556
  const magicBlockKeys = new Map(blocks.map(({ key, raw }) => [key, raw]));
114364
- // Find inlineCode nodes that match our placeholder tokens
114365
- const modifications = [];
114557
+ // Collect replacements to apply (we need to visit in reverse to maintain indices)
114558
+ const replacements = [];
114559
+ // First pass: collect all replacements
114366
114560
  visit(tree, 'inlineCode', (node, index, parent) => {
114367
114561
  if (!parent || index == null)
114368
114562
  return undefined;
@@ -114372,31 +114566,73 @@ const magicBlockRestorer = ({ blocks }) => tree => {
114372
114566
  const children = parseMagicBlock(raw);
114373
114567
  if (!children.length)
114374
114568
  return undefined;
114375
- if (children[0] && needsUnwrapping(children[0]) && parent.type === 'paragraph') {
114376
- // Find paragraph's parent and unwrap
114377
- let paragraphParent;
114378
- visit(tree, 'paragraph', (p, pIndex, pParent) => {
114379
- if (p === parent && pParent && 'children' in pParent) {
114380
- paragraphParent = pParent;
114381
- return false;
114569
+ // If parent is a paragraph and we're inserting block nodes (which must not be in paragraphs), lift them out
114570
+ if (parent.type === 'paragraph' && children.some(child => isBlockNode(child))) {
114571
+ const blockNodes = [];
114572
+ const inlineNodes = [];
114573
+ // Separate block and inline nodes
114574
+ children.forEach(child => {
114575
+ if (isBlockNode(child)) {
114576
+ blockNodes.push(child);
114382
114577
  }
114383
- return undefined;
114384
- });
114385
- if (paragraphParent) {
114386
- const paragraphIndex = paragraphParent.children.indexOf(parent);
114387
- if (paragraphIndex !== -1) {
114388
- modifications.push({ children, index: paragraphIndex, parent: paragraphParent });
114578
+ else {
114579
+ inlineNodes.push(child);
114389
114580
  }
114390
- }
114391
- return SKIP;
114581
+ });
114582
+ const before = parent.children.slice(0, index);
114583
+ const after = parent.children.slice(index + 1);
114584
+ replacements.push({
114585
+ parent,
114586
+ blockNodes,
114587
+ inlineNodes,
114588
+ before,
114589
+ after,
114590
+ });
114392
114591
  }
114393
- parent.children.splice(index, 1, ...children);
114394
- return [SKIP, index + children.length];
114395
- });
114396
- // Apply modifications in reverse order to avoid index shifting
114397
- modifications.reverse().forEach(({ children, index, parent }) => {
114398
- parent.children.splice(index, 1, ...children);
114592
+ else {
114593
+ // Normal case: just replace the inlineCode with the children
114594
+ parent.children.splice(index, 1, ...children);
114595
+ }
114596
+ return undefined;
114399
114597
  });
114598
+ // Second pass: apply replacements that require lifting block nodes out of paragraphs
114599
+ // Process in reverse order to maintain correct indices
114600
+ for (let i = replacements.length - 1; i >= 0; i -= 1) {
114601
+ const { after, before, blockNodes, inlineNodes, parent } = replacements[i];
114602
+ // Find the paragraph's position in the root
114603
+ const rootChildren = tree.children;
114604
+ const paraIndex = rootChildren.findIndex(child => child === parent);
114605
+ if (paraIndex === -1) {
114606
+ // Paragraph not found in root - fall back to normal replacement
114607
+ // This shouldn't happen normally, but handle it gracefully
114608
+ // Reconstruct the original index from before.length
114609
+ const originalIndex = before.length;
114610
+ parent.children.splice(originalIndex, 1, ...blockNodes, ...inlineNodes);
114611
+ // eslint-disable-next-line no-continue
114612
+ continue;
114613
+ }
114614
+ // Update or remove the paragraph
114615
+ if (inlineNodes.length > 0) {
114616
+ // Keep paragraph with inline nodes
114617
+ parent.children = [...before, ...inlineNodes, ...after];
114618
+ // Insert block nodes after the paragraph
114619
+ if (blockNodes.length > 0) {
114620
+ rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
114621
+ }
114622
+ }
114623
+ else if (before.length === 0 && after.length === 0) {
114624
+ // Remove empty paragraph and replace with block nodes
114625
+ rootChildren.splice(paraIndex, 1, ...blockNodes);
114626
+ }
114627
+ else {
114628
+ // Keep paragraph with remaining content
114629
+ parent.children = [...before, ...after];
114630
+ // Insert block nodes after the paragraph
114631
+ if (blockNodes.length > 0) {
114632
+ rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
114633
+ }
114634
+ }
114635
+ }
114400
114636
  };
114401
114637
  /* harmony default export */ const mdxish_magic_blocks = (magicBlockRestorer);
114402
114638
 
@@ -115250,6 +115486,40 @@ const makeUseMDXComponents = (more = {}) => {
115250
115486
 
115251
115487
 
115252
115488
 
115489
+
115490
+ /**
115491
+ * Parse JSON-marked string values in props back to their original types.
115492
+ * This handles arrays and objects that were serialized during JSX preprocessing.
115493
+ */
115494
+ function parseJsonProps(props) {
115495
+ if (!props)
115496
+ return props;
115497
+ const parsed = {};
115498
+ Object.entries(props).forEach(([key, value]) => {
115499
+ if (typeof value === 'string' && value.startsWith(JSON_VALUE_MARKER)) {
115500
+ try {
115501
+ parsed[key] = JSON.parse(value.slice(JSON_VALUE_MARKER.length));
115502
+ }
115503
+ catch {
115504
+ // If parsing fails, use the value without the marker
115505
+ parsed[key] = value.slice(JSON_VALUE_MARKER.length);
115506
+ }
115507
+ }
115508
+ else {
115509
+ parsed[key] = value;
115510
+ }
115511
+ });
115512
+ return parsed;
115513
+ }
115514
+ /**
115515
+ * Custom createElement wrapper that parses JSON-marked string props.
115516
+ * This is needed because rehype-react converts HAST to React, but complex
115517
+ * types (arrays/objects) get serialized to strings during markdown parsing.
115518
+ */
115519
+ function createElementWithJsonProps(type, props, ...children) {
115520
+ const parsedProps = parseJsonProps(props);
115521
+ return external_react_default().createElement(type, parsedProps, ...children);
115522
+ }
115253
115523
  /** Flatten CustomComponents into a component map for rehype-react */
115254
115524
  function exportComponentsForRehype(components) {
115255
115525
  const exported = Object.entries(components).reduce((memo, [tag, mod]) => {
@@ -115281,7 +115551,7 @@ function exportComponentsForRehype(components) {
115281
115551
  function createRehypeReactProcessor(components) {
115282
115552
  // @ts-expect-error - rehype-react types are incompatible with React.Fragment return type
115283
115553
  return unified().use((rehype_react_default()), {
115284
- createElement: (external_react_default()).createElement,
115554
+ createElement: createElementWithJsonProps,
115285
115555
  Fragment: (external_react_default()).Fragment,
115286
115556
  components,
115287
115557
  });