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>
This commit is contained in:
Ferdinand Thiessen 2023-08-10 14:25:46 +02:00
parent efeb517edd
commit 3d74ed85ff
8 changed files with 397 additions and 705 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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';

View file

@ -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
filepicker(title, callback, multiselect = false, mimetype = undefined, _modal = undefined, type = FilePickerType.Choose, path = undefined, options = undefined) {
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
}
$(options?.target ?? 'body').prepend(self.$filePicker)
self.$showGridView = $('button#picker-showgridview')
self.$showGridView.on('click keydown', function(event) {
if (isA11yActivation(event)) {
self._onGridviewChange()
/**
* 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) || '/'
}
})
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})
}
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
}
$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, type)
})
OC.hideMenus()
self.$filePicker.ocdialog('unsetEnterCallback')
self.$filePicker.click()
$input.val(newText)
}
})
$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', '.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)
}
})
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
})
})
if (multiselect) {
return (nodes) => fn(nodes.map(getPath), type)
} 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
})
}
return (nodes) => fn(getPath(nodes[0]), type)
}
}
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
}
})
// 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)
}
/**
* 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,
})
.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 }))
}
const buttons = []
if (type === FilePickerType.Choose) {
buttons.push({
label: t('core', 'Choose'),
type: 'primary',
callback: legacyCallback(callback, FilePickerType.Choose),
})
} else if (type === FilePickerType.Copy || type === FilePickerType.CopyMove) {
buttons.push({
label: t('core', 'Copy'),
callback: legacyCallback(callback, FilePickerType.Copy),
})
}
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',
})
})
}
const filter = {}
if (typeof options?.filter === 'function') {
filter.filterFn = (node) => options.filter(nodeToLegacyFile(node))
}
const mimetypeFilter = typeof mimetype === 'string' ? [mimetype] : (mimetype || [])
spawnDialog(FilePickerVue, {
...filter,
name: title,
buttons,
multiselect,
path,
mimetypeFilter,
allowPickDirectory: options?.allowDirectoryChooser === true || mimetypeFilter.includes('httpd/unix-directory'),
})
},
/**
* 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

View file

@ -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()

View file

@ -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
View file

@ -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",

View file

@ -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",