@@ -9,6 +9,7 @@ | |||
# ignore all apps except core ones | |||
/apps*/* | |||
!/apps/comments | |||
!/apps/dav | |||
!/apps/files | |||
!/apps/federation |
@@ -0,0 +1,34 @@ | |||
<?php | |||
/** | |||
* @author Vincent Petry <pvince81@owncloud.com> | |||
* | |||
* @copyright Copyright (c) 2016, ownCloud, Inc. | |||
* @license AGPL-3.0 | |||
* | |||
* This code is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License, version 3, | |||
* as published by the Free Software Foundation. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License, version 3, | |||
* along with this program. If not, see <http://www.gnu.org/licenses/> | |||
* | |||
*/ | |||
$eventDispatcher = \OC::$server->getEventDispatcher(); | |||
$eventDispatcher->addListener( | |||
'OCA\Files::loadAdditionalScripts', | |||
function() { | |||
\OCP\Util::addScript('oc-backbone-webdav'); | |||
\OCP\Util::addScript('comments', 'app'); | |||
\OCP\Util::addScript('comments', 'commentmodel'); | |||
\OCP\Util::addScript('comments', 'commentcollection'); | |||
\OCP\Util::addScript('comments', 'commentstabview'); | |||
\OCP\Util::addScript('comments', 'filesplugin'); | |||
\OCP\Util::addStyle('comments', 'comments'); | |||
} | |||
); |
@@ -0,0 +1,16 @@ | |||
<?xml version="1.0"?> | |||
<info> | |||
<id>comments</id> | |||
<name>Comments</name> | |||
<description>Files app plugin to add comments to files</description> | |||
<licence>AGPL</licence> | |||
<author>Arthur Shiwon, Vincent Petry</author> | |||
<default_enable/> | |||
<version>0.1</version> | |||
<dependencies> | |||
<owncloud min-version="9.0" max-version="9.0" /> | |||
</dependencies> | |||
<documentation> | |||
<user>user-comments</user> | |||
</documentation> | |||
</info> |
@@ -0,0 +1,20 @@ | |||
/* | |||
* Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com> | |||
* | |||
* This file is licensed under the Affero General Public License version 3 | |||
* or later. | |||
* | |||
* See the COPYING-README file. | |||
* | |||
*/ | |||
(function() { | |||
if (!OCA.Comments) { | |||
/** | |||
* @namespace | |||
*/ | |||
OCA.Comments = {}; | |||
} | |||
})(); | |||
@@ -0,0 +1,82 @@ | |||
/* | |||
* 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) { | |||
function filterFunction(model, term) { | |||
return model.get('name').substr(0, term.length) === term; | |||
} | |||
/** | |||
* @class OCA.Comments.CommentsCollection | |||
* @classdesc | |||
* | |||
* Collection of comments assigned to a file | |||
* | |||
*/ | |||
var CommentsCollection = OC.Backbone.Collection.extend( | |||
/** @lends OCA.Comments.CommentsCollection.prototype */ { | |||
sync: OC.Backbone.davSync, | |||
model: OCA.Comments.CommentModel, | |||
_objectType: 'files', | |||
_objectId: null, | |||
_endReached: false, | |||
_currentIndex: 0, | |||
initialize: function(models, options) { | |||
options = options || {}; | |||
if (options.objectType) { | |||
this._objectType = options.objectType; | |||
} | |||
if (options.objectId) { | |||
this._objectId = options.objectId; | |||
} | |||
}, | |||
url: function() { | |||
return OC.linkToRemote('dav') + '/comments/' + | |||
encodeURIComponent(this._objectType) + '/' + | |||
encodeURIComponent(this._objectId) + '/'; | |||
}, | |||
setObjectId: function(objectId) { | |||
this._objectId = objectId; | |||
}, | |||
hasMoreResults: function() { | |||
return !this._endReached; | |||
}, | |||
/** | |||
* Fetch the next set of results | |||
*/ | |||
fetchNext: function() { | |||
if (!this.hasMoreResults()) { | |||
return null; | |||
} | |||
if (this._currentIndex === 0) { | |||
return this.fetch(); | |||
} | |||
return this.fetch({remove: false}); | |||
}, | |||
reset: function() { | |||
this._currentIndex = 0; | |||
OC.Backbone.Collection.prototype.reset.apply(this, arguments); | |||
} | |||
}); | |||
OCA.Comments.CommentsCollection = CommentsCollection; | |||
})(OC, OCA); | |||
@@ -0,0 +1,47 @@ | |||
/* | |||
* 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.CommentModel | |||
* @classdesc | |||
* | |||
* Comment | |||
* | |||
*/ | |||
var CommentModel = OC.Backbone.Model.extend( | |||
/** @lends OCA.Comments.CommentModel.prototype */ { | |||
sync: OC.Backbone.davSync, | |||
defaults: { | |||
// TODO | |||
}, | |||
davProperties: { | |||
'id': '{' + NS_OWNCLOUD + '}id', | |||
'message': '{' + NS_OWNCLOUD + '}message', | |||
'actorType': '{' + NS_OWNCLOUD + '}actorType', | |||
'actorId': '{' + NS_OWNCLOUD + '}actorId', | |||
'actorDisplayName': '{' + NS_OWNCLOUD + '}actorDisplayName', | |||
'creationDateTime': '{' + NS_OWNCLOUD + '}creationDateTime', | |||
'objectType': '{' + NS_OWNCLOUD + '}objectType', | |||
'objectId': '{' + NS_OWNCLOUD + '}objectId' | |||
}, | |||
parse: function(data) { | |||
// TODO: parse non-string values | |||
return data; | |||
} | |||
}); | |||
OCA.Comments.CommentModel = CommentModel; | |||
})(OC, OCA); | |||
@@ -0,0 +1,166 @@ | |||
/* | |||
* Copyright (c) 2016 | |||
* | |||
* This file is licensed under the Affero General Public License version 3 | |||
* or later. | |||
* | |||
* See the COPYING-README file. | |||
* | |||
*/ | |||
(function() { | |||
var TEMPLATE = | |||
'<div>' + | |||
' <form class="newCommentForm">' + | |||
' <textarea></textarea>' + | |||
' <input type="submit" value="{{submitText}}" />' + | |||
' </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>'; | |||
var COMMENT_TEMPLATE = | |||
'<li>' + | |||
' <hr />' + | |||
' <div class="authorRow">' + | |||
' <span class="author"><em>{{actorDisplayName}}</em></span>' + | |||
' <span class="date">{{creationDateTime}}</span>' + | |||
' </div>' + | |||
' <div class="message">{{message}}</div>' + | |||
'</li>'; | |||
/** | |||
* @memberof OCA.Comments | |||
*/ | |||
var CommentsTabView = OCA.Files.DetailTabView.extend( | |||
/** @lends OCA.Comments.CommentsTabView.prototype */ { | |||
id: 'commentsTabView', | |||
className: 'tab commentsTabView', | |||
events: { | |||
'submit .newCommentForm': '_onSubmitComment' | |||
}, | |||
initialize: function() { | |||
OCA.Files.DetailTabView.prototype.initialize.apply(this, arguments); | |||
this.collection = new OCA.Comments.CommentsCollection(); | |||
this.collection.on('request', this._onRequest, this); | |||
this.collection.on('sync', this._onEndRequest, this); | |||
this.collection.on('add', this._onAddModel, this); | |||
// TODO: error handling | |||
_.bindAll(this, '_onSubmitComment'); | |||
}, | |||
template: function(params) { | |||
if (!this._template) { | |||
this._template = Handlebars.compile(TEMPLATE); | |||
} | |||
return this._template(_.extend({ | |||
submitText: t('comments', 'Submit comment') | |||
}, params)); | |||
}, | |||
commentTemplate: function(params) { | |||
if (!this._commentTemplate) { | |||
this._commentTemplate = Handlebars.compile(COMMENT_TEMPLATE); | |||
} | |||
return this._commentTemplate(params); | |||
}, | |||
getLabel: function() { | |||
return t('comments', 'Comments'); | |||
}, | |||
setFileInfo: function(fileInfo) { | |||
if (fileInfo) { | |||
this.render(); | |||
this.collection.setObjectId(fileInfo.id); | |||
// reset to first page | |||
this.collection.reset([], {silent: true}); | |||
this.nextPage(); | |||
} else { | |||
this.render(); | |||
this.collection.reset(); | |||
} | |||
}, | |||
render: function() { | |||
this.$el.html(this.template({ | |||
emptyResultLabel: t('comments', 'No other comments available'), | |||
moreLabel: t('comments', 'More comments...') | |||
})); | |||
this.$el.find('.has-tooltip').tooltip(); | |||
this.$container = this.$el.find('ul.comments'); | |||
this.delegateEvents(); | |||
}, | |||
_formatItem: function(commentModel) { | |||
// TODO: format | |||
return commentModel.attributes; | |||
}, | |||
_toggleLoading: function(state) { | |||
this._loading = state; | |||
this.$el.find('.loading').toggleClass('hidden', !state); | |||
}, | |||
_onRequest: function() { | |||
this._toggleLoading(true); | |||
this.$el.find('.showMore').addClass('hidden'); | |||
}, | |||
_onEndRequest: function() { | |||
this._toggleLoading(false); | |||
this.$el.find('.empty').toggleClass('hidden', !!this.collection.length); | |||
this.$el.find('.showMore').toggleClass('hidden', !this.collection.hasMoreResults()); | |||
}, | |||
_onAddModel: function(model, collection, options) { | |||
var $el = $(this.commentTemplate(this._formatItem(model))); | |||
if (!_.isUndefined(options.at) && collection.length > 1) { | |||
this.$container.find('li').eq(options.at).before($el); | |||
} else { | |||
this.$container.append($el); | |||
} | |||
}, | |||
nextPage: function() { | |||
if (this._loading || !this.collection.hasMoreResults()) { | |||
return; | |||
} | |||
this.collection.fetchNext(); | |||
}, | |||
_onClickShowMoreVersions: function(ev) { | |||
ev.preventDefault(); | |||
this.nextPage(); | |||
}, | |||
_onSubmitComment: function(e) { | |||
var $textArea = $(e.target).find('textarea'); | |||
e.preventDefault(); | |||
this.collection.create({ | |||
actorId: OC.currentUser, | |||
// FIXME: how to get current user's display name ? | |||
actorDisplayName: OC.currentUser, | |||
actorType: 'users', | |||
verb: 'comment', | |||
message: $textArea.val() | |||
}, {at: 0}); | |||
// TODO: spinner/disable field? | |||
$textArea.val(''); | |||
return false; | |||
} | |||
}); | |||
OCA.Comments.CommentsTabView = CommentsTabView; | |||
})(); | |||
@@ -0,0 +1,41 @@ | |||
/* | |||
* Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com> | |||
* | |||
* This file is licensed under the Affero General Public License version 3 | |||
* or later. | |||
* | |||
* See the COPYING-README file. | |||
* | |||
*/ | |||
(function() { | |||
OCA.Comments = _.extend({}, OCA.Comments); | |||
if (!OCA.Comments) { | |||
/** | |||
* @namespace | |||
*/ | |||
OCA.Comments = {}; | |||
} | |||
/** | |||
* @namespace | |||
*/ | |||
OCA.Comments.FilesPlugin = { | |||
allowedLists: [ | |||
'files', | |||
'favorites' | |||
], | |||
attach: function(fileList) { | |||
if (this.allowedLists.indexOf(fileList.id) < 0) { | |||
return; | |||
} | |||
fileList.registerTabView(new OCA.Comments.CommentsTabView('commentsTabView')); | |||
} | |||
}; | |||
})(); | |||
OC.Plugins.register('OCA.Files.FileList', OCA.Comments.FilesPlugin); | |||
@@ -3,6 +3,7 @@ | |||
"activity", | |||
"admin_audit", | |||
"encryption", | |||
"comments", | |||
"dav", | |||
"enterprise_key", | |||
"external", |