Skip to content

fix(viewer): migrate to @nextcloud/viewer for Nextcloud 36 - #375

Merged
AndyScherzinger merged 11 commits into
mainfrom
fix/viewer-library
Oct 17, 2026
Merged

AndyScherzinger merged 11 commits into
mainfrom
fix/viewer-library

Conversation

@AndyScherzinger

@AndyScherzinger AndyScherzinger commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Closes #353

Mind maps no longer open on Nextcloud 36, for two reasons:

  • The editor page returns a 500. templates/viewer.php used \OC::$server[...], and array access on the server container is gone in 36 (Cannot use object of type OC\Server as array). It now uses OCP\Server::get().
  • The handler is never registered. feat(viewer): drop the bundled viewer app for @nextcloud/viewer server#63954 removed the viewer app: LoadViewer is no longer dispatched, and OCA.Viewer.registerHandler() / setRootElement() no longer exist.

Changes

  • The handler is registered with @nextcloud/viewer (^2.0.0-beta.18) from an init script loaded on BeforeTemplateRenderedEvent. The viewer also registers the Files "View" / "Open with" actions for it.
  • src/views/MindMap.js is now a native custom element rendering the editor iframe, replacing the Vue 2 component built on the viewer mixin. It emits loaded as soon as the iframe is inserted: the viewer hides a handler until then, and the editor lays out its toolbar and nodes once on startup, so it must not start hidden (in Firefox the toolbar otherwise stays empty).
  • The iframe closes the viewer through OCA.FilesMindMap.close() → getViewer().close().
  • The handler uses the viewer's dark theme. With 2.0.0-beta.17+ the default theme follows the user's theme, but on a light theme the header title stays light on white and is barely readable (Header title is barely readable with the default theme on a light theme nextcloud-libraries/nextcloud-viewer#150). default can come back once that is fixed.
  • The init script holds only the handler registration and the element. The editor logic (mindmap.js, with dialogs, axios and jszip) loads when a mind map is first shown. What every page loads is about 170 kB (62 kB gzip), most of it the @nextcloud/viewer entry itself.
  • On 32 the hidden file action that opens mind maps from the file list is still registered, from its own chunk loaded only there. @nextcloud/files 4 (33+) passes a context object to enabled()/exec(), so this action never matched there, and the viewer's own actions cover it.
  • max-version raised to 36.
  • The custom renderBuiltUrl in vite.config.ts is dropped. It emitted a bare OC.filePath(), which a minified top-level function OC in the bundle shadows, so lazy chunks failed to load wherever this app's copy of the viewer is mounted (e.g. 32). @nextcloud/vite-config already emits window.OC.filePath().
  • The error shown when opening a mind map fails now gets the message instead of the Error object, which made the toast itself throw.
  • Removed as dead code:
    • public.js and LoadPublicViewerListener: on 32+ public share pages are rendered by the Files app and open single-file shares through the default file action, so the #files-public-content container they waited for never exists.
    • css/style.css and LoadAdditionalListener, which only loaded it, plus its link in the editor page and the template creator's icon-mindmap fallback. None of its rules apply on 32+, and setIconSvgInline() exists since 29.
    • src/__tests__/MindMap.spec.js, replaced by mindmapElement.spec.js. Besides testing the removed Vue component, it collided with mindmap.spec.js on case-insensitive filesystems.

Testing

Tested in Chromium and Firefox at the head of this branch:

32.0.15 (viewer 5.0.0) 34.0.5 (viewer 7.0.0-dev) 36 (master, no viewer app)
Open from Files ✅ ✅ ✅
Edit and save ✅ ✅ ✅
Open public link share ✅ ✅ ✅
Editor renders when the iframe loads slowly (Firefox) ✅ ✅ ✅

The last row holds back the editor page's load event, which reproduces the empty toolbar with the previous approach of emitting loaded on the iframe's load event. On 36 this app's copy of the viewer is elected over the server's 2.0.0-beta.15. On 32 and 34 the old viewer app keeps its OCA.Viewer global while the library's registry (window._nc_viewer_scope) only holds this handler, so both coexist. 33 and 35 were not tested separately. Vitest passes.

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI (N/A)

@nextcloud-command nextcloud-command added the AI assisted This PR contains AI-assisted commits label Oct 5, 2026
Nextcloud 36 dropped array access on the server container, so
\OC::$server[...] fails with "Cannot use object of type OC\Server as
array" and the viewer iframe returns a 500. OCP\Server::get() is the
public API for this and works on all supported versions.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Nextcloud 36 removes the viewer app. OCA.Viewer.registerHandler() and
setRootElement() are gone and the LoadViewer event is never dispatched,
so mind maps no longer open at all.

- Register the handler with @nextcloud/viewer from an init script loaded
  on BeforeTemplateRenderedEvent.
- Render the editor iframe from a native custom element instead of a
  Vue 2 component built on the viewer mixin.
- Open and close the viewer through getViewer(). The edit file action is
  only registered on Nextcloud 32, from 33 on the viewer registers the
  actions for its handlers itself.
- Mount the element directly into the public share page instead of
  pointing the viewer at it.

Closes #353

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The custom renderBuiltUrl emitted a bare OC.filePath(), which a
minified top-level function named OC can shadow. With the viewer
library bundled, lazy chunks then fail to load wherever this app's
copy of the viewer is the one mounted, e.g. on Nextcloud 32.
@nextcloud/vite-config already emits window.OC.filePath() itself.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The toast was handed the Error object, which made it throw and hid
the actual error.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
On Nextcloud 32 and later the public share page is rendered by the
Files app and opens single-file shares through the default file action,
so the #files-public-content container public.js waited for does not
exist on any supported version.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The handler registration runs on every page, but pulled in the whole
editor logic with it: dialogs, axios, jszip, and @nextcloud/files-legacy
with its WebDAV client and Node polyfills, about 1 MB.

- Register the handler from constants and the element only, and load
  mindmap.js when a mind map is first shown.
- Load the Nextcloud 32 file action from its own chunk, only on 32.

What every page loads drops from 1062 kB (301 kB gzip) to 169 kB
(61 kB gzip), most of it the @nextcloud/viewer entry itself.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
@AndyScherzinger AndyScherzinger added this to the v0.1.4 milestone Oct 17, 2026
Where no newer copy is on the page, this app's copy is the viewer
that gets mounted, so ship the current one. Its breaking changes do not
affect this app: open() is called without callbacks, and compare() and
canCompare are not used. The 'default' theme now follows the user's
theme again, as it did with the viewer app.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
None of the rules in css/style.css apply on a supported version:

- the #body-public rules styled the old public share page, which the
  Files app renders since at least Nextcloud 32
- the .ie rule targeted Internet Explorer
- nothing renders a #mmframe element
- .icon-mindmap was only the template creator's fallback for servers
  without setIconSvgInline(), which exists since Nextcloud 29

Drop it together with LoadAdditionalListener, which only added it to
the Files app, its link in the editor iframe, and the fallback.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
From @nextcloud/viewer 2.0.0-beta.17 the default theme follows the
user's theme, but on a light theme the header title stays light on the
white header and is barely readable. The dark theme keeps the header
readable on every theme.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The viewer hides a handler until it emits `loaded`. Emitting it on the
iframe's load event left the editor hidden while it started, so it laid
out its toolbar and nodes without a size: in Firefox the toolbar stayed
empty and the root node sat clipped in the corner, depending on how
quickly the iframe loaded. Emit `loaded` as soon as the iframe is
inserted, as the Vue component did with doneLoading().

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI assisted This PR contains AI-assisted commits

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate off the OCA.Viewer global before Nextcloud 36

2 participants