@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.
@@ -59,10 +59,19 @@ function Anchor(props: Props) {
59
59
  const { children, href = '', target = '', title = '', ...attrs } = props;
60
60
  const baseUrl: string = useContext(BaseUrlContext);
61
61
 
62
+ // Unwrap any nested anchor elements that GFM's autolinker may have created.
63
+ // This prevents invalid nested <a> tags when the Anchor's text content looks like a URL.
64
+ const unwrappedChildren = React.Children.map(children, child => {
65
+ if (React.isValidElement(child) && child.type === 'a') {
66
+ return child.props.children;
67
+ }
68
+ return child;
69
+ });
70
+
62
71
  return (
63
72
  // eslint-disable-next-line react/jsx-props-no-spreading
64
73
  <a {...attrs} href={getHref(href, baseUrl)} target={target} title={title} {...docLink(href)}>
65
- {children}
74
+ {unwrappedChildren}
66
75
  </a>
67
76
  );
68
77
  }
@@ -61,7 +61,8 @@ const Code = (props: CodeProps) => {
61
61
  const copyButtons = useContext(CodeOptsContext) || props.copyButtons;
62
62
  const isHydrated = useHydrated();
63
63
 
64
- const language = isHydrated ? canonicalLanguage(lang) : '';
64
+ const language = canonicalLanguage(lang);
65
+ const isMermaid = language === 'mermaid';
65
66
 
66
67
  const codeRef = createRef<HTMLElement>();
67
68
 
@@ -78,7 +79,7 @@ const Code = (props: CodeProps) => {
78
79
  ? syntaxHighlighter(code, language, codeOpts, { mdx: true })
79
80
  : code;
80
81
 
81
- if (language === 'mermaid') {
82
+ if (isHydrated && isMermaid) {
82
83
  return code;
83
84
  }
84
85
 
@@ -4,6 +4,7 @@ import syntaxHighlighterUtils from '@readme/syntax-highlighter/utils';
4
4
  import React, { useContext, useEffect } from 'react';
5
5
 
6
6
  import ThemeContext from '../../contexts/Theme';
7
+ import useHydrated from '../../hooks/useHydrated';
7
8
 
8
9
  let mermaid: Mermaid;
9
10
 
@@ -16,11 +17,26 @@ interface Props {
16
17
  const CodeTabs = (props: Props) => {
17
18
  const { children } = props;
18
19
  const theme = useContext(ThemeContext);
19
- const hasMermaid = !Array.isArray(children) && children.props?.children.props.lang === 'mermaid';
20
+ const isHydrated = useHydrated();
20
21
 
21
- // render Mermaid diagram
22
+ // Handle both array (from rehype-react in rendering mdxish) and single element (MDX/JSX runtime) cases
23
+ // The children here is the individual code block objects
24
+ const childrenArray = Array.isArray(children) ? children : [children];
25
+
26
+ // The structure varies depending on rendering context:
27
+ // - When rendered via rehype-react: pre.props.children is an array where the first element is the Code component
28
+ // - When rendered via MDX/JSX runtime: pre.props.children is directly the Code component
29
+ const getCodeComponent = (pre: JSX.Element) => {
30
+ return Array.isArray(pre?.props?.children) ? pre.props.children[0] : pre?.props?.children;
31
+ };
32
+
33
+ const containAtLeastOneMermaid = childrenArray.some(pre => getCodeComponent(pre)?.props?.lang === 'mermaid');
34
+
35
+ // Render Mermaid diagram
22
36
  useEffect(() => {
23
- if (typeof window !== 'undefined' && hasMermaid) {
37
+ // Ensure we only render mermaids when frontend is hydrated to avoid hydration errors
38
+ // because mermaid mutates the DOM before react hydrates
39
+ if (typeof window !== 'undefined' && containAtLeastOneMermaid && isHydrated) {
24
40
  import('mermaid').then(module => {
25
41
  mermaid = module.default;
26
42
  mermaid.initialize({
@@ -32,7 +48,7 @@ const CodeTabs = (props: Props) => {
32
48
  });
33
49
  });
34
50
  }
35
- }, [hasMermaid, theme]);
51
+ }, [containAtLeastOneMermaid, theme, isHydrated]);
36
52
 
37
53
  function handleClick({ target }, index: number) {
38
54
  const $wrap = target.parentElement.parentElement;
@@ -46,22 +62,25 @@ const CodeTabs = (props: Props) => {
46
62
  target.classList.add('CodeTabs_active');
47
63
  }
48
64
 
49
- // render single Mermaid diagram
50
- if (hasMermaid) {
51
- const value = children.props.children.props.value;
52
- return <pre className="mermaid-render mermaid_single">{value}</pre>;
65
+ // We want to render single mermaid diagrams without the code tabs UI
66
+ if (childrenArray.length === 1) {
67
+ const codeComponent = getCodeComponent(childrenArray[0]);
68
+ if (codeComponent?.props?.lang === 'mermaid') {
69
+ const value = codeComponent?.props?.value;
70
+ return <pre className="mermaid-render mermaid_single">{value}</pre>;
71
+ }
53
72
  }
54
73
 
55
74
  return (
56
75
  <div className={`CodeTabs CodeTabs_initial theme-${theme}`}>
57
76
  <div className="CodeTabs-toolbar">
58
- {(Array.isArray(children) ? children : [children]).map((pre, i) => {
77
+ {childrenArray.map((pre, i) => {
59
78
  // the first or only child should be our Code component
60
- const codeComponent = Array.isArray(pre.props?.children)
79
+ const tabCodeComponent = Array.isArray(pre.props?.children)
61
80
  ? pre.props.children[0]
62
81
  : pre.props?.children;
63
- const lang = codeComponent?.props?.lang;
64
- const meta = codeComponent?.props?.meta;
82
+ const lang = tabCodeComponent?.props?.lang;
83
+ const meta = tabCodeComponent?.props?.meta;
65
84
 
66
85
  /* istanbul ignore next */
67
86
  return (
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ declare const utils: {
6
6
  getHref: typeof getHref;
7
7
  calloutIcons: {};
8
8
  };
9
- export { compile, exports, hast, run, mdast, mdastV6, mdx, mdxish, mdxishTags, migrate, mix, plain, renderMdxish, remarkPlugins, stripComments, tags, } from './lib';
9
+ export { compile, exports, hast, run, mdast, mdastV6, mdx, mdxish, mdxishAstProcessor, mdxishMdastToMd, mdxishTags, migrate, mix, plain, renderMdxish, remarkPlugins, stripComments, tags, } from './lib';
10
10
  export { default as Owlmoji } from './lib/owlmoji';
11
11
  export { Components, utils };
12
12
  export { tailwindCompiler } from './utils/tailwind-compiler';
@@ -7,7 +7,7 @@ export { default as mdast } from './mdast';
7
7
  export { default as mdastV6 } from './mdastV6';
8
8
  export { default as mdx } from './mdx';
9
9
  export { default as mix } from './mix';
10
- export { default as mdxish } from './mdxish';
10
+ export { default as mdxish, mdxishAstProcessor, mdxishMdastToMd } from './mdxish';
11
11
  export type { MdxishOpts } from './mdxish';
12
12
  export { default as migrate } from './migrate';
13
13
  export { default as plain } from './plain';
@@ -1,13 +1,27 @@
1
1
  import type { CustomComponents } from '../types';
2
2
  import type { Root } from 'hast';
3
+ import type { Root as MdastRoot } from 'mdast';
3
4
  import { type JSXContext } from '../processor/transform/mdxish/preprocess-jsx-expressions';
4
5
  export interface MdxishOpts {
5
6
  components?: CustomComponents;
6
7
  jsxContext?: JSXContext;
7
8
  useTailwind?: boolean;
8
9
  }
10
+ export declare function mdxishAstProcessor(mdContent: string, opts?: MdxishOpts): {
11
+ processor: import("unified").Processor<MdastRoot, import("unist").Node, import("unist").Node, undefined, undefined>;
12
+ /**
13
+ * @todo we need to return this transformed content for now
14
+ * but ultimately need to properly tokenize our special markdown syntax
15
+ * into hast nodes instead of relying on transformed content
16
+ */
17
+ parserReadyContent: string;
18
+ };
9
19
  /**
10
- * Process markdown content with MDX syntax support.
20
+ * Converts an Mdast to a Markdown string.
21
+ */
22
+ export declare function mdxishMdastToMd(mdast: MdastRoot): string;
23
+ /**
24
+ * Processes markdown content with MDX syntax support and returns a HAST.
11
25
  * Detects and renders custom component tags from the components hash.
12
26
  *
13
27
  * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}