@dxos/react-ui-editor 0.3.11-main.c01834e → 0.3.11-main.c244d7e

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 (104) hide show
  1. package/dist/lib/browser/index.mjs +765 -768
  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 +14 -8
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +19 -17
  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 -6
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  11. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -1
  13. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  14. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  15. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  16. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  17. package/dist/types/src/extensions/automerge/defs.d.ts +7 -9
  18. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  19. package/dist/types/src/extensions/automerge/semaphore.d.ts +7 -12
  20. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  21. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
  22. package/dist/types/src/extensions/awareness.d.ts +2 -0
  23. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  24. package/dist/types/src/extensions/basic.d.ts +4 -5
  25. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  26. package/dist/types/src/extensions/code.d.ts.map +1 -1
  27. package/dist/types/src/extensions/comments.d.ts +31 -18
  28. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  29. package/dist/types/src/{util → extensions}/cursor.d.ts +10 -2
  30. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  31. package/dist/types/src/extensions/hr.d.ts.map +1 -1
  32. package/dist/types/src/extensions/index.d.ts +2 -0
  33. package/dist/types/src/extensions/index.d.ts.map +1 -1
  34. package/dist/types/src/extensions/markdown/bundle.d.ts +2 -5
  35. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  36. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  37. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  38. package/dist/types/src/extensions/outliner.d.ts +4 -0
  39. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  40. package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
  41. package/dist/types/src/extensions/yjs/cursor.d.ts +1 -1
  42. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -1
  43. package/dist/types/src/hooks/automerge.d.ts +2 -1
  44. package/dist/types/src/hooks/automerge.d.ts.map +1 -1
  45. package/dist/types/src/hooks/defs.d.ts +28 -0
  46. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  47. package/dist/types/src/hooks/index.d.ts +2 -1
  48. package/dist/types/src/hooks/index.d.ts.map +1 -1
  49. package/dist/types/src/hooks/useTextModel.d.ts +2 -32
  50. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  51. package/dist/types/src/hooks/yjs.d.ts +2 -1
  52. package/dist/types/src/hooks/yjs.d.ts.map +1 -1
  53. package/dist/types/src/index.d.ts +2 -0
  54. package/dist/types/src/index.d.ts.map +1 -1
  55. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  56. package/dist/types/src/themes/default.d.ts.map +1 -1
  57. package/dist/types/src/util.d.ts +7 -0
  58. package/dist/types/src/util.d.ts.map +1 -0
  59. package/package.json +25 -24
  60. package/src/components/TextEditor/MarkdownEditor.stories.tsx +60 -61
  61. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  62. package/src/components/TextEditor/TextEditor.tsx +113 -92
  63. package/src/components/TextEditor/comments.stories.tsx +357 -0
  64. package/src/extensions/autocomplete.ts +23 -19
  65. package/src/extensions/automerge/automerge.ts +25 -49
  66. package/src/extensions/automerge/cursor.ts +2 -1
  67. package/src/extensions/automerge/defs.ts +10 -31
  68. package/src/extensions/automerge/semaphore.ts +26 -19
  69. package/src/extensions/automerge/update-automerge.ts +2 -2
  70. package/src/extensions/awareness.ts +45 -34
  71. package/src/extensions/basic.ts +22 -17
  72. package/src/extensions/code.ts +24 -18
  73. package/src/extensions/comments.ts +242 -234
  74. package/src/extensions/cursor.ts +50 -0
  75. package/src/extensions/hr.ts +49 -50
  76. package/src/extensions/image.ts +2 -2
  77. package/src/extensions/index.ts +2 -0
  78. package/src/extensions/link.ts +7 -7
  79. package/src/extensions/markdown/bundle.ts +18 -24
  80. package/src/extensions/markdown/highlight.ts +0 -8
  81. package/src/extensions/mention.ts +6 -1
  82. package/src/extensions/outliner.ts +12 -0
  83. package/src/extensions/table.ts +2 -2
  84. package/src/extensions/tasklist.ts +78 -86
  85. package/src/extensions/yjs/cursor.ts +2 -1
  86. package/src/extensions/yjs/yjs.test.ts +1 -1
  87. package/src/extensions/yjs/yjs.ts +2 -2
  88. package/src/hooks/automerge.ts +2 -1
  89. package/src/hooks/defs.ts +47 -0
  90. package/src/hooks/index.ts +3 -1
  91. package/src/hooks/useTextModel.ts +3 -49
  92. package/src/hooks/yjs.ts +2 -23
  93. package/src/index.ts +2 -0
  94. package/src/styles/markdown.ts +0 -2
  95. package/src/themes/default.ts +17 -50
  96. package/src/util.ts +41 -0
  97. package/dist/types/src/util/cursor.d.ts.map +0 -1
  98. package/dist/types/src/util/index.d.ts +0 -3
  99. package/dist/types/src/util/index.d.ts.map +0 -1
  100. package/dist/types/src/util/util.d.ts +0 -5
  101. package/dist/types/src/util/util.d.ts.map +0 -1
  102. package/src/util/cursor.ts +0 -29
  103. package/src/util/index.ts +0 -6
  104. package/src/util/util.ts +0 -18
