Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,17 @@ for what happens between those two sentences.

If you are not registering a handler, no server-side setup is needed. A plain
`import { getViewer } from '@nextcloud/viewer'` in your regular bundle is enough —
no `\OCP\Util::addInitScript` required, the server always ships a copy of its own.
no `\OCP\Util::addInitScript` required, the server always ships a copy of its own
and registers the handlers for images, video and audio on every page.

Importing the package registers nothing by itself. Only a page the server does
not set up, such as a standalone playground, needs to ask for those handlers:

```ts
import { registerDefaultHandlers } from '@nextcloud/viewer'

registerDefaultHandlers()
```

Only call `open()` in response to an actual user interaction, not eagerly at
import or mount time — see [how a page ends up with one
Expand Down
4 changes: 3 additions & 1 deletion __tests__/defaults.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ describe('default handlers', () => {
expect(scope.handlers!.has('images')).toBe(true)
})

it('do not complain about themselves when asked for explicitly', async () => {
it('register once, however often they are asked for', async () => {
const { registerDefaultHandlers } = await importPackage()
const { logger } = await import('../lib/services/logger.ts')
const warn = vi.spyOn(logger, 'warn')

registerDefaultHandlers()
registerDefaultHandlers()

expect(scope.handlers!.size).toBe(3)
expect(warn).not.toHaveBeenCalled()
})
})
13 changes: 10 additions & 3 deletions __tests__/entry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@ describe('importing @nextcloud/viewer', () => {
expect(getViewer()).toBe(scope.service)
})

it('registers the image, video and audio handlers', async () => {
const { getHandlers } = await importEntry()
it('registers no handler until asked, then the image, video and audio ones', async () => {
const { getHandlers, registerDefaultHandlers } = await importEntry()

// The server asks from an init script; a second copy on the page
// registering on import would only warn about the first
expect(getHandlers().size).toBe(0)

registerDefaultHandlers()

expect([...getHandlers().keys()].sort()).toEqual(['audios', 'images', 'videos'])
})
Expand Down Expand Up @@ -59,7 +65,8 @@ describe('importing @nextcloud/viewer', () => {

describe('the offered implementation', () => {
it('mounts the viewer into the page and hands it to the service', async () => {
const { getViewer } = await importEntry()
const { getViewer, registerDefaultHandlers } = await importEntry()
registerDefaultHandlers()

await scope.candidates[0]!.load()

Expand Down
8 changes: 5 additions & 3 deletions lib/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ let registered = false
* Register the handlers for the file types the viewer shows out of the box:
* images, video and audio.
*
* Importing the package does this, so there is normally nothing to call.
* It stays exported for a consumer that wants them registered at a point
* of its own choosing, and does nothing on any call after the first.
* The server calls this from an init script on every page, so an app only
* needs to when it hosts the viewer on a page of its own. Importing the
* package deliberately does not: with several copies on a page, each one
* registering would only warn about the others. Any call after the first
* does nothing.
*/
export function registerDefaultHandlers(): void {
if (registered) {
Expand Down
7 changes: 0 additions & 7 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { registerDefaultHandlers } from './defaults.ts'
import { registerImplementation } from './scope.ts'
import { loadTranslations } from './utils/l10n.ts'
import { getViewer } from './viewer.ts'
Expand All @@ -27,12 +26,6 @@ registerImplementation({
// It is an empty shell until the viewer is mounted.
getViewer()

// Images, video and audio are what the viewer is for, so an app gets them by
// importing the package rather than by remembering to ask. It has to happen
// here, at import: the Files list reads the available actions when it first
// renders, and a handler registered after that is a file that does not open.
registerDefaultHandlers()

export { canView, getHandlers, registerHandler } from './handlers.ts'
export type { IHandler } from './handlers.ts'
export { getViewer, Viewer } from './viewer.ts'
Expand Down
4 changes: 2 additions & 2 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ const translations = readdirSync('./l10n')

/**
* The strings the package can show before the viewer is loaded: the file
* actions it registers on import, and the handler names listed under
* "Open with …". Every other string belongs to the viewer itself and
* actions it registers along with the handlers, and the handler names listed
* under "Open with …". Every other string belongs to the viewer itself and
* arrives with it.
*
* A string used by the entry but missing here is not an error, it just
Expand Down
Loading