Skip to content

Commit 5732401

Browse files
committed
feat: add API package to register handlers in init scripts
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 42ee29c commit 5732401

File tree

12 files changed

+285
-13
lines changed

12 files changed

+285
-13
lines changed

README.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,46 @@ OCA.Viewer.close()
146146
```
147147

148148
### 🔍 Add you own file view
149+
150+
If you want to make your app compatible with this app, you can use the methods provided by the [`@nextcloud/viewer`](https://www.npmjs.com/package/@nextcloud/viewer) npm.js package:
151+
1. Create a vue component which use the `path` and `mime` props (they will be automatically passed by the viewer)
152+
2. Register your mime viewer with the following:
153+
``` js
154+
import { registerHandler } from '@nextcloud/viewer'
155+
import VideoView from 'VideoView.vue'
156+
157+
registerHandler({
158+
// unique id
159+
id: 'video',
160+
161+
// optional, it will group every view of this group and
162+
// use the proper view when building the file list
163+
// of the slideshow.
164+
// e.g. you open an image/jpeg that have the `media` group
165+
// you will be able to see the video/mpeg from the `video` handler
166+
// files that also have the `media` group set.
167+
group: 'media',
168+
169+
// the list of mimes your component is able to display
170+
mimes: [
171+
'video/mpeg',
172+
'video/ogg',
173+
'video/webm',
174+
'video/mp4'
175+
],
176+
177+
// your vue component view
178+
component: VideoView
179+
})
180+
```
181+
3. Make sure your script is loaded with `\OCP\Util::addInitScript` so that the handler is registered **before** the viewer is loaded.
182+
4. if you feel like your mime should be integrated on this repo, you can also create a pull request with your object on the `models` directory and the view on the `components` directory. Please have a look at what's already here and take example of it. 🙇‍♀️
183+
184+
185+
### Legacy handler registration
186+
> [!CAUTION]
187+
> Using OCA.Viewer for registering your handlers is not recommended as this might break depending on the script loading order
188+
149189
If you want to make your app compatible with this app, you can use the `OCA.Viewer` methods
150190
1. Create a vue component which use the `path` and `mime` props (they will be automatically passed by the viewer)
151191
2. Register your mime viewer with the following:
@@ -176,4 +216,4 @@ If you want to make your app compatible with this app, you can use the `OCA.View
176216
component: VideoView
177217
})
178218
```
179-
3. if you feel like your mime should be integrated on this repo, you can also create a pull request with your object on the `models` directory and the view on the `components` directory. Please have a look at what's already here and take example of it. 🙇‍♀️
219+
3. Make sure your script is loaded with `\OCP\Util::addScript` (in contrast to using the API package)!

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api_package/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
2+
# SPDX-License-Identifier: AGPL-3.0-or-later
3+
4+
/dist
5+
/package-lock.json

src/api_package/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
# Nextcloud Viewer integration
6+
7+
[![REUSE status](https://api.reuse.software/badge/github.com/nextcloud/viewer)](https://api.reuse.software/info/github.com/nextcloud/viewer)
8+
9+
10+
## Usage
11+
### 🔍 Add you own file view
12+
13+
If you want to make your app compatible with this app, you can use the methods provided by the [`@nextcloud/viewer`](https://www.npmjs.com/package/@nextcloud/viewer) npm.js package:
14+
1. Create a vue component which use the `path` and `mime` props (they will be automatically passed by the viewer)
15+
2. Register your mime viewer with the following:
16+
``` js
17+
import { registerHandler } from '@nextcloud/viewer'
18+
import VideoView from 'VideoView.vue'
19+
20+
registerHandler({
21+
// unique id
22+
id: 'video',
23+
24+
// optional, it will group every view of this group and
25+
// use the proper view when building the file list
26+
// of the slideshow.
27+
// e.g. you open an image/jpeg that have the `media` group
28+
// you will be able to see the video/mpeg from the `video` handler
29+
// files that also have the `media` group set.
30+
group: 'media',
31+
32+
// the list of mimes your component is able to display
33+
mimes: [
34+
'video/mpeg',
35+
'video/ogg',
36+
'video/webm',
37+
'video/mp4'
38+
],
39+
40+
// your vue component view
41+
component: VideoView
42+
})
43+
```
44+
3. Make sure your script is loaded with `\OCP\Util::addInitScript` so that the handler is registered **before** the viewer is loaded.
45+
46+
> [!TIP]
47+
> If you feel like your mime should be integrated on this repo, you can also create a pull request with your object on the `models` directory and the view on the `components` directory. Please have a look at what's already here and take example of it. 🙇‍♀️

src/api_package/REUSE.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
2+
# SPDX-License-Identifier: AGPL-3.0-or-later
3+
4+
version = 1
5+
SPDX-PackageName = "@nextcloud/viewer"
6+
SPDX-PackageSupplier = "2025 Nextcloud GmbH and Nextcloud contributors"
7+
SPDX-PackageDownloadLocation = "https://github.com/nextcloud/viewer"
8+
9+
[[annotations]]
10+
path = ["package.json", "tsconfig.json"]
11+
precedence = "aggregate"
12+
SPDX-FileCopyrightText = "2025 Nextcloud GmbH and Nextcloud contributors"
13+
SPDX-License-Identifier = "AGPL-3.0-or-later"

src/api_package/global.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*!
2+
* SPDX-License-Identifier: AGPL-3.0-or-later
3+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
4+
*/
5+
6+
import type { IHandler } from './index.ts'
7+
8+
declare global {
9+
interface Window {
10+
/**
11+
* Registered viewer handlers.
12+
*/
13+
// eslint-disable-next-line camelcase
14+
_oca_viewer_handlers: Map<string, IHandler>
15+
}
16+
}

src/api_package/index.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type { AsyncComponent, Component } from 'vue'
7+
8+
export interface IHandler {
9+
/**
10+
* Unique identifier for the handler
11+
*/
12+
id: string
13+
14+
/**
15+
* Indicate support for comparing two files
16+
*/
17+
canCompare?: boolean
18+
19+
/**
20+
* Vue 2 component to render the file.
21+
*/
22+
component: Component | AsyncComponent
23+
24+
/**
25+
* Group identifier to combine for navigating to the next/previous files
26+
*/
27+
group?: string
28+
29+
/**
30+
* List of mime types that are supported for opening
31+
*/
32+
mimes?: string[]
33+
34+
/**
35+
* Aliases for mime types, used to map different mime types to the same handler.
36+
*/
37+
mimesAliases?: Record<string, string>
38+
39+
/**
40+
* Viewer modal theme (one of 'dark', 'light', 'default')
41+
*/
42+
theme?: 'dark' | 'light' | 'default'
43+
}
44+
45+
/**
46+
* Register a new handler for the viewer.
47+
* This needs to be called before the viewer is initialized to ensure the handler is available.
48+
* So this should be called from an initialization script (`OCP\Util::addInitScript`).
49+
*
50+
* @param handler - The handler to register
51+
* @throws Error if the handler is invalid
52+
*/
53+
export function registerHandler(handler: IHandler): void {
54+
validateHandler(handler)
55+
56+
window._oca_viewer_handlers ??= new Map<string, IHandler>()
57+
if (window._oca_viewer_handlers.has(handler.id)) {
58+
console.warn(`Handler with id ${handler.id} is already registered.`)
59+
return
60+
}
61+
62+
window._oca_viewer_handlers.set(handler.id, handler)
63+
}
64+
65+
/**
66+
* Validate the handler object.
67+
*
68+
* @param handler - The handler to validate
69+
*/
70+
function validateHandler(handler: IHandler) {
71+
const { id, mimes, mimesAliases, component } = handler
72+
73+
// checking valid handler id
74+
if (!id || id.trim() === '' || typeof id !== 'string') {
75+
throw new Error('The handler does not have a valid id')
76+
}
77+
78+
// Nothing available to process! Failure
79+
if ((!mimes || !Array.isArray(mimes)) && !mimesAliases) {
80+
throw new Error('Handler needs a valid mime array or mimesAliases')
81+
}
82+
83+
// checking valid handler component data
84+
if ((!component || (typeof component !== 'object' && typeof component !== 'function'))) {
85+
throw new Error('The handler does not have a valid component')
86+
}
87+
}

src/api_package/package.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@nextcloud/viewer",
3+
"version": "1.0.0",
4+
"description": "Nextcloud viewer app API bindings",
5+
"keywords": [
6+
"nextcloud",
7+
"viewer",
8+
"api"
9+
],
10+
"homepage": "https://github.com/nextcloud/viewer",
11+
"bugs": {
12+
"url": "https://github.com/nextcloud/viewer/issues"
13+
},
14+
"repository": {
15+
"type": "git",
16+
"url": "git+https://github.com/nextcloud/viewer.git"
17+
},
18+
"license": "AGPL-3.0-or-later",
19+
"author": "Nextcloud GmbH and Nextcloud contributors",
20+
"type": "module",
21+
"exports": {
22+
"types": "./dist/index.d.ts",
23+
"default": "./dist/index.js"
24+
},
25+
"main": "dist/index.js",
26+
"files": [
27+
"dist"
28+
],
29+
"scripts": {
30+
"build": "tsc"
31+
},
32+
"peerDependencies": {
33+
"vue": "^2.7.16"
34+
}
35+
}

src/api_package/tsconfig.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"include": ["index.ts", "global.d.ts"],
3+
"compilerOptions": {
4+
"lib": [
5+
"DOM",
6+
"ES2020"
7+
],
8+
"outDir": "dist",
9+
"strict": true,
10+
"declaration": true,
11+
"module": "nodenext",
12+
"moduleResolution": "nodenext",
13+
"target": "ES2020",
14+
"allowImportingTsExtensions": true,
15+
"erasableSyntaxOnly": true,
16+
"rewriteRelativeImportExtensions": true
17+
},
18+
}

src/views/Viewer.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,13 @@ export default defineComponent({
528528
529529
// register on load
530530
document.addEventListener('DOMContentLoaded', () => {
531+
// load all init handlers
532+
if (window._oca_viewer_handlers) {
533+
window._oca_viewer_handlers.forEach((handler) => {
534+
OCA.Viewer.registerHandler(handler)
535+
})
536+
}
537+
531538
// register all primary components mimes
532539
this.handlers.forEach(handler => {
533540
this.registerHandler(handler)

0 commit comments

Comments
 (0)