Browse Source
refactor: Remove OC.SystemTags JS API
refactor: Remove OC.SystemTags JS API
Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>pull/55320/head
committed by
Ferdinand Thiessen
17 changed files with 0 additions and 1588 deletions
-
1apps/systemtags/lib/AppInfo/Application.php
-
5core/css/systemtags.css
-
1core/css/systemtags.css.map
-
79core/css/systemtags.scss
-
54core/js/tests/specs/systemtags/systemtagsSpec.js
-
68core/js/tests/specs/systemtags/systemtagscollectionSpec.js
-
614core/js/tests/specs/systemtags/systemtagsinputfieldSpec.js
-
13core/src/systemtags/merged-systemtags.js
-
58core/src/systemtags/systemtagmodel.js
-
53core/src/systemtags/systemtags.js
-
88core/src/systemtags/systemtagscollection.js
-
444core/src/systemtags/systemtagsinputfield.js
-
84core/src/systemtags/systemtagsmappingcollection.js
-
13core/src/systemtags/templates/result.handlebars
-
7core/src/systemtags/templates/result_form.handlebars
-
5core/src/systemtags/templates/selection.handlebars
-
1webpack.modules.js
@ -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}} |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue