diff options
author | Thomas Müller <thomas.mueller@tmit.eu> | 2016-02-04 10:47:49 +0100 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2016-02-04 10:47:49 +0100 |
commit | 6a5b0eafa5e98ea8d30a46f9d6f8072960b0beca (patch) | |
tree | 2120dbe7913c2386256c3969b1a2c5d72c0cc762 /apps | |
parent | 2c41a266c0ecf5dcfcbc2160761823ddd0f08525 (diff) | |
parent | 805ba20bae69a984802b479a0a2e499ed6980c7d (diff) | |
download | nextcloud-server-6a5b0eafa5e98ea8d30a46f9d6f8072960b0beca.tar.gz nextcloud-server-6a5b0eafa5e98ea8d30a46f9d6f8072960b0beca.zip |
Merge pull request #22108 from owncloud/comments-edit
Comment owner can now edit or delete
Diffstat (limited to 'apps')
-rw-r--r-- | apps/comments/css/comments.css | 27 | ||||
-rw-r--r-- | apps/comments/js/commentmodel.js | 13 | ||||
-rw-r--r-- | apps/comments/js/commentstabview.js | 202 | ||||
-rw-r--r-- | apps/comments/tests/js/commentstabviewSpec.js | 134 |
4 files changed, 332 insertions, 44 deletions
diff --git a/apps/comments/css/comments.css b/apps/comments/css/comments.css index 5e247aaeb71..b86ed38efe7 100644 --- a/apps/comments/css/comments.css +++ b/apps/comments/css/comments.css @@ -50,6 +50,33 @@ right: 0; } +#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..2c5e9414751 100644 --- a/apps/comments/js/commentstabview.js +++ b/apps/comments/js/commentstabview.js @@ -8,27 +8,37 @@ * */ +/* global Handlebars */ + (function(OC, OCA) { var TEMPLATE = - '<div class="newCommentRow comment">' + + '<ul class="comments">' + + '</ul>' + + '<div class="empty hidden">{{emptyResultLabel}}</div>' + + '<input type="button" class="showMore hidden" value="{{moreLabel}}"' + + ' name="show-more" id="show-more" />' + + '<div class="loading hidden" style="height: 50px"></div>'; + + var EDIT_COMMENT_TEMPLATE = + '<div class="newCommentRow comment" data-id="{{id}}">' + ' <div class="authorRow">' + ' {{#if avatarEnabled}}' + - ' <div class="avatar" data-username="{{userId}}"></div>' + + ' <div class="avatar" data-username="{{actorId}}"></div>' + ' {{/if}}' + - ' <div class="author">{{userDisplayName}}</div>' + + ' <div class="author">{{actorDisplayName}}</div>' + + '{{#if isEditMode}}' + + ' <a href="#" class="action delete icon icon-delete has-tooltip" title="{{deleteTooltip}}"></a>' + + '{{/if}}' + ' </div>' + ' <form class="newCommentForm">' + - ' <textarea class="message" placeholder="{{newMessagePlaceholder}}"></textarea>' + + ' <textarea class="message" placeholder="{{newMessagePlaceholder}}">{{{message}}}</textarea>' + ' <input class="submit" type="submit" value="{{submitText}}" />' + + '{{#if isEditMode}}' + + ' <input class="cancel" type="button" value="{{cancelText}}" />' + + '{{/if}}' + ' <div class="submitLoading icon-loading-small hidden"></div>'+ ' </form>' + - ' <ul class="comments">' + - ' </ul>' + - '</div>' + - '<div class="empty hidden">{{emptyResultLabel}}</div>' + - '<input type="button" class="showMore hidden" value="{{moreLabel}}"' + - ' name="show-more" id="show-more" />' + - '<div class="loading hidden" style="height: 50px"></div>'; + '</div>'; var COMMENT_TEMPLATE = '<li class="comment{{#if isUnread}} unread{{/if}}" data-id="{{id}}">' + @@ -37,6 +47,9 @@ ' <div class="avatar" data-username="{{actorId}}"> </div>' + ' {{/if}}' + ' <div class="author">{{actorDisplayName}}</div>' + + '{{#if isUserAuthor}}' + + ' <a href="#" class="action edit icon icon-rename has-tooltip" title="{{editTooltip}}"></a>' + + '{{/if}}' + ' <div class="date has-tooltip" title="{{altDate}}">{{date}}</div>' + ' </div>' + ' <div class="message">{{{formattedMessage}}}</div>' + @@ -52,7 +65,10 @@ events: { 'submit .newCommentForm': '_onSubmitComment', - 'click .showMore': '_onClickShowMore' + 'click .showMore': '_onClickShowMore', + 'click .action.edit': '_onClickEditComment', + 'click .action.delete': '_onClickDeleteComment', + 'click .cancel': '_onClickCloseComment' }, initialize: function() { @@ -65,7 +81,6 @@ this._avatarsEnabled = !!OC.config.enable_avatars; // TODO: error handling - _.bindAll(this, '_onSubmitComment'); }, template: function(params) { @@ -75,10 +90,24 @@ var currentUser = OC.getCurrentUser(); return this._template(_.extend({ avatarEnabled: this._avatarsEnabled, - userId: currentUser.uid, - userDisplayName: currentUser.displayName, + 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, + 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)); }, @@ -87,7 +116,9 @@ this._commentTemplate = Handlebars.compile(COMMENT_TEMPLATE); } return this._commentTemplate(_.extend({ - avatarEnabled: this._avatarsEnabled + avatarEnabled: this._avatarsEnabled, + editTooltip: t('comments', 'Edit comment'), + isUserAuthor: OC.getCurrentUser().uid === params.actorId }, params)); }, @@ -115,6 +146,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); @@ -136,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) { @@ -203,13 +237,69 @@ 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()); + $formRow.find('.has-tooltip').tooltip(); + + 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'); + 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); + } + }); + + + 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 +315,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; } 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<br>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; |