Skip to content

Commit 6127779

Browse files
committed
Print async react directives
1 parent 338e50b commit 6127779

File tree

1 file changed

+129
-15
lines changed

1 file changed

+129
-15
lines changed

src/examples/print.stories.tsx

Lines changed: 129 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,70 @@
11
import React from 'react'
2-
import { MDXEditor, MDXEditorMethods } from '../'
3-
import demoMarkdown from './assets/live-demo-contents.md?raw'
4-
import { ALL_PLUGINS } from './_boilerplate'
2+
import { MDXEditor, MDXEditorMethods, directivesPlugin } from '../'
3+
4+
interface AsyncDirectiveContextType {
5+
registerDirective: (id: string) => void
6+
markReady: (id: string) => void
7+
waitForAllReady: () => Promise<void>
8+
}
9+
10+
const AsyncDirectiveContext = React.createContext<AsyncDirectiveContextType | null>(null)
11+
12+
export function AsyncDirectiveProvider({ children }: { children: React.ReactNode }) {
13+
const directivesRef = React.useRef<Map<string, boolean>>(new Map())
14+
const resolverRef = React.useRef<(() => void) | null>(null)
15+
const promiseRef = React.useRef<Promise<void> | null>(null)
16+
17+
const registerDirective = React.useCallback((id: string) => {
18+
directivesRef.current.set(id, false)
19+
}, [])
20+
21+
const markReady = React.useCallback((id: string) => {
22+
directivesRef.current.set(id, true)
23+
const allReady = directivesRef.current.size === 0 || Array.from(directivesRef.current.values()).every((ready) => ready)
24+
25+
if (allReady && resolverRef.current) {
26+
resolverRef.current()
27+
resolverRef.current = null
28+
promiseRef.current = null
29+
directivesRef.current.clear()
30+
}
31+
}, [])
32+
33+
const waitForAllReady = React.useCallback(() => {
34+
const allReady = directivesRef.current.size === 0 || Array.from(directivesRef.current.values()).every((ready) => ready)
35+
36+
if (allReady) {
37+
return Promise.resolve()
38+
}
39+
40+
if (!promiseRef.current) {
41+
promiseRef.current = new Promise<void>((resolve) => {
42+
resolverRef.current = resolve
43+
})
44+
}
45+
46+
return promiseRef.current
47+
}, [])
48+
49+
const value = React.useMemo(
50+
() => ({
51+
registerDirective,
52+
markReady,
53+
waitForAllReady
54+
}),
55+
[registerDirective, markReady, waitForAllReady]
56+
)
57+
58+
return <AsyncDirectiveContext.Provider value={value}>{children}</AsyncDirectiveContext.Provider>
59+
}
60+
61+
export function useAsyncDirectiveContext() {
62+
const context = React.useContext(AsyncDirectiveContext)
63+
if (!context) {
64+
throw new Error('useAsyncDirectiveContext must be used within AsyncDirectiveProvider')
65+
}
66+
return context
67+
}
568

669
function printHTML(html: string) {
770
const printWindow = window.open('', '', 'width=800,height=600')
@@ -18,31 +81,82 @@ function printHTML(html: string) {
1881
printWindow.close()
1982
}
2083

21-
export function Basics() {
84+
const markdownWithAsyncDirective = `
85+
Hello world!
86+
87+
::asyncDirective
88+
`
89+
90+
function BasicsContent() {
2291
const mdxEditorRef = React.useRef<MDXEditorMethods>(null)
92+
const { waitForAllReady } = useAsyncDirectiveContext()
2393

2494
return (
2595
<div>
2696
<button
2797
onClick={async () => {
28-
mdxEditorRef.current?.setMarkdown(demoMarkdown)
98+
mdxEditorRef.current?.setMarkdown(markdownWithAsyncDirective)
2999
// skip one tick to allow the editor to update
30100
await Promise.resolve()
101+
// wait for all async directives to be ready
102+
await waitForAllReady()
31103
printHTML(mdxEditorRef.current?.getContentEditableHTML() ?? '')
32104
}}
33105
>
34106
Print the contents of the editor
35107
</button>
36-
<div style={{ position: 'absolute', visibility: 'hidden' }}>
37-
<MDXEditor
38-
ref={mdxEditorRef}
39-
markdown={''}
40-
onChange={(md) => {
41-
console.log('change', { md })
42-
}}
43-
plugins={ALL_PLUGINS}
44-
/>
45-
</div>
108+
<MDXEditor
109+
ref={mdxEditorRef}
110+
markdown={''}
111+
plugins={[
112+
directivesPlugin({
113+
directiveDescriptors: [
114+
{
115+
name: 'asyncDirective',
116+
testNode: (node) => node.name === 'asyncDirective',
117+
attributes: [],
118+
hasChildren: false,
119+
Editor: () => {
120+
const [isReady, setIsReady] = React.useState(false)
121+
const { registerDirective, markReady } = useAsyncDirectiveContext()
122+
const directiveIdRef = React.useRef(`async-directive-${Math.random()}`)
123+
124+
React.useLayoutEffect(() => {
125+
const id = directiveIdRef.current
126+
registerDirective(id)
127+
128+
const timeout = setTimeout(() => {
129+
setIsReady(true)
130+
// wait for the set state to re-render
131+
setTimeout(() => {
132+
markReady(id)
133+
}, 50)
134+
}, 500)
135+
136+
return () => {
137+
clearTimeout(timeout)
138+
}
139+
}, [registerDirective, markReady])
140+
141+
if (!isReady) {
142+
return <div>Loading async directive...</div>
143+
}
144+
145+
return <div>Async Directive Content</div>
146+
}
147+
}
148+
]
149+
})
150+
]}
151+
/>
46152
</div>
47153
)
48154
}
155+
156+
export function Basics() {
157+
return (
158+
<AsyncDirectiveProvider>
159+
<BasicsContent />
160+
</AsyncDirectiveProvider>
161+
)
162+
}

0 commit comments

Comments
 (0)