@dxos/plugin-markdown 0.8.4-main.406dc2a → 0.8.4-main.548089c

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 (224) hide show
  1. package/dist/lib/browser/MarkdownCard-L3BS2SED.mjs +12 -0
  2. package/dist/lib/browser/MarkdownContainer-KF3YL6CI.mjs +15 -0
  3. package/dist/lib/browser/{anchor-sort-YWJI3BKT.mjs → anchor-sort-3HGPGCOO.mjs} +4 -5
  4. package/dist/lib/browser/anchor-sort-3HGPGCOO.mjs.map +7 -0
  5. package/dist/lib/browser/{app-graph-serializer-KYDFCUOW.mjs → app-graph-serializer-POZN234F.mjs} +6 -6
  6. package/dist/lib/browser/app-graph-serializer-POZN234F.mjs.map +7 -0
  7. package/dist/lib/browser/blueprint-definition-GIPKFDY5.mjs +13 -0
  8. package/dist/lib/browser/blueprint-definition-GIPKFDY5.mjs.map +7 -0
  9. package/dist/lib/browser/{chunk-O6XUPW6S.mjs → chunk-22XSSNBS.mjs} +7 -4
  10. package/dist/lib/browser/{chunk-O6XUPW6S.mjs.map → chunk-22XSSNBS.mjs.map} +2 -2
  11. package/dist/lib/browser/chunk-2YCH7AOL.mjs +827 -0
  12. package/dist/lib/browser/chunk-2YCH7AOL.mjs.map +7 -0
  13. package/dist/lib/browser/{chunk-ODB2PTBP.mjs → chunk-BQTYJOFB.mjs} +4 -4
  14. package/dist/lib/browser/chunk-BQTYJOFB.mjs.map +7 -0
  15. package/dist/lib/browser/{MarkdownCard-AGWOTODZ.mjs → chunk-BU7S64EA.mjs} +36 -24
  16. package/dist/lib/browser/chunk-BU7S64EA.mjs.map +7 -0
  17. package/dist/lib/browser/{chunk-6KU5DKP7.mjs → chunk-GH6GQSBL.mjs} +8 -8
  18. package/dist/lib/browser/chunk-GH6GQSBL.mjs.map +7 -0
  19. package/dist/lib/browser/{chunk-OY6CGPOO.mjs → chunk-IBCHVMZW.mjs} +2 -2
  20. package/dist/lib/browser/{chunk-OY6CGPOO.mjs.map → chunk-IBCHVMZW.mjs.map} +2 -2
  21. package/dist/lib/browser/chunk-IGS3RCFE.mjs +20 -0
  22. package/dist/lib/browser/chunk-IGS3RCFE.mjs.map +7 -0
  23. package/dist/lib/browser/{chunk-XMT6PMU5.mjs → chunk-QYSEJ5GP.mjs} +9 -9
  24. package/dist/lib/browser/chunk-QYSEJ5GP.mjs.map +7 -0
  25. package/dist/lib/browser/chunk-Y53FQREH.mjs +150 -0
  26. package/dist/lib/browser/chunk-Y53FQREH.mjs.map +7 -0
  27. package/dist/lib/browser/index.mjs +22 -21
  28. package/dist/lib/browser/index.mjs.map +3 -3
  29. package/dist/lib/browser/{intent-resolver-XHVCZZHU.mjs → intent-resolver-Z5L7TXUK.mjs} +5 -5
  30. package/dist/lib/browser/{intent-resolver-XHVCZZHU.mjs.map → intent-resolver-Z5L7TXUK.mjs.map} +3 -3
  31. package/dist/lib/browser/meta.json +1 -1
  32. package/dist/lib/browser/{react-surface-3A2GO3BN.mjs → react-surface-45NYBFCG.mjs} +56 -54
  33. package/dist/lib/browser/react-surface-45NYBFCG.mjs.map +7 -0
  34. package/dist/lib/browser/{settings-XY265Y2Q.mjs → settings-TZUDB5EW.mjs} +3 -3
  35. package/dist/lib/browser/{state-6QODXCSZ.mjs → state-BTUKVZHY.mjs} +3 -3
  36. package/dist/lib/browser/toolkit.mjs +13 -0
  37. package/dist/lib/browser/toolkit.mjs.map +7 -0
  38. package/dist/lib/browser/types/index.mjs +2 -2
  39. package/dist/lib/node-esm/MarkdownCard-UUQLN6XK.mjs +13 -0
  40. package/dist/lib/node-esm/MarkdownCard-UUQLN6XK.mjs.map +7 -0
  41. package/dist/lib/node-esm/MarkdownContainer-PQBLVUW4.mjs +16 -0
  42. package/dist/lib/node-esm/MarkdownContainer-PQBLVUW4.mjs.map +7 -0
  43. package/dist/lib/node-esm/{anchor-sort-FCRYL2FX.mjs → anchor-sort-PCDXEBJ2.mjs} +4 -5
  44. package/dist/lib/node-esm/anchor-sort-PCDXEBJ2.mjs.map +7 -0
  45. package/dist/lib/node-esm/{app-graph-serializer-FAUQM3BH.mjs → app-graph-serializer-NF65JYAS.mjs} +6 -6
  46. package/dist/lib/node-esm/app-graph-serializer-NF65JYAS.mjs.map +7 -0
  47. package/dist/lib/node-esm/blueprint-definition-ENKJZYQS.mjs +14 -0
  48. package/dist/lib/node-esm/blueprint-definition-ENKJZYQS.mjs.map +7 -0
  49. package/dist/lib/node-esm/chunk-2PYNDVNK.mjs +828 -0
  50. package/dist/lib/node-esm/chunk-2PYNDVNK.mjs.map +7 -0
  51. package/dist/lib/node-esm/{MarkdownCard-B2IWTFOC.mjs → chunk-6XVGFGRW.mjs} +36 -24
  52. package/dist/lib/node-esm/chunk-6XVGFGRW.mjs.map +7 -0
  53. package/dist/lib/node-esm/{chunk-XO3IEQJE.mjs → chunk-AMHACOXW.mjs} +7 -4
  54. package/dist/lib/node-esm/{chunk-XO3IEQJE.mjs.map → chunk-AMHACOXW.mjs.map} +2 -2
  55. package/dist/lib/node-esm/chunk-ENYE7TQ5.mjs +22 -0
  56. package/dist/lib/node-esm/chunk-ENYE7TQ5.mjs.map +7 -0
  57. package/dist/lib/node-esm/{chunk-CB2R4YIY.mjs → chunk-GMMVSXQ6.mjs} +2 -2
  58. package/dist/lib/node-esm/{chunk-CB2R4YIY.mjs.map → chunk-GMMVSXQ6.mjs.map} +2 -2
  59. package/dist/lib/node-esm/chunk-HAIEWPU7.mjs +151 -0
  60. package/dist/lib/node-esm/chunk-HAIEWPU7.mjs.map +7 -0
  61. package/dist/lib/node-esm/{chunk-FWZKC6X5.mjs → chunk-NGYJNQ6A.mjs} +9 -9
  62. package/dist/lib/node-esm/chunk-NGYJNQ6A.mjs.map +7 -0
  63. package/dist/lib/node-esm/{chunk-ZBXV4ON7.mjs → chunk-PLZ7EVCT.mjs} +8 -8
  64. package/dist/lib/node-esm/chunk-PLZ7EVCT.mjs.map +7 -0
  65. package/dist/lib/node-esm/{chunk-VCG2U522.mjs → chunk-SHAMSMKQ.mjs} +4 -4
  66. package/dist/lib/node-esm/chunk-SHAMSMKQ.mjs.map +7 -0
  67. package/dist/lib/node-esm/index.mjs +22 -21
  68. package/dist/lib/node-esm/index.mjs.map +3 -3
  69. package/dist/lib/node-esm/{intent-resolver-7A2EXGZQ.mjs → intent-resolver-6B3PFQ5F.mjs} +5 -5
  70. package/dist/lib/node-esm/{intent-resolver-7A2EXGZQ.mjs.map → intent-resolver-6B3PFQ5F.mjs.map} +3 -3
  71. package/dist/lib/node-esm/meta.json +1 -1
  72. package/dist/lib/node-esm/{react-surface-RCLL5WVQ.mjs → react-surface-CPMCARNW.mjs} +56 -54
  73. package/dist/lib/node-esm/react-surface-CPMCARNW.mjs.map +7 -0
  74. package/dist/lib/node-esm/{settings-H3UDD3KO.mjs → settings-CJ3T5EX4.mjs} +3 -3
  75. package/dist/lib/node-esm/{state-W3PECOJX.mjs → state-K6EH7SRZ.mjs} +3 -3
  76. package/dist/lib/node-esm/toolkit.mjs +14 -0
  77. package/dist/lib/node-esm/toolkit.mjs.map +7 -0
  78. package/dist/lib/node-esm/types/index.mjs +2 -2
  79. package/dist/types/src/MarkdownPlugin.d.ts.map +1 -1
  80. package/dist/types/src/capabilities/anchor-sort.d.ts +2 -4
  81. package/dist/types/src/capabilities/anchor-sort.d.ts.map +1 -1
  82. package/dist/types/src/capabilities/blueprint-definition.d.ts +5 -3
  83. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -1
  84. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  85. package/dist/types/src/capabilities/index.d.ts +1 -5
  86. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  87. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  88. package/dist/types/src/components/MarkdownCard/MarkdownCard.d.ts +2 -2
  89. package/dist/types/src/components/MarkdownCard/MarkdownCard.d.ts.map +1 -1
  90. package/dist/types/src/components/MarkdownContainer.d.ts +8 -12
  91. package/dist/types/src/components/MarkdownContainer.d.ts.map +1 -1
  92. package/dist/types/src/components/MarkdownContainer.stories.d.ts +3 -0
  93. package/dist/types/src/components/MarkdownContainer.stories.d.ts.map +1 -1
  94. package/dist/types/src/components/MarkdownEditor/FileUpload.d.ts +11 -0
  95. package/dist/types/src/components/MarkdownEditor/FileUpload.d.ts.map +1 -0
  96. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts +42 -23
  97. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts.map +1 -1
  98. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.stories.d.ts +5 -110
  99. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.stories.d.ts.map +1 -1
  100. package/dist/types/src/components/MarkdownEditor/MarkdownEditorContent.d.ts +26 -0
  101. package/dist/types/src/components/MarkdownEditor/MarkdownEditorContent.d.ts.map +1 -0
  102. package/dist/types/src/components/MarkdownEditor/MarkdownEditorToolbar.d.ts +12 -0
  103. package/dist/types/src/components/MarkdownEditor/MarkdownEditorToolbar.d.ts.map +1 -0
  104. package/dist/types/src/components/Suggestions.stories.d.ts.map +1 -1
  105. package/dist/types/src/components/index.d.ts +3 -1
  106. package/dist/types/src/components/index.d.ts.map +1 -1
  107. package/dist/types/src/functions/create.d.ts +8 -0
  108. package/dist/types/src/functions/create.d.ts.map +1 -0
  109. package/dist/types/src/functions/create.test.d.ts +2 -0
  110. package/dist/types/src/functions/create.test.d.ts.map +1 -0
  111. package/dist/types/src/functions/index.d.ts +17 -2
  112. package/dist/types/src/functions/index.d.ts.map +1 -1
  113. package/dist/types/src/functions/{diff.d.ts → update.d.ts} +1 -1
  114. package/dist/types/src/functions/update.d.ts.map +1 -0
  115. package/dist/types/src/functions/update.test.d.ts +2 -0
  116. package/dist/types/src/functions/update.test.d.ts.map +1 -0
  117. package/dist/types/src/hooks/index.d.ts +3 -0
  118. package/dist/types/src/hooks/index.d.ts.map +1 -1
  119. package/dist/types/src/hooks/useExtensions.d.ts +21 -0
  120. package/dist/types/src/hooks/useExtensions.d.ts.map +1 -0
  121. package/dist/types/src/hooks/useLinkQuery.d.ts +4 -0
  122. package/dist/types/src/hooks/useLinkQuery.d.ts.map +1 -0
  123. package/dist/types/src/hooks/usePopoverMenuOptions.d.ts +9 -0
  124. package/dist/types/src/hooks/usePopoverMenuOptions.d.ts.map +1 -0
  125. package/dist/types/src/hooks/useSelectCurrentThread.d.ts.map +1 -1
  126. package/dist/types/src/testing.d.ts +6 -0
  127. package/dist/types/src/testing.d.ts.map +1 -0
  128. package/dist/types/src/toolkit.d.ts +3 -0
  129. package/dist/types/src/toolkit.d.ts.map +1 -0
  130. package/dist/types/src/translations.d.ts +3 -0
  131. package/dist/types/src/translations.d.ts.map +1 -1
  132. package/dist/types/src/types/Markdown.d.ts +1 -1
  133. package/dist/types/src/types/Markdown.d.ts.map +1 -1
  134. package/dist/types/src/types/index.d.ts.map +1 -1
  135. package/dist/types/src/util.d.ts +3 -3
  136. package/dist/types/src/util.d.ts.map +1 -1
  137. package/dist/types/tsconfig.tsbuildinfo +1 -1
  138. package/package.json +58 -42
  139. package/src/MarkdownPlugin.tsx +10 -11
  140. package/src/capabilities/anchor-sort.ts +3 -3
  141. package/src/capabilities/app-graph-serializer.ts +3 -3
  142. package/src/capabilities/artifact-definition.ts +3 -3
  143. package/src/capabilities/blueprint-definition.ts +30 -26
  144. package/src/capabilities/capabilities.ts +1 -0
  145. package/src/capabilities/index.ts +1 -2
  146. package/src/capabilities/intent-resolver.ts +1 -1
  147. package/src/capabilities/react-surface.tsx +42 -64
  148. package/src/components/MarkdownCard/MarkdownCard.stories.tsx +3 -3
  149. package/src/components/MarkdownCard/MarkdownCard.tsx +35 -23
  150. package/src/components/MarkdownContainer.stories.tsx +54 -27
  151. package/src/components/MarkdownContainer.tsx +77 -221
  152. package/src/components/MarkdownEditor/FileUpload.tsx +63 -0
  153. package/src/components/MarkdownEditor/MarkdownEditor.stories.tsx +57 -37
  154. package/src/components/MarkdownEditor/MarkdownEditor.tsx +217 -268
  155. package/src/components/MarkdownEditor/MarkdownEditorContent.tsx +134 -0
  156. package/src/components/MarkdownEditor/MarkdownEditorToolbar.tsx +63 -0
  157. package/src/components/Suggestions.stories.tsx +14 -8
  158. package/src/components/index.ts +3 -1
  159. package/src/functions/create.conversations.json +1 -0
  160. package/src/functions/create.test.ts +128 -0
  161. package/src/functions/create.ts +34 -0
  162. package/src/functions/index.ts +9 -2
  163. package/src/functions/update.conversations.json +1 -0
  164. package/src/functions/update.test.ts +151 -0
  165. package/src/functions/{diff.ts → update.ts} +2 -2
  166. package/src/hooks/index.ts +3 -0
  167. package/src/{extensions.tsx → hooks/useExtensions.tsx} +57 -78
  168. package/src/hooks/useLinkQuery.ts +83 -0
  169. package/src/hooks/usePopoverMenuOptions.ts +71 -0
  170. package/src/hooks/useSelectCurrentThread.tsx +13 -3
  171. package/src/meta.ts +3 -3
  172. package/src/testing.ts +27 -0
  173. package/src/toolkit.ts +6 -0
  174. package/src/translations.ts +3 -0
  175. package/src/types/Markdown.ts +6 -6
  176. package/src/types/index.ts +1 -0
  177. package/src/util.tsx +7 -8
  178. package/dist/lib/browser/MarkdownCard-AGWOTODZ.mjs.map +0 -7
  179. package/dist/lib/browser/MarkdownContainer-MV2UNAUV.mjs +0 -751
  180. package/dist/lib/browser/MarkdownContainer-MV2UNAUV.mjs.map +0 -7
  181. package/dist/lib/browser/anchor-sort-YWJI3BKT.mjs.map +0 -7
  182. package/dist/lib/browser/app-graph-serializer-KYDFCUOW.mjs.map +0 -7
  183. package/dist/lib/browser/blueprint-definition-BHRMFZAC.mjs +0 -11
  184. package/dist/lib/browser/chunk-6KU5DKP7.mjs.map +0 -7
  185. package/dist/lib/browser/chunk-HBBEHOP3.mjs +0 -106
  186. package/dist/lib/browser/chunk-HBBEHOP3.mjs.map +0 -7
  187. package/dist/lib/browser/chunk-ODB2PTBP.mjs.map +0 -7
  188. package/dist/lib/browser/chunk-XMT6PMU5.mjs.map +0 -7
  189. package/dist/lib/browser/chunk-Z5PDJNBV.mjs +0 -22
  190. package/dist/lib/browser/chunk-Z5PDJNBV.mjs.map +0 -7
  191. package/dist/lib/browser/react-surface-3A2GO3BN.mjs.map +0 -7
  192. package/dist/lib/browser/toolkit-YA65QX2S.mjs +0 -76
  193. package/dist/lib/browser/toolkit-YA65QX2S.mjs.map +0 -7
  194. package/dist/lib/node-esm/MarkdownCard-B2IWTFOC.mjs.map +0 -7
  195. package/dist/lib/node-esm/MarkdownContainer-J2R3DLCQ.mjs +0 -752
  196. package/dist/lib/node-esm/MarkdownContainer-J2R3DLCQ.mjs.map +0 -7
  197. package/dist/lib/node-esm/anchor-sort-FCRYL2FX.mjs.map +0 -7
  198. package/dist/lib/node-esm/app-graph-serializer-FAUQM3BH.mjs.map +0 -7
  199. package/dist/lib/node-esm/blueprint-definition-XYFKMIDR.mjs +0 -12
  200. package/dist/lib/node-esm/chunk-7RDNIMTF.mjs +0 -24
  201. package/dist/lib/node-esm/chunk-7RDNIMTF.mjs.map +0 -7
  202. package/dist/lib/node-esm/chunk-FVI7LPC3.mjs +0 -107
  203. package/dist/lib/node-esm/chunk-FVI7LPC3.mjs.map +0 -7
  204. package/dist/lib/node-esm/chunk-FWZKC6X5.mjs.map +0 -7
  205. package/dist/lib/node-esm/chunk-VCG2U522.mjs.map +0 -7
  206. package/dist/lib/node-esm/chunk-ZBXV4ON7.mjs.map +0 -7
  207. package/dist/lib/node-esm/react-surface-RCLL5WVQ.mjs.map +0 -7
  208. package/dist/lib/node-esm/toolkit-HSIKUGNK.mjs +0 -77
  209. package/dist/lib/node-esm/toolkit-HSIKUGNK.mjs.map +0 -7
  210. package/dist/types/src/capabilities/toolkit.d.ts +0 -4
  211. package/dist/types/src/capabilities/toolkit.d.ts.map +0 -1
  212. package/dist/types/src/components/Toolbar.stories.d.ts +0 -48
  213. package/dist/types/src/components/Toolbar.stories.d.ts.map +0 -1
  214. package/dist/types/src/extensions.d.ts +0 -22
  215. package/dist/types/src/extensions.d.ts.map +0 -1
  216. package/dist/types/src/functions/diff.d.ts.map +0 -1
  217. package/src/capabilities/toolkit.ts +0 -49
  218. package/src/components/Toolbar.stories.tsx +0 -118
  219. /package/dist/lib/browser/{blueprint-definition-BHRMFZAC.mjs.map → MarkdownCard-L3BS2SED.mjs.map} +0 -0
  220. /package/dist/lib/{node-esm/blueprint-definition-XYFKMIDR.mjs.map → browser/MarkdownContainer-KF3YL6CI.mjs.map} +0 -0
  221. /package/dist/lib/browser/{settings-XY265Y2Q.mjs.map → settings-TZUDB5EW.mjs.map} +0 -0
  222. /package/dist/lib/browser/{state-6QODXCSZ.mjs.map → state-BTUKVZHY.mjs.map} +0 -0
  223. /package/dist/lib/node-esm/{settings-H3UDD3KO.mjs.map → settings-CJ3T5EX4.mjs.map} +0 -0
  224. /package/dist/lib/node-esm/{state-W3PECOJX.mjs.map → state-K6EH7SRZ.mjs.map} +0 -0
