diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 442c79bc1..cdbd9c978 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -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 ? this.setState({isEditing: false})} /> : } @@ -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} diff --git a/client/coral-embed-stream/src/EditableCommentContent.js b/client/coral-embed-stream/src/EditableCommentContent.js index 09c5fae9b..f792985ec 100644 --- a/client/coral-embed-stream/src/EditableCommentContent.js +++ b/client/coral-embed-stream/src/EditableCommentContent.js @@ -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 (
@@ -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 */} diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index fbe24c8c7..ba25b5b87 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -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); diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js index ab64ee98f..a1b07f5ca 100644 --- a/client/coral-embed-stream/src/Stream.js +++ b/client/coral-embed-stream/src/Stream.js @@ -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} diff --git a/client/coral-framework/graphql/mutations/editComment.graphql b/client/coral-framework/graphql/mutations/editComment.graphql new file mode 100644 index 000000000..7becd6def --- /dev/null +++ b/client/coral-framework/graphql/mutations/editComment.graphql @@ -0,0 +1,7 @@ +mutation EditComment ($id: ID!, $edit: EditCommentInput) { + editComment(id:$id, edit:$edit) { + errors { + translation_key + } + } +} diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index 4a3abb48e..2d57c8aac 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -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), + } + ] + }); + } + }; + } +}); diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 88ead57af..35317920f 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -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", diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 3f690e95b..4bb228adb 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -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; } diff --git a/services/comments.js b/services/comments.js index 93bf9e097..40f0ccce0 100644 --- a/services/comments.js +++ b/services/comments.js @@ -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 { diff --git a/test/server/graph/mutations/editComment.js b/test/server/graph/mutations/editComment.js index 4cf162308..cd213ac35 100644 --- a/test/server/graph/mutations/editComment.js +++ b/test/server/graph/mutations/editComment.js @@ -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