@dxos/plugin-markdown 0.7.4-staging.f7e8224 → 0.7.5-feature-compute.4d9d99a

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 (175) hide show
  1. package/dist/lib/browser/{MarkdownContainer-6OPC5MKP.mjs → MarkdownContainer-ARRY4I6S.mjs} +175 -126
  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-TZN5FGB2.mjs → chunk-ADAYSA5G.mjs} +24 -13
  6. package/dist/lib/browser/chunk-ADAYSA5G.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-EMIIXXVX.mjs +66 -0
  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 +99 -452
  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-6OKJOHTZ.cjs → MarkdownContainer-TV6W64EN.cjs} +167 -120
  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-34WFGSZU.cjs +86 -0
  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-KEPAM4JP.cjs → chunk-UFFFUC6W.cjs} +39 -14
  42. package/dist/lib/node/chunk-UFFFUC6W.cjs.map +7 -0
  43. package/dist/lib/node/index.cjs +90 -450
  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-GBNSGROQ.mjs → MarkdownContainer-3ZHQTTMQ.mjs} +175 -126
  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-CDFNMFGT.mjs +67 -0
  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-NUMUUCYF.mjs → chunk-Y5RSQVGH.mjs} +24 -13
  71. package/dist/lib/node-esm/chunk-Y5RSQVGH.mjs.map +7 -0
  72. package/dist/lib/node-esm/index.mjs +99 -452
  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 +4 -4
  114. package/dist/types/src/extensions.d.ts.map +1 -1
  115. package/dist/types/src/hooks/useSelectCurrentThread.d.ts.map +1 -1
  116. package/dist/types/src/index.d.ts +2 -3
  117. package/dist/types/src/index.d.ts.map +1 -1
  118. package/dist/types/src/meta.d.ts +1 -2
  119. package/dist/types/src/meta.d.ts.map +1 -1
  120. package/dist/types/src/translations.d.ts +3 -0
  121. package/dist/types/src/translations.d.ts.map +1 -1
  122. package/dist/types/src/types/index.d.ts +1 -1
  123. package/dist/types/src/types/index.d.ts.map +1 -1
  124. package/dist/types/src/types/schema.d.ts +75 -0
  125. package/dist/types/src/types/schema.d.ts.map +1 -0
  126. package/dist/types/src/types/types.d.ts +40 -35
  127. package/dist/types/src/types/types.d.ts.map +1 -1
  128. package/dist/types/src/util.d.ts +1 -5
  129. package/dist/types/src/util.d.ts.map +1 -1
  130. package/dist/types/tsconfig.tsbuildinfo +1 -0
  131. package/package.json +33 -37
  132. package/src/MarkdownPlugin.tsx +84 -283
  133. package/src/capabilities/app-graph-serializer.ts +50 -0
  134. package/src/capabilities/capabilities.ts +20 -0
  135. package/src/capabilities/index.ts +14 -0
  136. package/src/capabilities/intent-resolver.ts +27 -0
  137. package/src/capabilities/react-surface.tsx +73 -0
  138. package/src/capabilities/settings.ts +25 -0
  139. package/src/capabilities/state.ts +31 -0
  140. package/src/capabilities/thread.ts +34 -0
  141. package/src/components/MarkdownContainer.tsx +14 -10
  142. package/src/components/MarkdownEditor.stories.tsx +16 -5
  143. package/src/components/MarkdownEditor.tsx +87 -61
  144. package/src/components/Toolbar.stories.tsx +16 -22
  145. package/src/extensions.tsx +21 -19
  146. package/src/hooks/useSelectCurrentThread.tsx +17 -14
  147. package/src/index.ts +2 -5
  148. package/src/meta.ts +1 -1
  149. package/src/translations.ts +1 -0
  150. package/src/types/index.ts +1 -1
  151. package/src/types/{document.ts → schema.ts} +4 -7
  152. package/src/types/types.ts +36 -58
  153. package/src/util.tsx +5 -11
  154. package/dist/lib/browser/MarkdownContainer-6OPC5MKP.mjs.map +0 -7
  155. package/dist/lib/browser/chunk-4X6YX3KU.mjs.map +0 -7
  156. package/dist/lib/browser/chunk-CMSUKMPM.mjs +0 -41
  157. package/dist/lib/browser/chunk-CMSUKMPM.mjs.map +0 -7
  158. package/dist/lib/browser/chunk-TZN5FGB2.mjs.map +0 -7
  159. package/dist/lib/browser/meta.mjs +0 -9
  160. package/dist/lib/browser/meta.mjs.map +0 -7
  161. package/dist/lib/node/MarkdownContainer-6OKJOHTZ.cjs.map +0 -7
  162. package/dist/lib/node/chunk-KEPAM4JP.cjs.map +0 -7
  163. package/dist/lib/node/chunk-PHHIPRJC.cjs.map +0 -7
  164. package/dist/lib/node/chunk-YGMWZIIJ.cjs +0 -61
  165. package/dist/lib/node/chunk-YGMWZIIJ.cjs.map +0 -7
  166. package/dist/lib/node/meta.cjs.map +0 -7
  167. package/dist/lib/node-esm/MarkdownContainer-GBNSGROQ.mjs.map +0 -7
  168. package/dist/lib/node-esm/chunk-BABK7FMW.mjs.map +0 -7
  169. package/dist/lib/node-esm/chunk-ERJ52KN2.mjs +0 -42
  170. package/dist/lib/node-esm/chunk-ERJ52KN2.mjs.map +0 -7
  171. package/dist/lib/node-esm/chunk-NUMUUCYF.mjs.map +0 -7
  172. package/dist/lib/node-esm/meta.mjs +0 -10
  173. package/dist/lib/node-esm/meta.mjs.map +0 -7
  174. package/dist/types/src/types/document.d.ts +0 -106
  175. package/dist/types/src/types/document.d.ts.map +0 -1
@@ -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 (
@@ -77,26 +76,31 @@ export const DocumentEditor = ({ id, document: doc, settings, viewMode, ...props
77
76
 
78
77
  // Migrate gradually to `fallbackName`.
79
78
  useEffect(() => {
80
- if (!doc.fallbackName && doc.content?.content) {
81
- doc.fallbackName = getFallbackName(doc.content.content);
79
+ if (typeof doc.fallbackName === 'string') {
80
+ return;
81
+ }
82
+
83
+ const fallbackName = doc.content?.target?.content ? getFallbackName(doc.content.target.content) : undefined;
84
+ if (fallbackName) {
85
+ doc.fallbackName = fallbackName;
82
86
  }
83
87
  }, [doc, doc.content]);
84
88
 
85
89
  // File dragging.
86
- const fileManagerPlugin = useResolvePlugin(parseFileManagerPlugin);
90
+ const [upload] = useCapabilities(Capabilities.FileUploader);
87
91
  const handleFileUpload = useMemo(() => {
88
- if (space === undefined || fileManagerPlugin?.provides.file.upload === undefined) {
92
+ if (space === undefined || upload === undefined) {
89
93
  return undefined;
90
94
  }
91
95
 
92
96
  // TODO(burdon): Re-order props: space, file.
93
- return async (file: File) => fileManagerPlugin?.provides?.file?.upload?.(file, space);
94
- }, [space, fileManagerPlugin]);
97
+ return async (file: File) => upload!(file, space);
98
+ }, [space, upload]);
95
99
 
96
100
  return (
97
101
  <MarkdownEditor
98
102
  id={id}
99
- initialValue={doc.content?.content}
103
+ initialValue={doc.content?.target?.content}
100
104
  viewMode={viewMode}
101
105
  toolbar={settings.toolbar}
102
106
  inputMode={settings.editorInputMode}
@@ -7,13 +7,18 @@ import '@dxos-theme';
7
7
  import { type Meta } from '@storybook/react';
8
8
  import React, { useMemo } from 'react';
9
9
 
10
+ import { IntentPlugin } from '@dxos/app-framework';
11
+ import { withPluginManager } from '@dxos/app-framework/testing';
10
12
  import { createDocAccessor, createObject } from '@dxos/react-client/echo';
11
13
  import { Main } from '@dxos/react-ui';
12
- import { editorWithToolbarLayout, automerge } from '@dxos/react-ui-editor';
14
+ import { AttendableContainer } from '@dxos/react-ui-attention';
15
+ import { withAttention } from '@dxos/react-ui-attention/testing';
16
+ import { editorWithToolbarLayout, automerge, translations as editorTranslations } from '@dxos/react-ui-editor';
13
17
  import { topbarBlockPaddingStart } from '@dxos/react-ui-theme';
14
18
  import { withLayout, withTheme } from '@dxos/storybook-utils';
15
19
 
16
20
  import { MarkdownEditor, type MarkdownEditorProps } from './MarkdownEditor';
21
+ import translations from '../translations';
17
22
 
18
23
  const content = Array.from({ length: 100 })
19
24
  .map((_, i) => `Line ${i + 1}`)
@@ -27,14 +32,15 @@ type StoryProps = MarkdownEditorProps & {
27
32
  const DefaultStory = ({ content = '# Test', toolbar }: StoryProps) => {
28
33
  const doc = useMemo(() => createObject({ content }), [content]);
29
34
  const extensions = useMemo(() => [automerge(createDocAccessor(doc, ['content']))], [doc]);
30
-
31
35
  return (
32
36
  <Main.Content
33
37
  bounce
34
38
  data-toolbar={toolbar ? 'enabled' : 'disabled'}
35
39
  classNames={[topbarBlockPaddingStart, editorWithToolbarLayout]}
36
40
  >
37
- <MarkdownEditor id='test' initialValue={doc.content} extensions={extensions} toolbar={toolbar} />
41
+ <AttendableContainer id='test'>
42
+ <MarkdownEditor id='test' initialValue={doc.content} extensions={extensions} toolbar={toolbar} />
43
+ </AttendableContainer>
38
44
  </Main.Content>
39
45
  );
40
46
  };
@@ -56,8 +62,13 @@ const meta: Meta<typeof MarkdownEditor> = {
56
62
  title: 'plugins/plugin-markdown/EditorMain',
57
63
  component: MarkdownEditor,
58
64
  render: DefaultStory,
59
- decorators: [withTheme, withLayout({ tooltips: true })],
60
- parameters: { layout: 'fullscreen' },
65
+ decorators: [
66
+ withTheme,
67
+ withLayout({ tooltips: true }),
68
+ withAttention,
69
+ withPluginManager({ plugins: [IntentPlugin()] }),
70
+ ],
71
+ parameters: { layout: 'fullscreen', translations: [...translations, ...editorTranslations] },
61
72
  };
62
73
 
63
74
  export default meta;
@@ -5,18 +5,18 @@
5
5
  import { openSearchPanel } from '@codemirror/search';
6
6
  import { type EditorView } from '@codemirror/view';
7
7
  import React, { useMemo, useEffect, useCallback } from 'react';
8
+ import { useDropzone } from 'react-dropzone';
8
9
 
9
- import { type FileInfo, LayoutAction, NavigationAction, useIntentDispatcher } from '@dxos/app-framework';
10
+ import { createIntent, type FileInfo, LayoutAction, NavigationAction, useIntentDispatcher } from '@dxos/app-framework';
10
11
  import { useThemeContext, useTranslation } from '@dxos/react-ui';
11
- import { useAttention } from '@dxos/react-ui-attention';
12
12
  import {
13
- type Action,
13
+ type EditorAction,
14
14
  type DNDOptions,
15
15
  type EditorViewMode,
16
16
  type EditorInputMode,
17
17
  type EditorSelectionState,
18
18
  type EditorStateStore,
19
- Toolbar,
19
+ EditorToolbar,
20
20
  type UseTextEditorProps,
21
21
  createBasicExtensions,
22
22
  createMarkdownExtensions,
@@ -24,23 +24,23 @@ import {
24
24
  dropFile,
25
25
  editorContent,
26
26
  editorGutter,
27
- processAction,
27
+ processEditorPayload,
28
28
  useActionHandler,
29
29
  useCommentState,
30
30
  useCommentClickListener,
31
31
  useFormattingState,
32
32
  useTextEditor,
33
+ stackItemContentEditorClassNames,
34
+ useEditorToolbarState,
35
+ createEditorAction,
33
36
  } from '@dxos/react-ui-editor';
34
37
  import { StackItem } from '@dxos/react-ui-stack';
35
- import { mx, textBlockWidth } from '@dxos/react-ui-theme';
36
38
  import { isNotFalsy, nonNullable } from '@dxos/util';
37
39
 
38
40
  import { useSelectCurrentThread } from '../hooks';
39
41
  import { MARKDOWN_PLUGIN } from '../meta';
40
42
  import { type MarkdownPluginState } from '../types';
41
43
 
42
- const DEFAULT_VIEW_MODE: EditorViewMode = 'preview';
43
-
44
44
  export type MarkdownEditorProps = {
45
45
  id: string;
46
46
  role?: string;
@@ -75,9 +75,9 @@ export const MarkdownEditor = ({
75
75
  }: MarkdownEditorProps) => {
76
76
  const { t } = useTranslation(MARKDOWN_PLUGIN);
77
77
  const { themeMode } = useThemeContext();
78
- const dispatch = useIntentDispatcher();
79
- const [formattingState, formattingObserver] = useFormattingState();
80
- const { hasAttention } = useAttention(id);
78
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
79
+ const toolbarState = useEditorToolbarState({ viewMode });
80
+ const formattingObserver = useFormattingState(toolbarState);
81
81
 
82
82
  // Restore last selection and scroll point.
83
83
  const { scrollTo, selection } = useMemo<EditorSelectionState>(() => editorStateStore?.getState(id) ?? {}, [id]);
@@ -89,28 +89,21 @@ export const MarkdownEditor = ({
89
89
  [extensionProviders],
90
90
  );
91
91
 
92
- // TODO(Zan): Move these into thread plugin as well?
93
- const [commentsState, commentObserver] = useCommentState();
94
- const onCommentClick = useCallback(() => {
95
- void dispatch([
96
- {
97
- action: NavigationAction.OPEN,
98
- data: { activeParts: { complementary: 'comments' } },
99
- },
100
- {
101
- action: LayoutAction.SET_LAYOUT,
102
- data: { element: 'complementary', state: true },
103
- },
104
- ]);
92
+ // TODO(Zan): Factor out to thread plugin.
93
+ const commentObserver = useCommentState(toolbarState);
94
+ const onCommentClick = useCallback(async () => {
95
+ await dispatch(createIntent(NavigationAction.Open, { activeParts: { complementary: 'comments' } }));
96
+ await dispatch(createIntent(LayoutAction.SetLayout, { element: 'complementary', state: true }));
105
97
  }, [dispatch]);
106
98
  const commentClickObserver = useCommentClickListener(onCommentClick);
107
99
 
100
+ // TODO(wittjosiah): Factor out to file uploader plugin.
108
101
  // Drag files.
109
102
  const handleDrop: DNDOptions['onDrop'] = async (view, { files }) => {
110
103
  const file = files[0];
111
104
  const info = file && onFileUpload ? await onFileUpload(file) : undefined;
112
105
  if (info) {
113
- processAction(view, { type: 'image', data: info.url });
106
+ processEditorPayload(view, { type: 'image', data: info.url });
114
107
  }
115
108
  };
116
109
 
@@ -156,57 +149,79 @@ export const MarkdownEditor = ({
156
149
  useTest(editorView);
157
150
  useSelectCurrentThread(editorView, id);
158
151
 
152
+ // https://react-dropzone.js.org/#src
153
+ const { acceptedFiles, getInputProps, open } = useDropzone({
154
+ multiple: false,
155
+ noDrag: true,
156
+ accept: {
157
+ 'image/*': ['.jpg', '.jpeg', '.png', '.gif'],
158
+ },
159
+ });
160
+
161
+ useEffect(() => {
162
+ if (editorView && onFileUpload && acceptedFiles.length) {
163
+ requestAnimationFrame(async () => {
164
+ // NOTE: Clone file since react-dropzone patches in a non-standard `path` property, which confuses IPFS.
165
+ const f = acceptedFiles[0];
166
+ const file = new File([f], f.name, {
167
+ type: f.type,
168
+ lastModified: f.lastModified,
169
+ });
170
+
171
+ const info = await onFileUpload(file);
172
+ if (info) {
173
+ processEditorPayload(editorView, { type: 'image', data: info.url });
174
+ }
175
+ });
176
+ }
177
+ }, [acceptedFiles, editorView]);
178
+
159
179
  // Toolbar handler.
160
180
  const handleToolbarAction = useActionHandler(editorView);
161
- const handleAction = (action: Action) => {
162
- switch (action.type) {
163
- case 'search': {
164
- if (editorView) {
165
- openSearchPanel(editorView);
181
+ const handleAction = useCallback(
182
+ (action: EditorAction) => {
183
+ switch (action.properties.type) {
184
+ case 'search': {
185
+ if (editorView) {
186
+ openSearchPanel(editorView);
187
+ }
188
+ return;
189
+ }
190
+ case 'view-mode': {
191
+ onViewModeChange?.(id, action.properties.data);
192
+ return;
193
+ }
194
+ case 'image': {
195
+ open();
196
+ return;
166
197
  }
167
- return;
168
- }
169
- case 'view-mode': {
170
- onViewModeChange?.(id, action.data);
171
- return;
172
198
  }
173
- }
174
199
 
175
- handleToolbarAction?.(action);
176
- };
200
+ handleToolbarAction?.(action);
201
+ },
202
+ [editorView, onViewModeChange, open],
203
+ );
177
204
 
178
205
  return (
179
- <StackItem.Content toolbar={toolbar}>
206
+ <StackItem.Content toolbar={!!toolbar}>
180
207
  {toolbar && (
181
- <div
182
- role='none'
183
- className={mx(
184
- 'attention-surface is-full border-be !border-separator',
185
- role === 'section' && 'sticky block-start-0 z-[1] -mbe-px min-is-0',
186
- )}
187
- >
188
- <Toolbar.Root
189
- classNames={[textBlockWidth, !hasAttention && 'opacity-20']}
190
- state={formattingState && { ...formattingState, ...commentsState }}
208
+ <>
209
+ <EditorToolbar
210
+ attendableId={id}
211
+ role={role}
212
+ state={toolbarState}
213
+ customActions={onFileUpload ? createUploadAction : undefined}
191
214
  onAction={handleAction}
192
- >
193
- <Toolbar.Markdown />
194
- {onFileUpload && <Toolbar.Custom onUpload={onFileUpload} />}
195
- <Toolbar.Separator />
196
- <Toolbar.View mode={viewMode ?? DEFAULT_VIEW_MODE} />
197
- <Toolbar.Actions />
198
- </Toolbar.Root>
199
- </div>
215
+ />
216
+ <input {...getInputProps()} />
217
+ </>
200
218
  )}
201
219
  <div
202
220
  role='none'
203
221
  ref={parentRef}
204
222
  data-testid='composer.markdownRoot'
205
223
  data-toolbar={toolbar ? 'enabled' : 'disabled'}
206
- className={mx(
207
- 'ch-focus-ring-inset data-[toolbar=disabled]:pbs-2 attention-surface',
208
- role === 'article' ? 'min-bs-0' : '[&_.cm-scroller]:overflow-hidden [&_.cm-scroller]:min-bs-24',
209
- )}
224
+ className={stackItemContentEditorClassNames(role)}
210
225
  {...focusAttributes}
211
226
  />
212
227
  </StackItem.Content>
@@ -223,3 +238,14 @@ const useTest = (view?: EditorView) => {
223
238
  }
224
239
  }, [view]);
225
240
  };
241
+
242
+ export const createUploadAction = () => ({
243
+ nodes: [
244
+ createEditorAction(
245
+ { type: 'image', testId: 'editor.toolbar.image' },
246
+ 'ph--image-square--regular',
247
+ 'upload image label',
248
+ ),
249
+ ],
250
+ edges: [{ source: 'root', target: 'image' }],
251
+ });
@@ -13,9 +13,8 @@ import { faker } from '@dxos/random';
13
13
  import { createDocAccessor, createObject } from '@dxos/react-client/echo';
14
14
  import { useThemeContext } from '@dxos/react-ui';
15
15
  import {
16
- type Action,
16
+ type EditorAction,
17
17
  type Comment,
18
- type EditorViewMode,
19
18
  comments,
20
19
  createBasicExtensions,
21
20
  createDataExtensions,
@@ -24,32 +23,33 @@ import {
24
23
  decorateMarkdown,
25
24
  editorContent,
26
25
  formattingKeymap,
27
- Toolbar,
26
+ EditorToolbar,
28
27
  translations,
29
28
  useActionHandler,
30
29
  useComments,
31
30
  useFormattingState,
32
31
  useTextEditor,
32
+ useEditorToolbarState,
33
33
  } from '@dxos/react-ui-editor';
34
- import { textBlockWidth } from '@dxos/react-ui-theme';
34
+ import { TextType } from '@dxos/schema';
35
35
  import { withLayout, withTheme } from '@dxos/storybook-utils';
36
36
 
37
- import { TextType } from '../types';
38
-
39
37
  faker.seed(101);
40
38
 
39
+ const _onUpload = async (file: File) => ({ url: file.name });
40
+
41
41
  const DefaultStory: FC<{ content?: string }> = ({ content = '' }) => {
42
42
  const { themeMode } = useThemeContext();
43
43
  const [text] = useState(createObject(create(TextType, { content })));
44
- const [formattingState, formattingObserver] = useFormattingState();
45
- const [viewMode, setViewMode] = useState<EditorViewMode>('preview');
44
+ const toolbarState = useEditorToolbarState({ viewMode: 'preview' });
45
+ const formattingObserver = useFormattingState(toolbarState);
46
46
  const { parentRef, view } = useTextEditor(() => {
47
47
  return {
48
48
  id: text.id,
49
49
  initialValue: text.content,
50
50
  extensions: [
51
51
  formattingObserver,
52
- createBasicExtensions({ readonly: viewMode === 'readonly' }),
52
+ createBasicExtensions({ readonly: toolbarState.viewMode === 'readonly' }),
53
53
  createMarkdownExtensions({ themeMode }),
54
54
  createThemeExtensions({ themeMode, syntaxHighlighting: true, slots: { editor: { className: editorContent } } }),
55
55
  createDataExtensions({ id: text.id, text: createDocAccessor(text, ['content']) }),
@@ -61,15 +61,15 @@ const DefaultStory: FC<{ content?: string }> = ({ content = '' }) => {
61
61
  },
62
62
  }),
63
63
  formattingKeymap(),
64
- ...(viewMode !== 'source' ? [decorateMarkdown()] : []),
64
+ ...(toolbarState.viewMode !== 'source' ? [decorateMarkdown()] : []),
65
65
  ],
66
66
  };
67
- }, [text, formattingObserver, viewMode, themeMode]);
67
+ }, [text, formattingObserver, toolbarState.viewMode, themeMode]);
68
68
 
69
69
  const handleToolbarAction = useActionHandler(view);
70
- const handleAction = (action: Action) => {
70
+ const handleAction = (action: EditorAction) => {
71
71
  if (action.type === 'view-mode') {
72
- setViewMode(action.data);
72
+ toolbarState.viewMode = action.properties.data;
73
73
  } else {
74
74
  handleToolbarAction?.(action);
75
75
  }
@@ -80,13 +80,7 @@ const DefaultStory: FC<{ content?: string }> = ({ content = '' }) => {
80
80
 
81
81
  return (
82
82
  <div role='none' className='fixed inset-0 flex flex-col'>
83
- <Toolbar.Root onAction={handleAction} state={formattingState} classNames={textBlockWidth}>
84
- <Toolbar.View mode={viewMode} />
85
- <Toolbar.Markdown />
86
- <Toolbar.Custom onUpload={async (file) => ({ url: file.name })} />
87
- <Toolbar.Separator />
88
- <Toolbar.Actions />
89
- </Toolbar.Root>
83
+ <EditorToolbar onAction={handleAction} state={toolbarState ?? {}} />
90
84
  <div ref={parentRef} />
91
85
  </div>
92
86
  );
@@ -108,9 +102,9 @@ export const Default = {
108
102
  },
109
103
  };
110
104
 
111
- const meta: Meta<typeof Toolbar.Root> = {
105
+ const meta: Meta<typeof EditorToolbar> = {
112
106
  title: 'plugins/plugin-markdown/Toolbar',
113
- component: Toolbar.Root,
107
+ component: EditorToolbar,
114
108
  render: DefaultStory as any,
115
109
  decorators: [withTheme, withLayout({ tooltips: true })],
116
110
  parameters: { translations, layout: 'fullscreen' },