@dxos/react-ui-editor 0.3.11-main.4eab977 → 0.3.11-main.508f304

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 (107) hide show
  1. package/dist/lib/browser/index.mjs +779 -760
  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 +16 -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 +20 -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/index.d.ts +0 -1
  20. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
  21. package/dist/types/src/extensions/automerge/semaphore.d.ts +7 -12
  22. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  23. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
  24. package/dist/types/src/extensions/awareness.d.ts +2 -0
  25. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  26. package/dist/types/src/extensions/basic.d.ts +4 -5
  27. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  28. package/dist/types/src/extensions/code.d.ts.map +1 -1
  29. package/dist/types/src/extensions/comments.d.ts +31 -18
  30. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  31. package/dist/types/src/{util → extensions}/cursor.d.ts +10 -2
  32. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  33. package/dist/types/src/extensions/hr.d.ts.map +1 -1
  34. package/dist/types/src/extensions/index.d.ts +2 -0
  35. package/dist/types/src/extensions/index.d.ts.map +1 -1
  36. package/dist/types/src/extensions/markdown/bundle.d.ts +2 -5
  37. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  38. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  39. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  40. package/dist/types/src/extensions/outliner.d.ts +4 -0
  41. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  42. package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
  43. package/dist/types/src/extensions/yjs/cursor.d.ts +1 -1
  44. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -1
  45. package/dist/types/src/hooks/automerge.d.ts +2 -1
  46. package/dist/types/src/hooks/automerge.d.ts.map +1 -1
  47. package/dist/types/src/hooks/defs.d.ts +28 -0
  48. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  49. package/dist/types/src/hooks/index.d.ts +2 -1
  50. package/dist/types/src/hooks/index.d.ts.map +1 -1
  51. package/dist/types/src/hooks/useTextModel.d.ts +2 -32
  52. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  53. package/dist/types/src/hooks/yjs.d.ts +2 -1
  54. package/dist/types/src/hooks/yjs.d.ts.map +1 -1
  55. package/dist/types/src/index.d.ts +1 -0
  56. package/dist/types/src/index.d.ts.map +1 -1
  57. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  58. package/dist/types/src/themes/default.d.ts.map +1 -1
  59. package/dist/types/src/util.d.ts +7 -0
  60. package/dist/types/src/util.d.ts.map +1 -0
  61. package/package.json +32 -31
  62. package/src/components/TextEditor/MarkdownEditor.stories.tsx +77 -79
  63. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  64. package/src/components/TextEditor/TextEditor.tsx +124 -92
  65. package/src/components/TextEditor/comments.stories.tsx +357 -0
  66. package/src/extensions/autocomplete.ts +6 -3
  67. package/src/extensions/automerge/automerge.ts +40 -46
  68. package/src/extensions/automerge/cursor.ts +2 -1
  69. package/src/extensions/automerge/defs.ts +10 -31
  70. package/src/extensions/automerge/index.ts +0 -1
  71. package/src/extensions/automerge/semaphore.ts +23 -20
  72. package/src/extensions/automerge/update-automerge.ts +2 -2
  73. package/src/extensions/awareness.ts +45 -34
  74. package/src/extensions/basic.ts +22 -17
  75. package/src/extensions/code.ts +24 -18
  76. package/src/extensions/comments.ts +242 -234
  77. package/src/extensions/cursor.ts +50 -0
  78. package/src/extensions/hr.ts +49 -50
  79. package/src/extensions/image.ts +2 -2
  80. package/src/extensions/index.ts +2 -0
  81. package/src/extensions/link.ts +7 -7
  82. package/src/extensions/markdown/bundle.ts +18 -24
  83. package/src/extensions/markdown/highlight.ts +0 -8
  84. package/src/extensions/mention.ts +9 -2
  85. package/src/extensions/outliner.ts +12 -0
  86. package/src/extensions/table.ts +2 -2
  87. package/src/extensions/tasklist.ts +78 -86
  88. package/src/extensions/yjs/cursor.ts +2 -1
  89. package/src/extensions/yjs/yjs.test.ts +1 -1
  90. package/src/extensions/yjs/yjs.ts +2 -2
  91. package/src/hooks/automerge.ts +2 -1
  92. package/src/hooks/defs.ts +47 -0
  93. package/src/hooks/index.ts +3 -1
  94. package/src/hooks/useTextModel.ts +3 -49
  95. package/src/hooks/yjs.ts +2 -23
  96. package/src/index.ts +1 -0
  97. package/src/styles/markdown.ts +0 -2
  98. package/src/themes/default.ts +17 -50
  99. package/src/util.ts +41 -0
  100. package/dist/types/src/util/cursor.d.ts.map +0 -1
  101. package/dist/types/src/util/index.d.ts +0 -3
  102. package/dist/types/src/util/index.d.ts.map +0 -1
  103. package/dist/types/src/util/util.d.ts +0 -5
  104. package/dist/types/src/util/util.d.ts.map +0 -1
  105. package/src/util/cursor.ts +0 -29
  106. package/src/util/index.ts +0 -6
  107. package/src/util/util.ts +0 -18
