@dxos/react-ui-editor 0.3.10 → 0.3.11-main.00a28cb

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 (222) hide show
  1. package/dist/lib/browser/index.mjs +1724 -1105
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +24 -13
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +15 -19
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -3
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -2
  11. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  14. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  15. package/dist/types/src/components/TextEditor/index.d.ts +0 -2
  16. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  17. package/dist/types/src/{hooks/yjs → components/TextEditor}/yjs.stories.d.ts +1 -1
  18. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
  19. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
  20. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  21. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  22. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  23. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  24. package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
  25. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  26. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  27. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  28. package/dist/types/src/extensions/automerge/semaphore.d.ts +14 -0
  29. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  30. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  31. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  32. package/dist/types/src/extensions/automerge/update-codemirror.d.ts +5 -0
  33. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  34. package/dist/types/src/extensions/awareness.d.ts +46 -0
  35. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  36. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  37. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  38. package/dist/types/src/extensions/code.d.ts.map +1 -0
  39. package/dist/types/src/extensions/comments.d.ts +59 -0
  40. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  41. package/dist/types/src/extensions/cursor.d.ts +25 -0
  42. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  43. package/dist/types/src/extensions/hr.d.ts +2 -0
  44. package/dist/types/src/extensions/hr.d.ts.map +1 -0
  45. package/dist/types/src/extensions/image.d.ts.map +1 -0
  46. package/dist/types/src/{components/TextEditor/extensions → extensions}/index.d.ts +7 -3
  47. package/dist/types/src/extensions/index.d.ts.map +1 -0
  48. package/dist/types/src/{components/TextEditor/extensions → extensions}/link.d.ts +1 -0
  49. package/dist/types/src/extensions/link.d.ts.map +1 -0
  50. package/dist/types/src/extensions/listener.d.ts +10 -0
  51. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  52. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  53. package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/highlight.d.ts +0 -1
  54. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  55. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  56. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  57. package/dist/types/src/extensions/outliner.d.ts +4 -0
  58. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  59. package/dist/types/src/extensions/table.d.ts.map +1 -0
  60. package/dist/types/src/extensions/tasklist.d.ts +7 -0
  61. package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
  62. package/dist/types/src/{components/TextEditor/extensions/demo.d.ts → extensions/typewriter.d.ts} +2 -3
  63. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  64. package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
  65. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
  66. package/dist/types/src/extensions/yjs/index.d.ts +2 -0
  67. package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
  68. package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
  69. package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
  70. package/dist/types/src/extensions/yjs/yjs.test.d.ts +2 -0
  71. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
  72. package/dist/types/src/hooks/automerge.d.ts +3 -0
  73. package/dist/types/src/hooks/automerge.d.ts.map +1 -0
  74. package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
  75. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  76. package/dist/types/src/hooks/index.d.ts +3 -1
  77. package/dist/types/src/hooks/index.d.ts.map +1 -1
  78. package/dist/types/src/hooks/useTextModel.d.ts +15 -11
  79. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  80. package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
  81. package/dist/types/src/hooks/yjs.d.ts.map +1 -0
  82. package/dist/types/src/index.d.ts +6 -0
  83. package/dist/types/src/index.d.ts.map +1 -1
  84. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  85. package/dist/types/src/styles/index.d.ts +1 -0
  86. package/dist/types/src/styles/index.d.ts.map +1 -1
  87. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  88. package/dist/types/src/styles/tokens.d.ts +1 -0
  89. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  90. package/dist/types/src/testing/replicator.d.ts +2 -2
  91. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  92. package/dist/types/src/{components/TextEditor/themes → themes}/default.d.ts +5 -13
  93. package/dist/types/src/themes/default.d.ts.map +1 -0
  94. package/dist/types/src/themes/index.d.ts.map +1 -0
  95. package/dist/types/src/util.d.ts +5 -0
  96. package/dist/types/src/util.d.ts.map +1 -0
  97. package/package.json +28 -22
  98. package/src/components/TextEditor/MarkdownEditor.stories.tsx +150 -87
  99. package/src/components/TextEditor/TextEditor.stories.tsx +14 -35
  100. package/src/components/TextEditor/TextEditor.tsx +87 -79
  101. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +67 -8
  102. package/src/components/TextEditor/codemirror.stories.ts +3 -2
  103. package/src/components/TextEditor/comments.stories.tsx +355 -0
  104. package/src/components/TextEditor/index.ts +0 -3
  105. package/src/{hooks/yjs → components/TextEditor}/yjs.stories.tsx +1 -1
  106. package/src/{components/TextEditor/extensions → extensions}/autocomplete.ts +18 -17
  107. package/src/extensions/automerge/automerge.ts +92 -0
  108. package/src/extensions/automerge/cursor.ts +52 -0
  109. package/src/extensions/automerge/defs.ts +66 -0
  110. package/src/extensions/automerge/index.ts +6 -0
  111. package/src/{hooks/automerge/PatchSemaphore.ts → extensions/automerge/semaphore.ts} +26 -25
  112. package/src/{hooks/automerge/translation/codemirror-to-automerge.ts → extensions/automerge/update-automerge.ts} +6 -7
  113. package/src/{hooks/automerge/translation/automerge-to-codemirror.ts → extensions/automerge/update-codemirror.ts} +14 -11
  114. package/src/extensions/awareness.ts +310 -0
  115. package/src/extensions/code.ts +104 -0
  116. package/src/extensions/comments.ts +408 -0
  117. package/src/extensions/cursor.ts +46 -0
  118. package/src/extensions/hr.ts +72 -0
  119. package/src/extensions/image.ts +87 -0
  120. package/src/{components/TextEditor/extensions → extensions}/index.ts +7 -3
  121. package/src/{components/TextEditor/extensions → extensions}/link.ts +116 -62
  122. package/src/extensions/listener.ts +35 -0
  123. package/src/{components/TextEditor/extensions → extensions}/markdown/highlight.ts +4 -16
  124. package/src/extensions/outliner.ts +12 -0
  125. package/src/{components/TextEditor/extensions → extensions}/table.ts +23 -4
  126. package/src/extensions/tasklist.ts +96 -0
  127. package/src/{components/TextEditor/extensions/demo.ts → extensions/typewriter.ts} +2 -3
  128. package/src/extensions/yjs/cursor.ts +22 -0
  129. package/src/extensions/yjs/index.ts +5 -0
  130. package/src/extensions/yjs/yjs.test.ts +35 -0
  131. package/src/extensions/yjs/yjs.ts +16 -0
  132. package/src/hooks/automerge.ts +52 -0
  133. package/src/hooks/awareness-provider.ts +120 -0
  134. package/src/hooks/index.ts +4 -1
  135. package/src/hooks/useTextModel.ts +40 -79
  136. package/src/hooks/{yjs/space-provider.ts → yjs.ts} +49 -19
  137. package/src/index.ts +6 -0
  138. package/src/styles/index.ts +1 -0
  139. package/src/styles/markdown.ts +0 -2
  140. package/src/styles/tokens.ts +3 -0
  141. package/src/testing/replicator.ts +10 -16
  142. package/src/themes/default.ts +275 -0
  143. package/src/util.ts +18 -0
  144. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
  145. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
  146. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
  147. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +0 -1
  148. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +0 -18
  149. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +0 -1
  150. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +0 -1
  151. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts +0 -10
  152. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +0 -1
  153. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts +0 -24
  154. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts.map +0 -1
  155. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
  156. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  157. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
  158. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +0 -9
  159. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
  160. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  161. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
  162. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  163. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
  164. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
  165. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +0 -3
  166. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
  167. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
  168. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
  169. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
  170. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
  171. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts +0 -19
  172. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +0 -1
  173. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  174. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  175. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  176. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  177. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  178. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  179. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  180. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts +0 -5
  181. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts.map +0 -1
  182. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts +0 -6
  183. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts.map +0 -1
  184. package/dist/types/src/hooks/automerge/translation/index.d.ts +0 -3
  185. package/dist/types/src/hooks/automerge/translation/index.d.ts.map +0 -1
  186. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  187. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  188. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  189. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  190. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  191. package/src/components/TextEditor/extensions/code.ts +0 -99
  192. package/src/components/TextEditor/extensions/comments.ts +0 -173
  193. package/src/components/TextEditor/extensions/experimental.tsx +0 -114
  194. package/src/components/TextEditor/extensions/highlight.ts +0 -128
  195. package/src/components/TextEditor/extensions/image.ts +0 -67
  196. package/src/components/TextEditor/extensions/listener.ts +0 -23
  197. package/src/components/TextEditor/extensions/tasklist.ts +0 -121
  198. package/src/components/TextEditor/extensions/tooltip.tsx +0 -61
  199. package/src/components/TextEditor/themes/default.ts +0 -279
  200. package/src/hooks/automerge/handle.ts +0 -14
  201. package/src/hooks/automerge/index.ts +0 -6
  202. package/src/hooks/automerge/plugin.ts +0 -134
  203. package/src/hooks/automerge/translation/index.ts +0 -6
  204. package/src/hooks/yjs/index.ts +0 -6
  205. /package/dist/types/src/{components/TextEditor/extensions → extensions}/autocomplete.d.ts +0 -0
  206. /package/dist/types/src/{components/TextEditor/extensions → extensions}/basic.d.ts +0 -0
  207. /package/dist/types/src/{components/TextEditor/extensions → extensions}/blast.d.ts +0 -0
  208. /package/dist/types/src/{components/TextEditor/extensions → extensions}/code.d.ts +0 -0
  209. /package/dist/types/src/{components/TextEditor/extensions → extensions}/image.d.ts +0 -0
  210. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/bundle.d.ts +0 -0
  211. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/index.d.ts +0 -0
  212. /package/dist/types/src/{components/TextEditor/extensions → extensions}/mention.d.ts +0 -0
  213. /package/dist/types/src/{components/TextEditor/extensions → extensions}/table.d.ts +0 -0
  214. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  215. /package/dist/types/src/{components/TextEditor/themes → themes}/index.d.ts +0 -0
  216. /package/src/{components/TextEditor/extensions → extensions}/basic.ts +0 -0
  217. /package/src/{components/TextEditor/extensions → extensions}/blast.ts +0 -0
  218. /package/src/{components/TextEditor/extensions → extensions}/markdown/bundle.ts +0 -0
  219. /package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +0 -0
  220. /package/src/{components/TextEditor/extensions → extensions}/mention.ts +0 -0
  221. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  222. /package/src/{components/TextEditor/themes → themes}/index.ts +0 -0
