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
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,16 @@ import { registerHandler } from '@nextcloud/viewer'
import { defineCustomElement } from 'vue'
import NoteView from './views/NoteView.vue'

const tagname = 'myapp-note-view'
const tagName = 'myapp-note-view'

if (!window.customElements.get(tagname)) {
window.customElements.define(tagname, defineCustomElement(NoteView, { shadowRoot: false }))
if (!window.customElements.get(tagName)) {
window.customElements.define(tagName, defineCustomElement(NoteView, { shadowRoot: false }))
}

registerHandler({
id: 'myapp-notes',
displayName: t('myapp', 'Notes'),
tagname,
tagName,
enabled: (nodes) => nodes.every((node) => node.mime === 'application/x-myapp-note'),
})
```
Expand Down Expand Up @@ -192,7 +192,7 @@ To show your own file type in the viewer, register a handler with the

Handlers are rendered as **native custom elements**, not as Vue components passed
directly to the viewer. You register a custom element with the browser, then
reference it from your handler by its `tagname`.
reference it from your handler by its `tagName`.

#### 1. Create your view component

Expand Down Expand Up @@ -250,7 +250,7 @@ const src = computed(() => props.file.encodedSource)

Turn your component into a custom element with Vue's `defineCustomElement`, define
it on `window.customElements`, then register a handler that points at it via
`tagname`:
`tagName`:

```ts
import MyIconSvg from '@mdi/svg/svg/file-image.svg?raw'
Expand All @@ -261,13 +261,13 @@ import MyView from './MyView.vue'

// A valid custom element tag name: lowercase, must contain a hyphen,
// no consecutive hyphens, no leading/trailing hyphen.
const tagname = 'my-app-viewer'
const tagName = 'my-app-viewer'

// Define the custom element. `shadowRoot: false` keeps the element in the
// light DOM so Nextcloud's global styles and CSS variables apply. A tag can
// only be defined once per page, hence the check.
if (!window.customElements.get(tagname)) {
window.customElements.define(tagname, defineCustomElement(MyView, { shadowRoot: false }))
if (!window.customElements.get(tagName)) {
window.customElements.define(tagName, defineCustomElement(MyView, { shadowRoot: false }))
}

// Register the handler.
Expand All @@ -279,7 +279,7 @@ registerHandler({
displayName: t('myapp', 'My viewer'),

// The custom element tag name registered above.
tagname,
tagName,

// Optional inline SVG icon for the "Open with …" menu entry.
iconSvgInline: MyIconSvg,
Expand Down Expand Up @@ -315,7 +315,7 @@ The full handler shape (see the `IHandler` interface):
| --------------- | ------------------------------------- | -------- | ------------------------------------------------------------------ |
| `id` | `string` | yes | Unique, non-empty handler identifier |
| `displayName` | `string` | yes | Translated name shown in the "Open with …" menu |
| `tagname` | `string` | yes | Registered custom element tag name (must contain a hyphen) |
| `tagName` | `string` | yes | Registered custom element tag name (must contain a hyphen) |
| `enabled` | `(nodes: File[]) => boolean` | yes | Whether the handler can open the given files |
| `iconSvgInline` | `string` | no | Inline SVG icon for the menu entry |
| `group` | `string` | no | Group used to combine handlers when opening a folder |
Expand All @@ -325,13 +325,13 @@ The full handler shape (see the `IHandler` interface):

Gotchas:

- `tagname` must be lowercase, contain a hyphen, and have no leading, trailing
- `tagName` must be lowercase, contain a hyphen, and have no leading, trailing
or consecutive hyphens (e.g. `my-app-viewer`). An invalid one throws.
- `id` must be unique **across every app on the page**, not just your own:
it is not namespaced for you. A collision does not throw: the second
registration is silently dropped with a console warning, so pick something
specific to your app (`myapp-image`, not `image`).
- Registering the same handler again, with the same `id` and `tagname`, is
- Registering the same handler again, with the same `id` and `tagName`, is
quietly ignored. That is what happens when several copies of the package
on a page register the defaults, so there is nothing to guard against.
- Registering after the viewer has already read the handler list is not an
Expand Down Expand Up @@ -452,7 +452,7 @@ instead, and the viewer works with `@nextcloud/files` nodes rather than the
| `OCA.Viewer.mimetypes.includes(node.mime)` | `canView(node)` |
| `OCA.Viewer.mimetypesCompare.includes(node.mime)` | `canView(node)` |
| `OCA.Viewer.availableHandlers` | `getHandlers()`, or `canView(node)` to test one file |
| `OCA.Viewer.registerHandler({ component })` | `registerHandler({ tagname })`, see above |
| `OCA.Viewer.registerHandler({ component })` | `registerHandler({ tagName })`, see above |
| `canCompare: true` on a handler | nothing, any handler can be compared |
| `\OCP\Util::addScript` for the registration | `\OCP\Util::addInitScript` |
| A listener for `OCA\Viewer\Event\LoadViewer` | a listener for `BeforeTemplateRenderedEvent`, see the [tutorial](#4-load-it-on-every-page) |
Expand All @@ -471,7 +471,7 @@ Inside a handler, what used to be read off the global comes in as props:
Two changes are worth calling out because they are not a rename:

Handlers are custom elements now, not Vue components handed to the viewer. A
handler names a `tagname` you have defined on `window.customElements`, which is
handler names a `tagName` you have defined on `window.customElements`, which is
what lets the viewer render a handler written in any framework, or none. The
[registration section](#-add-your-own-file-view) walks through it.

Expand Down Expand Up @@ -514,7 +514,7 @@ The handler registry and the elected viewer live under a window key named for
the **handler ABI**, not for this package's version:

```js
window._nc_viewer_scope.handlers_v1
window._nc_viewer_scope.handlers_v2
```

Keying it by package version would split the registry on every major release,
Expand Down
14 changes: 7 additions & 7 deletions __tests__/component/Viewer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { mountViewer } from './mountViewer.ts'
function imageHandler() {
return makeHandler({
id: 'image',
tagname: 'oca-viewer-image',
tagName: 'oca-viewer-image',
group: 'media',
enabled: (nodes) => nodes.every((n) => n.mime?.startsWith('image/')),
})
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('Viewer.open()', () => {
it('filters currentFileList to files of the same handler group', async () => {
const pdfHandler = makeHandler({
id: 'pdf',
tagname: 'oca-viewer-pdf',
tagName: 'oca-viewer-pdf',
group: 'documents',
enabled: (nodes) => nodes.every((n) => n.mime === 'application/pdf'),
})
Expand Down Expand Up @@ -105,7 +105,7 @@ describe('Viewer.open()', () => {
['dark', false],
['default', false],
] as const)('gives the modal a light backdrop only for a %s themed handler', async (theme, lightBackdrop) => {
const handler = makeHandler({ id: theme, tagname: `oca-viewer-${theme}`, theme, enabled: () => true })
const handler = makeHandler({ id: theme, tagName: `oca-viewer-${theme}`, theme, enabled: () => true })
const { vm, wrapper, modalProps } = mountViewer([handler])
const f1 = makeFile()
await vm.open([f1], f1)
Expand Down Expand Up @@ -371,7 +371,7 @@ describe('Viewer preload', () => {
const preload = vi.fn(async () => {})
const handler = makeHandler({
id: 'image',
tagname: 'oca-viewer-image',
tagName: 'oca-viewer-image',
group: 'media',
preload,
enabled: (nodes) => nodes.every((n) => n.mime?.startsWith('image/')),
Expand All @@ -396,7 +396,7 @@ describe('Viewer preload', () => {
// reason for the file the user clicked to never show up
const handler = makeHandler({
id: 'image',
tagname: 'oca-viewer-image',
tagName: 'oca-viewer-image',
preload: (() => {
throw new Error('boom')
}) as never,
Expand Down Expand Up @@ -645,13 +645,13 @@ describe('opening over a viewer that is still open', () => {
function pdfHandler() {
return makeHandler({
id: 'pdf',
tagname: 'oca-viewer-pdf',
tagName: 'oca-viewer-pdf',
enabled: (nodes) => nodes.every((n) => n.mime === 'application/pdf'),
})
}

function officeHandler() {
return makeHandler({ id: 'office', tagname: 'oca-viewer-office', enabled: () => false })
return makeHandler({ id: 'office', tagName: 'oca-viewer-office', enabled: () => false })
}

it('still tells the first opener when a handler reopens without options', async () => {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/component/handlerContract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ beforeAll(() => {
function probeHandler(overrides = {}) {
return makeHandler({
id: 'probe',
tagname: 'oca-viewer-probe',
tagName: 'oca-viewer-probe',
enabled: () => true,
...overrides,
})
Expand Down
8 changes: 4 additions & 4 deletions __tests__/component/viewerApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const CANNOT_OPEN = 'We were not able to open the file.'
function imageHandler(overrides = {}) {
return makeHandler({
id: 'image',
tagname: 'oca-viewer-image',
tagName: 'oca-viewer-image',
enabled: (nodes) => nodes.every((n) => n.mime?.startsWith('image/')),
...overrides,
})
Expand Down Expand Up @@ -121,7 +121,7 @@ describe('open() with bad input', () => {
describe('open() with a forced handler', () => {
// Both take every image, so the first one registered is what
// getHandlerForFile answers with, whatever the opener asked for.
const editor = () => imageHandler({ id: 'editor', tagname: 'oca-viewer-editor' })
const editor = () => imageHandler({ id: 'editor', tagName: 'oca-viewer-editor' })

it('keeps the file list of the handler that was asked for', async () => {
const { vm, wrapper, modalProps, renderedTags } = mountViewer([imageHandler(), editor()])
Expand Down Expand Up @@ -152,7 +152,7 @@ describe('open() with a forced handler', () => {
const images = imageHandler({ group: 'media' })
const videos = makeHandler({
id: 'videos',
tagname: 'oca-viewer-videos',
tagName: 'oca-viewer-videos',
group: 'media',
enabled: (nodes) => nodes.every((n) => n.mime?.startsWith('video/')),
})
Expand Down Expand Up @@ -303,7 +303,7 @@ describe('compare() with bad input', () => {
})

it('uses the given handler for both sides', async () => {
const other = makeHandler({ id: 'other', tagname: 'oca-viewer-other' })
const other = makeHandler({ id: 'other', tagName: 'oca-viewer-other' })
const { vm, wrapper, renderedTags } = mountViewer([imageHandler(), other])

await vm.compare(makeFile(), makeFile(), 'other')
Expand Down
4 changes: 2 additions & 2 deletions __tests__/encryptedFiles.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ describe('an end-to-end encrypted file', () => {
const plain = makeFile({ mime: 'text/markdown', attributes: { 'e2ee-is-encrypted': false } })
const unmarked = makeFile({ mime: 'text/markdown' })

const custom = makeHandler({ id: 'custom', tagname: 'oca-viewer-custom' })
const dav = makeHandler({ id: 'dav', tagname: 'oca-viewer-dav', supportsEndToEndEncryption: true })
const custom = makeHandler({ id: 'custom', tagName: 'oca-viewer-custom' })
const dav = makeHandler({ id: 'dav', tagName: 'oca-viewer-dav', supportsEndToEndEncryption: true })

it('is refused by a handler that has not opted in, without asking it', () => {
const enabled = vi.fn(() => true)
Expand Down
2 changes: 1 addition & 1 deletion __tests__/factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function makeHandler(overrides: Partial<IHandler> = {}): IHandler {
return {
id: 'test',
displayName: 'Test handler',
tagname: 'oca-viewer-test',
tagName: 'oca-viewer-test',
enabled: () => true,
...overrides,
}
Expand Down
Loading
Loading