@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.node.js CHANGED
@@ -19358,10 +19358,47 @@ var dist_utils_default = /*#__PURE__*/__webpack_require__.n(dist_utils);
19358
19358
 
19359
19359
  let mermaid;
19360
19360
  const { uppercase } = (dist_utils_default());
19361
+ // Module-level queue that batches mermaid nodes across all CodeTabs instances into a
19362
+ // single mermaid.run() call. This is necessary because mermaid generates SVG element IDs
19363
+ // using Date.now(), which collides when multiple diagrams render in the same millisecond.
19364
+ // Colliding IDs cause diagrams to overlap or break layout.
19365
+ //
19366
+ // Why not use `deterministicIDSeed` with a unique ID per diagram? Mermaid's implementation
19367
+ // only uses seed.length (not the seed value) to compute the starting ID, so every UUID
19368
+ // (36 chars) produces the same `mermaid-36` prefix — the collision remains.
19369
+ // See: https://github.com/mermaid-js/mermaid/blob/mermaid%4011.12.0/packages/mermaid/src/utils.ts#L755-L761
19370
+ //
19371
+ // These vars must be module-scoped (not per-instance refs) because the batching requires
19372
+ // cross-instance coordination. They are short-lived: the queue drains on the next macrotask
19373
+ // and cleanup clears everything on unmount.
19374
+ let mermaidQueue = [];
19375
+ let mermaidFlushTimer = null;
19376
+ let currentTheme;
19377
+ function queueMermaidNode(node, theme) {
19378
+ mermaidQueue.push(node);
19379
+ currentTheme = theme;
19380
+ if (!mermaidFlushTimer) {
19381
+ // setTimeout(0) defers to a macrotask, after all useEffects have queued their nodes
19382
+ mermaidFlushTimer = setTimeout(async () => {
19383
+ const nodes = [...mermaidQueue];
19384
+ mermaidQueue = [];
19385
+ mermaidFlushTimer = null;
19386
+ const module = await __webpack_require__.e(/* import() */ 486).then(__webpack_require__.bind(__webpack_require__, 5486));
19387
+ mermaid = module.default;
19388
+ mermaid.initialize({
19389
+ startOnLoad: false,
19390
+ theme: currentTheme === 'dark' ? 'dark' : 'default',
19391
+ deterministicIds: true,
19392
+ });
19393
+ await mermaid.run({ nodes });
19394
+ }, 0);
19395
+ }
19396
+ }
19361
19397
  const CodeTabs = (props) => {
19362
19398
  const { children } = props;
19363
19399
  const theme = (0,external_react_.useContext)(Theme);
19364
19400
  const isHydrated = useHydrated();
19401
+ const mermaidRef = (0,external_react_.useRef)(null);
19365
19402
  // Handle both array (from rehype-react in rendering mdxish) and single element (MDX/JSX runtime) cases
19366
19403
  // The children here is the individual code block objects
19367
19404
  const childrenArray = Array.isArray(children) ? children : [children];
@@ -19372,22 +19409,20 @@ const CodeTabs = (props) => {
19372
19409
  return Array.isArray(pre?.props?.children) ? pre.props.children[0] : pre?.props?.children;
19373
19410
  };
19374
19411
  const containAtLeastOneMermaid = childrenArray.some(pre => getCodeComponent(pre)?.props?.lang === 'mermaid');
19375
- // Render Mermaid diagram
19376
19412
  (0,external_react_.useEffect)(() => {
19377
- // Ensure we only render mermaids when frontend is hydrated to avoid hydration errors
19378
- // because mermaid mutates the DOM before react hydrates
19379
- if (typeof window !== 'undefined' && containAtLeastOneMermaid && isHydrated) {
19380
- __webpack_require__.e(/* import() */ 486).then(__webpack_require__.bind(__webpack_require__, 5486)).then(module => {
19381
- mermaid = module.default;
19382
- mermaid.initialize({
19383
- startOnLoad: false,
19384
- theme: theme === 'dark' ? 'dark' : 'default',
19385
- });
19386
- mermaid.run({
19387
- nodes: document.querySelectorAll('.mermaid-render'),
19388
- });
19389
- });
19390
- }
19413
+ // Wait for hydration so mermaid's DOM mutations don't cause mismatches
19414
+ if (typeof window !== 'undefined' && containAtLeastOneMermaid && isHydrated && mermaidRef.current) {
19415
+ queueMermaidNode(mermaidRef.current, theme);
19416
+ }
19417
+ return () => {
19418
+ // Clear the batch timer on unmount to prevent mermaid from running
19419
+ // after the DOM is torn down
19420
+ if (mermaidFlushTimer) {
19421
+ clearTimeout(mermaidFlushTimer);
19422
+ mermaidFlushTimer = null;
19423
+ mermaidQueue = [];
19424
+ }
19425
+ };
19391
19426
  }, [containAtLeastOneMermaid, theme, isHydrated]);
19392
19427
  function handleClick({ target }, index) {
19393
19428
  const $wrap = target.parentElement.parentElement;
@@ -19403,15 +19438,13 @@ const CodeTabs = (props) => {
19403
19438
  const codeComponent = getCodeComponent(childrenArray[0]);
19404
19439
  if (codeComponent?.props?.lang === 'mermaid') {
19405
19440
  const value = codeComponent?.props?.value;
19406
- return external_react_default().createElement("pre", { className: "mermaid-render mermaid_single" }, value);
19441
+ return (external_react_default().createElement("pre", { ref: mermaidRef, className: "mermaid-render mermaid_single" }, value));
19407
19442
  }
19408
19443
  }
19409
19444
  return (external_react_default().createElement("div", { className: `CodeTabs CodeTabs_initial theme-${theme}` },
19410
19445
  external_react_default().createElement("div", { className: "CodeTabs-toolbar" }, childrenArray.map((pre, i) => {
19411
19446
  // the first or only child should be our Code component
19412
- const tabCodeComponent = Array.isArray(pre.props?.children)
19413
- ? pre.props.children[0]
19414
- : pre.props?.children;
19447
+ const tabCodeComponent = Array.isArray(pre.props?.children) ? pre.props.children[0] : pre.props?.children;
19415
19448
  const lang = tabCodeComponent?.props?.lang;
19416
19449
  const meta = tabCodeComponent?.props?.meta;
19417
19450
  /* istanbul ignore next */
@@ -24905,10 +24938,19 @@ const TailwindStyle = ({ children, darkModeDataAttribute }) => {
24905
24938
  records.forEach(record => {
24906
24939
  if (record.type === 'childList') {
24907
24940
  record.addedNodes.forEach(node => {
24908
- if (!(node instanceof HTMLElement) || !node.classList.contains(tailwindPrefix))
24941
+ if (!(node instanceof HTMLElement))
24909
24942
  return;
24910
- traverse(node, addClasses);
24911
- shouldUpdate = true;
24943
+ // Check the added node itself
24944
+ if (node.classList.contains(tailwindPrefix)) {
24945
+ traverse(node, addClasses);
24946
+ shouldUpdate = true;
24947
+ }
24948
+ // Also check descendants — React may insert a parent node
24949
+ // whose children contain TailwindRoot elements
24950
+ node.querySelectorAll(`.${tailwindPrefix}`).forEach(child => {
24951
+ traverse(child, addClasses);
24952
+ shouldUpdate = true;
24953
+ });
24912
24954
  });
24913
24955
  }
24914
24956
  else if (record.type === 'attributes') {
@@ -24917,10 +24959,10 @@ const TailwindStyle = ({ children, darkModeDataAttribute }) => {
24917
24959
  addClasses(record.target);
24918
24960
  shouldUpdate = true;
24919
24961
  }
24920
- if (shouldUpdate) {
24921
- setClasses(Array.from(classesSet.current));
24922
- }
24923
24962
  });
24963
+ if (shouldUpdate) {
24964
+ setClasses(Array.from(classesSet.current));
24965
+ }
24924
24966
  });
24925
24967
  observer.observe(ref.current.parentElement, {
24926
24968
  subtree: true,
@@ -90604,9 +90646,416 @@ const mdast = (text, opts = {}) => {
90604
90646
  };
90605
90647
  /* harmony default export */ const lib_mdast = (mdast);
90606
90648
 
90649
+ ;// ./lib/utils/mdxish/protect-code-blocks.ts
90650
+ /**
90651
+ * Replaces code blocks and inline code with placeholders to protect them from preprocessing.
90652
+ *
90653
+ * @param content - The markdown content to process
90654
+ * @returns Object containing protected content and arrays of original code blocks
90655
+ * @example
90656
+ * ```typescript
90657
+ * const input = 'Text with `inline code` and ```fenced block```';
90658
+ * protectCodeBlocks(input)
90659
+ * // Returns: {
90660
+ * // protectedCode: {
90661
+ * // codeBlocks: ['```fenced block```'],
90662
+ * // inlineCode: ['`inline code`']
90663
+ * // },
90664
+ * // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
90665
+ * // }
90666
+ * ```
90667
+ */
90668
+ function protectCodeBlocks(content) {
90669
+ const codeBlocks = [];
90670
+ const inlineCode = [];
90671
+ let protectedContent = '';
90672
+ let remaining = content;
90673
+ let codeBlockStart = remaining.indexOf('```');
90674
+ while (codeBlockStart !== -1) {
90675
+ protectedContent += remaining.slice(0, codeBlockStart);
90676
+ remaining = remaining.slice(codeBlockStart);
90677
+ const codeBlockEnd = remaining.indexOf('```', 3);
90678
+ if (codeBlockEnd === -1) {
90679
+ break;
90680
+ }
90681
+ const match = remaining.slice(0, codeBlockEnd + 3);
90682
+ const index = codeBlocks.length;
90683
+ codeBlocks.push(match);
90684
+ protectedContent += `___CODE_BLOCK_${index}___`;
90685
+ remaining = remaining.slice(codeBlockEnd + 3);
90686
+ codeBlockStart = remaining.indexOf('```');
90687
+ }
90688
+ protectedContent += remaining;
90689
+ protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
90690
+ const index = inlineCode.length;
90691
+ inlineCode.push(match);
90692
+ return `___INLINE_CODE_${index}___`;
90693
+ });
90694
+ return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
90695
+ }
90696
+ /**
90697
+ * Restores inline code by replacing placeholders with original content.
90698
+ *
90699
+ * @param content - Content with inline code placeholders
90700
+ * @param protectedCode - The protected code arrays
90701
+ * @returns Content with inline code restored
90702
+ */
90703
+ function restoreInlineCode(content, protectedCode) {
90704
+ return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
90705
+ return protectedCode.inlineCode[parseInt(idx, 10)];
90706
+ });
90707
+ }
90708
+ /**
90709
+ * Restores fenced code blocks by replacing placeholders with original content.
90710
+ *
90711
+ * @param content - Content with code block placeholders
90712
+ * @param protectedCode - The protected code arrays
90713
+ * @returns Content with code blocks restored
90714
+ */
90715
+ function restoreFencedCodeBlocks(content, protectedCode) {
90716
+ return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
90717
+ return protectedCode.codeBlocks[parseInt(idx, 10)];
90718
+ });
90719
+ }
90720
+ /**
90721
+ * Restores all code blocks and inline code by replacing placeholders with original content.
90722
+ *
90723
+ * @param content - Content with code placeholders
90724
+ * @param protectedCode - The protected code arrays
90725
+ * @returns Content with all code blocks and inline code restored
90726
+ * @example
90727
+ * ```typescript
90728
+ * const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
90729
+ * const protectedCode = {
90730
+ * codeBlocks: ['```js\ncode\n```'],
90731
+ * inlineCode: ['`inline`']
90732
+ * };
90733
+ * restoreCodeBlocks(content, protectedCode)
90734
+ * // Returns: 'Text with `inline` and ```js\ncode\n```'
90735
+ * ```
90736
+ */
90737
+ function restoreCodeBlocks(content, protectedCode) {
90738
+ let restored = restoreFencedCodeBlocks(content, protectedCode);
90739
+ restored = restoreInlineCode(restored, protectedCode);
90740
+ return restored;
90741
+ }
90742
+
90743
+ ;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
90744
+
90745
+ // Base64 encode (Node.js + browser compatible)
90746
+ function base64Encode(str) {
90747
+ if (typeof Buffer !== 'undefined') {
90748
+ return Buffer.from(str, 'utf-8').toString('base64');
90749
+ }
90750
+ return btoa(unescape(encodeURIComponent(str)));
90751
+ }
90752
+ // Base64 decode (Node.js + browser compatible)
90753
+ function base64Decode(str) {
90754
+ if (typeof Buffer !== 'undefined') {
90755
+ return Buffer.from(str, 'base64').toString('utf-8');
90756
+ }
90757
+ return decodeURIComponent(escape(atob(str)));
90758
+ }
90759
+ function escapeHtmlAttribute(value) {
90760
+ return value
90761
+ .replace(/&/g, '&')
90762
+ .replace(/"/g, '"')
90763
+ .replace(/</g, '&lt;')
90764
+ .replace(/>/g, '&gt;')
90765
+ .replace(/\n/g, '&#10;');
90766
+ }
90767
+ // Marker prefix for JSON-serialized complex values (arrays/objects)
90768
+ // Using a prefix that won't conflict with regular string values
90769
+ const JSON_VALUE_MARKER = '__MDXISH_JSON__';
90770
+ // Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
90771
+ const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
90772
+ const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
90773
+ /**
90774
+ * Evaluates a JavaScript expression using context variables.
90775
+ *
90776
+ * @param expression
90777
+ * @param context
90778
+ * @returns The evaluated result
90779
+ * @example
90780
+ * ```typescript
90781
+ * const context = { baseUrl: 'https://example.com', path: '/api' };
90782
+ * evaluateExpression('baseUrl + path', context)
90783
+ * // Returns: 'https://example.com/api'
90784
+ * ```
90785
+ */
90786
+ function evaluateExpression(expression, context) {
90787
+ const contextKeys = Object.keys(context);
90788
+ const contextValues = Object.values(context);
90789
+ // eslint-disable-next-line no-new-func
90790
+ const func = new Function(...contextKeys, `return ${expression}`);
90791
+ return func(...contextValues);
90792
+ }
90793
+ /**
90794
+ * Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
90795
+ *
90796
+ * @param content
90797
+ * @returns Content with HTMLBlock template literals base64 encoded in HTML comments
90798
+ * @example
90799
+ * ```typescript
90800
+ * const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
90801
+ * protectHTMLBlockContent(input)
90802
+ * // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
90803
+ * ```
90804
+ */
90805
+ function protectHTMLBlockContent(content) {
90806
+ return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
90807
+ const encoded = base64Encode(templateContent);
90808
+ return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
90809
+ });
90810
+ }
90811
+ /**
90812
+ * Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
90813
+ *
90814
+ * @param content
90815
+ * @returns Content with JSX comments removed
90816
+ * @example
90817
+ * ```typescript
90818
+ * removeJSXComments('Text { /* comment *\/ } more text')
90819
+ * // Returns: 'Text more text'
90820
+ * ```
90821
+ */
90822
+ function removeJSXComments(content) {
90823
+ return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
90824
+ }
90825
+ /**
90826
+ * Extracts content between balanced braces, handling nested braces.
90827
+ *
90828
+ * @param content
90829
+ * @param start
90830
+ * @returns Object with extracted content and end position, or null if braces are unbalanced
90831
+ * @example
90832
+ * ```typescript
90833
+ * const input = 'foo{bar{baz}qux}end';
90834
+ * extractBalancedBraces(input, 3) // start at position 3 (after '{')
90835
+ * // Returns: { content: 'bar{baz}qux', end: 16 }
90836
+ * ```
90837
+ */
90838
+ function extractBalancedBraces(content, start) {
90839
+ let depth = 1;
90840
+ let pos = start;
90841
+ while (pos < content.length && depth > 0) {
90842
+ const char = content[pos];
90843
+ if (char === '{')
90844
+ depth += 1;
90845
+ else if (char === '}')
90846
+ depth -= 1;
90847
+ pos += 1;
90848
+ }
90849
+ if (depth !== 0)
90850
+ return null;
90851
+ return { content: content.slice(start, pos - 1), end: pos };
90852
+ }
90853
+ /**
90854
+ * Escapes problematic braces in content to prevent MDX expression parsing errors.
90855
+ * Handles three cases:
90856
+ * 1. Unbalanced braces (e.g., `{foo` without closing `}`)
90857
+ * 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
90858
+ * 3. Skips HTML elements to prevent backslashes appearing in output
90859
+ *
90860
+ */
90861
+ function escapeProblematicBraces(content) {
90862
+ // Skip HTML elements — their content should never be escaped because
90863
+ // rehypeRaw parses them into hast elements, making `\` literal text in output
90864
+ const htmlElements = [];
90865
+ const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
90866
+ const idx = htmlElements.length;
90867
+ htmlElements.push(match);
90868
+ return `___HTML_ELEM_${idx}___`;
90869
+ });
90870
+ const toEscape = new Set();
90871
+ // Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
90872
+ const chars = Array.from(safe);
90873
+ let strDelim = null;
90874
+ let strEscaped = false;
90875
+ // Stack of open braces with their state
90876
+ const openStack = [];
90877
+ // Track position of last newline (outside strings) to detect blank lines
90878
+ let lastNewlinePos = -2; // -2 means no recent newline
90879
+ for (let i = 0; i < chars.length; i += 1) {
90880
+ const ch = chars[i];
90881
+ // Track string delimiters inside expressions to ignore braces within them
90882
+ if (openStack.length > 0) {
90883
+ if (strDelim) {
90884
+ if (strEscaped)
90885
+ strEscaped = false;
90886
+ else if (ch === '\\')
90887
+ strEscaped = true;
90888
+ else if (ch === strDelim)
90889
+ strDelim = null;
90890
+ // eslint-disable-next-line no-continue
90891
+ continue;
90892
+ }
90893
+ if (ch === '"' || ch === "'" || ch === '`') {
90894
+ strDelim = ch;
90895
+ // eslint-disable-next-line no-continue
90896
+ continue;
90897
+ }
90898
+ // Track newlines to detect blank lines (paragraph boundaries)
90899
+ if (ch === '\n') {
90900
+ // Check if this newline creates a blank line (only whitespace since last newline)
90901
+ if (lastNewlinePos >= 0) {
90902
+ const between = chars.slice(lastNewlinePos + 1, i).join('');
90903
+ if (/^[ \t]*$/.test(between)) {
90904
+ // This is a blank line - mark all open expressions as paragraph-spanning
90905
+ openStack.forEach(entry => {
90906
+ entry.hasBlankLine = true;
90907
+ });
90908
+ }
90909
+ }
90910
+ lastNewlinePos = i;
90911
+ }
90912
+ }
90913
+ // Skip already-escaped braces (count preceding backslashes)
90914
+ if (ch === '{' || ch === '}') {
90915
+ let bs = 0;
90916
+ for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
90917
+ bs += 1;
90918
+ if (bs % 2 === 1) {
90919
+ // eslint-disable-next-line no-continue
90920
+ continue;
90921
+ }
90922
+ }
90923
+ if (ch === '{') {
90924
+ openStack.push({ pos: i, hasBlankLine: false });
90925
+ lastNewlinePos = -2; // Reset newline tracking for new expression
90926
+ }
90927
+ else if (ch === '}') {
90928
+ if (openStack.length > 0) {
90929
+ const entry = openStack.pop();
90930
+ // If expression spans paragraph boundary, escape both braces
90931
+ if (entry.hasBlankLine) {
90932
+ toEscape.add(entry.pos);
90933
+ toEscape.add(i);
90934
+ }
90935
+ }
90936
+ else {
90937
+ // Unbalanced closing brace (no matching open)
90938
+ toEscape.add(i);
90939
+ }
90940
+ }
90941
+ }
90942
+ // Any remaining open braces are unbalanced
90943
+ openStack.forEach(entry => toEscape.add(entry.pos));
90944
+ // If there are no problematic braces, return safe content as-is;
90945
+ // otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
90946
+ let result = toEscape.size === 0
90947
+ ? safe
90948
+ : chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
90949
+ // Restore HTML elements
90950
+ if (htmlElements.length > 0) {
90951
+ result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
90952
+ }
90953
+ return result;
90954
+ }
90955
+ /**
90956
+ * Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
90957
+ * Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
90958
+ *
90959
+ * @param content
90960
+ * @param context
90961
+ * @returns Content with attribute expressions evaluated and converted to HTML attributes
90962
+ * @example
90963
+ * ```typescript
90964
+ * const context = { baseUrl: 'https://example.com' };
90965
+ * const input = '<a href={baseUrl}>Link</a>';
90966
+ * evaluateAttributeExpressions(input, context)
90967
+ * // Returns: '<a href="https://example.com">Link</a>'
90968
+ * ```
90969
+ */
90970
+ function evaluateAttributeExpressions(content, context, protectedCode) {
90971
+ const attrStartRegex = /(\w+)=\{/g;
90972
+ let result = '';
90973
+ let lastEnd = 0;
90974
+ let match = attrStartRegex.exec(content);
90975
+ while (match !== null) {
90976
+ const attributeName = match[1];
90977
+ const braceStart = match.index + match[0].length;
90978
+ const extracted = extractBalancedBraces(content, braceStart);
90979
+ if (extracted) {
90980
+ // The expression might contain template literals in MDX component tag props
90981
+ // E.g. <Component greeting={`Hello World!`} />
90982
+ // that is marked as inline code. So we need to restore the inline codes
90983
+ // in the expression to evaluate it
90984
+ let expression = extracted.content;
90985
+ if (protectedCode) {
90986
+ expression = restoreInlineCode(expression, protectedCode);
90987
+ }
90988
+ const fullMatchEnd = extracted.end;
90989
+ result += content.slice(lastEnd, match.index);
90990
+ try {
90991
+ const evalResult = evaluateExpression(expression, context);
90992
+ if (typeof evalResult === 'object' && evalResult !== null) {
90993
+ if (attributeName === 'style') {
90994
+ const cssString = Object.entries(evalResult)
90995
+ .map(([key, value]) => {
90996
+ const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
90997
+ return `${cssKey}: ${value}`;
90998
+ })
90999
+ .join('; ');
91000
+ result += `style="${cssString}"`;
91001
+ }
91002
+ else {
91003
+ // These are arrays / objects attribute values
91004
+ // Mark JSON-serialized values with a prefix so they can be parsed back correctly
91005
+ const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
91006
+ // Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
91007
+ result += `${attributeName}="${jsonValue}"`;
91008
+ }
91009
+ }
91010
+ else if (attributeName === 'className') {
91011
+ // Escape special characters so that it doesn't break and split the attribute value to nodes
91012
+ // This will be restored later in the pipeline
91013
+ result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
91014
+ }
91015
+ else {
91016
+ result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
91017
+ }
91018
+ }
91019
+ catch (_error) {
91020
+ result += content.slice(match.index, fullMatchEnd);
91021
+ }
91022
+ lastEnd = fullMatchEnd;
91023
+ attrStartRegex.lastIndex = fullMatchEnd;
91024
+ }
91025
+ match = attrStartRegex.exec(content);
91026
+ }
91027
+ result += content.slice(lastEnd);
91028
+ return result;
91029
+ }
91030
+ /**
91031
+ * Preprocesses JSX-like expressions in markdown before parsing.
91032
+ * Inline expressions are handled separately; attribute expressions are processed here.
91033
+ *
91034
+ * @param content
91035
+ * @param context
91036
+ * @returns Preprocessed content ready for markdown parsing
91037
+ */
91038
+ function preprocessJSXExpressions(content, context = {}) {
91039
+ // Step 0: Base64 encode HTMLBlock content
91040
+ let processed = protectHTMLBlockContent(content);
91041
+ // Step 1: Protect code blocks and inline code
91042
+ const { protectedCode, protectedContent } = protectCodeBlocks(processed);
91043
+ // Step 2: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
91044
+ // For inline expressions, we use a library to parse the expression & evaluate it later
91045
+ // For attribute expressions, it was difficult to use a library to parse them, so do it manually
91046
+ processed = evaluateAttributeExpressions(protectedContent, context, protectedCode);
91047
+ // Step 3: Escape problematic braces to prevent MDX expression parsing errors
91048
+ // This handles both unbalanced braces and paragraph-spanning expressions in one pass
91049
+ processed = escapeProblematicBraces(processed);
91050
+ // Step 4: Restore protected code blocks
91051
+ processed = restoreCodeBlocks(processed, protectedCode);
91052
+ return processed;
91053
+ }
91054
+
90607
91055
  ;// ./processor/utils.ts
90608
91056
 
90609
91057
 
91058
+
90610
91059
  /**
90611
91060
  * Formats the hProperties of a node as a string, so they can be compiled back into JSX/MDX.
90612
91061
  * This currently sets all the values to a string since we process/compile the MDX on the fly
@@ -90662,7 +91111,17 @@ const getHPropKeys = (node) => {
90662
91111
  const getAttrs = (jsx) => jsx.attributes.reduce((memo, attr) => {
90663
91112
  if ('name' in attr) {
90664
91113
  if (typeof attr.value === 'string') {
90665
- memo[attr.name] = attr.value;
91114
+ if (attr.value.startsWith(JSON_VALUE_MARKER)) {
91115
+ try {
91116
+ memo[attr.name] = JSON.parse(attr.value.slice(JSON_VALUE_MARKER.length));
91117
+ }
91118
+ catch {
91119
+ memo[attr.name] = attr.value;
91120
+ }
91121
+ }
91122
+ else {
91123
+ memo[attr.name] = attr.value;
91124
+ }
90666
91125
  }
90667
91126
  else if (attr.value === null) {
90668
91127
  memo[attr.name] = true;
@@ -91436,10 +91895,10 @@ const hasFlowContent = (nodes) => {
91436
91895
  * Process a Table node: re-parse text-only cell content, then output as
91437
91896
  * a markdown table (phrasing-only) or keep as JSX <Table> (has flow content).
91438
91897
  */
91439
- const processTableNode = (node, index, parent) => {
91898
+ const processTableNode = (node, index, parent, documentPosition) => {
91440
91899
  if (node.name !== 'Table')
91441
91900
  return;
91442
- const { position } = node;
91901
+ const position = documentPosition ?? node.position;
91443
91902
  const { align: alignAttr } = getAttrs(node);
91444
91903
  const align = Array.isArray(alignAttr) ? alignAttr : null;
91445
91904
  let tableHasFlowContent = false;
@@ -91562,7 +92021,7 @@ const mdxishTables = () => tree => {
91562
92021
  const parsed = tableNodeProcessor.runSync(tableNodeProcessor.parse(node.value));
91563
92022
  visit(parsed, isMDXElement, (tableNode) => {
91564
92023
  if (tableNode.name === 'Table') {
91565
- processTableNode(tableNode, index, parent);
92024
+ processTableNode(tableNode, index, parent, node.position);
91566
92025
  // Stop after the outermost Table so nested Tables don't overwrite parent.children[index]
91567
92026
  // we let it get handled naturally
91568
92027
  return EXIT;
@@ -114037,412 +114496,6 @@ const mdxComponentHandlers = {
114037
114496
  [NodeTypes.htmlBlock]: htmlBlockHandler,
114038
114497
  };
114039
114498
 
114040
- ;// ./lib/utils/mdxish/protect-code-blocks.ts
114041
- /**
114042
- * Replaces code blocks and inline code with placeholders to protect them from preprocessing.
114043
- *
114044
- * @param content - The markdown content to process
114045
- * @returns Object containing protected content and arrays of original code blocks
114046
- * @example
114047
- * ```typescript
114048
- * const input = 'Text with `inline code` and ```fenced block```';
114049
- * protectCodeBlocks(input)
114050
- * // Returns: {
114051
- * // protectedCode: {
114052
- * // codeBlocks: ['```fenced block```'],
114053
- * // inlineCode: ['`inline code`']
114054
- * // },
114055
- * // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
114056
- * // }
114057
- * ```
114058
- */
114059
- function protectCodeBlocks(content) {
114060
- const codeBlocks = [];
114061
- const inlineCode = [];
114062
- let protectedContent = '';
114063
- let remaining = content;
114064
- let codeBlockStart = remaining.indexOf('```');
114065
- while (codeBlockStart !== -1) {
114066
- protectedContent += remaining.slice(0, codeBlockStart);
114067
- remaining = remaining.slice(codeBlockStart);
114068
- const codeBlockEnd = remaining.indexOf('```', 3);
114069
- if (codeBlockEnd === -1) {
114070
- break;
114071
- }
114072
- const match = remaining.slice(0, codeBlockEnd + 3);
114073
- const index = codeBlocks.length;
114074
- codeBlocks.push(match);
114075
- protectedContent += `___CODE_BLOCK_${index}___`;
114076
- remaining = remaining.slice(codeBlockEnd + 3);
114077
- codeBlockStart = remaining.indexOf('```');
114078
- }
114079
- protectedContent += remaining;
114080
- protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
114081
- const index = inlineCode.length;
114082
- inlineCode.push(match);
114083
- return `___INLINE_CODE_${index}___`;
114084
- });
114085
- return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
114086
- }
114087
- /**
114088
- * Restores inline code by replacing placeholders with original content.
114089
- *
114090
- * @param content - Content with inline code placeholders
114091
- * @param protectedCode - The protected code arrays
114092
- * @returns Content with inline code restored
114093
- */
114094
- function restoreInlineCode(content, protectedCode) {
114095
- return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
114096
- return protectedCode.inlineCode[parseInt(idx, 10)];
114097
- });
114098
- }
114099
- /**
114100
- * Restores fenced code blocks by replacing placeholders with original content.
114101
- *
114102
- * @param content - Content with code block placeholders
114103
- * @param protectedCode - The protected code arrays
114104
- * @returns Content with code blocks restored
114105
- */
114106
- function restoreFencedCodeBlocks(content, protectedCode) {
114107
- return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
114108
- return protectedCode.codeBlocks[parseInt(idx, 10)];
114109
- });
114110
- }
114111
- /**
114112
- * Restores all code blocks and inline code by replacing placeholders with original content.
114113
- *
114114
- * @param content - Content with code placeholders
114115
- * @param protectedCode - The protected code arrays
114116
- * @returns Content with all code blocks and inline code restored
114117
- * @example
114118
- * ```typescript
114119
- * const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
114120
- * const protectedCode = {
114121
- * codeBlocks: ['```js\ncode\n```'],
114122
- * inlineCode: ['`inline`']
114123
- * };
114124
- * restoreCodeBlocks(content, protectedCode)
114125
- * // Returns: 'Text with `inline` and ```js\ncode\n```'
114126
- * ```
114127
- */
114128
- function restoreCodeBlocks(content, protectedCode) {
114129
- let restored = restoreFencedCodeBlocks(content, protectedCode);
114130
- restored = restoreInlineCode(restored, protectedCode);
114131
- return restored;
114132
- }
114133
-
114134
- ;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
114135
-
114136
- // Base64 encode (Node.js + browser compatible)
114137
- function base64Encode(str) {
114138
- if (typeof Buffer !== 'undefined') {
114139
- return Buffer.from(str, 'utf-8').toString('base64');
114140
- }
114141
- return btoa(unescape(encodeURIComponent(str)));
114142
- }
114143
- // Base64 decode (Node.js + browser compatible)
114144
- function base64Decode(str) {
114145
- if (typeof Buffer !== 'undefined') {
114146
- return Buffer.from(str, 'base64').toString('utf-8');
114147
- }
114148
- return decodeURIComponent(escape(atob(str)));
114149
- }
114150
- function escapeHtmlAttribute(value) {
114151
- return value
114152
- .replace(/&/g, '&amp;')
114153
- .replace(/"/g, '&quot;')
114154
- .replace(/</g, '&lt;')
114155
- .replace(/>/g, '&gt;')
114156
- .replace(/\n/g, '&#10;');
114157
- }
114158
- // Marker prefix for JSON-serialized complex values (arrays/objects)
114159
- // Using a prefix that won't conflict with regular string values
114160
- const JSON_VALUE_MARKER = '__MDXISH_JSON__';
114161
- // Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
114162
- const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
114163
- const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
114164
- /**
114165
- * Evaluates a JavaScript expression using context variables.
114166
- *
114167
- * @param expression
114168
- * @param context
114169
- * @returns The evaluated result
114170
- * @example
114171
- * ```typescript
114172
- * const context = { baseUrl: 'https://example.com', path: '/api' };
114173
- * evaluateExpression('baseUrl + path', context)
114174
- * // Returns: 'https://example.com/api'
114175
- * ```
114176
- */
114177
- function evaluateExpression(expression, context) {
114178
- const contextKeys = Object.keys(context);
114179
- const contextValues = Object.values(context);
114180
- // eslint-disable-next-line no-new-func
114181
- const func = new Function(...contextKeys, `return ${expression}`);
114182
- return func(...contextValues);
114183
- }
114184
- /**
114185
- * Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
114186
- *
114187
- * @param content
114188
- * @returns Content with HTMLBlock template literals base64 encoded in HTML comments
114189
- * @example
114190
- * ```typescript
114191
- * const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
114192
- * protectHTMLBlockContent(input)
114193
- * // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
114194
- * ```
114195
- */
114196
- function protectHTMLBlockContent(content) {
114197
- return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
114198
- const encoded = base64Encode(templateContent);
114199
- return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
114200
- });
114201
- }
114202
- /**
114203
- * Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
114204
- *
114205
- * @param content
114206
- * @returns Content with JSX comments removed
114207
- * @example
114208
- * ```typescript
114209
- * removeJSXComments('Text { /* comment *\/ } more text')
114210
- * // Returns: 'Text more text'
114211
- * ```
114212
- */
114213
- function removeJSXComments(content) {
114214
- return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
114215
- }
114216
- /**
114217
- * Extracts content between balanced braces, handling nested braces.
114218
- *
114219
- * @param content
114220
- * @param start
114221
- * @returns Object with extracted content and end position, or null if braces are unbalanced
114222
- * @example
114223
- * ```typescript
114224
- * const input = 'foo{bar{baz}qux}end';
114225
- * extractBalancedBraces(input, 3) // start at position 3 (after '{')
114226
- * // Returns: { content: 'bar{baz}qux', end: 16 }
114227
- * ```
114228
- */
114229
- function extractBalancedBraces(content, start) {
114230
- let depth = 1;
114231
- let pos = start;
114232
- while (pos < content.length && depth > 0) {
114233
- const char = content[pos];
114234
- if (char === '{')
114235
- depth += 1;
114236
- else if (char === '}')
114237
- depth -= 1;
114238
- pos += 1;
114239
- }
114240
- if (depth !== 0)
114241
- return null;
114242
- return { content: content.slice(start, pos - 1), end: pos };
114243
- }
114244
- /**
114245
- * Escapes problematic braces in content to prevent MDX expression parsing errors.
114246
- * Handles three cases:
114247
- * 1. Unbalanced braces (e.g., `{foo` without closing `}`)
114248
- * 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
114249
- * 3. Skips HTML elements to prevent backslashes appearing in output
114250
- *
114251
- */
114252
- function escapeProblematicBraces(content) {
114253
- // Skip HTML elements — their content should never be escaped because
114254
- // rehypeRaw parses them into hast elements, making `\` literal text in output
114255
- const htmlElements = [];
114256
- const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
114257
- const idx = htmlElements.length;
114258
- htmlElements.push(match);
114259
- return `___HTML_ELEM_${idx}___`;
114260
- });
114261
- const toEscape = new Set();
114262
- // Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
114263
- const chars = Array.from(safe);
114264
- let strDelim = null;
114265
- let strEscaped = false;
114266
- // Stack of open braces with their state
114267
- const openStack = [];
114268
- // Track position of last newline (outside strings) to detect blank lines
114269
- let lastNewlinePos = -2; // -2 means no recent newline
114270
- for (let i = 0; i < chars.length; i += 1) {
114271
- const ch = chars[i];
114272
- // Track string delimiters inside expressions to ignore braces within them
114273
- if (openStack.length > 0) {
114274
- if (strDelim) {
114275
- if (strEscaped)
114276
- strEscaped = false;
114277
- else if (ch === '\\')
114278
- strEscaped = true;
114279
- else if (ch === strDelim)
114280
- strDelim = null;
114281
- // eslint-disable-next-line no-continue
114282
- continue;
114283
- }
114284
- if (ch === '"' || ch === "'" || ch === '`') {
114285
- strDelim = ch;
114286
- // eslint-disable-next-line no-continue
114287
- continue;
114288
- }
114289
- // Track newlines to detect blank lines (paragraph boundaries)
114290
- if (ch === '\n') {
114291
- // Check if this newline creates a blank line (only whitespace since last newline)
114292
- if (lastNewlinePos >= 0) {
114293
- const between = chars.slice(lastNewlinePos + 1, i).join('');
114294
- if (/^[ \t]*$/.test(between)) {
114295
- // This is a blank line - mark all open expressions as paragraph-spanning
114296
- openStack.forEach(entry => {
114297
- entry.hasBlankLine = true;
114298
- });
114299
- }
114300
- }
114301
- lastNewlinePos = i;
114302
- }
114303
- }
114304
- // Skip already-escaped braces (count preceding backslashes)
114305
- if (ch === '{' || ch === '}') {
114306
- let bs = 0;
114307
- for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
114308
- bs += 1;
114309
- if (bs % 2 === 1) {
114310
- // eslint-disable-next-line no-continue
114311
- continue;
114312
- }
114313
- }
114314
- if (ch === '{') {
114315
- openStack.push({ pos: i, hasBlankLine: false });
114316
- lastNewlinePos = -2; // Reset newline tracking for new expression
114317
- }
114318
- else if (ch === '}') {
114319
- if (openStack.length > 0) {
114320
- const entry = openStack.pop();
114321
- // If expression spans paragraph boundary, escape both braces
114322
- if (entry.hasBlankLine) {
114323
- toEscape.add(entry.pos);
114324
- toEscape.add(i);
114325
- }
114326
- }
114327
- else {
114328
- // Unbalanced closing brace (no matching open)
114329
- toEscape.add(i);
114330
- }
114331
- }
114332
- }
114333
- // Any remaining open braces are unbalanced
114334
- openStack.forEach(entry => toEscape.add(entry.pos));
114335
- // If there are no problematic braces, return safe content as-is;
114336
- // otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
114337
- let result = toEscape.size === 0
114338
- ? safe
114339
- : chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
114340
- // Restore HTML elements
114341
- if (htmlElements.length > 0) {
114342
- result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
114343
- }
114344
- return result;
114345
- }
114346
- /**
114347
- * Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
114348
- * Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
114349
- *
114350
- * @param content
114351
- * @param context
114352
- * @returns Content with attribute expressions evaluated and converted to HTML attributes
114353
- * @example
114354
- * ```typescript
114355
- * const context = { baseUrl: 'https://example.com' };
114356
- * const input = '<a href={baseUrl}>Link</a>';
114357
- * evaluateAttributeExpressions(input, context)
114358
- * // Returns: '<a href="https://example.com">Link</a>'
114359
- * ```
114360
- */
114361
- function evaluateAttributeExpressions(content, context, protectedCode) {
114362
- const attrStartRegex = /(\w+)=\{/g;
114363
- let result = '';
114364
- let lastEnd = 0;
114365
- let match = attrStartRegex.exec(content);
114366
- while (match !== null) {
114367
- const attributeName = match[1];
114368
- const braceStart = match.index + match[0].length;
114369
- const extracted = extractBalancedBraces(content, braceStart);
114370
- if (extracted) {
114371
- // The expression might contain template literals in MDX component tag props
114372
- // E.g. <Component greeting={`Hello World!`} />
114373
- // that is marked as inline code. So we need to restore the inline codes
114374
- // in the expression to evaluate it
114375
- let expression = extracted.content;
114376
- if (protectedCode) {
114377
- expression = restoreInlineCode(expression, protectedCode);
114378
- }
114379
- const fullMatchEnd = extracted.end;
114380
- result += content.slice(lastEnd, match.index);
114381
- try {
114382
- const evalResult = evaluateExpression(expression, context);
114383
- if (typeof evalResult === 'object' && evalResult !== null) {
114384
- if (attributeName === 'style') {
114385
- const cssString = Object.entries(evalResult)
114386
- .map(([key, value]) => {
114387
- const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
114388
- return `${cssKey}: ${value}`;
114389
- })
114390
- .join('; ');
114391
- result += `style="${cssString}"`;
114392
- }
114393
- else {
114394
- // These are arrays / objects attribute values
114395
- // Mark JSON-serialized values with a prefix so they can be parsed back correctly
114396
- const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
114397
- // Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
114398
- result += `${attributeName}="${jsonValue}"`;
114399
- }
114400
- }
114401
- else if (attributeName === 'className') {
114402
- // Escape special characters so that it doesn't break and split the attribute value to nodes
114403
- // This will be restored later in the pipeline
114404
- result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
114405
- }
114406
- else {
114407
- result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
114408
- }
114409
- }
114410
- catch (_error) {
114411
- result += content.slice(match.index, fullMatchEnd);
114412
- }
114413
- lastEnd = fullMatchEnd;
114414
- attrStartRegex.lastIndex = fullMatchEnd;
114415
- }
114416
- match = attrStartRegex.exec(content);
114417
- }
114418
- result += content.slice(lastEnd);
114419
- return result;
114420
- }
114421
- /**
114422
- * Preprocesses JSX-like expressions in markdown before parsing.
114423
- * Inline expressions are handled separately; attribute expressions are processed here.
114424
- *
114425
- * @param content
114426
- * @param context
114427
- * @returns Preprocessed content ready for markdown parsing
114428
- */
114429
- function preprocessJSXExpressions(content, context = {}) {
114430
- // Step 0: Base64 encode HTMLBlock content
114431
- let processed = protectHTMLBlockContent(content);
114432
- // Step 1: Protect code blocks and inline code
114433
- const { protectedCode, protectedContent } = protectCodeBlocks(processed);
114434
- // Step 2: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
114435
- // For inline expressions, we use a library to parse the expression & evaluate it later
114436
- // For attribute expressions, it was difficult to use a library to parse them, so do it manually
114437
- processed = evaluateAttributeExpressions(protectedContent, context, protectedCode);
114438
- // Step 3: Escape problematic braces to prevent MDX expression parsing errors
114439
- // This handles both unbalanced braces and paragraph-spanning expressions in one pass
114440
- processed = escapeProblematicBraces(processed);
114441
- // Step 4: Restore protected code blocks
114442
- processed = restoreCodeBlocks(processed, protectedCode);
114443
- return processed;
114444
- }
114445
-
114446
114499
  ;// ./processor/transform/mdxish/evaluate-expressions.ts
114447
114500
 
114448
114501
 
@@ -116236,11 +116289,33 @@ const parseBlock = (text) => {
116236
116289
  const tree = contentParser.runSync(contentParser.parse(text));
116237
116290
  return tree.children;
116238
116291
  };
116239
- const parseInline = (text) => {
116292
+ /**
116293
+ * Minimal parser for api-header titles.
116294
+ * Disables markdown constructs that are not parsed in legacy (headings, lists)
116295
+ */
116296
+ const apiHeaderTitleParser = unified()
116297
+ .data('micromarkExtensions', [
116298
+ legacyVariable(),
116299
+ looseHtmlEntity(),
116300
+ {
116301
+ disable: {
116302
+ null: [
116303
+ 'blockQuote',
116304
+ 'headingAtx',
116305
+ 'list',
116306
+ 'thematicBreak',
116307
+ ],
116308
+ },
116309
+ },
116310
+ ])
116311
+ .data('fromMarkdownExtensions', [legacyVariableFromMarkdown(), looseHtmlEntityFromMarkdown()])
116312
+ .use(remarkParse)
116313
+ .use(remarkGfm);
116314
+ const parseApiHeaderTitle = (text) => {
116240
116315
  if (!text.trim())
116241
116316
  return textToInline(text);
116242
- const tree = contentParser.runSync(contentParser.parse(text));
116243
- return tree.children;
116317
+ const tree = apiHeaderTitleParser.runSync(apiHeaderTitleParser.parse(text));
116318
+ return tree.children.flatMap(n => n.type === 'paragraph' && 'children' in n ? n.children : [n]);
116244
116319
  };
116245
116320
  /**
116246
116321
  * Transform a magicBlock node into final MDAST nodes.
@@ -116299,7 +116374,7 @@ function transformMagicBlock(blockType, data, rawValue, options = {}) {
116299
116374
  const depth = headerJson.level || (compatibilityMode ? 1 : 2);
116300
116375
  return [
116301
116376
  wrapPinnedBlocks({
116302
- children: 'title' in headerJson ? parseInline(headerJson.title || '') : [],
116377
+ children: 'title' in headerJson ? parseApiHeaderTitle(headerJson.title || '') : [],
116303
116378
  depth,
116304
116379
  type: 'heading',
116305
116380
  }, data),
@@ -117531,11 +117606,66 @@ const transformMagicBlockEmbed = (node) => {
117531
117606
  position,
117532
117607
  };
117533
117608
  };
117609
+ const mdxish_jsx_to_mdast_isTableCell = (node) => isMDXElement(node) && ['th', 'td'].includes(node.name);
117610
+ /**
117611
+ * Converts a JSX <Table> element to an MDAST table node with alignment.
117612
+ * Returns null for header-less tables since MDAST always promotes the first row to <thead>.
117613
+ */
117614
+ const transformTable = (jsx) => {
117615
+ let hasThead = false;
117616
+ visit(jsx, isMDXElement, (child) => {
117617
+ if (child.name === 'thead')
117618
+ hasThead = true;
117619
+ });
117620
+ if (!hasThead)
117621
+ return null;
117622
+ const { align: alignAttr } = getAttrs(jsx);
117623
+ const align = Array.isArray(alignAttr) ? alignAttr : null;
117624
+ const rows = [];
117625
+ visit(jsx, isMDXElement, (child) => {
117626
+ if (child.name !== 'thead' && child.name !== 'tbody')
117627
+ return;
117628
+ visit(child, isMDXElement, (row) => {
117629
+ if (row.name !== 'tr')
117630
+ return;
117631
+ const cells = [];
117632
+ visit(row, mdxish_jsx_to_mdast_isTableCell, (cell) => {
117633
+ const parsedChildren = cell.children.flatMap(parsedNode => {
117634
+ if (parsedNode.type === 'paragraph' && 'children' in parsedNode && parsedNode.children) {
117635
+ return parsedNode.children;
117636
+ }
117637
+ return [parsedNode];
117638
+ });
117639
+ cells.push({
117640
+ type: 'tableCell',
117641
+ children: parsedChildren,
117642
+ position: cell.position,
117643
+ });
117644
+ });
117645
+ rows.push({
117646
+ type: 'tableRow',
117647
+ children: cells,
117648
+ position: row.position,
117649
+ });
117650
+ });
117651
+ });
117652
+ const columnCount = rows[0]?.children?.length || 0;
117653
+ const alignArray = align && columnCount > 0
117654
+ ? align.slice(0, columnCount).concat(Array.from({ length: Math.max(0, columnCount - align.length) }, () => null))
117655
+ : Array.from({ length: columnCount }, () => null);
117656
+ return {
117657
+ type: 'table',
117658
+ align: alignArray,
117659
+ position: jsx.position,
117660
+ children: rows,
117661
+ };
117662
+ };
117534
117663
  const COMPONENT_MAP = {
117535
117664
  Callout: transformCallout,
117536
117665
  Embed: transformEmbed,
117537
117666
  Image: transformImage,
117538
117667
  Recipe: transformRecipe,
117668
+ Table: transformTable,
117539
117669
  };
117540
117670
  /**
117541
117671
  * Transform mdxJsxFlowElement nodes and magic block nodes into proper MDAST node types.
@@ -117558,6 +117688,8 @@ const mdxishJsxToMdast = () => tree => {
117558
117688
  if (!transformer)
117559
117689
  return;
117560
117690
  const newNode = transformer(node);
117691
+ if (!newNode)
117692
+ return;
117561
117693
  // Replace the JSX node with the MDAST node
117562
117694
  parent.children[index] = newNode;
117563
117695
  });
@@ -117838,6 +117970,126 @@ function restoreSnakeCase(placeholderName, mapping) {
117838
117970
  return matchingKey ? mapping[matchingKey] : placeholderName;
117839
117971
  }
117840
117972
 
117973
+ ;// ./processor/transform/mdxish/mdxish-tables-to-jsx.ts
117974
+
117975
+
117976
+ const mdxish_tables_to_jsx_alignToStyle = (align) => {
117977
+ if (!align || align === 'left')
117978
+ return null;
117979
+ return {
117980
+ type: 'mdxJsxAttribute',
117981
+ name: 'style',
117982
+ value: {
117983
+ type: 'mdxJsxAttributeValueExpression',
117984
+ value: `{ textAlign: "${align}" }`,
117985
+ },
117986
+ };
117987
+ };
117988
+ const mdxish_tables_to_jsx_isTableCell = (node) => ['tableHead', 'tableCell'].includes(node.type);
117989
+ const mdxish_tables_to_jsx_isLiteral = (node) => 'value' in node;
117990
+ /**
117991
+ * Mdxish-specific version of `tablesToJsx`. Differs from the shared MDX version:
117992
+ *
117993
+ * - Excludes `html` nodes from triggering JSX conversion because raw HTML
117994
+ * inside JSX `<Table>` breaks remarkMdx parsing on the deserialization roundtrip.
117995
+ * - Skips empty cells instead of aborting the entire visit so that flow content
117996
+ * in later cells is still detected.
117997
+ */
117998
+ const mdxishTablesToJsx = () => tree => {
117999
+ visit(tree, (node) => ['table', 'tableau'].includes(node.type), (table, index, parent) => {
118000
+ let hasFlowContent = false;
118001
+ visit(table, mdxish_tables_to_jsx_isTableCell, (cell) => {
118002
+ if (cell.children.length === 0)
118003
+ return;
118004
+ const content = cell.children.length === 1 && cell.children[0].type === 'paragraph'
118005
+ ? cell.children[0].children[0]
118006
+ : cell.children[0];
118007
+ if (!content)
118008
+ return;
118009
+ visit(cell, 'break', (_, breakIndex, breakParent) => {
118010
+ breakParent.children.splice(breakIndex, 1, { type: 'text', value: '\n' });
118011
+ });
118012
+ if (!(phrasing(content) || content.type === 'plain') && content.type !== 'escape') {
118013
+ if (content.type === 'html')
118014
+ return;
118015
+ hasFlowContent = true;
118016
+ return EXIT;
118017
+ }
118018
+ visit(cell, mdxish_tables_to_jsx_isLiteral, (node) => {
118019
+ if (node.value.match(/\n/)) {
118020
+ hasFlowContent = true;
118021
+ return EXIT;
118022
+ }
118023
+ });
118024
+ });
118025
+ if (!hasFlowContent) {
118026
+ table.type = 'table';
118027
+ return;
118028
+ }
118029
+ const styles = table.align.map(mdxish_tables_to_jsx_alignToStyle);
118030
+ const head = {
118031
+ attributes: [],
118032
+ type: 'mdxJsxFlowElement',
118033
+ name: 'thead',
118034
+ children: [
118035
+ {
118036
+ attributes: [],
118037
+ type: 'mdxJsxFlowElement',
118038
+ name: 'tr',
118039
+ children: table.children[0].children.map((cell, cellIndex) => {
118040
+ return {
118041
+ attributes: [],
118042
+ type: 'mdxJsxFlowElement',
118043
+ name: 'th',
118044
+ children: cell.children,
118045
+ ...(styles[cellIndex] && { attributes: [styles[cellIndex]] }),
118046
+ };
118047
+ }),
118048
+ },
118049
+ ],
118050
+ };
118051
+ const body = {
118052
+ attributes: [],
118053
+ type: 'mdxJsxFlowElement',
118054
+ name: 'tbody',
118055
+ children: table.children.splice(1).map(row => {
118056
+ return {
118057
+ attributes: [],
118058
+ type: 'mdxJsxFlowElement',
118059
+ name: 'tr',
118060
+ children: row.children.map((cell, cellIndex) => {
118061
+ return {
118062
+ type: 'mdxJsxFlowElement',
118063
+ name: 'td',
118064
+ children: cell.children,
118065
+ ...(styles[cellIndex] && { attributes: [styles[cellIndex]] }),
118066
+ };
118067
+ }),
118068
+ };
118069
+ }),
118070
+ };
118071
+ const attributes = [
118072
+ {
118073
+ type: 'mdxJsxAttribute',
118074
+ name: 'align',
118075
+ value: {
118076
+ type: 'mdxJsxAttributeValueExpression',
118077
+ value: JSON.stringify(table.align),
118078
+ },
118079
+ },
118080
+ ];
118081
+ const jsx = {
118082
+ type: 'mdxJsxFlowElement',
118083
+ name: 'Table',
118084
+ attributes: table.align.find(a => a) ? attributes : [],
118085
+ children: [head, body],
118086
+ };
118087
+ parent.children[index] = jsx;
118088
+ });
118089
+ return tree;
118090
+ };
118091
+ /* harmony default export */ const mdxish_tables_to_jsx = (mdxishTablesToJsx);
118092
+
117841
118093
  ;// ./processor/transform/mdxish/normalize-table-separator.ts
117842
118094
  /**
117843
118095
  * Preprocessor to normalize malformed GFM table separator syntax.
@@ -120006,6 +120258,8 @@ function loadComponents() {
120006
120258
 
120007
120259
 
120008
120260
 
120261
+
120262
+
120009
120263
 
120010
120264
 
120011
120265
 
@@ -120103,18 +120357,28 @@ function mdxishAstProcessor(mdContent, opts = {}) {
120103
120357
  };
120104
120358
  }
120105
120359
  /**
120106
- * Converts an Mdast to a Markdown string.
120360
+ * Registers the mdx-jsx serialization extension so remark-stringify
120361
+ * can convert JSX nodes (e.g. `<Table>`) to markdown.
120362
+ */
120363
+ function mdxJsxStringify() {
120364
+ const data = this.data();
120365
+ const extensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
120366
+ extensions.push({ extensions: [mdxJsxToMarkdown()] });
120367
+ }
120368
+ /**
120369
+ * Serializes an Mdast back into a markdown string.
120107
120370
  */
120108
120371
  function mdxishMdastToMd(mdast) {
120109
- const md = unified()
120372
+ const processor = unified()
120110
120373
  .use(remarkGfm)
120374
+ .use(mdxish_tables_to_jsx)
120111
120375
  .use(mdxishCompilers)
120376
+ .use(mdxJsxStringify)
120112
120377
  .use(remarkStringify, {
120113
120378
  bullet: '-',
120114
120379
  emphasis: '_',
120115
- })
120116
- .stringify(mdast);
120117
- return md;
120380
+ });
120381
+ return processor.stringify(processor.runSync(mdast));
120118
120382
  }
120119
120383
  /**
120120
120384
  * Processes markdown content with MDX syntax support and returns a HAST.