From aa4bf59e77a26a212ccc598f885a704e3330f0fc Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Mon, 24 Aug 2026 16:53:22 +0200 Subject: [PATCH 1/4] feat: define proper routes in the frontend Signed-off-by: Ferdinand Thiessen --- lib/Controller/PageController.php | 1 + package-lock.json | 696 ++++++++++++++---------------- package.json | 3 +- src/App.spec.ts | 34 +- src/App.vue | 4 +- src/main.ts | 2 + src/router.spec.ts | 180 ++++++++ src/router.ts | 47 ++ src/utils/fileCategories.spec.ts | 64 ++- src/utils/fileCategories.ts | 67 ++- src/views/OfficeOverview.spec.ts | 132 +++++- src/views/OfficeOverview.vue | 41 +- 12 files changed, 842 insertions(+), 429 deletions(-) create mode 100644 src/router.spec.ts create mode 100644 src/router.ts diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index e039556..7ff2809 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -28,6 +28,7 @@ public function __construct( #[NoAdminRequired] #[OpenAPI(OpenAPI::SCOPE_IGNORE)] #[FrontpageRoute(verb: 'GET', url: '/')] + #[FrontpageRoute(verb: 'GET', url: '/{path}', requirements: ['path' => '.*'], defaults: ['path' => ''], postfix: 'path')] public function index(): TemplateResponse { // editor-url is not provided here — OfficeOverview.vue calls // loadState('office', 'editor-url', null) and falls back to /f/{fileid} diff --git a/package-lock.json b/package-lock.json index a2067cf..6448651 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "@nextcloud/l10n": "^3.4.1", "@nextcloud/router": "^3.1.0", "@nextcloud/vue": "^9.3.1", - "vue": "^3.5.41" + "vue": "^3.5.41", + "vue-router": "^5.2.0" }, "devDependencies": { "@nextcloud/browserslist-config": "^3.1.2", @@ -170,15 +171,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -390,14 +391,14 @@ } }, "node_modules/@cacheable/memory": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.9.tgz", - "integrity": "sha512-HdMx6DoGywB30vacDbBsITbIX4pgFqj1zsrV58jZBUw3klzkNoXhj7qOqAgledhxG7YZI5rBSJg7Zp8/VG0DuA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@cacheable/utils": "^2.4.1", + "@cacheable/utils": "^2.5.0", "@keyv/bigmap": "^1.3.1", "hookified": "^1.15.1", "keyv": "^5.6.0" @@ -433,9 +434,9 @@ } }, "node_modules/@cacheable/utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.4.1.tgz", - "integrity": "sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", "dev": true, "license": "MIT", "peer": true, @@ -646,22 +647,22 @@ } }, "node_modules/@emnapi/core": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", - "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.2", + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", - "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, @@ -671,9 +672,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, @@ -705,7 +706,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -722,7 +722,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -739,7 +738,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -756,7 +754,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -773,7 +770,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -790,7 +786,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -807,7 +802,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -824,7 +818,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -841,7 +834,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -858,7 +850,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -875,7 +866,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -892,7 +882,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -909,7 +898,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -926,7 +914,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -943,7 +930,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -960,7 +946,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -977,7 +962,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -994,7 +978,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1011,7 +994,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1028,7 +1010,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1045,7 +1026,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1062,7 +1042,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1079,7 +1058,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1096,7 +1074,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1113,7 +1090,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1130,7 +1106,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1276,28 +1251,28 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.11" + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, "node_modules/@humanwhocodes/config-array": { @@ -1386,9 +1361,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", "dev": true, "license": "MIT", "engines": { @@ -1805,9 +1780,9 @@ } }, "node_modules/@nextcloud/eslint-plugin/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "peer": true, @@ -1832,9 +1807,9 @@ } }, "node_modules/@nextcloud/event-bus/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2115,9 +2090,9 @@ } }, "node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz", + "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==", "funding": [ { "type": "github", @@ -2189,7 +2164,6 @@ "version": "2.5.6", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -2230,7 +2204,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2252,7 +2225,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2274,7 +2246,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2296,7 +2267,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2318,7 +2288,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2340,7 +2309,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2362,7 +2330,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2384,7 +2351,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2406,7 +2372,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2428,7 +2393,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2450,7 +2414,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2472,7 +2435,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2494,7 +2456,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2602,7 +2563,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2616,7 +2576,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2630,7 +2589,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2644,7 +2602,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2658,7 +2615,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2672,7 +2628,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2686,7 +2641,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2700,7 +2654,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2714,7 +2667,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2728,7 +2680,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2742,7 +2693,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2756,7 +2706,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2770,7 +2719,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2784,7 +2732,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2798,7 +2745,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2812,7 +2758,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2826,7 +2771,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2840,7 +2784,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2854,7 +2797,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2868,7 +2810,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2882,7 +2823,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2896,7 +2836,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2910,7 +2849,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2924,7 +2862,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2938,7 +2875,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3239,9 +3175,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, "node_modules/@types/estree-jsx": { @@ -3490,9 +3426,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "peer": true, @@ -3856,43 +3792,6 @@ "node": ">=14.0.0" } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", @@ -4108,9 +4007,9 @@ } }, "node_modules/@vue-macros/common": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.2.tgz", - "integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.4.tgz", + "integrity": "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==", "license": "MIT", "dependencies": { "@vue/compiler-sfc": "^3.5.22", @@ -4208,30 +4107,30 @@ } }, "node_modules/@vue/devtools-api": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.2.tgz", - "integrity": "sha512-vA0O112YqyDuNA1s7Yb2gCgToQ/OxOWiFDO5ThLCcDy0ldHnSd1dUTaSYhOldbqoNgumE4dxtGAoAaSUKUD1Zg==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.2.1.tgz", + "integrity": "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==", "license": "MIT", "dependencies": { - "@vue/devtools-kit": "^8.1.2" + "@vue/devtools-kit": "^8.2.1" } }, "node_modules/@vue/devtools-kit": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.2.tgz", - "integrity": "sha512-f75/upc+GCyjXErpgPGz4582ujS0L/adAltGy+tqXMGUJpgAcfGr6CxnnhpZY8BHuMYt6KpbF8uaFrrQG66rGQ==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz", + "integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==", "license": "MIT", "dependencies": { - "@vue/devtools-shared": "^8.1.2", + "@vue/devtools-shared": "^8.2.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "node_modules/@vue/devtools-shared": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.2.tgz", - "integrity": "sha512-X9RyVFYAdkBe4IUf5v48TxBF/6QPmF8CmWrDAjXzfUHrgQ/HGfTC1A6TqgXqZ03ye66l3AD51BAGD69IvKM9sw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz", + "integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==", "license": "MIT" }, "node_modules/@vue/eslint-config-typescript": { @@ -4577,6 +4476,18 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/are-docs-informative": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", @@ -4805,13 +4716,14 @@ } }, "node_modules/ast-walker-scope": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.8.3.tgz", - "integrity": "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", + "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.4", - "ast-kit": "^2.1.3" + "@babel/parser": "^7.29.2", + "@babel/types": "^7.29.0", + "ast-kit": "^2.2.0" }, "engines": { "node": ">=20.19.0" @@ -5140,23 +5052,6 @@ "dev": true, "license": "MIT" }, - "node_modules/browserify-sign/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, "node_modules/browserify-zlib": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", @@ -5285,9 +5180,9 @@ } }, "node_modules/builtins/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "peer": true, @@ -5305,15 +5200,15 @@ "license": "MIT" }, "node_modules/cacheable": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.5.tgz", - "integrity": "sha512-EQfaKe09tl615iNvq/TBRWTFf1AKJNXYQSsMx0Z3EI0nA+pVsVPS8wJhnRlkbdacKPh1d0qVIhwTc2zsQNFEEg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@cacheable/memory": "^2.0.8", - "@cacheable/utils": "^2.4.1", + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", "hookified": "^1.15.0", "keyv": "^5.6.0", "qified": "^0.10.1" @@ -6107,7 +6002,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, "license": "Apache-2.0", "optional": true, "peer": true, @@ -6568,7 +6462,7 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -6712,9 +6606,9 @@ } }, "node_modules/eslint-compat-utils/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "peer": true, @@ -6997,9 +6891,9 @@ } }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "peer": true, @@ -7090,9 +6984,9 @@ } }, "node_modules/eslint-plugin-n/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "peer": true, @@ -7145,9 +7039,9 @@ } }, "node_modules/eslint-plugin-vue/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "peer": true, @@ -7818,7 +7712,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -8531,7 +8424,7 @@ "version": "5.1.9", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true }, @@ -8806,9 +8699,9 @@ } }, "node_modules/is-bun-module/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "peer": true, @@ -8899,7 +8792,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "engines": { @@ -8957,7 +8850,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -9210,6 +9103,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-unsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", + "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -10690,7 +10595,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, "license": "MIT", "optional": true, "peer": true @@ -10836,6 +10740,12 @@ "node": ">=0.10.0" } }, + "node_modules/nostics": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nostics/-/nostics-1.2.0.tgz", + "integrity": "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==", + "license": "MIT" + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -11261,9 +11171,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", "funding": [ { "type": "github", @@ -11381,9 +11291,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", "engines": { "node": ">=12" @@ -11542,9 +11452,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", "dev": true, "license": "MIT", "peer": true, @@ -12121,28 +12031,11 @@ "dev": true, "license": "MIT" }, - "node_modules/ripemd160/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/ripemd160/node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, "node_modules/rollup": { "version": "4.60.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -12274,13 +12167,6 @@ } } }, - "node_modules/rollup/node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -12388,7 +12274,7 @@ "version": "1.100.0", "resolved": "https://registry.npmjs.org/sass/-/sass-1.100.0.tgz", "integrity": "sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -12882,15 +12768,22 @@ } }, "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "~5.2.0" + "safe-buffer": "~5.1.0" } }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -12902,9 +12795,9 @@ } }, "node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", "dev": true, "license": "MIT", "peer": true, @@ -12936,9 +12829,9 @@ } }, "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", "dev": true, "license": "MIT", "peer": true, @@ -13300,9 +13193,9 @@ } }, "node_modules/stylelint-config-recommended-vue/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "peer": true, @@ -13341,9 +13234,9 @@ } }, "node_modules/stylelint-scss/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "dev": true, "license": "MIT", "peer": true, @@ -13417,33 +13310,33 @@ } }, "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.3.tgz", - "integrity": "sha512-oMbq0PD6VIiIwMF6LIa7MEwd/l9huKwmqRKXqmrkqIZv8CvRbfowL+L0ryAl8h//HfAS0zS+4SbYoRyAoA6BJA==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "flat-cache": "^6.1.22" + "flat-cache": "^6.1.23" } }, "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.22", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.22.tgz", - "integrity": "sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug==", + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "cacheable": "^2.3.4", + "cacheable": "^2.5.0", "flatted": "^3.4.2", "hookified": "^1.15.0" } }, "node_modules/stylelint/node_modules/globby": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.0.tgz", - "integrity": "sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.4.tgz", + "integrity": "sha512-c8B/VNLmxRcmqqenRA9t+9IyOjf9+V6lTxPaUJLqOCONdQkWZ0ETYgX0qbtJqPsgCNusT9MZ5Jeidw8Eb9tn2g==", "dev": true, "license": "MIT", "peer": true, @@ -13452,6 +13345,7 @@ "fast-glob": "^3.3.3", "ignore": "^7.0.5", "is-path-inside": "^4.0.0", + "micromatch": "^4.0.8", "slash": "^5.1.0", "unicorn-magic": "^0.4.0" }, @@ -13463,9 +13357,9 @@ } }, "node_modules/stylelint/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "peer": true, @@ -13530,9 +13424,9 @@ } }, "node_modules/stylelint/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "dev": true, "license": "MIT", "peer": true, @@ -13750,9 +13644,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -14247,27 +14141,67 @@ } }, "node_modules/unplugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", - "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz", + "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "picomatch": "^4.0.3", + "picomatch": "^4.0.4", "webpack-virtual-modules": "^0.6.2" }, "engines": { "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/unplugin-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", - "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", "license": "MIT", "dependencies": { "pathe": "^2.0.3", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=20.19.0" @@ -14450,7 +14384,7 @@ "version": "7.3.6", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", @@ -14582,7 +14516,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14599,7 +14532,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14616,7 +14548,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14633,7 +14564,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14650,7 +14580,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14667,7 +14596,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14684,7 +14612,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14701,7 +14628,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14718,7 +14644,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14735,7 +14660,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14752,7 +14676,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14769,7 +14692,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14786,7 +14708,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14803,7 +14724,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14820,7 +14740,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14837,7 +14756,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14854,7 +14772,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14871,7 +14788,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14888,7 +14804,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14905,7 +14820,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14922,7 +14836,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14939,7 +14852,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14956,7 +14868,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14973,7 +14884,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14990,7 +14900,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15007,7 +14916,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15021,7 +14929,7 @@ "version": "0.28.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -15261,9 +15169,9 @@ } }, "node_modules/vue-eslint-parser/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "peer": true, @@ -15284,37 +15192,39 @@ } }, "node_modules/vue-router": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.7.tgz", - "integrity": "sha512-dqfk8kvRbCutmCOCj/XLDqDEYxc1wBdAOGLuVy5M93ifYMsBd5fIjfaPN4tQAbxr5IprdBDIox1gr4wYyOx/SA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.2.0.tgz", + "integrity": "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw==", "license": "MIT", "dependencies": { - "@babel/generator": "^8.0.0-rc.4", - "@vue-macros/common": "^3.1.1", - "@vue/devtools-api": "^8.1.1", - "ast-walker-scope": "^0.8.3", + "@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", - "local-pkg": "^1.1.2", + "local-pkg": "^1.2.1", "magic-string": "^0.30.21", - "mlly": "^1.8.0", + "mlly": "^1.8.2", "muggle-string": "^0.4.1", + "nostics": "^1.1.4", "pathe": "^2.0.3", - "picomatch": "^4.0.3", + "picomatch": "^4.0.5", "scule": "^1.3.0", - "tinyglobby": "^0.2.15", - "unplugin": "^3.0.0", - "unplugin-utils": "^0.3.1", - "yaml": "^2.8.2" + "tinyglobby": "^0.2.17", + "unplugin": "^3.3.0", + "unplugin-utils": "^0.3.2", + "yaml": "^2.9.0" }, "funding": { "url": "https://github.com/sponsors/posva" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", - "@vue/compiler-sfc": "^3.5.34", - "pinia": "^3.0.4", - "vue": "^3.5.34" + "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", + "pinia": "^3.0.4 || ^4.0.2", + "vite": "^7.3.0 || ^8.0.0", + "vue": "^3.5.34 || ^4.0.0" }, "peerDependenciesMeta": { "@pinia/colada": { @@ -15325,17 +15235,20 @@ }, "pinia": { "optional": true + }, + "vite": { + "optional": true } } }, "node_modules/vue-router/node_modules/@babel/generator": { - "version": "8.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0-rc.5.tgz", - "integrity": "sha512-nFZPWz3FHIS7y6rMIVoa/WBwjdutfIaRJIBQjzn+t3RnecZoRNlGmGcyR2wb0T/IgSd50Kz/6dG8/LvMCRunjg==", + "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-rc.5", - "@babel/types": "^8.0.0-rc.5", + "@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", @@ -15346,30 +15259,30 @@ } }, "node_modules/vue-router/node_modules/@babel/helper-string-parser": { - "version": "8.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0-rc.5.tgz", - "integrity": "sha512-sN7R8rBvDurfaziNfDEIjIntlazmlkCDGO4SNl2RJ3wRCn+QxspLV7hzYAE8WWVd2joVuT8sUxeePdLp2idI1A==", + "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.0-rc.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-rc.5.tgz", - "integrity": "sha512-ehJDxHvtbZ85RtX/L2fi0h9AGsBNqB5Euv1EB8RMAvGYvD+2X+QbpzzOpbklnNXO+WSZJNOaetw2BBj27xsWVg==", + "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.0-rc.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0-rc.5.tgz", - "integrity": "sha512-/Mfg83rK3+jsRbl4Vbd0jqxc6M1A1/WNFtgrowRM1unEsD3XcNnrBdMM0JWakd0/RN9lseQKwPduW1TiEwKOlQ==", + "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.0-rc.5" + "@babel/types": "^8.0.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -15379,13 +15292,13 @@ } }, "node_modules/vue-router/node_modules/@babel/types": { - "version": "8.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0-rc.5.tgz", - "integrity": "sha512-JeSVu/m8x/zpp4CLjYHVNXuhEyOkhPXuxM8YOXjh6L4LlvQNKuUNOTo5KdBuKAcTDHw8DquToTaEkhsBqPXOaA==", + "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-rc.5", - "@babel/helper-validator-identifier": "^8.0.0-rc.5" + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" }, "engines": { "node": "^22.18.0 || >=24.11.0" @@ -15461,9 +15374,9 @@ } }, "node_modules/webdav/node_modules/fast-xml-parser": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.8.0.tgz", - "integrity": "sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.0.tgz", + "integrity": "sha512-9IGxMqvqLOnqP+Egi1nqDHKv5k8aZ7r9n558enxcucmyVGEBNPAU+MOg/8jPIS7rO7sSq4gFm1/nHtiaubMruw==", "funding": [ { "type": "github", @@ -15472,27 +15385,46 @@ ], "license": "MIT", "dependencies": { - "@nodable/entities": "^2.1.0", + "@nodable/entities": "^3.0.0", "fast-xml-builder": "^1.2.0", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.3.0", - "xml-naming": "^0.1.0" + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.2", + "xml-naming": "^0.3.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "node_modules/webdav/node_modules/strnum": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", - "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.2.tgz", + "integrity": "sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, + "node_modules/webdav/node_modules/xml-naming": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } }, "node_modules/webidl-conversions": { "version": "8.0.1", @@ -15724,9 +15656,9 @@ } }, "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index afd5dc2..9eadd25 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "@nextcloud/l10n": "^3.4.1", "@nextcloud/router": "^3.1.0", "@nextcloud/vue": "^9.3.1", - "vue": "^3.5.41" + "vue": "^3.5.41", + "vue-router": "^5.2.0" }, "devDependencies": { "@nextcloud/browserslist-config": "^3.1.2", diff --git a/src/App.spec.ts b/src/App.spec.ts index 2589b61..59e5a83 100644 --- a/src/App.spec.ts +++ b/src/App.spec.ts @@ -1,27 +1,33 @@ import { shallowMount } from '@vue/test-utils' import { describe, expect, it, vi } from 'vitest' +import { createMemoryHistory, createRouter } from 'vue-router' import App from './App.vue' -// App.vue's only child, OfficeOverview.vue, runs fetchAll() at module -// evaluation time — stub its two network-touching services so mounting -// App doesn't attempt a real fetch. +// App.vue's only child is the routed view, which fetches on setup — a +// placeholder route component keeps that out of this spec. Which view a URL +// resolves to is covered by router.spec.ts. vi.mock('./services/templates.ts', () => ({ getTemplates: vi.fn().mockResolvedValue([]), createFromTemplate: vi.fn(), })) -vi.mock('./services/officeFiles.ts', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - getAllOfficeFiles: vi.fn().mockResolvedValue({ nodes: [], truncated: false }), - invalidateOfficeFilesCache: vi.fn(), - } -}) describe('App', () => { - it('renders OfficeOverview', () => { - const wrapper = shallowMount(App) + it('renders the routed view, so the URL decides what is shown', async () => { + // Memory history rather than router.ts's own web-history instance: jsdom + // shares one real window.history across tests. + const router = createRouter({ + history: createMemoryHistory(), + routes: [{ name: 'creator', path: '/:creatorId?', component: { template: '
' } }], + }) + router.push('/') + await router.isReady() + + const wrapper = shallowMount(App, { global: { plugins: [router] } }) + + expect(wrapper.findComponent({ name: 'RouterView' }).exists()).toBe(true) - expect(wrapper.findComponent({ name: 'OfficeOverview' }).exists()).toBe(true) + // shallowMount stubs vnodes process-wide until the wrapper is unmounted; + // leaving it mounted would stub every later render in this run. + wrapper.unmount() }) }) diff --git a/src/App.vue b/src/App.vue index 296b787..e76de2d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,9 +4,9 @@ --> diff --git a/src/main.ts b/src/main.ts index 547b082..9ad1fb1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,7 @@ import { createApp } from 'vue' import App from './App.vue' +import { router } from './router.ts' const app = createApp(App) +app.use(router) app.mount('#office') diff --git a/src/router.spec.ts b/src/router.spec.ts new file mode 100644 index 0000000..07efd7f --- /dev/null +++ b/src/router.spec.ts @@ -0,0 +1,180 @@ +import { flushPromises, mount } from '@vue/test-utils' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { loadState } from '@nextcloud/initial-state' +import { createMemoryHistory, createRouter } from 'vue-router' +import { fakeLoadState, makeCreator } from './test-utils/fixtures.ts' +import { historyBase, routes } from './router.ts' +import OfficeOverview from './views/OfficeOverview.vue' +import type { VueWrapper } from '@vue/test-utils' + +// The two creators the overview gets from the templates API in this spec; their +// categories come from the initial state (see fakeLoadState). +const CREATORS = [ + makeCreator({ extension: '.odt' }), + makeCreator({ + label: 'Spreadsheet', + extension: '.ods', + mimetypes: ['application/vnd.oasis.opendocument.spreadsheet'], + }), +] + +// OfficeOverview.vue fetches on setup — stub its two network-touching services +// so importing the route table doesn't fetch. +vi.mock('./services/templates.ts', () => ({ + getTemplates: vi.fn(() => Promise.resolve(CREATORS)), + createFromTemplate: vi.fn(), +})) +vi.mock('./services/officeFiles.ts', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + getAllOfficeFiles: vi.fn(() => Promise.resolve({ nodes: [], truncated: false })), + invalidateOfficeFilesCache: vi.fn(), + } +}) + +beforeEach(() => { + vi.mocked(loadState).mockImplementation(fakeLoadState()) +}) + +// The exported route table, matched through a memory history: the real router's +// web history is tied to the app's base URL, which doesn't exist under jsdom. +const router = createRouter({ history: createMemoryHistory(), routes }) + +describe('routes', () => { + it('serves the overview at the app root, with no creator id', () => { + const resolved = router.resolve('/') + + expect(resolved.matched[0].components?.default).toBe(OfficeOverview) + expect(resolved.params.creatorId).toBeUndefined() + }) + + it('serves the overview for a creator URL and hands it the id', () => { + const resolved = router.resolve('/spreadsheets') + + expect(resolved.matched[0].components?.default).toBe(OfficeOverview) + expect(resolved.params.creatorId).toBe('spreadsheets') + }) + + it('builds a creator URL from the id', () => { + expect(router.resolve({ name: 'creator', params: { creatorId: 'diagrams' } }).path) + .toBe('/diagrams') + }) + + // PageController's catch-all serves the shell for any path under the app, so + // an unmatched one must not leave the view unmounted. + it('sends a path below the creator level back to the overview', () => { + const resolved = router.resolve('/documents/extra') + + expect(resolved.matched[0].redirect).toBe('/') + }) + + it('tolerates a trailing slash on a creator URL', () => { + const resolved = router.resolve('/documents/') + + expect(resolved.matched[0].components?.default).toBe(OfficeOverview) + expect(resolved.params.creatorId).toBe('documents') + }) +}) + +// Regression: with a base in the form the current URL does not use, the base is +// never stripped, so a creator URL matches no route and the fallback swallows it +// — the creator on the URL is then silently ignored. +describe('historyBase', () => { + it('is the app path of a rewritten URL', () => { + expect(historyBase('/apps/office/spreadsheets')).toBe('/apps/office') + }) + + it('keeps the index.php prefix of a non-rewritten URL', () => { + expect(historyBase('/index.php/apps/office/spreadsheets')).toBe('/index.php/apps/office') + }) + + it('keeps the webroot of a subdirectory install', () => { + expect(historyBase('/nextcloud/index.php/apps/office/diagrams')).toBe('/nextcloud/index.php/apps/office') + }) + + it('is the app path itself when the URL carries no creator', () => { + expect(historyBase('/apps/office')).toBe('/apps/office') + }) + + // generateUrl() is mocked to return the path unchanged (see vitest.setup.ts). + it('falls back to the generated URL for a path outside the app', () => { + expect(historyBase('/some/other/place')).toBe('/apps/office') + }) +}) + +// The router the app really runs on: web history, a base taken from the URL the +// page was loaded from, and an initial navigation that resolves after the mount. +// The specs elsewhere use a memory history and mount the view directly, which +// skips both — and that is where the creator on the URL went missing. +describe('booting at a creator URL', () => { + let wrapper: VueWrapper | null = null + + // The app's shell, stubbed to render its slots: the view's own markup (the + // category heading) is what these assert on, and the real components bring + // teleports and observers that only misbehave under jsdom. + function stubRenderingAllSlots(name: string) { + return { + name, + template: '
', + } + } + + async function bootAt(url: string) { + window.history.replaceState({}, '', url) + // router.ts reads the URL when it creates the router, so it can only be + // imported once the URL is in place. + vi.resetModules() + const { router } = await import('./router.ts') + const { default: App } = await import('./App.vue') + + wrapper = mount(App, { + global: { + plugins: [router], + stubs: { + NcContent: stubRenderingAllSlots('NcContent'), + NcAppNavigation: stubRenderingAllSlots('NcAppNavigation'), + NcAppContent: stubRenderingAllSlots('NcAppContent'), + }, + }, + }) + // Only mounting installs the router, and only that starts the first + // navigation — so this has to come after mount(), not before it. + await router.isReady() + // Two rounds: the first renders the view once the navigation resolved, the + // second lets its creator request settle so the heading is there. + await flushPromises() + await flushPromises() + return { wrapper, router } + } + + afterEach(() => { + // Also resets @vue/test-utils' process-wide vnode stubbing. + wrapper?.unmount() + wrapper = null + window.history.replaceState({}, '', '/') + }) + + it('opens the category the URL names', async () => { + const { wrapper, router } = await bootAt('/apps/office/spreadsheets') + + expect(router.currentRoute.value.params.creatorId).toBe('spreadsheets') + expect(wrapper.text()).toContain('Recent Spreadsheets') + }) + + // generateUrl() only returns the form the instance advertises, so the base has + // to follow the URL in use, not the other way round. + it('opens the category the URL names behind an index.php front controller', async () => { + const { wrapper, router } = await bootAt('/index.php/apps/office/spreadsheets') + + expect(router.currentRoute.value.params.creatorId).toBe('spreadsheets') + expect(wrapper.text()).toContain('Recent Spreadsheets') + }) + + it('falls back to the first creator at the app root, naming it on the URL', async () => { + const { wrapper, router } = await bootAt('/apps/office') + + expect(wrapper.text()).toContain('Recent Documents') + expect(router.currentRoute.value.params.creatorId).toBe('documents') + }) +}) diff --git a/src/router.ts b/src/router.ts new file mode 100644 index 0000000..69dd2e9 --- /dev/null +++ b/src/router.ts @@ -0,0 +1,47 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { generateUrl } from '@nextcloud/router' +import { createRouter, createWebHistory } from 'vue-router' +import OfficeOverview from './views/OfficeOverview.vue' +import type { RouteRecordRaw } from 'vue-router' + +// creatorId is a category id (see categoryId()). Omitted, the overview falls +// back to the first creator and rewrites the URL to its id. +export const routes: RouteRecordRaw[] = [ + { + name: 'creator', + path: '/:creatorId?', + component: OfficeOverview, + }, + { + // PageController's catch-all serves the shell for any path under the app, + // so anything deeper than a creator id has to land somewhere: the overview, + // not a blank page. + path: '/:pathMatch(.*)*', + redirect: '/', + }, +] + +const APP_PATH = '/apps/office' + +// The app answers under both /apps/office and /index.php/apps/office, while +// generateUrl() always returns the single form the instance advertises +// (modRewriteWorking). A base in the other form is never stripped off the +// pathname, so no route matches and every deep link lands on the fallback — +// hence the base comes from the URL the page was actually loaded from. +export function historyBase(pathname: string): string { + const appPathEnd = pathname.lastIndexOf(APP_PATH) + return appPathEnd === -1 + ? generateUrl(APP_PATH) + : pathname.slice(0, appPathEnd + APP_PATH.length) +} + +export const router = createRouter({ + // PageController registers a catch-all route so deep-link reloads still serve + // the app shell. + history: createWebHistory(historyBase(window.location.pathname)), + routes, +}) diff --git a/src/utils/fileCategories.spec.ts b/src/utils/fileCategories.spec.ts index f04edec..4ed47bd 100644 --- a/src/utils/fileCategories.spec.ts +++ b/src/utils/fileCategories.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { makeCreator } from '../test-utils/fixtures.ts' -import { categoryMimes, categoryName } from './fileCategories.ts' +import { categoryId, categoryMimes, categoryName, creatorById } from './fileCategories.ts' describe('categoryName', () => { it('maps a known mime to its category name', () => { @@ -14,6 +14,68 @@ describe('categoryName', () => { }) }) +describe('categoryId', () => { + it('maps a known mime to its category id', () => { + const creator = makeCreator({ mimetypes: ['application/vnd.ms-excel'] }) + expect(categoryId(creator)).toBe('spreadsheets') + }) + + // The id lands in the URL, so it must not move when the admin flips + // doc_format — the same category advertises different mimes and extensions + // then, and a bookmarked link has to keep working. + it('is the same for the ODF and the OOXML creator of one category', () => { + const odf = makeCreator({ + extension: '.odt', + mimetypes: ['application/vnd.oasis.opendocument.text'], + }) + const ooxml = makeCreator({ + extension: '.docx', + mimetypes: ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'], + }) + + expect(categoryId(odf)).toBe(categoryId(ooxml)) + }) + + it('falls back to app and extension when no mime maps to a category', () => { + const creator = makeCreator({ + app: 'customapp', + extension: '.xyz', + mimetypes: ['application/x-custom'], + }) + expect(categoryId(creator)).toBe('customapp-xyz') + }) + + // The label is translated, so it must never leak into the id. + it('does not derive the fallback id from the (localised) label', () => { + const creator = makeCreator({ label: 'Dokument', mimetypes: ['application/x-custom'] }) + expect(categoryId(creator)).not.toContain('Dokument') + }) +}) + +describe('creatorById', () => { + const documents = makeCreator({ mimetypes: ['application/vnd.oasis.opendocument.text'] }) + const spreadsheets = makeCreator({ mimetypes: ['application/vnd.oasis.opendocument.spreadsheet'] }) + + it('finds the creator whose category id matches', () => { + expect(creatorById([documents, spreadsheets], 'spreadsheets')).toBe(spreadsheets) + }) + + it('returns null for an id no creator matches', () => { + expect(creatorById([documents, spreadsheets], 'nonsense')).toBeNull() + }) + + it('returns null when there is no id at all', () => { + expect(creatorById([documents], null)).toBeNull() + }) + + // Two suites can both offer documents; the first one wins so a link always + // resolves to exactly one creator. + it('returns the first of two creators sharing a category', () => { + const other = makeCreator({ app: 'otheroffice', mimetypes: ['application/msword'] }) + expect(creatorById([documents, other], 'documents')).toBe(documents) + }) +}) + describe('categoryMimes', () => { it('unions the category\'s full ODF+OOXML mime set with the creator\'s own mimes', () => { const creator = makeCreator({ diff --git a/src/utils/fileCategories.ts b/src/utils/fileCategories.ts index 36f51c4..50ed183 100644 --- a/src/utils/fileCategories.ts +++ b/src/utils/fileCategories.ts @@ -1,21 +1,30 @@ import { translate as t } from '@nextcloud/l10n' import type { TemplateCreator } from '../services/templates.ts' +// Category ids appear in the app's URLs (/apps/office/), so they must stay +// stable and locale-independent — hence ids as keys, labels looked up by id. +const CATEGORY_LABELS: Record = { + documents: t('office', 'Documents'), + spreadsheets: t('office', 'Spreadsheets'), + presentations: t('office', 'Presentations'), + diagrams: t('office', 'Diagrams'), +} + const MIME_CATEGORIES: Record = { - 'application/vnd.oasis.opendocument.text': t('office', 'Documents'), - 'application/vnd.oasis.opendocument.text-template': t('office', 'Documents'), - 'application/msword': t('office', 'Documents'), - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': t('office', 'Documents'), - 'application/vnd.oasis.opendocument.spreadsheet': t('office', 'Spreadsheets'), - 'application/vnd.oasis.opendocument.spreadsheet-template': t('office', 'Spreadsheets'), - 'application/vnd.ms-excel': t('office', 'Spreadsheets'), - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': t('office', 'Spreadsheets'), - 'application/vnd.oasis.opendocument.presentation': t('office', 'Presentations'), - 'application/vnd.oasis.opendocument.presentation-template': t('office', 'Presentations'), - 'application/vnd.ms-powerpoint': t('office', 'Presentations'), - 'application/vnd.openxmlformats-officedocument.presentationml.presentation': t('office', 'Presentations'), - 'application/vnd.oasis.opendocument.graphics': t('office', 'Diagrams'), - 'application/vnd.oasis.opendocument.graphics-template': t('office', 'Diagrams'), + 'application/vnd.oasis.opendocument.text': 'documents', + 'application/vnd.oasis.opendocument.text-template': 'documents', + 'application/msword': 'documents', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'documents', + 'application/vnd.oasis.opendocument.spreadsheet': 'spreadsheets', + 'application/vnd.oasis.opendocument.spreadsheet-template': 'spreadsheets', + 'application/vnd.ms-excel': 'spreadsheets', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'spreadsheets', + 'application/vnd.oasis.opendocument.presentation': 'presentations', + 'application/vnd.oasis.opendocument.presentation-template': 'presentations', + 'application/vnd.ms-powerpoint': 'presentations', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'presentations', + 'application/vnd.oasis.opendocument.graphics': 'diagrams', + 'application/vnd.oasis.opendocument.graphics-template': 'diagrams', } // Every office mimetype we can open, regardless of the configured create format @@ -24,11 +33,31 @@ const MIME_CATEGORIES: Record = { // from this full set instead — otherwise existing ODF files would never be found. export const ALL_OFFICE_MIMES = Object.keys(MIME_CATEGORIES) -export function categoryName(creator: TemplateCreator): string { +function creatorCategory(creator: TemplateCreator): string | null { for (const mime of (creator.mimetypes ?? [])) { if (MIME_CATEGORIES[mime]) return MIME_CATEGORIES[mime] } - return creator.label + return null +} + +export function categoryName(creator: TemplateCreator): string { + const category = creatorCategory(creator) + return category ? CATEGORY_LABELS[category] : creator.label +} + +// The creator's identity on the URL. A category id survives a doc_format switch, +// which changes the creator's extension and mimetypes, so bookmarked links keep +// working. Creators outside the static map fall back to app + extension. +export function categoryId(creator: TemplateCreator): string { + return creatorCategory(creator) + ?? `${creator.app}-${creator.extension.replace(/^\./, '')}` +} + +// Two creators can share a category (two suites both offering documents): the +// navigation lists both, the URL addresses the first. +export function creatorById(creators: TemplateCreator[], id: string | null): TemplateCreator | null { + if (!id) return null + return creators.find(creator => categoryId(creator) === id) ?? null } // All mimetypes belonging to the creator's category (both ODF and OOXML), so a @@ -36,7 +65,9 @@ export function categoryName(creator: TemplateCreator): string { // The creator's own mimes are always kept, so anything it advertises beyond our // static map (and any creator mapping to no known category) is still covered. export function categoryMimes(creator: TemplateCreator): string[] { - const category = categoryName(creator) - const fromCategory = ALL_OFFICE_MIMES.filter(mime => MIME_CATEGORIES[mime] === category) + const category = creatorCategory(creator) + const fromCategory = category + ? ALL_OFFICE_MIMES.filter(mime => MIME_CATEGORIES[mime] === category) + : [] return [...new Set([...fromCategory, ...creator.mimetypes])] } diff --git a/src/views/OfficeOverview.spec.ts b/src/views/OfficeOverview.spec.ts index 9f233ff..96c9ce2 100644 --- a/src/views/OfficeOverview.spec.ts +++ b/src/views/OfficeOverview.spec.ts @@ -2,8 +2,10 @@ import { flushPromises, shallowMount } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' import { getCurrentUser } from '@nextcloud/auth' import { loadState } from '@nextcloud/initial-state' +import { createMemoryHistory, createRouter } from 'vue-router' import { makeCreator, makeNode } from '../test-utils/fixtures.ts' import type { Node } from '@nextcloud/files' +import type { Router } from 'vue-router' const getTemplatesMock = vi.fn() const createFromTemplateMock = vi.fn() @@ -50,6 +52,25 @@ const NC_DIALOG_STUB = { template: '
', } +// OfficeOverview derives the active creator from the route, so every mount needs +// a router. Memory history keeps jsdom's single shared window.history — and the +// app's /apps/office base, which doesn't exist there — out of it. The record +// mirrors src/router.ts's, with a placeholder component since the view under +// test is mounted directly rather than through RouterView. Module-level so tests +// can assert on the URL the view navigated to. +let router: Router + +function createTestRouter(): Router { + return createRouter({ + history: createMemoryHistory(), + routes: [{ + name: 'creator', + path: '/:creatorId?', + component: { template: '
' }, + }], + }) +} + // Module-level side effects (getCurrentUser()?.uid, fetchAll(), loadState()) run // at import time, so mocks must be configured before each fresh dynamic import. // vi.resetModules() + a fresh dynamic import are required per test to avoid @@ -62,11 +83,15 @@ const NC_DIALOG_STUB = { // extraStubs merges in on top of the defaults below — used by tests that need // a named slot rendered on a component that's normally left as a plain // shallow stub (e.g. NcListItem's #icon, FileCard's #preview). -async function mountOverview(extraStubs: Record = {}) { +async function mountOverview(extraStubs: Record = {}, path = '/') { vi.resetModules() + router = createTestRouter() + router.push(path) + await router.isReady() const { default: OfficeOverview } = await import('./OfficeOverview.vue') const wrapper = shallowMount(OfficeOverview, { global: { + plugins: [router], stubs: { NcDialog: NC_DIALOG_STUB, NcAppNavigation: stubRenderingAllSlots('NcAppNavigation'), @@ -123,8 +148,13 @@ describe('OfficeOverview > rendering states', () => { getAllOfficeFilesMock.mockResolvedValue(officeFilesResult([])) vi.resetModules() + // No isReady() here, unlike mountOverview: the router's first navigation + // only starts when the plugin is installed at mount, and this test asserts + // on the state before that resolves — with no route resolved there is no + // creator id, so the view shows the loading spinner. + router = createTestRouter() const { default: OfficeOverview } = await import('./OfficeOverview.vue') - const wrapper = shallowMount(OfficeOverview) + const wrapper = shallowMount(OfficeOverview, { global: { plugins: [router] } }) expect(wrapper.findComponent({ name: 'NcLoadingIcon' }).exists()).toBe(true) expect(wrapper.findComponent({ name: 'NcEmptyContent' }).exists()).toBe(false) @@ -211,6 +241,104 @@ describe('OfficeOverview > rendering states', () => { }) }) +describe('OfficeOverview > creator on the URL', () => { + const documents = makeCreator({ + label: 'Document', + extension: '.odt', + mimetypes: ['application/vnd.oasis.opendocument.text'], + }) + const spreadsheets = makeCreator({ + label: 'Spreadsheet', + extension: '.ods', + mimetypes: ['application/vnd.oasis.opendocument.spreadsheet'], + }) + + function mountWithBothCategories(path: string) { + getTemplatesMock.mockResolvedValue([documents, spreadsheets]) + getAllOfficeFilesMock.mockResolvedValue(officeFilesResult([ + makeNode({ owner: 'alice', basename: 'report.odt' }), + makeNode({ owner: 'alice', basename: 'budget.ods', mime: 'application/vnd.oasis.opendocument.spreadsheet' }), + ])) + return mountOverview({}, path) + } + + it('opens the category named by the URL, not the first one', async () => { + const wrapper = await mountWithBothCategories('/spreadsheets') + + expect(wrapper.text()).toContain('Recent Spreadsheets') + expect(wrapper.findComponent({ name: 'NcListItem' }).props('name')).toBe('budget.ods') + }) + + it('rewrites the URL to the first creator when it names no creator', async () => { + await mountWithBothCategories('/') + + expect(router.currentRoute.value.params.creatorId).toBe('documents') + }) + + // A stale bookmark or an uninstalled suite must not leave an empty page: fall + // back to the first creator and correct the address to match what's shown. + it('falls back to the first creator and rewrites the URL for an unknown id', async () => { + const wrapper = await mountWithBothCategories('/typewriters') + + expect(wrapper.text()).toContain('Recent Documents') + expect(router.currentRoute.value.params.creatorId).toBe('documents') + }) + + it('replaces rather than pushes the corrected URL, so Back leaves the app', async () => { + await mountWithBothCategories('/') + + // One entry: the corrected URL took the place of the one we opened. + expect(window.history.length).toBe(1) + }) + + it('follows a route change (link click, back/forward) to the other category', async () => { + const wrapper = await mountWithBothCategories('/documents') + + await router.push('/spreadsheets') + await flushPromises() + + expect(wrapper.text()).toContain('Recent Spreadsheets') + }) + + it('clears an active search when the route switches category', async () => { + const wrapper = await mountWithBothCategories('/documents') + await wrapper.findComponent({ name: 'NcAppNavigationSearch' }).vm.$emit('update:modelValue', 'report') + await flushPromises() + expect(wrapper.findComponent({ name: 'NcAppNavigationSearch' }).props('modelValue')).toBe('report') + + await router.push('/spreadsheets') + await flushPromises() + + expect(wrapper.findComponent({ name: 'NcAppNavigationSearch' }).props('modelValue')).toBe('') + }) + + it('gives every navigation entry a link to its own creator URL', async () => { + const wrapper = await mountWithBothCategories('/documents') + + const items = wrapper.findAllComponents({ name: 'NcAppNavigationItem' }) + expect(items.map(item => item.props('to'))).toEqual([ + { name: 'creator', params: { creatorId: 'documents' } }, + { name: 'creator', params: { creatorId: 'spreadsheets' } }, + ]) + }) + + it('marks only the routed entry as active', async () => { + const wrapper = await mountWithBothCategories('/spreadsheets') + + const items = wrapper.findAllComponents({ name: 'NcAppNavigationItem' }) + expect(items.map(item => item.props('active'))).toEqual([false, true]) + }) + + it('leaves the URL alone while no creator is loaded yet', async () => { + getTemplatesMock.mockResolvedValue([]) + getAllOfficeFilesMock.mockResolvedValue(officeFilesResult([])) + + await mountOverview({}, '/') + + expect(router.currentRoute.value.params.creatorId).toBeUndefined() + }) +}) + describe('OfficeOverview > preview thumbnails', () => { // FilePreview's own rendering (image src, error-to-icon fallback) is // covered by FilePreview.spec.ts in isolation. These tests are wiring diff --git a/src/views/OfficeOverview.vue b/src/views/OfficeOverview.vue index 15702db..e150682 100644 --- a/src/views/OfficeOverview.vue +++ b/src/views/OfficeOverview.vue @@ -5,6 +5,7 @@