Skip to content

feat(resourceEditor): introduce resourceEditor extension type#2571

Draft
dschmidt wants to merge 3 commits into
opencloud-eu:mainfrom
dschmidt:feat/resource-editor-extension
Draft

feat(resourceEditor): introduce resourceEditor extension type#2571
dschmidt wants to merge 3 commits into
opencloud-eu:mainfrom
dschmidt:feat/resource-editor-extension

Conversation

@dschmidt
Copy link
Copy Markdown
Contributor

@dschmidt dschmidt commented May 20, 2026

Summary

Introduces a new resourceEditor extension type that lets file viewers
and editors register themselves typed against ResourceEditorBindings,
and ships a working embed host so the same editor component is mountable
both behind a route and inside a modal / sidebar / mail-widget / sharing
dialog.

Replaces the previous AppWrapper + AppWrapperRoute pair with four
clearer building blocks:

  • useResourceEditor: resource-agnostic composable. Takes resource
    • space as refs, runs capability-driven loading (url / currentContent
      based on what the editor component declares), and the save / dirty /
      autosave flow for editors.
  • useRouteFileLoader: encapsulates the route-bound resource
    resolution (driveAliasAndItem + fileId, share-space reconstruction,
    import-with-extension copy) that the route host needs.
  • ResourceEditorRouteHost: drop-in replacement for the old route
    mount: TopBar, FileSideBar, Ctrl+S, onBeforeRouteLeave guard.
  • ResourceEditorHost: embed host. Accepts resource + space as
    props, auto-resolves the matching extension from the registry (or takes
    an explicit extension / extensionId), supports a readOnly prop and
    exposes editor state via slot bindings and defineExpose.

AppWrapper.vue and AppWrapperRoute remain as @deprecated shims that
synthesise a ResourceEditorExtension from the legacy props, so
web-app-external and the out-of-repo web-extensions keep working.

Apps migrated to the new path: web-app-pdf-viewer, web-app-preview,
web-app-text-editor, web-app-epub-reader.

Side note: useTextEditor.readonly

To make external read-only control work for the embed host's preview /
edit toggle, useTextEditor now accepts readonly as
MaybeRefOrGetter<boolean> and tracks it via a computed. The exposed
TextEditorInstance.readonly is now a ComputedRef<boolean>, no in-tree
caller wrote to it.

dschmidt added 2 commits May 20, 2026 22:58
Introduces a new `resourceEditor` extension type that lets file viewers
and editors register themselves typed against `ResourceEditorBindings`
instead of the previous untyped `wrappedComponent` slot contract. The
loading / saving / dirty / autosave logic moves out of `AppWrapper.vue`
into a `useResourceEditor` composable; the route-mounted host becomes
`ResourceEditorRouteHost`. A lightweight `ResourceEditorHost` exists
for embed use-cases (still route-bound today, made fully functional in
a follow-up).

AppWrapper and AppWrapperRoute remain as @deprecated shims that
synthesize a ResourceEditorExtension from the legacy props so web-app-
external and the out-of-repo web-extensions (codemirror, tiptap,
json-viewer, draw-io) keep working untouched.

Apps migrated to the new path: web-app-pdf-viewer, web-app-preview,
web-app-text-editor, web-app-epub-reader. web-app-external stays on
the legacy shim by design (AppProviderService doesn't fit cleanly
into the bindings contract).
Makes `ResourceEditorHost` actually usable outside a route. The composable
is now resource-agnostic: it takes `resource`/`space` as refs and runs
capability-driven loading plus save/dirty/autosave on top. Resource
resolution is split off into `useRouteFileLoader`, which the route host
wires together with `useResourceEditor` for its existing behaviour.

The embed host receives `resource` + `space` as props, auto-resolves a
matching `resourceEditor` extension from the registry via the new
`resolveResourceEditor` helper (extension/mimeType with `family/*` glob
support, optional `matches()` predicate, `hasPriority` tie-break), and
also accepts an explicit `extension` / `extensionId` override. A `readOnly`
prop forces preview-only mounts; the host exposes its editor state via
slot bindings and `defineExpose` for parents that drive their own
toolbar/modal chrome.

To make external read-only control actually work, `useTextEditor.readonly`
now accepts a `MaybeRefOrGetter<boolean>` and tracks it via a computed.
The local writable ref and its sync watcher are gone.

ResourceEditorExtension regains the discriminator metadata that Phase A
had stripped (`extensions`, `mimeTypes`, `matches`, `hasPriority`); the
host consumes them for auto-resolution. The `useFileActions` integration
keeps going through `appInfo.extensions[]` for now, collapsing that
duplication is a follow-up.
@dschmidt dschmidt force-pushed the feat/resource-editor-extension branch from f56de8b to 7f61723 Compare May 20, 2026 23:42
Three issues from a thorough multi-reviewer pass on the branch:

1. `ResourceEditorHost` constructed `useResourceEditor` unconditionally
   with `extension: () => resolvedExtension.value!`. When no extension
   matched (e.g. a resource the registry can't handle), the composable
   read `unref(extensionRef).appId` and crashed before the template
   could render the `#no-editor` slot. Split the host into an outer
   shell that gates on `resolvedExtension` and an inner
   `ResourceEditorMount` that runs the composable only once an
   extension is resolved. Mount stays internal to the package.

2. `web-app-epub-reader` wrapped its App in `defineAsyncComponent` to
   defer the epubjs bundle. The wrapper has no `props`/`emits` until
   the chunk resolves, so the composable's capability introspection
   (`hasProp('currentContent')`) returned `false` and content was
   never loaded. App.vue is now statically imported and the epubjs
   dependency itself is dynamically imported inside App.vue's setup,
   keeping the heavy lib deferred while exposing the component's
   props/emits to the host. Test helper exports `flushPromises` so
   the existing spec can wait for the dynamic import.

3. `useResourceEditor`'s `fileSizeLimit` modal was dispatched whenever
   `resource` or `space` changed. Preview's photo-roll re-fires that
   watcher on every active-file swap, so the modal popped on every
   click. Track the last resource id we prompted for and only
   re-dispatch on actual transitions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant