@dxos/plugin-markdown 0.8.4-main.5ea62a8 → 0.8.4-main.ae835ea

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 (206) hide show
  1. package/dist/lib/browser/MarkdownCard-SLM6QZYC.mjs +12 -0
  2. package/dist/lib/browser/MarkdownContainer-UZFQC6XY.mjs +15 -0
  3. package/dist/lib/browser/{anchor-sort-E33BSTYF.mjs → anchor-sort-4XPPLMZS.mjs} +5 -5
  4. package/dist/lib/browser/{anchor-sort-E33BSTYF.mjs.map → anchor-sort-4XPPLMZS.mjs.map} +1 -1
  5. package/dist/lib/browser/{app-graph-serializer-OX62DNPT.mjs → app-graph-serializer-QQFV4K6P.mjs} +8 -8
  6. package/dist/lib/browser/app-graph-serializer-QQFV4K6P.mjs.map +7 -0
  7. package/dist/lib/browser/blueprint-definition-BC5R3T72.mjs +11 -0
  8. package/dist/lib/browser/blueprint-definition-BC5R3T72.mjs.map +7 -0
  9. package/dist/lib/browser/{chunk-LAVZ2W6X.mjs → chunk-2LLVTQCK.mjs} +6 -5
  10. package/dist/lib/browser/chunk-2LLVTQCK.mjs.map +7 -0
  11. package/dist/lib/browser/{MarkdownCard-JLUQITYK.mjs → chunk-3VILQLA4.mjs} +46 -31
  12. package/dist/lib/browser/chunk-3VILQLA4.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-5AYTOIUF.mjs +822 -0
  14. package/dist/lib/browser/chunk-5AYTOIUF.mjs.map +7 -0
  15. package/dist/lib/browser/{chunk-Z7P6JGGW.mjs → chunk-A3CQYGCN.mjs} +7 -4
  16. package/dist/lib/browser/{chunk-Z7P6JGGW.mjs.map → chunk-A3CQYGCN.mjs.map} +2 -2
  17. package/dist/lib/browser/{chunk-ODB2PTBP.mjs → chunk-BQTYJOFB.mjs} +4 -4
  18. package/dist/lib/browser/chunk-BQTYJOFB.mjs.map +7 -0
  19. package/dist/lib/browser/chunk-GLEYXJX3.mjs +22 -0
  20. package/dist/lib/browser/{chunk-D7UYVHL6.mjs.map → chunk-GLEYXJX3.mjs.map} +3 -3
  21. package/dist/lib/browser/{chunk-OY6CGPOO.mjs → chunk-IBCHVMZW.mjs} +2 -2
  22. package/dist/lib/browser/{chunk-OY6CGPOO.mjs.map → chunk-IBCHVMZW.mjs.map} +2 -2
  23. package/dist/lib/browser/{chunk-ZVVKLB5L.mjs → chunk-JAETS5LE.mjs} +33 -48
  24. package/dist/lib/browser/chunk-JAETS5LE.mjs.map +7 -0
  25. package/dist/lib/browser/{chunk-BEE7VQPU.mjs → chunk-UKTCPHLI.mjs} +9 -8
  26. package/dist/lib/browser/chunk-UKTCPHLI.mjs.map +7 -0
  27. package/dist/lib/browser/index.mjs +21 -13
  28. package/dist/lib/browser/index.mjs.map +3 -3
  29. package/dist/lib/browser/{intent-resolver-WDDH56JC.mjs → intent-resolver-VQGMBNXZ.mjs} +7 -7
  30. package/dist/lib/browser/intent-resolver-VQGMBNXZ.mjs.map +7 -0
  31. package/dist/lib/browser/meta.json +1 -1
  32. package/dist/lib/browser/{react-surface-LN2XK2UN.mjs → react-surface-WOMJOPJE.mjs} +52 -50
  33. package/dist/lib/browser/react-surface-WOMJOPJE.mjs.map +7 -0
  34. package/dist/lib/browser/{settings-AABBTB4Q.mjs → settings-LBXJHVBU.mjs} +5 -5
  35. package/dist/lib/browser/{settings-AABBTB4Q.mjs.map → settings-LBXJHVBU.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-YPIVDB4A.mjs +66 -0
  39. package/dist/lib/browser/toolkit-YPIVDB4A.mjs.map +7 -0
  40. package/dist/lib/browser/types/index.mjs +2 -2
  41. package/dist/lib/node-esm/MarkdownCard-MCWEFW4F.mjs +13 -0
  42. package/dist/lib/node-esm/MarkdownCard-MCWEFW4F.mjs.map +7 -0
  43. package/dist/lib/node-esm/MarkdownContainer-KAQOK7K5.mjs +16 -0
  44. package/dist/lib/node-esm/MarkdownContainer-KAQOK7K5.mjs.map +7 -0
  45. package/dist/lib/node-esm/{anchor-sort-ALP2NH24.mjs → anchor-sort-4SXYVYXT.mjs} +5 -5
  46. package/dist/lib/node-esm/{anchor-sort-ALP2NH24.mjs.map → anchor-sort-4SXYVYXT.mjs.map} +1 -1
  47. package/dist/lib/node-esm/{app-graph-serializer-56TD3BMX.mjs → app-graph-serializer-KBVRLQN2.mjs} +8 -8
  48. package/dist/lib/node-esm/app-graph-serializer-KBVRLQN2.mjs.map +7 -0
  49. package/dist/lib/node-esm/blueprint-definition-FPNOTEYC.mjs +12 -0
  50. package/dist/lib/node-esm/blueprint-definition-FPNOTEYC.mjs.map +7 -0
  51. package/dist/lib/node-esm/chunk-2Q4WCKWT.mjs +823 -0
  52. package/dist/lib/node-esm/chunk-2Q4WCKWT.mjs.map +7 -0
  53. package/dist/lib/node-esm/{chunk-Y422WR6A.mjs → chunk-DOB2MJAX.mjs} +33 -48
  54. package/dist/lib/node-esm/chunk-DOB2MJAX.mjs.map +7 -0
  55. package/dist/lib/node-esm/chunk-GGKPPGWA.mjs +24 -0
  56. package/dist/lib/node-esm/{chunk-JPXFCBC4.mjs.map → chunk-GGKPPGWA.mjs.map} +3 -3
  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-FXILAQ5F.mjs → chunk-JELROKGD.mjs} +9 -8
  60. package/dist/lib/node-esm/chunk-JELROKGD.mjs.map +7 -0
  61. package/dist/lib/node-esm/{chunk-O6EXWGGS.mjs → chunk-QH4MC5BE.mjs} +6 -5
  62. package/dist/lib/node-esm/chunk-QH4MC5BE.mjs.map +7 -0
  63. package/dist/lib/node-esm/{chunk-VCG2U522.mjs → chunk-SHAMSMKQ.mjs} +4 -4
  64. package/dist/lib/node-esm/chunk-SHAMSMKQ.mjs.map +7 -0
  65. package/dist/lib/node-esm/{chunk-J7A6TUB2.mjs → chunk-SJ2QRGPM.mjs} +7 -4
  66. package/dist/lib/node-esm/{chunk-J7A6TUB2.mjs.map → chunk-SJ2QRGPM.mjs.map} +2 -2
  67. package/dist/lib/node-esm/{MarkdownCard-XL5EVSJ7.mjs → chunk-YYSASY7X.mjs} +46 -31
  68. package/dist/lib/node-esm/chunk-YYSASY7X.mjs.map +7 -0
  69. package/dist/lib/node-esm/index.mjs +21 -13
  70. package/dist/lib/node-esm/index.mjs.map +3 -3
  71. package/dist/lib/node-esm/{intent-resolver-2I5HKCUU.mjs → intent-resolver-Q4XVI5EX.mjs} +7 -7
  72. package/dist/lib/node-esm/intent-resolver-Q4XVI5EX.mjs.map +7 -0
  73. package/dist/lib/node-esm/meta.json +1 -1
  74. package/dist/lib/node-esm/{react-surface-DJGGKYBD.mjs → react-surface-FAMZTAXK.mjs} +52 -50
  75. package/dist/lib/node-esm/react-surface-FAMZTAXK.mjs.map +7 -0
  76. package/dist/lib/node-esm/{settings-CXGR6DH4.mjs → settings-2YRA67H6.mjs} +5 -5
  77. package/dist/lib/node-esm/{settings-CXGR6DH4.mjs.map → settings-2YRA67H6.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-36BFLIR3.mjs +67 -0
  81. package/dist/lib/node-esm/toolkit-36BFLIR3.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/artifact-definition.d.ts.map +1 -1
  86. package/dist/types/src/capabilities/blueprint-definition.d.ts +1 -1
  87. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -1
  88. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  89. package/dist/types/src/capabilities/index.d.ts +1 -0
  90. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  91. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  92. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  93. package/dist/types/src/capabilities/toolkit.d.ts +20 -0
  94. package/dist/types/src/capabilities/toolkit.d.ts.map +1 -0
  95. package/dist/types/src/components/MarkdownCard/MarkdownCard.d.ts.map +1 -1
  96. package/dist/types/src/components/MarkdownCard/MarkdownCard.stories.d.ts +0 -1
  97. package/dist/types/src/components/MarkdownCard/MarkdownCard.stories.d.ts.map +1 -1
  98. package/dist/types/src/components/MarkdownContainer.d.ts +8 -12
  99. package/dist/types/src/components/MarkdownContainer.d.ts.map +1 -1
  100. package/dist/types/src/components/MarkdownContainer.stories.d.ts +7 -4
  101. package/dist/types/src/components/MarkdownContainer.stories.d.ts.map +1 -1
  102. package/dist/types/src/components/MarkdownEditor/FileUpload.d.ts +11 -0
  103. package/dist/types/src/components/MarkdownEditor/FileUpload.d.ts.map +1 -0
  104. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts +42 -23
  105. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts.map +1 -1
  106. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.stories.d.ts +5 -110
  107. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.stories.d.ts.map +1 -1
  108. package/dist/types/src/components/MarkdownEditor/MarkdownEditorContent.d.ts +26 -0
  109. package/dist/types/src/components/MarkdownEditor/MarkdownEditorContent.d.ts.map +1 -0
  110. package/dist/types/src/components/MarkdownEditor/MarkdownEditorToolbar.d.ts +12 -0
  111. package/dist/types/src/components/MarkdownEditor/MarkdownEditorToolbar.d.ts.map +1 -0
  112. package/dist/types/src/components/Suggestions.stories.d.ts +1 -2
  113. package/dist/types/src/components/Suggestions.stories.d.ts.map +1 -1
  114. package/dist/types/src/components/index.d.ts +3 -1
  115. package/dist/types/src/components/index.d.ts.map +1 -1
  116. package/dist/types/src/functions/diff.d.ts.map +1 -1
  117. package/dist/types/src/functions/index.d.ts +0 -1
  118. package/dist/types/src/functions/index.d.ts.map +1 -1
  119. package/dist/types/src/functions/open.d.ts.map +1 -1
  120. package/dist/types/src/hooks/index.d.ts +3 -0
  121. package/dist/types/src/hooks/index.d.ts.map +1 -1
  122. package/dist/types/src/hooks/useExtensions.d.ts +21 -0
  123. package/dist/types/src/hooks/useExtensions.d.ts.map +1 -0
  124. package/dist/types/src/hooks/useLinkQuery.d.ts +4 -0
  125. package/dist/types/src/hooks/useLinkQuery.d.ts.map +1 -0
  126. package/dist/types/src/hooks/usePopoverMenuOptions.d.ts +9 -0
  127. package/dist/types/src/hooks/usePopoverMenuOptions.d.ts.map +1 -0
  128. package/dist/types/src/hooks/useSelectCurrentThread.d.ts +1 -1
  129. package/dist/types/src/hooks/useSelectCurrentThread.d.ts.map +1 -1
  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 +6 -4
  133. package/dist/types/src/types/Markdown.d.ts.map +1 -1
  134. package/dist/types/src/types/MarkdownAction.d.ts +3 -2
  135. package/dist/types/src/types/MarkdownAction.d.ts.map +1 -1
  136. package/dist/types/src/util.d.ts +1 -1
  137. package/dist/types/src/util.d.ts.map +1 -1
  138. package/dist/types/tsconfig.tsbuildinfo +1 -1
  139. package/package.json +58 -55
  140. package/src/MarkdownPlugin.tsx +100 -93
  141. package/src/capabilities/app-graph-serializer.ts +2 -2
  142. package/src/capabilities/artifact-definition.ts +4 -3
  143. package/src/capabilities/blueprint-definition.ts +5 -5
  144. package/src/capabilities/capabilities.ts +1 -0
  145. package/src/capabilities/index.ts +1 -0
  146. package/src/capabilities/intent-resolver.ts +2 -1
  147. package/src/capabilities/react-surface.tsx +35 -57
  148. package/src/capabilities/toolkit.ts +53 -0
  149. package/src/components/MarkdownCard/MarkdownCard.stories.tsx +3 -6
  150. package/src/components/MarkdownCard/MarkdownCard.tsx +42 -32
  151. package/src/components/MarkdownContainer.stories.tsx +75 -38
  152. package/src/components/MarkdownContainer.tsx +84 -218
  153. package/src/components/MarkdownEditor/FileUpload.tsx +63 -0
  154. package/src/components/MarkdownEditor/MarkdownEditor.stories.tsx +56 -35
  155. package/src/components/MarkdownEditor/MarkdownEditor.tsx +220 -272
  156. package/src/components/MarkdownEditor/MarkdownEditorContent.tsx +134 -0
  157. package/src/components/MarkdownEditor/MarkdownEditorToolbar.tsx +63 -0
  158. package/src/components/Suggestions.stories.tsx +28 -27
  159. package/src/components/index.ts +3 -1
  160. package/src/functions/diff.ts +4 -2
  161. package/src/functions/index.ts +0 -1
  162. package/src/functions/open.ts +4 -2
  163. package/src/hooks/index.ts +3 -0
  164. package/src/{extensions.tsx → hooks/useExtensions.tsx} +56 -114
  165. package/src/hooks/useLinkQuery.ts +82 -0
  166. package/src/hooks/usePopoverMenuOptions.ts +71 -0
  167. package/src/hooks/useSelectCurrentThread.tsx +2 -2
  168. package/src/meta.ts +3 -3
  169. package/src/translations.ts +3 -0
  170. package/src/types/Markdown.ts +6 -4
  171. package/src/types/MarkdownAction.ts +1 -1
  172. package/src/util.tsx +9 -2
  173. package/dist/lib/browser/MarkdownCard-JLUQITYK.mjs.map +0 -7
  174. package/dist/lib/browser/MarkdownContainer-7M37DXAD.mjs +0 -781
  175. package/dist/lib/browser/MarkdownContainer-7M37DXAD.mjs.map +0 -7
  176. package/dist/lib/browser/app-graph-serializer-OX62DNPT.mjs.map +0 -7
  177. package/dist/lib/browser/blueprint-definition-Z3RQGWUD.mjs +0 -11
  178. package/dist/lib/browser/chunk-BEE7VQPU.mjs.map +0 -7
  179. package/dist/lib/browser/chunk-D7UYVHL6.mjs +0 -20
  180. package/dist/lib/browser/chunk-LAVZ2W6X.mjs.map +0 -7
  181. package/dist/lib/browser/chunk-ODB2PTBP.mjs.map +0 -7
  182. package/dist/lib/browser/chunk-ZVVKLB5L.mjs.map +0 -7
  183. package/dist/lib/browser/intent-resolver-WDDH56JC.mjs.map +0 -7
  184. package/dist/lib/browser/react-surface-LN2XK2UN.mjs.map +0 -7
  185. package/dist/lib/node-esm/MarkdownCard-XL5EVSJ7.mjs.map +0 -7
  186. package/dist/lib/node-esm/MarkdownContainer-K3BPAGWO.mjs +0 -782
  187. package/dist/lib/node-esm/MarkdownContainer-K3BPAGWO.mjs.map +0 -7
  188. package/dist/lib/node-esm/app-graph-serializer-56TD3BMX.mjs.map +0 -7
  189. package/dist/lib/node-esm/blueprint-definition-735OAX33.mjs +0 -12
  190. package/dist/lib/node-esm/chunk-FXILAQ5F.mjs.map +0 -7
  191. package/dist/lib/node-esm/chunk-JPXFCBC4.mjs +0 -22
  192. package/dist/lib/node-esm/chunk-O6EXWGGS.mjs.map +0 -7
  193. package/dist/lib/node-esm/chunk-VCG2U522.mjs.map +0 -7
  194. package/dist/lib/node-esm/chunk-Y422WR6A.mjs.map +0 -7
  195. package/dist/lib/node-esm/intent-resolver-2I5HKCUU.mjs.map +0 -7
  196. package/dist/lib/node-esm/react-surface-DJGGKYBD.mjs.map +0 -7
  197. package/dist/types/src/components/Toolbar.stories.d.ts +0 -48
  198. package/dist/types/src/components/Toolbar.stories.d.ts.map +0 -1
  199. package/dist/types/src/extensions.d.ts +0 -24
  200. package/dist/types/src/extensions.d.ts.map +0 -1
  201. package/dist/types/src/functions/create.d.ts +0 -12
  202. package/dist/types/src/functions/create.d.ts.map +0 -1
  203. package/src/components/Toolbar.stories.tsx +0 -118
  204. package/src/functions/create.ts +0 -23
  205. /package/dist/lib/browser/{blueprint-definition-Z3RQGWUD.mjs.map → MarkdownCard-SLM6QZYC.mjs.map} +0 -0
  206. /package/dist/lib/{node-esm/blueprint-definition-735OAX33.mjs.map → browser/MarkdownContainer-UZFQC6XY.mjs.map} +0 -0
@@ -1,295 +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';
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
- type EditorInputMode,
17
- type EditorSelectionState,
18
- type EditorStateStore,
19
- EditorToolbar,
20
- type EditorToolbarActionGraphProps,
21
- type EditorViewMode,
22
- RefPopover,
23
- type UseCommandMenuOptions,
24
- type UseTextEditorProps,
25
- addLink,
26
- coreSlashCommands,
27
- createBasicExtensions,
28
- createElement,
29
- createMarkdownExtensions,
30
- createThemeExtensions,
31
- dropFile,
32
- editorGutter,
33
- editorSlots,
34
- filterItems,
35
- linkSlashCommands,
36
- processEditorPayload,
37
- stackItemContentEditorClassNames,
38
- useCommandMenu,
16
+ type EditorToolbarState,
17
+ PopoverMenuProvider,
18
+ type PreviewBlock,
19
+ type PreviewOptions,
20
+ type UsePopoverMenu,
39
21
  useEditorToolbarState,
40
- useFormattingState,
41
- useTextEditor,
22
+ usePopoverMenu,
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 UsePopoverMenuOptionsProps,
30
+ useExtensions,
31
+ usePopoverMenuOptions,
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 = ({
73
- extensions: _extensions,
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,
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
- return createElement('div', undefined, [
105
- createElement('span', { text: 'Press' }),
106
- ...trigger.map((text) =>
107
- createElement('span', {
108
- className: 'border border-separator rounded-sm mx-1 px-1.5 pt-[1px] pb-[2px]',
109
- text,
110
- }),
111
- ),
112
- createElement('span', { text: 'for commands.' }),
113
- ]);
114
- },
115
- },
116
- getMenu,
117
- };
118
- }, [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
+ );
119
128
 
