@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/dx.config.ts ADDED
@@ -0,0 +1,35 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Config2 } from '@dxos/app-framework/config';
6
+ import { trim } from '@dxos/util';
7
+
8
+ export default Config2.make({
9
+ plugin: {
10
+ key: 'org.dxos.plugin.file',
11
+ name: 'File',
12
+ author: 'DXOS',
13
+ description: trim`
14
+ FilePlugin enables DXOS Composer to store and preview binary files — images, videos, and PDFs — as
15
+ first-class ECHO objects. Uploaded bytes are routed through a pluggable backend system: the built-in
16
+ inline backend serialises the file directly onto the ECHO document, while external backends (e.g. WNFS)
17
+ can be contributed by other plugins.
18
+
19
+ Accepted formats are images (jpg, jpeg, png, gif, webp, svg), videos (mp4, webm, mov), and PDFs,
20
+ up to a 4 MB limit per file for the inline backend. The preview surface renders the appropriate HTML
21
+ element for each MIME class and surfaces inside article, section, and slide roles.
22
+
23
+ A UrlResolver capability lets external-storage backends convert opaque storage URIs into renderable
24
+ blob, data, or HTTP URLs at view time, keeping the ECHO object schema backend-agnostic. A companion
25
+ Markdown extension allows file objects to be embedded directly in Markdown documents.
26
+
27
+ Backend selection is user-configurable in the plugin settings panel, which lists all registered
28
+ backends with their descriptions. Selecting a backend persists the choice to the ECHO-backed settings
29
+ atom so the preference is shared across devices.
30
+ `,
31
+ icon: { key: 'ph--file--regular', hue: 'teal' },
32
+ source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-file',
33
+ spec: 'PLUGIN.mdl',
34
+ },
35
+ });
package/package.json ADDED
@@ -0,0 +1,139 @@
1
+ {
2
+ "name": "@dxos/plugin-file",
3
+ "version": "0.0.0",
4
+ "description": "Inline file (image / PDF) storage plugin for DXOS",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
11
+ "license": "FSL-1.1-Apache-2.0",
12
+ "author": "DXOS.org",
13
+ "sideEffects": true,
14
+ "type": "module",
15
+ "imports": {
16
+ "#skills": {
17
+ "source": "./src/skills/index.ts",
18
+ "types": "./dist/types/src/skills/index.d.ts",
19
+ "default": "./dist/lib/neutral/skills/index.mjs"
20
+ },
21
+ "#capabilities": {
22
+ "source": "./src/capabilities/index.ts",
23
+ "types": "./dist/types/src/capabilities/index.d.ts",
24
+ "default": "./dist/lib/neutral/capabilities/index.mjs"
25
+ },
26
+ "#components": {
27
+ "source": "./src/components/index.ts",
28
+ "types": "./dist/types/src/components/index.d.ts",
29
+ "default": "./dist/lib/neutral/components/index.mjs"
30
+ },
31
+ "#containers": {
32
+ "source": "./src/containers/index.ts",
33
+ "types": "./dist/types/src/containers/index.d.ts",
34
+ "default": "./dist/lib/neutral/containers/index.mjs"
35
+ },
36
+ "#meta": {
37
+ "source": "./src/meta.ts",
38
+ "types": "./dist/types/src/meta.d.ts",
39
+ "default": "./dist/lib/neutral/meta.mjs"
40
+ },
41
+ "#operations": {
42
+ "source": "./src/operations/index.ts",
43
+ "types": "./dist/types/src/operations/index.d.ts",
44
+ "default": "./dist/lib/neutral/operations/index.mjs"
45
+ },
46
+ "#plugin": {
47
+ "source": {
48
+ "node": "./src/FilePlugin.node.ts",
49
+ "default": "./src/FilePlugin.tsx"
50
+ },
51
+ "types": "./dist/types/src/FilePlugin.d.ts",
52
+ "node": "./dist/lib/neutral/FilePlugin.node.mjs",
53
+ "default": "./dist/lib/neutral/FilePlugin.mjs"
54
+ },
55
+ "#translations": {
56
+ "source": "./src/translations.ts",
57
+ "types": "./dist/types/src/translations.d.ts",
58
+ "default": "./dist/lib/neutral/translations.mjs"
59
+ },
60
+ "#types": {
61
+ "source": "./src/types/index.ts",
62
+ "types": "./dist/types/src/types/index.d.ts",
63
+ "default": "./dist/lib/neutral/types/index.mjs"
64
+ }
65
+ },
66
+ "exports": {
67
+ ".": {
68
+ "source": "./src/index.ts",
69
+ "types": "./dist/types/src/index.d.ts",
70
+ "default": "./dist/lib/neutral/index.mjs"
71
+ },
72
+ "./assets/PLUGIN.mdl": "./PLUGIN.mdl",
73
+ "./plugin": {
74
+ "source": "./src/plugin.ts",
75
+ "types": "./dist/types/src/plugin.d.ts",
76
+ "default": "./dist/lib/neutral/plugin.mjs"
77
+ },
78
+ "./translations": {
79
+ "source": "./src/translations.ts",
80
+ "types": "./dist/types/src/translations.d.ts",
81
+ "default": "./dist/lib/neutral/translations.mjs"
82
+ },
83
+ "./types": {
84
+ "source": "./src/types/index.ts",
85
+ "types": "./dist/types/src/types/index.d.ts",
86
+ "default": "./dist/lib/neutral/types/index.mjs"
87
+ }
88
+ },
89
+ "types": "dist/types/src/index.d.ts",
90
+ "files": [
91
+ "dist",
92
+ "dx.config.ts",
93
+ "src",
94
+ "PLUGIN.mdl"
95
+ ],
96
+ "dependencies": {
97
+ "@codemirror/language": "catalog:",
98
+ "@codemirror/state": "catalog:",
99
+ "@codemirror/view": "catalog:",
100
+ "@dxos/app-framework": "workspace:*",
101
+ "@dxos/app-toolkit": "workspace:*",
102
+ "@dxos/compute": "workspace:*",
103
+ "@dxos/echo": "workspace:*",
104
+ "@dxos/effect": "workspace:*",
105
+ "@dxos/keys": "workspace:*",
106
+ "@dxos/plugin-client": "workspace:*",
107
+ "@dxos/plugin-markdown": "workspace:*",
108
+ "@dxos/plugin-space": "workspace:*",
109
+ "@dxos/react-client": "workspace:*",
110
+ "@dxos/react-ui": "workspace:*",
111
+ "@dxos/react-ui-form": "workspace:*",
112
+ "@dxos/types": "workspace:*",
113
+ "@dxos/ui-editor": "workspace:*",
114
+ "@dxos/util": "workspace:*",
115
+ "@effect-atom/atom-react": "catalog:",
116
+ "effect": "catalog:",
117
+ "react-dropzone": "catalog:"
118
+ },
119
+ "devDependencies": {
120
+ "@dxos/plugin-testing": "workspace:*",
121
+ "@dxos/storybook-utils": "workspace:*",
122
+ "@dxos/ui-theme": "workspace:*",
123
+ "@types/react": "catalog:",
124
+ "@types/react-dom": "catalog:",
125
+ "react": "catalog:",
126
+ "react-dom": "catalog:",
127
+ "vite": "catalog:"
128
+ },
129
+ "peerDependencies": {
130
+ "@dxos/react-ui": "workspace:*",
131
+ "@dxos/ui-theme": "workspace:*",
132
+ "effect": "catalog:",
133
+ "react": "catalog:",
134
+ "react-dom": "catalog:"
135
+ },
136
+ "publishConfig": {
137
+ "access": "public"
138
+ }
139
+ }
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Plugin } from '@dxos/app-framework';
6
+ import { AppPlugin } from '@dxos/app-toolkit';
7
+
8
+ import { CreateObject, OperationHandler } from '#capabilities';
9
+ import { meta } from '#meta';
10
+ import { File } from '#types';
11
+
12
+ export const FilePlugin = Plugin.define(meta).pipe(
13
+ AppPlugin.addCreateObjectModule({ activate: CreateObject }),
14
+ AppPlugin.addOperationHandlerModule({ activate: OperationHandler }),
15
+ AppPlugin.addSchemaModule({ schema: [File.File] }),
16
+ Plugin.make,
17
+ );
18
+
19
+ export default FilePlugin;
@@ -0,0 +1,26 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { ClientPlugin } from '@dxos/plugin-client/plugin';
8
+ import { createComposerTestApp } from '@dxos/plugin-testing/harness';
9
+
10
+ import { FilePlugin } from '#plugin';
11
+
12
+ import { meta } from './meta';
13
+
14
+ const moduleId = (name: string) => `${meta.profile.key}.module.${name}`;
15
+
16
+ describe('FilePlugin', () => {
17
+ test('modules activate on the expected events', async ({ expect }) => {
18
+ await using harness = await createComposerTestApp({
19
+ plugins: [ClientPlugin({}), FilePlugin()],
20
+ });
21
+
22
+ expect(harness.manager.getActive()).toEqual(
23
+ expect.arrayContaining([moduleId('CreateObject'), moduleId('schema'), moduleId('OperationHandler')]),
24
+ );
25
+ });
26
+ });
@@ -0,0 +1,56 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Plugin } from '@dxos/app-framework';
6
+ import { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';
7
+ import { ClientEvents } from '@dxos/plugin-client';
8
+ import { MarkdownEvents } from '@dxos/plugin-markdown';
9
+
10
+ import {
11
+ CreateObject,
12
+ FileUploader,
13
+ InlineBackend,
14
+ Markdown,
15
+ OperationHandler,
16
+ ReactSurface,
17
+ Settings,
18
+ SkillDefinition,
19
+ } from '#capabilities';
20
+ import { meta } from '#meta';
21
+ import { translations } from '#translations';
22
+ import { File } from '#types';
23
+
24
+ // eslint-disable-next-line import/no-relative-packages
25
+ import pluginSpec from '../PLUGIN.mdl?raw';
26
+
27
+ export const FilePlugin = Plugin.define(meta).pipe(
28
+ AppPlugin.addSkillDefinitionModule({ activate: SkillDefinition }),
29
+ AppPlugin.addCreateObjectModule({ activate: CreateObject }),
30
+ AppPlugin.addOperationHandlerModule({ activate: OperationHandler }),
31
+ AppPlugin.addSchemaModule({ schema: [File.File] }),
32
+ AppPlugin.addSettingsModule({ activate: Settings }),
33
+ AppPlugin.addSurfaceModule({ activate: ReactSurface }),
34
+ AppPlugin.addTranslationsModule({ translations }),
35
+ Plugin.addModule({
36
+ id: 'inline-backend',
37
+ activatesOn: AppActivationEvents.SetupSchema,
38
+ activate: InlineBackend,
39
+ }),
40
+ Plugin.addModule({
41
+ id: 'file-uploader',
42
+ activatesOn: ClientEvents.ClientReady,
43
+ activate: FileUploader,
44
+ }),
45
+ Plugin.addModule({
46
+ id: 'markdown',
47
+ activatesOn: MarkdownEvents.SetupExtensions,
48
+ activate: Markdown,
49
+ }),
50
+ AppPlugin.addPluginAssetModule({
51
+ asset: { pluginId: meta.profile.key, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },
52
+ }),
53
+ Plugin.make,
54
+ );
55
+
56
+ export default FilePlugin;
@@ -0,0 +1,30 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capability } from '@dxos/app-framework';
8
+ import { Operation } from '@dxos/compute';
9
+ import { Type } from '@dxos/echo';
10
+ import { SpaceCapabilities, SpaceOperation } from '@dxos/plugin-space';
11
+
12
+ import { File, FileAction, FileOperation } from '#types';
13
+
14
+ export default Capability.makeModule(
15
+ Effect.fnUntraced(function* () {
16
+ return Capability.contributes(SpaceCapabilities.CreateObjectEntry, {
17
+ id: Type.getTypename(File.File),
18
+ inputSchema: FileAction.CreateFileSchema,
19
+ createObject: (props, options) =>
20
+ Effect.gen(function* () {
21
+ const { object } = yield* Operation.invoke(FileOperation.Create, { ...props, db: options.db });
22
+ return yield* Operation.invoke(SpaceOperation.AddObject, {
23
+ object,
24
+ target: options.target,
25
+ targetNodeId: options.targetNodeId,
26
+ });
27
+ }),
28
+ });
29
+ }),
30
+ );
@@ -0,0 +1,35 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capabilities, Capability } from '@dxos/app-framework';
8
+ import { AppCapabilities } from '@dxos/app-toolkit';
9
+ import { EffectEx } from '@dxos/effect';
10
+ import { SpaceOperation } from '@dxos/plugin-space';
11
+
12
+ import { FileOperation } from '#types';
13
+
14
+ export default Capability.makeModule(
15
+ Effect.fnUntraced(function* () {
16
+ const capabilities = yield* Capability.Service;
17
+
18
+ return Capability.contributes(AppCapabilities.FileUploader, (db, file) => {
19
+ const { invoke } = capabilities.get(Capabilities.OperationInvoker);
20
+ const program = Effect.gen(function* () {
21
+ const { object } = yield* invoke(FileOperation.Create, { db, file });
22
+ yield* invoke(SpaceOperation.AddObject, { target: db, object });
23
+ const external = object.data._tag === 'external' ? object.data : undefined;
24
+ return {
25
+ name: object.name ?? file.name,
26
+ type: object.type,
27
+ ...(external?.url ? { url: external.url } : {}),
28
+ ...(external?.cid ? { cid: external.cid } : {}),
29
+ };
30
+ });
31
+
32
+ return EffectEx.runAndForwardErrors(program);
33
+ });
34
+ }),
35
+ );
@@ -0,0 +1,18 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Capability } from '@dxos/app-framework';
6
+ import { OperationHandlerSet } from '@dxos/compute';
7
+
8
+ export const SkillDefinition = Capability.lazy('SkillDefinition', () => import('./skill-definition'));
9
+ export const CreateObject = Capability.lazy('CreateObject', () => import('./create-object'));
10
+ export const FileUploader = Capability.lazy('FileUploader', () => import('./file-uploader'));
11
+ export const InlineBackend = Capability.lazy('InlineBackend', () => import('./inline-backend'));
12
+ export const Markdown = Capability.lazy('MarkdownExtension', () => import('./markdown-extension'));
13
+ export const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(
14
+ 'OperationHandler',
15
+ () => import('./operation-handler'),
16
+ );
17
+ export const ReactSurface = Capability.lazy('ReactSurface', () => import('./react-surface'));
18
+ export const Settings = Capability.lazy('Settings', () => import('./settings'));
@@ -0,0 +1,41 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capability } from '@dxos/app-framework';
8
+
9
+ import { File, FileCapabilities, MAX_FILE_SIZE, Settings, isAcceptedMimeType } from '#types';
10
+
11
+ import { FileTooLargeError, UnsupportedFileTypeError } from '../operations/create';
12
+
13
+ /**
14
+ * Inline backend: file bytes are stored on the ECHO object itself.
15
+ * Exported standalone for direct testing.
16
+ */
17
+ export const inlineBackend: FileCapabilities.Backend = {
18
+ id: Settings.DEFAULT_BACKEND_ID,
19
+ name: 'Inline (ECHO)',
20
+ description: 'Store the file bytes directly inside the ECHO document. Capped at 4MB; images, videos, and PDFs only.',
21
+ upload: async (file) => {
22
+ if (!isAcceptedMimeType(file.type)) {
23
+ throw new UnsupportedFileTypeError(file.type);
24
+ }
25
+ // Check size before reading into memory to avoid allocating a large buffer.
26
+ if (file.size > MAX_FILE_SIZE) {
27
+ throw new FileTooLargeError(file.size);
28
+ }
29
+ const bytes = new Uint8Array(await file.arrayBuffer());
30
+ return {
31
+ name: file.name,
32
+ type: file.type,
33
+ size: bytes.byteLength,
34
+ data: File.inlineData(bytes),
35
+ };
36
+ },
37
+ };
38
+
39
+ export default Capability.makeModule(() =>
40
+ Effect.succeed(Capability.contributes(FileCapabilities.Backend, inlineBackend)),
41
+ );
@@ -0,0 +1,38 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capability } from '@dxos/app-framework';
8
+ import { MarkdownCapabilities, type MarkdownExtensionProvider } from '@dxos/plugin-markdown/types';
9
+ import { getSpace } from '@dxos/react-client/echo';
10
+
11
+ import { FileCapabilities } from '#types';
12
+
13
+ import { image } from '../extensions';
14
+
15
+ export default Capability.makeModule(
16
+ Effect.fnUntraced(function* () {
17
+ const capabilities = yield* Capability.Service;
18
+
19
+ const provider: MarkdownExtensionProvider = ({ document, viewMode }) => {
20
+ if (viewMode === 'source') {
21
+ return undefined;
22
+ }
23
+
24
+ if (document) {
25
+ const space = getSpace(document);
26
+ if (!space) {
27
+ return undefined;
28
+ }
29
+ const resolvers = capabilities.getAll(FileCapabilities.UrlResolver);
30
+ return [image({ space, resolvers })];
31
+ }
32
+
33
+ return undefined;
34
+ };
35
+
36
+ return Capability.contributes(MarkdownCapabilities.ExtensionProvider, [provider]);
37
+ }),
38
+ );
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capabilities, Capability } from '@dxos/app-framework';
8
+ import type { OperationHandlerSet } from '@dxos/compute';
9
+
10
+ import { FileOperationHandlerSet } from '#operations';
11
+
12
+ export default Capability.makeModule<OperationHandlerSet.OperationHandlerSet>(
13
+ Effect.fnUntraced(function* () {
14
+ return Capability.contributes(Capabilities.OperationHandler, FileOperationHandlerSet);
15
+ }),
16
+ );
@@ -0,0 +1,61 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import React, { useCallback } from 'react';
7
+
8
+ import { Capabilities, Capability } from '@dxos/app-framework';
9
+ import { Surface, useSettingsState } from '@dxos/app-framework/ui';
10
+ import { AppSurface } from '@dxos/app-toolkit/ui';
11
+ import { SchemaEx } from '@dxos/effect';
12
+ import { type FormFieldRendererProps } from '@dxos/react-ui-form';
13
+
14
+ import { FileInput } from '#components';
15
+ import { FileArticle, FileSettings } from '#containers';
16
+ import { meta } from '#meta';
17
+ import { File, FileAction, type Settings } from '#types';
18
+
19
+ export default Capability.makeModule(() =>
20
+ Effect.succeed(
21
+ Capability.contributes(Capabilities.ReactSurface, [
22
+ Surface.create({
23
+ id: 'article',
24
+ filter: AppSurface.oneOf(
25
+ AppSurface.object(AppSurface.Article, File.File),
26
+ AppSurface.object(AppSurface.Section, File.File),
27
+ AppSurface.object(AppSurface.Slide, File.File),
28
+ ),
29
+ component: ({ data, role }) => <FileArticle role={role} subject={data.subject} />,
30
+ }),
31
+ Surface.create({
32
+ id: 'createForm',
33
+ filter: AppSurface.formInputBySchema(
34
+ (ast) => !!SchemaEx.findAnnotation<Record<string, string[]>>(ast, FileAction.UploadAnnotationId),
35
+ ),
36
+ component: ({ data, ...props }) => {
37
+ const ast = data.fieldPropertyAst;
38
+ if (!ast) {
39
+ return null;
40
+ }
41
+
42
+ const inputProps = { ...props, type: ast } as unknown as FormFieldRendererProps;
43
+ const handleChange = useCallback(
44
+ (file: File) => inputProps.onValueChange?.(ast, file),
45
+ [ast, inputProps.onValueChange],
46
+ );
47
+
48
+ return <FileInput schema={data.schema} onChange={handleChange} />;
49
+ },
50
+ }),
51
+ Surface.create({
52
+ id: 'pluginSettings',
53
+ filter: AppSurface.settings(AppSurface.Article, meta.profile.key),
54
+ component: ({ data: { subject } }) => {
55
+ const { settings, updateSettings } = useSettingsState<Settings.Settings>(subject.atom);
56
+ return <FileSettings settings={settings} onSettingsChange={updateSettings} />;
57
+ },
58
+ }),
59
+ ]),
60
+ ),
61
+ );
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capability } from '@dxos/app-framework';
8
+ import { AppCapabilities } from '@dxos/app-toolkit';
9
+ import { createKvsStore } from '@dxos/effect';
10
+
11
+ import { meta } from '#meta';
12
+ import { FileCapabilities, Settings } from '#types';
13
+
14
+ export default Capability.makeModule(() =>
15
+ Effect.sync(() => {
16
+ const settingsAtom = createKvsStore({
17
+ key: meta.profile.key,
18
+ schema: Settings.Settings,
19
+ defaultValue: () => ({}),
20
+ });
21
+
22
+ return [
23
+ Capability.contributes(FileCapabilities.SettingsAtom, settingsAtom),
24
+ Capability.contributes(AppCapabilities.Settings, {
25
+ prefix: meta.profile.key,
26
+ schema: Settings.Settings,
27
+ atom: settingsAtom,
28
+ }),
29
+ ];
30
+ }),
31
+ );
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capability } from '@dxos/app-framework';
8
+ import { AppCapabilities } from '@dxos/app-toolkit';
9
+
10
+ import { FileSkill } from '#skills';
11
+
12
+ const skillDefinition = () => Effect.succeed([Capability.contributes(AppCapabilities.SkillDefinition, FileSkill)]);
13
+
14
+ export default skillDefinition;
@@ -0,0 +1,48 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import type * as Schema from 'effect/Schema';
6
+ import React, { useCallback } from 'react';
7
+ import { useDropzone } from 'react-dropzone';
8
+
9
+ import { SchemaEx } from '@dxos/effect';
10
+ import { useTranslation } from '@dxos/react-ui';
11
+ import { mx } from '@dxos/ui-theme';
12
+
13
+ import { meta } from '#meta';
14
+ import { FileAction, MAX_FILE_SIZE } from '#types';
15
+
16
+ export type FileInputProps = {
17
+ schema: Schema.Schema.AnyNoContext;
18
+ onChange: (file: File) => void;
19
+ };
20
+
21
+ export const FileInput = ({ schema, onChange }: FileInputProps) => {
22
+ const { t } = useTranslation(meta.profile.key);
23
+ const accept = SchemaEx.findAnnotation<Record<string, string[]>>(schema.ast, FileAction.UploadAnnotationId);
24
+
25
+ const onDropAccepted = useCallback((files: File[]) => onChange?.(files[0]), [onChange]);
26
+
27
+ const { acceptedFiles, getRootProps, getInputProps, isFocused, isDragAccept, isDragReject } = useDropzone({
28
+ multiple: false,
29
+ accept,
30
+ maxSize: MAX_FILE_SIZE,
31
+ onDropAccepted,
32
+ });
33
+
34
+ return (
35
+ <div
36
+ {...getRootProps()}
37
+ className={mx(
38
+ 'flex flex-col items-center p-8 border border-separator',
39
+ isFocused && 'focus-ring',
40
+ isDragAccept && 'bg-attention-surface',
41
+ isDragReject && 'border-rose-bg',
42
+ )}
43
+ >
44
+ <input {...getInputProps()} />
45
+ {acceptedFiles[0] ? <p>{acceptedFiles[0].name}</p> : <p>{t('file-input.placeholder')}</p>}
46
+ </div>
47
+ );
48
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './FileInput';