\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');
(function(OC, OCA) {
- var NS_OWNCLOUD = 'http://owncloud.org/ns';
-
/**
* @class OCA.Comments.CommentCollection
* @classdesc
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) {
reset: function() {
this._endReached = false;
+ this._summaryModel = null;
return OC.Backbone.Collection.prototype.reset.apply(this, arguments);
},
var success = options.success;
options = _.extend({
remove: false,
+ parse: true,
data: body,
davProperties: CommentCollection.prototype.model.prototype.davProperties,
success: function(resp) {
}, 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);
}
});
'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;
}
});
'<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>' +
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();
}
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) {
actorType: 'users',
verb: 'comment',
message: $textArea.val(),
- creationDateTime: (new Date()).getTime()
+ creationDateTime: (new Date()).toUTCString()
}, {
at: 0,
success: function() {
--- /dev/null
+/*
+ * 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);
+
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);
+ });
+ });
});
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,
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];
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);
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() {
});
});
+ 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);
+ });
+ });
});
'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'
],