summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorThomas Müller <thomas.mueller@tmit.eu>2016-02-04 10:47:49 +0100
committerThomas Müller <thomas.mueller@tmit.eu>2016-02-04 10:47:49 +0100
commit6a5b0eafa5e98ea8d30a46f9d6f8072960b0beca (patch)
tree2120dbe7913c2386256c3969b1a2c5d72c0cc762 /apps
parent2c41a266c0ecf5dcfcbc2160761823ddd0f08525 (diff)
parent805ba20bae69a984802b479a0a2e499ed6980c7d (diff)
downloadnextcloud-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.css27
-rw-r--r--apps/comments/js/commentmodel.js13
-rw-r--r--apps/comments/js/commentstabview.js202
-rw-r--r--apps/comments/tests/js/commentstabviewSpec.js134
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;