Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/systemtags/systemtagsinputfield.js')
-rw-r--r--core/src/systemtags/systemtagsinputfield.js463
1 files changed, 463 insertions, 0 deletions
diff --git a/core/src/systemtags/systemtagsinputfield.js b/core/src/systemtags/systemtagsinputfield.js
new file mode 100644
index 00000000000..f0aac9381d6
--- /dev/null
+++ b/core/src/systemtags/systemtagsinputfield.js
@@ -0,0 +1,463 @@
+/**
+ * Copyright (c) 2015
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Vincent Petry <vincent@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* 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 {bool} [options.multiple=false] whether to allow selecting multiple tags
+ * @param {bool} [options.allowActions=true] whether tags can be renamed/delete within the dropdown
+ * @param {bool} [options.allowCreate=true] whether new tags can be created
+ * @param {bool} [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('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)[0].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)[0].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)