@dxos/plugin-markdown 0.8.2 → 0.8.3-main.7f5a14c

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 (143) hide show
  1. package/dist/lib/browser/{MarkdownContainer-5IEINNQB.mjs → MarkdownContainer-3KTC7Q4C.mjs} +132 -18
  2. package/dist/lib/browser/MarkdownContainer-3KTC7Q4C.mjs.map +7 -0
  3. package/dist/lib/browser/{MarkdownPreview-YW5CS3ID.mjs → MarkdownPreview-F4PYFW5L.mjs} +15 -22
  4. package/dist/lib/browser/MarkdownPreview-F4PYFW5L.mjs.map +7 -0
  5. package/dist/lib/browser/{anchor-sort-VS4OZVPP.mjs → anchor-sort-BMAN2ABT.mjs} +4 -4
  6. package/dist/lib/browser/anchor-sort-BMAN2ABT.mjs.map +7 -0
  7. package/dist/lib/browser/{app-graph-serializer-V6RLEHVY.mjs → app-graph-serializer-FLQI6GFL.mjs} +3 -3
  8. package/dist/lib/browser/{artifact-definition-5NAODQLG.mjs → artifact-definition-FQ2R6KPT.mjs} +6 -6
  9. package/dist/lib/browser/artifact-definition-FQ2R6KPT.mjs.map +7 -0
  10. package/dist/lib/browser/{chunk-C5RABVIX.mjs → chunk-CX5GYZYO.mjs} +2 -2
  11. package/dist/lib/browser/{chunk-ACAID3XF.mjs → chunk-LCMXUTQB.mjs} +7 -7
  12. package/dist/lib/browser/{chunk-77NGW7EO.mjs → chunk-LXSRQPEP.mjs} +9 -9
  13. package/dist/lib/browser/chunk-LXSRQPEP.mjs.map +7 -0
  14. package/dist/lib/browser/{chunk-ECSM56YC.mjs → chunk-N2D26K6W.mjs} +4 -5
  15. package/dist/lib/browser/chunk-N2D26K6W.mjs.map +7 -0
  16. package/dist/lib/browser/index.mjs +7 -8
  17. package/dist/lib/browser/index.mjs.map +3 -3
  18. package/dist/lib/browser/{intent-resolver-4GDYST4Y.mjs → intent-resolver-6ZOABX2J.mjs} +6 -7
  19. package/dist/lib/browser/intent-resolver-6ZOABX2J.mjs.map +7 -0
  20. package/dist/lib/browser/meta.json +1 -1
  21. package/dist/lib/browser/{react-surface-QE4SKXBT.mjs → react-surface-RJQKYJJQ.mjs} +8 -8
  22. package/dist/lib/browser/react-surface-RJQKYJJQ.mjs.map +7 -0
  23. package/dist/lib/browser/{settings-W5CK4PXP.mjs → settings-PLH54VC7.mjs} +4 -4
  24. package/dist/lib/browser/settings-PLH54VC7.mjs.map +7 -0
  25. package/dist/lib/browser/types/index.mjs +1 -1
  26. package/dist/lib/node/{MarkdownContainer-LSNNPNRB.cjs → MarkdownContainer-NG4H6AZJ.cjs} +181 -67
  27. package/dist/lib/node/MarkdownContainer-NG4H6AZJ.cjs.map +7 -0
  28. package/dist/lib/node/{MarkdownPreview-G34HSQEB.cjs → MarkdownPreview-GCJJCXY6.cjs} +24 -31
  29. package/dist/lib/node/MarkdownPreview-GCJJCXY6.cjs.map +7 -0
  30. package/dist/lib/node/{anchor-sort-NHVF23EU.cjs → anchor-sort-V3T4SFFI.cjs} +12 -12
  31. package/dist/lib/node/anchor-sort-V3T4SFFI.cjs.map +7 -0
  32. package/dist/lib/node/{app-graph-serializer-CLALIYN3.cjs → app-graph-serializer-BZPM7HHJ.cjs} +9 -9
  33. package/dist/lib/node/{artifact-definition-VEAHK7BX.cjs → artifact-definition-U27MH5SC.cjs} +16 -16
  34. package/dist/lib/node/artifact-definition-U27MH5SC.cjs.map +7 -0
  35. package/dist/lib/node/{chunk-RQS4KBMG.cjs → chunk-3HHV4MM6.cjs} +6 -7
  36. package/dist/lib/node/chunk-3HHV4MM6.cjs.map +7 -0
  37. package/dist/lib/node/{chunk-C4HR7UXE.cjs → chunk-4DYNEQG3.cjs} +10 -10
  38. package/dist/lib/node/{chunk-G7RBJX22.cjs → chunk-CJLYFGPI.cjs} +12 -12
  39. package/dist/lib/node/chunk-CJLYFGPI.cjs.map +7 -0
  40. package/dist/lib/node/{chunk-ZDTL47I7.cjs → chunk-SYEFGLXN.cjs} +6 -6
  41. package/dist/lib/node/index.cjs +26 -27
  42. package/dist/lib/node/index.cjs.map +3 -3
  43. package/dist/lib/node/{intent-resolver-AUZVK3NZ.cjs → intent-resolver-OEFLRNEJ.cjs} +14 -15
  44. package/dist/lib/node/intent-resolver-OEFLRNEJ.cjs.map +7 -0
  45. package/dist/lib/node/meta.json +1 -1
  46. package/dist/lib/node/{react-surface-WJZTEBYO.cjs → react-surface-5RKEWAXS.cjs} +15 -15
  47. package/dist/lib/node/react-surface-5RKEWAXS.cjs.map +7 -0
  48. package/dist/lib/node/{settings-IRKU3WPM.cjs → settings-E3NUTXJ4.cjs} +7 -7
  49. package/dist/lib/node/settings-E3NUTXJ4.cjs.map +7 -0
  50. package/dist/lib/node/types/index.cjs +7 -7
  51. package/dist/lib/node/types/index.cjs.map +1 -1
  52. package/dist/lib/node-esm/{MarkdownContainer-UZSLXMWO.mjs → MarkdownContainer-DZPXCA6J.mjs} +132 -18
  53. package/dist/lib/node-esm/MarkdownContainer-DZPXCA6J.mjs.map +7 -0
  54. package/dist/lib/node-esm/{MarkdownPreview-TCV7BI32.mjs → MarkdownPreview-KFDRV4GC.mjs} +15 -22
  55. package/dist/lib/node-esm/MarkdownPreview-KFDRV4GC.mjs.map +7 -0
  56. package/dist/lib/node-esm/{anchor-sort-G2HLCYFK.mjs → anchor-sort-BXL7BE67.mjs} +4 -4
  57. package/dist/lib/node-esm/anchor-sort-BXL7BE67.mjs.map +7 -0
  58. package/dist/lib/node-esm/{app-graph-serializer-C3RNTQGM.mjs → app-graph-serializer-EBH54X6Z.mjs} +3 -3
  59. package/dist/lib/node-esm/{artifact-definition-7TIJW2CO.mjs → artifact-definition-NQOHB6S5.mjs} +6 -6
  60. package/dist/lib/node-esm/artifact-definition-NQOHB6S5.mjs.map +7 -0
  61. package/dist/lib/node-esm/{chunk-6RPARLIK.mjs → chunk-K26TX5V4.mjs} +9 -9
  62. package/dist/lib/node-esm/chunk-K26TX5V4.mjs.map +7 -0
  63. package/dist/lib/node-esm/{chunk-TCFJNUAE.mjs → chunk-Q7WUBLL3.mjs} +2 -2
  64. package/dist/lib/node-esm/{chunk-NCMPVEXO.mjs → chunk-T2Y2BT53.mjs} +4 -5
  65. package/dist/lib/node-esm/chunk-T2Y2BT53.mjs.map +7 -0
  66. package/dist/lib/node-esm/{chunk-EIUTPXGL.mjs → chunk-WANCCPU7.mjs} +7 -7
  67. package/dist/lib/node-esm/index.mjs +7 -8
  68. package/dist/lib/node-esm/index.mjs.map +3 -3
  69. package/dist/lib/node-esm/{intent-resolver-FTNXUNI2.mjs → intent-resolver-CLMSVF2K.mjs} +6 -7
  70. package/dist/lib/node-esm/intent-resolver-CLMSVF2K.mjs.map +7 -0
  71. package/dist/lib/node-esm/meta.json +1 -1
  72. package/dist/lib/node-esm/{react-surface-XNM3YDFB.mjs → react-surface-Z3DX37JV.mjs} +8 -8
  73. package/dist/lib/node-esm/react-surface-Z3DX37JV.mjs.map +7 -0
  74. package/dist/lib/node-esm/{settings-MK7D7LHQ.mjs → settings-SIY33P3F.mjs} +4 -4
  75. package/dist/lib/node-esm/settings-SIY33P3F.mjs.map +7 -0
  76. package/dist/lib/node-esm/types/index.mjs +1 -1
  77. package/dist/types/src/MarkdownPlugin.d.ts.map +1 -1
  78. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  79. package/dist/types/src/components/MarkdownContainer.d.ts +1 -1
  80. package/dist/types/src/components/MarkdownContainer.d.ts.map +1 -1
  81. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts +4 -2
  82. package/dist/types/src/components/MarkdownEditor/MarkdownEditor.d.ts.map +1 -1
  83. package/dist/types/src/components/MarkdownPreview/MarkdownPreview.d.ts +1 -1
  84. package/dist/types/src/components/MarkdownPreview/MarkdownPreview.d.ts.map +1 -1
  85. package/dist/types/src/components/MarkdownPreview/MarkdownPreview.stories.d.ts +2 -6
  86. package/dist/types/src/components/MarkdownPreview/MarkdownPreview.stories.d.ts.map +1 -1
  87. package/dist/types/src/components/Suggestions.stories.d.ts.map +1 -1
  88. package/dist/types/src/components/index.d.ts +1 -1
  89. package/dist/types/src/components/index.d.ts.map +1 -1
  90. package/dist/types/src/types/schema.d.ts.map +1 -1
  91. package/dist/types/src/util.d.ts.map +1 -1
  92. package/package.json +38 -39
  93. package/src/MarkdownPlugin.tsx +3 -5
  94. package/src/capabilities/anchor-sort.ts +2 -2
  95. package/src/capabilities/artifact-definition.ts +3 -3
  96. package/src/capabilities/intent-resolver.ts +4 -5
  97. package/src/capabilities/react-surface.tsx +4 -4
  98. package/src/capabilities/settings.ts +2 -2
  99. package/src/components/MarkdownContainer.tsx +61 -7
  100. package/src/components/MarkdownEditor/MarkdownEditor.stories.tsx +3 -3
  101. package/src/components/MarkdownEditor/MarkdownEditor.tsx +196 -144
  102. package/src/components/MarkdownPreview/MarkdownPreview.stories.tsx +8 -7
  103. package/src/components/MarkdownPreview/MarkdownPreview.tsx +14 -28
  104. package/src/components/Suggestions.stories.tsx +8 -9
  105. package/src/components/Toolbar.stories.tsx +3 -3
  106. package/src/types/schema.ts +2 -3
  107. package/src/util.tsx +5 -7
  108. package/dist/lib/browser/MarkdownContainer-5IEINNQB.mjs.map +0 -7
  109. package/dist/lib/browser/MarkdownPreview-YW5CS3ID.mjs.map +0 -7
  110. package/dist/lib/browser/anchor-sort-VS4OZVPP.mjs.map +0 -7
  111. package/dist/lib/browser/artifact-definition-5NAODQLG.mjs.map +0 -7
  112. package/dist/lib/browser/chunk-77NGW7EO.mjs.map +0 -7
  113. package/dist/lib/browser/chunk-ECSM56YC.mjs.map +0 -7
  114. package/dist/lib/browser/intent-resolver-4GDYST4Y.mjs.map +0 -7
  115. package/dist/lib/browser/react-surface-QE4SKXBT.mjs.map +0 -7
  116. package/dist/lib/browser/settings-W5CK4PXP.mjs.map +0 -7
  117. package/dist/lib/node/MarkdownContainer-LSNNPNRB.cjs.map +0 -7
  118. package/dist/lib/node/MarkdownPreview-G34HSQEB.cjs.map +0 -7
  119. package/dist/lib/node/anchor-sort-NHVF23EU.cjs.map +0 -7
  120. package/dist/lib/node/artifact-definition-VEAHK7BX.cjs.map +0 -7
  121. package/dist/lib/node/chunk-G7RBJX22.cjs.map +0 -7
  122. package/dist/lib/node/chunk-RQS4KBMG.cjs.map +0 -7
  123. package/dist/lib/node/intent-resolver-AUZVK3NZ.cjs.map +0 -7
  124. package/dist/lib/node/react-surface-WJZTEBYO.cjs.map +0 -7
  125. package/dist/lib/node/settings-IRKU3WPM.cjs.map +0 -7
  126. package/dist/lib/node-esm/MarkdownContainer-UZSLXMWO.mjs.map +0 -7
  127. package/dist/lib/node-esm/MarkdownPreview-TCV7BI32.mjs.map +0 -7
  128. package/dist/lib/node-esm/anchor-sort-G2HLCYFK.mjs.map +0 -7
  129. package/dist/lib/node-esm/artifact-definition-7TIJW2CO.mjs.map +0 -7
  130. package/dist/lib/node-esm/chunk-6RPARLIK.mjs.map +0 -7
  131. package/dist/lib/node-esm/chunk-NCMPVEXO.mjs.map +0 -7
  132. package/dist/lib/node-esm/intent-resolver-FTNXUNI2.mjs.map +0 -7
  133. package/dist/lib/node-esm/react-surface-XNM3YDFB.mjs.map +0 -7
  134. package/dist/lib/node-esm/settings-MK7D7LHQ.mjs.map +0 -7
  135. /package/dist/lib/browser/{app-graph-serializer-V6RLEHVY.mjs.map → app-graph-serializer-FLQI6GFL.mjs.map} +0 -0
  136. /package/dist/lib/browser/{chunk-C5RABVIX.mjs.map → chunk-CX5GYZYO.mjs.map} +0 -0
  137. /package/dist/lib/browser/{chunk-ACAID3XF.mjs.map → chunk-LCMXUTQB.mjs.map} +0 -0
  138. /package/dist/lib/node/{app-graph-serializer-CLALIYN3.cjs.map → app-graph-serializer-BZPM7HHJ.cjs.map} +0 -0
  139. /package/dist/lib/node/{chunk-C4HR7UXE.cjs.map → chunk-4DYNEQG3.cjs.map} +0 -0
  140. /package/dist/lib/node/{chunk-ZDTL47I7.cjs.map → chunk-SYEFGLXN.cjs.map} +0 -0
  141. /package/dist/lib/node-esm/{app-graph-serializer-C3RNTQGM.mjs.map → app-graph-serializer-EBH54X6Z.mjs.map} +0 -0
  142. /package/dist/lib/node-esm/{chunk-TCFJNUAE.mjs.map → chunk-Q7WUBLL3.mjs.map} +0 -0
  143. /package/dist/lib/node-esm/{chunk-EIUTPXGL.mjs.map → chunk-WANCCPU7.mjs.map} +0 -0
@@ -3,33 +3,40 @@
3
3
  //
4
4
 
5
5
  import { type EditorView } from '@codemirror/view';
6
- import React, { useMemo, useEffect, useCallback } from 'react';
6
+ import React, { useMemo, useEffect, useCallback, forwardRef, useImperativeHandle, useRef } from 'react';
7
7
  import { useDropzone } from 'react-dropzone';
8
8
 
9
9
  import { type FileInfo } from '@dxos/app-framework';
10
10
  import { invariant } from '@dxos/invariant';
11
- import { useThemeContext, useTranslation } from '@dxos/react-ui';
11
+ import { toLocalizedString, useThemeContext, useTranslation } from '@dxos/react-ui';
12
12
  import {
13
- type DNDOptions,
14
- type EditorViewMode,
15
- type EditorInputMode,
16
- type EditorSelectionState,
17
- type EditorStateStore,
18
- EditorToolbar,
19
- type UseTextEditorProps,
13
+ addLink,
20
14
  createBasicExtensions,
21
15
  createMarkdownExtensions,
22
16
  createThemeExtensions,
23
17
  dropFile,
24
- editorSlots,
25
18
  editorGutter,
19
+ editorSlots,
20
+ EditorToolbar,
26
21
  processEditorPayload,
22
+ RefPopover,
27
23
  stackItemContentEditorClassNames,
24
+ type DNDOptions,
25
+ type EditorInputMode,
26
+ type EditorSelectionState,
27
+ type EditorStateStore,
28
+ type EditorToolbarActionGraphProps,
29
+ type EditorViewMode,
30
+ type CommandMenuGroup,
31
+ type UseTextEditorProps,
32
+ useEditorToolbarState,
28
33
  useFormattingState,
34
+ useCommandMenu,
29
35
  useTextEditor,
30
- useEditorToolbarState,
31
- addLink,
32
- type EditorToolbarActionGraphProps,
36
+ filterItems,
37
+ coreSlashCommands,
38
+ CommandMenu,
39
+ linkSlashCommands,
33
40
  } from '@dxos/react-ui-editor';
