diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-06-22 13:56:30 +0200 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-06-24 20:03:09 +0200 |
commit | 079d026d39565e7b5547741bd62bd69082347ad6 (patch) | |
tree | 5a06c2d6acd420f69bad26eebfb7d0129a59a4f6 | |
parent | 879eaa7681b83efb1ed7de2ec8abb6ac3c62ccdd (diff) | |
download | nextcloud-server-079d026d39565e7b5547741bd62bd69082347ad6.tar.gz nextcloud-server-079d026d39565e7b5547741bd62bd69082347ad6.zip |
feat(files): Allow uploading directories
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r-- | __mocks__/@nextcloud/axios.ts | 8 | ||||
-rw-r--r-- | apps/files/src/views/FilesList.vue | 45 | ||||
-rw-r--r-- | package-lock.json | 188 | ||||
-rw-r--r-- | package.json | 10 |
4 files changed, 162 insertions, 89 deletions
diff --git a/__mocks__/@nextcloud/axios.ts b/__mocks__/@nextcloud/axios.ts index cef18f20df4..5133574d9ed 100644 --- a/__mocks__/@nextcloud/axios.ts +++ b/__mocks__/@nextcloud/axios.ts @@ -3,6 +3,14 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ export default { + interceptors: { + response: { + use: () => {}, + }, + request: { + use: () => {}, + }, + }, get: async () => ({ status: 200, data: {} }), delete: async () => ({ status: 200, data: {} }), post: async () => ({ status: 200, data: {} }), diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index fabf1cae6b9..8ba5a85ddac 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -37,10 +37,12 @@ <!-- Uploader --> <UploadPicker v-else-if="currentFolder" - :content="dirContents" - :destination="currentFolder" - :multiple="true" + allow-folders class="files-list__header-upload-button" + :content="getContent" + :destination="currentFolder" + :forbidden-characters="forbiddenCharacters" + multiple @failed="onUploadFail" @uploaded="onUpload" /> </template> @@ -79,9 +81,11 @@ <template v-if="dir !== '/'" #action> <!-- Uploader --> <UploadPicker v-if="currentFolder && canUpload && !isQuotaExceeded" - :content="dirContents" - :destination="currentFolder" + allow-folders class="files-list__header-upload-button" + :content="getContent" + :destination="currentFolder" + :forbidden-characters="forbiddenCharacters" multiple @failed="onUploadFail" @uploaded="onUpload" /> @@ -118,10 +122,10 @@ import { getCapabilities } from '@nextcloud/capabilities' import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus' import { Folder, Node, Permission } from '@nextcloud/files' import { translate as t } from '@nextcloud/l10n' -import { join, dirname } from 'path' -import { showError } from '@nextcloud/dialogs' +import { join, dirname, normalize } from 'path' +import { showError, showWarning } from '@nextcloud/dialogs' import { Type } from '@nextcloud/sharing' -import { UploadPicker } from '@nextcloud/upload' +import { UploadPicker, UploadStatus } from '@nextcloud/upload' import { loadState } from '@nextcloud/initial-state' import { defineComponent } from 'vue' @@ -190,6 +194,7 @@ export default defineComponent({ const { currentView } = useNavigation() const enableGridView = (loadState('core', 'config', [])['enable_non-accessible_features'] ?? true) + const forbiddenCharacters = loadState<string[]>('files', 'forbiddenCharacters', []) return { currentView, @@ -200,9 +205,10 @@ export default defineComponent({ uploaderStore, userConfigStore, viewConfigStore, - enableGridView, // non reactive data + enableGridView, + forbiddenCharacters, Type, } }, @@ -228,6 +234,19 @@ export default defineComponent({ }, 500) }, + /** + * Get a callback function for the uploader to fetch directory contents for conflict resolution + */ + getContent() { + const view = this.currentView + return async (path?: string) => { + // as the path is allowed to be undefined we need to normalize the path ('//' to '/') + const normalizedPath = normalize(`${this.currentFolder?.path ?? ''}/${path ?? ''}`) + // use the current view to fetch the content for the requested path + return (await view.getContents(normalizedPath)).contents + } + }, + userConfig(): UserConfig { return this.userConfigStore.userConfig }, @@ -590,8 +609,7 @@ export default defineComponent({ onUpload(upload: Upload) { // Let's only refresh the current Folder // Navigating to a different folder will refresh it anyway - const destinationSource = dirname(upload.source) - const needsRefresh = destinationSource === this.currentFolder?.source + const needsRefresh = dirname(upload.source) === this.currentFolder!.source // TODO: fetch uploaded files data only // Use parseInt(upload.response?.headers?.['oc-fileid']) to get the fileid @@ -604,6 +622,11 @@ export default defineComponent({ async onUploadFail(upload: Upload) { const status = upload.response?.status || 0 + if (upload.status === UploadStatus.CANCELLED) { + showWarning(t('files', 'Upload was cancelled by user')) + return + } + // Check known status codes if (status === 507) { showError(t('files', 'Not enough free space')) diff --git a/package-lock.json b/package-lock.json index 69d17a2f601..c159cb2fc65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,13 +29,13 @@ "@nextcloud/paths": "^2.1.0", "@nextcloud/router": "^3.0.0", "@nextcloud/sharing": "^0.1.0", - "@nextcloud/upload": "^1.1.1", + "@nextcloud/upload": "^1.4.1", "@nextcloud/vue": "^8.11.2", "@simplewebauthn/browser": "^10.0.0", "@skjnldsv/sanitize-svg": "^1.0.2", - "@vueuse/components": "^10.9.0", - "@vueuse/core": "^10.9.0", - "@vueuse/integrations": "^10.9.0", + "@vueuse/components": "^10.11.0", + "@vueuse/core": "^10.11.0", + "@vueuse/integrations": "^10.11.0", "backbone": "^1.4.1", "blueimp-md5": "^2.19.0", "browserslist-useragent-regexp": "^4.1.1", @@ -85,7 +85,7 @@ "vuedraggable": "^2.24.3", "vuex": "^3.6.2", "vuex-router-sync": "^5.0.0", - "webdav": "^5.5.0" + "webdav": "^5.6.0" }, "devDependencies": { "@babel/node": "^7.22.10", @@ -4632,20 +4632,21 @@ } }, "node_modules/@nextcloud/upload": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@nextcloud/upload/-/upload-1.1.1.tgz", - "integrity": "sha512-cMEIL3dJtAJVdyQnEqVpBrNlH736KxepXykZZCRKWB5dNxoK3mChMV96a/bCGceR2bdCR9mOY9VQKn2CweA5cA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@nextcloud/upload/-/upload-1.4.1.tgz", + "integrity": "sha512-5Y/Vred7hz/RJv5ftC206H0BXVgvQqNnXMOs+J3hTslNN0WU4ku3m0g4qlyg+zaDjThBsXIPJA1dpWuRuV8rHA==", "dependencies": { "@nextcloud/auth": "^2.2.1", - "@nextcloud/axios": "^2.4.0", + "@nextcloud/axios": "^2.5.0", "@nextcloud/dialogs": "^5.2.0", - "@nextcloud/files": "^3.1.1", - "@nextcloud/l10n": "^2.2.0", - "@nextcloud/logger": "^2.7.0", + "@nextcloud/files": "^3.5.1", + "@nextcloud/l10n": "^3.1.0", + "@nextcloud/logger": "^3.0.2", "@nextcloud/paths": "^2.1.0", "@nextcloud/router": "^3.0.0", - "axios": "^1.6.8", - "buffer": "^6.0.3", + "@nextcloud/sharing": "^0.2.2", + "axios": "^1.7.2", + "axios-retry": "^4.4.0", "crypto-browserify": "^3.12.0", "p-cancelable": "^4.0.1", "p-queue": "^8.0.0", @@ -4660,17 +4661,34 @@ "vue": "^2.7.16" } }, - "node_modules/@nextcloud/upload/node_modules/@nextcloud/logger": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@nextcloud/logger/-/logger-2.7.0.tgz", - "integrity": "sha512-DSJg9H1jT2zfr7uoP4tL5hKncyY+LOuxJzLauj0M/f6gnpoXU5WG1Zw8EFPOrRWjkC0ZE+NCqrMnITgdRRpXJQ==", + "node_modules/@nextcloud/upload/node_modules/@nextcloud/l10n": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-3.1.0.tgz", + "integrity": "sha512-unciqr8QSJ29vFBw9S1bquyoj1PTWHszNL8tcUNuxUAYpq0hX+8o7rpB5gimELA4sj4m9+VCJwgLtBZd1Yj0lg==", "dependencies": { - "@nextcloud/auth": "^2.0.0", - "core-js": "^3.6.4" + "@nextcloud/router": "^3.0.1", + "@nextcloud/typings": "^1.8.0", + "@types/dompurify": "^3.0.5", + "@types/escape-html": "^1.0.4", + "dompurify": "^3.1.2", + "escape-html": "^1.0.3", + "node-gettext": "^3.0.0" }, "engines": { "node": "^20.0.0", - "npm": "^9.0.0" + "npm": "^10.0.0" + } + }, + "node_modules/@nextcloud/upload/node_modules/@nextcloud/sharing": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@nextcloud/sharing/-/sharing-0.2.2.tgz", + "integrity": "sha512-ui0ZoVazroA+cF4+homhFSFAddd/P4uRYMfG3rw3QR8o6igrVFe0f0l21kYtUwXU0oC0K4v3k8j93zCTfz6v3g==", + "dependencies": { + "@nextcloud/initial-state": "^2.2.0" + }, + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" } }, "node_modules/@nextcloud/upload/node_modules/eventemitter3": { @@ -6597,19 +6615,19 @@ } }, "node_modules/@vueuse/components": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.9.0.tgz", - "integrity": "sha512-BHQpA0yIi3y7zKa1gYD0FUzLLkcRTqVhP8smnvsCK6GFpd94Nziq1XVPD7YpFeho0k5BzbBiNZF7V/DpkJ967A==", + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.11.0.tgz", + "integrity": "sha512-ZvLZI23d5ZAtva5fGyYh/jQtZO8l+zJ5tAXyYNqHJZkq1o5yWyqZhENvSv5mfDmN5IuAOp4tq02mRmX/ipFGcg==", "dependencies": { - "@vueuse/core": "10.9.0", - "@vueuse/shared": "10.9.0", - "vue-demi": ">=0.14.7" + "@vueuse/core": "10.11.0", + "@vueuse/shared": "10.11.0", + "vue-demi": ">=0.14.8" } }, "node_modules/@vueuse/components/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -6632,23 +6650,23 @@ } }, "node_modules/@vueuse/core": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz", - "integrity": "sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==", + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.0.tgz", + "integrity": "sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==", "dependencies": { "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.9.0", - "@vueuse/shared": "10.9.0", - "vue-demi": ">=0.14.7" + "@vueuse/metadata": "10.11.0", + "@vueuse/shared": "10.11.0", + "vue-demi": ">=0.14.8" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -6671,30 +6689,30 @@ } }, "node_modules/@vueuse/integrations": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.9.0.tgz", - "integrity": "sha512-acK+A01AYdWSvL4BZmCoJAcyHJ6EqhmkQEXbQLwev1MY7NBnS+hcEMx/BzVoR9zKI+UqEPMD9u6PsyAuiTRT4Q==", + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.11.0.tgz", + "integrity": "sha512-Pp6MtWEIr+NDOccWd8j59Kpjy5YDXogXI61Kb1JxvSfVBO8NzFQkmrKmSZz47i+ZqHnIzxaT38L358yDHTncZg==", "dependencies": { - "@vueuse/core": "10.9.0", - "@vueuse/shared": "10.9.0", - "vue-demi": ">=0.14.7" + "@vueuse/core": "10.11.0", + "@vueuse/shared": "10.11.0", + "vue-demi": ">=0.14.8" }, "funding": { "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "async-validator": "*", - "axios": "*", - "change-case": "*", - "drauu": "*", - "focus-trap": "*", - "fuse.js": "*", - "idb-keyval": "*", - "jwt-decode": "*", - "nprogress": "*", - "qrcode": "*", - "sortablejs": "*", - "universal-cookie": "*" + "async-validator": "^4", + "axios": "^1", + "change-case": "^4", + "drauu": "^0.3", + "focus-trap": "^7", + "fuse.js": "^6", + "idb-keyval": "^6", + "jwt-decode": "^3", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^6" }, "peerDependenciesMeta": { "async-validator": { @@ -6736,9 +6754,9 @@ } }, "node_modules/@vueuse/integrations/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -6761,28 +6779,28 @@ } }, "node_modules/@vueuse/metadata": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.9.0.tgz", - "integrity": "sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==", + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.0.tgz", + "integrity": "sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.9.0.tgz", - "integrity": "sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==", + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.0.tgz", + "integrity": "sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==", "dependencies": { - "vue-demi": ">=0.14.7" + "vue-demi": ">=0.14.8" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -7656,15 +7674,26 @@ } }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, + "node_modules/axios-retry": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.4.1.tgz", + "integrity": "sha512-JGzNoglDHtHWIEvvAampB0P7jxQ/sT4COmW0FgSQkVg6o4KqNjNMBI6uFVOq517hkw/OAYYAG08ADtBlV8lvmQ==", + "dependencies": { + "is-retry-allowed": "^2.2.0" + }, + "peerDependencies": { + "axios": "0.x || 1.x" + } + }, "node_modules/b4a": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", @@ -8505,6 +8534,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, "funding": [ { "type": "github", @@ -8519,6 +8549,7 @@ "url": "https://feross.org/support" } ], + "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -15418,6 +15449,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-set": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", diff --git a/package.json b/package.json index 8b6ec95a7bc..564535c621a 100644 --- a/package.json +++ b/package.json @@ -57,13 +57,13 @@ "@nextcloud/paths": "^2.1.0", "@nextcloud/router": "^3.0.0", "@nextcloud/sharing": "^0.1.0", - "@nextcloud/upload": "^1.1.1", + "@nextcloud/upload": "^1.4.1", "@nextcloud/vue": "^8.11.2", "@simplewebauthn/browser": "^10.0.0", "@skjnldsv/sanitize-svg": "^1.0.2", - "@vueuse/components": "^10.9.0", - "@vueuse/core": "^10.9.0", - "@vueuse/integrations": "^10.9.0", + "@vueuse/components": "^10.11.0", + "@vueuse/core": "^10.11.0", + "@vueuse/integrations": "^10.11.0", "backbone": "^1.4.1", "blueimp-md5": "^2.19.0", "browserslist-useragent-regexp": "^4.1.1", @@ -113,7 +113,7 @@ "vuedraggable": "^2.24.3", "vuex": "^3.6.2", "vuex-router-sync": "^5.0.0", - "webdav": "^5.5.0" + "webdav": "^5.6.0" }, "devDependencies": { "@babel/node": "^7.22.10", |