import React from 'react' ; import ReactDOM from 'react-dom' ; import './index.less' ; import { Editor , EditorState , RichUtils , getDefaultKeyBinding , convertToRaw , convertFromRaw , } from 'draft-js' ; import { BoldOutlined , UnorderedListOutlined , OrderedListOutlined , ItalicOutlined , UnderlineOutlined , CodeOutlined , MenuUnfoldOutlined , SendOutlined , } from '@ant-design/icons' ; import { Tooltip } from 'antd' ; import { convertToHTML } from 'draft-convert' ; import renderHTML from 'react-render-html' ; import { connect } from 'react-redux' ; const mapDispatchToProps = ( dispatch ) => { return { addNote : ( payload ) => dispatch ( { type : 'ADD_NOTE' , payload } ) , updateNote : ( payload ) => dispatch ( { type : 'UPDATE_NOTE' , payload } ) , } ; } ; class RichEditor extends React . Component { constructor ( props ) { super ( props ) ; if ( props . contentState ) { const contentState = convertFromRaw ( props . contentState ) ; const editorState = EditorState . createWithContent ( contentState ) ; this . state = { editorState : editorState } ; } else { this . state = { editorState : EditorState . createEmpty ( ) } ; } this . focus = ( ) => this . refs . editor . focus ( ) ; this . onChange = ( editorState ) => this . setState ( { editorState } ) ; this . handleKeyCommand = this . _handleKeyCommand . bind ( this ) ; this . mapKeyToEditorCommand = this . _mapKeyToEditorCommand . bind ( this ) ; this . toggleBlockType = this . _toggleBlockType . bind ( this ) ; this . toggleInlineStyle = this . _toggleInlineStyle . bind ( this ) ; } _handleKeyCommand ( command , editorState ) { const newState = RichUtils . handleKeyCommand ( editorState , command ) ; if ( newState ) { this . onChange ( newState ) ; return true ; } return false ; } _mapKeyToEditorCommand ( e ) { if ( e . keyCode === 9 ) { const newEditorState = RichUtils . onTab ( e , this . state . editorState , 4 ) ; if ( newEditorState !== this . state . editorState ) { this . onChange ( newEditorState ) ; } return ; } return getDefaultKeyBinding ( e ) ; } _toggleBlockType ( blockType ) { this . onChange ( RichUtils . toggleBlockType ( this . state . editorState , blockType ) ) ; } _toggleInlineStyle ( inlineStyle ) { this . onChange ( RichUtils . toggleInlineStyle ( this . state . editorState , inlineStyle ) ) ; } async _updateDb ( endpoint , body , method = 'POST' ) { return await fetch ( endpoint , { method , headers : { 'Content-Type' : 'application/json' , } , body : JSON . stringify ( body ) , } ) ; } async _saveContent ( ) { const { editorState } = this . state ; const { addNote } = this . props ; const contentState = editorState . getCurrentContent ( ) ; const contentStateRaw = convertToRaw ( contentState ) ; const time = new Date ( ) . getTime ( ) ; const contentObj = { id : `note_${ time }` , _id : `note_${ time }` , tags : [ ] , updatedTime : time , createdTime : time , content : contentStateRaw , } ; const res = await this . _updateDb ( 'http://localhost:3000/notes' , contentObj ) ; if ( res . ok ) { addNote ( contentObj ) ; this . setState ( { editorState : EditorState . createEmpty ( ) , } ) ; } } async _updateContent ( ) { const { doc , id } = this . props ; const newDoc = { ... doc } ; const { editorState } = this . state ; const { updateNote } = this . props ; const contentState = editorState . getCurrentContent ( ) ; const contentStateRaw = convertToRaw ( contentState ) ; newDoc . content = contentStateRaw ; newDoc . updatedTime = new Date ( ) . getTime ( ) ; const res = await this . _updateDb ( `http://localhost:3000/notes/${ id }` , newDoc , 'PUT' ) ; if ( res . ok ) { updateNote ( newDoc ) ; this . props . resetReadOnly ( ) ; } } render ( ) { const { editorState } = this . state ; const { readOnly , id } = this . props ; let className = 'RichEditor-editor' ; let contentState = editorState . getCurrentContent ( ) ; if ( ! contentState . hasText ( ) ) { if ( contentState . getBlockMap ( ) . first ( ) . getType ( ) !== 'unstyled' ) { className += ' RichEditor-hidePlaceholder' ; } } if ( readOnly ) { try { const html = convertToHTML ( editorState . getCurrentContent ( ) ) ; return < div > { renderHTML ( html ) } < /div>; } catch ( e ) { return < div > { e . toString ( ) } < /div>; } } return ( < div className = "RichEditor-root" > < div className = { className } onClick = { this . focus } > < Editor blockStyleFn = { getBlockStyle } customStyleMap = { styleMap } editorState = { editorState } handleKeyCommand = { this . handleKeyCommand } keyBindingFn = { this . mapKeyToEditorCommand } onChange = { this . onChange } ref = "editor" spellCheck / > < /div> < div className = "RichEditor-controls-parent" > < BlockStyleControls editorState = { editorState } onBlockToggle = { this . toggleBlockType } onStyleToggle = { this . toggleInlineStyle } / > { } { } { } { } < Tooltip title = "Send" > < SendOutlined onMouseDown = { ( ) => { if ( id ) { this . _updateContent ( ) ; } else { this . _saveContent ( ) ; } } } style = { { fontSize : 16 , color : '#1890ff' , cursor : 'pointer' } } /> < /Tooltip> < /div> < /div>); } } export default connect ( null , mapDispatchToProps ) ( RichEditor ) ; const styleMap = { CODE : { backgroundColor : 'rgba(0, 0, 0, 0.05)' , fontFamily : '"Inconsolata", "Menlo", "Consolas", monospace' , fontSize : 16 , padding : 2 , } , } ; function getBlockStyle ( block ) { switch ( block . getType ( ) ) { case 'blockquote' : return 'RichEditor-blockquote' ; default : return null ; } } class StyleButton extends React . Component { constructor ( ) { super ( ) ; this . onToggle = ( e ) => { e . preventDefault ( ) ; this . props . onToggle ( this . props . style ) ; } ; } render ( ) { let className = 'RichEditor-styleButton' ; if ( this . props . active ) { className += ' RichEditor-activeButton' ; } if ( this . props . icon ) { return ( < Tooltip title = { this . props . label } > < span className = { className } style = { { fontSize : 16 } } onMouseDown = { this . onToggle } > { this . props . icon } < /span> < /Tooltip>); } return ( < Tooltip title = { this . props . label } > < span className = { className } onMouseDown = { this . onToggle } > { this . props . label } < /span> < /Tooltip>); } } const BLOCK_TYPES = [ { label : 'H1' , style : 'header-one' } , { label : 'H2' , style : 'header-two' } , { label : 'H3' , style : 'header-three' } , { label : 'Blockquote' , style : 'blockquote' , icon : < MenuUnfoldOutlined /> }, { label : 'Unordered List' , style : 'unordered-list-item' , icon : < UnorderedListOutlined />, } , { label : 'Ordered List' , style : 'ordered-list-item' , icon : < OrderedListOutlined />, } , { label : 'Code Block' , style : 'code-block' , icon : < CodeOutlined /> }, ] ; const BlockStyleControls = ( props ) => { const { editorState } = props ; const selection = editorState . getSelection ( ) ; const blockType = editorState . getCurrentContent ( ) . getBlockForKey ( selection . getStartKey ( ) ) . getType ( ) ; const currentStyle = props . editorState . getCurrentInlineStyle ( ) ; return ( < span className = "RichEditor-controls" > { BLOCK_TYPES . map ( ( type ) => ( < StyleButton key = { type . label } active = { type . style === blockType } label = { type . label } onToggle = { props . onBlockToggle } style = { type . style } icon = { type . icon ? type . icon : null } / > ) ) } { INLINE_STYLES . map ( ( type ) => ( < StyleButton key = { type . label } active = { currentStyle . has ( type . style ) } label = { type . label } onToggle = { props . onStyleToggle } style = { type . style } icon = { type . icon ? type . icon : null } / > ) ) } < /span>); } ; var INLINE_STYLES = [ { label : 'Bold' , style : 'BOLD' , icon : < BoldOutlined /> }, { label : 'Italic' , style : 'ITALIC' , icon : < ItalicOutlined /> }, { label : 'Underline' , style : 'UNDERLINE' , icon : < UnderlineOutlined /> }, ] ; const InlineStyleControls = ( props ) => { const currentStyle = props . editorState . getCurrentInlineStyle ( ) ; return ( < span className = "RichEditor-controls" > { INLINE_STYLES . map ( ( type ) => ( < StyleButton key = { type . label } active = { currentStyle . has ( type . style ) } label = { type . label } onToggle = { props . onToggle } style = { type . style } icon = { type . icon ? type . icon : null } / > ) ) } < /span>); } ;