application/components/common/simple-editor.js
import React from 'react';
import PropTypes from 'prop-types';
import {AtomicBlockUtils, Editor, EditorState, RichUtils, ContentState, CompositeDecorator, Entity, getDefaultKeyBinding, KeyBindingUtil, convertToRaw, convertFromRaw, Modifier} from 'draft-js';
import {stateFromHTML} from 'draft-js-import-html';
import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
import InlineStyleControls from './simple-editor/inline-style-controls';
import BlockStyleControls from './simple-editor/block-style-controls';
import FormatStyleControls from './simple-editor/format-style-controls';
import StyleButton from './simple-editor/style-button';
import linkStrategy from './simple-editor/strategy/link';
import LinkDecorator from './simple-editor/decorator/link';
import mentionStrategy from './simple-editor/strategy/mention';
import mentionModifier from './simple-editor/modifier/mention';
import MentionDecorator from './simple-editor/decorator/mention';
import MentionMenu from './simple-editor/mention-menu';
import EditorStore from './simple-editor/service';
import Events from './../../util/events';
/**
* The SimpleEditor is a stripped down version of the RichEditor.
*
* The SimpleEditor content is not intended to be parsed as HTML, or used
* for display purposes. Its intended use is internal notes, text fields and
* the like.
*/
class SimpleEditor extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.toggleBlockType = this.toggleBlockType.bind(this);
this.toggleInlineStyle = this.toggleInlineStyle.bind(this);
this.handleKeyCommand = this.handleKeyCommand.bind(this);
this.onUpdate = this.onUpdate.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.store = new EditorStore;
this.state = this.store.stores.references.getState();
this.decorator = new CompositeDecorator([
{
'strategy': linkStrategy,
'component': LinkDecorator,
'props': {
'store': this.store,
'editor': this
}
},
{
'strategy': mentionStrategy,
'component': MentionDecorator,
'props': {
'store': this.store,
'editor': this
}
}
]);
}
componentWillMount() {
// because the data needs to be managed by the API and it's not necessary
// that we maintain a strict intrepretation of the data we can just stick
// with strings for content
if (this.props.value) {
this.setState({editorState: EditorState.createWithContent(stateFromHTML(this.props.value), this.decorator)});
} else {
this.setState({editorState: EditorState.createEmpty(this.decorator)});
}
this.store.stores.references.listen(this.onUpdate);
}
componentWillUnmount() {
this.store.stores.references.unlisten(this.onUpdate);
}
componentWillReceiveProps(nextProps) {
if (nextProps.value != this.props.value) {
this.setState({editorState: EditorState.createWithContent(stateFromHTML(nextProps.value), this.decorator)});
}
}
onUpdate(state) {
console.log('UPDATE EDITOR STORE', state);
this.setState(state);
}
onChange(editorState) {
if (this.props.onChange) {
this.props.onChange(convertToRaw(editorState.getCurrentContent()), editorState, this.props.name);
}
return this.setState({editorState});
}
handleKeyCommand(command) {
const {editorState} = this.state;
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
this.onChange(newState);
return true;
}
return false;
}
toggleBlockType(blockType) {
this.onChange(
RichUtils.toggleBlockType(
this.state.editorState,
blockType
)
);
}
toggleInlineStyle(inlineStyle) {
this.onChange(
RichUtils.toggleInlineStyle(
this.state.editorState,
inlineStyle
)
);
}
focus() {
this.refs.editor.focus();
}
handleKeyDown(e) {
if (this.state.users.size) {
// we have a list of users
if (e.which === 38) {
// arrow up
this.store.actions.references.decrementSelectedMention();
e.preventDefault();
e.stopPropagation();
} else if (e.which === 40) {
// arrow down
this.store.actions.references.incrementSelectedMention();
e.preventDefault();
e.stopPropagation();
} else if (e.which === 13) {
// enter/return
e.preventDefault();
e.stopPropagation();
this.store.actions.references.markSelectedUser();
// the modifier does the heavy lifting
this.onChange(mentionModifier(this.state.editorState, this.store));
}
}
}
handleMentionClick(item, index) {
this.store.actions.references.setSelectedUser(index);
// the modifier does the heavy lifting
this.onChange(mentionModifier(this.state.editorState, this.store));
}
handleReturn(e) {
if (e && e.shiftKey) {
const {editorState} = this.state;
this.onChange(
RichUtils.insertSoftNewline(
editorState
)
);
return true;
}
}
render() {
const {editorState} = this.state;
return (
<div className="simple-editor-root">
{
this.props.label
? <label>{this.props.label}</label>
: ''
}
{
this.props.showToolbar === false
? ''
: (
<div className='simple-editor-toolbar'>
<div className='row middle-xs'>
<div className='col-xs-12'>
<div className='box'>
<FormatStyleControls
editorState={editorState}
onToggle={this.toggleBlockType}
/>
<BlockStyleControls
editorState={editorState}
onToggle={this.toggleBlockType}
/>
<InlineStyleControls
editorState={editorState}
onToggle={this.toggleInlineStyle}
/>
{this.props.toolbarRight ? this.props.toolbarRight : ''}
</div>
</div>
</div>
</div>
)
}
<div className={this.props.showToolbar ? 'simple-editor-editor with-toolbar' : 'simple-editor-editor'} onClick={this.focus.bind(this)} onKeyDown={this.handleKeyDown} style={this.props.style}>
<Editor
editorState={editorState}
onChange={this.onChange}
handleKeyCommand={this.handleKeyCommand}
handleReturn={this.handleReturn.bind(this)}
ref="editor"
spellCheck={true}
readOnly={this.props.readOnly ? true : false}
/>
{
this.props.mentions !== false
? <MentionMenu onSelect={this.handleMentionClick.bind(this)} store={this.store} editor={this} selectedIndex={this.state.selectedIndex} />
: ''
}
</div>
</div>
);
}
}
SimpleEditor.propTypes = {
onChange: PropTypes.func,
value: PropTypes.any
};
export default SimpleEditor;