120
- const { commandMenu, groupsRef, currentItem, onSelect, ...refPopoverProps } = useCommandMenu(options);
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
+ };
121
145
 
122
- const extensions = useMemo(() => [_extensions, commandMenu].filter(isNotFalsy), [_extensions, 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);
123
164
 
124
165
  return (
125
- <RefPopover modal={false} {...refPopoverProps}>
126
- <MarkdownEditorImpl ref={viewRef} {...props} extensions={extensions} />
127
- <CommandMenu groups={groupsRef.current} currentItem={currentItem} onSelect={onSelect} />
128
- </RefPopover>
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
+ />
175
+ </PopoverMenuProvider>
129
176
  );
130
177
  };
131
178
 
132
- const MarkdownEditorImpl = forwardRef<EditorView | undefined, MarkdownEditorProps>(
133
- (
134
- {
135
- id,
136
- role = 'article',
137
- initialValue,
138
- customActions,
139
- editorStateStore,
140
- extensions,
141
- extensionProviders,
142
- scrollPastEnd,
143
- toolbar,
144
- viewMode,
145
- onFileUpload,
146
- onViewModeChange,
147
- },
148
- forwardedRef,
149
- ) => {
150
- const { t } = useTranslation(meta.id);
151
- const { themeMode } = useThemeContext();
152
- const toolbarState = useEditorToolbarState({ viewMode });
153
- const formattingObserver = useFormattingState(toolbarState);
154
-
155
- // Restore last selection and scroll point.
156
- const { scrollTo, selection } = useMemo<EditorSelectionState>(() => editorStateStore?.getState(id) ?? {}, [id]);
157
-
158
- // Extensions from other plugins.
159
- // TODO(burdon): Reconcile with DocumentEditor.useExtensions.
160
- const providerExtensions = useMemo(
161
- () => extensionProviders?.flatMap((provider) => provider({})).filter(isNonNullable),
162
- [extensionProviders],
163
- );
164
-
165
- // TODO(wittjosiah): Factor out to file uploader plugin.
166
- // Drag files.
167
- const handleDrop: DNDOptions['onDrop'] = async (view, { files }) => {
168
- const file = files[0];
169
- const info = file && onFileUpload ? await onFileUpload(file) : undefined;
170
- if (info) {
171
- processEditorPayload(view, { type: 'image', data: info.url });
172
- }
173
- };
174
-
175
- const {
176
- parentRef,
177
- view: editorView,
178
- focusAttributes,
179
- } = useTextEditor(
180
- () => ({
181
- initialValue,
182
- extensions: [
183
- formattingObserver,
184
- createBasicExtensions({
185
- readOnly: viewMode === 'readonly',
186
- placeholder: t('editor placeholder'),
187
- scrollPastEnd: role === 'section' ? false : scrollPastEnd,
188
- search: true,
189
- }),
190
- createMarkdownExtensions({ themeMode }),
191
- createThemeExtensions({ themeMode, syntaxHighlighting: true, slots: editorSlots }),
192
- editorGutter,
193
- role !== 'section' && onFileUpload && dropFile({ onDrop: handleDrop }),
194
- providerExtensions,
195
- extensions,
196
- ].filter(isNotFalsy),
197
- ...(role !== 'section' && {
198
- id,
199
- scrollTo,
200
- selection,
201
- // TODO(wittjosiah): Autofocus based on layout is racy.
202
- // autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
203
- moveToEndOfLine: true,
204
- }),
205
- }),
206
- [id, formattingObserver, viewMode, themeMode, extensions, providerExtensions],
207
- );
208
-
209
- useImperativeHandle(forwardedRef, () => editorView, [editorView]);
210
- useTest(editorView);
211
- useSelectCurrentThread(editorView, id);
212
-
213
- // https://react-dropzone.js.org/#src
214
- const { acceptedFiles, getInputProps, open } = useDropzone({
215
- multiple: false,
216
- noDrag: true,
217
- accept: {
218
- 'image/*': ['.jpg', '.jpeg', '.png', '.gif'],
219
- },
220
- });
221
-
222
- useEffect(() => {
223
- if (editorView && onFileUpload && acceptedFiles.length) {
224
- requestAnimationFrame(async () => {
225
- // NOTE: Clone file since react-dropzone patches in a non-standard `path` property, which confuses IPFS.
226
- const f = acceptedFiles[0];
227
- const file = new File([f], f.name, {
228
- type: f.type,
229
- lastModified: f.lastModified,
230
- });
231
-
232
- const info = await onFileUpload(file);
233
- if (info) {
234
- addLink({ url: info.url, image: true })(editorView);
235
- }
236
- });
237
- }
238
- }, [acceptedFiles, editorView, onFileUpload]);
239
-
240
- const getView = useCallback(() => {
241
- invariant(editorView);
242
- return editorView;
243
- }, [editorView]);
244
-
245
- const handleViewModeChange = useCallback(
246
- (mode: EditorViewMode) => onViewModeChange?.(id, mode),
247
- [id, onViewModeChange],
248
- );
249
-
250
- const handleImageUpload = useCallback(() => {
251
- if (onFileUpload) {
252
- open();
253
- }
254
- }, [onFileUpload]);
255
-
256
- return (
257
- <StackItem.Content toolbar={!!toolbar}>
258
- {toolbar && (
259
- <>
260
- <EditorToolbar
261
- attendableId={id}
262
- role={role}
263
- state={toolbarState}
264
- customActions={customActions}
265
- getView={getView}
266
- image={handleImageUpload}
267
- viewMode={handleViewModeChange}
268
- />
269
- <input {...getInputProps()} />
270
- </>
271
- )}
272
- <div
273
- role='none'
274
- ref={parentRef}
275
- data-testid='composer.markdownRoot'
276
- data-toolbar={toolbar ? 'enabled' : 'disabled'}
277
- className={stackItemContentEditorClassNames(role)}
278
- data-popover-collision-boundary={true}
279
- {...focusAttributes}
280
- />
281
- </StackItem.Content>
282
- );
283
- },
284
- );
285
-
286
- // Expose editor view for playwright tests.
287
- // TODO(wittjosiah): Find a better way to expose this or find a way to limit it to test runs.
288
- const useTest = (view?: EditorView) => {
289
- useEffect(() => {
290
- const composer = (window as any).composer;
291
- if (composer) {
292
- composer.editorView = view;
293
- }
294
- }, [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,
295
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
+ };