From 0da138d8152579f23d723d61480e50ad3deb8c36 Mon Sep 17 00:00:00 2001 From: Akhil Date: Tue, 15 Sep 2026 14:21:23 +0530 Subject: [PATCH 01/14] fix: cleanup context before normalizing Signed-off-by: Akhil --- lib/private/Log.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/private/Log.php b/lib/private/Log.php index 64b052e361c72..abfe8eda0d88e 100644 --- a/lib/private/Log.php +++ b/lib/private/Log.php @@ -341,6 +341,8 @@ public function logException(Throwable $exception, array $context = []): void { return; } + unset($context['exception']); + $context = array_map($this->normalizer->format(...), $context); $data = $context; unset($data['app'], $data['level']); From f1178aee8ae6889454195d3fb07eb655087368a6 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Sun, 14 Jun 2026 23:12:46 +0200 Subject: [PATCH 02/14] chore: prepare migration of files to Vue 3 Signed-off-by: Ferdinand Thiessen --- build/frontend-legacy/webpack.modules.cjs | 9 - .../{frontend-legacy => frontend}/apps/files | 0 build/frontend/vite.config.ts | 9 + package-lock.json | 277 ++++++++---------- package.json | 8 +- 5 files changed, 132 insertions(+), 171 deletions(-) rename build/{frontend-legacy => frontend}/apps/files (100%) diff --git a/build/frontend-legacy/webpack.modules.cjs b/build/frontend-legacy/webpack.modules.cjs index f209d4196dfee..598281e0ddec1 100644 --- a/build/frontend-legacy/webpack.modules.cjs +++ b/build/frontend-legacy/webpack.modules.cjs @@ -24,15 +24,6 @@ module.exports = { 'twofactor-request-token': path.join(__dirname, 'core/src', 'twofactor-request-token.ts'), update: path.join(__dirname, 'core/src', 'update.ts'), }, - files: { - sidebar: path.join(__dirname, 'apps/files/src', 'sidebar.ts'), - main: path.join(__dirname, 'apps/files/src', 'main.ts'), - init: path.join(__dirname, 'apps/files/src', 'init.ts'), - search: path.join(__dirname, 'apps/files/src/plugins/search', 'folderSearch.ts'), - 'settings-admin': path.join(__dirname, 'apps/files/src', 'main-settings-admin.ts'), - 'settings-personal': path.join(__dirname, 'apps/files/src', 'main-settings-personal.ts'), - 'reference-files': path.join(__dirname, 'apps/files/src', 'reference-files.ts'), - }, files_sharing: { additionalScripts: path.join(__dirname, 'apps/files_sharing/src', 'additionalScripts.js'), collaboration: path.join(__dirname, 'apps/files_sharing/src', 'collaborationresourceshandler.js'), diff --git a/build/frontend-legacy/apps/files b/build/frontend/apps/files similarity index 100% rename from build/frontend-legacy/apps/files rename to build/frontend/apps/files diff --git a/build/frontend/vite.config.ts b/build/frontend/vite.config.ts index c292c3f371202..dc104bde4268c 100644 --- a/build/frontend/vite.config.ts +++ b/build/frontend/vite.config.ts @@ -36,6 +36,15 @@ const modules = { 'settings-admin': resolve(import.meta.dirname, 'apps/federatedfilesharing/src', 'settings-admin.ts'), 'settings-personal': resolve(import.meta.dirname, 'apps/federatedfilesharing/src', 'settings-personal.ts'), }, + files: { + sidebar: resolve(import.meta.dirname, 'apps/files/src', 'sidebar.ts'), + main: resolve(import.meta.dirname, 'apps/files/src', 'main.ts'), + init: resolve(import.meta.dirname, 'apps/files/src', 'init.ts'), + search: resolve(import.meta.dirname, 'apps/files/src/plugins/search', 'folderSearch.ts'), + 'settings-admicn': resolve(import.meta.dirname, 'apps/files/src', 'main-settings-admin.ts'), + 'settings-personal': resolve(import.meta.dirname, 'apps/files/src', 'main-settings-personal.ts'), + 'reference-files': resolve(import.meta.dirname, 'apps/files/src', 'reference-files.ts'), + }, files_external: { auth_rsa: resolve(import.meta.dirname, 'apps/files_external/src', 'auth-rsa.ts'), diff --git a/package-lock.json b/package-lock.json index cc3930d83125c..55378a65be3e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@nextcloud/capabilities": "^1.2.1", "@nextcloud/dialogs": "^7.5.0", "@nextcloud/event-bus": "^3.3.3", - "@nextcloud/files": "^4.0.0", + "@nextcloud/files": "^4.1.0-beta.2", "@nextcloud/initial-state": "^3.0.0", "@nextcloud/l10n": "^3.4.1", "@nextcloud/logger": "^3.0.3", @@ -26,7 +26,7 @@ "@nextcloud/paths": "^3.1.0", "@nextcloud/router": "^3.1.0", "@nextcloud/sharing": "^1.0.0-beta.4", - "@nextcloud/vue": "^9.10.0", + "@nextcloud/vue": "^9.12.0", "@vueuse/core": "^14.1.0", "@vueuse/integrations": "^14.1.0", "color": "^5.0.3", @@ -34,6 +34,7 @@ "marked": "^18.0.12", "p-queue": "^9.3.3", "pinia": "^4.0.3", + "query-string": "^9.4.0", "sortablejs": "^1.15.7", "vue": "^3.5.42", "vue-router": "^5.0.6", @@ -1964,53 +1965,33 @@ } }, "node_modules/@nextcloud/files": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-4.0.0.tgz", - "integrity": "sha512-TmecnZIS+PGWGtRh7RpGEboCT4K6iTbHULUcfR6hs3eEzjDVsCc1Ldf8popGY/70lbpdlfYle8xbXnPIo3qaXA==", + "version": "4.1.0-beta.2", + "resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-4.1.0-beta.2.tgz", + "integrity": "sha512-tJlhPT8oAmdgyYJtrh4ufrRXfFS+LRZzZdTio64S8izTBXML0Gu/7PVUcOUglY0x/fFgUS5euc0GwNsZZecjbw==", "license": "AGPL-3.0-or-later", "dependencies": { - "@nextcloud/auth": "^2.5.3", + "@nextcloud/auth": "^2.6.0", + "@nextcloud/axios": "^2.6.0", "@nextcloud/capabilities": "^1.2.1", "@nextcloud/l10n": "^3.4.1", "@nextcloud/logger": "^3.0.3", - "@nextcloud/paths": "^3.0.0", + "@nextcloud/paths": "^3.1.0", "@nextcloud/router": "^3.1.0", - "@nextcloud/sharing": "^0.3.0", + "@nextcloud/sharing": "^0.4.0", + "axios-retry": "^4.5.0", "is-svg": "^6.1.0", + "p-queue": "^9.3.3", "typescript-event-target": "^1.1.2", - "webdav": "^5.9.0" - }, - "engines": { - "node": "^24.0.0" - } - }, - "node_modules/@nextcloud/files/node_modules/@nextcloud/files": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-3.12.2.tgz", - "integrity": "sha512-vBo8tf3Xh6efiF8CrEo3pKj9AtvAF6RdDGO1XKL65IxV8+UUd9Uxl2lUExHlzoDRRczCqfGfaWfRRaFhYqce5Q==", - "license": "AGPL-3.0-or-later", - "optional": true, - "dependencies": { - "@nextcloud/auth": "^2.5.3", - "@nextcloud/capabilities": "^1.2.1", - "@nextcloud/l10n": "^3.4.1", - "@nextcloud/logger": "^3.0.3", - "@nextcloud/paths": "^3.0.0", - "@nextcloud/router": "^3.1.0", - "@nextcloud/sharing": "^0.3.0", - "cancelable-promise": "^4.3.1", - "is-svg": "^6.1.0", - "typescript-event-target": "^1.1.1", - "webdav": "^5.8.0" + "webdav": "^5.10.0" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + "node": "^24.0.0 || >=26.0.0" } }, "node_modules/@nextcloud/files/node_modules/@nextcloud/sharing": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nextcloud/sharing/-/sharing-0.3.0.tgz", - "integrity": "sha512-kV7qeUZvd1fTKeFyH+W5Qq5rNOqG9rLATZM3U9MBxWXHJs3OxMqYQb8UQ3NYONzsX3zDGJmdQECIGHm1ei2sCA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@nextcloud/sharing/-/sharing-0.4.0.tgz", + "integrity": "sha512-1hUNyc7uJdBpnimOnEshJjEtAPAjzDYVl6qmWqF5ZxoN9wOvbExw0QjX3xFIbHbX2dmvbRNLBj0RzLzipmZyeg==", "license": "GPL-3.0-or-later", "dependencies": { "@nextcloud/initial-state": "^3.0.0", @@ -2020,7 +2001,7 @@ "node": "^20.0.0 || ^22.0.0 || ^24.0.0" }, "optionalDependencies": { - "@nextcloud/files": "^3.12.0" + "@nextcloud/files": "^3.12.2 || ^4.0.0" } }, "node_modules/@nextcloud/initial-state": { @@ -2197,9 +2178,9 @@ } }, "node_modules/@nextcloud/vue": { - "version": "9.11.0", - "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-9.11.0.tgz", - "integrity": "sha512-LRsyU9Mxs0b2xWqbxygps8O7/i1z4QhNQD9/QDni+VqFY3V0N95mAgmKJm6vfoFuxTAMXERp9bjqFn3UV542gw==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-9.12.0.tgz", + "integrity": "sha512-NuBj7wVKjUaEc9mNHSbnLl6NvLoVqUlHXV/pzn1NED1BtLb6iGwKh1KQX/BnOzGFOCkbFwTGMVgynH6i0ndjDQ==", "license": "AGPL-3.0-or-later", "dependencies": { "@ckpack/vue-color": "^1.6.0", @@ -2221,7 +2202,7 @@ "blurhash": "^2.0.5", "clone": "^2.1.2", "debounce": "^3.0.0", - "dompurify": "^3.4.14", + "dompurify": "^3.4.15", "emoji-mart-vue-fast": "^15.0.5", "escape-html": "^1.0.3", "floating-vue": "^5.2.2", @@ -2245,10 +2226,22 @@ "unist-builder": "^4.0.0", "unist-util-visit-parents": "^6.0.2", "vue": "^3.5.18", - "vue-router": "^5.2.0" + "vue-router": "^5.3.1" }, "engines": { "node": "^20.11.0 || ^22 || ^24" + }, + "peerDependencies": { + "@nextcloud/dialogs": "^7.4.0", + "@nextcloud/files": "^4.1.0-beta.2" + }, + "peerDependenciesMeta": { + "@nextcloud/dialogs": { + "optional": true + }, + "@nextcloud/files": { + "optional": true + } } }, "node_modules/@nextcloud/vue-select": { @@ -3662,12 +3655,6 @@ "@types/sizzle": "*" } }, - "node_modules/@types/jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", - "license": "MIT" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -4999,6 +4986,18 @@ "proxy-from-env": "^2.1.0" } }, + "node_modules/axios-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", + "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", + "license": "Apache-2.0", + "dependencies": { + "is-retry-allowed": "^2.2.0" + }, + "peerDependencies": { + "axios": "0.x || 1.x" + } + }, "node_modules/b4a": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", @@ -6454,6 +6453,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/decode-uri-component": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.5.0.tgz", + "integrity": "sha512-1BiQVoK8C9gUbQU6NzAtO/tkz2qOFpEObMWpcFvhx4fYnj4Oc5yzaJN/LD36ihkVUdXyh5ZekzX+yM+ty/SrPg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -6756,9 +6764,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.14", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.14.tgz", - "integrity": "sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.15.tgz", + "integrity": "sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -7762,6 +7770,18 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz", + "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -9069,6 +9089,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-set": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", @@ -9414,18 +9446,6 @@ "node": "^22.14.0 || >=24.0.0" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -9447,18 +9467,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsonfile": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", @@ -11772,6 +11780,23 @@ ], "license": "MIT" }, + "node_modules/query-string": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.5.1.tgz", + "integrity": "sha512-/zO3RwuRCMTIcEgq6YMv4OrtEE1XzBG7w5N6zc6ydYnkWYOWsnLI/5894hYEzfESfMOT4cHCsRTIdxsSl1KjGg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.5.0", + "filter-obj": "^5.1.0", + "split-on-first": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/querystring-es3": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", @@ -12801,6 +12826,18 @@ "dev": true, "license": "ISC" }, + "node_modules/split-on-first": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz", + "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/splitpanes": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/splitpanes/-/splitpanes-4.0.4.tgz", @@ -15104,17 +15141,16 @@ } }, "node_modules/vue-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.2.0.tgz", - "integrity": "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.3.1.tgz", + "integrity": "sha512-GDBZzgmILxA/kFnkFbJjQZdZ2QQbngnIMMuoUcjhZIfH1RGMaPjPwX5ASnV38qamuA9uhO0RDjSBHTDNG2uXyQ==", "license": "MIT", "dependencies": { - "@babel/generator": "^8.0.0", "@vue-macros/common": "^3.1.3", "@vue/devtools-api": "^8.1.5", "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", - "json5": "^2.2.3", + "confbox": "^0.2.4", "local-pkg": "^1.2.1", "magic-string": "^0.30.21", "mlly": "^1.8.2", @@ -15125,8 +15161,7 @@ "scule": "^1.3.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", - "unplugin-utils": "^0.3.2", - "yaml": "^2.9.0" + "unplugin-utils": "^0.3.2" }, "funding": { "url": "https://github.com/sponsors/posva" @@ -15153,69 +15188,6 @@ } } }, - "node_modules/vue-router/node_modules/@babel/generator": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", - "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^8.0.0", - "@babel/types": "^8.0.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "@types/jsesc": "^2.5.0", - "jsesc": "^3.0.2" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/vue-router/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/vue-router/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", - "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/vue-router/node_modules/@babel/parser": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz", - "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", - "license": "MIT", - "dependencies": { - "@babel/types": "^8.0.4" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/vue-router/node_modules/@babel/types": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", - "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.4" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, "node_modules/vuedraggable": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", @@ -15610,21 +15582,6 @@ "node": ">=10" } }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yargs": { "version": "17.7.3", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", diff --git a/package.json b/package.json index dfc70cb753067..19009dee32d84 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,9 @@ "browserslist": [ "extends @nextcloud/browserslist-config" ], + "overrides": { + "@nextcloud/files": "$@nextcloud/files" + }, "dependencies": { "@mdi/js": "^7.4.47", "@mdi/svg": "^7.4.47", @@ -47,7 +50,7 @@ "@nextcloud/capabilities": "^1.2.1", "@nextcloud/dialogs": "^7.5.0", "@nextcloud/event-bus": "^3.3.3", - "@nextcloud/files": "^4.0.0", + "@nextcloud/files": "^4.1.0-beta.2", "@nextcloud/initial-state": "^3.0.0", "@nextcloud/l10n": "^3.4.1", "@nextcloud/logger": "^3.0.3", @@ -55,7 +58,7 @@ "@nextcloud/paths": "^3.1.0", "@nextcloud/router": "^3.1.0", "@nextcloud/sharing": "^1.0.0-beta.4", - "@nextcloud/vue": "^9.10.0", + "@nextcloud/vue": "^9.12.0", "@vueuse/core": "^14.1.0", "@vueuse/integrations": "^14.1.0", "color": "^5.0.3", @@ -63,6 +66,7 @@ "marked": "^18.0.12", "p-queue": "^9.3.3", "pinia": "^4.0.3", + "query-string": "^9.4.0", "sortablejs": "^1.15.7", "vue": "^3.5.42", "vue-router": "^5.0.6", From 82748c1e42425c4f502bbbbe7d39151623b7e77c Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Mon, 15 Jun 2026 14:25:52 +0200 Subject: [PATCH 03/14] fix: allow legacy apps to import from other apps Signed-off-by: Ferdinand Thiessen --- apps/files_sharing/src/files_views/publicFileShare.ts | 2 +- apps/files_sharing/src/files_views/publicShare.ts | 11 ++++++----- apps/files_sharing/src/init-public.ts | 2 +- apps/files_sharing/src/mixins/SharesMixin.js | 2 +- build/frontend-legacy/vitest.config.mts | 1 + 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/files_sharing/src/files_views/publicFileShare.ts b/apps/files_sharing/src/files_views/publicFileShare.ts index bf4fd0df573ab..cba5094caf869 100644 --- a/apps/files_sharing/src/files_views/publicFileShare.ts +++ b/apps/files_sharing/src/files_views/publicFileShare.ts @@ -9,8 +9,8 @@ import LinkSvg from '@mdi/svg/svg/link.svg?raw' import { Folder, getNavigation, Permission, View } from '@nextcloud/files' import { getDefaultPropfind, getRemoteURL, getRootPath, resultToNode } from '@nextcloud/files/dav' import { translate as t } from '@nextcloud/l10n' -import { client } from '../../../files/src/services/WebdavClient.ts' import logger from '../services/logger.ts' +import { client } from '~/apps/files/src/services/WebdavClient.ts' export default () => { const view = new View({ diff --git a/apps/files_sharing/src/files_views/publicShare.ts b/apps/files_sharing/src/files_views/publicShare.ts index 1666eeece97eb..435352c89c93e 100644 --- a/apps/files_sharing/src/files_views/publicShare.ts +++ b/apps/files_sharing/src/files_views/publicShare.ts @@ -1,11 +1,12 @@ -import LinkSvg from '@mdi/svg/svg/link.svg?raw' -import { getNavigation, View } from '@nextcloud/files' -/** +/* * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { translate as t } from '@nextcloud/l10n' -import { getContents } from '../../../files/src/services/Files.ts' + +import LinkSvg from '@mdi/svg/svg/link.svg?raw' +import { getNavigation, View } from '@nextcloud/files' +import { t } from '@nextcloud/l10n' +import { getContents } from '~/apps/files/src/services/Files.ts' export default () => { const view = new View({ diff --git a/apps/files_sharing/src/init-public.ts b/apps/files_sharing/src/init-public.ts index b59389cb5c22e..2c53693bd20a4 100644 --- a/apps/files_sharing/src/init-public.ts +++ b/apps/files_sharing/src/init-public.ts @@ -9,7 +9,7 @@ import type { ShareAttribute } from './sharing.d.ts' import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus' import { getNavigation } from '@nextcloud/files' import { loadState } from '@nextcloud/initial-state' -import RouterService from '../../files/src/services/RouterService.ts' +import RouterService from '~/apps/files/src/services/RouterService.ts' import registerFileDropView from './files_views/publicFileDrop.ts' import registerPublicFileShareView from './files_views/publicFileShare.ts' import registerPublicShareView from './files_views/publicShare.ts' diff --git a/apps/files_sharing/src/mixins/SharesMixin.js b/apps/files_sharing/src/mixins/SharesMixin.js index 9a84bee0206c4..b4fc8a02af9e3 100644 --- a/apps/files_sharing/src/mixins/SharesMixin.js +++ b/apps/files_sharing/src/mixins/SharesMixin.js @@ -9,13 +9,13 @@ import { emit } from '@nextcloud/event-bus' import { ShareType } from '@nextcloud/sharing' import debounce from 'debounce' import PQueue from 'p-queue' -import { fetchNode } from '../../../files/src/services/WebdavClient.ts' import { getBundledPermissions } from '../lib/SharePermissionsToolBox.js' import Share from '../models/Share.ts' import Config from '../services/ConfigService.ts' import logger from '../services/logger.ts' import GeneratePassword from '../utils/GeneratePassword.ts' import SharesRequests from './ShareRequests.js' +import { fetchNode } from '~/apps/files/src/services/WebdavClient.ts' export default { mixins: [SharesRequests], diff --git a/build/frontend-legacy/vitest.config.mts b/build/frontend-legacy/vitest.config.mts index 3189ba12a4483..35f77fb91c725 100644 --- a/build/frontend-legacy/vitest.config.mts +++ b/build/frontend-legacy/vitest.config.mts @@ -43,6 +43,7 @@ export default defineConfig({ resolve: { preserveSymlinks: true, alias: { + '~': resolve(import.meta.dirname, '../..'), vue$: resolve(__dirname, './node_modules/vue/dist/vue.js'), }, }, From 39ec0acceeea4b33e7e59baf0a2268956ea6a935 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Mon, 15 Jun 2026 01:01:30 +0200 Subject: [PATCH 04/14] refactor(files): migrate to Vue 3 Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: Ferdinand Thiessen --- apps/files/src/actions/favoriteAction.ts | 3 +- apps/files/src/actions/moveOrCopyAction.ts | 6 +- apps/files/src/components/BreadCrumbs.vue | 4 +- .../src/components/DragAndDropPreview.vue | 6 +- apps/files/src/components/FileEntry.vue | 10 +- .../components/FileEntry/FileEntryName.vue | 11 ++- .../src/components/FileEntryGrid.spec.ts | 18 ++-- apps/files/src/components/FileEntryGrid.vue | 10 +- apps/files/src/components/FileEntryMixin.ts | 7 +- apps/files/src/components/FilesListHeader.vue | 2 +- .../src/components/FilesListTableHeader.vue | 2 +- .../FilesListTableHeaderActions.vue | 6 +- .../components/FilesNavigationList.spec.ts | 12 +-- apps/files/src/components/VirtualList.vue | 2 +- .../src/composables/useBeforeNavigation.ts | 2 +- apps/files/src/composables/useFileActions.ts | 6 +- .../src/composables/useFileListActions.ts | 6 +- apps/files/src/composables/useHotKeys.spec.ts | 16 ++-- apps/files/src/composables/useHotKeys.ts | 2 +- apps/files/src/composables/usePreviewImage.ts | 5 +- .../src/composables/useRouteParameters.ts | 4 +- apps/files/src/composables/useViews.spec.ts | 4 +- apps/files/src/filters/ModifiedFilter.ts | 20 +--- apps/files/src/filters/TypeFilter.ts | 27 +----- apps/files/src/main-settings-admin.ts | 12 +-- apps/files/src/main-settings-personal.ts | 12 +-- apps/files/src/main.ts | 22 ++--- apps/files/src/mixins/filesSorting.ts | 4 +- apps/files/src/newMenu/newFromTemplate.ts | 23 ++--- apps/files/src/reference-files.ts | 44 ++++----- apps/files/src/store/dragging.ts | 5 +- apps/files/src/store/files.ts | 6 +- apps/files/src/store/keyboard.ts | 9 +- apps/files/src/store/paths.ts | 11 +-- apps/files/src/store/renaming.ts | 6 +- apps/files/src/store/selection.ts | 13 ++- apps/files/src/store/userconfig.ts | 4 +- apps/files/src/store/viewConfig.ts | 6 +- apps/files/src/utils/actionUtils.ts | 5 +- apps/files/src/utils/dragUtils.ts | 22 +++-- apps/files/src/utils/generateUrl.spec.ts | 42 +++++++++ apps/files/src/utils/generateUrl.ts | 32 +++++++ .../views/DialogConfirmFileExtension.spec.ts | 8 +- .../src/views/DialogConfirmFileHidden.spec.ts | 8 +- .../src/views/FileReferencePickerElement.vue | 10 +- apps/files/src/views/FilesNavigation.spec.ts | 23 ++--- apps/files/src/views/FilesNavigation.vue | 14 +-- apps/files/src/views/FilesSidebar.vue | 2 +- apps/files/src/views/ReferenceFileWidget.vue | 2 +- apps/files/src/views/search.ts | 18 ++-- build/frontend/__tests__/mock-window.js | 11 +++ package-lock.json | 92 +++++++------------ package.json | 3 +- 53 files changed, 327 insertions(+), 333 deletions(-) create mode 100644 apps/files/src/utils/generateUrl.spec.ts create mode 100644 apps/files/src/utils/generateUrl.ts diff --git a/apps/files/src/actions/favoriteAction.ts b/apps/files/src/actions/favoriteAction.ts index 668bbad5e3e3d..60413fe2552f3 100644 --- a/apps/files/src/actions/favoriteAction.ts +++ b/apps/files/src/actions/favoriteAction.ts @@ -15,7 +15,6 @@ import { encodePath } from '@nextcloud/paths' import { generateUrl } from '@nextcloud/router' import { isPublicShare } from '@nextcloud/sharing/public' import PQueue from 'p-queue' -import Vue from 'vue' import { logger } from '../utils/logger.ts' const queue = new PQueue({ concurrency: 5 }) @@ -106,7 +105,7 @@ export async function favoriteNode(node: INode, view: IView, willFavorite: boole } // Update the node webdav attribute - Vue.set(node.attributes, 'favorite', willFavorite ? 1 : 0) + node.attributes.favorite = willFavorite ? 1 : 0 emit('files:node:updated', node) // Dispatch event to whoever is interested diff --git a/apps/files/src/actions/moveOrCopyAction.ts b/apps/files/src/actions/moveOrCopyAction.ts index e676a71e67ba7..99a8385b2ee43 100644 --- a/apps/files/src/actions/moveOrCopyAction.ts +++ b/apps/files/src/actions/moveOrCopyAction.ts @@ -16,8 +16,6 @@ import { FileType, getUniqueName, NodeStatus, Permission } from '@nextcloud/file import { defaultRootPath, getClient, getDefaultPropfind, resultToNode } from '@nextcloud/files/dav' import { n, t } from '@nextcloud/l10n' import { basename, join } from '@nextcloud/paths' -import { getConflicts } from '@nextcloud/upload' -import Vue from 'vue' import { getContents } from '../services/Files.ts' import { logger } from '../utils/logger.ts' import { canCopy, canMove, getQueue, MoveCopyAction } from './moveOrCopyActionUtils.ts' @@ -165,7 +163,7 @@ export async function* handleCopyMoveNodesTo(nodes: INode[], destination: IFolde try { for (const node of nodes) { // Set loading state - Vue.set(node, 'status', NodeStatus.LOADING) + node.status = NodeStatus.LOADING yield queue.add(async () => { try { const client = getClient() @@ -211,7 +209,7 @@ export async function* handleCopyMoveNodesTo(nodes: INode[], destination: IFolde } throw error } finally { - Vue.set(node, 'status', undefined) + node.status = undefined } }) } diff --git a/apps/files/src/components/BreadCrumbs.vue b/apps/files/src/components/BreadCrumbs.vue index 39bd54da0d6c5..1e309c45f503b 100644 --- a/apps/files/src/components/BreadCrumbs.vue +++ b/apps/files/src/components/BreadCrumbs.vue @@ -18,10 +18,10 @@ :to="section.to" :force-icon-text="index === 0 && !isNarrow" force-menu - :open.sync="isMenuOpen" + v-model:open="isMenuOpen" :title="titleForSection(index, section)" :aria-description="ariaForSection(section)" - @dragover.native="onDragOver($event, section.dir)" + @dragover="onDragOver($event, section.dir)" @drop="onDrop($event, section.dir)"> @@ -31,7 +31,9 @@ :name="t('files', 'Files settings')" data-cy-files-navigation-settings-button @click.prevent.stop="settingsOpened = true"> - + @@ -43,7 +45,7 @@ import { emit } from '@nextcloud/event-bus' import { getNavigation } from '@nextcloud/files' import { t } from '@nextcloud/l10n' import { computed, provide, ref, watchEffect } from 'vue' -import { useRoute } from 'vue-router/composables' +import { useRoute } from 'vue-router' import NcAppNavigation from '@nextcloud/vue/components/NcAppNavigation' import NcAppNavigationItem from '@nextcloud/vue/components/NcAppNavigationItem' import IconCog from 'vue-material-design-icons/CogOutline.vue' @@ -79,15 +81,15 @@ const currentNavigationViewId = computed(() => { .sort((a, b) => Object.keys(b.params!).length - Object.keys(a.params!).length) // if we have a full match use that if (matchingParams.length > 0) { - return matchingParams[0] + return matchingParams[0]!.id } // otherwise check if at least view + dir matches const matchingDir = views.find((view) => view.params?.dir === route.query.dir) if (matchingDir) { - return matchingDir + return matchingDir.id } // finally fallback to the parent view - return views.find((view) => view.id === currentViewId.value)! + return views.find((view) => view.id === currentViewId.value)?.id }) provide('currentNavigationView', currentNavigationViewId) diff --git a/apps/files/src/views/FilesSidebar.vue b/apps/files/src/views/FilesSidebar.vue index c83a7cf794ed4..6e5f2a318b424 100644 --- a/apps/files/src/views/FilesSidebar.vue +++ b/apps/files/src/views/FilesSidebar.vue @@ -73,7 +73,7 @@ function onToggle(open: boolean) { import('./SearchEmptyView.vue')) + let instance: App const Navigation = getNavigation() Navigation.register(new View({ @@ -28,13 +28,11 @@ export function registerSearchView() { caption: t('files', 'Search results within your files.'), async emptyView(el) { - if (!view) { - view = (await import('./SearchEmptyView.vue')).default - } else { - instance.$destroy() + if (instance) { + instance.unmount() } - instance = new Vue(view) - instance.$mount(el) + instance = createApp(EmptyView) + instance.mount(el) }, icon: MagnifySvg, diff --git a/build/frontend/__tests__/mock-window.js b/build/frontend/__tests__/mock-window.js index 24a5e251934b1..01b7b2b280e38 100644 --- a/build/frontend/__tests__/mock-window.js +++ b/build/frontend/__tests__/mock-window.js @@ -14,6 +14,17 @@ window.OCP = { ...window.OCP } window._oc_webroot = '' +// jsdom does not implement `ResizeObserver`, but it is used at module scope by +// composables like `useFileListWidth`, so importing them would throw. +// Specs that assert on resizing replace this with a full mock (`mockResizeObserver`). +if (!('ResizeObserver' in window)) { + window.ResizeObserver = class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} + } +} + // jsdom does not implement `innerText` at all, while the specification defines it // to fall back to `textContent` for elements that are not being rendered. // @see https://github.com/jsdom/jsdom/issues/1245 diff --git a/package-lock.json b/package-lock.json index 55378a65be3e0..aba405083980e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,6 @@ "marked": "^18.0.12", "p-queue": "^9.3.3", "pinia": "^4.0.3", - "query-string": "^9.4.0", "sortablejs": "^1.15.7", "vue": "^3.5.42", "vue-router": "^5.0.6", @@ -49,6 +48,7 @@ "@nextcloud/stylelint-config": "^3.2.2", "@nextcloud/typings": "^1.10.0", "@nextcloud/vite-config": "^2.5.2", + "@pinia/testing": "^2.0.1", "@playwright/test": "^1.63.0", "@testing-library/jest-dom": "^7.0.1", "@testing-library/vue": "^8.1.0", @@ -64,6 +64,7 @@ "is-svg": "^6.1.0", "jsdom": "^30.0.1", "jsdom-testing-mocks": "^1.16.0", + "mime": "^4.1.0", "msw": "^2.15.0", "sass": "^1.104.0", "stylelint": "^17.15.0", @@ -2697,6 +2698,22 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@pinia/testing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-2.0.1.tgz", + "integrity": "sha512-pQ4a4SCzdiLrhM4yv/TQG2ufNmktNlLZjjN6X/qMhXvZIiDN3L4L4WrChmcufX2LIarbl/ZPWOty1RB5KZIb2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "nostics": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "pinia": ">=4.0.2" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -5572,13 +5589,6 @@ "node": ">=6" } }, - "node_modules/cancelable-promise": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/cancelable-promise/-/cancelable-promise-4.3.1.tgz", - "integrity": "sha512-A/8PwLk/T7IJDfUdQ68NR24QHa8rIlnN/stiJEBo6dmVUkD4K14LswG0w3VwdeK/o7qOwRUR1k2MhK5Rpy2m7A==", - "license": "MIT", - "optional": true - }, "node_modules/caniuse-lite": { "version": "1.0.30001810", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", @@ -6453,15 +6463,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/decode-uri-component": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.5.0.tgz", - "integrity": "sha512-1BiQVoK8C9gUbQU6NzAtO/tkz2qOFpEObMWpcFvhx4fYnj4Oc5yzaJN/LD36ihkVUdXyh5ZekzX+yM+ty/SrPg==", - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -7770,18 +7771,6 @@ "node": ">=8" } }, - "node_modules/filter-obj": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz", - "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -10479,6 +10468,22 @@ "dev": true, "license": "MIT" }, + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -11780,23 +11785,6 @@ ], "license": "MIT" }, - "node_modules/query-string": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.5.1.tgz", - "integrity": "sha512-/zO3RwuRCMTIcEgq6YMv4OrtEE1XzBG7w5N6zc6ydYnkWYOWsnLI/5894hYEzfESfMOT4cHCsRTIdxsSl1KjGg==", - "license": "MIT", - "dependencies": { - "decode-uri-component": "^0.5.0", - "filter-obj": "^5.1.0", - "split-on-first": "^3.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/querystring-es3": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", @@ -12826,18 +12814,6 @@ "dev": true, "license": "ISC" }, - "node_modules/split-on-first": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz", - "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/splitpanes": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/splitpanes/-/splitpanes-4.0.4.tgz", diff --git a/package.json b/package.json index 19009dee32d84..eff8f3640f686 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "marked": "^18.0.12", "p-queue": "^9.3.3", "pinia": "^4.0.3", - "query-string": "^9.4.0", "sortablejs": "^1.15.7", "vue": "^3.5.42", "vue-router": "^5.0.6", @@ -81,6 +80,7 @@ "@nextcloud/stylelint-config": "^3.2.2", "@nextcloud/typings": "^1.10.0", "@nextcloud/vite-config": "^2.5.2", + "@pinia/testing": "^2.0.1", "@playwright/test": "^1.63.0", "@testing-library/jest-dom": "^7.0.1", "@testing-library/vue": "^8.1.0", @@ -96,6 +96,7 @@ "is-svg": "^6.1.0", "jsdom": "^30.0.1", "jsdom-testing-mocks": "^1.16.0", + "mime": "^4.1.0", "msw": "^2.15.0", "sass": "^1.104.0", "stylelint": "^17.15.0", From 0f00c88cc6fc3bbecfce29a663c7d104eb5d37b4 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Mon, 15 Jun 2026 14:28:29 +0200 Subject: [PATCH 05/14] chore: resolve pinia as shared module Signed-off-by: Ferdinand Thiessen --- apps/files/src/actions/deleteUtils.ts | 4 ++-- apps/files/src/actions/downloadAction.ts | 6 +++--- apps/files/src/actions/renameAction.spec.ts | 4 ++-- apps/files/src/actions/renameAction.ts | 4 ++-- .../FileListFilter/FileListFilterToSearch.vue | 4 ++-- apps/files/src/composables/useHotKeys.spec.ts | 10 +++++----- apps/files/src/filters/FilenameFilter.ts | 4 ++-- apps/files/src/router/router.ts | 12 ++++++------ apps/files/src/services/Files.ts | 6 +++--- apps/files/src/services/Recent.ts | 10 +++++----- apps/files/src/services/Search.ts | 4 ++-- apps/files/src/sidebar.ts | 4 ++-- apps/files/src/store/index.ts | 11 ++--------- apps/files/src/views/SearchEmptyView.vue | 4 ++-- apps/files/src/views/folderTree.ts | 4 ++-- 15 files changed, 42 insertions(+), 49 deletions(-) diff --git a/apps/files/src/actions/deleteUtils.ts b/apps/files/src/actions/deleteUtils.ts index 6b1cbb9138b10..46f010e6146b0 100644 --- a/apps/files/src/actions/deleteUtils.ts +++ b/apps/files/src/actions/deleteUtils.ts @@ -11,7 +11,7 @@ import { getCapabilities } from '@nextcloud/capabilities' import { emit } from '@nextcloud/event-bus' import { FileType } from '@nextcloud/files' import { n, t } from '@nextcloud/l10n' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' import { useUserConfigStore } from '../store/userconfig.ts' export const isTrashbinEnabled = () => (getCapabilities() as Capabilities)?.files?.undelete === true @@ -134,7 +134,7 @@ export function displayName({ nodes, view }: { nodes: INode[], view: IView }) { * */ export function shouldAskForConfirmation() { - const userConfig = useUserConfigStore(getPinia()) + const userConfig = useUserConfigStore(pinia) return userConfig.userConfig.show_dialog_deletion !== false } diff --git a/apps/files/src/actions/downloadAction.ts b/apps/files/src/actions/downloadAction.ts index 63f943ddcedb3..14a476676758e 100644 --- a/apps/files/src/actions/downloadAction.ts +++ b/apps/files/src/actions/downloadAction.ts @@ -12,7 +12,7 @@ import { emit } from '@nextcloud/event-bus' import { DefaultType, FileType } from '@nextcloud/files' import { t } from '@nextcloud/l10n' import { useFilesStore } from '../store/files.ts' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' import { usePathsStore } from '../store/paths.ts' import { logger } from '../utils/logger.ts' import { isDownloadable } from '../utils/permissions.ts' @@ -161,8 +161,8 @@ async function downloadNodes(nodes: INode[]) { * @return The current directory node or null if not found */ function getCurrentDirectory(view: IView, directory: string): INode | null { - const filesStore = useFilesStore(getPinia()) - const pathsStore = usePathsStore(getPinia()) + const filesStore = useFilesStore(pinia) + const pathsStore = usePathsStore(pinia) if (!view?.id) { return null } diff --git a/apps/files/src/actions/renameAction.spec.ts b/apps/files/src/actions/renameAction.spec.ts index 1f25b86d4ebea..89e3f01001f7e 100644 --- a/apps/files/src/actions/renameAction.spec.ts +++ b/apps/files/src/actions/renameAction.spec.ts @@ -9,7 +9,7 @@ import * as eventBus from '@nextcloud/event-bus' import { File, Folder, Permission } from '@nextcloud/files' import { beforeEach, describe, expect, test, vi } from 'vitest' import { useFilesStore } from '../store/files.ts' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' import { action } from './renameAction.ts' const view = { @@ -25,7 +25,7 @@ beforeEach(() => { permissions: Permission.CREATE, root: '/files/admin', }) - const files = useFilesStore(getPinia()) + const files = useFilesStore(pinia) files.setRoot({ service: 'files', root }) }) diff --git a/apps/files/src/actions/renameAction.ts b/apps/files/src/actions/renameAction.ts index 28b2bcd29a853..b63f4e9fb2be5 100644 --- a/apps/files/src/actions/renameAction.ts +++ b/apps/files/src/actions/renameAction.ts @@ -11,7 +11,7 @@ import { Permission } from '@nextcloud/files' import { translate as t } from '@nextcloud/l10n' import { dirname } from 'path' import { useFilesStore } from '../store/files.ts' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' export const ACTION_RENAME = 'rename' @@ -31,7 +31,7 @@ export const action: IFileAction = { } const node = nodes[0] - const filesStore = useFilesStore(getPinia()) + const filesStore = useFilesStore(pinia) const parentNode = node.dirname === '/' ? filesStore.getRoot(view.id) : filesStore.getNode(dirname(node.source)) diff --git a/apps/files/src/components/FileListFilter/FileListFilterToSearch.vue b/apps/files/src/components/FileListFilter/FileListFilterToSearch.vue index 695f9c8ae76c5..882aca89d46e5 100644 --- a/apps/files/src/components/FileListFilter/FileListFilterToSearch.vue +++ b/apps/files/src/components/FileListFilter/FileListFilterToSearch.vue @@ -13,10 +13,10 @@ import { t } from '@nextcloud/l10n' import { computed } from 'vue' import NcButton from '@nextcloud/vue/components/NcButton' -import { getPinia } from '../../store/index.ts' +import { pinia } from '../../store/index.ts' import { useSearchStore } from '../../store/search.ts' -const searchStore = useSearchStore(getPinia()) +const searchStore = useSearchStore(pinia) const isVisible = computed(() => searchStore.query.length >= 3 && searchStore.scope === 'filter') diff --git a/apps/files/src/composables/useHotKeys.spec.ts b/apps/files/src/composables/useHotKeys.spec.ts index e1ee80b589587..f9de8dd8b5302 100644 --- a/apps/files/src/composables/useHotKeys.spec.ts +++ b/apps/files/src/composables/useHotKeys.spec.ts @@ -15,7 +15,7 @@ import { defineComponent, nextTick } from 'vue' import { action as deleteAction } from '../actions/deleteAction.ts' import { useActiveStore } from '../store/active.ts' import { useFilesStore } from '../store/files.ts' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' import { useUserConfigStore } from '../store/userconfig.ts' import { useHotKeys } from './useHotKeys.ts' @@ -74,7 +74,7 @@ beforeAll(() => { }) describe('HotKeysService testing', () => { - const activeStore = useActiveStore(getPinia()) + const activeStore = useActiveStore(pinia) let initialState: HTMLInputElement let component: ReturnType @@ -107,7 +107,7 @@ describe('HotKeysService testing', () => { permissions: Permission.CREATE, }) - const files = useFilesStore(getPinia()) + const files = useFilesStore(pinia) files.setRoot({ service: 'files', root }) // Setting the view first as it reset the active node @@ -171,7 +171,7 @@ describe('HotKeysService testing', () => { it('Pressing v should toggle grid view', async () => { vi.spyOn(axios, 'put').mockImplementationOnce(() => Promise.resolve()) - const userConfigStore = useUserConfigStore(getPinia()) + const userConfigStore = useUserConfigStore(pinia) userConfigStore.userConfig.grid_view = false expect(userConfigStore.userConfig.grid_view).toBe(false) @@ -187,7 +187,7 @@ describe('HotKeysService testing', () => { ])('Pressing v with modifier key %s should not toggle grid view', async (modifier: string) => { vi.spyOn(axios, 'put').mockImplementationOnce(() => Promise.resolve()) - const userConfigStore = useUserConfigStore(getPinia()) + const userConfigStore = useUserConfigStore(pinia) userConfigStore.userConfig.grid_view = false expect(userConfigStore.userConfig.grid_view).toBe(false) diff --git a/apps/files/src/filters/FilenameFilter.ts b/apps/files/src/filters/FilenameFilter.ts index 4bbc3c10da570..4985b7fd4a963 100644 --- a/apps/files/src/filters/FilenameFilter.ts +++ b/apps/files/src/filters/FilenameFilter.ts @@ -7,7 +7,7 @@ import type { IFileListFilterChip, INode } from '@nextcloud/files' import { subscribe } from '@nextcloud/event-bus' import { FileListFilter, registerFileListFilter } from '@nextcloud/files' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' import { useSearchStore } from '../store/search.ts' /** @@ -55,7 +55,7 @@ class FilenameFilter extends FileListFilter { }) } else { // make sure to also reset the search store when pressing the "X" on the filter chip - const store = useSearchStore(getPinia()) + const store = useSearchStore(pinia) if (store.scope === 'filter') { store.query = '' } diff --git a/apps/files/src/router/router.ts b/apps/files/src/router/router.ts index 94becd075382e..5825fb36b07ec 100644 --- a/apps/files/src/router/router.ts +++ b/apps/files/src/router/router.ts @@ -13,7 +13,7 @@ import queryString from 'query-string' import Vue from 'vue' import Router, { isNavigationFailure, NavigationFailureType } from 'vue-router' import { useFilesStore } from '../store/files.ts' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' import { usePathsStore } from '../store/paths.ts' import { defaultView } from '../utils/filesViews.ts' import { logger } from '../utils/logger.ts' @@ -148,11 +148,11 @@ router.beforeResolve((to, from, next) => { }) subscribe('files:node:deleted', (node: INode) => { - if (router.currentRoute.params.fileid === String(node.fileid)) { - const params = { ...router.currentRoute.params } - const { getPath } = usePathsStore(getPinia()) - const { getNode } = useFilesStore(getPinia()) - const source = getPath(router.currentRoute.params.view, node.dirname) + if (router.currentRoute.value.params.fileid === String(node.fileid)) { + const params = { ...router.currentRoute.value.params } + const { getPath } = usePathsStore(pinia) + const { getNode } = useFilesStore(pinia) + const source = getPath(router.currentRoute.value.params.view as string, node.dirname) const parentFolder = getNode(source!) if (source && parentFolder) { params.fileid = String(parentFolder.fileid) diff --git a/apps/files/src/services/Files.ts b/apps/files/src/services/Files.ts index 3941ee20ede96..e80a9d60b9aa0 100644 --- a/apps/files/src/services/Files.ts +++ b/apps/files/src/services/Files.ts @@ -8,7 +8,7 @@ import type { FileStat, ResponseDataDetailed } from 'webdav' import { getDefaultPropfind, getRootPath, resultToNode } from '@nextcloud/files/dav' import { join } from 'path' import { useFilesStore } from '../store/files.ts' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' import { useSearchStore } from '../store/search.ts' import { logger } from '../utils/logger.ts' import { client } from './WebdavClient.ts' @@ -23,7 +23,7 @@ import { searchNodes } from './WebDavSearch.ts' * @param options.signal - Abort signal to cancel the request */ export async function getContents(path = '/', options?: { signal: AbortSignal }): Promise { - const searchStore = useSearchStore(getPinia()) + const searchStore = useSearchStore(pinia) if (searchStore.query.length < 3) { return await defaultGetContents(path, options) @@ -78,7 +78,7 @@ export async function defaultGetContents(path: string, options?: { signal: Abort * @param signal - The aboort signal */ async function getLocalSearch(path: string, query: string, signal?: AbortSignal): Promise { - const filesStore = useFilesStore(getPinia()) + const filesStore = useFilesStore(pinia) let folder = filesStore.getDirectoryByPath('files', path) if (!folder) { const rootPath = join(getRootPath(), path) diff --git a/apps/files/src/services/Recent.ts b/apps/files/src/services/Recent.ts index 9bdf674b111cf..6673dacffee9c 100644 --- a/apps/files/src/services/Recent.ts +++ b/apps/files/src/services/Recent.ts @@ -9,7 +9,7 @@ import { getCurrentUser } from '@nextcloud/auth' import { Folder, Permission } from '@nextcloud/files' import { getRecentSearch, getRemoteURL, getRootPath, resultToNode } from '@nextcloud/files/dav' import { loadState } from '@nextcloud/initial-state' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' import { useUserConfigStore } from '../store/userconfig.ts' import { logger } from '../utils/logger.ts' import { client } from './WebdavClient.ts' @@ -27,8 +27,8 @@ const recentLimit = loadState('files', 'recent_limit', 100) * @param options Options including abort signal * @param options.signal Abort signal to cancel the request */ -export async function getContents(path = '/', options: { signal: AbortSignal }): Promise { - const store = useUserConfigStore(getPinia()) +export async function getContents(path = '/', options?: { signal: AbortSignal }): Promise { + const store = useUserConfigStore(pinia) /** * Filter function that returns only the visible nodes - or hidden if explicitly configured @@ -41,7 +41,7 @@ export async function getContents(path = '/', options: { signal: AbortSignal }): try { const contentsResponse = await client.search('/', { - signal: options.signal, + signal: options?.signal, details: true, data: getRecentSearch(lastTwoWeeksTimestamp, recentLimit), }) as ResponseDataDetailed @@ -65,7 +65,7 @@ export async function getContents(path = '/', options: { signal: AbortSignal }): contents, } } catch (error) { - if (options.signal.aborted) { + if (options?.signal.aborted) { logger.info('Fetching recent files aborted') throw new DOMException('Aborted', 'AbortError') } diff --git a/apps/files/src/services/Search.ts b/apps/files/src/services/Search.ts index ade5ef2b245be..b211bc088c91d 100644 --- a/apps/files/src/services/Search.ts +++ b/apps/files/src/services/Search.ts @@ -8,7 +8,7 @@ import type { ContentsWithRoot } from '@nextcloud/files' import { getCurrentUser } from '@nextcloud/auth' import { Folder, Permission } from '@nextcloud/files' import { defaultRemoteURL, getRootPath } from '@nextcloud/files/dav' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' import { useSearchStore } from '../store/search.ts' import { logger } from '../utils/logger.ts' import { searchNodes } from './WebDavSearch.ts' @@ -21,7 +21,7 @@ import { searchNodes } from './WebDavSearch.ts' * @param options.signal - Abort signal to cancel the request */ export async function getContents(path, options: { signal: AbortSignal }): Promise { - const searchStore = useSearchStore(getPinia()) + const searchStore = useSearchStore(pinia) try { const contents = await searchNodes(searchStore.query, { signal: options.signal }) diff --git a/apps/files/src/sidebar.ts b/apps/files/src/sidebar.ts index c8c98cc7940ff..008d92b687763 100644 --- a/apps/files/src/sidebar.ts +++ b/apps/files/src/sidebar.ts @@ -5,9 +5,9 @@ import type { ISidebar } from '@nextcloud/files' -import { getPinia } from './store/index.ts' +import { pinia } from './store/index.ts' import { useSidebarStore } from './store/sidebar.ts' // Provide sidebar implementation which is proxied by the `@nextcloud/files` library for app usage. window.OCA.Files ??= {} -window.OCA.Files._sidebar = () => useSidebarStore(getPinia()) satisfies Omit +window.OCA.Files._sidebar = () => useSidebarStore(pinia) satisfies Omit diff --git a/apps/files/src/store/index.ts b/apps/files/src/store/index.ts index e80661bdd16e8..b80efcdad15ba 100644 --- a/apps/files/src/store/index.ts +++ b/apps/files/src/store/index.ts @@ -6,13 +6,6 @@ import { createPinia } from 'pinia' /** - * Get the Pinia instance for the Files app. + * The Pinia instance for the Files app. */ -export function getPinia() { - if (window._nc_files_pinia) { - return window._nc_files_pinia - } - - window._nc_files_pinia = createPinia() - return window._nc_files_pinia -} +export const pinia = createPinia() diff --git a/apps/files/src/views/SearchEmptyView.vue b/apps/files/src/views/SearchEmptyView.vue index 00669493a3442..f99ca48189bfa 100644 --- a/apps/files/src/views/SearchEmptyView.vue +++ b/apps/files/src/views/SearchEmptyView.vue @@ -10,10 +10,10 @@ import debounce from 'debounce' import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent' import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' import NcInputField from '@nextcloud/vue/components/NcInputField' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' import { useSearchStore } from '../store/search.ts' -const searchStore = useSearchStore(getPinia()) +const searchStore = useSearchStore(pinia) const debouncedUpdate = debounce((value: string) => { searchStore.query = value }, 500) diff --git a/apps/files/src/views/folderTree.ts b/apps/files/src/views/folderTree.ts index 3e854a6b66a66..4b9693fb54d64 100644 --- a/apps/files/src/views/folderTree.ts +++ b/apps/files/src/views/folderTree.ts @@ -22,7 +22,7 @@ import { sourceRoot, } from '../services/FolderTree.ts' import { useFilesStore } from '../store/files.ts' -import { getPinia } from '../store/index.ts' +import { pinia } from '../store/index.ts' interface IFolderTreeView extends IView { loading?: boolean @@ -170,7 +170,7 @@ function getLoadChildViews(node: TreeNode | IFolder) { */ async function updateTreeChildren(path: string = '/') { await queue.add(async () => { - const filesStore = useFilesStore(getPinia()) + const filesStore = useFilesStore(pinia) const cachedNodes = filesStore.getNodesByPath(Navigation.active!.id, path) if (cachedNodes.length > 0) { // if there are nodes loaded in the path we dont need to fetch from API From 7a98630c8ede8da556c91ee5d0e38bd329408ace Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Mon, 15 Jun 2026 14:29:07 +0200 Subject: [PATCH 06/14] refactor(files): migrate from mixin to composable for files sorting Signed-off-by: Ferdinand Thiessen --- .../src/components/FilesListTableHeader.vue | 10 ++- .../components/FilesListTableHeaderButton.vue | 49 +++++--------- apps/files/src/composables/filesSorting.ts | 65 +++++++++++++++++++ apps/files/src/mixins/filesSorting.ts | 52 --------------- apps/files/src/views/FilesList.vue | 8 +-- 5 files changed, 89 insertions(+), 95 deletions(-) create mode 100644 apps/files/src/composables/filesSorting.ts delete mode 100644 apps/files/src/mixins/filesSorting.ts diff --git a/apps/files/src/components/FilesListTableHeader.vue b/apps/files/src/components/FilesListTableHeader.vue index eb1dd4c46af8f..9621d162b9ecf 100644 --- a/apps/files/src/components/FilesListTableHeader.vue +++ b/apps/files/src/components/FilesListTableHeader.vue @@ -12,7 +12,7 @@ + @update:modelValue="onToggleAll" /> - - - + +