@@ -15,16 +15,17 @@ import React, {
15
15
  useEffect,
16
16
  useImperativeHandle,
17
17
  useState,
18
+ useRef,
18
19
  } from 'react';
19
20
 
20
21
  import { generateName } from '@dxos/display-name';
21
22
  import { useThemeContext } from '@dxos/react-ui';
22
23
  import { getColorForValue, inputSurface, mx } from '@dxos/react-ui-theme';
23
24
 
24
- import { basicBundle, markdownBundle } from './extensions';
25
- import { defaultTheme, markdownTheme, textTheme } from './themes';
26
- import { type EditorModel } from '../../hooks';
25
+ import { basicBundle, markdownBundle, setCommentRange } from '../../extensions';
26
+ import { type CommentRange, type EditorModel } from '../../hooks';
27
27
  import { type ThemeStyles } from '../../styles';
28
+ import { defaultTheme, markdownTheme, textTheme } from '../../themes';
28
29
 
29
30
  export const EditorModes = ['default', 'vim'] as const;
30
31
  export type EditorMode = (typeof EditorModes)[number];
@@ -34,49 +35,55 @@ export type CursorInfo = {
34
35
  to: number;
35
36
  line: number;
36
37
  lines: number;
38
+ length: number;
37
39
  after?: string;
38
40
  };
