mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
refactor: Remove OC.SystemTags JS API
Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
This commit is contained in:
parent
3d97549a74
commit
e86ca11e0b
17 changed files with 0 additions and 1588 deletions
|
|
@ -49,7 +49,6 @@ class Application extends App implements IBootstrap {
|
|||
$dispatcher->addListener(
|
||||
LoadAdditionalScriptsEvent::class,
|
||||
function (): void {
|
||||
Util::addScript('core', 'systemtags');
|
||||
Util::addInitScript(self::APP_ID, 'init');
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/.systemtags-select2-dropdown .select2-result-label{height:25px}.systemtags-select2-dropdown .select2-result-label .checkmark{visibility:hidden;margin-inline:-5px 5px;padding:4px}.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;opacity:.5}.systemtags-select2-dropdown .select2-result-label .icon.rename{padding:4px}.systemtags-select2-dropdown .systemtags-actions{position:absolute;inset-inline-end:5px}.systemtags-select2-dropdown .systemtags-rename-form{display:inline-block;width:calc(100% - 20px);top:-6px;position:relative}.systemtags-select2-dropdown .systemtags-rename-form input{display:inline-block;height:30px;width:calc(100% - 40px)}.systemtags-select2-dropdown .label{width:85%;display:inline-block;overflow:hidden;text-overflow:ellipsis}.systemtags-select2-dropdown .label.hidden{display:none}.systemtags-select2-dropdown span{line-height:25px}.systemtags-select2-dropdown .systemtags-item{display:inline-block;height:25px;width:100%}.systemtags-select2-container{width:100%}.systemtags-select2-container .select2-choices{flex-wrap:nowrap !important;max-height:44px}.systemtags-select2-container .select2-choices .select2-search-choice.select2-locked .label{opacity:.5}#select2-drop.systemtags-select2-dropdown .select2-results li.select2-result{padding:5px}/*# sourceMappingURL=systemtags.css.map */
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"sourceRoot":"","sources":["systemtags.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA;AAAA,GAOC,mDACC,YAEA,8DACC,kBACA,uBACA,YAED,iFACC,aAGF,gFACC,mBAED,yDACC,qBACA,WACA,gEACC,YAGF,iDACC,kBACA,qBAED,qDACC,qBACA,wBACA,SACA,kBACA,2DACC,qBACA,YACA,wBAGF,oCACC,UACA,qBACA,gBACA,uBACA,2CACC,aAGF,kCACC,iBAED,8CACC,qBACA,YACA,WAIF,8BACC,WAEA,+CACC,4BACA,gBAGD,4FACC,WAIF,6EACC","file":"systemtags.css"}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
.systemtags-select2-dropdown {
|
||||
.select2-result-label {
|
||||
height: 25px;
|
||||
|
||||
.checkmark {
|
||||
visibility: hidden;
|
||||
margin-inline: -5px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.new-item .systemtags-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.select2-selected .select2-result-label .checkmark {
|
||||
visibility: visible;
|
||||
}
|
||||
.select2-result-label .icon {
|
||||
display: inline-block;
|
||||
opacity: .5;
|
||||
&.rename {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
.systemtags-actions {
|
||||
position: absolute;
|
||||
inset-inline-end: 5px;
|
||||
}
|
||||
.systemtags-rename-form {
|
||||
display: inline-block;
|
||||
width: calc(100% - 20px);
|
||||
top: -6px;
|
||||
position: relative;
|
||||
input {
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
}
|
||||
.label {
|
||||
width: 85%;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
span {
|
||||
line-height: 25px;
|
||||
}
|
||||
.systemtags-item {
|
||||
display: inline-block;
|
||||
height: 25px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.systemtags-select2-container {
|
||||
width: 100%;
|
||||
|
||||
.select2-choices {
|
||||
flex-wrap: nowrap !important;
|
||||
max-height: 44px;
|
||||
}
|
||||
|
||||
.select2-choices .select2-search-choice.select2-locked .label {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
#select2-drop.systemtags-select2-dropdown .select2-results li.select2-result {
|
||||
padding: 5px;
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
describe('OC.SystemTags tests', function() {
|
||||
it('describes non existing tag', function() {
|
||||
var $return = OC.SystemTags.getDescriptiveTag('23');
|
||||
expect($return.textContent).toEqual('Non-existing tag #23');
|
||||
expect($return.classList.contains('non-existing-tag')).toEqual(true);
|
||||
});
|
||||
|
||||
it('describes SystemTagModel', function() {
|
||||
var tag = new OC.SystemTags.SystemTagModel({
|
||||
id: 23,
|
||||
name: 'Twenty Three',
|
||||
userAssignable: true,
|
||||
userVisible: true
|
||||
});
|
||||
var $return = OC.SystemTags.getDescriptiveTag(tag);
|
||||
expect($return.textContent).toEqual('Twenty Three');
|
||||
expect($return.classList.contains('non-existing-tag')).toEqual(false);
|
||||
});
|
||||
|
||||
it('describes JSON tag object', function() {
|
||||
var $return = OC.SystemTags.getDescriptiveTag({
|
||||
id: 42,
|
||||
name: 'Fourty Two',
|
||||
userAssignable: true,
|
||||
userVisible: true
|
||||
});
|
||||
expect($return.textContent).toEqual('Fourty Two');
|
||||
expect($return.classList.contains('non-existing-tag')).toEqual(false);
|
||||
});
|
||||
|
||||
it('scope', function() {
|
||||
function testScope(userVisible, userAssignable, expectedText) {
|
||||
var $return = OC.SystemTags.getDescriptiveTag({
|
||||
id: 42,
|
||||
name: 'Fourty Two',
|
||||
userAssignable: userAssignable,
|
||||
userVisible: userVisible
|
||||
});
|
||||
expect($return.textContent).toEqual(expectedText);
|
||||
expect($return.classList.contains('non-existing-tag')).toEqual(false);
|
||||
}
|
||||
|
||||
testScope(true, true, 'Fourty Two');
|
||||
testScope(false, true, 'Fourty Two (Invisible)');
|
||||
testScope(false, false, 'Fourty Two (Invisible)');
|
||||
testScope(true, false, 'Fourty Two (Restricted)');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,614 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
describe('OC.SystemTags.SystemTagsInputField tests', function() {
|
||||
var view, select2Stub, clock;
|
||||
|
||||
beforeEach(function() {
|
||||
clock = sinon.useFakeTimers();
|
||||
var $container = $('<div class="testInputContainer"></div>');
|
||||
select2Stub = sinon.stub($.fn, 'select2');
|
||||
select2Stub.returnsThis();
|
||||
$('#testArea').append($container);
|
||||
});
|
||||
afterEach(function() {
|
||||
select2Stub.restore();
|
||||
OC.SystemTags.collection.reset();
|
||||
clock.restore();
|
||||
view.remove();
|
||||
view = undefined;
|
||||
});
|
||||
|
||||
describe('general behavior', function() {
|
||||
var $dropdown;
|
||||
|
||||
beforeEach(function() {
|
||||
view = new OC.SystemTags.SystemTagsInputField();
|
||||
$('.testInputContainer').append(view.$el);
|
||||
$dropdown = $('<div class="select2-dropdown"></div>');
|
||||
select2Stub.withArgs('dropdown').returns($dropdown);
|
||||
$('#testArea').append($dropdown);
|
||||
|
||||
view.render();
|
||||
});
|
||||
describe('rendering', function() {
|
||||
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('.rename').length).toEqual(1);
|
||||
});
|
||||
});
|
||||
describe('tag selection', function() {
|
||||
beforeEach(function() {
|
||||
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', userAssignable: false, canAssign: false}),
|
||||
]);
|
||||
});
|
||||
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);
|
||||
expect(result.userVisible).toEqual(true);
|
||||
expect(result.userAssignable).toEqual(true);
|
||||
expect(result.canAssign).toEqual(true);
|
||||
});
|
||||
it('creates dummy tag when user types non-matching name even with prefix of existing tag', function() {
|
||||
var opts = select2Stub.getCall(0).args[0];
|
||||
var result = opts.createSearchChoice('ab');
|
||||
expect(result.id).toEqual(-1);
|
||||
expect(result.name).toEqual('ab');
|
||||
expect(result.isNew).toEqual(true);
|
||||
expect(result.userVisible).toEqual(true);
|
||||
expect(result.userAssignable).toEqual(true);
|
||||
expect(result.canAssign).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,
|
||||
canAssign: true
|
||||
});
|
||||
|
||||
var newModel = new OC.SystemTags.SystemTagModel({
|
||||
id: '123',
|
||||
name: 'newname',
|
||||
userVisible: true,
|
||||
userAssignable: true,
|
||||
canAssign: 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');
|
||||
});
|
||||
it('triggers select event and still adds to list even in case of conflict', function() {
|
||||
var selectHandler = sinon.stub();
|
||||
view.on('select', selectHandler);
|
||||
var fetchStub = sinon.stub(OC.SystemTags.SystemTagsCollection.prototype, 'fetch');
|
||||
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,
|
||||
canAssign: 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'
|
||||
}]);
|
||||
|
||||
// simulate conflict response for tag creation
|
||||
createStub.yieldTo('error', view.collection, {status: 409});
|
||||
|
||||
// at this point it fetches from the server
|
||||
expect(fetchStub.calledOnce).toEqual(true);
|
||||
// simulate fetch result by adding model to the collection
|
||||
view.collection.add(newModel);
|
||||
fetchStub.yieldTo('success', view.collection);
|
||||
|
||||
expect(select2Stub.lastCall.args[0]).toEqual('data');
|
||||
expect(select2Stub.lastCall.args[1]).toEqual([{
|
||||
id: '1',
|
||||
name: 'abc'
|
||||
},
|
||||
newModel.toJSON()
|
||||
]);
|
||||
|
||||
// select event still called
|
||||
expect(selectHandler.calledOnce).toEqual(true);
|
||||
expect(selectHandler.getCall(0).args[0]).toEqual(newModel);
|
||||
|
||||
createStub.restore();
|
||||
fetchStub.restore();
|
||||
});
|
||||
});
|
||||
describe('tag actions', function() {
|
||||
var opts;
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
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()));
|
||||
|
||||
});
|
||||
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();
|
||||
});
|
||||
});
|
||||
describe('setting data', function() {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as admin', function() {
|
||||
var $dropdown;
|
||||
|
||||
beforeEach(function() {
|
||||
view = new OC.SystemTags.SystemTagsInputField({
|
||||
isAdmin: true
|
||||
});
|
||||
$('.testInputContainer').append(view.$el);
|
||||
$dropdown = $('<div class="select2-dropdown"></div>');
|
||||
select2Stub.withArgs('dropdown').returns($dropdown);
|
||||
$('#testArea').append($dropdown);
|
||||
|
||||
view.render();
|
||||
});
|
||||
it('formatResult renders tag name with visibility', function() {
|
||||
var opts = select2Stub.getCall(0).args[0];
|
||||
var $el = $(opts.formatResult({id: '1', name: 'test', userVisible: false, userAssignable: false}));
|
||||
expect($el.find('.label').text()).toEqual('test (Invisible)');
|
||||
});
|
||||
it('formatSelection renders tag name with visibility', function() {
|
||||
var opts = select2Stub.getCall(0).args[0];
|
||||
var $el = $(opts.formatSelection({id: '1', name: 'test', userVisible: false, userAssignable: false}));
|
||||
expect($el.text().trim()).toEqual('test (Invisible)');
|
||||
});
|
||||
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', userAssignable: false, canAssign: false}),
|
||||
new OC.SystemTags.SystemTagModel({id: '4', name: 'test4', userAssignable: false, canAssign: true})
|
||||
];
|
||||
});
|
||||
afterEach(function() {
|
||||
fetchStub.restore();
|
||||
});
|
||||
it('grabs values from the full collection', function() {
|
||||
var $el = view.$el.find('input');
|
||||
$el.val('1,3,4');
|
||||
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(3);
|
||||
expect(models[0].id).toEqual('1');
|
||||
expect(models[0].name).toEqual('test1');
|
||||
expect(models[0].locked).toBeFalsy();
|
||||
expect(models[1].id).toEqual('3');
|
||||
expect(models[1].name).toEqual('test3');
|
||||
expect(models[1].locked).toBeFalsy();
|
||||
expect(models[2].id).toEqual('4');
|
||||
expect(models[2].name).toEqual('test4');
|
||||
expect(models[2].locked).toBeFalsy();
|
||||
});
|
||||
});
|
||||
describe('autocomplete', function() {
|
||||
var fetchStub, opts;
|
||||
|
||||
beforeEach(function() {
|
||||
fetchStub = sinon.stub(OC.SystemTags.SystemTagsCollection.prototype, 'fetch');
|
||||
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', userAssignable: false, canAssign: false}),
|
||||
new OC.SystemTags.SystemTagModel({id: '4', name: 'Deg'}),
|
||||
]);
|
||||
});
|
||||
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,
|
||||
canAssign: true
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'abd',
|
||||
userVisible: true,
|
||||
userAssignable: false,
|
||||
canAssign: false
|
||||
}
|
||||
]);
|
||||
});
|
||||
it('completes case insensitive', function() {
|
||||
var callback = sinon.stub();
|
||||
opts.query({
|
||||
term: 'de',
|
||||
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: '2',
|
||||
name: 'def',
|
||||
userVisible: true,
|
||||
userAssignable: true,
|
||||
canAssign: true
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'Deg',
|
||||
userVisible: true,
|
||||
userAssignable: true,
|
||||
canAssign: true
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('tag actions', function() {
|
||||
var opts;
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
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()));
|
||||
|
||||
});
|
||||
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('as user', function() {
|
||||
var $dropdown;
|
||||
|
||||
beforeEach(function() {
|
||||
view = new OC.SystemTags.SystemTagsInputField({
|
||||
isAdmin: false
|
||||
});
|
||||
$('.testInputContainer').append(view.$el);
|
||||
$dropdown = $('<div class="select2-dropdown"></div>');
|
||||
select2Stub.withArgs('dropdown').returns($dropdown);
|
||||
$('#testArea').append($dropdown);
|
||||
|
||||
view.render();
|
||||
});
|
||||
it('formatResult renders tag name only', function() {
|
||||
var opts = select2Stub.getCall(0).args[0];
|
||||
var $el = $(opts.formatResult({id: '1', name: 'test'}));
|
||||
expect($el.find('.label').text()).toEqual('test');
|
||||
});
|
||||
it('formatSelection renders tag name only', function() {
|
||||
var opts = select2Stub.getCall(0).args[0];
|
||||
var $el = $(opts.formatSelection({id: '1', name: 'test'}));
|
||||
expect($el.text().trim()).toEqual('test');
|
||||
});
|
||||
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', userAssignable: false, canAssign: false}),
|
||||
new OC.SystemTags.SystemTagModel({id: '4', name: 'test4', userAssignable: false, canAssign: true})
|
||||
];
|
||||
view.render();
|
||||
});
|
||||
afterEach(function() {
|
||||
fetchStub.restore();
|
||||
});
|
||||
it('grabs values from the full collection', function() {
|
||||
var $el = view.$el.find('input');
|
||||
$el.val('1,3,4');
|
||||
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(3);
|
||||
expect(models[0].id).toEqual('1');
|
||||
expect(models[0].name).toEqual('test1');
|
||||
expect(models[0].locked).toBeFalsy();
|
||||
expect(models[1].id).toEqual('3');
|
||||
expect(models[1].name).toEqual('test3');
|
||||
// restricted / cannot assign locks the entry
|
||||
expect(models[1].locked).toEqual(true);
|
||||
expect(models[2].id).toEqual('4');
|
||||
expect(models[2].name).toEqual('test4');
|
||||
expect(models[2].locked).toBeFalsy();
|
||||
});
|
||||
});
|
||||
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', userAssignable: false, canAssign: false}),
|
||||
new OC.SystemTags.SystemTagModel({id: '4', name: 'Deg'}),
|
||||
new OC.SystemTags.SystemTagModel({id: '5', name: 'abe', userAssignable: false, canAssign: true})
|
||||
]);
|
||||
});
|
||||
afterEach(function() {
|
||||
fetchStub.restore();
|
||||
});
|
||||
it('completes results excluding non-assignable tags', 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,
|
||||
canAssign: true
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'abe',
|
||||
userVisible: true,
|
||||
userAssignable: false,
|
||||
canAssign: true
|
||||
}
|
||||
]);
|
||||
});
|
||||
it('completes case insensitive', function() {
|
||||
var callback = sinon.stub();
|
||||
opts.query({
|
||||
term: 'de',
|
||||
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: '2',
|
||||
name: 'def',
|
||||
userVisible: true,
|
||||
userAssignable: true,
|
||||
canAssign: true
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'Deg',
|
||||
userVisible: true,
|
||||
userAssignable: true,
|
||||
canAssign: true
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('tag actions', function() {
|
||||
var opts;
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
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()));
|
||||
|
||||
});
|
||||
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 only for admins
|
||||
expect($dropdown.find('.delete').length).toEqual(0);
|
||||
$dropdown.find('.delete').mouseup();
|
||||
|
||||
expect(destroyStub.notCalled).toEqual(true);
|
||||
|
||||
destroyStub.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import './systemtags.js'
|
||||
import './systemtagmodel.js'
|
||||
import './systemtagsmappingcollection.js'
|
||||
import './systemtagscollection.js'
|
||||
import './systemtagsinputfield.js'
|
||||
|
||||
import '../../css/systemtags.scss'
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* @param {object} OC The OC namespace
|
||||
*/
|
||||
|
||||
(function(OC) {
|
||||
if (OC?.Files?.Client) {
|
||||
_.extend(OC.Files.Client, {
|
||||
PROPERTY_FILEID: '{' + OC.Files.Client.NS_OWNCLOUD + '}id',
|
||||
PROPERTY_CAN_ASSIGN: '{' + OC.Files.Client.NS_OWNCLOUD + '}can-assign',
|
||||
PROPERTY_DISPLAYNAME: '{' + OC.Files.Client.NS_OWNCLOUD + '}display-name',
|
||||
PROPERTY_USERVISIBLE: '{' + OC.Files.Client.NS_OWNCLOUD + '}user-visible',
|
||||
PROPERTY_USERASSIGNABLE: '{' + OC.Files.Client.NS_OWNCLOUD + '}user-assignable',
|
||||
})
|
||||
|
||||
/**
|
||||
* @class OCA.SystemTags.SystemTagsCollection
|
||||
* @classdesc
|
||||
*
|
||||
* System tag
|
||||
*
|
||||
*/
|
||||
const SystemTagModel = OC.Backbone.Model.extend(
|
||||
/** @lends OCA.SystemTags.SystemTagModel.prototype */ {
|
||||
sync: OC.Backbone.davSync,
|
||||
|
||||
defaults: {
|
||||
userVisible: true,
|
||||
userAssignable: true,
|
||||
canAssign: true,
|
||||
},
|
||||
|
||||
davProperties: {
|
||||
id: OC.Files.Client.PROPERTY_FILEID,
|
||||
name: OC.Files.Client.PROPERTY_DISPLAYNAME,
|
||||
userVisible: OC.Files.Client.PROPERTY_USERVISIBLE,
|
||||
userAssignable: OC.Files.Client.PROPERTY_USERASSIGNABLE,
|
||||
// read-only, effective permissions computed by the server,
|
||||
canAssign: OC.Files.Client.PROPERTY_CAN_ASSIGN,
|
||||
},
|
||||
|
||||
parse(data) {
|
||||
return {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
userVisible: data.userVisible === true || data.userVisible === 'true',
|
||||
userAssignable: data.userAssignable === true || data.userAssignable === 'true',
|
||||
canAssign: data.canAssign === true || data.canAssign === 'true',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
OC.SystemTags = OC.SystemTags || {}
|
||||
OC.SystemTags.SystemTagModel = SystemTagModel
|
||||
}
|
||||
})(OC)
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
import escapeHTML from 'escape-html'
|
||||
|
||||
(function(OC) {
|
||||
/**
|
||||
* @namespace
|
||||
*/
|
||||
OC.SystemTags = {
|
||||
/**
|
||||
*
|
||||
* @param {OC.SystemTags.SystemTagModel|Object|String} tag
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
getDescriptiveTag: function(tag) {
|
||||
if (_.isUndefined(tag.name) && !_.isUndefined(tag.toJSON)) {
|
||||
tag = tag.toJSON()
|
||||
}
|
||||
|
||||
var $span = document.createElement('span')
|
||||
|
||||
if (_.isUndefined(tag.name)) {
|
||||
$span.classList.add('non-existing-tag')
|
||||
$span.textContent = t('core', 'Non-existing tag #{tag}', {
|
||||
tag: tag
|
||||
})
|
||||
return $span
|
||||
}
|
||||
|
||||
$span.textContent = escapeHTML(tag.name)
|
||||
|
||||
var scope
|
||||
if (!tag.userAssignable) {
|
||||
scope = t('core', 'Restricted')
|
||||
}
|
||||
if (!tag.userVisible) {
|
||||
// invisible also implicitly means not assignable
|
||||
scope = t('core', 'Invisible')
|
||||
}
|
||||
if (scope) {
|
||||
var $scope = document.createElement('em')
|
||||
$scope.textContent = ' (' + scope + ')'
|
||||
$span.appendChild($scope)
|
||||
}
|
||||
return $span
|
||||
}
|
||||
}
|
||||
})(OC)
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
(function(OC) {
|
||||
|
||||
function filterFunction(model, term) {
|
||||
return model.get('name').substr(0, term.length).toLowerCase() === term.toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {any} 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 || this.working || 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()
|
||||
}
|
||||
|
||||
this.working = true
|
||||
|
||||
var success = options.success
|
||||
options = _.extend({}, options)
|
||||
options.success = function() {
|
||||
self.fetched = true
|
||||
self.working = false
|
||||
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)
|
||||
|
|
@ -1,444 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
import templateResult from './templates/result.handlebars'
|
||||
import templateResultForm from './templates/result_form.handlebars'
|
||||
import templateSelection from './templates/selection.handlebars'
|
||||
|
||||
(function(OC) {
|
||||
|
||||
/**
|
||||
* @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,
|
||||
|
||||
_lastUsedTags: [],
|
||||
|
||||
className: 'systemTagsInputFieldContainer',
|
||||
|
||||
template: function(data) {
|
||||
return '<input class="systemTagsInputField" type="hidden" name="tags" value=""/>'
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new SystemTagsInputField
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {string} [options.objectType=files] object type for which tags are assigned to
|
||||
* @param {boolean} [options.multiple=false] whether to allow selecting multiple tags
|
||||
* @param {boolean} [options.allowActions=true] whether tags can be renamed/delete within the dropdown
|
||||
* @param {boolean} [options.allowCreate=true] whether new tags can be created
|
||||
* @param {boolean} [options.isAdmin=true] whether the user is an administrator
|
||||
* @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
|
||||
this._isAdmin = !!options.isAdmin
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
_.defer(_.bind(this._getLastUsedTags, this))
|
||||
|
||||
_.bindAll(
|
||||
this,
|
||||
'_refreshSelection',
|
||||
'_onClickRenameTag',
|
||||
'_onClickDeleteTag',
|
||||
'_onSelectTag',
|
||||
'_onDeselectTag',
|
||||
'_onSubmitRenameTag'
|
||||
)
|
||||
},
|
||||
|
||||
_getLastUsedTags: function() {
|
||||
var self = this
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: OC.generateUrl('/apps/systemtags/lastused'),
|
||||
success: function(response) {
|
||||
self._lastUsedTags = response
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
||||
var oldName = tagModel.get('name')
|
||||
var $renameForm = $(templateResultForm({
|
||||
cid: this.cid,
|
||||
name: oldName,
|
||||
deleteTooltip: t('core', 'Delete'),
|
||||
renameLabel: t('core', 'Rename'),
|
||||
isAdmin: this._isAdmin
|
||||
}))
|
||||
$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().trim()
|
||||
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()
|
||||
$(ev.target).tooltip('option', 'hide')
|
||||
$item.closest('.select2-result').remove()
|
||||
// TODO: spinner
|
||||
return false
|
||||
},
|
||||
|
||||
_addToSelect2Selection: function(selection) {
|
||||
var data = this.$tagsField.select2('data')
|
||||
data.push(selection)
|
||||
this.$tagsField.select2('data', data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.trim(),
|
||||
userVisible: true,
|
||||
userAssignable: true,
|
||||
canAssign: true
|
||||
}, {
|
||||
success: function(model) {
|
||||
self._addToSelect2Selection(model.toJSON())
|
||||
self._lastUsedTags.unshift(model.id)
|
||||
self.trigger('select', model)
|
||||
},
|
||||
error: function(model, xhr) {
|
||||
if (xhr.status === 409) {
|
||||
// re-fetch collection to get the missing tag
|
||||
self.collection.reset()
|
||||
self.collection.fetch({
|
||||
success: function(collection) {
|
||||
// find the tag in the collection
|
||||
var model = collection.where({
|
||||
name: e.object.name.trim(),
|
||||
userVisible: true,
|
||||
userAssignable: true
|
||||
})
|
||||
if (model.length) {
|
||||
model = model[0]
|
||||
// the tag already exists or was already assigned,
|
||||
// add it to the list anyway
|
||||
self._addToSelect2Selection(model.toJSON())
|
||||
self.trigger('select', model)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
this.$tagsField.select2('close')
|
||||
e.preventDefault()
|
||||
return false
|
||||
} else {
|
||||
tag = this.collection.get(e.object.id)
|
||||
this._lastUsedTags.unshift(tag.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(collection) {
|
||||
var tagModels = collection.filterByName(query.term.trim())
|
||||
if (!self._isAdmin) {
|
||||
tagModels = _.filter(tagModels, function(tagModel) {
|
||||
return tagModel.get('canAssign')
|
||||
})
|
||||
}
|
||||
query.callback({
|
||||
results: _.invoke(tagModels, 'toJSON')
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
_preventDefault: function(e) {
|
||||
e.stopPropagation()
|
||||
},
|
||||
|
||||
/**
|
||||
* Formats a single dropdown result
|
||||
*
|
||||
* @param {Object} data data to format
|
||||
* @returns {string} HTML markup
|
||||
*/
|
||||
_formatDropDownResult: function(data) {
|
||||
return templateResult(_.extend({
|
||||
renameTooltip: t('core', 'Rename'),
|
||||
allowActions: this._allowActions,
|
||||
tagMarkup: this._isAdmin ? OC.SystemTags.getDescriptiveTag(data).innerHTML : null,
|
||||
isAdmin: this._isAdmin
|
||||
}, data))
|
||||
},
|
||||
|
||||
/**
|
||||
* Formats a single selection item
|
||||
*
|
||||
* @param {Object} data data to format
|
||||
* @returns {string} HTML markup
|
||||
*/
|
||||
_formatSelection: function(data) {
|
||||
return templateSelection(_.extend({
|
||||
tagMarkup: this._isAdmin ? OC.SystemTags.getDescriptiveTag(data).innerHTML : null,
|
||||
isAdmin: this._isAdmin
|
||||
}, data))
|
||||
},
|
||||
|
||||
/**
|
||||
* Create new dummy choice for select2 when the user
|
||||
* types an arbitrary string
|
||||
*
|
||||
* @param {string} term entered term
|
||||
* @returns {Object} dummy tag
|
||||
*/
|
||||
_createSearchChoice: function(term) {
|
||||
term = term.trim()
|
||||
if (this.collection.filter(function(entry) {
|
||||
return entry.get('name') === term
|
||||
}).length) {
|
||||
return
|
||||
}
|
||||
if (!this._newTag) {
|
||||
this._newTag = {
|
||||
id: -1,
|
||||
name: term,
|
||||
userAssignable: true,
|
||||
userVisible: true,
|
||||
canAssign: true,
|
||||
isNew: true
|
||||
}
|
||||
} else {
|
||||
this._newTag.name = term
|
||||
}
|
||||
|
||||
return this._newTag
|
||||
},
|
||||
|
||||
_initSelection: function(element, callback) {
|
||||
var self = this
|
||||
var ids = $(element).val().split(',')
|
||||
|
||||
function modelToSelection(model) {
|
||||
var data = model.toJSON()
|
||||
if (!self._isAdmin && !data.canAssign) {
|
||||
// lock static tags for non-admins
|
||||
data.locked = true
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
function findSelectedObjects(ids) {
|
||||
var selectedModels = self.collection.filter(function(model) {
|
||||
return ids.indexOf(model.id) >= 0 && (self._isAdmin || model.get('userVisible'))
|
||||
})
|
||||
return _.map(selectedModels, modelToSelection)
|
||||
}
|
||||
|
||||
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', 'Collaborative 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: _.bind(this._formatSelection, this),
|
||||
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) {
|
||||
var aLastUsed = self._lastUsedTags.indexOf(a.id)
|
||||
var bLastUsed = self._lastUsedTags.indexOf(b.id)
|
||||
|
||||
if (aLastUsed !== bLastUsed) {
|
||||
if (bLastUsed === -1) {
|
||||
return -1
|
||||
}
|
||||
if (aLastUsed === -1) {
|
||||
return 1
|
||||
}
|
||||
return aLastUsed < bLastUsed ? -1 : 1
|
||||
}
|
||||
|
||||
// Both not found
|
||||
return OC.Util.naturalSortCompare(a.name, b.name)
|
||||
}
|
||||
if (aSelected && !bSelected) {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
})
|
||||
return results
|
||||
},
|
||||
formatNoMatches: function() {
|
||||
return t('core', 'No tags found')
|
||||
}
|
||||
})
|
||||
.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')
|
||||
}
|
||||
},
|
||||
|
||||
getValues: function() {
|
||||
this.$tagsField.select2('val')
|
||||
},
|
||||
|
||||
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)
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { generateRemoteUrl } from '@nextcloud/router'
|
||||
|
||||
(function(OC) {
|
||||
/**
|
||||
* @class OC.SystemTags.SystemTagsMappingCollection
|
||||
* @classdesc
|
||||
*
|
||||
* Collection of tags assigned to a an object
|
||||
*
|
||||
*/
|
||||
const 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
|
||||
*
|
||||
* @member int
|
||||
*/
|
||||
_objectId: null,
|
||||
|
||||
/**
|
||||
* Type of the object to filter by
|
||||
*
|
||||
* @member string
|
||||
*/
|
||||
_objectType: 'files',
|
||||
|
||||
model: OC.SystemTags.SystemTagModel,
|
||||
|
||||
url() {
|
||||
return generateRemoteUrl('dav') + '/systemtags-relations/' + this._objectType + '/' + this._objectId
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the object id to filter by or null for all.
|
||||
*
|
||||
* @param {number} objectId file id or null
|
||||
*/
|
||||
setObjectId(objectId) {
|
||||
this._objectId = objectId
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the object type to filter by or null for all.
|
||||
*
|
||||
* @param {number} objectType file id or null
|
||||
*/
|
||||
setObjectType(objectType) {
|
||||
this._objectType = objectType
|
||||
},
|
||||
|
||||
initialize(models, options) {
|
||||
options = options || {}
|
||||
if (!_.isUndefined(options.objectId)) {
|
||||
this._objectId = options.objectId
|
||||
}
|
||||
if (!_.isUndefined(options.objectType)) {
|
||||
this._objectType = options.objectType
|
||||
}
|
||||
},
|
||||
|
||||
getTagIds() {
|
||||
return this.map(function(model) {
|
||||
return model.id
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
OC.SystemTags = OC.SystemTags || {}
|
||||
OC.SystemTags.SystemTagsMappingCollection = SystemTagsMappingCollection
|
||||
})(OC)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<span class="systemtags-item{{#if isNew}} new-item{{/if}}" data-id="{{id}}">
|
||||
<span class="checkmark icon icon-checkmark"></span>
|
||||
{{#if isAdmin}}
|
||||
<span class="label">{{{tagMarkup}}}</span>
|
||||
{{else}}
|
||||
<span class="label">{{name}}</span>
|
||||
{{/if}}
|
||||
{{#allowActions}}
|
||||
<span class="systemtags-actions">
|
||||
<a href="#" class="rename icon icon-rename" title="{{renameTooltip}}"></a>
|
||||
</span>
|
||||
{{/allowActions}}
|
||||
</span>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<form class="systemtags-rename-form">
|
||||
<label class="hidden-visually" for="{{cid}}-rename-input">{{renameLabel}}</label>
|
||||
<input id="{{cid}}-rename-input" type="text" value="{{name}}">
|
||||
{{#if isAdmin}}
|
||||
<a href="#" class="delete icon icon-delete" title="{{deleteTooltip}}"></a>
|
||||
{{/if}}
|
||||
</form>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{{#if isAdmin}}
|
||||
<span class="label">{{{tagMarkup}}}</span>
|
||||
{{else}}
|
||||
<span class="label">{{name}}</span>
|
||||
{{/if}}
|
||||
|
|
@ -21,7 +21,6 @@ module.exports = {
|
|||
'public-page-menu': path.resolve(__dirname, 'core/src', 'public-page-menu.ts'),
|
||||
'public-page-user-menu': path.resolve(__dirname, 'core/src', 'public-page-user-menu.ts'),
|
||||
recommendedapps: path.join(__dirname, 'core/src', 'recommendedapps.js'),
|
||||
systemtags: path.resolve(__dirname, 'core/src', 'systemtags/merged-systemtags.js'),
|
||||
'unified-search': path.join(__dirname, 'core/src', 'unified-search.ts'),
|
||||
'legacy-unified-search': path.join(__dirname, 'core/src', 'legacy-unified-search.js'),
|
||||
'unsupported-browser': path.join(__dirname, 'core/src', 'unsupported-browser.js'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue