@dxos/react-ui-editor 0.3.11-main.b00882f → 0.3.11-main.b3f97d8

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 (168) hide show
  1. package/dist/lib/browser/index.mjs +2386 -968
  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 +1 -2
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  7. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +1 -1
  8. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/TextEditor/{yjs.stories.d.ts → hooks.stories.d.ts} +13 -2
  10. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -0
  11. package/dist/types/src/components/Toolbar/Toolbar.d.ts +29 -0
  12. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
  13. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +25 -0
  14. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
  15. package/dist/types/src/components/Toolbar/index.d.ts +2 -0
  16. package/dist/types/src/components/Toolbar/index.d.ts.map +1 -0
  17. package/dist/types/src/components/index.d.ts +1 -0
  18. package/dist/types/src/components/index.d.ts.map +1 -1
  19. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  20. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
  21. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
  22. package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
  23. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
  24. package/dist/types/src/extensions/automerge/defs.d.ts +5 -5
  25. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  26. package/dist/types/src/extensions/automerge/semaphore.d.ts +2 -1
  27. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  28. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  29. package/dist/types/src/extensions/awareness.d.ts +2 -4
  30. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  31. package/dist/types/src/extensions/basic.d.ts +1 -1
  32. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  33. package/dist/types/src/extensions/comments.d.ts +11 -3
  34. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  35. package/dist/types/src/extensions/index.d.ts +0 -7
  36. package/dist/types/src/extensions/index.d.ts.map +1 -1
  37. package/dist/types/src/extensions/markdown/bundle.d.ts +2 -2
  38. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  39. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
  40. package/dist/types/src/extensions/markdown/formatting.d.ts +57 -0
  41. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  42. package/dist/types/src/extensions/markdown/formatting.test.d.ts +3 -0
  43. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -0
  44. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  45. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  46. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  47. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  48. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  49. package/dist/types/src/extensions/markdown/index.d.ts +8 -0
  50. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  51. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  52. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  53. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  54. package/dist/types/src/hooks/awareness-provider.d.ts +7 -4
  55. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -1
  56. package/dist/types/src/hooks/defs.d.ts +1 -4
  57. package/dist/types/src/hooks/defs.d.ts.map +1 -1
  58. package/dist/types/src/hooks/index.d.ts +2 -2
  59. package/dist/types/src/hooks/index.d.ts.map +1 -1
  60. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  61. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  62. package/dist/types/src/hooks/useEditorView.d.ts +17 -0
  63. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
  64. package/dist/types/src/hooks/useTextEditor.d.ts +36 -14
  65. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  66. package/dist/types/src/hooks/useTextModel.d.ts +12 -0
  67. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  68. package/dist/types/src/index.d.ts +1 -1
  69. package/dist/types/src/index.d.ts.map +1 -1
  70. package/dist/types/src/styles/markdown.d.ts +2 -2
  71. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  72. package/dist/types/src/themes/default.d.ts.map +1 -1
  73. package/package.json +39 -29
  74. package/src/components/TextEditor/MarkdownEditor.stories.tsx +15 -19
  75. package/src/components/TextEditor/TextEditor.tsx +27 -19
  76. package/src/components/TextEditor/automerge.stories.tsx +1 -2
  77. package/src/components/TextEditor/hooks.stories.tsx +111 -0
  78. package/src/components/Toolbar/Toolbar.stories.tsx +102 -0
  79. package/src/components/Toolbar/Toolbar.tsx +245 -0
  80. package/src/components/Toolbar/index.ts +5 -0
  81. package/src/components/index.ts +1 -0
  82. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  83. package/src/extensions/automerge/automerge.test.ts +13 -0
  84. package/src/extensions/automerge/automerge.ts +26 -11
  85. package/src/extensions/automerge/cursor.ts +3 -3
  86. package/src/extensions/automerge/defs.ts +9 -9
  87. package/src/extensions/automerge/semaphore.ts +17 -19
  88. package/src/extensions/automerge/update-automerge.ts +12 -6
  89. package/src/extensions/automerge/update-codemirror.ts +5 -5
  90. package/src/extensions/awareness.ts +21 -35
  91. package/src/extensions/basic.ts +15 -13
  92. package/src/extensions/blast.ts +4 -1
  93. package/src/extensions/comments.ts +126 -68
  94. package/src/extensions/index.ts +0 -7
  95. package/src/extensions/markdown/bundle.ts +9 -8
  96. package/src/extensions/markdown/code.ts +150 -0
  97. package/src/extensions/markdown/formatting.test.ts +481 -0
  98. package/src/extensions/markdown/formatting.ts +1198 -0
  99. package/src/extensions/markdown/heading.ts +59 -0
  100. package/src/extensions/markdown/highlight.ts +9 -15
  101. package/src/extensions/{hr.ts → markdown/hr.ts} +3 -4
  102. package/src/extensions/markdown/index.ts +8 -0
  103. package/src/extensions/{link.ts → markdown/link.ts} +13 -4
  104. package/src/extensions/{tasklist.ts → markdown/tasklist.ts} +2 -3
  105. package/src/hooks/awareness-provider.ts +24 -20
  106. package/src/hooks/defs.ts +1 -6
  107. package/src/hooks/index.ts +2 -2
  108. package/src/hooks/useActionHandler.ts +93 -0
  109. package/src/hooks/useEditorView.ts +29 -0
  110. package/src/hooks/useTextEditor.ts +143 -19
  111. package/src/hooks/useTextModel.ts +65 -12
  112. package/src/index.ts +1 -1
  113. package/src/styles/markdown.ts +9 -10
  114. package/src/themes/default.ts +11 -8
  115. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  116. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  117. package/dist/types/src/components/TextEditor/comments.stories.d.ts +0 -26
  118. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +0 -1
  119. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  120. package/dist/types/src/extensions/code.d.ts.map +0 -1
  121. package/dist/types/src/extensions/hr.d.ts.map +0 -1
  122. package/dist/types/src/extensions/image.d.ts.map +0 -1
  123. package/dist/types/src/extensions/link.d.ts.map +0 -1
  124. package/dist/types/src/extensions/table.d.ts.map +0 -1
  125. package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
  126. package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
  127. package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
  128. package/dist/types/src/extensions/yjs/index.d.ts +0 -2
  129. package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
  130. package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
  131. package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
  132. package/dist/types/src/extensions/yjs/yjs.test.d.ts +0 -2
  133. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
  134. package/dist/types/src/hooks/automerge.d.ts +0 -4
  135. package/dist/types/src/hooks/automerge.d.ts.map +0 -1
  136. package/dist/types/src/hooks/yjs.d.ts +0 -28
  137. package/dist/types/src/hooks/yjs.d.ts.map +0 -1
  138. package/dist/types/src/testing/index.d.ts +0 -3
  139. package/dist/types/src/testing/index.d.ts.map +0 -1
  140. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  141. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  142. package/dist/types/src/testing/proto/index.d.ts +0 -2
  143. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  144. package/dist/types/src/testing/replicator.d.ts +0 -45
  145. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  146. package/src/components/TextEditor/codemirror.stories.ts +0 -115
  147. package/src/components/TextEditor/comments.stories.tsx +0 -357
  148. package/src/components/TextEditor/yjs.stories.tsx +0 -56
  149. package/src/extensions/code.ts +0 -104
  150. package/src/extensions/yjs/cursor.ts +0 -22
  151. package/src/extensions/yjs/index.ts +0 -5
  152. package/src/extensions/yjs/yjs.test.ts +0 -35
  153. package/src/extensions/yjs/yjs.ts +0 -16
  154. package/src/hooks/automerge.ts +0 -53
  155. package/src/hooks/yjs.ts +0 -157
  156. package/src/testing/index.ts +0 -6
  157. package/src/testing/proto/gen/schema.ts +0 -49
  158. package/src/testing/proto/index.ts +0 -5
  159. package/src/testing/proto/schema.proto +0 -15
  160. package/src/testing/replicator.ts +0 -184
  161. /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
  162. /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
  163. /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
  164. /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
  165. /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
  166. /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.ts} +0 -0
  167. /package/src/extensions/{image.ts → markdown/image.ts} +0 -0
  168. /package/src/extensions/{table.ts → markdown/table.ts} +0 -0