39
41
 
40
- export type TextEditorRef = {
41
- root: HTMLDivElement | null;
42
- state?: EditorState;
43
- view?: EditorView;
44
- };
45
-
46
42
  export type TextEditorSlots = {
47
43
  root?: Omit<ComponentProps<'div'>, 'ref'>;
48
- editor?: {
49
- className?: string;
50
- placeholder?: string;
51
- spellCheck?: boolean;
52
- tabIndex?: number;
53
- theme?: ThemeStyles;
54
- };
44
+ // editor?: {
45
+ // className?: string;
46
+ // };
55
47
  };
56
48
 
49
+ // TODO(burdon): Spellcheck?
57
50
  export type TextEditorProps = {
58
51
  model: EditorModel;
59
- readonly?: boolean;
60
- editorMode?: EditorMode;
52
+ autofocus?: boolean;
53
+ readonly?: boolean; // TODO(burdon): Move into model.
54
+ selection?: { anchor: number; head?: number };
55
+ comments?: CommentRange[]; // TODO(burdon): Move into extension?
61
56
  extensions?: Extension[];
57
+ editorMode?: EditorMode;
58
+ placeholder?: string;
59
+ theme?: ThemeStyles;
62
60
  slots?: TextEditorSlots;
63
61
  };
64
62
 
