@dxos/plugin-markdown 0.7.5-main.9cb18ac → 0.7.5-main.9d2a38b

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 (165) hide show
  1. package/dist/lib/browser/{MarkdownContainer-XY6NEUOA.mjs → MarkdownContainer-ARRY4I6S.mjs} +95 -46
  2. package/dist/lib/browser/MarkdownContainer-ARRY4I6S.mjs.map +7 -0
  3. package/dist/lib/browser/app-graph-serializer-HHWSLYIW.mjs +51 -0
  4. package/dist/lib/browser/app-graph-serializer-HHWSLYIW.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-R4MG2DP2.mjs → chunk-ADAYSA5G.mjs} +20 -9
  6. package/dist/lib/browser/chunk-ADAYSA5G.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-6FIHBJRV.mjs → chunk-EMIIXXVX.mjs} +17 -12
  8. package/dist/lib/browser/chunk-EMIIXXVX.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-FSAYVXSE.mjs +16 -0
  10. package/dist/lib/browser/chunk-FSAYVXSE.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-4X6YX3KU.mjs → chunk-YB2TJFNH.mjs} +3 -3
  12. package/dist/lib/browser/chunk-YB2TJFNH.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-YPDWX3WI.mjs +47 -0
  14. package/dist/lib/browser/chunk-YPDWX3WI.mjs.map +7 -0
  15. package/dist/lib/browser/index.mjs +100 -367
  16. package/dist/lib/browser/index.mjs.map +4 -4
  17. package/dist/lib/browser/intent-resolver-4KWMUMND.mjs +37 -0
  18. package/dist/lib/browser/intent-resolver-4KWMUMND.mjs.map +7 -0
  19. package/dist/lib/browser/meta.json +1 -1
  20. package/dist/lib/browser/react-surface-RL4CISJZ.mjs +152 -0
  21. package/dist/lib/browser/react-surface-RL4CISJZ.mjs.map +7 -0
  22. package/dist/lib/browser/settings-PTF73JDL.mjs +28 -0
  23. package/dist/lib/browser/settings-PTF73JDL.mjs.map +7 -0
  24. package/dist/lib/browser/state-DOVZP7XJ.mjs +37 -0
  25. package/dist/lib/browser/state-DOVZP7XJ.mjs.map +7 -0
  26. package/dist/lib/browser/thread-G4RS7NBZ.mjs +36 -0
  27. package/dist/lib/browser/thread-G4RS7NBZ.mjs.map +7 -0
  28. package/dist/lib/browser/types/index.mjs +4 -4
  29. package/dist/lib/node/{MarkdownContainer-EX6YDF6J.cjs → MarkdownContainer-TV6W64EN.cjs} +100 -53
  30. package/dist/lib/node/MarkdownContainer-TV6W64EN.cjs.map +7 -0
  31. package/dist/lib/node/app-graph-serializer-PJRST43Q.cjs +62 -0
  32. package/dist/lib/node/app-graph-serializer-PJRST43Q.cjs.map +7 -0
  33. package/dist/lib/node/{chunk-CQMXZ54Z.cjs → chunk-34WFGSZU.cjs} +21 -16
  34. package/dist/lib/node/chunk-34WFGSZU.cjs.map +7 -0
  35. package/dist/lib/node/chunk-7WZANRNS.cjs +64 -0
  36. package/dist/lib/node/chunk-7WZANRNS.cjs.map +7 -0
  37. package/dist/lib/node/{meta.cjs → chunk-G7WKHUGU.cjs} +13 -8
  38. package/dist/lib/node/chunk-G7WKHUGU.cjs.map +7 -0
  39. package/dist/lib/node/{chunk-PHHIPRJC.cjs → chunk-HTWAAR54.cjs} +7 -7
  40. package/dist/lib/node/chunk-HTWAAR54.cjs.map +7 -0
  41. package/dist/lib/node/{chunk-SXQAPZZU.cjs → chunk-UFFFUC6W.cjs} +37 -12
  42. package/dist/lib/node/chunk-UFFFUC6W.cjs.map +7 -0
  43. package/dist/lib/node/index.cjs +92 -366
  44. package/dist/lib/node/index.cjs.map +4 -4
  45. package/dist/lib/node/intent-resolver-QN25AFOP.cjs +50 -0
  46. package/dist/lib/node/intent-resolver-QN25AFOP.cjs.map +7 -0
  47. package/dist/lib/node/meta.json +1 -1
  48. package/dist/lib/node/react-surface-RZ2XV56V.cjs +165 -0
  49. package/dist/lib/node/react-surface-RZ2XV56V.cjs.map +7 -0
  50. package/dist/lib/node/settings-BO5P5R4I.cjs +42 -0
  51. package/dist/lib/node/settings-BO5P5R4I.cjs.map +7 -0
  52. package/dist/lib/node/state-F26NQP7F.cjs +51 -0
  53. package/dist/lib/node/state-F26NQP7F.cjs.map +7 -0
  54. package/dist/lib/node/thread-THWQ67WS.cjs +52 -0
  55. package/dist/lib/node/thread-THWQ67WS.cjs.map +7 -0
  56. package/dist/lib/node/types/index.cjs +7 -7
  57. package/dist/lib/node/types/index.cjs.map +1 -1
  58. package/dist/lib/node-esm/{MarkdownContainer-E7W623A7.mjs → MarkdownContainer-3ZHQTTMQ.mjs} +95 -46
  59. package/dist/lib/node-esm/MarkdownContainer-3ZHQTTMQ.mjs.map +7 -0
  60. package/dist/lib/node-esm/app-graph-serializer-JTHJUUS2.mjs +52 -0
  61. package/dist/lib/node-esm/app-graph-serializer-JTHJUUS2.mjs.map +7 -0
  62. package/dist/lib/node-esm/chunk-2GHK262V.mjs +17 -0
  63. package/dist/lib/node-esm/chunk-2GHK262V.mjs.map +7 -0
  64. package/dist/lib/node-esm/{chunk-BABK7FMW.mjs → chunk-AOKWCL3O.mjs} +3 -3
  65. package/dist/lib/node-esm/chunk-AOKWCL3O.mjs.map +7 -0
  66. package/dist/lib/node-esm/{chunk-ZVFBKBSA.mjs → chunk-CDFNMFGT.mjs} +17 -12
  67. package/dist/lib/node-esm/chunk-CDFNMFGT.mjs.map +7 -0
  68. package/dist/lib/node-esm/chunk-HLP536EW.mjs +48 -0
  69. package/dist/lib/node-esm/chunk-HLP536EW.mjs.map +7 -0
  70. package/dist/lib/node-esm/{chunk-Y76MM22C.mjs → chunk-Y5RSQVGH.mjs} +20 -9
  71. package/dist/lib/node-esm/chunk-Y5RSQVGH.mjs.map +7 -0
  72. package/dist/lib/node-esm/index.mjs +100 -367
  73. package/dist/lib/node-esm/index.mjs.map +4 -4
  74. package/dist/lib/node-esm/intent-resolver-F7MQOTG7.mjs +38 -0
  75. package/dist/lib/node-esm/intent-resolver-F7MQOTG7.mjs.map +7 -0
  76. package/dist/lib/node-esm/meta.json +1 -1
  77. package/dist/lib/node-esm/react-surface-RRXHEW4R.mjs +153 -0
  78. package/dist/lib/node-esm/react-surface-RRXHEW4R.mjs.map +7 -0
  79. package/dist/lib/node-esm/settings-E7P5FQ3F.mjs +29 -0
  80. package/dist/lib/node-esm/settings-E7P5FQ3F.mjs.map +7 -0
  81. package/dist/lib/node-esm/state-HXSOQNOV.mjs +38 -0
  82. package/dist/lib/node-esm/state-HXSOQNOV.mjs.map +7 -0
  83. package/dist/lib/node-esm/thread-CYEXBXTW.mjs +37 -0
  84. package/dist/lib/node-esm/thread-CYEXBXTW.mjs.map +7 -0
  85. package/dist/lib/node-esm/types/index.mjs +4 -4
  86. package/dist/types/src/MarkdownPlugin.d.ts +1 -3
  87. package/dist/types/src/MarkdownPlugin.d.ts.map +1 -1
  88. package/dist/types/src/capabilities/app-graph-serializer.d.ts +4 -0
  89. package/dist/types/src/capabilities/app-graph-serializer.d.ts.map +1 -0
  90. package/dist/types/src/capabilities/capabilities.d.ts +12 -0
  91. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -0
  92. package/dist/types/src/capabilities/index.d.ts +16 -0
  93. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  94. package/dist/types/src/capabilities/intent-resolver.d.ts +4 -0
  95. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
  96. package/dist/types/src/capabilities/react-surface.d.ts +4 -0
  97. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  98. package/dist/types/src/capabilities/settings.d.ts +4 -0
  99. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  100. package/dist/types/src/capabilities/state.d.ts +11 -0
  101. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  102. package/dist/types/src/capabilities/thread.d.ts +6 -0
  103. package/dist/types/src/capabilities/thread.d.ts.map +1 -0
  104. package/dist/types/src/components/MarkdownContainer.d.ts +1 -1
  105. package/dist/types/src/components/MarkdownContainer.d.ts.map +1 -1
  106. package/dist/types/src/components/MarkdownEditor.d.ts +15 -0
  107. package/dist/types/src/components/MarkdownEditor.d.ts.map +1 -1
  108. package/dist/types/src/components/MarkdownEditor.stories.d.ts.map +1 -1
  109. package/dist/types/src/components/Toolbar.stories.d.ts +2 -2
  110. package/dist/types/src/components/Toolbar.stories.d.ts.map +1 -1
  111. package/dist/types/src/components/index.d.ts +1 -1
  112. package/dist/types/src/components/index.d.ts.map +1 -1
  113. package/dist/types/src/extensions.d.ts +2 -2
  114. package/dist/types/src/extensions.d.ts.map +1 -1
  115. package/dist/types/src/index.d.ts +2 -3
  116. package/dist/types/src/index.d.ts.map +1 -1
  117. package/dist/types/src/meta.d.ts +1 -2
  118. package/dist/types/src/meta.d.ts.map +1 -1
  119. package/dist/types/src/translations.d.ts +3 -0
  120. package/dist/types/src/translations.d.ts.map +1 -1
  121. package/dist/types/src/types/schema.d.ts +0 -7
  122. package/dist/types/src/types/schema.d.ts.map +1 -1
  123. package/dist/types/src/types/types.d.ts +12 -26
  124. package/dist/types/src/types/types.d.ts.map +1 -1
  125. package/dist/types/src/util.d.ts +1 -5
  126. package/dist/types/src/util.d.ts.map +1 -1
  127. package/dist/types/tsconfig.tsbuildinfo +1 -1
  128. package/package.json +32 -37
  129. package/src/MarkdownPlugin.tsx +79 -213
  130. package/src/capabilities/app-graph-serializer.ts +50 -0
  131. package/src/capabilities/capabilities.ts +20 -0
  132. package/src/capabilities/index.ts +14 -0
  133. package/src/capabilities/intent-resolver.ts +27 -0
  134. package/src/capabilities/react-surface.tsx +73 -0
  135. package/src/capabilities/settings.ts +25 -0
  136. package/src/capabilities/state.ts +31 -0
  137. package/src/capabilities/thread.ts +34 -0
  138. package/src/components/MarkdownContainer.tsx +6 -7
  139. package/src/components/MarkdownEditor.stories.tsx +16 -5
  140. package/src/components/MarkdownEditor.tsx +82 -48
  141. package/src/components/Toolbar.stories.tsx +16 -22
  142. package/src/extensions.tsx +13 -10
  143. package/src/index.ts +2 -5
  144. package/src/meta.ts +1 -1
  145. package/src/translations.ts +1 -0
  146. package/src/types/schema.ts +1 -5
  147. package/src/types/types.ts +16 -44
  148. package/src/util.tsx +2 -8
  149. package/dist/lib/browser/MarkdownContainer-XY6NEUOA.mjs.map +0 -7
  150. package/dist/lib/browser/chunk-4X6YX3KU.mjs.map +0 -7
  151. package/dist/lib/browser/chunk-6FIHBJRV.mjs.map +0 -7
  152. package/dist/lib/browser/chunk-R4MG2DP2.mjs.map +0 -7
  153. package/dist/lib/browser/meta.mjs +0 -9
  154. package/dist/lib/browser/meta.mjs.map +0 -7
  155. package/dist/lib/node/MarkdownContainer-EX6YDF6J.cjs.map +0 -7
  156. package/dist/lib/node/chunk-CQMXZ54Z.cjs.map +0 -7
  157. package/dist/lib/node/chunk-PHHIPRJC.cjs.map +0 -7
  158. package/dist/lib/node/chunk-SXQAPZZU.cjs.map +0 -7
  159. package/dist/lib/node/meta.cjs.map +0 -7
  160. package/dist/lib/node-esm/MarkdownContainer-E7W623A7.mjs.map +0 -7
  161. package/dist/lib/node-esm/chunk-BABK7FMW.mjs.map +0 -7
  162. package/dist/lib/node-esm/chunk-Y76MM22C.mjs.map +0 -7
  163. package/dist/lib/node-esm/chunk-ZVFBKBSA.mjs.map +0 -7
  164. package/dist/lib/node-esm/meta.mjs +0 -10
  165. package/dist/lib/node-esm/meta.mjs.map +0 -7
@@ -1,85 +1,59 @@
1
1
  //
2
- // Copyright 2023 DXOS.org
2
+ // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { pipe } from 'effect';
6
- import React from 'react';
7
-
8
5
  import {
9
- parseIntentPlugin,
10
- resolvePlugin,
11
- type PluginDefinition,
12
- createSurface,
13
- createResolver,
6
+ Capabilities,
7
+ contributes,
14
8
  createIntent,
15
- chain,
9
+ defineModule,
10
+ definePlugin,
11
+ Events,
12
+ oneOf,
16
13
  } from '@dxos/app-framework';
17
- import type { BaseObject } from '@dxos/echo-schema';
18
- import { create, makeRef, RefArray } from '@dxos/live-object';
19
- import { LocalStorageStore } from '@dxos/local-storage';
20
- import { SpaceAction } from '@dxos/plugin-space';
21
- import { CollectionType } from '@dxos/plugin-space/types';
22
- import { createDocAccessor, fullyQualifiedId, getRangeFromCursor, isSpace } from '@dxos/react-client/echo';
23
- import {
24
- type EditorInputMode,
25
- type EditorViewMode,
26
- translations as editorTranslations,
27
- createEditorStateStore,
28
- } from '@dxos/react-ui-editor';
14
+ import { type BaseObject } from '@dxos/echo-schema';
15
+ import { RefArray } from '@dxos/live-object';
16
+ import { ClientCapabilities, ClientEvents } from '@dxos/plugin-client';
17
+ import { translations as editorTranslations } from '@dxos/react-ui-editor';
18
+ import { TextType } from '@dxos/schema';
29
19
 
