@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.js CHANGED
@@ -52943,12 +52943,45 @@ const plain = (node, opts = {}) => {
52943
52943
  };
52944
52944
  /* harmony default export */ const lib_plain = (plain);
52945
52945
 
52946
+ ;// ./processor/transform/extract-text.ts
52947
+ /**
52948
+ * Extracts text content from a single AST node recursively.
52949
+ * Works with both MDAST and HAST-like node structures.
52950
+ *
52951
+ * Placed this outside of the utils.ts file to avoid circular dependencies.
52952
+ *
52953
+ * @param node - The node to extract text from (can be MDAST Node or HAST-like structure)
52954
+ * @returns The concatenated text content
52955
+ */
52956
+ const extractText = (node) => {
52957
+ if (node.type === 'text' && typeof node.value === 'string') {
52958
+ return node.value;
52959
+ }
52960
+ // When a blockquote contains only an image (no text), treat it as having content
52961
+ // so the blockquote is no longer treated as empty and preserved correctly.
52962
+ if (node.type === 'image') {
52963
+ return typeof node.alt === 'string' && node.alt ? node.alt : '[image]';
52964
+ }
52965
+ if (node.children && Array.isArray(node.children)) {
52966
+ return node.children
52967
+ .map(child => {
52968
+ if (child && typeof child === 'object' && 'type' in child) {
52969
+ return extractText(child);
52970
+ }
52971
+ return '';
52972
+ })
52973
+ .join('');
52974
+ }
52975
+ return '';
52976
+ };
52977
+
52946
52978
  ;// ./processor/transform/callouts.ts
52947
52979
 
52948
52980
 
52949
52981
 
52950
52982
 
52951
52983
 
52984
+
52952
52985
  const callouts_regex = `^(${emoji_regex().source}|⚠)(\\s+|$)`;
52953
52986
  const findFirst = (node) => {
52954
52987
  if ('children' in node)
@@ -52969,42 +53002,76 @@ const wrapHeading = (node) => {
52969
53002
  },
52970
53003
  };
52971
53004
  };
