@readme/markdown 13.7.1 → 13.7.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.js CHANGED
@@ -11704,10 +11704,47 @@ var dist_utils_default = /*#__PURE__*/__webpack_require__.n(dist_utils);
11704
11704
 
11705
11705
  let mermaid;
11706
11706
  const { uppercase } = (dist_utils_default());
11707
+ // Module-level queue that batches mermaid nodes across all CodeTabs instances into a
11708
+ // single mermaid.run() call. This is necessary because mermaid generates SVG element IDs
11709
+ // using Date.now(), which collides when multiple diagrams render in the same millisecond.
11710
+ // Colliding IDs cause diagrams to overlap or break layout.
11711
+ //
11712
+ // Why not use `deterministicIDSeed` with a unique ID per diagram? Mermaid's implementation
11713
+ // only uses seed.length (not the seed value) to compute the starting ID, so every UUID
11714
+ // (36 chars) produces the same `mermaid-36` prefix — the collision remains.
11715
+ // See: https://github.com/mermaid-js/mermaid/blob/mermaid%4011.12.0/packages/mermaid/src/utils.ts#L755-L761
11716
+ //
11717
+ // These vars must be module-scoped (not per-instance refs) because the batching requires
11718
+ // cross-instance coordination. They are short-lived: the queue drains on the next macrotask
11719
+ // and cleanup clears everything on unmount.
11720
+ let mermaidQueue = [];
11721
+ let mermaidFlushTimer = null;
11722
+ let currentTheme;
11723
+ function queueMermaidNode(node, theme) {
11724
+ mermaidQueue.push(node);
11725
+ currentTheme = theme;
11726
+ if (!mermaidFlushTimer) {
11727
+ // setTimeout(0) defers to a macrotask, after all useEffects have queued their nodes
11728
+ mermaidFlushTimer = setTimeout(async () => {
11729
+ const nodes = [...mermaidQueue];
11730
+ mermaidQueue = [];
11731
+ mermaidFlushTimer = null;
11732
+ const module = await Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 1387, 23));
11733
+ mermaid = module.default;
11734
+ mermaid.initialize({
11735
+ startOnLoad: false,
11736
+ theme: currentTheme === 'dark' ? 'dark' : 'default',
11737
+ deterministicIds: true,
11738
+ });
11739
+ await mermaid.run({ nodes });
11740
+ }, 0);
11741
+ }
11742
+ }
11707
11743
  const CodeTabs = (props) => {
11708
11744
  const { children } = props;
11709
11745
  const theme = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useContext)(Theme);
11710
11746
  const isHydrated = useHydrated();
11747
+ const mermaidRef = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useRef)(null);
11711
11748
  // Handle both array (from rehype-react in rendering mdxish) and single element (MDX/JSX runtime) cases
11712
11749
  // The children here is the individual code block objects
11713
11750
  const childrenArray = Array.isArray(children) ? children : [children];
@@ -11718,22 +11755,20 @@ const CodeTabs = (props) => {
11718
11755
  return Array.isArray(pre?.props?.children) ? pre.props.children[0] : pre?.props?.children;
11719
11756
  };
11720
11757
  const containAtLeastOneMermaid = childrenArray.some(pre => getCodeComponent(pre)?.props?.lang === 'mermaid');
11721
- // Render Mermaid diagram
11722
11758
  (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useEffect)(() => {
11723
- // Ensure we only render mermaids when frontend is hydrated to avoid hydration errors
11724
- // because mermaid mutates the DOM before react hydrates
11725
- if (typeof window !== 'undefined' && containAtLeastOneMermaid && isHydrated) {
11726
- Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 1387, 23)).then(module => {
11727
- mermaid = module.default;
11728
- mermaid.initialize({
11729
- startOnLoad: false,
11730
- theme: theme === 'dark' ? 'dark' : 'default',
11731
- });
11732
- mermaid.run({
11733
- nodes: document.querySelectorAll('.mermaid-render'),
11734
- });
11735
- });
11736
- }
11759
+ // Wait for hydration so mermaid's DOM mutations don't cause mismatches
11760
+ if (typeof window !== 'undefined' && containAtLeastOneMermaid && isHydrated && mermaidRef.current) {
11761
+ queueMermaidNode(mermaidRef.current, theme);
11762
+ }
11763
+ return () => {
11764
+ // Clear the batch timer on unmount to prevent mermaid from running
11765
+ // after the DOM is torn down
11766
+ if (mermaidFlushTimer) {
11767
+ clearTimeout(mermaidFlushTimer);
11768
+ mermaidFlushTimer = null;
11769
+ mermaidQueue = [];
11770
+ }
11771
+ };
11737
11772
  }, [containAtLeastOneMermaid, theme, isHydrated]);
