Edit UI sends editComment mutation, handles errors

This commit is contained in:
Benjamin Goering
2017-05-02 22:07:23 -05:00
parent aa18a433f8
commit b9a3466e2e
10 changed files with 95 additions and 10 deletions
+6
View File
@@ -106,6 +106,9 @@ class Comment extends React.Component {
// dispatch action to ignore another user
ignoreUser: React.PropTypes.func,
// edit a comment, passed (id, { body })
editComment: React.PropTypes.func,
}
onClickEdit (e) {
@@ -215,12 +218,14 @@ class Comment extends React.Component {
{
this.state.isEditing
? <EditableCommentContent
editComment={this.props.editComment.bind(null, comment.id)}
addNotification={addNotification}
asset={asset}
comment={comment}
currentUser={currentUser}
maxCharCount={maxCharCount}
parentId={parentId}
stopEditing={() => this.setState({isEditing: false})}
/>
: <Content body={comment.body} />
}
@@ -303,6 +308,7 @@ class Comment extends React.Component {
addNotification={addNotification}
parentId={comment.id}
postItem={postItem}
editComment={this.props.editComment}
depth={depth + 1}
asset={asset}
highlighted={highlighted}
@@ -1,6 +1,10 @@
import React, {PropTypes} from 'react';
import {CommentForm} from 'coral-plugin-commentbox/CommentBox';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-framework/translations';
const lang = new I18n(translations);
/**
* Renders a Comment's body in such a way that the end-user can edit it and save changes
*/
@@ -12,7 +16,6 @@ export class EditableCommentContent extends React.Component {
// show notification to the user (e.g. for errors)
addNotification: PropTypes.func.isRequired,
asset: PropTypes.shape({
id: PropTypes.string.isRequired,
settings: PropTypes.shape({
charCountEnable: PropTypes.bool,
}),
@@ -29,14 +32,47 @@ export class EditableCommentContent extends React.Component {
}),
maxCharCount: PropTypes.number,
parentId: PropTypes.string,
// edit a comment, passed {{ body }}
editComment: React.PropTypes.func,
// called when editing should be stopped
stopEditing: React.PropTypes.func,
}
constructor(props) {
super(props);
this.editComment = this.editComment.bind(this);
}
async editComment(edit) {
const {editComment, addNotification, stopEditing} = this.props;
if (typeof editComment !== 'function') {return;}
let response;
let successfullyEdited = false;
try {
response = await editComment(edit);
const errors = (response && response.data && response.data.editComment)
? response.data.editComment.errors
: null;
if (errors && (errors.length === 1)) {
throw errors[0];
}
successfullyEdited = true;
} catch (error) {
if (error.translation_key) {
addNotification('error', lang.t(error.translation_key) || error.translation_key);
} else if (error.networkError) {
addNotification('error', lang.t('error.networkError'));
} else {
throw error;
}
}
if (successfullyEdited && typeof stopEditing === 'function') {
stopEditing();
}
}
stopEditing() {
this.setState({resetCounter: this.state.resetCounter + 1});
}
render() {
const saveComment = function () {
};
const originalBody = this.props.comment.body;
return (
<div style={{marginBottom: '10px'}}>
@@ -50,7 +86,7 @@ export class EditableCommentContent extends React.Component {
// original comment
return comment.body !== originalBody;
}}
saveComment={saveComment}
saveComment={this.editComment}
bodyLabel={'Edit this comment' /* @TODO (bengo) i18n */}
bodyPlaceholder=""
submitText={'Save changes' /* @TODO (bengo) i18n */}
+6 -1
View File
@@ -14,7 +14,7 @@ const {fetchAssetSuccess} = assetActions;
import {NEW_COMMENT_COUNT_POLL_INTERVAL} from 'coral-framework/constants/comments';
import {queryStream} from 'coral-framework/graphql/queries';
import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag, ignoreUser} from 'coral-framework/graphql/mutations';
import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag, ignoreUser, editComment} from 'coral-framework/graphql/mutations';
import {editName} from 'coral-framework/actions/user';
import {updateCountCache, viewAllComments} from 'coral-framework/actions/asset';
import {notificationActions, authActions, assetActions, pym} from 'coral-framework';
@@ -73,6 +73,9 @@ class Embed extends React.Component {
// dispatch action to ignore another user
ignoreUser: React.PropTypes.func,
// edit a comment, passed (id, { body })
editComment: React.PropTypes.func,
}
componentDidMount () {
@@ -265,6 +268,7 @@ class Embed extends React.Component {
open={openStream}
addNotification={this.props.addNotification}
postItem={this.props.postItem}
editComment={this.props.editComment}
setActiveReplyBox={this.setActiveReplyBox}
activeReplyBox={this.state.activeReplyBox}
asset={asset}
@@ -344,5 +348,6 @@ export default compose(
removeCommentTag,
ignoreUser,
deleteAction,
editComment,
queryStream,
)(Embed);
+5 -1
View File
@@ -28,7 +28,10 @@ class Stream extends React.Component {
ignoreUser: React.PropTypes.func,
// list of user ids that should be rendered as ignored
ignoredUsers: React.PropTypes.arrayOf(React.PropTypes.string)
ignoredUsers: React.PropTypes.arrayOf(React.PropTypes.string),
// edit a comment, passed (id, { body })
editComment: React.PropTypes.func,
}
constructor(props) {
@@ -74,6 +77,7 @@ class Stream extends React.Component {
addNotification={addNotification}
depth={0}
postItem={postItem}
editComment={this.props.editComment}
asset={asset}
currentUser={currentUser}
postLike={postLike}
@@ -0,0 +1,7 @@
mutation EditComment ($id: ID!, $edit: EditCommentInput) {
editComment(id:$id, edit:$edit) {
errors {
translation_key
}
}
}
@@ -8,6 +8,7 @@ import ADD_COMMENT_TAG from './addCommentTag.graphql';
import REMOVE_COMMENT_TAG from './removeCommentTag.graphql';
import IGNORE_USER from './ignoreUser.graphql';
import STOP_IGNORING_USER from './stopIgnoringUser.graphql';
import EDIT_COMMENT from './editComment.graphql';
import MY_IGNORED_USERS from '../queries/myIgnoredUsers.graphql';
import STREAM_QUERY from '../queries/streamQuery.graphql';
@@ -191,3 +192,24 @@ export const stopIgnoringUser = graphql(STOP_IGNORING_USER, {
};
}
});
export const editComment = graphql(EDIT_COMMENT, {
props: ({mutate, ownProps}) => {
return {
editComment: (id, edit) => {
return mutate({
variables: {
id,
edit,
},
refetchQueries: [
{
query: STREAM_QUERY,
variables: variablesForStreamQuery(ownProps),
}
]
});
}
};
}
});
+2
View File
@@ -23,8 +23,10 @@
"comments": "comments",
"commentIsIgnored": "This comment is hidden because you ignored this user.",
"error": {
"editWindowExpired": "You can no longer edit this comment. The time window to do so has expired.",
"emailNotVerified": "Email address {0} not verified.",
"email": "Not a valid E-Mail",
"networkError": "Failed to connect to server. Check your internet connection and try again.",
"password": "Password must be at least 8 characters",
"username": "Usernames can contain letters, numbers and _ only",
"confirmPassword": "Passwords don't match. Please, check again",
+4 -1
View File
@@ -251,7 +251,10 @@ const editComment = async ({user, loaders: {Comments}}, {id, edit}) => {
} catch (error) {
switch (error.name) {
case 'EditWindowExpired':
throw errors.ErrNotAuthorized;
throw new errors.APIError('You can no longer edit this comment. The window to do so has expired.', {
status: 401,
translation_key: 'error.editWindowExpired',
});
default:
throw error;
}
+1 -1
View File
@@ -15,7 +15,7 @@ const STATUSES = [
'NONE',
];
const EDIT_WINDOW_MS = 5 * 60 * 60; // 5 minutes
const EDIT_WINDOW_MS = 5 * 60 * 1000; // 5 minutes
module.exports = class CommentsService {
+1 -1
View File
@@ -96,7 +96,7 @@ describe('graph.mutations.editComment', () => {
});
expect(response.errors).to.be.empty;
expect(response.data.editComment.errors).to.not.be.empty;
expect(response.data.editComment.errors[0].translation_key).to.equal('NOT_AUTHORIZED');
expect(response.data.editComment.errors[0].translation_key).to.equal('error.editWindowExpired');
const commentAfterEdit = await CommentsService.findById(comment.id);
// it *hasn't* changed from the original