@dxos/plugin-markdown 0.8.4-main.dedc0f3 → 0.8.4-main.e8ec1fe

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 (241) hide show
  1. package/dist/lib/browser/MarkdownCard-JYMDPKV5.mjs +12 -0
  2. package/dist/lib/browser/MarkdownContainer-Y75XSVBX.mjs +15 -0
  3. package/dist/lib/browser/{anchor-sort-E33BSTYF.mjs → anchor-sort-3HGPGCOO.mjs} +6 -7
  4. package/dist/lib/browser/anchor-sort-3HGPGCOO.mjs.map +7 -0
  5. package/dist/lib/browser/{app-graph-serializer-OX62DNPT.mjs → app-graph-serializer-POZN234F.mjs} +10 -10
  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-Z7P6JGGW.mjs → chunk-22XSSNBS.mjs} +7 -4
  10. package/dist/lib/browser/{chunk-Z7P6JGGW.mjs.map → chunk-22XSSNBS.mjs.map} +2 -2
  11. package/dist/lib/browser/chunk-2MLGSYRN.mjs +20 -0
  12. package/dist/lib/browser/chunk-2MLGSYRN.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/{chunk-LAVZ2W6X.mjs → chunk-GH6GQSBL.mjs} +9 -8
  16. package/dist/lib/browser/chunk-GH6GQSBL.mjs.map +7 -0
  17. package/dist/lib/browser/{chunk-OY6CGPOO.mjs → chunk-IBCHVMZW.mjs} +2 -2
  18. package/dist/lib/browser/{chunk-OY6CGPOO.mjs.map → chunk-IBCHVMZW.mjs.map} +2 -2
  19. package/dist/lib/browser/chunk-K3LXOU3E.mjs +827 -0
  20. package/dist/lib/browser/chunk-K3LXOU3E.mjs.map +7 -0
  21. package/dist/lib/browser/chunk-PBJLFIOX.mjs +96 -0
  22. package/dist/lib/browser/chunk-PBJLFIOX.mjs.map +7 -0
  23. package/dist/lib/browser/{chunk-BEE7VQPU.mjs → chunk-QYSEJ5GP.mjs} +13 -12
  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 +25 -24
  28. package/dist/lib/browser/index.mjs.map +3 -3
  29. package/dist/lib/browser/{intent-resolver-WDDH56JC.mjs → intent-resolver-Z5L7TXUK.mjs} +8 -8
  30. package/dist/lib/browser/intent-resolver-Z5L7TXUK.mjs.map +7 -0
  31. package/dist/lib/browser/meta.json +1 -1
  32. package/dist/lib/browser/{react-surface-L3NTMD4D.mjs → react-surface-GO5ZOKNN.mjs} +59 -63
  33. package/dist/lib/browser/react-surface-GO5ZOKNN.mjs.map +7 -0
  34. package/dist/lib/browser/{settings-AABBTB4Q.mjs → settings-TZUDB5EW.mjs} +5 -5
  35. package/dist/lib/browser/{settings-AABBTB4Q.mjs.map → settings-TZUDB5EW.mjs.map} +1 -1
  36. package/dist/lib/browser/{state-FTHQQX7V.mjs → state-BTUKVZHY.mjs} +5 -5
  37. package/dist/lib/browser/{state-FTHQQX7V.mjs.map → state-BTUKVZHY.mjs.map} +1 -1
  38. package/dist/lib/browser/toolkit.mjs +13 -0
  39. package/dist/lib/browser/toolkit.mjs.map +7 -0
  40. package/dist/lib/browser/types/index.mjs +2 -2
  41. package/dist/lib/node-esm/MarkdownCard-ZXPJLUYO.mjs +13 -0
  42. package/dist/lib/node-esm/MarkdownCard-ZXPJLUYO.mjs.map +7 -0
  43. package/dist/lib/node-esm/MarkdownContainer-YRDSRDCS.mjs +16 -0
  44. package/dist/lib/node-esm/MarkdownContainer-YRDSRDCS.mjs.map +7 -0
  45. package/dist/lib/node-esm/{anchor-sort-ALP2NH24.mjs → anchor-sort-PCDXEBJ2.mjs} +6 -7
  46. package/dist/lib/node-esm/anchor-sort-PCDXEBJ2.mjs.map +7 -0
  47. package/dist/lib/node-esm/{app-graph-serializer-56TD3BMX.mjs → app-graph-serializer-NF65JYAS.mjs} +10 -10
  48. package/dist/lib/node-esm/app-graph-serializer-NF65JYAS.mjs.map +7 -0
  49. package/dist/lib/node-esm/blueprint-definition-ENKJZYQS.mjs +14 -0
  50. package/dist/lib/node-esm/blueprint-definition-ENKJZYQS.mjs.map +7 -0
  51. package/dist/lib/node-esm/{chunk-J7A6TUB2.mjs → chunk-AMHACOXW.mjs} +7 -4
  52. package/dist/lib/node-esm/{chunk-J7A6TUB2.mjs.map → chunk-AMHACOXW.mjs.map} +2 -2
  53. package/dist/lib/node-esm/chunk-CT7CFX5G.mjs +828 -0
  54. package/dist/lib/node-esm/chunk-CT7CFX5G.mjs.map +7 -0
  55. package/dist/lib/node-esm/{chunk-CB2R4YIY.mjs → chunk-GMMVSXQ6.mjs} +2 -2
  56. package/dist/lib/node-esm/{chunk-CB2R4YIY.mjs.map → chunk-GMMVSXQ6.mjs.map} +2 -2
  57. package/dist/lib/node-esm/chunk-HAIEWPU7.mjs +151 -0
  58. package/dist/lib/node-esm/chunk-HAIEWPU7.mjs.map +7 -0
  59. package/dist/lib/node-esm/chunk-KCHUTL3Q.mjs +22 -0
  60. package/dist/lib/node-esm/chunk-KCHUTL3Q.mjs.map +7 -0
  61. package/dist/lib/node-esm/{chunk-FXILAQ5F.mjs → chunk-NGYJNQ6A.mjs} +13 -12
  62. package/dist/lib/node-esm/chunk-NGYJNQ6A.mjs.map +7 -0
  63. package/dist/lib/node-esm/chunk-PIOOG7A5.mjs +97 -0
  64. package/dist/lib/node-esm/chunk-PIOOG7A5.mjs.map +7 -0
  65. package/dist/lib/node-esm/{chunk-O6EXWGGS.mjs → chunk-PLZ7EVCT.mjs} +9 -8
  66. package/dist/lib/node-esm/chunk-PLZ7EVCT.mjs.map +7 -0
  67. package/dist/lib/node-esm/{chunk-VCG2U522.mjs → chunk-SHAMSMKQ.mjs} +4 -4
  68. package/dist/lib/node-esm/chunk-SHAMSMKQ.mjs.map +7 -0
  69. package/dist/lib/node-esm/index.mjs +25 -24
  70. package/dist/lib/node-esm/index.mjs.map +3 -3
  71. package/dist/lib/node-esm/{intent-resolver-2I5HKCUU.mjs → intent-resolver-6B3PFQ5F.mjs} +8 -8
  72. package/dist/lib/node-esm/intent-resolver-6B3PFQ5F.mjs.map +7 -0
  73. package/dist/lib/node-esm/meta.json +1 -1
  74. package/dist/lib/node-esm/{react-surface-YZSZFR5D.mjs → react-surface-I46BPCWT.mjs} +59 -63
  75. package/dist/lib/node-esm/react-surface-I46BPCWT.mjs.map +7 -0
  76. package/dist/lib/node-esm/{settings-CXGR6DH4.mjs → settings-CJ3T5EX4.mjs} +5 -5
  77. package/dist/lib/node-esm/{settings-CXGR6DH4.mjs.map → settings-CJ3T5EX4.mjs.map} +1 -1
  78. package/dist/lib/node-esm/{state-NWMQ3XAI.mjs → state-K6EH7SRZ.mjs} +5 -5
  79. package/dist/lib/node-esm/{state-NWMQ3XAI.mjs.map → state-K6EH7SRZ.mjs.map} +1 -1
  80. package/dist/lib/node-esm/toolkit.mjs +14 -0
  81. package/dist/lib/node-esm/toolkit.mjs.map +7 -0
  82. package/dist/lib/node-esm/types/index.mjs +2 -2
  83. package/dist/types/src/MarkdownPlugin.d.ts +1 -1
  84. package/dist/types/src/MarkdownPlugin.d.ts.map +1 -1
  85. package/dist/types/src/capabilities/anchor-sort.d.ts +2 -4
  86. package/dist/types/src/capabilities/anchor-sort.d.ts.map +1 -1
  87. package/dist/types/src/capabilities/artifact-definition.d.ts.map +1 -1
  88. package/dist/types/src/capabilities/blueprint-definition.d.ts +5 -3
  89. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -1
  90. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  91. package/dist/types/src/capabilities/index.d.ts +1 -5
  92. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  93. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  94. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  95. package/dist/types/src/components/MarkdownCard/MarkdownCard.d.ts +3 -3
  96. package/dist/types/src/components/MarkdownCard/MarkdownCard.d.ts.map +1 -1
  97. package/dist/types/src/components/MarkdownCard/MarkdownCard.stories.d.ts +0 -1
  98. package/dist/types/src/components/MarkdownCard/MarkdownCard.stories.d.ts.map +1 -1
  99. package/dist/types/src/components/MarkdownContainer.d.ts +8 -12
  100. package/dist/types/src/components/MarkdownContainer.d.ts.map +1 -1
  101. package/dist/types/src/components/MarkdownContainer.stories.d.ts +7 -4
  102. package/dist/types/src/components/MarkdownContainer.stories.d.ts.map +1 -1
  103. package/dist/types/src/components/MarkdownEditor/FileUpload.d.ts +11 -0
  104. package/dist/types/src/components/MarkdownEditor/FileUpload.d.ts.map +1 -0
  105. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts +42 -23
  106. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts.map +1 -1
  107. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.stories.d.ts +5 -110
  108. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.stories.d.ts.map +1 -1
  109. package/dist/types/src/components/MarkdownEditor/MarkdownEditorContent.d.ts +26 -0
  110. package/dist/types/src/components/MarkdownEditor/MarkdownEditorContent.d.ts.map +1 -0
  111. package/dist/types/src/components/MarkdownEditor/MarkdownEditorToolbar.d.ts +12 -0
  112. package/dist/types/src/components/MarkdownEditor/MarkdownEditorToolbar.d.ts.map +1 -0
  113. package/dist/types/src/components/Suggestions.stories.d.ts +1 -2
  114. package/dist/types/src/components/Suggestions.stories.d.ts.map +1 -1
  115. package/dist/types/src/components/index.d.ts +3 -1
  116. package/dist/types/src/components/index.d.ts.map +1 -1
  117. package/dist/types/src/functions/create.d.ts +8 -0
  118. package/dist/types/src/functions/create.d.ts.map +1 -0
  119. package/dist/types/src/functions/create.test.d.ts +2 -0
  120. package/dist/types/src/functions/create.test.d.ts.map +1 -0
  121. package/dist/types/src/functions/index.d.ts +17 -2
  122. package/dist/types/src/functions/index.d.ts.map +1 -1
  123. package/dist/types/src/functions/open.d.ts +1 -1
  124. package/dist/types/src/functions/open.d.ts.map +1 -1
  125. package/dist/types/src/functions/{diff.d.ts → update.d.ts} +2 -2
  126. package/dist/types/src/functions/update.d.ts.map +1 -0
  127. package/dist/types/src/functions/update.test.d.ts +2 -0
  128. package/dist/types/src/functions/update.test.d.ts.map +1 -0
  129. package/dist/types/src/hooks/index.d.ts +3 -0
  130. package/dist/types/src/hooks/index.d.ts.map +1 -1
  131. package/dist/types/src/hooks/useEditorMenuOptions.d.ts +9 -0
  132. package/dist/types/src/hooks/useEditorMenuOptions.d.ts.map +1 -0
  133. package/dist/types/src/hooks/useExtensions.d.ts +21 -0
  134. package/dist/types/src/hooks/useExtensions.d.ts.map +1 -0
  135. package/dist/types/src/hooks/useLinkQuery.d.ts +4 -0
  136. package/dist/types/src/hooks/useLinkQuery.d.ts.map +1 -0
  137. package/dist/types/src/hooks/useSelectCurrentThread.d.ts +1 -1
  138. package/dist/types/src/hooks/useSelectCurrentThread.d.ts.map +1 -1
  139. package/dist/types/src/testing.d.ts +6 -0
  140. package/dist/types/src/testing.d.ts.map +1 -0
  141. package/dist/types/src/toolkit.d.ts +3 -0
  142. package/dist/types/src/toolkit.d.ts.map +1 -0
  143. package/dist/types/src/translations.d.ts +3 -0
  144. package/dist/types/src/translations.d.ts.map +1 -1
  145. package/dist/types/src/types/Markdown.d.ts +6 -4
  146. package/dist/types/src/types/Markdown.d.ts.map +1 -1
  147. package/dist/types/src/types/MarkdownAction.d.ts +2 -1
  148. package/dist/types/src/types/MarkdownAction.d.ts.map +1 -1
  149. package/dist/types/src/types/index.d.ts.map +1 -1
  150. package/dist/types/src/util.d.ts +3 -3
  151. package/dist/types/src/util.d.ts.map +1 -1
  152. package/dist/types/tsconfig.tsbuildinfo +1 -1
  153. package/package.json +71 -55
  154. package/src/MarkdownPlugin.tsx +99 -101
  155. package/src/capabilities/anchor-sort.ts +3 -3
  156. package/src/capabilities/app-graph-serializer.ts +5 -5
  157. package/src/capabilities/artifact-definition.ts +6 -5
  158. package/src/capabilities/blueprint-definition.ts +30 -26
  159. package/src/capabilities/capabilities.ts +1 -0
  160. package/src/capabilities/index.ts +1 -2
  161. package/src/capabilities/intent-resolver.ts +3 -2
  162. package/src/capabilities/react-surface.tsx +44 -66
  163. package/src/components/MarkdownCard/MarkdownCard.stories.tsx +6 -9
  164. package/src/components/MarkdownCard/MarkdownCard.tsx +52 -38
  165. package/src/components/MarkdownContainer.stories.tsx +74 -38
  166. package/src/components/MarkdownContainer.tsx +78 -220
  167. package/src/components/MarkdownEditor/FileUpload.tsx +63 -0
  168. package/src/components/MarkdownEditor/MarkdownEditor.stories.tsx +55 -35
  169. package/src/components/MarkdownEditor/MarkdownEditor.tsx +221 -272
  170. package/src/components/MarkdownEditor/MarkdownEditorContent.tsx +136 -0
  171. package/src/components/MarkdownEditor/MarkdownEditorToolbar.tsx +63 -0
  172. package/src/components/Suggestions.stories.tsx +41 -34
  173. package/src/components/index.ts +3 -1
  174. package/src/functions/create.conversations.json +1 -0
  175. package/src/functions/create.test.ts +128 -0
  176. package/src/functions/create.ts +34 -0
  177. package/src/functions/index.ts +9 -2
  178. package/src/functions/open.ts +4 -2
  179. package/src/functions/update.conversations.json +1 -0
  180. package/src/functions/update.test.ts +151 -0
  181. package/src/functions/{diff.ts → update.ts} +4 -2
  182. package/src/hooks/index.ts +3 -0
  183. package/src/hooks/useEditorMenuOptions.ts +71 -0
  184. package/src/{extensions.tsx → hooks/useExtensions.tsx} +60 -81
  185. package/src/hooks/useLinkQuery.ts +83 -0
  186. package/src/hooks/useSelectCurrentThread.tsx +15 -5
  187. package/src/meta.ts +3 -3
  188. package/src/testing.ts +27 -0
  189. package/src/toolkit.ts +6 -0
  190. package/src/translations.ts +3 -0
  191. package/src/types/Markdown.ts +9 -7
  192. package/src/types/MarkdownAction.ts +1 -1
  193. package/src/types/index.ts +1 -0
  194. package/src/util.tsx +10 -5
  195. package/dist/lib/browser/MarkdownCard-JLUQITYK.mjs +0 -80
  196. package/dist/lib/browser/MarkdownCard-JLUQITYK.mjs.map +0 -7
  197. package/dist/lib/browser/MarkdownContainer-JW7TRDSA.mjs +0 -755
  198. package/dist/lib/browser/MarkdownContainer-JW7TRDSA.mjs.map +0 -7
  199. package/dist/lib/browser/anchor-sort-E33BSTYF.mjs.map +0 -7
  200. package/dist/lib/browser/app-graph-serializer-OX62DNPT.mjs.map +0 -7
  201. package/dist/lib/browser/blueprint-definition-5YKFUHRU.mjs +0 -11
  202. package/dist/lib/browser/chunk-BEE7VQPU.mjs.map +0 -7
  203. package/dist/lib/browser/chunk-F6JJLKLN.mjs +0 -102
  204. package/dist/lib/browser/chunk-F6JJLKLN.mjs.map +0 -7
  205. package/dist/lib/browser/chunk-LAVZ2W6X.mjs.map +0 -7
  206. package/dist/lib/browser/chunk-ODB2PTBP.mjs.map +0 -7
  207. package/dist/lib/browser/chunk-SUOK6YMI.mjs +0 -22
  208. package/dist/lib/browser/chunk-SUOK6YMI.mjs.map +0 -7
  209. package/dist/lib/browser/intent-resolver-WDDH56JC.mjs.map +0 -7
  210. package/dist/lib/browser/react-surface-L3NTMD4D.mjs.map +0 -7
  211. package/dist/lib/browser/toolkit-2AJTHG74.mjs +0 -74
  212. package/dist/lib/browser/toolkit-2AJTHG74.mjs.map +0 -7
  213. package/dist/lib/node-esm/MarkdownCard-XL5EVSJ7.mjs +0 -81
  214. package/dist/lib/node-esm/MarkdownCard-XL5EVSJ7.mjs.map +0 -7
  215. package/dist/lib/node-esm/MarkdownContainer-HRGQXIXP.mjs +0 -756
  216. package/dist/lib/node-esm/MarkdownContainer-HRGQXIXP.mjs.map +0 -7
  217. package/dist/lib/node-esm/anchor-sort-ALP2NH24.mjs.map +0 -7
  218. package/dist/lib/node-esm/app-graph-serializer-56TD3BMX.mjs.map +0 -7
  219. package/dist/lib/node-esm/blueprint-definition-GVW67KGV.mjs +0 -12
  220. package/dist/lib/node-esm/chunk-DVK63TD3.mjs +0 -103
  221. package/dist/lib/node-esm/chunk-DVK63TD3.mjs.map +0 -7
  222. package/dist/lib/node-esm/chunk-FXILAQ5F.mjs.map +0 -7
  223. package/dist/lib/node-esm/chunk-JC2YWB5D.mjs +0 -24
  224. package/dist/lib/node-esm/chunk-JC2YWB5D.mjs.map +0 -7
  225. package/dist/lib/node-esm/chunk-O6EXWGGS.mjs.map +0 -7
  226. package/dist/lib/node-esm/chunk-VCG2U522.mjs.map +0 -7
  227. package/dist/lib/node-esm/intent-resolver-2I5HKCUU.mjs.map +0 -7
  228. package/dist/lib/node-esm/react-surface-YZSZFR5D.mjs.map +0 -7
  229. package/dist/lib/node-esm/toolkit-RC44I2MI.mjs +0 -75
  230. package/dist/lib/node-esm/toolkit-RC44I2MI.mjs.map +0 -7
  231. package/dist/types/src/capabilities/toolkit.d.ts +0 -4
  232. package/dist/types/src/capabilities/toolkit.d.ts.map +0 -1
  233. package/dist/types/src/components/Toolbar.stories.d.ts +0 -48
  234. package/dist/types/src/components/Toolbar.stories.d.ts.map +0 -1
  235. package/dist/types/src/extensions.d.ts +0 -22
  236. package/dist/types/src/extensions.d.ts.map +0 -1
  237. package/dist/types/src/functions/diff.d.ts.map +0 -1
  238. package/src/capabilities/toolkit.ts +0 -47
  239. package/src/components/Toolbar.stories.tsx +0 -118
  240. /package/dist/lib/browser/{blueprint-definition-5YKFUHRU.mjs.map → MarkdownCard-JYMDPKV5.mjs.map} +0 -0
  241. /package/dist/lib/{node-esm/blueprint-definition-GVW67KGV.mjs.map → browser/MarkdownContainer-Y75XSVBX.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 { 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
- CommandMenu,
14
- type CommandMenuGroup,
15
- type DNDOptions,
16
- Domino,
17
- type EditorInputMode,
18
- type EditorSelectionState,
19
- type EditorStateStore,
20
- EditorToolbar,
21
- type EditorToolbarActionGraphProps,
22
- type EditorViewMode,
23
- RefPopover,
24
- type UseCommandMenuOptions,
25
- type UseTextEditorProps,
26
- addLink,
27
- coreSlashCommands,
28
- createBasicExtensions,
29
- createMarkdownExtensions,
30
- createThemeExtensions,
31
- dropFile,
32
- editorGutter,
33
- editorSlots,
34
- filterItems,
35
- linkSlashCommands,
36
- processEditorPayload,
37
- stackItemContentEditorClassNames,
38
- useCommandMenu,
39
- useEditorToolbarState,
40
- useFormattingState,
41
- useTextEditor,
16
+ EditorMenuProvider,
17
+ type EditorToolbarState,
18
+ type PreviewBlock,
19
+ type PreviewOptions,
20
+ type UseEditorMenu,
21
+ useEditorMenu,
22
+ useEditorToolbar,
42
23
  } from '@dxos/react-ui-editor';
43
- import { StackItem } from '@dxos/react-ui-stack';
44
- import { isNonNullable, isNotFalsy } from '@dxos/util';
24
+ import { isNonNullable } from '@dxos/util';
25
+
26
+ import {
27
+ type DocumentType,
28
+ type ExtensionsOptions,
29
+ type UseEditorMenuOptionsProps,
30
+ useEditorMenuOptions,
31
+ useExtensions,
32
+ } from '../../hooks';
45
33
 
46
- import { useSelectCurrentThread } from '../../hooks';
47
- import { meta } from '../../meta';
48
- 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
+ //
49
46
 
50
- export type MarkdownEditorProps = {
47
+ type MarkdownEditorContextValue = {
51
48
  id: string;
52
- role?: string;
53
- toolbar?: boolean;
54
- inputMode?: EditorInputMode;
55
- scrollPastEnd?: boolean;
56
- slashCommandGroups?: CommandMenuGroup[];
57
- customActions?: EditorToolbarActionGraphProps['customActions'];
58
- // TODO(wittjosiah): Generalize custom toolbar actions (e.g. comment, upload, etc.)
59
- viewMode?: EditorViewMode;
60
- editorStateStore?: EditorStateStore;
61
- onViewModeChange?: (id: string, mode: EditorViewMode) => void;
62
- onLinkQuery?: (query?: string) => Promise<CommandMenuGroup[]>;
63
- onFileUpload?: (file: File) => Promise<FileInfo | undefined>;
64
- } & Pick<UseTextEditorProps, 'initialValue' | 'extensions'> &
65
- Partial<Pick<MarkdownPluginState, 'extensionProviders'>>;
66
-
67
- /**
68
- * Base markdown editor component.
69
- * This component provides all the features of the markdown editor that do no depend on ECHO.
70
- * This allows it to be used as a common editor for markdown content on arbitrary backends (e.g. files).
71
- */
72
- export const MarkdownEditor = ({
49
+ setEditorView: (view: EditorView) => void;
50
+ extensions: Extension[];
51
+ previewBlocks: PreviewBlock[];
52
+ toolbarState: Live<EditorToolbarState>;
53
+ popoverMenu: Omit<UseEditorMenu, '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<UseEditorMenuOptionsProps, '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,
73
81
  extensions: extensionsParam,
74
82
  slashCommandGroups,
75
83
  onLinkQuery,
76
84
  ...props
77
- }: MarkdownEditorProps) => {
78
- const { t } = useTranslation();
79
- const viewRef = useRef<EditorView>();
80
-
81
- const getMenu = useCallback(
82
- (trigger: string, query?: string) => {
83
- switch (trigger) {
84
- case '@':
85
- return onLinkQuery?.(query) ?? [];
86
- case '/':
87
- default:
88
- return filterItems([coreSlashCommands, linkSlashCommands, ...(slashCommandGroups ?? [])], (item) =>
89
- query ? toLocalizedString(item.label, t).toLowerCase().includes(query.toLowerCase()) : true,
90
- );
91
- }
92
- },
93
- [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
+ [],
94
100
  );
95
101
 
96
- const options = useMemo<UseCommandMenuOptions>(() => {
97
- const trigger = onLinkQuery ? ['/', '@'] : ['/'];
98
- return {
99
- viewRef,
100
- trigger,
101
- placeholder: {
102
- delay: 3_000,
103
- content: () =>
104
- Domino.of('div')
105
- .child(
106
- Domino.of('span').text('Press'),
107
- ...trigger.map((text) =>
108
- Domino.of('span')
109
- .classNames('border border-separator rounded-sm mx-1 px-1.5 pt-[1px] pb-[2px]')
110
- .text(text),
111
- ),
112
- Domino.of('span').text('for commands.'),
113
- )
114
- .build(),
115
- },
116
- getMenu,
117
- };
118
- }, [getMenu]);
102
+ // Toolbar state.
103
+ const toolbarState = useEditorToolbar({ viewMode });
104
+
105
+ // Context menu.
106
+ const menuOptions = useEditorMenuOptions({
107
+ editorView,
108
+ slashCommandGroups,
109
+ onLinkQuery,
110
+ });
111
+ const { extension: menuExtension, ...menuProps } = useEditorMenu(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
+ };
119
145
 
120
- const { commandMenu, groupsRef, currentItem, onSelect, ...refPopoverProps } = useCommandMenu(options);
121
- const extensions = useMemo(() => [extensionsParam, commandMenu].filter(isNotFalsy), [extensionsParam, commandMenu]);
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);
122
164
 
123
165
  return (
124
- <RefPopover modal={false} {...refPopoverProps}>
125
- <MarkdownEditorImpl ref={viewRef} {...props} extensions={extensions} />
126
- <CommandMenu groups={groupsRef.current} currentItem={currentItem} onSelect={onSelect} />
127
- </RefPopover>
166
+ <EditorMenuProvider 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
+ />
175
+ </EditorMenuProvider>
128
176
  );
129
177
  };
130
178
 
131
- const MarkdownEditorImpl = forwardRef<EditorView | undefined, 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(isNotFalsy),
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(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) => {
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,136 @@
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 EditorMenuGroup,
12
+ type EditorSelectionState,
13
+ type EditorStateStore,
14
+ type EditorToolbarState,
15
+ type EditorViewMode,
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?: EditorMenuGroup[];
41
+ editorStateStore?: EditorStateStore;
42
+ toolbarState?: Live<EditorToolbarState>;
43
+ onLinkQuery?: (query?: string) => Promise<EditorMenuGroup[]>;
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
+ selectionEnd: 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
+
110
+ useSelectCurrentThread(editorView, id);
111
+
112
+ useTest(editorView);
113
+
114
+ return (
115
+ <div
116
+ role='none'
117
+ ref={parentRef}
118
+ data-testid='composer.markdownRoot'
119
+ className={stackItemContentEditorClassNames(role)}
120
+ data-popover-collision-boundary={true}
121
+ {...focusAttributes}
122
+ />
123
+ );
124
+ },
125
+ );
126
+
127
+ // Expose editor view for playwright tests.
128
+ // TODO(wittjosiah): Find a better way to expose this or find a way to limit it to test runs.
129
+ const useTest = (view: EditorView | null) => {
130
+ useEffect(() => {
131
+ const composer = (window as any).composer;
132
+ if (composer) {
133
+ composer.editorView = view;
134
+ }
135
+ }, [view]);
136
+ };