feat: Add new Vue FilePicker from @nextcloud/dialogs and use it by default.

Still providing the legacy one until the Vue FilePicker is out of beta.
Pin beta releases so we do not get version conflicts.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/39792/head
Ferdinand Thiessen 9 months ago
parent efeb517edd
commit 3d74ed85ff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -8,5 +8,5 @@
@import 'mobile.scss';
@import 'tooltip.scss';
// If you include .css, it will be imported as url
@import '../../node_modules/@nextcloud/dialogs/dist/index';
@import '../../node_modules/@nextcloud/dialogs/dist/style';
@import 'public.scss';

@ -48,8 +48,7 @@ import _ from 'underscore'
import $ from 'jquery'
import OC from './index.js'
import OCA from '../OCA/index.js'
import { isA11yActivation } from '../Util/a11y.js'
import { FilePickerVue, FilePickerType, spawnDialog } from '@nextcloud/dialogs'
/**
* this class to ease the usage of jquery dialogs
@ -59,10 +58,15 @@ const Dialogs = {
YES_NO_BUTTONS: 70,
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
@ -226,8 +230,11 @@ const Dialogs = {
Dialogs.dialogsCounter++
})
},
/**
* 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,339 +244,109 @@ 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
}
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)
}
if (modal === undefined) {
modal = false
}
if (multiselect === undefined) {
multiselect = false
}
filepicker(title, callback, multiselect = false, mimetype = undefined, _modal = undefined, type = FilePickerType.Choose, path = undefined, options = undefined) {
$(options?.target ?? 'body').prepend(self.$filePicker)
self.$showGridView = $('button#picker-showgridview')
self.$showGridView.on('click keydown', function(event) {
if (isA11yActivation(event)) {
self._onGridviewChange()
}
})
self._getGridSettings()
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')
})
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()
})
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})
/**
* 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 true
return path
}
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'
})
$input.tooltip('_fixTitle')
$input.tooltip('show')
$input.addClass('error')
}
return false
if (multiselect) {
return (nodes) => fn(nodes.map(getPath), type)
} else {
return (nodes) => fn(getPath(nodes[0]), type)
}
}
$form.on('submit', function(event) {
event.stopPropagation()
event.preventDefault()
/**
* 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,
})
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, type)
})
OC.hideMenus()
self.$filePicker.ocdialog('unsetEnterCallback')
self.$filePicker.click()
$input.val(newText)
}
const buttons = []
if (type === FilePickerType.Choose) {
buttons.push({
label: t('core', 'Choose'),
type: 'primary',
callback: legacyCallback(callback, FilePickerType.Choose),
})
$input.on('input', function(event) {
$input.tooltip('hide')
} else if (type === FilePickerType.Copy || type === FilePickerType.CopyMove) {
buttons.push({
label: t('core', 'Copy'),
callback: legacyCallback(callback, FilePickerType.Copy),
})
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', '.crumb', 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, type)
}
}
if (type === FilePickerType.CopyMove || type === FilePickerType.Move) {
buttons.push({
label: t('core', 'Move'),
type: 'primary',
callback: legacyCallback(callback, FilePickerType.Move),
})
}
if (type === FilePickerType.Custom) {
(options.buttons || []).forEach((button) => {
buttons.push({
callback: legacyCallback(callback, button.type),
label: button.text,
type: button.defaultButton ? 'primary' : 'secondary',
})
self._fillFilePicker(path, type)
})
}
// 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)
}
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
})
}
}
const filter = {}
if (typeof options?.filter === 'function') {
filter.filterFn = (node) => options.filter(nodeToLegacyFile(node))
}
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 mimetypeFilter = typeof mimetype === 'string' ? [mimetype] : (mimetype || [])
// 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)
}
spawnDialog(FilePickerVue, {
...filter,
name: title,
buttons,
multiselect,
path,
mimetypeFilter,
allowPickDirectory: options?.allowDirectoryChooser === true || mimetypeFilter.includes('httpd/unix-directory'),
})
.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 ...
@ -1038,52 +815,7 @@ 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) {
@ -1116,274 +848,6 @@ const Dialogs = {
}
return defer.promise()
},
/**
* fills the filepicker with files
*/
_fillFilePicker: async function(dir, type) {
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', '/')
this._changeButtonsText(type, '')
}
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')
if (showHiddenInput?.value !== "1") {
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('<nav></nav>')
self.$dirTree.append(addButton)
var dir
var path = this.$filePicker.data('path')
var $template = $('<li data-dir="{dir}" tabindex="0"><a class="{classList}">{name}</a></li>').addClass('crumb')
var $breadcrumbs = $('<ul class="breadcrumb"></ul>')
if (path) {
var paths = path.split('/')
$.each(paths, function(index, dir) {
dir = paths.pop()
if (dir === '') {
return false
}
$breadcrumbs.prepend($template.octemplate({
dir: paths.join('/') + '/' + dir,
name: dir
}))
})
}
$template.octemplate({
dir: '',
name: t('core', 'Home'),
classList: 'icon-home'
}, { escapeFunction: null }).addClass('crumb svg crumbhome').prependTo($breadcrumbs)
this.$dirTree.find('> nav').prepend($breadcrumbs)
},
/**
* 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, type)
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'), type)
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

@ -35,6 +35,10 @@ import './globals.js'
import './jquery/index.js'
import { initCore } from './init.js'
import { registerAppsSlideToggle } from './OC/apps.js'
import { getRequestToken } from '@nextcloud/auth'
// eslint-disable-next-line camelcase
__webpack_nonce__ = btoa(getRequestToken())
window.addEventListener('DOMContentLoaded', function() {
initCore()

@ -1,61 +0,0 @@
<div id="{dialog_name}" title="{title}">
<span class="dirtree">
<nav></nav>
<span class="actions creatable"><a href="#" class="icon icon-add button button-add" aria-label="{newtext}"></a>
<nav class="menu popovermenu bubble menu-left newFolderMenu">
<ul><li>
<form class="filenameform">
<input type="text" value={newtext}>
<input class="icon-confirm" type="submit" value="">
</form>
</li></ul>
</nav>
</span>
</span>
<button id="picker-showgridview" class="icon-toggle-pictures"></button>
<div class="filelist-container">
<div class="emptycontent">
<div class="icon-folder"></div>
<h2>{emptytext}</h2>
</div>
<table id="picker-filestable" class="filelist list-container view-grid">
<thead>
<tr>
<th class="column-name">
<div id="column-name-container">
<a class="name sort columntitle" data-sort="name" tabindex="0">
<span>{nameCol}</span>
<span class="sort-indicator hidden icon-triangle-n"></span>
</a>
</div>
</th>
<th class="column-size">
<a class="size sort columntitle" data-sort="size" tabindex="0">
<span>{sizeCol}</span>
<span class="sort-indicator hidden icon-triangle-n"></span></a>
</th>
<th class="column-mtime">
<a id="modified" class="columntitle" data-sort="mtime" tabindex="0">
<span>{modifiedCol}</span>
<span class="sort-indicator hidden icon-triangle-n"></span></a>
</th>
</tr>
</thead>
<tbody>
<tr data-entryname="{filename}" data-type="{type}" tabindex="0">
<td class="filename"
style="background-image:url({icon})">
<span class="filename-parts">
<span class="filename-parts__first">{filename1}</span>
<span class="filename-parts__last">{filename2}</span>
</span>
</td>
<td class="filesize">{size}</td>
<td class="date">{date}</td>
</tr>
</tbody>
</table>
</div>
</div>

291
package-lock.json generated

@ -17,9 +17,9 @@
"@nextcloud/browserslist-config": "^2.3.0",
"@nextcloud/calendar-availability-vue": "^2.0.0-beta.1",
"@nextcloud/capabilities": "^1.0.4",
"@nextcloud/dialogs": "^4.1.0",
"@nextcloud/dialogs": "^5.0.0-beta.2",
"@nextcloud/event-bus": "^3.1.0",
"@nextcloud/files": "^3.0.0-beta.18",
"@nextcloud/files": "3.0.0-beta.18",
"@nextcloud/initial-state": "^2.0.0",
"@nextcloud/l10n": "^2.1.0",
"@nextcloud/logger": "^2.5.0",
@ -28,7 +28,7 @@
"@nextcloud/paths": "^2.1.0",
"@nextcloud/router": "^2.1.2",
"@nextcloud/sharing": "^0.1.0",
"@nextcloud/vue": "^8.0.0-beta.2",
"@nextcloud/vue": "8.0.0-beta.2",
"@nextcloud/vue-dashboard": "^2.0.1",
"@skjnldsv/sanitize-svg": "^1.0.2",
"@vueuse/components": "^10.2.0",
@ -3571,6 +3571,31 @@
}
},
"node_modules/@nextcloud/dialogs": {
"version": "5.0.0-beta.2",
"resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-5.0.0-beta.2.tgz",
"integrity": "sha512-IRf5iOzr0ZJnysdeBd6Q3tNfF7CgHFcsRCKBv/gayLCZQpV/kC4K8dg9ETtGqd/YNkfojqcOrEyRDzOy/sRE/w==",
"dependencies": {
"@mdi/svg": "^7.2.96",
"@nextcloud/files": "^3.0.0-beta.16",
"@nextcloud/l10n": "^2.2.0",
"@nextcloud/typings": "^1.7.0",
"@nextcloud/vue": "^8.0.0-beta.3",
"@types/toastify-js": "^1.12.0",
"@vueuse/core": "^10.3.0",
"toastify-js": "^1.12.0",
"vue-frag": "^1.4.3",
"vue-material-design-icons": "^5.2.0",
"webdav": "^5.2.3"
},
"engines": {
"node": "^20.0.0",
"npm": "^9.0.0"
},
"peerDependencies": {
"vue": "^2.7.14"
}
},
"node_modules/@nextcloud/dialogs/node_modules/@nextcloud/dialogs": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-4.1.0.tgz",
"integrity": "sha512-7e0QMdJKL1Pn/RxOA6Fjm2PMSEUSvhRXuyoZqNFN/rvvVK9mXOCvkRI+vYwuCBCzoTi1Bv3k12BoXxB2UHAufQ==",
@ -3585,6 +3610,183 @@
"npm": "^9.0.0"
}
},
"node_modules/@nextcloud/dialogs/node_modules/@nextcloud/vue": {
"version": "8.0.0-beta.4",
"resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-8.0.0-beta.4.tgz",
"integrity": "sha512-Kd5zGnfLVO05y8QytpUH9D3CMQACARty+guA548koq4cG5ad3myKGPNtP8qxFaUvzmTSYf57gGAbAbHX1eDPxg==",
"dependencies": {
"@floating-ui/dom": "^1.1.0",
"@nextcloud/auth": "^2.0.0",
"@nextcloud/axios": "^2.0.0",
"@nextcloud/browser-storage": "^0.2.0",
"@nextcloud/calendar-js": "^6.0.0",
"@nextcloud/capabilities": "^1.0.4",
"@nextcloud/dialogs": "^4.0.0",
"@nextcloud/event-bus": "^3.0.0",
"@nextcloud/initial-state": "^2.0.0",
"@nextcloud/l10n": "^2.0.1",
"@nextcloud/logger": "^2.2.1",
"@nextcloud/router": "^2.0.0",
"@nextcloud/vue-select": "^3.23.0",
"@skjnldsv/sanitize-svg": "^1.0.2",
"@vueuse/components": "^10.0.2",
"@vueuse/core": "^10.1.2",
"clone": "^2.1.2",
"debounce": "1.2.1",
"emoji-mart-vue-fast": "^15.0.0",
"escape-html": "^1.0.3",
"floating-vue": "^1.0.0-beta.19",
"focus-trap": "^7.4.3",
"linkify-string": "^4.0.0",
"md5": "^2.3.0",
"node-polyfill-webpack-plugin": "^2.0.1",
"rehype-react": "^7.1.2",
"remark-breaks": "^3.0.2",
"remark-external-links": "^9.0.1",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",
"splitpanes": "^2.4.1",
"string-length": "^5.0.1",
"striptags": "^3.2.0",
"tributejs": "^5.1.3",
"unified": "^11.0.1",
"unist-builder": "^4.0.0",
"unist-util-visit": "^5.0.0",
"vue": "^2.7.14",
"vue-color": "^2.8.1",
"vue-frag": "^1.4.3",
"vue-material-design-icons": "^5.1.2",
"vue2-datepicker": "^3.11.0"
},
"engines": {
"node": "^20.0.0",
"npm": "^9.0.0"
}
},
"node_modules/@nextcloud/dialogs/node_modules/@types/unist": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz",
"integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w=="
},
"node_modules/@nextcloud/dialogs/node_modules/is-plain-obj": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@nextcloud/dialogs/node_modules/unified": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.2.tgz",
"integrity": "sha512-Zta++onvS/dJ6xUvXQOR5q8XJZOkiMCE5wQ8Yv9mLR25pxRS567EX0GO6HZRxxNV/lznwfsvRZ/1pqe9K9QLeQ==",
"dependencies": {
"@types/unist": "^3.0.0",
"@ungap/structured-clone": "^1.0.0",
"bail": "^2.0.0",
"devlop": "^1.0.0",
"is-plain-obj": "^4.0.0",
"trough": "^2.0.0",
"vfile": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/@nextcloud/dialogs/node_modules/unist-builder": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-4.0.0.tgz",
"integrity": "sha512-wmRFnH+BLpZnTKpc5L7O67Kac89s9HMrtELpnNaE6TAobq5DTZZs5YaTQfAZBA9bFPECx2uVAPO31c+GVug8mg==",
"dependencies": {
"@types/unist": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/@nextcloud/dialogs/node_modules/unist-util-is": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
"integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
"dependencies": {
"@types/unist": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/@nextcloud/dialogs/node_modules/unist-util-stringify-position": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
"integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
"dependencies": {
"@types/unist": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/@nextcloud/dialogs/node_modules/unist-util-visit": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
"integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
"dependencies": {
"@types/unist": "^3.0.0",
"unist-util-is": "^6.0.0",
"unist-util-visit-parents": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/@nextcloud/dialogs/node_modules/unist-util-visit-parents": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
"integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
"dependencies": {
"@types/unist": "^3.0.0",
"unist-util-is": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/@nextcloud/dialogs/node_modules/vfile": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz",
"integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==",
"dependencies": {
"@types/unist": "^3.0.0",
"unist-util-stringify-position": "^4.0.0",
"vfile-message": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/@nextcloud/dialogs/node_modules/vfile-message": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
"integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
"dependencies": {
"@types/unist": "^3.0.0",
"unist-util-stringify-position": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/@nextcloud/eslint-config": {
"version": "8.3.0-beta.2",
"resolved": "https://registry.npmjs.org/@nextcloud/eslint-config/-/eslint-config-8.3.0-beta.2.tgz",
@ -3761,6 +3963,37 @@
"npm": "^7.0.0 || ^8.0.0"
}
},
"node_modules/@nextcloud/password-confirmation/node_modules/@nextcloud/dialogs": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-4.1.0.tgz",
"integrity": "sha512-7e0QMdJKL1Pn/RxOA6Fjm2PMSEUSvhRXuyoZqNFN/rvvVK9mXOCvkRI+vYwuCBCzoTi1Bv3k12BoXxB2UHAufQ==",
"dependencies": {
"@nextcloud/l10n": "^2.1.0",
"@nextcloud/typings": "^1.7.0",
"core-js": "^3.31.0",
"toastify-js": "^1.12.0"
},
"engines": {
"node": "^20.0.0",
"npm": "^9.0.0"
}
},
"node_modules/@nextcloud/password-confirmation/node_modules/@nextcloud/dialogs/node_modules/@nextcloud/l10n": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-2.2.0.tgz",
"integrity": "sha512-UAM2NJcl/NR46MANSF7Gr7q8/Up672zRyGrxLpN3k4URNmWQM9upkbRME+1K3T29wPrUyOIbQu710ZjvZafqFA==",
"dependencies": {
"@nextcloud/router": "^2.1.2",
"@nextcloud/typings": "^1.7.0",
"dompurify": "^3.0.3",
"escape-html": "^1.0.3",
"node-gettext": "^3.0.0"
},
"engines": {
"node": "^20.0.0",
"npm": "^9.0.0"
}
},
"node_modules/@nextcloud/password-confirmation/node_modules/@nextcloud/l10n": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.6.0.tgz",
@ -4252,6 +4485,21 @@
"vue": "2.x"
}
},
"node_modules/@nextcloud/vue/node_modules/@nextcloud/dialogs": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-4.1.0.tgz",
"integrity": "sha512-7e0QMdJKL1Pn/RxOA6Fjm2PMSEUSvhRXuyoZqNFN/rvvVK9mXOCvkRI+vYwuCBCzoTi1Bv3k12BoXxB2UHAufQ==",
"dependencies": {
"@nextcloud/l10n": "^2.1.0",
"@nextcloud/typings": "^1.7.0",
"core-js": "^3.31.0",
"toastify-js": "^1.12.0"
},
"engines": {
"node": "^20.0.0",
"npm": "^9.0.0"
}
},
"node_modules/@nextcloud/webpack-vue-config": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@nextcloud/webpack-vue-config/-/webpack-vue-config-6.0.0.tgz",
@ -5456,6 +5704,11 @@
"@types/jest": "*"
}
},
"node_modules/@types/toastify-js": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@types/toastify-js/-/toastify-js-1.12.0.tgz",
"integrity": "sha512-fqpDHaKhFukN9KRm24bbH0wozvHmSwjvkaLjBUrWcSfSS4zysIwTYqNLG3XbSNhRlsTNRNLGS23tp/VhPwsfHQ=="
},
"node_modules/@types/tough-cookie": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
@ -5828,6 +6081,11 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
},
"node_modules/@vue/compiler-sfc": {
"version": "2.7.14",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz",
@ -9848,6 +10106,18 @@
"dev": true,
"peer": true
},
"node_modules/devlop": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
"dependencies": {
"dequal": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/devtools-protocol": {
"version": "0.0.1147663",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz",
@ -18753,6 +19023,21 @@
"vue": "^2.7.14"
}
},
"node_modules/nextcloud-vue-collections/node_modules/@nextcloud/dialogs": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-4.1.0.tgz",
"integrity": "sha512-7e0QMdJKL1Pn/RxOA6Fjm2PMSEUSvhRXuyoZqNFN/rvvVK9mXOCvkRI+vYwuCBCzoTi1Bv3k12BoXxB2UHAufQ==",
"dependencies": {
"@nextcloud/l10n": "^2.1.0",
"@nextcloud/typings": "^1.7.0",
"core-js": "^3.31.0",
"toastify-js": "^1.12.0"
},
"engines": {
"node": "^20.0.0",
"npm": "^9.0.0"
}
},
"node_modules/nextcloud-vue-collections/node_modules/@nextcloud/vue": {
"version": "7.12.2",
"resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-7.12.2.tgz",

@ -43,9 +43,9 @@
"@nextcloud/browserslist-config": "^2.3.0",
"@nextcloud/calendar-availability-vue": "^2.0.0-beta.1",
"@nextcloud/capabilities": "^1.0.4",
"@nextcloud/dialogs": "^4.1.0",
"@nextcloud/dialogs": "^5.0.0-beta.2",
"@nextcloud/event-bus": "^3.1.0",
"@nextcloud/files": "^3.0.0-beta.18",
"@nextcloud/files": "3.0.0-beta.18",
"@nextcloud/initial-state": "^2.0.0",
"@nextcloud/l10n": "^2.1.0",
"@nextcloud/logger": "^2.5.0",
@ -54,7 +54,7 @@
"@nextcloud/paths": "^2.1.0",
"@nextcloud/router": "^2.1.2",
"@nextcloud/sharing": "^0.1.0",
"@nextcloud/vue": "^8.0.0-beta.2",
"@nextcloud/vue": "8.0.0-beta.2",
"@nextcloud/vue-dashboard": "^2.0.1",
"@skjnldsv/sanitize-svg": "^1.0.2",
"@vueuse/components": "^10.2.0",

Loading…
Cancel
Save