mirror of
https://github.com/nextcloud/server.git
synced 2026-06-10 17:23:59 -04:00
Merge pull request #22108 from owncloud/comments-edit
Comment owner can now edit or delete
This commit is contained in:
commit
6a5b0eafa5
4 changed files with 335 additions and 47 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,28 +8,38 @@
|
|||
*
|
||||
*/
|
||||
|
||||
/* global Handlebars */
|
||||
|
||||
(function(OC, OCA) {
|
||||
var TEMPLATE =
|
||||
'<div class="newCommentRow comment">' +
|
||||
' <div class="authorRow">' +
|
||||
' {{#if avatarEnabled}}' +
|
||||
' <div class="avatar" data-username="{{userId}}"></div>' +
|
||||
' {{/if}}' +
|
||||
' <div class="author">{{userDisplayName}}</div>' +
|
||||
' </div>' +
|
||||
' <form class="newCommentForm">' +
|
||||
' <textarea class="message" placeholder="{{newMessagePlaceholder}}"></textarea>' +
|
||||
' <input class="submit" type="submit" value="{{submitText}}" />' +
|
||||
' <div class="submitLoading icon-loading-small hidden"></div>'+
|
||||
' </form>' +
|
||||
' <ul class="comments">' +
|
||||
' </ul>' +
|
||||
'</div>' +
|
||||
'<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="{{actorId}}"></div>' +
|
||||
' {{/if}}' +
|
||||
' <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}}">{{{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>' +
|
||||
'</div>';
|
||||
|
||||
var COMMENT_TEMPLATE =
|
||||
'<li class="comment{{#if isUnread}} unread{{/if}}" data-id="{{id}}">' +
|
||||
' <div class="authorRow">' +
|
||||
|
|
@ -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);
|
||||
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);
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue