@dxos/react-ui-editor 0.3.11-main.53a52d9 → 0.3.11-main.55b2292

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 (80) hide show
  1. package/dist/lib/browser/index.mjs +673 -522
  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 +13 -14
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +14 -6
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +1 -1
  9. package/dist/types/src/components/TextEditor/comments.stories.d.ts +1 -1
  10. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -1
  11. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  12. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  13. package/dist/types/src/extensions/automerge/defs.d.ts +7 -9
  14. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  15. package/dist/types/src/extensions/automerge/index.d.ts +0 -1
  16. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
  17. package/dist/types/src/extensions/automerge/semaphore.d.ts +7 -12
  18. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  19. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
  20. package/dist/types/src/extensions/awareness.d.ts +2 -0
  21. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  22. package/dist/types/src/extensions/basic.d.ts +4 -5
  23. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  24. package/dist/types/src/extensions/code.d.ts.map +1 -1
  25. package/dist/types/src/extensions/comments.d.ts +29 -16
  26. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  27. package/dist/types/src/extensions/cursor.d.ts +3 -3
  28. package/dist/types/src/extensions/cursor.d.ts.map +1 -1
  29. package/dist/types/src/extensions/hr.d.ts.map +1 -1
  30. package/dist/types/src/extensions/index.d.ts +1 -0
  31. package/dist/types/src/extensions/index.d.ts.map +1 -1
  32. package/dist/types/src/extensions/markdown/bundle.d.ts +2 -5
  33. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  34. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  35. package/dist/types/src/extensions/outliner.d.ts +4 -0
  36. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  37. package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
  38. package/dist/types/src/hooks/automerge.d.ts +2 -1
  39. package/dist/types/src/hooks/automerge.d.ts.map +1 -1
  40. package/dist/types/src/hooks/defs.d.ts +28 -0
  41. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  42. package/dist/types/src/hooks/index.d.ts +2 -1
  43. package/dist/types/src/hooks/index.d.ts.map +1 -1
  44. package/dist/types/src/hooks/useTextModel.d.ts +2 -27
  45. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  46. package/dist/types/src/hooks/yjs.d.ts +2 -1
  47. package/dist/types/src/hooks/yjs.d.ts.map +1 -1
  48. package/dist/types/src/themes/default.d.ts.map +1 -1
  49. package/dist/types/src/util.d.ts +2 -0
  50. package/dist/types/src/util.d.ts.map +1 -1
  51. package/package.json +32 -32
  52. package/src/components/TextEditor/MarkdownEditor.stories.tsx +64 -69
  53. package/src/components/TextEditor/TextEditor.tsx +96 -68
  54. package/src/components/TextEditor/comments.stories.tsx +111 -69
  55. package/src/extensions/autocomplete.ts +6 -3
  56. package/src/extensions/automerge/automerge.ts +39 -45
  57. package/src/extensions/automerge/defs.ts +10 -31
  58. package/src/extensions/automerge/index.ts +0 -1
  59. package/src/extensions/automerge/semaphore.ts +23 -20
  60. package/src/extensions/automerge/update-automerge.ts +2 -2
  61. package/src/extensions/awareness.ts +20 -2
  62. package/src/extensions/basic.ts +22 -17
  63. package/src/extensions/code.ts +15 -17
  64. package/src/extensions/comments.ts +235 -132
  65. package/src/extensions/cursor.ts +7 -3
  66. package/src/extensions/hr.ts +49 -50
  67. package/src/extensions/index.ts +1 -0
  68. package/src/extensions/link.ts +3 -3
  69. package/src/extensions/markdown/bundle.ts +18 -24
  70. package/src/extensions/mention.ts +9 -2
  71. package/src/extensions/outliner.ts +12 -0
  72. package/src/extensions/tasklist.ts +76 -84
  73. package/src/extensions/yjs/yjs.test.ts +1 -1
  74. package/src/hooks/automerge.ts +2 -1
  75. package/src/hooks/defs.ts +47 -0
  76. package/src/hooks/index.ts +3 -1
  77. package/src/hooks/useTextModel.ts +2 -45
  78. package/src/hooks/yjs.ts +2 -1
  79. package/src/themes/default.ts +0 -8
  80. package/src/util.ts +26 -3
@@ -7,14 +7,14 @@ import { tags as tags2 } from "@lezer/highlight";
7
7
 
8
8
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
9
9
  import { EditorState as EditorState2 } from "@codemirror/state";
10
- import { EditorView as EditorView12 } from "@codemirror/view";
10
+ import { EditorView as EditorView13 } from "@codemirror/view";
11
11
  import { useFocusableGroup } from "@fluentui/react-tabster";
12
12
  import { vim } from "@replit/codemirror-vim";
13
13
  import defaultsDeep2 from "lodash.defaultsdeep";
