@dxos/plugin-file 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PLUGIN.mdl +86 -77
- package/dist/lib/FilePlugin.mjs +37 -0
- package/dist/lib/FilePlugin.mjs.map +1 -0
- package/dist/lib/FilePlugin.node.mjs +12 -0
- package/dist/lib/FilePlugin.node.mjs.map +1 -0
- package/dist/lib/capabilities/node.mjs +8 -0
- package/dist/lib/capabilities/node.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +15 -0
- package/dist/lib/capabilities.mjs.map +1 -0
- package/dist/lib/chunk-FileArticle.mjs +62 -0
- package/dist/lib/chunk-FileArticle.mjs.map +1 -0
- package/dist/lib/chunk-create-object.mjs +28 -0
- package/dist/lib/chunk-create-object.mjs.map +1 -0
- package/dist/lib/chunk-create.mjs +78 -0
- package/dist/lib/chunk-create.mjs.map +1 -0
- package/dist/lib/chunk-edge-backend.mjs +18 -0
- package/dist/lib/chunk-edge-backend.mjs.map +1 -0
- package/dist/lib/chunk-file-uploader.mjs +37 -0
- package/dist/lib/chunk-file-uploader.mjs.map +1 -0
- package/dist/lib/chunk-inline-backend.mjs +19 -0
- package/dist/lib/chunk-inline-backend.mjs.map +1 -0
- package/dist/lib/chunk-markdown-extension.mjs +182 -0
- package/dist/lib/chunk-markdown-extension.mjs.map +1 -0
- package/dist/lib/chunk-meta.mjs +44 -0
- package/dist/lib/chunk-meta.mjs.map +1 -0
- package/dist/lib/chunk-operation-handler.mjs +11 -0
- package/dist/lib/chunk-operation-handler.mjs.map +1 -0
- package/dist/lib/chunk-react-surface.mjs +55 -0
- package/dist/lib/chunk-react-surface.mjs.map +1 -0
- package/dist/lib/chunk-read.mjs +32 -0
- package/dist/lib/chunk-read.mjs.map +1 -0
- package/dist/lib/chunk-settings.mjs +23 -0
- package/dist/lib/chunk-settings.mjs.map +1 -0
- package/dist/lib/chunk-skill-definition.mjs +10 -0
- package/dist/lib/chunk-skill-definition.mjs.map +1 -0
- package/dist/lib/chunk-skills.mjs +28 -0
- package/dist/lib/chunk-skills.mjs.map +1 -0
- package/dist/lib/chunk-types.mjs +98 -0
- package/dist/lib/chunk-types.mjs.map +1 -0
- package/dist/lib/components.mjs +57 -0
- package/dist/lib/components.mjs.map +1 -0
- package/dist/lib/containers.mjs +56 -0
- package/dist/lib/containers.mjs.map +1 -0
- package/dist/lib/index.mjs +4 -0
- package/dist/lib/meta.mjs +2 -0
- package/dist/lib/operations.mjs +7 -0
- package/dist/lib/operations.mjs.map +1 -0
- package/dist/lib/plugin.mjs +9 -0
- package/dist/lib/plugin.mjs.map +1 -0
- package/dist/lib/skills.mjs +2 -0
- package/dist/lib/translations.mjs +30 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/types/src/FilePlugin.d.ts.map +1 -1
- package/dist/types/src/FilePlugin.node.d.ts.map +1 -1
- package/dist/types/src/capabilities/FileUploadField.d.ts +12 -0
- package/dist/types/src/capabilities/FileUploadField.d.ts.map +1 -0
- package/dist/types/src/capabilities/create-object.d.ts +1 -1
- package/dist/types/src/capabilities/edge-backend.d.ts +5 -0
- package/dist/types/src/capabilities/edge-backend.d.ts.map +1 -0
- package/dist/types/src/capabilities/file-uploader.d.ts +1 -1
- package/dist/types/src/capabilities/file-uploader.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +1 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/inline-backend.d.ts +2 -2
- package/dist/types/src/capabilities/inline-backend.d.ts.map +1 -1
- package/dist/types/src/capabilities/markdown-extension.d.ts +1 -1
- package/dist/types/src/capabilities/node.d.ts +5 -0
- package/dist/types/src/capabilities/node.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/settings.d.ts +1 -1
- package/dist/types/src/components/FileInput/FileInput.d.ts.map +1 -1
- package/dist/types/src/components/FilePreview/FilePreview.d.ts +1 -1
- package/dist/types/src/components/FilePreview/FilePreview.d.ts.map +1 -1
- package/dist/types/src/containers/FileArticle/FileArticle.d.ts +4 -1
- package/dist/types/src/containers/FileArticle/FileArticle.d.ts.map +1 -1
- package/dist/types/src/containers/FileSettings/FileSettings.d.ts +5 -3
- package/dist/types/src/containers/FileSettings/FileSettings.d.ts.map +1 -1
- package/dist/types/src/extensions/image.d.ts +0 -2
- package/dist/types/src/extensions/image.d.ts.map +1 -1
- package/dist/types/src/operations/create.d.ts +14 -5
- package/dist/types/src/operations/create.d.ts.map +1 -1
- package/dist/types/src/operations/read.d.ts.map +1 -1
- package/dist/types/src/types/FileCapabilities.d.ts +10 -28
- package/dist/types/src/types/FileCapabilities.d.ts.map +1 -1
- package/dist/types/src/types/Settings.d.ts +1 -1
- package/dist/types/src/types/Settings.d.ts.map +1 -1
- package/dist/types/src/types/limits.d.ts +0 -1
- package/dist/types/src/types/limits.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +10 -6
- package/package.json +45 -41
- package/src/FilePlugin.node.ts +2 -1
- package/src/FilePlugin.tsx +8 -1
- package/src/capabilities/FileUploadField.tsx +37 -0
- package/src/capabilities/edge-backend.ts +27 -0
- package/src/capabilities/file-uploader.ts +6 -4
- package/src/capabilities/index.ts +1 -0
- package/src/capabilities/inline-backend.ts +4 -21
- package/src/capabilities/markdown-extension.ts +3 -8
- package/src/capabilities/node.ts +16 -0
- package/src/capabilities/react-surface.ts +46 -0
- package/src/components/FileInput/FileInput.tsx +3 -2
- package/src/containers/FileArticle/FileArticle.tsx +49 -42
- package/src/containers/FileSettings/FileSettings.tsx +19 -14
- package/src/extensions/image.tsx +49 -28
- package/src/operations/create.test.ts +122 -32
- package/src/operations/create.ts +44 -24
- package/src/operations/read.ts +24 -6
- package/src/types/FileCapabilities.ts +10 -31
- package/src/types/Settings.ts +1 -1
- package/src/types/limits.ts +0 -2
- package/dist/lib/neutral/FileArticle-YU6NN3KN.mjs +0 -80
- package/dist/lib/neutral/FileArticle-YU6NN3KN.mjs.map +0 -7
- package/dist/lib/neutral/FilePlugin.mjs +0 -58
- package/dist/lib/neutral/FilePlugin.mjs.map +0 -7
- package/dist/lib/neutral/FilePlugin.node.mjs +0 -23
- package/dist/lib/neutral/FilePlugin.node.mjs.map +0 -7
- package/dist/lib/neutral/capabilities/index.mjs +0 -23
- package/dist/lib/neutral/capabilities/index.mjs.map +0 -7
- package/dist/lib/neutral/chunk-DKGTSUUH.mjs +0 -8
- package/dist/lib/neutral/chunk-DKGTSUUH.mjs.map +0 -7
- package/dist/lib/neutral/chunk-GMYSEHV4.mjs +0 -40
- package/dist/lib/neutral/chunk-GMYSEHV4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-LBZ4MCLI.mjs +0 -45
- package/dist/lib/neutral/chunk-LBZ4MCLI.mjs.map +0 -7
- package/dist/lib/neutral/chunk-YODPX62X.mjs +0 -136
- package/dist/lib/neutral/chunk-YODPX62X.mjs.map +0 -7
- package/dist/lib/neutral/chunk-ZUXUTRAJ.mjs +0 -80
- package/dist/lib/neutral/chunk-ZUXUTRAJ.mjs.map +0 -7
- package/dist/lib/neutral/components/index.mjs +0 -74
- package/dist/lib/neutral/components/index.mjs.map +0 -7
- package/dist/lib/neutral/containers/index.mjs +0 -59
- package/dist/lib/neutral/containers/index.mjs.map +0 -7
- package/dist/lib/neutral/create-IUUG7RI6.mjs +0 -17
- package/dist/lib/neutral/create-IUUG7RI6.mjs.map +0 -7
- package/dist/lib/neutral/create-object-V667G4ZL.mjs +0 -30
- package/dist/lib/neutral/create-object-V667G4ZL.mjs.map +0 -7
- package/dist/lib/neutral/file-uploader-BCJH6TUL.mjs +0 -41
- package/dist/lib/neutral/file-uploader-BCJH6TUL.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -32
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/inline-backend-FGGGDYRB.mjs +0 -37
- package/dist/lib/neutral/inline-backend-FGGGDYRB.mjs.map +0 -7
- package/dist/lib/neutral/markdown-extension-RA5YNMTW.mjs +0 -230
- package/dist/lib/neutral/markdown-extension-RA5YNMTW.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/meta.mjs +0 -8
- package/dist/lib/neutral/meta.mjs.map +0 -7
- package/dist/lib/neutral/operation-handler-CPDANWGH.mjs +0 -13
- package/dist/lib/neutral/operation-handler-CPDANWGH.mjs.map +0 -7
- package/dist/lib/neutral/operations/index.mjs +0 -8
- package/dist/lib/neutral/operations/index.mjs.map +0 -7
- package/dist/lib/neutral/plugin.mjs +0 -16
- package/dist/lib/neutral/plugin.mjs.map +0 -7
- package/dist/lib/neutral/react-surface-5BXKSHD2.mjs +0 -60
- package/dist/lib/neutral/react-surface-5BXKSHD2.mjs.map +0 -7
- package/dist/lib/neutral/read-HG6CSWKR.mjs +0 -28
- package/dist/lib/neutral/read-HG6CSWKR.mjs.map +0 -7
- package/dist/lib/neutral/settings-RR3RESL2.mjs +0 -28
- package/dist/lib/neutral/settings-RR3RESL2.mjs.map +0 -7
- package/dist/lib/neutral/skill-definition-YHBGWBUS.mjs +0 -15
- package/dist/lib/neutral/skill-definition-YHBGWBUS.mjs.map +0 -7
- package/dist/lib/neutral/skills/index.mjs +0 -11
- package/dist/lib/neutral/skills/index.mjs.map +0 -7
- package/dist/lib/neutral/translations.mjs +0 -36
- package/dist/lib/neutral/translations.mjs.map +0 -7
- package/dist/lib/neutral/types/index.mjs +0 -22
- package/dist/lib/neutral/types/index.mjs.map +0 -7
- package/src/capabilities/react-surface.tsx +0 -61
package/PLUGIN.mdl
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: org.dxos.plugin.file
|
|
3
3
|
name: FilePlugin
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
FilePlugin enables DXOS Composer to store and preview binary files — images, videos, and PDFs — as first-class ECHO objects.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
A File object's `data` field is a reference to a `Blob` ECHO object (defined in `@dxos/echo`), which abstracts where the
|
|
9
|
+
bytes actually live and how they're read. Uploads are routed through a pluggable storage system: the built-in `inline`
|
|
10
|
+
storage embeds bytes on the Blob object itself, while external storage (e.g. WNFS, edge) can be contributed by other
|
|
11
|
+
plugins by registering a `BlobBackend` on the ECHO Hypergraph. `Blob.url`/`Blob.read` resolve a renderable URL or raw
|
|
12
|
+
bytes uniformly regardless of backend, so the File schema and preview surfaces stay backend-agnostic without a
|
|
13
|
+
dedicated URL-resolver capability.
|
|
12
14
|
|
|
13
15
|
## Extensions
|
|
14
16
|
|
|
@@ -25,55 +27,33 @@ Each extension is defined in the Appendix or resolved via its URI.
|
|
|
25
27
|
|
|
26
28
|
## Types
|
|
27
29
|
|
|
28
|
-
```mdl
|
|
29
|
-
type FileDataInline
|
|
30
|
-
desc: File bytes stored directly on the ECHO object.
|
|
31
|
-
fields:
|
|
32
|
-
_tag: 'inline'
|
|
33
|
-
bytes: Uint8Array
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
```mdl
|
|
37
|
-
type FileDataExternal
|
|
38
|
-
desc: File bytes stored outside ECHO; referenced by URL.
|
|
39
|
-
fields:
|
|
40
|
-
_tag: 'external'
|
|
41
|
-
url: string
|
|
42
|
-
cid?: string # optional content-addressed identifier
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
```mdl
|
|
46
|
-
type FileData
|
|
47
|
-
desc: Discriminated union of inline and external storage.
|
|
48
|
-
literals: FileDataInline | FileDataExternal
|
|
49
|
-
```
|
|
50
|
-
|
|
51
30
|
```mdl
|
|
52
31
|
type File
|
|
53
|
-
desc: ECHO object (typename dxos.
|
|
32
|
+
desc: ECHO object (typename org.dxos.type.file, version 0.2.0) representing an uploaded binary asset.
|
|
54
33
|
fields:
|
|
55
34
|
name?: string
|
|
56
35
|
type: string # MIME type
|
|
57
36
|
size: number # byte count
|
|
58
|
-
data:
|
|
37
|
+
data: Ref<Blob> # reference to a Blob object (org.dxos.type.blob, defined in @dxos/echo)
|
|
59
38
|
timestamp?: string # ISO 8601 upload time
|
|
60
39
|
```
|
|
61
40
|
|
|
62
41
|
```mdl
|
|
63
|
-
type
|
|
64
|
-
desc:
|
|
42
|
+
type BackendDescriptor
|
|
43
|
+
desc: |
|
|
44
|
+
UI-facing descriptor for a registered storage option. Carries no upload/read behavior itself —
|
|
45
|
+
that lives in the matching `BlobBackend` registered on the ECHO Hypergraph under `storage`.
|
|
65
46
|
fields:
|
|
66
|
-
name: string
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
data: FileData
|
|
47
|
+
name: string # display label shown in settings
|
|
48
|
+
description?: string # description shown next to the backend in settings
|
|
49
|
+
storage: string # Blob.fromBytes's `storage` option; also the settings key (e.g. 'inline', 'wnfs', 'edge')
|
|
70
50
|
```
|
|
71
51
|
|
|
72
52
|
```mdl
|
|
73
53
|
type Settings
|
|
74
54
|
desc: Per-user plugin settings stored in the ECHO space.
|
|
75
55
|
fields:
|
|
76
|
-
backend?: string # active backend
|
|
56
|
+
backend?: string # active backend's storage name; defaults to 'inline'
|
|
77
57
|
```
|
|
78
58
|
|
|
79
59
|
## Components
|
|
@@ -86,7 +66,7 @@ component FilePreview
|
|
|
86
66
|
all other types fall back to a download link.
|
|
87
67
|
props:
|
|
88
68
|
type: string # MIME type
|
|
89
|
-
url: string # renderable
|
|
69
|
+
url: string # renderable data:, blob:, or http(s): URL
|
|
90
70
|
layout: |
|
|
91
71
|
┌──────────────────────────────┐
|
|
92
72
|
│ <img> / <video> / <iframe> │
|
|
@@ -118,7 +98,7 @@ component FileSettings
|
|
|
118
98
|
┌──────────────────────────────┐
|
|
119
99
|
│ Storage backend: [select ▼] │
|
|
120
100
|
│ (one row per registered │
|
|
121
|
-
│
|
|
101
|
+
│ BackendDescriptor) │
|
|
122
102
|
└──────────────────────────────┘
|
|
123
103
|
```
|
|
124
104
|
|
|
@@ -126,9 +106,11 @@ component FileSettings
|
|
|
126
106
|
component FileArticle
|
|
127
107
|
desc: |
|
|
128
108
|
Full-viewport article container rendered for article, section, and slide
|
|
129
|
-
roles.
|
|
130
|
-
|
|
131
|
-
|
|
109
|
+
roles. Loads the File's referenced Blob and resolves a renderable URL via
|
|
110
|
+
Blob.url; if the backend has no getUrl, falls back to Blob.read and
|
|
111
|
+
constructs a blob: object URL (revoked on unmount). Delegates to
|
|
112
|
+
FilePreview. Shows an empty state while the URL is resolving or if the
|
|
113
|
+
File is not attached to a database.
|
|
132
114
|
props:
|
|
133
115
|
role: AppSurface.Role
|
|
134
116
|
subject: File
|
|
@@ -147,17 +129,20 @@ component FileArticle
|
|
|
147
129
|
op Create
|
|
148
130
|
key: org.dxos.plugin.file.operation.create
|
|
149
131
|
desc: |
|
|
150
|
-
Validates a browser File object
|
|
151
|
-
|
|
152
|
-
|
|
132
|
+
Validates a browser File object's MIME type, resolves the active
|
|
133
|
+
BackendDescriptor, and composes File.fromBytes (which hashes/uploads the
|
|
134
|
+
bytes via the Blob API and adds the resulting Blob to the database) to
|
|
135
|
+
construct a File ECHO object. Does not add the File object itself to the
|
|
136
|
+
space — callers do that separately.
|
|
153
137
|
input:
|
|
154
138
|
file: BrowserFile # browser File (globalThis.File)
|
|
155
139
|
db: Database
|
|
156
140
|
output: { object: File }
|
|
157
141
|
errors:
|
|
158
142
|
UnsupportedFileTypeError: MIME type is not image/*, video/*, or application/pdf
|
|
159
|
-
FileTooLargeError: file exceeds the 4 MB inline limit
|
|
160
|
-
|
|
143
|
+
FileTooLargeError: file exceeds the 4 MB inline limit (only possible when the
|
|
144
|
+
resolved backend's storage is 'inline'; other backends scale beyond it)
|
|
145
|
+
NoBackendError: no BackendDescriptor capability is registered
|
|
161
146
|
```
|
|
162
147
|
|
|
163
148
|
## Features
|
|
@@ -168,47 +153,57 @@ feat F-1: File upload
|
|
|
168
153
|
req F-1.1: Accepted MIME types MUST be image/* (jpg, jpeg, png, gif, webp, svg),
|
|
169
154
|
video/* (mp4, webm, mov), and application/pdf only.
|
|
170
155
|
|
|
171
|
-
req F-1.2: Maximum upload size MUST be 4 MB
|
|
156
|
+
req F-1.2: Maximum upload size MUST be 4 MB when the resolved backend's storage
|
|
157
|
+
is 'inline'. Other backends (e.g. edge, wnfs) are not subject to this cap.
|
|
172
158
|
|
|
173
159
|
req F-1.3:
|
|
174
160
|
when: user submits the Create Object form with a valid file
|
|
175
161
|
then:
|
|
176
162
|
- op:Create is invoked with the selected file and active database
|
|
177
163
|
- the resulting File object is added to the space via SpaceOperation.AddObject
|
|
178
|
-
- FileUploader capability returns metadata to the caller (name, type,
|
|
164
|
+
- FileUploader capability returns metadata to the caller (name, type, and a
|
|
165
|
+
renderable url resolved via Blob.url, if one is available)
|
|
179
166
|
|
|
180
167
|
req F-1.4:
|
|
181
168
|
when: file MIME type is not accepted
|
|
182
169
|
then: UnsupportedFileTypeError returned; no ECHO write occurs
|
|
183
170
|
|
|
184
171
|
req F-1.5:
|
|
185
|
-
when: file size exceeds the limit
|
|
172
|
+
when: file size exceeds the limit and the resolved backend's storage is 'inline'
|
|
186
173
|
then: FileTooLargeError returned; no ECHO write occurs
|
|
187
174
|
```
|
|
188
175
|
|
|
189
176
|
```mdl
|
|
190
177
|
feat F-2: Backend capability system
|
|
191
178
|
|
|
192
|
-
req F-2.1: Any plugin may contribute a
|
|
193
|
-
may be registered simultaneously.
|
|
179
|
+
req F-2.1: Any plugin may contribute a BackendDescriptor capability. Multiple
|
|
180
|
+
descriptors may be registered simultaneously.
|
|
194
181
|
|
|
195
|
-
req F-2.2: The inline
|
|
196
|
-
|
|
182
|
+
req F-2.2: The inline BackendDescriptor MUST always be registered by FilePlugin
|
|
183
|
+
itself, mapping to Blob's built-in 'inline' storage (bytes embedded on the
|
|
184
|
+
Blob object; no separate BlobBackend registration needed).
|
|
197
185
|
|
|
198
186
|
req F-2.3: The active backend is determined by Settings.backend. If the
|
|
199
187
|
configured id is absent or unrecognised, the inline backend is used.
|
|
200
188
|
|
|
201
|
-
req F-2.4: A
|
|
202
|
-
|
|
203
|
-
|
|
189
|
+
req F-2.4: A BackendDescriptor's `storage` names the Blob storage used by
|
|
190
|
+
File.fromBytes; the actual upload/read/getUrl implementation lives in a
|
|
191
|
+
separate `BlobBackend` registered on the ECHO Hypergraph (via
|
|
192
|
+
`client.graph.registerBlobBackend`), typically by the same plugin that
|
|
193
|
+
contributes the descriptor (e.g. plugin-wnfs, or `@dxos/client` itself for
|
|
194
|
+
'edge'). FilePlugin's descriptor and the Hypergraph's BlobBackend registry
|
|
195
|
+
are independent — a descriptor with no matching registered backend fails at
|
|
196
|
+
upload time with a `backend-not-registered` error, not at settings-selection time.
|
|
204
197
|
```
|
|
205
198
|
|
|
206
199
|
```mdl
|
|
207
200
|
feat F-3: File preview
|
|
208
201
|
|
|
209
|
-
req F-3.1: FileArticle MUST resolve a renderable URL before
|
|
210
|
-
FilePreview. For inline files
|
|
211
|
-
|
|
202
|
+
req F-3.1: FileArticle MUST resolve a renderable URL via Blob.url before
|
|
203
|
+
rendering FilePreview. For inline files this is always a data: URL
|
|
204
|
+
(constructed synchronously from the stored bytes, no Blob.read needed). For
|
|
205
|
+
external files with no registered getUrl, FileArticle falls back to
|
|
206
|
+
Blob.read and constructs a blob: URL, revoked on unmount.
|
|
212
207
|
|
|
213
208
|
req F-3.2: FilePreview MUST render the appropriate element for each MIME class:
|
|
214
209
|
image/* → <img>, video/* → <video controls>, application/pdf → <iframe>,
|
|
@@ -222,14 +217,16 @@ feat F-3: File preview
|
|
|
222
217
|
feat F-4: Markdown integration
|
|
223
218
|
|
|
224
219
|
req F-4.1: A Markdown extension contributed at MarkdownEvents.SetupExtensions
|
|
225
|
-
MUST allow files to be embedded in Markdown documents as inline attachments
|
|
220
|
+
MUST allow files to be embedded in Markdown documents as inline attachments,
|
|
221
|
+
resolving each embedded File's Blob URL the same way as FileArticle (Blob.url,
|
|
222
|
+
falling back to Blob.read + blob: URL).
|
|
226
223
|
```
|
|
227
224
|
|
|
228
225
|
```mdl
|
|
229
226
|
feat F-5: Settings
|
|
230
227
|
|
|
231
|
-
req F-5.1: The Settings panel MUST list all registered
|
|
232
|
-
id, display name, and optional description.
|
|
228
|
+
req F-5.1: The Settings panel MUST list all registered BackendDescriptor
|
|
229
|
+
capabilities with their id, display name, and optional description.
|
|
233
230
|
|
|
234
231
|
req F-5.2: Selecting a backend MUST persist the choice to Settings.backend
|
|
235
232
|
via the SettingsAtom capability.
|
|
@@ -239,17 +236,17 @@ feat F-5: Settings
|
|
|
239
236
|
|
|
240
237
|
```mdl
|
|
241
238
|
test T-1: Upload image inline
|
|
242
|
-
given: only the inline
|
|
239
|
+
given: only the inline BackendDescriptor is registered
|
|
243
240
|
when: user uploads a 1 MB PNG via the Create Object form
|
|
244
241
|
then:
|
|
245
|
-
- op:Create returns a File with data._tag === 'inline'
|
|
242
|
+
- op:Create returns a File whose data Ref resolves to a Blob with data._tag === 'inline'
|
|
246
243
|
- File.type === 'image/png', File.size === byte count
|
|
247
244
|
- File is added to the space
|
|
248
245
|
```
|
|
249
246
|
|
|
250
247
|
```mdl
|
|
251
248
|
test T-2: Reject oversized file
|
|
252
|
-
given: inline backend
|
|
249
|
+
given: inline backend selected, 4 MB limit
|
|
253
250
|
when: user uploads a 5 MB JPEG
|
|
254
251
|
then:
|
|
255
252
|
- FileTooLargeError is returned
|
|
@@ -268,41 +265,53 @@ test T-3: Reject unsupported MIME type
|
|
|
268
265
|
|
|
269
266
|
```mdl
|
|
270
267
|
test T-4: Preview inline image
|
|
271
|
-
given: a File with data._tag === 'inline' and type === 'image/png'
|
|
268
|
+
given: a File whose data Ref resolves to a Blob with data._tag === 'inline' and type === 'image/png'
|
|
272
269
|
when: FileArticle is rendered
|
|
273
270
|
then:
|
|
274
|
-
- a
|
|
271
|
+
- Blob.url resolves a data: URL synchronously from the Blob's bytes (no Blob.read call)
|
|
275
272
|
- FilePreview renders an <img> element with that URL
|
|
276
|
-
- the blob: URL is revoked on unmount
|
|
277
273
|
```
|
|
278
274
|
|
|
279
275
|
```mdl
|
|
280
276
|
test T-5: Preview PDF
|
|
281
|
-
given: a File with type === 'application/pdf' and a resolved HTTP URL
|
|
277
|
+
given: a File with type === 'application/pdf' and a Blob resolved to an HTTP or data URL
|
|
282
278
|
when: FilePreview is rendered
|
|
283
279
|
then: an <iframe> element is rendered with the file URL as src
|
|
284
280
|
```
|
|
285
281
|
|
|
286
282
|
```mdl
|
|
287
283
|
test T-6: Backend fallback to inline
|
|
288
|
-
given: Settings.backend is 'wnfs' but no
|
|
284
|
+
given: Settings.backend is 'wnfs' but no BackendDescriptor with storage 'wnfs' is registered
|
|
289
285
|
when: op:Create is invoked
|
|
290
286
|
then: the inline backend is used and the file is stored inline
|
|
291
287
|
```
|
|
292
288
|
|
|
293
289
|
```mdl
|
|
294
|
-
test T-7: External
|
|
295
|
-
given: a File with data._tag === 'external' and
|
|
296
|
-
|
|
290
|
+
test T-7: External blob resolved via a registered BlobBackend's getUrl
|
|
291
|
+
given: a File whose data Ref resolves to a Blob with data._tag === 'external' and a
|
|
292
|
+
BlobBackend registered on the ECHO Hypergraph that resolves the Blob's URI scheme
|
|
293
|
+
and implements getUrl
|
|
297
294
|
when: FileArticle is rendered
|
|
298
295
|
then:
|
|
299
|
-
-
|
|
296
|
+
- Blob.url calls the registered backend's getUrl and resolves Some(url)
|
|
300
297
|
- FilePreview receives the resolved renderable URL
|
|
298
|
+
- Blob.read is never called
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```mdl
|
|
302
|
+
test T-8: External blob without getUrl falls back to reading bytes
|
|
303
|
+
given: a File whose data Ref resolves to an external Blob whose registered
|
|
304
|
+
BlobBackend implements get but not getUrl
|
|
305
|
+
when: FileArticle is rendered
|
|
306
|
+
then:
|
|
307
|
+
- Blob.url resolves None
|
|
308
|
+
- Blob.read is called to fetch the bytes
|
|
309
|
+
- a blob: object URL is created from the bytes and revoked on unmount
|
|
301
310
|
```
|
|
302
311
|
|
|
303
312
|
```mdl
|
|
304
|
-
test T-
|
|
305
|
-
given: no
|
|
313
|
+
test T-9: No backend error
|
|
314
|
+
given: no BackendDescriptor capability is registered
|
|
306
315
|
when: op:Create is invoked
|
|
307
316
|
then: NoBackendError is returned
|
|
308
317
|
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { meta } from "#meta";
|
|
2
|
+
import { Plugin } from "@dxos/app-framework";
|
|
3
|
+
import { Blob } from "@dxos/echo";
|
|
4
|
+
import { AppActivationEvents, AppPlugin } from "@dxos/app-toolkit";
|
|
5
|
+
import { ClientEvents } from "@dxos/plugin-client";
|
|
6
|
+
import { MarkdownEvents } from "@dxos/plugin-markdown";
|
|
7
|
+
import { CreateObject, EdgeBackend, FileUploader, InlineBackend, Markdown, OperationHandler, ReactSurface, Settings, SkillDefinition } from "#capabilities";
|
|
8
|
+
import { translations } from "#translations";
|
|
9
|
+
import { File } from "#types";
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/FilePlugin.tsx
|
|
12
|
+
var FilePlugin = Plugin.define(meta).pipe(AppPlugin.addSkillDefinitionModule({ activate: SkillDefinition }), AppPlugin.addCreateObjectModule({ activate: CreateObject }), AppPlugin.addOperationHandlerModule({ activate: OperationHandler }), AppPlugin.addSchemaModule({ schema: [File.File, Blob.Blob] }), AppPlugin.addSettingsModule({ activate: Settings }), AppPlugin.addSurfaceModule({ activate: ReactSurface }), AppPlugin.addTranslationsModule({ translations }), Plugin.addModule({
|
|
13
|
+
id: "inline-backend",
|
|
14
|
+
activatesOn: AppActivationEvents.SetupSchema,
|
|
15
|
+
activate: InlineBackend
|
|
16
|
+
}), Plugin.addModule({
|
|
17
|
+
id: "file-uploader",
|
|
18
|
+
activatesOn: ClientEvents.ClientReady,
|
|
19
|
+
activate: FileUploader
|
|
20
|
+
}), Plugin.addModule({
|
|
21
|
+
id: "edge-backend",
|
|
22
|
+
activatesOn: ClientEvents.ClientReady,
|
|
23
|
+
activate: EdgeBackend
|
|
24
|
+
}), Plugin.addModule({
|
|
25
|
+
id: "markdown",
|
|
26
|
+
activatesOn: MarkdownEvents.SetupExtensions,
|
|
27
|
+
activate: Markdown
|
|
28
|
+
}), AppPlugin.addPluginAssetModule({ asset: {
|
|
29
|
+
pluginId: meta.profile.key,
|
|
30
|
+
path: "PLUGIN.mdl",
|
|
31
|
+
content: "---\nid: org.dxos.plugin.file\nname: FilePlugin\nversion: 0.2.0\n---\n\nFilePlugin enables DXOS Composer to store and preview binary files — images, videos, and PDFs — as first-class ECHO objects.\nA File object's `data` field is a reference to a `Blob` ECHO object (defined in `@dxos/echo`), which abstracts where the\nbytes actually live and how they're read. Uploads are routed through a pluggable storage system: the built-in `inline`\nstorage embeds bytes on the Blob object itself, while external storage (e.g. WNFS, edge) can be contributed by other\nplugins by registering a `BlobBackend` on the ECHO Hypergraph. `Blob.url`/`Blob.read` resolve a renderable URL or raw\nbytes uniformly regardless of backend, so the File schema and preview surfaces stay backend-agnostic without a\ndedicated URL-resolver capability.\n\n## Extensions\n\nThe following extension dialects are used in this document.\nEach extension is defined in the Appendix or resolved via its URI.\n\n| Term | URI |\n|-------------|--------------------------------|\n| `type` | `org.dxos.mdl.type@1.0` |\n| `feat` | `org.dxos.mdl.feat@1.0` |\n| `test` | `org.dxos.mdl.test@1.0` |\n| `component` | `org.dxos.mdl.component@1.0` |\n| `op` | `org.dxos.mdl.op@1.0` |\n\n## Types\n\n```mdl\ntype File\n desc: ECHO object (typename org.dxos.type.file, version 0.2.0) representing an uploaded binary asset.\n fields:\n name?: string\n type: string # MIME type\n size: number # byte count\n data: Ref<Blob> # reference to a Blob object (org.dxos.type.blob, defined in @dxos/echo)\n timestamp?: string # ISO 8601 upload time\n```\n\n```mdl\ntype BackendDescriptor\n desc: |\n UI-facing descriptor for a registered storage option. Carries no upload/read behavior itself —\n that lives in the matching `BlobBackend` registered on the ECHO Hypergraph under `storage`.\n fields:\n name: string # display label shown in settings\n description?: string # description shown next to the backend in settings\n storage: string # Blob.fromBytes's `storage` option; also the settings key (e.g. 'inline', 'wnfs', 'edge')\n```\n\n```mdl\ntype Settings\n desc: Per-user plugin settings stored in the ECHO space.\n fields:\n backend?: string # active backend's storage name; defaults to 'inline'\n```\n\n## Components\n\n```mdl\ncomponent FilePreview\n desc: |\n Renders a File object using the appropriate HTML element for its MIME type.\n Images use <img>, videos use <video controls>, PDFs use <iframe>, and\n all other types fall back to a download link.\n props:\n type: string # MIME type\n url: string # renderable data:, blob:, or http(s): URL\n layout: |\n ┌──────────────────────────────┐\n │ <img> / <video> / <iframe> │\n │ or [Download file] link │\n └──────────────────────────────┘\n```\n\n```mdl\ncomponent FileInput\n desc: Drag-and-drop / browse file picker used inside the Create Object form.\n props:\n schema: Schema # effect/Schema with UploadAnnotation on the file field\n emits:\n onChange(file: File)\n layout: |\n ┌──────────────────────────────┐\n │ [ Drop file or click here ] │\n └──────────────────────────────┘\n```\n\n```mdl\ncomponent FileSettings\n desc: Plugin settings panel rendered in the Composer settings surface.\n props:\n settings: Settings\n emits:\n onSettingsChange(settings: Settings)\n layout: |\n ┌──────────────────────────────┐\n │ Storage backend: [select ▼] │\n │ (one row per registered │\n │ BackendDescriptor) │\n └──────────────────────────────┘\n```\n\n```mdl\ncomponent FileArticle\n desc: |\n Full-viewport article container rendered for article, section, and slide\n roles. Loads the File's referenced Blob and resolves a renderable URL via\n Blob.url; if the backend has no getUrl, falls back to Blob.read and\n constructs a blob: object URL (revoked on unmount). Delegates to\n FilePreview. Shows an empty state while the URL is resolving or if the\n File is not attached to a database.\n props:\n role: AppSurface.Role\n subject: File\n layout: |\n ┌──────────────────────────────┐\n │ │\n │ FilePreview │\n │ (fills container) │\n │ │\n └──────────────────────────────┘\n```\n\n## Operations\n\n```mdl\nop Create\n key: org.dxos.plugin.file.operation.create\n desc: |\n Validates a browser File object's MIME type, resolves the active\n BackendDescriptor, and composes File.fromBytes (which hashes/uploads the\n bytes via the Blob API and adds the resulting Blob to the database) to\n construct a File ECHO object. Does not add the File object itself to the\n space — callers do that separately.\n input:\n file: BrowserFile # browser File (globalThis.File)\n db: Database\n output: { object: File }\n errors:\n UnsupportedFileTypeError: MIME type is not image/*, video/*, or application/pdf\n FileTooLargeError: file exceeds the 4 MB inline limit (only possible when the\n resolved backend's storage is 'inline'; other backends scale beyond it)\n NoBackendError: no BackendDescriptor capability is registered\n```\n\n## Features\n\n```mdl\nfeat F-1: File upload\n\n req F-1.1: Accepted MIME types MUST be image/* (jpg, jpeg, png, gif, webp, svg),\n video/* (mp4, webm, mov), and application/pdf only.\n\n req F-1.2: Maximum upload size MUST be 4 MB when the resolved backend's storage\n is 'inline'. Other backends (e.g. edge, wnfs) are not subject to this cap.\n\n req F-1.3:\n when: user submits the Create Object form with a valid file\n then:\n - op:Create is invoked with the selected file and active database\n - the resulting File object is added to the space via SpaceOperation.AddObject\n - FileUploader capability returns metadata to the caller (name, type, and a\n renderable url resolved via Blob.url, if one is available)\n\n req F-1.4:\n when: file MIME type is not accepted\n then: UnsupportedFileTypeError returned; no ECHO write occurs\n\n req F-1.5:\n when: file size exceeds the limit and the resolved backend's storage is 'inline'\n then: FileTooLargeError returned; no ECHO write occurs\n```\n\n```mdl\nfeat F-2: Backend capability system\n\n req F-2.1: Any plugin may contribute a BackendDescriptor capability. Multiple\n descriptors may be registered simultaneously.\n\n req F-2.2: The inline BackendDescriptor MUST always be registered by FilePlugin\n itself, mapping to Blob's built-in 'inline' storage (bytes embedded on the\n Blob object; no separate BlobBackend registration needed).\n\n req F-2.3: The active backend is determined by Settings.backend. If the\n configured id is absent or unrecognised, the inline backend is used.\n\n req F-2.4: A BackendDescriptor's `storage` names the Blob storage used by\n File.fromBytes; the actual upload/read/getUrl implementation lives in a\n separate `BlobBackend` registered on the ECHO Hypergraph (via\n `client.graph.registerBlobBackend`), typically by the same plugin that\n contributes the descriptor (e.g. plugin-wnfs, or `@dxos/client` itself for\n 'edge'). FilePlugin's descriptor and the Hypergraph's BlobBackend registry\n are independent — a descriptor with no matching registered backend fails at\n upload time with a `backend-not-registered` error, not at settings-selection time.\n```\n\n```mdl\nfeat F-3: File preview\n\n req F-3.1: FileArticle MUST resolve a renderable URL via Blob.url before\n rendering FilePreview. For inline files this is always a data: URL\n (constructed synchronously from the stored bytes, no Blob.read needed). For\n external files with no registered getUrl, FileArticle falls back to\n Blob.read and constructs a blob: URL, revoked on unmount.\n\n req F-3.2: FilePreview MUST render the appropriate element for each MIME class:\n image/* → <img>, video/* → <video controls>, application/pdf → <iframe>,\n other → <a download>.\n\n req F-3.3: FileArticle surfaces are registered for article, section,\n and slide roles so files embed correctly in Markdown and Presenter layouts.\n```\n\n```mdl\nfeat F-4: Markdown integration\n\n req F-4.1: A Markdown extension contributed at MarkdownEvents.SetupExtensions\n MUST allow files to be embedded in Markdown documents as inline attachments,\n resolving each embedded File's Blob URL the same way as FileArticle (Blob.url,\n falling back to Blob.read + blob: URL).\n```\n\n```mdl\nfeat F-5: Settings\n\n req F-5.1: The Settings panel MUST list all registered BackendDescriptor\n capabilities with their id, display name, and optional description.\n\n req F-5.2: Selecting a backend MUST persist the choice to Settings.backend\n via the SettingsAtom capability.\n```\n\n## Acceptance\n\n```mdl\ntest T-1: Upload image inline\n given: only the inline BackendDescriptor is registered\n when: user uploads a 1 MB PNG via the Create Object form\n then:\n - op:Create returns a File whose data Ref resolves to a Blob with data._tag === 'inline'\n - File.type === 'image/png', File.size === byte count\n - File is added to the space\n```\n\n```mdl\ntest T-2: Reject oversized file\n given: inline backend selected, 4 MB limit\n when: user uploads a 5 MB JPEG\n then:\n - FileTooLargeError is returned\n - no File object is created\n - no ECHO write occurs\n```\n\n```mdl\ntest T-3: Reject unsupported MIME type\n given: inline backend registered\n when: user uploads a .zip file\n then:\n - UnsupportedFileTypeError is returned\n - no File object is created\n```\n\n```mdl\ntest T-4: Preview inline image\n given: a File whose data Ref resolves to a Blob with data._tag === 'inline' and type === 'image/png'\n when: FileArticle is rendered\n then:\n - Blob.url resolves a data: URL synchronously from the Blob's bytes (no Blob.read call)\n - FilePreview renders an <img> element with that URL\n```\n\n```mdl\ntest T-5: Preview PDF\n given: a File with type === 'application/pdf' and a Blob resolved to an HTTP or data URL\n when: FilePreview is rendered\n then: an <iframe> element is rendered with the file URL as src\n```\n\n```mdl\ntest T-6: Backend fallback to inline\n given: Settings.backend is 'wnfs' but no BackendDescriptor with storage 'wnfs' is registered\n when: op:Create is invoked\n then: the inline backend is used and the file is stored inline\n```\n\n```mdl\ntest T-7: External blob resolved via a registered BlobBackend's getUrl\n given: a File whose data Ref resolves to a Blob with data._tag === 'external' and a\n BlobBackend registered on the ECHO Hypergraph that resolves the Blob's URI scheme\n and implements getUrl\n when: FileArticle is rendered\n then:\n - Blob.url calls the registered backend's getUrl and resolves Some(url)\n - FilePreview receives the resolved renderable URL\n - Blob.read is never called\n```\n\n```mdl\ntest T-8: External blob without getUrl falls back to reading bytes\n given: a File whose data Ref resolves to an external Blob whose registered\n BlobBackend implements get but not getUrl\n when: FileArticle is rendered\n then:\n - Blob.url resolves None\n - Blob.read is called to fetch the bytes\n - a blob: object URL is created from the bytes and revoked on unmount\n```\n\n```mdl\ntest T-9: No backend error\n given: no BackendDescriptor capability is registered\n when: op:Create is invoked\n then: NoBackendError is returned\n```\n\n---\n\n## Appendix: Extension Definitions\n\n```mdl\next type\n uri: org.dxos.mdl.type@1.0\n desc: A named data structure with typed fields and optional literals.\n fields:\n desc?: Prose\n fields?: FieldMap\n literals?: UnionList\n extends?: TypeRef[]\n```\n\n```mdl\next feat\n uri: org.dxos.mdl.feat@1.0\n desc: A named feature grouping one or more requirements.\n fields:\n desc?: Prose\n req: RequirementList\n nesting: self\n```\n\n```mdl\next test\n uri: org.dxos.mdl.test@1.0\n desc: An acceptance scenario expressed as given / when / then steps.\n fields:\n given?: Step | Step[]\n when?: Step | Step[]\n then: Step | Step[]\n tags?: TagList\n```\n\n```mdl\next component\n uri: org.dxos.mdl.component@1.0\n desc: A UI component with props, internal state, slots, actions, and events.\n fields:\n desc?: Prose\n props?: FieldMap\n state?: FieldMap\n slots?: FieldMap\n actions?: ActionMap\n emits?: EventMap\n layout?: CodeBlock\n```\n\n```mdl\next op\n uri: org.dxos.mdl.op@1.0\n desc: A named operation with typed inputs, outputs, and declared errors.\n fields:\n desc?: Prose\n input?: FieldMap\n output?: TypeExpr\n errors?: ErrorMap\n effects?: EffectList\n requires?: ServiceList\n note?: Prose\n```\n",
|
|
32
|
+
mimeType: "application/x-mdl"
|
|
33
|
+
} }), Plugin.make);
|
|
34
|
+
//#endregion
|
|
35
|
+
export { FilePlugin, FilePlugin as default };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=FilePlugin.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilePlugin.mjs","names":[],"sources":["../../src/FilePlugin.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\nimport { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';\nimport { Blob } from '@dxos/echo';\nimport { ClientEvents } from '@dxos/plugin-client';\nimport { MarkdownEvents } from '@dxos/plugin-markdown';\n\nimport {\n CreateObject,\n EdgeBackend,\n FileUploader,\n InlineBackend,\n Markdown,\n OperationHandler,\n ReactSurface,\n Settings,\n SkillDefinition,\n} from '#capabilities';\nimport { meta } from '#meta';\nimport { translations } from '#translations';\nimport { File } from '#types';\n\n// eslint-disable-next-line import/no-relative-packages\nimport pluginSpec from '../PLUGIN.mdl?raw';\n\nexport const FilePlugin = Plugin.define(meta).pipe(\n AppPlugin.addSkillDefinitionModule({ activate: SkillDefinition }),\n AppPlugin.addCreateObjectModule({ activate: CreateObject }),\n AppPlugin.addOperationHandlerModule({ activate: OperationHandler }),\n AppPlugin.addSchemaModule({ schema: [File.File, Blob.Blob] }),\n AppPlugin.addSettingsModule({ activate: Settings }),\n AppPlugin.addSurfaceModule({ activate: ReactSurface }),\n AppPlugin.addTranslationsModule({ translations }),\n Plugin.addModule({\n id: 'inline-backend',\n activatesOn: AppActivationEvents.SetupSchema,\n activate: InlineBackend,\n }),\n Plugin.addModule({\n id: 'file-uploader',\n activatesOn: ClientEvents.ClientReady,\n activate: FileUploader,\n }),\n Plugin.addModule({\n id: 'edge-backend',\n activatesOn: ClientEvents.ClientReady,\n activate: EdgeBackend,\n }),\n Plugin.addModule({\n id: 'markdown',\n activatesOn: MarkdownEvents.SetupExtensions,\n activate: Markdown,\n }),\n AppPlugin.addPluginAssetModule({\n asset: { pluginId: meta.profile.key, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },\n }),\n Plugin.make,\n);\n\nexport default FilePlugin;\n"],"mappings":";;;;;;;;;;;AA4BA,IAAa,aAAa,OAAO,OAAO,IAAI,CAAC,CAAC,KAC5C,UAAU,yBAAyB,EAAE,UAAU,gBAAgB,CAAC,GAChE,UAAU,sBAAsB,EAAE,UAAU,aAAa,CAAC,GAC1D,UAAU,0BAA0B,EAAE,UAAU,iBAAiB,CAAC,GAClE,UAAU,gBAAgB,EAAE,QAAQ,CAAC,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,GAC5D,UAAU,kBAAkB,EAAE,UAAU,SAAS,CAAC,GAClD,UAAU,iBAAiB,EAAE,UAAU,aAAa,CAAC,GACrD,UAAU,sBAAsB,EAAE,aAAa,CAAC,GAChD,OAAO,UAAU;CACf,IAAI;CACJ,aAAa,oBAAoB;CACjC,UAAU;AACZ,CAAC,GACD,OAAO,UAAU;CACf,IAAI;CACJ,aAAa,aAAa;CAC1B,UAAU;AACZ,CAAC,GACD,OAAO,UAAU;CACf,IAAI;CACJ,aAAa,aAAa;CAC1B,UAAU;AACZ,CAAC,GACD,OAAO,UAAU;CACf,IAAI;CACJ,aAAa,eAAe;CAC5B,UAAU;AACZ,CAAC,GACD,UAAU,qBAAqB,EAC7B,OAAO;CAAE,UAAU,KAAK,QAAQ;CAAK,MAAM;CAAc,SAAS;CAAY,UAAU;AAAoB,EAC9G,CAAC,GACD,OAAO,IACT"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { meta } from "#meta";
|
|
2
|
+
import { Plugin } from "@dxos/app-framework";
|
|
3
|
+
import { Blob } from "@dxos/echo";
|
|
4
|
+
import { AppPlugin } from "@dxos/app-toolkit";
|
|
5
|
+
import { CreateObject, OperationHandler } from "#capabilities";
|
|
6
|
+
import { File } from "#types";
|
|
7
|
+
//#region src/FilePlugin.node.ts
|
|
8
|
+
var FilePlugin = Plugin.define(meta).pipe(AppPlugin.addCreateObjectModule({ activate: CreateObject }), AppPlugin.addOperationHandlerModule({ activate: OperationHandler }), AppPlugin.addSchemaModule({ schema: [File.File, Blob.Blob] }), Plugin.make);
|
|
9
|
+
//#endregion
|
|
10
|
+
export { FilePlugin, FilePlugin as default };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=FilePlugin.node.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilePlugin.node.mjs","names":[],"sources":["../../src/FilePlugin.node.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\nimport { AppPlugin } from '@dxos/app-toolkit';\nimport { Blob } from '@dxos/echo';\n\nimport { CreateObject, OperationHandler } from '#capabilities';\nimport { meta } from '#meta';\nimport { File } from '#types';\n\nexport const FilePlugin = Plugin.define(meta).pipe(\n AppPlugin.addCreateObjectModule({ activate: CreateObject }),\n AppPlugin.addOperationHandlerModule({ activate: OperationHandler }),\n AppPlugin.addSchemaModule({ schema: [File.File, Blob.Blob] }),\n Plugin.make,\n);\n\nexport default FilePlugin;\n"],"mappings":";;;;;;;AAYA,IAAa,aAAa,OAAO,OAAO,IAAI,CAAC,CAAC,KAC5C,UAAU,sBAAsB,EAAE,UAAU,aAAa,CAAC,GAC1D,UAAU,0BAA0B,EAAE,UAAU,iBAAiB,CAAC,GAClE,UAAU,gBAAgB,EAAE,QAAQ,CAAC,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,GAC5D,OAAO,IACT"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Capability } from "@dxos/app-framework";
|
|
2
|
+
//#region src/capabilities/node.ts
|
|
3
|
+
var CreateObject = Capability.lazy("CreateObject", () => import("../chunk-create-object.mjs"));
|
|
4
|
+
var OperationHandler = Capability.lazy("OperationHandler", () => import("../chunk-operation-handler.mjs"));
|
|
5
|
+
//#endregion
|
|
6
|
+
export { CreateObject, OperationHandler };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=node.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.mjs","names":[],"sources":["../../../src/capabilities/node.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Capability } from '@dxos/app-framework';\nimport { OperationHandlerSet } from '@dxos/compute';\n\n// The capabilities `FilePlugin.node` activates, and only those. `Capability.lazy` defers the\n// import at runtime but a bundler still walks it, so listing the React surfaces here would pull\n// the plugin's components into every node and bun build.\n\nexport const CreateObject = Capability.lazy('CreateObject', () => import('./create-object'));\nexport const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(\n 'OperationHandler',\n () => import('./operation-handler'),\n);\n"],"mappings":";;AAWA,IAAa,eAAe,WAAW,KAAK,sBAAsB,OAAO,6BAAkB;AAC3F,IAAa,mBAAmB,WAAW,KACzC,0BACM,OAAO,iCACf"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Capability } from "@dxos/app-framework";
|
|
2
|
+
//#region src/capabilities/index.ts
|
|
3
|
+
var SkillDefinition = Capability.lazy("SkillDefinition", () => import("./chunk-skill-definition.mjs"));
|
|
4
|
+
var CreateObject = Capability.lazy("CreateObject", () => import("./chunk-create-object.mjs"));
|
|
5
|
+
var EdgeBackend = Capability.lazy("EdgeBackend", () => import("./chunk-edge-backend.mjs"));
|
|
6
|
+
var FileUploader = Capability.lazy("FileUploader", () => import("./chunk-file-uploader.mjs"));
|
|
7
|
+
var InlineBackend = Capability.lazy("InlineBackend", () => import("./chunk-inline-backend.mjs"));
|
|
8
|
+
var Markdown = Capability.lazy("MarkdownExtension", () => import("./chunk-markdown-extension.mjs"));
|
|
9
|
+
var OperationHandler = Capability.lazy("OperationHandler", () => import("./chunk-operation-handler.mjs"));
|
|
10
|
+
var ReactSurface = Capability.lazy("ReactSurface", () => import("./chunk-react-surface.mjs"));
|
|
11
|
+
var Settings = Capability.lazy("Settings", () => import("./chunk-settings.mjs"));
|
|
12
|
+
//#endregion
|
|
13
|
+
export { CreateObject, EdgeBackend, FileUploader, InlineBackend, Markdown, OperationHandler, ReactSurface, Settings, SkillDefinition };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=capabilities.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.mjs","names":[],"sources":["../../src/capabilities/index.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Capability } from '@dxos/app-framework';\nimport { OperationHandlerSet } from '@dxos/compute';\n\nexport const SkillDefinition = Capability.lazy('SkillDefinition', () => import('./skill-definition'));\nexport const CreateObject = Capability.lazy('CreateObject', () => import('./create-object'));\nexport const EdgeBackend = Capability.lazy('EdgeBackend', () => import('./edge-backend'));\nexport const FileUploader = Capability.lazy('FileUploader', () => import('./file-uploader'));\nexport const InlineBackend = Capability.lazy('InlineBackend', () => import('./inline-backend'));\nexport const Markdown = Capability.lazy('MarkdownExtension', () => import('./markdown-extension'));\nexport const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(\n 'OperationHandler',\n () => import('./operation-handler'),\n);\nexport const ReactSurface = Capability.lazy('ReactSurface', () => import('./react-surface'));\nexport const Settings = Capability.lazy('Settings', () => import('./settings'));\n"],"mappings":";;AAOA,IAAa,kBAAkB,WAAW,KAAK,yBAAyB,OAAO,+BAAqB;AACpG,IAAa,eAAe,WAAW,KAAK,sBAAsB,OAAO,4BAAkB;AAC3F,IAAa,cAAc,WAAW,KAAK,qBAAqB,OAAO,2BAAiB;AACxF,IAAa,eAAe,WAAW,KAAK,sBAAsB,OAAO,4BAAkB;AAC3F,IAAa,gBAAgB,WAAW,KAAK,uBAAuB,OAAO,6BAAmB;AAC9F,IAAa,WAAW,WAAW,KAAK,2BAA2B,OAAO,iCAAuB;AACjG,IAAa,mBAAmB,WAAW,KACzC,0BACM,OAAO,gCACf;AACA,IAAa,eAAe,WAAW,KAAK,sBAAsB,OAAO,4BAAkB;AAC3F,IAAa,WAAW,WAAW,KAAK,kBAAkB,OAAO,uBAAa"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Blob, Database, Obj } from "@dxos/echo";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { EffectEx } from "@dxos/effect";
|
|
4
|
+
import { Panel } from "@dxos/react-ui";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Option from "effect/Option";
|
|
8
|
+
import { FilePreview } from "#components";
|
|
9
|
+
//#region src/containers/FileArticle/FileArticle.tsx
|
|
10
|
+
var FileArticle = ({ role, subject: file }) => {
|
|
11
|
+
const [rendered, setRendered] = useState(void 0);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
setRendered(void 0);
|
|
14
|
+
const db = Obj.getDatabase(file);
|
|
15
|
+
if (!db) return;
|
|
16
|
+
let cancelled = false;
|
|
17
|
+
let createdBlobUrl;
|
|
18
|
+
const program = Effect.gen(function* () {
|
|
19
|
+
const blob = yield* Database.load(file.data);
|
|
20
|
+
const type = blob.type ?? "application/octet-stream";
|
|
21
|
+
const urlOption = yield* Blob.url(blob);
|
|
22
|
+
if (Option.isSome(urlOption)) return {
|
|
23
|
+
url: urlOption.value,
|
|
24
|
+
type
|
|
25
|
+
};
|
|
26
|
+
const bytes = yield* Blob.read(blob);
|
|
27
|
+
return {
|
|
28
|
+
url: URL.createObjectURL(new globalThis.Blob([bytes], { type })),
|
|
29
|
+
type
|
|
30
|
+
};
|
|
31
|
+
}).pipe(Effect.provide(Database.layer(db)), Effect.catchAll(() => Effect.succeed(void 0)));
|
|
32
|
+
EffectEx.runPromise(program).then((result) => {
|
|
33
|
+
if (cancelled) {
|
|
34
|
+
if (result?.url.startsWith("blob:")) URL.revokeObjectURL(result.url);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (result?.url.startsWith("blob:")) createdBlobUrl = result.url;
|
|
38
|
+
setRendered(result);
|
|
39
|
+
});
|
|
40
|
+
return () => {
|
|
41
|
+
cancelled = true;
|
|
42
|
+
if (createdBlobUrl) URL.revokeObjectURL(createdBlobUrl);
|
|
43
|
+
};
|
|
44
|
+
}, [file.id]);
|
|
45
|
+
if (!rendered) return null;
|
|
46
|
+
return /* @__PURE__ */ jsx(Panel.Root, {
|
|
47
|
+
role,
|
|
48
|
+
classNames: "dx-document",
|
|
49
|
+
children: /* @__PURE__ */ jsx(Panel.Content, {
|
|
50
|
+
asChild: true,
|
|
51
|
+
children: /* @__PURE__ */ jsx(FilePreview, {
|
|
52
|
+
type: rendered.type,
|
|
53
|
+
url: rendered.url
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
FileArticle.displayName = "FileArticle";
|
|
59
|
+
//#endregion
|
|
60
|
+
export { FileArticle as default };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=chunk-FileArticle.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-FileArticle.mjs","names":[],"sources":["../../src/containers/FileArticle/FileArticle.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\nimport React, { useEffect, useState } from 'react';\n\nimport { type AppSurface } from '@dxos/app-toolkit/ui';\nimport { Blob, Database, Obj } from '@dxos/echo';\nimport { EffectEx } from '@dxos/effect';\nimport { Panel } from '@dxos/react-ui';\n\nimport { FilePreview } from '#components';\nimport { File } from '#types';\n\nexport type FileArticleProps = AppSurface.ObjectArticleProps<File.File>;\n\nexport const FileArticle = ({ role, subject: file }: FileArticleProps) => {\n const [rendered, setRendered] = useState<{ url: string; type: string } | undefined>(undefined);\n\n useEffect(() => {\n setRendered(undefined);\n\n const db = Obj.getDatabase(file);\n if (!db) {\n return;\n }\n\n let cancelled = false;\n let createdBlobUrl: string | undefined;\n\n const program = Effect.gen(function* () {\n const blob = yield* Database.load(file.data);\n const type = blob.type ?? 'application/octet-stream';\n const urlOption = yield* Blob.url(blob);\n if (Option.isSome(urlOption)) {\n return { url: urlOption.value, type };\n }\n const bytes = yield* Blob.read(blob);\n // `Uint8Array` is generic over `ArrayBufferLike` (incl. `SharedArrayBuffer`) while DOM's\n // `BlobPart` only covers `ArrayBuffer`-backed views — a gap between the DOM lib types and\n // the TS standard lib, not fixable by typing `bytes` differently.\n const url = URL.createObjectURL(new globalThis.Blob([bytes as BlobPart], { type }));\n return { url, type };\n }).pipe(\n Effect.provide(Database.layer(db)),\n Effect.catchAll(() => Effect.succeed(undefined)),\n );\n\n void EffectEx.runPromise(program).then((result) => {\n if (cancelled) {\n if (result?.url.startsWith('blob:')) {\n URL.revokeObjectURL(result.url);\n }\n return;\n }\n if (result?.url.startsWith('blob:')) {\n createdBlobUrl = result.url;\n }\n setRendered(result);\n });\n\n return () => {\n cancelled = true;\n if (createdBlobUrl) {\n URL.revokeObjectURL(createdBlobUrl);\n }\n };\n // Keyed on `file.id` rather than `file`/`file.data` directly: ECHO's reactive proxy returns a\n // fresh `Ref` wrapper for `.data` on every access, so including it (or the proxy object itself)\n // here would rerun this effect on every render — clearing the render state and re-resolving it\n // each time, which flickers the image while the object settles.\n }, [file.id]);\n\n if (!rendered) {\n return null;\n }\n\n return (\n <Panel.Root role={role} classNames='dx-document'>\n <Panel.Content asChild>\n <FilePreview type={rendered.type} url={rendered.url} />\n </Panel.Content>\n </Panel.Root>\n );\n};\n\nexport default FileArticle;\n\nFileArticle.displayName = 'FileArticle';\n"],"mappings":";;;;;;;;;AAkBA,IAAa,eAAe,EAAE,MAAM,SAAS,WAA6B;CACxE,MAAM,CAAC,UAAU,eAAe,SAAoD,KAAA,CAAS;CAE7F,gBAAgB;EACd,YAAY,KAAA,CAAS;EAErB,MAAM,KAAK,IAAI,YAAY,IAAI;EAC/B,IAAI,CAAC,IACH;EAGF,IAAI,YAAY;EAChB,IAAI;EAEJ,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,IAAI;GAC3C,MAAM,OAAO,KAAK,QAAQ;GAC1B,MAAM,YAAY,OAAO,KAAK,IAAI,IAAI;GACtC,IAAI,OAAO,OAAO,SAAS,GACzB,OAAO;IAAE,KAAK,UAAU;IAAO;GAAK;GAEtC,MAAM,QAAQ,OAAO,KAAK,KAAK,IAAI;GAKnC,OAAO;IAAE,KADG,IAAI,gBAAgB,IAAI,WAAW,KAAK,CAAC,KAAiB,GAAG,EAAE,KAAK,CAAC,CACxE;IAAK;GAAK;EACrB,CAAC,CAAC,CAAC,KACD,OAAO,QAAQ,SAAS,MAAM,EAAE,CAAC,GACjC,OAAO,eAAe,OAAO,QAAQ,KAAA,CAAS,CAAC,CACjD;EAEA,SAAc,WAAW,OAAO,CAAC,CAAC,MAAM,WAAW;GACjD,IAAI,WAAW;IACb,IAAI,QAAQ,IAAI,WAAW,OAAO,GAChC,IAAI,gBAAgB,OAAO,GAAG;IAEhC;GACF;GACA,IAAI,QAAQ,IAAI,WAAW,OAAO,GAChC,iBAAiB,OAAO;GAE1B,YAAY,MAAM;EACpB,CAAC;EAED,aAAa;GACX,YAAY;GACZ,IAAI,gBACF,IAAI,gBAAgB,cAAc;EAEtC;CAKF,GAAG,CAAC,KAAK,EAAE,CAAC;CAEZ,IAAI,CAAC,UACH,OAAO;CAGT,OACE,oBAAC,MAAM,MAAP;EAAkB;EAAM,YAAW;YACjC,oBAAC,MAAM,SAAP;GAAe,SAAA;aACb,oBAAC,aAAD;IAAa,MAAM,SAAS;IAAM,KAAK,SAAS;GAAM,CAAA;EACzC,CAAA;CACL,CAAA;AAEhB;AAIA,YAAY,cAAc"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Operation } from "@dxos/compute";
|
|
2
|
+
import { Capability } from "@dxos/app-framework";
|
|
3
|
+
import { Type } from "@dxos/echo";
|
|
4
|
+
import { File, FileAction, FileOperation } from "#types";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import { SpaceCapabilities, SpaceOperation } from "@dxos/plugin-space";
|
|
7
|
+
//#region src/capabilities/create-object.ts
|
|
8
|
+
var create_object_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
9
|
+
return Capability.contributes(SpaceCapabilities.CreateObjectEntry, {
|
|
10
|
+
id: Type.getTypename(File.File),
|
|
11
|
+
inputSchema: FileAction.CreateFileSchema,
|
|
12
|
+
createObject: (props, options) => Effect.gen(function* () {
|
|
13
|
+
const { object } = yield* Operation.invoke(FileOperation.Create, {
|
|
14
|
+
...props,
|
|
15
|
+
db: options.db
|
|
16
|
+
});
|
|
17
|
+
return yield* Operation.invoke(SpaceOperation.AddObject, {
|
|
18
|
+
object,
|
|
19
|
+
target: options.target,
|
|
20
|
+
targetNodeId: options.targetNodeId
|
|
21
|
+
});
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
}));
|
|
25
|
+
//#endregion
|
|
26
|
+
export { create_object_default as default };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=chunk-create-object.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-create-object.mjs","names":[],"sources":["../../src/capabilities/create-object.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Capability } from '@dxos/app-framework';\nimport { Operation } from '@dxos/compute';\nimport { Type } from '@dxos/echo';\nimport { SpaceCapabilities, SpaceOperation } from '@dxos/plugin-space';\n\nimport { File, FileAction, FileOperation } from '#types';\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n return Capability.contributes(SpaceCapabilities.CreateObjectEntry, {\n id: Type.getTypename(File.File),\n inputSchema: FileAction.CreateFileSchema,\n createObject: (props, options) =>\n Effect.gen(function* () {\n const { object } = yield* Operation.invoke(FileOperation.Create, { ...props, db: options.db });\n return yield* Operation.invoke(SpaceOperation.AddObject, {\n object,\n target: options.target,\n targetNodeId: options.targetNodeId,\n });\n }),\n });\n }),\n);\n"],"mappings":";;;;;;;AAaA,IAAA,wBAAe,WAAW,WACxB,OAAO,WAAW,aAAa;CAC7B,OAAO,WAAW,YAAY,kBAAkB,mBAAmB;EACjE,IAAI,KAAK,YAAY,KAAK,IAAI;EAC9B,aAAa,WAAW;EACxB,eAAe,OAAO,YACpB,OAAO,IAAI,aAAa;GACtB,MAAM,EAAE,WAAW,OAAO,UAAU,OAAO,cAAc,QAAQ;IAAE,GAAG;IAAO,IAAI,QAAQ;GAAG,CAAC;GAC7F,OAAO,OAAO,UAAU,OAAO,eAAe,WAAW;IACvD;IACA,QAAQ,QAAQ;IAChB,cAAc,QAAQ;GACxB,CAAC;EACH,CAAC;CACL,CAAC;AACH,CAAC,CACH"}
|