53005
+ /**
53006
+ * Checks if a blockquote matches the expected callout structure:
53007
+ * blockquote > paragraph > text node
53008
+ */
53009
+ const isCalloutStructure = (node) => {
53010
+ const firstChild = node.children?.[0];
53011
+ if (!firstChild || firstChild.type !== 'paragraph')
53012
+ return false;
53013
+ if (!('children' in firstChild))
53014
+ return false;
53015
+ const firstTextChild = firstChild.children?.[0];
53016
+ return firstTextChild?.type === 'text';
53017
+ };
53018
+ const processBlockquote = (node, index, parent) => {
53019
+ if (!isCalloutStructure(node)) {
53020
+ // Only stringify empty blockquotes (no extractable text content)
53021
+ // Preserve blockquotes with actual content (e.g., headings, lists, etc.)
53022
+ const content = extractText(node);
53023
+ const isEmpty = !content || content.trim() === '';
53024
+ if (isEmpty && index !== undefined && parent) {
53025
+ const textNode = {
53026
+ type: 'text',
53027
+ value: '>',
53028
+ };
53029
+ const paragraphNode = {
53030
+ type: 'paragraph',
53031
+ children: [textNode],
53032
+ position: node.position,
53033
+ };
53034
+ parent.children.splice(index, 1, paragraphNode);
53035
+ }
53036
+ return;
53037
+ }
53038
+ // isCalloutStructure ensures node.children[0] is a Paragraph with children
53039
+ const firstParagraph = node.children[0];
53040
+ const startText = lib_plain(firstParagraph).toString();
53041
+ const [match, icon] = startText.match(callouts_regex) || [];
53042
+ if (icon && match) {
53043
+ const heading = startText.slice(match.length);
53044
+ const empty = !heading.length && firstParagraph.children.length === 1;
53045
+ const theme = themes[icon] || 'default';
53046
+ const firstChild = findFirst(node.children[0]);
53047
+ if (firstChild && 'value' in firstChild && typeof firstChild.value === 'string') {
53048
+ firstChild.value = firstChild.value.slice(match.length);
53049
+ }
53050
+ if (heading) {
53051
+ node.children[0] = wrapHeading(node);
53052
+ // @note: We add to the offset/column the length of the unicode
53053
+ // character that was stripped off, so that the start position of the
53054
+ // heading/text matches where it actually starts.
53055
+ node.children[0].position.start.offset += match.length;
53056
+ node.children[0].position.start.column += match.length;
53057
+ }
53058
+ Object.assign(node, {
53059
+ type: NodeTypes.callout,
53060
+ data: {
53061
+ hName: 'Callout',
53062
+ hProperties: {
53063
+ icon,
53064
+ ...(empty && { empty }),
53065
+ theme,
53066
+ },
53067
+ },
53068
+ });
53069
+ }
53070
+ };
52972
53071
  const calloutTransformer = () => {
52973
53072
  return (tree) => {
52974
- visit(tree, 'blockquote', (node) => {
52975
- if (!(node.children[0].type === 'paragraph' && node.children[0].children[0].type === 'text'))
52976
- return;
52977
- // @ts-expect-error -- @todo: update plain to accept mdast
52978
- const startText = lib_plain(node.children[0]).toString();
52979
- const [match, icon] = startText.match(callouts_regex) || [];
52980
- if (icon && match) {
52981
- const heading = startText.slice(match.length);
52982
- const empty = !heading.length && node.children[0].children.length === 1;
52983
- const theme = themes[icon] || 'default';
52984
- const firstChild = findFirst(node.children[0]);
52985
- if (firstChild && 'value' in firstChild && typeof firstChild.value === 'string') {
52986
- firstChild.value = firstChild.value.slice(match.length);
52987
- }
52988
- if (heading) {
52989
- node.children[0] = wrapHeading(node);
52990
- // @note: We add to the offset/column the length of the unicode
52991
- // character that was stripped off, so that the start position of the
52992
- // heading/text matches where it actually starts.
52993
- node.children[0].position.start.offset += match.length;
52994
- node.children[0].position.start.column += match.length;
52995
- }
52996
- Object.assign(node, {
52997
- type: NodeTypes.callout,
52998
- data: {
52999
- hName: 'Callout',
53000
- hProperties: {
53001
- icon,
53002
- ...(empty && { empty }),
53003
- theme,
53004
- },
53005
- },
53006
- });
53007
- }
53073
+ visit(tree, 'blockquote', (node, index, parent) => {
53074
+ processBlockquote(node, index, parent);
53008
53075
  });
53009
53076
  };
53010
53077
  };
@@ -70670,6 +70737,17 @@ const parseTag = (value) => {
70670
70737
  contentAfterTag,
70671
70738
  };
70672
70739
  };
70740
+ /**
70741
+ * Parse substring content of a node and update the parent's children to include the new nodes.
70742
+ */
70743
+ const parseSibling = (stack, parent, index, sibling) => {
70744
+ const siblingNodes = parseMdChildren(sibling);
70745
+ // The new sibling nodes might contain new components to be processed
70746
+ if (siblingNodes.length > 0) {
70747
+ parent.children.splice(index + 1, 0, ...siblingNodes);
70748
+ stack.push(parent);
70749
+ }
70750
+ };
70673
70751
  /**
70674
70752
  * Create an MdxJsxFlowElement node from component data.
70675
70753
  */
@@ -70812,11 +70890,12 @@ const mdxishComponentBlocks = () => tree => {
70812
70890
  if ('children' in node && Array.isArray(node.children)) {
70813
70891
  stack.push(node);
70814
70892
  }
70815
- // Only visit HTML nodes with an actual html tag
70893
+ // Only visit HTML nodes with an actual html tag,
70894
+ // which means a potential unparsed MDX component
70816
70895
  const value = node.value;
70817
70896
  if (node.type !== 'html' || typeof value !== 'string')
70818
70897
  return;
70819
- const parsed = parseTag(value);
70898
+ const parsed = parseTag(value.trim());
70820
70899
  if (!parsed)
70821
70900
  return;
70822
70901
  const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
@@ -70833,11 +70912,19 @@ const mdxishComponentBlocks = () => tree => {
70833
70912
  startPosition: node.position,
70834
70913
  });
70835
70914
  substituteNodeWithMdxNode(parent, index, componentNode);
70915
+ // Check and parse if there's relevant content after the current closing tag
70916
+ const remainingContent = contentAfterTag.trim();
70917
+ if (remainingContent) {
70918
+ parseSibling(stack, parent, index, remainingContent);
70919
+ }
70836
70920
  return;
70837
70921
  }
