@dxos/plugin-markdown 0.6.8-main.046e6cf

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 (107) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +15 -0
  3. package/dist/lib/browser/DocumentCard-CDWDPILF.mjs +11 -0
  4. package/dist/lib/browser/DocumentCard-CDWDPILF.mjs.map +7 -0
  5. package/dist/lib/browser/DocumentEditor-VMHFHWOQ.mjs +11 -0
  6. package/dist/lib/browser/DocumentEditor-VMHFHWOQ.mjs.map +7 -0
  7. package/dist/lib/browser/MarkdownEditor-KYUQ45PC.mjs +10 -0
  8. package/dist/lib/browser/MarkdownEditor-KYUQ45PC.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-4GGD6YJO.mjs +19 -0
  10. package/dist/lib/browser/chunk-4GGD6YJO.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-6ZL2GJCQ.mjs +177 -0
  12. package/dist/lib/browser/chunk-6ZL2GJCQ.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-DX3K37SM.mjs +86 -0
  14. package/dist/lib/browser/chunk-DX3K37SM.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-MDX3MAMP.mjs +119 -0
  16. package/dist/lib/browser/chunk-MDX3MAMP.mjs.map +7 -0
  17. package/dist/lib/browser/chunk-PZDW7KVZ.mjs +172 -0
  18. package/dist/lib/browser/chunk-PZDW7KVZ.mjs.map +7 -0
  19. package/dist/lib/browser/chunk-RETREORA.mjs +39 -0
  20. package/dist/lib/browser/chunk-RETREORA.mjs.map +7 -0
  21. package/dist/lib/browser/index.mjs +506 -0
  22. package/dist/lib/browser/index.mjs.map +7 -0
  23. package/dist/lib/browser/meta.json +1 -0
  24. package/dist/lib/browser/meta.mjs +9 -0
  25. package/dist/lib/browser/meta.mjs.map +7 -0
  26. package/dist/lib/browser/types/index.mjs +12 -0
  27. package/dist/lib/browser/types/index.mjs.map +7 -0
  28. package/dist/lib/node/DocumentCard-SOTFILJY.cjs +32 -0
  29. package/dist/lib/node/DocumentCard-SOTFILJY.cjs.map +7 -0
  30. package/dist/lib/node/DocumentEditor-CUKHGS5R.cjs +29 -0
  31. package/dist/lib/node/DocumentEditor-CUKHGS5R.cjs.map +7 -0
  32. package/dist/lib/node/MarkdownEditor-GGFCD26C.cjs +31 -0
  33. package/dist/lib/node/MarkdownEditor-GGFCD26C.cjs.map +7 -0
  34. package/dist/lib/node/chunk-FW5O2I25.cjs +114 -0
  35. package/dist/lib/node/chunk-FW5O2I25.cjs.map +7 -0
  36. package/dist/lib/node/chunk-IUQ2SKGY.cjs +202 -0
  37. package/dist/lib/node/chunk-IUQ2SKGY.cjs.map +7 -0
  38. package/dist/lib/node/chunk-TGMR2CKU.cjs +52 -0
  39. package/dist/lib/node/chunk-TGMR2CKU.cjs.map +7 -0
  40. package/dist/lib/node/chunk-TO3FCKT7.cjs +202 -0
  41. package/dist/lib/node/chunk-TO3FCKT7.cjs.map +7 -0
  42. package/dist/lib/node/chunk-TZDYK4MV.cjs +151 -0
  43. package/dist/lib/node/chunk-TZDYK4MV.cjs.map +7 -0
  44. package/dist/lib/node/chunk-VYLYUDDI.cjs +58 -0
  45. package/dist/lib/node/chunk-VYLYUDDI.cjs.map +7 -0
  46. package/dist/lib/node/index.cjs +517 -0
  47. package/dist/lib/node/index.cjs.map +7 -0
  48. package/dist/lib/node/meta.cjs +30 -0
  49. package/dist/lib/node/meta.cjs.map +7 -0
  50. package/dist/lib/node/meta.json +1 -0
  51. package/dist/lib/node/types/index.cjs +34 -0
  52. package/dist/lib/node/types/index.cjs.map +7 -0
  53. package/dist/types/src/MarkdownPlugin.d.ts +4 -0
  54. package/dist/types/src/MarkdownPlugin.d.ts.map +1 -0
  55. package/dist/types/src/components/DocumentCard.d.ts +16 -0
  56. package/dist/types/src/components/DocumentCard.d.ts.map +1 -0
  57. package/dist/types/src/components/DocumentEditor.d.ts +14 -0
  58. package/dist/types/src/components/DocumentEditor.d.ts.map +1 -0
  59. package/dist/types/src/components/HeadingMenu.d.ts +13 -0
  60. package/dist/types/src/components/HeadingMenu.d.ts.map +1 -0
  61. package/dist/types/src/components/Layout.d.ts +6 -0
  62. package/dist/types/src/components/Layout.d.ts.map +1 -0
  63. package/dist/types/src/components/MarkdownEditor.d.ts +19 -0
  64. package/dist/types/src/components/MarkdownEditor.d.ts.map +1 -0
  65. package/dist/types/src/components/MarkdownEditor.stories.d.ts +27 -0
  66. package/dist/types/src/components/MarkdownEditor.stories.d.ts.map +1 -0
  67. package/dist/types/src/components/MarkdownSettings.d.ts +6 -0
  68. package/dist/types/src/components/MarkdownSettings.d.ts.map +1 -0
  69. package/dist/types/src/components/Toolbar.stories.d.ts +9 -0
  70. package/dist/types/src/components/Toolbar.stories.d.ts.map +1 -0
  71. package/dist/types/src/components/index.d.ts +13 -0
  72. package/dist/types/src/components/index.d.ts.map +1 -0
  73. package/dist/types/src/extensions.d.ts +20 -0
  74. package/dist/types/src/extensions.d.ts.map +1 -0
  75. package/dist/types/src/index.d.ts +6 -0
  76. package/dist/types/src/index.d.ts.map +1 -0
  77. package/dist/types/src/meta.d.ts +15 -0
  78. package/dist/types/src/meta.d.ts.map +1 -0
  79. package/dist/types/src/translations.d.ts +29 -0
  80. package/dist/types/src/translations.d.ts.map +1 -0
  81. package/dist/types/src/types/document.d.ts +97 -0
  82. package/dist/types/src/types/document.d.ts.map +1 -0
  83. package/dist/types/src/types/index.d.ts +3 -0
  84. package/dist/types/src/types/index.d.ts.map +1 -0
  85. package/dist/types/src/types/types.d.ts +42 -0
  86. package/dist/types/src/types/types.d.ts.map +1 -0
  87. package/dist/types/src/util.d.ts +11 -0
  88. package/dist/types/src/util.d.ts.map +1 -0
  89. package/package.json +94 -0
  90. package/src/MarkdownPlugin.tsx +341 -0
  91. package/src/components/DocumentCard.tsx +107 -0
  92. package/src/components/DocumentEditor.tsx +140 -0
  93. package/src/components/HeadingMenu.tsx +46 -0
  94. package/src/components/Layout.tsx +27 -0
  95. package/src/components/MarkdownEditor.stories.tsx +55 -0
  96. package/src/components/MarkdownEditor.tsx +241 -0
  97. package/src/components/MarkdownSettings.tsx +105 -0
  98. package/src/components/Toolbar.stories.tsx +118 -0
  99. package/src/components/index.ts +21 -0
  100. package/src/extensions.tsx +175 -0
  101. package/src/index.ts +11 -0
  102. package/src/meta.tsx +19 -0
  103. package/src/translations.ts +36 -0
  104. package/src/types/document.ts +17 -0
  105. package/src/types/index.ts +6 -0
  106. package/src/types/types.ts +75 -0
  107. package/src/util.tsx +48 -0
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@dxos/plugin-markdown",
3
+ "version": "0.6.8-main.046e6cf",
4
+ "description": "DXOS Surface plugin for interacting with Markdown",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "license": "MIT",
8
+ "author": "DXOS.org",
9
+ "exports": {
10
+ ".": {
11
+ "browser": "./dist/lib/browser/index.mjs",
12
+ "node": {
13
+ "default": "./dist/lib/node/index.cjs"
14
+ },
15
+ "types": "./dist/types/src/index.d.ts"
16
+ },
17
+ "./meta": {
18
+ "browser": "./dist/lib/browser/meta.mjs",
19
+ "node": {
20
+ "default": "./dist/lib/node/meta.cjs"
21
+ },
22
+ "types": "./dist/types/src/meta.d.ts"
23
+ },
24
+ "./types": {
25
+ "browser": "./dist/lib/browser/types/index.mjs",
26
+ "node": {
27
+ "default": "./dist/lib/node/types/index.cjs"
28
+ },
29
+ "types": "./dist/types/src/types/index.d.ts"
30
+ }
31
+ },
32
+ "types": "dist/types/src/index.d.ts",
33
+ "typesVersions": {
34
+ "*": {
35
+ "meta": [
36
+ "dist/types/src/meta.d.ts"
37
+ ],
38
+ "types": [
39
+ "dist/types/src/types/index.d.ts"
40
+ ]
41
+ }
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "src"
46
+ ],
47
+ "dependencies": {
48
+ "@codemirror/view": "^6.29.1",
49
+ "@preact/signals-core": "^1.6.0",
50
+ "@dxos/async": "0.6.8-main.046e6cf",
51
+ "@dxos/echo-schema": "0.6.8-main.046e6cf",
52
+ "@dxos/keys": "0.6.8-main.046e6cf",
53
+ "@dxos/invariant": "0.6.8-main.046e6cf",
54
+ "@dxos/local-storage": "0.6.8-main.046e6cf",
55
+ "@dxos/plugin-attention": "0.6.8-main.046e6cf",
56
+ "@dxos/log": "0.6.8-main.046e6cf",
57
+ "@dxos/plugin-client": "0.6.8-main.046e6cf",
58
+ "@dxos/plugin-graph": "0.6.8-main.046e6cf",
59
+ "@dxos/plugin-settings": "0.6.8-main.046e6cf",
60
+ "@dxos/plugin-theme": "0.6.8-main.046e6cf",
61
+ "@dxos/plugin-space": "0.6.8-main.046e6cf",
62
+ "@dxos/react-async": "0.6.8-main.046e6cf",
63
+ "@dxos/react-client": "0.6.8-main.046e6cf",
64
+ "@dxos/react-ui-card": "0.6.8-main.046e6cf",
65
+ "@dxos/app-framework": "0.6.8-main.046e6cf",
66
+ "@dxos/react-ui-deck": "0.6.8-main.046e6cf",
67
+ "@dxos/react-ui-editor": "0.6.8-main.046e6cf",
68
+ "@dxos/react-ui-mosaic": "0.6.8-main.046e6cf",
69
+ "@dxos/util": "0.6.8-main.046e6cf",
70
+ "@dxos/react-ui-stack": "0.6.8-main.046e6cf"
71
+ },
72
+ "devDependencies": {
73
+ "@phosphor-icons/react": "^2.1.5",
74
+ "@types/react": "~18.2.0",
75
+ "@types/react-dom": "~18.2.0",
76
+ "react": "~18.2.0",
77
+ "react-dom": "~18.2.0",
78
+ "vite": "^5.3.4",
79
+ "@dxos/random": "0.6.8-main.046e6cf",
80
+ "@dxos/react-ui": "0.6.8-main.046e6cf",
81
+ "@dxos/react-ui-theme": "0.6.8-main.046e6cf",
82
+ "@dxos/storybook-utils": "0.6.8-main.046e6cf"
83
+ },
84
+ "optionalDependencies": {
85
+ "@phosphor-icons/react": "^2.1.5",
86
+ "react": "^18.0.0",
87
+ "react-dom": "^18.0.0",
88
+ "@dxos/react-ui": "0.6.8-main.046e6cf",
89
+ "@dxos/react-ui-theme": "0.6.8-main.046e6cf"
90
+ },
91
+ "publishConfig": {
92
+ "access": "public"
93
+ }
94
+ }
@@ -0,0 +1,341 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type IconProps, TextAa } from '@phosphor-icons/react';
6
+ import React, { type Ref } from 'react';
7
+
8
+ import {
9
+ isObject,
10
+ parseIntentPlugin,
11
+ resolvePlugin,
12
+ LayoutAction,
13
+ type LayoutCoordinate,
14
+ NavigationAction,
15
+ type PluginDefinition,
16
+ } from '@dxos/app-framework';
17
+ import { create } from '@dxos/echo-schema';
18
+ import { LocalStorageStore } from '@dxos/local-storage';
19
+ import { parseClientPlugin } from '@dxos/plugin-client';
20
+ import { type ActionGroup, createExtension, isActionGroup } from '@dxos/plugin-graph';
21
+ import { SpaceAction } from '@dxos/plugin-space';
22
+ import { CollectionType } from '@dxos/plugin-space/types';
23
+ import { fullyQualifiedId, isSpace, loadObjectReferences } from '@dxos/react-client/echo';
24
+ import {
25
+ type EditorInputMode,
26
+ type EditorViewMode,
27
+ EditorViewModes,
28
+ translations as editorTranslations,
29
+ } from '@dxos/react-ui-editor';
30
+ import { isTileComponentProps } from '@dxos/react-ui-mosaic';
31
+
32
+ import { type DocumentItemProps, DocumentCard, DocumentEditor, MarkdownEditor, MarkdownSettings } from './components';
33
+ import meta, { MARKDOWN_PLUGIN } from './meta';
34
+ import translations from './translations';
35
+ import { DocumentType, TextType } from './types';
36
+ import {
37
+ type MarkdownPluginProvides,
38
+ type MarkdownSettingsProps,
39
+ MarkdownAction,
40
+ type MarkdownPluginState,
41
+ } from './types';
42
+ import { markdownExtensionPlugins, serializer } from './util';
43
+
44
+ /**
45
+ * Checks if an object conforms to the interface needed to render an editor.
46
+ */
47
+ const isEditorModel = (data: any): data is { id: string; text: string } => {
48
+ return (
49
+ data &&
50
+ typeof data === 'object' &&
51
+ 'id' in data &&
52
+ typeof data.id === 'string' &&
53
+ 'text' in data &&
54
+ typeof data.text === 'string'
55
+ );
56
+ };
57
+
58
+ export const MarkdownPlugin = (): PluginDefinition<MarkdownPluginProvides> => {
59
+ const settings = new LocalStorageStore<MarkdownSettingsProps>(MARKDOWN_PLUGIN, {
60
+ defaultViewMode: 'preview',
61
+ toolbar: true,
62
+ experimental: false,
63
+ });
64
+
65
+ const state = new LocalStorageStore<MarkdownPluginState>(MARKDOWN_PLUGIN, { extensionProviders: [], viewMode: {} });
66
+
67
+ const getViewMode = (id?: string) => {
68
+ return (id && state.values.viewMode[id]) || settings.values.defaultViewMode;
69
+ };
70
+
71
+ const setViewMode = (id: string, nextViewMode: EditorViewMode) => {
72
+ state.values.viewMode[id] = nextViewMode;
73
+ };
74
+
75
+ return {
76
+ meta,
77
+ ready: async (plugins) => {
78
+ settings
79
+ .prop({
80
+ key: 'defaultViewMode',
81
+ storageKey: 'default-view-mode',
82
+ type: LocalStorageStore.enum<EditorViewMode>(),
83
+ })
84
+ .prop({
85
+ key: 'editorInputMode',
86
+ storageKey: 'editor-mode',
87
+ type: LocalStorageStore.enum<EditorInputMode>({ allowUndefined: true }),
88
+ })
89
+ .prop({ key: 'toolbar', type: LocalStorageStore.bool({ allowUndefined: true }) })
90
+ .prop({ key: 'experimental', type: LocalStorageStore.bool({ allowUndefined: true }) })
91
+ .prop({ key: 'debug', type: LocalStorageStore.bool({ allowUndefined: true }) })
92
+ .prop({ key: 'typewriter', type: LocalStorageStore.string({ allowUndefined: true }) })
93
+ .prop({ key: 'numberedHeadings', type: LocalStorageStore.bool({ allowUndefined: true }) })
94
+ .prop({ key: 'folding', type: LocalStorageStore.bool({ allowUndefined: true }) });
95
+
96
+ state.prop({
97
+ key: 'viewMode',
98
+ storageKey: 'view-mode',
99
+ type: LocalStorageStore.json<{ [key: string]: EditorViewMode }>(),
100
+ });
101
+
102
+ markdownExtensionPlugins(plugins).forEach((plugin) => {
103
+ const { extensions } = plugin.provides.markdown;
104
+ state.values.extensionProviders.push(extensions);
105
+ });
106
+ },
107
+ provides: {
108
+ settings: settings.values,
109
+ metadata: {
110
+ records: {
111
+ [DocumentType.typename]: {
112
+ label: (object: any) => (object instanceof DocumentType ? object.name ?? object.fallbackName : undefined),
113
+ placeholder: ['document title placeholder', { ns: MARKDOWN_PLUGIN }],
114
+ icon: (props: IconProps) => <TextAa {...props} />,
115
+ iconSymbol: 'ph--text-aa--regular',
116
+ graphProps: {
117
+ managesAutofocus: true,
118
+ },
119
+ // TODO(wittjosiah): Move out of metadata.
120
+ loadReferences: (doc: DocumentType) => loadObjectReferences(doc, (doc) => [doc.content, ...doc.threads]),
121
+ serializer,
122
+ },
123
+ },
124
+ },
125
+ translations: [...translations, ...editorTranslations],
126
+ echo: {
127
+ schema: [DocumentType, TextType],
128
+ },
129
+ graph: {
130
+ builder: (plugins) => {
131
+ const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
132
+ const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatch;
133
+ if (!client || !dispatch) {
134
+ return [];
135
+ }
136
+
137
+ return createExtension({
138
+ id: MarkdownAction.CREATE,
139
+ filter: (node): node is ActionGroup => isActionGroup(node) && node.id.startsWith(SpaceAction.ADD_OBJECT),
140
+ actions: ({ node }) => {
141
+ const id = node.id.split('/').at(-1);
142
+ const [spaceId, objectId] = id?.split(':') ?? [];
143
+ const space = client.spaces.get().find((space) => space.id === spaceId);
144
+ const object = objectId && space?.db.getObjectById(objectId);
145
+ const target = objectId ? object : space;
146
+ if (!target) {
147
+ return;
148
+ }
149
+
150
+ return [
151
+ {
152
+ id: `${MARKDOWN_PLUGIN}/create/${node.id}`,
153
+ data: async () => {
154
+ await dispatch([
155
+ { plugin: MARKDOWN_PLUGIN, action: MarkdownAction.CREATE },
156
+ { action: SpaceAction.ADD_OBJECT, data: { target } },
157
+ { action: NavigationAction.OPEN },
158
+ ]);
159
+ },
160
+ properties: {
161
+ label: ['create document label', { ns: MARKDOWN_PLUGIN }],
162
+ icon: (props: IconProps) => <TextAa {...props} />,
163
+ iconSymbol: 'ph--text-aa--regular',
164
+ testId: 'markdownPlugin.createObject',
165
+ },
166
+ },
167
+ ];
168
+ },
169
+ });
170
+ },
171
+ serializer: (plugins) => {
172
+ const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatch;
173
+ if (!dispatch) {
174
+ return [];
175
+ }
176
+ return [
177
+ {
178
+ inputType: DocumentType.typename,
179
+ outputType: 'text/markdown',
180
+ // Reconcile with metadata serializers.
181
+ serialize: async (node) => {
182
+ const doc = node.data;
183
+ const content = await loadObjectReferences(doc, (doc) => doc.content);
184
+ return {
185
+ name:
186
+ doc.name ||
187
+ doc.fallbackName ||
188
+ translations[0]['en-US'][MARKDOWN_PLUGIN]['document title placeholder'],
189
+ data: content.content,
190
+ type: 'text/markdown',
191
+ };
192
+ },
193
+ deserialize: async (data, ancestors) => {
194
+ const space = ancestors.find(isSpace);
195
+ const target =
196
+ ancestors.findLast((ancestor) => ancestor instanceof CollectionType) ??
197
+ space?.properties[CollectionType.typename];
198
+ if (!space || !target) {
199
+ return;
200
+ }
201
+
202
+ const result = await dispatch([
203
+ {
204
+ plugin: MARKDOWN_PLUGIN,
205
+ action: MarkdownAction.CREATE,
206
+ data: { name: data.name, content: data.data },
207
+ },
208
+ {
209
+ action: SpaceAction.ADD_OBJECT,
210
+ data: { target },
211
+ },
212
+ ]);
213
+
214
+ return result?.data.object;
215
+ },
216
+ },
217
+ ];
218
+ },
219
+ },
220
+ stack: {
221
+ creators: [
222
+ {
223
+ id: 'create-stack-section-doc',
224
+ testId: 'markdownPlugin.createSection',
225
+ type: ['plugin name', { ns: MARKDOWN_PLUGIN }],
226
+ label: ['create stack section label', { ns: MARKDOWN_PLUGIN }],
227
+ icon: (props: any) => <TextAa {...props} />,
228
+ intent: {
229
+ plugin: MARKDOWN_PLUGIN,
230
+ action: MarkdownAction.CREATE,
231
+ },
232
+ },
233
+ ],
234
+ },
235
+ surface: {
236
+ component: ({ data, role, ...props }, forwardedRef) => {
237
+ const doc =
238
+ data.active instanceof DocumentType
239
+ ? data.active
240
+ : data.object instanceof DocumentType
241
+ ? data.object
242
+ : undefined;
243
+
244
+ switch (role) {
245
+ case 'section':
246
+ case 'article': {
247
+ if (doc && doc.content) {
248
+ return (
249
+ <DocumentEditor
250
+ role={role}
251
+ coordinate={data.coordinate as LayoutCoordinate}
252
+ document={doc}
253
+ extensionProviders={state.values.extensionProviders}
254
+ settings={settings.values}
255
+ viewMode={getViewMode(fullyQualifiedId(doc))}
256
+ onViewModeChange={setViewMode}
257
+ scrollPastEnd
258
+ />
259
+ );
260
+ } else if (isEditorModel(data.object)) {
261
+ return (
262
+ <MarkdownEditor
263
+ id={data.object.id}
264
+ role={role}
265
+ coordinate={data.coordinate as LayoutCoordinate}
266
+ initialValue={data.object.text}
267
+ extensionProviders={state.values.extensionProviders}
268
+ inputMode={settings.values.editorInputMode}
269
+ toolbar={settings.values.toolbar}
270
+ viewMode={getViewMode(data.object.id)}
271
+ onViewModeChange={setViewMode}
272
+ scrollPastEnd
273
+ />
274
+ );
275
+ }
276
+ break;
277
+ }
278
+
279
+ case 'card': {
280
+ if (
281
+ isObject(data.content) &&
282
+ typeof data.content.id === 'string' &&
283
+ data.content.object instanceof DocumentType
284
+ ) {
285
+ // isTileComponentProps is a type guard for these props.
286
+ // `props` will not pass this guard without transforming `data` into `item`.
287
+ const cardProps = {
288
+ ...props,
289
+ item: {
290
+ id: data.content.id,
291
+ object: data.content.object,
292
+ color: typeof data.content.color === 'string' ? data.content.color : undefined,
293
+ } as DocumentItemProps,
294
+ };
295
+
296
+ return isTileComponentProps(cardProps) ? (
297
+ <DocumentCard {...cardProps} settings={settings.values} ref={forwardedRef as Ref<HTMLDivElement>} />
298
+ ) : null;
299
+ }
300
+ break;
301
+ }
302
+
303
+ case 'settings': {
304
+ return data.plugin === meta.id ? <MarkdownSettings settings={settings.values} /> : null;
305
+ }
306
+ }
307
+
308
+ return null;
309
+ },
310
+ },
311
+ intent: {
312
+ resolver: ({ action, data }) => {
313
+ switch (action) {
314
+ case MarkdownAction.CREATE: {
315
+ const doc = create(DocumentType, {
316
+ name: data?.name,
317
+ content: create(TextType, { content: data?.content ?? '' }),
318
+ threads: [],
319
+ });
320
+
321
+ return {
322
+ data: doc,
323
+ intents: [[{ action: LayoutAction.SCROLL_INTO_VIEW, data: { id: doc.id } }]],
324
+ };
325
+ }
326
+
327
+ case MarkdownAction.SET_VIEW_MODE: {
328
+ const { id, viewMode } = data ?? {};
329
+ if (typeof id === 'string' && EditorViewModes.includes(viewMode)) {
330
+ state.values.viewMode[id] = viewMode;
331
+ return { data: true };
332
+ }
333
+
334
+ break;
335
+ }
336
+ }
337
+ },
338
+ },
339
+ },
340
+ };
341
+ };
@@ -0,0 +1,107 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React, { forwardRef } from 'react';
6
+
7
+ import { createDocAccessor } from '@dxos/react-client/echo';
8
+ import { DropdownMenu, Input, useThemeContext, useTranslation } from '@dxos/react-ui';
9
+ import { Card } from '@dxos/react-ui-card';
10
+ import {
11
+ createBasicExtensions,
12
+ createDataExtensions,
13
+ createThemeExtensions,
14
+ useTextEditor,
15
+ } from '@dxos/react-ui-editor';
16
+ import type { MosaicTileComponent } from '@dxos/react-ui-mosaic';
17
+ import { focusRing, mx } from '@dxos/react-ui-theme';
18
+
19
+ import { createBaseExtensions } from '../extensions';
20
+ import { MARKDOWN_PLUGIN } from '../meta';
21
+ import { type DocumentType, type MarkdownSettingsProps } from '../types';
22
+
23
+ export type DocumentItemProps = {
24
+ id: string;
25
+ object: DocumentType;
26
+ color?: string;
27
+ };
28
+
29
+ export type DocumentCardProps = {
30
+ settings: MarkdownSettingsProps;
31
+ };
32
+
33
+ /**
34
+ * @deprecated
35
+ */
36
+ // TODO(wittjosiah): Unify with DocumentEditor.
37
+ export const DocumentCard: MosaicTileComponent<DocumentItemProps, HTMLDivElement, DocumentCardProps> = forwardRef(
38
+ (
39
+ {
40
+ classNames,
41
+ isDragging,
42
+ draggableStyle,
43
+ draggableProps,
44
+ item: { id, object, color },
45
+ grow,
46
+ settings,
47
+ onSelect,
48
+ onAction,
49
+ },
50
+ forwardRef,
51
+ ) => {
52
+ const { t } = useTranslation(MARKDOWN_PLUGIN);
53
+ const { themeMode } = useThemeContext();
54
+ const { parentRef, focusAttributes } = useTextEditor(
55
+ () => ({
56
+ initialValue: object.content?.content,
57
+ extensions: [
58
+ createBasicExtensions({ placeholder: t('editor placeholder') }),
59
+ createThemeExtensions({ themeMode }),
60
+ createDataExtensions({
61
+ id: object.id,
62
+ text: object.content && createDocAccessor(object.content, ['content']),
63
+ }),
64
+ createBaseExtensions({
65
+ document: object,
66
+ debug: settings.debug,
67
+ experimental: settings.experimental,
68
+ }),
69
+ ],
70
+ }),
71
+ [object, object.content, themeMode],
72
+ );
73
+
74
+ return (
75
+ <div role='none' ref={forwardRef} className='flex w-full' style={draggableStyle}>
76
+ <Card.Root classNames={mx('w-full snap-center', color, isDragging && 'opacity-20', classNames)} grow={grow}>
77
+ <Card.Header onDoubleClick={() => onSelect?.()}>
78
+ <Card.DragHandle {...draggableProps} />
79
+ <Input.Root>
80
+ <Input.TextInput
81
+ variant='subdued'
82
+ classNames='p-0'
83
+ placeholder={t('document title placeholder')}
84
+ value={object.name}
85
+ onChange={(event) => (object.name = event.target.value)}
86
+ />
87
+ </Input.Root>
88
+ <Card.Menu>
89
+ {/* TODO(burdon): Handle events/intents? */}
90
+ <DropdownMenu.Item onClick={() => onAction?.({ id, action: 'delete' })}>
91
+ <span className='grow'>Delete</span>
92
+ </DropdownMenu.Item>
93
+ <DropdownMenu.Item onClick={() => onAction?.({ id, action: 'set-color' })}>
94
+ <span className='grow'>Change color</span>
95
+ </DropdownMenu.Item>
96
+ </Card.Menu>
97
+ </Card.Header>
98
+ <Card.Body>
99
+ <div {...focusAttributes} ref={parentRef} className={mx(focusRing, 'rounded-sm h-full p-1 text-sm')} />
100
+ </Card.Body>
101
+ </Card.Root>
102
+ </div>
103
+ );
104
+ },
105
+ );
106
+
107
+ export default DocumentCard;
@@ -0,0 +1,140 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { EditorView } from '@codemirror/view';
6
+ import React, { useEffect, useMemo } from 'react';
7
+
8
+ import { useResolvePlugin, parseFileManagerPlugin, useIntentDispatcher } from '@dxos/app-framework';
9
+ import { createDocAccessor, fullyQualifiedId, getSpace } from '@dxos/react-client/echo';
10
+ import { useIdentity } from '@dxos/react-client/halo';
11
+ import {
12
+ createDataExtensions,
13
+ listener,
14
+ localStorageStateStoreAdapter,
15
+ state,
16
+ type Extension,
17
+ } from '@dxos/react-ui-editor';
18
+
19
+ import MarkdownEditor, { type MarkdownEditorProps } from './MarkdownEditor';
20
+ import { createBaseExtensions } from '../extensions';
21
+ import { type DocumentType, type MarkdownPluginState, type MarkdownSettingsProps } from '../types';
22
+ import { getFallbackName, setFallbackName } from '../util';
23
+
24
+ type DocumentEditorProps = {
25
+ document: DocumentType;
26
+ settings: MarkdownSettingsProps;
27
+ } & Omit<MarkdownEditorProps, 'id' | 'inputMode' | 'toolbar' | 'extensions'> &
28
+ Pick<MarkdownPluginState, 'extensionProviders'>;
29
+
30
+ /**
31
+ * Editor for a `DocumentType`.
32
+ */
33
+ const DocumentEditor = ({
34
+ document: doc,
35
+ extensionProviders = [],
36
+ viewMode,
37
+ settings,
38
+ ...props
39
+ }: DocumentEditorProps) => {
40
+ const space = getSpace(doc);
41
+ const identity = useIdentity();
42
+ const dispatch = useIntentDispatcher();
43
+
44
+ const baseExtensions = useMemo(() => {
45
+ // TODO(wittjosiah): Autocomplete is not working and this query is causing performance issues.
46
+ // const query = space?.db.query(Filter.schema(DocumentType));
47
+ // query?.subscribe();
48
+ return createBaseExtensions({
49
+ viewMode,
50
+ settings,
51
+ document: doc,
52
+ dispatch,
53
+ // query,
54
+ });
55
+ }, [doc, viewMode, dispatch, settings, settings.folding, settings.numberedHeadings]);
56
+
57
+ const providerExtensions = useMemo(
58
+ () =>
59
+ extensionProviders.reduce((acc: Extension[], provider) => {
60
+ const provided = typeof provider === 'function' ? provider({ document: doc }) : provider;
61
+ acc.push(...provided);
62
+ return acc;
63
+ }, []),
64
+ [extensionProviders],
65
+ );
66
+
67
+ const extensions = useMemo(
68
+ () => [
69
+ // NOTE: Data extensions must be first so that automerge is updated before other extensions compute their state.
70
+ createDataExtensions({
71
+ id: doc.id,
72
+ text: doc.content && createDocAccessor(doc.content, ['content']),
73
+ space,
74
+ identity,
75
+ }),
76
+ state(localStorageStateStoreAdapter),
77
+ listener({
78
+ onChange: (text) => {
79
+ setFallbackName(doc, text);
80
+ },
81
+ }),
82
+ providerExtensions,
83
+ baseExtensions,
84
+ ],
85
+ [doc, doc.content, space, baseExtensions, providerExtensions, identity],
86
+ );
87
+
88
+ const initialValue = useMemo(() => doc.content?.content, [doc.content]);
89
+
90
+ // Migrate gradually to `fallbackName`.
91
+ useEffect(() => {
92
+ if (!doc.fallbackName && doc.content?.content) {
93
+ doc.fallbackName = getFallbackName(doc.content.content);
94
+ }
95
+ }, [doc, doc.content]);
96
+
97
+ const { scrollTo /*, selection */ } = useMemo(() => {
98
+ const { scrollTo, selection } = localStorageStateStoreAdapter.getState(doc.id) ?? {};
99
+ return {
100
+ scrollTo: scrollTo?.from ? EditorView.scrollIntoView(scrollTo.from, { y: 'start', yMargin: 0 }) : undefined,
101
+ selection,
102
+ };
103
+ }, [doc]);
104
+
105
+ const fileManagerPlugin = useResolvePlugin(parseFileManagerPlugin);
106
+
107
+ const handleFileUpload = useMemo(() => {
108
+ if (space === undefined) {
109
+ return undefined;
110
+ }
111
+
112
+ if (fileManagerPlugin?.provides.file.upload === undefined) {
113
+ return undefined;
114
+ }
115
+
116
+ return async (file: File) => {
117
+ return fileManagerPlugin?.provides?.file?.upload?.(file, space);
118
+ };
119
+ }, [fileManagerPlugin, space]);
120
+
121
+ return (
122
+ <MarkdownEditor
123
+ id={fullyQualifiedId(doc)}
124
+ initialValue={initialValue}
125
+ extensions={extensions}
126
+ scrollTo={scrollTo}
127
+ // TODO(wittjosiah): Ensure selection is within the document.
128
+ // selection={selection}
129
+ onFileUpload={handleFileUpload}
130
+ inputMode={settings.editorInputMode}
131
+ toolbar={settings.toolbar}
132
+ viewMode={viewMode}
133
+ {...props}
134
+ />
135
+ );
136
+ };
137
+
138
+ export default DocumentEditor;
139
+
140
+ export type DocumentEditor = typeof DocumentEditor;