@readme/markdown 11.12.0 → 11.13.0

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
@@ -11366,6 +11366,8 @@ __webpack_require__.d(__webpack_exports__, {
11366
11366
  mdastV6: () => (/* reexport */ lib_mdastV6),
11367
11367
  mdx: () => (/* reexport */ lib_mdx),
11368
11368
  mdxish: () => (/* reexport */ lib_mdxish),
11369
+ mdxishAstProcessor: () => (/* reexport */ mdxishAstProcessor),
11370
+ mdxishMdastToMd: () => (/* reexport */ mdxishMdastToMd),
11369
11371
  mdxishTags: () => (/* reexport */ mdxishTags),
11370
11372
  migrate: () => (/* reexport */ lib_migrate),
11371
11373
  mix: () => (/* reexport */ lib_mix),
@@ -11513,9 +11515,17 @@ function docLink(href) {
11513
11515
  function Anchor(props) {
11514
11516
  const { children, href = '', target = '', title = '', ...attrs } = props;
11515
11517
  const baseUrl = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useContext)(BaseUrl);
11518
+ // Unwrap any nested anchor elements that GFM's autolinker may have created.
11519
+ // This prevents invalid nested <a> tags when the Anchor's text content looks like a URL.
11520
+ const unwrappedChildren = external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().Children.map(children, child => {
11521
+ if (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().isValidElement(child) && child.type === 'a') {
11522
+ return child.props.children;
11523
+ }
11524
+ return child;
11525
+ });
11516
11526
  return (
11517
11527
  // eslint-disable-next-line react/jsx-props-no-spreading
11518
- external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("a", { ...attrs, href: getHref(href, baseUrl), target: target, title: title, ...docLink(href) }, children));
11528
+ external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("a", { ...attrs, href: getHref(href, baseUrl), target: target, title: title, ...docLink(href) }, unwrappedChildren));
11519
11529
  }
11520
11530
  /* harmony default export */ const components_Anchor = (Anchor);
11521
11531
 
@@ -11653,7 +11663,8 @@ const Code = (props) => {
11653
11663
  const theme = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useContext)(Theme);
11654
11664
  const copyButtons = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useContext)(CodeOpts) || props.copyButtons;
11655
11665
  const isHydrated = useHydrated();
11656
- const language = isHydrated ? canonicalLanguage(lang) : '';
11666
+ const language = canonicalLanguage(lang);
11667
+ const isMermaid = language === 'mermaid';
11657
11668
  const codeRef = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.createRef)();
