From 13981b9daa3d537d86386490137142647b0d17f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 10:23:02 +0200 Subject: [PATCH 01/14] feat(viewer): migrate to @nextcloud/viewer package API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Register with registerHandler() from @nextcloud/viewer instead of the OCA.Viewer.registerHandler() global, which goes away with the viewer app (nextcloud/server#63954). The handler's element is text-viewer, defined in onInit, so the script loaded on every page only registers the handler and the editor loads when a text file opens. The wrapper maps the viewer's file to the existing ViewerComponent props and turns swiping off, so selecting text does not step to the next file. The listener moves from OCA\Viewer\Event\LoadViewer to BeforeTemplateRenderedEvent, skipping error pages like the server's own viewer listener. The empty text-viewer stylesheet is no longer loaded, and the Vue 2 ViewerView wrapper and the LoadViewer stub are gone. downloadCallback has no equivalent in the new API and is dropped. Assisted-by: ClaudeCode:claude-sonnet-4-6 Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- lib/AppInfo/Application.php | 4 +- lib/Listeners/LoadViewerListener.php | 26 +- package-lock.json | 959 ++++++++++++++++++++++----- package.json | 1 + src/tests/viewer.spec.ts | 51 ++ src/viewer.js | 65 +- src/views/TextViewerWrapper.vue | 39 ++ src/views/ViewerView.js | 44 -- tests/stub.php | 5 - 9 files changed, 922 insertions(+), 272 deletions(-) create mode 100644 src/tests/viewer.spec.ts create mode 100644 src/views/TextViewerWrapper.vue delete mode 100644 src/views/ViewerView.js diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index cd1c8f2c703..213bc543ccf 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -28,11 +28,11 @@ use OCA\Text\Middleware\SessionMiddleware; use OCA\Text\Notification\Notifier; use OCA\TpAssistant\Event\BeforeAssistantNotificationEvent; -use OCA\Viewer\Event\LoadViewer; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent as HttpBeforeTemplateRenderedEvent; use OCP\DB\Events\AddMissingIndicesEvent; use OCP\DirectEditing\RegisterDirectEditorEvent; use OCP\Files\Events\Node\BeforeNodeDeletedEvent; @@ -53,7 +53,7 @@ public function register(IRegistrationContext $context): void { $context->registerConfigLexicon(\OCA\Text\ConfigLexicon::class); $context->registerEventListener(RegisterDirectEditorEvent::class, RegisterDirectEditorEventListener::class); - $context->registerEventListener(LoadViewer::class, LoadViewerListener::class); + $context->registerEventListener(HttpBeforeTemplateRenderedEvent::class, LoadViewerListener::class); $context->registerEventListener(LoadAdditionalScriptsEvent::class, FilesLoadAdditionalScriptsListener::class); $context->registerEventListener(BeforeTemplateRenderedEvent::class, FilesSharingLoadAdditionalScriptsListener::class); $context->registerEventListener(LoadEditor::class, LoadEditorListener::class); diff --git a/lib/Listeners/LoadViewerListener.php b/lib/Listeners/LoadViewerListener.php index a5b4872c708..4a9fe962f3a 100644 --- a/lib/Listeners/LoadViewerListener.php +++ b/lib/Listeners/LoadViewerListener.php @@ -8,30 +8,24 @@ namespace OCA\Text\Listeners; -use OCA\Text\Service\InitialStateProvider; -use OCA\Viewer\Event\LoadViewer; -use OCP\Collaboration\Reference\RenderReferenceEvent; +use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; +use OCP\AppFramework\Http\TemplateResponse; use OCP\EventDispatcher\Event; -use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventListener; use OCP\Util; -/** @implements IEventListener */ +/** @implements IEventListener */ class LoadViewerListener implements IEventListener { - public function __construct( - private readonly InitialStateProvider $initialStateProvider, - private readonly IEventDispatcher $eventDispatcher, - ) { - } - public function handle(Event $event): void { - if (!$event instanceof LoadViewer) { + if (!($event instanceof BeforeTemplateRenderedEvent)) { + return; + } + + if ($event->getResponse()->getRenderAs() === TemplateResponse::RENDER_AS_ERROR) { return; } - Util::addScript('text', 'text-viewer', 'viewer'); - Util::addStyle('text', 'text-viewer'); - $this->eventDispatcher->dispatchTyped(new RenderReferenceEvent()); - $this->initialStateProvider->provideState(); + // Only registers the handler: the editor loads when a text file opens + Util::addInitScript('text', 'text-viewer'); } } diff --git a/package-lock.json b/package-lock.json index 2eafc9eb042..4f3efb35ab3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@nextcloud/notify_push": "^1.4.0", "@nextcloud/router": "^3.2.0", "@nextcloud/sharing": "^0.4.0", + "@nextcloud/viewer": "^2.0.0-beta.15", "@nextcloud/vue": "^9.13.1", "@quartzy/markdown-it-mentions": "^0.2.0", "@tiptap/core": "3.31.3", @@ -2754,7 +2755,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2771,7 +2771,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2788,7 +2787,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2805,7 +2803,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2822,7 +2819,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2839,7 +2835,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2856,7 +2851,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2873,7 +2867,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2890,7 +2883,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2907,7 +2899,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2924,7 +2915,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2941,7 +2931,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2958,7 +2947,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2975,7 +2963,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2992,7 +2979,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3009,7 +2995,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3026,7 +3011,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3043,7 +3027,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3060,7 +3043,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3077,7 +3059,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3094,7 +3075,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3111,7 +3091,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3128,7 +3107,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3145,7 +3123,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3162,7 +3139,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3179,7 +3155,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3662,6 +3637,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -4246,6 +4234,24 @@ "node": "^24.0.0 || >=26.0.0" } }, + "node_modules/@nextcloud/image-editor": { + "version": "1.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@nextcloud/image-editor/-/image-editor-1.0.0-beta.7.tgz", + "integrity": "sha512-JZG4HCX3XMm2hc54xvEs8xPo60q4g+qvevJu7WWEAqeGTK3erl6q70224OhGC7gDijMZE+kC753WKpJLnYBMwA==", + "license": "AGPL-3.0-or-later", + "dependencies": { + "@nextcloud/l10n": "^3.4.0", + "konva": "~10.3.2" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + }, + "peerDependencies": { + "@nextcloud/dialogs": "^7.0.0", + "@nextcloud/vue": "^9.0.0", + "vue": "^3.5.0" + } + }, "node_modules/@nextcloud/initial-state": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@nextcloud/initial-state/-/initial-state-3.0.0.tgz", @@ -4357,6 +4363,40 @@ "node": "^20.0.0 || ^22.0.0 || ^24.0.0" } }, + "node_modules/@nextcloud/viewer": { + "version": "2.0.0-beta.15", + "resolved": "https://registry.npmjs.org/@nextcloud/viewer/-/viewer-2.0.0-beta.15.tgz", + "integrity": "sha512-kRLwIWB50B/2IbyGqYlNJIDxpnFt1yyI7zV3Qps+Q2mp9F0I/Nnw/OBkm0NNbDP3PL5o4tcVVq/4Psfuw2izag==", + "license": "AGPL-3.0-or-later", + "dependencies": { + "@mdi/svg": "^7.4.47", + "@nextcloud/image-editor": "^1.0.0-beta.6", + "@nextcloud/logger": "^3.0.3", + "@skjnldsv/vue-plyr": "^7.5.0", + "debounce": "^3.0.0", + "dompurify": "^3.4.14", + "opensheetmusicdisplay": "^2.1.3", + "vue-material-design-icons": "^5.3.1", + "webdav": "^5.10.0" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + }, + "peerDependencies": { + "@nextcloud/axios": "^2.5.2", + "@nextcloud/capabilities": "^1.2.1", + "@nextcloud/dialogs": "^7.5.0", + "@nextcloud/event-bus": "^3.3.3", + "@nextcloud/files": "^4.0.0", + "@nextcloud/initial-state": "^3.0.0", + "@nextcloud/l10n": "^3.4.1", + "@nextcloud/paths": "^3.1.0", + "@nextcloud/router": "^3.1.0", + "@nextcloud/sharing": "^0.4.0 || ^1.0.0-beta.2", + "@nextcloud/vue": "^9.13.0", + "vue": "^3.5.0" + } + }, "node_modules/@nextcloud/vite-config": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/@nextcloud/vite-config/-/vite-config-2.5.4.tgz", @@ -4564,7 +4604,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -4605,7 +4644,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4627,7 +4665,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4649,7 +4686,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4671,7 +4707,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4693,7 +4728,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4715,7 +4749,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4737,7 +4770,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4759,7 +4791,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4781,7 +4812,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4803,7 +4833,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4825,7 +4854,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4847,7 +4875,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4869,7 +4896,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5067,7 +5093,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5081,7 +5106,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5095,7 +5119,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5109,7 +5132,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5123,7 +5145,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5137,7 +5158,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5151,7 +5171,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5165,7 +5184,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5179,7 +5197,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5193,7 +5210,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5207,7 +5223,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5221,7 +5236,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5235,7 +5249,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5249,7 +5262,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5263,7 +5275,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5277,7 +5288,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5291,7 +5301,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5318,7 +5327,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5332,7 +5340,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5346,7 +5353,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5360,7 +5366,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5374,7 +5379,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5388,7 +5392,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5402,7 +5405,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5606,6 +5608,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@skjnldsv/vue-plyr": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@skjnldsv/vue-plyr/-/vue-plyr-7.5.0.tgz", + "integrity": "sha512-JmbKPe8Eh0yTP+7w4VYeqGhrUqDKM4qSsCn37CbADAvvwMvceBwYC0IBXil0RBtyzStxwXPtOj1oI//lQ5DeTw==", + "license": "MIT", + "dependencies": { + "plyr": "^3.7.8", + "vue": "^3.3.4" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0", + "npm": "^9.0.0 || ^10.0.0" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -6613,7 +6629,7 @@ "version": "24.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -6665,6 +6681,12 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@types/vexflow": { + "version": "1.2.42", + "resolved": "https://registry.npmjs.org/@types/vexflow/-/vexflow-1.2.42.tgz", + "integrity": "sha512-bj3If1sm1FYJN0tJMV2BJNrwoeQ6xfrTt+rbmFvUytyxUhklLlW79MR4uwQ+upzrNJLVy012TnC7oZqSpEglSw==", + "license": "MIT" + }, "node_modules/@types/web-bluetooth": { "version": "0.0.21", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", @@ -8136,7 +8158,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -8186,6 +8208,16 @@ "require-from-string": "^2.0.2" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/birpc": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", @@ -8195,11 +8227,18 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/bit-twiddle": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bit-twiddle/-/bit-twiddle-1.0.2.tgz", + "integrity": "sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==", + "license": "MIT", + "optional": true + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -8254,7 +8293,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -8470,7 +8509,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -8760,7 +8799,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/chrome-remote-interface": { @@ -9128,7 +9167,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, "license": "MIT" }, "node_modules/cose-base": { @@ -9293,6 +9331,12 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/custom-event-polyfill": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz", + "integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==", + "license": "MIT" + }, "node_modules/cypress": { "version": "16.1.0", "resolved": "https://registry.npmjs.org/cypress/-/cypress-16.1.0.tgz", @@ -10004,6 +10048,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -10037,6 +10097,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -10203,7 +10273,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, "license": "Apache-2.0", "optional": true, "peer": true, @@ -10665,7 +10734,7 @@ "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -10697,6 +10766,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", @@ -11429,6 +11508,16 @@ "node": ">=4" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -11439,6 +11528,13 @@ "node": ">=12.0.0" } }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0", + "optional": true + }, "node_modules/exsolve": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", @@ -11647,6 +11743,13 @@ "flat-cache": "^6.1.23" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT", + "optional": true + }, "node_modules/filing-cabinet": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-5.2.0.tgz", @@ -11716,7 +11819,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -12209,7 +12312,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fs-extra": { @@ -12232,7 +12335,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, @@ -12384,7 +12486,7 @@ "version": "4.13.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -12413,6 +12515,32 @@ "url": "https://github.com/fisker/git-hooks-list?sponsor=1" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT", + "optional": true + }, + "node_modules/gl": { + "version": "9.0.0-rc.10", + "resolved": "https://registry.npmjs.org/gl/-/gl-9.0.0-rc.10.tgz", + "integrity": "sha512-G6lYaWoan0d2d8UO0UmaSS8zqyyZwYt6q2dFVJ2hD62sRWUwkMIH+Jp7gEoQesLlo28Nzelh+GIYz1qOUa5WmQ==", + "hasInstallScript": true, + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "bindings": "^1.5.0", + "bit-twiddle": "^1.0.2", + "glsl-tokenizer": "^2.1.5", + "nan": "^2.26.2", + "node-gyp": "^12.2.0", + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -12533,6 +12661,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/glsl-tokenizer": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/glsl-tokenizer/-/glsl-tokenizer-2.1.5.tgz", + "integrity": "sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA==", + "license": "MIT", + "optional": true, + "dependencies": { + "through2": "^0.6.3" + } + }, "node_modules/gonzales-pe": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", @@ -12565,7 +12703,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/hachure-fill": { @@ -12954,7 +13092,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -12981,11 +13119,17 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/immutable": { "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 }, @@ -13025,7 +13169,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -13238,7 +13381,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", "engines": { "node": ">=0.10.0" @@ -13278,7 +13421,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", "dependencies": { "is-extglob": "^2.1.1" @@ -13348,7 +13491,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -13963,36 +14106,84 @@ "verror": "1.10.0" } }, - "node_modules/katex": { - "version": "0.18.9", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.18.9.tgz", - "integrity": "sha512-8ad9RyoKsb/g8/yLFE+KAlP+DhbCTRUNi/V9XGsxn0R+trJJltNwzcDNo0q/DEkOy5fQUQTAQyCXCYSE+OakTQ==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { - "commander": "^15.0.0" - }, - "bin": { - "katex": "cli.js" + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" } }, - "node_modules/katex/node_modules/commander": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", - "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", - "license": "MIT", - "engines": { - "node": ">=22.12.0" - } + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" }, - "node_modules/keyv": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", - "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", - "dev": true, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/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==", + "license": "MIT" + }, + "node_modules/jszip/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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/katex": { + "version": "0.18.9", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.18.9.tgz", + "integrity": "sha512-8ad9RyoKsb/g8/yLFE+KAlP+DhbCTRUNi/V9XGsxn0R+trJJltNwzcDNo0q/DEkOy5fQUQTAQyCXCYSE+OakTQ==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^15.0.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, "license": "MIT", "dependencies": { "@keyv/serialize": "^1.1.1" @@ -14009,6 +14200,38 @@ "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", "license": "MIT" }, + "node_modules/konva": { + "version": "10.3.3", + "resolved": "https://registry.npmjs.org/konva/-/konva-10.3.3.tgz", + "integrity": "sha512-j3Rrn504bYFwJ0zV0ZbSOQRsM9KMBLOpEq6xZuyIjbaKeFbBXwgiM5SA4IDq4nwM1l49GVKs66Mo+sl7+4G7PQ==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/lavrton" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/konva" + }, + { + "type": "github", + "url": "https://github.com/sponsors/lavrton" + } + ], + "license": "MIT", + "peerDependencies": { + "canvas": "^3.0.0", + "skia-canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + }, + "skia-canvas": { + "optional": true + } + } + }, "node_modules/layerr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/layerr/-/layerr-3.0.0.tgz", @@ -14066,6 +14289,15 @@ "url": "https://github.com/sponsors/dmonad" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/linkify-it": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-6.1.0.tgz", @@ -14194,6 +14426,12 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/loadjs": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loadjs/-/loadjs-4.3.0.tgz", + "integrity": "sha512-vNX4ZZLJBeDEOBvdr2v/F+0aN5oMuPu7JTqrMwp+DtgK+AryOlpy6Xtm2/HpNr+azEa828oQjOtWsB6iDtSfSQ==", + "license": "MIT" + }, "node_modules/local-pkg": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", @@ -14417,6 +14655,19 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -15381,7 +15632,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -15395,7 +15646,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -15469,6 +15720,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -15502,7 +15766,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15512,12 +15776,25 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, + "devOptional": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/mitt": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", @@ -15528,7 +15805,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/mlly": { @@ -15630,7 +15907,6 @@ "version": "2.28.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.28.0.tgz", "integrity": "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==", - "dev": true, "license": "MIT", "optional": true }, @@ -15652,6 +15928,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT", + "optional": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -15675,11 +15958,36 @@ "integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==", "license": "MIT" }, + "node_modules/node-abi": { + "version": "3.97.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.97.0.tgz", + "integrity": "sha512-6Pu8F0eXGZlyz0EepPJrWgWDcBI8rKt2Wj8exOJU1tjVbu/VyZy5ZaLYS5N+Jp/Y3IP3sIRKcIOElTu7uC6KKA==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-addon-api": { "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 @@ -15721,6 +16029,96 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "license": "ISC", + "optional": true, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/node-releases": { "version": "2.0.54", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", @@ -15924,7 +16322,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -15946,6 +16344,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opensheetmusicdisplay": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/opensheetmusicdisplay/-/opensheetmusicdisplay-2.2.0.tgz", + "integrity": "sha512-KUJ1OXhjGZP3n1ZTkLoEotL7+g4pQts9jSICY1I1WvShPI7F4Lr6G9Hqmmaoo1jNAaQB89Zy2yOC+mEquv6OuQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/vexflow": "^1.2.38", + "jszip": "3.10.1", + "loglevel": "^1.8.0", + "typescript-collections": "^1.3.3", + "vexflow": "1.2.93" + }, + "optionalDependencies": { + "gl": "^9.0.0-rc.10" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -16067,7 +16481,6 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, "license": "(MIT AND Zlib)" }, "node_modules/parse-asn1": { @@ -16402,6 +16815,23 @@ "node": ">=4" } }, + "node_modules/plyr": { + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/plyr/-/plyr-3.8.5.tgz", + "integrity": "sha512-SqGU1s76fq4lI8ABYhi1uxwrcLGYqmyiMazFAUIOQKKwt6S7FfflEMljLaM3SV8HUUb1KdZ04FY/SNFRpPZHUw==", + "deprecated": "Plyr is deprecated and receives security updates only. Use Video.js 10 for new projects. Migration guide: https://videojs.org/docs/framework/html/guides/migrate-from-plyr", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "custom-event-polyfill": "^1.0.7", + "loadjs": "^4.3.0", + "rangetouch": "^2.0.1", + "url-polyfill": "^1.1.14" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, "node_modules/points-on-curve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", @@ -16488,6 +16918,44 @@ "postcss": "^8.2.9" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/precinct": { "version": "12.2.0", "resolved": "https://registry.npmjs.org/precinct/-/precinct-12.2.0.tgz", @@ -16565,6 +17033,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -16579,7 +17057,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, "license": "MIT" }, "node_modules/property-information": { @@ -16906,7 +17383,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -17060,11 +17537,50 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/rangetouch": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/rangetouch/-/rangetouch-2.0.1.tgz", + "integrity": "sha512-sln+pNSc8NGaHoLzwNBssFSf/rSYkqeBXzX1AtJlkJiUaVSJSbRAWJk+4omsXkN+EJalzkZhWQ3th1m0FpR5xA==", + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC", + "optional": true + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -17431,7 +17947,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -17782,7 +18298,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -17851,7 +18366,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -17896,7 +18411,7 @@ "version": "1.94.2", "resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz", "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -17945,7 +18460,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -17962,7 +18477,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "engines": { @@ -18047,7 +18562,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true, "license": "MIT" }, "node_modules/sha.js": { @@ -18255,6 +18769,53 @@ "node": ">=14.16" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -18646,7 +19207,7 @@ "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==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -18915,11 +19476,28 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -18932,7 +19510,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "bl": "^4.0.3", @@ -18945,6 +19523,26 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=18" + } + }, "node_modules/teex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", @@ -18975,6 +19573,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==", + "license": "MIT", + "optional": true, + "dependencies": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/through2/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT", + "optional": true + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "license": "MIT", + "optional": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT", + "optional": true + }, "node_modules/timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", @@ -19091,7 +19727,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -19221,7 +19857,7 @@ "version": "4.20.6", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "esbuild": "~0.25.0", @@ -19258,7 +19894,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" @@ -19316,6 +19952,12 @@ "node": ">=14.17" } }, + "node_modules/typescript-collections": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/typescript-collections/-/typescript-collections-1.3.3.tgz", + "integrity": "sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ==", + "license": "MIT" + }, "node_modules/typescript-eslint": { "version": "8.62.1", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.1.tgz", @@ -19362,7 +20004,7 @@ "version": "6.29.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=18.17" @@ -19372,7 +20014,7 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -19701,6 +20343,12 @@ "requires-port": "^1.0.0" } }, + "node_modules/url-polyfill": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/url-polyfill/-/url-polyfill-1.1.14.tgz", + "integrity": "sha512-p4f3TTAG6ADVF3mwbXw7hGw+QJyw5CnNGvYh5fCuQQZIiuKUswqcznyV3pGDP9j0TSmC4UvRKm8kl1QsX1diiQ==", + "license": "MIT" + }, "node_modules/url/node_modules/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -19726,7 +20374,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/uuid": { @@ -19771,6 +20418,12 @@ "extsprintf": "^1.2.0" } }, + "node_modules/vexflow": { + "version": "1.2.93", + "resolved": "https://registry.npmjs.org/vexflow/-/vexflow-1.2.93.tgz", + "integrity": "sha512-LwHQDCc257Lwju35BhyZuPYcVWu0hIUqEdM7j9+B+bq91bSelssnAG5JR8odTUtgGuwwvGwLhXw37wtmHNCS6Q==", + "license": "MIT" + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -19960,7 +20613,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -19977,7 +20629,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -19994,7 +20645,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20011,7 +20661,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20028,7 +20677,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20045,7 +20693,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20062,7 +20709,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20079,7 +20725,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20096,7 +20741,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20113,7 +20757,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20130,7 +20773,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20147,7 +20789,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20164,7 +20805,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20181,7 +20821,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20198,7 +20837,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20215,7 +20853,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20232,7 +20869,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20249,7 +20885,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20266,7 +20901,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20283,7 +20917,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20300,7 +20933,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20317,7 +20949,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20334,7 +20965,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20351,7 +20981,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20368,7 +20997,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20385,7 +21013,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -20995,7 +21622,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/ws": { @@ -21056,7 +21683,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.4" diff --git a/package.json b/package.json index fae02f62cb1..41caaf660eb 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@nextcloud/notify_push": "^1.4.0", "@nextcloud/router": "^3.2.0", "@nextcloud/sharing": "^0.4.0", + "@nextcloud/viewer": "^2.0.0-beta.15", "@nextcloud/vue": "^9.13.1", "@quartzy/markdown-it-mentions": "^0.2.0", "@tiptap/core": "3.31.3", diff --git a/src/tests/viewer.spec.ts b/src/tests/viewer.spec.ts new file mode 100644 index 00000000000..da3f145fe47 --- /dev/null +++ b/src/tests/viewer.spec.ts @@ -0,0 +1,51 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { IHandler } from '@nextcloud/viewer' + +import { beforeAll, describe, expect, it, vi } from 'vitest' + +const registerHandler = vi.hoisted(() => vi.fn()) +vi.mock('@nextcloud/viewer', () => ({ registerHandler })) +// The editor itself is not what is tested here, only that it is loaded late +vi.mock('../views/TextViewerWrapper.vue', async () => { + const { defineComponent } = await import('vue') + return { default: defineComponent({ render: () => null }) } +}) + +describe('the text viewer handler', () => { + let handler: IHandler + + beforeAll(async () => { + await import('../viewer.js') + // Read now: the test setup clears mocks between tests + expect(registerHandler).toHaveBeenCalledTimes(1) + handler = registerHandler.mock.calls[0][0] + }) + + it('is registered under a tag name starting with the app id', () => { + expect(handler).toMatchObject({ id: 'text', tagName: 'text-viewer', canCompare: true }) + }) + + it.each(['text/markdown', 'text/plain', 'application/json'])('opens %s', (mime) => { + expect(handler.enabled([{ mime }] as never)).toBe(true) + }) + + it('does not open a picture, nor a mix with one', () => { + expect(handler.enabled([{ mime: 'image/png' }] as never)).toBe(false) + expect(handler.enabled([{ mime: 'text/markdown' }, { mime: 'image/png' }] as never)).toBe(false) + }) + + // The registration runs on every page, the editor only once a text file opens + it('defines its element only in onInit', async () => { + expect(window.customElements.get('text-viewer')).toBeUndefined() + + await handler.onInit!() + expect(window.customElements.get('text-viewer')).toBeDefined() + + // A second call, from another copy of the viewer, must not throw + await expect(handler.onInit!()).resolves.toBeUndefined() + }) +}) diff --git a/src/viewer.js b/src/viewer.js index 7a0117c3245..ce4bcc3e536 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1,46 +1,33 @@ -import { logger } from './helpers/logger.ts' -import { openMimetypesMarkdown, openMimetypesPlainText } from './helpers/mime.js' - /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ + +import { t } from '@nextcloud/l10n' +import { registerHandler } from '@nextcloud/viewer' +import { openMimetypesMarkdown, openMimetypesPlainText } from './helpers/mime.js' + import 'vite/modulepreload-polyfill' -/** - * Wrapper for async registration of ViewerComponent. - * Note: it should be named function - the name is used for component registration. - * - * @return {Promise} ViewerComponent - */ -function AsyncTextViewerComponent() { - return import('./views/ViewerView.js') -} +const tagName = 'text-viewer' +const mimes = new Set([...openMimetypesMarkdown, ...openMimetypesPlainText]) -if (typeof OCA.Viewer === 'undefined') { - logger.error('Viewer app is not installed') -} else { - OCA.Viewer.registerHandler({ - id: 'text', - mimes: [...openMimetypesMarkdown, ...openMimetypesPlainText], - component: AsyncTextViewerComponent, - group: null, - theme: 'default', - canCompare: true, - downloadCallback: async (fileInfo) => { - // Save any unsaved changes before download - const editors = window.OCA?.Text?.editorComponents - if (editors instanceof Set) { - for (const editor of editors) { - if (editor?.fileId === fileInfo.fileid && editor?.dirty) { - logger.debug('Saving file before download', { - fileId: fileInfo.fileid, - }) - await editor.save() - return - } - } - } - }, - }) -} +// This script runs on every page: the editor is only loaded, and its element +// defined, the first time the viewer opens a text file +registerHandler({ + id: 'text', + displayName: t('text', 'Text'), + tagName, + enabled: (nodes) => nodes.every((node) => mimes.has(node.mime ?? '')), + onInit: async () => { + const [{ defineCustomElement }, { default: TextViewerWrapper }] = await Promise.all([ + import('vue'), + import('./views/TextViewerWrapper.vue'), + ]) + if (window.customElements.get(tagName) === undefined) { + window.customElements.define(tagName, defineCustomElement(TextViewerWrapper, { shadowRoot: false })) + } + }, + theme: 'default', + canCompare: true, +}) diff --git a/src/views/TextViewerWrapper.vue b/src/views/TextViewerWrapper.vue new file mode 100644 index 00000000000..95213a15c0f --- /dev/null +++ b/src/views/TextViewerWrapper.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/views/ViewerView.js b/src/views/ViewerView.js deleted file mode 100644 index 6e5e4529992..00000000000 --- a/src/views/ViewerView.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { createApp, defineComponent } from 'vue' -import ViewerComponent from '../components/ViewerComponent.vue' - -// The vue instance used inside text constructed with the import above. -let innerApp - -/** - * This thin Component wrapper can be rendered inside the viewer. - * - * The viewers vue instance is used for this component as it simply exports - * the options for the options api. - * - * When mounted this component constructs the vue instance - * used inside text based on texts vue import. - */ -export default defineComponent({ - name: 'ViewerView', - render: (h) => h('div', { style: { display: 'contents' } }), - props: ViewerComponent.props, - inheritAttrs: false, - mounted() { - innerApp = createApp(ViewerComponent, { - ...this.$props, - ...this.$attrs, - onLoadedHandler: () => this.$emit('update:loaded', true), - }) - innerApp.mount(this.$el) - }, - // Viewer still uses Vue 2 - beforeDestroy() { - innerApp.unmount() - innerApp = undefined - }, - // Once Viewer migrated to Vue 3 - beforeUnmount() { - innerApp.unmount() - innerApp = undefined - }, -}) diff --git a/tests/stub.php b/tests/stub.php index cd14233ef89..0ca6b52e163 100644 --- a/tests/stub.php +++ b/tests/stub.php @@ -35,11 +35,6 @@ class LoadAdditionalScriptsEvent extends \OCP\EventDispatcher\Event { } } -namespace OCA\Viewer\Event { - class LoadViewer extends \OCP\EventDispatcher\Event { - } -} - namespace OCA\Files_Sharing\Event { abstract class BeforeTemplateRenderedEvent extends \OCP\EventDispatcher\Event { abstract public function getShare(): \OCP\Share\IShare; From 4662d01fa26311d459009f8ca7bd855179a474ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 13:10:07 +0200 Subject: [PATCH 02/14] test: run the end-to-end tests against the viewer the server ships MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The viewer is part of the server from 36: installing the app failed the Playwright setup ("viewer already installed"), and Cypress put the old viewer app on top of the server's. Cypress also stopped ignoring the ResizeObserver warning, whose wording Chrome changed. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- .github/workflows/cypress-e2e.yml | 9 --------- cypress/support/commands.js | 4 +++- playwright/start-nextcloud-server.mjs | 3 ++- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/cypress-e2e.yml b/.github/workflows/cypress-e2e.yml index a7aacc75159..484cf9beb82 100644 --- a/.github/workflows/cypress-e2e.yml +++ b/.github/workflows/cypress-e2e.yml @@ -37,14 +37,6 @@ jobs: ref: ${{ matrix.server-versions }} submodules: true - - name: Checkout viewer - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - with: - persist-credentials: false - repository: nextcloud/viewer - ref: ${{ matrix.server-versions }} - path: apps/viewer - - name: Checkout assistant if: matrix.server-versions == 'main' uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 @@ -162,7 +154,6 @@ jobs: export OC_PASS=1234561 php occ user:add --password-from-env user1 php occ user:add --password-from-env user2 - php occ app:enable viewer php occ app:enable text php occ app:enable assistant --force php occ app:enable testing diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 1840fb6d31d..4c138a494df 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -464,5 +464,7 @@ Cypress.Commands.add('createDirectEditingLinkForNewFile', (path) => { Cypress.on( 'uncaught:exception', - (err) => !err.message.includes('ResizeObserver loop limit exceeded'), + // Chrome said "limit exceeded", it now says "completed with undelivered + // notifications": a warning either way, not an error of the page + (err) => !err.message.includes('ResizeObserver loop'), ) diff --git a/playwright/start-nextcloud-server.mjs b/playwright/start-nextcloud-server.mjs index 83d0ee70a15..e01cbbcda79 100644 --- a/playwright/start-nextcloud-server.mjs +++ b/playwright/start-nextcloud-server.mjs @@ -65,7 +65,8 @@ if (await isServerRunning()) { } else { const ip = await start() await waitOnNextcloud(ip) - await configureNextcloud(['text', 'viewer']) + // The viewer ships with the server + await configureNextcloud(['text']) } // Idle to wait for shutdown From 3bf539d93ae20f88aec45b7d6cd27d9cb4260293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 13:54:41 +0200 Subject: [PATCH 03/14] test: find the viewer by the class of its modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The viewer's modal is no longer #viewer, which is only where it mounts now: the dialog itself is teleported to the body, with the viewer__modal class and still its data-handler. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- cypress/e2e/nodes/CodeBlock.spec.js | 2 +- cypress/e2e/viewer.spec.js | 6 +++--- cypress/support/commands.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cypress/e2e/nodes/CodeBlock.spec.js b/cypress/e2e/nodes/CodeBlock.spec.js index ca7e2de66bc..d947e76481e 100644 --- a/cypress/e2e/nodes/CodeBlock.spec.js +++ b/cypress/e2e/nodes/CodeBlock.spec.js @@ -148,7 +148,7 @@ describe('Front matter support', function() { .contains('function') // Mermaid diagram - cy.get('#viewer .modal-container__content').scrollTo('bottom') + cy.get('.viewer__modal .modal-container__content').scrollTo('bottom') cy.getContent() .find('.split-view__preview') .eq(2) diff --git a/cypress/e2e/viewer.spec.js b/cypress/e2e/viewer.spec.js index 3d7fe575054..c1acec9e856 100644 --- a/cypress/e2e/viewer.spec.js +++ b/cypress/e2e/viewer.spec.js @@ -7,7 +7,7 @@ import { initUserAndFiles, randUser } from '../utils/index.js' const user = randUser() describe('Open test.md in viewer', function() { - const getViewer = () => cy.get('#viewer') + const getViewer = () => cy.get('.viewer__modal') before(function() { initUserAndFiles(user, 'test.md', 'empty.md') @@ -67,8 +67,8 @@ describe('Open test.md in viewer', function() { it('Closes the editor', function() { cy.openFile('test.md') - cy.get('#viewer .modal-header button.header-close').click() - cy.get('#viewer').should('not.exist') + cy.get('.viewer__modal .modal-header button.header-close').click() + cy.get('.viewer__modal').should('not.exist') }) it('Can use tab keys for list in the viewer', function() { diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 4c138a494df..55eb7137bb8 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -261,8 +261,8 @@ Cypress.Commands.add('openFile', (fileName, params = {}) => { Cypress.Commands.add('closeFile', (params = {}) => { cy.intercept({ method: 'POST', url: '**/apps/text/session/*/close' }).as('close') - cy.get('#viewer .modal-header button.header-close').click(params) - cy.get('#viewer .modal-header').should('not.exist') + cy.get('.viewer__modal .modal-header button.header-close').click(params) + cy.get('.viewer__modal .modal-header').should('not.exist') cy.wait('@close', { timeout: 7000 }) }) @@ -297,7 +297,7 @@ Cypress.Commands.add('getFile', (fileName) => { }) Cypress.Commands.add('getModal', () => { - return cy.get('#viewer[data-handler="text"]') + return cy.get('.viewer__modal[data-handler="text"]') }) Cypress.Commands.add('getEditor', { prevSubject: 'optional' }, (subject) => { From 6afa379f1e0684f49c3639c7628947763d18879b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:15:03 +0200 Subject: [PATCH 04/14] test: look for the viewer's modal on public shares too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The viewer's modal is gone once closed rather than hidden, and is viewer__modal rather than .viewer. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- cypress/e2e/share.spec.js | 2 +- cypress/e2e/shareWithAttachments.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/share.spec.js b/cypress/e2e/share.spec.js index e72c32e223c..f0e16830050 100644 --- a/cypress/e2e/share.spec.js +++ b/cypress/e2e/share.spec.js @@ -103,7 +103,7 @@ describe('Open test.md in viewer', function() { .find('h2') .should('contain', 'Hello world') cy.getModal().find('.modal-header button.header-close').click() - cy.get('.modal-mask').should('not.be.visible') + cy.get('.viewer__modal').should('not.exist') // cy.get('#rich-workspace').getContent().should('contain', 'Hello world') }) }) diff --git a/cypress/e2e/shareWithAttachments.spec.js b/cypress/e2e/shareWithAttachments.spec.js index fc85807a7ee..c4564a7dc25 100644 --- a/cypress/e2e/shareWithAttachments.spec.js +++ b/cypress/e2e/shareWithAttachments.spec.js @@ -94,7 +94,7 @@ describe('Public Share with attachments', () => { cy.visit(`/s/${this.token}`) cy.get('.editor__content-wrapper').should('exist') cy.get('.editor__content-wrapper .name', { timeout: 10_000 }).click() - cy.get('.viewer').should('exist') + cy.get('.viewer__modal').should('exist') cy.get('.language-plaintext').should('contain', 'multiple lines') }) }) From 7432ed53d0785598e730176a5751967236eb5bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:09:29 +0200 Subject: [PATCH 05/14] fix(viewer): fill the viewer, and open older versions to read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The viewer centres the element a handler renders and leaves it to size itself, which suits a picture. The editor takes the width of its parent, so with the element left inline the two sized each other and never settled: the editor kept resizing, elements under the cursor detached, and Chrome warned about a ResizeObserver loop. A version of a file carries the file's own id, and with it the editor opened the current document. It is read from its source instead, as the old viewer did. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- .../components/TextViewerWrapper.spec.ts | 58 +++++++++++++++++++ src/views/TextViewerWrapper.vue | 16 ++++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/tests/components/TextViewerWrapper.spec.ts diff --git a/src/tests/components/TextViewerWrapper.spec.ts b/src/tests/components/TextViewerWrapper.spec.ts new file mode 100644 index 00000000000..ad444774203 --- /dev/null +++ b/src/tests/components/TextViewerWrapper.spec.ts @@ -0,0 +1,58 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { IFile } from '@nextcloud/files' + +import { mount } from '@vue/test-utils' +import { describe, expect, it, vi } from 'vitest' +import { defineComponent } from 'vue' + +// The editor itself is not what is tested here, only what it is handed +vi.mock('../../components/ViewerComponent.vue', () => ({ + default: defineComponent({ + name: 'ViewerComponent', + props: ['filename', 'fileid', 'mime', 'source', 'e2EeIsEncrypted', 'active', 'onLoadedHandler'], + template: '
', + }), +})) + +import TextViewerWrapper from '../../views/TextViewerWrapper.vue' + +/** + * A markdown file, or one of its older versions. + * + * @param root - Where the file lives + * @param source - Its URL + */ +function makeFile(root = '/files/alice', source = 'https://cloud.example.com/remote.php/dav/files/alice/notes.md'): IFile { + // Only what the wrapper reads: @nextcloud/files is mocked for all tests + return { fileid: 42, source, encodedSource: source, root, path: '/notes.md', mime: 'text/markdown', attributes: {} } as unknown as IFile +} + +describe('the text viewer wrapper', () => { + it('opens the file itself for editing', () => { + const file = makeFile() + const editor = mount(TextViewerWrapper, { props: { file } }).findComponent({ name: 'ViewerComponent' }) + + expect(editor.props('fileid')).toBe(42) + expect(editor.props('source')).toBe(file.encodedSource) + }) + + // A version carries the id of the file it belongs to: handed that, the + // editor opened the current document instead of the version + it('opens an older version from its source, to read', () => { + const version = makeFile('/versions/alice/versions/42', 'https://cloud.example.com/remote.php/dav/versions/alice/versions/42/1691420501') + const editor = mount(TextViewerWrapper, { props: { file: version } }).findComponent({ name: 'ViewerComponent' }) + + expect(editor.props('fileid')).toBeNull() + expect(editor.props('source')).toBe(version.encodedSource) + }) + + it('stops the viewer from swiping, so text can be selected', () => { + const wrapper = mount(TextViewerWrapper, { props: { file: makeFile() } }) + + expect(wrapper.emitted('update:canSwipe')).toEqual([[false]]) + }) +}) diff --git a/src/views/TextViewerWrapper.vue b/src/views/TextViewerWrapper.vue index 95213a15c0f..5daa4d3c742 100644 --- a/src/views/TextViewerWrapper.vue +++ b/src/views/TextViewerWrapper.vue @@ -22,6 +22,9 @@ const emit = defineEmits<{ // An end-to-end encrypted file is read from its source, not opened collaboratively const isEncrypted = computed(() => Boolean(props.file.attributes?.['e2ee-is-encrypted'])) +// So is an older version of a file, which carries the file's own id: with it, +// the editor would open the current document rather than that version +const isVersion = computed(() => props.file.root?.startsWith('/versions/') ?? false) // Swiping to select text would otherwise step to the next file onMounted(() => emit('update:canSwipe', false)) @@ -30,10 +33,21 @@ onMounted(() => emit('update:canSwipe', false)) + + From c36f078fabe9309f567f43ca286d61c5595d4e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:09:30 +0200 Subject: [PATCH 06/14] fix: load the reference providers where files open in the viewer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They came with the old viewer's LoadViewer event. Without them the smart picker in a file opened from Files lacked its providers, Link a file among them, and links rendered no preview. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- lib/Listeners/FilesLoadAdditionalScriptsListener.php | 6 ++++++ lib/Listeners/FilesSharingLoadAdditionalScriptsListener.php | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/lib/Listeners/FilesLoadAdditionalScriptsListener.php b/lib/Listeners/FilesLoadAdditionalScriptsListener.php index d8284159c6f..54f5344b7ae 100644 --- a/lib/Listeners/FilesLoadAdditionalScriptsListener.php +++ b/lib/Listeners/FilesLoadAdditionalScriptsListener.php @@ -10,7 +10,9 @@ use OCA\Files\Event\LoadAdditionalScriptsEvent; use OCA\Text\Service\InitialStateProvider; +use OCP\Collaboration\Reference\RenderReferenceEvent; use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventListener; /** @@ -19,6 +21,7 @@ class FilesLoadAdditionalScriptsListener implements IEventListener { public function __construct( private readonly InitialStateProvider $initialStateProvider, + private readonly IEventDispatcher $eventDispatcher, ) { } @@ -34,5 +37,8 @@ public function handle(Event $event): void { \OCP\Util::addStyle('text', 'text-files'); $this->initialStateProvider->provideState(); + // The viewer no longer has an event of its own to hang this on: the + // smart picker and link previews of a file opened from here need it + $this->eventDispatcher->dispatchTyped(new RenderReferenceEvent()); } } diff --git a/lib/Listeners/FilesSharingLoadAdditionalScriptsListener.php b/lib/Listeners/FilesSharingLoadAdditionalScriptsListener.php index c99e84ab7c8..6ac423a90c9 100644 --- a/lib/Listeners/FilesSharingLoadAdditionalScriptsListener.php +++ b/lib/Listeners/FilesSharingLoadAdditionalScriptsListener.php @@ -10,7 +10,9 @@ use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent; use OCA\Text\Service\InitialStateProvider; +use OCP\Collaboration\Reference\RenderReferenceEvent; use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventListener; use OCP\IConfig; use OCP\Util; @@ -20,6 +22,7 @@ class FilesSharingLoadAdditionalScriptsListener implements IEventListener { public function __construct( IConfig $config, protected InitialStateProvider $initialStateProvider, + private readonly IEventDispatcher $eventDispatcher, ) { } @@ -34,6 +37,8 @@ public function handle(Event $event): void { Util::addStyle('text', 'text-init'); $this->initialStateProvider->provideState(); + // For the smart picker and link previews, as in the Files app + $this->eventDispatcher->dispatchTyped(new RenderReferenceEvent()); $node = $event->getShare()->getNode(); if ($node instanceof \OCP\Files\File) { $this->initialStateProvider->provideFileId($node->getId()); From 2a35a09f720347a3a7669289296cbea37aeae9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:09:30 +0200 Subject: [PATCH 07/14] test: open the sidebar from the viewer's header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is a button there now rather than an entry of the viewer's menu. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- playwright/support/sections/ViewerSection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/playwright/support/sections/ViewerSection.ts b/playwright/support/sections/ViewerSection.ts index 20484687d9e..37dd2d79ebf 100644 --- a/playwright/support/sections/ViewerSection.ts +++ b/playwright/support/sections/ViewerSection.ts @@ -18,7 +18,8 @@ export class ViewerSection { } public async openSidebarTab(name: 'Versions') { - await this.clickAction('Open sidebar') + // A button in the viewer's header, not an entry of its menu + await this.el.getByRole('button', { name: 'Open sidebar' }).click() await this.page.getByRole('tab', { name }).click() return this.page.getByRole('tabpanel', { name }) } From 3b9d70044ce90e770f2c48f716e85827af74012f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:28:56 +0200 Subject: [PATCH 08/14] test(smart-picker): wait for the link field to have the focus before typing in it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The picker focuses its field once its dialog is ready. Until then the viewer around the editor kept the focus, so the link typed straight away went into the document instead. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- playwright/e2e/smart-picker.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/playwright/e2e/smart-picker.spec.ts b/playwright/e2e/smart-picker.spec.ts index e5ce2d7c6c3..8973fd6d038 100644 --- a/playwright/e2e/smart-picker.spec.ts +++ b/playwright/e2e/smart-picker.spec.ts @@ -29,6 +29,10 @@ test('Create heading', async ({ editor }) => { test('Insert Link', async ({ editor }) => { await editor.type('/Any') await editor.getSuggestion('Any link').click() + // The picker focuses its field once its dialog is ready; until then, the + // viewer around the editor keeps the focus, and what is typed lands in + // the document + await expect(editor.referencePicker).toBeFocused() await editor.referencePicker.fill('https://github.com') await expect(editor.referenceWidget).toContainText('GitHub') await editor.referencePicker.press('Enter') From 8f1d95b1e707f13beb694073fbc46c31f86a1c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:52:08 +0200 Subject: [PATCH 09/14] chore(deps): bump @nextcloud/viewer to 2.0.0-beta.16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It no longer remounts the file shown when Text announces one of its own saves, which rebuilt the editor under the user. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4f3efb35ab3..c3940b21644 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@nextcloud/notify_push": "^1.4.0", "@nextcloud/router": "^3.2.0", "@nextcloud/sharing": "^0.4.0", - "@nextcloud/viewer": "^2.0.0-beta.15", + "@nextcloud/viewer": "^2.0.0-beta.16", "@nextcloud/vue": "^9.13.1", "@quartzy/markdown-it-mentions": "^0.2.0", "@tiptap/core": "3.31.3", @@ -4364,9 +4364,9 @@ } }, "node_modules/@nextcloud/viewer": { - "version": "2.0.0-beta.15", - "resolved": "https://registry.npmjs.org/@nextcloud/viewer/-/viewer-2.0.0-beta.15.tgz", - "integrity": "sha512-kRLwIWB50B/2IbyGqYlNJIDxpnFt1yyI7zV3Qps+Q2mp9F0I/Nnw/OBkm0NNbDP3PL5o4tcVVq/4Psfuw2izag==", + "version": "2.0.0-beta.16", + "resolved": "https://registry.npmjs.org/@nextcloud/viewer/-/viewer-2.0.0-beta.16.tgz", + "integrity": "sha512-1H64eqQ2YNyQnEzeMDmg4+EDQaEkSnY5JBGnCuZtMEUDZtFQmp3A50EHWDqjhRciSJi2D+FLHc40LnNk9e8ohA==", "license": "AGPL-3.0-or-later", "dependencies": { "@mdi/svg": "^7.4.47", diff --git a/package.json b/package.json index 41caaf660eb..9f4f4c5ff31 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@nextcloud/notify_push": "^1.4.0", "@nextcloud/router": "^3.2.0", "@nextcloud/sharing": "^0.4.0", - "@nextcloud/viewer": "^2.0.0-beta.15", + "@nextcloud/viewer": "^2.0.0-beta.16", "@nextcloud/vue": "^9.13.1", "@quartzy/markdown-it-mentions": "^0.2.0", "@tiptap/core": "3.31.3", From b700594de5493c652c38fa75694a3c88864e4075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 22:38:39 +0200 Subject: [PATCH 10/14] fix(print): let a document print over all of its pages in the viewer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The print styles freed the old viewer's #viewer from its fixed position. The new viewer's modal is .viewer__modal, and its container and content both scroll within the window, so only the first page printed. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- src/css/print.scss | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/css/print.scss b/src/css/print.scss index 79bbe3d2275..ab73c62eac3 100644 --- a/src/css/print.scss +++ b/src/css/print.scss @@ -32,7 +32,7 @@ display: none !important; } - #viewer[data-handler='text'] { + .viewer__modal[data-handler='text'] { // Hide top border border: none; width: 100% !important; @@ -43,9 +43,20 @@ // Hide modal header (close button) display: none !important; } - .modal-container { - // Make sure top aligned as we hided the menubar */ - top: 0px; + .modal-wrapper { + height: fit-content; + } + .modal-container, .modal-container__content { + // Make sure top aligned as we hided the menubar, and let the + // content run over as many pages as it needs: the viewer pins + // the container between its header and its footer, and both + // scroll what does not fit + top: 0 !important; + bottom: auto !important; + height: fit-content !important; + overflow: visible !important; + } + text-viewer { height: fit-content; } } From 6cb6fb55ddb29361755b3b52352f5a527ce8a2d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 22:38:40 +0200 Subject: [PATCH 11/14] test: open pages relative to the server's index.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A path starting with a slash leaves out index.php, which a server without pretty URLs needs: the Files app never loaded for these tests. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- playwright/e2e/editor-api-create-table.spec.ts | 2 +- playwright/e2e/editor-api.spec.ts | 4 ++-- playwright/e2e/folder-description/create.spec.ts | 2 +- playwright/e2e/propfind.spec.ts | 8 ++++---- playwright/support/fixtures/folder-description.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/playwright/e2e/editor-api-create-table.spec.ts b/playwright/e2e/editor-api-create-table.spec.ts index 4b063f4cb70..52163777080 100644 --- a/playwright/e2e/editor-api-create-table.spec.ts +++ b/playwright/e2e/editor-api-create-table.spec.ts @@ -9,7 +9,7 @@ import { test } from '../support/fixtures/editor-api.ts' test.describe('createTable API', () => { test.beforeEach(async ({ page }) => { // Open the files app so we're somewhere with `window.OCA.Text` available - await page.goto('/apps/files') + await page.goto('apps/files') // Load the editor API bundle const textRoot = await page.evaluate(() => (window as any).OC.appswebroots.text) diff --git a/playwright/e2e/editor-api.spec.ts b/playwright/e2e/editor-api.spec.ts index 703c2a97aff..279a6d914ee 100644 --- a/playwright/e2e/editor-api.spec.ts +++ b/playwright/e2e/editor-api.spec.ts @@ -12,7 +12,7 @@ const fileTest = mergeTests(uploadFileTest, test) test.describe('editor API at window.OCA.Text - MarkdownContentEditor.vue without collaboration session', () => { test.beforeEach(async ({ page }) => { // Open the files app so we're somewhere with `window.OCA.Text` available - await page.goto('/apps/files') + await page.goto('apps/files') // Load the editor API bundle const textRoot = await page.evaluate(() => (window as any).OC.appswebroots.text) @@ -65,7 +65,7 @@ fileTest.describe( fileTest.beforeEach(async ({ page }) => { // Open the files app so we're somewhere with `window.OCA.Text` available - await page.goto('/apps/files') + await page.goto('apps/files') // Load the editor API bundle const textRoot = await page.evaluate(() => (window as any).OC.appswebroots.text) diff --git a/playwright/e2e/folder-description/create.spec.ts b/playwright/e2e/folder-description/create.spec.ts index bb88e057a87..1ebaf19beb6 100644 --- a/playwright/e2e/folder-description/create.spec.ts +++ b/playwright/e2e/folder-description/create.spec.ts @@ -10,7 +10,7 @@ import { test as randomUserTest } from '../../support/fixtures/random-user.ts' const test = mergeTests(editorTest, randomUserTest) test('from new menu', async ({ page, editor }) => { - await page.goto('/apps/files') + await page.goto('apps/files') await page.getByRole('button', { name: 'New' }).click() await page.getByRole('menuitem', { name: 'Add folder description' }).click() await editor.content.click() diff --git a/playwright/e2e/propfind.spec.ts b/playwright/e2e/propfind.spec.ts index 72d4f09bf6f..17b44310578 100644 --- a/playwright/e2e/propfind.spec.ts +++ b/playwright/e2e/propfind.spec.ts @@ -26,7 +26,7 @@ test.describe('Text PROPFIND extension', () => { const properties = [PROPERTY_WORKSPACE_FLAT, PROPERTY_WORKSPACE_FILE_FLAT] const fiveSecondsAgo = Math.floor(Date.now() / 1000) - 5 - await page.goto('/apps/dashboard') + await page.goto('apps/dashboard') await user.uploadFile({ name: 'Readme.md', content: '', mtime: fiveSecondsAgo }) const [root1] = await propfindFolder(user, '/', 0, properties) @@ -44,7 +44,7 @@ test.describe('Text PROPFIND extension', () => { test('never adds rich workspace property to nested folders for flat properties', async ({ page, user }) => { const properties = [PROPERTY_WORKSPACE_FLAT, PROPERTY_WORKSPACE_FILE_FLAT] - await page.goto('/apps/dashboard') + await page.goto('apps/dashboard') await createFolder({ name: 'workspace-flat', owner: user }) const results1 = await propfindFolder(user, '/', 1, properties) @@ -61,7 +61,7 @@ test.describe('Text PROPFIND extension', () => { test('adds rich workspace property to nested folders for the default properties', async ({ page, user }) => { const properties = [PROPERTY_WORKSPACE, PROPERTY_WORKSPACE_FILE] - await page.goto('/apps/dashboard') + await page.goto('apps/dashboard') await createFolder({ name: 'workspace', owner: user }) const results1 = await propfindFolder(user, '/', 1, properties) @@ -81,7 +81,7 @@ test.describe('Text PROPFIND extension', () => { }) test('does not return a rich workspace property', async ({ page, user }) => { - await page.goto('/apps/dashboard') + await page.goto('apps/dashboard') const results1 = await propfindFolder(user, '/', 1, [PROPERTY_WORKSPACE_FLAT, PROPERTY_WORKSPACE_FILE_FLAT]) for (const result of results1) { diff --git a/playwright/support/fixtures/folder-description.ts b/playwright/support/fixtures/folder-description.ts index 11cd97a7be6..3d6ba5b4991 100644 --- a/playwright/support/fixtures/folder-description.ts +++ b/playwright/support/fixtures/folder-description.ts @@ -23,7 +23,7 @@ export const test = base.extend({ open: ({ file, page }, use) => use(async () => { // Make sure file is initialized. expect(file.name).toBe('Readme.md') - await page.goto('/apps/files') + await page.goto('apps/files') }), }) From 09207dd99fea56e8da7f33dda9106807aad4e1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Tue, 6 Oct 2026 23:27:16 +0200 Subject: [PATCH 12/14] fix(print): start the printed document at the top of the page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The viewer pins its container under its header with rules as specific as Text's print ones, which load later and won: the document printed a header's height down the first page. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- src/css/print.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/css/print.scss b/src/css/print.scss index ab73c62eac3..071e3294af9 100644 --- a/src/css/print.scss +++ b/src/css/print.scss @@ -32,7 +32,9 @@ display: none !important; } - .viewer__modal[data-handler='text'] { + // .modal-mask as well, to outweigh the viewer's own rules for its container, + // which are as specific and load after these + .viewer__modal.modal-mask[data-handler='text'] { // Hide top border border: none; width: 100% !important; From 0d153dab5ea212d9fa15b4ff57ae3b6304a85058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Wed, 7 Oct 2026 09:01:44 +0200 Subject: [PATCH 13/14] chore(deps): bump @nextcloud/viewer to 2.0.0-beta.17 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index c3940b21644..168b2e181ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@nextcloud/notify_push": "^1.4.0", "@nextcloud/router": "^3.2.0", "@nextcloud/sharing": "^0.4.0", - "@nextcloud/viewer": "^2.0.0-beta.16", + "@nextcloud/viewer": "^2.0.0-beta.17", "@nextcloud/vue": "^9.13.1", "@quartzy/markdown-it-mentions": "^0.2.0", "@tiptap/core": "3.31.3", @@ -4364,23 +4364,25 @@ } }, "node_modules/@nextcloud/viewer": { - "version": "2.0.0-beta.16", - "resolved": "https://registry.npmjs.org/@nextcloud/viewer/-/viewer-2.0.0-beta.16.tgz", - "integrity": "sha512-1H64eqQ2YNyQnEzeMDmg4+EDQaEkSnY5JBGnCuZtMEUDZtFQmp3A50EHWDqjhRciSJi2D+FLHc40LnNk9e8ohA==", + "version": "2.0.0-beta.17", + "resolved": "https://registry.npmjs.org/@nextcloud/viewer/-/viewer-2.0.0-beta.17.tgz", + "integrity": "sha512-qO6CgUhCmoOBT179+61entCSvM99X1bpQFMkblhKD58VO6Yx2zOA6lGvHQfnwnVPUlbB5hfu/TB+eCTJ99x8fQ==", "license": "AGPL-3.0-or-later", "dependencies": { "@mdi/svg": "^7.4.47", "@nextcloud/image-editor": "^1.0.0-beta.6", "@nextcloud/logger": "^3.0.3", + "@nextcloud/vue": "^9.13.0", "@skjnldsv/vue-plyr": "^7.5.0", "debounce": "^3.0.0", "dompurify": "^3.4.14", "opensheetmusicdisplay": "^2.1.3", + "vue": "^3.5.0", "vue-material-design-icons": "^5.3.1", "webdav": "^5.10.0" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + "node": "^24.0.0 || >=26.0.0" }, "peerDependencies": { "@nextcloud/axios": "^2.5.2", @@ -4392,9 +4394,7 @@ "@nextcloud/l10n": "^3.4.1", "@nextcloud/paths": "^3.1.0", "@nextcloud/router": "^3.1.0", - "@nextcloud/sharing": "^0.4.0 || ^1.0.0-beta.2", - "@nextcloud/vue": "^9.13.0", - "vue": "^3.5.0" + "@nextcloud/sharing": "^0.4.0 || ^1.0.0-beta.2" } }, "node_modules/@nextcloud/vite-config": { diff --git a/package.json b/package.json index 9f4f4c5ff31..9bc46ff7e7f 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@nextcloud/notify_push": "^1.4.0", "@nextcloud/router": "^3.2.0", "@nextcloud/sharing": "^0.4.0", - "@nextcloud/viewer": "^2.0.0-beta.16", + "@nextcloud/viewer": "^2.0.0-beta.17", "@nextcloud/vue": "^9.13.1", "@quartzy/markdown-it-mentions": "^0.2.0", "@tiptap/core": "3.31.3", From 9c2f381eff9e82f8ccaaeefabe14cd5799e7644e Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 7 Oct 2026 11:11:12 +0200 Subject: [PATCH 14/14] fix(viewer): fix CSS selectors for new viewer in ViewerComponent Also remove obsolete rule from Vue 2 and nextcloud-vue v8 times. Signed-off-by: Jonas Assisted-by: ClaudeCode:claude-opus-5.5 --- playwright/e2e/versions.spec.ts | 2 +- src/components/ViewerComponent.vue | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/playwright/e2e/versions.spec.ts b/playwright/e2e/versions.spec.ts index 8538b2e4324..34d10b4b838 100644 --- a/playwright/e2e/versions.spec.ts +++ b/playwright/e2e/versions.spec.ts @@ -67,7 +67,7 @@ test.describe('Versions with distant timestamps', () => { // Test that version contents are vertically aligned const oldBox = await oldVersion.getByRole('heading', { name: 'V1' }).boundingBox() - const currentBox = await oldVersion.getByRole('heading', { name: 'V3' }).boundingBox() + const currentBox = await current.getByRole('heading', { name: 'V3' }).boundingBox() expect(Math.abs(oldBox!.y - currentBox!.y)).toBeLessThan(5) }) }) diff --git a/src/components/ViewerComponent.vue b/src/components/ViewerComponent.vue index 5d53d81f68f..1aa314b343d 100644 --- a/src/components/ViewerComponent.vue +++ b/src/components/ViewerComponent.vue @@ -167,12 +167,8 @@ body .toastify.dialogs { margin-top: calc(45px + var(--default-clickable-area)); } -.viewer--split .source-viewer .editor__content-wrapper { +.viewer__comparison .source-viewer .editor__content-wrapper { // Account for missing menubar for old version in version comparison margin-top: calc(var(--default-clickable-area) + 2 * var(--default-grid-baseline)); } - -.viewer[data-handler="text"] .modal-wrapper .modal-container { - bottom: 0; -}