@@ -4,7 +4,7 @@
4
4
 
5
5
  import '@dxosTheme';
6
6
 
7
- import { EditorView } from '@codemirror/view';
7
+ import { type EditorView } from '@codemirror/view';
8
8
  import { faker } from '@faker-js/faker';
9
9
  import { ArrowSquareOut } from '@phosphor-icons/react';
10
10
  import defaultsDeep from 'lodash.defaultsdeep';
@@ -12,11 +12,12 @@ import React, { type FC, StrictMode, useRef, useState } from 'react';
12
12
  import { createRoot } from 'react-dom/client';
13
13
 
14
14
  import { TextObject } from '@dxos/echo-schema';
15
+ import { keySymbols, parseShortcut } from '@dxos/keyboard';
15
16
  import { PublicKey } from '@dxos/keys';
16
17
  import { fixedInsetFlexLayout, getSize, groupSurface, mx } from '@dxos/react-ui-theme';
17
18
  import { withTheme } from '@dxos/storybook-utils';
18
19
 
19
- import { MarkdownEditor, type TextEditorProps, type TextEditorRef } from './TextEditor';
20
+ import { MarkdownEditor, type TextEditorProps } from './TextEditor';
20
21
  import {
21
22
  autocomplete,
22
23
  blast,
@@ -25,7 +26,6 @@ import {
25
26
  defaultOptions,
26
27
  hr,
27
28
  image,
28
- listener,
29
29
  link,
30
30
  mention,
31
31
  table,
@@ -33,8 +33,10 @@ import {
33
33
  typewriter,
34
34
  type CommentsOptions,
35
35
  type LinkOptions,
36
+ outliner,
37
+ useComments,
36
38
  } from '../../extensions';
37
- import { type CommentRange, useTextModel } from '../../hooks';
39
+ import { type Comment, useTextModel } from '../../hooks';
38
40
 
39
41
  // Extensions:
40
42
  // TODO(burdon): Table of contents.
@@ -54,8 +56,8 @@ const text = {
54
56
  '- [x] decorator',
55
57
  '- [ ] checkbox',
56
58
  ' - [ ] state',
57
- ' - [ ] indent',
58
- ' - [x] style',
59
+ ' - [ ] indent',
60
+ ' - [x] style',
59
61
  '',
60
62
  ),
61
63
 
@@ -161,6 +163,8 @@ const links = [
161
163
  { label: 'StackEdit', apply: '[StackEdit](https://stackedit.io/app)' },
162
164
  ];
163
165
 
166
+ const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
167
+
164
168
  const hover =
165
169
  'rounded-sm text-base text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
166
170
 
@@ -182,15 +186,15 @@ const Key: FC<{ char: string }> = ({ char }) => (
182
186
  </span>
183
187
  );
184
188
 
185
- const onCommentsHover: CommentsOptions['onHover'] = (el) => {
189
+ const onCommentsHover: CommentsOptions['onHover'] = (el, shortcut) => {
186
190
  createRoot(el).render(
187
191
  <StrictMode>
188
192
  <div className='flex items-center gap-2 px-2 py-2 bg-neutral-700 text-white text-xs rounded'>
189
193
  <div>Create comment</div>
190
- {/* TODO(burdon): Unify shortcuts. */}
191
194
  <div className='flex gap-1'>
192
- <Key char='⌘' />
193
- <Key char="'" />
195
+ {keySymbols(parseShortcut(shortcut)).map((char) => (
196
+ <Key key={char} char={char} />
197
+ ))}
194
198
  </div>
195
199
  </div>
196
200
  </StrictMode>,
@@ -201,7 +205,7 @@ const onRenderLink: LinkOptions['onRender'] = (el, url) => {
201
205
  createRoot(el).render(
202
206
  <StrictMode>
203
207
  <a href={url} target='_blank' rel='noreferrer' className={hover}>
204
- <ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1 mb-1')} />
208
+ <ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1 mb-[2px]')} />
205
209
  </a>
206
210
  </StrictMode>,
207
211
  );
@@ -209,23 +213,24 @@ const onRenderLink: LinkOptions['onRender'] = (el, url) => {
209
213
 
210
214
  type StoryProps = {
211
215
  text?: string;
216
+ comments?: Comment[];
212
217
  automerge?: boolean;
213
- } & Pick<TextEditorProps, 'comments' | 'readonly' | 'extensions' | 'slots'>;
218
+ } & Pick<TextEditorProps, 'readonly' | 'placeholder' | 'slots' | 'extensions'>;
214
219
 
215
- const Story = ({ text, automerge, ...props }: StoryProps) => {
216
- const ref = useRef<TextEditorRef>(null);
220
+ const Story = ({ text, comments, automerge, placeholder = 'New document.', ...props }: StoryProps) => {
217
221
  const [item] = useState({ text: new TextObject(text, undefined, undefined, { automerge }) });
222
+ const view = useRef<EditorView>(null);
218
223
  const model = useTextModel({ text: item.text });
224
+ useComments(view.current, comments);
219
225
  if (!model) {
220
226
  return null;
221
227
  }
222
228
 
223
229
  return (
224
230
  <div className={mx(fixedInsetFlexLayout, groupSurface)}>
225
- <div className='flex justify-center overflow-y-scroll'>
226
- <div className='flex flex-col w-[800px] py-16'>
227
- <MarkdownEditor ref={ref} model={model} {...props} />
228
- <div className='flex shrink-0 h-[300px]'></div>
231
+ <div className='flex h-full justify-center'>
232
+ <div className='flex flex-col h-full w-[800px]'>
233
+ <MarkdownEditor ref={view} model={model} placeholder={placeholder} {...props} />
229
234
  </div>
230
235
  </div>
231
236
  </div>
@@ -239,7 +244,7 @@ export default {
239
244
  render: Story,
240
245
  };
241
246
 
242
- const extensions = [
247
+ const defaults = [
243
248
  autocomplete({
244
249
  onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
245
250
  }),
@@ -247,16 +252,19 @@ const extensions = [
247
252
  hr(),
248
253
  image(),
249
254
  link({ onRender: onRenderLink, onHover: onHoverLinkTooltip }),
255
+ // mention({
256
+ // onSearch: (text) => names.filter((name) => name.toLowerCase().startsWith(text.toLowerCase())),
257
+ // }),
250
258
  table(),
251
259
  tasklist(),
252
260
  ];
253
261
 
254
262
  export const Default = {
255
- render: () => <Story text={document} extensions={extensions} />,
263
+ render: () => <Story text={document} extensions={defaults} />,
256
264
  };
257
265
 
258
266
  export const Readonly = {
259
- render: () => <Story text={document} extensions={extensions} readonly />,
267
+ render: () => <Story text={document} extensions={defaults} readonly />,
260
268
  };
261
269
 
262
270
  const large = faker.helpers.multiple(() => faker.lorem.paragraph({ min: 8, max: 16 }), { count: 20 }).join('\n\n');
@@ -265,15 +273,18 @@ export const Large = {
265
273
  render: () => <Story text={str('# Large Document', '', large)} extensions={[]} />,
266
274
  };
267
275
 
276
+ export const Empty = {
277
+ render: () => <Story />,
278
+ };
279
+
268
280
  export const NoExtensions = {
269
281
  render: () => <Story text={document} />,
270
282
  };
271
283
 
272
- // TODO(burdon): Cursor bug if no newline after BR (cursor down just loops back to start of each paragraph).
273
284
  export const HorizontalRule = {
274
285
  render: () => (
275
286
  <Story
276
- text={str('# Horizontal Rule', '', text.paragraphs, '---', '', text.paragraphs, '---', '', text.paragraphs)}
287
+ text={str('# Horizontal Rule', '', text.paragraphs, '---', text.paragraphs, '---', text.paragraphs)}
277
288
  extensions={[hr()]}
278
289
  />
279
290
  ),
@@ -300,18 +311,17 @@ export const Image = {
300
311
  render: () => <Story text={str(text.image, text.footer)} readonly extensions={[image()]} />,
301
312
  };
302
313
 
303
- export const Tasklist = {
314
+ export const Lists = {
315
+ render: () => (
316
+ <Story text={str(text.tasks, '', text.list, '', text.numbered, text.footer)} extensions={[tasklist()]} />
317
+ ),
318
+ };
319
+
320
+ export const Outliner = {
304
321
  render: () => (
305
322
  <Story
306
- text={str(text.tasks, '', text.list, text.footer)}
307
- extensions={[
308
- tasklist(),
309
- listener({
310
- onChange: (text) => {
311
- console.log(text);
312
- },
313
- }),
314
- ]}
323
+ text={str('# Outliner', '', 'Block', ': this is a block', ': with multiple lines', text.footer)}
324
+ extensions={[outliner()]}
315
325
  />
316
326
  ),
317
327
  };
@@ -319,7 +329,7 @@ export const Tasklist = {
319
329
  export const Autocomplete = {
320
330
  render: () => (
321
331
  <Story
322
- text={str('# Autocomplete', '', 'Press CTRL-SPACE', text.footer)}
332
+ text={str('# Autocomplete', '', 'Press Ctrl-Space...', text.footer)}
323
333
  extensions={[
324
334
  link({ onRender: onRenderLink }),
325
335
  autocomplete({
@@ -330,8 +340,6 @@ export const Autocomplete = {
330
340
  ),
331
341
  };
332
342
 
333
- const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
334
-
335
343
  export const Mention = {
336
344
  render: () => (
337
345
  <Story
@@ -347,18 +355,18 @@ export const Mention = {
347
355
 
348
356
  export const Comments = {
349
357
  render: () => {
350
- const [commentsStates, setCommentStates] = useState<CommentRange[]>([]);
358
+ const [commentRanges, setCommentRanges] = useState<Comment[]>([]);
351
359
 
352
360
  return (
353
361
  <Story
354
362
  text={str('# Comments', '', text.paragraphs, text.footer)}
355
- comments={commentsStates}
363
+ comments={commentRanges}
356
364
  extensions={[
357
365
  comments({
358
366
  onHover: onCommentsHover,
359
- onCreate: (relPos) => {
367
+ onCreate: (range) => {
360
368
  const id = PublicKey.random().toHex();
361
- setCommentStates((comments) => [...comments, { id, cursor: relPos }]);
369
+ setCommentRanges((commentRanges) => [...commentRanges, { id, cursor: range }]);
362
370
  return id;
363
371
  },
364
372
  onSelect: (state) => {
@@ -367,9 +375,9 @@ export const Comments = {
367
375
  console.log(
368
376
  'update',
369
377
  JSON.stringify({
370
- active: state.active?.slice(0, 8),
371
- closest: state.closest?.slice(0, 8),
372
- ranges: state.ranges.length,
378
+ comments: state.comments.length,
379
+ active: state.selection.active?.slice(0, 8),
380
+ closest: state.selection.closest?.slice(0, 8),
373
381
  }),
374
382
  );
375
383
  }
@@ -381,8 +389,15 @@ export const Comments = {
381
389
  },
382
390
  };
383
391
 
392
+ const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewriter')?.split(',');
393
+
384
394
  export const Typewriter = {
385
- render: () => <Story text={str('# Typewriter', '', text.paragraphs, text.footer)} extensions={[typewriter()]} />,
395
+ render: () => (
396
+ <Story
397
+ text={str('# Typewriter', '', text.paragraphs, text.footer)}
398
+ extensions={[typewriter({ items: typewriterItems })]}
399
+ />
400
+ ),
386
401
  };
387
402
 
388
403
  export const Blast = {
@@ -390,9 +405,7 @@ export const Blast = {
390
405
  <Story
391
406
  text={str('# Blast', '', text.paragraphs, text.code, text.paragraphs)}
392
407
  extensions={[
393
- typewriter({
394
- items: localStorage.getItem('dxos.composer.extension.demo')?.split(','),
395
- }),
408
+ typewriter({ items: typewriterItems }),
396
409
  blast(
397
410
  defaultsDeep(
398
411
  {
@@ -409,17 +422,3 @@ export const Blast = {
409
422
  />
410
423
  ),
411
424
  };
412
-
413
- export const Diagnostics = {
414
- render: () => (
415
- <Story
416
- text={document}
417
- extensions={[
418
- // Cursor moved.
419
- EditorView.updateListener.of((update) => {
420
- console.log('update', update.view.state.selection.main.head);
421
- }),
422
- ]}
423
- />
424
- ),
425
- };
@@ -4,17 +4,15 @@
4
4
 
5
5
  import '@dxosTheme';
6
6
 
7
- import defaultsDeep from 'lodash.defaultsdeep';
8
- import React, { useRef, useState } from 'react';
7
+ import React, { useState } from 'react';
9
8
 
10
9
  import { TextObject } from '@dxos/echo-schema';
11
10
  import { fixedInsetFlexLayout, groupSurface, mx } from '@dxos/react-ui-theme';
12
11
  import { withTheme } from '@dxos/storybook-utils';
13
12
 
14
- import { defaultSlots, TextEditor, type TextEditorProps, type TextEditorRef, type TextEditorSlots } from './TextEditor';
13
+ import { TextEditor, type TextEditorProps } from './TextEditor';
15
14
  import { listener } from '../../extensions';
16
15
  import { useTextModel } from '../../hooks';
17
- import { textTheme } from '../../themes';
18
16
 
19
17
  const initialText = [
20
18
  '# TextEditor',
@@ -30,8 +28,7 @@ const Story = ({
30
28
  text,
31
29
  automerge,
32
30
  ...props
33
- }: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'slots'>) => {
34
- const ref = useRef<TextEditorRef>(null);
31
+ }: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'placeholder' | 'slots'>) => {
35
32
  const [item] = useState({ text: new TextObject(text, undefined, undefined, { automerge }) });
36
33
  const model = useTextModel({ text: item.text });
37
34
  if (!model) {
@@ -40,10 +37,9 @@ const Story = ({
40
37
 
41
38
  return (
42
39
  <div className={mx(fixedInsetFlexLayout, groupSurface)}>
43
- <div className='flex justify-center overflow-y-scroll'>
40
+ <div className='flex justify-center'>
44
41
  <div className='flex flex-col w-[800px] py-16'>
45
- <TextEditor ref={ref} model={model} {...props} />
46
- <div className='flex shrink-0 h-[300px]'></div>
42
+ <TextEditor model={model} {...props} />
47
43
  </div>
48
44
  </div>
49
45
  </div>
@@ -58,49 +54,19 @@ export default {
58
54
  };
59
55
 
60
56
  export const Default = {
61
- render: () => (
62
- <Story
63
- slots={defaultsDeep(
64
- { editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
65
- defaultSlots,
66
- )}
67
- />
68
- ),
69
- };
70
-
71
- export const Listener = {
72
- render: () => (
73
- <Story
74
- slots={defaultsDeep(
75
- { editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
76
- defaultSlots,
77
- )}
78
- extensions={[listener({ onChange: (text) => console.log(text) })]}
79
- />
80
- ),
57
+ render: () => <Story placeholder='Enter text...' />,
81
58
  };
82
59
 
83
60
  export const Text = {
84
- render: () => (
85
- <Story
86
- text={initialText}
87
- slots={defaultsDeep(
88
- { editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
89
- defaultSlots,
90
- )}
91
- />
92
- ),
61
+ render: () => <Story text={initialText} placeholder='Enter text...' />,
93
62
  };
94
63
 
95
64
  export const Automerge = {
65
+ render: () => <Story text={initialText} placeholder='Enter text...' automerge />,
66
+ };
67
+
68
+ export const Listener = {
96
69
  render: () => (
97
- <Story
98
- automerge
99
- text={initialText}
100
- slots={defaultsDeep(
101
- { editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
102
- defaultSlots,
103
- )}
104
- />
70
+ <Story placeholder='Enter text...' extensions={[listener({ onChange: (text) => console.log(text) })]} />
105
71
  ),
106
72
  };