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