application/components/common/rich-editor2/plugins/note.js
import React from 'react';
import ToolbarButton from '../common/toolbar-button';
import Avatar from 'material-ui/Avatar';
import Chip from 'material-ui/Chip';
import FlatButton from 'material-ui/FlatButton';
import uuid from 'uuid';
import {findDOMNode} from 'slate-react';
import {connect} from 'react-redux';
import {noteCommentSetSelected, noteCommentsFetch, noteCommentCreate} from '../../../../redux/actions/note-comment-actions';
import SimpleEditor from '../../simple-editor';
import {stateToHTML} from 'draft-js-export-html';
import LoadingIndicator from '../../loading-indicator';
import {timeToFormat} from '../../../../util/strings';
export const SERIALIZER_RULES = [
{
serialize(obj, children) {
if (obj.object == 'inline' && obj.type == 'editorial-note') {
// this should only return the children
return <React.Fragment>{children}</React.Fragment>;
}
}
}
];
export function handleNewNote(e) {
const {editor} = this;
const {editorContent} = this.state;
const hasNote = this.hasInline('editorial-note');
if (!hasNote) {
const id = uuid.v4();
editor.wrapInline({
type: 'editorial-note',
data: {
uuid: id
}
});
} else {
editor.unwrapInline('editorial-note');
}
}
export class NoteButton extends ToolbarButton
{
handleOnClick(e) {
return handleNewNote.call(this.props.editor, e);
}
getLabel() {
return <i className='fa fa-comment-o'></i>;
}
}
export class NoteNode extends React.Component
{
constructor(props) {
super(props);
this.parentEditor = this.props.editor;
}
handleClick(e) {
const {data} = this.props.node;
const id = data.get('uuid');
if (this.props.editor.props.readOnly) {
window.Store.dispatch(noteCommentSetSelected(id));
}
}
render() {
const {data} = this.props.node;
const id = data.get('uuid');
return (
<span {...this.props.attributes} className='note-node' onClick={this.handleClick.bind(this)}>
{this.props.children}
</span>
);
}
}
export function NotePlugin(options) {
return {
renderEditor(props, editor, next) {
const children = next();
if (editor.value.inlines.some(inline => inline.type == 'editorial-note')) {
const node = editor.value.document.getClosestInline(editor.value.selection.start.key);
if (node) {
const uuid = node.data.get('uuid');
window.Store.dispatch(noteCommentSetSelected(uuid));
} else {
window.Store.dispatch(noteCommentSetSelected(null));
}
} else {
window.Store.dispatch(noteCommentSetSelected(null));
}
return (
<React.Fragment>
{children}
<WrappedNoteViewer editor={editor} />
</React.Fragment>
);
},
renderNode(props, editor, next) {
const {attributes, children} = props;
switch(props.node.type) {
case 'editorial-note':
return <NoteNode {...props} editor={editor}/>;
default:
return next();
}
}
}
}
class NoteViewer extends React.Component
{
render() {
const notes = this.props.editor.value.document.filterDescendants((node) => {
return node.type == 'editorial-note';
});
return (
<div className="slate-editor-note-container">
{notes.map((note, i) => {
return (
<WrappedNoteContainer key={i} note={note} editor={this.props.editor} />
)
})}
</div>
)
}
}
const WrappedNoteViewer = connect((state) => {
return {
noteComments: state.noteComments
};
})(NoteViewer);
class NoteContainer extends React.Component
{
constructor(props) {
super(props);
this.state = {
collapsed: true,
parentNode: false,
domNode: false,
offsetTop: 0,
offsetLeft: -100,
};
}
componentWillMount() {
const {dispatch} = this.props;
dispatch(noteCommentsFetch({'root':this.props.note.data.get('uuid')}));
}
static getDerivedStateFromProps(nextProps, prevState) {
let domNode;
try {
domNode = findDOMNode(nextProps.note);
} catch (err) {
try {
domNode = findDOMNode(nextProps.editor.value.document.getClosestBlock(nextProps.note.key));
} catch (err) {
return prevState;
}
}
let nextState = {};
let parent = domNode;
if (!prevState.parentNode) {
for(;;) {
if (parent.className.indexOf('editor-container') === -1) {
parent = parent.parentElement;
continue;
}
break;
}
nextState.parentNode = parent;
} else {
parent = prevState.parentNode;
}
const offsetTop = domNode.getBoundingClientRect().top + window.scrollY - 50;
const offsetLeft = parent.getBoundingClientRect().width;
nextState.offsetTop = offsetTop;
nextState.offsetLeft = offsetLeft;
return Object.assign({}, prevState, nextState);
}
render() {
const styles = {
position: 'absolute',
top: this.state.offsetTop + 'px',
left: this.state.offsetLeft + 'px',
zIndex: 999
};
if (this.props.noteComments.selected != this.props.note.data.get('uuid')) {
return (
''
);
} else {
return (
<WrappedNoteItem className='floating-note-container' style={styles} note={this.props.note} editor={this.props.editor} />
);
}
}
}
const WrappedNoteContainer = connect((state) => {
return {
noteComments: state.noteComments
};
})(NoteContainer);
class NoteItem extends React.Component
{
constructor(props) {
super(props)
}
componentWillMount() {
const {dispatch} = this.props;
dispatch(noteCommentsFetch(this.props.note.data.get('uuid')));
}
handleEditorChange(raw, state, name) {
this.setState({noteContent: stateToHTML(state.getCurrentContent())});
}
saveReply(e) {
const {dispatch} = this.props;
const data = {
'contentUuid': this.props.editor.props.contentUuid,
'comment': this.state.noteContent,
'version': this.props.editor.props.contentVersion
};
const ref = this.props.note.data.get('uuid');
dispatch(noteCommentCreate(ref, data))
.then(() => dispatch(noteCommentsFetch(ref)));
}
handleResolve(e) {
if (!confirm('Are you sure you want to resolve this note?')) {
return;
}
this.props.editor.moveToRangeOfNode(this.props.note)
.unwrapInline('editorial-note');
}
render() {
const style = Object.assign({}, this.props.style, {
'zIndex': 9999,
});
if (this.props.noteComments.isFetching) {
return (
<div style={style} className='note-item'>
<LoadingIndicator />
</div>
);
}
const text = this.props.note.text;
const daynjeryose = function(item) {
return {
__html: item.get('comment')
};
};
return (
<div style={style} className='note-item'>
<span className='resolve-button' onClick={this.handleResolve.bind(this)} title="Resolve Comment"><i className='fa fa-check'></i></span>
<span className='note-node'>"{text}"</span>
{
this.props.noteComments.items.size
? (
<React.Fragment>
{this.props.noteComments.items.toSeq().map((item) =>
<div key={item.get('uuid')} className='note-reply'>
<Chip>
<Avatar
src={item.get('user').get('gravatar')}
/>
{item.get('user').get('name')} said:
</Chip>
<div dangerouslySetInnerHTML={daynjeryose(item)}></div>
<span className='date'> at {timeToFormat(item.get('created_at'), 'hh:mma, M/DD')}</span>
</div>
)}
</React.Fragment>
)
: ''
}
<SimpleEditor ref='simpleEditor' showToolbar={false} value='' onChange={this.handleEditorChange.bind(this)} style={{'fontSize': '12px'}}/>
<FlatButton onClick={this.saveReply.bind(this)} label='Add Comment' />
</div>
);
}
}
const WrappedNoteItem = connect((state) => {
return {
noteComments: state.noteComments
};
})(NoteItem);