11738
11773
  function handleClick({ target }, index) {
11739
11774
  const $wrap = target.parentElement.parentElement;
@@ -11749,15 +11784,13 @@ const CodeTabs = (props) => {
11749
11784
  const codeComponent = getCodeComponent(childrenArray[0]);
11750
11785
  if (codeComponent?.props?.lang === 'mermaid') {
11751
11786
  const value = codeComponent?.props?.value;
11752
- return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("pre", { className: "mermaid-render mermaid_single" }, value);
11787
+ return (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("pre", { ref: mermaidRef, className: "mermaid-render mermaid_single" }, value));
11753
11788
  }
11754
11789
  }
11755
11790
  return (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { className: `CodeTabs CodeTabs_initial theme-${theme}` },
11756
11791
  external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { className: "CodeTabs-toolbar" }, childrenArray.map((pre, i) => {
11757
11792
  // the first or only child should be our Code component
11758
- const tabCodeComponent = Array.isArray(pre.props?.children)
11759
- ? pre.props.children[0]
11760
- : pre.props?.children;
11793
+ const tabCodeComponent = Array.isArray(pre.props?.children) ? pre.props.children[0] : pre.props?.children;
11761
11794
  const lang = tabCodeComponent?.props?.lang;
11762
11795
  const meta = tabCodeComponent?.props?.meta;
11763
11796
  /* istanbul ignore next */
@@ -12309,10 +12342,19 @@ const TailwindStyle = ({ children, darkModeDataAttribute }) => {
12309
12342
  records.forEach(record => {
12310
12343
  if (record.type === 'childList') {
12311
12344
  record.addedNodes.forEach(node => {
12312
- if (!(node instanceof HTMLElement) || !node.classList.contains(tailwindPrefix))
12345
+ if (!(node instanceof HTMLElement))
12313
12346
  return;
12314
- traverse(node, addClasses);
12315
- shouldUpdate = true;
12347
+ // Check the added node itself
12348
+ if (node.classList.contains(tailwindPrefix)) {
12349
+ traverse(node, addClasses);
12350
+ shouldUpdate = true;
12351
+ }
12352
+ // Also check descendants — React may insert a parent node
12353
+ // whose children contain TailwindRoot elements
12354
+ node.querySelectorAll(`.${tailwindPrefix}`).forEach(child => {
12355
+ traverse(child, addClasses);
12356
+ shouldUpdate = true;
12357
+ });
12316
12358
  });
12317
12359
  }
12318
12360
  else if (record.type === 'attributes') {
@@ -12321,10 +12363,10 @@ const TailwindStyle = ({ children, darkModeDataAttribute }) => {
12321
12363
  addClasses(record.target);
12322
12364
  shouldUpdate = true;
12323
12365
  }
12324
- if (shouldUpdate) {
12325
- setClasses(Array.from(classesSet.current));
12326
- }
12327
12366
  });
12367
+ if (shouldUpdate) {
12368
+ setClasses(Array.from(classesSet.current));
12369
+ }
12328
12370
  });
12329
12371
  observer.observe(ref.current.parentElement, {
12330
12372
  subtree: true,
@@ -70410,9 +70452,416 @@ const mdast = (text, opts = {}) => {
70410
70452
  };
70411
70453
  /* harmony default export */ const lib_mdast = (mdast);
70412
70454
 
70455
+ ;// ./lib/utils/mdxish/protect-code-blocks.ts
70456
+ /**
70457
+ * Replaces code blocks and inline code with placeholders to protect them from preprocessing.
70458
+ *
70459
+ * @param content - The markdown content to process
70460
+ * @returns Object containing protected content and arrays of original code blocks
70461
+ * @example
70462
+ * ```typescript
70463
+ * const input = 'Text with `inline code` and ```fenced block```';
70464
+ * protectCodeBlocks(input)
70465
+ * // Returns: {
70466
+ * // protectedCode: {
70467
+ * // codeBlocks: ['```fenced block```'],
70468
+ * // inlineCode: ['`inline code`']
70469
+ * // },
70470
+ * // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
70471
+ * // }
70472
+ * ```
70473
+ */
70474
+ function protectCodeBlocks(content) {
70475
+ const codeBlocks = [];
70476
+ const inlineCode = [];
70477
+ let protectedContent = '';
70478
+ let remaining = content;
70479
+ let codeBlockStart = remaining.indexOf('```');
70480
+ while (codeBlockStart !== -1) {
70481
+ protectedContent += remaining.slice(0, codeBlockStart);
70482
+ remaining = remaining.slice(codeBlockStart);
70483
+ const codeBlockEnd = remaining.indexOf('```', 3);
70484
+ if (codeBlockEnd === -1) {
70485
+ break;
70486
+ }
70487
+ const match = remaining.slice(0, codeBlockEnd + 3);
70488
+ const index = codeBlocks.length;
70489
+ codeBlocks.push(match);
70490
+ protectedContent += `___CODE_BLOCK_${index}___`;
70491
+ remaining = remaining.slice(codeBlockEnd + 3);
70492
+ codeBlockStart = remaining.indexOf('```');
70493
+ }
70494
+ protectedContent += remaining;
70495
+ protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
70496
+ const index = inlineCode.length;
70497
+ inlineCode.push(match);
70498
+ return `___INLINE_CODE_${index}___`;
70499
+ });
70500
+ return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
70501
+ }
70502
+ /**
70503
+ * Restores inline code by replacing placeholders with original content.
70504
+ *
70505
+ * @param content - Content with inline code placeholders
70506
+ * @param protectedCode - The protected code arrays
70507
+ * @returns Content with inline code restored
70508
+ */
70509
+ function restoreInlineCode(content, protectedCode) {
70510
+ return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
70511
+ return protectedCode.inlineCode[parseInt(idx, 10)];
70512
+ });
70513
+ }
70514
+ /**
70515
+ * Restores fenced code blocks by replacing placeholders with original content.
70516
+ *
70517
+ * @param content - Content with code block placeholders
70518
+ * @param protectedCode - The protected code arrays
70519
+ * @returns Content with code blocks restored
70520
+ */
70521
+ function restoreFencedCodeBlocks(content, protectedCode) {
70522
+ return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
70523
+ return protectedCode.codeBlocks[parseInt(idx, 10)];
70524
+ });
70525
+ }
70526
+ /**
70527
+ * Restores all code blocks and inline code by replacing placeholders with original content.
70528
+ *
70529
+ * @param content - Content with code placeholders
70530
+ * @param protectedCode - The protected code arrays
70531
+ * @returns Content with all code blocks and inline code restored
70532
+ * @example
70533
+ * ```typescript
70534
+ * const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
70535
+ * const protectedCode = {
70536
+ * codeBlocks: ['```js\ncode\n```'],
70537
+ * inlineCode: ['`inline`']
70538
+ * };
70539
+ * restoreCodeBlocks(content, protectedCode)
70540
+ * // Returns: 'Text with `inline` and ```js\ncode\n```'
70541
+ * ```
70542
+ */
70543
+ function restoreCodeBlocks(content, protectedCode) {
70544
+ let restored = restoreFencedCodeBlocks(content, protectedCode);
70545
+ restored = restoreInlineCode(restored, protectedCode);
70546
+ return restored;
70547
+ }
70548
+
70549
+ ;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
70550
+
70551
+ // Base64 encode (Node.js + browser compatible)
70552
+ function base64Encode(str) {
70553
+ if (typeof Buffer !== 'undefined') {
70554
+ return Buffer.from(str, 'utf-8').toString('base64');
70555
+ }
70556
+ return btoa(unescape(encodeURIComponent(str)));
70557
+ }
70558
+ // Base64 decode (Node.js + browser compatible)
70559
+ function base64Decode(str) {
70560
+ if (typeof Buffer !== 'undefined') {
70561
+ return Buffer.from(str, 'base64').toString('utf-8');
70562
+ }
70563
+ return decodeURIComponent(escape(atob(str)));
70564
+ }
70565
+ function escapeHtmlAttribute(value) {
70566
+ return value
70567
+ .replace(/&/g, '&')
70568
+ .replace(/"/g, '"')
70569
+ .replace(/</g, '&lt;')
70570
+ .replace(/>/g, '&gt;')
70571
+ .replace(/\n/g, '&#10;');
70572
+ }
70573
+ // Marker prefix for JSON-serialized complex values (arrays/objects)
70574
+ // Using a prefix that won't conflict with regular string values
70575
+ const JSON_VALUE_MARKER = '__MDXISH_JSON__';
70576
+ // Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
70577
+ const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
70578
+ const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
70579
+ /**
70580
+ * Evaluates a JavaScript expression using context variables.
70581
+ *
70582
+ * @param expression
70583
+ * @param context
70584
+ * @returns The evaluated result
70585
+ * @example
70586
+ * ```typescript
70587
+ * const context = { baseUrl: 'https://example.com', path: '/api' };
70588
+ * evaluateExpression('baseUrl + path', context)
70589
+ * // Returns: 'https://example.com/api'
70590
+ * ```
70591
+ */
70592
+ function evaluateExpression(expression, context) {
70593
+ const contextKeys = Object.keys(context);
70594
+ const contextValues = Object.values(context);
70595
+ // eslint-disable-next-line no-new-func
70596
+ const func = new Function(...contextKeys, `return ${expression}`);
70597
+ return func(...contextValues);
70598
+ }
70599
+ /**
70600
+ * Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
70601
+ *
70602
+ * @param content
70603
+ * @returns Content with HTMLBlock template literals base64 encoded in HTML comments
70604
+ * @example
70605
+ * ```typescript
70606
+ * const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
70607
+ * protectHTMLBlockContent(input)
70608
+ * // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
70609
+ * ```
70610
+ */
70611
+ function protectHTMLBlockContent(content) {
70612
+ return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
70613
+ const encoded = base64Encode(templateContent);
70614
+ return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
70615
+ });
70616
+ }
70617
+ /**
70618
+ * Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
70619
+ *
70620
+ * @param content
70621
+ * @returns Content with JSX comments removed
70622
+ * @example
70623
+ * ```typescript
70624
+ * removeJSXComments('Text { /* comment *\/ } more text')
70625
+ * // Returns: 'Text more text'
70626
+ * ```
70627
+ */
70628
+ function removeJSXComments(content) {
70629
+ return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
70630
+ }
70631
+ /**
70632
+ * Extracts content between balanced braces, handling nested braces.
70633
+ *
70634
+ * @param content
70635
+ * @param start
70636
+ * @returns Object with extracted content and end position, or null if braces are unbalanced
70637
+ * @example
70638
+ * ```typescript
70639
+ * const input = 'foo{bar{baz}qux}end';
70640
+ * extractBalancedBraces(input, 3) // start at position 3 (after '{')
70641
+ * // Returns: { content: 'bar{baz}qux', end: 16 }
70642
+ * ```
70643
+ */
70644
+ function extractBalancedBraces(content, start) {
70645
+ let depth = 1;
70646
+ let pos = start;
70647
+ while (pos < content.length && depth > 0) {
70648
+ const char = content[pos];
70649
+ if (char === '{')
70650
+ depth += 1;
70651
+ else if (char === '}')
70652
+ depth -= 1;
70653
+ pos += 1;
70654
+ }
70655
+ if (depth !== 0)
70656
+ return null;
70657
+ return { content: content.slice(start, pos - 1), end: pos };
70658
+ }
70659
+ /**
70660
+ * Escapes problematic braces in content to prevent MDX expression parsing errors.
70661
+ * Handles three cases:
70662
+ * 1. Unbalanced braces (e.g., `{foo` without closing `}`)
70663
+ * 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
70664
+ * 3. Skips HTML elements to prevent backslashes appearing in output
70665
+ *
70666
+ */
70667
+ function escapeProblematicBraces(content) {
70668
+ // Skip HTML elements — their content should never be escaped because
70669
+ // rehypeRaw parses them into hast elements, making `\` literal text in output
70670
+ const htmlElements = [];
70671
+ const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
70672
+ const idx = htmlElements.length;
70673
+ htmlElements.push(match);
70674
+ return `___HTML_ELEM_${idx}___`;
70675
+ });
70676
+ const toEscape = new Set();
70677
+ // Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
70678
+ const chars = Array.from(safe);
70679
+ let strDelim = null;
70680
+ let strEscaped = false;
70681
+ // Stack of open braces with their state
70682
+ const openStack = [];
70683
+ // Track position of last newline (outside strings) to detect blank lines
70684
+ let lastNewlinePos = -2; // -2 means no recent newline
70685
+ for (let i = 0; i < chars.length; i += 1) {
70686
+ const ch = chars[i];
70687
+ // Track string delimiters inside expressions to ignore braces within them
70688
+ if (openStack.length > 0) {
70689
+ if (strDelim) {
70690
+ if (strEscaped)
70691
+ strEscaped = false;
70692
+ else if (ch === '\\')
70693
+ strEscaped = true;
70694
+ else if (ch === strDelim)
70695
+ strDelim = null;
70696
+ // eslint-disable-next-line no-continue
70697
+ continue;
70698
+ }
70699
+ if (ch === '"' || ch === "'" || ch === '`') {
70700
+ strDelim = ch;
70701
+ // eslint-disable-next-line no-continue
70702
+ continue;
70703
+ }
70704
+ // Track newlines to detect blank lines (paragraph boundaries)
70705
+ if (ch === '\n') {
70706
+ // Check if this newline creates a blank line (only whitespace since last newline)
70707
+ if (lastNewlinePos >= 0) {
70708
+ const between = chars.slice(lastNewlinePos + 1, i).join('');
70709
+ if (/^[ \t]*$/.test(between)) {
70710
+ // This is a blank line - mark all open expressions as paragraph-spanning
70711
+ openStack.forEach(entry => {
70712
+ entry.hasBlankLine = true;
70713
+ });
70714
+ }
70715
+ }
70716
+ lastNewlinePos = i;
70717
+ }
70718
+ }
70719
+ // Skip already-escaped braces (count preceding backslashes)
70720
+ if (ch === '{' || ch === '}') {
70721
+ let bs = 0;
70722
+ for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
70723
+ bs += 1;
70724
+ if (bs % 2 === 1) {
70725
+ // eslint-disable-next-line no-continue
70726
+ continue;
70727
+ }
70728
+ }
70729
+ if (ch === '{') {
70730
+ openStack.push({ pos: i, hasBlankLine: false });
70731
+ lastNewlinePos = -2; // Reset newline tracking for new expression
70732
+ }
70733
+ else if (ch === '}') {
70734
+ if (openStack.length > 0) {
70735
+ const entry = openStack.pop();
70736
+ // If expression spans paragraph boundary, escape both braces
70737
+ if (entry.hasBlankLine) {
70738
+ toEscape.add(entry.pos);
70739
+ toEscape.add(i);
70740
+ }
70741
+ }
70742
+ else {
70743
+ // Unbalanced closing brace (no matching open)
70744
+ toEscape.add(i);
70745
+ }
70746
+ }
70747
+ }
70748
+ // Any remaining open braces are unbalanced
70749
+ openStack.forEach(entry => toEscape.add(entry.pos));
70750
+ // If there are no problematic braces, return safe content as-is;
70751
+ // otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
70752
+ let result = toEscape.size === 0
70753
+ ? safe
70754
+ : chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
70755
+ // Restore HTML elements
70756
+ if (htmlElements.length > 0) {
70757
+ result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
70758
+ }
70759
+ return result;
70760
+ }
70761
+ /**
70762
+ * Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
70763
+ * Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
70764
+ *
70765
+ * @param content
70766
+ * @param context
70767
+ * @returns Content with attribute expressions evaluated and converted to HTML attributes
70768
+ * @example
70769
+ * ```typescript
70770
+ * const context = { baseUrl: 'https://example.com' };
70771
+ * const input = '<a href={baseUrl}>Link</a>';
70772
+ * evaluateAttributeExpressions(input, context)
70773
+ * // Returns: '<a href="https://example.com">Link</a>'
70774
+ * ```
70775
+ */
70776
+ function evaluateAttributeExpressions(content, context, protectedCode) {
70777
+ const attrStartRegex = /(\w+)=\{/g;
70778
+ let result = '';
70779
+ let lastEnd = 0;
70780
+ let match = attrStartRegex.exec(content);
70781
+ while (match !== null) {
70782
+ const attributeName = match[1];
70783
+ const braceStart = match.index + match[0].length;
70784
+ const extracted = extractBalancedBraces(content, braceStart);
70785
+ if (extracted) {
70786
+ // The expression might contain template literals in MDX component tag props
70787
+ // E.g. <Component greeting={`Hello World!`} />
70788
+ // that is marked as inline code. So we need to restore the inline codes
70789
+ // in the expression to evaluate it
70790
+ let expression = extracted.content;
70791
+ if (protectedCode) {
70792
+ expression = restoreInlineCode(expression, protectedCode);
70793
+ }
70794
+ const fullMatchEnd = extracted.end;
70795
+ result += content.slice(lastEnd, match.index);
70796
+ try {
70797
+ const evalResult = evaluateExpression(expression, context);
70798
+ if (typeof evalResult === 'object' && evalResult !== null) {
70799
+ if (attributeName === 'style') {
70800
+ const cssString = Object.entries(evalResult)
70801
+ .map(([key, value]) => {
70802
+ const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
70803
+ return `${cssKey}: ${value}`;
70804
+ })
70805
+ .join('; ');
70806
+ result += `style="${cssString}"`;
70807
+ }
70808
+ else {
70809
+ // These are arrays / objects attribute values
70810
+ // Mark JSON-serialized values with a prefix so they can be parsed back correctly
70811
+ const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
70812
+ // Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
70813
+ result += `${attributeName}="${jsonValue}"`;
70814
+ }
70815
+ }
70816
+ else if (attributeName === 'className') {
70817
+ // Escape special characters so that it doesn't break and split the attribute value to nodes
70818
+ // This will be restored later in the pipeline
70819
+ result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
70820
+ }
70821
+ else {
70822
+ result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
70823
+ }
70824
+ }
70825
+ catch (_error) {
70826
+ result += content.slice(match.index, fullMatchEnd);
70827
+ }
70828
+ lastEnd = fullMatchEnd;
70829
+ attrStartRegex.lastIndex = fullMatchEnd;
70830
+ }
70831
+ match = attrStartRegex.exec(content);
70832
+ }
70833
+ result += content.slice(lastEnd);
70834
+ return result;
70835
+ }
70836
+ /**
70837
+ * Preprocesses JSX-like expressions in markdown before parsing.
70838
+ * Inline expressions are handled separately; attribute expressions are processed here.
70839
+ *
70840
+ * @param content
70841
+ * @param context
70842
+ * @returns Preprocessed content ready for markdown parsing
70843
+ */
70844
+ function preprocessJSXExpressions(content, context = {}) {
70845
+ // Step 0: Base64 encode HTMLBlock content
70846
+ let processed = protectHTMLBlockContent(content);
70847
+ // Step 1: Protect code blocks and inline code
70848
+ const { protectedCode, protectedContent } = protectCodeBlocks(processed);
70849
+ // Step 2: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
70850
+ // For inline expressions, we use a library to parse the expression & evaluate it later
70851
+ // For attribute expressions, it was difficult to use a library to parse them, so do it manually
70852
+ processed = evaluateAttributeExpressions(protectedContent, context, protectedCode);
70853
+ // Step 3: Escape problematic braces to prevent MDX expression parsing errors
70854
+ // This handles both unbalanced braces and paragraph-spanning expressions in one pass
70855
+ processed = escapeProblematicBraces(processed);
70856
+ // Step 4: Restore protected code blocks
70857
+ processed = restoreCodeBlocks(processed, protectedCode);
70858
+ return processed;
70859
+ }
70860
+
70413
70861
  ;// ./processor/utils.ts
70414
70862
 
70415
70863
 
70864
+
70416
70865
  /**
70417
70866
  * Formats the hProperties of a node as a string, so they can be compiled back into JSX/MDX.
70418
70867
  * This currently sets all the values to a string since we process/compile the MDX on the fly
@@ -70468,7 +70917,17 @@ const getHPropKeys = (node) => {
70468
70917
  const getAttrs = (jsx) => jsx.attributes.reduce((memo, attr) => {
70469
70918
  if ('name' in attr) {
70470
70919
  if (typeof attr.value === 'string') {
70471
- memo[attr.name] = attr.value;
70920
+ if (attr.value.startsWith(JSON_VALUE_MARKER)) {
70921
+ try {
70922
+ memo[attr.name] = JSON.parse(attr.value.slice(JSON_VALUE_MARKER.length));
70923
+ }
70924
+ catch {
70925
+ memo[attr.name] = attr.value;
70926
+ }
70927
+ }
70928
+ else {
70929
+ memo[attr.name] = attr.value;
70930
+ }
70472
70931
  }
70473
70932
  else if (attr.value === null) {
70474
70933
  memo[attr.name] = true;
@@ -71242,10 +71701,10 @@ const hasFlowContent = (nodes) => {
71242
71701
  * Process a Table node: re-parse text-only cell content, then output as
71243
71702
  * a markdown table (phrasing-only) or keep as JSX <Table> (has flow content).
71244
71703
  */
71245
- const processTableNode = (node, index, parent) => {
71704
+ const processTableNode = (node, index, parent, documentPosition) => {
71246
71705
  if (node.name !== 'Table')
71247
71706
  return;
71248
- const { position } = node;
71707
+ const position = documentPosition ?? node.position;
71249
71708
  const { align: alignAttr } = getAttrs(node);
71250
71709
  const align = Array.isArray(alignAttr) ? alignAttr : null;
71251
71710
  let tableHasFlowContent = false;
@@ -71368,7 +71827,7 @@ const mdxishTables = () => tree => {
71368
71827
  const parsed = tableNodeProcessor.runSync(tableNodeProcessor.parse(node.value));
71369
71828
  visit(parsed, isMDXElement, (tableNode) => {
71370
71829
  if (tableNode.name === 'Table') {
71371
- processTableNode(tableNode, index, parent);
71830
+ processTableNode(tableNode, index, parent, node.position);
71372
71831
  // Stop after the outermost Table so nested Tables don't overwrite parent.children[index]
71373
71832
  // we let it get handled naturally
71374
71833
  return EXIT;
@@ -93843,412 +94302,6 @@ const mdxComponentHandlers = {
93843
94302
  [NodeTypes.htmlBlock]: htmlBlockHandler,
93844
94303
  };
93845
94304
 
93846
- ;// ./lib/utils/mdxish/protect-code-blocks.ts
93847
- /**
93848
- * Replaces code blocks and inline code with placeholders to protect them from preprocessing.
93849
- *
93850
- * @param content - The markdown content to process
93851
- * @returns Object containing protected content and arrays of original code blocks
93852
- * @example
93853
- * ```typescript
93854
- * const input = 'Text with `inline code` and ```fenced block```';
93855
- * protectCodeBlocks(input)
93856
- * // Returns: {
93857
- * // protectedCode: {
93858
- * // codeBlocks: ['```fenced block```'],
93859
- * // inlineCode: ['`inline code`']
93860
- * // },
93861
- * // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
93862
- * // }
93863
- * ```
93864
- */
93865
- function protectCodeBlocks(content) {
93866
- const codeBlocks = [];
93867
- const inlineCode = [];
93868
- let protectedContent = '';
93869
- let remaining = content;
93870
- let codeBlockStart = remaining.indexOf('```');
93871
- while (codeBlockStart !== -1) {
93872
- protectedContent += remaining.slice(0, codeBlockStart);
93873
- remaining = remaining.slice(codeBlockStart);
93874
- const codeBlockEnd = remaining.indexOf('```', 3);
93875
- if (codeBlockEnd === -1) {
93876
- break;
93877
- }
93878
- const match = remaining.slice(0, codeBlockEnd + 3);
93879
- const index = codeBlocks.length;
93880
- codeBlocks.push(match);
93881
- protectedContent += `___CODE_BLOCK_${index}___`;
93882
- remaining = remaining.slice(codeBlockEnd + 3);
93883
- codeBlockStart = remaining.indexOf('```');
93884
- }
93885
- protectedContent += remaining;
93886
- protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
93887
- const index = inlineCode.length;
93888
- inlineCode.push(match);
93889
- return `___INLINE_CODE_${index}___`;
93890
- });
93891
- return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
93892
- }
93893
- /**
93894
- * Restores inline code by replacing placeholders with original content.
93895
- *
93896
- * @param content - Content with inline code placeholders
93897
- * @param protectedCode - The protected code arrays
93898
- * @returns Content with inline code restored
93899
- */
93900
- function restoreInlineCode(content, protectedCode) {
93901
- return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
93902
- return protectedCode.inlineCode[parseInt(idx, 10)];
93903
- });
93904
- }
93905
- /**
93906
- * Restores fenced code blocks by replacing placeholders with original content.
93907
- *
93908
- * @param content - Content with code block placeholders
93909
- * @param protectedCode - The protected code arrays
93910
- * @returns Content with code blocks restored
93911
- */
93912
- function restoreFencedCodeBlocks(content, protectedCode) {
93913
- return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
93914
- return protectedCode.codeBlocks[parseInt(idx, 10)];
93915
- });
93916
- }
93917
- /**
93918
- * Restores all code blocks and inline code by replacing placeholders with original content.
93919
- *
93920
- * @param content - Content with code placeholders
93921
- * @param protectedCode - The protected code arrays
93922
- * @returns Content with all code blocks and inline code restored
93923
- * @example
93924
- * ```typescript
93925
- * const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
93926
- * const protectedCode = {
93927
- * codeBlocks: ['```js\ncode\n```'],
93928
- * inlineCode: ['`inline`']
93929
- * };
93930
- * restoreCodeBlocks(content, protectedCode)
93931
- * // Returns: 'Text with `inline` and ```js\ncode\n```'
93932
- * ```
93933
- */
93934
- function restoreCodeBlocks(content, protectedCode) {
93935
- let restored = restoreFencedCodeBlocks(content, protectedCode);
93936
- restored = restoreInlineCode(restored, protectedCode);
93937
- return restored;
93938
- }
93939
-
93940
- ;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
93941
-
93942
- // Base64 encode (Node.js + browser compatible)
93943
- function base64Encode(str) {
93944
- if (typeof Buffer !== 'undefined') {
93945
- return Buffer.from(str, 'utf-8').toString('base64');
93946
- }
93947
- return btoa(unescape(encodeURIComponent(str)));
93948
- }
93949
- // Base64 decode (Node.js + browser compatible)
93950
- function base64Decode(str) {
93951
- if (typeof Buffer !== 'undefined') {
93952
- return Buffer.from(str, 'base64').toString('utf-8');
93953
- }
93954
- return decodeURIComponent(escape(atob(str)));
93955
- }
93956
- function escapeHtmlAttribute(value) {
93957
- return value
93958
- .replace(/&/g, '&amp;')
93959
- .replace(/"/g, '&quot;')
93960
- .replace(/</g, '&lt;')
93961
- .replace(/>/g, '&gt;')
93962
- .replace(/\n/g, '&#10;');
93963
- }
93964
- // Marker prefix for JSON-serialized complex values (arrays/objects)
93965
- // Using a prefix that won't conflict with regular string values
93966
- const JSON_VALUE_MARKER = '__MDXISH_JSON__';
93967
- // Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
93968
- const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
93969
- const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
93970
- /**
93971
- * Evaluates a JavaScript expression using context variables.
93972
- *
93973
- * @param expression
93974
- * @param context
93975
- * @returns The evaluated result
93976
- * @example
93977
- * ```typescript
93978
- * const context = { baseUrl: 'https://example.com', path: '/api' };
93979
- * evaluateExpression('baseUrl + path', context)
93980
- * // Returns: 'https://example.com/api'
93981
- * ```
93982
- */
93983
- function evaluateExpression(expression, context) {
93984
- const contextKeys = Object.keys(context);
93985
- const contextValues = Object.values(context);
93986
- // eslint-disable-next-line no-new-func
93987
- const func = new Function(...contextKeys, `return ${expression}`);
93988
- return func(...contextValues);
93989
- }
93990
- /**
93991
- * Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
93992
- *
93993
- * @param content
93994
- * @returns Content with HTMLBlock template literals base64 encoded in HTML comments
93995
- * @example
93996
- * ```typescript
93997
- * const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
93998
- * protectHTMLBlockContent(input)
93999
- * // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
94000
- * ```
94001
- */
94002
- function protectHTMLBlockContent(content) {
94003
- return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
94004
- const encoded = base64Encode(templateContent);
94005
- return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
94006
- });
94007
- }
94008
- /**
94009
- * Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
94010
- *
94011
- * @param content
94012
- * @returns Content with JSX comments removed
94013
- * @example
94014
- * ```typescript
94015
- * removeJSXComments('Text { /* comment *\/ } more text')
94016
- * // Returns: 'Text more text'
94017
- * ```
94018
- */
94019
- function removeJSXComments(content) {
94020
- return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
94021
- }
94022
- /**
94023
- * Extracts content between balanced braces, handling nested braces.
94024
- *
94025
- * @param content
94026
- * @param start
94027
- * @returns Object with extracted content and end position, or null if braces are unbalanced
94028
- * @example
94029
- * ```typescript
94030
- * const input = 'foo{bar{baz}qux}end';
94031
- * extractBalancedBraces(input, 3) // start at position 3 (after '{')
94032
- * // Returns: { content: 'bar{baz}qux', end: 16 }
94033
- * ```
94034
- */
94035
- function extractBalancedBraces(content, start) {
94036
- let depth = 1;
94037
- let pos = start;
94038
- while (pos < content.length && depth > 0) {
94039
- const char = content[pos];
94040
- if (char === '{')
94041
- depth += 1;
94042
- else if (char === '}')
94043
- depth -= 1;
94044
- pos += 1;
94045
- }
94046
- if (depth !== 0)
94047
- return null;
94048
- return { content: content.slice(start, pos - 1), end: pos };
94049
- }
94050
- /**
94051
- * Escapes problematic braces in content to prevent MDX expression parsing errors.
94052
- * Handles three cases:
94053
- * 1. Unbalanced braces (e.g., `{foo` without closing `}`)
94054
- * 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
94055
- * 3. Skips HTML elements to prevent backslashes appearing in output
94056
- *
94057
- */
94058
- function escapeProblematicBraces(content) {
94059
- // Skip HTML elements — their content should never be escaped because
94060
- // rehypeRaw parses them into hast elements, making `\` literal text in output
94061
- const htmlElements = [];
94062
- const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
94063
- const idx = htmlElements.length;
94064
- htmlElements.push(match);
94065
- return `___HTML_ELEM_${idx}___`;
94066
- });
94067
- const toEscape = new Set();
94068
- // Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
94069
- const chars = Array.from(safe);
94070
- let strDelim = null;
94071
- let strEscaped = false;
94072
- // Stack of open braces with their state
94073
- const openStack = [];
94074
- // Track position of last newline (outside strings) to detect blank lines
94075
- let lastNewlinePos = -2; // -2 means no recent newline
94076
- for (let i = 0; i < chars.length; i += 1) {
94077
- const ch = chars[i];
94078
- // Track string delimiters inside expressions to ignore braces within them
94079
- if (openStack.length > 0) {
94080
- if (strDelim) {
94081
- if (strEscaped)
94082
- strEscaped = false;
94083
- else if (ch === '\\')
94084
- strEscaped = true;
94085
- else if (ch === strDelim)
94086
- strDelim = null;
94087
- // eslint-disable-next-line no-continue
94088
- continue;
94089
- }
94090
- if (ch === '"' || ch === "'" || ch === '`') {
94091
- strDelim = ch;
94092
- // eslint-disable-next-line no-continue
94093
- continue;
94094
- }
94095
- // Track newlines to detect blank lines (paragraph boundaries)
94096
- if (ch === '\n') {
94097
- // Check if this newline creates a blank line (only whitespace since last newline)
94098
- if (lastNewlinePos >= 0) {
94099
- const between = chars.slice(lastNewlinePos + 1, i).join('');
94100
- if (/^[ \t]*$/.test(between)) {
94101
- // This is a blank line - mark all open expressions as paragraph-spanning
94102
- openStack.forEach(entry => {
94103
- entry.hasBlankLine = true;
94104
- });
94105
- }
94106
- }
94107
- lastNewlinePos = i;
94108
- }
94109
- }
94110
- // Skip already-escaped braces (count preceding backslashes)
94111
- if (ch === '{' || ch === '}') {
94112
- let bs = 0;
94113
- for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
94114
- bs += 1;
94115
- if (bs % 2 === 1) {
94116
- // eslint-disable-next-line no-continue
94117
- continue;
94118
- }
94119
- }
94120
- if (ch === '{') {
94121
- openStack.push({ pos: i, hasBlankLine: false });
94122
- lastNewlinePos = -2; // Reset newline tracking for new expression
94123
- }
94124
- else if (ch === '}') {
94125
- if (openStack.length > 0) {
94126
- const entry = openStack.pop();
94127
- // If expression spans paragraph boundary, escape both braces
94128
- if (entry.hasBlankLine) {
94129
- toEscape.add(entry.pos);
94130
- toEscape.add(i);
94131
- }
94132
- }
94133
- else {
94134
- // Unbalanced closing brace (no matching open)
94135
- toEscape.add(i);
94136
- }
94137
- }
94138
- }
94139
- // Any remaining open braces are unbalanced
94140
- openStack.forEach(entry => toEscape.add(entry.pos));
94141
- // If there are no problematic braces, return safe content as-is;
94142
- // otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
94143
- let result = toEscape.size === 0
94144
- ? safe
94145
- : chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
94146
- // Restore HTML elements
94147
- if (htmlElements.length > 0) {
94148
- result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
94149
- }
94150
- return result;
94151
- }
94152
- /**
94153
- * Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
94154
- * Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
94155
- *
94156
- * @param content
94157
- * @param context
94158
- * @returns Content with attribute expressions evaluated and converted to HTML attributes
94159
- * @example
94160
- * ```typescript
94161
- * const context = { baseUrl: 'https://example.com' };
94162
- * const input = '<a href={baseUrl}>Link</a>';
94163
- * evaluateAttributeExpressions(input, context)
94164
- * // Returns: '<a href="https://example.com">Link</a>'
94165
- * ```
94166
- */
94167
- function evaluateAttributeExpressions(content, context, protectedCode) {
94168
- const attrStartRegex = /(\w+)=\{/g;
94169
- let result = '';
94170
- let lastEnd = 0;
94171
- let match = attrStartRegex.exec(content);
94172
- while (match !== null) {
94173
- const attributeName = match[1];
94174
- const braceStart = match.index + match[0].length;
94175
- const extracted = extractBalancedBraces(content, braceStart);
94176
- if (extracted) {
94177
- // The expression might contain template literals in MDX component tag props
94178
- // E.g. <Component greeting={`Hello World!`} />
94179
- // that is marked as inline code. So we need to restore the inline codes
94180
- // in the expression to evaluate it
94181
- let expression = extracted.content;
94182
- if (protectedCode) {
94183
- expression = restoreInlineCode(expression, protectedCode);
94184
- }
94185
- const fullMatchEnd = extracted.end;
94186
- result += content.slice(lastEnd, match.index);
94187
- try {
94188
- const evalResult = evaluateExpression(expression, context);
94189
- if (typeof evalResult === 'object' && evalResult !== null) {
94190
- if (attributeName === 'style') {
94191
- const cssString = Object.entries(evalResult)
94192
- .map(([key, value]) => {
94193
- const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
94194
- return `${cssKey}: ${value}`;
94195
- })
94196
- .join('; ');
94197
- result += `style="${cssString}"`;
94198
- }
94199
- else {
94200
- // These are arrays / objects attribute values
94201
- // Mark JSON-serialized values with a prefix so they can be parsed back correctly
94202
- const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
94203
- // Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
94204
- result += `${attributeName}="${jsonValue}"`;
94205
- }
94206
- }
94207
- else if (attributeName === 'className') {
94208
- // Escape special characters so that it doesn't break and split the attribute value to nodes
94209
- // This will be restored later in the pipeline
94210
- result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
94211
- }
94212
- else {
94213
- result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
94214
- }
94215
- }
94216
- catch (_error) {
94217
- result += content.slice(match.index, fullMatchEnd);
94218
- }
94219
- lastEnd = fullMatchEnd;
94220
- attrStartRegex.lastIndex = fullMatchEnd;
94221
- }
94222
- match = attrStartRegex.exec(content);
94223
- }
94224
- result += content.slice(lastEnd);
94225
- return result;
94226
- }
94227
- /**
94228
- * Preprocesses JSX-like expressions in markdown before parsing.
94229
- * Inline expressions are handled separately; attribute expressions are processed here.
94230
- *
94231
- * @param content
94232
- * @param context
94233
- * @returns Preprocessed content ready for markdown parsing
94234
- */
94235
- function preprocessJSXExpressions(content, context = {}) {
94236
- // Step 0: Base64 encode HTMLBlock content
94237
- let processed = protectHTMLBlockContent(content);
94238
- // Step 1: Protect code blocks and inline code
94239
- const { protectedCode, protectedContent } = protectCodeBlocks(processed);
94240
- // Step 2: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
94241
- // For inline expressions, we use a library to parse the expression & evaluate it later
94242
- // For attribute expressions, it was difficult to use a library to parse them, so do it manually
94243
- processed = evaluateAttributeExpressions(protectedContent, context, protectedCode);
94244
- // Step 3: Escape problematic braces to prevent MDX expression parsing errors
94245
- // This handles both unbalanced braces and paragraph-spanning expressions in one pass
94246
- processed = escapeProblematicBraces(processed);
94247
- // Step 4: Restore protected code blocks
94248
- processed = restoreCodeBlocks(processed, protectedCode);
94249
- return processed;
94250
- }
94251
-
94252
94305
  ;// ./processor/transform/mdxish/evaluate-expressions.ts
94253
94306
 
94254
94307
 
@@ -96042,11 +96095,33 @@ const parseBlock = (text) => {
96042
96095
  const tree = contentParser.runSync(contentParser.parse(text));
96043
96096
  return tree.children;
96044
96097
  };
96045
- const parseInline = (text) => {
96098
+ /**
96099
+ * Minimal parser for api-header titles.
96100
+ * Disables markdown constructs that are not parsed in legacy (headings, lists)
96101
+ */
96102
+ const apiHeaderTitleParser = unified()
96103
+ .data('micromarkExtensions', [
96104
+ legacyVariable(),
96105
+ looseHtmlEntity(),
96106
+ {
96107
+ disable: {
96108
+ null: [
96109
+ 'blockQuote',
96110
+ 'headingAtx',
96111
+ 'list',
96112
+ 'thematicBreak',
96113
+ ],
96114
+ },
96115
+ },
96116
+ ])
96117
+ .data('fromMarkdownExtensions', [legacyVariableFromMarkdown(), looseHtmlEntityFromMarkdown()])
96118
+ .use(remarkParse)
96119
+ .use(remarkGfm);
96120
+ const parseApiHeaderTitle = (text) => {
96046
96121
  if (!text.trim())
96047
96122
  return textToInline(text);
96048
- const tree = contentParser.runSync(contentParser.parse(text));
96049
- return tree.children;
96123
+ const tree = apiHeaderTitleParser.runSync(apiHeaderTitleParser.parse(text));
96124
+ return tree.children.flatMap(n => n.type === 'paragraph' && 'children' in n ? n.children : [n]);
96050
96125
  };
96051
96126
  /**
96052
96127
  * Transform a magicBlock node into final MDAST nodes.
@@ -96105,7 +96180,7 @@ function transformMagicBlock(blockType, data, rawValue, options = {}) {
96105
96180
  const depth = headerJson.level || (compatibilityMode ? 1 : 2);
96106
96181
  return [
96107
96182
  wrapPinnedBlocks({
96108
- children: 'title' in headerJson ? parseInline(headerJson.title || '') : [],
96183
+ children: 'title' in headerJson ? parseApiHeaderTitle(headerJson.title || '') : [],
96109
96184
  depth,
96110
96185
  type: 'heading',
96111
96186
  }, data),
@@ -97337,11 +97412,66 @@ const transformMagicBlockEmbed = (node) => {
97337
97412
  position,
97338
97413
  };
97339
97414
  };
97415
+ const mdxish_jsx_to_mdast_isTableCell = (node) => isMDXElement(node) && ['th', 'td'].includes(node.name);
97416
+ /**
97417
+ * Converts a JSX <Table> element to an MDAST table node with alignment.
97418
+ * Returns null for header-less tables since MDAST always promotes the first row to <thead>.
97419
+ */
97420
+ const transformTable = (jsx) => {
97421
+ let hasThead = false;
97422
+ visit(jsx, isMDXElement, (child) => {
97423
+ if (child.name === 'thead')
97424
+ hasThead = true;
97425
+ });
97426
+ if (!hasThead)
97427
+ return null;
97428
+ const { align: alignAttr } = getAttrs(jsx);
97429
+ const align = Array.isArray(alignAttr) ? alignAttr : null;
97430
+ const rows = [];
97431
+ visit(jsx, isMDXElement, (child) => {
97432
+ if (child.name !== 'thead' && child.name !== 'tbody')
97433
+ return;
97434
+ visit(child, isMDXElement, (row) => {
97435
+ if (row.name !== 'tr')
97436
+ return;
97437
+ const cells = [];
97438
+ visit(row, mdxish_jsx_to_mdast_isTableCell, (cell) => {
97439
+ const parsedChildren = cell.children.flatMap(parsedNode => {
97440
+ if (parsedNode.type === 'paragraph' && 'children' in parsedNode && parsedNode.children) {
97441
+ return parsedNode.children;
97442
+ }
97443
+ return [parsedNode];
97444
+ });
97445
+ cells.push({
97446
+ type: 'tableCell',
97447
+ children: parsedChildren,
97448
+ position: cell.position,
97449
+ });
97450
+ });
97451
+ rows.push({
97452
+ type: 'tableRow',
97453
+ children: cells,
97454
+ position: row.position,
97455
+ });
97456
+ });
97457
+ });
97458
+ const columnCount = rows[0]?.children?.length || 0;
97459
+ const alignArray = align && columnCount > 0
97460
+ ? align.slice(0, columnCount).concat(Array.from({ length: Math.max(0, columnCount - align.length) }, () => null))
97461
+ : Array.from({ length: columnCount }, () => null);
97462
+ return {
97463
+ type: 'table',
97464
+ align: alignArray,
97465
+ position: jsx.position,
97466
+ children: rows,
97467
+ };
97468
+ };
97340
97469
  const COMPONENT_MAP = {
97341
97470
  Callout: transformCallout,
97342
97471
  Embed: transformEmbed,
97343
97472
  Image: transformImage,
97344
97473
  Recipe: transformRecipe,
97474
+ Table: transformTable,
97345
97475
  };
97346
97476
  /**
97347
97477
  * Transform mdxJsxFlowElement nodes and magic block nodes into proper MDAST node types.
@@ -97364,6 +97494,8 @@ const mdxishJsxToMdast = () => tree => {
97364
97494
  if (!transformer)
97365
97495
  return;
97366
97496
  const newNode = transformer(node);
97497
+ if (!newNode)
97498
+ return;
97367
97499
  // Replace the JSX node with the MDAST node
97368
97500
  parent.children[index] = newNode;
97369
97501
  });
@@ -97644,6 +97776,126 @@ function restoreSnakeCase(placeholderName, mapping) {
97644
97776
  return matchingKey ? mapping[matchingKey] : placeholderName;
97645
97777
  }
97646
97778
 
97779
+ ;// ./processor/transform/mdxish/mdxish-tables-to-jsx.ts
97780
+
97781
+
97782
+ const mdxish_tables_to_jsx_alignToStyle = (align) => {
97783
+ if (!align || align === 'left')
97784
+ return null;
97785
+ return {
97786
+ type: 'mdxJsxAttribute',
97787
+ name: 'style',
97788
+ value: {
97789
+ type: 'mdxJsxAttributeValueExpression',
97790
+ value: `{ textAlign: "${align}" }`,
97791
+ },
97792
+ };
97793
+ };
97794
+ const mdxish_tables_to_jsx_isTableCell = (node) => ['tableHead', 'tableCell'].includes(node.type);
97795
+ const mdxish_tables_to_jsx_isLiteral = (node) => 'value' in node;
97796
+ /**
97797
+ * Mdxish-specific version of `tablesToJsx`. Differs from the shared MDX version:
97798
+ *
97799
+ * - Excludes `html` nodes from triggering JSX conversion because raw HTML
97800
+ * inside JSX `<Table>` breaks remarkMdx parsing on the deserialization roundtrip.
97801
+ * - Skips empty cells instead of aborting the entire visit so that flow content
97802
+ * in later cells is still detected.
97803
+ */
97804
+ const mdxishTablesToJsx = () => tree => {
97805
+ visit(tree, (node) => ['table', 'tableau'].includes(node.type), (table, index, parent) => {
97806
+ let hasFlowContent = false;
97807
+ visit(table, mdxish_tables_to_jsx_isTableCell, (cell) => {
97808
+ if (cell.children.length === 0)
97809
+ return;
97810
+ const content = cell.children.length === 1 && cell.children[0].type === 'paragraph'
97811
+ ? cell.children[0].children[0]
97812
+ : cell.children[0];
97813
+ if (!content)
97814
+ return;
97815
+ visit(cell, 'break', (_, breakIndex, breakParent) => {
97816
+ breakParent.children.splice(breakIndex, 1, { type: 'text', value: '\n' });
97817
+ });
97818
+ if (!(phrasing(content) || content.type === 'plain') && content.type !== 'escape') {
97819
+ if (content.type === 'html')
97820
+ return;
97821
+ hasFlowContent = true;
97822
+ return EXIT;
97823
+ }
97824
+ visit(cell, mdxish_tables_to_jsx_isLiteral, (node) => {
97825
+ if (node.value.match(/\n/)) {
97826
+ hasFlowContent = true;
97827
+ return EXIT;
97828
+ }
97829
+ });
97830
+ });
97831
+ if (!hasFlowContent) {
97832
+ table.type = 'table';
97833
+ return;
97834
+ }
97835
+ const styles = table.align.map(mdxish_tables_to_jsx_alignToStyle);
97836
+ const head = {
97837
+ attributes: [],
97838
+ type: 'mdxJsxFlowElement',
97839
+ name: 'thead',
97840
+ children: [
97841
+ {
97842
+ attributes: [],
97843
+ type: 'mdxJsxFlowElement',
97844
+ name: 'tr',
97845
+ children: table.children[0].children.map((cell, cellIndex) => {
97846
+ return {
97847
+ attributes: [],
97848
+ type: 'mdxJsxFlowElement',
97849
+ name: 'th',
97850
+ children: cell.children,
97851
+ ...(styles[cellIndex] && { attributes: [styles[cellIndex]] }),
97852
+ };
97853
+ }),
97854
+ },
97855
+ ],
97856
+ };
97857
+ const body = {
97858
+ attributes: [],
97859
+ type: 'mdxJsxFlowElement',
97860
+ name: 'tbody',
97861
+ children: table.children.splice(1).map(row => {
97862
+ return {
97863
+ attributes: [],
97864
+ type: 'mdxJsxFlowElement',
97865
+ name: 'tr',
97866
+ children: row.children.map((cell, cellIndex) => {
97867
+ return {
97868
+ type: 'mdxJsxFlowElement',
97869
+ name: 'td',
97870
+ children: cell.children,
97871
+ ...(styles[cellIndex] && { attributes: [styles[cellIndex]] }),
97872
+ };
97873
+ }),
97874
+ };
97875
+ }),
97876
+ };
97877
+ const attributes = [
97878
+ {
97879
+ type: 'mdxJsxAttribute',
97880
+ name: 'align',
97881
+ value: {
97882
+ type: 'mdxJsxAttributeValueExpression',
97883
+ value: JSON.stringify(table.align),
97884
+ },
97885
+ },
97886
+ ];
97887
+ const jsx = {
97888
+ type: 'mdxJsxFlowElement',
97889
+ name: 'Table',
97890
+ attributes: table.align.find(a => a) ? attributes : [],
97891
+ children: [head, body],
97892
+ };
97893
+ parent.children[index] = jsx;
97894
+ });
97895
+ return tree;
97896
+ };
97897
+ /* harmony default export */ const mdxish_tables_to_jsx = (mdxishTablesToJsx);
97898
+
97647
97899
  ;// ./processor/transform/mdxish/normalize-table-separator.ts
97648
97900
  /**
97649
97901
  * Preprocessor to normalize malformed GFM table separator syntax.
@@ -99812,6 +100064,8 @@ function loadComponents() {
99812
100064
 
99813
100065
 
99814
100066
 
100067
+
100068
+
99815
100069
 
99816
100070
 
99817
100071
 
@@ -99909,18 +100163,28 @@ function mdxishAstProcessor(mdContent, opts = {}) {
99909
100163
  };
99910
100164
  }
99911
100165
  /**
99912
- * Converts an Mdast to a Markdown string.
100166
+ * Registers the mdx-jsx serialization extension so remark-stringify
100167
+ * can convert JSX nodes (e.g. `<Table>`) to markdown.
100168
+ */
100169
+ function mdxJsxStringify() {
100170
+ const data = this.data();
100171
+ const extensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
100172
+ extensions.push({ extensions: [mdxJsxToMarkdown()] });
100173
+ }
100174
+ /**
100175
+ * Serializes an Mdast back into a markdown string.
99913
100176
  */
99914
100177
  function mdxishMdastToMd(mdast) {
99915
- const md = unified()
100178
+ const processor = unified()
99916
100179
  .use(remarkGfm)
100180
+ .use(mdxish_tables_to_jsx)
99917
100181
  .use(mdxishCompilers)
100182
+ .use(mdxJsxStringify)
99918
100183
  .use(remarkStringify, {
99919
100184
  bullet: '-',
99920
100185
  emphasis: '_',
99921
- })
99922
- .stringify(mdast);
99923
- return md;
100186
+ });
100187
+ return processor.stringify(processor.runSync(mdast));
99924
100188
  }
99925
100189
  /**
99926
100190
  * Processes markdown content with MDX syntax support and returns a HAST.