@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.
Files changed (187) hide show
  1. package/LICENSE +105 -0
  2. package/PLUGIN.mdl +372 -0
  3. package/dist/lib/neutral/FileArticle-YU6NN3KN.mjs +80 -0
  4. package/dist/lib/neutral/FileArticle-YU6NN3KN.mjs.map +7 -0
  5. package/dist/lib/neutral/FilePlugin.mjs +58 -0
  6. package/dist/lib/neutral/FilePlugin.mjs.map +7 -0
  7. package/dist/lib/neutral/FilePlugin.node.mjs +23 -0
  8. package/dist/lib/neutral/FilePlugin.node.mjs.map +7 -0
  9. package/dist/lib/neutral/capabilities/index.mjs +23 -0
  10. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  11. package/dist/lib/neutral/chunk-DKGTSUUH.mjs +8 -0
  12. package/dist/lib/neutral/chunk-DKGTSUUH.mjs.map +7 -0
  13. package/dist/lib/neutral/chunk-GMYSEHV4.mjs +40 -0
  14. package/dist/lib/neutral/chunk-GMYSEHV4.mjs.map +7 -0
  15. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  16. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  17. package/dist/lib/neutral/chunk-LBZ4MCLI.mjs +45 -0
  18. package/dist/lib/neutral/chunk-LBZ4MCLI.mjs.map +7 -0
  19. package/dist/lib/neutral/chunk-YODPX62X.mjs +136 -0
  20. package/dist/lib/neutral/chunk-YODPX62X.mjs.map +7 -0
  21. package/dist/lib/neutral/chunk-ZUXUTRAJ.mjs +80 -0
  22. package/dist/lib/neutral/chunk-ZUXUTRAJ.mjs.map +7 -0
  23. package/dist/lib/neutral/components/index.mjs +74 -0
  24. package/dist/lib/neutral/components/index.mjs.map +7 -0
  25. package/dist/lib/neutral/containers/index.mjs +59 -0
  26. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  27. package/dist/lib/neutral/create-IUUG7RI6.mjs +17 -0
  28. package/dist/lib/neutral/create-IUUG7RI6.mjs.map +7 -0
  29. package/dist/lib/neutral/create-object-V667G4ZL.mjs +30 -0
  30. package/dist/lib/neutral/create-object-V667G4ZL.mjs.map +7 -0
  31. package/dist/lib/neutral/file-uploader-BCJH6TUL.mjs +41 -0
  32. package/dist/lib/neutral/file-uploader-BCJH6TUL.mjs.map +7 -0
  33. package/dist/lib/neutral/index.mjs +32 -0
  34. package/dist/lib/neutral/index.mjs.map +7 -0
  35. package/dist/lib/neutral/inline-backend-FGGGDYRB.mjs +37 -0
  36. package/dist/lib/neutral/inline-backend-FGGGDYRB.mjs.map +7 -0
  37. package/dist/lib/neutral/markdown-extension-RA5YNMTW.mjs +230 -0
  38. package/dist/lib/neutral/markdown-extension-RA5YNMTW.mjs.map +7 -0
  39. package/dist/lib/neutral/meta.json +1 -0
  40. package/dist/lib/neutral/meta.mjs +8 -0
  41. package/dist/lib/neutral/meta.mjs.map +7 -0
  42. package/dist/lib/neutral/operation-handler-CPDANWGH.mjs +13 -0
  43. package/dist/lib/neutral/operation-handler-CPDANWGH.mjs.map +7 -0
  44. package/dist/lib/neutral/operations/index.mjs +8 -0
  45. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  46. package/dist/lib/neutral/plugin.mjs +16 -0
  47. package/dist/lib/neutral/plugin.mjs.map +7 -0
  48. package/dist/lib/neutral/react-surface-5BXKSHD2.mjs +60 -0
  49. package/dist/lib/neutral/react-surface-5BXKSHD2.mjs.map +7 -0
  50. package/dist/lib/neutral/read-HG6CSWKR.mjs +28 -0
  51. package/dist/lib/neutral/read-HG6CSWKR.mjs.map +7 -0
  52. package/dist/lib/neutral/settings-RR3RESL2.mjs +28 -0
  53. package/dist/lib/neutral/settings-RR3RESL2.mjs.map +7 -0
  54. package/dist/lib/neutral/skill-definition-YHBGWBUS.mjs +15 -0
  55. package/dist/lib/neutral/skill-definition-YHBGWBUS.mjs.map +7 -0
  56. package/dist/lib/neutral/skills/index.mjs +11 -0
  57. package/dist/lib/neutral/skills/index.mjs.map +7 -0
  58. package/dist/lib/neutral/translations.mjs +36 -0
  59. package/dist/lib/neutral/translations.mjs.map +7 -0
  60. package/dist/lib/neutral/types/index.mjs +22 -0
  61. package/dist/lib/neutral/types/index.mjs.map +7 -0
  62. package/dist/types/dx.config.d.ts +28 -0
  63. package/dist/types/dx.config.d.ts.map +1 -0
  64. package/dist/types/src/FilePlugin.d.ts +4 -0
  65. package/dist/types/src/FilePlugin.d.ts.map +1 -0
  66. package/dist/types/src/FilePlugin.node.d.ts +4 -0
  67. package/dist/types/src/FilePlugin.node.d.ts.map +1 -0
  68. package/dist/types/src/FilePlugin.test.d.ts +2 -0
  69. package/dist/types/src/FilePlugin.test.d.ts.map +1 -0
  70. package/dist/types/src/capabilities/create-object.d.ts +5 -0
  71. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  72. package/dist/types/src/capabilities/file-uploader.d.ts +5 -0
  73. package/dist/types/src/capabilities/file-uploader.d.ts.map +1 -0
  74. package/dist/types/src/capabilities/index.d.ts +11 -0
  75. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  76. package/dist/types/src/capabilities/inline-backend.d.ts +11 -0
  77. package/dist/types/src/capabilities/inline-backend.d.ts.map +1 -0
  78. package/dist/types/src/capabilities/markdown-extension.d.ts +5 -0
  79. package/dist/types/src/capabilities/markdown-extension.d.ts.map +1 -0
  80. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  81. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  82. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  83. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  84. package/dist/types/src/capabilities/settings.d.ts +5 -0
  85. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  86. package/dist/types/src/capabilities/skill-definition.d.ts +5 -0
  87. package/dist/types/src/capabilities/skill-definition.d.ts.map +1 -0
  88. package/dist/types/src/components/FileInput/FileInput.d.ts +8 -0
  89. package/dist/types/src/components/FileInput/FileInput.d.ts.map +1 -0
  90. package/dist/types/src/components/FileInput/index.d.ts +2 -0
  91. package/dist/types/src/components/FileInput/index.d.ts.map +1 -0
  92. package/dist/types/src/components/FilePreview/FilePreview.d.ts +12 -0
  93. package/dist/types/src/components/FilePreview/FilePreview.d.ts.map +1 -0
  94. package/dist/types/src/components/FilePreview/index.d.ts +2 -0
  95. package/dist/types/src/components/FilePreview/index.d.ts.map +1 -0
  96. package/dist/types/src/components/index.d.ts +3 -0
  97. package/dist/types/src/components/index.d.ts.map +1 -0
  98. package/dist/types/src/containers/FileArticle/FileArticle.d.ts +7 -0
  99. package/dist/types/src/containers/FileArticle/FileArticle.d.ts.map +1 -0
  100. package/dist/types/src/containers/FileArticle/index.d.ts +2 -0
  101. package/dist/types/src/containers/FileArticle/index.d.ts.map +1 -0
  102. package/dist/types/src/containers/FileSettings/FileSettings.d.ts +6 -0
  103. package/dist/types/src/containers/FileSettings/FileSettings.d.ts.map +1 -0
  104. package/dist/types/src/containers/FileSettings/index.d.ts +2 -0
  105. package/dist/types/src/containers/FileSettings/index.d.ts.map +1 -0
  106. package/dist/types/src/containers/index.d.ts +4 -0
  107. package/dist/types/src/containers/index.d.ts.map +1 -0
  108. package/dist/types/src/extensions/image.d.ts +12 -0
  109. package/dist/types/src/extensions/image.d.ts.map +1 -0
  110. package/dist/types/src/extensions/index.d.ts +2 -0
  111. package/dist/types/src/extensions/index.d.ts.map +1 -0
  112. package/dist/types/src/index.d.ts +4 -0
  113. package/dist/types/src/index.d.ts.map +1 -0
  114. package/dist/types/src/meta.d.ts +33 -0
  115. package/dist/types/src/meta.d.ts.map +1 -0
  116. package/dist/types/src/operations/create.d.ts +25 -0
  117. package/dist/types/src/operations/create.d.ts.map +1 -0
  118. package/dist/types/src/operations/create.test.d.ts +2 -0
  119. package/dist/types/src/operations/create.test.d.ts.map +1 -0
  120. package/dist/types/src/operations/index.d.ts +3 -0
  121. package/dist/types/src/operations/index.d.ts.map +1 -0
  122. package/dist/types/src/operations/read.d.ts +5 -0
  123. package/dist/types/src/operations/read.d.ts.map +1 -0
  124. package/dist/types/src/plugin.d.ts +4 -0
  125. package/dist/types/src/plugin.d.ts.map +1 -0
  126. package/dist/types/src/skills/file-skill.d.ts +5 -0
  127. package/dist/types/src/skills/file-skill.d.ts.map +1 -0
  128. package/dist/types/src/skills/index.d.ts +2 -0
  129. package/dist/types/src/skills/index.d.ts.map +1 -0
  130. package/dist/types/src/translations.d.ts +40 -0
  131. package/dist/types/src/translations.d.ts.map +1 -0
  132. package/dist/types/src/types/FileCapabilities.d.ts +41 -0
  133. package/dist/types/src/types/FileCapabilities.d.ts.map +1 -0
  134. package/dist/types/src/types/FileOperation.d.ts +42 -0
  135. package/dist/types/src/types/FileOperation.d.ts.map +1 -0
  136. package/dist/types/src/types/Settings.d.ts +8 -0
  137. package/dist/types/src/types/Settings.d.ts.map +1 -0
  138. package/dist/types/src/types/index.d.ts +7 -0
  139. package/dist/types/src/types/index.d.ts.map +1 -0
  140. package/dist/types/src/types/limits.d.ts +4 -0
  141. package/dist/types/src/types/limits.d.ts.map +1 -0
  142. package/dist/types/src/types/types.d.ts +9 -0
  143. package/dist/types/src/types/types.d.ts.map +1 -0
  144. package/dist/types/tsconfig.tsbuildinfo +1 -0
  145. package/dx.config.ts +35 -0
  146. package/package.json +139 -0
  147. package/src/FilePlugin.node.ts +19 -0
  148. package/src/FilePlugin.test.ts +26 -0
  149. package/src/FilePlugin.tsx +56 -0
  150. package/src/capabilities/create-object.ts +30 -0
  151. package/src/capabilities/file-uploader.ts +35 -0
  152. package/src/capabilities/index.ts +18 -0
  153. package/src/capabilities/inline-backend.ts +41 -0
  154. package/src/capabilities/markdown-extension.ts +38 -0
  155. package/src/capabilities/operation-handler.ts +16 -0
  156. package/src/capabilities/react-surface.tsx +61 -0
  157. package/src/capabilities/settings.ts +31 -0
  158. package/src/capabilities/skill-definition.ts +14 -0
  159. package/src/components/FileInput/FileInput.tsx +48 -0
  160. package/src/components/FileInput/index.ts +5 -0
  161. package/src/components/FilePreview/FilePreview.tsx +55 -0
  162. package/src/components/FilePreview/index.ts +5 -0
  163. package/src/components/index.ts +6 -0
  164. package/src/containers/FileArticle/FileArticle.tsx +84 -0
  165. package/src/containers/FileArticle/index.ts +5 -0
  166. package/src/containers/FileSettings/FileSettings.tsx +67 -0
  167. package/src/containers/FileSettings/index.ts +5 -0
  168. package/src/containers/index.ts +9 -0
  169. package/src/extensions/image.tsx +232 -0
  170. package/src/extensions/index.ts +5 -0
  171. package/src/index.ts +7 -0
  172. package/src/meta.ts +9 -0
  173. package/src/operations/create.test.ts +53 -0
  174. package/src/operations/create.ts +87 -0
  175. package/src/operations/index.ts +10 -0
  176. package/src/operations/read.ts +35 -0
  177. package/src/plugin.ts +11 -0
  178. package/src/skills/file-skill.ts +38 -0
  179. package/src/skills/index.ts +5 -0
  180. package/src/translations.ts +37 -0
  181. package/src/types/FileCapabilities.ts +58 -0
  182. package/src/types/FileOperation.ts +44 -0
  183. package/src/types/Settings.ts +23 -0
  184. package/src/types/index.ts +12 -0
  185. package/src/types/limits.ts +21 -0
  186. package/src/types/types.ts +21 -0
  187. 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