@readme/markdown 13.7.2 → 13.7.4
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/components/CodeTabs/index.tsx +62 -19
- package/components/Glossary/index.tsx +3 -0
- package/components/TailwindStyle/index.tsx +8 -17
- package/dist/lib/mdast-util/gemoji/index.d.ts +2 -0
- package/dist/lib/micromark/gemoji/index.d.ts +4 -0
- package/dist/lib/micromark/gemoji/syntax.d.ts +9 -0
- package/dist/main.js +738 -232
- package/dist/main.node.js +738 -232
- package/dist/main.node.js.map +1 -1
- package/dist/processor/transform/extract-text.d.ts +1 -1
- package/dist/processor/transform/mdxish/close-self-closing-html-tags.d.ts +23 -0
- package/dist/processor/transform/mdxish/magic-blocks/types.d.ts +0 -12
- package/dist/processor/transform/mdxish/mdxish-jsx-to-mdast.d.ts +5 -3
- package/dist/processor/transform/mdxish/types.d.ts +15 -0
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Mermaid } from 'mermaid';
|
|
2
2
|
|
|
3
3
|
import syntaxHighlighterUtils from '@readme/syntax-highlighter/utils';
|
|
4
|
-
import React, { useContext, useEffect } from 'react';
|
|
4
|
+
import React, { useContext, useEffect, useRef } from 'react';
|
|
5
5
|
|
|
6
6
|
import ThemeContext from '../../contexts/Theme';
|
|
7
7
|
import useHydrated from '../../hooks/useHydrated';
|
|
@@ -10,6 +10,47 @@ let mermaid: Mermaid;
|
|
|
10
10
|
|
|
11
11
|
const { uppercase } = syntaxHighlighterUtils;
|
|
12
12
|
|
|
13
|
+
// Module-level queue that batches mermaid nodes across all CodeTabs instances into a
|
|
14
|
+
// single mermaid.run() call. This is necessary because mermaid generates SVG element IDs
|
|
15
|
+
// using Date.now(), which collides when multiple diagrams render in the same millisecond.
|
|
16
|
+
// Colliding IDs cause diagrams to overlap or break layout.
|
|
17
|
+
//
|
|
18
|
+
// Why not use `deterministicIDSeed` with a unique ID per diagram? Mermaid's implementation
|
|
19
|
+
// only uses seed.length (not the seed value) to compute the starting ID, so every UUID
|
|
20
|
+
// (36 chars) produces the same `mermaid-36` prefix — the collision remains.
|
|
21
|
+
// See: https://github.com/mermaid-js/mermaid/blob/mermaid%4011.12.0/packages/mermaid/src/utils.ts#L755-L761
|
|
22
|
+
//
|
|
23
|
+
// These vars must be module-scoped (not per-instance refs) because the batching requires
|
|
24
|
+
// cross-instance coordination. They are short-lived: the queue drains on the next macrotask
|
|
25
|
+
// and cleanup clears everything on unmount.
|
|
26
|
+
let mermaidQueue: HTMLPreElement[] = [];
|
|
27
|
+
let mermaidFlushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
28
|
+
let currentTheme: string | undefined;
|
|
29
|
+
|
|
30
|
+
function queueMermaidNode(node: HTMLPreElement, theme: string) {
|
|
31
|
+
mermaidQueue.push(node);
|
|
32
|
+
currentTheme = theme;
|
|
33
|
+
|
|
34
|
+
if (!mermaidFlushTimer) {
|
|
35
|
+
// setTimeout(0) defers to a macrotask, after all useEffects have queued their nodes
|
|
36
|
+
mermaidFlushTimer = setTimeout(async () => {
|
|
37
|
+
const nodes = [...mermaidQueue];
|
|
38
|
+
mermaidQueue = [];
|
|
39
|
+
mermaidFlushTimer = null;
|
|
40
|
+
|
|
41
|
+
const module = await import('mermaid');
|
|
42
|
+
mermaid = module.default;
|
|
43
|
+
mermaid.initialize({
|
|
44
|
+
startOnLoad: false,
|
|
45
|
+
theme: currentTheme === 'dark' ? 'dark' : 'default',
|
|
46
|
+
deterministicIds: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await mermaid.run({ nodes });
|
|
50
|
+
}, 0);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
13
54
|
interface Props {
|
|
14
55
|
children: JSX.Element | JSX.Element[];
|
|
15
56
|
}
|
|
@@ -18,6 +59,7 @@ const CodeTabs = (props: Props) => {
|
|
|
18
59
|
const { children } = props;
|
|
19
60
|
const theme = useContext(ThemeContext);
|
|
20
61
|
const isHydrated = useHydrated();
|
|
62
|
+
const mermaidRef = useRef<HTMLPreElement>(null);
|
|
21
63
|
|
|
22
64
|
// Handle both array (from rehype-react in rendering mdxish) and single element (MDX/JSX runtime) cases
|
|
23
65
|
// The children here is the individual code block objects
|
|
@@ -32,22 +74,21 @@ const CodeTabs = (props: Props) => {
|
|
|
32
74
|
|
|
33
75
|
const containAtLeastOneMermaid = childrenArray.some(pre => getCodeComponent(pre)?.props?.lang === 'mermaid');
|
|
34
76
|
|
|
35
|
-
// Render Mermaid diagram
|
|
36
77
|
useEffect(() => {
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
import('mermaid').then(module => {
|
|
41
|
-
mermaid = module.default;
|
|
42
|
-
mermaid.initialize({
|
|
43
|
-
startOnLoad: false,
|
|
44
|
-
theme: theme === 'dark' ? 'dark' : 'default',
|
|
45
|
-
});
|
|
46
|
-
mermaid.run({
|
|
47
|
-
nodes: document.querySelectorAll('.mermaid-render'),
|
|
48
|
-
});
|
|
49
|
-
});
|
|
78
|
+
// Wait for hydration so mermaid's DOM mutations don't cause mismatches
|
|
79
|
+
if (typeof window !== 'undefined' && containAtLeastOneMermaid && isHydrated && mermaidRef.current) {
|
|
80
|
+
queueMermaidNode(mermaidRef.current, theme);
|
|
50
81
|
}
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
// Clear the batch timer on unmount to prevent mermaid from running
|
|
85
|
+
// after the DOM is torn down
|
|
86
|
+
if (mermaidFlushTimer) {
|
|
87
|
+
clearTimeout(mermaidFlushTimer);
|
|
88
|
+
mermaidFlushTimer = null;
|
|
89
|
+
mermaidQueue = [];
|
|
90
|
+
}
|
|
91
|
+
};
|
|
51
92
|
}, [containAtLeastOneMermaid, theme, isHydrated]);
|
|
52
93
|
|
|
53
94
|
function handleClick({ target }, index: number) {
|
|
@@ -67,7 +108,11 @@ const CodeTabs = (props: Props) => {
|
|
|
67
108
|
const codeComponent = getCodeComponent(childrenArray[0]);
|
|
68
109
|
if (codeComponent?.props?.lang === 'mermaid') {
|
|
69
110
|
const value = codeComponent?.props?.value;
|
|
70
|
-
return
|
|
111
|
+
return (
|
|
112
|
+
<pre ref={mermaidRef} className="mermaid-render mermaid_single">
|
|
113
|
+
{value}
|
|
114
|
+
</pre>
|
|
115
|
+
);
|
|
71
116
|
}
|
|
72
117
|
}
|
|
73
118
|
|
|
@@ -76,9 +121,7 @@ const CodeTabs = (props: Props) => {
|
|
|
76
121
|
<div className="CodeTabs-toolbar">
|
|
77
122
|
{childrenArray.map((pre, i) => {
|
|
78
123
|
// the first or only child should be our Code component
|
|
79
|
-
const tabCodeComponent = Array.isArray(pre.props?.children)
|
|
80
|
-
? pre.props.children[0]
|
|
81
|
-
: pre.props?.children;
|
|
124
|
+
const tabCodeComponent = Array.isArray(pre.props?.children) ? pre.props.children[0] : pre.props?.children;
|
|
82
125
|
const lang = tabCodeComponent?.props?.lang;
|
|
83
126
|
const meta = tabCodeComponent?.props?.meta;
|
|
84
127
|
|
|
@@ -12,6 +12,9 @@ interface Props extends React.PropsWithChildren {
|
|
|
12
12
|
|
|
13
13
|
const Glossary = ({ children, term: termProp, terms }: Props) => {
|
|
14
14
|
const term = (Array.isArray(children) ? children[0] : children) || termProp;
|
|
15
|
+
|
|
16
|
+
if (!term) return null;
|
|
17
|
+
|
|
15
18
|
const foundTerm = terms.find(i => term.toLowerCase() === i?.term?.toLowerCase());
|
|
16
19
|
|
|
17
20
|
if (!foundTerm) return <span>{term}</span>;
|
|
@@ -75,20 +75,10 @@ const TailwindStyle = ({ children, darkModeDataAttribute }: Props) => {
|
|
|
75
75
|
records.forEach(record => {
|
|
76
76
|
if (record.type === 'childList') {
|
|
77
77
|
record.addedNodes.forEach(node => {
|
|
78
|
-
if (!(node instanceof HTMLElement)) return;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
traverse(node, addClasses);
|
|
83
|
-
shouldUpdate = true;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Also check descendants — React may insert a parent node
|
|
87
|
-
// whose children contain TailwindRoot elements
|
|
88
|
-
node.querySelectorAll(`.${tailwindPrefix}`).forEach(child => {
|
|
89
|
-
traverse(child, addClasses);
|
|
90
|
-
shouldUpdate = true;
|
|
91
|
-
});
|
|
78
|
+
if (!(node instanceof HTMLElement) || !node.classList.contains(tailwindPrefix)) return;
|
|
79
|
+
|
|
80
|
+
traverse(node, addClasses);
|
|
81
|
+
shouldUpdate = true;
|
|
92
82
|
});
|
|
93
83
|
} else if (record.type === 'attributes') {
|
|
94
84
|
if (record.attributeName !== 'class' || !(record.target instanceof HTMLElement)) return;
|
|
@@ -97,10 +87,11 @@ const TailwindStyle = ({ children, darkModeDataAttribute }: Props) => {
|
|
|
97
87
|
shouldUpdate = true;
|
|
98
88
|
}
|
|
99
89
|
|
|
100
|
-
if (shouldUpdate) {
|
|
101
|
-
setClasses(Array.from(classesSet.current));
|
|
102
|
-
}
|
|
103
90
|
});
|
|
91
|
+
|
|
92
|
+
if (shouldUpdate) {
|
|
93
|
+
setClasses(Array.from(classesSet.current));
|
|
94
|
+
}
|
|
104
95
|
});
|
|
105
96
|
|
|
106
97
|
observer.observe(ref.current.parentElement, {
|