import React , { useEffect , useReducer , useMemo , useRef , useImperativeHandle } from 'react' ; import MarkdownPreview , { MarkdownPreviewProps , MarkdownPreviewRef } from '@uiw/react-markdown-preview' ; import TextArea , { ITextAreaProps } from './components/TextArea' ; import Toolbar from './components/Toolbar' ; import DragBar from './components/DragBar' ; import { getCommands , getExtraCommands , ICommand } from './commands' ; import { reducer , EditorContext , ContextStore , PreviewType } from './Context' ; import './index.less' ; export interface IProps { prefixCls ? : string ; className ? : string ; } export interface MDEditorProps extends Omit < React . HTMLAttributes < HTMLDivElement > , 'onChange' > , IProps { /**
     * The Markdown value.
     */ value ? : string ; /**
     * Event handler for the `onChange` event.
     */ onChange ? : ( value ? : string ) => void ; /**
     * Can be used to make `Markdown Editor` focus itself on initialization. Defaults to on.
     * it will be set to true when either the source `textarea` is focused,
     * or it has an `autofocus` attribute and no other element is focused.
     */ autoFocus ? : ITextAreaProps [ 'autoFocus' ] ; /**
     * The height of the editor.
     */ height ? : number ; default toolbarHeight ? : number ; /**
     * Show drag and drop tool. Set the height of the editor.
     */ visiableDragbar ? : boolean ; /**
     * Show markdown preview.
     */ preview ? : PreviewType ; /**
     * Full screen display editor.
     */ fullscreen ? : boolean ; /**
     * Maximum drag height. `visiableDragbar=true`
     */ maxHeight ? : number ; /**
     * Minimum drag height. `visiableDragbar=true`
     */ minHeight ? : number ; /**
     * This is reset [react-markdown](https://github.com/rexxars/react-markdown) settings.
     */ previewOptions ? : Omit < MarkdownPreviewProps , 'source' > ; /**
     * Set the `textarea` related props.
     */ textareaProps ? : ITextAreaProps ; /** Use div to replace TextArea or re-render TextArea */ renderTextarea ? : ITextAreaProps [ 'renderTextarea' ] ; default highlightEnable ? : boolean ; /**
     * The number of characters to insert when pressing tab key.
     * Default `2` spaces.
     */ tabSize ? : number ; /**
     * If `false`, the `tab` key inserts a tab character into the textarea. If `true`, the `tab` key executes default behavior e.g. focus shifts to next element.
     */ defaultTabEnable ? : boolean ; /**
     * You can create your own commands or reuse existing commands.
     */ commands ? : ICommand [ ] ; /**
     * Filter or modify your commands.
     * https://github.com/uiwjs/react-md-editor/issues/296
     */ commandsFilter ? : ( command : ICommand , isExtra : boolean ) => false | ICommand ; /**
     * You can create your own commands or reuse existing commands.
     */ extraCommands ? : ICommand [ ] ; /**
     * Hide the tool bar
     */ hideToolbar ? : boolean ; /** Whether to enable scrolling */ enableScroll ? : boolean ; } function setGroupPopFalse ( data = { } ) { Object . keys ( data ) . forEach ( ( keyname ) => { data [ keyname ] = false ; } ) ; return data ; } const InternalMDEditor = ( props , ref ? ) => { const { prefixCls = 'w-md-editor' , className , value : propsValue , commands = getCommands ( ) , commandsFilter , extraCommands = getExtraCommands ( ) , height = 200 , toolbarHeight = 29 , enableScroll = true , visiableDragbar = true , highlightEnable = true , preview : previewType = 'live' , fullscreen = false , previewOptions = { } , textareaProps , maxHeight = 1200 , minHeight = 100 , autoFocus , tabSize = 2 , defaultTabEnable = false , onChange , hideToolbar , renderTextarea , ... other } = props || { } ; const cmds = commands . map ( ( item ) => ( commandsFilter ? commandsFilter ( item , false ) : item ) ) . filter ( Boolean ) as ICommand [ ] ; const extraCmds = extraCommands . map ( ( item ) => ( commandsFilter ? commandsFilter ( item , true ) : item ) ) . filter ( Boolean ) as ICommand [ ] ; let [ state , dispatch ] = useReducer ( reducer , { markdown : propsValue , preview : previewType , height , highlightEnable , tabSize , defaultTabEnable , scrollTop : 0 , scrollTopPreview : 0 , commands : cmds , extraCommands : extraCmds , fullscreen , barPopup : { } , } ) ; const container = useRef < HTMLDivElement > ( null ) ; const previewRef = useRef < MarkdownPreviewRef > ( null ) ; const enableScrollRef = useRef ( enableScroll ) ; useImperativeHandle ( ref , ( ) => ( { ... state } ) ) ; useMemo ( ( ) => ( enableScrollRef . current = enableScroll ) , [ enableScroll ] ) ; useEffect ( ( ) => { const stateInit = { } ; if ( container . current ) { stateInit . container = container . current || undefined ; } stateInit . markdown = propsValue || '' ; stateInit . barPopup = { } ; if ( dispatch ) { dispatch ( { ... state , ... stateInit } ) ; } } , [ ] ) ; const cls = [ className , prefixCls , state . preview ? `${ prefixCls }-show-${ state . preview }` : null , state . fullscreen ? `${ prefixCls }-fullscreen` : null , ] . filter ( Boolean ) . join ( ' ' ) . trim ( ) ; useMemo ( ( ) => propsValue !== state . markdown && dispatch ( { markdown : propsValue || '' } ) , [ propsValue , state . markdown ] ) ; useMemo ( ( ) => previewType !== state . preview && dispatch ( { preview : previewType } ) , [ previewType ] ) ; useMemo ( ( ) => height !== state . height && dispatch ( { height : height } ) , [ height ] ) ; useMemo ( ( ) => tabSize !== state . tabSize && dispatch ( { tabSize } ) , [ tabSize ] ) ; useMemo ( ( ) => highlightEnable !== state . highlightEnable && dispatch ( { highlightEnable } ) , [ highlightEnable ] ) ; useMemo ( ( ) => autoFocus !== state . autoFocus && dispatch ( { autoFocus : autoFocus } ) , [ autoFocus ] ) ; useMemo ( ( ) => fullscreen !== state . fullscreen && dispatch ( { fullscreen : fullscreen } ) , [ fullscreen ] ) ; const textareaDomRef = useRef < HTMLDivElement > ( ) ; const active = useRef < 'text' | 'preview' > ( 'preview' ) ; const initScroll = useRef ( false ) ; useMemo ( ( ) => { textareaDomRef . current = state . textareaWarp ; if ( state . textareaWarp ) { state . textareaWarp . addEventListener ( 'mouseover' , ( ) => { active . current = 'text' ; } ) ; state . textareaWarp . addEventListener ( 'mouseleave' , ( ) => { active . current = 'preview' ; } ) ; } } , [ state . textareaWarp ] ) ; const handleScroll = ( e , type ) => { if ( ! enableScrollRef . current ) return ; const textareaDom = textareaDomRef . current ; const previewDom = previewRef . current ? previewRef . current . mdp . current : undefined ; if ( ! initScroll . current ) { active . current = type ; initScroll . current = true ; } if ( textareaDom && previewDom ) { const scale = ( textareaDom . scrollHeight - textareaDom . offsetHeight ) / ( previewDom . scrollHeight - previewDom . offsetHeight ) ; if ( e . target === textareaDom && active . current === 'text' ) { previewDom . scrollTop = textareaDom . scrollTop / scale ; } if ( e . target === previewDom && active . current === 'preview' ) { textareaDom . scrollTop = previewDom . scrollTop * scale ; } let scrollTop = 0 ; if ( active . current === 'text' ) { scrollTop = textareaDom . scrollTop || 0 ; } else if ( active . current === 'preview' ) { scrollTop = previewDom . scrollTop || 0 ; } dispatch ( { scrollTop } ) ; } } ; return ( < EditorContext . Provider value = { { ... state , dispatch } } > < div ref = { container } className = { cls } { ... other } onClick = { ( ) => { dispatch ( { barPopup : { ... setGroupPopFalse ( state . barPopup ) } } ) ; } } style = { { ... other . style , height : state . fullscreen ? '100%' : hideToolbar ? Number ( state . height ) - toolbarHeight : state . height , } } > { ! hideToolbar && < Toolbar prefixCls = { prefixCls } height = { toolbarHeight } / > } < div className = { `${ prefixCls }-content` } style = { { height : state . fullscreen ? `calc(100% - ${ toolbarHeight }px)` : Number ( state . height ) - toolbarHeight , } } > { / ( edit | live ) / . test ( state . preview || '' ) && ( < TextArea className = { `${ prefixCls }-input` } prefixCls = { prefixCls } autoFocus = { autoFocus } { ... textareaProps } onChange = { ( evn ) => { onChange && onChange ( evn . target . value ) ; if ( textareaProps && textareaProps . onChange ) { textareaProps . onChange ( evn ) ; } } } renderTextarea = { renderTextarea } onScroll = { ( e ) => handleScroll ( e , 'text' ) } />)} { /(live|preview)/ . test ( state . preview || '' ) && ( < MarkdownPreview { ... previewOptions } onScroll = { ( e ) => handleScroll ( e , 'preview' ) } ref = { previewRef } source = { state . markdown || '' } className = { `${ prefixCls }-preview ${ previewOptions . className || '' }` } />)} < /div> { visiableDragbar && ! state . fullscreen && ( < DragBar prefixCls = { prefixCls } height = { state . height as number } maxHeight = { maxHeight ! } minHeight = { minHeight ! } onChange = { ( newHeight ) => { dispatch ( { height : newHeight } ) ; } } />)} < /div> < /EditorContext.Provider>); } ; const mdEditor = React . forwardRef < ContextStore , MDEditorProps > ( InternalMDEditor ) ; type MDEditor = typeof mdEditor & { Markdown : typeof MarkdownPreview ; } ; ( mdEditor as MDEditor ) . Markdown = MarkdownPreview ; export default mdEditor as MDEditor ;