70838
70922
  // Case 2: Self-contained block (closing tag in content)
70839
70923
  if (contentAfterTag.includes(closingTagStr)) {
70840
- const componentInnerContent = contentAfterTag.substring(0, contentAfterTag.lastIndexOf(closingTagStr)).trim();
70924
+ // Find the first closing tag
70925
+ const closingTagIndex = contentAfterTag.indexOf(closingTagStr);
70926
+ const componentInnerContent = contentAfterTag.substring(0, closingTagIndex).trim();
70927
+ const contentAfterClose = contentAfterTag.substring(closingTagIndex + closingTagStr.length).trim();
70841
70928
  const componentNode = createComponentNode({
70842
70929
  tag,
70843
70930
  attributes,
@@ -70845,6 +70932,14 @@ const mdxishComponentBlocks = () => tree => {
70845
70932
  startPosition: node.position,
70846
70933
  });
70847
70934
  substituteNodeWithMdxNode(parent, index, componentNode);
70935
+ // After the closing tag, there might be more content to be processed
70936
+ if (contentAfterClose) {
70937
+ parseSibling(stack, parent, index, contentAfterClose);
70938
+ }
70939
+ else if (componentNode.children.length > 0) {
70940
+ // The content inside the component block might contain new components to be processed
70941
+ stack.push(componentNode);
70942
+ }
70848
70943
  return;
70849
70944
  }
70850
70945
  // Case 3: Multi-sibling component (closing tag in a following sibling)
@@ -70870,8 +70965,13 @@ const mdxishComponentBlocks = () => tree => {
70870
70965
  });
70871
70966
  // Remove all nodes from opening tag to closing tag (inclusive) and replace with component node
70872
70967
  parent.children.splice(index, closingIndex - index + 1, componentNode);
70968
+ // Since we might be merging sibling nodes together and combining content,
70969
+ // there might be new components to process
70970
+ if (componentNode.children.length > 0) {
70971
+ stack.push(componentNode);
70972
+ }
70873
70973
  };