@@ -1,294 +1,243 @@
1
1
  //
2
- // Copyright 2023 DXOS.org
2
+ // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import { type Extension } from '@codemirror/state';
5
6
  import { type EditorView } from '@codemirror/view';
6
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
7
- import { useDropzone } from 'react-dropzone';
8
-
9
- import { type FileInfo } from '@dxos/app-framework';
10
- import { invariant } from '@dxos/invariant';
11
- import { Domino, toLocalizedString, useThemeContext, useTranslation } from '@dxos/react-ui';
7
+ import { createContext } from '@radix-ui/react-context';
8
+ import React, { type PropsWithChildren, useMemo, useState } from 'react';
9
+ import { createPortal } from 'react-dom';
10
+
11
+ import { Surface } from '@dxos/app-framework/react';
12
+ import { DXN } from '@dxos/keys';
13
+ import { type Live } from '@dxos/live-object';
14
+ import { useClient } from '@dxos/react-client';
12
15
  import {
13
- type DNDOptions,
14
- type EditorInputMode,
15
- type EditorSelectionState,
16
- type EditorStateStore,
17
- EditorToolbar,
18
- type EditorToolbarActionGraphProps,
19
- type EditorViewMode,
20
- type PopoverMenuGroup,
16
+ type EditorToolbarState,
21
17
  PopoverMenuProvider,
22
- type UsePopoverMenuProps,
23
- type UseTextEditorProps,
24
- addLink,
25
- createBasicExtensions,
26
- createMarkdownExtensions,
27
- createThemeExtensions,
28
- dropFile,
29
- editorGutter,
30
- editorSlots,
31
- filterMenuGroups,
32
- formattingCommands,
33
- linkSlashCommands,
34
- processEditorPayload,
35
- stackItemContentEditorClassNames,
18
+ type PreviewBlock,
19
+ type PreviewOptions,
20
+ type UsePopoverMenu,
36
21
  useEditorToolbarState,
37
- useFormattingState,
38
22
  usePopoverMenu,
39
- useTextEditor,
40
23
  } from '@dxos/react-ui-editor';
