@dxos/plugin-file 0.0.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/LICENSE +105 -0
- package/PLUGIN.mdl +372 -0
- package/dist/lib/neutral/FileArticle-YU6NN3KN.mjs +80 -0
- package/dist/lib/neutral/FileArticle-YU6NN3KN.mjs.map +7 -0
- package/dist/lib/neutral/FilePlugin.mjs +58 -0
- package/dist/lib/neutral/FilePlugin.mjs.map +7 -0
- package/dist/lib/neutral/FilePlugin.node.mjs +23 -0
- package/dist/lib/neutral/FilePlugin.node.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +23 -0
- package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
- package/dist/lib/neutral/chunk-DKGTSUUH.mjs +8 -0
- package/dist/lib/neutral/chunk-DKGTSUUH.mjs.map +7 -0
- package/dist/lib/neutral/chunk-GMYSEHV4.mjs +40 -0
- package/dist/lib/neutral/chunk-GMYSEHV4.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/chunk-LBZ4MCLI.mjs +45 -0
- package/dist/lib/neutral/chunk-LBZ4MCLI.mjs.map +7 -0
- package/dist/lib/neutral/chunk-YODPX62X.mjs +136 -0
- package/dist/lib/neutral/chunk-YODPX62X.mjs.map +7 -0
- package/dist/lib/neutral/chunk-ZUXUTRAJ.mjs +80 -0
- package/dist/lib/neutral/chunk-ZUXUTRAJ.mjs.map +7 -0
- package/dist/lib/neutral/components/index.mjs +74 -0
- package/dist/lib/neutral/components/index.mjs.map +7 -0
- package/dist/lib/neutral/containers/index.mjs +59 -0
- package/dist/lib/neutral/containers/index.mjs.map +7 -0
- package/dist/lib/neutral/create-IUUG7RI6.mjs +17 -0
- package/dist/lib/neutral/create-IUUG7RI6.mjs.map +7 -0
- package/dist/lib/neutral/create-object-V667G4ZL.mjs +30 -0
- package/dist/lib/neutral/create-object-V667G4ZL.mjs.map +7 -0
- package/dist/lib/neutral/file-uploader-BCJH6TUL.mjs +41 -0
- package/dist/lib/neutral/file-uploader-BCJH6TUL.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +32 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/inline-backend-FGGGDYRB.mjs +37 -0
- package/dist/lib/neutral/inline-backend-FGGGDYRB.mjs.map +7 -0
- package/dist/lib/neutral/markdown-extension-RA5YNMTW.mjs +230 -0
- package/dist/lib/neutral/markdown-extension-RA5YNMTW.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/meta.mjs +8 -0
- package/dist/lib/neutral/meta.mjs.map +7 -0
- package/dist/lib/neutral/operation-handler-CPDANWGH.mjs +13 -0
- package/dist/lib/neutral/operation-handler-CPDANWGH.mjs.map +7 -0
- package/dist/lib/neutral/operations/index.mjs +8 -0
- package/dist/lib/neutral/operations/index.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +16 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/react-surface-5BXKSHD2.mjs +60 -0
- package/dist/lib/neutral/react-surface-5BXKSHD2.mjs.map +7 -0
- package/dist/lib/neutral/read-HG6CSWKR.mjs +28 -0
- package/dist/lib/neutral/read-HG6CSWKR.mjs.map +7 -0
- package/dist/lib/neutral/settings-RR3RESL2.mjs +28 -0
- package/dist/lib/neutral/settings-RR3RESL2.mjs.map +7 -0
- package/dist/lib/neutral/skill-definition-YHBGWBUS.mjs +15 -0
- package/dist/lib/neutral/skill-definition-YHBGWBUS.mjs.map +7 -0
- package/dist/lib/neutral/skills/index.mjs +11 -0
- package/dist/lib/neutral/skills/index.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +36 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/lib/neutral/types/index.mjs +22 -0
- package/dist/lib/neutral/types/index.mjs.map +7 -0
- package/dist/types/dx.config.d.ts +28 -0
- package/dist/types/dx.config.d.ts.map +1 -0
- package/dist/types/src/FilePlugin.d.ts +4 -0
- package/dist/types/src/FilePlugin.d.ts.map +1 -0
- package/dist/types/src/FilePlugin.node.d.ts +4 -0
- package/dist/types/src/FilePlugin.node.d.ts.map +1 -0
- package/dist/types/src/FilePlugin.test.d.ts +2 -0
- package/dist/types/src/FilePlugin.test.d.ts.map +1 -0
- package/dist/types/src/capabilities/create-object.d.ts +5 -0
- package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
- package/dist/types/src/capabilities/file-uploader.d.ts +5 -0
- package/dist/types/src/capabilities/file-uploader.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +11 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/inline-backend.d.ts +11 -0
- package/dist/types/src/capabilities/inline-backend.d.ts.map +1 -0
- package/dist/types/src/capabilities/markdown-extension.d.ts +5 -0
- package/dist/types/src/capabilities/markdown-extension.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +5 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/settings.d.ts +5 -0
- package/dist/types/src/capabilities/settings.d.ts.map +1 -0
- package/dist/types/src/capabilities/skill-definition.d.ts +5 -0
- package/dist/types/src/capabilities/skill-definition.d.ts.map +1 -0
- package/dist/types/src/components/FileInput/FileInput.d.ts +8 -0
- package/dist/types/src/components/FileInput/FileInput.d.ts.map +1 -0
- package/dist/types/src/components/FileInput/index.d.ts +2 -0
- package/dist/types/src/components/FileInput/index.d.ts.map +1 -0
- package/dist/types/src/components/FilePreview/FilePreview.d.ts +12 -0
- package/dist/types/src/components/FilePreview/FilePreview.d.ts.map +1 -0
- package/dist/types/src/components/FilePreview/index.d.ts +2 -0
- package/dist/types/src/components/FilePreview/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +3 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/containers/FileArticle/FileArticle.d.ts +7 -0
- package/dist/types/src/containers/FileArticle/FileArticle.d.ts.map +1 -0
- package/dist/types/src/containers/FileArticle/index.d.ts +2 -0
- package/dist/types/src/containers/FileArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/FileSettings/FileSettings.d.ts +6 -0
- package/dist/types/src/containers/FileSettings/FileSettings.d.ts.map +1 -0
- package/dist/types/src/containers/FileSettings/index.d.ts +2 -0
- package/dist/types/src/containers/FileSettings/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +4 -0
- package/dist/types/src/containers/index.d.ts.map +1 -0
- package/dist/types/src/extensions/image.d.ts +12 -0
- package/dist/types/src/extensions/image.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +2 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +33 -0
- package/dist/types/src/meta.d.ts.map +1 -0
- package/dist/types/src/operations/create.d.ts +25 -0
- package/dist/types/src/operations/create.d.ts.map +1 -0
- package/dist/types/src/operations/create.test.d.ts +2 -0
- package/dist/types/src/operations/create.test.d.ts.map +1 -0
- package/dist/types/src/operations/index.d.ts +3 -0
- package/dist/types/src/operations/index.d.ts.map +1 -0
- package/dist/types/src/operations/read.d.ts +5 -0
- package/dist/types/src/operations/read.d.ts.map +1 -0
- package/dist/types/src/plugin.d.ts +4 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/skills/file-skill.d.ts +5 -0
- package/dist/types/src/skills/file-skill.d.ts.map +1 -0
- package/dist/types/src/skills/index.d.ts +2 -0
- package/dist/types/src/skills/index.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +40 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types/FileCapabilities.d.ts +41 -0
- package/dist/types/src/types/FileCapabilities.d.ts.map +1 -0
- package/dist/types/src/types/FileOperation.d.ts +42 -0
- package/dist/types/src/types/FileOperation.d.ts.map +1 -0
- package/dist/types/src/types/Settings.d.ts +8 -0
- package/dist/types/src/types/Settings.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +7 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/src/types/limits.d.ts +4 -0
- package/dist/types/src/types/limits.d.ts.map +1 -0
- package/dist/types/src/types/types.d.ts +9 -0
- package/dist/types/src/types/types.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/dx.config.ts +35 -0
- package/package.json +139 -0
- package/src/FilePlugin.node.ts +19 -0
- package/src/FilePlugin.test.ts +26 -0
- package/src/FilePlugin.tsx +56 -0
- package/src/capabilities/create-object.ts +30 -0
- package/src/capabilities/file-uploader.ts +35 -0
- package/src/capabilities/index.ts +18 -0
- package/src/capabilities/inline-backend.ts +41 -0
- package/src/capabilities/markdown-extension.ts +38 -0
- package/src/capabilities/operation-handler.ts +16 -0
- package/src/capabilities/react-surface.tsx +61 -0
- package/src/capabilities/settings.ts +31 -0
- package/src/capabilities/skill-definition.ts +14 -0
- package/src/components/FileInput/FileInput.tsx +48 -0
- package/src/components/FileInput/index.ts +5 -0
- package/src/components/FilePreview/FilePreview.tsx +55 -0
- package/src/components/FilePreview/index.ts +5 -0
- package/src/components/index.ts +6 -0
- package/src/containers/FileArticle/FileArticle.tsx +84 -0
- package/src/containers/FileArticle/index.ts +5 -0
- package/src/containers/FileSettings/FileSettings.tsx +67 -0
- package/src/containers/FileSettings/index.ts +5 -0
- package/src/containers/index.ts +9 -0
- package/src/extensions/image.tsx +232 -0
- package/src/extensions/index.ts +5 -0
- package/src/index.ts +7 -0
- package/src/meta.ts +9 -0
- package/src/operations/create.test.ts +53 -0
- package/src/operations/create.ts +87 -0
- package/src/operations/index.ts +10 -0
- package/src/operations/read.ts +35 -0
- package/src/plugin.ts +11 -0
- package/src/skills/file-skill.ts +38 -0
- package/src/skills/index.ts +5 -0
- package/src/translations.ts +37 -0
- package/src/types/FileCapabilities.ts +58 -0
- package/src/types/FileOperation.ts +44 -0
- package/src/types/Settings.ts +23 -0
- package/src/types/index.ts +12 -0
- package/src/types/limits.ts +21 -0
- package/src/types/types.ts +21 -0
- package/src/vite-env.d.ts +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, ALv2 Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-Apache-2.0
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 DXOS
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the Apache License, Version 2.0 that is effective on the second anniversary of
|
|
91
|
+
the date we make the Software available. On or after that date, you may use the
|
|
92
|
+
Software under the Apache License, Version 2.0, in which case the following
|
|
93
|
+
will apply:
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
96
|
+
this file except in compliance with the License.
|
|
97
|
+
|
|
98
|
+
You may obtain a copy of the License at
|
|
99
|
+
|
|
100
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
101
|
+
|
|
102
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
103
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
|
+
specific language governing permissions and limitations under the License.
|
package/PLUGIN.mdl
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: org.dxos.plugin.file
|
|
3
|
+
name: FilePlugin
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
FilePlugin enables DXOS Composer to store and preview binary files — images, videos, and PDFs — as first-class ECHO objects.
|
|
8
|
+
Uploaded bytes are routed through a pluggable backend system: the built-in `inline` backend serialises the file directly
|
|
9
|
+
onto the ECHO document, while external backends (e.g. WNFS) can be contributed by other plugins.
|
|
10
|
+
A URL-resolver capability lets external-storage backends convert opaque storage URIs into renderable blob, data, or HTTP URLs
|
|
11
|
+
at view time, keeping the ECHO object schema backend-agnostic.
|
|
12
|
+
|
|
13
|
+
## Extensions
|
|
14
|
+
|
|
15
|
+
The following extension dialects are used in this document.
|
|
16
|
+
Each extension is defined in the Appendix or resolved via its URI.
|
|
17
|
+
|
|
18
|
+
| Term | URI |
|
|
19
|
+
|-------------|--------------------------------|
|
|
20
|
+
| `type` | `org.dxos.mdl.type@1.0` |
|
|
21
|
+
| `feat` | `org.dxos.mdl.feat@1.0` |
|
|
22
|
+
| `test` | `org.dxos.mdl.test@1.0` |
|
|
23
|
+
| `component` | `org.dxos.mdl.component@1.0` |
|
|
24
|
+
| `op` | `org.dxos.mdl.op@1.0` |
|
|
25
|
+
|
|
26
|
+
## Types
|
|
27
|
+
|
|
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
|
+
```mdl
|
|
52
|
+
type File
|
|
53
|
+
desc: ECHO object (typename dxos.org.type.file) representing an uploaded binary asset.
|
|
54
|
+
fields:
|
|
55
|
+
name?: string
|
|
56
|
+
type: string # MIME type
|
|
57
|
+
size: number # byte count
|
|
58
|
+
data: FileData
|
|
59
|
+
timestamp?: string # ISO 8601 upload time
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```mdl
|
|
63
|
+
type UploadResult
|
|
64
|
+
desc: Normalised return value from a Backend.upload call.
|
|
65
|
+
fields:
|
|
66
|
+
name: string
|
|
67
|
+
type: string
|
|
68
|
+
size: number
|
|
69
|
+
data: FileData
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```mdl
|
|
73
|
+
type Settings
|
|
74
|
+
desc: Per-user plugin settings stored in the ECHO space.
|
|
75
|
+
fields:
|
|
76
|
+
backend?: string # active backend id; defaults to 'inline'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Components
|
|
80
|
+
|
|
81
|
+
```mdl
|
|
82
|
+
component FilePreview
|
|
83
|
+
desc: |
|
|
84
|
+
Renders a File object using the appropriate HTML element for its MIME type.
|
|
85
|
+
Images use <img>, videos use <video controls>, PDFs use <iframe>, and
|
|
86
|
+
all other types fall back to a download link.
|
|
87
|
+
props:
|
|
88
|
+
type: string # MIME type
|
|
89
|
+
url: string # renderable blob:, data:, or http(s): URL
|
|
90
|
+
layout: |
|
|
91
|
+
┌──────────────────────────────┐
|
|
92
|
+
│ <img> / <video> / <iframe> │
|
|
93
|
+
│ or [Download file] link │
|
|
94
|
+
└──────────────────────────────┘
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```mdl
|
|
98
|
+
component FileInput
|
|
99
|
+
desc: Drag-and-drop / browse file picker used inside the Create Object form.
|
|
100
|
+
props:
|
|
101
|
+
schema: Schema # effect/Schema with UploadAnnotation on the file field
|
|
102
|
+
emits:
|
|
103
|
+
onChange(file: File)
|
|
104
|
+
layout: |
|
|
105
|
+
┌──────────────────────────────┐
|
|
106
|
+
│ [ Drop file or click here ] │
|
|
107
|
+
└──────────────────────────────┘
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```mdl
|
|
111
|
+
component FileSettings
|
|
112
|
+
desc: Plugin settings panel rendered in the Composer settings surface.
|
|
113
|
+
props:
|
|
114
|
+
settings: Settings
|
|
115
|
+
emits:
|
|
116
|
+
onSettingsChange(settings: Settings)
|
|
117
|
+
layout: |
|
|
118
|
+
┌──────────────────────────────┐
|
|
119
|
+
│ Storage backend: [select ▼] │
|
|
120
|
+
│ (one row per registered │
|
|
121
|
+
│ backend with description) │
|
|
122
|
+
└──────────────────────────────┘
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```mdl
|
|
126
|
+
component FileArticle
|
|
127
|
+
desc: |
|
|
128
|
+
Full-viewport article container rendered for article, section, and slide
|
|
129
|
+
roles. Resolves a renderable URL from the File object via inline blob
|
|
130
|
+
conversion or registered UrlResolver capabilities, then delegates to
|
|
131
|
+
FilePreview. Shows an empty state while the URL is resolving.
|
|
132
|
+
props:
|
|
133
|
+
role: AppSurface.Role
|
|
134
|
+
subject: File
|
|
135
|
+
layout: |
|
|
136
|
+
┌──────────────────────────────┐
|
|
137
|
+
│ │
|
|
138
|
+
│ FilePreview │
|
|
139
|
+
│ (fills container) │
|
|
140
|
+
│ │
|
|
141
|
+
└──────────────────────────────┘
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Operations
|
|
145
|
+
|
|
146
|
+
```mdl
|
|
147
|
+
op Create
|
|
148
|
+
key: org.dxos.plugin.file.operation.create
|
|
149
|
+
desc: |
|
|
150
|
+
Validates a browser File object (MIME type and size), routes the upload to the
|
|
151
|
+
active Backend capability, and constructs a File ECHO object from the result.
|
|
152
|
+
Does not add the object to the space — callers do that separately.
|
|
153
|
+
input:
|
|
154
|
+
file: BrowserFile # browser File (globalThis.File)
|
|
155
|
+
db: Database
|
|
156
|
+
output: { object: File }
|
|
157
|
+
errors:
|
|
158
|
+
UnsupportedFileTypeError: MIME type is not image/*, video/*, or application/pdf
|
|
159
|
+
FileTooLargeError: file exceeds the 4 MB inline limit
|
|
160
|
+
NoBackendError: no Backend capability is registered
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Features
|
|
164
|
+
|
|
165
|
+
```mdl
|
|
166
|
+
feat F-1: File upload
|
|
167
|
+
|
|
168
|
+
req F-1.1: Accepted MIME types MUST be image/* (jpg, jpeg, png, gif, webp, svg),
|
|
169
|
+
video/* (mp4, webm, mov), and application/pdf only.
|
|
170
|
+
|
|
171
|
+
req F-1.2: Maximum upload size MUST be 4 MB for the inline backend.
|
|
172
|
+
|
|
173
|
+
req F-1.3:
|
|
174
|
+
when: user submits the Create Object form with a valid file
|
|
175
|
+
then:
|
|
176
|
+
- op:Create is invoked with the selected file and active database
|
|
177
|
+
- the resulting File object is added to the space via SpaceOperation.AddObject
|
|
178
|
+
- FileUploader capability returns metadata to the caller (name, type, url/cid)
|
|
179
|
+
|
|
180
|
+
req F-1.4:
|
|
181
|
+
when: file MIME type is not accepted
|
|
182
|
+
then: UnsupportedFileTypeError returned; no ECHO write occurs
|
|
183
|
+
|
|
184
|
+
req F-1.5:
|
|
185
|
+
when: file size exceeds the limit
|
|
186
|
+
then: FileTooLargeError returned; no ECHO write occurs
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```mdl
|
|
190
|
+
feat F-2: Backend capability system
|
|
191
|
+
|
|
192
|
+
req F-2.1: Any plugin may contribute a Backend capability. Multiple backends
|
|
193
|
+
may be registered simultaneously.
|
|
194
|
+
|
|
195
|
+
req F-2.2: The inline backend MUST always be registered by FilePlugin itself.
|
|
196
|
+
It serialises bytes directly onto the ECHO object.
|
|
197
|
+
|
|
198
|
+
req F-2.3: The active backend is determined by Settings.backend. If the
|
|
199
|
+
configured id is absent or unrecognised, the inline backend is used.
|
|
200
|
+
|
|
201
|
+
req F-2.4: A UrlResolver capability allows external backends to convert an
|
|
202
|
+
opaque storage URI (e.g. wnfs://…) into a renderable URL at view time.
|
|
203
|
+
FileArticle iterates registered resolvers and uses the first that matches.
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```mdl
|
|
207
|
+
feat F-3: File preview
|
|
208
|
+
|
|
209
|
+
req F-3.1: FileArticle MUST resolve a renderable URL before rendering
|
|
210
|
+
FilePreview. For inline files a blob: URL is created from the stored bytes
|
|
211
|
+
and revoked on unmount.
|
|
212
|
+
|
|
213
|
+
req F-3.2: FilePreview MUST render the appropriate element for each MIME class:
|
|
214
|
+
image/* → <img>, video/* → <video controls>, application/pdf → <iframe>,
|
|
215
|
+
other → <a download>.
|
|
216
|
+
|
|
217
|
+
req F-3.3: FileArticle surfaces are registered for article, section,
|
|
218
|
+
and slide roles so files embed correctly in Markdown and Presenter layouts.
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```mdl
|
|
222
|
+
feat F-4: Markdown integration
|
|
223
|
+
|
|
224
|
+
req F-4.1: A Markdown extension contributed at MarkdownEvents.SetupExtensions
|
|
225
|
+
MUST allow files to be embedded in Markdown documents as inline attachments.
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```mdl
|
|
229
|
+
feat F-5: Settings
|
|
230
|
+
|
|
231
|
+
req F-5.1: The Settings panel MUST list all registered backends with their
|
|
232
|
+
id, display name, and optional description.
|
|
233
|
+
|
|
234
|
+
req F-5.2: Selecting a backend MUST persist the choice to Settings.backend
|
|
235
|
+
via the SettingsAtom capability.
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Acceptance
|
|
239
|
+
|
|
240
|
+
```mdl
|
|
241
|
+
test T-1: Upload image inline
|
|
242
|
+
given: only the inline backend is registered
|
|
243
|
+
when: user uploads a 1 MB PNG via the Create Object form
|
|
244
|
+
then:
|
|
245
|
+
- op:Create returns a File with data._tag === 'inline'
|
|
246
|
+
- File.type === 'image/png', File.size === byte count
|
|
247
|
+
- File is added to the space
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```mdl
|
|
251
|
+
test T-2: Reject oversized file
|
|
252
|
+
given: inline backend registered, 4 MB limit
|
|
253
|
+
when: user uploads a 5 MB JPEG
|
|
254
|
+
then:
|
|
255
|
+
- FileTooLargeError is returned
|
|
256
|
+
- no File object is created
|
|
257
|
+
- no ECHO write occurs
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```mdl
|
|
261
|
+
test T-3: Reject unsupported MIME type
|
|
262
|
+
given: inline backend registered
|
|
263
|
+
when: user uploads a .zip file
|
|
264
|
+
then:
|
|
265
|
+
- UnsupportedFileTypeError is returned
|
|
266
|
+
- no File object is created
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```mdl
|
|
270
|
+
test T-4: Preview inline image
|
|
271
|
+
given: a File with data._tag === 'inline' and type === 'image/png'
|
|
272
|
+
when: FileArticle is rendered
|
|
273
|
+
then:
|
|
274
|
+
- a blob: URL is created from File.data.bytes
|
|
275
|
+
- FilePreview renders an <img> element with that URL
|
|
276
|
+
- the blob: URL is revoked on unmount
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
```mdl
|
|
280
|
+
test T-5: Preview PDF
|
|
281
|
+
given: a File with type === 'application/pdf' and a resolved HTTP URL
|
|
282
|
+
when: FilePreview is rendered
|
|
283
|
+
then: an <iframe> element is rendered with the file URL as src
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```mdl
|
|
287
|
+
test T-6: Backend fallback to inline
|
|
288
|
+
given: Settings.backend is 'wnfs' but no WNFS backend is registered
|
|
289
|
+
when: op:Create is invoked
|
|
290
|
+
then: the inline backend is used and the file is stored inline
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
```mdl
|
|
294
|
+
test T-7: External URL resolved by UrlResolver
|
|
295
|
+
given: a File with data._tag === 'external' and data.url === 'wnfs://cid/file.jpg'
|
|
296
|
+
and: a UrlResolver capability registered that handles 'wnfs://' URIs
|
|
297
|
+
when: FileArticle is rendered
|
|
298
|
+
then:
|
|
299
|
+
- UrlResolver.resolve is called with the wnfs:// URL
|
|
300
|
+
- FilePreview receives the resolved renderable URL
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
```mdl
|
|
304
|
+
test T-8: No backend error
|
|
305
|
+
given: no Backend capability is registered
|
|
306
|
+
when: op:Create is invoked
|
|
307
|
+
then: NoBackendError is returned
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Appendix: Extension Definitions
|
|
313
|
+
|
|
314
|
+
```mdl
|
|
315
|
+
ext type
|
|
316
|
+
uri: org.dxos.mdl.type@1.0
|
|
317
|
+
desc: A named data structure with typed fields and optional literals.
|
|
318
|
+
fields:
|
|
319
|
+
desc?: Prose
|
|
320
|
+
fields?: FieldMap
|
|
321
|
+
literals?: UnionList
|
|
322
|
+
extends?: TypeRef[]
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
```mdl
|
|
326
|
+
ext feat
|
|
327
|
+
uri: org.dxos.mdl.feat@1.0
|
|
328
|
+
desc: A named feature grouping one or more requirements.
|
|
329
|
+
fields:
|
|
330
|
+
desc?: Prose
|
|
331
|
+
req: RequirementList
|
|
332
|
+
nesting: self
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
```mdl
|
|
336
|
+
ext test
|
|
337
|
+
uri: org.dxos.mdl.test@1.0
|
|
338
|
+
desc: An acceptance scenario expressed as given / when / then steps.
|
|
339
|
+
fields:
|
|
340
|
+
given?: Step | Step[]
|
|
341
|
+
when?: Step | Step[]
|
|
342
|
+
then: Step | Step[]
|
|
343
|
+
tags?: TagList
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
```mdl
|
|
347
|
+
ext component
|
|
348
|
+
uri: org.dxos.mdl.component@1.0
|
|
349
|
+
desc: A UI component with props, internal state, slots, actions, and events.
|
|
350
|
+
fields:
|
|
351
|
+
desc?: Prose
|
|
352
|
+
props?: FieldMap
|
|
353
|
+
state?: FieldMap
|
|
354
|
+
slots?: FieldMap
|
|
355
|
+
actions?: ActionMap
|
|
356
|
+
emits?: EventMap
|
|
357
|
+
layout?: CodeBlock
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
```mdl
|
|
361
|
+
ext op
|
|
362
|
+
uri: org.dxos.mdl.op@1.0
|
|
363
|
+
desc: A named operation with typed inputs, outputs, and declared errors.
|
|
364
|
+
fields:
|
|
365
|
+
desc?: Prose
|
|
366
|
+
input?: FieldMap
|
|
367
|
+
output?: TypeExpr
|
|
368
|
+
errors?: ErrorMap
|
|
369
|
+
effects?: EffectList
|
|
370
|
+
requires?: ServiceList
|
|
371
|
+
note?: Prose
|
|
372
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import "./chunk-J5LGTIGS.mjs";
|
|
2
|
+
|
|
3
|
+
// src/containers/FileArticle/FileArticle.tsx
|
|
4
|
+
import React, { useEffect, useState } from "react";
|
|
5
|
+
import { useCapabilities } from "@dxos/app-framework/ui";
|
|
6
|
+
import { getSpace } from "@dxos/react-client/echo";
|
|
7
|
+
import { Panel } from "@dxos/react-ui";
|
|
8
|
+
import { FilePreview } from "#components";
|
|
9
|
+
import { FileCapabilities } from "#types";
|
|
10
|
+
var FileArticle = ({ role, subject: file }) => {
|
|
11
|
+
const resolvers = useCapabilities(FileCapabilities.UrlResolver);
|
|
12
|
+
const [renderUrl, setRenderUrl] = useState(void 0);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const { data } = file;
|
|
15
|
+
if (data._tag === "inline") {
|
|
16
|
+
const url = URL.createObjectURL(new Blob([
|
|
17
|
+
data.bytes
|
|
18
|
+
], {
|
|
19
|
+
type: file.type
|
|
20
|
+
}));
|
|
21
|
+
setRenderUrl(url);
|
|
22
|
+
return () => URL.revokeObjectURL(url);
|
|
23
|
+
}
|
|
24
|
+
let cancelled = false;
|
|
25
|
+
let createdBlobUrl;
|
|
26
|
+
if (/^(?:https?|data|blob):/i.test(data.url)) {
|
|
27
|
+
setRenderUrl(data.url);
|
|
28
|
+
} else {
|
|
29
|
+
const resolver = resolvers.find((r) => r.test(data.url));
|
|
30
|
+
if (!resolver) {
|
|
31
|
+
setRenderUrl(void 0);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
setRenderUrl(void 0);
|
|
35
|
+
void resolver.resolve(data.url, file, getSpace(file)).then((url) => {
|
|
36
|
+
if (cancelled) {
|
|
37
|
+
if (url?.startsWith("blob:")) {
|
|
38
|
+
URL.revokeObjectURL(url);
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (url?.startsWith("blob:")) {
|
|
43
|
+
createdBlobUrl = url;
|
|
44
|
+
}
|
|
45
|
+
setRenderUrl(url);
|
|
46
|
+
}).catch(() => {
|
|
47
|
+
if (!cancelled) {
|
|
48
|
+
setRenderUrl(void 0);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return () => {
|
|
53
|
+
cancelled = true;
|
|
54
|
+
if (createdBlobUrl) {
|
|
55
|
+
URL.revokeObjectURL(createdBlobUrl);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}, [
|
|
59
|
+
file,
|
|
60
|
+
file.data,
|
|
61
|
+
file.type,
|
|
62
|
+
resolvers
|
|
63
|
+
]);
|
|
64
|
+
if (!renderUrl) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return /* @__PURE__ */ React.createElement(Panel.Root, {
|
|
68
|
+
role,
|
|
69
|
+
className: "dx-document"
|
|
70
|
+
}, /* @__PURE__ */ React.createElement(Panel.Content, {
|
|
71
|
+
asChild: true
|
|
72
|
+
}, /* @__PURE__ */ React.createElement(FilePreview, {
|
|
73
|
+
type: file.type,
|
|
74
|
+
url: renderUrl
|
|
75
|
+
})));
|
|
76
|
+
};
|
|
77
|
+
export {
|
|
78
|
+
FileArticle as default
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=FileArticle-YU6NN3KN.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/containers/FileArticle/FileArticle.tsx"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useEffect, useState } from 'react';\n\nimport { useCapabilities } from '@dxos/app-framework/ui';\nimport { type AppSurface } from '@dxos/app-toolkit/ui';\nimport { getSpace } from '@dxos/react-client/echo';\nimport { Panel } from '@dxos/react-ui';\n\nimport { FilePreview } from '#components';\nimport { File, FileCapabilities } from '#types';\n\nexport type FileArticleProps = AppSurface.ObjectArticleProps<File.File>;\n\nexport const FileArticle = ({ role, subject: file }: FileArticleProps) => {\n const resolvers = useCapabilities(FileCapabilities.UrlResolver);\n const [renderUrl, setRenderUrl] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n const { data } = file;\n if (data._tag === 'inline') {\n const url = URL.createObjectURL(new Blob([data.bytes as BlobPart], { type: file.type }));\n setRenderUrl(url);\n return () => URL.revokeObjectURL(url);\n }\n\n // External URL: pass through `http(s):`/`data:`/`blob:` directly; otherwise dispatch to a resolver.\n let cancelled = false;\n let createdBlobUrl: string | undefined;\n\n if (/^(?:https?|data|blob):/i.test(data.url)) {\n setRenderUrl(data.url);\n } else {\n const resolver = resolvers.find((r) => r.test(data.url));\n if (!resolver) {\n setRenderUrl(undefined);\n return;\n }\n setRenderUrl(undefined);\n void resolver\n .resolve(data.url, file, getSpace(file))\n .then((url) => {\n if (cancelled) {\n if (url?.startsWith('blob:')) {\n URL.revokeObjectURL(url);\n }\n return;\n }\n if (url?.startsWith('blob:')) {\n createdBlobUrl = url;\n }\n setRenderUrl(url);\n })\n .catch(() => {\n if (!cancelled) {\n setRenderUrl(undefined);\n }\n });\n }\n\n return () => {\n cancelled = true;\n if (createdBlobUrl) {\n URL.revokeObjectURL(createdBlobUrl);\n }\n };\n }, [file, file.data, file.type, resolvers]);\n\n if (!renderUrl) {\n return null;\n }\n\n return (\n <Panel.Root role={role} className='dx-document'>\n <Panel.Content asChild>\n <FilePreview type={file.type} url={renderUrl} />\n </Panel.Content>\n </Panel.Root>\n );\n};\n\nexport default FileArticle;\n"],
|
|
5
|
+
"mappings": ";;;AAIA,OAAOA,SAASC,WAAWC,gBAAgB;AAE3C,SAASC,uBAAuB;AAEhC,SAASC,gBAAgB;AACzB,SAASC,aAAa;AAEtB,SAASC,mBAAmB;AAC5B,SAAeC,wBAAwB;AAIhC,IAAMC,cAAc,CAAC,EAAEC,MAAMC,SAASC,KAAI,MAAoB;AACnE,QAAMC,YAAYT,gBAAgBI,iBAAiBM,WAAW;AAC9D,QAAM,CAACC,WAAWC,YAAAA,IAAgBb,SAA6Bc,MAAAA;AAE/Df,YAAU,MAAA;AACR,UAAM,EAAEgB,KAAI,IAAKN;AACjB,QAAIM,KAAKC,SAAS,UAAU;AAC1B,YAAMC,MAAMC,IAAIC,gBAAgB,IAAIC,KAAK;QAACL,KAAKM;SAAoB;QAAEC,MAAMb,KAAKa;MAAK,CAAA,CAAA;AACrFT,mBAAaI,GAAAA;AACb,aAAO,MAAMC,IAAIK,gBAAgBN,GAAAA;IACnC;AAGA,QAAIO,YAAY;AAChB,QAAIC;AAEJ,QAAI,0BAA0BC,KAAKX,KAAKE,GAAG,GAAG;AAC5CJ,mBAAaE,KAAKE,GAAG;IACvB,OAAO;AACL,YAAMU,WAAWjB,UAAUkB,KAAK,CAACC,MAAMA,EAAEH,KAAKX,KAAKE,GAAG,CAAA;AACtD,UAAI,CAACU,UAAU;AACbd,qBAAaC,MAAAA;AACb;MACF;AACAD,mBAAaC,MAAAA;AACb,WAAKa,SACFG,QAAQf,KAAKE,KAAKR,MAAMP,SAASO,IAAAA,CAAAA,EACjCsB,KAAK,CAACd,QAAAA;AACL,YAAIO,WAAW;AACb,cAAIP,KAAKe,WAAW,OAAA,GAAU;AAC5Bd,gBAAIK,gBAAgBN,GAAAA;UACtB;AACA;QACF;AACA,YAAIA,KAAKe,WAAW,OAAA,GAAU;AAC5BP,2BAAiBR;QACnB;AACAJ,qBAAaI,GAAAA;MACf,CAAA,EACCgB,MAAM,MAAA;AACL,YAAI,CAACT,WAAW;AACdX,uBAAaC,MAAAA;QACf;MACF,CAAA;IACJ;AAEA,WAAO,MAAA;AACLU,kBAAY;AACZ,UAAIC,gBAAgB;AAClBP,YAAIK,gBAAgBE,cAAAA;MACtB;IACF;EACF,GAAG;IAAChB;IAAMA,KAAKM;IAAMN,KAAKa;IAAMZ;GAAU;AAE1C,MAAI,CAACE,WAAW;AACd,WAAO;EACT;AAEA,SACE,sBAAA,cAACT,MAAM+B,MAAI;IAAC3B;IAAY4B,WAAU;KAChC,sBAAA,cAAChC,MAAMiC,SAAO;IAACC,SAAAA;KACb,sBAAA,cAACjC,aAAAA;IAAYkB,MAAMb,KAAKa;IAAML,KAAKL;;AAI3C;",
|
|
6
|
+
"names": ["React", "useEffect", "useState", "useCapabilities", "getSpace", "Panel", "FilePreview", "FileCapabilities", "FileArticle", "role", "subject", "file", "resolvers", "UrlResolver", "renderUrl", "setRenderUrl", "undefined", "data", "_tag", "url", "URL", "createObjectURL", "Blob", "bytes", "type", "revokeObjectURL", "cancelled", "createdBlobUrl", "test", "resolver", "find", "r", "resolve", "then", "startsWith", "catch", "Root", "className", "Content", "asChild"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import "./chunk-J5LGTIGS.mjs";
|
|
2
|
+
|
|
3
|
+
// src/FilePlugin.tsx
|
|
4
|
+
import { Plugin } from "@dxos/app-framework";
|
|
5
|
+
import { AppActivationEvents, AppPlugin } from "@dxos/app-toolkit";
|
|
6
|
+
import { ClientEvents } from "@dxos/plugin-client";
|
|
7
|
+
import { MarkdownEvents } from "@dxos/plugin-markdown";
|
|
8
|
+
import { CreateObject, FileUploader, InlineBackend, Markdown, OperationHandler, ReactSurface, Settings, SkillDefinition } from "#capabilities";
|
|
9
|
+
import { meta } from "#meta";
|
|
10
|
+
import { translations } from "#translations";
|
|
11
|
+
import { File } from "#types";
|
|
12
|
+
|
|
13
|
+
// raw-loader:/Users/jdw/Code/dxos/packages/plugins/plugin-file/PLUGIN.mdl
|
|
14
|
+
var PLUGIN_default = "---\nid: org.dxos.plugin.file\nname: FilePlugin\nversion: 0.1.0\n---\n\nFilePlugin enables DXOS Composer to store and preview binary files \u2014 images, videos, and PDFs \u2014 as first-class ECHO objects.\nUploaded bytes are routed through a pluggable backend system: the built-in `inline` backend serialises the file directly\nonto the ECHO document, while external backends (e.g. WNFS) can be contributed by other plugins.\nA URL-resolver capability lets external-storage backends convert opaque storage URIs into renderable blob, data, or HTTP URLs\nat view time, keeping the ECHO object schema backend-agnostic.\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 FileDataInline\n desc: File bytes stored directly on the ECHO object.\n fields:\n _tag: 'inline'\n bytes: Uint8Array\n```\n\n```mdl\ntype FileDataExternal\n desc: File bytes stored outside ECHO; referenced by URL.\n fields:\n _tag: 'external'\n url: string\n cid?: string # optional content-addressed identifier\n```\n\n```mdl\ntype FileData\n desc: Discriminated union of inline and external storage.\n literals: FileDataInline | FileDataExternal\n```\n\n```mdl\ntype File\n desc: ECHO object (typename dxos.org.type.file) representing an uploaded binary asset.\n fields:\n name?: string\n type: string # MIME type\n size: number # byte count\n data: FileData\n timestamp?: string # ISO 8601 upload time\n```\n\n```mdl\ntype UploadResult\n desc: Normalised return value from a Backend.upload call.\n fields:\n name: string\n type: string\n size: number\n data: FileData\n```\n\n```mdl\ntype Settings\n desc: Per-user plugin settings stored in the ECHO space.\n fields:\n backend?: string # active backend id; 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 blob:, data:, or http(s): URL\n layout: |\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 <img> / <video> / <iframe> \u2502\n \u2502 or [Download file] link \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\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 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 [ Drop file or click here ] \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\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 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 Storage backend: [select \u25BC] \u2502\n \u2502 (one row per registered \u2502\n \u2502 backend with description) \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n```mdl\ncomponent FileArticle\n desc: |\n Full-viewport article container rendered for article, section, and slide\n roles. Resolves a renderable URL from the File object via inline blob\n conversion or registered UrlResolver capabilities, then delegates to\n FilePreview. Shows an empty state while the URL is resolving.\n props:\n role: AppSurface.Role\n subject: File\n layout: |\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502\n \u2502 FilePreview \u2502\n \u2502 (fills container) \u2502\n \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\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 (MIME type and size), routes the upload to the\n active Backend capability, and constructs a File ECHO object from the result.\n Does not add the object to the space \u2014 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\n NoBackendError: no Backend 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 for the inline backend.\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, url/cid)\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\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 Backend capability. Multiple backends\n may be registered simultaneously.\n\n req F-2.2: The inline backend MUST always be registered by FilePlugin itself.\n It serialises bytes directly onto the ECHO object.\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 UrlResolver capability allows external backends to convert an\n opaque storage URI (e.g. wnfs://\u2026) into a renderable URL at view time.\n FileArticle iterates registered resolvers and uses the first that matches.\n```\n\n```mdl\nfeat F-3: File preview\n\n req F-3.1: FileArticle MUST resolve a renderable URL before rendering\n FilePreview. For inline files a blob: URL is created from the stored bytes\n and revoked on unmount.\n\n req F-3.2: FilePreview MUST render the appropriate element for each MIME class:\n image/* \u2192 <img>, video/* \u2192 <video controls>, application/pdf \u2192 <iframe>,\n other \u2192 <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```\n\n```mdl\nfeat F-5: Settings\n\n req F-5.1: The Settings panel MUST list all registered backends with their\n 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 backend is registered\n when: user uploads a 1 MB PNG via the Create Object form\n then:\n - op:Create returns a File 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 registered, 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 with data._tag === 'inline' and type === 'image/png'\n when: FileArticle is rendered\n then:\n - a blob: URL is created from File.data.bytes\n - FilePreview renders an <img> element with that URL\n - the blob: URL is revoked on unmount\n```\n\n```mdl\ntest T-5: Preview PDF\n given: a File with type === 'application/pdf' and a resolved HTTP 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 WNFS backend 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 URL resolved by UrlResolver\n given: a File with data._tag === 'external' and data.url === 'wnfs://cid/file.jpg'\n and: a UrlResolver capability registered that handles 'wnfs://' URIs\n when: FileArticle is rendered\n then:\n - UrlResolver.resolve is called with the wnfs:// URL\n - FilePreview receives the resolved renderable URL\n```\n\n```mdl\ntest T-8: No backend error\n given: no Backend 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";
|
|
15
|
+
|
|
16
|
+
// src/FilePlugin.tsx
|
|
17
|
+
var FilePlugin = Plugin.define(meta).pipe(AppPlugin.addSkillDefinitionModule({
|
|
18
|
+
activate: SkillDefinition
|
|
19
|
+
}), AppPlugin.addCreateObjectModule({
|
|
20
|
+
activate: CreateObject
|
|
21
|
+
}), AppPlugin.addOperationHandlerModule({
|
|
22
|
+
activate: OperationHandler
|
|
23
|
+
}), AppPlugin.addSchemaModule({
|
|
24
|
+
schema: [
|
|
25
|
+
File.File
|
|
26
|
+
]
|
|
27
|
+
}), AppPlugin.addSettingsModule({
|
|
28
|
+
activate: Settings
|
|
29
|
+
}), AppPlugin.addSurfaceModule({
|
|
30
|
+
activate: ReactSurface
|
|
31
|
+
}), AppPlugin.addTranslationsModule({
|
|
32
|
+
translations
|
|
33
|
+
}), Plugin.addModule({
|
|
34
|
+
id: "inline-backend",
|
|
35
|
+
activatesOn: AppActivationEvents.SetupSchema,
|
|
36
|
+
activate: InlineBackend
|
|
37
|
+
}), Plugin.addModule({
|
|
38
|
+
id: "file-uploader",
|
|
39
|
+
activatesOn: ClientEvents.ClientReady,
|
|
40
|
+
activate: FileUploader
|
|
41
|
+
}), Plugin.addModule({
|
|
42
|
+
id: "markdown",
|
|
43
|
+
activatesOn: MarkdownEvents.SetupExtensions,
|
|
44
|
+
activate: Markdown
|
|
45
|
+
}), AppPlugin.addPluginAssetModule({
|
|
46
|
+
asset: {
|
|
47
|
+
pluginId: meta.profile.key,
|
|
48
|
+
path: "PLUGIN.mdl",
|
|
49
|
+
content: PLUGIN_default,
|
|
50
|
+
mimeType: "application/x-mdl"
|
|
51
|
+
}
|
|
52
|
+
}), Plugin.make);
|
|
53
|
+
var FilePlugin_default = FilePlugin;
|
|
54
|
+
export {
|
|
55
|
+
FilePlugin,
|
|
56
|
+
FilePlugin_default as default
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=FilePlugin.mjs.map
|