@@ -27,6 +27,7 @@ $eventDispatcher->addListener( | |||
\OCP\Util::addScript('comments', 'app'); | |||
\OCP\Util::addScript('comments', 'commentmodel'); | |||
\OCP\Util::addScript('comments', 'commentcollection'); | |||
\OCP\Util::addScript('comments', 'commentsummarymodel'); | |||
\OCP\Util::addScript('comments', 'commentstabview'); | |||
\OCP\Util::addScript('comments', 'filesplugin'); | |||
\OCP\Util::addStyle('comments', 'comments'); |
@@ -10,8 +10,6 @@ | |||
(function(OC, OCA) { | |||
var NS_OWNCLOUD = 'http://owncloud.org/ns'; | |||
/** | |||
* @class OCA.Comments.CommentCollection | |||
* @classdesc | |||
@@ -26,12 +24,40 @@ | |||
model: OCA.Comments.CommentModel, | |||
/** | |||
* Object type | |||
* | |||
* @type string | |||
*/ | |||
_objectType: 'files', | |||
/** | |||
* Object id | |||
* | |||
* @type string | |||
*/ | |||
_objectId: null, | |||
/** | |||
* True if there are no more page results left to fetch | |||
* | |||
* @type bool | |||
*/ | |||
_endReached: false, | |||
/** | |||
* Number of comments to fetch per page | |||
* | |||
* @type int | |||
*/ | |||
_limit : 20, | |||
/** | |||
* Initializes the collection | |||
* | |||
* @param {string} [options.objectType] object type | |||
* @param {string} [options.objectId] object id | |||
*/ | |||
initialize: function(models, options) { | |||
options = options || {}; | |||
if (options.objectType) { | |||
@@ -58,6 +84,7 @@ | |||
reset: function() { | |||
this._endReached = false; | |||
this._summaryModel = null; | |||
return OC.Backbone.Collection.prototype.reset.apply(this, arguments); | |||
}, | |||
@@ -81,6 +108,7 @@ | |||
var success = options.success; | |||
options = _.extend({ | |||
remove: false, | |||
parse: true, | |||
data: body, | |||
davProperties: CommentCollection.prototype.model.prototype.davProperties, | |||
success: function(resp) { | |||
@@ -102,6 +130,35 @@ | |||
}, options); | |||
return this.sync('REPORT', this, options); | |||
}, | |||
/** | |||
* Returns the matching summary model | |||
* | |||
* @return {OCA.Comments.CommentSummaryModel} summary model | |||
*/ | |||
getSummaryModel: function() { | |||
if (!this._summaryModel) { | |||
this._summaryModel = new OCA.Comments.CommentSummaryModel({ | |||
id: this._objectId, | |||
objectType: this._objectType | |||
}); | |||
} | |||
return this._summaryModel; | |||
}, | |||
/** | |||
* Updates the read marker for this comment thread | |||
* | |||
* @param {Date} [date] optional date, defaults to now | |||
* @param {Object} [options] backbone options | |||
*/ | |||
updateReadMarker: function(date, options) { | |||
options = options || {}; | |||
return this.getSummaryModel().save({ | |||
readMarker: (date || new Date()).toUTCString() | |||
}, options); | |||
} | |||
}); | |||
@@ -34,11 +34,12 @@ | |||
'actorDisplayName': '{' + NS_OWNCLOUD + '}actorDisplayName', | |||
'creationDateTime': '{' + NS_OWNCLOUD + '}creationDateTime', | |||
'objectType': '{' + NS_OWNCLOUD + '}objectType', | |||
'objectId': '{' + NS_OWNCLOUD + '}objectId' | |||
'objectId': '{' + NS_OWNCLOUD + '}objectId', | |||
'isUnread': '{' + NS_OWNCLOUD + '}isUnread' | |||
}, | |||
parse: function(data) { | |||
// TODO: parse non-string values | |||
data.isUnread = (data.isUnread === 'true'); | |||
return data; | |||
} | |||
}); |
@@ -31,7 +31,7 @@ | |||
'<div class="loading hidden" style="height: 50px"></div>'; | |||
var COMMENT_TEMPLATE = | |||
'<li class="comment">' + | |||
'<li class="comment{{#if isUnread}} unread{{/if}}" data-id="{{id}}">' + | |||
' <div class="authorRow">' + | |||
' {{#if avatarEnabled}}' + | |||
' <div class="avatar" data-username="{{actorId}}"> </div>' + | |||
@@ -97,12 +97,14 @@ | |||
setFileInfo: function(fileInfo) { | |||
if (fileInfo) { | |||
this.model = fileInfo; | |||
this.render(); | |||
this.collection.setObjectId(fileInfo.id); | |||
// reset to first page | |||
this.collection.reset([], {silent: true}); | |||
this.nextPage(); | |||
} else { | |||
this.model = null; | |||
this.render(); | |||
this.collection.reset(); | |||
} | |||
@@ -139,10 +141,29 @@ | |||
this.$el.find('.showMore').addClass('hidden'); | |||
}, | |||
_onEndRequest: function() { | |||
_onEndRequest: function(type) { | |||
var fileInfoModel = this.model; | |||
this._toggleLoading(false); | |||
this.$el.find('.empty').toggleClass('hidden', !!this.collection.length); | |||
this.$el.find('.showMore').toggleClass('hidden', !this.collection.hasMoreResults()); | |||
if (type !== 'REPORT') { | |||
return; | |||
} | |||
// find first unread comment | |||
var firstUnreadComment = this.collection.findWhere({isUnread: true}); | |||
if (firstUnreadComment) { | |||
// update read marker | |||
this.collection.updateReadMarker( | |||
null, | |||
{ | |||
success: function() { | |||
fileInfoModel.set('commentsUnread', 0); | |||
} | |||
} | |||
); | |||
} | |||
}, | |||
_onAddModel: function(model, collection, options) { | |||
@@ -210,7 +231,7 @@ | |||
actorType: 'users', | |||
verb: 'comment', | |||
message: $textArea.val(), | |||
creationDateTime: (new Date()).getTime() | |||
creationDateTime: (new Date()).toUTCString() | |||
}, { | |||
at: 0, | |||
success: function() { |
@@ -0,0 +1,65 @@ | |||
/* | |||
* Copyright (c) 2016 | |||
* | |||
* This file is licensed under the Affero General Public License version 3 | |||
* or later. | |||
* | |||
* See the COPYING-README file. | |||
* | |||
*/ | |||
(function(OC, OCA) { | |||
var NS_OWNCLOUD = 'http://owncloud.org/ns'; | |||
/** | |||
* @class OCA.Comments.CommentSummaryModel | |||
* @classdesc | |||
* | |||
* Model containing summary information related to comments | |||
* like the read marker. | |||
* | |||
*/ | |||
var CommentSummaryModel = OC.Backbone.Model.extend( | |||
/** @lends OCA.Comments.CommentSummaryModel.prototype */ { | |||
sync: OC.Backbone.davSync, | |||
/** | |||
* Object type | |||
* | |||
* @type string | |||
*/ | |||
_objectType: 'files', | |||
/** | |||
* Object id | |||
* | |||
* @type string | |||
*/ | |||
_objectId: null, | |||
davProperties: { | |||
'readMarker': '{' + NS_OWNCLOUD + '}readMarker' | |||
}, | |||
/** | |||
* Initializes the summary model | |||
* | |||
* @param {string} [options.objectType] object type | |||
* @param {string} [options.objectId] object id | |||
*/ | |||
initialize: function(attrs, options) { | |||
options = options || {}; | |||
if (options.objectType) { | |||
this._objectType = options.objectType; | |||
} | |||
}, | |||
url: function() { | |||
return OC.linkToRemote('dav') + '/comments/' + | |||
encodeURIComponent(this._objectType) + '/' + | |||
encodeURIComponent(this.id) + '/'; | |||
} | |||
}); | |||
OCA.Comments.CommentSummaryModel = CommentSummaryModel; | |||
})(OC, OCA); | |||
@@ -100,5 +100,49 @@ describe('OCA.Comments.CommentCollection', function() { | |||
expect(collection.hasMoreResults()).toEqual(true); | |||
}); | |||
describe('resetting read marker', function() { | |||
var updateStub; | |||
var clock; | |||
beforeEach(function() { | |||
updateStub = sinon.stub(OCA.Comments.CommentSummaryModel.prototype, 'save'); | |||
clock = sinon.useFakeTimers(Date.UTC(2016, 1, 3, 10, 5, 9)); | |||
}); | |||
afterEach(function() { | |||
updateStub.restore(); | |||
clock.restore(); | |||
}); | |||
it('resets read marker to the default date', function() { | |||
var successStub = sinon.stub(); | |||
collection.updateReadMarker(null, { | |||
success: successStub | |||
}); | |||
expect(updateStub.calledOnce).toEqual(true); | |||
expect(updateStub.lastCall.args[0]).toEqual({ | |||
readMarker: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString() | |||
}); | |||
updateStub.yieldTo('success'); | |||
expect(successStub.calledOnce).toEqual(true); | |||
}); | |||
it('resets read marker to the given date', function() { | |||
var successStub = sinon.stub(); | |||
collection.updateReadMarker(new Date(Date.UTC(2016, 1, 2, 3, 4, 5)), { | |||
success: successStub | |||
}); | |||
expect(updateStub.calledOnce).toEqual(true); | |||
expect(updateStub.lastCall.args[0]).toEqual({ | |||
readMarker: new Date(Date.UTC(2016, 1, 2, 3, 4, 5)).toUTCString() | |||
}); | |||
updateStub.yieldTo('success'); | |||
expect(successStub.calledOnce).toEqual(true); | |||
}); | |||
}); | |||
}); | |||
@@ -48,7 +48,7 @@ describe('OCA.Comments.CommentsTabView tests', function() { | |||
objectType: 'files', | |||
objectId: 5, | |||
message: 'First', | |||
creationDateTime: Date.UTC(2016, 1, 3, 10, 5, 0) | |||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 0)).toUTCString() | |||
}); | |||
var comment2 = new OCA.Comments.CommentModel({ | |||
id: 2, | |||
@@ -58,7 +58,7 @@ describe('OCA.Comments.CommentsTabView tests', function() { | |||
objectType: 'files', | |||
objectId: 5, | |||
message: 'Second\nNewline', | |||
creationDateTime: Date.UTC(2016, 1, 3, 10, 0, 0) | |||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 0, 0)).toUTCString() | |||
}); | |||
testComments = [comment1, comment2]; | |||
@@ -142,7 +142,7 @@ describe('OCA.Comments.CommentsTabView tests', function() { | |||
objectType: 'files', | |||
objectId: 5, | |||
message: 'Third', | |||
creationDateTime: Date.UTC(2016, 1, 3, 5, 0, 0) | |||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 5, 0, 0)).toUTCString() | |||
}); | |||
view.collection.add(comment3); | |||
@@ -184,7 +184,7 @@ describe('OCA.Comments.CommentsTabView tests', function() { | |||
actorType: 'users', | |||
verb: 'comment', | |||
message: 'New message', | |||
creationDateTime: Date.UTC(2016, 1, 3, 10, 5, 9) | |||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString() | |||
}); | |||
}); | |||
it('does not create a comment if the field is empty', function() { | |||
@@ -195,4 +195,38 @@ describe('OCA.Comments.CommentsTabView tests', function() { | |||
}); | |||
}); | |||
describe('read marker', function() { | |||
var updateMarkerStub; | |||
beforeEach(function() { | |||
updateMarkerStub = sinon.stub(OCA.Comments.CommentCollection.prototype, 'updateReadMarker'); | |||
}); | |||
afterEach(function() { | |||
updateMarkerStub.restore(); | |||
}); | |||
it('resets the read marker after REPORT', function() { | |||
testComments[0].set('isUnread', true, {silent: true}); | |||
testComments[1].set('isUnread', true, {silent: true}); | |||
view.collection.set(testComments); | |||
view.collection.trigger('sync', 'REPORT'); | |||
expect(updateMarkerStub.calledOnce).toEqual(true); | |||
expect(updateMarkerStub.lastCall.args[0]).toBeFalsy(); | |||
}); | |||
it('does not reset the read marker if there was no unread comments', function() { | |||
view.collection.set(testComments); | |||
view.collection.trigger('sync', 'REPORT'); | |||
expect(updateMarkerStub.notCalled).toEqual(true); | |||
}); | |||
it('does not reset the read marker when posting comments', function() { | |||
testComments[0].set('isUnread', true, {silent: true}); | |||
testComments[1].set('isUnread', true, {silent: true}); | |||
view.collection.set(testComments); | |||
view.collection.trigger('sync', 'POST'); | |||
expect(updateMarkerStub.notCalled).toEqual(true); | |||
}); | |||
}); | |||
}); |
@@ -89,6 +89,7 @@ module.exports = function(config) { | |||
'apps/comments/js/app.js', | |||
'apps/comments/js/commentmodel.js', | |||
'apps/comments/js/commentcollection.js', | |||
'apps/comments/js/commentsummarymodel.js', | |||
'apps/comments/js/commentstabview.js', | |||
'apps/comments/js/filesplugin.js' | |||
], |