From bbefe1ed64c0b4a9b77dc8d9b06b9f56c6b7f6e8 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 3 Feb 2016 18:44:14 +0100 Subject: Comment owner can now edit or delete --- apps/comments/css/comments.css | 27 ++++++ apps/comments/js/commentmodel.js | 13 ++- apps/comments/js/commentstabview.js | 183 +++++++++++++++++++++++++++++------- 3 files changed, 185 insertions(+), 38 deletions(-) (limited to 'apps') diff --git a/apps/comments/css/comments.css b/apps/comments/css/comments.css index 5e247aaeb71..f4e7d144ec5 100644 --- a/apps/comments/css/comments.css +++ b/apps/comments/css/comments.css @@ -46,10 +46,37 @@ } #commentsTabView .comment .date { + position: absolute; + right: 20px; +} + +#commentsTabView .comment .action { + opacity: 0; + vertical-align: middle; + display: inline-block; +} + +#commentsTabView .comment:hover .action { + opacity: 0.3; +} + +#commentsTabView .comment .action:hover { + opacity: 1; +} + +#commentsTabView .comment .action.delete { position: absolute; right: 0; } +#commentsTabView .comment.disabled { + opacity: 0.3; +} + +#commentsTabView .comment.disabled .action { + visibility: hidden; +} + .app-files .action-comment>img { margin-right: 5px; } diff --git a/apps/comments/js/commentmodel.js b/apps/comments/js/commentmodel.js index ba04fd61de3..89492707b61 100644 --- a/apps/comments/js/commentmodel.js +++ b/apps/comments/js/commentmodel.js @@ -39,8 +39,17 @@ }, parse: function(data) { - data.isUnread = (data.isUnread === 'true'); - return data; + return { + id: data.id, + message: data.message, + actorType: data.actorType, + actorId: data.actorId, + actorDisplayName: data.actorDisplayName, + creationDateTime: data.creationDateTime, + objectType: data.objectType, + objectId: data.objectId, + isUnread: (data.isUnread === 'true') + }; } }); diff --git a/apps/comments/js/commentstabview.js b/apps/comments/js/commentstabview.js index 188d8c5943c..9db39408c70 100644 --- a/apps/comments/js/commentstabview.js +++ b/apps/comments/js/commentstabview.js @@ -8,27 +8,34 @@ * */ +/* global Handlebars */ + (function(OC, OCA) { var TEMPLATE = - '
' + + '' + + '' + + '' + + ''; + + var EDIT_COMMENT_TEMPLATE = + '
' + '
' + ' {{#if avatarEnabled}}' + - '
' + + '
' + ' {{/if}}' + - '
{{userDisplayName}}
' + + '
{{actorDisplayName}}
' + + '{{#if isEditMode}}' + + ' ' + + '{{/if}}' + '
' + '
' + - ' ' + + ' ' + ' ' + ' '+ '
' + - '
    ' + - '
' + - '
' + - '' + - '' + - ''; + '
'; var COMMENT_TEMPLATE = '
  • ' + @@ -37,7 +44,13 @@ '
    ' + ' {{/if}}' + '
    {{actorDisplayName}}
    ' + + '{{#if isUserAuthor}}' + + ' ' + + '{{/if}}' + '
    {{date}}
    ' + + '{{#if isUserAuthor}}' + + ' ' + + '{{/if}}' + ' ' + '
    {{{formattedMessage}}}
    ' + '
  • '; @@ -52,7 +65,10 @@ events: { 'submit .newCommentForm': '_onSubmitComment', - 'click .showMore': '_onClickShowMore' + 'click .showMore': '_onClickShowMore', + 'click .action.edit': '_onClickEditComment', + 'click .action.delete': '_onClickDeleteComment', + 'click .action.close': '_onClickCloseComment' }, initialize: function() { @@ -65,7 +81,6 @@ this._avatarsEnabled = !!OC.config.enable_avatars; // TODO: error handling - _.bindAll(this, '_onSubmitComment'); }, template: function(params) { @@ -74,6 +89,18 @@ } var currentUser = OC.getCurrentUser(); return this._template(_.extend({ + avatarEnabled: this._avatarsEnabled, + actorId: currentUser.uid, + actorDisplayName: currentUser.displayName + }, params)); + }, + + editCommentTemplate: function(params) { + if (!this._editCommentTemplate) { + this._editCommentTemplate = Handlebars.compile(EDIT_COMMENT_TEMPLATE); + } + var currentUser = OC.getCurrentUser(); + return this._editCommentTemplate(_.extend({ avatarEnabled: this._avatarsEnabled, userId: currentUser.uid, userDisplayName: currentUser.displayName, @@ -87,7 +114,10 @@ this._commentTemplate = Handlebars.compile(COMMENT_TEMPLATE); } return this._commentTemplate(_.extend({ - avatarEnabled: this._avatarsEnabled + avatarEnabled: this._avatarsEnabled, + deleteTooltip: t('comments', 'Delete comment'), + editTooltip: t('comments', 'Edit comment'), + isUserAuthor: OC.getCurrentUser().uid === params.actorId }, params)); }, @@ -115,6 +145,7 @@ emptyResultLabel: t('comments', 'No other comments available'), moreLabel: t('comments', 'More comments...') })); + this.$el.find('.comments').before(this.editCommentTemplate({})); this.$el.find('.has-tooltip').tooltip(); this.$container = this.$el.find('ul.comments'); this.$el.find('.avatar').avatar(OC.getCurrentUser().uid, 28); @@ -203,13 +234,65 @@ this.collection.fetchNext(); }, + _onClickEditComment: function(ev) { + ev.preventDefault(); + var $comment = $(ev.target).closest('.comment'); + var commentId = $comment.data('id'); + var commentToEdit = this.collection.get(commentId); + var $formRow = $(this.editCommentTemplate(_.extend({ + isEditMode: true, + submitText: t('comments', 'Save') + }, commentToEdit.attributes))); + + $comment.addClass('hidden'); + // spawn form + $comment.after($formRow); + $formRow.data('commentEl', $comment); + + // copy avatar element from original to avoid flickering + $formRow.find('.avatar').replaceWith($comment.find('.avatar').clone()); + + return false; + }, + + _onClickCloseComment: function(ev) { + ev.preventDefault(); + var $row = $(ev.target).closest('.comment'); + $row.data('commentEl').removeClass('hidden'); + $row.remove(); + return false; + }, + + _onClickDeleteComment: function(ev) { + ev.preventDefault(); + var $comment = $(ev.target).closest('.comment'); + var commentId = $comment.data('id'); + // TODO: undo logic + + $comment.addClass('disabled'); + this.collection.get(commentId).destroy({ + success: function() { + $comment.remove(); + }, + error: function(msg) { + $comment.removeClass('disabled'); + OC.Notification.showTemporary(msg); + } + }); + + + return false; + }, + _onClickShowMore: function(ev) { ev.preventDefault(); this.nextPage(); }, _onSubmitComment: function(e) { + var self = this; var $form = $(e.target); + var commentId = $form.closest('.comment').data('id'); var currentUser = OC.getCurrentUser(); var $submit = $form.find('.submit'); var $loading = $form.find('.submitLoading'); @@ -225,28 +308,56 @@ $submit.addClass('hidden'); $loading.removeClass('hidden'); - this.collection.create({ - actorId: currentUser.uid, - actorDisplayName: currentUser.displayName, - actorType: 'users', - verb: 'comment', - message: $textArea.val(), - creationDateTime: (new Date()).toUTCString() - }, { - at: 0, - success: function() { - $submit.removeClass('hidden'); - $loading.addClass('hidden'); - $textArea.val('').prop('disabled', false); - }, - error: function(msg) { - $submit.removeClass('hidden'); - $loading.addClass('hidden'); - $textArea.prop('disabled', false); - - OC.Notification.showTemporary(msg); - } - }); + if (commentId) { + // edit mode + var comment = this.collection.get(commentId); + comment.save({ + message: $textArea.val() + }, { + success: function(model) { + var $row = $form.closest('.comment'); + $submit.removeClass('hidden'); + $loading.addClass('hidden'); + $row.data('commentEl') + .removeClass('hidden') + .find('.message') + .html(self._formatMessage(model.get('message'))); + $row.remove(); + }, + error: function(msg) { + $submit.removeClass('hidden'); + $loading.addClass('hidden'); + $textArea.prop('disabled', false); + + OC.Notification.showTemporary(msg); + } + }); + } else { + this.collection.create({ + actorId: currentUser.uid, + actorDisplayName: currentUser.displayName, + actorType: 'users', + verb: 'comment', + message: $textArea.val(), + creationDateTime: (new Date()).toUTCString() + }, { + at: 0, + // wait for real creation before adding + wait: true, + success: function() { + $submit.removeClass('hidden'); + $loading.addClass('hidden'); + $textArea.val('').prop('disabled', false); + }, + error: function(msg) { + $submit.removeClass('hidden'); + $loading.addClass('hidden'); + $textArea.prop('disabled', false); + + OC.Notification.showTemporary(msg); + } + }); + } return false; } -- cgit v1.2.3 From 1caee3ac2e6406df2e9ab644925bff106836b0a4 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 3 Feb 2016 20:32:37 +0100 Subject: Move comment delete action into edit mode --- apps/comments/css/comments.css | 2 +- apps/comments/js/commentstabview.js | 33 ++++++++++++++++++++------------- 2 files changed, 21 insertions(+), 14 deletions(-) (limited to 'apps') diff --git a/apps/comments/css/comments.css b/apps/comments/css/comments.css index f4e7d144ec5..b86ed38efe7 100644 --- a/apps/comments/css/comments.css +++ b/apps/comments/css/comments.css @@ -47,7 +47,7 @@ #commentsTabView .comment .date { position: absolute; - right: 20px; + right: 0; } #commentsTabView .comment .action { diff --git a/apps/comments/js/commentstabview.js b/apps/comments/js/commentstabview.js index 9db39408c70..2c5e9414751 100644 --- a/apps/comments/js/commentstabview.js +++ b/apps/comments/js/commentstabview.js @@ -27,12 +27,15 @@ ' {{/if}}' + '
    {{actorDisplayName}}
    ' + '{{#if isEditMode}}' + - ' ' + + ' ' + '{{/if}}' + ' ' + '
    ' + ' ' + ' ' + + '{{#if isEditMode}}' + + ' ' + + '{{/if}}' + ' '+ '
    ' + ''; @@ -48,9 +51,6 @@ ' ' + '{{/if}}' + '
    {{date}}
    ' + - '{{#if isUserAuthor}}' + - ' ' + - '{{/if}}' + ' ' + '
    {{{formattedMessage}}}
    ' + ''; @@ -68,7 +68,7 @@ 'click .showMore': '_onClickShowMore', 'click .action.edit': '_onClickEditComment', 'click .action.delete': '_onClickDeleteComment', - 'click .action.close': '_onClickCloseComment' + 'click .cancel': '_onClickCloseComment' }, initialize: function() { @@ -102,10 +102,12 @@ var currentUser = OC.getCurrentUser(); return this._editCommentTemplate(_.extend({ avatarEnabled: this._avatarsEnabled, - userId: currentUser.uid, - userDisplayName: currentUser.displayName, + actorId: currentUser.uid, + actorDisplayName: currentUser.displayName, newMessagePlaceholder: t('comments', 'Type in a new comment...'), - submitText: t('comments', 'Post') + deleteTooltip: t('comments', 'Delete comment'), + submitText: t('comments', 'Post'), + cancelText: t('comments', 'Cancel') }, params)); }, @@ -115,7 +117,6 @@ } return this._commentTemplate(_.extend({ avatarEnabled: this._avatarsEnabled, - deleteTooltip: t('comments', 'Delete comment'), editTooltip: t('comments', 'Edit comment'), isUserAuthor: OC.getCurrentUser().uid === params.actorId }, params)); @@ -167,9 +168,11 @@ this.$el.find('.loading').toggleClass('hidden', !state); }, - _onRequest: function() { - this._toggleLoading(true); - this.$el.find('.showMore').addClass('hidden'); + _onRequest: function(type) { + if (type === 'REPORT') { + this._toggleLoading(true); + this.$el.find('.showMore').addClass('hidden'); + } }, _onEndRequest: function(type) { @@ -251,6 +254,7 @@ // copy avatar element from original to avoid flickering $formRow.find('.avatar').replaceWith($comment.find('.avatar').clone()); + $formRow.find('.has-tooltip').tooltip(); return false; }, @@ -267,14 +271,17 @@ ev.preventDefault(); var $comment = $(ev.target).closest('.comment'); var commentId = $comment.data('id'); - // TODO: undo logic + var $loading = $comment.find('.submitLoading'); $comment.addClass('disabled'); + $loading.removeClass('hidden'); this.collection.get(commentId).destroy({ success: function() { + $comment.data('commentEl').remove(); $comment.remove(); }, error: function(msg) { + $loading.addClass('hidden'); $comment.removeClass('disabled'); OC.Notification.showTemporary(msg); } -- cgit v1.2.3 From 805ba20bae69a984802b479a0a2e499ed6980c7d Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 3 Feb 2016 21:06:37 +0100 Subject: Added unit tests for edit/delete comments --- apps/comments/tests/js/commentstabviewSpec.js | 134 ++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) (limited to 'apps') diff --git a/apps/comments/tests/js/commentstabviewSpec.js b/apps/comments/tests/js/commentstabviewSpec.js index 432fa5ddc4c..4c3d38290ba 100644 --- a/apps/comments/tests/js/commentstabviewSpec.js +++ b/apps/comments/tests/js/commentstabviewSpec.js @@ -195,6 +195,140 @@ describe('OCA.Comments.CommentsTabView tests', function() { }); }); + describe('editing comments', function() { + var saveStub; + var currentUserStub; + + beforeEach(function() { + saveStub = sinon.stub(OCA.Comments.CommentModel.prototype, 'save'); + currentUserStub = sinon.stub(OC, 'getCurrentUser'); + currentUserStub.returns({ + uid: 'testuser', + displayName: 'Test User' + }); + view.collection.add({ + id: 1, + actorId: 'testuser', + actorDisplayName: 'Test User', + actorType: 'users', + verb: 'comment', + message: 'New message', + creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString() + }); + view.collection.add({ + id: 2, + actorId: 'anotheruser', + actorDisplayName: 'Another User', + actorType: 'users', + verb: 'comment', + message: 'New message from another user', + creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString() + }); + }); + afterEach(function() { + saveStub.restore(); + currentUserStub.restore(); + }); + + it('shows edit link for owner comments', function() { + var $comment = view.$el.find('.comment[data-id=1]'); + expect($comment.length).toEqual(1); + expect($comment.find('.action.edit').length).toEqual(1); + }); + + it('does not show edit link for other user\'s comments', function() { + var $comment = view.$el.find('.comment[data-id=2]'); + expect($comment.length).toEqual(1); + expect($comment.find('.action.edit').length).toEqual(0); + }); + + it('shows edit form when clicking edit', function() { + var $comment = view.$el.find('.comment[data-id=1]'); + $comment.find('.action.edit').click(); + + expect($comment.hasClass('hidden')).toEqual(true); + var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]'); + expect($formRow.length).toEqual(1); + }); + + it('saves message and updates comment item when clicking save', function() { + var $comment = view.$el.find('.comment[data-id=1]'); + $comment.find('.action.edit').click(); + + var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]'); + expect($formRow.length).toEqual(1); + + $formRow.find('textarea').val('modified\nmessage'); + $formRow.find('form').submit(); + + expect(saveStub.calledOnce).toEqual(true); + expect(saveStub.lastCall.args[0]).toEqual({ + message: 'modified\nmessage' + }); + + var model = view.collection.get(1); + // simulate the fact that save sets the attribute + model.set('message', 'modified\nmessage'); + saveStub.yieldTo('success', model); + + // original comment element is visible again + expect($comment.hasClass('hidden')).toEqual(false); + // and its message was updated + expect($comment.find('.message').html()).toEqual('modified
    message'); + + // form row is gone + $formRow = view.$el.find('.newCommentRow.comment[data-id=1]'); + expect($formRow.length).toEqual(0); + }); + + it('restores original comment when cancelling', function() { + var $comment = view.$el.find('.comment[data-id=1]'); + $comment.find('.action.edit').click(); + + var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]'); + expect($formRow.length).toEqual(1); + + $formRow.find('textarea').val('modified\nmessage'); + $formRow.find('.cancel').click(); + + expect(saveStub.notCalled).toEqual(true); + + // original comment element is visible again + expect($comment.hasClass('hidden')).toEqual(false); + // and its message was not updated + expect($comment.find('.message').html()).toEqual('New message'); + + // form row is gone + $formRow = view.$el.find('.newCommentRow.comment[data-id=1]'); + expect($formRow.length).toEqual(0); + }); + + it('destroys model when clicking delete', function() { + var destroyStub = sinon.stub(OCA.Comments.CommentModel.prototype, 'destroy'); + var $comment = view.$el.find('.comment[data-id=1]'); + $comment.find('.action.edit').click(); + + var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]'); + expect($formRow.length).toEqual(1); + + $formRow.find('.delete').click(); + + expect(destroyStub.calledOnce).toEqual(true); + expect(destroyStub.thisValues[0].id).toEqual(1); + + destroyStub.yieldTo('success'); + + // original comment element is gone + $comment = view.$el.find('.comment[data-id=1]'); + expect($comment.length).toEqual(0); + + // form row is gone + $formRow = view.$el.find('.newCommentRow.comment[data-id=1]'); + expect($formRow.length).toEqual(0); + + destroyStub.restore(); + }); + }); describe('read marker', function() { var updateMarkerStub; -- cgit v1.2.3