14
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState, useRef } from "react";
15
- import { generateName } from "@dxos/display-name";
16
- import { useThemeContext } from "@dxos/react-ui";
17
- import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
14
+ import React, { forwardRef, useCallback, useEffect as useEffect3, useImperativeHandle, useRef, useState } from "react";
15
+ import { log as log2 } from "@dxos/log";
16
+ import { useThemeContext as useThemeContext2 } from "@dxos/react-ui";
17
+ import { inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
18
18
 
19
19
  // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
20
20
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
@@ -29,9 +29,11 @@ var autocomplete = ({ onSearch }) => {
29
29
  // https://codemirror.net/examples/autocompletion
30
30
  // https://codemirror.net/docs/ref/#autocomplete.autocompletion
31
31
  autocompletion({
32
- defaultKeymap: false,
33
- // Don't start unless key press.
34
- activateOnTyping: false
32
+ activateOnTyping: true,
33
+ // closeOnBlur: false,
34
+ // defaultKeymap: false,
35
+ // TODO(burdon): Styles/fragments.
36
+ tooltipClass: () => "shadow rounded"
35
37
  }),
36
38
  // TODO(burdon): Option to create new page?
37
39
  // TODO(burdon): Optional decoration via addToOptions
@@ -51,9 +53,10 @@ var autocomplete = ({ onSearch }) => {
51
53
  };
52
54
 
53
55
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
54
- import { Facet as Facet2, StateField } from "@codemirror/state";
55
- import { ViewPlugin } from "@codemirror/view";
56
+ import { StateField } from "@codemirror/state";
57
+ import { EditorView, ViewPlugin } from "@codemirror/view";
56
58
  import { next as A } from "@dxos/automerge/automerge";
59
+ import { invariant } from "@dxos/invariant";
57
60
 
58
61
  // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
59
62
  import get from "lodash.get";
@@ -94,12 +97,12 @@ var cursorConverter = (handle, path) => ({
94
97
  // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
95
98
  import { Annotation, StateEffect } from "@codemirror/state";
96
99
  var effectType = StateEffect.define({});
100
+ var reconcileAnnotationType = Annotation.define();
101
+ var getPath = (state, field) => state.field(field).path;
102
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
97
103
  var updateHeads = (newHeads) => effectType.of({
98
104
  newHeads
99
105
  });
100
- var getLastHeads = (state, field) => state.field(field).lastHeads;
101
- var getPath = (state, field) => state.field(field).path;
102
- var reconcileAnnotationType = Annotation.define();
103
106
  var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
104
107
 
105
108
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
@@ -237,40 +240,42 @@ var charPath = (textPath, candidatePath) => {
237
240
 
238
241
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
239
242
  var PatchSemaphore = class {
240
- constructor(_field) {
241
- this._field = _field;
243
+ // prettier-ignore
244
+ constructor(_handle, _state) {
245
+ this._handle = _handle;
246
+ this._state = _state;
242
247
  this._inReconcile = false;
243
- this._queue = [];
244
- this.reconcile = (handle, view) => {
245
- if (this._inReconcile) {
246
- return;
247
- }
248
- this._inReconcile = true;
249
- const path = getPath(view.state, this._field);
250
- const oldHeads = getLastHeads(view.state, this._field);
251
- let selection = view.state.selection;
252
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
253
- const toInvert = transactions.slice().reverse();
254
- for (const tx of toInvert) {
255
- const inverted = tx.changes.invert(tx.startState.doc);
256
- selection = selection.map(inverted);
257
- view.dispatch({
258
- changes: inverted,
259
- annotations: reconcileAnnotationType.of(true)
260
- });
261
- }
262
- let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
263
- if (newHeads === null || newHeads === void 0) {
264
- newHeads = automerge2.getHeads(handle.docSync());
265
- }
266
- const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(handle.docSync(), oldHeads, newHeads);
267
- updateCodeMirror(view, selection, path, diff);
248
+ }
249
+ // NOTE: Cannot destruct view.state.
250
+ reconcile(view) {
251
+ if (this._inReconcile) {
252
+ return;
253
+ }
254
+ this._inReconcile = true;
255
+ const path = getPath(view.state, this._state);
256
+ const oldHeads = getLastHeads(view.state, this._state);
257
+ let selection = view.state.selection;
258
+ const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
259
+ const toInvert = transactions.slice().reverse();
260
+ for (const tx of toInvert) {
261
+ const inverted = tx.changes.invert(tx.startState.doc);
262
+ selection = selection.map(inverted);
268
263
  view.dispatch({
269
- effects: updateHeads(newHeads),
270
- annotations: reconcileAnnotationType.of({})
264
+ changes: inverted,
265
+ annotations: reconcileAnnotationType.of(true)
271
266
  });
272
- this._inReconcile = false;
273
- };
267
+ }
268
+ let newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
269
+ if (newHeads === null || newHeads === void 0) {
270
+ newHeads = automerge2.getHeads(this._handle.docSync());
271
+ }
272
+ const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(this._handle.docSync(), oldHeads, newHeads);
273
+ updateCodeMirror(view, selection, path, diff);
274
+ view.dispatch({
275
+ effects: updateHeads(newHeads),
276
+ annotations: reconcileAnnotationType.of({})
277
+ });
278
+ this._inReconcile = false;
274
279
  }
275
280
  };
276
281
 
@@ -280,14 +285,15 @@ var defaultCursorConverter = {
280
285
  toCursor: (position) => position.toString(),
281
286
  fromCursor: (cursor) => parseInt(cursor)
282
287
  };
283
- var Cursor = class {
288
+ var Cursor = class _Cursor {
284
289
  static {
285
290
  this.converter = Facet.define({
286
291
  combine: (providers) => providers[0] ?? defaultCursorConverter
287
292
  });
288
293
  }
289
294
  static {
290
- this.getCursorFromRange = (cursorConverter3, range) => {
295
+ this.getCursorFromRange = (state, range) => {
296
+ const cursorConverter3 = state.facet(_Cursor.converter);
291
297
  const from = cursorConverter3.toCursor(range.from);
292
298
  const to = cursorConverter3.toCursor(range.to, -1);
293
299
  return [
@@ -297,7 +303,8 @@ var Cursor = class {
297
303
  };
298
304
  }
299
305
  static {
300
- this.getRangeFromCursor = (cursorConverter3, cursor) => {
306
+ this.getRangeFromCursor = (state, cursor) => {
307
+ const cursorConverter3 = state.facet(_Cursor.converter);
301
308
  const parts = cursor.split(":");
302
309
  const from = cursorConverter3.fromCursor(parts[0]);
303
310
  const to = cursorConverter3.fromCursor(parts[1]);
@@ -310,21 +317,19 @@ var Cursor = class {
310
317
  };
311
318
 
312
319
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
313
- var semaphoreFacet = Facet2.define({
314
- combine: (values) => values.at(-1)
315
- });
320
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts";
316
321
  var automerge3 = ({ handle, path }) => {
317
- const stateField = StateField.define({
322
+ const state = StateField.define({
318
323
  create: () => ({
324
+ path: path.slice(),
319
325
  lastHeads: A.getHeads(handle.docSync()),
320
- unreconciledTransactions: [],
321
- path: path.slice()
326
+ unreconciledTransactions: []
322
327
  }),
323
328
  update: (value, tr) => {
324
329
  const result = {
330
+ path: path.slice(),
325
331
  lastHeads: value.lastHeads,
326
- unreconciledTransactions: value.unreconciledTransactions.slice(),
327
- path: path.slice()
332
+ unreconciledTransactions: value.unreconciledTransactions.slice()
328
333
  };
329
334
  let clearUnreconciled = false;
330
335
  for (const effect of tr.effects) {
@@ -343,41 +348,50 @@ var automerge3 = ({ handle, path }) => {
343
348
  return result;
344
349
  }
345
350
  });
346
- const semaphore = new PatchSemaphore(stateField);
347
- const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
348
- constructor(_view) {
349
- this._view = _view;
350
- this._handleChange = () => {
351
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
352
- };
353
- handle.addListener("change", this._handleChange);
354
- }
355
- update(update2) {
356
- if (update2.transactions.length > 0 && update2.transactions.some((tr) => !isReconcileTx(tr))) {
357
- queueMicrotask(() => {
358
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
351
+ const semaphore = new PatchSemaphore(handle, state);
352
+ return [
353
+ Cursor.converter.of(cursorConverter(handle, path)),
354
+ // Reconcile external updates.
355
+ ViewPlugin.fromClass(class {
356
+ constructor(_view) {
357
+ this._view = _view;
358
+ invariant(this._view, void 0, {
359
+ F: __dxlog_file,
360
+ L: 66,
361
+ S: this,
362
+ A: [
363
+ "this._view",
364
+ ""
365
+ ]
359
366
  });
367
+ handle.addListener("change", this._handleChange.bind(this));
360
368
  }
361
- }
362
- destroy() {
363
- handle.addListener("change", this._handleChange);
364
- }
365
- });
366
- return {
367
- extension: [
368
- Cursor.converter.of(cursorConverter(handle, path)),
369
- semaphoreFacet.of(semaphore),
370
- stateField,
371
- viewPlugin
372
- ]
373
- };
369
+ destroy() {
370
+ handle.removeListener("change", this._handleChange.bind(this));
371
+ }
372
+ _handleChange() {
373
+ semaphore.reconcile(this._view);
374
+ }
375
+ }),
376
+ // Reconcile local updates.
377
+ EditorView.updateListener.of(({ view, changes }) => {
378
+ if (!changes.empty) {
379
+ semaphore.reconcile(view);
380
+ }
381
+ }),
382
+ state
383
+ ];
374
384
  };
375
385
 
376
386
  // packages/ui/react-ui-editor/src/extensions/awareness.ts
377
- import { Annotation as Annotation2, Facet as Facet3, RangeSet } from "@codemirror/state";
378
- import { Decoration, EditorView, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
387
+ import { Annotation as Annotation2, Facet as Facet2, RangeSet } from "@codemirror/state";
388
+ import { Decoration, EditorView as EditorView2, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
389
+ import { useEffect } from "react";
379
390
  import { Event } from "@dxos/async";
380
391
  import { Context } from "@dxos/context";
392
+ import { generateName } from "@dxos/display-name";
393
+ import { useThemeContext } from "@dxos/react-ui";
394
+ import { getColorForValue } from "@dxos/react-ui-theme";
381
395
  var dummyProvider = {
382
396
  remoteStateChange: new Event(),
383
397
  open: () => {
@@ -388,7 +402,7 @@ var dummyProvider = {
388
402
  },
389
403
  getRemoteStates: () => []
390
404
  };
391
- var AwarenessProvider = Facet3.define({
405
+ var AwarenessProvider = Facet2.define({
392
406
  combine: (providers) => providers[0] ?? dummyProvider
393
407
  });
394
408
  var RemoteSelectionChangedAnnotation = Annotation2.define();
@@ -401,6 +415,29 @@ var awareness = (provider = dummyProvider) => {
401
415
  styles
402
416
  ];
403
417
  };
418
+ var useAwareness = ({ awareness: awareness2, peer }) => {
419
+ const { themeMode } = useThemeContext();
420
+ useEffect(() => {
421
+ if (awareness2 && peer) {
422
+ awareness2.setLocalStateField("user", {
423
+ name: peer.name ?? generateName(peer.id),
424
+ color: getColorForValue({
425
+ value: peer.id,
426
+ type: "color"
427
+ }),
428
+ colorLight: getColorForValue({
429
+ value: peer.id,
430
+ themeMode,
431
+ type: "highlight"
432
+ })
433
+ });
434
+ }
435
+ }, [
436
+ awareness2,
437
+ peer,
438
+ themeMode
439
+ ]);
440
+ };
404
441
  var RemoteSelectionsDecorator = class {
405
442
  constructor(view) {
406
443
  this.decorations = RangeSet.of([]);
@@ -549,7 +586,7 @@ var RemoteCaretWidget = class extends WidgetType {
549
586
  return true;
550
587
  }
551
588
  };
552
- var styles = EditorView.baseTheme({
589
+ var styles = EditorView2.baseTheme({
553
590
  ".cm-collab-selection": {},
554
591
  ".cm-collab-selectionLine": {
555
592
  padding: 0,
@@ -595,7 +632,7 @@ var styles = EditorView.baseTheme({
595
632
  zIndex: 101,
596
633
  transition: "opacity .3s ease-in-out",
597
634
  backgroundColor: "inherit",
598
- // these should be separate
635
+ // These should be separate.
599
636
  opacity: 0,
600
637
  transitionDelay: "0s",
601
638
  whiteSpace: "nowrap"
@@ -608,26 +645,28 @@ var styles = EditorView.baseTheme({
608
645
 
609
646
  // packages/ui/react-ui-editor/src/extensions/basic.ts
610
647
  import { closeBrackets } from "@codemirror/autocomplete";
648
+ import { history } from "@codemirror/commands";
611
649
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
612
650
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
613
- import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
614
- var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
615
- EditorView2.lineWrapping,
616
- // TODO(burdon): Compare with minimalSetup.
651
+ import { drawSelection, EditorView as EditorView3, highlightActiveLine, placeholder } from "@codemirror/view";
652
+ var basicBundle = ({ themeMode, placeholder: _placeholder, multiline = true }) => [
653
+ multiline ? EditorView3.lineWrapping : [],
617
654
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
618
655
  bracketMatching(),
619
656
  closeBrackets(),
620
657
  drawSelection(),
621
- _placeholder && placeholder(_placeholder),
622
- // TODO(burdon): Is this required?
658
+ highlightActiveLine(),
659
+ history(),
660
+ _placeholder ? placeholder(_placeholder) : [],
661
+ // https://github.com/codemirror/theme-one-dark
623
662
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
624
- ].filter(Boolean);
663
+ ];
625
664
 
626
665
  // packages/ui/react-ui-editor/src/extensions/blast.ts
627
- import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
666
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
628
667
  import defaultsDeep from "lodash.defaultsdeep";
629
- import { invariant } from "@dxos/invariant";
630
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
668
+ import { invariant as invariant2 } from "@dxos/invariant";
669
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
631
670
  var defaultOptions = {
632
671
  effect: 2,
633
672
  maxParticles: 200,
@@ -672,7 +711,7 @@ var blast = (options = defaultOptions) => {
672
711
  };
673
712
  return [
674
713
  // Cursor moved.
675
- EditorView3.updateListener.of((update2) => {
714
+ EditorView4.updateListener.of((update2) => {
676
715
  if (blaster?.node !== update2.view.scrollDOM) {
677
716
  if (blaster) {
678
717
  blaster.destroy();
@@ -753,8 +792,8 @@ var Blaster = class {
753
792
  return this._node;
754
793
  }
755
794
  initialize() {
756
- invariant(!this._canvas && !this._ctx, void 0, {
757
- F: __dxlog_file,
795
+ invariant2(!this._canvas && !this._ctx, void 0, {
796
+ F: __dxlog_file2,
758
797
  L: 138,
759
798
  S: this,
760
799
  A: [
@@ -790,8 +829,8 @@ var Blaster = class {
790
829
  }
791
830
  }
792
831
  start() {
793
- invariant(this._canvas && this._ctx, void 0, {
794
- F: __dxlog_file,
832
+ invariant2(this._canvas && this._ctx, void 0, {
833
+ F: __dxlog_file2,
795
834
  L: 177,
796
835
  S: this,
797
836
  A: [
@@ -934,7 +973,7 @@ var random = (min, max) => {
934
973
  };
935
974
 
936
975
  // packages/ui/react-ui-editor/src/extensions/code.ts
937
- import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
976
+ import { ViewPlugin as ViewPlugin3, EditorView as EditorView5, Decoration as Decoration2 } from "@codemirror/view";
938
977
  import { mx as mx2 } from "@dxos/react-ui-theme";
939
978
 
940
979
  // packages/ui/react-ui-editor/src/styles/cursors.ts
@@ -1025,7 +1064,7 @@ var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue)
1025
1064
 
1026
1065
  // packages/ui/react-ui-editor/src/extensions/code.ts
1027
1066
  var CODE_REGEX = /```[\s\S]*?```/gs;
1028
- var styles2 = EditorView4.baseTheme({
1067
+ var styles2 = EditorView5.baseTheme({
1029
1068
  "& .cm-codeblock": {
1030
1069
  fontFamily: getToken("fontFamily.mono", []).join(",")
1031
1070
  },
@@ -1059,20 +1098,18 @@ var getLineRange = (lines, from, to) => {
1059
1098
  ];
1060
1099
  };
1061
1100
  var code2 = () => {
1062
- const getDecorations = (view) => {
1101
+ const buildDecorations5 = (view) => {
1063
1102
  const decorations = [];
1064
- if (!view.hasFocus || view.state.readOnly) {
1065
- const text = view.state.doc.sliceString(0);
1066
- const matches = text.matchAll(CODE_REGEX);
1067
- const blocks = view.viewportLineBlocks;
1068
- for (const match of matches) {
1069
- const range = getLineRange(blocks, match.index, match.index + match[0].length);
1070
- for (let i = range[0]; i <= range[1]; i++) {
1071
- const block = blocks[i];
1072
- decorations.push(Decoration2.line({
1073
- class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1074
- }).range(block.from));
1075
- }
1103
+ const text = view.state.doc.sliceString(0);
1104
+ const matches = text.matchAll(CODE_REGEX);
1105
+ const blocks = view.viewportLineBlocks;
1106
+ for (const match of matches) {
1107
+ const range = getLineRange(blocks, match.index, match.index + match[0].length);
1108
+ for (let i = range[0]; i <= range[1]; i++) {
1109
+ const block = blocks[i];
1110
+ decorations.push(Decoration2.line({
1111
+ class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1112
+ }).range(block.from));
1076
1113
  }
1077
1114
  }
1078
1115
  return Decoration2.set(decorations);
@@ -1081,7 +1118,7 @@ var code2 = () => {
1081
1118
  ViewPlugin3.fromClass(class {
1082
1119
  constructor(view) {
1083
1120
  this.hasFocus = false;
1084
- this.decorations = getDecorations(view);
1121
+ this.decorations = buildDecorations5(view);
1085
1122
  }
1086
1123
  update(update2) {
1087
1124
  if (
@@ -1089,7 +1126,7 @@ var code2 = () => {
1089
1126
  this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1090
1127
  ) {
1091
1128
  this.hasFocus = update2.view.hasFocus;
1092
- this.decorations = getDecorations(update2.view);
1129
+ this.decorations = buildDecorations5(update2.view);
1093
1130
  }
1094
1131
  }
1095
1132
  }, {
@@ -1101,32 +1138,51 @@ var code2 = () => {
1101
1138
 
1102
1139
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1103
1140
  import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1104
- import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
1141
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView6 } from "@codemirror/view";
1105
1142
  import sortBy from "lodash.sortby";
1143
+ import { useEffect as useEffect2 } from "react";
1106
1144
  import { debounce } from "@dxos/async";
1107
- import { invariant as invariant2 } from "@dxos/invariant";
1108
- import { log as log2 } from "@dxos/log";
1145
+ import { invariant as invariant3 } from "@dxos/invariant";
1109
1146
  import { nonNullable } from "@dxos/util";
1110
1147
 
1111
1148
  // packages/ui/react-ui-editor/src/util.ts
1112
1149
  import { log } from "@dxos/log";
1113
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1150
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1114
1151
  var callbackWrapper = (fn) => (...args) => {
1115
1152
  try {
1116
1153
  return fn(...args);
1117
- } catch (error) {
1118
- log.catch(error, void 0, {
1119
- F: __dxlog_file2,
1120
- L: 16,
1154
+ } catch (err) {
1155
+ log.catch(err, void 0, {
1156
+ F: __dxlog_file3,
1157
+ L: 18,
1121
1158
  S: void 0,
1122
1159
  C: (f, a) => f(...a)
1123
1160
  });
1124
1161
  }
1125
1162
  };
1163
+ var logChanges = (trs) => {
1164
+ const changes = trs.flatMap((tr) => {
1165
+ if (tr.changes.empty) {
1166
+ return void 0;
1167
+ }
1168
+ const changes2 = [];
1169
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => changes2.push(JSON.stringify({
1170
+ fromA,
1171
+ toA,
1172
+ fromB,
1173
+ toB,
1174
+ inserted: inserted.toString()
1175
+ })));
1176
+ return changes2;
1177
+ }).filter(Boolean);
1178
+ if (changes.length) {
1179
+ console.log("changes", changes);
1180
+ }
1181
+ };
1126
1182
 
1127
1183
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1128
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1129
- var styles3 = EditorView5.baseTheme({
1184
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1185
+ var styles3 = EditorView6.baseTheme({
1130
1186
  "& .cm-comment": {
1131
1187
  backgroundColor: getToken("extend.colors.yellow.50")
1132
1188
  },
@@ -1142,49 +1198,59 @@ var marks = {
1142
1198
  class: "cm-comment-active"
1143
1199
  })
1144
1200
  };
1145
- var setFocus = (view, thread) => {
1146
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1147
- if (!range) {
1201
+ var setFocus = (view, id, center = true) => {
1202
+ const comment = view.state.field(commentsState).comments.find((range2) => range2.comment.id === id);
1203
+ if (!comment?.comment.cursor) {
1148
1204
  return;
1149
1205
  }
1150
- view.dispatch({
1151
- effects: setSelection.of({
1152
- active: thread
1153
- }),
1154
- selection: {
1155
- anchor: range.from
1156
- },
1157
- scrollIntoView: true
1158
- });
1206
+ const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);
1207
+ if (range) {
1208
+ view.dispatch({
1209
+ selection: {
1210
+ anchor: range.from
1211
+ },
1212
+ effects: [
1213
+ //
1214
+ EditorView6.scrollIntoView(range.from, center ? {
1215
+ y: "center"
1216
+ } : void 0),
1217
+ setSelection.of({
1218
+ active: id
1219
+ })
1220
+ ]
1221
+ });
1222
+ }
1159
1223
  };
1160
- var setCommentRange = StateEffect2.define();
1224
+ var setComments = StateEffect2.define();
1161
1225
  var setSelection = StateEffect2.define();
1162
1226
  var setCommentState = StateEffect2.define();
1163
- var commentsStateField = StateField2.define({
1227
+ var commentsState = StateField2.define({
1164
1228
  create: () => ({
1165
- ranges: []
1229
+ comments: [],
1230
+ selection: {}
1166
1231
  }),
1167
1232
  update: (value, tr) => {
1168
- const cursorConverter3 = tr.state.facet(Cursor.converter);
1169
1233
  for (const effect of tr.effects) {
1170
1234
  if (effect.is(setSelection)) {
1171
1235
  return {
1172
1236
  ...value,
1173
- ...effect.value
1237
+ selection: effect.value
1174
1238
  };
1175
1239
  }
1176
- if (effect.is(setCommentRange)) {
1177
- const { comments: comments2 } = effect.value;
1178
- const ranges = comments2.map((comment) => {
1179
- const range = Cursor.getRangeFromCursor(cursorConverter3, comment.cursor);
1240
+ if (effect.is(setComments)) {
1241
+ const comments2 = effect.value.map((comment) => {
1242
+ if (!comment.cursor) {
1243
+ return void 0;
1244
+ }
1245
+ const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);
1180
1246
  return range && {
1181
- ...comment,
1182
- ...range
1247
+ comment,
1248
+ range
1183
1249
  };
1184
1250
  }).filter(nonNullable);
1185
1251
  return {
1186
1252
  ...value,
1187
- ranges
1253
+ comments: comments2
1188
1254
  };
1189
1255
  }
1190
1256
  if (effect.is(setCommentState)) {
@@ -1194,50 +1260,109 @@ var commentsStateField = StateField2.define({
1194
1260
  return value;
1195
1261
  }
1196
1262
  });
1197
- var highlightDecorations = EditorView5.decorations.compute([
1198
- commentsStateField
1263
+ var highlightDecorations = EditorView6.decorations.compute([
1264
+ commentsState
1199
1265
  ], (state) => {
1200
- const { active, ranges } = state.field(commentsStateField);
1201
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1202
- if (selection.from === selection.to) {
1266
+ const { selection: { active }, comments: comments2 } = state.field(commentsState);
1267
+ const decorations = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
1268
+ const range = comment.range;
1269
+ if (!range || range.from === range.to) {
1270
+ console.warn("Invalid range:", range);
1203
1271
  return void 0;
1204
1272
  }
1205
- const range = {
1206
- from: selection.from,
1207
- to: selection.to
1208
- };
1209
- if (selection.id === active) {
1273
+ if (comment.comment.id === active) {
1210
1274
  return marks.highlightActive.range(range.from, range.to);
1211
1275
  } else {
1212
1276
  return marks.highlight.range(range.from, range.to);
1213
1277
  }
1214
- }).filter(nonNullable) ?? [];
1278
+ }).filter(nonNullable);
1215
1279
  return Decoration3.set(decorations);
1216
1280
  });
1281
+ var trackPastedComments = (onUpdate) => {
1282
+ let tracked = null;
1283
+ const handleTrack = (event, view) => {
1284
+ const comments2 = view.state.field(commentsState);
1285
+ const { main } = view.state.selection;
1286
+ const selectedRanges = comments2.comments.filter(({ range }) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1287
+ if (!selectedRanges.length) {
1288
+ tracked = null;
1289
+ } else {
1290
+ tracked = {
1291
+ text: view.state.doc.slice(main.from, main.to),
1292
+ comments: selectedRanges.map(({ comment, range }) => ({
1293
+ id: comment.id,
1294
+ from: range.from - main.from,
1295
+ to: range.to - main.from
1296
+ }))
1297
+ };
1298
+ }
1299
+ };
1300
+ return [
1301
+ EditorView6.domEventHandlers({
1302
+ cut: handleTrack,
1303
+ copy: handleTrack
1304
+ }),
1305
+ // Handle paste.
1306
+ EditorView6.updateListener.of(({ view, state, transactions }) => {
1307
+ if (tracked) {
1308
+ const paste = transactions.find((tr) => tr.isUserEvent("input.paste"));
1309
+ if (paste) {
1310
+ let found = -1;
1311
+ paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1312
+ if (text.eq(tracked.text)) {
1313
+ for (let i = transactions.indexOf(paste) + 1; i < transactions.length; i++) {
1314
+ fromB = transactions[i].changes.mapPos(fromB);
1315
+ }
1316
+ found = fromB;
1317
+ }
1318
+ });
1319
+ if (found > -1) {
1320
+ for (const moved of tracked.comments) {
1321
+ const range = {
1322
+ from: found + moved.from,
1323
+ to: found + moved.to
1324
+ };
1325
+ const cursor = Cursor.getCursorFromRange(state, range);
1326
+ onUpdate(moved.id, cursor);
1327
+ }
1328
+ }
1329
+ tracked = null;
1330
+ }
1331
+ }
1332
+ })
1333
+ ];
1334
+ };
1217
1335
  var comments = (options = {}) => {
1218
1336
  const { key: shortcut = "meta-'" } = options;
1219
1337
  const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1220
1338
  const createCommentThread = (view) => {
1221
- const cursorConverter3 = view.state.facet(Cursor.converter);
1222
- invariant2(options.onCreate, void 0, {
1223
- F: __dxlog_file3,
1224
- L: 177,
1339
+ invariant3(options.onCreate, void 0, {
1340
+ F: __dxlog_file4,
1341
+ L: 259,
1225
1342
  S: void 0,
1226
1343
  A: [
1227
1344
  "options.onCreate",
1228
1345
  ""
1229
1346
  ]
1230
1347
  });
1231
- const { head, from, to } = view.state.selection.main;
1348
+ const { from, to } = view.state.selection.main;
1232
1349
  if (from === to) {
1233
1350
  return false;
1234
1351
  }
1235
- const cursor = Cursor.getCursorFromRange(cursorConverter3, {
1352
+ if (to === view.state.doc.length) {
1353
+ view.dispatch({
1354
+ changes: {
1355
+ from: to,
1356
+ insert: "\n"
1357
+ }
1358
+ });
1359
+ }
1360
+ const cursor = Cursor.getCursorFromRange(view.state, {
1236
1361
  from,
1237
1362
  to
1238
1363
  });
1239
1364
  if (cursor) {
1240
- const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
1365
+ const id = options.onCreate?.(cursor, view.coordsAtPos(from));
1241
1366
  if (id) {
1242
1367
  view.dispatch({
1243
1368
  effects: setSelection.of({
@@ -1247,25 +1372,13 @@ var comments = (options = {}) => {
1247
1372
  anchor: from
1248
1373
  }
1249
1374
  });
1250
- if (options.footnote) {
1251
- const tag = `[^${id}]`;
1252
- view.dispatch({
1253
- changes: {
1254
- from: head,
1255
- insert: tag
1256
- },
1257
- selection: {
1258
- anchor: head + tag.length
1259
- }
1260
- });
1261
- }
1262
1375
  return true;
1263
1376
  }
1264
1377
  }
1265
1378
  return false;
1266
1379
  };
1267
1380
  return [
1268
- commentsStateField,
1381
+ commentsState,
1269
1382
  highlightDecorations,
1270
1383
  styles3,
1271
1384
  //
@@ -1290,7 +1403,7 @@ var comments = (options = {}) => {
1290
1403
  above: true,
1291
1404
  create: () => {
1292
1405
  const el = document.createElement("div");
1293
- options.onHover?.(el, shortcut);
1406
+ options.onHover(el, shortcut);
1294
1407
  return {
1295
1408
  dom: el,
1296
1409
  offset: {
@@ -1303,139 +1416,154 @@ var comments = (options = {}) => {
1303
1416
  }
1304
1417
  return null;
1305
1418
  }, {
1306
- hideOnChange: true,
1419
+ // TODO(burdon): Hide on change triggered immediately?
1420
+ // hideOnChange: true,
1307
1421
  hoverTime: 1e3
1308
1422
  }) : [],
1309
1423
  //
1310
- // Monitor cursor movement and text updates.
1311
- // TODO(burdon): Is there a better (finer grained) way to do this?
1424
+ // Track deleted ranges and update ranges for decorations.
1312
1425
  //
1313
- EditorView5.updateListener.of(({ view, state, changes }) => {
1314
- const cursorConverter3 = view.state.facet(Cursor.converter);
1315
- {
1316
- let mod = false;
1317
- const { active, ranges } = state.field(commentsStateField);
1318
- changes.iterChanges((from, to, from2, to2) => {
1319
- ranges.forEach((range) => {
1320
- if (from2 === to2) {
1321
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1322
- if (!newRange || newRange.to - newRange.from === 0) {
1323
- log2.info("deleted comment", {
1324
- thread: range.id
1325
- }, {
1326
- F: __dxlog_file3,
1327
- L: 274,
1328
- S: void 0,
1329
- C: (f, a) => f(...a)
1330
- });
1331
- }
1332
- }
1333
- if (from <= range.to) {
1334
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1335
- Object.assign(range, newRange);
1336
- mod = true;
1426
+ EditorView6.updateListener.of(({ view, state, changes }) => {
1427
+ let mod = false;
1428
+ const { comments: comments2, ...value } = state.field(commentsState);
1429
+ changes.iterChanges((from, to, from2, to2) => {
1430
+ comments2.forEach(({ comment, range }) => {
1431
+ if (from2 === to2) {
1432
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1433
+ if (!newRange || newRange.to - newRange.from === 0) {
1434
+ options.onDelete?.(comment.id);
1337
1435
  }
1338
- });
1436
+ }
1437
+ if (from <= range.to) {
1438
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1439
+ Object.assign(range, newRange);
1440
+ mod = true;
1441
+ }
1442
+ });
1443
+ });
1444
+ if (mod) {
1445
+ view.dispatch({
1446
+ effects: setCommentState.of({
1447
+ comments: comments2,
1448
+ ...value
1449
+ })
1339
1450
  });
1340
- if (mod) {
1341
- view.dispatch({
1342
- effects: setCommentState.of({
1343
- active,
1344
- ranges
1345
- })
1346
- });
1347
- }
1348
1451
  }
1349
- {
1350
- const { head } = state.selection.main;
1351
- let min = Infinity;
1352
- const { active, closest, ranges } = state.field(commentsStateField);
1353
- const selected = {
1354
- active: void 0,
1355
- closest: void 0
1356
- };
1357
- ranges.forEach((comment) => {
1358
- const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1359
- if (head >= comment.from && head <= comment.to) {
1360
- selected.active = comment.id;
1361
- }
1452
+ }),
1453
+ //
1454
+ // Track selection/proximity.
1455
+ //
1456
+ EditorView6.updateListener.of(({ view, state, changes }) => {
1457
+ let min = Infinity;
1458
+ const { selection: { active, closest }, comments: comments2 } = state.field(commentsState);
1459
+ const { head } = state.selection.main;
1460
+ const selection = {};
1461
+ comments2.forEach(({ comment, range }) => {
1462
+ if (head >= range.from && head <= range.to) {
1463
+ selection.active = comment.id;
1464
+ selection.closest = void 0;
1465
+ }
1466
+ if (!selection.active) {
1467
+ const d = Math.min(Math.abs(head - range.from), Math.abs(head - range.to));
1362
1468
  if (d < min) {
1363
- selected.closest = comment.id;
1469
+ selection.closest = comment.id;
1364
1470
  min = d;
1365
1471
  }
1366
- });
1367
- if (selected.active !== active || selected.closest !== closest) {
1368
- view.dispatch({
1369
- effects: setSelection.of(selected)
1370
- });
1371
- handleSelect({
1372
- ...selected,
1373
- ranges: ranges.map((range) => ({
1374
- ...range,
1375
- location: view.coordsAtPos(range.from)
1376
- }))
1377
- });
1378
1472
  }
1473
+ });
1474
+ if (selection.active !== active || selection.closest !== closest) {
1475
+ view.dispatch({
1476
+ effects: setSelection.of(selection)
1477
+ });
1478
+ handleSelect({
1479
+ selection,
1480
+ comments: comments2.map(({ comment, range }) => ({
1481
+ comment,
1482
+ range,
1483
+ location: view.coordsAtPos(range.from)
1484
+ }))
1485
+ });
1379
1486
  }
1380
- })
1487
+ }),
1488
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
1381
1489
  ];
1382
1490
  };
1491
+ var useComments = (view, comments2) => {
1492
+ useEffect2(() => {
1493
+ if (view && comments2) {
1494
+ view.dispatch({
1495
+ effects: setComments.of(comments2)
1496
+ });
1497
+ }
1498
+ }, [
1499
+ view,
1500
+ comments2
1501
+ ]);
1502
+ };
1383
1503
 
1384
1504
  // packages/ui/react-ui-editor/src/extensions/hr.ts
1385
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1386
- var styles4 = EditorView6.baseTheme({
1505
+ import { syntaxTree } from "@codemirror/language";
1506
+ import { RangeSetBuilder } from "@codemirror/state";
1507
+ import { Decoration as Decoration4, EditorView as EditorView7, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1508
+ var styles4 = EditorView7.baseTheme({
1387
1509
  "& .cm-hr": {
1388
- // TODO(burdon): ???
1389
1510
  // Note that block-level decorations should not have vertical margins,
1390
- borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1511
+ borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
1391
1512
  }
1392
1513
  });
1393
1514
  var HorizontalRuleWidget = class extends WidgetType2 {
1394
- constructor(_pos) {
1395
- super();
1396
- this._pos = _pos;
1397
- }
1398
- eq(other) {
1399
- return this._pos === other._pos;
1400
- }
1401
- toDOM(view) {
1515
+ toDOM() {
1402
1516
  const el = document.createElement("div");
1403
1517
  el.className = "cm-hr";
1404
1518
  return el;
1405
1519
  }
1406
1520
  };
1407
- var placeholderMatcher = new MatchDecorator({
1408
- regexp: /^---$/g,
1409
- decoration: (match, view, pos) => Decoration4.replace({
1410
- widget: new HorizontalRuleWidget(pos)
1411
- })
1521
+ var decoration = Decoration4.replace({
1522
+ widget: new HorizontalRuleWidget()
1412
1523
  });
1413
- var hr = () => [
1414
- styles4,
1415
- ViewPlugin4.fromClass(class {
1416
- constructor(view) {
1417
- this.rules = placeholderMatcher.createDeco(view);
1418
- }
1419
- update(update2) {
1420
- this.rules = placeholderMatcher.updateDeco(update2, this.rules);
1421
- }
1422
- }, {
1423
- decorations: (instance) => instance.rules,
1424
- // TODO(burdon): Is this really atomic (cursor can move into ---).
1425
- provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1426
- return view.plugin(plugin)?.rules || Decoration4.none;
1524
+ var buildDecorations = (view) => {
1525
+ const builder = new RangeSetBuilder();
1526
+ const { state } = view;
1527
+ const cursor = state.selection.main.head;
1528
+ for (const { from, to } of view.visibleRanges) {
1529
+ syntaxTree(state).iterate({
1530
+ enter: (node) => {
1531
+ if (node.name === "HorizontalRule") {
1532
+ if (cursor <= node.from || cursor >= node.to) {
1533
+ builder.add(node.from, node.to, decoration);
1534
+ }
1535
+ }
1536
+ },
1537
+ from,
1538
+ to
1539
+ });
1540
+ }
1541
+ return builder.finish();
1542
+ };
1543
+ var hr = () => {
1544
+ return [
1545
+ styles4,
1546
+ ViewPlugin4.fromClass(class {
1547
+ constructor(view) {
1548
+ this.decorations = buildDecorations(view);
1549
+ }
1550
+ update(update2) {
1551
+ this.decorations = buildDecorations(update2.view);
1552
+ }
1553
+ }, {
1554
+ decorations: (v) => v.decorations
1427
1555
  })
1428
- })
1429
- ];
1556
+ ];
1557
+ };
1430
1558
 
1431
1559
  // packages/ui/react-ui-editor/src/extensions/image.ts
1432
- import { syntaxTree } from "@codemirror/language";
1560
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1433
1561
  import { StateField as StateField3 } from "@codemirror/state";
1434
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
1562
+ import { Decoration as Decoration5, EditorView as EditorView8, WidgetType as WidgetType3 } from "@codemirror/view";
1435
1563
  var image = (options = {}) => {
1436
1564
  return StateField3.define({
1437
1565
  create: (state) => {
1438
- return Decoration5.set(buildDecorations(0, state.doc.length, state));
1566
+ return Decoration5.set(buildDecorations2(0, state.doc.length, state));
1439
1567
  },
1440
1568
  update: (value, tr) => {
1441
1569
  if (!tr.docChanged && !tr.selection) {
@@ -1455,16 +1583,16 @@ var image = (options = {}) => {
1455
1583
  filterFrom: from,
1456
1584
  filterTo: to,
1457
1585
  filter: () => false,
1458
- add: buildDecorations(from, to, tr.state)
1586
+ add: buildDecorations2(from, to, tr.state)
1459
1587
  });
1460
1588
  },
1461
- provide: (field) => EditorView7.decorations.from(field)
1589
+ provide: (field) => EditorView8.decorations.from(field)
1462
1590
  });
1463
1591
  };
1464
- var buildDecorations = (from, to, state) => {
1592
+ var buildDecorations2 = (from, to, state) => {
1465
1593
  const decorations = [];
1466
1594
  const cursor = state.selection.main.head;
1467
- syntaxTree(state).iterate({
1595
+ syntaxTree2(state).iterate({
1468
1596
  enter: (node) => {
1469
1597
  if (node.name === "Image") {
1470
1598
  const urlNode = node.node.getChild("URL");
@@ -1501,19 +1629,19 @@ var ImageWidget = class extends WidgetType3 {
1501
1629
  };
1502
1630
 
1503
1631
  // packages/ui/react-ui-editor/src/extensions/link.ts
1504
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1505
- import { RangeSetBuilder } from "@codemirror/state";
1506
- import { Decoration as Decoration6, WidgetType as WidgetType4, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1632
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1633
+ import { RangeSetBuilder as RangeSetBuilder2 } from "@codemirror/state";
1634
+ import { hoverTooltip as hoverTooltip2, Decoration as Decoration6, ViewPlugin as ViewPlugin5, WidgetType as WidgetType4 } from "@codemirror/view";
1507
1635
  import { tooltipContent } from "@dxos/react-ui-theme";
1508
1636
  var link = (options = {}) => {
1509
1637
  const extensions = [
1510
1638
  ViewPlugin5.fromClass(class {
1511
1639
  constructor(view) {
1512
- this.decorations = buildDecorations2(view, options);
1640
+ this.decorations = buildDecorations3(view, options);
1513
1641
  }
1514
1642
  update(update2) {
1515
1643
  if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1516
- this.decorations = buildDecorations2(update2.view, options);
1644
+ this.decorations = buildDecorations3(update2.view, options);
1517
1645
  }
1518
1646
  }
1519
1647
  }, {
@@ -1526,7 +1654,7 @@ var link = (options = {}) => {
1526
1654
  // https://codemirror.net/docs/ref/#view.hoverTooltip
1527
1655
  // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1528
1656
  hoverTooltip2((view, pos, side) => {
1529
- const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1657
+ const syntax = syntaxTree3(view.state).resolveInner(pos, side);
1530
1658
  let link2 = null;
1531
1659
  for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1532
1660
  link2 = node.name === "Link" ? node : null;
@@ -1602,12 +1730,12 @@ var LinkText = class extends WidgetType4 {
1602
1730
  return link2;
1603
1731
  }
1604
1732
  };
1605
- var buildDecorations2 = (view, options) => {
1606
- const builder = new RangeSetBuilder();
1733
+ var buildDecorations3 = (view, options) => {
1734
+ const builder = new RangeSetBuilder2();
1607
1735
  const { state } = view;
1608
1736
  const cursor = state.selection.main.head;
1609
1737
  for (const { from, to } of view.visibleRanges) {
1610
- syntaxTree2(state).iterate({
1738
+ syntaxTree3(state).iterate({
1611
1739
  enter: (node) => {
1612
1740
  if (node.name === "Link") {
1613
1741
  const marks2 = node.node.getChildren("LinkMark");
@@ -1638,14 +1766,14 @@ var buildDecorations2 = (view, options) => {
1638
1766
  };
1639
1767
 
1640
1768
  // packages/ui/react-ui-editor/src/extensions/listener.ts
1641
- import { EditorView as EditorView8 } from "@codemirror/view";
1769
+ import { EditorView as EditorView9 } from "@codemirror/view";
1642
1770
  var listener = ({ onFocus, onChange }) => {
1643
1771
  const extensions = [];
1644
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1772
+ onFocus && extensions.push(EditorView9.focusChangeEffect.of((_, focused) => {
1645
1773
  onFocus(focused);
1646
1774
  return null;
1647
1775
  }));
1648
- onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1776
+ onChange && extensions.push(EditorView9.updateListener.of((update2) => {
1649
1777
  onChange(update2.state.doc.toString());
1650
1778
  }));
1651
1779
  return extensions;
@@ -1653,14 +1781,14 @@ var listener = ({ onFocus, onChange }) => {
1653
1781
 
1654
1782
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1655
1783
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1656
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1784
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1657
1785
  import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1658
1786
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1659
1787
  import { languages } from "@codemirror/language-data";
1660
1788
  import { searchKeymap } from "@codemirror/search";
1661
1789
  import { EditorState } from "@codemirror/state";
1662
1790
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1663
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1791
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView10, highlightActiveLine as highlightActiveLine2, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1664
1792
 
1665
1793
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1666
1794
  import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
@@ -1855,17 +1983,17 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1855
1983
  return [
1856
1984
  EditorState.allowMultipleSelections.of(true),
1857
1985
  EditorState.tabSize.of(2),
1858
- EditorView9.lineWrapping,
1859
- customKeymap(),
1986
+ EditorView10.lineWrapping,
1987
+ // https://github.com/codemirror/basic-setup
1860
1988
  bracketMatching2(),
1861
1989
  closeBrackets2(),
1862
1990
  crosshairCursor(),
1863
1991
  dropCursor(),
1864
1992
  drawSelection2(),
1865
- highlightActiveLine(),
1866
- history(),
1993
+ highlightActiveLine2(),
1994
+ history2(),
1867
1995
  indentOnInput(),
1868
- _placeholder && placeholder2(_placeholder),
1996
+ _placeholder ? placeholder2(_placeholder) : [],
1869
1997
  // Main extension.
1870
1998
  // https://github.com/codemirror/lang-markdown
1871
1999
  // https://codemirror.net/5/mode/markdown/index.html (demo).
@@ -1887,34 +2015,40 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1887
2015
  // https://github.com/codemirror/theme-one-dark
1888
2016
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1889
2017
  // Custom styles.
1890
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1891
- ].filter(Boolean);
1892
- };
1893
- var customKeymap = () => keymap4.of([
1894
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1895
- indentWithTab,
1896
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1897
- ...standardKeymap,
1898
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1899
- ...historyKeymap,
1900
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1901
- ...closeBracketsKeymap,
1902
- // https://codemirror.net/docs/ref/#search.searchKeymap
1903
- ...searchKeymap
1904
- ]);
2018
+ syntaxHighlighting2(markdownHighlightStyle(readonly)),
2019
+ keymap4.of([
2020
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
2021
+ indentWithTab,
2022
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
2023
+ ...closeBracketsKeymap,
2024
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
2025
+ ...historyKeymap,
2026
+ // https://codemirror.net/docs/ref/#search.searchKeymap
2027
+ ...searchKeymap,
2028
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
2029
+ ...standardKeymap
2030
+ ])
2031
+ ];
2032
+ };
1905
2033
 
1906
2034
  // packages/ui/react-ui-editor/src/extensions/mention.ts
1907
2035
  import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1908
2036
  var mention = ({ onSearch }) => {
1909
2037
  return autocompletion2({
2038
+ // TODO(burdon): Not working.
2039
+ activateOnTyping: true,
2040
+ // activateOnTypingDelay: 100,
2041
+ // selectOnOpen: true,
1910
2042
  closeOnBlur: false,
2043
+ // defaultKeymap: false,
2044
+ icons: false,
1911
2045
  override: [
1912
2046
  (context) => {
2047
+ console.log(context);
1913
2048
  const match = context.matchBefore(/@(\w+)?/);
1914
2049
  if (!match || match.from === match.to && !context.explicit) {
1915
2050
  return null;
1916
2051
  }
1917
- console.log(match);
1918
2052
  return {
1919
2053
  from: match.from,
1920
2054
  options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
@@ -1926,19 +2060,24 @@ var mention = ({ onSearch }) => {
1926
2060
  });
1927
2061
  };
1928
2062
 
2063
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
2064
+ var outliner = (options = {}) => {
2065
+ return [];
2066
+ };
2067
+
1929
2068
  // packages/ui/react-ui-editor/src/extensions/table.ts
1930
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1931
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1932
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
2069
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2070
+ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField4 } from "@codemirror/state";
2071
+ import { Decoration as Decoration7, EditorView as EditorView11, WidgetType as WidgetType5 } from "@codemirror/view";
1933
2072
  var table = (options = {}) => {
1934
2073
  return StateField4.define({
1935
2074
  create: (state) => update(state, options),
1936
2075
  update: (_, tr) => update(tr.state, options),
1937
- provide: (field) => EditorView10.decorations.from(field)
2076
+ provide: (field) => EditorView11.decorations.from(field)
1938
2077
  });
1939
2078
  };
1940
2079
  var update = (state, options) => {
1941
- const builder = new RangeSetBuilder2();
2080
+ const builder = new RangeSetBuilder3();
1942
2081
  const cursor = state.selection.main.head;
1943
2082
  const tables = [];
1944
2083
  const getTable = () => tables[tables.length - 1];
@@ -1946,7 +2085,7 @@ var update = (state, options) => {
1946
2085
  const table2 = getTable();
1947
2086
  return table2.rows?.[table2.rows.length - 1];
1948
2087
  };
1949
- syntaxTree3(state).iterate({
2088
+ syntaxTree4(state).iterate({
1950
2089
  enter: (node) => {
1951
2090
  switch (node.name) {
1952
2091
  case "Table": {
@@ -2025,85 +2164,98 @@ var TableWidget = class extends WidgetType5 {
2025
2164
  };
2026
2165
 
2027
2166
  // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2028
- import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2029
- var styles5 = EditorView11.baseTheme({
2030
- "& .cm-task-item": {
2031
- paddingLeft: "4px",
2032
- paddingRight: "8px"
2167
+ import { syntaxTree as syntaxTree5 } from "@codemirror/language";
2168
+ import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
2169
+ import { EditorView as EditorView12, Decoration as Decoration8, WidgetType as WidgetType6, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2170
+ var styles5 = EditorView12.baseTheme({
2171
+ "& .cm-task": {
2172
+ color: getToken("extend.colors.blue.500")
2173
+ },
2174
+ "& .cm-task-checkbox": {
2175
+ marginLeft: "4px",
2176
+ marginRight: "4px"
2033
2177
  }
2034
2178
  });
2035
2179
  var CheckboxWidget = class extends WidgetType6 {
2036
- constructor(_checked, _indent, _onCheck) {
2180
+ constructor(_checked) {
2037
2181
  super();
2038
2182
  this._checked = _checked;
2039
- this._indent = _indent;
2040
- this._onCheck = _onCheck;
2041
2183
  }
2042
2184
  eq(other) {
2043
- return this._checked === other._checked && this._indent === other._indent;
2185
+ return this._checked === other._checked;
2044
2186
  }
2045
2187
  toDOM(view) {
2046
- const wrap = document.createElement("span");
2047
- wrap.className = "cm-task-item";
2048
- wrap.setAttribute("aria-hidden", "true");
2049
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
2050
- const input = wrap.appendChild(document.createElement("input"));
2188
+ const input = document.createElement("input");
2189
+ input.className = "cm-task-checkbox";
2051
2190
  input.type = "checkbox";
2052
2191
  input.checked = this._checked;
2053
- if (!this._onCheck) {
2192
+ if (view.state.readOnly) {
2054
2193
  input.setAttribute("disabled", "true");
2055
- }
2056
- if (this._onCheck) {
2057
- input.onchange = (event) => {
2058
- this._onCheck?.(event.target.checked);
2059
- return true;
2194
+ } else {
2195
+ input.onmousedown = (event) => {
2196
+ const pos = view.posAtDOM(input);
2197
+ const text = view.state.sliceDoc(pos, pos + 3);
2198
+ if (text === (this._checked ? "[x]" : "[ ]")) {
2199
+ view.dispatch({
2200
+ changes: {
2201
+ from: pos + 1,
2202
+ to: pos + 2,
2203
+ insert: this._checked ? " " : "x"
2204
+ }
2205
+ });
2206
+ event.preventDefault();
2207
+ }
2060
2208
  };
2061
2209
  }
2062
- return wrap;
2210
+ return input;
2063
2211
  }
2064
2212
  ignoreEvent() {
2065
2213
  return false;
2066
2214
  }
2067
2215
  };
2216
+ var checkedDecoration = Decoration8.replace({
2217
+ widget: new CheckboxWidget(true)
2218
+ });
2219
+ var uncheckedDecoration = Decoration8.replace({
2220
+ widget: new CheckboxWidget(false)
2221
+ });
2222
+ var buildDecorations4 = (view) => {
2223
+ const builder = new RangeSetBuilder4();
2224
+ const { state } = view;
2225
+ const cursor = state.selection.main.head;
2226
+ for (const { from, to } of view.visibleRanges) {
2227
+ syntaxTree5(state).iterate({
2228
+ enter: (node) => {
2229
+ if (node.name === "TaskMarker") {
2230
+ if (cursor < node.from || cursor > node.to) {
2231
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2232
+ builder.add(node.from - 2, node.from - 1, Decoration8.mark({
2233
+ class: "cm-task"
2234
+ }));
2235
+ builder.add(node.from, node.to, checked ? checkedDecoration : uncheckedDecoration);
2236
+ }
2237
+ }
2238
+ },
2239
+ from,
2240
+ to
2241
+ });
2242
+ }
2243
+ return builder.finish();
2244
+ };
2068
2245
  var tasklist = (options = {}) => {
2069
- const taskMatcher = new MatchDecorator2({
2070
- regexp: /^(\s*)- \[([ xX])\]\s/g,
2071
- decoration: (match, view, pos) => {
2072
- const indent = Math.floor(match[1].length / 2);
2073
- const checked = match[2] === "x" || match[2] === "X";
2074
- return Decoration8.replace({
2075
- widget: new CheckboxWidget(checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2076
- const idx = pos + match[0].indexOf("[") + 1;
2077
- view.dispatch({
2078
- changes: {
2079
- from: idx,
2080
- to: idx + 1,
2081
- insert: checked2 ? "x" : " "
2082
- },
2083
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
2084
- selection: {
2085
- anchor: idx + 3
2086
- }
2087
- });
2088
- })
2089
- });
2090
- }
2091
- });
2092
- const tasks = ViewPlugin6.fromClass(class {
2093
- constructor(view) {
2094
- this.tasks = taskMatcher.createDeco(view);
2095
- }
2096
- update(update2) {
2097
- this.tasks = taskMatcher.updateDeco(update2, this.tasks);
2098
- }
2099
- }, {
2100
- decorations: (value) => value.tasks,
2101
- provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2102
- return view.plugin(plugin)?.tasks || Decoration8.none;
2103
- })
2104
- });
2105
2246
  return [
2106
- tasks,
2247
+ ViewPlugin6.fromClass(class {
2248
+ constructor(view) {
2249
+ this.decorations = buildDecorations4(view);
2250
+ }
2251
+ update(update2) {
2252
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2253
+ this.decorations = buildDecorations4(update2.view);
2254
+ }
2255
+ }
2256
+ }, {
2257
+ decorations: (v) => v.decorations
2258
+ }),
2107
2259
  styles5
2108
2260
  ];
2109
2261
  };
@@ -2315,13 +2467,6 @@ var defaultTheme = {
2315
2467
  textDecoration: "none"
2316
2468
  },
2317
2469
  //
2318
- // widgets
2319
- //
2320
- ".cm-widgetBuffer": {
2321
- display: "none",
2322
- height: 0
2323
- },
2324
- //
2325
2470
  // table
2326
2471
  //
2327
2472
  ".cm-table *": {
@@ -2430,90 +2575,83 @@ var codeTheme = {
2430
2575
  };
2431
2576
 
2432
2577
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2578
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
2433
2579
  var EditorModes = [
2434
2580
  "default",
2435
2581
  "vim"
2436
2582
  ];
2437
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, readonly, comments: comments2, extensions = [], editorMode, theme, slots = defaultSlots }, forwardedRef) => {
2583
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autofocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2438
2584
  const tabsterDOMAttribute = useFocusableGroup({
2439
2585
  tabBehavior: "limited"
2440
2586
  });
2441
- const { themeMode } = useThemeContext();
2587
+ const { themeMode } = useThemeContext2();
2442
2588
  const rootRef = useRef(null);
2443
2589
  const [view, setView] = useState(null);
2444
2590
  useImperativeHandle(forwardedRef, () => view, [
2445
2591
  view
2446
2592
  ]);
2447
- useEffect(() => {
2448
- if (view && focus) {
2449
- view.focus();
2593
+ useEffect3(() => {
2594
+ if (autofocus) {
2595
+ view?.focus();
2450
2596
  }
2451
2597
  }, [
2452
2598
  view,
2453
- focus
2599
+ autofocus
2454
2600
  ]);
2455
- const { awareness: awareness2, peer } = model;
2456
- useEffect(() => {
2457
- if (awareness2 && peer) {
2458
- awareness2.setLocalStateField("user", {
2459
- name: peer.name ?? generateName(peer.id),
2460
- color: getColorForValue({
2461
- value: peer.id,
2462
- type: "color"
2463
- }),
2464
- colorLight: getColorForValue({
2465
- value: peer.id,
2466
- themeMode,
2467
- type: "highlight"
2468
- })
2469
- });
2470
- }
2471
- }, [
2472
- awareness2,
2473
- peer,
2474
- themeMode
2475
- ]);
2476
- useEffect(() => {
2477
- if (view && comments2 !== void 0) {
2478
- view.dispatch({
2479
- effects: setCommentRange.of({
2480
- model,
2481
- comments: comments2
2482
- })
2483
- });
2484
- }
2485
- }, [
2486
- view,
2487
- comments2
2488
- ]);
2489
- useEffect(() => {
2601
+ useAwareness(model);
2602
+ useEffect3(() => {
2490
2603
  if (!model || !rootRef.current) {
2491
2604
  return;
2492
2605
  }
2493
2606
  const state = EditorState2.create({
2494
2607
  doc: model.text(),
2495
- // TODO(burdon): Composer should store and set selection when switching documents.
2496
2608
  selection,
2497
2609
  extensions: [
2498
- readonly && EditorState2.readOnly.of(readonly),
2499
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2500
- editorMode === "vim" && vim(),
2610
+ // TODO(burdon): Doesn't catch errors in keymap functions.
2611
+ EditorView13.exceptionSink.of((err) => {
2612
+ log2.catch(err, void 0, {
2613
+ F: __dxlog_file5,
2614
+ L: 126,
2615
+ S: void 0,
2616
+ C: (f, a) => f(...a)
2617
+ });
2618
+ }),
2501
2619
  // Theme.
2502
- // TODO(burdon): Make theme configurable.
2503
- EditorView12.baseTheme(defaultTheme),
2504
- EditorView12.theme(theme ?? {}),
2505
- EditorView12.darkTheme.of(themeMode === "dark"),
2620
+ // TODO(burdon): Make configurable.
2621
+ EditorView13.baseTheme(defaultTheme),
2622
+ EditorView13.theme(theme ?? {}),
2623
+ EditorView13.darkTheme.of(themeMode === "dark"),
2624
+ EditorView13.editorAttributes.of({
2625
+ class: slots.editor?.className ?? ""
2626
+ }),
2627
+ EditorView13.contentAttributes.of({
2628
+ class: slots.content?.className ?? ""
2629
+ }),
2630
+ // State.
2631
+ EditorState2.readOnly.of(!!readonly),
2506
2632
  // Storage and replication.
2633
+ // NOTE: This must come before user extensions.
2507
2634
  model.extension,
2635
+ // TODO(burdon): Factor out (requires special handling for Escape/focus).
2636
+ editorMode === "vim" && vim(),
2508
2637
  // Custom.
2509
2638
  ...extensions
2510
2639
  ].filter(Boolean)
2511
2640
  });
2512
- view?.destroy();
2513
- const newView = new EditorView12({
2641
+ const newView = new EditorView13({
2642
+ state,
2514
2643
  parent: rootRef.current,
2515
- state
2644
+ scrollTo,
2645
+ // NOTE: Uncomment to debug/monitor all transactions.
2646
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
2647
+ dispatchTransactions: (trs, view2) => {
2648
+ if (debug) {
2649
+ logChanges(trs);
2650
+ }
2651
+ view2.update(trs);
2652
+ }
2516
2653
  });
2654
+ view?.destroy();
2517
2655
  setView(newView);
2518
2656
  return () => {
2519
2657
  newView?.destroy();
@@ -2529,8 +2667,14 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, read
2529
2667
  const handleKeyUp = useCallback((event) => {
2530
2668
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2531
2669
  switch (key) {
2670
+ case "Enter": {
2671
+ view?.focus();
2672
+ break;
2673
+ }
2532
2674
  case "Escape": {
2533
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
2675
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
2676
+ rootRef.current?.focus();
2677
+ }
2534
2678
  break;
2535
2679
  }
2536
2680
  }
@@ -2541,24 +2685,24 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, read
2541
2685
  return /* @__PURE__ */ React.createElement("div", {
2542
2686
  key: model.id,
2543
2687
  role: "none",
2544
- ref: rootRef,
2545
2688
  tabIndex: 0,
2546
2689
  onKeyUp: handleKeyUp,
2547
2690
  ...slots.root,
2548
- ...editorMode !== "vim" && tabsterDOMAttribute
2691
+ ...editorMode !== "vim" && tabsterDOMAttribute,
2692
+ ref: rootRef
2549
2693
  });
2550
2694
  });
2551
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
2552
- const { themeMode } = useThemeContext();
2695
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, multiline, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
2696
+ const { themeMode } = useThemeContext2();
2553
2697
  const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2554
2698
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2555
2699
  ref: forwardedRef,
2556
2700
  readonly,
2557
2701
  extensions: [
2558
2702
  basicBundle({
2559
- readonly,
2560
2703
  themeMode,
2561
- placeholder: placeholder3
2704
+ placeholder: placeholder3,
2705
+ multiline
2562
2706
  }),
2563
2707
  ...extensions
2564
2708
  ],
@@ -2567,16 +2711,16 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholde
2567
2711
  ...props
2568
2712
  });
2569
2713
  });
2570
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
2571
- const { themeMode } = useThemeContext();
2714
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
2715
+ const { themeMode } = useThemeContext2();
2572
2716
  const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2573
2717
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2574
2718
  ref: forwardedRef,
2575
2719
  readonly,
2576
2720
  extensions: [
2577
2721
  markdownBundle({
2578
- readonly,
2579
2722
  themeMode,
2723
+ readonly,
2580
2724
  placeholder: placeholder3
2581
2725
  }),
2582
2726
  ...extensions
@@ -2588,7 +2732,11 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeh
2588
2732
  });
2589
2733
  var defaultSlots = {
2590
2734
  root: {
2591
- className: mx3("p-2 overflow-y-auto", inputSurface)
2735
+ // TODO(burdon): Add focusRing by default/as property?
2736
+ className: mx3("flex flex-col grow overflow-y-auto", inputSurface)
2737
+ },
2738
+ editor: {
2739
+ className: "h-full p-2"
2592
2740
  }
2593
2741
  };
2594
2742
  var defaultTextSlots = {
@@ -2606,9 +2754,9 @@ import { isNotNullOrUndefined } from "@dxos/util";
2606
2754
  // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2607
2755
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2608
2756
  import { Context as Context2 } from "@dxos/context";
2609
- import { invariant as invariant3 } from "@dxos/invariant";
2757
+ import { invariant as invariant4 } from "@dxos/invariant";
2610
2758
  import { log as log3 } from "@dxos/log";
2611
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2759
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2612
2760
  var DEBOUNCE_INTERVAL = 100;
2613
2761
  var SpaceAwarenessProvider = class {
2614
2762
  constructor(params) {
@@ -2649,7 +2797,7 @@ var SpaceAwarenessProvider = class {
2649
2797
  log3.debug("failed to query awareness", {
2650
2798
  err
2651
2799
  }, {
2652
- F: __dxlog_file4,
2800
+ F: __dxlog_file6,
2653
2801
  L: 85,
2654
2802
  S: this,
2655
2803
  C: (f, a) => f(...a)
@@ -2662,8 +2810,8 @@ var SpaceAwarenessProvider = class {
2662
2810
  this._postTask = void 0;
2663
2811
  }
2664
2812
  update(position) {
2665
- invariant3(this._postTask, void 0, {
2666
- F: __dxlog_file4,
2813
+ invariant4(this._postTask, void 0, {
2814
+ F: __dxlog_file6,
2667
2815
  L: 96,
2668
2816
  S: this,
2669
2817
  A: [
@@ -2682,8 +2830,8 @@ var SpaceAwarenessProvider = class {
2682
2830
  return Array.from(this._remoteStates.values());
2683
2831
  }
2684
2832
  _handleQueryMessage() {
2685
- invariant3(this._postTask, void 0, {
2686
- F: __dxlog_file4,
2833
+ invariant4(this._postTask, void 0, {
2834
+ F: __dxlog_file6,
2687
2835
  L: 111,
2688
2836
  S: this,
2689
2837
  A: [
@@ -2694,8 +2842,8 @@ var SpaceAwarenessProvider = class {
2694
2842
  this._postTask.schedule();
2695
2843
  }
2696
2844
  _handlePostMessage(message) {
2697
- invariant3(message.kind === "post", void 0, {
2698
- F: __dxlog_file4,
2845
+ invariant4(message.kind === "post", void 0, {
2846
+ F: __dxlog_file6,
2699
2847
  L: 116,
2700
2848
  S: this,
2701
2849
  A: [
@@ -2708,23 +2856,63 @@ var SpaceAwarenessProvider = class {
2708
2856
  }
2709
2857
  };
2710
2858
 
2711
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2859
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
2712
2860
  import { StateField as StateField5 } from "@codemirror/state";
2713
- import { useEffect as useEffect2, useState as useState2 } from "react";
2714
- import { isAutomergeObject } from "@dxos/react-client/echo";
2861
+ var modelState = StateField5.define({
2862
+ create: () => void 0,
2863
+ update: (model) => model
2864
+ });
2865
+
2866
+ // packages/ui/react-ui-editor/src/hooks/automerge.ts
2867
+ var createAutomergeModel = ({ space, identity, text }) => {
2868
+ const obj = text;
2869
+ const doc = getRawDoc(obj, [
2870
+ obj.field
2871
+ ]);
2872
+ const awarenessProvider = space && new SpaceAwarenessProvider({
2873
+ space,
2874
+ channel: `automerge.awareness.${obj.id}`,
2875
+ info: {
2876
+ displayName: identity?.profile?.displayName ?? "",
2877
+ color: cursorColor.color,
2878
+ lightColor: cursorColor.light
2879
+ },
2880
+ peerId: identity?.identityKey.toHex() ?? "Anonymous"
2881
+ });
2882
+ const model = {
2883
+ id: obj.id,
2884
+ content: doc,
2885
+ text: () => get4(doc.handle.docSync(), doc.path),
2886
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2887
+ extension: [
2888
+ modelState.init(() => model),
2889
+ automerge3({
2890
+ handle: doc.handle,
2891
+ path: doc.path
2892
+ }),
2893
+ awarenessProvider && AwarenessProvider.of(awarenessProvider),
2894
+ awareness()
2895
+ ].filter(isNotNullOrUndefined),
2896
+ peer: identity ? {
2897
+ id: identity.identityKey.toHex(),
2898
+ name: identity.profile?.displayName
2899
+ } : void 0
2900
+ };
2901
+ return model;
2902
+ };
2715
2903
 
2716
2904
  // packages/ui/react-ui-editor/src/hooks/yjs.ts
2717
2905
  import * as decoding from "lib0/decoding";
2718
2906
  import * as encoding from "lib0/encoding";
2719
2907
  import { Observable } from "lib0/observable";
2720
2908
  import * as YP from "y-protocols/awareness";
2721
- import { invariant as invariant4 } from "@dxos/invariant";
2909
+ import { invariant as invariant5 } from "@dxos/invariant";
2722
2910
  import { log as log4 } from "@dxos/log";
2723
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2911
+ var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2724
2912
  var createYjsModel = ({ identity, space, text }) => {
2725
- invariant4(text?.doc && text?.content, void 0, {
2726
- F: __dxlog_file5,
2727
- L: 21,
2913
+ invariant5(text?.doc && text?.content, void 0, {
2914
+ F: __dxlog_file7,
2915
+ L: 22,
2728
2916
  S: void 0,
2729
2917
  A: [
2730
2918
  "text?.doc && text?.content",
@@ -2788,8 +2976,8 @@ var YAwarenessProvider = class extends Observable {
2788
2976
  removed,
2789
2977
  origin
2790
2978
  }, {
2791
- F: __dxlog_file5,
2792
- L: 100,
2979
+ F: __dxlog_file7,
2980
+ L: 101,
2793
2981
  S: this,
2794
2982
  C: (f, a) => f(...a)
2795
2983
  });
@@ -2801,8 +2989,8 @@ var YAwarenessProvider = class extends Observable {
2801
2989
  }
2802
2990
  _handleSpaceMessage({ payload }) {
2803
2991
  log4("space message", payload, {
2804
- F: __dxlog_file5,
2805
- L: 109,
2992
+ F: __dxlog_file7,
2993
+ L: 110,
2806
2994
  S: this,
2807
2995
  C: (f, a) => f(...a)
2808
2996
  });
@@ -2846,14 +3034,12 @@ var YAwarenessProvider = class extends Observable {
2846
3034
  };
2847
3035
 
2848
3036
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2849
- var modelState = StateField5.define({
2850
- create: () => void 0,
2851
- update: (model) => model
2852
- });
3037
+ import { useEffect as useEffect4, useState as useState2 } from "react";
3038
+ import { isAutomergeObject } from "@dxos/react-client/echo";
2853
3039
  var useTextModel = (props) => {
2854
3040
  const { identity, space, text } = props;
2855
3041
  const [model, setModel] = useState2();
2856
- useEffect2(() => setModel(createModel(props)), [
3042
+ useEffect4(() => setModel(createModel(props)), [
2857
3043
  identity,
2858
3044
  space,
2859
3045
  text
@@ -2870,44 +3056,6 @@ var createModel = (props) => {
2870
3056
  return void 0;
2871
3057
  }
2872
3058
  };
2873
-
2874
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2875
- var createAutomergeModel = ({ space, identity, text }) => {
2876
- const obj = text;
2877
- const doc = getRawDoc(obj, [
2878
- obj.field
2879
- ]);
2880
- const awarenessProvider = space && new SpaceAwarenessProvider({
2881
- space,
2882
- channel: `automerge.awareness.${obj.id}`,
2883
- info: {
2884
- displayName: identity?.profile?.displayName ?? "",
2885
- color: cursorColor.color,
2886
- lightColor: cursorColor.light
2887
- },
2888
- peerId: identity?.identityKey.toHex() ?? "Anonymous"
2889
- });
2890
- const model = {
2891
- id: obj.id,
2892
- content: doc,
2893
- text: () => get4(doc.handle.docSync(), doc.path),
2894
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2895
- extension: [
2896
- modelState.init(() => model),
2897
- automerge3({
2898
- handle: doc.handle,
2899
- path: doc.path
2900
- }),
2901
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
2902
- awareness()
2903
- ].filter(isNotNullOrUndefined),
2904
- peer: identity ? {
2905
- id: identity.identityKey.toHex(),
2906
- name: identity.profile?.displayName
2907
- } : void 0
2908
- };
2909
- return model;
2910
- };
2911
3059
  export {
2912
3060
  AwarenessProvider,
2913
3061
  BaseTextEditor,
@@ -2933,7 +3081,6 @@ export {
2933
3081
  comments,
2934
3082
  createAutomergeModel,
2935
3083
  createYjsModel,
2936
- cursorConverter,
2937
3084
  defaultMarkdownSlots,
2938
3085
  defaultOptions,
2939
3086
  defaultSlots,
@@ -2944,6 +3091,7 @@ export {
2944
3091
  image,
2945
3092
  link,
2946
3093
  listener,
3094
+ logChanges,
2947
3095
  markdownBundle,
2948
3096
  markdownHighlightStyle,
2949
3097
  markdownTags,
@@ -2951,7 +3099,8 @@ export {
2951
3099
  markdownTheme,
2952
3100
  mention,
2953
3101
  modelState,
2954
- setCommentRange,
3102
+ outliner,
3103
+ setComments,
2955
3104
  setFocus,
2956
3105
  setSelection,
2957
3106
  table,
@@ -2959,6 +3108,8 @@ export {
2959
3108
  tasklist,
2960
3109
  textTheme,
2961
3110
  typewriter,
3111
+ useAwareness,
3112
+ useComments,
2962
3113
  useTextModel,
2963
3114
  yjs
2964
3115
  };