@@ -19,11 +19,14 @@ import { withTheme } from '@dxos/storybook-utils';
19
19
 
20
20
  import { MarkdownEditor, type TextEditorProps } from './TextEditor';
21
21
  import {
22
+ type CommentsOptions,
23
+ type LinkOptions,
22
24
  autocomplete,
23
25
  blast,
24
26
  code,
25
27
  comments,
26
28
  defaultOptions,
29
+ heading,
27
30
  hr,
28
31
  image,
29
32
  link,
@@ -31,16 +34,11 @@ import {
31
34
  table,
32
35
  tasklist,
33
36
  typewriter,
34
- type CommentsOptions,
35
- type LinkOptions,
36
37
  useComments,
38
+ formatting,
37
39
  } from '../../extensions';
38
40
  import { type Comment, useTextModel } from '../../hooks';
39
41
 
40
- // Extensions:
41
- // TODO(burdon): Table of contents.
42
- // TODO(burdon): Front-matter
43
-
44
42
  faker.seed(101);
45
43
 
46
44
  const str = (...lines: string[]) => lines.join('\n');
@@ -82,7 +80,7 @@ const text = {
82
80
  '## Code',
83
81
  '',
84
82
  '```',
85
- 'const x = 100;',
83
+ '$ ls -las',
86
84
  '```',
87
85
  '',
88
86
  '```tsx',
@@ -213,11 +211,10 @@ const onRenderLink: LinkOptions['onRender'] = (el, url) => {
213
211
  type StoryProps = {
214
212
  text?: string;
215
213
  comments?: Comment[];
216
- automerge?: boolean;
217
214
  } & Pick<TextEditorProps, 'readonly' | 'placeholder' | 'slots' | 'extensions'>;
218
215
 
219
- const Story = ({ text, comments, automerge, placeholder = 'New document.', ...props }: StoryProps) => {
220
- const [item] = useState({ text: new TextObject(text, undefined, undefined, { automerge }) });
216
+ const Story = ({ text, comments, placeholder = 'New document.', ...props }: StoryProps) => {
217
+ const [item] = useState({ text: new TextObject(text) });
221
218
  const view = useRef<EditorView>(null);
222
219
  const model = useTextModel({ text: item.text });
223
220
  useComments(view.current, comments);
@@ -248,12 +245,11 @@ const defaults = [
248
245
  onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
249
246
  }),
250
247
  code(),
248
+ formatting(),
249
+ heading(),
251
250
  hr(),
252
251
  image(),
253
252
  link({ onRender: onRenderLink, onHover: onHoverLinkTooltip }),
254
- // mention({
255
- // onSearch: (text) => names.filter((name) => name.toLowerCase().startsWith(text.toLowerCase())),
256
- // }),
257
253
  table(),
258
254
  tasklist(),
259
255
  ];
@@ -290,11 +286,11 @@ export const Links = {
290
286
  };
291
287
 
292
288
  export const Code = {
293
- render: () => <Story text={str(text.code, text.footer)} extensions={[code()]} readonly />,
289
+ render: () => <Story text={str(text.code, text.footer)} extensions={[code()]} />,
294
290
  };
295
291
 
296
292
  export const Image = {
297
- render: () => <Story text={str(text.image, text.footer)} readonly extensions={[image()]} />,
293
+ render: () => <Story text={str(text.image, text.footer)} extensions={[image()]} />,
298
294
  };
299
295
 
300
296
  export const Lists = {
@@ -345,18 +341,18 @@ export const Mention = {
345
341
 
346
342
  export const Comments = {
347
343
  render: () => {
348
- const [commentRanges, setCommentRanges] = useState<Comment[]>([]);
344
+ const [_comments, setComments] = useState<Comment[]>([]);
349
345
 
350
346
  return (
351
347
  <Story
352
348
  text={str('# Comments', '', text.paragraphs, text.footer)}
353
- comments={commentRanges}
349
+ comments={_comments}
354
350
  extensions={[
355
351
  comments({
356
352
  onHover: onCommentsHover,
357
- onCreate: (range) => {
353
+ onCreate: ({ cursor }) => {
358
354
  const id = PublicKey.random().toHex();
359
- setCommentRanges((commentRanges) => [...commentRanges, { id, cursor: range }]);
355
+ setComments((commentRanges) => [...commentRanges, { id, cursor }]);
360
356
  return id;
361
357
  },
362
358
  onSelect: (state) => {
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { EditorState, type Extension, type StateEffect } from '@codemirror/state';
6
6
  import { EditorView } from '@codemirror/view';
7
- import { useFocusableGroup } from '@fluentui/react-tabster';
7
+ // import { useFocusableGroup } from '@fluentui/react-tabster';
8
8
  import { vim } from '@replit/codemirror-vim';
9
9
  import defaultsDeep from 'lodash.defaultsdeep';
10
10
  import React, {
@@ -20,9 +20,10 @@ import React, {
20
20
 
21
21
  import { log } from '@dxos/log';
22
22
  import { useThemeContext } from '@dxos/react-ui';
23
- import { inputSurface, mx } from '@dxos/react-ui-theme';
23
+ import { focusRing, mx } from '@dxos/react-ui-theme';
24
+ import { isNotFalsy } from '@dxos/util';
24
25
 
25
- import { basicBundle, markdownBundle, useAwareness } from '../../extensions';
26
+ import { createBasicBundle, createMarkdownExtensions } from '../../extensions';
26
27
  import { type EditorModel } from '../../hooks';
27
28
  import { type ThemeStyles } from '../../styles';
28
29
  import { defaultTheme, markdownTheme, textTheme } from '../../themes';
@@ -86,7 +87,12 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
86
87
  },
87
88
  forwardedRef,
88
89
  ) => {
89
- const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
90
+ // TODO(burdon): Hook causes error even if properties are not spread into div.
91
+ // Uncaught TypeError: Cannot read properties of undefined (reading 'relatedTarget')
92
+ // Uses event.detail, which is deprecated (not event.details). At runtime the event has a property `details`.
93
+ // https://github.com/microsoft/tabster/blob/master/src/State/FocusedElement.ts#L348 (e.detail.relatedTarget)
94
+ // https://github.com/microsoft/keyborg/blob/49e49b2c3ba0a5f6cc518ac46825d7551def8109/src/FocusEvent.ts#L58
95
+ // const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
90
96
  const { themeMode } = useThemeContext();
91
97
 
92
98
  const rootRef = useRef<HTMLDivElement>(null);
@@ -98,14 +104,16 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
98
104
 
99
105
  // Set focus.
100
106
  useEffect(() => {
101
- if (autoFocus) {
107
+ if (autoFocus && !view?.hasFocus) {
108
+ if (view?.state.selection.main?.from === 0) {
109
+ // Start at end of line.
110
+ const { to } = view.state.doc.lineAt(0);
111
+ view?.dispatch({ selection: { anchor: to } });
112
+ }
102
113
  view?.focus();
103
114
  }
104
115
  }, [view, autoFocus]);
105
116
 
106
- // Monitor awareness.
107
- useAwareness(model);
108
-
109
117
  // Create editor state and view.
110
118
  // The view is recreated if the model or extensions are changed.
111
119
  useEffect(() => {
@@ -135,18 +143,19 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
135
143
  EditorView.contentAttributes.of({ class: slots.content?.className ?? '' }),
136
144
 
137
145
  // State.
146
+ EditorView.editable.of(!readonly),
138
147
  EditorState.readOnly.of(!!readonly),
139
148
 
140
149
  // Storage and replication.
141
150
  // NOTE: This must come before user extensions.
142
151
  model.extension,
143
152
 
144
- // TODO(burdon): Factor out (requires special handling for Escape/focus).
153
+ // TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
145
154
  editorMode === 'vim' && vim(),
146
155
 
147
156
  // Custom.
148
157
  ...extensions,
149
- ].filter(Boolean) as Extension[],
158
+ ].filter(isNotFalsy),
150
159
  });
151
160
 
152
161
  //
@@ -178,6 +187,7 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
178
187
 
179
188
  // Handles tab/focus.
180
189
  // Pressing Escape focuses the outer div (to support tab navigation); pressing Enter refocuses the editor.
190
+ // TODO(burdon): Convert to keymap?
181
191
  const handleKeyUp = useCallback(
182
192
  (event: KeyboardEvent) => {
183
193
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
@@ -189,7 +199,7 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
189
199
 
190
200
  case 'Escape': {
191
201
  if (editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey)) {
192
- rootRef.current?.focus();
202
+ view?.focus();
193
203
  }
194
204
  break;
195
205
  }
@@ -203,16 +213,15 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
203
213
  key={model.id}
204
214
  role='none'
205
215
  tabIndex={0}
206
- onKeyUp={handleKeyUp}
207
216
  {...slots.root}
208
- {...(editorMode !== 'vim' && tabsterDOMAttribute)}
217
+ // {...(editorMode !== 'vim' && tabsterDOMAttribute)}
218
+ onKeyUp={handleKeyUp}
209
219
  ref={rootRef}
210
220
  />
211
221
  );
212
222
  },
213
223
  );
214
224
 
215
- // TODO(burdon): Single-line/scroll.
216
225
  export const TextEditor = forwardRef<EditorView, TextEditorProps>(
217
226
  ({ readonly, placeholder, lineWrapping, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
218
227
  const { themeMode } = useThemeContext();
@@ -221,7 +230,7 @@ export const TextEditor = forwardRef<EditorView, TextEditorProps>(
221
230
  <BaseTextEditor
222
231
  ref={forwardedRef}
223
232
  readonly={readonly}
224
- extensions={[basicBundle({ themeMode, placeholder, lineWrapping }), ...extensions]}
233
+ extensions={[createBasicBundle({ themeMode, placeholder, lineWrapping }), ...extensions]}
225
234
  theme={theme}
226
235
  slots={updatedSlots}
227
236
  {...props}
@@ -238,7 +247,7 @@ export const MarkdownEditor = forwardRef<EditorView, TextEditorProps>(
238
247
  <BaseTextEditor
239
248
  ref={forwardedRef}
240
249
  readonly={readonly}
241
- extensions={[markdownBundle({ themeMode, readonly, placeholder }), ...extensions]}
250
+ extensions={[createMarkdownExtensions({ themeMode, placeholder }), ...extensions]}
242
251
  theme={theme}
243
252
  slots={updatedSlots}
244
253
  {...props}
@@ -249,11 +258,10 @@ export const MarkdownEditor = forwardRef<EditorView, TextEditorProps>(
249
258
 
250
259
  export const defaultSlots: TextEditorSlots = {
251
260
  root: {
252
- // TODO(burdon): Add focusRing by default/as property?
253
- className: mx('flex flex-col grow overflow-y-auto', inputSurface),
261
+ className: mx('grow', focusRing),
254
262
  },
255
263
  editor: {
256
- className: 'h-full p-2',
264
+ className: 'bs-full',
257
265
  },
258
266
  };
259
267
 
@@ -13,7 +13,7 @@ import React, { useEffect, useRef, useState } from 'react';
13
13
 
14
14
  import { type Prop } from '@dxos/automerge/automerge';
15
15
  import { Repo, type DocHandle } from '@dxos/automerge/automerge-repo';
16
- import { Filter, setGlobalAutomergePreference } from '@dxos/echo-schema';
16
+ import { Filter } from '@dxos/echo-schema';
17
17
  import { type PublicKey } from '@dxos/keys';
18
18
  import { Expando, TextObject, useSpace } from '@dxos/react-client/echo';
19
19
  import { useIdentity } from '@dxos/react-client/halo';
@@ -130,7 +130,6 @@ const EchoStory = ({ id, spaceKey }: { id: number; spaceKey: PublicKey }) => {
130
130
 
131
131
  export const WithEcho = {
132
132
  render: () => {
133
- setGlobalAutomergePreference(true);
134
133
  return (
135
134
  <ClientRepeater
136
135
  count={2}
@@ -0,0 +1,111 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import '@dxosTheme';
6
+
7
+ import React from 'react';
8
+
9
+ import { useThemeContext } from '@dxos/react-ui';
10
+ import { fixedInsetFlexLayout, groupSurface, mx } from '@dxos/react-ui-theme';
11
+ import { withTheme } from '@dxos/storybook-utils';
12
+
13
+ import { TextEditor } from './TextEditor';
14
+ import {
15
+ code,
16
+ createMarkdownExtensions,
17
+ formatting,
18
+ heading,
19
+ hr,
20
+ image,
21
+ link,
22
+ table,
23
+ tasklist,
24
+ useFormattingState,
25
+ } from '../../extensions';
26
+ import { createDataExtensions, createThemeExtensions, useActionHandler, useTextEditor } from '../../hooks';
27
+ import { markdownTheme } from '../../themes';
28
+ import { Toolbar } from '../Toolbar';
29
+
30
+ // TODO(burdon): Demo toolbar with hooks.
31
+ // TODO(burdon): Build components from hooks and adapters for model/extensions, etc.
32
+ // TODO(burdon): Remove BaseTextEditor.
33
+ // TODO(burdon): Move scrolling container layout/logic into TextEditor/MarkdownEditor components.
34
+ // TODO(burdon): Define keymap in composer framework format.
35
+
36
+ type StoryProps = {
37
+ autoFocus?: boolean;
38
+ placeholder?: string;
39
+ doc?: string;
40
+ readonly?: boolean;
41
+ };
42
+
43
+ const Story = ({ autoFocus, placeholder, doc, readonly }: StoryProps) => {
44
+ const { themeMode } = useThemeContext();
45
+ const [formattingState, trackFormatting] = useFormattingState();
46
+ const { parentRef, view } = useTextEditor({
47
+ autoFocus,
48
+ doc,
49
+ extensions: [
50
+ //
51
+ createDataExtensions({ readonly }),
52
+ createThemeExtensions({
53
+ themeMode,
54
+ theme: markdownTheme,
55
+ slots: {
56
+ // TODO(burdon): Document classes re base theme.
57
+ // Semantic tokens (e.g., replacement of input surface).
58
+ editor: {
59
+ className: 'h-full p-4 bg-white text-black dark:bg-black dark:text-white',
60
+ },
61
+ },
62
+ }),
63
+ // TODO(burdon): Move lineWrapping.
64
+ createMarkdownExtensions({ placeholder, lineWrapping: true }),
65
+ // TODO(burdon): Move into markdown bundle (with React callbacks).
66
+ code(),
67
+ formatting(),
68
+ heading(),
69
+ hr(),
70
+ image(),
71
+ link(),
72
+ table(),
73
+ tasklist(),
74
+ trackFormatting,
75
+ ],
76
+ });
77
+
78
+ const handleAction = useActionHandler(view);
79
+
80
+ // FIXME This doesn't update the state on view changes. Also not
81
+ // sure if view is even guaranteed to exist at this point.
82
+ return (
83
+ <div className={mx(fixedInsetFlexLayout, groupSurface)}>
84
+ <div className='flex h-full justify-center'>
85
+ <div className='flex flex-col h-full w-[800px]'>
86
+ <Toolbar.Root onAction={handleAction} state={formattingState}>
87
+ <Toolbar.Markdown />
88
+ </Toolbar.Root>
89
+
90
+ {/* TODO(burdon): Handle scrolling in component wrapper (like this). */}
91
+ <div role='none' className='h-full overflow-y-auto' ref={parentRef} />
92
+ </div>
93
+ </div>
94
+ </div>
95
+ );
96
+ };
97
+
98
+ export default {
99
+ title: 'react-ui-editor/useTextEditor',
100
+ component: TextEditor,
101
+ decorators: [withTheme],
102
+ render: (args: StoryProps) => <Story {...args} />,
103
+ };
104
+
105
+ export const Default = {
106
+ args: {
107
+ autoFocus: true,
108
+ placeholder: 'Text...',
109
+ doc: '# Demo\n\nThis is a document.\n\n',
110
+ },
111
+ };
@@ -0,0 +1,102 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import '@dxosTheme';
6
+
7
+ import { faker } from '@faker-js/faker';
8
+ import React, { type FC, useMemo, useState } from 'react';
9
+
10
+ import { TextObject } from '@dxos/echo-schema';
11
+ import { PublicKey } from '@dxos/keys';
12
+ import { fixedInsetFlexLayout, groupSurface, mx } from '@dxos/react-ui-theme';
13
+ import { withTheme } from '@dxos/storybook-utils';
14
+
15
+ import { Toolbar } from './Toolbar';
16
+ import {
17
+ code,
18
+ comments,
19
+ formatting,
20
+ heading,
21
+ image,
22
+ table,
23
+ tasklist,
24
+ useComments,
25
+ useFormattingState,
26
+ } from '../../extensions';
27
+ import { type Comment, useActionHandler, useEditorView, useTextModel } from '../../hooks';
28
+ import { MarkdownEditor } from '../TextEditor';
29
+
30
+ faker.seed(101);
31
+
32
+ const Story: FC<{ content: string }> = ({ content }) => {
33
+ const [item] = useState({ text: new TextObject(content) });
34
+ const [_comments, setComments] = useState<Comment[]>([]);
35
+ const [editorRef, editorView] = useEditorView();
36
+ const model = useTextModel({ text: item.text });
37
+ const [formattingState, formattingObserver] = useFormattingState();
38
+ const extensions = useMemo(
39
+ () => [
40
+ code(),
41
+ comments({
42
+ onCreate: ({ cursor }) => {
43
+ const id = PublicKey.random().toHex();
44
+ setComments((comments) => [...comments, { id, cursor }]);
45
+ return id;
46
+ },
47
+ }),
48
+ formatting(),
49
+ heading(),
50
+ image(),
51
+ table(),
52
+ tasklist(),
53
+ formattingObserver,
54
+ ],
55
+ [],
56
+ );
57
+
58
+ useComments(editorView, _comments);
59
+ const handleAction = useActionHandler(editorView);
60
+
61
+ if (!model) {
62
+ return null;
63
+ }
64
+
65
+ return (
66
+ <div className={mx(fixedInsetFlexLayout, groupSurface)}>
67
+ <div className='flex h-full justify-center'>
68
+ <div className='flex flex-col h-full w-[800px]'>
69
+ <Toolbar.Root onAction={handleAction} state={formattingState}>
70
+ <Toolbar.Markdown />
71
+ <Toolbar.Separator />
72
+ <Toolbar.Extended />
73
+ </Toolbar.Root>
74
+ <MarkdownEditor ref={editorRef} model={model} extensions={extensions} />
75
+ </div>
76
+ </div>
77
+ </div>
78
+ );
79
+ };
80
+
81
+ export default {
82
+ title: 'react-ui-editor/Toolbar',
83
+ component: Toolbar,
84
+ render: (args: any) => <Story {...args} />,
85
+ decorators: [withTheme],
86
+ };
87
+
88
+ const content = [
89
+ 'Demo',
90
+ '',
91
+ 'The editor supports **Markdown** styles.',
92
+ '',
93
+ faker.lorem.paragraph({ min: 5, max: 8 }),
94
+ '',
95
+ '',
96
+ ].join('\n');
97
+
98
+ export const Default = {
99
+ args: {
100
+ content,
101
+ },
102
+ };