11658
11669
  const codeOpts = {
11659
11670
  inline: !lang,
@@ -11664,7 +11675,7 @@ const Code = (props) => {
11664
11675
  const highlightedCode = syntaxHighlighter && typeof syntaxHighlighter === 'function' && code && isHydrated
11665
11676
  ? syntaxHighlighter(code, language, codeOpts, { mdx: true })
11666
11677
  : code;
11667
- if (language === 'mermaid') {
11678
+ if (isHydrated && isMermaid) {
11668
11679
  return code;
11669
11680
  }
11670
11681
  return (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement((external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default()).Fragment, null,
@@ -11680,15 +11691,28 @@ var dist_utils_default = /*#__PURE__*/__webpack_require__.n(dist_utils);
11680
11691
 
11681
11692
 
11682
11693
 
11694
+
11683
11695
  let mermaid;
11684
11696
  const { uppercase } = (dist_utils_default());
11685
11697
  const CodeTabs = (props) => {
11686
11698
  const { children } = props;
11687
11699
  const theme = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useContext)(Theme);
11688
- const hasMermaid = !Array.isArray(children) && children.props?.children.props.lang === 'mermaid';
11689
- // render Mermaid diagram
11700
+ const isHydrated = useHydrated();
11701
+ // Handle both array (from rehype-react in rendering mdxish) and single element (MDX/JSX runtime) cases
11702
+ // The children here is the individual code block objects
11703
+ const childrenArray = Array.isArray(children) ? children : [children];
11704
+ // The structure varies depending on rendering context:
11705
+ // - When rendered via rehype-react: pre.props.children is an array where the first element is the Code component
11706
+ // - When rendered via MDX/JSX runtime: pre.props.children is directly the Code component
11707
+ const getCodeComponent = (pre) => {
11708
+ return Array.isArray(pre?.props?.children) ? pre.props.children[0] : pre?.props?.children;
11709
+ };
11710
+ const containAtLeastOneMermaid = childrenArray.some(pre => getCodeComponent(pre)?.props?.lang === 'mermaid');
11711
+ // Render Mermaid diagram
11690
11712
  (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useEffect)(() => {
11691
- if (typeof window !== 'undefined' && hasMermaid) {
11713
+ // Ensure we only render mermaids when frontend is hydrated to avoid hydration errors
11714
+ // because mermaid mutates the DOM before react hydrates
11715
+ if (typeof window !== 'undefined' && containAtLeastOneMermaid && isHydrated) {
11692
11716
  Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 1387, 23)).then(module => {
11693
11717
  mermaid = module.default;
11694
11718
  mermaid.initialize({
@@ -11700,7 +11724,7 @@ const CodeTabs = (props) => {
11700
11724
  });
11701
11725
  });
11702
11726
  }
11703
- }, [hasMermaid, theme]);
11727
+ }, [containAtLeastOneMermaid, theme, isHydrated]);
11704
11728
  function handleClick({ target }, index) {
11705
11729
  const $wrap = target.parentElement.parentElement;
11706
11730
  const $open = [].slice.call($wrap.querySelectorAll('.CodeTabs_active'));
@@ -11710,19 +11734,22 @@ const CodeTabs = (props) => {
11710
11734
  codeblocks[index].classList.add('CodeTabs_active');
11711
11735
  target.classList.add('CodeTabs_active');
11712
11736
  }
11713
- // render single Mermaid diagram
11714
- if (hasMermaid) {
11715
- const value = children.props.children.props.value;
11716
- return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("pre", { className: "mermaid-render mermaid_single" }, value);
11737
+ // We want to render single mermaid diagrams without the code tabs UI
11738
+ if (childrenArray.length === 1) {
11739
+ const codeComponent = getCodeComponent(childrenArray[0]);
11740
+ if (codeComponent?.props?.lang === 'mermaid') {
11741
+ const value = codeComponent?.props?.value;
11742
+ return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("pre", { className: "mermaid-render mermaid_single" }, value);
11743
+ }
11717
11744
  }
11718
11745
  return (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { className: `CodeTabs CodeTabs_initial theme-${theme}` },
11719
- external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { className: "CodeTabs-toolbar" }, (Array.isArray(children) ? children : [children]).map((pre, i) => {
11746
+ external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { className: "CodeTabs-toolbar" }, childrenArray.map((pre, i) => {
11720
11747
  // the first or only child should be our Code component
11721
- const codeComponent = Array.isArray(pre.props?.children)
11748
+ const tabCodeComponent = Array.isArray(pre.props?.children)
11722
11749
  ? pre.props.children[0]
11723
11750
  : pre.props?.children;
11724
- const lang = codeComponent?.props?.lang;
11725
- const meta = codeComponent?.props?.meta;
11751
+ const lang = tabCodeComponent?.props?.lang;
11752
+ const meta = tabCodeComponent?.props?.meta;
11726
11753
  /* istanbul ignore next */
11727
11754
  return (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("button", { key: i, onClick: e => handleClick(e, i), type: "button", value: lang }, meta || `${!lang ? 'Text' : uppercase(lang)}`));
11728
11755
  })),
@@ -70584,77 +70611,197 @@ const mdxToHast = () => tree => {
70584
70611
  ;// ./processor/transform/mdxish/mdxish-component-blocks.ts
70585
70612
 
70586
70613
 
70587
- const tagPattern = /^<([A-Z][A-Za-z0-9_]*)([^>]*?)(\/?)>([\s\S]*)?$/;
70588
- const attributePattern = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s"'>]+))?/g;
70614
+ const pascalCaseTagPattern = /^<([A-Z][A-Za-z0-9_]*)([^>]*?)(\/?)>([\s\S]*)?$/;
70615
+ const tagAttributePattern = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s"'>]+))?/g;
70616
+ /**
70617
+ * Maximum number of siblings to scan forward when looking for a closing tag
70618
+ * to avoid scanning too far and degrading performance
70619
+ */
70620
+ const MAX_LOOKAHEAD = 30;
70621
+ /**
70622
+ * Tags that have dedicated transformers and should NOT be handled by this plugin.
70623
+ * These components have special parsing requirements that the generic component
70624
+ * block transformer cannot handle correctly.
70625
+ */
70626
+ const EXCLUDED_TAGS = new Set(['HTMLBlock', 'Table']);
70589
70627
  const inlineMdProcessor = unified().use(remarkParse);
70590
70628
  const isClosingTag = (value, tag) => value.trim() === `</${tag}>`;
70591
- // Remove matching closing tag from paragraph children; returns updated paragraph and removal status
70592
- const stripClosingFromParagraph = (node, tag) => {
70593
- if (!Array.isArray(node.children))
70594
- return { paragraph: node, found: false };
70595
- const children = [...node.children];
70596
- const closingIndex = children.findIndex(child => child.type === 'html' && isClosingTag(child.value || '', tag));
70597
- if (closingIndex === -1)
70598
- return { paragraph: node, found: false };
70599
- children.splice(closingIndex, 1);
70600
- return {
70601
- paragraph: { ...node, children },
70602
- found: true,
70603
- };
70604
- };
70605
- // Replace two child nodes (opening HTML tag + paragraph) with a single replacement node
70606
- const replaceChild = (parent, index, replacement) => {
70607
- parent.children.splice(index, 2, replacement);
70608
- };
70609
- // Parse markdown inside a component's inline content into mdast children.
70629
+ /**
70630
+ * Parse markdown content into mdast children nodes.
70631
+ */
70610
70632
  const parseMdChildren = (value) => {
70611
70633
  const parsed = inlineMdProcessor.parse(value);
70612
70634
  return parsed.children || [];
70613
70635
  };
70614
- // Convert raw attribute string into mdxJsxAttribute entries (strings only; no expressions).
70615
- // Handles both key-value attributes (theme="info") and boolean attributes (empty).
70636
+ /**
70637
+ * Convert raw attribute string into mdxJsxAttribute entries.
70638
+ * Handles both key-value attributes (theme="info") and boolean attributes (empty).
70639
+ */
70616
70640
  const parseAttributes = (raw) => {
70617
70641
  const attributes = [];
70618
70642
  const attrString = raw.trim();
70619
70643
  if (!attrString)
70620
70644
  return attributes;
70621
- // Reset regex lastIndex since it's a global regex that maintains state
70622
- attributePattern.lastIndex = 0;
70623
- let match = attributePattern.exec(attrString);
70645
+ tagAttributePattern.lastIndex = 0;
70646
+ let match = tagAttributePattern.exec(attrString);
70624
70647
  while (match !== null) {
70625
70648
  const [, attrName, attrValue] = match;
70626
- // Boolean attribute (no value) -> set to null
70627
- // Attribute with value -> clean and set string value
70628
- // Note: Attribute value types can't directly be numbers & booleans. String, nulls, undefined are supported.
70629
70649
  const value = attrValue ? attrValue.replace(/^['"]|['"]$/g, '') : null;
70630
- attributes.push({
70631
- type: 'mdxJsxAttribute',
70632
- name: attrName,
70633
- value,
70634
- });
70635
- match = attributePattern.exec(attrString);
70650
+ attributes.push({ type: 'mdxJsxAttribute', name: attrName, value });
70651
+ match = tagAttributePattern.exec(attrString);
70636
70652
  }
70637
70653
  return attributes;
70638
70654
  };
70639
- // Parse a single HTML-ish tag string into tag name, attributes, self-closing flag, and inline content.
70655
+ /**
70656
+ * Parse an HTML tag string into structured data.
70657
+ */
70640
70658
  const parseTag = (value) => {
70641
- const match = value.match(tagPattern);
70659
+ const match = value.match(pascalCaseTagPattern);
70642
70660
  if (!match)
70643
70661
  return null;
70644
- const [, tag, attrString = '', selfClosing = '', content = ''] = match;
70645
- const attributes = parseAttributes(attrString);
70662
+ const [, tag, attrString = '', selfClosing = '', contentAfterTag = ''] = match;
70646
70663
  return {
70647
70664
  tag,
70648
- attributes,
70665
+ attributes: parseAttributes(attrString),
70649
70666
  selfClosing: !!selfClosing,
70650
- content,
70667
+ contentAfterTag,
70651
70668
  };
70652
70669
  };
70653
- // Transform PascalCase HTML blocks into mdxJsxFlowElement nodes.
70654
- // Remark parses unknown tags as raw HTML; we rewrite them so MDX/rehype treats them as components.
70670
+ /**
70671
+ * Create an MdxJsxFlowElement node from component data.
70672
+ */
70673
+ const createComponentNode = ({ tag, attributes, children, startPosition, endPosition }) => ({
70674
+ type: 'mdxJsxFlowElement',
70675
+ name: tag,
70676
+ attributes,
70677
+ children,
70678
+ position: {
70679
+ start: startPosition?.start,
70680
+ end: endPosition?.end ?? startPosition?.end,
70681
+ },
70682
+ });
70683
+ /**
70684
+ * Remove a closing tag from a paragraph's children and return the updated paragraph.
70685
+ */
70686
+ const stripClosingTagFromParagraph = (node, tag) => {
70687
+ if (!Array.isArray(node.children))
70688
+ return { paragraph: node, found: false };
70689
+ const children = [...node.children];
70690
+ const closingIndex = children.findIndex(child => child.type === 'html' && isClosingTag(child.value || '', tag));
70691
+ if (closingIndex === -1)
70692
+ return { paragraph: node, found: false };
70693
+ children.splice(closingIndex, 1);
70694
+ return { paragraph: { ...node, children }, found: true };
70695
+ };
70696
+ /**
70697
+ * Scan forward through siblings to find a closing tag.
70698
+ * Handles:
70699
+ * - Exact match HTML siblings (e.g., `</Tag>`)
70700
+ * - HTML siblings with embedded closing tag (e.g., `...\n</Tag>`)
70701
+ * - Paragraph siblings containing the closing tag as a child
70702
+ *
70703
+ * Returns null if not found within MAX_LOOKAHEAD siblings
70704
+ */
70705
+ const scanForClosingTag = (parent, startIndex, tag) => {
70706
+ const closingTagStr = `</${tag}>`;
70707
+ const maxIndex = Math.min(startIndex + MAX_LOOKAHEAD, parent.children.length);
70708
+ let i = startIndex + 1;
70709
+ for (; i < maxIndex; i += 1) {
70710
+ const sibling = parent.children[i];
70711
+ // Check HTML siblings
70712
+ if (sibling.type === 'html') {
70713
+ const siblingValue = sibling.value || '';
70714
+ // Exact match (standalone closing tag)
70715
+ if (isClosingTag(siblingValue, tag)) {
70716
+ return { closingIndex: i, extraClosingChildren: [] };
70717
+ }
70718
+ // Embedded closing tag (closing tag at end of HTML block content)
70719
+ if (siblingValue.includes(closingTagStr)) {
70720
+ const contentBeforeClose = siblingValue.substring(0, siblingValue.lastIndexOf(closingTagStr)).trim();
70721
+ const extraChildren = contentBeforeClose
70722
+ ? parseMdChildren(contentBeforeClose)
70723
+ : [];
70724
+ return { closingIndex: i, extraClosingChildren: extraChildren };
70725
+ }
70726
+ }
70727
+ // Check paragraph siblings
70728
+ if (sibling.type === 'paragraph') {
70729
+ const { paragraph, found } = stripClosingTagFromParagraph(sibling, tag);
70730
+ if (found) {
70731
+ return { closingIndex: i, extraClosingChildren: [], strippedParagraph: paragraph };
70732
+ }
70733
+ }
70734
+ }
70735
+ if (i < parent.children.length) {
70736
+ // eslint-disable-next-line no-console
70737
+ console.warn(`Closing tag </${tag}> not found within ${MAX_LOOKAHEAD} siblings, stopping scan`);
70738
+ }
70739
+ return null;
70740
+ };
70741
+ const substituteNodeWithMdxNode = (parent, index, mdxNode) => {
70742
+ parent.children.splice(index, 1, mdxNode);
70743
+ };
70744
+ /**
70745
+ * Transform PascalCase HTML nodes into mdxJsxFlowElement nodes.
70746
+ *
70747
+ * Remark parses unknown/custom component tags as raw HTML nodes.
70748
+ * These are the custom readme MDX syntax for components.
70749
+ * This transformer identifies these patterns and converts them to proper MDX JSX elements so they
70750
+ * can be accurately recognized and rendered later with their component definition code.
70751
+ * Though for some tags, we need to handle them specially
70752
+ *
70753
+ * ## Supported HTML Structures
70754
+ *
70755
+ * ### 1. Self-closing tags
70756
+ * ```
70757
+ * <Component />
70758
+ * ```
70759
+ * Parsed as: `html: "<Component />"`
70760
+ *
70761
+ * ### 2. Self-contained blocks (entire component in single HTML node)
70762
+ * ```
70763
+ * <Button>Click me</Button>
70764
+ * ```
70765
+ * ```
70766
+ * <Component>
70767
+ * <h2>Title</h2>
70768
+ * <p>Content</p>
70769
+ * </Component>
70770
+ * ```
70771
+ * Parsed as: `html: "<Component>\n <h2>Title</h2>\n <p>Content</p>\n</Component>"`
70772
+ * The opening tag, content, and closing tag are all captured in one HTML node.
70773
+ *
70774
+ * ### 3. Multi-sibling components (closing tag in a following sibling)
70775
+ * Handles various structures where the closing tag is in a later sibling, such as:
70776
+ *
70777
+ * #### 3a. Block components (closing tag in sibling paragraph)
70778
+ * ```
70779
+ * <Callout>
70780
+ * Some **markdown** content
70781
+ * </Callout>
70782
+ * ```
70783
+ *
70784
+ * #### 3b. Multi-paragraph components (closing tag several siblings away)
70785
+ * ```
70786
+ * <Callout>
70787
+ *
70788
+ * First paragraph
70789
+ *
70790
+ * Second paragraph
70791
+ * </Callout>
70792
+ * ```
70793
+ *
70794
+ * #### 3c. Nested components split by blank lines (closing tag embedded in HTML sibling)
70795
+ * ```
70796
+ * <Outer>
70797
+ * <Inner>content</Inner>
70798
+ *
70799
+ * <Inner>content</Inner>
70800
+ * </Outer>
70801
+ * ```
70802
+ */
70655
70803
  const mdxishComponentBlocks = () => tree => {
70656
70804
  const stack = [tree];
70657
- // Process children depth-first, rewriting opening/closing component HTML pairs
70658
70805
  const processChildNode = (parent, index) => {
70659
70806
  const node = parent.children[index];
70660
70807
  if (!node)
@@ -70662,46 +70809,66 @@ const mdxishComponentBlocks = () => tree => {
70662
70809
  if ('children' in node && Array.isArray(node.children)) {
70663
70810
  stack.push(node);
70664
70811
  }
70812
+ // Only visit HTML nodes with an actual html tag
70665
70813
  const value = node.value;
70666
70814
  if (node.type !== 'html' || typeof value !== 'string')
70667
70815
  return;
70668
70816
  const parsed = parseTag(value);
70669
70817
  if (!parsed)
70670
70818
  return;
70671
- const { tag, attributes, selfClosing, content = '' } = parsed;
70672
- const extraChildren = content ? parseMdChildren(content.trimStart()) : [];
70819
+ const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
70820
+ // Skip tags that have dedicated transformers
70821
+ if (EXCLUDED_TAGS.has(tag))
70822
+ return;
70823
+ const closingTagStr = `</${tag}>`;
70824
+ // Case 1: Self-closing tag
70673
70825
  if (selfClosing) {
70674
- const componentNode = {
70675
- type: 'mdxJsxFlowElement',
70676
- name: tag,
70826
+ const componentNode = createComponentNode({
70827
+ tag,
70677
70828
  attributes,
70678
70829
  children: [],
70679
- position: node.position,
70680
- };
70681
- parent.children.splice(index, 1, componentNode);
70830
+ startPosition: node.position,
70831
+ });
70832
+ substituteNodeWithMdxNode(parent, index, componentNode);
70682
70833
  return;
70683
70834
  }
70684
- const next = parent.children[index + 1];
70685
- if (!next || next.type !== 'paragraph')
70835
+ // Case 2: Self-contained block (closing tag in content)
70836
+ if (contentAfterTag.includes(closingTagStr)) {
70837
+ const componentInnerContent = contentAfterTag.substring(0, contentAfterTag.lastIndexOf(closingTagStr)).trim();
70838
+ const componentNode = createComponentNode({
70839
+ tag,
70840
+ attributes,
70841
+ children: componentInnerContent ? parseMdChildren(componentInnerContent) : [],
70842
+ startPosition: node.position,
70843
+ });
70844
+ substituteNodeWithMdxNode(parent, index, componentNode);
70686
70845
  return;
70687
- const { paragraph, found } = stripClosingFromParagraph(next, tag);
70688
- if (!found)
70846
+ }
70847
+ // Case 3: Multi-sibling component (closing tag in a following sibling)
70848
+ // Scans forward through siblings to find closing tag in HTML or paragraph nodes
70849
+ const scanResult = scanForClosingTag(parent, index, tag);
70850
+ if (!scanResult)
70689
70851
  return;
70690
- const componentNode = {
70691
- type: 'mdxJsxFlowElement',
70692
- name: tag,
70852
+ const { closingIndex, extraClosingChildren, strippedParagraph } = scanResult;
70853
+ const extraChildren = contentAfterTag ? parseMdChildren(contentAfterTag.trimStart()) : [];
70854
+ // Collect all intermediate siblings between opening tag and closing tag
70855
+ const intermediateChildren = parent.children.slice(index + 1, closingIndex);
70856
+ // For paragraph siblings, include the paragraph's children (with closing tag stripped)
70857
+ // For HTML siblings, include any content parsed from before the closing tag
70858
+ const closingChildren = strippedParagraph
70859
+ ? strippedParagraph.children
70860
+ : extraClosingChildren;
70861
+ const componentNode = createComponentNode({
70862
+ tag,
70693
70863
  attributes,
70694
- children: [
70695
- ...extraChildren,
70696
- ...paragraph.children,
70697
- ],
70698
- position: {
70699
- start: node.position?.start,
70700
- end: next.position?.end,
70701
- },
70702
- };
70703
- replaceChild(parent, index, componentNode);
70864
+ children: [...extraChildren, ...intermediateChildren, ...closingChildren],
70865
+ startPosition: node.position,
70866
+ endPosition: parent.children[closingIndex]?.position,
70867
+ });
70868
+ // Remove all nodes from opening tag to closing tag (inclusive) and replace with component node
70869
+ parent.children.splice(index, closingIndex - index + 1, componentNode);
70704
70870
  };
70871
+ // Travel the tree depth-first
70705
70872
  while (stack.length) {
70706
70873
  const parent = stack.pop();
70707
70874
  if (parent?.children) {
@@ -71074,10 +71241,20 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
71074
71241
  hProperties.url = hProperties.href;
71075
71242
  delete hProperties.href;
71076
71243
  }
71244
+ // Unwrap any autolinked children to prevent nested links.
71245
+ // GFM's autolink feature can convert URL-like text inside Anchor children
71246
+ // into link nodes, which would create invalid nested links when Anchor
71247
+ // is converted back to a link node.
71248
+ const children = node.children.flatMap(child => {
71249
+ if (child.type === 'link') {
71250
+ return child.children;
71251
+ }
71252
+ return child;
71253
+ });
71077
71254
  // @ts-expect-error we don't have a mechanism to enforce the URL attribute type right now
71078
71255
  const mdNode = {
71079
71256
  ...hProperties,
71080
- children: node.children,
71257
+ children,
71081
71258
  type: types[node.name],
71082
71259
  position: node.position,
71083
71260
  };
@@ -86366,16 +86543,17 @@ const tocHastToMdx = (toc, components) => {
86366
86543
 
86367
86544
 
86368
86545
 
86369
- const { codeTabsTransformer: compile_codeTabsTransformer, ...transforms } = defaultTransforms;
86370
86546
  const sanitizeSchema = cjs_default()(defaultSchema, {
86371
86547
  protocols: ['doc', 'ref', 'blog', 'changelog', 'page'],
86372
86548
  });
86373
86549
  const compile_compile = (text, { components = {}, missingComponents, copyButtons, useTailwind, ...opts } = {}) => {
86550
+ // Destructure at runtime to avoid circular dependency issues
86551
+ const { codeTabsTransformer, ...transforms } = defaultTransforms;
86374
86552
  const remarkPlugins = [
86375
86553
  remarkFrontmatter,
86376
86554
  remarkGfm,
86377
86555
  ...Object.values(transforms),
86378
- [compile_codeTabsTransformer, { copyButtons }],
86556
+ [codeTabsTransformer, { copyButtons }],
86379
86557
  [
86380
86558
  handle_missing_components,
86381
86559
  { components, missingComponents: ['ignore', 'throw'].includes(missingComponents) ? missingComponents : 'ignore' },
@@ -92845,8 +93023,11 @@ function smartCamelCase(str) {
92845
93023
  }
92846
93024
  // Sort by length (longest first) to prevent shorter matches (e.g., "column" in "columns")
92847
93025
  const sortedBoundaries = [...allBoundaries].sort((a, b) => b.length - a.length);
93026
+ // Use case-sensitive matching ('g' not 'gi') so that once a letter is
93027
+ // capitalized by a longer boundary, shorter boundaries won't re-match it.
93028
+ // This prevents issues like 'iconcolor' becoming 'iconColOr' instead of 'iconColor'.
92848
93029
  return sortedBoundaries.reduce((res, word) => {
92849
- const regex = new RegExp(`(${word})([a-z])`, 'gi');
93030
+ const regex = new RegExp(`(${word})([a-z])`, 'g');
92850
93031
  return res.replace(regex, (_, prefix, nextChar) => prefix.toLowerCase() + nextChar.toUpperCase());
92851
93032
  }, str);
92852
93033
  }
@@ -92972,7 +93153,21 @@ const htmlBlockHandler = (_state, node) => {
92972
93153
  children: [],
92973
93154
  };
92974
93155
  };
93156
+ // Convert embed magic blocks to Embed components
93157
+ const embedHandler = (state, node) => {
93158
+ // Assert to get the minimum properties we need
93159
+ const { data } = node;
93160
+ return {
93161
+ type: 'element',
93162
+ // To differentiate between regular embeds and magic block embeds,
93163
+ // magic block embeds have a certain hName
93164
+ tagName: data?.hName === NodeTypes.embedBlock ? 'Embed' : 'embed',
93165
+ properties: data?.hProperties,
93166
+ children: state.all(node),
93167
+ };
93168
+ };
92975
93169
  const mdxComponentHandlers = {
93170
+ embed: embedHandler,
92976
93171
  mdxFlowExpression: mdxExpressionHandler,
92977
93172
  mdxJsxFlowElement: mdxJsxElementHandler,
92978
93173
  mdxJsxTextElement: mdxJsxElementHandler,
@@ -93636,8 +93831,6 @@ const mdxishHtmlBlocks = () => tree => {
93636
93831
  * Taken from the v6 branch
93637
93832
  */
93638
93833
  const RGXP = /^\s*\[block:([^\]]*)\]([^]+?)\[\/block\]/;
93639
- /** Parses markdown in table cells */
93640
- const cellParser = unified().use(remarkParse).use(remarkGfm);
93641
93834
  /**
93642
93835
  * Wraps a node in a "pinned" container if sidebar: true is set in the JSON.
93643
93836
  * Pinned blocks are displayed in a sidebar/floating position in the UI.
@@ -93671,11 +93864,13 @@ const imgWidthBySize = new Proxy(imgSizeValues, {
93671
93864
  const textToInline = (text) => [{ type: 'text', value: text }];
93672
93865
  // Simple text to block nodes (wraps in paragraph)
93673
93866
  const textToBlock = (text) => [{ children: textToInline(text), type: 'paragraph' }];
93867
+ /** Parses markdown and html to markdown nodes */
93868
+ const contentParser = unified().use(remarkParse).use(remarkGfm);
93674
93869
  // Table cells may contain html or markdown content, so we need to parse it accordingly instead of keeping it as raw text
93675
- const parseInline = (text) => {
93870
+ const parseTableCell = (text) => {
93676
93871
  if (!text.trim())
93677
93872
  return [{ type: 'text', value: '' }];
93678
- const tree = cellParser.runSync(cellParser.parse(text));
93873
+ const tree = contentParser.runSync(contentParser.parse(text));
93679
93874
  // If there are multiple block-level nodes, keep them as-is to preserve the document structure and spacing
93680
93875
  if (tree.children.length > 1) {
93681
93876
  return tree.children;
@@ -93684,6 +93879,13 @@ const parseInline = (text) => {
93684
93879
  // This unwraps the extra p node that might appear & wrapping the content
93685
93880
  n.type === 'paragraph' && 'children' in n ? n.children : [n]);
93686
93881
  };
93882
+ // Parse markdown/HTML into block-level nodes (preserves paragraphs, headings, lists, etc.)
93883
+ const parseBlock = (text) => {
93884
+ if (!text.trim())
93885
+ return [{ type: 'paragraph', children: [{ type: 'text', value: '' }] }];
93886
+ const tree = contentParser.runSync(contentParser.parse(text));
93887
+ return tree.children;
93888
+ };
93687
93889
  /**
93688
93890
  * Parse a magic block string and return MDAST nodes.
93689
93891
  *
@@ -93762,7 +93964,7 @@ function parseMagicBlock(raw, options = {}) {
93762
93964
  data: {
93763
93965
  hProperties: {
93764
93966
  ...(imgData.align && { align: imgData.align }),
93765
- className: imgData.border ? 'border' : '',
93967
+ ...(imgData.border && { border: imgData.border.toString() }),
93766
93968
  ...(imgData.sizing && { width: imgWidthBySize[imgData.sizing] }),
93767
93969
  },
93768
93970
  },
@@ -93801,8 +94003,9 @@ function parseMagicBlock(raw, options = {}) {
93801
94003
  const [icon, theme] = Array.isArray(resolvedType) ? resolvedType : ['👍', 'default'];
93802
94004
  if (!(calloutJson.title || calloutJson.body))
93803
94005
  return [];
93804
- const titleBlocks = textToBlock(calloutJson.title || '');
93805
- const bodyBlocks = textToBlock(calloutJson.body || '');
94006
+ // Parses html & markdown content
94007
+ const titleBlocks = parseBlock(calloutJson.title || '');
94008
+ const bodyBlocks = parseBlock(calloutJson.body || '');
93806
94009
  const children = [];
93807
94010
  if (titleBlocks.length > 0 && titleBlocks[0].type === 'paragraph') {
93808
94011
  const firstTitle = titleBlocks[0];
@@ -93817,14 +94020,17 @@ function parseMagicBlock(raw, options = {}) {
93817
94020
  else {
93818
94021
  children.push(...titleBlocks, ...bodyBlocks);
93819
94022
  }
94023
+ // If there is no title or title is empty
94024
+ const empty = !titleBlocks.length || !titleBlocks[0].children[0]?.value;
93820
94025
  // Create mdxJsxFlowElement directly for mdxish
93821
94026
  const calloutElement = {
93822
94027
  type: 'mdxJsxFlowElement',
93823
94028
  name: 'Callout',
93824
- attributes: toAttributes({ icon, theme: theme || 'default', type: theme || 'default' }, [
94029
+ attributes: toAttributes({ icon, theme: theme || 'default', type: theme || 'default', empty }, [
93825
94030
  'icon',
93826
94031
  'theme',
93827
94032
  'type',
94033
+ 'empty',
93828
94034
  ]),
93829
94035
  children: children,
93830
94036
  };
@@ -93853,7 +94059,7 @@ function parseMagicBlock(raw, options = {}) {
93853
94059
  return mapped;
93854
94060
  }, []);
93855
94061
  // In compatibility mode, wrap cell content in paragraphs; otherwise inline text
93856
- const tokenizeCell = compatibilityMode ? textToBlock : parseInline;
94062
+ const tokenizeCell = compatibilityMode ? textToBlock : parseTableCell;
93857
94063
  const children = Array.from({ length: rows + 1 }, (_, y) => ({
93858
94064
  children: Array.from({ length: cols }, (__, x) => ({
93859
94065
  children: sparseData[y]?.[x] ? tokenizeCell(sparseData[y][x]) : [{ type: 'text', value: '' }],
@@ -93882,9 +94088,9 @@ function parseMagicBlock(raw, options = {}) {
93882
94088
  return [
93883
94089
  wrapPinnedBlocks({
93884
94090
  children: [
93885
- { children: [{ type: 'text', value: title || null }], title: embedJson.provider, type: 'link', url },
94091
+ { children: [{ type: 'text', value: title || '' }], title: embedJson.provider, type: 'link', url },
93886
94092
  ],
93887
- data: { hName: 'rdme-embed', hProperties: { ...embedJson, href: url, html, title, url } },
94093
+ data: { hName: 'embed-block', hProperties: { ...embedJson, href: url, html, title, url } },
93888
94094
  type: 'embed',
93889
94095
  }, json),
93890
94096
  ];
@@ -93987,6 +94193,38 @@ const magicBlockRestorer = ({ blocks }) => tree => {
93987
94193
  };
93988
94194
  /* harmony default export */ const mdxish_magic_blocks = (magicBlockRestorer);
93989
94195
 
94196
+ ;// ./processor/transform/mdxish/mdxish-mermaid.ts
94197
+
94198
+ /**
94199
+ * Rehype plugin for mdxish pipeline to add mermaid-render className to mermaid code blocks.
94200
+ * The mermaid-render class is used to identify the mermaid diagrams elements for the
94201
+ * mermaid library to transform. See components/CodeTabs/index.tsx for context
94202
+ */
94203
+ const mdxishMermaidTransformer = () => (tree) => {
94204
+ visit(tree, 'element', (node) => {
94205
+ if (node.tagName !== 'pre' || node.children.length !== 1)
94206
+ return;
94207
+ const [child] = node.children;
94208
+ if (child.type === 'element' &&
94209
+ child.tagName === 'code' &&
94210
+ child.properties?.lang === 'mermaid') {
94211
+ // Combine existing className with the new mermaid-render class
94212
+ const existingClassName = node.properties?.className;
94213
+ const classNameArray = Array.isArray(existingClassName)
94214
+ ? existingClassName.filter(c => typeof c === 'string' || typeof c === 'number')
94215
+ : existingClassName && (typeof existingClassName === 'string' || typeof existingClassName === 'number')
94216
+ ? [existingClassName]
94217
+ : [];
94218
+ node.properties = {
94219
+ ...node.properties,
94220
+ className: ['mermaid-render', ...classNameArray],
94221
+ };
94222
+ }
94223
+ });
94224
+ return tree;
94225
+ };
94226
+ /* harmony default export */ const mdxish_mermaid = (mdxishMermaidTransformer);
94227
+
93990
94228
  ;// ./lib/constants.ts
93991
94229
  /**
93992
94230
  * Pattern to match component tags (PascalCase or snake_case)
@@ -94214,6 +94452,45 @@ const restoreSnakeCaseComponentNames = (options) => {
94214
94452
  };
94215
94453
  /* harmony default export */ const restore_snake_case_component_name = (restoreSnakeCaseComponentNames);
94216
94454
 
94455
+ ;// ./processor/transform/mdxish/retain-boolean-attributes.ts
94456
+
94457
+ // Private Use Area character (U+E000) which is extremely unlikely to appear in real content.
94458
+ const TEMP_TRUE_BOOLEAN_VALUE = 'readme-this-is-a-temporary-boolean-attribute-\uE000';
94459
+ const TEMP_FALSE_BOOLEAN_VALUE = 'readme-this-is-a-temporary-boolean-attribute-\uE001';
94460
+ /**
94461
+ * Preserves boolean properties when passed to rehypeRaw because
94462
+ * rehypeRaw converts boolean properties in nodes to strings (e.g. true -> ""),
94463
+ * which can change the truthiness of the property. Hence we need to preserve the boolean properties.
94464
+ */
94465
+ const preserveBooleanProperties = () => tree => {
94466
+ visit(tree, 'element', (node) => {
94467
+ if (!node.properties)
94468
+ return;
94469
+ Object.entries(node.properties).forEach(([key, value]) => {
94470
+ if (typeof value === 'boolean') {
94471
+ node.properties[key] = value ? TEMP_TRUE_BOOLEAN_VALUE : TEMP_FALSE_BOOLEAN_VALUE;
94472
+ }
94473
+ });
94474
+ });
94475
+ return tree;
94476
+ };
94477
+ const restoreBooleanProperties = () => tree => {
94478
+ visit(tree, 'element', (node) => {
94479
+ if (!node.properties)
94480
+ return;
94481
+ Object.entries(node.properties).forEach(([key, value]) => {
94482
+ if (value === TEMP_TRUE_BOOLEAN_VALUE) {
94483
+ node.properties[key] = true;
94484
+ }
94485
+ else if (value === TEMP_FALSE_BOOLEAN_VALUE) {
94486
+ node.properties[key] = false;
94487
+ }
94488
+ });
94489
+ });
94490
+ return tree;
94491
+ };
94492
+
94493
+
94217
94494
  ;// ./processor/transform/mdxish/variables-text.ts
94218
94495
 
94219
94496
 
@@ -94241,6 +94518,8 @@ const variablesTextTransformer = () => tree => {
94241
94518
  if (parent.type === 'inlineCode')
94242
94519
  return;
94243
94520
  const text = node.value;
94521
+ if (typeof text !== 'string' || !text.trim())
94522
+ return;
94244
94523
  if (!text.includes('{user.') && !text.includes('{user['))
94245
94524
  return;
94246
94525
  const matches = [...text.matchAll(USER_VAR_REGEX)];
@@ -94390,19 +94669,17 @@ function loadComponents() {
94390
94669
 
94391
94670
 
94392
94671
 
94672
+
94673
+
94674
+
94675
+
94393
94676
 
94394
94677
 
94395
94678
 
94396
94679
 
94397
94680
 
94398
94681
  const defaultTransformers = [callouts, code_tabs, gemoji_, transform_embeds];
94399
- /**
94400
- * Process markdown content with MDX syntax support.
94401
- * Detects and renders custom component tags from the components hash.
94402
- *
94403
- * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
94404
- */
94405
- function mdxish(mdContent, opts = {}) {
94682
+ function mdxishAstProcessor(mdContent, opts = {}) {
94406
94683
  const { components: userComponents = {}, jsxContext = {}, useTailwind } = opts;
94407
94684
  const components = {
94408
94685
  ...loadComponents(),
@@ -94437,9 +94714,46 @@ function mdxish(mdContent, opts = {}) {
94437
94714
  .use(evaluate_expressions, { context: jsxContext }) // Evaluate MDX expressions using jsxContext
94438
94715
  .use(variables_text) // Parse {user.*} patterns from text (can't rely on remarkMdx)
94439
94716
  .use(useTailwind ? transform_tailwind : undefined, { components: tempComponentsMap })
94440
- .use(remarkGfm)
94717
+ .use(remarkGfm);
94718
+ return {
94719
+ processor,
94720
+ /**
94721
+ * @todo we need to return this transformed content for now
94722
+ * but ultimately need to properly tokenize our special markdown syntax
94723
+ * into hast nodes instead of relying on transformed content
94724
+ */
94725
+ parserReadyContent,
94726
+ };
94727
+ }
94728
+ /**
94729
+ * Converts an Mdast to a Markdown string.
94730
+ */
94731
+ function mdxishMdastToMd(mdast) {
94732
+ const md = unified().use(remarkGfm).use(processor_compile).use(remarkStringify, {
94733
+ bullet: '-',
94734
+ emphasis: '_',
94735
+ }).stringify(mdast);
94736
+ return md;
94737
+ }
94738
+ /**
94739
+ * Processes markdown content with MDX syntax support and returns a HAST.
94740
+ * Detects and renders custom component tags from the components hash.
94741
+ *
94742
+ * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
94743
+ */
94744
+ function mdxish(mdContent, opts = {}) {
94745
+ const { components: userComponents = {} } = opts;
94746
+ const components = {
94747
+ ...loadComponents(),
94748
+ ...userComponents,
94749
+ };
94750
+ const { processor, parserReadyContent } = mdxishAstProcessor(mdContent, opts);
94751
+ processor
94441
94752
  .use(remarkRehype, { allowDangerousHtml: true, handlers: mdxComponentHandlers })
94753
+ .use(preserveBooleanProperties) // RehypeRaw converts boolean properties to empty strings
94442
94754
  .use(rehypeRaw, { passThrough: ['html-block'] })
94755
+ .use(restoreBooleanProperties)
94756
+ .use(mdxish_mermaid) // Add mermaid-render className to pre wrappers
94443
94757
  .use(rehypeSlug)
94444
94758
  .use(rehypeMdxishComponents, {
94445
94759
  components,