@dxos/plugin-markdown 0.7.5-main.9d26e3a → 0.7.5-main.e9bb01b

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 (170) hide show
  1. package/dist/lib/browser/{MarkdownContainer-XY6NEUOA.mjs → MarkdownContainer-ZVCWASOD.mjs} +155 -92
  2. package/dist/lib/browser/MarkdownContainer-ZVCWASOD.mjs.map +7 -0
  3. package/dist/lib/browser/app-graph-serializer-BG3MHYI4.mjs +51 -0
  4. package/dist/lib/browser/app-graph-serializer-BG3MHYI4.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-EZ65DY2X.mjs +16 -0
  6. package/dist/lib/browser/chunk-EZ65DY2X.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-R4MG2DP2.mjs → chunk-NPLFZ76Q.mjs} +20 -9
  8. package/dist/lib/browser/chunk-NPLFZ76Q.mjs.map +7 -0
  9. package/dist/lib/browser/{chunk-6FIHBJRV.mjs → chunk-P4K367ZX.mjs} +17 -12
  10. package/dist/lib/browser/chunk-P4K367ZX.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-ROJ4VUZB.mjs +47 -0
  12. package/dist/lib/browser/chunk-ROJ4VUZB.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-YTHIPV5Q.mjs +22 -0
  14. package/dist/lib/browser/chunk-YTHIPV5Q.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-CCOPGHVY.mjs +43 -0
  18. package/dist/lib/browser/intent-resolver-CCOPGHVY.mjs.map +7 -0
  19. package/dist/lib/browser/meta.json +1 -1
  20. package/dist/lib/browser/react-surface-FDSOMV5N.mjs +149 -0
  21. package/dist/lib/browser/react-surface-FDSOMV5N.mjs.map +7 -0
  22. package/dist/lib/browser/settings-U7E4DUWJ.mjs +28 -0
  23. package/dist/lib/browser/settings-U7E4DUWJ.mjs.map +7 -0
  24. package/dist/lib/browser/state-QMQXUPSI.mjs +37 -0
  25. package/dist/lib/browser/state-QMQXUPSI.mjs.map +7 -0
  26. package/dist/lib/browser/thread-Y4WMNFBC.mjs +36 -0
  27. package/dist/lib/browser/thread-Y4WMNFBC.mjs.map +7 -0
  28. package/dist/lib/browser/types/index.mjs +4 -4
  29. package/dist/lib/node/{MarkdownContainer-EX6YDF6J.cjs → MarkdownContainer-HJR4TB7X.cjs} +159 -98
  30. package/dist/lib/node/MarkdownContainer-HJR4TB7X.cjs.map +7 -0
  31. package/dist/lib/node/app-graph-serializer-QZWERV4M.cjs +62 -0
  32. package/dist/lib/node/app-graph-serializer-QZWERV4M.cjs.map +7 -0
  33. package/dist/lib/node/{meta.cjs → chunk-23NQGZX3.cjs} +13 -8
  34. package/dist/lib/node/chunk-23NQGZX3.cjs.map +7 -0
  35. package/dist/lib/node/{chunk-SXQAPZZU.cjs → chunk-2WJ7TUBY.cjs} +37 -12
  36. package/dist/lib/node/chunk-2WJ7TUBY.cjs.map +7 -0
  37. package/dist/lib/node/{chunk-CQMXZ54Z.cjs → chunk-CZXXBKMN.cjs} +21 -16
  38. package/dist/lib/node/chunk-CZXXBKMN.cjs.map +7 -0
  39. package/dist/lib/node/{chunk-PHHIPRJC.cjs → chunk-QZ4XQYNC.cjs} +16 -9
  40. package/dist/lib/node/chunk-QZ4XQYNC.cjs.map +7 -0
  41. package/dist/lib/node/chunk-ZO5ABSHT.cjs +64 -0
  42. package/dist/lib/node/chunk-ZO5ABSHT.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-5LCIY27K.cjs +56 -0
  46. package/dist/lib/node/intent-resolver-5LCIY27K.cjs.map +7 -0
  47. package/dist/lib/node/meta.json +1 -1
  48. package/dist/lib/node/react-surface-Y4IB5T27.cjs +162 -0
  49. package/dist/lib/node/react-surface-Y4IB5T27.cjs.map +7 -0
  50. package/dist/lib/node/settings-4OYDMRVY.cjs +42 -0
  51. package/dist/lib/node/settings-4OYDMRVY.cjs.map +7 -0
  52. package/dist/lib/node/state-QSA5AOLH.cjs +51 -0
  53. package/dist/lib/node/state-QSA5AOLH.cjs.map +7 -0
  54. package/dist/lib/node/thread-BZE6RKFE.cjs +52 -0
  55. package/dist/lib/node/thread-BZE6RKFE.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-ILR7OVRM.mjs} +155 -92
  59. package/dist/lib/node-esm/MarkdownContainer-ILR7OVRM.mjs.map +7 -0
  60. package/dist/lib/node-esm/app-graph-serializer-H7LI476D.mjs +52 -0
  61. package/dist/lib/node-esm/app-graph-serializer-H7LI476D.mjs.map +7 -0
  62. package/dist/lib/node-esm/chunk-BBVPC53M.mjs +24 -0
  63. package/dist/lib/node-esm/chunk-BBVPC53M.mjs.map +7 -0
  64. package/dist/lib/node-esm/{chunk-ZVFBKBSA.mjs → chunk-MMVIEZ43.mjs} +17 -12
  65. package/dist/lib/node-esm/chunk-MMVIEZ43.mjs.map +7 -0
  66. package/dist/lib/node-esm/chunk-OXPPXUUU.mjs +48 -0
  67. package/dist/lib/node-esm/chunk-OXPPXUUU.mjs.map +7 -0
  68. package/dist/lib/node-esm/{chunk-Y76MM22C.mjs → chunk-T62F7XQE.mjs} +20 -9
  69. package/dist/lib/node-esm/chunk-T62F7XQE.mjs.map +7 -0
  70. package/dist/lib/node-esm/chunk-Y5QKDC4V.mjs +17 -0
  71. package/dist/lib/node-esm/chunk-Y5QKDC4V.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-HFEI5NFC.mjs +44 -0
  75. package/dist/lib/node-esm/intent-resolver-HFEI5NFC.mjs.map +7 -0
  76. package/dist/lib/node-esm/meta.json +1 -1
  77. package/dist/lib/node-esm/react-surface-N5F4I26E.mjs +150 -0
  78. package/dist/lib/node-esm/react-surface-N5F4I26E.mjs.map +7 -0
  79. package/dist/lib/node-esm/settings-MEN2YBLA.mjs +29 -0
  80. package/dist/lib/node-esm/settings-MEN2YBLA.mjs.map +7 -0
  81. package/dist/lib/node-esm/state-FF4NXLWU.mjs +38 -0
  82. package/dist/lib/node-esm/state-FF4NXLWU.mjs.map +7 -0
  83. package/dist/lib/node-esm/thread-O2YDKT77.mjs +37 -0
  84. package/dist/lib/node-esm/thread-O2YDKT77.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/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 +2 -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/schema.d.ts +0 -7
  123. package/dist/types/src/types/schema.d.ts.map +1 -1
  124. package/dist/types/src/types/types.d.ts +12 -26
  125. package/dist/types/src/types/types.d.ts.map +1 -1
  126. package/dist/types/src/util.d.ts +1 -5
  127. package/dist/types/src/util.d.ts.map +1 -1
  128. package/dist/types/tsconfig.tsbuildinfo +1 -1
  129. package/package.json +32 -37
  130. package/src/MarkdownPlugin.tsx +73 -215
  131. package/src/capabilities/app-graph-serializer.ts +50 -0
  132. package/src/capabilities/capabilities.ts +20 -0
  133. package/src/capabilities/index.ts +14 -0
  134. package/src/capabilities/intent-resolver.ts +33 -0
  135. package/src/capabilities/react-surface.tsx +71 -0
  136. package/src/capabilities/settings.ts +25 -0
  137. package/src/capabilities/state.ts +31 -0
  138. package/src/capabilities/thread.ts +34 -0
  139. package/src/components/MarkdownContainer.tsx +6 -7
  140. package/src/components/MarkdownEditor.stories.tsx +16 -5
  141. package/src/components/MarkdownEditor.tsx +89 -51
  142. package/src/components/MarkdownSettings.tsx +3 -3
  143. package/src/components/Toolbar.stories.tsx +16 -22
  144. package/src/extensions.tsx +18 -14
  145. package/src/hooks/useSelectCurrentThread.tsx +13 -8
  146. package/src/index.ts +2 -5
  147. package/src/meta.ts +7 -2
  148. package/src/translations.ts +1 -0
  149. package/src/types/schema.ts +1 -5
  150. package/src/types/types.ts +16 -44
  151. package/src/util.tsx +2 -8
  152. package/dist/lib/browser/MarkdownContainer-XY6NEUOA.mjs.map +0 -7
  153. package/dist/lib/browser/chunk-4X6YX3KU.mjs +0 -15
  154. package/dist/lib/browser/chunk-4X6YX3KU.mjs.map +0 -7
  155. package/dist/lib/browser/chunk-6FIHBJRV.mjs.map +0 -7
  156. package/dist/lib/browser/chunk-R4MG2DP2.mjs.map +0 -7
  157. package/dist/lib/browser/meta.mjs +0 -9
  158. package/dist/lib/browser/meta.mjs.map +0 -7
  159. package/dist/lib/node/MarkdownContainer-EX6YDF6J.cjs.map +0 -7
  160. package/dist/lib/node/chunk-CQMXZ54Z.cjs.map +0 -7
  161. package/dist/lib/node/chunk-PHHIPRJC.cjs.map +0 -7
  162. package/dist/lib/node/chunk-SXQAPZZU.cjs.map +0 -7
  163. package/dist/lib/node/meta.cjs.map +0 -7
  164. package/dist/lib/node-esm/MarkdownContainer-E7W623A7.mjs.map +0 -7
  165. package/dist/lib/node-esm/chunk-BABK7FMW.mjs +0 -17
  166. package/dist/lib/node-esm/chunk-BABK7FMW.mjs.map +0 -7
  167. package/dist/lib/node-esm/chunk-Y76MM22C.mjs.map +0 -7
  168. package/dist/lib/node-esm/chunk-ZVFBKBSA.mjs.map +0 -7
  169. package/dist/lib/node-esm/meta.mjs +0 -10
  170. package/dist/lib/node-esm/meta.mjs.map +0 -7
@@ -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 { createIntent, type FileInfo, LayoutAction, NavigationAction, useIntentDispatcher } from '@dxos/app-framework';
10
+ import { createIntent, type FileInfo, LayoutAction, 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;
@@ -76,8 +76,8 @@ export const MarkdownEditor = ({
76
76
  const { t } = useTranslation(MARKDOWN_PLUGIN);
77
77
  const { themeMode } = useThemeContext();
78
78
  const { dispatchPromise: dispatch } = useIntentDispatcher();
79
- const [formattingState, formattingObserver] = useFormattingState();
80
- const { hasAttention } = useAttention(id);
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,20 +89,26 @@ 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();
92
+ // TODO(Zan): Factor out to thread plugin.
93
+ const commentObserver = useCommentState(toolbarState);
94
94
  const onCommentClick = useCallback(async () => {
95
- await dispatch(createIntent(NavigationAction.Open, { activeParts: { complementary: 'comments' } }));
96
- await dispatch(createIntent(LayoutAction.SetLayout, { element: 'complementary', state: true }));
95
+ await dispatch(
96
+ createIntent(LayoutAction.UpdateComplementary, {
97
+ part: 'complementary',
98
+ subject: 'comments',
99
+ options: { state: 'expanded' },
100
+ }),
101
+ );
97
102
  }, [dispatch]);
98
103
  const commentClickObserver = useCommentClickListener(onCommentClick);
99
104
 
105
+ // TODO(wittjosiah): Factor out to file uploader plugin.
100
106
  // Drag files.
101
107
  const handleDrop: DNDOptions['onDrop'] = async (view, { files }) => {
102
108
  const file = files[0];
103
109
  const info = file && onFileUpload ? await onFileUpload(file) : undefined;
104
110
  if (info) {
105
- processAction(view, { type: 'image', data: info.url });
111
+ processEditorPayload(view, { type: 'image', data: info.url });
106
112
  }
107
113
  };
108
114
 
@@ -148,57 +154,79 @@ export const MarkdownEditor = ({
148
154
  useTest(editorView);
149
155
  useSelectCurrentThread(editorView, id);
150
156
 
157
+ // https://react-dropzone.js.org/#src
158
+ const { acceptedFiles, getInputProps, open } = useDropzone({
159
+ multiple: false,
160
+ noDrag: true,
161
+ accept: {
162
+ 'image/*': ['.jpg', '.jpeg', '.png', '.gif'],
163
+ },
164
+ });
165
+
166
+ useEffect(() => {
167
+ if (editorView && onFileUpload && acceptedFiles.length) {
168
+ requestAnimationFrame(async () => {
169
+ // NOTE: Clone file since react-dropzone patches in a non-standard `path` property, which confuses IPFS.
170
+ const f = acceptedFiles[0];
171
+ const file = new File([f], f.name, {
172
+ type: f.type,
173
+ lastModified: f.lastModified,
174
+ });
175
+
176
+ const info = await onFileUpload(file);
177
+ if (info) {
178
+ processEditorPayload(editorView, { type: 'image', data: info.url });
179
+ }
180
+ });
181
+ }
182
+ }, [acceptedFiles, editorView]);
183
+
151
184
  // Toolbar handler.
152
185
  const handleToolbarAction = useActionHandler(editorView);
153
- const handleAction = (action: Action) => {
154
- switch (action.type) {
155
- case 'search': {
156
- if (editorView) {
157
- openSearchPanel(editorView);
186
+ const handleAction = useCallback(
187
+ (action: EditorAction) => {
188
+ switch (action.properties.type) {
189
+ case 'search': {
190
+ if (editorView) {
191
+ openSearchPanel(editorView);
192
+ }
193
+ return;
194
+ }
195
+ case 'view-mode': {
196
+ onViewModeChange?.(id, action.properties.data);
197
+ return;
198
+ }
199
+ case 'image': {
200
+ open();
201
+ return;
158
202
  }
159
- return;
160
- }
161
- case 'view-mode': {
162
- onViewModeChange?.(id, action.data);
163
- return;
164
203
  }
165
- }
166
204
 
167
- handleToolbarAction?.(action);
168
- };
205
+ handleToolbarAction?.(action);
206
+ },
207
+ [editorView, onViewModeChange, open],
208
+ );
169
209
 
170
210
  return (
171
- <StackItem.Content toolbar={toolbar}>
211
+ <StackItem.Content toolbar={!!toolbar}>
172
212
  {toolbar && (
173
- <div
174
- role='none'
175
- className={mx(
176
- 'attention-surface is-full border-be !border-separator',
177
- role === 'section' && 'sticky block-start-0 z-[1] -mbe-px min-is-0',
178
- )}
179
- >
180
- <Toolbar.Root
181
- classNames={[textBlockWidth, !hasAttention && 'opacity-20']}
182
- state={formattingState && { ...formattingState, ...commentsState }}
213
+ <>
214
+ <EditorToolbar
215
+ attendableId={id}
216
+ role={role}
217
+ state={toolbarState}
218
+ customActions={onFileUpload ? createUploadAction : undefined}
183
219
  onAction={handleAction}
184
- >
185
- <Toolbar.Markdown />
186
- {onFileUpload && <Toolbar.Custom onUpload={onFileUpload} />}
187
- <Toolbar.Separator />
188
- <Toolbar.View mode={viewMode ?? DEFAULT_VIEW_MODE} />
189
- <Toolbar.Actions />
190
- </Toolbar.Root>
191
- </div>
220
+ />
221
+ <input {...getInputProps()} />
222
+ </>
192
223
  )}
193
224
  <div
194
225
  role='none'
195
226
  ref={parentRef}
196
227
  data-testid='composer.markdownRoot'
197
228
  data-toolbar={toolbar ? 'enabled' : 'disabled'}
198
- className={mx(
199
- 'ch-focus-ring-inset data-[toolbar=disabled]:pbs-2 attention-surface',
200
- role === 'article' ? 'min-bs-0' : '[&_.cm-scroller]:overflow-hidden [&_.cm-scroller]:min-bs-24',
201
- )}
229
+ className={stackItemContentEditorClassNames(role)}
202
230
  {...focusAttributes}
203
231
  />
204
232
  </StackItem.Content>
@@ -215,3 +243,13 @@ const useTest = (view?: EditorView) => {
215
243
  }
216
244
  }, [view]);
217
245
  };
246
+
247
+ export const createUploadAction = () => ({
248
+ nodes: [
249
+ createEditorAction({ type: 'image', testId: 'editor.toolbar.image' }, 'ph--image-square--regular', [
250
+ 'upload image label',
251
+ { ns: MARKDOWN_PLUGIN },
252
+ ]),
253
+ ],
254
+ edges: [{ source: 'root', target: 'image' }],
255
+ });
@@ -6,7 +6,7 @@ import React from 'react';
6
6
 
7
7
  import { Input, Select, useTranslation } from '@dxos/react-ui';
8
8
  import { type EditorInputMode, EditorInputModes, type EditorViewMode, EditorViewModes } from '@dxos/react-ui-editor';
9
- import { DeprecatedFormInput } from '@dxos/react-ui-form';
9
+ import { DeprecatedFormContainer, DeprecatedFormInput } from '@dxos/react-ui-form';
10
10
 
11
11
  import { MARKDOWN_PLUGIN } from '../meta';
12
12
  import { type MarkdownSettingsProps } from '../types';
@@ -16,7 +16,7 @@ export const MarkdownSettings = ({ settings }: { settings: MarkdownSettingsProps
16
16
 
17
17
  // TODO(wittjosiah): Add skill test confirmation for entering vim mode.
18
18
  return (
19
- <>
19
+ <DeprecatedFormContainer>
20
20
  <DeprecatedFormInput label={t('default view mode label')}>
21
21
  <Select.Root
22
22
  value={settings.defaultViewMode}
@@ -100,6 +100,6 @@ export const MarkdownSettings = ({ settings }: { settings: MarkdownSettingsProps
100
100
  >
101
101
  <Input.Switch checked={settings.debug} onCheckedChange={(checked) => (settings.debug = !!checked)} />
102
102
  </DeprecatedFormInput>
103
- </>
103
+ </DeprecatedFormContainer>
104
104
  );
105
105
  };
@@ -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' },
@@ -5,7 +5,13 @@
5
5
  import React, { type AnchorHTMLAttributes, type ReactNode, useMemo } from 'react';
6
6
  import { createRoot } from 'react-dom/client';
7
7
 
8
- import { createIntent, NavigationAction, type PromiseIntentDispatcher, useIntentDispatcher } from '@dxos/app-framework';
8
+ import {
9
+ createIntent,
10
+ LayoutAction,
11
+ type PromiseIntentDispatcher,
12
+ useCapabilities,
13
+ useIntentDispatcher,
14
+ } from '@dxos/app-framework';
9
15
  import { invariant } from '@dxos/invariant';
10
16
  import { createDocAccessor, fullyQualifiedId, getSpace, type Query } from '@dxos/react-client/echo';
11
17
  import { useIdentity } from '@dxos/react-client/halo';
@@ -29,7 +35,8 @@ import {
29
35
  import { defaultTx } from '@dxos/react-ui-theme';
30
36
  import { isNotFalsy } from '@dxos/util';
31
37
 
32
- import { type DocumentType, type MarkdownPluginState, type MarkdownSettingsProps } from './types';
38
+ import { MarkdownCapabilities } from './capabilities';
39
+ import { type DocumentType, type MarkdownSettingsProps } from './types';
33
40
  import { setFallbackName } from './util';
34
41
 
35
42
  type ExtensionsOptions = {
@@ -42,13 +49,7 @@ type ExtensionsOptions = {
42
49
  };
43
50
 
44
51
  // TODO(burdon): Merge with createBaseExtensions below.
45
- export const useExtensions = ({
46
- document,
47
- settings,
48
- viewMode,
49
- editorStateStore,
50
- extensionProviders,
51
- }: ExtensionsOptions & Pick<MarkdownPluginState, 'extensionProviders'>): Extension[] => {
52
+ export const useExtensions = ({ document, settings, viewMode, editorStateStore }: ExtensionsOptions): Extension[] => {
52
53
  const { dispatchPromise: dispatch } = useIntentDispatcher();
53
54
  const identity = useIdentity();
54
55
  const space = getSpace(document);
@@ -79,12 +80,14 @@ export const useExtensions = ({
79
80
  ],
80
81
  );
81
82
 
83
+ const extensionProviders = useCapabilities(MarkdownCapabilities.Extensions);
84
+
82
85
  //
83
86
  // External extensions from other plugins.
84
87
  //
85
88
  const pluginExtensions = useMemo<Extension[] | undefined>(
86
89
  () =>
87
- extensionProviders?.reduce((acc: Extension[], provider) => {
90
+ extensionProviders.flat().reduce((acc: Extension[], provider) => {
88
91
  const extension = typeof provider === 'function' ? provider({ document }) : provider;
89
92
  if (extension) {
90
93
  acc.push(extension);
@@ -145,11 +148,12 @@ const createBaseExtensions = ({ document, dispatch, settings, query, viewMode }:
145
148
  dispatch && document
146
149
  ? onRenderLink((id: string) => {
147
150
  void dispatch(
148
- createIntent(NavigationAction.AddToActive, {
149
- id,
151
+ createIntent(LayoutAction.Open, {
150
152
  part: 'main',
151
- pivotId: fullyQualifiedId(document),
152
- scrollIntoView: true,
153
+ subject: [id],
154
+ options: {
155
+ pivotId: fullyQualifiedId(document),
156
+ },
153
157
  }),
154
158
  );
155
159
  })
@@ -6,6 +6,7 @@ import { EditorView } from '@codemirror/view';
6
6
  import { useMemo } from 'react';
7
7
 
8
8
  import { createResolver, LayoutAction, useIntentResolver } from '@dxos/app-framework';
9
+ import { S } from '@dxos/echo-schema';
9
10
  import { invariant } from '@dxos/invariant';
10
11
  import { Cursor, setSelection } from '@dxos/react-ui-editor';
11
12
 
@@ -17,9 +18,17 @@ import { MARKDOWN_PLUGIN } from '../meta';
17
18
  export const useSelectCurrentThread = (editorView: EditorView | undefined, documentId: string) => {
18
19
  const scrollIntoViewResolver = useMemo(
19
20
  () =>
20
- createResolver(
21
- LayoutAction.ScrollIntoView,
22
- ({ cursor }) => {
21
+ createResolver({
22
+ intent: LayoutAction.UpdateLayout,
23
+ position: 'hoist',
24
+ filter: (data): data is { part: 'current'; subject: string; options: { cursor: string } } => {
25
+ if (!S.is(LayoutAction.ScrollIntoView.fields.input)(data)) {
26
+ return false;
27
+ }
28
+
29
+ return !!editorView && data.subject === documentId && !!data.options?.cursor;
30
+ },
31
+ resolve: ({ options: { cursor } }) => {
23
32
  invariant(editorView, 'Editor view is not defined.');
24
33
  const range = Cursor.getRangeFromCursor(editorView.state, cursor!);
25
34
  if (range) {
@@ -39,11 +48,7 @@ export const useSelectCurrentThread = (editorView: EditorView | undefined, docum
39
48
  });
40
49
  }
41
50
  },
42
- {
43
- disposition: 'hoist',
44
- filter: (data) => !!editorView && data.id === documentId && !!data.cursor,
45
- },
46
- ),
51
+ }),
47
52
  [documentId, editorView],
48
53
  );
49
54
 
package/src/index.ts CHANGED
@@ -2,10 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { MarkdownPlugin } from './MarkdownPlugin';
6
-
7
- export default MarkdownPlugin;
8
-
5
+ export { MarkdownCapabilities } from './capabilities';
9
6
  export * from './MarkdownPlugin';
10
- export * from './types';
7
+ export * from './meta';
11
8
  export * from './util';
package/src/meta.ts CHANGED
@@ -6,10 +6,15 @@ import { type PluginMeta } from '@dxos/app-framework';
6
6
 
7
7
  export const MARKDOWN_PLUGIN = 'dxos.org/plugin/markdown';
8
8
 
9
- export default {
9
+ export const meta = {
10
10
  id: MARKDOWN_PLUGIN,
11
11
  name: 'Markdown',
12
- description: 'Text editor supporting extended Markdown.',
12
+ description: `Composer offers a Modern Markdown editor that is collaborative and fully extensible. It provides rich text editing as well as read only and markdown view.
13
+
14
+ In addition to markdown capabilities, it also support threaded in-line comments which can be accessed from the right hand sidebar at any times inside the document.
15
+
16
+ Your AI agent will have access to all markdown docs in your Space which means you can use them to extend the memory of your personal agent and add long term context for automated workflows.`,
13
17
  source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-markdown',
14
18
  icon: 'ph--text-aa--regular',
19
+ screenshots: ['https://dxos.network/plugin-details-markdown-dark.png'],
15
20
  } satisfies PluginMeta;
@@ -34,6 +34,7 @@ export default [
34
34
  'settings debug placeholder': 'Typewriter script...',
35
35
  'toggle view mode label': 'Toggle read-only',
36
36
  'default view mode label': 'Default view mode',
37
+ 'upload image label': 'Upload image',
37
38
  },
38
39
  },
39
40
  },
@@ -4,11 +4,7 @@
4
4
 
5
5
  import { Ref, S, TypedObject } from '@dxos/echo-schema';
6
6
  import { ThreadType } from '@dxos/plugin-space/types';
7
-
8
- // TODO(burdon): Factor out.
9
- export class TextType extends TypedObject({ typename: 'dxos.org/type/Text', version: '0.1.0' })({
10
- content: S.String,
11
- }) {}
7
+ import { TextType } from '@dxos/schema';
12
8
 
13
9
  export class DocumentType extends TypedObject({ typename: 'dxos.org/type/Document', version: '0.1.0' })({
14
10
  name: S.optional(S.String),
@@ -2,17 +2,9 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import type {
6
- GraphSerializerProvides,
7
- IntentResolverProvides,
8
- MetadataRecordsProvides,
9
- SettingsProvides,
10
- SurfaceProvides,
11
- TranslationsProvides,
12
- } from '@dxos/app-framework';
13
5
  import { S } from '@dxos/echo-schema';
14
- import { type SchemaProvides } from '@dxos/plugin-space';
15
- import { type Extension, type EditorInputMode, EditorViewMode } from '@dxos/react-ui-editor';
6
+ // TODO(wittjosiah): This pulls in UI code into the types entrypoint.
7
+ import { type Extension, EditorInputMode, EditorViewMode } from '@dxos/react-ui-editor';
16
8
 
17
9
  import { DocumentType } from './schema';
18
10
  import { MARKDOWN_PLUGIN } from '../meta';
@@ -46,13 +38,6 @@ export type MarkdownExtensionProvider = (props: { document?: DocumentType }) =>
46
38
 
47
39
  export type OnChange = (text: string) => void;
48
40
 
49
- export type MarkdownExtensionProvides = {
50
- // TODO(burdon): Rename.
51
- markdown: {
52
- extensions: MarkdownExtensionProvider;
53
- };
54
- };
55
-
56
41
  export type MarkdownPluginState = {
57
42
  // Codemirror extensions provided by other plugins.
58
43
  extensionProviders?: MarkdownExtensionProvider[];
@@ -62,31 +47,18 @@ export type MarkdownPluginState = {
62
47
  viewMode: Record<string, EditorViewMode>;
63
48
  };
64
49
 
65
- export type MarkdownSettingsProps = {
66
- defaultViewMode: EditorViewMode;
67
- editorInputMode?: EditorInputMode;
68
- experimental?: boolean;
69
- debug?: boolean;
70
- toolbar?: boolean;
71
- typewriter?: string;
72
- // TODO(burdon): Per document settings.
73
- numberedHeadings?: boolean;
74
- folding?: boolean;
75
- };
76
-
77
- // TODO(Zan): Move this to the plugin-space plugin or another common location when we implement comments in sheets.
78
- type ThreadProvides<T> = {
79
- thread: {
80
- predicate: (obj: any) => obj is T;
81
- createSort: (obj: T) => (anchorA: string | undefined, anchorB: string | undefined) => number;
82
- };
83
- };
50
+ export const MarkdownSettingsSchema = S.mutable(
51
+ S.Struct({
52
+ defaultViewMode: EditorViewMode,
53
+ editorInputMode: S.optional(EditorInputMode),
54
+ experimental: S.optional(S.Boolean),
55
+ debug: S.optional(S.Boolean),
56
+ toolbar: S.optional(S.Boolean),
57
+ typewriter: S.optional(S.String),
58
+ // TODO(burdon): Per document settings.
59
+ numberedHeadings: S.optional(S.Boolean),
60
+ folding: S.optional(S.Boolean),
61
+ }),
62
+ );
84
63
 
85
- export type MarkdownPluginProvides = SurfaceProvides &
86
- IntentResolverProvides &
87
- GraphSerializerProvides &
88
- MetadataRecordsProvides &
89
- SettingsProvides<MarkdownSettingsProps> &
90
- TranslationsProvides &
91
- SchemaProvides &
92
- ThreadProvides<DocumentType>;
64
+ export type MarkdownSettingsProps = S.Schema.Type<typeof MarkdownSettingsSchema>;