70874
- // Travel the tree depth-first
70974
+ // Process the nodes with the components depth-first to maintain the correct order of the nodes
70875
70975
  while (stack.length) {
70876
70976
  const parent = stack.pop();
70877
70977
  if (parent?.children) {
@@ -70892,6 +70992,7 @@ const mdxishComponentBlocks = () => tree => {
70892
70992
 
70893
70993
 
70894
70994
 
70995
+
70895
70996
  const isTableCell = (node) => isMDXElement(node) && ['th', 'td'].includes(node.name);
70896
70997
  const tableTypes = {
70897
70998
  tr: 'tableRow',
@@ -70916,18 +71017,13 @@ const isTextOnly = (children) => {
70916
71017
  });
70917
71018
  };
70918
71019
  /**
70919
- * Extract text content from children nodes
71020
+ * Convenience wrapper that extracts text content from an array of children nodes.
70920
71021
  */
70921
- const extractText = (children) => {
71022
+ const extractTextFromChildren = (children) => {
70922
71023
  return children
70923
71024
  .map(child => {
70924
71025
  if (child && typeof child === 'object' && 'type' in child) {
70925
- if (child.type === 'text' && 'value' in child && typeof child.value === 'string') {
70926
- return child.value;
70927
- }
70928
- if (child.type === 'mdxJsxTextElement' && 'children' in child && Array.isArray(child.children)) {
70929
- return extractText(child.children);
70930
- }
71026
+ return extractText(child);
70931
71027
  }
70932
71028
  return '';
70933
71029
  })
@@ -70958,7 +71054,7 @@ const processTableNode = (node, index, parent) => {
70958
71054
  let parsedChildren = cellChildren;
70959
71055
  // If cell contains only text nodes, try to re-parse as markdown
70960
71056
  if (isTextOnly(cellChildren)) {
70961
- const textContent = extractText(cellChildren);
71057
+ const textContent = extractTextFromChildren(cellChildren);
70962
71058
  if (textContent.trim()) {
70963
71059
  try {
70964
71060
  const parsed = parseMarkdown(textContent);
@@ -86373,7 +86469,7 @@ const CUSTOM_PROP_BOUNDARIES = [
86373
86469
  /**
86374
86470
  * Tags that should be passed through and handled at runtime (not by the mdxish plugin)
86375
86471
  */
86376
- const RUNTIME_COMPONENT_TAGS = new Set(['Variable', 'variable']);
86472
+ const RUNTIME_COMPONENT_TAGS = new Set(['Variable', 'variable', 'rdme-pin']);
86377
86473
  /**
86378
86474
  * Standard HTML tags that should never be treated as custom components.
86379
86475
  * Uses the html-tags package, converted to a Set<string> for efficient lookups.
@@ -93004,6 +93100,33 @@ const INLINE_COMPONENT_TAGS = new Set(['anchor', 'glossary']);
93004
93100
  function isElementContentNode(node) {
93005
93101
  return node.type === 'element' || node.type === 'text' || node.type === 'comment';
93006
93102
  }
93103
+ /**
93104
+ * Components are assumed to be block-level, so whitespace between them can be removed
93105
+ * We want to remove them because it can be treated as actual children of the component
93106
+ * when it doesn't need to.
93107
+ *
93108
+ * It can be a problem because it can be passed as args to the components, like the ones
93109
+ * defined in /components, which can break the type assumptions of the components and cause
93110
+ * type errors, accessing properties that don't exist.
93111
+ */
93112
+ function areAllChildrenComponents(children) {
93113
+ return children.every(child => {
93114
+ // Whitespace-only text nodes don't affect the check
93115
+ if (child.type === 'text' && !child.value.trim())
93116
+ return true;
93117
+ // Text with actual content means we have mixed content
93118
+ if (child.type === 'text')
93119
+ return false;
93120
+ // Comments don't affect the check
93121
+ if (child.type === 'comment')
93122
+ return true;
93123
+ // Standard HTML tags are not considered components
93124
+ if (child.type === 'element' && 'tagName' in child) {
93125
+ return !STANDARD_HTML_TAGS.has(child.tagName.toLowerCase());
93126
+ }
93127
+ return false;
93128
+ });
93129
+ }
93007
93130
  /** Check if nodes represent a single paragraph with only text (no markdown formatting) */
93008
93131
  function isSingleParagraphTextNode(nodes) {
93009
93132
  return (nodes.length === 1 &&
@@ -93048,6 +93171,7 @@ function isActualHtmlTag(tagName, originalExcerpt) {
93048
93171
  function parseTextChildren(node, processMarkdown) {
93049
93172
  if (!node.children?.length)
93050
93173
  return;
93174
+ // First pass: Recursively process text children as they may contain stringified markdown / mdx content
93051
93175
  node.children = node.children.flatMap(child => {
93052
93176
  if (child.type !== 'text' || !child.value.trim())
93053
93177
  return [child];
@@ -93059,6 +93183,15 @@ function parseTextChildren(node, processMarkdown) {
93059
93183
  }
93060
93184
  return children;
93061
93185
  });
93186
+ // Post-processing: remove whitespace-only text nodes if all siblings are components
93187
+ // This prevents whitespace between component children from being counted as extra children
93188
+ if (areAllChildrenComponents(node.children)) {
93189
+ node.children = node.children.filter(child => {
93190
+ if (child.type === 'text' && !child.value.trim())
93191
+ return false;
93192
+ return true;
93193
+ });
93194
+ }
93062
93195
  }
93063
93196
  /** Convert node properties from kebab-case/lowercase to camelCase */
93064
93197
  function normalizeProperties(node) {
@@ -93121,6 +93254,15 @@ const mdxExpressionHandler = (_state, node) => ({
93121
93254
  type: 'text',
93122
93255
  value: node.value || '',
93123
93256
  });
93257
+ // Since we serialize component / html tag attributes
93258
+ function decodeHtmlEntities(value) {
93259
+ return value
93260
+ .replace(/&quot;/g, '"')
93261
+ .replace(/&lt;/g, '<')
93262
+ .replace(/&gt;/g, '>')
93263
+ .replace(/&#10;/g, '\n')
93264
+ .replace(/&amp;/g, '&');
93265
+ }
93124
93266
  // Convert MDX JSX elements to HAST elements, preserving attributes and children
93125
93267
  const mdxJsxElementHandler = (state, node) => {
93126
93268
  const { attributes = [], name } = node;
@@ -93132,7 +93274,7 @@ const mdxJsxElementHandler = (state, node) => {
93132
93274
  properties[attribute.name] = true;
93133
93275
  }
93134
93276
  else if (typeof attribute.value === 'string') {
93135
- properties[attribute.name] = attribute.value;
93277
+ properties[attribute.name] = decodeHtmlEntities(attribute.value);
93136
93278
  }
93137
93279
  else {
93138
93280
  properties[attribute.name] = attribute.value.value;
@@ -93194,6 +93336,17 @@ function base64Decode(str) {
93194
93336
  }
93195
93337
  return decodeURIComponent(escape(atob(str)));
93196
93338
  }
93339
+ function escapeHtmlAttribute(value) {
93340
+ return value
93341
+ .replace(/&/g, '&amp;')
93342
+ .replace(/"/g, '&quot;')
93343
+ .replace(/</g, '&lt;')
93344
+ .replace(/>/g, '&gt;')
93345
+ .replace(/\n/g, '&#10;');
93346
+ }
93347
+ // Marker prefix for JSON-serialized complex values (arrays/objects)
93348
+ // Using a prefix that won't conflict with regular string values
93349
+ const JSON_VALUE_MARKER = '__MDXISH_JSON__';
93197
93350
  // Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
93198
93351
  const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
93199
93352
  const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
@@ -93323,6 +93476,16 @@ function extractBalancedBraces(content, start) {
93323
93476
  return null;
93324
93477
  return { content: content.slice(start, pos - 1), end: pos };
93325
93478
  }
93479
+ function restoreInlineCode(content, protectedCode) {
93480
+ return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
93481
+ return protectedCode.inlineCode[parseInt(idx, 10)];
93482
+ });
93483
+ }
93484
+ function restoreCodeBlocks(content, protectedCode) {
93485
+ return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
93486
+ return protectedCode.codeBlocks[parseInt(idx, 10)];
93487
+ });
93488
+ }
93326
93489
  /**
93327
93490
  * Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
93328
93491
  * Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
@@ -93338,7 +93501,7 @@ function extractBalancedBraces(content, start) {
93338
93501
  * // Returns: '<a href="https://example.com">Link</a>'
93339
93502
  * ```
93340
93503
  */
93341
- function evaluateAttributeExpressions(content, context) {
93504
+ function evaluateAttributeExpressions(content, context, protectedCode) {
93342
93505
  const attrStartRegex = /(\w+)=\{/g;
93343
93506
  let result = '';
93344
93507
  let lastEnd = 0;
@@ -93348,7 +93511,14 @@ function evaluateAttributeExpressions(content, context) {
93348
93511
  const braceStart = match.index + match[0].length;
93349
93512
  const extracted = extractBalancedBraces(content, braceStart);
93350
93513
  if (extracted) {
93351
- const expression = extracted.content;
93514
+ // The expression might contain template literals in MDX component tag props
93515
+ // E.g. <Component greeting={`Hello World!`} />
93516
+ // that is marked as inline code. So we need to restore the inline codes
93517
+ // in the expression to evaluate it
93518
+ let expression = extracted.content;
93519
+ if (protectedCode) {
93520
+ expression = restoreInlineCode(expression, protectedCode);
93521
+ }
93352
93522
  const fullMatchEnd = extracted.end;
93353
93523
  result += content.slice(lastEnd, match.index);
93354
93524
  try {
@@ -93364,14 +93534,20 @@ function evaluateAttributeExpressions(content, context) {
93364
93534
  result += `style="${cssString}"`;
93365
93535
  }
93366
93536
  else {
93367
- result += `${attributeName}='${JSON.stringify(evalResult)}'`;
93537
+ // These are arrays / objects attribute values
93538
+ // Mark JSON-serialized values with a prefix so they can be parsed back correctly
93539
+ const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
93540
+ // Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
93541
+ result += `${attributeName}="${jsonValue}"`;
93368
93542
  }
93369
93543
  }
93370
93544
  else if (attributeName === 'className') {
93371
- result += `class="${evalResult}"`;
93545
+ // Escape special characters so that it doesn't break and split the attribute value to nodes
93546
+ // This will be restored later in the pipeline
93547
+ result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
93372
93548
  }
93373
93549
  else {
93374
- result += `${attributeName}="${evalResult}"`;
93550
+ result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
93375
93551
  }
93376
93552
  }
93377
93553
  catch (_error) {
@@ -93402,13 +93578,9 @@ function evaluateAttributeExpressions(content, context) {
93402
93578
  * // Returns: 'Text with `inline` and ```js\ncode\n```'
93403
93579
  * ```
93404
93580
  */
93405
- function restoreCodeBlocks(content, protectedCode) {
93406
- let restored = content.replace(/___CODE_BLOCK_(\d+)___/g, (_match, index) => {
93407
- return protectedCode.codeBlocks[parseInt(index, 10)];
93408
- });
93409
- restored = restored.replace(/___INLINE_CODE_(\d+)___/g, (_match, index) => {
93410
- return protectedCode.inlineCode[parseInt(index, 10)];
93411
- });
93581
+ function restoreProtectedCodes(content, protectedCode) {
93582
+ let restored = restoreCodeBlocks(content, protectedCode);
93583
+ restored = restoreInlineCode(restored, protectedCode);
93412
93584
  return restored;
93413
93585
  }
93414
93586
  /**
@@ -93429,9 +93601,9 @@ function preprocessJSXExpressions(content, context = {}) {
93429
93601
  // Step 3: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
93430
93602
  // For inline expressions, we use a library to parse the expression & evaluate it later
93431
93603
  // For attribute expressions, it was difficult to use a library to parse them, so do it manually
93432
- processed = evaluateAttributeExpressions(processed, context);
93604
+ processed = evaluateAttributeExpressions(processed, context, protectedCode);
93433
93605
  // Step 4: Restore protected code blocks
93434
- processed = restoreCodeBlocks(processed, protectedCode);
93606
+ processed = restoreProtectedCodes(processed, protectedCode);
93435
93607
  return processed;
93436
93608
  }
93437
93609
 
@@ -93843,7 +94015,7 @@ const wrapPinnedBlocks = (node, json) => {
93843
94015
  return node;
93844
94016
  return {
93845
94017
  children: [node],
93846
- data: { className: 'pin', hName: 'rdme-pin' },
94018
+ data: { hName: 'rdme-pin', hProperties: { className: 'pin' } },
93847
94019
  type: 'rdme-pin',
93848
94020
  };
93849
94021
  };
@@ -93931,15 +94103,16 @@ function parseMagicBlock(raw, options = {}) {
93931
94103
  type: 'code',
93932
94104
  value: obj.code.trim(),
93933
94105
  }));
93934
- // Single code block without a tab name renders as a plain code block
94106
+ // Single code block without a tab name (meta or language) renders as a plain code block
94107
+ // Otherwise, we want to render it as a code tabs block
93935
94108
  if (children.length === 1) {
93936
94109
  if (!children[0].value)
93937
94110
  return [];
93938
- if (children[0].meta)
94111
+ if (!(children[0].meta || children[0].lang))
93939
94112
  return [wrapPinnedBlocks(children[0], json)];
93940
94113
  }
93941
- // Multiple code blocks render as tabbed code blocks
93942
- return [wrapPinnedBlocks({ children, className: 'tabs', data: { hName: 'code-tabs' }, type: 'code-tabs' }, json)];
94114
+ // Multiple code blocks or a single code block with a tab name (meta or language) renders as a code tabs block
94115
+ return [wrapPinnedBlocks({ children, className: 'tabs', data: { hName: 'CodeTabs' }, type: 'code-tabs' }, json)];
93943
94116
  }
93944
94117
  // API header: renders as a heading element (h1-h6)
93945
94118
  case 'api-header': {
@@ -94140,11 +94313,31 @@ function parseMagicBlock(raw, options = {}) {
94140
94313
  }
94141
94314
  }
94142
94315
  /**
94143
- * Check if a child node is a flow element that needs unwrapping (mdxJsxFlowElement, etc.)
94316
+ * Block-level node types that cannot be nested inside paragraphs.
94144
94317
  */
94145
- const needsUnwrapping = (child) => {
94146
- return child.type === 'mdxJsxFlowElement';
94147
- };
94318
+ const blockTypes = [
94319
+ 'heading',
94320
+ 'code',
94321
+ 'code-tabs',
94322
+ 'paragraph',
94323
+ 'blockquote',
94324
+ 'list',
94325
+ 'table',
94326
+ 'thematicBreak',
94327
+ 'html',
94328
+ 'yaml',
94329
+ 'toml',
94330
+ 'rdme-pin',
94331
+ 'rdme-callout',
94332
+ 'html-block',
94333
+ 'embed',
94334
+ 'figure',
94335
+ 'mdxJsxFlowElement',
94336
+ ];
94337
+ /**
94338
+ * Check if a node is a block-level node (cannot be inside a paragraph)
94339
+ */
94340
+ const isBlockNode = (node) => blockTypes.includes(node.type);
94148
94341
  /**
94149
94342
  * Unified plugin that restores magic blocks from placeholder tokens.
94150
94343
  *
@@ -94157,8 +94350,9 @@ const magicBlockRestorer = ({ blocks }) => tree => {
94157
94350
  return;
94158
94351
  // Map: key → original raw magic block content
94159
94352
  const magicBlockKeys = new Map(blocks.map(({ key, raw }) => [key, raw]));
94160
- // Find inlineCode nodes that match our placeholder tokens
94161
- const modifications = [];
94353
+ // Collect replacements to apply (we need to visit in reverse to maintain indices)
94354
+ const replacements = [];
94355
+ // First pass: collect all replacements
94162
94356
  visit(tree, 'inlineCode', (node, index, parent) => {
94163
94357
  if (!parent || index == null)
94164
94358
  return undefined;
@@ -94168,31 +94362,73 @@ const magicBlockRestorer = ({ blocks }) => tree => {
94168
94362
  const children = parseMagicBlock(raw);
94169
94363
  if (!children.length)
94170
94364
  return undefined;
94171
- if (children[0] && needsUnwrapping(children[0]) && parent.type === 'paragraph') {
94172
- // Find paragraph's parent and unwrap
94173
- let paragraphParent;
94174
- visit(tree, 'paragraph', (p, pIndex, pParent) => {
94175
- if (p === parent && pParent && 'children' in pParent) {
94176
- paragraphParent = pParent;
94177
- return false;
94365
+ // If parent is a paragraph and we're inserting block nodes (which must not be in paragraphs), lift them out
94366
+ if (parent.type === 'paragraph' && children.some(child => isBlockNode(child))) {
94367
+ const blockNodes = [];
94368
+ const inlineNodes = [];
94369
+ // Separate block and inline nodes
94370
+ children.forEach(child => {
94371
+ if (isBlockNode(child)) {
94372
+ blockNodes.push(child);
94178
94373
  }
94179
- return undefined;
94180
- });
94181
- if (paragraphParent) {
94182
- const paragraphIndex = paragraphParent.children.indexOf(parent);
94183
- if (paragraphIndex !== -1) {
94184
- modifications.push({ children, index: paragraphIndex, parent: paragraphParent });
94374
+ else {
94375
+ inlineNodes.push(child);
94185
94376
  }
94186
- }
94187
- return SKIP;
94377
+ });
94378
+ const before = parent.children.slice(0, index);
94379
+ const after = parent.children.slice(index + 1);
94380
+ replacements.push({
94381
+ parent,
94382
+ blockNodes,
94383
+ inlineNodes,
94384
+ before,
94385
+ after,
94386
+ });
94188
94387
  }
94189
- parent.children.splice(index, 1, ...children);
94190
- return [SKIP, index + children.length];
94191
- });
94192
- // Apply modifications in reverse order to avoid index shifting
94193
- modifications.reverse().forEach(({ children, index, parent }) => {
94194
- parent.children.splice(index, 1, ...children);
94388
+ else {
94389
+ // Normal case: just replace the inlineCode with the children
94390
+ parent.children.splice(index, 1, ...children);
94391
+ }
94392
+ return undefined;
94195
94393
  });
94394
+ // Second pass: apply replacements that require lifting block nodes out of paragraphs
94395
+ // Process in reverse order to maintain correct indices
94396
+ for (let i = replacements.length - 1; i >= 0; i -= 1) {
94397
+ const { after, before, blockNodes, inlineNodes, parent } = replacements[i];
94398
+ // Find the paragraph's position in the root
94399
+ const rootChildren = tree.children;
94400
+ const paraIndex = rootChildren.findIndex(child => child === parent);
94401
+ if (paraIndex === -1) {
94402
+ // Paragraph not found in root - fall back to normal replacement
94403
+ // This shouldn't happen normally, but handle it gracefully
94404
+ // Reconstruct the original index from before.length
94405
+ const originalIndex = before.length;
94406
+ parent.children.splice(originalIndex, 1, ...blockNodes, ...inlineNodes);
94407
+ // eslint-disable-next-line no-continue
94408
+ continue;
94409
+ }
94410
+ // Update or remove the paragraph
94411
+ if (inlineNodes.length > 0) {
94412
+ // Keep paragraph with inline nodes
94413
+ parent.children = [...before, ...inlineNodes, ...after];
94414
+ // Insert block nodes after the paragraph
94415
+ if (blockNodes.length > 0) {
94416
+ rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
94417
+ }
94418
+ }
94419
+ else if (before.length === 0 && after.length === 0) {
94420
+ // Remove empty paragraph and replace with block nodes
94421
+ rootChildren.splice(paraIndex, 1, ...blockNodes);
94422
+ }
94423
+ else {
94424
+ // Keep paragraph with remaining content
94425
+ parent.children = [...before, ...after];
94426
+ // Insert block nodes after the paragraph
94427
+ if (blockNodes.length > 0) {
94428
+ rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
94429
+ }
94430
+ }
94431
+ }
94196
94432
  };
94197
94433
  /* harmony default export */ const mdxish_magic_blocks = (magicBlockRestorer);
94198
94434
 
@@ -95046,6 +95282,40 @@ const makeUseMDXComponents = (more = {}) => {
95046
95282
 
95047
95283
 
95048
95284
 
95285
+
95286
+ /**
95287
+ * Parse JSON-marked string values in props back to their original types.
95288
+ * This handles arrays and objects that were serialized during JSX preprocessing.
95289
+ */
95290
+ function parseJsonProps(props) {
95291
+ if (!props)
95292
+ return props;
95293
+ const parsed = {};
95294
+ Object.entries(props).forEach(([key, value]) => {
95295
+ if (typeof value === 'string' && value.startsWith(JSON_VALUE_MARKER)) {
95296
+ try {
95297
+ parsed[key] = JSON.parse(value.slice(JSON_VALUE_MARKER.length));
95298
+ }
95299
+ catch {
95300
+ // If parsing fails, use the value without the marker
95301
+ parsed[key] = value.slice(JSON_VALUE_MARKER.length);
95302
+ }
95303
+ }
95304
+ else {
95305
+ parsed[key] = value;
95306
+ }
95307
+ });
95308
+ return parsed;
95309
+ }
95310
+ /**
95311
+ * Custom createElement wrapper that parses JSON-marked string props.
95312
+ * This is needed because rehype-react converts HAST to React, but complex
95313
+ * types (arrays/objects) get serialized to strings during markdown parsing.
95314
+ */
95315
+ function createElementWithJsonProps(type, props, ...children) {
95316
+ const parsedProps = parseJsonProps(props);
95317
+ return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement(type, parsedProps, ...children);
95318
+ }
95049
95319
  /** Flatten CustomComponents into a component map for rehype-react */
95050
95320
  function exportComponentsForRehype(components) {
95051
95321
  const exported = Object.entries(components).reduce((memo, [tag, mod]) => {
@@ -95077,7 +95347,7 @@ function exportComponentsForRehype(components) {
95077
95347
  function createRehypeReactProcessor(components) {
95078
95348
  // @ts-expect-error - rehype-react types are incompatible with React.Fragment return type
95079
95349
  return unified().use((rehype_react_default()), {
95080
- createElement: (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default()).createElement,
95350
+ createElement: createElementWithJsonProps,
95081
95351
  Fragment: (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default()).Fragment,
95082
95352
  components,
95083
95353
  });