Home Reference Source

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;