Rolled the reply field into CommentWidget, where it belongs
This commit is contained in:
parent
dcef9d5cab
commit
0e70226aaa
|
@ -1,7 +1,6 @@
|
||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import setPosition from './setPosition';
|
import setPosition from './setPosition';
|
||||||
import TagWidget from './widgets/tag/TagWidget';
|
import TagWidget from './widgets/tag/TagWidget';
|
||||||
import TypeSelectorWidget from './widgets/type/TypeSelectorWidget';
|
|
||||||
import CommentWidget from './widgets/comment/CommentWidget';
|
import CommentWidget from './widgets/comment/CommentWidget';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,7 +14,6 @@ const Editor = props => {
|
||||||
|
|
||||||
// The current state of the edited annotation vs. original
|
// The current state of the edited annotation vs. original
|
||||||
const [ currentAnnotation, setCurrentAnnotation ] = useState();
|
const [ currentAnnotation, setCurrentAnnotation ] = useState();
|
||||||
const [ currentReply, setCurrentReply ] = useState('');
|
|
||||||
|
|
||||||
// Reference to the DOM element, so we can set position
|
// Reference to the DOM element, so we can set position
|
||||||
const element = useRef();
|
const element = useRef();
|
||||||
|
@ -23,7 +21,6 @@ const Editor = props => {
|
||||||
// Re-render: set derived annotation state & position the editor
|
// Re-render: set derived annotation state & position the editor
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentAnnotation(props.annotation);
|
setCurrentAnnotation(props.annotation);
|
||||||
setCurrentReply('');
|
|
||||||
|
|
||||||
if (element.current)
|
if (element.current)
|
||||||
setPosition(props.containerEl, element.current, props.bounds);
|
setPosition(props.containerEl, element.current, props.bounds);
|
||||||
|
@ -48,25 +45,24 @@ const Editor = props => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const onOk = _ => {
|
const onOk = _ => {
|
||||||
// If there is a non-empty reply, append it as a comment body
|
// Removes the 'draft' flag from all bodies
|
||||||
const updated = currentReply.trim() ?
|
const undraft = annotation => annotation.clone({
|
||||||
currentAnnotation.clone({
|
body : annotation.bodies.map(({ draft, ...rest }) => rest)
|
||||||
body: [ ...currentAnnotation.bodies, { type: 'TextualBody', value: currentReply.trim() } ]
|
});
|
||||||
}) : currentAnnotation;
|
|
||||||
|
|
||||||
// Current annotation is either a selection (if it was created from
|
// Current annotation is either a selection (if it was created from
|
||||||
// scratch just now) or an annotation (if it existed already and was
|
// scratch just now) or an annotation (if it existed already and was
|
||||||
// opened for editing)
|
// opened for editing)
|
||||||
if (updated.bodies.length === 0) {
|
if (currentAnnotation.bodies.length === 0) {
|
||||||
if (updated.isSelection)
|
if (currentAnnotation.isSelection)
|
||||||
props.onCancel();
|
props.onCancel();
|
||||||
else
|
else
|
||||||
props.onAnnotationDeleted(props.annotation);
|
props.onAnnotationDeleted(props.annotation);
|
||||||
} else {
|
} else {
|
||||||
if (updated.isSelection)
|
if (currentAnnotation.isSelection)
|
||||||
props.onAnnotationCreated(updated.toAnnotation());
|
props.onAnnotationCreated(undraft(currentAnnotation).toAnnotation());
|
||||||
else
|
else
|
||||||
props.onAnnotationUpdated(updated, props.annotation);
|
props.onAnnotationUpdated(undraft(currentAnnotation), props.annotation);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -76,16 +72,15 @@ const Editor = props => {
|
||||||
<div className="inner">
|
<div className="inner">
|
||||||
<CommentWidget
|
<CommentWidget
|
||||||
annotation={currentAnnotation}
|
annotation={currentAnnotation}
|
||||||
currentReply={currentReply}
|
onAppendBody={onAppendBody}
|
||||||
onUpdateComment={onUpdateBody}
|
onUpdateBody={onUpdateBody}
|
||||||
onDeleteComment={onRemoveBody}
|
onRemoveBody={onRemoveBody}
|
||||||
onUpdateReply={evt => setCurrentReply(evt.target.value.trim())}
|
onSaveAndClose={onOk} />
|
||||||
onOk={onOk} />
|
|
||||||
|
|
||||||
<TagWidget
|
<TagWidget
|
||||||
annotation={currentAnnotation}
|
annotation={currentAnnotation}
|
||||||
onAddTag={onAppendBody}
|
onAppendBody={onAppendBody}
|
||||||
onRemoveTag={onRemoveBody} />
|
onRemoveBody={onRemoveBody} />
|
||||||
|
|
||||||
{ props.readOnly ? (
|
{ props.readOnly ? (
|
||||||
<div className="footer">
|
<div className="footer">
|
||||||
|
|
|
@ -2,10 +2,8 @@ import Editor from './Editor';
|
||||||
|
|
||||||
import CommentWidget from './widgets/comment/CommentWidget';
|
import CommentWidget from './widgets/comment/CommentWidget';
|
||||||
import TagWidget from './widgets/tag/TagWidget';
|
import TagWidget from './widgets/tag/TagWidget';
|
||||||
import TypeSelectorWidget from './widgets/type/TypeSelectorWidget';
|
|
||||||
|
|
||||||
Editor.CommentWidget = CommentWidget;
|
Editor.CommentWidget = CommentWidget;
|
||||||
Editor.TagWidget = TagWidget;
|
Editor.TagWidget = TagWidget;
|
||||||
Editor.TypeSelectorWidget = TypeSelectorWidget;
|
|
||||||
|
|
||||||
export { Editor };
|
export { Editor };
|
||||||
|
|
|
@ -32,7 +32,7 @@ const Comment = props => {
|
||||||
editable={isEditable}
|
editable={isEditable}
|
||||||
content={props.body.value}
|
content={props.body.value}
|
||||||
onChange={onUpdateComment}
|
onChange={onUpdateComment}
|
||||||
onOk={props.onOk}
|
onSaveAndClose={props.onSaveAndClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -2,36 +2,72 @@ import React from 'react';
|
||||||
import Comment from './Comment';
|
import Comment from './Comment';
|
||||||
import TextEntryField from './TextEntryField';
|
import TextEntryField from './TextEntryField';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comments are TextualBodies where the purpose field is either
|
||||||
|
* blank or 'commenting' or 'replying'
|
||||||
|
*/
|
||||||
|
const isComment = body =>
|
||||||
|
body.type === 'TextualBody' && (
|
||||||
|
!body.hasOwnProperty('purpose') || body.purpose === 'commenting' || body.purpose === 'replying'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The draft reply is a comment body with a 'draft' flag
|
||||||
|
*/
|
||||||
|
const getDraftReply = (existingDraft, isReply) => {
|
||||||
|
return existingDraft ? existingDraft : {
|
||||||
|
type: 'TextualBody', value: '', purpose: isReply ? 'replying' : 'commenting', draft: true
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a list of comment bodies, followed by a 'reply' field.
|
* Renders a list of comment bodies, followed by a 'reply' field.
|
||||||
*/
|
*/
|
||||||
const CommentWidget = props => {
|
const CommentWidget = props => {
|
||||||
|
|
||||||
const commentBodies = props.annotation ?
|
// All comments (draft + non-draft)
|
||||||
props.annotation.bodies.filter(b => // No purpose or 'commenting', 'replying'
|
const all = props.annotation ?
|
||||||
!b.hasOwnProperty('purpose') || b.purpose === 'commenting' || b.purpose === 'replying'
|
props.annotation.bodies.filter(isComment) : [];
|
||||||
) : [];
|
|
||||||
|
// Non-draft comments
|
||||||
|
const comments = all.filter(b => !b.draft);
|
||||||
|
|
||||||
|
// Draft reply
|
||||||
|
const draftReply = getDraftReply(all.find(b => b.draft), comments.length > 0);
|
||||||
|
|
||||||
|
const onEditReply = evt => {
|
||||||
|
const prev = draftReply.value.trim();
|
||||||
|
const updated = evt.target.value.trim();
|
||||||
|
|
||||||
|
if (prev.length === 0 && updated.length > 0) {
|
||||||
|
props.onAppendBody({ ...draftReply, value: updated });
|
||||||
|
} else if (prev.length > 0 && updated.length === 0) {
|
||||||
|
props.onRemoveBody(draftReply);
|
||||||
|
} else {
|
||||||
|
props.onUpdateBody(draftReply, { ...draftReply, value: updated });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{ commentBodies.map((body, idx) =>
|
{ comments.map((body, idx) =>
|
||||||
<Comment
|
<Comment
|
||||||
key={idx}
|
key={idx}
|
||||||
readOnly={props.readOnly}
|
readOnly={props.readOnly}
|
||||||
body={body}
|
body={body}
|
||||||
onUpdate={props.onUpdateComment}
|
onUpdate={props.onUpdateBody}
|
||||||
onDelete={props.onDeleteComment}
|
onDelete={props.onRemoveBody}
|
||||||
onOk={props.onOk} />
|
onSaveAndClose={props.onSaveAndClose} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{ !props.readOnly && props.annotation &&
|
{ !props.readOnly && props.annotation &&
|
||||||
<div className="r6o-widget comment editable">
|
<div className="r6o-widget comment editable">
|
||||||
<TextEntryField
|
<TextEntryField
|
||||||
content={props.currentReply}
|
content={draftReply.value}
|
||||||
editable={true}
|
editable={true}
|
||||||
placeholder={commentBodies.length > 0 ? 'Add a reply...' : 'Add a comment...'}
|
placeholder={comments.length > 0 ? 'Add a reply...' : 'Add a comment...'}
|
||||||
onChange={props.onUpdateReply}
|
onChange={onEditReply}
|
||||||
onOk={() => props.onOk()}
|
onSaveAndClose={() => props.onSaveAndClose()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default class TextEntryField extends Component {
|
||||||
// CTRL+Enter functions as Ok
|
// CTRL+Enter functions as Ok
|
||||||
onKeyDown = evt => {
|
onKeyDown = evt => {
|
||||||
if (evt.which === 13 && evt.ctrlKey)
|
if (evt.which === 13 && evt.ctrlKey)
|
||||||
this.props.onOk();
|
this.props.onSaveAndClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus on render
|
// Focus on render
|
||||||
|
|
Loading…
Reference in New Issue