Home Reference Source

application/components/common/rich-editor/note-viewer.js

import React from 'react';
import FlatButton from 'material-ui/FlatButton';
import {List, ListItem} from 'material-ui/List';
import Divider from 'material-ui/Divider';
import Subheader from 'material-ui/Subheader';
import IconButton from 'material-ui/IconButton';
import FontIcon from 'material-ui/FontIcon';

import {timeToFormat} from './../../../util/strings';

import {CopyNoteService, CopyNoteStore} from './../../../services/copyNote-service';
import NoteCommentFlux from './../../../services/noteComment-service';
import CurrentUser from './../../../current-user';

import Gravatar from './../gravatar';

import Events from './../../../util/events';
import BaseView from './../../base-view';
import {Row, Col} from './../../flexbox';

import LoadingIndicator from './../loading-indicator';

import SimpleEditor from './../simple-editor';
import {stateToHTML} from 'draft-js-export-html';
// import stateToHTML from './../../../exportToHTML_modified';

/**
 * Render the new comment box and container for individual
 * note comments.
 */
class CommentBox extends BaseView {

    constructor(props) {
        super(props);

        // The flux container allows us to load the object as
        // an instance instead of a singleton
        this.service = new NoteCommentFlux();

        this.confirm = this.confirm.bind(this);
        this.onChange = this.onChange.bind(this);

        this.onInputKeyDown = this.onInputKeyDown.bind(this);

        this.state = this.service.stores.noteComments.getState();

    }

    componentWillMount() {
        this.state.isLoading = false;

        this.service.stores.noteComments.listen(this.onChange);
        this.service.actions.noteComments.fetch(this.props.parentReference);
    }

    componentDidMount() {
        //setTimeout(() => this.refs.simpleEditor.focus(), 0);
    }

    componentWillUnmount() {
        this.service.stores.noteComments.unlisten(this.onChange);
    }

    onChange(state) {
        this.setState(state);
    }

    /**
     * Save a new note comment and fetch any pending updates
     */
    confirm(e) {
        // const comment = this.refs.comment.value;

        const data = {
            comment: this.state.noteContent,
            version: this.props.version,
            draftUuid: this.props.parentReference,
            contentUuid: this.props.contentUuid
        };

        this.setState({'isLoading': true});

        this.service.actions.noteComments.createAndFetch(this.props.parentReference, data, () => {
            this.setState({'isLoading': false});
            this.props.handleNewComment();
        });
    }

    /**
     * Handle CMD+Enter to save comment
     */
    onInputKeyDown(e) {
        if (e.which === 13 && e.metaKey) {
            this.confirm(e);
        }
    }

    handleEditorChange(raw, state, name) {
        this.setState({noteContent: stateToHTML(state.getCurrentContent())});
    }

    render() {
        if (this.state.isLoading) {
            return <LoadingIndicator size="small" />;
        }

        let commentBox = '';
        if (this.props.currentlySelected) {
            commentBox = (
                <div>
                    <FullWidthDivider />
                    <SimpleEditor ref='simpleEditor' showToolbar={false} value='' onChange={this.handleEditorChange.bind(this)} style={{'fontSize': '12px'}} parentReference={this.props.parentReference}/>
                    <FlatButton onClick={this.confirm} label='Add reply' />
                </div>
            );
        }

        let comments = '';
        if (this.state.comments.size) {
            comments = this.state.comments.valueSeq().map((comment, i) => {
                return (
                    <div key={i}>
                        <SingleNote contents={comment.get('comment')} user={comment.get('user').get('name')} email={comment.get('user').get('email')} version={comment.get('version')} created_at={comment.get('created_at')} parentReference={this.props.parentReference} currentlySelected={this.props.currentlySelected}/>
                        {
                            this.state.comments.size !== (i+1)
                            ? (
                               <FullWidthDivider />
                            )
                            : ''
                        }
                    </div>
                );
            });
        }

        return (
            <div>
                {comments}
                {commentBox}
            </div>
        );
    }
}

/**
 * NoteBox component renders the note container and its children.
 *
 * Looking for the style that changes the highlight color on select?
 * Check the CopyNote service... yeah - sorry.
 */
class NoteBox extends BaseView {

    constructor(props) {
        super(props);

        this.service = new NoteCommentFlux();
        this.state = this.service.stores.noteComments.getState();

        this.state.expanded = false;

        this.onEditorUpdate = this.onEditorUpdate.bind(this);
        this.onHandleClick = this.onHandleClick.bind(this);
        this.onChange = this.onChange.bind(this);

        this.handleNewComment = this.handleNewComment.bind(this);
    }

    componentWillMount() {
        this.service.stores.noteComments.listen(this.onChange);
        this.service.actions.noteComments.fetch(this.props.parentReference);
    }

    componentWillUnmount() {
        this.service.stores.noteComments.unlisten(this.onChange);
    }

    onHandleClick() {
        CopyNoteService.setLastSelectedNote(this.props.parentReference);

        if (!this.state.expanded) {
            this.toggleExpand();
        }
    }

    onChange(state) {
        this.setState(state);
    }

    handleNewComment(e) {
        // if this is the first new comment we have to refresh comments for ui
        if (!this.state.comments.size) {
            this.service.actions.noteComments.fetch(this.props.parentReference);
        }

    }