41
- import { StackItem } from '@dxos/react-ui-stack';
42
- import { isNonNullable, isTruthy } from '@dxos/util';
24
+ import { isNonNullable } from '@dxos/util';
25
+
26
+ import {
27
+ type DocumentType,
28
+ type ExtensionsOptions,
29
+ type UsePopoverMenuOptionsProps,
30
+ useExtensions,
31
+ usePopoverMenuOptions,
32
+ } from '../../hooks';
43
33
 
44
- import { useSelectCurrentThread } from '../../hooks';
45
- import { meta } from '../../meta';
46
- import { type MarkdownPluginState } from '../../types';
34
+ import {
35
+ MarkdownEditorContent as NaturalMarkdownEditorContent,
36
+ type MarkdownEditorContentProps as NaturalMarkdownEditorContentProps,
37
+ } from './MarkdownEditorContent';
38
+ import {
39
+ MarkdownEditorToolbar as NaturalMarkdownToolbar,
40
+ type MarkdownEditorToolbarProps as NaturalMarkdownToolbarProps,
41
+ } from './MarkdownEditorToolbar';
42
+
43
+ //
44
+ // Context
45
+ //
47
46
 
48
- export type MarkdownEditorProps = {
47
+ type MarkdownEditorContextValue = {
49
48
  id: string;
50
- role?: string;
51
- toolbar?: boolean;
52
- inputMode?: EditorInputMode;
53
- scrollPastEnd?: boolean;
54
- slashCommandGroups?: PopoverMenuGroup[];
55
- customActions?: EditorToolbarActionGraphProps['customActions'];
56
- // TODO(wittjosiah): Generalize custom toolbar actions (e.g. comment, upload, etc.)
57
- viewMode?: EditorViewMode;
58
- editorStateStore?: EditorStateStore;
59
- onViewModeChange?: (id: string, mode: EditorViewMode) => void;
60
- onLinkQuery?: (query?: string) => Promise<PopoverMenuGroup[]>;
61
- onFileUpload?: (file: File) => Promise<FileInfo | undefined>;
62
- } & (Pick<UseTextEditorProps, 'initialValue' | 'extensions'> &
63
- Partial<Pick<MarkdownPluginState, 'extensionProviders'>>);
64
-
65
- /**
66
- * Base markdown editor component.
67
- * This component provides all the features of the markdown editor that do no depend on ECHO.
68
- * This allows it to be used as a common editor for markdown content on arbitrary backends (e.g. files).
69
- */
70
- export const MarkdownEditor = ({
49
+ setEditorView: (view: EditorView) => void;
50
+ extensions: Extension[];
51
+ previewBlocks: PreviewBlock[];
52
+ toolbarState: Live<EditorToolbarState>;
53
+ popoverMenu: Omit<UsePopoverMenu, 'extension'>;
54
+ } & (Pick<ExtensionsOptions, 'viewMode'> &
55
+ Pick<NaturalMarkdownToolbarProps, 'editorView' | 'onFileUpload' | 'onViewModeChange'>);
56
+
57
+ const [MarkdownEditorContextProvider, useMarkdownEditorContext] =
58
+ createContext<MarkdownEditorContextValue>('MarkdownEditor.Context');
59
+
60
+ //
61
+ // MarkdownEditor.Root
62
+ //
63
+
64
+ type MarkdownEditorRootProps = PropsWithChildren<
65
+ {
66
+ object?: DocumentType;
67
+ extensions?: Extension[];
68
+ } & Pick<MarkdownEditorContextValue, 'id' | 'onFileUpload' | 'onViewModeChange' | 'viewMode'> &
69
+ Pick<UsePopoverMenuOptionsProps, 'slashCommandGroups' | 'onLinkQuery'> &
70
+ Pick<ExtensionsOptions, 'editorStateStore' | 'selectionManager' | 'settings'>
71
+ >;
72
+
73
+ const MarkdownEditorRoot = ({
74
+ children,
75
+ id,
76
+ object,
77
+ editorStateStore,
78
+ selectionManager,
79
+ settings,
80
+ viewMode,
71
81
  extensions: extensionsParam,
72
82
  slashCommandGroups,
73
83
  onLinkQuery,
74
84
  ...props
75
- }: MarkdownEditorProps) => {
76
- const { t } = useTranslation();
77
- const viewRef = useRef<EditorView>(null);
78
-
79
- const getMenu = useCallback<NonNullable<UsePopoverMenuProps['getMenu']>>(
80
- ({ text, trigger }) => {
81
- switch (trigger) {
82
- case '@': {
83
- return onLinkQuery?.(text) ?? [];
84
- }
85
-
86
- case '/':
87
- default: {
88
- return filterMenuGroups([formattingCommands, linkSlashCommands, ...(slashCommandGroups ?? [])], (item) =>
89
- text ? toLocalizedString(item.label, t).toLowerCase().includes(text.toLowerCase()) : true,
90
- );
91
- }
92
- }
93
- },
94
- [onLinkQuery, slashCommandGroups],
85
+ }: MarkdownEditorRootProps) => {
86
+ const [editorView, setEditorView] = useState<EditorView>();
87
+
88
+ // Preview blocks.
89
+ const [previewBlocks, setPreviewBlocks] = useState<PreviewBlock[]>([]);
90
+ const previewOptions = useMemo<PreviewOptions>(
91
+ () => ({
92
+ addBlockContainer: (block) => {
93
+ setPreviewBlocks((prev) => [...prev, block]);
94
+ },
95
+ removeBlockContainer: ({ link }) => {
96
+ setPreviewBlocks((prev) => prev.filter(({ link: prevLink }) => prevLink.ref !== link.ref));
97
+ },
98
+ }),
99
+ [],
95
100
  );
96
101
 
97
- const options = useMemo<UsePopoverMenuProps>(() => {
98
- const trigger = onLinkQuery ? ['/', '@'] : ['/'];
99
- return {
100
- viewRef,
101
- trigger,
102
- placeholder: {
103
- delay: 3_000,
104
- content: () =>
105
- Domino.of('div')
106
- .children(
107
- Domino.of('span').text('Press'),
108
- ...trigger.map((text) =>
109
- Domino.of('span')
110
- .classNames('mx-1 px-1.5 pt-[1px] pb-[2px] border border-separator rounded-sm')
111
- .text(text),
112
- ),
113
- Domino.of('span').text('for commands.'),
114
- )
115
- .build(),
116
- },
117
- getMenu,
118
- };
119
- }, [onLinkQuery, getMenu]);
102
+ // Toolbar state.
103
+ const toolbarState = useEditorToolbarState({ viewMode });
104
+
105
+ // Context menu.
106
+ const menuOptions = usePopoverMenuOptions({
107
+ editorView,
108
+ slashCommandGroups,
109
+ onLinkQuery,
110
+ });
111
+ const { extension: menuExtension, ...menuProps } = usePopoverMenu(menuOptions);
112
+
113
+ // Extensions.
114
+ const coreExtensions = useExtensions({
115
+ id,
116
+ object,
117
+ editorStateStore,
118
+ previewOptions,
119
+ selectionManager,
120
+ settings,
121
+ viewMode,
122
+ });
123
+
124
+ const extensions = useMemo(
125
+ () => [coreExtensions, menuExtension, extensionsParam].filter(isNonNullable),
126
+ [coreExtensions, menuExtension, extensionsParam],
127
+ );
128
+
129
+ return (
130
+ <MarkdownEditorContextProvider
131
+ id={id}
132
+ editorView={editorView}
133
+ setEditorView={setEditorView}
134
+ extensions={extensions}
135
+ previewBlocks={previewBlocks}
136
+ toolbarState={toolbarState}
137
+ popoverMenu={menuProps}
138
+ viewMode={viewMode}
139
+ {...props}
140
+ >
141
+ {children}
142
+ </MarkdownEditorContextProvider>
143
+ );
144
+ };
120
145
 
121
- const { groupsRef, extension, ...commandMenuProps } = usePopoverMenu(options);
122
- const extensions = useMemo(() => [extensionsParam, extension].filter(isTruthy), [extensionsParam, extension]);
146
+ MarkdownEditorRoot.displayName = 'MarkdownEditor.Root';
147
+
148
+ //
149
+ // MarkdownEditor.Main
150
+ //
151
+
152
+ type MarkdownEditorContentProps = Omit<NaturalMarkdownEditorContentProps, 'id' | 'extensions' | 'toolbarState'>;
153
+
154
+ const MarkdownEditorContent = (props: MarkdownEditorContentProps) => {
155
+ const {
156
+ id,
157
+ extensions,
158
+ editorView,
159
+ setEditorView,
160
+ toolbarState,
161
+ viewMode,
162
+ popoverMenu: { groupsRef, ...menuProps },
163
+ } = useMarkdownEditorContext(MarkdownEditorContent.displayName);
123
164
 
124
165
  return (
125
- <PopoverMenuProvider view={viewRef.current} groups={groupsRef.current} {...commandMenuProps}>
126
- <MarkdownEditorImpl ref={viewRef} {...props} extensions={extensions} />
166
+ <PopoverMenuProvider view={editorView} groups={groupsRef.current} {...menuProps}>
167
+ <NaturalMarkdownEditorContent
168
+ {...props}
169
+ id={id}
170
+ extensions={extensions}
171
+ toolbarState={toolbarState}
172
+ viewMode={viewMode}
173
+ ref={setEditorView}
174
+ />
127
175
  </PopoverMenuProvider>
128
176
  );
129
177
  };
130
178
 
131
- const MarkdownEditorImpl = forwardRef<EditorView | null, MarkdownEditorProps>(
132
- (
133
- {
134
- id,
135
- role = 'article',
136
- initialValue,
137
- customActions,
138
- editorStateStore,
139
- extensions,
140
- extensionProviders,
141
- scrollPastEnd,
142
- toolbar,
143
- viewMode,
144
- onFileUpload,
145
- onViewModeChange,
146
- },
147
- forwardedRef,
148
- ) => {
149
- const { t } = useTranslation(meta.id);
150
- const { themeMode } = useThemeContext();
151
- const toolbarState = useEditorToolbarState({ viewMode });
152
- const formattingObserver = useFormattingState(toolbarState);
153
-
154
- // Restore last selection and scroll point.
155
- const { scrollTo, selection } = useMemo<EditorSelectionState>(() => editorStateStore?.getState(id) ?? {}, [id]);
156
-
157
- // Extensions from other plugins.
158
- // TODO(burdon): Reconcile with DocumentEditor.useExtensions.
159
- const providerExtensions = useMemo(
160
- () => extensionProviders?.flatMap((provider) => provider({})).filter(isNonNullable),
161
- [extensionProviders],
162
- );
163
-
164
- // TODO(wittjosiah): Factor out to file uploader plugin.
165
- // Drag files.
166
- const handleDrop: DNDOptions['onDrop'] = async (view, { files }) => {
167
- const file = files[0];
168
- const info = file && onFileUpload ? await onFileUpload(file) : undefined;
169
- if (info) {
170
- processEditorPayload(view, { type: 'image', data: info.url });
171
- }
172
- };
173
-
174
- const {
175
- parentRef,
176
- view: editorView,
177
- focusAttributes,
178
- } = useTextEditor(
179
- () => ({
180
- initialValue,
181
- extensions: [
182
- formattingObserver,
183
- createBasicExtensions({
184
- readOnly: viewMode === 'readonly',
185
- placeholder: t('editor placeholder'),
186
- scrollPastEnd: role === 'section' ? false : scrollPastEnd,
187
- search: true,
188
- }),
189
- createMarkdownExtensions(),
190
- createThemeExtensions({ themeMode, syntaxHighlighting: true, slots: editorSlots }),
191
- editorGutter,
192
- role !== 'section' && onFileUpload && dropFile({ onDrop: handleDrop }),
193
- providerExtensions,
194
- extensions,
195
- ].filter(isTruthy),
196
- ...(role !== 'section' && {
197
- id,
198
- scrollTo,
199
- selection,
200
- // TODO(wittjosiah): Autofocus based on layout is racy.
201
- // autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
202
- moveToEndOfLine: true,
203
- }),
204
- }),
205
- [id, formattingObserver, viewMode, themeMode, extensions, providerExtensions],
206
- );
207
-
208
- useImperativeHandle<EditorView | null, EditorView | null>(forwardedRef, () => editorView, [editorView]);
209
- useTest(editorView);
210
- useSelectCurrentThread(editorView, id);
211
-
212
- // https://react-dropzone.js.org/#src
213
- const { acceptedFiles, getInputProps, open } = useDropzone({
214
- multiple: false,
215
- noDrag: true,
216
- accept: {
217
- 'image/*': ['.jpg', '.jpeg', '.png', '.gif'],
218
- },
219
- });
220
-
221
- useEffect(() => {
222
- if (editorView && onFileUpload && acceptedFiles.length) {
223
- requestAnimationFrame(async () => {
224
- // NOTE: Clone file since react-dropzone patches in a non-standard `path` property, which confuses IPFS.
225
- const f = acceptedFiles[0];
226
- const file = new File([f], f.name, {
227
- type: f.type,
228
- lastModified: f.lastModified,
229
- });
230
-
231
- const info = await onFileUpload(file);
232
- if (info) {
233
- addLink({ url: info.url, image: true })(editorView);
234
- }
235
- });
236
- }
237
- }, [acceptedFiles, editorView, onFileUpload]);
238
-
239
- const getView = useCallback(() => {
240
- invariant(editorView);
241
- return editorView;
242
- }, [editorView]);
243
-
244
- const handleViewModeChange = useCallback(
245
- (mode: EditorViewMode) => onViewModeChange?.(id, mode),
246
- [id, onViewModeChange],
247
- );
248
-
249
- const handleImageUpload = useCallback(() => {
250
- if (onFileUpload) {
251
- open();
252
- }
253
- }, [onFileUpload]);
254
-
255
- return (
256
- <StackItem.Content toolbar={!!toolbar}>
257
- {toolbar && (
258
- <>
259
- <EditorToolbar
260
- attendableId={id}
261
- role={role}
262
- state={toolbarState}
263
- customActions={customActions}
264
- getView={getView}
265
- image={handleImageUpload}
266
- viewMode={handleViewModeChange}
267
- />
268
- <input {...getInputProps()} />
269
- </>
270
- )}
271
- <div
272
- role='none'
273
- ref={parentRef}
274
- data-testid='composer.markdownRoot'
275
- data-toolbar={toolbar ? 'enabled' : 'disabled'}
276
- className={stackItemContentEditorClassNames(role)}
277
- data-popover-collision-boundary={true}
278
- {...focusAttributes}
279
- />
280
- </StackItem.Content>
281
- );
282
- },
283
- );
284
-
285
- // Expose editor view for playwright tests.
286
- // TODO(wittjosiah): Find a better way to expose this or find a way to limit it to test runs.
287
- const useTest = (view: EditorView | null) => {
288
- useEffect(() => {
289
- const composer = (window as any).composer;
290
- if (composer) {
291
- composer.editorView = view;
292
- }
293
- }, [view]);
179
+ MarkdownEditorContent.displayName = 'MarkdownEditor.Content';
180
+
181
+ //
182
+ // MarkdownEditor.Toolbar
183
+ //
184
+
185
+ type MarkdownEditorToolbarProps = Omit<
186
+ NaturalMarkdownToolbarProps,
187
+ 'state' | 'editorView' | 'onFileUpload' | 'onViewModeChange'
188
+ >;
189
+
190
+ const MarkdownEditorToolbar = (props: MarkdownEditorToolbarProps) => {
191
+ const { toolbarState, ...rootProps } = useMarkdownEditorContext(MarkdownEditorToolbar.displayName);
192
+
193
+ return <NaturalMarkdownToolbar {...props} {...rootProps} state={toolbarState} />;
194
+ };
195
+
196
+ MarkdownEditorToolbar.displayName = 'MarkdownEditor.Toolbar';
197
+
198
+ //
199
+ // MarkdownEditor.Blocks (embedded objects)
200
+ //
201
+
202
+ type MarkdownEditorBlocksProps = {};
203
+
204
+ const MarkdownEditorBlocks = (_props: MarkdownEditorBlocksProps) => {
205
+ const { previewBlocks } = useMarkdownEditorContext(MarkdownEditorBlocks.displayName);
206
+
207
+ return (
208
+ <>
209
+ {previewBlocks.map(({ link, el }) => (
210
+ <PreviewBlock key={link.ref} link={link} el={el} />
211
+ ))}
212
+ </>
213
+ );
214
+ };
215
+
216
+ MarkdownEditorBlocks.displayName = 'MarkdownEditor.Blocks';
217
+
218
+ const PreviewBlock = ({ el, link }: PreviewBlock) => {
219
+ const client = useClient();
220
+ const dxn = DXN.parse(link.ref);
221
+ const subject = client.graph.ref(dxn).target;
222
+ const data = useMemo(() => ({ subject }), [subject]);
223
+
224
+ return createPortal(<Surface role='card--transclusion' data={data} limit={1} />, el);
225
+ };
226
+
227
+ //
228
+ // MarkdownEditor
229
+ //
230
+
231
+ export const MarkdownEditor = {
232
+ Root: MarkdownEditorRoot,
233
+ Content: MarkdownEditorContent,
234
+ Toolbar: MarkdownEditorToolbar,
235
+ Blocks: MarkdownEditorBlocks,
236
+ };
237
+
238
+ export type {
239
+ MarkdownEditorRootProps,
240
+ MarkdownEditorContentProps,
241
+ MarkdownEditorToolbarProps,
242
+ MarkdownEditorBlocksProps,
294
243
  };
@@ -0,0 +1,134 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type EditorView } from '@codemirror/view';
6
+ import React, { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react';
7
+
8
+ import { type Live } from '@dxos/live-object';
9
+ import { useDynamicRef, useThemeContext, useTranslation } from '@dxos/react-ui';
10
+ import {
11
+ type EditorSelectionState,
12
+ type EditorStateStore,
13
+ type EditorToolbarState,
14
+ type EditorViewMode,
15
+ type PopoverMenuGroup,
16
+ type UseTextEditorProps,
17
+ createBasicExtensions,
18
+ createMarkdownExtensions,
19
+ createThemeExtensions,
20
+ dropFile,
21
+ editorGutter,
22
+ editorSlots,
23
+ formattingListener,
24
+ processEditorPayload,
25
+ stackItemContentEditorClassNames,
26
+ useTextEditor,
27
+ } from '@dxos/react-ui-editor';
28
+ import { isTruthy } from '@dxos/util';
29
+
30
+ import { useSelectCurrentThread } from '../../hooks';
31
+ import { meta } from '../../meta';
32
+
33
+ import { type MarkdownEditorToolbarProps } from './MarkdownEditorToolbar';
34
+
35
+ export type MarkdownEditorContentProps = {
36
+ id: string;
37
+ role?: string;
38
+ viewMode?: EditorViewMode;
39
+ scrollPastEnd?: boolean;
40
+ slashCommandGroups?: PopoverMenuGroup[];
41
+ editorStateStore?: EditorStateStore;
42
+ toolbarState?: Live<EditorToolbarState>;
43
+ onLinkQuery?: (query?: string) => Promise<PopoverMenuGroup[]>;
44
+ } & (Pick<UseTextEditorProps, 'initialValue' | 'extensions'> & Pick<MarkdownEditorToolbarProps, 'onFileUpload'>);
45
+
46
+ export const MarkdownEditorContent = forwardRef<EditorView | null, MarkdownEditorContentProps>(
47
+ (
48
+ { id, role, initialValue, editorStateStore, toolbarState, extensions, viewMode, scrollPastEnd, onFileUpload },
49
+ forwardedRef,
50
+ ) => {
51
+ const { t } = useTranslation(meta.id);
52
+ const { themeMode } = useThemeContext();
53
+
54
+ // TODO(burdon): Toolbar state is not reactive.
55
+ const toolbarStateRef = useDynamicRef(toolbarState);
56
+
57
+ // Restore last selection and scroll point.
58
+ const { scrollTo, selection } = useMemo<EditorSelectionState>(() => editorStateStore?.getState(id) ?? {}, [id]);
59
+
60
+ const {
61
+ parentRef,
62
+ view: editorView,
63
+ focusAttributes,
64
+ } = useTextEditor(
65
+ () => ({
66
+ ...(role !== 'section' && {
67
+ id,
68
+ scrollTo,
69
+ selection,
70
+ // TODO(wittjosiah): Autofocus based on layout is racy.
71
+ // autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
72
+ moveToEndOfLine: true,
73
+ }),
74
+ initialValue,
75
+ extensions: [
76
+ createBasicExtensions({
77
+ readOnly: viewMode === 'readonly',
78
+ placeholder: t('editor placeholder'),
79
+ scrollPastEnd: scrollPastEnd && role !== 'section',
80
+ search: true,
81
+ }),
82
+ createThemeExtensions({
83
+ themeMode,
84
+ slots: editorSlots,
85
+ syntaxHighlighting: true,
86
+ }),
87
+ createMarkdownExtensions(),
88
+ formattingListener(() => toolbarStateRef.current),
89
+ editorGutter,
90
+ role !== 'section' &&
91
+ onFileUpload &&
92
+ dropFile({
93
+ // TODO(wittjosiah): Factor out to file uploader plugin.
94
+ onDrop: async (view, { files }) => {
95
+ const file = files[0];
96
+ const info = file && onFileUpload ? await onFileUpload(file) : undefined;
97
+ if (info) {
98
+ processEditorPayload(view, { type: 'image', data: info.url });
99
+ }
100
+ },
101
+ }),
102
+ extensions,
103
+ ].filter(isTruthy),
104
+ }),
105
+ [id, viewMode, themeMode, extensions],
106
+ );
107
+
108
+ useImperativeHandle<EditorView | null, EditorView | null>(forwardedRef, () => editorView, [editorView]);
109
+ useSelectCurrentThread(editorView, id);
110
+ useTest(editorView);
111
+
112
+ return (
113
+ <div
114
+ role='none'
115
+ ref={parentRef}
116
+ data-testid='composer.markdownRoot'
117
+ className={stackItemContentEditorClassNames(role)}
118
+ data-popover-collision-boundary={true}
119
+ {...focusAttributes}
120
+ />
121
+ );
122
+ },
123
+ );
124
+
125
+ // Expose editor view for playwright tests.
126
+ // TODO(wittjosiah): Find a better way to expose this or find a way to limit it to test runs.
127
+ const useTest = (view: EditorView | null) => {
128
+ useEffect(() => {
129
+ const composer = (window as any).composer;
130
+ if (composer) {
131
+ composer.editorView = view;
132
+ }
133
+ }, [view]);
134
+ };