11/* eslint-disable @typescript-eslint/no-var-requires */
2- import { useEffect , useState , useCallback , useRef } from 'react' ;
3- import CodeIcon from '@rsuite/icons/Code' ;
4- import classNames from 'classnames' ;
2+ import React from 'react' ;
53import MarkdownRenderer from './MarkdownRenderer' ;
6- import CodeEditor from './CodeEditor' ;
74import parseHTML from './utils/parseHTML' ;
8- import Preview from './Preview' ;
9- import canUseDOM from './utils/canUseDOM' ;
10-
11- const React = require ( 'react' ) ;
12- const ReactDOM = require ( 'react-dom' ) ;
13-
14- export interface CodeViewProps extends Omit < React . HTMLAttributes < HTMLElement > , 'onChange' > {
15- /** Code editor theme, applied to CodeMirror */
16- theme ?: 'light' | 'dark' ;
5+ import Renderer , { RendererProps } from './Renderer' ;
176
7+ export interface CodeViewProps extends RendererProps {
188 /** The code to be rendered is executed. Usually imported via markdown-loader. */
199 children ?: any ;
2010
2111 /** The code to be rendered is executed */
2212 sourceCode ?: string ;
23-
24- /** Dependent objects required by the executed code */
25- dependencies ?: object ;
26-
27- /** Renders a code editor that can modify the source code */
28- editable ?: boolean ;
29-
30- /** Editor properties */
31- editor ?: {
32- className ?: string ;
33-
34- /** Add a prefix to the className of the buttons on the toolbar */
35- classPrefix ?: string ;
36-
37- /** The className of the code button displayed on the toolbar */
38- buttonClassName ?: string ;
39-
40- /** Customize the code icon on the toolbar */
41- icon ?: React . ReactNode ;
42- } ;
43-
44- /**
45- * swc configuration
46- * https://swc.rs/docs/configuration/compilation
47- */
48- transformOptions ?: object ;
49-
50- /** Customize the rendering toolbar */
51- renderToolbar ?: ( buttons : React . ReactNode ) => React . ReactNode ;
52-
53- /** Callback triggered after code change */
54- onChange ?: ( code ?: string ) => void ;
55-
56- /**
57- * A compiler that transforms the code. Use swc.transformSync by default
58- * See https://swc.rs/docs/usage/wasm
59- */
60- compiler ?: ( code : string ) => string ;
61-
62- /** Executed before compiling the code */
63- beforeCompile ?: ( code : string ) => string ;
64-
65- /** Executed after compiling the code */
66- afterCompile ?: ( code : string ) => string ;
6713}
6814
69- const defaultTransformOptions = {
70- jsc : {
71- parser : {
72- syntax : 'ecmascript' ,
73- jsx : true
74- }
75- }
76- } ;
77-
7815const CodeView = React . forwardRef ( ( props : CodeViewProps , ref : React . Ref < HTMLDivElement > ) => {
7916 const {
8017 children,
18+ sourceCode,
8119 dependencies,
8220 editor = { } ,
8321 theme = 'light' ,
84- editable : isEditable = false ,
85- transformOptions = defaultTransformOptions ,
86- sourceCode,
22+ editable,
23+ transformOptions,
8724 renderToolbar,
8825 onChange,
8926 beforeCompile,
@@ -92,141 +29,33 @@ const CodeView = React.forwardRef((props: CodeViewProps, ref: React.Ref<HTMLDivE
9229 ...rest
9330 } = props ;
9431
95- const {
96- classPrefix,
97- icon : codeIcon ,
98- className : editorClassName ,
99- buttonClassName,
100- ...editorProps
101- } = editor ;
102-
103- const [ initialized , setInitialized ] = useState ( false ) ;
104- const transfrom = useRef < any > ( null ) ;
105-
106- useEffect ( ( ) => {
107- if ( ! canUseDOM ) {
108- return ;
109- }
110-
111- import ( '@swc/wasm-web' ) . then ( async module => {
112- await module . default ( ) ;
113- transfrom . current = module . transformSync ;
114- setInitialized ( true ) ;
115- } ) ;
116- } , [ ] ) ;
117-
11832 const sourceStr : string = children ?. __esModule ? children . default : sourceCode ;
119- const { code, beforeHTML, afterHTML } = parseHTML ( sourceStr ) || { } ;
120- const [ editable , setEditable ] = useState ( isEditable ) ;
121- const [ hasError , setHasError ] = useState ( false ) ;
122- const [ errorMessage , setErrorMessage ] = useState ( null ) ;
123- const [ compiledReactNode , setCompiledReactNode ] = useState ( null ) ;
124-
125- const handleExpandEditor = useCallback ( ( ) => {
126- setEditable ( ! editable ) ;
127- } , [ editable ] ) ;
128-
129- const handleError = useCallback ( error => {
130- setHasError ( true ) ;
131- setErrorMessage ( error . message ) ;
132- } , [ ] ) ;
133-
134- const prefix = name => ( classPrefix ? `${ classPrefix } -${ name } ` : name ) ;
135-
136- const executeCode = useCallback (
137- ( pendCode : string = code ) => {
138- if ( ! canUseDOM ) {
139- return ;
140- }
141-
142- const originalRender = ReactDOM . render ;
143-
144- // Redefine the render function, which will reset to the default value after `eval` is executed.
145- ReactDOM . render = element => {
146- setCompiledReactNode ( element ) ;
147- } ;
148-
149- try {
150- const statement = dependencies
151- ? Object . keys ( dependencies ) . map ( key => `var ${ key } = dependencies.${ key } ;` )
152- : [ ] ;
153-
154- const beforeCompileCode = beforeCompile ?.( pendCode ) || pendCode ;
155-
156- if ( beforeCompileCode ) {
157- const { code : compiledCode } = compiler
158- ? compiler ( beforeCompileCode )
159- : transfrom . current ?.( beforeCompileCode , transformOptions ) ;
160-
161- eval ( `${ statement . join ( '\n' ) } ${ afterCompile ?.( compiledCode ) || compiledCode } ` ) ;
162- }
163- } catch ( err ) {
164- console . error ( err ) ;
165- } finally {
166- // Reset the render function to the original value.
167- ReactDOM . render = originalRender ;
168- }
169- } ,
170- [ code , dependencies , beforeCompile , compiler , transformOptions , afterCompile ]
171- ) ;
172-
173- useEffect ( ( ) => {
174- if ( initialized ) {
175- executeCode ( code ) ;
176- }
177- } , [ initialized , code , executeCode ] ) ;
178-
179- const handleCodeChange = useCallback (
180- ( code ?: string ) => {
181- setHasError ( false ) ;
182- setErrorMessage ( null ) ;
183- onChange ?.( code ) ;
184-
185- if ( initialized ) {
186- executeCode ( code ) ;
187- }
188- } ,
189- [ executeCode , initialized , onChange ]
190- ) ;
191-
192- const codeButton = (
193- < button
194- role = "switch"
195- aria-checked = { editable }
196- aria-label = "Show the full source"
197- className = { classNames ( prefix ( 'btn' ) , prefix ( 'btn-xs' ) , buttonClassName ) }
198- onClick = { handleExpandEditor }
199- >
200- { typeof codeIcon !== 'undefined' ? (
201- codeIcon
202- ) : (
203- < CodeIcon className = { classNames ( prefix ( 'icon' ) , prefix ( 'icon-code' ) ) } />
204- ) }
205- </ button >
206- ) ;
207-
208- const showCodeEditor = editable && code && initialized ;
33+ const fragments = parseHTML ( sourceStr ) ;
20934
21035 return (
21136 < div ref = { ref } { ...rest } >
212- < MarkdownRenderer > { beforeHTML } </ MarkdownRenderer >
213- < div className = "rcv-container" >
214- < Preview hasError = { hasError } errorMessage = { errorMessage } onError = { handleError } >
215- { compiledReactNode }
216- </ Preview >
217- < div className = "rcv-toolbar" > { renderToolbar ? renderToolbar ( codeButton ) : codeButton } </ div >
218- { showCodeEditor && (
219- < CodeEditor
220- { ...editorProps }
221- key = "jsx"
222- onChange = { handleCodeChange }
223- className = { classNames ( editorClassName , 'rcv-editor' ) }
224- editorConfig = { { lineNumbers : true , theme : `base16-${ theme } ` } }
225- code = { code }
226- />
227- ) }
228- </ div >
229- < MarkdownRenderer > { afterHTML } </ MarkdownRenderer >
37+ { fragments ?. map ( fragment => {
38+ if ( fragment . type === 'code' ) {
39+ return (
40+ < Renderer
41+ key = { fragment . key }
42+ code = { fragment . content }
43+ editable = { editable }
44+ theme = { theme }
45+ dependencies = { dependencies }
46+ transformOptions = { transformOptions }
47+ renderToolbar = { renderToolbar }
48+ onChange = { onChange }
49+ beforeCompile = { beforeCompile }
50+ compiler = { compiler }
51+ afterCompile = { afterCompile }
52+ editor = { editor }
53+ />
54+ ) ;
55+ } else if ( fragment . type === 'html' ) {
56+ return < MarkdownRenderer key = { fragment . key } > { fragment . content } </ MarkdownRenderer > ;
57+ }
58+ } ) }
23059 </ div >
23160 ) ;
23261} ) ;
0 commit comments