Rolled the reply field into CommentWidget, where it belongs

This commit is contained in:
Rainer Simon 2020-04-04 20:47:04 +02:00
parent dcef9d5cab
commit 0e70226aaa
5 changed files with 65 additions and 36 deletions

View File

@ -1,7 +1,6 @@
import React, { useState, useRef, useEffect } from 'react';
import setPosition from './setPosition';
import TagWidget from './widgets/tag/TagWidget';
import TypeSelectorWidget from './widgets/type/TypeSelectorWidget';
import CommentWidget from './widgets/comment/CommentWidget';
/**
@ -15,7 +14,6 @@ const Editor = props => {
// The current state of the edited annotation vs. original
const [ currentAnnotation, setCurrentAnnotation ] = useState();
const [ currentReply, setCurrentReply ] = useState('');
// Reference to the DOM element, so we can set position
const element = useRef();
@ -23,7 +21,6 @@ const Editor = props => {
// Re-render: set derived annotation state & position the editor
useEffect(() => {
setCurrentAnnotation(props.annotation);
setCurrentReply('');
if (element.current)
setPosition(props.containerEl, element.current, props.bounds);
@ -48,25 +45,24 @@ const Editor = props => {
);
const onOk = _ => {
// If there is a non-empty reply, append it as a comment body
const updated = currentReply.trim() ?
currentAnnotation.clone({
body: [ ...currentAnnotation.bodies, { type: 'TextualBody', value: currentReply.trim() } ]
}) : currentAnnotation;
// Removes the 'draft' flag from all bodies
const undraft = annotation => annotation.clone({
body : annotation.bodies.map(({ draft, ...rest }) => rest)
});
// Current annotation is either a selection (if it was created from
// scratch just now) or an annotation (if it existed already and was
// opened for editing)
if (updated.bodies.length === 0) {
if (updated.isSelection)
if (currentAnnotation.bodies.length === 0) {
if (currentAnnotation.isSelection)
props.onCancel();
else
props.onAnnotationDeleted(props.annotation);
} else {
if (updated.isSelection)
props.onAnnotationCreated(updated.toAnnotation());
if (currentAnnotation.isSelection)
props.onAnnotationCreated(undraft(currentAnnotation).toAnnotation());
else
props.onAnnotationUpdated(updated, props.annotation);
props.onAnnotationUpdated(undraft(currentAnnotation), props.annotation);
}
};
@ -76,16 +72,15 @@ const Editor = props => {
<div className="inner">
<CommentWidget
annotation={currentAnnotation}
currentReply={currentReply}
onUpdateComment={onUpdateBody}
onDeleteComment={onRemoveBody}
onUpdateReply={evt => setCurrentReply(evt.target.value.trim())}
onOk={onOk} />
onAppendBody={onAppendBody}
onUpdateBody={onUpdateBody}
onRemoveBody={onRemoveBody}
onSaveAndClose={onOk} />
<TagWidget
annotation={currentAnnotation}
onAddTag={onAppendBody}
onRemoveTag={onRemoveBody} />
onAppendBody={onAppendBody}
onRemoveBody={onRemoveBody} />
{ props.readOnly ? (
<div className="footer">

View File

@ -2,10 +2,8 @@ import Editor from './Editor';
import CommentWidget from './widgets/comment/CommentWidget';
import TagWidget from './widgets/tag/TagWidget';
import TypeSelectorWidget from './widgets/type/TypeSelectorWidget';
Editor.CommentWidget = CommentWidget;
Editor.TagWidget = TagWidget;
Editor.TypeSelectorWidget = TypeSelectorWidget;
export { Editor };

View File

@ -32,7 +32,7 @@ const Comment = props => {
editable={isEditable}
content={props.body.value}
onChange={onUpdateComment}
onOk={props.onOk}
onSaveAndClose={props.onSaveAndClose}
/>
<div

View File

@ -2,36 +2,72 @@ import React from 'react';
import Comment from './Comment';
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.
*/
const CommentWidget = props => {
const commentBodies = props.annotation ?
props.annotation.bodies.filter(b => // No purpose or 'commenting', 'replying'
!b.hasOwnProperty('purpose') || b.purpose === 'commenting' || b.purpose === 'replying'
) : [];
// All comments (draft + non-draft)
const all = props.annotation ?
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 (
<>
{ commentBodies.map((body, idx) =>
{ comments.map((body, idx) =>
<Comment
key={idx}
readOnly={props.readOnly}
body={body}
onUpdate={props.onUpdateComment}
onDelete={props.onDeleteComment}
onOk={props.onOk} />
onUpdate={props.onUpdateBody}
onDelete={props.onRemoveBody}
onSaveAndClose={props.onSaveAndClose} />
)}
{ !props.readOnly && props.annotation &&
<div className="r6o-widget comment editable">
<TextEntryField
content={props.currentReply}
content={draftReply.value}
editable={true}
placeholder={commentBodies.length > 0 ? 'Add a reply...' : 'Add a comment...'}
onChange={props.onUpdateReply}
onOk={() => props.onOk()}
placeholder={comments.length > 0 ? 'Add a reply...' : 'Add a comment...'}
onChange={onEditReply}
onSaveAndClose={() => props.onSaveAndClose()}
/>
</div>
}

View File

@ -13,7 +13,7 @@ export default class TextEntryField extends Component {
// CTRL+Enter functions as Ok
onKeyDown = evt => {
if (evt.which === 13 && evt.ctrlKey)
this.props.onOk();
this.props.onSaveAndClose();
}
// Focus on render