65
63
  /**
66
64
  * Base text editor.
67
- * NOTE: Rather than adding properties, try to create extensions that can be reused.
68
65
  */
69
- export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
70
- ({ model, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
71
- const { themeMode } = useThemeContext();
66
+ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
67
+ (
68
+ { model, autofocus, readonly, selection, comments, extensions = [], editorMode, theme, slots = defaultSlots },
69
+ forwardedRef,
70
+ ) => {
72
71
  const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
72
+ const { themeMode } = useThemeContext();
73
73
 
74
- const [root, setRoot] = useState<HTMLDivElement | null>(null);
75
- const [state, setState] = useState<EditorState>();
76
- const [view, setView] = useState<EditorView>();
77
- useImperativeHandle(forwardedRef, () => ({ root, state, view }), [view, state, root]);
74
+ // The editor view ref should only be used as an escape hatch.
75
+ const rootRef = useRef<HTMLDivElement>(null);
76
+ const [view, setView] = useState<EditorView | null>(null);
77
+ // NOTE: This does not cause the parent to re-render, so the ref is not available immediately.
78
+ useImperativeHandle<EditorView | null, EditorView | null>(forwardedRef, () => view, [view]);
78
79
 
79
- // TODO(burdon): Factor out?
80
+ useEffect(() => {
81
+ if (autofocus) {
82
+ view?.focus();
83
+ }
84
+ }, [autofocus, view]);
85
+
86
+ // TODO(burdon): Factor out as extension/hook.
80
87
  const { awareness, peer } = model;
81
88
  useEffect(() => {
82
89
  if (awareness && peer) {
@@ -88,30 +95,35 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
88
95
  }
89
96
  }, [awareness, peer, themeMode]);
90
97
 
91
- // TODO(burdon): Get from model.
92
- // useHighlights(view, [
93
- // { id: 'c-1', from: 0, to: 20 },
94
- // { id: 'c-2', from: 100, to: 120 },
95
- // ]);
98
+ // TODO(burdon): Factor out as extension/hook.
99
+ useEffect(() => {
100
+ if (view && comments !== undefined) {
101
+ view.dispatch({
102
+ effects: setCommentRange.of({ model, comments }),
103
+ });
104
+ }
105
+ }, [view, comments]);
96
106
 
97
107
  useEffect(() => {
98
- if (!root) {
108
+ if (!model || !rootRef.current) {
99
109
  return;
100
110
  }
101
111
 
112
+ // https://codemirror.net/docs/ref/#state.EditorStateConfig
102
113
  const state = EditorState.create({
103
114
  doc: model.text(),
115
+ // TODO(burdon): Composer should store and set selection when switching documents.
116
+ selection,
104
117
  extensions: [
105
118
  readonly && EditorState.readOnly.of(readonly),
106
119
 
107
- // TODO(burdon): Factor out VIM mode?
120
+ // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
108
121
  editorMode === 'vim' && vim(),
109
122
 
110
123
  // Theme.
111
- // TODO(burdon): Make optional.
124
+ // TODO(burdon): Make theme configurable.
112
125
  EditorView.baseTheme(defaultTheme),
113
- EditorView.theme(slots?.editor?.theme ?? {}),
114
- // TODO(burdon): themeMode doesn't change in storybooks.
126
+ EditorView.theme(theme ?? {}),
115
127
  EditorView.darkTheme.of(themeMode === 'dark'),
116
128
 
117
129
  // Storage and replication.
@@ -124,39 +136,37 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
124
136
 
125
137
  // NOTE: This repaints the editor.
126
138
  // If the new state is derived from the old state, it will likely not be visible other than the cursor resetting.
127
- // Ideally this should not be hit except when changing between text objects.
139
+ // Ideally this should not happen except when changing between text objects.
128
140
  view?.destroy();
129
- setView(
130
- new EditorView({
131
- state,
132
- parent: root,
133
- // NOTE: Uncomment to spy on all transactions.
134
- // https://codemirror.net/docs/ref/#view.EditorView.dispatch
135
- // dispatch: (transaction, view) => {
136
- // view.update([transaction]);
137
- // },
138
- }),
139
- );
140
- setState(state);
141
+ const newView = new EditorView({
142
+ parent: rootRef.current,
143
+ state,
144
+ // NOTE: Uncomment to spy on all transactions.
145
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
146
+ // dispatch: (transaction, view) => {
147
+ // view.update([transaction]);
148
+ // },
149
+ });
141
150
 
151
+ setView(newView);
142
152
  return () => {
143
- view?.destroy();
144
- setView(undefined);
145
- setState(undefined);
153
+ newView?.destroy();
154
+ setView(null);
146
155
  };
147
- }, [root, model, themeMode, readonly, editorMode]);
156
+ }, [rootRef, model, readonly, editorMode, themeMode]);
148
157
 
149
158
  const handleKeyUp = useCallback(
150
159
  (event: KeyboardEvent) => {
151
160
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
152
161
  switch (key) {
153
- case 'Enter': {
154
- view?.contentDOM.focus();
155
- break;
156
- }
162
+ // TODO(burdon): Is this required (for vim mode?)
163
+ // case 'Enter': {
164
+ // view?.contentDOM.focus();
165
+ // break;
166
+ // }
157
167
 
158
168
  case 'Escape': {
159
- editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
169
+ editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
160
170
  break;
161
171
  }
162
172
  }
@@ -167,42 +177,46 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
167
177
  return (
168
178
  <div
169
179
  key={model.id}
170
- ref={setRoot}
180
+ role='none'
171
181
  tabIndex={0}
172
- {...slots?.root}
173
- {...(editorMode !== 'vim' && tabsterDOMAttribute)}
174
182
  onKeyUp={handleKeyUp}
183
+ {...slots.root}
184
+ {...(editorMode !== 'vim' && tabsterDOMAttribute)}
185
+ ref={rootRef}
175
186
  />
176
187
  );
177
188
  },
178
189
  );
179
190
 
180
- export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
181
- ({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
191
+ export const TextEditor = forwardRef<EditorView, TextEditorProps>(
192
+ ({ readonly, placeholder, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
182
193
  const { themeMode } = useThemeContext();
183
- const slots = defaultsDeep({}, _slots, defaultTextSlots);
194
+ const updatedSlots = defaultsDeep({}, slots, defaultTextSlots);
184
195
  return (
185
196
  <BaseTextEditor
186
197
  ref={forwardedRef}
187
198
  readonly={readonly}
188
- extensions={[basicBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
189
- slots={slots}
199
+ extensions={[basicBundle({ readonly, themeMode, placeholder }), ...extensions]}
200
+ theme={theme}
201
+ slots={updatedSlots}
190
202
  {...props}
191
203
  />
192
204
  );
193
205
  },
194
206
  );
195
207
 
196
- export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
197
- ({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
208
+ // TODO(burdon): Remove (Just provide bundle, slots).
209
+ export const MarkdownEditor = forwardRef<EditorView, TextEditorProps>(
210
+ ({ readonly, placeholder, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
198
211
  const { themeMode } = useThemeContext();
199
- const slots = defaultsDeep({}, _slots, defaultMarkdownSlots);
212
+ const updatedSlots = defaultsDeep({}, slots, defaultMarkdownSlots);
200
213
  return (
201
214
  <BaseTextEditor
202
215
  ref={forwardedRef}
203
216
  readonly={readonly}
204
- extensions={[markdownBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
205
- slots={slots}
217
+ extensions={[markdownBundle({ readonly, themeMode, placeholder }), ...extensions]}
218
+ theme={theme}
219
+ slots={updatedSlots}
206
220
  {...props}
207
221
  />
208
222
  );
@@ -211,20 +225,14 @@ export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
211
225
 
212
226
  export const defaultSlots: TextEditorSlots = {
213
227
  root: {
214
- className: mx('p-2', inputSurface),
228
+ className: mx('w-full p-2 overflow-y-auto', inputSurface),
215
229
  },
216
230
  };
217
231
 
218
232
  export const defaultTextSlots: TextEditorSlots = {
219
233
  ...defaultSlots,
220
- editor: {
221
- theme: textTheme,
222
- },
223
234
  };
224
235
 
225
236
  export const defaultMarkdownSlots: TextEditorSlots = {
226
237
  ...defaultSlots,
227
- editor: {
228
- theme: markdownTheme,
229
- },
230
238
  };
@@ -2,19 +2,29 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@preact/signals-react'; // Register react integration
5
+ import '@dxosTheme';
6
+
6
7
  import { BroadcastChannelNetworkAdapter } from '@automerge/automerge-repo-network-broadcastchannel';
7
8
  import { EditorView } from '@codemirror/view';
9
+ import '@preact/signals-react'; // Register react integration
8
10
  import { basicSetup } from 'codemirror';
9
11
  import get from 'lodash.get';
10
12
  import React, { useEffect, useRef, useState } from 'react';
11
13
 
12
- // TODO(burdon): Why separate imports?
13
14
  import { type Prop } from '@dxos/automerge/automerge';
14
- import { type DocHandle, Repo } from '@dxos/automerge/automerge-repo';
15
+ import { Repo, type DocHandle } from '@dxos/automerge/automerge-repo';
16
+ import { Filter, setGlobalAutomergePreference } from '@dxos/echo-schema';
17
+ import { type PublicKey } from '@dxos/keys';
18
+ import { Expando, TextObject, useSpace } from '@dxos/react-client/echo';
19
+ import { useIdentity } from '@dxos/react-client/halo';
20
+ import { ClientRepeater } from '@dxos/react-client/testing';
21
+ import { withTheme } from '@dxos/storybook-utils';
15
22
 
16
- import { type IDocHandle } from './handle';
17
- import { automergePlugin } from './plugin';
23
+ import { MarkdownEditor } from './TextEditor';
24
+ import { type IDocHandle, automerge, awareness } from '../../extensions';
25
+ import { useTextModel } from '../../hooks';
26
+
27
+ // TODO(burdon): Move to components.
18
28
 
19
29
  type EditorProps = {
20
30
  handle: IDocHandle;
@@ -28,7 +38,7 @@ const Editor = ({ handle, path }: EditorProps) => {
28
38
  useEffect(() => {
29
39
  const view = (editorRoot.current = new EditorView({
30
40
  doc: get(handle.docSync()!, path),
31
- extensions: [basicSetup, automergePlugin(handle, path)],
41
+ extensions: [basicSetup, automerge({ handle, path }), awareness()],
32
42
  parent: containerRef.current as any,
33
43
  }));
34
44
 
@@ -85,8 +95,57 @@ const Story = () => {
85
95
  export default {
86
96
  title: 'react-ui-editor/Automerge',
87
97
  component: Editor,
98
+ render: () => <Story />,
88
99
  };
89
100
 
90
- export const Default = {
91
- render: () => <Story />,
101
+ export const Default = {};
102
+
103
+ const EchoStory = ({ id, spaceKey }: { id: number; spaceKey: PublicKey }) => {
104
+ const identity = useIdentity();
105
+ const space = useSpace(spaceKey);
106
+ // TODO(dmaretskyi): useQuery doesn't work.
107
+ const [obj] = space?.db.query(Filter.from({ type: 'test' })).objects ?? [];
108
+
109
+ const model = useTextModel({
110
+ text: obj?.content,
111
+ identity,
112
+ space,
113
+ });
114
+
115
+ if (!model) {
116
+ return null;
117
+ }
118
+
119
+ return (
120
+ // <div className={mx(fixedInsetFlexLayout, groupSurface)}>
121
+ <div className='flex justify-center overflow-y-scroll'>
122
+ <div className='flex flex-col w-[800px] py-16'>
123
+ <MarkdownEditor model={model} />
124
+ <div className='flex shrink-0 h-[300px]'></div>
125
+ </div>
126
+ </div>
127
+ // </div>
128
+ );
129
+ };
130
+
131
+ export const WithEcho = {
132
+ render: () => {
133
+ setGlobalAutomergePreference(true);
134
+ return (
135
+ <ClientRepeater
136
+ count={2}
137
+ createSpace
138
+ onCreateSpace={async (space) => {
139
+ space.db.add(
140
+ new Expando({
141
+ type: 'test',
142
+ content: new TextObject('Hello world!'),
143
+ }),
144
+ );
145
+ }}
146
+ Component={EchoStory}
147
+ />
148
+ );
149
+ },
150
+ decorators: [withTheme],
92
151
  };
@@ -17,7 +17,8 @@ import { type Identity } from '@dxos/react-client/halo';
17
17
  import { joinCommonSpace, TestBuilder, textGenerator } from '@dxos/react-client/testing';
18
18
  import { YText } from '@dxos/text-model';
19
19
 
20
- import { cursorColor, SpaceAwarenessProvider } from '../../hooks';
20
+ import { YAwarenessProvider } from '../../hooks';
21
+ import { cursorColor } from '../../styles';
21
22
  import { EditorDocument, types as schema } from '../../testing';
22
23
 
23
24
  export default {
@@ -35,7 +36,7 @@ const updateEditor = async (id: number, editor: HTMLDivElement, identity: Identi
35
36
  return;
36
37
  }
37
38
 
38
- const { awareness } = new SpaceAwarenessProvider({ space, doc, channel: `yjs.awareness.${text.id}` });
39
+ const { awareness } = new YAwarenessProvider({ space, doc, channel: `yjs.awareness.${text.id}` });
39
40
  awareness.setLocalStateField('user', {
40
41
  name: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
41
42
  // TODO(wittjosiah): Pick colours from theme based on identity key.