30
- import { MarkdownContainer, MarkdownSettings } from './components';
31
- import meta, { MARKDOWN_PLUGIN } from './meta';
32
- import translations from './translations';
33
- import { DocumentType, isEditorModel, TextType } from './types';
34
20
  import {
35
- type MarkdownPluginProvides,
36
- type MarkdownSettingsProps,
37
- MarkdownAction,
38
- type MarkdownPluginState,
39
- } from './types';
40
- import { markdownExtensionPlugins, serializer } from './util';
41
-
42
- export const MarkdownPlugin = (): PluginDefinition<MarkdownPluginProvides> => {
43
- const settings = new LocalStorageStore<MarkdownSettingsProps>(MARKDOWN_PLUGIN, {
44
- defaultViewMode: 'preview',
45
- toolbar: true,
46
- numberedHeadings: true,
47
- folding: true,
48
- experimental: false,
49
- });
50
-
51
- const editorStateStore = createEditorStateStore(`${MARKDOWN_PLUGIN}/editor`);
52
-
53
- const state = new LocalStorageStore<MarkdownPluginState>(MARKDOWN_PLUGIN, { extensionProviders: [], viewMode: {} });
54
-
55
- const getViewMode = (id: string) => (id && state.values.viewMode[id]) || settings.values.defaultViewMode;
56
- const setViewMode = (id: string, viewMode: EditorViewMode) => (state.values.viewMode[id] = viewMode);
57
-
58
- return {
59
- meta,
60
- ready: async ({ plugins }) => {
61
- settings
62
- .prop({ key: 'defaultViewMode', type: LocalStorageStore.enum<EditorViewMode>() })
63
- .prop({ key: 'editorInputMode', type: LocalStorageStore.enum<EditorInputMode>({ allowUndefined: true }) })
64
- .prop({ key: 'toolbar', type: LocalStorageStore.bool({ allowUndefined: true }) })
65
- .prop({ key: 'experimental', type: LocalStorageStore.bool({ allowUndefined: true }) })
66
- .prop({ key: 'debug', type: LocalStorageStore.bool({ allowUndefined: true }) })
67
- .prop({ key: 'typewriter', type: LocalStorageStore.string({ allowUndefined: true }) })
68
- .prop({ key: 'numberedHeadings', type: LocalStorageStore.bool({ allowUndefined: true }) })
69
- .prop({ key: 'folding', type: LocalStorageStore.bool({ allowUndefined: true }) });
70
-
71
- state.prop({ key: 'viewMode', type: LocalStorageStore.json<{ [key: string]: EditorViewMode }>() });
72
-
73
- markdownExtensionPlugins(plugins).forEach((plugin) => {
74
- const { extensions } = plugin.provides.markdown;
75
- state.values.extensionProviders?.push(extensions);
76
- });
77
- },
78
- provides: {
79
- settings: settings.values,
80
- metadata: {
81
- records: {
82
- [DocumentType.typename]: {
21
+ MarkdownState,
22
+ MarkdownSettings,
23
+ ReactSurface,
24
+ IntentResolver,
25
+ AppGraphSerializer,
26
+ Thread,
27
+ } from './capabilities';
28
+ import { MARKDOWN_PLUGIN, meta } from './meta';
29
+ import translations from './translations';
30
+ import { DocumentType, MarkdownAction } from './types';
31
+ import { serializer } from './util';
32
+
33
+ export const MarkdownPlugin = () =>
34
+ definePlugin(meta, [
35
+ defineModule({
36
+ id: `${meta.id}/module/settings`,
37
+ activatesOn: Events.SetupSettings,
38
+ activate: MarkdownSettings,
39
+ }),
40
+ defineModule({
41
+ id: `${meta.id}/module/state`,
42
+ activatesOn: Events.Startup,
43
+ activate: MarkdownState,
44
+ }),
45
+ defineModule({
46
+ id: `${meta.id}/module/translations`,
47
+ activatesOn: Events.SetupTranslations,
48
+ activate: () => contributes(Capabilities.Translations, [...translations, ...editorTranslations]),
49
+ }),
50
+ defineModule({
51
+ id: `${meta.id}/module/metadata`,
52
+ activatesOn: oneOf(Events.Startup, Events.SetupAppGraph),
53
+ activate: () =>
54
+ contributes(Capabilities.Metadata, {
55
+ id: DocumentType.typename,
56
+ metadata: {
83
57
  createObject: (props: { name?: string }) => createIntent(MarkdownAction.Create, props),
84
58
  label: (object: any) => (object instanceof DocumentType ? object.name || object.fallbackName : undefined),
85
59
  placeholder: ['document title placeholder', { ns: MARKDOWN_PLUGIN }],
@@ -92,142 +66,34 @@ export const MarkdownPlugin = (): PluginDefinition<MarkdownPluginProvides> => {
92
66
  await RefArray.loadAll<BaseObject>([doc.content, ...doc.threads]),
93
67
  serializer,
94
68
  },
95
- },
96
- },
97
- translations: [...translations, ...editorTranslations],
98
- echo: {
99
- schema: [DocumentType],
100
- system: [TextType],
101
- },
102
- graph: {
103
- serializer: (plugins) => {
104
- const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatchPromise;
105
- if (!dispatch) {
106
- return [];
107
- }
108
-
109
- return [
110
- {
111
- inputType: DocumentType.typename,
112
- outputType: 'text/markdown',
113
- // Reconcile with metadata serializers.
114
- serialize: async (node) => {
115
- const doc = node.data;
116
- const content = await doc.content.load();
117
- return {
118
- name:
119
- doc.name ||
120
- doc.fallbackName ||
121
- translations[0]['en-US'][MARKDOWN_PLUGIN]['document title placeholder'],
122
- data: content.content,
123
- type: 'text/markdown',
124
- };
125
- },
126
- deserialize: async (data, ancestors) => {
127
- const space = ancestors.find(isSpace);
128
- const target =
129
- ancestors.findLast((ancestor) => ancestor instanceof CollectionType) ??
130
- space?.properties[CollectionType.typename]?.target;
131
- if (!space || !target) {
132
- return;
133
- }
134
-
135
- const result = await dispatch(
136
- pipe(
137
- createIntent(MarkdownAction.Create, { name: data.name, content: data.data }),
138
- chain(SpaceAction.AddObject, { target }),
139
- ),
140
- );
141
-
142
- return result.data?.object;
143
- },
144
- },
145
- ];
146
- },
147
- },
148
- thread: {
149
- predicate: (obj) => obj instanceof DocumentType,
150
- createSort: (doc: DocumentType) => {
151
- const accessor = doc.content.target ? createDocAccessor(doc.content.target, ['content']) : undefined;
152
- if (!accessor) {
153
- return (_) => 0;
154
- }
155
-
156
- const getStartPosition = (cursor: string | undefined) => {
157
- const range = cursor ? getRangeFromCursor(accessor, cursor) : undefined;
158
- return range?.start ?? Number.MAX_SAFE_INTEGER;
159
- };
160
-
161
- return (anchorA: string | undefined, anchorB: string | undefined): number => {
162
- if (anchorA === undefined || anchorB === undefined) {
163
- return 0;
164
- }
165
- const posA = getStartPosition(anchorA);
166
- const posB = getStartPosition(anchorB);
167
- return posA - posB;
168
- };
169
- },
170
- },
171
- surface: {
172
- definitions: () => [
173
- createSurface({
174
- id: `${MARKDOWN_PLUGIN}/document`,
175
- role: ['article', 'section'],
176
- filter: (data): data is { subject: DocumentType } => data.subject instanceof DocumentType,
177
- component: ({ data, role }) => (
178
- <MarkdownContainer
179
- id={fullyQualifiedId(data.subject)}
180
- object={data.subject}
181
- role={role}
182
- settings={settings.values}
183
- extensionProviders={state.values.extensionProviders}
184
- viewMode={getViewMode(fullyQualifiedId(data.subject))}
185
- editorStateStore={editorStateStore}
186
- onViewModeChange={setViewMode}
187
- />
188
- ),
189
- }),
190
- createSurface({
191
- id: `${MARKDOWN_PLUGIN}/editor`,
192
- role: ['article', 'section'],
193
- filter: (data): data is { subject: { id: string; text: string } } => isEditorModel(data.subject),
194
- component: ({ data, role }) => (
195
- <MarkdownContainer
196
- id={data.subject.id}
197
- object={data.subject}
198
- role={role}
199
- settings={settings.values}
200
- extensionProviders={state.values.extensionProviders}
201
- viewMode={getViewMode(data.subject.id)}
202
- editorStateStore={editorStateStore}
203
- onViewModeChange={setViewMode}
204
- />
205
- ),
206
- }),
207
- createSurface({
208
- id: `${MARKDOWN_PLUGIN}/settings`,
209
- role: 'settings',
210
- filter: (data): data is any => data.subject === MARKDOWN_PLUGIN,
211
- component: () => <MarkdownSettings settings={settings.values} />,
212
- }),
213
- ],
214
- },
215
- intent: {
216
- resolvers: () => [
217
- createResolver(MarkdownAction.Create, ({ name, content }) => {
218
- const doc = create(DocumentType, {
219
- name,
220
- content: makeRef(create(TextType, { content: content ?? '' })),
221
- threads: [],
222
- });
223
-
224
- return { data: { object: doc } };
225
- }),
226
- createResolver(MarkdownAction.SetViewMode, ({ id, viewMode }) => {
227
- state.values.viewMode[id] = viewMode;
228
- }),
229
- ],
230
- },
231
- },
232
- };
233
- };
69
+ }),
70
+ }),
71
+ defineModule({
72
+ id: `${meta.id}/module/schema`,
73
+ activatesOn: ClientEvents.SetupClient,
74
+ activate: () => [
75
+ contributes(ClientCapabilities.SystemSchema, [TextType]),
76
+ contributes(ClientCapabilities.Schema, [DocumentType]),
77
+ ],
78
+ }),
79
+ defineModule({
80
+ id: `${meta.id}/module/react-surface`,
81
+ activatesOn: Events.Startup,
82
+ activate: ReactSurface,
83
+ }),
84
+ defineModule({
85
+ id: `${meta.id}/module/intent-resolver`,
86
+ activatesOn: Events.SetupIntents,
87
+ activate: IntentResolver,
88
+ }),
89
+ defineModule({
90
+ id: `${meta.id}/module/app-graph-serializer`,
91
+ activatesOn: Events.Startup,
92
+ activate: AppGraphSerializer,
93
+ }),
94
+ defineModule({
95
+ id: `${meta.id}/module/thread`,
96
+ activatesOn: Events.Startup,
97
+ activate: Thread,
98
+ }),
99
+ ]);
@@ -0,0 +1,50 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { pipe } from 'effect';
6
+
7
+ import { contributes, Capabilities, type PluginsContext, chain, createIntent } from '@dxos/app-framework';
8
+ import { SpaceAction, CollectionType } from '@dxos/plugin-space/types';
9
+ import { isSpace } from '@dxos/react-client/echo';
10
+
11
+ import { MARKDOWN_PLUGIN } from '../meta';
12
+ import translations from '../translations';
13
+ import { MarkdownAction, DocumentType } from '../types';
14
+
15
+ export default (context: PluginsContext) =>
16
+ contributes(Capabilities.AppGraphSerializer, [
17
+ {
18
+ inputType: DocumentType.typename,
19
+ outputType: 'text/markdown',
20
+ // Reconcile with metadata serializers.
21
+ serialize: async (node) => {
22
+ const doc = node.data;
23
+ const content = await doc.content.load();
24
+ return {
25
+ name: doc.name || doc.fallbackName || translations[0]['en-US'][MARKDOWN_PLUGIN]['document title placeholder'],
26
+ data: content.content,
27
+ type: 'text/markdown',
28
+ };
29
+ },
30
+ deserialize: async (data, ancestors) => {
31
+ const space = ancestors.find(isSpace);
32
+ const target =
33
+ ancestors.findLast((ancestor) => ancestor instanceof CollectionType) ??
34
+ space?.properties[CollectionType.typename]?.target;
35
+ if (!space || !target) {
36
+ return;
37
+ }
38
+
39
+ const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
40
+ const result = await dispatch(
41
+ pipe(
42
+ createIntent(MarkdownAction.Create, { name: data.name, content: data.data }),
43
+ chain(SpaceAction.AddObject, { target }),
44
+ ),
45
+ );
46
+
47
+ return result.data?.object;
48
+ },
49
+ },
50
+ ]);
@@ -0,0 +1,20 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { defineCapability } from '@dxos/app-framework';
6
+ import { type EditorViewMode, type EditorStateStore } from '@dxos/react-ui-editor';
7
+
8
+ import { MARKDOWN_PLUGIN } from '../meta';
9
+ import { type MarkdownExtensionProvider, type MarkdownPluginState } from '../types';
10
+
11
+ export namespace MarkdownCapabilities {
12
+ export const State = defineCapability<{
13
+ state: MarkdownPluginState;
14
+ editorState: EditorStateStore;
15
+ getViewMode: (id: string) => EditorViewMode;
16
+ setViewMode: (id: string, viewMode: EditorViewMode) => void;
17
+ }>(`${MARKDOWN_PLUGIN}/capability/state`);
18
+
19
+ export const Extensions = defineCapability<MarkdownExtensionProvider[]>(`${MARKDOWN_PLUGIN}/capability/extensions`);
20
+ }
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { lazy } from '@dxos/app-framework';
6
+
7
+ export const AppGraphSerializer = lazy(() => import('./app-graph-serializer'));
8
+ export const IntentResolver = lazy(() => import('./intent-resolver'));
9
+ export const ReactSurface = lazy(() => import('./react-surface'));
10
+ export const MarkdownSettings = lazy(() => import('./settings'));
11
+ export const MarkdownState = lazy(() => import('./state'));
12
+ export const Thread = lazy(() => import('./thread'));
13
+
14
+ export * from './capabilities';
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Capabilities, contributes, createResolver, type PluginsContext } from '@dxos/app-framework';
6
+ import { makeRef, create } from '@dxos/live-object';
7
+ import { TextType } from '@dxos/schema';
8
+
9
+ import { MarkdownCapabilities } from './capabilities';
10
+ import { DocumentType, MarkdownAction } from '../types';
11
+
12
+ export default (context: PluginsContext) =>
13
+ contributes(Capabilities.IntentResolver, [
14
+ createResolver(MarkdownAction.Create, ({ name, content }) => {
15
+ const doc = create(DocumentType, {
16
+ name,
17
+ content: makeRef(create(TextType, { content: content ?? '' })),
18
+ threads: [],
19
+ });
20
+
21
+ return { data: { object: doc } };
22
+ }),
23
+ createResolver(MarkdownAction.SetViewMode, ({ id, viewMode }) => {
24
+ const { state } = context.requestCapability(MarkdownCapabilities.State);
25
+ state.viewMode[id] = viewMode;
26
+ }),
27
+ ]);
@@ -0,0 +1,73 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { createSurface, contributes, Capabilities, useCapability } from '@dxos/app-framework';
8
+ import { fullyQualifiedId } from '@dxos/react-client/echo';
9
+
10
+ import { MarkdownCapabilities } from './capabilities';
11
+ import { MarkdownContainer, MarkdownSettings } from '../components';
12
+ import { MARKDOWN_PLUGIN } from '../meta';
13
+ import { DocumentType, isEditorModel, type MarkdownSettingsProps } from '../types';
14
+
15
+ export default () =>
16
+ contributes(Capabilities.ReactSurface, [
17
+ createSurface({
18
+ id: `${MARKDOWN_PLUGIN}/document`,
19
+ role: ['article', 'section'],
20
+ filter: (data): data is { subject: DocumentType } => data.subject instanceof DocumentType,
21
+ component: ({ data, role }) => {
22
+ const settingsStore = useCapability(Capabilities.SettingsStore);
23
+ const settings = settingsStore.getStore<MarkdownSettingsProps>(MARKDOWN_PLUGIN)!.value;
24
+ const { state, editorState, getViewMode, setViewMode } = useCapability(MarkdownCapabilities.State);
25
+
26
+ return (
27
+ <MarkdownContainer
28
+ id={fullyQualifiedId(data.subject)}
29
+ object={data.subject}
30
+ role={role}
31
+ settings={settings}
32
+ extensionProviders={state.extensionProviders}
33
+ viewMode={getViewMode(fullyQualifiedId(data.subject))}
34
+ editorStateStore={editorState}
35
+ onViewModeChange={setViewMode}
36
+ />
37
+ );
38
+ },
39
+ }),
40
+ createSurface({
41
+ id: `${MARKDOWN_PLUGIN}/editor`,
42
+ role: ['article', 'section'],
43
+ filter: (data): data is { subject: { id: string; text: string } } => isEditorModel(data.subject),
44
+ component: ({ data, role }) => {
45
+ const settingsStore = useCapability(Capabilities.SettingsStore);
46
+ const settings = settingsStore.getStore<MarkdownSettingsProps>(MARKDOWN_PLUGIN)!.value;
47
+ const { state, editorState, getViewMode, setViewMode } = useCapability(MarkdownCapabilities.State);
48
+
49
+ return (
50
+ <MarkdownContainer
51
+ id={data.subject.id}
52
+ object={data.subject}
53
+ role={role}
54
+ settings={settings}
55
+ extensionProviders={state.extensionProviders}
56
+ viewMode={getViewMode(data.subject.id)}
57
+ editorStateStore={editorState}
58
+ onViewModeChange={setViewMode}
59
+ />
60
+ );
61
+ },
62
+ }),
63
+ createSurface({
64
+ id: `${MARKDOWN_PLUGIN}/settings`,
65
+ role: 'settings',
66
+ filter: (data): data is any => data.subject === MARKDOWN_PLUGIN,
67
+ component: () => {
68
+ const settingsStore = useCapability(Capabilities.SettingsStore);
69
+ const settings = settingsStore.getStore<MarkdownSettingsProps>(MARKDOWN_PLUGIN)!.value;
70
+ return <MarkdownSettings settings={settings} />;
71
+ },
72
+ }),
73
+ ]);
@@ -0,0 +1,25 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Capabilities, contributes } from '@dxos/app-framework';
6
+ import { create } from '@dxos/live-object';
7
+
8
+ import { MARKDOWN_PLUGIN } from '../meta';
9
+ import { type MarkdownSettingsProps, MarkdownSettingsSchema } from '../types';
10
+
11
+ export default () => {
12
+ const settings = create<MarkdownSettingsProps>({
13
+ defaultViewMode: 'preview',
14
+ toolbar: true,
15
+ numberedHeadings: true,
16
+ folding: true,
17
+ experimental: false,
18
+ });
19
+
20
+ return contributes(Capabilities.Settings, {
21
+ schema: MarkdownSettingsSchema,
22
+ prefix: MARKDOWN_PLUGIN,
23
+ value: settings,
24
+ });
25
+ };
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Capabilities, contributes, type PluginsContext } from '@dxos/app-framework';
6
+ import { LocalStorageStore } from '@dxos/local-storage';
7
+ import { type EditorViewMode, createEditorStateStore } from '@dxos/react-ui-editor';
8
+
9
+ import { MarkdownCapabilities } from './capabilities';
10
+ import { MARKDOWN_PLUGIN } from '../meta';
11
+ import { type MarkdownPluginState, type MarkdownSettingsProps } from '../types';
12
+
13
+ export default (context: PluginsContext) => {
14
+ const state = new LocalStorageStore<MarkdownPluginState>(MARKDOWN_PLUGIN, { extensionProviders: [], viewMode: {} });
15
+
16
+ state.prop({ key: 'viewMode', type: LocalStorageStore.json<{ [key: string]: EditorViewMode }>() });
17
+
18
+ // TODO(wittjosiah): Fold into state.
19
+ const editorState = createEditorStateStore(`${MARKDOWN_PLUGIN}/editor`);
20
+
21
+ const getViewMode = (id: string) => {
22
+ const defaultViewMode = context
23
+ .requestCapability(Capabilities.SettingsStore)
24
+ .getStore<MarkdownSettingsProps>(MARKDOWN_PLUGIN)!.value.defaultViewMode;
25
+ return (id && state.values.viewMode[id]) || defaultViewMode;
26
+ };
27
+
28
+ const setViewMode = (id: string, viewMode: EditorViewMode) => (state.values.viewMode[id] = viewMode);
29
+
30
+ return contributes(MarkdownCapabilities.State, { state: state.values, editorState, getViewMode, setViewMode });
31
+ };
@@ -0,0 +1,34 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { contributes } from '@dxos/app-framework';
6
+ import { ThreadCapabilities } from '@dxos/plugin-space';
7
+ import { createDocAccessor, getRangeFromCursor } from '@dxos/react-client/echo';
8
+
9
+ import { DocumentType } from '../types';
10
+
11
+ export default () =>
12
+ contributes(ThreadCapabilities.Thread, {
13
+ predicate: (obj) => obj instanceof DocumentType,
14
+ createSort: (doc: DocumentType) => {
15
+ const accessor = doc.content.target ? createDocAccessor(doc.content.target, ['content']) : undefined;
16
+ if (!accessor) {
17
+ return (_) => 0;
18
+ }
19
+
20
+ const getStartPosition = (cursor: string | undefined) => {
21
+ const range = cursor ? getRangeFromCursor(accessor, cursor) : undefined;
22
+ return range?.start ?? Number.MAX_SAFE_INTEGER;
23
+ };
24
+
25
+ return (anchorA: string | undefined, anchorB: string | undefined): number => {
26
+ if (anchorA === undefined || anchorB === undefined) {
27
+ return 0;
28
+ }
29
+ const posA = getStartPosition(anchorA);
30
+ const posB = getStartPosition(anchorB);
31
+ return posA - posB;
32
+ };
33
+ },
34
+ });
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React, { useEffect, useMemo } from 'react';
6
6
 