@@ -8,10 +8,10 @@ import { type EditorState, type StateField, type Text, type Transaction } from '
8
8
 
9
9
  import { next as am, type Heads } from '@dxos/automerge/automerge';
10
10
 
11
- import { type IDocHandle, type Value } from './defs';
11
+ import { type IDocHandle, type State } from './defs';
12
12
 
13
13
  export const updateAutomerge = (
14
- field: StateField<Value>,
14
+ field: StateField<State>,
15
15
  handle: IDocHandle,
16
16
  transactions: Transaction[],
17
17
  state: EditorState,
@@ -12,11 +12,16 @@ import {
12
12
  type ViewUpdate,
13
13
  WidgetType,
14
14
  } from '@codemirror/view';
15
+ import { useEffect } from 'react';
15
16
 
16
17
  import { Event } from '@dxos/async';
17
18
  import { Context } from '@dxos/context';
19
+ import { generateName } from '@dxos/display-name';
20
+ import { useThemeContext } from '@dxos/react-ui';
21
+ import { getColorForValue } from '@dxos/react-ui-theme';
18
22
 
19
- import { CursorConverter } from '../util';
23
+ import { Cursor, type CursorConverter } from './cursor';
24
+ import { type EditorModel } from '../hooks';
20
25
 
21
26
  export interface AwarenessProvider {
22
27
  remoteStateChange: Event<void>;
@@ -66,7 +71,6 @@ export type AwarenessState = {
66
71
  /**
67
72
  * Extension provides presence information about other peers.
68
73
  */
69
- // TODO(burdon): Why provide default?
70
74
  export const awareness = (provider = dummyProvider): Extension => {
71
75
  return [
72
76
  AwarenessProvider.of(provider),
@@ -77,6 +81,19 @@ export const awareness = (provider = dummyProvider): Extension => {
77
81
  ];
78
82
  };
79
83
 
84
+ export const useAwareness = ({ awareness, peer }: EditorModel) => {
85
+ const { themeMode } = useThemeContext();
86
+ useEffect(() => {
87
+ if (awareness && peer) {
88
+ awareness.setLocalStateField('user', {
89
+ name: peer.name ?? generateName(peer.id),
90
+ color: getColorForValue({ value: peer.id, type: 'color' }),
91
+ colorLight: getColorForValue({ value: peer.id, themeMode, type: 'highlight' }),
92
+ });
93
+ }
94
+ }, [awareness, peer, themeMode]);
95
+ };
96
+
80
97
  /**
81
98
  * Generates selection decorations from remote peers.
82
99
  */
@@ -91,7 +108,7 @@ export class RemoteSelectionsDecorator implements PluginValue {
91
108
  private _lastHead?: number = undefined;
92
109
 
93
110
  constructor(view: EditorView) {
94
- this._cursorConverter = view.state.facet(CursorConverter);
111
+ this._cursorConverter = view.state.facet(Cursor.converter);
95
112
  this._provider = view.state.facet(AwarenessProvider);
96
113
  this._provider.open();
97
114
  this._provider.remoteStateChange.on(this._ctx, () => {
@@ -156,7 +173,7 @@ export class RemoteSelectionsDecorator implements PluginValue {
156
173
  to: end,
157
174
  value: Decoration.mark({
158
175
  attributes: { style: `background-color: ${lightColor}` },
159
- class: 'cm-ySelection',
176
+ class: 'cm-collab-selection',
160
177
  }),
161
178
  });
162
179
  } else {
@@ -166,7 +183,7 @@ export class RemoteSelectionsDecorator implements PluginValue {
166
183
  to: startLine.from + startLine.length,
167
184
  value: Decoration.mark({
168
185
  attributes: { style: `background-color: ${color}` },
169
- class: 'cm-ySelection',
186
+ class: 'cm-collab-selection',
170
187
  }),
171
188
  });
172
189
 
@@ -176,7 +193,7 @@ export class RemoteSelectionsDecorator implements PluginValue {
176
193
  to: end,
177
194
  value: Decoration.mark({
178
195
  attributes: { style: `background-color: ${color}` },
179
- class: 'cm-ySelection',
196
+ class: 'cm-collab-selection',
180
197
  }),
181
198
  });
182
199
 
@@ -186,7 +203,7 @@ export class RemoteSelectionsDecorator implements PluginValue {
186
203
  from: linePos,
187
204
  to: linePos,
188
205
  value: Decoration.line({
189
- attributes: { style: `background-color: ${color}`, class: 'cm-yLineSelection' },
206
+ attributes: { style: `background-color: ${color}`, class: 'cm-collab-selectionLine' },
190
207
  }),
191
208
  });
192
209
  }
@@ -208,24 +225,22 @@ export class RemoteSelectionsDecorator implements PluginValue {
208
225
  }
209
226
 
210
227
  class RemoteCaretWidget extends WidgetType {
211
- constructor(public name: string, public color: string) {
228
+ constructor(private readonly _name: string, private readonly _color: string) {
212
229
  super();
213
- this.name = name;
214
- this.color = color;
215
230
  }
216
231
 
217
- toDOM(): HTMLElement {
232
+ override toDOM(): HTMLElement {
218
233
  const span = document.createElement('span');
219
- span.className = 'cm-ySelectionCaret';
220
- span.style.backgroundColor = this.color;
221
- span.style.borderColor = this.color;
234
+ span.className = 'cm-collab-selectionCaret';
235
+ span.style.backgroundColor = this._color;
236
+ span.style.borderColor = this._color;
222
237
 
223
238
  const dot = document.createElement('div');
224
- dot.className = 'cm-ySelectionCaretDot';
239
+ dot.className = 'cm-collab-selectionCaretDot';
225
240
 
226
241
  const info = document.createElement('div');
227
- info.className = 'cm-ySelectionInfo';
228
- info.innerText = this.name;
242
+ info.className = 'cm-collab-selectionInfo';
243
+ info.innerText = this._name;
229
244
 
230
245
  span.appendChild(document.createTextNode('\u2060'));
231
246
  span.appendChild(dot);
@@ -236,18 +251,14 @@ class RemoteCaretWidget extends WidgetType {
236
251
  return span;
237
252
  }
238
253
 
239
- override eq(widget: this) {
240
- return widget.color === this.color;
241
- }
242
-
243
- compare(widget: this) {
244
- return widget.color === this.color;
245
- }
246
-
247
254
  override updateDOM() {
248
255
  return false;
249
256
  }
250
257
 
258
+ override eq(widget: this) {
259
+ return widget._color === this._color;
260
+ }
261
+
251
262
  override get estimatedHeight() {
252
263
  return -1;
253
264
  }
@@ -257,14 +268,14 @@ class RemoteCaretWidget extends WidgetType {
257
268
  }
258
269
  }
259
270
 
260
- // TODO(burdon): Rename prefix (y for yjs?)
271
+ // TODO(burdon): Mixed case?
261
272
  const styles = EditorView.baseTheme({
262
- '.cm-ySelection': {},
263
- '.cm-yLineSelection': {
273
+ '.cm-collab-selection': {},
274
+ '.cm-collab-selectionLine': {
264
275
  padding: 0,
265
276
  margin: '0px 2px 0px 4px',
266
277
  },
267
- '.cm-ySelectionCaret': {
278
+ '.cm-collab-selectionCaret': {
268
279
  position: 'relative',
269
280
  borderLeft: '1px solid black',
270
281
  borderRight: '1px solid black',
@@ -273,7 +284,7 @@ const styles = EditorView.baseTheme({
273
284
  boxSizing: 'border-box',
274
285
  display: 'inline',
275
286
  },
276
- '.cm-ySelectionCaretDot': {
287
+ '.cm-collab-selectionCaretDot': {
277
288
  borderRadius: '50%',
278
289
  position: 'absolute',
279
290
  width: '.4em',
@@ -284,11 +295,11 @@ const styles = EditorView.baseTheme({
284
295
  transition: 'transform .3s ease-in-out',
285
296
  boxSizing: 'border-box',
286
297
  },
287
- '.cm-ySelectionCaret:hover > .cm-ySelectionCaretDot': {
298
+ '.cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot': {
288
299
  transformOrigin: 'bottom center',
289
300
  transform: 'scale(0)',
290
301
  },
291
- '.cm-ySelectionInfo': {
302
+ '.cm-collab-selectionInfo': {
292
303
  position: 'absolute',
293
304
  top: '-1.05em',
294
305
  left: '-1px',
@@ -304,12 +315,12 @@ const styles = EditorView.baseTheme({
304
315
  zIndex: 101,
305
316
  transition: 'opacity .3s ease-in-out',
306
317
  backgroundColor: 'inherit',
307
- // these should be separate
318
+ // These should be separate.
308
319
  opacity: 0,
309
320
  transitionDelay: '0s',
310
321
  whiteSpace: 'nowrap',
311
322
  },
312
- '.cm-ySelectionCaret:hover > .cm-ySelectionInfo': {
323
+ '.cm-collab-selectionCaret:hover > .cm-collab-selectionInfo': {
313
324
  opacity: 1,
314
325
  transitionDelay: '0s',
315
326
  },
@@ -3,30 +3,35 @@
3
3
  //
4
4
 
5
5
  import { closeBrackets } from '@codemirror/autocomplete';
6
+ import { history } from '@codemirror/commands';
6
7
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
7
8
  import { type Extension } from '@codemirror/state';
8
9
  import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
9
- import { drawSelection, EditorView, placeholder } from '@codemirror/view';
10
+ import { drawSelection, EditorView, highlightActiveLine, placeholder } from '@codemirror/view';
10
11
 
11
- import type { ThemeMode } from '@dxos/react-ui';
12
+ import { type ThemeMode } from '@dxos/react-ui';
13
+
14
+ import { type TextEditorProps } from '../components';
12
15
 
13
16
  export type BasicBundleOptions = {
14
- readonly?: boolean;
15
17
  themeMode?: ThemeMode;
16
- placeholder?: string;
17
- };
18
+ } & Pick<TextEditorProps, 'placeholder' | 'multiline'>;
18
19
 
19
- export const basicBundle = ({ readonly, themeMode, placeholder: _placeholder }: BasicBundleOptions): Extension[] =>
20
- [
21
- EditorView.lineWrapping,
20
+ export const basicBundle = ({
21
+ themeMode,
22
+ placeholder: _placeholder,
23
+ multiline = true,
24
+ }: BasicBundleOptions): Extension[] => [
25
+ multiline ? EditorView.lineWrapping : [],
22
26
 
23
- // TODO(burdon): Compare with minimalSetup.
24
- // https://codemirror.net/docs/ref/#codemirror.minimalSetup
25
- bracketMatching(),
26
- closeBrackets(),
27
- drawSelection(),
28
- _placeholder && placeholder(_placeholder),
27
+ // https://codemirror.net/docs/ref/#codemirror.minimalSetup
28
+ bracketMatching(),
29
+ closeBrackets(),
30
+ drawSelection(),
31
+ highlightActiveLine(),
32
+ history(),
33
+ _placeholder ? placeholder(_placeholder) : [],
29
34
 
30
- // TODO(burdon): Is this required?
31
- themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
32
- ].filter(Boolean) as Extension[];
35
+ // https://github.com/codemirror/theme-one-dark
36
+ themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
37
+ ];
@@ -53,22 +53,20 @@ const getLineRange = (lines: BlockInfo[], from: number, to: number) => {
53
53
  };
54
54
 
55
55
  export const code = () => {
56
- const getDecorations = (view: EditorView) => {
56
+ const buildDecorations = (view: EditorView) => {
57
57
  const decorations: Range<Decoration>[] = [];
58
- if (view.state.readOnly) {
59
- const text = view.state.doc.sliceString(0);
60
- const matches = text.matchAll(CODE_REGEX);
61
- const blocks = view.viewportLineBlocks;
62
- for (const match of matches) {
63
- const range = getLineRange(blocks, match.index!, match.index! + match[0].length);
64
- for (let i = range[0]; i <= range[1]; i++) {
65
- const block = blocks[i];
66
- decorations.push(
67
- Decoration.line({
68
- class: mx('cm-codeblock', i === range[0] && 'cm-codeblock-first', i === range[1] && 'cm-codeblock-last'),
69
- }).range(block.from),
70
- );
71
- }
58
+ const text = view.state.doc.sliceString(0);
59
+ const matches = text.matchAll(CODE_REGEX);
60
+ const blocks = view.viewportLineBlocks;
61
+ for (const match of matches) {
62
+ const range = getLineRange(blocks, match.index!, match.index! + match[0].length);
63
+ for (let i = range[0]; i <= range[1]; i++) {
64
+ const block = blocks[i];
65
+ decorations.push(
66
+ Decoration.line({
67
+ class: mx('cm-codeblock', i === range[0] && 'cm-codeblock-first', i === range[1] && 'cm-codeblock-last'),
68
+ }).range(block.from),
69
+ );
72
70
  }
73
71
  }
74
72
 
@@ -78,14 +76,22 @@ export const code = () => {
78
76
  return [
79
77
  ViewPlugin.fromClass(
80
78
  class {
79
+ hasFocus = false;
81
80
  decorations: DecorationSet;
82
81
  constructor(view: EditorView) {
83
- this.decorations = getDecorations(view);
82
+ this.decorations = buildDecorations(view);
84
83
  }
85
84
 
86
85
  update(update: ViewUpdate) {
87
- if (update.docChanged || update.viewportChanged) {
88
- this.decorations = getDecorations(update.view);
86
+ if (
87
+ // TODO(burdon): Generalize for other extensions.
88
+ this.hasFocus !== update.view.hasFocus ||
89
+ update.startState.readOnly !== update.view.state.readOnly ||
90
+ update.docChanged ||
91
+ update.viewportChanged
92
+ ) {
93
+ this.hasFocus = update.view.hasFocus;
94
+ this.decorations = buildDecorations(update.view);
89
95
  }
90
96
  }
91
97
  },