    /**
     * Watches for changes in the editor state and repositions itself
     * based on the note inline style's parent block
     */
    onEditorUpdate(e) {
    }

    componentDidMount() {
        // check bounding box for overflown objects?
    }

    toggleExpand() {
        this.setState({'expanded': !this.state.expanded});
    }

    render() {
        return (
            <div
                style={{'position':'relative'}}
                className={this.props.currentlySelected ? "richEditor-copyNoteView selected" : "richEditor-copyNoteView"}
                >
                <IconButton tooltip="Expand/Collapse" onClick={this.toggleExpand.bind(this)} style={{'position':'absolute','top':0,'right':0,'zIndex':9}}>
                    <FontIcon className="mui-icons">{this.state.expanded ? 'expand_less' : 'expand_more'}</FontIcon>
                </IconButton>
                <div
                    onClick={this.onHandleClick.bind(this)}
                    >
                    {this.props.children}
                </div>
                {
                    this.state.expanded
                    ? (
                        <Row
                            onClick={this.onHandleClick.bind(this)}
                            >
                            <Col xs={12}>
                                {
                                    this.state.comments.size
                                    ? (
                                        <FullWidthDivider />
                                    )
                                    :''
                                }
                                <CommentBox currentlySelected={this.props.currentlySelected} parentReference={this.props.parentReference} version={this.props.version} contentUuid={this.props.contentUuid} handleNewComment={this.handleNewComment} />
                            </Col>
                        </Row>
                    )
                    : ''
                }
                {
                    this.state.comments.size && !this.state.expanded
                    ? (
                        <span className='more-indicator' onClick={this.toggleExpand.bind(this)}>
                            <i className='fa fa-angle-down'></i>
                            view replies
                            <i className='fa fa-angle-down'></i>
                        </span>
                    )
                    : ''
                }
            </div>
        );
    }
}

/**
 * Render a single note or comment
 */
class SingleNote extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            height: null,
            open: false,
            maxHeight: '160px'
        }
    }

    componentDidMount() {
        setTimeout(() => {
            let ref = this.refs.commentText;

            this.setState({height: ref.clientHeight});
        }, 1)
    }

    showMore() {
        CopyNoteService.setLastSelectedNote(this.props.parentReference);

        setTimeout(() => {
            this.setState({maxHeight:'100%', open:true})
        }, 1)
    }

    render() {

        if (!this.props.currentlySelected) {
            this.state.maxHeight = '160px';
            this.state.open = false;
        } else if (this.state.open) {
            this.state.maxHeight = '100%';
        }

        const dhanjerruz = () => {
            let str = this.props.contents;
            str = str.replace(/(\@[\w]+)/g, "<span class=\"mention\">\$1</span>");

            return {
                '__html': str
            };
        };

        return (
            <div className="richEditor-singleComment" style={{border:'0px solid', marginBottom:'0px'}}>
                <Row middle='xs'>
                    <Col xs={2}>
                        <Gravatar email={this.props.email} size={35} style={{'maxWidth': '100%'}} />
                    </Col>
                    <Col xs={10}>
                        <strong>{this.props.user}</strong>
                        <div className="meta">
                            {timeToFormat(this.props.created_at, 'hh:mma, M/DD')} (v {this.props.version})
                        </div>
                    </Col>
                </Row>
                <div ref='commentText' style={{maxHeight:this.state.maxHeight, overflow:'hidden'}} dangerouslySetInnerHTML={dhanjerruz()}></div>
                {
                    this.state.height == 160 && !this.state.open
                    ? (
                        <div style={{textAlign:'right'}}>
                            <FontIcon
                                className='mui-icons'
                                onClick={this.showMore.bind(this)}
                                style={{cursor:'pointer'}}
                                title='Show More'
                            >
                                more_horiz
                            </FontIcon>
                        </div>
                    )
                    : ''
                }
            </div>

        );
    }
}

/**
 * Render the entered note as a floating box. This component has two parts,
 * the original note, which is part of the document's meta data, and the note
 * comments, which are retrieved from the API.
 */
 class NoteViewer extends BaseView {

    constructor(props) {
        super(props);
        this.displayName = 'NoteViewer';

        this.onChange = this.onChange.bind(this);
        this.state = CopyNoteStore.getState();
    }

    onChange(state) {
        this.setState(state);

    }

    componentWillMount() {
        CopyNoteStore.listen(this.onChange);
    }

    componentWillUnmount() {
        CopyNoteStore.unlisten(this.onChange);
    }

    render() {

        const children = this.state.notes.map((note, i) => {
            return (
                <NoteBox key={i} parentReference={note.id} version={this.props.version} currentlySelected={this.state.selection === note.id ? true : false} contentUuid={this.props.contentUuid}>
                    <SingleNote parentReference={note.id} contents={note.contents} currentlySelected={this.state.selection === note.id ? true : false} user={note.user} email={note.email} version={note.version} created_at={note.created_at} />
                </NoteBox>
            );
        });

        return <div>{children}</div>;
    }
}

const FullWidthDivider = () => {
    return (
        <hr style={{
            margin: '-1px -10px 20px -10px',
            backgroundColor: 'rgb(224, 224, 224)',
            height: '1px',
            border: 'none'
        }} />
    )
}

export {NoteViewer as default, NoteBox, SingleNote, CommentBox};
// export default NoteViewer;