34
41
  import { StackItem } from '@dxos/react-ui-stack';
35
42
  import { isNotFalsy, isNonNullable } from '@dxos/util';
@@ -43,12 +50,14 @@ export type MarkdownEditorProps = {
43
50
  role?: string;
44
51
  inputMode?: EditorInputMode;
45
52
  scrollPastEnd?: boolean;
53
+ slashCommandGroups?: CommandMenuGroup[];
46
54
  toolbar?: boolean;
47
55
  customActions?: EditorToolbarActionGraphProps['customActions'];
48
56
  // TODO(wittjosiah): Generalize custom toolbar actions (e.g. comment, upload, etc.)
49
57
  viewMode?: EditorViewMode;
50
58
  editorStateStore?: EditorStateStore;
51
59
  onViewModeChange?: (id: string, mode: EditorViewMode) => void;
60
+ onLinkQuery?: (query?: string) => Promise<CommandMenuGroup[]>;
52
61
  onFileUpload?: (file: File) => Promise<FileInfo | undefined>;
53
62
  } & Pick<UseTextEditorProps, 'initialValue' | 'extensions'> &
54
63
  Partial<Pick<MarkdownPluginState, 'extensionProviders'>>;
@@ -60,151 +69,194 @@ export type MarkdownEditorProps = {
60
69
  * This allows it to be used as a common editor for markdown content on arbitrary backends (e.g. files).
61
70
  */
62
71
  export const MarkdownEditor = ({
63
- id,
64
- role = 'article',
65
- initialValue,
66
- extensions,
67
- extensionProviders,
68
- scrollPastEnd,
69
- toolbar,
70
- customActions,
71
- viewMode,
72
- editorStateStore,
73
- onFileUpload,
74
- onViewModeChange,
72
+ extensions: _extensions,
73
+ slashCommandGroups,
74
+ onLinkQuery,
75
+ ...props
75
76
  }: MarkdownEditorProps) => {
76
- const { t } = useTranslation(MARKDOWN_PLUGIN);
77
- const { themeMode } = useThemeContext();
78
- const toolbarState = useEditorToolbarState({ viewMode });
79
- const formattingObserver = useFormattingState(toolbarState);
80
-
81
- // Restore last selection and scroll point.
82
- const { scrollTo, selection } = useMemo<EditorSelectionState>(() => editorStateStore?.getState(id) ?? {}, [id]);
83
-
84
- // Extensions from other plugins.
85
- // TODO(burdon): Reconcile with DocumentEditor.useExtensions.
86
- const providerExtensions = useMemo(
87
- () => extensionProviders?.flatMap((provider) => provider({})).filter(isNonNullable),
88
- [extensionProviders],
77
+ const { t } = useTranslation();
78
+ const viewRef = useRef<EditorView>();
79
+ const getGroups = useCallback(
80
+ (trigger: string, query?: string) => {
81
+ switch (trigger) {
82
+ case '@':
83
+ return onLinkQuery?.(query) ?? [];
84
+ case '/':
85
+ default:
86
+ return filterItems([coreSlashCommands, linkSlashCommands, ...(slashCommandGroups ?? [])], (item) =>
87
+ query ? toLocalizedString(item.label, t).toLowerCase().includes(query.toLowerCase()) : true,
88
+ );
89
+ }
90
+ },
91
+ [onLinkQuery, slashCommandGroups],
89
92
  );
93
+ const { commandMenu, groupsRef, currentItem, onSelect, ...refPopoverProps } = useCommandMenu({
94
+ viewRef,
95
+ getGroups,
96
+ trigger: onLinkQuery ? ['/', '@'] : '/',
97
+ });
98
+ const extensions = useMemo(() => [_extensions, commandMenu].filter(isNotFalsy), [_extensions, commandMenu]);
90
99
 
91
- // TODO(wittjosiah): Factor out to file uploader plugin.
92
- // Drag files.
93
- const handleDrop: DNDOptions['onDrop'] = async (view, { files }) => {
94
- const file = files[0];
95
- const info = file && onFileUpload ? await onFileUpload(file) : undefined;
96
- if (info) {
97
- processEditorPayload(view, { type: 'image', data: info.url });
98
- }
99
- };
100
-
101
- const {
102
- parentRef,
103
- view: editorView,
104
- focusAttributes,
105
- } = useTextEditor(
106
- () => ({
100
+ return (
101
+ <RefPopover modal={false} {...refPopoverProps}>
102
+ <MarkdownEditorImpl ref={viewRef} {...props} extensions={extensions} />
103
+ <CommandMenu groups={groupsRef.current} currentItem={currentItem} onSelect={onSelect} />
104
+ </RefPopover>
105
+ );
106
+ };
107
+
108
+ const MarkdownEditorImpl = forwardRef<EditorView | undefined, MarkdownEditorProps>(
109
+ (
110
+ {
111
+ id,
112
+ role = 'article',
107
113
  initialValue,
108
- extensions: [
109
- formattingObserver,
110
- createBasicExtensions({
111
- readOnly: viewMode === 'readonly',
112
- placeholder: t('editor placeholder'),
113
- scrollPastEnd: role === 'section' ? false : scrollPastEnd,
114
+ extensions,
115
+ extensionProviders,
116
+ scrollPastEnd,
117
+ toolbar,
118
+ customActions,
119
+ viewMode,
120
+ editorStateStore,
121
+ onFileUpload,
122
+ onViewModeChange,
123
+ },
124
+ forwardedRef,
125
+ ) => {
126
+ const { t } = useTranslation(MARKDOWN_PLUGIN);
127
+ const { themeMode } = useThemeContext();
128
+ const toolbarState = useEditorToolbarState({ viewMode });
129
+ const formattingObserver = useFormattingState(toolbarState);
130
+
131
+ // Restore last selection and scroll point.
132
+ const { scrollTo, selection } = useMemo<EditorSelectionState>(() => editorStateStore?.getState(id) ?? {}, [id]);
133
+
134
+ // Extensions from other plugins.
135
+ // TODO(burdon): Reconcile with DocumentEditor.useExtensions.
136
+ const providerExtensions = useMemo(
137
+ () => extensionProviders?.flatMap((provider) => provider({})).filter(isNonNullable),
138
+ [extensionProviders],
139
+ );
140
+
141
+ // TODO(wittjosiah): Factor out to file uploader plugin.
142
+ // Drag files.
143
+ const handleDrop: DNDOptions['onDrop'] = async (view, { files }) => {
144
+ const file = files[0];
145
+ const info = file && onFileUpload ? await onFileUpload(file) : undefined;
146
+ if (info) {
147
+ processEditorPayload(view, { type: 'image', data: info.url });
148
+ }
149
+ };
150
+
151
+ const {
152
+ parentRef,
153
+ view: editorView,
154
+ focusAttributes,
155
+ } = useTextEditor(
156
+ () => ({
157
+ initialValue,
158
+ extensions: [
159
+ formattingObserver,
160
+ createBasicExtensions({
161
+ readOnly: viewMode === 'readonly',
162
+ placeholder: t('editor placeholder'),
163
+ scrollPastEnd: role === 'section' ? false : scrollPastEnd,
164
+ }),
165
+ createMarkdownExtensions({ themeMode }),
166
+ createThemeExtensions({ themeMode, syntaxHighlighting: true, slots: editorSlots }),
167
+ editorGutter,
168
+ role !== 'section' && onFileUpload && dropFile({ onDrop: handleDrop }),
169
+ providerExtensions,
170
+ extensions,
171
+ ].filter(isNotFalsy),
172
+ ...(role !== 'section' && {
173
+ id,
174
+ scrollTo,
175
+ selection,
176
+ // TODO(wittjosiah): Autofocus based on layout is racy.
177
+ // autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
178
+ moveToEndOfLine: true,
114
179
  }),
115
- createMarkdownExtensions({ themeMode }),
116
- createThemeExtensions({ themeMode, syntaxHighlighting: true, slots: editorSlots }),
117
- editorGutter,
118
- role !== 'section' && onFileUpload && dropFile({ onDrop: handleDrop }),
119
- providerExtensions,
120
- extensions,
121
- ].filter(isNotFalsy),
122
- ...(role !== 'section' && {
123
- id,
124
- scrollTo,
125
- selection,
126
- // TODO(wittjosiah): Autofocus based on layout is racy.
127
- // autoFocus: layoutPlugin?.provides.layout ? layoutPlugin?.provides.layout.scrollIntoView === id : true,
128
- moveToEndOfLine: true,
129
180
  }),
130
- }),
131
- [id, formattingObserver, viewMode, themeMode, extensions, providerExtensions],
132
- );
181
+ [id, formattingObserver, viewMode, themeMode, extensions, providerExtensions],
182
+ );
133
183
 
134
- useTest(editorView);
135
- useSelectCurrentThread(editorView, id);
184
+ useImperativeHandle(forwardedRef, () => editorView, [editorView]);
185
+ useTest(editorView);
186
+ useSelectCurrentThread(editorView, id);
136
187
 
137
- // https://react-dropzone.js.org/#src
138
- const { acceptedFiles, getInputProps, open } = useDropzone({
139
- multiple: false,
140
- noDrag: true,
141
- accept: {
142
- 'image/*': ['.jpg', '.jpeg', '.png', '.gif'],
143
- },
144
- });
188
+ // https://react-dropzone.js.org/#src
189
+ const { acceptedFiles, getInputProps, open } = useDropzone({
190
+ multiple: false,
191
+ noDrag: true,
192
+ accept: {
193
+ 'image/*': ['.jpg', '.jpeg', '.png', '.gif'],
194
+ },
195
+ });
145
196
 
146
- useEffect(() => {
147
- if (editorView && onFileUpload && acceptedFiles.length) {
148
- requestAnimationFrame(async () => {
149
- // NOTE: Clone file since react-dropzone patches in a non-standard `path` property, which confuses IPFS.
150
- const f = acceptedFiles[0];
151
- const file = new File([f], f.name, {
152
- type: f.type,
153
- lastModified: f.lastModified,
197
+ useEffect(() => {
198
+ if (editorView && onFileUpload && acceptedFiles.length) {
199
+ requestAnimationFrame(async () => {
200
+ // NOTE: Clone file since react-dropzone patches in a non-standard `path` property, which confuses IPFS.
201
+ const f = acceptedFiles[0];
202
+ const file = new File([f], f.name, {
203
+ type: f.type,
204
+ lastModified: f.lastModified,
205
+ });
206
+
207
+ const info = await onFileUpload(file);
208
+ if (info) {
209
+ addLink({ url: info.url, image: true })(editorView);
210
+ }
154
211
  });
212
+ }
213
+ }, [acceptedFiles, editorView, onFileUpload]);
155
214
 
156
- const info = await onFileUpload(file);
157
- if (info) {
158
- addLink({ url: info.url, image: true })(editorView);
159
- }
160
- });
161
- }
162
- }, [acceptedFiles, editorView, onFileUpload]);
215
+ const getView = useCallback(() => {
216
+ invariant(editorView);
217
+ return editorView;
218
+ }, [editorView]);
163
219
 
164
- const getView = useCallback(() => {
165
- invariant(editorView);
166
- return editorView;
167
- }, [editorView]);
220
+ const handleViewModeChange = useCallback(
221
+ (mode: EditorViewMode) => onViewModeChange?.(id, mode),
222
+ [id, onViewModeChange],
223
+ );
168
224
 
169
- const handleViewModeChange = useCallback(
170
- (mode: EditorViewMode) => onViewModeChange?.(id, mode),
171
- [id, onViewModeChange],
172
- );
225
+ const handleImageUpload = useCallback(() => {
226
+ if (onFileUpload) {
227
+ open();
228
+ }
229
+ }, [onFileUpload]);
173
230
 
174
- const handleImageUpload = useCallback(() => {
175
- if (onFileUpload) {
176
- open();
177
- }
178
- }, [onFileUpload]);
179
-
180
- return (
181
- <StackItem.Content toolbar={!!toolbar}>
182
- {toolbar && (
183
- <>
184
- <EditorToolbar
185
- attendableId={id}
186
- role={role}
187
- state={toolbarState}
188
- customActions={customActions}
189
- getView={getView}
190
- image={handleImageUpload}
191
- viewMode={handleViewModeChange}
192
- />
193
- <input {...getInputProps()} />
194
- </>
195
- )}
196
- <div
197
- role='none'
198
- ref={parentRef}
199
- data-testid='composer.markdownRoot'
200
- data-toolbar={toolbar ? 'enabled' : 'disabled'}
201
- className={stackItemContentEditorClassNames(role)}
202
- data-popover-collision-boundary={true}
203
- {...focusAttributes}
204
- />
205
- </StackItem.Content>
206
- );
207
- };
231
+ return (
232
+ <StackItem.Content toolbar={!!toolbar}>
233
+ {toolbar && (
234
+ <>
235
+ <EditorToolbar
236
+ attendableId={id}
237
+ role={role}
238
+ state={toolbarState}
239
+ customActions={customActions}
240
+ getView={getView}
241
+ image={handleImageUpload}
242
+ viewMode={handleViewModeChange}
243
+ />
244
+ <input {...getInputProps()} />
245
+ </>
246
+ )}
247
+ <div
248
+ role='none'
249
+ ref={parentRef}
250
+ data-testid='composer.markdownRoot'
251
+ data-toolbar={toolbar ? 'enabled' : 'disabled'}
252
+ className={stackItemContentEditorClassNames(role)}
253
+ data-popover-collision-boundary={true}
254
+ {...focusAttributes}
255
+ />
256
+ </StackItem.Content>
257
+ );
258
+ },
259
+ );
208
260
 
209
261
  // Expose editor view for playwright tests.
210
262
  // TODO(wittjosiah): Find a better way to expose this or find a way to limit it to test runs.
@@ -9,10 +9,9 @@ import React from 'react';
9
9
 
10
10
  import { IntentPlugin } from '@dxos/app-framework';
11
11
  import { withPluginManager } from '@dxos/app-framework/testing';
12
- import { create } from '@dxos/echo-schema';
12
+ import { Obj, Ref } from '@dxos/echo';
13
13
  import { DocumentType } from '@dxos/plugin-markdown/types';
14
14
  import { faker } from '@dxos/random';
15
- import { makeRef } from '@dxos/react-client/echo';
16
15
  import { Icon, Popover } from '@dxos/react-ui';
17
16
  import { DataType } from '@dxos/schema';
18
17
  import { withTheme, withLayout } from '@dxos/storybook-utils';
@@ -29,7 +28,9 @@ const meta: Meta<typeof MarkdownPreview> = {
29
28
  return (
30
29
  <Popover.Root open>
31
30
  <Popover.Content>
32
- <MarkdownPreview subject={subject} role='popover' />
31
+ <Popover.Viewport>
32
+ <MarkdownPreview subject={subject} role='popover' />
33
+ </Popover.Viewport>
33
34
  <Popover.Arrow />
34
35
  </Popover.Content>
35
36
  <Popover.Trigger>
@@ -54,10 +55,10 @@ const meta: Meta<typeof MarkdownPreview> = {
54
55
  export default meta;
55
56
 
56
57
  const data = (() => {
57
- const document = create(DocumentType, {
58
+ const document = Obj.make(DocumentType, {
58
59
  name: faker.lorem.words(3),
59
- content: makeRef(
60
- create(DataType.Text, {
60
+ content: Ref.make(
61
+ Obj.make(DataType.Text, {
61
62
  content: faker.lorem.paragraphs(3),
62
63
  }),
63
64
  ),
@@ -68,6 +69,6 @@ const data = (() => {
68
69
 
69
70
  export const Default = {
70
71
  args: {
71
- subject: create(DocumentType, data.document),
72
+ subject: Obj.make(DocumentType, data.document),
72
73
  },
73
74
  };
@@ -6,19 +6,11 @@ import { pipe } from 'effect';
6
6
  import React, { useCallback } from 'react';
7
7
 
8
8
  import { chain, createIntent, LayoutAction, useIntentDispatcher } from '@dxos/app-framework';
9
- import { isInstanceOf } from '@dxos/echo-schema';
10
- import {
11
- type PreviewProps,
12
- defaultCard,
13
- kanbanCardWithoutPoster,
14
- popoverCard,
15
- previewTitle,
16
- previewProse,
17
- previewChrome,
18
- } from '@dxos/plugin-preview';
9
+ import { Obj } from '@dxos/echo';
10
+ import { type PreviewProps } from '@dxos/plugin-preview';
19
11
  import { fullyQualifiedId } from '@dxos/react-client/echo';
20
12
  import { Button, Icon, useTranslation } from '@dxos/react-ui';
21
- import { mx } from '@dxos/react-ui-theme';
13
+ import { Card } from '@dxos/react-ui-stack';
22
14
  import { DataType } from '@dxos/schema';
23
15
 
24
16
  import { MARKDOWN_PLUGIN } from '../../meta';
@@ -27,23 +19,23 @@ import { getAbstract, getFallbackName } from '../../util';
27
19
 
28
20
  // TODO(burdon): Factor out.
29
21
  const getTitle = (subject: DocumentType | DataType.Text, fallback: string) => {
30
- if (isInstanceOf(DocumentType, subject)) {
22
+ if (Obj.instanceOf(DocumentType, subject)) {
31
23
  return subject.name ?? subject.fallbackName ?? getFallbackName(subject.content?.target?.content ?? fallback);
32
- } else if (isInstanceOf(DataType.Text, subject)) {
24
+ } else if (Obj.instanceOf(DataType.Text, subject)) {
33
25
  return getFallbackName(subject.content);
34
26
  }
35
27
  };
36
28
 
37
29
  // TODO(burdon): Factor out.
38
30
  const getSnippet = (subject: DocumentType | DataType.Text, fallback: string) => {
39
- if (isInstanceOf(DocumentType, subject)) {
31
+ if (Obj.instanceOf(DocumentType, subject)) {
40
32
  return getAbstract(subject.content?.target?.content ?? fallback);
41
- } else if (isInstanceOf(DataType.Text, subject)) {
33
+ } else if (Obj.instanceOf(DataType.Text, subject)) {
42
34
  return getAbstract(subject.content);
43
35
  }
44
36
  };
45
37
 
46
- export const MarkdownPreview = ({ classNames, subject, role }: PreviewProps<DocumentType | DataType.Text>) => {
38
+ export const MarkdownPreview = ({ subject, role }: PreviewProps<DocumentType | DataType.Text>) => {
47
39
  const { dispatchPromise: dispatch } = useIntentDispatcher();
48
40
  const { t } = useTranslation(MARKDOWN_PLUGIN);
49
41
  const snippet = getSnippet(subject, t('fallback abstract'));
@@ -65,21 +57,15 @@ export const MarkdownPreview = ({ classNames, subject, role }: PreviewProps<Docu
65
57
  );
66
58
 
67
59
  return (
68
- <div
69
- role='none'
70
- className={mx(
71
- role === 'popover' ? popoverCard : role === 'card--kanban' ? kanbanCardWithoutPoster : defaultCard,
72
- classNames,
73
- )}
74
- >
75
- <h2 className={mx(previewTitle, previewProse)}>{getTitle(subject, t('fallback title'))}</h2>
76
- {snippet && <p className={mx(previewProse, 'line-clamp-3 break-words col-span-2')}>{snippet}</p>}
77
- <div role='none' className={previewChrome}>
60
+ <Card.Container role={role}>
61
+ <Card.Heading>{getTitle(subject, t('fallback title'))}</Card.Heading>
62
+ {snippet && <Card.Text classNames='line-clamp-3 break-words col-span-2'>{snippet}</Card.Text>}
63
+ <Card.Chrome>
78
64
  <Button onClick={handleNavigate}>
79
65
  <span className='grow'>{t('navigate to document label')}</span>
80
66
  <Icon icon='ph--arrow-right--regular' />
81
67
  </Button>
82
- </div>
83
- </div>
68
+ </Card.Chrome>
69
+ </Card.Container>
84
70
  );
85
71
  };
@@ -20,11 +20,9 @@ import {
20
20
  useIntentDispatcher,
21
21
  } from '@dxos/app-framework';
22
22
  import { withPluginManager } from '@dxos/app-framework/testing';
23
- import { Type } from '@dxos/echo';
24
- import { create, createQueueDxn, type Expando } from '@dxos/echo-schema';
23
+ import { Obj, Ref, Type } from '@dxos/echo';
25
24
  import { invariant } from '@dxos/invariant';
26
25
  import { DXN } from '@dxos/keys';
27
- import { live, makeRef, refFromDXN } from '@dxos/live-object';
28
26
  import { ClientPlugin } from '@dxos/plugin-client';
29
27
  import { PreviewPlugin } from '@dxos/plugin-preview';
30
28
  import { SpacePlugin } from '@dxos/plugin-space';
@@ -69,13 +67,13 @@ const TestChat: FC<{ doc: DocumentType; content: string }> = ({ doc, content })
69
67
  const { editorState } = useCapability(MarkdownCapabilities.State);
70
68
 
71
69
  const space = useSpace();
72
- const queueDxn = useMemo(() => space && createQueueDxn(space.id), [space]);
70
+ const queueDxn = useMemo(() => space && space.queues.create().dxn, [space]);
73
71
  const queue = useQueue<Message>(queueDxn);
74
72
 
75
73
  const handleInsert = async () => {
76
74
  invariant(space);
77
75
  invariant(queue);
78
- queue.append([create(Message, { role: 'assistant', content: [{ type: 'text', text: 'Hello' }] })]);
76
+ queue.append([Obj.make(Message, { role: 'assistant', content: [{ type: 'text', text: 'Hello' }] })]);
79
77
  const message = queue.objects.at(-1);
80
78
  invariant(message);
81
79
 
@@ -96,8 +94,8 @@ const TestChat: FC<{ doc: DocumentType; content: string }> = ({ doc, content })
96
94
 
97
95
  void dispatch(
98
96
  createIntent(CollaborationActions.InsertContent, {
99
- target: doc as any as Expando, // TODO(burdon): Common base type.
100
- object: refFromDXN(new DXN(DXN.kind.QUEUE, [...queue.dxn.parts, message.id])),
97
+ target: doc as any as Type.Expando,
98
+ object: Ref.fromDXN(new DXN(DXN.kind.QUEUE, [...queue.dxn.parts, message.id])),
101
99
  at: cursor,
102
100
  label: 'Proposal',
103
101
  }),
@@ -131,8 +129,8 @@ const DefaultStory = ({ document, chat }: { document: string; chat: string }) =>
131
129
 
132
130
  // Create links.
133
131
  content: document.replaceAll(/\[(\w+)\]/g, (_, label) => {
134
- const obj = space.db.add(live(TestItem, { title: label, description: faker.lorem.paragraph() }));
135
- const dxn = makeRef(obj).dxn.toString();
132
+ const obj = space.db.add(Obj.make(TestItem, { title: label, description: faker.lorem.paragraph() }));
133
+ const dxn = Ref.make(obj).dxn.toString();
136
134
  return `[${label}][${dxn}]`;
137
135
  }),
138
136
  }),
@@ -179,6 +177,7 @@ const meta: Meta<typeof DefaultStory> = {
179
177
  ],
180
178
  parameters: {
181
179
  translations,
180
+ controls: { disable: true },
182
181
  },
183
182
  };
184
183
 
@@ -7,11 +7,11 @@ import '@dxos-theme';
7
7
  import { type Meta } from '@storybook/react';
8
8
  import React, { type FC, useCallback, useState } from 'react';
9
9
 
10
+ import { Obj } from '@dxos/echo';
10
11
  import { invariant } from '@dxos/invariant';
11
12
  import { PublicKey } from '@dxos/keys';
12
- import { live } from '@dxos/live-object';
13
13
  import { faker } from '@dxos/random';
14
- import { createDocAccessor, createObject } from '@dxos/react-client/echo';
14
+ import { createDocAccessor } from '@dxos/react-client/echo';
15
15
  import { useThemeContext } from '@dxos/react-ui';
16
16
  import {
17
17
  EditorToolbar,
@@ -38,7 +38,7 @@ faker.seed(101);
38
38
 
39
39
  const DefaultStory: FC<{ content?: string }> = ({ content = '' }) => {
40
40
  const { themeMode } = useThemeContext();
41
- const [text] = useState(createObject(live(DataType.Text, { content })));
41
+ const [text] = useState(Obj.make(DataType.Text, { content }));
42
42
  const toolbarState = useEditorToolbarState({ viewMode: 'preview' });
43
43
  const formattingObserver = useFormattingState(toolbarState);
44
44
  const { parentRef, view } = useTextEditor(() => {
@@ -4,9 +4,8 @@
4
4
 
5
5
  import { Schema } from 'effect';
6
6
 
7
- import { Type, Ref } from '@dxos/echo';
7
+ import { Obj, Ref, Type } from '@dxos/echo';
8
8
  import { LabelAnnotation } from '@dxos/echo-schema';
9
- import { live } from '@dxos/live-object';
10
9
  import { DataType } from '@dxos/schema';
11
10
 
12
11
  export const DocumentSchema = Schema.Struct({
@@ -25,7 +24,7 @@ export type DocumentType = Schema.Schema.Type<typeof DocumentType>;
25
24
 
26
25
  // TODO(burdon): Replace when defaults are supported.
27
26
  export const createDocument = ({ name, content }: { name: string; content: string }) =>
28
- live(DocumentType, { name, content: Ref.make(live(DataType.Text, { content })) });
27
+ Obj.make(DocumentType, { name, content: Ref.make(Obj.make(DataType.Text, { content })) });
29
28
 
30
29
  /**
31
30
  * Checks if an object conforms to the interface needed to render an editor.
package/src/util.tsx CHANGED
@@ -3,14 +3,14 @@
3
3
  //
4
4
 
5
5
  import { debounce } from '@dxos/async';
6
+ import { Obj, Ref } from '@dxos/echo';
6
7
  import { type TypedObjectSerializer } from '@dxos/plugin-space/types';
7
- import { live, createObject, isEchoObject, loadObjectReferences, Ref } from '@dxos/react-client/echo';
8
8
  import { DataType } from '@dxos/schema';
9
9
 
10
10
  import { DocumentType, type MarkdownProperties } from './types';
11
11
 
12
12
  export const isMarkdownProperties = (data: unknown): data is MarkdownProperties =>
13
- (isEchoObject(data) as boolean)
13
+ (Obj.isObject(data) as boolean)
14
14
  ? true
15
15
  : data && typeof data === 'object'
16
16
  ? 'title' in data && typeof data.title === 'string'
@@ -35,14 +35,12 @@ export const setFallbackName = debounce((doc: DocumentType, content: string) =>
35
35
 
36
36
  export const serializer: TypedObjectSerializer<DocumentType> = {
37
37
  serialize: async ({ object }): Promise<string> => {
38
- const content = await loadObjectReferences(object, (doc) => doc.content);
39
- return JSON.stringify({ name: object.name, fallbackName: object.fallbackName, content: content.target?.content });
38
+ const { content } = await object.content.load();
39
+ return JSON.stringify({ name: object.name, fallbackName: object.fallbackName, content });
40
40
  },
41
41
 
42
42
  deserialize: async ({ content: serialized }) => {
43
43
  const { name, fallbackName, content } = JSON.parse(serialized);
44
- return createObject(
45
- live(DocumentType, { name, fallbackName, content: Ref.make(live(DataType.Text, { content })) }),
46
- );
44
+ return Obj.make(DocumentType, { name, fallbackName, content: Ref.make(Obj.make(DataType.Text, { content })) });
47
45
  },
48
46
  };