diff options
author | Thomas Müller <thomas.mueller@tmit.eu> | 2016-01-21 12:12:40 +0100 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2016-01-21 12:12:40 +0100 |
commit | ec8022d241541b25a0e4d01857bf6a0c1ed74eca (patch) | |
tree | e8acb962f8f82a8b5ea5c1b65fb16de149680c65 /apps | |
parent | 50b303f3add4cc7ff8747f0e0fc4d0ac885fcd76 (diff) | |
parent | 5d4bb4b9c1b04bcbe1c64317d4f59bb151cbe298 (diff) | |
download | nextcloud-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.php | 4 | ||||
-rw-r--r-- | apps/dav/tests/unit/systemtag/systemtagplugin.php | 4 | ||||
-rw-r--r-- | apps/systemtags/appinfo/app.php | 40 | ||||
-rw-r--r-- | apps/systemtags/appinfo/info.xml | 17 | ||||
-rw-r--r-- | apps/systemtags/js/app.js | 20 | ||||
-rw-r--r-- | apps/systemtags/js/filesplugin.js | 41 | ||||
-rw-r--r-- | apps/systemtags/js/systemtagsinfoview.js | 135 | ||||
-rw-r--r-- | apps/systemtags/tests/js/systemtagsinfoviewSpec.js | 149 |
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(); + + }); + }); +}); |