11import 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
669function 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