Merge pull request #20847 from owncloud/systemtags-ui
System tags sidebar section
This commit is contained in:
commit
ec8022d241
|
@ -21,7 +21,8 @@
|
||||||
!/apps/files_versions
|
!/apps/files_versions
|
||||||
!/apps/user_ldap
|
!/apps/user_ldap
|
||||||
!/apps/user_webdavauth
|
!/apps/user_webdavauth
|
||||||
!apps/provisioning_api
|
!/apps/provisioning_api
|
||||||
|
!/apps/systemtags
|
||||||
/apps/files_external/3rdparty/irodsphp/PHPUnitTest
|
/apps/files_external/3rdparty/irodsphp/PHPUnitTest
|
||||||
/apps/files_external/3rdparty/irodsphp/web
|
/apps/files_external/3rdparty/irodsphp/web
|
||||||
/apps/files_external/3rdparty/irodsphp/prods/test
|
/apps/files_external/3rdparty/irodsphp/prods/test
|
||||||
|
|
|
@ -127,7 +127,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
|
||||||
$url .= '/';
|
$url .= '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
$response->setHeader('Location', $url . $tag->getId());
|
$response->setHeader('Content-Location', $url . $tag->getId());
|
||||||
|
|
||||||
// created
|
// created
|
||||||
$response->setStatus(201);
|
$response->setStatus(201);
|
||||||
|
@ -147,7 +147,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
|
||||||
* @throws UnsupportedMediaType if the content type is not supported
|
* @throws UnsupportedMediaType if the content type is not supported
|
||||||
*/
|
*/
|
||||||
private function createTag($data, $contentType = 'application/json') {
|
private function createTag($data, $contentType = 'application/json') {
|
||||||
if ($contentType === 'application/json') {
|
if (explode(';', $contentType)[0] === 'application/json') {
|
||||||
$data = json_decode($data, true);
|
$data = json_decode($data, true);
|
||||||
} else {
|
} else {
|
||||||
throw new UnsupportedMediaType();
|
throw new UnsupportedMediaType();
|
||||||
|
|
|
@ -201,7 +201,7 @@ class SystemTagPlugin extends \Test\TestCase {
|
||||||
|
|
||||||
$response->expects($this->once())
|
$response->expects($this->once())
|
||||||
->method('setHeader')
|
->method('setHeader')
|
||||||
->with('Location', 'http://example.com/dav/systemtags/1');
|
->with('Content-Location', 'http://example.com/dav/systemtags/1');
|
||||||
|
|
||||||
$this->plugin->httpPost($request, $response);
|
$this->plugin->httpPost($request, $response);
|
||||||
}
|
}
|
||||||
|
@ -266,7 +266,7 @@ class SystemTagPlugin extends \Test\TestCase {
|
||||||
|
|
||||||
$response->expects($this->once())
|
$response->expects($this->once())
|
||||||
->method('setHeader')
|
->method('setHeader')
|
||||||
->with('Location', 'http://example.com/dav/systemtags/1');
|
->with('Content-Location', 'http://example.com/dav/systemtags/1');
|
||||||
|
|
||||||
$this->plugin->httpPost($request, $response);
|
$this->plugin->httpPost($request, $response);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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');
|
||||||
|
}
|
||||||
|
);
|
|
@ -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>
|
|
@ -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 = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -288,6 +288,7 @@ Feature: provisioning
|
||||||
| files_trashbin |
|
| files_trashbin |
|
||||||
| files_versions |
|
| files_versions |
|
||||||
| provisioning_api |
|
| provisioning_api |
|
||||||
|
| systemtags |
|
||||||
|
|
||||||
Scenario: get app info
|
Scenario: get app info
|
||||||
Given As an "admin"
|
Given As an "admin"
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3
|
||||||
|
* or later.
|
||||||
|
*
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
.systemtags-select2-dropdown .select2-selected {
|
||||||
|
display: list-item;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
.systemtags-select2-dropdown .select2-highlighted,
|
||||||
|
.systemtags-select2-dropdown .select2-selected.select2-highlighted {
|
||||||
|
background: #3875d7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.systemtags-select2-dropdown .select2-highlighted {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
.systemtags-select2-dropdown .select2-result-label .checkmark {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.systemtags-select2-dropdown .select2-result-label .new-item .systemtags-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.systemtags-select2-dropdown .select2-selected .select2-result-label .checkmark {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.systemtags-select2-dropdown .select2-result-label .icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.systemtags-select2-dropdown .systemtags-actions {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.systemtags-select2-dropdown .systemtags-rename-form {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.systemtags-select2-container {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.systemtags-select2-container .select2-choices {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
background: #fff;
|
||||||
|
color: #555;
|
||||||
|
box-sizing: content-box;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
margin: 3px 3px 3px 0;
|
||||||
|
padding: 7px 6px 5px;
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.systemtags-select2-container .select2-choices .select2-search-choice {
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
.systemtags-select2-container .select2-choices .select2-search-choice-close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.systemtags-select2-container .select2-choices .select2-search-field input {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
|
@ -44,6 +44,10 @@
|
||||||
"mimetype.js",
|
"mimetype.js",
|
||||||
"mimetypelist.js",
|
"mimetypelist.js",
|
||||||
"files/fileinfo.js",
|
"files/fileinfo.js",
|
||||||
"files/client.js"
|
"files/client.js",
|
||||||
|
"systemtags/systemtagmodel.js",
|
||||||
|
"systemtags/systemtagscollection.js",
|
||||||
|
"systemtags/systemtagsmappingcollection.js",
|
||||||
|
"systemtags/systemtagsinputfield.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,7 +167,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function callPropPatch(client, options, model, headers) {
|
function callPropPatch(client, options, model, headers) {
|
||||||
client.propPatch(
|
return client.propPatch(
|
||||||
options.url,
|
options.url,
|
||||||
convertModelAttributesToDavProperties(model.changed, options.davProperties),
|
convertModelAttributesToDavProperties(model.changed, options.davProperties),
|
||||||
headers
|
headers
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3
|
||||||
|
* or later.
|
||||||
|
*
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global Select2 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select2 extension for toggling values in a multi-select dropdown
|
||||||
|
*/
|
||||||
|
(function(Select2) {
|
||||||
|
|
||||||
|
var Select2FindHighlightableChoices = Select2.class.multi.prototype.findHighlightableChoices;
|
||||||
|
Select2.class.multi.prototype.findHighlightableChoices = function () {
|
||||||
|
if (this.opts.toggleSelect) {
|
||||||
|
return this.results.find('.select2-result-selectable:not(.select2-disabled)');
|
||||||
|
}
|
||||||
|
return Select2FindHighlightableChoices.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
var Select2TriggerSelect = Select2.class.multi.prototype.triggerSelect;
|
||||||
|
Select2.class.multi.prototype.triggerSelect = function (data) {
|
||||||
|
if (this.opts.toggleSelect && this.val().indexOf(this.id(data)) !== -1) {
|
||||||
|
var self = this;
|
||||||
|
var val = this.id(data);
|
||||||
|
|
||||||
|
var selectionEls = this.container.find('.select2-search-choice').filter(function() {
|
||||||
|
return (self.id($(this).data('select2-data')) === val);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.unselect(selectionEls)) {
|
||||||
|
// also unselect in dropdown
|
||||||
|
this.results.find('.select2-result.select2-selected').each(function () {
|
||||||
|
var $this = $(this);
|
||||||
|
if (self.id($this.data('select2-data')) === val) {
|
||||||
|
$this.removeClass('select2-selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.clearSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return Select2TriggerSelect.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})(Select2);
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3
|
||||||
|
* or later.
|
||||||
|
*
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(OC) {
|
||||||
|
var NS_OWNCLOUD = 'http://owncloud.org/ns';
|
||||||
|
/**
|
||||||
|
* @class OCA.SystemTags.SystemTagsCollection
|
||||||
|
* @classdesc
|
||||||
|
*
|
||||||
|
* System tag
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
var SystemTagModel = OC.Backbone.Model.extend(
|
||||||
|
/** @lends OCA.SystemTags.SystemTagModel.prototype */ {
|
||||||
|
sync: OC.Backbone.davSync,
|
||||||
|
|
||||||
|
defaults: {
|
||||||
|
userVisible: true,
|
||||||
|
userAssignable: true
|
||||||
|
},
|
||||||
|
|
||||||
|
davProperties: {
|
||||||
|
'id': '{' + NS_OWNCLOUD + '}id',
|
||||||
|
'name': '{' + NS_OWNCLOUD + '}display-name',
|
||||||
|
'userVisible': '{' + NS_OWNCLOUD + '}user-visible',
|
||||||
|
'userAssignable': '{' + NS_OWNCLOUD + '}user-assignable'
|
||||||
|
},
|
||||||
|
|
||||||
|
parse: function(data) {
|
||||||
|
return {
|
||||||
|
id: data.id,
|
||||||
|
name: data.name,
|
||||||
|
userVisible: data.userVisible === '1',
|
||||||
|
userAssignable: data.userAssignable === '1'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace
|
||||||
|
*/
|
||||||
|
OC.SystemTags = OC.SystemTags || {};
|
||||||
|
OC.SystemTags.SystemTagModel = SystemTagModel;
|
||||||
|
})(OC);
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3
|
||||||
|
* or later.
|
||||||
|
*
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(OC) {
|
||||||
|
|
||||||
|
function filterFunction(model, term) {
|
||||||
|
return model.get('name').substr(0, term.length) === term;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class OCA.SystemTags.SystemTagsCollection
|
||||||
|
* @classdesc
|
||||||
|
*
|
||||||
|
* Collection of tags assigned to a file
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
var SystemTagsCollection = OC.Backbone.Collection.extend(
|
||||||
|
/** @lends OC.SystemTags.SystemTagsCollection.prototype */ {
|
||||||
|
|
||||||
|
sync: OC.Backbone.davSync,
|
||||||
|
|
||||||
|
model: OC.SystemTags.SystemTagModel,
|
||||||
|
|
||||||
|
url: function() {
|
||||||
|
return OC.linkToRemote('dav') + '/systemtags/';
|
||||||
|
},
|
||||||
|
|
||||||
|
filterByName: function(name) {
|
||||||
|
return this.filter(function(model) {
|
||||||
|
return filterFunction(model, name);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function() {
|
||||||
|
this.fetched = false;
|
||||||
|
return OC.Backbone.Collection.prototype.reset.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy fetch.
|
||||||
|
* Only fetches once, subsequent calls will directly call the success handler.
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
* @param [options.force] true to force fetch even if cached entries exist
|
||||||
|
*
|
||||||
|
* @see Backbone.Collection#fetch
|
||||||
|
*/
|
||||||
|
fetch: function(options) {
|
||||||
|
var self = this;
|
||||||
|
options = options || {};
|
||||||
|
if (this.fetched || options.force) {
|
||||||
|
// directly call handler
|
||||||
|
if (options.success) {
|
||||||
|
options.success(this, null, options);
|
||||||
|
}
|
||||||
|
// trigger sync event
|
||||||
|
this.trigger('sync', this, null, options);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = options.success;
|
||||||
|
options = _.extend({}, options);
|
||||||
|
options.success = function() {
|
||||||
|
self.fetched = true;
|
||||||
|
if (success) {
|
||||||
|
return success.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return OC.Backbone.Collection.prototype.fetch.call(this, options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
OC.SystemTags = OC.SystemTags || {};
|
||||||
|
OC.SystemTags.SystemTagsCollection = SystemTagsCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type OC.SystemTags.SystemTagsCollection
|
||||||
|
*/
|
||||||
|
OC.SystemTags.collection = new OC.SystemTags.SystemTagsCollection();
|
||||||
|
})(OC);
|
||||||
|
|
|
@ -0,0 +1,372 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3
|
||||||
|
* or later.
|
||||||
|
*
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global Handlebars */
|
||||||
|
|
||||||
|
(function(OC) {
|
||||||
|
var TEMPLATE =
|
||||||
|
'<input class="systemTagsInputField" type="hidden" name="tags" value=""/>';
|
||||||
|
|
||||||
|
var RESULT_TEMPLATE =
|
||||||
|
'<span class="systemtags-item{{#if isNew}} new-item{{/if}}" data-id="{{id}}">' +
|
||||||
|
' <span class="checkmark icon icon-checkmark"></span>' +
|
||||||
|
' <span class="label">{{name}}</span>' +
|
||||||
|
'{{#allowActions}}' +
|
||||||
|
' <span class="systemtags-actions">' +
|
||||||
|
' <a href="#" class="rename icon icon-rename" title="{{renameTooltip}}"></a>' +
|
||||||
|
' </span>' +
|
||||||
|
'{{/allowActions}}' +
|
||||||
|
'</span>';
|
||||||
|
|
||||||
|
var RENAME_FORM_TEMPLATE =
|
||||||
|
'<form class="systemtags-rename-form">' +
|
||||||
|
' <label class="hidden-visually" for="{{cid}}-rename-input">{{renameLabel}}</label>' +
|
||||||
|
' <input id="{{cid}}-rename-input" type="text" value="{{name}}">' +
|
||||||
|
' <a href="#" class="delete icon icon-delete" title="{{deleteTooltip}}"></a>' +
|
||||||
|
'</form>';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class OC.SystemTags.SystemTagsInputField
|
||||||
|
* @classdesc
|
||||||
|
*
|
||||||
|
* Displays a file's system tags
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
var SystemTagsInputField = OC.Backbone.View.extend(
|
||||||
|
/** @lends OC.SystemTags.SystemTagsInputField.prototype */ {
|
||||||
|
|
||||||
|
_rendered: false,
|
||||||
|
|
||||||
|
_newTag: null,
|
||||||
|
|
||||||
|
className: 'systemTagsInputFieldContainer',
|
||||||
|
|
||||||
|
template: function(data) {
|
||||||
|
if (!this._template) {
|
||||||
|
this._template = Handlebars.compile(TEMPLATE);
|
||||||
|
}
|
||||||
|
return this._template(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new SystemTagsInputField
|
||||||
|
*
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {string} [options.objectType=files] object type for which tags are assigned to
|
||||||
|
* @param {bool} [options.multiple=false] whether to allow selecting multiple tags
|
||||||
|
* @param {bool} [options.allowActions=true] whether tags can be renamed/delete within the dropdown
|
||||||
|
* @param {bool} [options.allowCreate=true] whether new tags can be created
|
||||||
|
* @param {Function} options.initSelection function to convert selection to data
|
||||||
|
*/
|
||||||
|
initialize: function(options) {
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
this._multiple = !!options.multiple;
|
||||||
|
this._allowActions = _.isUndefined(options.allowActions) || !!options.allowActions;
|
||||||
|
this._allowCreate = _.isUndefined(options.allowCreate) || !!options.allowCreate;
|
||||||
|
|
||||||
|
if (_.isFunction(options.initSelection)) {
|
||||||
|
this._initSelection = options.initSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.collection = options.collection || OC.SystemTags.collection;
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.collection.on('change:name remove', function() {
|
||||||
|
// refresh selection
|
||||||
|
_.defer(self._refreshSelection);
|
||||||
|
});
|
||||||
|
|
||||||
|
_.bindAll(
|
||||||
|
this,
|
||||||
|
'_refreshSelection',
|
||||||
|
'_onClickRenameTag',
|
||||||
|
'_onClickDeleteTag',
|
||||||
|
'_onSelectTag',
|
||||||
|
'_onDeselectTag',
|
||||||
|
'_onSubmitRenameTag'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the selection, triggering a call to
|
||||||
|
* select2's initSelection
|
||||||
|
*/
|
||||||
|
_refreshSelection: function() {
|
||||||
|
this.$tagsField.select2('val', this.$tagsField.val());
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler whenever the user clicked the "rename" action.
|
||||||
|
* This will display the rename field.
|
||||||
|
*/
|
||||||
|
_onClickRenameTag: function(ev) {
|
||||||
|
var $item = $(ev.target).closest('.systemtags-item');
|
||||||
|
var tagId = $item.attr('data-id');
|
||||||
|
var tagModel = this.collection.get(tagId);
|
||||||
|
if (!this._renameFormTemplate) {
|
||||||
|
this._renameFormTemplate = Handlebars.compile(RENAME_FORM_TEMPLATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldName = tagModel.get('name');
|
||||||
|
var $renameForm = $(this._renameFormTemplate({
|
||||||
|
cid: this.cid,
|
||||||
|
name: oldName,
|
||||||
|
deleteTooltip: t('core', 'Delete'),
|
||||||
|
renameLabel: t('core', 'Rename'),
|
||||||
|
}));
|
||||||
|
$item.find('.label').after($renameForm);
|
||||||
|
$item.find('.label, .systemtags-actions').addClass('hidden');
|
||||||
|
$item.closest('.select2-result').addClass('has-form');
|
||||||
|
|
||||||
|
$renameForm.find('[title]').tooltip({
|
||||||
|
placement: 'bottom',
|
||||||
|
container: 'body'
|
||||||
|
});
|
||||||
|
$renameForm.find('input').focus().selectRange(0, oldName.length);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler whenever the rename form has been submitted after
|
||||||
|
* the user entered a new tag name.
|
||||||
|
* This will submit the change to the server.
|
||||||
|
*
|
||||||
|
* @param {Object} ev event
|
||||||
|
*/
|
||||||
|
_onSubmitRenameTag: function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
var $form = $(ev.target);
|
||||||
|
var $item = $form.closest('.systemtags-item');
|
||||||
|
var tagId = $item.attr('data-id');
|
||||||
|
var tagModel = this.collection.get(tagId);
|
||||||
|
var newName = $(ev.target).find('input').val();
|
||||||
|
if (newName && newName !== tagModel.get('name')) {
|
||||||
|
tagModel.save({'name': newName});
|
||||||
|
// TODO: spinner, and only change text after finished saving
|
||||||
|
$item.find('.label').text(newName);
|
||||||
|
}
|
||||||
|
$item.find('.label, .systemtags-actions').removeClass('hidden');
|
||||||
|
$form.remove();
|
||||||
|
$item.closest('.select2-result').removeClass('has-form');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler whenever a tag must be deleted
|
||||||
|
*
|
||||||
|
* @param {Object} ev event
|
||||||
|
*/
|
||||||
|
_onClickDeleteTag: function(ev) {
|
||||||
|
var $item = $(ev.target).closest('.systemtags-item');
|
||||||
|
var tagId = $item.attr('data-id');
|
||||||
|
this.collection.get(tagId).destroy();
|
||||||
|
$item.closest('.select2-result').remove();
|
||||||
|
// TODO: spinner
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler whenever a tag is selected.
|
||||||
|
* Also called whenever tag creation is requested through the dummy tag object.
|
||||||
|
*
|
||||||
|
* @param {Object} e event
|
||||||
|
*/
|
||||||
|
_onSelectTag: function(e) {
|
||||||
|
var self = this;
|
||||||
|
var tag;
|
||||||
|
if (e.object && e.object.isNew) {
|
||||||
|
// newly created tag, check if existing
|
||||||
|
// create a new tag
|
||||||
|
tag = this.collection.create({
|
||||||
|
name: e.object.name,
|
||||||
|
userVisible: true,
|
||||||
|
userAssignable: true
|
||||||
|
}, {
|
||||||
|
success: function(model) {
|
||||||
|
var data = self.$tagsField.select2('data');
|
||||||
|
data.push(model.toJSON());
|
||||||
|
self.$tagsField.select2('data', data);
|
||||||
|
self.trigger('select', model);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$tagsField.select2('close');
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
tag = this.collection.get(e.object.id);
|
||||||
|
}
|
||||||
|
this._newTag = null;
|
||||||
|
this.trigger('select', tag);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler whenever a tag gets deselected.
|
||||||
|
*
|
||||||
|
* @param {Object} e event
|
||||||
|
*/
|
||||||
|
_onDeselectTag: function(e) {
|
||||||
|
this.trigger('deselect', e.choice.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autocomplete function for dropdown results
|
||||||
|
*
|
||||||
|
* @param {Object} query select2 query object
|
||||||
|
*/
|
||||||
|
_queryTagsAutocomplete: function(query) {
|
||||||
|
var self = this;
|
||||||
|
this.collection.fetch({
|
||||||
|
success: function() {
|
||||||
|
query.callback({
|
||||||
|
results: _.invoke(self.collection.filterByName(query.term), 'toJSON')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_preventDefault: function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a single dropdown result
|
||||||
|
*
|
||||||
|
* @param {Object} data data to format
|
||||||
|
* @return {string} HTML markup
|
||||||
|
*/
|
||||||
|
_formatDropDownResult: function(data) {
|
||||||
|
if (!this._resultTemplate) {
|
||||||
|
this._resultTemplate = Handlebars.compile(RESULT_TEMPLATE);
|
||||||
|
}
|
||||||
|
return this._resultTemplate(_.extend({
|
||||||
|
renameTooltip: t('core', 'Rename'),
|
||||||
|
allowActions: this._allowActions
|
||||||
|
}, data));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new dummy choice for select2 when the user
|
||||||
|
* types an arbitrary string
|
||||||
|
*
|
||||||
|
* @param {string} term entered term
|
||||||
|
* @return {Object} dummy tag
|
||||||
|
*/
|
||||||
|
_createSearchChoice: function(term) {
|
||||||
|
if (this.collection.filterByName(term).length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._newTag) {
|
||||||
|
this._newTag = {
|
||||||
|
id: -1,
|
||||||
|
name: term,
|
||||||
|
isNew: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this._newTag.name = term;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._newTag;
|
||||||
|
},
|
||||||
|
|
||||||
|
_initSelection: function(element, callback) {
|
||||||
|
var self = this;
|
||||||
|
var ids = $(element).val().split(',');
|
||||||
|
|
||||||
|
function findSelectedObjects(ids) {
|
||||||
|
var selectedModels = self.collection.filter(function(model) {
|
||||||
|
return ids.indexOf(model.id) >= 0;
|
||||||
|
});
|
||||||
|
return _.invoke(selectedModels, 'toJSON');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.collection.fetch({
|
||||||
|
success: function() {
|
||||||
|
callback(findSelectedObjects(ids));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders this details view
|
||||||
|
*/
|
||||||
|
render: function() {
|
||||||
|
var self = this;
|
||||||
|
this.$el.html(this.template());
|
||||||
|
|
||||||
|
this.$el.find('[title]').tooltip({placement: 'bottom'});
|
||||||
|
this.$tagsField = this.$el.find('[name=tags]');
|
||||||
|
this.$tagsField.select2({
|
||||||
|
placeholder: t('core', 'Global tags'),
|
||||||
|
containerCssClass: 'systemtags-select2-container',
|
||||||
|
dropdownCssClass: 'systemtags-select2-dropdown',
|
||||||
|
closeOnSelect: false,
|
||||||
|
allowClear: false,
|
||||||
|
multiple: this._multiple,
|
||||||
|
toggleSelect: this._multiple,
|
||||||
|
query: _.bind(this._queryTagsAutocomplete, this),
|
||||||
|
id: function(tag) {
|
||||||
|
return tag.id;
|
||||||
|
},
|
||||||
|
initSelection: _.bind(this._initSelection, this),
|
||||||
|
formatResult: _.bind(this._formatDropDownResult, this),
|
||||||
|
formatSelection: function(tag) {
|
||||||
|
return '<span class="label">' + escapeHTML(tag.name) + '</span>' +
|
||||||
|
'<span class="comma">, </span>';
|
||||||
|
},
|
||||||
|
createSearchChoice: this._allowCreate ? _.bind(this._createSearchChoice, this) : undefined,
|
||||||
|
sortResults: function(results) {
|
||||||
|
var selectedItems = _.pluck(self.$tagsField.select2('data'), 'id');
|
||||||
|
results.sort(function(a, b) {
|
||||||
|
var aSelected = selectedItems.indexOf(a.id) >= 0;
|
||||||
|
var bSelected = selectedItems.indexOf(b.id) >= 0;
|
||||||
|
if (aSelected === bSelected) {
|
||||||
|
return OC.Util.naturalSortCompare(a.name, b.name);
|
||||||
|
}
|
||||||
|
if (aSelected && !bSelected) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('select2-selecting', this._onSelectTag)
|
||||||
|
.on('select2-removing', this._onDeselectTag);
|
||||||
|
|
||||||
|
var $dropDown = this.$tagsField.select2('dropdown');
|
||||||
|
// register events for inside the dropdown
|
||||||
|
$dropDown.on('mouseup', '.rename', this._onClickRenameTag);
|
||||||
|
$dropDown.on('mouseup', '.delete', this._onClickDeleteTag);
|
||||||
|
$dropDown.on('mouseup', '.select2-result-selectable.has-form', this._preventDefault);
|
||||||
|
$dropDown.on('submit', '.systemtags-rename-form', this._onSubmitRenameTag);
|
||||||
|
|
||||||
|
this.delegateEvents();
|
||||||
|
},
|
||||||
|
|
||||||
|
remove: function() {
|
||||||
|
if (this.$tagsField) {
|
||||||
|
this.$tagsField.select2('destroy');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setValues: function(values) {
|
||||||
|
this.$tagsField.select2('val', values);
|
||||||
|
},
|
||||||
|
|
||||||
|
setData: function(data) {
|
||||||
|
this.$tagsField.select2('data', data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
OC.SystemTags = OC.SystemTags || {};
|
||||||
|
OC.SystemTags.SystemTagsInputField = SystemTagsInputField;
|
||||||
|
|
||||||
|
})(OC);
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3
|
||||||
|
* or later.
|
||||||
|
*
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(OC) {
|
||||||
|
/**
|
||||||
|
* @class OC.SystemTags.SystemTagsMappingCollection
|
||||||
|
* @classdesc
|
||||||
|
*
|
||||||
|
* Collection of tags assigned to a an object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
var SystemTagsMappingCollection = OC.Backbone.Collection.extend(
|
||||||
|
/** @lends OC.SystemTags.SystemTagsMappingCollection.prototype */ {
|
||||||
|
|
||||||
|
sync: OC.Backbone.davSync,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use PUT instead of PROPPATCH
|
||||||
|
*/
|
||||||
|
usePUT: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id of the file for which to filter activities by
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
_objectId: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of the object to filter by
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
_objectType: 'files',
|
||||||
|
|
||||||
|
model: OC.SystemTags.SystemTagModel,
|
||||||
|
|
||||||
|
url: function() {
|
||||||
|
return OC.linkToRemote('dav') + '/systemtags-relations/' + this._objectType + '/' + this._objectId;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the object id to filter by or null for all.
|
||||||
|
*
|
||||||
|
* @param {int} objectId file id or null
|
||||||
|
*/
|
||||||
|
setObjectId: function(objectId) {
|
||||||
|
this._objectId = objectId;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the object type to filter by or null for all.
|
||||||
|
*
|
||||||
|
* @param {int} objectType file id or null
|
||||||
|
*/
|
||||||
|
setObjectType: function(objectType) {
|
||||||
|
this._objectType = objectType;
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function(models, options) {
|
||||||
|
options = options || {};
|
||||||
|
if (!_.isUndefined(options.objectId)) {
|
||||||
|
this._objectId = options.objectId;
|
||||||
|
}
|
||||||
|
if (!_.isUndefined(options.objectType)) {
|
||||||
|
this._objectType = options.objectType;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getTagIds: function() {
|
||||||
|
return this.map(function(model) {
|
||||||
|
return model.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
OC.SystemTags = OC.SystemTags || {};
|
||||||
|
OC.SystemTags.SystemTagsMappingCollection = SystemTagsMappingCollection;
|
||||||
|
})(OC);
|
||||||
|
|
|
@ -160,7 +160,7 @@ window.isPhantom = /phantom/i.test(navigator.userAgent);
|
||||||
OC.Plugins._plugins = [];
|
OC.Plugins._plugins = [];
|
||||||
|
|
||||||
// dummy select2 (which isn't loaded during the tests)
|
// dummy select2 (which isn't loaded during the tests)
|
||||||
$.fn.select2 = function() {};
|
$.fn.select2 = function() { return this; };
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* 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('OC.SystemTags.SystemTagsCollection tests', function() {
|
||||||
|
var collection;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
collection = new OC.SystemTags.SystemTagsCollection();
|
||||||
|
});
|
||||||
|
it('fetches only once, until reset', function() {
|
||||||
|
var syncStub = sinon.stub(collection, 'sync');
|
||||||
|
var callback = sinon.stub();
|
||||||
|
var callback2 = sinon.stub();
|
||||||
|
var callback3 = sinon.stub();
|
||||||
|
var eventHandler = sinon.stub();
|
||||||
|
|
||||||
|
collection.on('sync', eventHandler);
|
||||||
|
|
||||||
|
collection.fetch({
|
||||||
|
success: callback
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(callback.notCalled).toEqual(true);
|
||||||
|
expect(syncStub.calledOnce).toEqual(true);
|
||||||
|
expect(eventHandler.notCalled).toEqual(true);
|
||||||
|
|
||||||
|
syncStub.yieldTo('success', collection);
|
||||||
|
|
||||||
|
expect(callback.calledOnce).toEqual(true);
|
||||||
|
expect(callback.firstCall.args[0]).toEqual(collection);
|
||||||
|
expect(eventHandler.calledOnce).toEqual(true);
|
||||||
|
expect(eventHandler.firstCall.args[0]).toEqual(collection);
|
||||||
|
|
||||||
|
collection.fetch({
|
||||||
|
success: callback2
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(eventHandler.calledTwice).toEqual(true);
|
||||||
|
expect(eventHandler.secondCall.args[0]).toEqual(collection);
|
||||||
|
|
||||||
|
// not re-called
|
||||||
|
expect(syncStub.calledOnce).toEqual(true);
|
||||||
|
|
||||||
|
expect(callback.calledOnce).toEqual(true);
|
||||||
|
expect(callback2.calledOnce).toEqual(true);
|
||||||
|
expect(callback2.firstCall.args[0]).toEqual(collection);
|
||||||
|
|
||||||
|
expect(collection.fetched).toEqual(true);
|
||||||
|
|
||||||
|
collection.reset();
|
||||||
|
|
||||||
|
expect(collection.fetched).toEqual(false);
|
||||||
|
|
||||||
|
collection.fetch({
|
||||||
|
success: callback3
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(syncStub.calledTwice).toEqual(true);
|
||||||
|
|
||||||
|
syncStub.yieldTo('success', collection);
|
||||||
|
expect(callback3.calledOnce).toEqual(true);
|
||||||
|
expect(callback3.firstCall.args[0]).toEqual(collection);
|
||||||
|
|
||||||
|
syncStub.restore();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,308 @@
|
||||||
|
/**
|
||||||
|
* 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('OC.SystemTags.SystemTagsInputField tests', function() {
|
||||||
|
var view, select2Stub;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
var $container = $('<div class="testInputContainer"></div>');
|
||||||
|
select2Stub = sinon.stub($.fn, 'select2');
|
||||||
|
select2Stub.returnsThis();
|
||||||
|
$('#testArea').append($container);
|
||||||
|
view = new OC.SystemTags.SystemTagsInputField();
|
||||||
|
$container.append(view.$el);
|
||||||
|
});
|
||||||
|
afterEach(function() {
|
||||||
|
select2Stub.restore();
|
||||||
|
OC.SystemTags.collection.reset();
|
||||||
|
view.remove();
|
||||||
|
view = undefined;
|
||||||
|
});
|
||||||
|
describe('rendering', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
view.render();
|
||||||
|
});
|
||||||
|
it('calls select2 on rendering', function() {
|
||||||
|
expect(view.$el.find('input[name=tags]').length).toEqual(1);
|
||||||
|
expect(select2Stub.called).toEqual(true);
|
||||||
|
});
|
||||||
|
it('formatResult renders rename button', function() {
|
||||||
|
var opts = select2Stub.getCall(0).args[0];
|
||||||
|
var $el = $(opts.formatResult({id: '1', name: 'test'}));
|
||||||
|
expect($el.find('.label').text()).toEqual('test');
|
||||||
|
expect($el.find('.rename').length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('initSelection', function() {
|
||||||
|
var fetchStub;
|
||||||
|
var testTags;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
fetchStub = sinon.stub(OC.SystemTags.SystemTagsCollection.prototype, 'fetch');
|
||||||
|
testTags = [
|
||||||
|
new OC.SystemTags.SystemTagModel({id: '1', name: 'test1'}),
|
||||||
|
new OC.SystemTags.SystemTagModel({id: '2', name: 'test2'}),
|
||||||
|
new OC.SystemTags.SystemTagModel({id: '3', name: 'test3'}),
|
||||||
|
];
|
||||||
|
view.render();
|
||||||
|
});
|
||||||
|
afterEach(function() {
|
||||||
|
fetchStub.restore();
|
||||||
|
});
|
||||||
|
it('grabs values from the full collection', function() {
|
||||||
|
var $el = view.$el.find('input');
|
||||||
|
$el.val('1,3');
|
||||||
|
var opts = select2Stub.getCall(0).args[0];
|
||||||
|
var callback = sinon.stub();
|
||||||
|
opts.initSelection($el, callback);
|
||||||
|
|
||||||
|
expect(fetchStub.calledOnce).toEqual(true);
|
||||||
|
view.collection.add(testTags);
|
||||||
|
fetchStub.yieldTo('success', view.collection);
|
||||||
|
|
||||||
|
expect(callback.calledOnce).toEqual(true);
|
||||||
|
var models = callback.getCall(0).args[0];
|
||||||
|
expect(models.length).toEqual(2);
|
||||||
|
expect(models[0].id).toEqual('1');
|
||||||
|
expect(models[0].name).toEqual('test1');
|
||||||
|
expect(models[1].id).toEqual('3');
|
||||||
|
expect(models[1].name).toEqual('test3');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('tag selection', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
view.render();
|
||||||
|
var $el = view.$el.find('input');
|
||||||
|
$el.val('1');
|
||||||
|
|
||||||
|
view.collection.add([
|
||||||
|
new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}),
|
||||||
|
new OC.SystemTags.SystemTagModel({id: '2', name: 'def'}),
|
||||||
|
new OC.SystemTags.SystemTagModel({id: '3', name: 'abd'}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
afterEach(function() {
|
||||||
|
});
|
||||||
|
it('does not create dummy tag when user types non-matching name', function() {
|
||||||
|
var opts = select2Stub.getCall(0).args[0];
|
||||||
|
var result = opts.createSearchChoice('abc');
|
||||||
|
expect(result).not.toBeDefined();
|
||||||
|
});
|
||||||
|
it('creates dummy tag when user types non-matching name', function() {
|
||||||
|
var opts = select2Stub.getCall(0).args[0];
|
||||||
|
var result = opts.createSearchChoice('abnew');
|
||||||
|
expect(result.id).toEqual(-1);
|
||||||
|
expect(result.name).toEqual('abnew');
|
||||||
|
expect(result.isNew).toEqual(true);
|
||||||
|
});
|
||||||
|
it('creates the real tag and fires select event after user selects the dummy tag', function() {
|
||||||
|
var selectHandler = sinon.stub();
|
||||||
|
view.on('select', selectHandler);
|
||||||
|
var createStub = sinon.stub(OC.SystemTags.SystemTagsCollection.prototype, 'create');
|
||||||
|
view.$el.find('input').trigger(new $.Event('select2-selecting', {
|
||||||
|
object: {
|
||||||
|
id: -1,
|
||||||
|
name: 'newname',
|
||||||
|
isNew: true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(createStub.calledOnce).toEqual(true);
|
||||||
|
expect(createStub.getCall(0).args[0]).toEqual({
|
||||||
|
name: 'newname',
|
||||||
|
userVisible: true,
|
||||||
|
userAssignable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var newModel = new OC.SystemTags.SystemTagModel({
|
||||||
|
id: '123',
|
||||||
|
name: 'newname',
|
||||||
|
userVisible: true,
|
||||||
|
userAssignable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// not called yet
|
||||||
|
expect(selectHandler.notCalled).toEqual(true);
|
||||||
|
|
||||||
|
select2Stub.withArgs('data').returns([{
|
||||||
|
id: '1',
|
||||||
|
name: 'abc'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
createStub.yieldTo('success', newModel);
|
||||||
|
|
||||||
|
expect(select2Stub.lastCall.args[0]).toEqual('data');
|
||||||
|
expect(select2Stub.lastCall.args[1]).toEqual([{
|
||||||
|
id: '1',
|
||||||
|
name: 'abc'
|
||||||
|
},
|
||||||
|
newModel.toJSON()
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(selectHandler.calledOnce).toEqual(true);
|
||||||
|
expect(selectHandler.getCall(0).args[0]).toEqual(newModel);
|
||||||
|
|
||||||
|
createStub.restore();
|
||||||
|
});
|
||||||
|
it('triggers select event after selecting an existing tag', function() {
|
||||||
|
var selectHandler = sinon.stub();
|
||||||
|
view.on('select', selectHandler);
|
||||||
|
view.$el.find('input').trigger(new $.Event('select2-selecting', {
|
||||||
|
object: {
|
||||||
|
id: '2',
|
||||||
|
name: 'def'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(selectHandler.calledOnce).toEqual(true);
|
||||||
|
expect(selectHandler.getCall(0).args[0]).toEqual(view.collection.get('2'));
|
||||||
|
});
|
||||||
|
it('triggers deselect event after deselecting an existing tag', function() {
|
||||||
|
var selectHandler = sinon.stub();
|
||||||
|
view.on('deselect', selectHandler);
|
||||||
|
view.$el.find('input').trigger(new $.Event('select2-removing', {
|
||||||
|
choice: {
|
||||||
|
id: '2',
|
||||||
|
name: 'def'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(selectHandler.calledOnce).toEqual(true);
|
||||||
|
expect(selectHandler.getCall(0).args[0]).toEqual('2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('autocomplete', function() {
|
||||||
|
var fetchStub, opts;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
fetchStub = sinon.stub(OC.SystemTags.SystemTagsCollection.prototype, 'fetch');
|
||||||
|
view.render();
|
||||||
|
opts = select2Stub.getCall(0).args[0];
|
||||||
|
|
||||||
|
view.collection.add([
|
||||||
|
new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}),
|
||||||
|
new OC.SystemTags.SystemTagModel({id: '2', name: 'def'}),
|
||||||
|
new OC.SystemTags.SystemTagModel({id: '3', name: 'abd'}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
afterEach(function() {
|
||||||
|
fetchStub.restore();
|
||||||
|
});
|
||||||
|
it('completes results', function() {
|
||||||
|
var callback = sinon.stub();
|
||||||
|
opts.query({
|
||||||
|
term: 'ab',
|
||||||
|
callback: callback
|
||||||
|
});
|
||||||
|
expect(fetchStub.calledOnce).toEqual(true);
|
||||||
|
|
||||||
|
fetchStub.yieldTo('success', view.collection);
|
||||||
|
|
||||||
|
expect(callback.calledOnce).toEqual(true);
|
||||||
|
expect(callback.getCall(0).args[0].results).toEqual([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'abc',
|
||||||
|
userVisible: true,
|
||||||
|
userAssignable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
name: 'abd',
|
||||||
|
userVisible: true,
|
||||||
|
userAssignable: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('tag actions', function() {
|
||||||
|
var $dropdown, opts;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
$dropdown = $('<div class="select2-dropdown"></div>');
|
||||||
|
select2Stub.withArgs('dropdown').returns($dropdown);
|
||||||
|
$('#testArea').append($dropdown);
|
||||||
|
|
||||||
|
view.render();
|
||||||
|
|
||||||
|
opts = select2Stub.getCall(0).args[0];
|
||||||
|
|
||||||
|
view.collection.add([
|
||||||
|
new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$dropdown.append(opts.formatResult(view.collection.get('1').toJSON()));
|
||||||
|
|
||||||
|
});
|
||||||
|
afterEach(function() {
|
||||||
|
});
|
||||||
|
it('displays rename form when clicking rename', function() {
|
||||||
|
$dropdown.find('.rename').mouseup();
|
||||||
|
expect($dropdown.find('form.systemtags-rename-form').length).toEqual(1);
|
||||||
|
expect($dropdown.find('form.systemtags-rename-form input').val()).toEqual('abc');
|
||||||
|
});
|
||||||
|
it('renames model and submits change when submitting form', function() {
|
||||||
|
var saveStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'save');
|
||||||
|
$dropdown.find('.rename').mouseup();
|
||||||
|
$dropdown.find('form input').val('abc_renamed');
|
||||||
|
$dropdown.find('form').trigger(new $.Event('submit'));
|
||||||
|
|
||||||
|
expect(saveStub.calledOnce).toEqual(true);
|
||||||
|
expect(saveStub.getCall(0).args[0]).toEqual({'name': 'abc_renamed'});
|
||||||
|
|
||||||
|
expect($dropdown.find('.label').text()).toEqual('abc_renamed');
|
||||||
|
expect($dropdown.find('form').length).toEqual(0);
|
||||||
|
|
||||||
|
saveStub.restore();
|
||||||
|
});
|
||||||
|
it('deletes model and submits change when clicking delete', function() {
|
||||||
|
var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy');
|
||||||
|
|
||||||
|
expect($dropdown.find('.delete').length).toEqual(0);
|
||||||
|
$dropdown.find('.rename').mouseup();
|
||||||
|
// delete button appears
|
||||||
|
expect($dropdown.find('.delete').length).toEqual(1);
|
||||||
|
$dropdown.find('.delete').mouseup();
|
||||||
|
|
||||||
|
expect(destroyStub.calledOnce).toEqual(true);
|
||||||
|
expect(destroyStub.calledOn(view.collection.get('1')));
|
||||||
|
|
||||||
|
destroyStub.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('setting data', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
view.render();
|
||||||
|
});
|
||||||
|
it('sets value when calling setValues', function() {
|
||||||
|
var vals = ['1', '2'];
|
||||||
|
view.setValues(vals);
|
||||||
|
expect(select2Stub.lastCall.args[0]).toEqual('val');
|
||||||
|
expect(select2Stub.lastCall.args[1]).toEqual(vals);
|
||||||
|
});
|
||||||
|
it('sets data when calling setData', function() {
|
||||||
|
var vals = [{id: '1', name: 'test1'}, {id: '2', name: 'test2'}];
|
||||||
|
view.setData(vals);
|
||||||
|
expect(select2Stub.lastCall.args[0]).toEqual('data');
|
||||||
|
expect(select2Stub.lastCall.args[1]).toEqual(vals);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -28,6 +28,7 @@
|
||||||
"password_policy",
|
"password_policy",
|
||||||
"provisioning_api",
|
"provisioning_api",
|
||||||
"sharepoint",
|
"sharepoint",
|
||||||
|
"systemtags",
|
||||||
"templateeditor",
|
"templateeditor",
|
||||||
"updater",
|
"updater",
|
||||||
"user_external",
|
"user_external",
|
||||||
|
|
|
@ -82,6 +82,16 @@ module.exports = function(config) {
|
||||||
],
|
],
|
||||||
testFiles: ['apps/files_versions/tests/js/**/*.js']
|
testFiles: ['apps/files_versions/tests/js/**/*.js']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'systemtags',
|
||||||
|
srcFiles: [
|
||||||
|
// need to enforce loading order...
|
||||||
|
'apps/systemtags/js/app.js',
|
||||||
|
'apps/systemtags/js/systemtagsinfoview.js',
|
||||||
|
'apps/systemtags/js/filesplugin.js'
|
||||||
|
],
|
||||||
|
testFiles: ['apps/systemtags/tests/js/**/*.js']
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
srcFiles: [
|
srcFiles: [
|
||||||
|
|
Loading…
Reference in New Issue