diff options
Diffstat (limited to 'core/src/OC/dialogs.js')
-rw-r--r-- | core/src/OC/dialogs.js | 1110 |
1 files changed, 257 insertions, 853 deletions
diff --git a/core/src/OC/dialogs.js b/core/src/OC/dialogs.js index 286f9848290..5c6934e67a2 100644 --- a/core/src/OC/dialogs.js +++ b/core/src/OC/dialogs.js @@ -1,79 +1,51 @@ /** - * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> - * @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev> - * - * @author Bartek Przybylski <bart.p.pl@gmail.com> - * @author Christopher Schäpers <kondou@ts.unde.re> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Calviño Sánchez <danxuliu@gmail.com> - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Florian Schunk <florian.schunk@rwth-aachen.de> - * @author Gary Kim <gary@garykim.dev> - * @author Hendrik Leppelsack <hendrik@leppelsack.de> - * @author Jan-Christoph Borchardt <hey@jancborchardt.net> - * @author Joas Schilling <coding@schilljs.com> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Julius Härtl <jus@bitgrid.net> - * @author Loïc Hermann <loic.hermann@sciam.fr> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Olivier Paroz <github@oparoz.com> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Sujith Haridasan <Sujith_Haridasan@mentor.com> - * @author Thomas Citharel <nextcloud@tcit.fr> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0-or-later - * - * 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/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later */ /* eslint-disable */ import _ from 'underscore' import $ from 'jquery' -import OC from './index' -import OCA from '../OCA/index' -import { isA11yActivation } from '../Util/a11y' +import IconMove from '@mdi/svg/svg/folder-move.svg?raw' +import IconCopy from '@mdi/svg/svg/folder-multiple-outline.svg?raw' + +import OC from './index.js' +import { DialogBuilder, FilePickerType, getFilePickerBuilder, spawnDialog } from '@nextcloud/dialogs' +import { translate as t } from '@nextcloud/l10n' +import { basename } from 'path' +import { defineAsyncComponent } from 'vue' /** * this class to ease the usage of jquery dialogs */ const Dialogs = { // dialog button types + /** @deprecated use `@nextcloud/dialogs` */ YES_NO_BUTTONS: 70, + /** @deprecated use `@nextcloud/dialogs` */ OK_BUTTONS: 71, + /** @deprecated use FilePickerType from `@nextcloud/dialogs` */ FILEPICKER_TYPE_CHOOSE: 1, + /** @deprecated use FilePickerType from `@nextcloud/dialogs` */ FILEPICKER_TYPE_MOVE: 2, + /** @deprecated use FilePickerType from `@nextcloud/dialogs` */ FILEPICKER_TYPE_COPY: 3, + /** @deprecated use FilePickerType from `@nextcloud/dialogs` */ FILEPICKER_TYPE_COPY_MOVE: 4, + /** @deprecated use FilePickerType from `@nextcloud/dialogs` */ FILEPICKER_TYPE_CUSTOM: 5, - // used to name each dialog - dialogsCounter: 0, - /** * displays alert dialog * @param {string} text content of dialog * @param {string} title dialog title * @param {function} callback which will be triggered when user presses OK * @param {boolean} [modal] make the dialog modal + * + * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog */ alert: function(text, title, callback, modal) { this.message( @@ -85,12 +57,15 @@ const Dialogs = { modal ) }, + /** * displays info dialog * @param {string} text content of dialog * @param {string} title dialog title * @param {function} callback which will be triggered when user presses OK * @param {boolean} [modal] make the dialog modal + * + * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog */ info: function(text, title, callback, modal) { this.message(text, title, 'info', Dialogs.OK_BUTTON, callback, modal) @@ -103,6 +78,8 @@ const Dialogs = { * @param {function} callback which will be triggered when user presses OK (true or false would be passed to callback respectively) * @param {boolean} [modal] make the dialog modal * @returns {Promise} + * + * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog */ confirm: function(text, title, callback, modal) { return this.message( @@ -122,16 +99,34 @@ const Dialogs = { * @param {function} callback which will be triggered when user presses OK (true or false would be passed to callback respectively) * @param {boolean} [modal] make the dialog modal * @returns {Promise} + * + * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog */ - confirmDestructive: function(text, title, buttons, callback, modal) { - return this.message( - text, - title, - 'none', - buttons, - callback, - modal === undefined ? true : modal - ) + confirmDestructive: function(text, title, buttons = Dialogs.OK_BUTTONS, callback = () => {}, modal) { + return (new DialogBuilder()) + .setName(title) + .setText(text) + .setButtons( + buttons === Dialogs.OK_BUTTONS + ? [ + { + label: t('core', 'Yes'), + type: 'error', + callback: () => { + callback.clicked = true + callback(true) + }, + } + ] + : Dialogs._getLegacyButtons(buttons, callback) + ) + .build() + .show() + .then(() => { + if (!callback.clicked) { + callback(false) + } + }) }, /** * displays confirmation dialog @@ -140,17 +135,35 @@ const Dialogs = { * @param {function} callback which will be triggered when user presses OK (true or false would be passed to callback respectively) * @param {boolean} [modal] make the dialog modal * @returns {Promise} + * + * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog */ confirmHtml: function(text, title, callback, modal) { - return this.message( - text, - title, - 'notice', - Dialogs.YES_NO_BUTTONS, - callback, - modal, - true - ) + return (new DialogBuilder()) + .setName(title) + .setText('') + .setButtons([ + { + label: t('core', 'No'), + callback: () => {}, + }, + { + label: t('core', 'Yes'), + type: 'primary', + callback: () => { + callback.clicked = true + callback(true) + }, + }, + ]) + .build() + .setHTML(text) + .show() + .then(() => { + if (!callback.clicked) { + callback(false) + } + }) }, /** * displays prompt dialog @@ -161,73 +174,32 @@ const Dialogs = { * @param {string} name name of the input field * @param {boolean} password whether the input should be a password input * @returns {Promise} + * + * @deprecated Use NcDialog from `@nextcloud/vue` instead */ prompt: function(text, title, callback, modal, name, password) { - return $.when(this._getMessageTemplate()).then(function($tmpl) { - var dialogName = 'oc-dialog-' + Dialogs.dialogsCounter + '-content' - var dialogId = '#' + dialogName - var $dlg = $tmpl.octemplate({ - dialog_name: dialogName, - title: title, - message: text, - type: 'notice' - }) - var input = $('<input/>') - input.attr('type', password ? 'password' : 'text').attr('id', dialogName + '-input').attr('placeholder', name) - var label = $('<label/>').attr('for', dialogName + '-input').text(name + ': ') - $dlg.append(label) - $dlg.append(input) - if (modal === undefined) { - modal = false - } - $('body').append($dlg) - - // wrap callback in _.once(): - // only call callback once and not twice (button handler and close - // event) but call it for the close event, if ESC or the x is hit - if (callback !== undefined) { - callback = _.once(callback) - } - - var buttonlist = [{ - text: t('core', 'No'), - click: function() { - if (callback !== undefined) { - // eslint-disable-next-line standard/no-callback-literal - callback(false, input.val()) - } - $(dialogId).ocdialog('close') - } - }, { - text: t('core', 'Yes'), - click: function() { - if (callback !== undefined) { - // eslint-disable-next-line standard/no-callback-literal - callback(true, input.val()) - } - $(dialogId).ocdialog('close') + return new Promise((resolve) => { + spawnDialog( + defineAsyncComponent(() => import('../components/LegacyDialogPrompt.vue')), + { + text, + name: title, + callback, + inputName: name, + isPassword: !!password }, - defaultButton: true - }] - - $(dialogId).ocdialog({ - closeOnEscape: true, - modal: modal, - buttons: buttonlist, - close: function() { - // callback is already fired if Yes/No is clicked directly - if (callback !== undefined) { - // eslint-disable-next-line standard/no-callback-literal - callback(false, input.val()) - } - } - }) - input.focus() - Dialogs.dialogsCounter++ + (...args) => { + callback(...args) + resolve() + }, + ) }) }, + /** - * show a file picker to pick a file from + * Legacy wrapper to the new Vue based filepicker from `@nextcloud/dialogs` + * + * Prefer to use the Vue filepicker directly instead. * * In order to pick several types of mime types they need to be passed as an * array of strings. @@ -237,441 +209,196 @@ const Dialogs = { * should be used instead. * * @param {string} title dialog title - * @param {function} callback which will be triggered when user presses Choose + * @param {Function} callback which will be triggered when user presses Choose * @param {boolean} [multiselect] whether it should be possible to select multiple files - * @param {string[]} [mimetypeFilter] mimetype to filter by - directories will always be included - * @param {boolean} [modal] make the dialog modal + * @param {string[]} [mimetype] mimetype to filter by - directories will always be included + * @param {boolean} [_modal] do not use * @param {string} [type] Type of file picker : Choose, copy, move, copy and move * @param {string} [path] path to the folder that the the file can be picket from - * @param {Object} [options] additonal options that need to be set + * @param {object} [options] additonal options that need to be set * @param {Function} [options.filter] filter function for advanced filtering + * @param {boolean} [options.allowDirectoryChooser] Allow to select directories + * @deprecated since 27.1.0 use the filepicker from `@nextcloud/dialogs` instead */ - filepicker: function(title, callback, multiselect, mimetypeFilter, modal, type, path, options) { - var self = this - - this.filepicker.sortField = 'name' - this.filepicker.sortOrder = 'asc' - // avoid opening the picker twice - if (this.filepicker.loading) { - return - } + filepicker(title, callback, multiselect = false, mimetype = undefined, _modal = undefined, type = FilePickerType.Choose, path = undefined, options = undefined) { - if (type === undefined) { - type = this.FILEPICKER_TYPE_CHOOSE - } - - var emptyText = t('core', 'No files in here') - var newText = t('files', 'New folder') - if (type === this.FILEPICKER_TYPE_COPY || type === this.FILEPICKER_TYPE_MOVE || type === this.FILEPICKER_TYPE_COPY_MOVE) { - emptyText = t('core', 'No more subfolders in here') - } - - this.filepicker.loading = true - this.filepicker.filesClient = (OCA.Sharing && OCA.Sharing.PublicApp && OCA.Sharing.PublicApp.fileList) ? OCA.Sharing.PublicApp.fileList.filesClient : OC.Files.getClient() - - this.filelist = null - path = path || '' - options = Object.assign({ - allowDirectoryChooser: false - }, options) - - $.when(this._getFilePickerTemplate()).then(function($tmpl) { - self.filepicker.loading = false - var dialogName = 'oc-dialog-filepicker-content' - if (self.$filePicker) { - self.$filePicker.ocdialog('close') - } - - if (mimetypeFilter === undefined || mimetypeFilter === null) { - mimetypeFilter = [] - } - if (typeof (mimetypeFilter) === 'string') { - mimetypeFilter = [mimetypeFilter] - } - - self.$filePicker = $tmpl.octemplate({ - dialog_name: dialogName, - title: title, - emptytext: emptyText, - newtext: newText, - nameCol: t('core', 'Name'), - sizeCol: t('core', 'Size'), - modifiedCol: t('core', 'Modified') - }).data('path', path).data('multiselect', multiselect).data('mimetype', mimetypeFilter).data('allowDirectoryChooser', options.allowDirectoryChooser) - if (typeof(options.filter) === 'function') { - self.$filePicker.data('filter', options.filter) + /** + * Create legacy callback wrapper to support old filepicker syntax + * @param fn The original callback + * @param type The file picker type which was used to pick the file(s) + */ + const legacyCallback = (fn, type) => { + const getPath = (node) => { + const root = node?.root || '' + let path = node?.path || '' + // TODO: Fix this in @nextcloud/files + if (path.startsWith(root)) { + path = path.slice(root.length) || '/' + } + return path } - if (modal === undefined) { - modal = false - } - if (multiselect === undefined) { - multiselect = false + if (multiselect) { + return (nodes) => fn(nodes.map(getPath), type) + } else { + return (nodes) => fn(getPath(nodes[0]), type) } + } - $('body').prepend(self.$filePicker) - - self.$showGridView = $('button#picker-showgridview') - self.$showGridView.on('click keydown', function(event) { - if (isA11yActivation(event)) { - self._onGridviewChange() - } - }) - self._getGridSettings() + /** + * Coverting a Node into a legacy file info to support the OC.dialogs.filepicker filter function + * @param node The node to convert + */ + const nodeToLegacyFile = (node) => ({ + id: node.fileid || null, + path: node.path, + mimetype: node.mime || null, + mtime: node.mtime?.getTime() || null, + permissions: node.permissions, + name: node.attributes?.displayName || node.basename, + etag: node.attributes?.etag || null, + hasPreview: node.attributes?.hasPreview || null, + mountType: node.attributes?.mountType || null, + quotaAvailableBytes: node.attributes?.quotaAvailableBytes || null, + icon: null, + sharePermissions: null, + }) - var newButton = self.$filePicker.find('.actions.creatable .button-add') - if (type === self.FILEPICKER_TYPE_CHOOSE && !options.allowDirectoryChooser) { - self.$filePicker.find('.actions.creatable').hide() - } - newButton.on('focus', function() { - self.$filePicker.ocdialog('setEnterCallback', function(event) { - event.stopImmediatePropagation() - event.preventDefault() - newButton.click() - }) - }) - newButton.on('blur', function() { - self.$filePicker.ocdialog('unsetEnterCallback') - }) + const builder = getFilePickerBuilder(title) - OC.registerMenu(newButton, self.$filePicker.find('.menu'), function() { - $input.tooltip('hide') - $input.focus() - self.$filePicker.ocdialog('setEnterCallback', function(event) { - event.stopImmediatePropagation() - event.preventDefault() - self.$filePicker.submit() + // Setup buttons + if (type === this.FILEPICKER_TYPE_CUSTOM) { + (options.buttons || []).forEach((button) => { + builder.addButton({ + callback: legacyCallback(callback, button.type), + label: button.text, + type: button.defaultButton ? 'primary' : 'secondary', }) - var newName = $input.val() - var lastPos = newName.lastIndexOf('.') - if (lastPos === -1) { - lastPos = newName.length - } - $input.selectRange(0, lastPos) }) - var $form = self.$filePicker.find('.filenameform') - var $input = $form.find('input[type=\'text\']') - var $submit = $form.find('input[type=\'submit\']') - $input.on('keydown', function(event) { - if (isA11yActivation(event)) { - event.stopImmediatePropagation() - event.preventDefault() - $form.submit() - } - }) - $submit.on('click', function(event) { - event.stopImmediatePropagation() - event.preventDefault() - $form.submit() - }) - - /** - * Checks whether the given file name is valid. - * - * @param name file name to check - * @return true if the file name is valid. - * @throws a string exception with an error message if - * the file name is not valid - * - * NOTE: This function is duplicated in the files app: - * https://github.com/nextcloud/server/blob/b9bc2417e7a8dc81feb0abe20359bedaf864f790/apps/files/js/files.js#L127-L148 - */ - var isFileNameValid = function (name) { - var trimmedName = name.trim(); - if (trimmedName === '.' || trimmedName === '..') - { - throw t('files', '"{name}" is an invalid file name.', {name: name}) - } else if (trimmedName.length === 0) { - throw t('files', 'File name cannot be empty.') - } else if (trimmedName.indexOf('/') !== -1) { - throw t('files', '"/" is not allowed inside a file name.') - } else if (!!(trimmedName.match(OC.config.blacklist_files_regex))) { - throw t('files', '"{name}" is not an allowed filetype', {name: name}) + } else { + builder.setButtonFactory((nodes, path) => { + const buttons = [] + const [node] = nodes + const target = node?.displayname || node?.basename || basename(path) + + if (type === FilePickerType.Choose) { + buttons.push({ + callback: legacyCallback(callback, FilePickerType.Choose), + label: node && !this.multiSelect ? t('core', 'Choose {file}', { file: target }) : t('core', 'Choose'), + type: 'primary', + }) } - - return true - } - - var checkInput = function() { - var filename = $input.val() - try { - if (!isFileNameValid(filename)) { - // isFileNameValid(filename) throws an exception itself - } else if (self.filelist.find(function(file) { - return file.name === this - }, filename)) { - throw t('files', '{newName} already exists', { newName: filename }, undefined, { - escape: false - }) - } else { - return true - } - } catch (error) { - $input.attr('title', error) - $input.tooltip({ - placement: 'right', - trigger: 'manual', - 'container': '.newFolderMenu' + if (type === FilePickerType.CopyMove || type === FilePickerType.Copy) { + buttons.push({ + callback: legacyCallback(callback, FilePickerType.Copy), + label: target ? t('core', 'Copy to {target}', { target }) : t('core', 'Copy'), + type: 'primary', + icon: IconCopy, }) - $input.tooltip('_fixTitle') - $input.tooltip('show') - $input.addClass('error') } - return false - } - - $form.on('submit', function(event) { - event.stopPropagation() - event.preventDefault() - - if (checkInput()) { - var newname = $input.val() - self.filepicker.filesClient.createDirectory(self.$filePicker.data('path') + "/" + newname).always(function (status) { - self._fillFilePicker(self.$filePicker.data('path') + "/" + newname) + if (type === FilePickerType.Move || type === FilePickerType.CopyMove) { + buttons.push({ + callback: legacyCallback(callback, FilePickerType.Move), + label: target ? t('core', 'Move to {target}', { target }) : t('core', 'Move'), + type: type === FilePickerType.Move ? 'primary' : 'secondary', + icon: IconMove, }) - OC.hideMenus() - self.$filePicker.ocdialog('unsetEnterCallback') - self.$filePicker.click() - $input.val(newText) } + return buttons }) - $input.on('input', function(event) { - $input.tooltip('hide') - }) - - self.$filePicker.ready(function() { - self.$fileListHeader = self.$filePicker.find('.filelist thead tr') - self.$filelist = self.$filePicker.find('.filelist tbody') - self.$filelistContainer = self.$filePicker.find('.filelist-container') - self.$dirTree = self.$filePicker.find('.dirtree') - self.$dirTree.on('click keydown', 'div:not(:last-child)', self, function(event) { - if (isA11yActivation(event)) { - self._handleTreeListSelect(event, type) - } - }) - self.$filelist.on('click keydown', 'tr', function(event) { - if (isA11yActivation(event)) { - self._handlePickerClick(event, $(this), type) - } - }) - self.$fileListHeader.on('click keydown', 'a', function(event) { - if (isA11yActivation(event)) { - var dir = self.$filePicker.data('path') - self.filepicker.sortField = $(event.currentTarget).data('sort') - self.filepicker.sortOrder = self.filepicker.sortOrder === 'asc' ? 'desc' : 'asc' - self._fillFilePicker(dir) - } - }) - self._fillFilePicker(path) - }) - - // build buttons - var functionToCall = function(returnType) { - if (callback !== undefined) { - var datapath - if (multiselect === true) { - datapath = [] - self.$filelist.find('tr.filepicker_element_selected').each(function(index, element) { - datapath.push(self.$filePicker.data('path') + '/' + $(element).data('entryname')) - }) - } else { - datapath = self.$filePicker.data('path') - var selectedName = self.$filelist.find('tr.filepicker_element_selected').data('entryname') - if (selectedName) { - datapath += '/' + selectedName - } - } - callback(datapath, returnType) - self.$filePicker.ocdialog('close') - } - } - - var chooseCallback = function() { - functionToCall(Dialogs.FILEPICKER_TYPE_CHOOSE) - } - - var copyCallback = function() { - functionToCall(Dialogs.FILEPICKER_TYPE_COPY) - } + } - var moveCallback = function() { - functionToCall(Dialogs.FILEPICKER_TYPE_MOVE) - } + if (mimetype) { + builder.setMimeTypeFilter(typeof mimetype === 'string' ? [mimetype] : (mimetype || [])) + } + if (typeof options?.filter === 'function') { + builder.setFilter((node) => options.filter(nodeToLegacyFile(node))) + } + builder.allowDirectories(options?.allowDirectoryChooser === true || mimetype?.includes('httpd/unix-directory') || false) + .setMultiSelect(multiselect) + .startAt(path) + .build() + .pick() + }, - var buttonlist = [] - if (type === Dialogs.FILEPICKER_TYPE_CHOOSE) { - buttonlist.push({ - text: t('core', 'Choose'), - click: chooseCallback, - defaultButton: true - }) - } else if (type === Dialogs.FILEPICKER_TYPE_CUSTOM) { - options.buttons.forEach(function(button) { - buttonlist.push({ - text: button.text, - click: function() { - functionToCall(button.type) - }, - defaultButton: button.defaultButton - }) - }) - } else { - if (type === Dialogs.FILEPICKER_TYPE_COPY || type === Dialogs.FILEPICKER_TYPE_COPY_MOVE) { - buttonlist.push({ - text: t('core', 'Copy'), - click: copyCallback, - defaultButton: false - }) - } - if (type === Dialogs.FILEPICKER_TYPE_MOVE || type === Dialogs.FILEPICKER_TYPE_COPY_MOVE) { - buttonlist.push({ - text: t('core', 'Move'), - click: moveCallback, - defaultButton: true - }) - } - } + /** + * Displays raw dialog + * You better use a wrapper instead ... + * + * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog + */ + message: function(content, title, dialogType, buttons, callback = () => {}, modal, allowHtml) { + const builder = (new DialogBuilder()) + .setName(title) + .setText(allowHtml ? '' : content) + .setButtons(Dialogs._getLegacyButtons(buttons, callback)) + + switch (dialogType) { + case 'alert': + builder.setSeverity('warning') + break + case 'notice': + builder.setSeverity('info') + break + default: + break + } - self.$filePicker.ocdialog({ - closeOnEscape: true, - // max-width of 600 - width: 600, - height: 500, - modal: modal, - buttons: buttonlist, - style: { - buttons: 'aside' - }, - close: function() { - try { - $(this).ocdialog('destroy').remove() - } catch (e) { - } - self.$filePicker = null - } - }) + const dialog = builder.build() + + if (allowHtml) { + dialog.setHTML(content) + } - // We can access primary class only from oc-dialog. - // Hence this is one of the approach to get the choose button. - var getOcDialog = self.$filePicker.closest('.oc-dialog') - var buttonEnableDisable = getOcDialog.find('.primary') - if (self.$filePicker.data('mimetype').indexOf('httpd/unix-directory') !== -1 || self.$filePicker.data('allowDirectoryChooser')) { - buttonEnableDisable.prop('disabled', false) - } else { - buttonEnableDisable.prop('disabled', true) + return dialog.show().then(() => { + if(!callback._clicked) { + callback(false) } }) - .fail(function(status, error) { - // If the method is called while navigating away - // from the page, it is probably not needed ;) - self.filepicker.loading = false - if (status !== 0) { - alert(t('core', 'Error loading file picker template: {error}', { error: error })) - } - }) }, + /** - * Displays raw dialog - * You better use a wrapper instead ... + * Helper for legacy API + * @deprecated */ - message: function(content, title, dialogType, buttons, callback, modal, allowHtml) { - return $.when(this._getMessageTemplate()).then(function($tmpl) { - var dialogName = 'oc-dialog-' + Dialogs.dialogsCounter + '-content' - var dialogId = '#' + dialogName - var $dlg = $tmpl.octemplate({ - dialog_name: dialogName, - title: title, - message: content, - type: dialogType - }, allowHtml ? { escapeFunction: '' } : {}) - if (modal === undefined) { - modal = false - } - $('body').append($dlg) - var buttonlist = [] - switch (buttons) { + _getLegacyButtons(buttons, callback) { + const buttonList = [] + + switch (typeof buttons === 'object' ? buttons.type : buttons) { case Dialogs.YES_NO_BUTTONS: - buttonlist = [{ - text: t('core', 'No'), - click: function() { - if (callback !== undefined) { - callback(false) - } - $(dialogId).ocdialog('close') - } - }, - { - text: t('core', 'Yes'), - click: function() { - if (callback !== undefined) { - callback(true) - } - $(dialogId).ocdialog('close') + buttonList.push({ + label: buttons?.cancel ?? t('core', 'No'), + callback: () => { + callback._clicked = true + callback(false) }, - defaultButton: true - }] + }) + buttonList.push({ + label: buttons?.confirm ?? t('core', 'Yes'), + type: 'primary', + callback: () => { + callback._clicked = true + callback(true) + }, + }) break - case Dialogs.OK_BUTTON: - var functionToCall = function() { - $(dialogId).ocdialog('close') - if (callback !== undefined) { - callback() - } - } - buttonlist[0] = { - text: t('core', 'OK'), - click: functionToCall, - defaultButton: true - } + case Dialogs.OK_BUTTONS: + buttonList.push({ + label: buttons?.confirm ?? t('core', 'OK'), + type: 'primary', + callback: () => { + callback._clicked = true + callback(true) + }, + }) break default: - if (typeof(buttons) === 'object') { - switch (buttons.type) { - case Dialogs.YES_NO_BUTTONS: - buttonlist = [{ - text: buttons.cancel || t('core', 'No'), - click: function() { - if (callback !== undefined) { - callback(false) - } - $(dialogId).ocdialog('close') - } - }, - { - text: buttons.confirm || t('core', 'Yes'), - click: function() { - if (callback !== undefined) { - callback(true) - } - $(dialogId).ocdialog('close') - }, - defaultButton: true, - classes: buttons.confirmClasses - }] - break - } - } + console.error('Invalid call to OC.dialogs') break - } - - $(dialogId).ocdialog({ - closeOnEscape: true, - closeCallback: () => { callback && callback(false) }, - modal: modal, - buttons: buttonlist - }) - Dialogs.dialogsCounter++ - }) - .fail(function(status, error) { - // If the method is called while navigating away from - // the page, we still want to deliver the message. - if (status === 0) { - alert(title + ': ' + content) - } else { - alert(t('core', 'Error loading message template: {error}', { error: error })) - } - }) + } + return buttonList }, + _fileexistsshown: false, /** * Displays file exists dialog @@ -680,6 +407,8 @@ const Dialogs = { * @param {object} replacement file with name, size and mtime * @param {object} controller with onCancel, onSkip, onReplace and onRename methods * @returns {Promise} jquery promise that resolves after the dialog template was loaded + * + * @deprecated 29.0.0 Use openConflictPicker from the @nextcloud/upload package instead */ fileexists: function(data, original, replacement, controller) { var self = this @@ -1038,73 +767,12 @@ const Dialogs = { // } return dialogDeferred.promise() }, - // get the gridview setting and set the input accordingly - _getGridSettings: function() { - const self = this - $.get(OC.generateUrl('/apps/files/api/v1/showgridview'), function(response) { - self.$showGridView - .removeClass('icon-toggle-filelist icon-toggle-pictures') - .addClass(response.gridview ? 'icon-toggle-filelist' : 'icon-toggle-pictures') - self.$showGridView.attr( - 'aria-label', - response.gridview ? t('files', 'Show list view') : t('files', 'Show grid view'), - ) - $('.list-container').toggleClass('view-grid', response.gridview) - }) - }, - _onGridviewChange: function() { - const isGridView = this.$showGridView.hasClass('icon-toggle-filelist') - // only save state if user is logged in - if (OC.currentUser) { - $.post(OC.generateUrl('/apps/files/api/v1/showgridview'), { show: !isGridView }) - } - this.$showGridView - .removeClass('icon-toggle-filelist icon-toggle-pictures') - .addClass(isGridView ? 'icon-toggle-pictures' : 'icon-toggle-filelist') - this.$showGridView.attr( - 'aria-label', - isGridView ? t('files', 'Show grid view') : t('files', 'Show list view'), - ) - this.$filePicker.find('.list-container').toggleClass('view-grid', !isGridView) - }, - _getFilePickerTemplate: function() { - var defer = $.Deferred() - if (!this.$filePickerTemplate) { - var self = this - $.get(OC.filePath('core', 'templates', 'filepicker.html'), function(tmpl) { - self.$filePickerTemplate = $(tmpl) - self.$listTmpl = self.$filePickerTemplate.find('.filelist tbody tr:first-child').detach() - defer.resolve(self.$filePickerTemplate) - }) - .fail(function(jqXHR, textStatus, errorThrown) { - defer.reject(jqXHR.status, errorThrown) - }) - } else { - defer.resolve(this.$filePickerTemplate) - } - return defer.promise() - }, - _getMessageTemplate: function() { - var defer = $.Deferred() - if (!this.$messageTemplate) { - var self = this - $.get(OC.filePath('core', 'templates', 'message.html'), function(tmpl) { - self.$messageTemplate = $(tmpl) - defer.resolve(self.$messageTemplate) - }) - .fail(function(jqXHR, textStatus, errorThrown) { - defer.reject(jqXHR.status, errorThrown) - }) - } else { - defer.resolve(this.$messageTemplate) - } - return defer.promise() - }, + _getFileExistsTemplate: function() { var defer = $.Deferred() if (!this.$fileexistsTemplate) { var self = this - $.get(OC.filePath('files', 'templates', 'fileexists.html'), function(tmpl) { + $.get(OC.filePath('core', 'templates/legacy', 'fileexists.html'), function(tmpl) { self.$fileexistsTemplate = $(tmpl) defer.resolve(self.$fileexistsTemplate) }) @@ -1116,270 +784,6 @@ const Dialogs = { } return defer.promise() }, - - /** - * fills the filepicker with files - */ - _fillFilePicker: async function(dir) { - var self = this - this.$filelist.empty() - this.$filePicker.find('.emptycontent').hide() - this.$filelistContainer.addClass('icon-loading') - this.$filePicker.data('path', dir) - var filter = this.$filePicker.data('mimetype') - var advancedFilter = this.$filePicker.data('filter') - if (typeof (filter) === 'string') { - filter = [filter] - } - self.$fileListHeader.find('.sort-indicator').addClass('hidden').removeClass('icon-triangle-n').removeClass('icon-triangle-s') - self.$fileListHeader.find('[data-sort=' + self.filepicker.sortField + '] .sort-indicator').removeClass('hidden') - if (self.filepicker.sortOrder === 'asc') { - self.$fileListHeader.find('[data-sort=' + self.filepicker.sortField + '] .sort-indicator').addClass('icon-triangle-n') - } else { - self.$fileListHeader.find('[data-sort=' + self.filepicker.sortField + '] .sort-indicator').addClass('icon-triangle-s') - } - - // Wrap within a method because a promise cannot return multiple values - // But the client impleemntation still does it... - var getFolderContents = async function(dir) { - return self.filepicker.filesClient.getFolderContents(dir) - .then((status, files) => { - return files - }) - } - - try { - var files = await getFolderContents(dir) - } catch (error) { - // fallback to root if requested dir is non-existent - console.error('Requested path does not exists, falling back to root') - var files = await getFolderContents('/') - this.$filePicker.data('path', '/') - } - - self.filelist = files - if (filter && filter.length > 0 && filter.indexOf('*') === -1) { - files = files.filter(function(file) { - return file.type === 'dir' || filter.indexOf(file.mimetype) !== -1 - }) - } - - if (advancedFilter) { - files = files.filter(advancedFilter) - } - - // Check if the showHidden input field exist and if it exist follow it - // Otherwise just show the hidden files - const showHiddenInput = document.getElementById('showHiddenFiles') - const showHidden = showHiddenInput === null || showHiddenInput.value === "1" - if (!showHidden) { - files = files.filter(function(file) { - return !file.name.startsWith('.') - }) - } - - var Comparators = { - name: function(fileInfo1, fileInfo2) { - if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') { - return -1 - } - if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') { - return 1 - } - return OC.Util.naturalSortCompare(fileInfo1.name, fileInfo2.name) - }, - size: function(fileInfo1, fileInfo2) { - return fileInfo1.size - fileInfo2.size - }, - mtime: function(fileInfo1, fileInfo2) { - return fileInfo1.mtime - fileInfo2.mtime - } - } - var comparator = Comparators[self.filepicker.sortField] || Comparators.name - files = files.sort(function(file1, file2) { - var isFavorite = function(fileInfo) { - return fileInfo.tags && fileInfo.tags.indexOf(OC.TAG_FAVORITE) >= 0 - } - - if (isFavorite(file1) && !isFavorite(file2)) { - return -1 - } else if (!isFavorite(file1) && isFavorite(file2)) { - return 1 - } - - return self.filepicker.sortOrder === 'asc' ? comparator(file1, file2) : -comparator(file1, file2) - }) - - self._fillSlug() - - if (files.length === 0) { - self.$filePicker.find('.emptycontent').show() - self.$fileListHeader.hide() - } else { - self.$filePicker.find('.emptycontent').hide() - self.$fileListHeader.show() - } - - self.$filelist.empty(); - - $.each(files, function(idx, entry) { - if (entry.isEncrypted && entry.mimetype === 'httpd/unix-directory') { - entry.icon = OC.MimeType.getIconUrl('dir-encrypted') - } else { - entry.icon = OC.MimeType.getIconUrl(entry.mimetype) - } - - var simpleSize, sizeColor - if (typeof (entry.size) !== 'undefined' && entry.size >= 0) { - simpleSize = OC.Util.humanFileSize(parseInt(entry.size, 10), true) - sizeColor = Math.round(160 - Math.pow((entry.size / (1024 * 1024)), 2)) - } else { - simpleSize = t('files', 'Pending') - sizeColor = 80 - } - - // split the filename in half if the size is bigger than 20 char - // for ellipsis - if (entry.name.length >= 10) { - // leave maximum 10 letters - var split = Math.min(Math.floor(entry.name.length / 2), 10) - var filename1 = entry.name.substr(0, entry.name.length - split) - var filename2 = entry.name.substr(entry.name.length - split) - } else { - var filename1 = entry.name - var filename2 = '' - } - - var $row = self.$listTmpl.octemplate({ - type: entry.type, - dir: dir, - filename: entry.name, - filename1: filename1, - filename2: filename2, - date: OC.Util.relativeModifiedDate(entry.mtime), - size: simpleSize, - sizeColor: sizeColor, - icon: entry.icon - }) - if (entry.type === 'file') { - var urlSpec = { - file: dir + '/' + entry.name, - x: 100, - y: 100 - } - var img = new Image() - var previewUrl = OC.generateUrl('/core/preview.png?') + $.param(urlSpec) - img.onload = function() { - if (img.width > 5) { - $row.find('td.filename').attr('style', 'background-image:url(' + previewUrl + ')') - } - } - img.src = previewUrl - } - self.$filelist.append($row) - }) - - self.$filelistContainer.removeClass('icon-loading') - }, - /** - * fills the tree list with directories - */ - _fillSlug: function() { - var addButton = this.$dirTree.find('.actions.creatable').detach() - this.$dirTree.empty() - var self = this - - self.$dirTree.append(addButton) - - var dir - var path = this.$filePicker.data('path') - var $template = $('<div data-dir="{dir}" tabindex="0"><a>{name}</a></div>').addClass('crumb') - if (path) { - var paths = path.split('/') - $.each(paths, function(index, dir) { - dir = paths.pop() - if (dir === '') { - return false - } - self.$dirTree.prepend($template.octemplate({ - dir: paths.join('/') + '/' + dir, - name: dir - })) - }) - } - - $template.octemplate({ - dir: '', - name: '' // Ugly but works ;) - }, { escapeFunction: null }).prependTo(this.$dirTree) - - }, - /** - * handle selection made in the tree list - */ - _handleTreeListSelect: function(event, type) { - var self = event.data - var dir = $(event.target).closest('.crumb').data('dir') - self._fillFilePicker(dir) - var getOcDialog = (event.target).closest('.oc-dialog') - var buttonEnableDisable = $('.primary', getOcDialog) - this._changeButtonsText(type, dir.split(/[/]+/).pop()) - if (this.$filePicker.data('mimetype').indexOf('httpd/unix-directory') !== -1 || this.$filePicker.data('allowDirectoryChooser')) { - buttonEnableDisable.prop('disabled', false) - } else { - buttonEnableDisable.prop('disabled', true) - } - }, - /** - * handle clicks made in the filepicker - */ - _handlePickerClick: function(event, $element, type) { - var getOcDialog = this.$filePicker.closest('.oc-dialog') - var buttonEnableDisable = getOcDialog.find('.primary') - if ($element.data('type') === 'file') { - if (this.$filePicker.data('multiselect') !== true || !event.ctrlKey) { - this.$filelist.find('.filepicker_element_selected').removeClass('filepicker_element_selected') - } - $element.toggleClass('filepicker_element_selected') - buttonEnableDisable.prop('disabled', false) - } else if ($element.data('type') === 'dir') { - this._fillFilePicker(this.$filePicker.data('path') + '/' + $element.data('entryname')) - this._changeButtonsText(type, $element.data('entryname')) - if (this.$filePicker.data('mimetype').indexOf('httpd/unix-directory') !== -1 || this.$filePicker.data('allowDirectoryChooser')) { - buttonEnableDisable.prop('disabled', false) - } else { - buttonEnableDisable.prop('disabled', true) - } - } - }, - - /** - * Handle - * @param type of action - * @param dir on which to change buttons text - * @private - */ - _changeButtonsText: function(type, dir) { - var copyText = dir === '' ? t('core', 'Copy') : t('core', 'Copy to {folder}', { folder: dir }) - var moveText = dir === '' ? t('core', 'Move') : t('core', 'Move to {folder}', { folder: dir }) - var buttons = $('.oc-dialog-buttonrow button') - switch (type) { - case this.FILEPICKER_TYPE_CHOOSE: - break - case this.FILEPICKER_TYPE_CUSTOM: - break - case this.FILEPICKER_TYPE_COPY: - buttons.text(copyText) - break - case this.FILEPICKER_TYPE_MOVE: - buttons.text(moveText) - break - case this.FILEPICKER_TYPE_COPY_MOVE: - buttons.eq(0).text(copyText) - buttons.eq(1).text(moveText) - break - } - } } export default Dialogs |