@dxos/react-ui-editor 0.3.11-main.8be6c8a → 0.3.11-main.8feb6a5

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 (63) hide show
  1. package/dist/lib/browser/index.mjs +250 -333
  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.map +1 -1
  5. package/dist/types/src/components/TextEditor/TextEditor.d.ts +2 -7
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  7. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +4 -4
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  10. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  11. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  12. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  13. package/dist/types/src/extensions/code.d.ts.map +1 -1
  14. package/dist/types/src/extensions/comments.d.ts +6 -2
  15. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  16. package/dist/types/src/{util → extensions}/cursor.d.ts +9 -1
  17. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  18. package/dist/types/src/extensions/index.d.ts +1 -0
  19. package/dist/types/src/extensions/index.d.ts.map +1 -1
  20. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  21. package/dist/types/src/extensions/tasklist.d.ts +5 -1
  22. package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
  23. package/dist/types/src/extensions/yjs/cursor.d.ts +1 -1
  24. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -1
  25. package/dist/types/src/hooks/useTextModel.d.ts +0 -5
  26. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  27. package/dist/types/src/hooks/yjs.d.ts.map +1 -1
  28. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  29. package/dist/types/src/themes/default.d.ts.map +1 -1
  30. package/dist/types/src/util.d.ts.map +1 -0
  31. package/package.json +24 -23
  32. package/src/components/TextEditor/MarkdownEditor.stories.tsx +12 -12
  33. package/src/components/TextEditor/TextEditor.stories.tsx +10 -43
  34. package/src/components/TextEditor/TextEditor.tsx +21 -31
  35. package/src/components/TextEditor/comments.stories.tsx +329 -0
  36. package/src/extensions/automerge/automerge.ts +2 -2
  37. package/src/extensions/automerge/cursor.ts +2 -1
  38. package/src/extensions/awareness.ts +25 -32
  39. package/src/extensions/code.ts +10 -2
  40. package/src/extensions/comments.ts +86 -116
  41. package/src/extensions/cursor.ts +46 -0
  42. package/src/extensions/hr.ts +2 -2
  43. package/src/extensions/image.ts +2 -2
  44. package/src/extensions/index.ts +1 -0
  45. package/src/extensions/link.ts +4 -4
  46. package/src/extensions/markdown/highlight.ts +0 -8
  47. package/src/extensions/table.ts +2 -2
  48. package/src/extensions/tasklist.ts +53 -77
  49. package/src/extensions/yjs/cursor.ts +2 -1
  50. package/src/extensions/yjs/yjs.test.ts +1 -1
  51. package/src/extensions/yjs/yjs.ts +2 -2
  52. package/src/hooks/useTextModel.ts +3 -6
  53. package/src/hooks/yjs.ts +1 -23
  54. package/src/styles/markdown.ts +0 -2
  55. package/src/themes/default.ts +17 -50
  56. package/dist/types/src/util/cursor.d.ts.map +0 -1
  57. package/dist/types/src/util/index.d.ts +0 -3
  58. package/dist/types/src/util/index.d.ts.map +0 -1
  59. package/dist/types/src/util/util.d.ts.map +0 -1
  60. package/src/util/cursor.ts +0 -29
  61. package/src/util/index.ts +0 -6
  62. /package/dist/types/src/{util/util.d.ts → util.d.ts} +0 -0
  63. /package/src/{util/util.ts → util.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-editor",
3
- "version": "0.3.11-main.8be6c8a",
3
+ "version": "0.3.11-main.8feb6a5",
4
4
  "description": "Document editing experience within a DXOS shell.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -15,7 +15,7 @@
15
15
  "dependencies": {
16
16
  "@codemirror/autocomplete": "^6.11.1",
17
17
  "@codemirror/commands": "^6.3.2",
18
- "@codemirror/lang-markdown": "^6.2.3",
18
+ "@codemirror/lang-markdown": "^6.2.4",
19
19
  "@codemirror/language": "^6.9.3",
20
20
  "@codemirror/language-data": "^6.3.1",
21
21
  "@codemirror/lint": "^6.4.2",
@@ -38,20 +38,20 @@
38
38
  "y-codemirror.next": "^0.3.2",
39
39
  "y-protocols": "^1.0.5",
40
40
  "yjs": "^13.6.10",
41
- "@dxos/async": "0.3.11-main.8be6c8a",
42
- "@dxos/context": "0.3.11-main.8be6c8a",
43
- "@dxos/display-name": "0.3.11-main.8be6c8a",
44
- "@dxos/automerge": "0.3.11-main.8be6c8a",
45
- "@dxos/debug": "0.3.11-main.8be6c8a",
46
- "@dxos/echo-schema": "0.3.11-main.8be6c8a",
47
- "@dxos/invariant": "0.3.11-main.8be6c8a",
48
- "@dxos/log": "0.3.11-main.8be6c8a",
49
- "@dxos/react-async": "0.3.11-main.8be6c8a",
50
- "@dxos/protocols": "0.3.11-main.8be6c8a",
51
- "@dxos/react-ui-theme": "0.3.11-main.8be6c8a",
52
- "@dxos/react-ui": "0.3.11-main.8be6c8a",
53
- "@dxos/text-model": "0.3.11-main.8be6c8a",
54
- "@dxos/util": "0.3.11-main.8be6c8a"
41
+ "@dxos/async": "0.3.11-main.8feb6a5",
42
+ "@dxos/automerge": "0.3.11-main.8feb6a5",
43
+ "@dxos/context": "0.3.11-main.8feb6a5",
44
+ "@dxos/debug": "0.3.11-main.8feb6a5",
45
+ "@dxos/echo-schema": "0.3.11-main.8feb6a5",
46
+ "@dxos/display-name": "0.3.11-main.8feb6a5",
47
+ "@dxos/invariant": "0.3.11-main.8feb6a5",
48
+ "@dxos/protocols": "0.3.11-main.8feb6a5",
49
+ "@dxos/react-ui": "0.3.11-main.8feb6a5",
50
+ "@dxos/react-ui-theme": "0.3.11-main.8feb6a5",
51
+ "@dxos/util": "0.3.11-main.8feb6a5",
52
+ "@dxos/log": "0.3.11-main.8feb6a5",
53
+ "@dxos/text-model": "0.3.11-main.8feb6a5",
54
+ "@dxos/react-async": "0.3.11-main.8feb6a5"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@automerge/automerge-repo-network-broadcastchannel": "^1.0.19",
@@ -67,18 +67,19 @@
67
67
  "deepsignal": "1.4.0-shallow.0",
68
68
  "react": "^18.2.0",
69
69
  "react-dom": "^18.2.0",
70
- "@dxos/echo-signals": "0.3.11-main.8be6c8a",
71
- "@dxos/echo-typegen": "0.3.11-main.8be6c8a",
72
- "@dxos/react-ui": "0.3.11-main.8be6c8a",
73
- "@dxos/config": "0.3.11-main.8be6c8a",
74
- "@dxos/storybook-utils": "0.3.11-main.8be6c8a",
75
- "@dxos/react-client": "0.3.11-main.8be6c8a"
70
+ "@dxos/echo-signals": "0.3.11-main.8feb6a5",
71
+ "@dxos/config": "0.3.11-main.8feb6a5",
72
+ "@dxos/echo-typegen": "0.3.11-main.8feb6a5",
73
+ "@dxos/keyboard": "0.3.11-main.8feb6a5",
74
+ "@dxos/react-ui": "0.3.11-main.8feb6a5",
75
+ "@dxos/react-client": "0.3.11-main.8feb6a5",
76
+ "@dxos/storybook-utils": "0.3.11-main.8feb6a5"
76
77
  },
77
78
  "peerDependencies": {
78
79
  "@phosphor-icons/react": "^2.0.5",
79
80
  "react": "^18.2.0",
80
81
  "react-dom": "^18.2.0",
81
- "@dxos/react-client": "0.3.11-main.8be6c8a"
82
+ "@dxos/react-client": "0.3.11-main.8feb6a5"
82
83
  },
83
84
  "publishConfig": {
84
85
  "access": "public"
@@ -12,6 +12,7 @@ import React, { type FC, StrictMode, 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';
@@ -182,15 +183,15 @@ const Key: FC<{ char: string }> = ({ char }) => (
182
183
  </span>
183
184
  );
184
185
 
185
- const onCommentsHover: CommentsOptions['onHover'] = (el) => {
186
+ const onCommentsHover: CommentsOptions['onHover'] = (el, shortcut) => {
186
187
  createRoot(el).render(
187
188
  <StrictMode>
188
189
  <div className='flex items-center gap-2 px-2 py-2 bg-neutral-700 text-white text-xs rounded'>
189
190
  <div>Create comment</div>
190
- {/* TODO(burdon): Unify shortcuts. */}
191
191
  <div className='flex gap-1'>
192
- <Key char='⌘' />
193
- <Key char="'" />
192
+ {keySymbols(parseShortcut(shortcut)).map((char) => (
193
+ <Key key={char} char={char} />
194
+ ))}
194
195
  </div>
195
196
  </div>
196
197
  </StrictMode>,
@@ -201,7 +202,7 @@ const onRenderLink: LinkOptions['onRender'] = (el, url) => {
201
202
  createRoot(el).render(
202
203
  <StrictMode>
203
204
  <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')} />
205
+ <ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1 mb-[2px]')} />
205
206
  </a>
206
207
  </StrictMode>,
207
208
  );
@@ -221,10 +222,9 @@ const Story = ({ text, automerge, ...props }: StoryProps) => {
221
222
 
222
223
  return (
223
224
  <div className={mx(fixedInsetFlexLayout, groupSurface)}>
224
- <div className='flex justify-center overflow-y-scroll'>
225
- <div className='flex flex-col w-[800px] py-16'>
225
+ <div className='flex h-full justify-center'>
226
+ <div className='flex flex-col h-full w-[800px]'>
226
227
  <MarkdownEditor model={model} {...props} />
227
- <div className='flex shrink-0 h-[300px]'></div>
228
228
  </div>
229
229
  </div>
230
230
  </div>
@@ -350,18 +350,18 @@ export const Mention = {
350
350
 
351
351
  export const Comments = {
352
352
  render: () => {
353
- const [commentsStates, setCommentStates] = useState<CommentRange[]>([]);
353
+ const [commentRanges, setCommentRanges] = useState<CommentRange[]>([]);
354
354
 
355
355
  return (
356
356
  <Story
357
357
  text={str('# Comments', '', text.paragraphs, text.footer)}
358
- comments={commentsStates}
358
+ comments={commentRanges}
359
359
  extensions={[
360
360
  comments({
361
361
  onHover: onCommentsHover,
362
- onCreate: (relPos) => {
362
+ onCreate: (range) => {
363
363
  const id = PublicKey.random().toHex();
364
- setCommentStates((comments) => [...comments, { id, cursor: relPos }]);
364
+ setCommentRanges((commentRanges) => [...commentRanges, { id, cursor: range }]);
365
365
  return id;
366
366
  },
367
367
  onSelect: (state) => {
@@ -4,17 +4,15 @@
4
4
 
5
5
  import '@dxosTheme';
6
6
 
7
- import defaultsDeep from 'lodash.defaultsdeep';
8
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 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,7 +28,7 @@ const Story = ({
30
28
  text,
31
29
  automerge,
32
30
  ...props
33
- }: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'slots'>) => {
31
+ }: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'placeholder' | 'slots'>) => {
34
32
  const [item] = useState({ text: new TextObject(text, undefined, undefined, { automerge }) });
35
33
  const model = useTextModel({ text: item.text });
36
34
  if (!model) {
@@ -39,10 +37,9 @@ const Story = ({
39
37
 
40
38
  return (
41
39
  <div className={mx(fixedInsetFlexLayout, groupSurface)}>
42
- <div className='flex justify-center overflow-y-scroll'>
40
+ <div className='flex justify-center'>
43
41
  <div className='flex flex-col w-[800px] py-16'>
44
42
  <TextEditor model={model} {...props} />
45
- <div className='flex shrink-0 h-[300px]'></div>
46
43
  </div>
47
44
  </div>
48
45
  </div>
@@ -57,49 +54,19 @@ export default {
57
54
  };
58
55
 
59
56
  export const Default = {
60
- render: () => (
61
- <Story
62
- slots={defaultsDeep(
63
- { editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
64
- defaultSlots,
65
- )}
66
- />
67
- ),
68
- };
69
-
70
- export const Listener = {
71
- render: () => (
72
- <Story
73
- slots={defaultsDeep(
74
- { editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
75
- defaultSlots,
76
- )}
77
- extensions={[listener({ onChange: (text) => console.log(text) })]}
78
- />
79
- ),
57
+ render: () => <Story placeholder='Enter text...' />,
80
58
  };
81
59
 
82
60
  export const Text = {
83
- render: () => (
84
- <Story
85
- text={initialText}
86
- slots={defaultsDeep(
87
- { editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
88
- defaultSlots,
89
- )}
90
- />
91
- ),
61
+ render: () => <Story text={initialText} placeholder='Enter text...' />,
92
62
  };
93
63
 
94
64
  export const Automerge = {
65
+ render: () => <Story text={initialText} placeholder='Enter text...' automerge />,
66
+ };
67
+
68
+ export const Listener = {
95
69
  render: () => (
96
- <Story
97
- automerge
98
- text={initialText}
99
- slots={defaultsDeep(
100
- { editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
101
- defaultSlots,
102
- )}
103
- />
70
+ <Story placeholder='Enter text...' extensions={[listener({ onChange: (text) => console.log(text) })]} />
104
71
  ),
105
72
  };
@@ -41,15 +41,12 @@ export type CursorInfo = {
41
41
 
42
42
  export type TextEditorSlots = {
43
43
  root?: Omit<ComponentProps<'div'>, 'ref'>;
44
- editor?: {
45
- className?: string;
46
- placeholder?: string;
47
- spellCheck?: boolean;
48
- tabIndex?: number;
49
- theme?: ThemeStyles;
50
- };
44
+ // editor?: {
45
+ // className?: string;
46
+ // };
51
47
  };
52
48
 
49
+ // TODO(burdon): Spellcheck?
53
50
  export type TextEditorProps = {
54
51
  model: EditorModel;
55
52
  focus?: boolean;
@@ -58,6 +55,8 @@ export type TextEditorProps = {
58
55
  comments?: CommentRange[]; // TODO(burdon): Move into extension.
59
56
  extensions?: Extension[];
60
57
  editorMode?: EditorMode;
58
+ placeholder?: string;
59
+ theme?: ThemeStyles;
61
60
  slots?: TextEditorSlots;
62
61
  };
63
62
 
@@ -66,7 +65,7 @@ export type TextEditorProps = {
66
65
  */
67
66
  export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
68
67
  (
69
- { model, focus, selection, readonly, comments, extensions = [], editorMode, slots = defaultSlots },
68
+ { model, focus, selection, readonly, comments, extensions = [], editorMode, theme, slots = defaultSlots },
70
69
  forwardedRef,
71
70
  ) => {
72
71
  const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
@@ -98,7 +97,7 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
98
97
 
99
98
  // TODO(burdon): Factor out as extension.
100
99
  useEffect(() => {
101
- if (view && comments?.length) {
100
+ if (view && comments !== undefined) {
102
101
  view.dispatch({
103
102
  effects: setCommentRange.of({ model, comments }),
104
103
  });
@@ -110,10 +109,10 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
110
109
  return;
111
110
  }
112
111
 
113
- // TODO(burdon): Remember cursor position.
114
112
  // https://codemirror.net/docs/ref/#state.EditorStateConfig
115
113
  const state = EditorState.create({
116
114
  doc: model.text(),
115
+ // TODO(burdon): Composer should store and set selection when switching documents.
117
116
  selection,
118
117
  extensions: [
119
118
  readonly && EditorState.readOnly.of(readonly),
@@ -124,7 +123,7 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
124
123
  // Theme.
125
124
  // TODO(burdon): Make theme configurable.
126
125
  EditorView.baseTheme(defaultTheme),
127
- EditorView.theme(slots?.editor?.theme ?? {}),
126
+ EditorView.theme(theme ?? {}),
128
127
  EditorView.darkTheme.of(themeMode === 'dark'),
129
128
 
130
129
  // Storage and replication.
@@ -160,7 +159,7 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
160
159
  (event: KeyboardEvent) => {
161
160
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
162
161
  switch (key) {
163
- // TODO(burdon): ???
162
+ // TODO(burdon): Is this required (for vim mode?)
164
163
  // case 'Enter': {
165
164
  // view?.contentDOM.focus();
166
165
  // break;
@@ -178,28 +177,27 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
178
177
  return (
179
178
  <div
180
179
  key={model.id}
180
+ role='none'
181
181
  ref={rootRef}
182
182
  tabIndex={0}
183
- {...slots?.root}
184
- {...(editorMode !== 'vim' && tabsterDOMAttribute)}
185
183
  onKeyUp={handleKeyUp}
184
+ {...slots.root}
185
+ {...(editorMode !== 'vim' && tabsterDOMAttribute)}
186
186
  />
187
187
  );
188
188
  },
189
189
  );
190
190
 
191
191
  export const TextEditor = forwardRef<EditorView, TextEditorProps>(
192
- ({ readonly, extensions = [], slots, ...props }, forwardedRef) => {
192
+ ({ readonly, placeholder, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
193
193
  const { themeMode } = useThemeContext();
194
194
  const updatedSlots = defaultsDeep({}, slots, defaultTextSlots);
195
195
  return (
196
196
  <BaseTextEditor
197
197
  ref={forwardedRef}
198
198
  readonly={readonly}
199
- extensions={[
200
- basicBundle({ readonly, themeMode, placeholder: updatedSlots?.editor?.placeholder }),
201
- ...extensions,
202
- ]}
199
+ extensions={[basicBundle({ readonly, themeMode, placeholder }), ...extensions]}
200
+ theme={theme}
203
201
  slots={updatedSlots}
204
202
  {...props}
205
203
  />
@@ -209,17 +207,15 @@ export const TextEditor = forwardRef<EditorView, TextEditorProps>(
209
207
 
210
208
  // TODO(burdon): Remove (Just provide bundle, slots).
211
209
  export const MarkdownEditor = forwardRef<EditorView, TextEditorProps>(
212
- ({ readonly, extensions = [], slots, ...props }, forwardedRef) => {
210
+ ({ readonly, placeholder, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
213
211
  const { themeMode } = useThemeContext();
214
212
  const updatedSlots = defaultsDeep({}, slots, defaultMarkdownSlots);
215
213
  return (
216
214
  <BaseTextEditor
217
215
  ref={forwardedRef}
218
216
  readonly={readonly}
219
- extensions={[
220
- markdownBundle({ readonly, themeMode, placeholder: updatedSlots?.editor?.placeholder }),
221
- ...extensions,
222
- ]}
217
+ extensions={[markdownBundle({ readonly, themeMode, placeholder }), ...extensions]}
218
+ theme={theme}
223
219
  slots={updatedSlots}
224
220
  {...props}
225
221
  />
@@ -229,20 +225,14 @@ export const MarkdownEditor = forwardRef<EditorView, TextEditorProps>(
229
225
 
230
226
  export const defaultSlots: TextEditorSlots = {
231
227
  root: {
232
- className: mx('p-2', inputSurface),
228
+ className: mx('p-2 overflow-y-auto', inputSurface),
233
229
  },
234
230
  };
235
231
 
236
232
  export const defaultTextSlots: TextEditorSlots = {
237
233
  ...defaultSlots,
238
- editor: {
239
- theme: textTheme,
240
- },
241
234
  };
242
235
 
243
236
  export const defaultMarkdownSlots: TextEditorSlots = {
244
237
  ...defaultSlots,
245
- editor: {
246
- theme: markdownTheme,
247
- },
248
238
  };