import { CompositeDecorator , DefaultDraftBlockRenderMap , DraftBlockRenderMap , DraftDecorator , DraftEditorCommand , DraftStyleMap , Editor , EditorProps , EditorState , } from 'draft-js' ; import { Map } from 'immutable' ; import PropTypes from 'prop-types' ; import React , { Component , KeyboardEvent , ReactElement } from 'react' ; import { AriaProps , EditorPlugin , EditorRef , PluginFunctions } from '..' ; import { keyBindingFn } from './defaultKeyBindings' ; import { handleKeyCommand } from './defaultKeyCommands' ; import { createPluginHooks } from './PluginHooks' ; import resolveDecorators from './resolveDecorators' ; export interface PluginEditorProps extends Omit < EditorProps , 'keyBindingFn' > { plugins ? : EditorPlugin [ ] ; defaultKeyBindings ? : boolean ; defaultKeyCommands ? : boolean ; defaultBlockRenderMap ? : boolean ; keyBindingFn ? ( event ) : DraftEditorCommand | string | null | undefined ; decorators ? : Array < CompositeDecorator | DraftDecorator > ; } interface DecoratorType { decorators ? : Immutable . List < string > ; _decorators ? : string [ ] ; } const getDecoratorLength = ( obj ? ) : number | undefined => { if ( obj ?. decorators != null ) { return obj . decorators ?. size ; } else if ( obj ?. _decorators != null ) { return obj . _decorators ?. length ; } return undefined ; } ; /**
 * The main editor component
 */ class PluginEditor extends Component < PluginEditorProps > { static propTypes = { editorState : PropTypes . object . isRequired , onChange : PropTypes . func . isRequired , plugins : PropTypes . array , defaultKeyBindings : PropTypes . bool , defaultKeyCommands : PropTypes . bool , defaultBlockRenderMap : PropTypes . bool , customStyleMap : PropTypes . object , decorators : PropTypes . array , } ; static defaultProps = { defaultBlockRenderMap : true , defaultKeyBindings : true , defaultKeyCommands : true , customStyleMap : { } , plugins : [ ] , decorators : [ ] , } as Partial < PluginEditorProps > ; editor : Editor | null = null ; state = { readOnly : false , } ; constructor ( props ) { super ( props ) ; const plugins = [ this . props , ... this . resolvePlugins ( ) ] as EditorPlugin [ ] ; plugins . forEach ( ( plugin ) => { if ( plugin && typeof plugin . initialize === 'function' ) { plugin . initialize ( this . getPluginMethods ( ) ) ; } } ) ; } focus ( ) { if ( this . editor ) { this . editor . focus ( ) ; } } blur ( ) { if ( this . editor ) { this . editor . blur ( ) ; } } componentDidMount ( ) { const decorator = resolveDecorators ( this . props , this . getEditorState , this . onChange ) ; const editorState = EditorState . set ( this . props . editorState , { decorator } ) ; this . onChange ( EditorState . moveFocusToEnd ( editorState ) ) ; } componentDidUpdate ( prevProps ) { const next = this . props ; const currDec = prevProps . editorState . getDecorator ( ) ; const nextDec = next . editorState . getDecorator ( ) ; if ( ! currDec ) { return ; } if ( currDec === nextDec ) { return ; } if ( currDec && nextDec && getDecoratorLength ( currDec as DecoratorType ) === getDecoratorLength ( nextDec as DecoratorType ) ) { return ; } const editorState = EditorState . set ( next . editorState , { decorator : currDec , } ) ; this . onChange ( EditorState . moveFocusToEnd ( editorState ) ) ; } componentWillUnmount ( ) { this . resolvePlugins ( ) . forEach ( ( plugin ) => { if ( plugin . willUnmount ) { plugin . willUnmount ( { getEditorState : this . getEditorState , setEditorState : this . onChange , } ) ; } } ) ; } onChange = ( editorState ) : void => { let newEditorState = editorState ; this . resolvePlugins ( ) . forEach ( ( plugin ) => { if ( plugin . onChange ) { newEditorState = plugin . onChange ( newEditorState , this . getPluginMethods ( ) ) ; } } ) ; if ( this . props . onChange ) { this . props . onChange ( newEditorState ) ; } } ; getPlugins = ( ) : EditorPlugin [ ] => [ ... this . props . plugins ! ] ; getProps = ( ) : PluginEditorProps => ( { ... this . props } ) ; getReadOnly = ( ) : boolean => this . props . readOnly || this . state . readOnly ; setReadOnly = ( readOnly ) : void => { if ( readOnly !== this . state . readOnly ) { this . setState ( { readOnly } ) ; } } ; getEditorRef = ( ) : EditorRef => this . editor as unknown as EditorRef ; getEditorState = ( ) : EditorState => this . props . editorState ; getPluginMethods = ( ) : PluginFunctions => ( { getPlugins : this . getPlugins , getProps : this . getProps , setEditorState : this . onChange , getEditorState : this . getEditorState , getReadOnly : this . getReadOnly , setReadOnly : this . setReadOnly , getEditorRef : this . getEditorRef , } ) ; createPluginHooks = ( ) : Partial < EditorProps > => { const plugins = [ this . props , ... this . resolvePlugins ( ) ] as EditorPlugin [ ] ; return createPluginHooks ( plugins , this . getPluginMethods ( ) ) ; } ; resolvePlugins = ( ) : EditorPlugin [ ] => { const plugins = this . getPlugins ( ) ; if ( this . props . defaultKeyBindings === true ) { plugins . push ( { keyBindingFn } ) ; } if ( this . props . defaultKeyCommands === true ) { plugins . push ( { handleKeyCommand } ) ; } return plugins ; } ; resolveCustomStyleMap = ( ) : DraftStyleMap => { const customStyleMap = this . props . plugins ! . filter ( ( plug ) => plug . customStyleMap !== undefined ) . map ( ( plug ) => plug . customStyleMap ) as DraftStyleMap [ ] ; return customStyleMap . concat ( [ this . props . customStyleMap ! ] ) . reduce < DraftStyleMap > ( ( styles , style ) => ( { ... styles , ... style , } ) , { } ) ; } ; resolveblockRenderMap = ( ) : DraftBlockRenderMap => { let blockRenderMap = this . props . plugins ! . filter ( ( plug ) => plug . blockRenderMap !== undefined ) . reduce ( ( maps , plug ) => maps . merge ( plug . blockRenderMap ! ) , Map ( { } ) ) as DraftBlockRenderMap ; if ( this . props . defaultBlockRenderMap ) { blockRenderMap = DefaultDraftBlockRenderMap . merge ( blockRenderMap ) ; } if ( this . props . blockRenderMap ) { blockRenderMap = blockRenderMap . merge ( this . props . blockRenderMap ) ; } return blockRenderMap ; } ; resolveAccessibilityProps = ( ) : AriaProps => { let accessibilityProps = { } ; this . resolvePlugins ( ) . forEach ( ( plugin ) => { if ( typeof plugin . getAccessibilityProps !== 'function' ) { return ; } const props = plugin . getAccessibilityProps ( ) ; const popupProps = { } ; if ( accessibilityProps . ariaHasPopup === undefined ) { popupProps . ariaHasPopup = props . ariaHasPopup ; } else if ( props . ariaHasPopup === 'true' ) { popupProps . ariaHasPopup = 'true' ; } if ( accessibilityProps . ariaExpanded === undefined ) { popupProps . ariaExpanded = props . ariaExpanded ; } else if ( props . ariaExpanded === true ) { popupProps . ariaExpanded = true ; } accessibilityProps = { ... accessibilityProps , ... props , ... popupProps , } ; } ) ; return accessibilityProps ; } ; render ( ) { const pluginHooks = this . createPluginHooks ( ) ; const customStyleMap = this . resolveCustomStyleMap ( ) ; const accessibilityProps = this . resolveAccessibilityProps ( ) ; const blockRenderMap = this . resolveblockRenderMap ( ) ; const { keyBindingFn , ... editorProps } = this . props ; return ( < Editor { ... editorProps } { ... accessibilityProps } { ... pluginHooks } readOnly = { this . props . readOnly || this . state . readOnly } customStyleMap = { customStyleMap } blockRenderMap = { blockRenderMap } onChange = { this . onChange } editorState = { this . props . editorState } ref = { ( element ) => { this . editor = element ; } } />); } } export default PluginEditor ;