summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorThomas Müller <thomas.mueller@tmit.eu>2016-01-21 12:12:40 +0100
committerThomas Müller <thomas.mueller@tmit.eu>2016-01-21 12:12:40 +0100
commitec8022d241541b25a0e4d01857bf6a0c1ed74eca (patch)
treee8acb962f8f82a8b5ea5c1b65fb16de149680c65 /apps
parent50b303f3add4cc7ff8747f0e0fc4d0ac885fcd76 (diff)
parent5d4bb4b9c1b04bcbe1c64317d4f59bb151cbe298 (diff)
downloadnextcloud-server-ec8022d241541b25a0e4d01857bf6a0c1ed74eca.tar.gz
nextcloud-server-ec8022d241541b25a0e4d01857bf6a0c1ed74eca.zip
Merge pull request #20847 from owncloud/systemtags-ui
System tags sidebar section
Diffstat (limited to 'apps')
-rw-r--r--apps/dav/lib/systemtag/systemtagplugin.php4
-rw-r--r--apps/dav/tests/unit/systemtag/systemtagplugin.php4
-rw-r--r--apps/systemtags/appinfo/app.php40
-rw-r--r--apps/systemtags/appinfo/info.xml17
-rw-r--r--apps/systemtags/js/app.js20
-rw-r--r--apps/systemtags/js/filesplugin.js41
-rw-r--r--apps/systemtags/js/systemtagsinfoview.js135
-rw-r--r--apps/systemtags/tests/js/systemtagsinfoviewSpec.js149
8 files changed, 406 insertions, 4 deletions
diff --git a/apps/dav/lib/systemtag/systemtagplugin.php b/apps/dav/lib/systemtag/systemtagplugin.php
index 2cab9ba8d50..e104bb8dac4 100644
--- a/apps/dav/lib/systemtag/systemtagplugin.php
+++ b/apps/dav/lib/systemtag/systemtagplugin.php
@@ -127,7 +127,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
$url .= '/';
}
- $response->setHeader('Location', $url . $tag->getId());
+ $response->setHeader('Content-Location', $url . $tag->getId());
// created
$response->setStatus(201);
@@ -147,7 +147,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
* @throws UnsupportedMediaType if the content type is not supported
*/
private function createTag($data, $contentType = 'application/json') {
- if ($contentType === 'application/json') {
+ if (explode(';', $contentType)[0] === 'application/json') {
$data = json_decode($data, true);
} else {
throw new UnsupportedMediaType();
diff --git a/apps/dav/tests/unit/systemtag/systemtagplugin.php b/apps/dav/tests/unit/systemtag/systemtagplugin.php
index e0fbd40f5b9..1d22af75188 100644
--- a/apps/dav/tests/unit/systemtag/systemtagplugin.php
+++ b/apps/dav/tests/unit/systemtag/systemtagplugin.php
@@ -201,7 +201,7 @@ class SystemTagPlugin extends \Test\TestCase {
$response->expects($this->once())
->method('setHeader')
- ->with('Location', 'http://example.com/dav/systemtags/1');
+ ->with('Content-Location', 'http://example.com/dav/systemtags/1');
$this->plugin->httpPost($request, $response);
}
@@ -266,7 +266,7 @@ class SystemTagPlugin extends \Test\TestCase {
$response->expects($this->once())
->method('setHeader')
- ->with('Location', 'http://example.com/dav/systemtags/1');
+ ->with('Content-Location', 'http://example.com/dav/systemtags/1');
$this->plugin->httpPost($request, $response);
}
diff --git a/apps/systemtags/appinfo/app.php b/apps/systemtags/appinfo/app.php
new file mode 100644
index 00000000000..d07902f777f
--- /dev/null
+++ b/apps/systemtags/appinfo/app.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, 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() {
+ // FIXME: no public API for these ?
+ \OC_Util::addVendorScript('select2/select2');
+ \OC_Util::addVendorStyle('select2/select2');
+ \OCP\Util::addScript('select2-toggleselect');
+ \OCP\Util::addScript('oc-backbone-webdav');
+ \OCP\Util::addScript('systemtags/systemtagmodel');
+ \OCP\Util::addScript('systemtags/systemtagsmappingcollection');
+ \OCP\Util::addScript('systemtags/systemtagscollection');
+ \OCP\Util::addScript('systemtags/systemtagsinputfield');
+ \OCP\Util::addScript('systemtags', 'app');
+ \OCP\Util::addScript('systemtags', 'filesplugin');
+ \OCP\Util::addScript('systemtags', 'systemtagsinfoview');
+ \OCP\Util::addStyle('systemtags');
+ }
+);
diff --git a/apps/systemtags/appinfo/info.xml b/apps/systemtags/appinfo/info.xml
new file mode 100644
index 00000000000..59b7fc01eb6
--- /dev/null
+++ b/apps/systemtags/appinfo/info.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<info>
+ <id>systemtags</id>
+ <name>System tags</name>
+ <description>System-wide tags user interface</description>
+ <licence>AGPL</licence>
+ <author>Vincent Petry</author>
+ <default_enable/>
+ <version>0.1</version>
+ <dependencies>
+ <owncloud min-version="9.0" />
+ <owncloud max-version="9.1" />
+ </dependencies>
+ <documentation>
+ <user>user-systemtags</user>
+ </documentation>
+</info>
diff --git a/apps/systemtags/js/app.js b/apps/systemtags/js/app.js
new file mode 100644
index 00000000000..f55aa5c9a6e
--- /dev/null
+++ b/apps/systemtags/js/app.js
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2015 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.SystemTags) {
+ /**
+ * @namespace
+ */
+ OCA.SystemTags = {};
+ }
+
+})();
+
diff --git a/apps/systemtags/js/filesplugin.js b/apps/systemtags/js/filesplugin.js
new file mode 100644
index 00000000000..471440c2e09
--- /dev/null
+++ b/apps/systemtags/js/filesplugin.js
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015 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.SystemTags = _.extend({}, OCA.SystemTags);
+ if (!OCA.SystemTags) {
+ /**
+ * @namespace
+ */
+ OCA.SystemTags = {};
+ }
+
+ /**
+ * @namespace
+ */
+ OCA.SystemTags.FilesPlugin = {
+ allowedLists: [
+ 'files',
+ 'favorites'
+ ],
+
+ attach: function(fileList) {
+ if (this.allowedLists.indexOf(fileList.id) < 0) {
+ return;
+ }
+
+ fileList.registerDetailView(new OCA.SystemTags.SystemTagsInfoView());
+ }
+ };
+
+})();
+
+OC.Plugins.register('OCA.Files.FileList', OCA.SystemTags.FilesPlugin);
+
diff --git a/apps/systemtags/js/systemtagsinfoview.js b/apps/systemtags/js/systemtagsinfoview.js
new file mode 100644
index 00000000000..b1820bfcd91
--- /dev/null
+++ b/apps/systemtags/js/systemtagsinfoview.js
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2015
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+(function(OCA) {
+ /**
+ * @class OCA.SystemTags.SystemTagsInfoView
+ * @classdesc
+ *
+ * Displays a file's system tags
+ *
+ */
+ var SystemTagsInfoView = OCA.Files.DetailFileInfoView.extend(
+ /** @lends OCA.SystemTags.SystemTagsInfoView.prototype */ {
+
+ _rendered: false,
+
+ className: 'systemTagsInfoView hidden',
+
+ /**
+ * @type OC.SystemTags.SystemTagsInputField
+ */
+ _inputView: null,
+
+ initialize: function(options) {
+ var self = this;
+ options = options || {};
+
+ this._inputView = new OC.SystemTags.SystemTagsInputField({
+ multiple: true,
+ allowActions: true,
+ allowCreate: true,
+ initSelection: function(element, callback) {
+ callback(self.selectedTagsCollection.toJSON());
+ }
+ });
+
+ this.selectedTagsCollection = new OC.SystemTags.SystemTagsMappingCollection([], {objectType: 'files'});
+
+ this._inputView.collection.on('change:name', this._onTagRenamedGlobally, this);
+ this._inputView.collection.on('remove', this._onTagDeletedGlobally, this);
+
+ this._inputView.on('select', this._onSelectTag, this);
+ this._inputView.on('deselect', this._onDeselectTag, this);
+ },
+
+ /**
+ * Event handler whenever a tag was selected
+ */
+ _onSelectTag: function(tag) {
+ // create a mapping entry for this tag
+ this.selectedTagsCollection.create(tag.toJSON());
+ },
+
+ /**
+ * Event handler whenever a tag gets deselected.
+ * Removes the selected tag from the mapping collection.
+ *
+ * @param {string} tagId tag id
+ */
+ _onDeselectTag: function(tagId) {
+ this.selectedTagsCollection.get(tagId).destroy();
+ },
+
+ /**
+ * Event handler whenever a tag was renamed globally.
+ *
+ * This will automatically adjust the tag mapping collection to
+ * container the new name.
+ *
+ * @param {OC.Backbone.Model} changedTag tag model that has changed
+ */
+ _onTagRenamedGlobally: function(changedTag) {
+ // also rename it in the selection, if applicable
+ var selectedTagMapping = this.selectedTagsCollection.get(changedTag.id);
+ if (selectedTagMapping) {
+ selectedTagMapping.set(changedTag.toJSON());
+ }
+ },
+
+ /**
+ * Event handler whenever a tag was deleted globally.
+ *
+ * This will automatically adjust the tag mapping collection to
+ * container the new name.
+ *
+ * @param {OC.Backbone.Model} changedTag tag model that has changed
+ */
+ _onTagDeletedGlobally: function(tagId) {
+ // also rename it in the selection, if applicable
+ this.selectedTagsCollection.remove(tagId);
+ },
+
+ setFileInfo: function(fileInfo) {
+ var self = this;
+ if (!this._rendered) {
+ this.render();
+ }
+
+ if (fileInfo) {
+ this.selectedTagsCollection.setObjectId(fileInfo.id);
+ this.selectedTagsCollection.fetch({
+ success: function(collection) {
+ collection.fetched = true;
+ self._inputView.setData(collection.toJSON());
+ self.$el.removeClass('hidden');
+ }
+ });
+ }
+ this.$el.addClass('hidden');
+ },
+
+ /**
+ * Renders this details view
+ */
+ render: function() {
+ this.$el.append(this._inputView.$el);
+ this._inputView.render();
+ },
+
+ remove: function() {
+ this._inputView.remove();
+ }
+ });
+
+ OCA.SystemTags.SystemTagsInfoView = SystemTagsInfoView;
+
+})(OCA);
+
diff --git a/apps/systemtags/tests/js/systemtagsinfoviewSpec.js b/apps/systemtags/tests/js/systemtagsinfoviewSpec.js
new file mode 100644
index 00000000000..971ad8fc17e
--- /dev/null
+++ b/apps/systemtags/tests/js/systemtagsinfoviewSpec.js
@@ -0,0 +1,149 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2016 Vincent Petry <pvince81@owncloud.com>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+describe('OCA.SystemTags.SystemTagsInfoView tests', function() {
+ var view;
+
+ beforeEach(function() {
+ view = new OCA.SystemTags.SystemTagsInfoView();
+ $('#testArea').append(view.$el);
+ });
+ afterEach(function() {
+ view.remove();
+ view = undefined;
+ });
+ describe('rendering', function() {
+ it('renders input field view', function() {
+ view.render();
+ expect(view.$el.find('input[name=tags]').length).toEqual(1);
+ });
+ it('fetches selected tags then renders when setting file info', function() {
+ var fetchStub = sinon.stub(OC.SystemTags.SystemTagsMappingCollection.prototype, 'fetch');
+ var setDataStub = sinon.stub(OC.SystemTags.SystemTagsInputField.prototype, 'setData');
+
+ expect(view.$el.hasClass('hidden')).toEqual(true);
+
+ view.setFileInfo({id: '123'});
+ expect(view.$el.find('input[name=tags]').length).toEqual(1);
+
+ expect(fetchStub.calledOnce).toEqual(true);
+ expect(view.selectedTagsCollection.url())
+ .toEqual(OC.linkToRemote('dav') + '/systemtags-relations/files/123');
+
+ view.selectedTagsCollection.add([
+ {id: '1', name: 'test1'},
+ {id: '3', name: 'test3'}
+ ]);
+
+ fetchStub.yieldTo('success', view.selectedTagsCollection);
+ expect(setDataStub.calledOnce).toEqual(true);
+ expect(setDataStub.getCall(0).args[0]).toEqual([{
+ id: '1', name: 'test1', userVisible: true, userAssignable: true
+ }, {
+ id: '3', name: 'test3', userVisible: true, userAssignable: true
+ }]);
+
+ expect(view.$el.hasClass('hidden')).toEqual(false);
+
+ fetchStub.restore();
+ setDataStub.restore();
+ });
+ it('overrides initSelection to use the local collection', function() {
+ var inputViewSpy = sinon.spy(OC.SystemTags, 'SystemTagsInputField');
+ var element = $('<input type="hidden" val="1,3"/>');
+ view.remove();
+ view = new OCA.SystemTags.SystemTagsInfoView();
+ view.selectedTagsCollection.add([
+ {id: '1', name: 'test1'},
+ {id: '3', name: 'test3'}
+ ]);
+
+ var callback = sinon.stub();
+ inputViewSpy.getCall(0).args[0].initSelection(element, callback);
+
+ expect(callback.calledOnce).toEqual(true);
+ expect(callback.getCall(0).args[0]).toEqual([{
+ id: '1', name: 'test1', userVisible: true, userAssignable: true
+ }, {
+ id: '3', name: 'test3', userVisible: true, userAssignable: true
+ }]);
+
+ inputViewSpy.restore();
+ });
+ });
+ describe('events', function() {
+ var allTagsCollection;
+ beforeEach(function() {
+ allTagsCollection = view._inputView.collection;
+
+ allTagsCollection.add([
+ {id: '1', name: 'test1'},
+ {id: '2', name: 'test2'},
+ {id: '3', name: 'test3'}
+ ]);
+
+ view.selectedTagsCollection.add([
+ {id: '1', name: 'test1'},
+ {id: '3', name: 'test3'}
+ ]);
+ view.render();
+ });
+
+ it('renames model in selection collection on rename', function() {
+ allTagsCollection.get('3').set('name', 'test3_renamed');
+
+ expect(view.selectedTagsCollection.get('3').get('name')).toEqual('test3_renamed');
+ });
+
+ it('adds tag to selection collection when selected by input', function() {
+ var createStub = sinon.stub(OC.SystemTags.SystemTagsMappingCollection.prototype, 'create');
+ view._inputView.trigger('select', allTagsCollection.get('2'));
+
+ expect(createStub.calledOnce).toEqual(true);
+ expect(createStub.getCall(0).args[0]).toEqual({
+ id: '2',
+ name: 'test2',
+ userVisible: true,
+ userAssignable: true
+ });
+
+ createStub.restore();
+ });
+ it('removes tag from selection collection when deselected by input', function() {
+ var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy');
+ view._inputView.trigger('deselect', '3');
+
+ expect(destroyStub.calledOnce).toEqual(true);
+ expect(destroyStub.calledOn(view.selectedTagsCollection.get('3'))).toEqual(true);
+
+ destroyStub.restore();
+ });
+
+ it('removes tag from selection whenever the tag was deleted globally', function() {
+ expect(view.selectedTagsCollection.get('3')).not.toBeFalsy();
+
+ allTagsCollection.remove('3');
+
+ expect(view.selectedTagsCollection.get('3')).toBeFalsy();
+
+ });
+ });
+});