7
- import { useResolvePlugin, parseFileManagerPlugin } from '@dxos/app-framework';
7
+ import { Capabilities, useCapabilities } from '@dxos/app-framework';
8
8
  import { fullyQualifiedId, getSpace } from '@dxos/react-client/echo';
9
9
 
10
10
  import { MarkdownEditor, type MarkdownEditorProps } from './MarkdownEditor';
@@ -27,7 +27,6 @@ const MarkdownContainer = ({
27
27
  id,
28
28
  role,
29
29
  object,
30
- extensionProviders,
31
30
  settings,
32
31
  viewMode,
33
32
  editorStateStore,
@@ -35,7 +34,7 @@ const MarkdownContainer = ({
35
34
  }: MarkdownContainerProps) => {
36
35
  const scrollPastEnd = role === 'article';
37
36
  const doc = object instanceof DocumentType ? object : undefined;
38
- const extensions = useExtensions({ extensionProviders, document: doc, settings, viewMode, editorStateStore });
37
+ const extensions = useExtensions({ document: doc, settings, viewMode, editorStateStore });
39
38
 
40
39
  if (doc) {
41
40
  return (
@@ -88,15 +87,15 @@ export const DocumentEditor = ({ id, document: doc, settings, viewMode, ...props
88
87
  }, [doc, doc.content]);
89
88
 
90
89
  // File dragging.
91
- const fileManagerPlugin = useResolvePlugin(parseFileManagerPlugin);
90
+ const [upload] = useCapabilities(Capabilities.FileUploader);
92
91
  const handleFileUpload = useMemo(() => {
93
- if (space === undefined || fileManagerPlugin?.provides.file.upload === undefined) {
92
+ if (space === undefined || upload === undefined) {
94
93
  return undefined;
95
94
  }
96
95
 
97
96
  // TODO(burdon): Re-order props: space, file.
98
- return async (file: File) => fileManagerPlugin?.provides?.file?.upload?.(file, space);
99
- }, [space, fileManagerPlugin]);
97
+ return async (file: File) => upload!(file, space);
98
+ }, [space, upload]);
100
99
 
101
100
  return (
102
101
  <MarkdownEditor