@dxos/react-ui-editor 0.3.11-main.d56f337 → 0.3.11-main.da09c3c

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 (78) hide show
  1. package/dist/lib/browser/index.mjs +574 -493
  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 +13 -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 +26 -17
  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 +1 -5
  38. package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
  39. package/dist/types/src/hooks/automerge.d.ts +2 -1
  40. package/dist/types/src/hooks/automerge.d.ts.map +1 -1
  41. package/dist/types/src/hooks/defs.d.ts +28 -0
  42. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  43. package/dist/types/src/hooks/index.d.ts +2 -1
  44. package/dist/types/src/hooks/index.d.ts.map +1 -1
  45. package/dist/types/src/hooks/useTextModel.d.ts +2 -27
  46. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  47. package/dist/types/src/hooks/yjs.d.ts +2 -1
  48. package/dist/types/src/hooks/yjs.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 +30 -30
  52. package/src/components/TextEditor/MarkdownEditor.stories.tsx +64 -69
  53. package/src/components/TextEditor/TextEditor.tsx +85 -68
  54. package/src/components/TextEditor/comments.stories.tsx +103 -75
  55. package/src/extensions/autocomplete.ts +6 -3
  56. package/src/extensions/automerge/automerge.ts +23 -48
  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 +195 -157
  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 +49 -33
  73. package/src/hooks/automerge.ts +2 -1
  74. package/src/hooks/defs.ts +47 -0
  75. package/src/hooks/index.ts +3 -1
  76. package/src/hooks/useTextModel.ts +2 -45
  77. package/src/hooks/yjs.ts +2 -1
  78. 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,8 +53,8 @@ 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 } from "@codemirror/view";
56
58
  import { next as A } from "@dxos/automerge/automerge";
57
59
 
58
60
  // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
@@ -94,12 +96,12 @@ var cursorConverter = (handle, path) => ({
94
96
  // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
95
97
  import { Annotation, StateEffect } from "@codemirror/state";
96
98
  var effectType = StateEffect.define({});
99
+ var reconcileAnnotationType = Annotation.define();
100
+ var getPath = (state, field) => state.field(field).path;
101
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
97
102
  var updateHeads = (newHeads) => effectType.of({
98
103
  newHeads
99
104
  });
100
- var getLastHeads = (state, field) => state.field(field).lastHeads;
101
- var getPath = (state, field) => state.field(field).path;
102
- var reconcileAnnotationType = Annotation.define();
103
105
  var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
104
106
 
105
107
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
@@ -237,40 +239,42 @@ var charPath = (textPath, candidatePath) => {
237
239
 
238
240
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
239
241
  var PatchSemaphore = class {
240
- constructor(_field) {
241
- this._field = _field;
242
+ // prettier-ignore
243
+ constructor(_handle, _state) {
244
+ this._handle = _handle;
245
+ this._state = _state;
242
246
  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);
247
+ }
248
+ // NOTE: Cannot destruct view.state.
249
+ reconcile(view) {
250
+ if (this._inReconcile) {
251
+ return;
252
+ }
253
+ this._inReconcile = true;
254
+ const path = getPath(view.state, this._state);
255
+ const oldHeads = getLastHeads(view.state, this._state);
256
+ let selection = view.state.selection;
257
+ const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
258
+ const toInvert = transactions.slice().reverse();
259
+ for (const tx of toInvert) {
260
+ const inverted = tx.changes.invert(tx.startState.doc);
261
+ selection = selection.map(inverted);
268
262
  view.dispatch({
269
- effects: updateHeads(newHeads),
270
- annotations: reconcileAnnotationType.of({})
263
+ changes: inverted,
264
+ annotations: reconcileAnnotationType.of(true)
271
265
  });
272
- this._inReconcile = false;
273
- };
266
+ }
267
+ let newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
268
+ if (newHeads === null || newHeads === void 0) {
269
+ newHeads = automerge2.getHeads(this._handle.docSync());
270
+ }
271
+ const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(this._handle.docSync(), oldHeads, newHeads);
272
+ updateCodeMirror(view, selection, path, diff);
273
+ view.dispatch({
274
+ effects: updateHeads(newHeads),
275
+ annotations: reconcileAnnotationType.of({})
276
+ });
277
+ this._inReconcile = false;
274
278
  }
275
279
  };
276
280
 
@@ -280,14 +284,15 @@ var defaultCursorConverter = {
280
284
  toCursor: (position) => position.toString(),
281
285
  fromCursor: (cursor) => parseInt(cursor)
282
286
  };
283
- var Cursor = class {
287
+ var Cursor = class _Cursor {
284
288
  static {
285
289
  this.converter = Facet.define({
286
290
  combine: (providers) => providers[0] ?? defaultCursorConverter
287
291
  });
288
292
  }
289
293
  static {
290
- this.getCursorFromRange = (cursorConverter3, range) => {
294
+ this.getCursorFromRange = (state, range) => {
295
+ const cursorConverter3 = state.facet(_Cursor.converter);
291
296
  const from = cursorConverter3.toCursor(range.from);
292
297
  const to = cursorConverter3.toCursor(range.to, -1);
293
298
  return [
@@ -297,7 +302,8 @@ var Cursor = class {
297
302
  };
298
303
  }
299
304
  static {
300
- this.getRangeFromCursor = (cursorConverter3, cursor) => {
305
+ this.getRangeFromCursor = (state, cursor) => {
306
+ const cursorConverter3 = state.facet(_Cursor.converter);
301
307
  const parts = cursor.split(":");
302
308
  const from = cursorConverter3.fromCursor(parts[0]);
303
309
  const to = cursorConverter3.fromCursor(parts[1]);
@@ -310,21 +316,18 @@ var Cursor = class {
310
316
  };
311
317
 
312
318
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
313
- var semaphoreFacet = Facet2.define({
314
- combine: (values) => values.at(-1)
315
- });
316
319
  var automerge3 = ({ handle, path }) => {
317
- const stateField = StateField.define({
320
+ const state = StateField.define({
318
321
  create: () => ({
322
+ path: path.slice(),
319
323
  lastHeads: A.getHeads(handle.docSync()),
320
- unreconciledTransactions: [],
321
- path: path.slice()
324
+ unreconciledTransactions: []
322
325
  }),
323
326
  update: (value, tr) => {
324
327
  const result = {
328
+ path: path.slice(),
325
329
  lastHeads: value.lastHeads,
326
- unreconciledTransactions: value.unreconciledTransactions.slice(),
327
- path: path.slice()
330
+ unreconciledTransactions: value.unreconciledTransactions.slice()
328
331
  };
329
332
  let clearUnreconciled = false;
330
333
  for (const effect of tr.effects) {
@@ -343,41 +346,27 @@ var automerge3 = ({ handle, path }) => {
343
346
  return result;
344
347
  }
345
348
  });
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);
359
- });
349
+ const semaphore = new PatchSemaphore(handle, state);
350
+ return [
351
+ Cursor.converter.of(cursorConverter(handle, path)),
352
+ EditorView.updateListener.of(({ view, changes }) => {
353
+ if (!changes.empty) {
354
+ semaphore.reconcile(view);
360
355
  }
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
- };
356
+ }),
357
+ state
358
+ ];
374
359
  };
375
360
 
376
361
  // 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";
362
+ import { Annotation as Annotation2, Facet as Facet2, RangeSet } from "@codemirror/state";
363
+ import { Decoration, EditorView as EditorView2, ViewPlugin, WidgetType } from "@codemirror/view";
364
+ import { useEffect } from "react";
379
365
  import { Event } from "@dxos/async";
380
366
  import { Context } from "@dxos/context";
367
+ import { generateName } from "@dxos/display-name";
368
+ import { useThemeContext } from "@dxos/react-ui";
369
+ import { getColorForValue } from "@dxos/react-ui-theme";
381
370
  var dummyProvider = {
382
371
  remoteStateChange: new Event(),
383
372
  open: () => {
@@ -388,19 +377,42 @@ var dummyProvider = {
388
377
  },
389
378
  getRemoteStates: () => []
390
379
  };
391
- var AwarenessProvider = Facet3.define({
380
+ var AwarenessProvider = Facet2.define({
392
381
  combine: (providers) => providers[0] ?? dummyProvider
393
382
  });
394
383
  var RemoteSelectionChangedAnnotation = Annotation2.define();
395
384
  var awareness = (provider = dummyProvider) => {
396
385
  return [
397
386
  AwarenessProvider.of(provider),
398
- ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
387
+ ViewPlugin.fromClass(RemoteSelectionsDecorator, {
399
388
  decorations: (value) => value.decorations
400
389
  }),
401
390
  styles
402
391
  ];
403
392
  };
393
+ var useAwareness = ({ awareness: awareness2, peer }) => {
394
+ const { themeMode } = useThemeContext();
395
+ useEffect(() => {
396
+ if (awareness2 && peer) {
397
+ awareness2.setLocalStateField("user", {
398
+ name: peer.name ?? generateName(peer.id),
399
+ color: getColorForValue({
400
+ value: peer.id,
401
+ type: "color"
402
+ }),
403
+ colorLight: getColorForValue({
404
+ value: peer.id,
405
+ themeMode,
406
+ type: "highlight"
407
+ })
408
+ });
409
+ }
410
+ }, [
411
+ awareness2,
412
+ peer,
413
+ themeMode
414
+ ]);
415
+ };
404
416
  var RemoteSelectionsDecorator = class {
405
417
  constructor(view) {
406
418
  this.decorations = RangeSet.of([]);
@@ -549,7 +561,7 @@ var RemoteCaretWidget = class extends WidgetType {
549
561
  return true;
550
562
  }
551
563
  };
552
- var styles = EditorView.baseTheme({
564
+ var styles = EditorView2.baseTheme({
553
565
  ".cm-collab-selection": {},
554
566
  ".cm-collab-selectionLine": {
555
567
  padding: 0,
@@ -595,7 +607,7 @@ var styles = EditorView.baseTheme({
595
607
  zIndex: 101,
596
608
  transition: "opacity .3s ease-in-out",
597
609
  backgroundColor: "inherit",
598
- // these should be separate
610
+ // These should be separate.
599
611
  opacity: 0,
600
612
  transitionDelay: "0s",
601
613
  whiteSpace: "nowrap"
@@ -608,23 +620,25 @@ var styles = EditorView.baseTheme({
608
620
 
609
621
  // packages/ui/react-ui-editor/src/extensions/basic.ts
610
622
  import { closeBrackets } from "@codemirror/autocomplete";
623
+ import { history } from "@codemirror/commands";
611
624
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
612
625
  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.
626
+ import { drawSelection, EditorView as EditorView3, highlightActiveLine, placeholder } from "@codemirror/view";
627
+ var basicBundle = ({ themeMode, placeholder: _placeholder, multiline = true }) => [
628
+ multiline ? EditorView3.lineWrapping : [],
617
629
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
618
630
  bracketMatching(),
619
631
  closeBrackets(),
620
632
  drawSelection(),
621
- _placeholder && placeholder(_placeholder),
622
- // TODO(burdon): Is this required?
633
+ highlightActiveLine(),
634
+ history(),
635
+ _placeholder ? placeholder(_placeholder) : [],
636
+ // https://github.com/codemirror/theme-one-dark
623
637
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
624
- ].filter(Boolean);
638
+ ];
625
639
 
626
640
  // packages/ui/react-ui-editor/src/extensions/blast.ts
627
- import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
641
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
628
642
  import defaultsDeep from "lodash.defaultsdeep";
629
643
  import { invariant } from "@dxos/invariant";
630
644
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
@@ -672,7 +686,7 @@ var blast = (options = defaultOptions) => {
672
686
  };
673
687
  return [
674
688
  // Cursor moved.
675
- EditorView3.updateListener.of((update2) => {
689
+ EditorView4.updateListener.of((update2) => {
676
690
  if (blaster?.node !== update2.view.scrollDOM) {
677
691
  if (blaster) {
678
692
  blaster.destroy();
@@ -934,7 +948,7 @@ var random = (min, max) => {
934
948
  };
935
949
 
936
950
  // packages/ui/react-ui-editor/src/extensions/code.ts
937
- import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
951
+ import { ViewPlugin as ViewPlugin2, EditorView as EditorView5, Decoration as Decoration2 } from "@codemirror/view";
938
952
  import { mx as mx2 } from "@dxos/react-ui-theme";
939
953
 
940
954
  // packages/ui/react-ui-editor/src/styles/cursors.ts
@@ -1025,7 +1039,7 @@ var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue)
1025
1039
 
1026
1040
  // packages/ui/react-ui-editor/src/extensions/code.ts
1027
1041
  var CODE_REGEX = /```[\s\S]*?```/gs;
1028
- var styles2 = EditorView4.baseTheme({
1042
+ var styles2 = EditorView5.baseTheme({
1029
1043
  "& .cm-codeblock": {
1030
1044
  fontFamily: getToken("fontFamily.mono", []).join(",")
1031
1045
  },
@@ -1059,29 +1073,27 @@ var getLineRange = (lines, from, to) => {
1059
1073
  ];
1060
1074
  };
1061
1075
  var code2 = () => {
1062
- const getDecorations = (view) => {
1076
+ const buildDecorations5 = (view) => {
1063
1077
  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
- }
1078
+ const text = view.state.doc.sliceString(0);
1079
+ const matches = text.matchAll(CODE_REGEX);
1080
+ const blocks = view.viewportLineBlocks;
1081
+ for (const match of matches) {
1082
+ const range = getLineRange(blocks, match.index, match.index + match[0].length);
1083
+ for (let i = range[0]; i <= range[1]; i++) {
1084
+ const block = blocks[i];
1085
+ decorations.push(Decoration2.line({
1086
+ class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1087
+ }).range(block.from));
1076
1088
  }
1077
1089
  }
1078
1090
  return Decoration2.set(decorations);
1079
1091
  };
1080
1092
  return [
1081
- ViewPlugin3.fromClass(class {
1093
+ ViewPlugin2.fromClass(class {
1082
1094
  constructor(view) {
1083
1095
  this.hasFocus = false;
1084
- this.decorations = getDecorations(view);
1096
+ this.decorations = buildDecorations5(view);
1085
1097
  }
1086
1098
  update(update2) {
1087
1099
  if (
@@ -1089,7 +1101,7 @@ var code2 = () => {
1089
1101
  this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1090
1102
  ) {
1091
1103
  this.hasFocus = update2.view.hasFocus;
1092
- this.decorations = getDecorations(update2.view);
1104
+ this.decorations = buildDecorations5(update2.view);
1093
1105
  }
1094
1106
  }
1095
1107
  }, {
@@ -1101,11 +1113,11 @@ var code2 = () => {
1101
1113
 
1102
1114
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1103
1115
  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";
1116
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView6 } from "@codemirror/view";
1105
1117
  import sortBy from "lodash.sortby";
1118
+ import { useEffect as useEffect2 } from "react";
1106
1119
  import { debounce } from "@dxos/async";
1107
1120
  import { invariant as invariant2 } from "@dxos/invariant";
1108
- import { log as log2 } from "@dxos/log";
1109
1121
  import { nonNullable } from "@dxos/util";
1110
1122
 
1111
1123
  // packages/ui/react-ui-editor/src/util.ts
@@ -1114,19 +1126,38 @@ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src
1114
1126
  var callbackWrapper = (fn) => (...args) => {
1115
1127
  try {
1116
1128
  return fn(...args);
1117
- } catch (error) {
1118
- log.catch(error, void 0, {
1129
+ } catch (err) {
1130
+ log.catch(err, void 0, {
1119
1131
  F: __dxlog_file2,
1120
- L: 16,
1132
+ L: 18,
1121
1133
  S: void 0,
1122
1134
  C: (f, a) => f(...a)
1123
1135
  });
1124
1136
  }
1125
1137
  };
1138
+ var logChanges = (trs) => {
1139
+ const changes = trs.flatMap((tr) => {
1140
+ if (tr.changes.empty) {
1141
+ return void 0;
1142
+ }
1143
+ const changes2 = [];
1144
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => changes2.push(JSON.stringify({
1145
+ fromA,
1146
+ toA,
1147
+ fromB,
1148
+ toB,
1149
+ inserted: inserted.toString()
1150
+ })));
1151
+ return changes2;
1152
+ }).filter(Boolean);
1153
+ if (changes.length) {
1154
+ console.log("changes", changes);
1155
+ }
1156
+ };
1126
1157
 
1127
1158
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1128
1159
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1129
- var styles3 = EditorView5.baseTheme({
1160
+ var styles3 = EditorView6.baseTheme({
1130
1161
  "& .cm-comment": {
1131
1162
  backgroundColor: getToken("extend.colors.yellow.50")
1132
1163
  },
@@ -1142,49 +1173,59 @@ var marks = {
1142
1173
  class: "cm-comment-active"
1143
1174
  })
1144
1175
  };
1145
- var setFocus = (view, thread) => {
1146
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1147
- if (!range) {
1176
+ var setFocus = (view, id, center = true) => {
1177
+ const comment = view.state.field(commentsState).comments.find((range2) => range2.comment.id === id);
1178
+ if (!comment?.comment.cursor) {
1148
1179
  return;
1149
1180
  }
1150
- view.dispatch({
1151
- effects: setSelection.of({
1152
- active: thread
1153
- }),
1154
- selection: {
1155
- anchor: range.from
1156
- },
1157
- scrollIntoView: true
1158
- });
1181
+ const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);
1182
+ if (range) {
1183
+ view.dispatch({
1184
+ selection: {
1185
+ anchor: range.from
1186
+ },
1187
+ effects: [
1188
+ //
1189
+ EditorView6.scrollIntoView(range.from, center ? {
1190
+ y: "center"
1191
+ } : void 0),
1192
+ setSelection.of({
1193
+ active: id
1194
+ })
1195
+ ]
1196
+ });
1197
+ }
1159
1198
  };
1160
- var setCommentRange = StateEffect2.define();
1199
+ var setComments = StateEffect2.define();
1161
1200
  var setSelection = StateEffect2.define();
1162
1201
  var setCommentState = StateEffect2.define();
1163
- var commentsStateField = StateField2.define({
1202
+ var commentsState = StateField2.define({
1164
1203
  create: () => ({
1165
- ranges: []
1204
+ comments: [],
1205
+ selection: {}
1166
1206
  }),
1167
1207
  update: (value, tr) => {
1168
- const cursorConverter3 = tr.state.facet(Cursor.converter);
1169
1208
  for (const effect of tr.effects) {
1170
1209
  if (effect.is(setSelection)) {
1171
1210
  return {
1172
1211
  ...value,
1173
- ...effect.value
1212
+ selection: effect.value
1174
1213
  };
1175
1214
  }
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);
1215
+ if (effect.is(setComments)) {
1216
+ const comments2 = effect.value.map((comment) => {
1217
+ if (!comment.cursor) {
1218
+ return void 0;
1219
+ }
1220
+ const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);
1180
1221
  return range && {
1181
- ...comment,
1182
- ...range
1222
+ comment,
1223
+ range
1183
1224
  };
1184
1225
  }).filter(nonNullable);
1185
1226
  return {
1186
1227
  ...value,
1187
- ranges
1228
+ comments: comments2
1188
1229
  };
1189
1230
  }
1190
1231
  if (effect.is(setCommentState)) {
@@ -1194,72 +1235,70 @@ var commentsStateField = StateField2.define({
1194
1235
  return value;
1195
1236
  }
1196
1237
  });
1197
- var highlightDecorations = EditorView5.decorations.compute([
1198
- commentsStateField
1238
+ var highlightDecorations = EditorView6.decorations.compute([
1239
+ commentsState
1199
1240
  ], (state) => {
1200
- const { active, ranges } = state.field(commentsStateField);
1201
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1202
- if (selection.from === selection.to) {
1241
+ const { selection: { active }, comments: comments2 } = state.field(commentsState);
1242
+ const decorations = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
1243
+ const range = comment.range;
1244
+ if (!range || range.from === range.to) {
1245
+ console.warn("Invalid range:", range);
1203
1246
  return void 0;
1204
1247
  }
1205
- const range = {
1206
- from: selection.from,
1207
- to: selection.to
1208
- };
1209
- if (selection.id === active) {
1248
+ if (comment.comment.id === active) {
1210
1249
  return marks.highlightActive.range(range.from, range.to);
1211
1250
  } else {
1212
1251
  return marks.highlight.range(range.from, range.to);
1213
1252
  }
1214
- }).filter(nonNullable) ?? [];
1253
+ }).filter(nonNullable);
1215
1254
  return Decoration3.set(decorations);
1216
1255
  });
1217
- var trackPastedComments = (onMove) => {
1256
+ var trackPastedComments = (onUpdate) => {
1218
1257
  let tracked = null;
1219
- const registerCopy = (event, view) => {
1220
- const comments2 = view.state.field(commentsStateField);
1258
+ const handleTrack = (event, view) => {
1259
+ const comments2 = view.state.field(commentsState);
1221
1260
  const { main } = view.state.selection;
1222
- const inSel = comments2.ranges.filter((range) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1223
- if (!inSel.length) {
1261
+ const selectedRanges = comments2.comments.filter(({ range }) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1262
+ if (!selectedRanges.length) {
1224
1263
  tracked = null;
1225
1264
  } else {
1226
1265
  tracked = {
1227
1266
  text: view.state.doc.slice(main.from, main.to),
1228
- comments: inSel.map((range) => ({
1267
+ comments: selectedRanges.map(({ comment, range }) => ({
1268
+ id: comment.id,
1229
1269
  from: range.from - main.from,
1230
- to: range.to - main.from,
1231
- id: range.id
1270
+ to: range.to - main.from
1232
1271
  }))
1233
1272
  };
1234
1273
  }
1235
1274
  };
1236
1275
  return [
1237
- EditorView5.domEventHandlers({
1238
- cut: registerCopy,
1239
- copy: registerCopy
1276
+ EditorView6.domEventHandlers({
1277
+ cut: handleTrack,
1278
+ copy: handleTrack
1240
1279
  }),
1241
- EditorView5.updateListener.of((update2) => {
1280
+ // Handle paste.
1281
+ EditorView6.updateListener.of(({ view, state, transactions }) => {
1242
1282
  if (tracked) {
1243
- const paste = update2.transactions.find((tr) => tr.isUserEvent("input.paste"));
1283
+ const paste = transactions.find((tr) => tr.isUserEvent("input.paste"));
1244
1284
  if (paste) {
1245
1285
  let found = -1;
1246
1286
  paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1247
1287
  if (text.eq(tracked.text)) {
1248
- for (let i = update2.transactions.indexOf(paste) + 1; i < update2.transactions.length; i++) {
1249
- fromB = update2.transactions[i].changes.mapPos(fromB);
1288
+ for (let i = transactions.indexOf(paste) + 1; i < transactions.length; i++) {
1289
+ fromB = transactions[i].changes.mapPos(fromB);
1250
1290
  }
1251
1291
  found = fromB;
1252
1292
  }
1253
1293
  });
1254
1294
  if (found > -1) {
1255
- const active = update2.view.state.field(commentsStateField).ranges;
1256
1295
  for (const moved of tracked.comments) {
1257
- if (active.some((range) => range.id === moved.id && range.from === range.to)) {
1258
- onMove(moved.id, Cursor.getCursorFromRange(update2.view.state.facet(Cursor.converter), {
1259
- from: found + moved.from,
1260
- to: found + moved.to
1261
- }));
1262
- }
1296
+ const range = {
1297
+ from: found + moved.from,
1298
+ to: found + moved.to
1299
+ };
1300
+ const cursor = Cursor.getCursorFromRange(state, range);
1301
+ onUpdate(moved.id, cursor);
1263
1302
  }
1264
1303
  }
1265
1304
  tracked = null;
@@ -1272,26 +1311,33 @@ var comments = (options = {}) => {
1272
1311
  const { key: shortcut = "meta-'" } = options;
1273
1312
  const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1274
1313
  const createCommentThread = (view) => {
1275
- const cursorConverter3 = view.state.facet(Cursor.converter);
1276
1314
  invariant2(options.onCreate, void 0, {
1277
1315
  F: __dxlog_file3,
1278
- L: 240,
1316
+ L: 259,
1279
1317
  S: void 0,
1280
1318
  A: [
1281
1319
  "options.onCreate",
1282
1320
  ""
1283
1321
  ]
1284
1322
  });
1285
- const { head, from, to } = view.state.selection.main;
1323
+ const { from, to } = view.state.selection.main;
1286
1324
  if (from === to) {
1287
1325
  return false;
1288
1326
  }
1289
- const cursor = Cursor.getCursorFromRange(cursorConverter3, {
1327
+ if (to === view.state.doc.length) {
1328
+ view.dispatch({
1329
+ changes: {
1330
+ from: to,
1331
+ insert: "\n"
1332
+ }
1333
+ });
1334
+ }
1335
+ const cursor = Cursor.getCursorFromRange(view.state, {
1290
1336
  from,
1291
1337
  to
1292
1338
  });
1293
1339
  if (cursor) {
1294
- const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
1340
+ const id = options.onCreate?.(cursor, view.coordsAtPos(from));
1295
1341
  if (id) {
1296
1342
  view.dispatch({
1297
1343
  effects: setSelection.of({
@@ -1301,25 +1347,13 @@ var comments = (options = {}) => {
1301
1347
  anchor: from
1302
1348
  }
1303
1349
  });
1304
- if (options.footnote) {
1305
- const tag = `[^${id}]`;
1306
- view.dispatch({
1307
- changes: {
1308
- from: head,
1309
- insert: tag
1310
- },
1311
- selection: {
1312
- anchor: head + tag.length
1313
- }
1314
- });
1315
- }
1316
1350
  return true;
1317
1351
  }
1318
1352
  }
1319
1353
  return false;
1320
1354
  };
1321
1355
  return [
1322
- commentsStateField,
1356
+ commentsState,
1323
1357
  highlightDecorations,
1324
1358
  styles3,
1325
1359
  //
@@ -1344,7 +1378,7 @@ var comments = (options = {}) => {
1344
1378
  above: true,
1345
1379
  create: () => {
1346
1380
  const el = document.createElement("div");
1347
- options.onHover?.(el, shortcut);
1381
+ options.onHover(el, shortcut);
1348
1382
  return {
1349
1383
  dom: el,
1350
1384
  offset: {
@@ -1357,140 +1391,154 @@ var comments = (options = {}) => {
1357
1391
  }
1358
1392
  return null;
1359
1393
  }, {
1360
- hideOnChange: true,
1394
+ // TODO(burdon): Hide on change triggered immediately?
1395
+ // hideOnChange: true,
1361
1396
  hoverTime: 1e3
1362
1397
  }) : [],
1363
1398
  //
1364
- // Monitor cursor movement and text updates.
1365
- // TODO(burdon): Is there a better (finer grained) way to do this?
1399
+ // Track deleted ranges and update ranges for decorations.
1366
1400
  //
1367
- EditorView5.updateListener.of(({ view, state, changes }) => {
1368
- const cursorConverter3 = view.state.facet(Cursor.converter);
1369
- {
1370
- let mod = false;
1371
- const { active, ranges } = state.field(commentsStateField);
1372
- changes.iterChanges((from, to, from2, to2) => {
1373
- ranges.forEach((range) => {
1374
- if (from2 === to2) {
1375
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1376
- if (!newRange || newRange.to - newRange.from === 0) {
1377
- log2.info("deleted comment", {
1378
- thread: range.id
1379
- }, {
1380
- F: __dxlog_file3,
1381
- L: 337,
1382
- S: void 0,
1383
- C: (f, a) => f(...a)
1384
- });
1385
- }
1386
- }
1387
- if (from <= range.to) {
1388
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1389
- Object.assign(range, newRange);
1390
- mod = true;
1401
+ EditorView6.updateListener.of(({ view, state, changes }) => {
1402
+ let mod = false;
1403
+ const { comments: comments2, ...value } = state.field(commentsState);
1404
+ changes.iterChanges((from, to, from2, to2) => {
1405
+ comments2.forEach(({ comment, range }) => {
1406
+ if (from2 === to2) {
1407
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1408
+ if (!newRange || newRange.to - newRange.from === 0) {
1409
+ options.onDelete?.(comment.id);
1391
1410
  }
1392
- });
1411
+ }
1412
+ if (from <= range.to) {
1413
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1414
+ Object.assign(range, newRange);
1415
+ mod = true;
1416
+ }
1417
+ });
1418
+ });
1419
+ if (mod) {
1420
+ view.dispatch({
1421
+ effects: setCommentState.of({
1422
+ comments: comments2,
1423
+ ...value
1424
+ })
1393
1425
  });
1394
- if (mod) {
1395
- view.dispatch({
1396
- effects: setCommentState.of({
1397
- active,
1398
- ranges
1399
- })
1400
- });
1401
- }
1402
1426
  }
1403
- {
1404
- const { head } = state.selection.main;
1405
- let min = Infinity;
1406
- const { active, closest, ranges } = state.field(commentsStateField);
1407
- const selected = {
1408
- active: void 0,
1409
- closest: void 0
1410
- };
1411
- ranges.forEach((comment) => {
1412
- const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1413
- if (head >= comment.from && head <= comment.to) {
1414
- selected.active = comment.id;
1415
- }
1427
+ }),
1428
+ //
1429
+ // Track selection/proximity.
1430
+ //
1431
+ EditorView6.updateListener.of(({ view, state, changes }) => {
1432
+ let min = Infinity;
1433
+ const { selection: { active, closest }, comments: comments2 } = state.field(commentsState);
1434
+ const { head } = state.selection.main;
1435
+ const selection = {};
1436
+ comments2.forEach(({ comment, range }) => {
1437
+ if (head >= range.from && head <= range.to) {
1438
+ selection.active = comment.id;
1439
+ selection.closest = void 0;
1440
+ }
1441
+ if (!selection.active) {
1442
+ const d = Math.min(Math.abs(head - range.from), Math.abs(head - range.to));
1416
1443
  if (d < min) {
1417
- selected.closest = comment.id;
1444
+ selection.closest = comment.id;
1418
1445
  min = d;
1419
1446
  }
1420
- });
1421
- if (selected.active !== active || selected.closest !== closest) {
1422
- view.dispatch({
1423
- effects: setSelection.of(selected)
1424
- });
1425
- handleSelect({
1426
- ...selected,
1427
- ranges: ranges.map((range) => ({
1428
- ...range,
1429
- location: view.coordsAtPos(range.from)
1430
- }))
1431
- });
1432
1447
  }
1448
+ });
1449
+ if (selection.active !== active || selection.closest !== closest) {
1450
+ view.dispatch({
1451
+ effects: setSelection.of(selection)
1452
+ });
1453
+ handleSelect({
1454
+ selection,
1455
+ comments: comments2.map(({ comment, range }) => ({
1456
+ comment,
1457
+ range,
1458
+ location: view.coordsAtPos(range.from)
1459
+ }))
1460
+ });
1433
1461
  }
1434
1462
  }),
1435
- options.onMove ? trackPastedComments(options.onMove) : []
1463
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
1436
1464
  ];
1437
1465
  };
1466
+ var useComments = (view, comments2) => {
1467
+ useEffect2(() => {
1468
+ if (view && comments2) {
1469
+ view.dispatch({
1470
+ effects: setComments.of(comments2)
1471
+ });
1472
+ }
1473
+ }, [
1474
+ view,
1475
+ comments2
1476
+ ]);
1477
+ };
1438
1478
 
1439
1479
  // packages/ui/react-ui-editor/src/extensions/hr.ts
1440
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1441
- var styles4 = EditorView6.baseTheme({
1480
+ import { syntaxTree } from "@codemirror/language";
1481
+ import { RangeSetBuilder } from "@codemirror/state";
1482
+ import { Decoration as Decoration4, EditorView as EditorView7, ViewPlugin as ViewPlugin3, WidgetType as WidgetType2 } from "@codemirror/view";
1483
+ var styles4 = EditorView7.baseTheme({
1442
1484
  "& .cm-hr": {
1443
- // TODO(burdon): ???
1444
1485
  // Note that block-level decorations should not have vertical margins,
1445
- borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1486
+ borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
1446
1487
  }
1447
1488
  });
1448
1489
  var HorizontalRuleWidget = class extends WidgetType2 {
1449
- constructor(_pos) {
1450
- super();
1451
- this._pos = _pos;
1452
- }
1453
- eq(other) {
1454
- return this._pos === other._pos;
1455
- }
1456
- toDOM(view) {
1490
+ toDOM() {
1457
1491
  const el = document.createElement("div");
1458
1492
  el.className = "cm-hr";
1459
1493
  return el;
1460
1494
  }
1461
1495
  };
1462
- var placeholderMatcher = new MatchDecorator({
1463
- regexp: /^---$/g,
1464
- decoration: (match, view, pos) => Decoration4.replace({
1465
- widget: new HorizontalRuleWidget(pos)
1466
- })
1496
+ var decoration = Decoration4.replace({
1497
+ widget: new HorizontalRuleWidget()
1467
1498
  });
1468
- var hr = () => [
1469
- styles4,
1470
- ViewPlugin4.fromClass(class {
1471
- constructor(view) {
1472
- this.rules = placeholderMatcher.createDeco(view);
1473
- }
1474
- update(update2) {
1475
- this.rules = placeholderMatcher.updateDeco(update2, this.rules);
1476
- }
1477
- }, {
1478
- decorations: (instance) => instance.rules,
1479
- // TODO(burdon): Is this really atomic (cursor can move into ---).
1480
- provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1481
- return view.plugin(plugin)?.rules || Decoration4.none;
1499
+ var buildDecorations = (view) => {
1500
+ const builder = new RangeSetBuilder();
1501
+ const { state } = view;
1502
+ const cursor = state.selection.main.head;
1503
+ for (const { from, to } of view.visibleRanges) {
1504
+ syntaxTree(state).iterate({
1505
+ enter: (node) => {
1506
+ if (node.name === "HorizontalRule") {
1507
+ if (cursor <= node.from || cursor >= node.to) {
1508
+ builder.add(node.from, node.to, decoration);
1509
+ }
1510
+ }
1511
+ },
1512
+ from,
1513
+ to
1514
+ });
1515
+ }
1516
+ return builder.finish();
1517
+ };
1518
+ var hr = () => {
1519
+ return [
1520
+ styles4,
1521
+ ViewPlugin3.fromClass(class {
1522
+ constructor(view) {
1523
+ this.decorations = buildDecorations(view);
1524
+ }
1525
+ update(update2) {
1526
+ this.decorations = buildDecorations(update2.view);
1527
+ }
1528
+ }, {
1529
+ decorations: (v) => v.decorations
1482
1530
  })
1483
- })
1484
- ];
1531
+ ];
1532
+ };
1485
1533
 
1486
1534
  // packages/ui/react-ui-editor/src/extensions/image.ts
1487
- import { syntaxTree } from "@codemirror/language";
1535
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1488
1536
  import { StateField as StateField3 } from "@codemirror/state";
1489
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
1537
+ import { Decoration as Decoration5, EditorView as EditorView8, WidgetType as WidgetType3 } from "@codemirror/view";
1490
1538
  var image = (options = {}) => {
1491
1539
  return StateField3.define({
1492
1540
  create: (state) => {
1493
- return Decoration5.set(buildDecorations(0, state.doc.length, state));
1541
+ return Decoration5.set(buildDecorations2(0, state.doc.length, state));
1494
1542
  },
1495
1543
  update: (value, tr) => {
1496
1544
  if (!tr.docChanged && !tr.selection) {
@@ -1510,16 +1558,16 @@ var image = (options = {}) => {
1510
1558
  filterFrom: from,
1511
1559
  filterTo: to,
1512
1560
  filter: () => false,
1513
- add: buildDecorations(from, to, tr.state)
1561
+ add: buildDecorations2(from, to, tr.state)
1514
1562
  });
1515
1563
  },
1516
- provide: (field) => EditorView7.decorations.from(field)
1564
+ provide: (field) => EditorView8.decorations.from(field)
1517
1565
  });
1518
1566
  };
1519
- var buildDecorations = (from, to, state) => {
1567
+ var buildDecorations2 = (from, to, state) => {
1520
1568
  const decorations = [];
1521
1569
  const cursor = state.selection.main.head;
1522
- syntaxTree(state).iterate({
1570
+ syntaxTree2(state).iterate({
1523
1571
  enter: (node) => {
1524
1572
  if (node.name === "Image") {
1525
1573
  const urlNode = node.node.getChild("URL");
@@ -1556,19 +1604,19 @@ var ImageWidget = class extends WidgetType3 {
1556
1604
  };
1557
1605
 
1558
1606
  // packages/ui/react-ui-editor/src/extensions/link.ts
1559
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1560
- import { RangeSetBuilder } from "@codemirror/state";
1561
- import { Decoration as Decoration6, WidgetType as WidgetType4, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1607
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1608
+ import { RangeSetBuilder as RangeSetBuilder2 } from "@codemirror/state";
1609
+ import { hoverTooltip as hoverTooltip2, Decoration as Decoration6, ViewPlugin as ViewPlugin4, WidgetType as WidgetType4 } from "@codemirror/view";
1562
1610
  import { tooltipContent } from "@dxos/react-ui-theme";
1563
1611
  var link = (options = {}) => {
1564
1612
  const extensions = [
1565
- ViewPlugin5.fromClass(class {
1613
+ ViewPlugin4.fromClass(class {
1566
1614
  constructor(view) {
1567
- this.decorations = buildDecorations2(view, options);
1615
+ this.decorations = buildDecorations3(view, options);
1568
1616
  }
1569
1617
  update(update2) {
1570
1618
  if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1571
- this.decorations = buildDecorations2(update2.view, options);
1619
+ this.decorations = buildDecorations3(update2.view, options);
1572
1620
  }
1573
1621
  }
1574
1622
  }, {
@@ -1581,7 +1629,7 @@ var link = (options = {}) => {
1581
1629
  // https://codemirror.net/docs/ref/#view.hoverTooltip
1582
1630
  // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1583
1631
  hoverTooltip2((view, pos, side) => {
1584
- const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1632
+ const syntax = syntaxTree3(view.state).resolveInner(pos, side);
1585
1633
  let link2 = null;
1586
1634
  for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1587
1635
  link2 = node.name === "Link" ? node : null;
@@ -1657,12 +1705,12 @@ var LinkText = class extends WidgetType4 {
1657
1705
  return link2;
1658
1706
  }
1659
1707
  };
1660
- var buildDecorations2 = (view, options) => {
1661
- const builder = new RangeSetBuilder();
1708
+ var buildDecorations3 = (view, options) => {
1709
+ const builder = new RangeSetBuilder2();
1662
1710
  const { state } = view;
1663
1711
  const cursor = state.selection.main.head;
1664
1712
  for (const { from, to } of view.visibleRanges) {
1665
- syntaxTree2(state).iterate({
1713
+ syntaxTree3(state).iterate({
1666
1714
  enter: (node) => {
1667
1715
  if (node.name === "Link") {
1668
1716
  const marks2 = node.node.getChildren("LinkMark");
@@ -1693,14 +1741,14 @@ var buildDecorations2 = (view, options) => {
1693
1741
  };
1694
1742
 
1695
1743
  // packages/ui/react-ui-editor/src/extensions/listener.ts
1696
- import { EditorView as EditorView8 } from "@codemirror/view";
1744
+ import { EditorView as EditorView9 } from "@codemirror/view";
1697
1745
  var listener = ({ onFocus, onChange }) => {
1698
1746
  const extensions = [];
1699
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1747
+ onFocus && extensions.push(EditorView9.focusChangeEffect.of((_, focused) => {
1700
1748
  onFocus(focused);
1701
1749
  return null;
1702
1750
  }));
1703
- onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1751
+ onChange && extensions.push(EditorView9.updateListener.of((update2) => {
1704
1752
  onChange(update2.state.doc.toString());
1705
1753
  }));
1706
1754
  return extensions;
@@ -1708,14 +1756,14 @@ var listener = ({ onFocus, onChange }) => {
1708
1756
 
1709
1757
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1710
1758
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1711
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1759
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1712
1760
  import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1713
1761
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1714
1762
  import { languages } from "@codemirror/language-data";
1715
1763
  import { searchKeymap } from "@codemirror/search";
1716
1764
  import { EditorState } from "@codemirror/state";
1717
1765
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1718
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1766
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView10, highlightActiveLine as highlightActiveLine2, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1719
1767
 
1720
1768
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1721
1769
  import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
@@ -1910,17 +1958,17 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1910
1958
  return [
1911
1959
  EditorState.allowMultipleSelections.of(true),
1912
1960
  EditorState.tabSize.of(2),
1913
- EditorView9.lineWrapping,
1914
- customKeymap(),
1961
+ EditorView10.lineWrapping,
1962
+ // https://github.com/codemirror/basic-setup
1915
1963
  bracketMatching2(),
1916
1964
  closeBrackets2(),
1917
1965
  crosshairCursor(),
1918
1966
  dropCursor(),
1919
1967
  drawSelection2(),
1920
- highlightActiveLine(),
1921
- history(),
1968
+ highlightActiveLine2(),
1969
+ history2(),
1922
1970
  indentOnInput(),
1923
- _placeholder && placeholder2(_placeholder),
1971
+ _placeholder ? placeholder2(_placeholder) : [],
1924
1972
  // Main extension.
1925
1973
  // https://github.com/codemirror/lang-markdown
1926
1974
  // https://codemirror.net/5/mode/markdown/index.html (demo).
@@ -1942,34 +1990,40 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1942
1990
  // https://github.com/codemirror/theme-one-dark
1943
1991
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1944
1992
  // Custom styles.
1945
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1946
- ].filter(Boolean);
1947
- };
1948
- var customKeymap = () => keymap4.of([
1949
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1950
- indentWithTab,
1951
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1952
- ...standardKeymap,
1953
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1954
- ...historyKeymap,
1955
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1956
- ...closeBracketsKeymap,
1957
- // https://codemirror.net/docs/ref/#search.searchKeymap
1958
- ...searchKeymap
1959
- ]);
1993
+ syntaxHighlighting2(markdownHighlightStyle(readonly)),
1994
+ keymap4.of([
1995
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1996
+ indentWithTab,
1997
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1998
+ ...closeBracketsKeymap,
1999
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
2000
+ ...historyKeymap,
2001
+ // https://codemirror.net/docs/ref/#search.searchKeymap
2002
+ ...searchKeymap,
2003
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
2004
+ ...standardKeymap
2005
+ ])
2006
+ ];
2007
+ };
1960
2008
 
1961
2009
  // packages/ui/react-ui-editor/src/extensions/mention.ts
1962
2010
  import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1963
2011
  var mention = ({ onSearch }) => {
1964
2012
  return autocompletion2({
2013
+ // TODO(burdon): Not working.
2014
+ activateOnTyping: true,
2015
+ // activateOnTypingDelay: 100,
2016
+ // selectOnOpen: true,
1965
2017
  closeOnBlur: false,
2018
+ // defaultKeymap: false,
2019
+ icons: false,
1966
2020
  override: [
1967
2021
  (context) => {
2022
+ console.log(context);
1968
2023
  const match = context.matchBefore(/@(\w+)?/);
1969
2024
  if (!match || match.from === match.to && !context.explicit) {
1970
2025
  return null;
1971
2026
  }
1972
- console.log(match);
1973
2027
  return {
1974
2028
  from: match.from,
1975
2029
  options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
@@ -1981,19 +2035,24 @@ var mention = ({ onSearch }) => {
1981
2035
  });
1982
2036
  };
1983
2037
 
2038
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
2039
+ var outliner = (options = {}) => {
2040
+ return [];
2041
+ };
2042
+
1984
2043
  // packages/ui/react-ui-editor/src/extensions/table.ts
1985
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1986
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1987
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
2044
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2045
+ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField4 } from "@codemirror/state";
2046
+ import { Decoration as Decoration7, EditorView as EditorView11, WidgetType as WidgetType5 } from "@codemirror/view";
1988
2047
  var table = (options = {}) => {
1989
2048
  return StateField4.define({
1990
2049
  create: (state) => update(state, options),
1991
2050
  update: (_, tr) => update(tr.state, options),
1992
- provide: (field) => EditorView10.decorations.from(field)
2051
+ provide: (field) => EditorView11.decorations.from(field)
1993
2052
  });
1994
2053
  };
1995
2054
  var update = (state, options) => {
1996
- const builder = new RangeSetBuilder2();
2055
+ const builder = new RangeSetBuilder3();
1997
2056
  const cursor = state.selection.main.head;
1998
2057
  const tables = [];
1999
2058
  const getTable = () => tables[tables.length - 1];
@@ -2001,7 +2060,7 @@ var update = (state, options) => {
2001
2060
  const table2 = getTable();
2002
2061
  return table2.rows?.[table2.rows.length - 1];
2003
2062
  };
2004
- syntaxTree3(state).iterate({
2063
+ syntaxTree4(state).iterate({
2005
2064
  enter: (node) => {
2006
2065
  switch (node.name) {
2007
2066
  case "Table": {
@@ -2080,9 +2139,18 @@ var TableWidget = class extends WidgetType5 {
2080
2139
  };
2081
2140
 
2082
2141
  // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2083
- import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2084
- import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
2085
- import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType6, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2142
+ import { syntaxTree as syntaxTree5 } from "@codemirror/language";
2143
+ import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
2144
+ import { EditorView as EditorView12, Decoration as Decoration8, WidgetType as WidgetType6, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
2145
+ var styles5 = EditorView12.baseTheme({
2146
+ "& .cm-task": {
2147
+ color: getToken("extend.colors.blue.500")
2148
+ },
2149
+ "& .cm-task-checkbox": {
2150
+ marginLeft: "4px",
2151
+ marginRight: "4px"
2152
+ }
2153
+ });
2086
2154
  var CheckboxWidget = class extends WidgetType6 {
2087
2155
  constructor(_checked) {
2088
2156
  super();
@@ -2093,6 +2161,7 @@ var CheckboxWidget = class extends WidgetType6 {
2093
2161
  }
2094
2162
  toDOM(view) {
2095
2163
  const input = document.createElement("input");
2164
+ input.className = "cm-task-checkbox";
2096
2165
  input.type = "checkbox";
2097
2166
  input.checked = this._checked;
2098
2167
  if (view.state.readOnly) {
@@ -2119,39 +2188,27 @@ var CheckboxWidget = class extends WidgetType6 {
2119
2188
  return false;
2120
2189
  }
2121
2190
  };
2122
- var checkedDeco = Decoration8.replace({
2191
+ var checkedDecoration = Decoration8.replace({
2123
2192
  widget: new CheckboxWidget(true)
2124
2193
  });
2125
- var uncheckedDeco = Decoration8.replace({
2194
+ var uncheckedDecoration = Decoration8.replace({
2126
2195
  widget: new CheckboxWidget(false)
2127
2196
  });
2128
- var tasklist = (options = {}) => {
2129
- return ViewPlugin6.fromClass(class {
2130
- constructor(view) {
2131
- this.decorations = buildDecorations3(view);
2132
- }
2133
- update(update2) {
2134
- if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2135
- this.decorations = buildDecorations3(update2.view);
2136
- }
2137
- }
2138
- }, {
2139
- decorations: (v) => v.decorations,
2140
- provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2141
- return view.plugin(plugin)?.decorations || Decoration8.none;
2142
- })
2143
- });
2144
- };
2145
- var buildDecorations3 = (view) => {
2146
- const builder = new RangeSetBuilder3();
2197
+ var buildDecorations4 = (view) => {
2198
+ const builder = new RangeSetBuilder4();
2147
2199
  const { state } = view;
2148
2200
  const cursor = state.selection.main.head;
2149
2201
  for (const { from, to } of view.visibleRanges) {
2150
- syntaxTree4(state).iterate({
2202
+ syntaxTree5(state).iterate({
2151
2203
  enter: (node) => {
2152
- if (node.name === "TaskMarker" && (cursor < node.from || cursor > node.to)) {
2153
- const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2154
- builder.add(node.from, node.to, checked ? checkedDeco : uncheckedDeco);
2204
+ if (node.name === "TaskMarker") {
2205
+ if (cursor < node.from || cursor > node.to) {
2206
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2207
+ builder.add(node.from - 2, node.from - 1, Decoration8.mark({
2208
+ class: "cm-task"
2209
+ }));
2210
+ builder.add(node.from, node.to, checked ? checkedDecoration : uncheckedDecoration);
2211
+ }
2155
2212
  }
2156
2213
  },
2157
2214
  from,
@@ -2160,6 +2217,23 @@ var buildDecorations3 = (view) => {
2160
2217
  }
2161
2218
  return builder.finish();
2162
2219
  };
2220
+ var tasklist = (options = {}) => {
2221
+ return [
2222
+ ViewPlugin5.fromClass(class {
2223
+ constructor(view) {
2224
+ this.decorations = buildDecorations4(view);
2225
+ }
2226
+ update(update2) {
2227
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2228
+ this.decorations = buildDecorations4(update2.view);
2229
+ }
2230
+ }
2231
+ }, {
2232
+ decorations: (v) => v.decorations
2233
+ }),
2234
+ styles5
2235
+ ];
2236
+ };
2163
2237
 
2164
2238
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
2165
2239
  import { keymap as keymap5 } from "@codemirror/view";
@@ -2476,90 +2550,84 @@ var codeTheme = {
2476
2550
  };
2477
2551
 
2478
2552
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2553
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
2479
2554
  var EditorModes = [
2480
2555
  "default",
2481
2556
  "vim"
2482
2557
  ];
2483
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, readonly, comments: comments2, extensions = [], editorMode, theme, slots = defaultSlots }, forwardedRef) => {
2558
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autofocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [] }, forwardedRef) => {
2484
2559
  const tabsterDOMAttribute = useFocusableGroup({
2485
2560
  tabBehavior: "limited"
2486
2561
  });
2487
- const { themeMode } = useThemeContext();
2562
+ const { themeMode } = useThemeContext2();
2488
2563
  const rootRef = useRef(null);
2489
2564
  const [view, setView] = useState(null);
2490
2565
  useImperativeHandle(forwardedRef, () => view, [
2491
2566
  view
2492
2567
  ]);
2493
- useEffect(() => {
2494
- if (view && focus) {
2495
- view.focus();
2568
+ useEffect3(() => {
2569
+ if (autofocus) {
2570
+ view?.focus();
2496
2571
  }
2497
2572
  }, [
2498
2573
  view,
2499
- focus
2574
+ autofocus
2500
2575
  ]);
2501
- const { awareness: awareness2, peer } = model;
2502
- useEffect(() => {
2503
- if (awareness2 && peer) {
2504
- awareness2.setLocalStateField("user", {
2505
- name: peer.name ?? generateName(peer.id),
2506
- color: getColorForValue({
2507
- value: peer.id,
2508
- type: "color"
2509
- }),
2510
- colorLight: getColorForValue({
2511
- value: peer.id,
2512
- themeMode,
2513
- type: "highlight"
2514
- })
2515
- });
2516
- }
2517
- }, [
2518
- awareness2,
2519
- peer,
2520
- themeMode
2521
- ]);
2522
- useEffect(() => {
2523
- if (view && comments2 !== void 0) {
2524
- view.dispatch({
2525
- effects: setCommentRange.of({
2526
- model,
2527
- comments: comments2
2528
- })
2529
- });
2530
- }
2531
- }, [
2532
- view,
2533
- comments2
2534
- ]);
2535
- useEffect(() => {
2576
+ useAwareness(model);
2577
+ useEffect3(() => {
2536
2578
  if (!model || !rootRef.current) {
2537
2579
  return;
2538
2580
  }
2539
2581
  const state = EditorState2.create({
2540
2582
  doc: model.text(),
2541
- // TODO(burdon): Composer should store and set selection when switching documents.
2542
2583
  selection,
2543
2584
  extensions: [
2544
- readonly && EditorState2.readOnly.of(readonly),
2545
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2546
- editorMode === "vim" && vim(),
2585
+ // TODO(burdon): Doesn't catch errors in keymap functions.
2586
+ EditorView13.exceptionSink.of((err) => {
2587
+ log2.catch(err, void 0, {
2588
+ F: __dxlog_file4,
2589
+ L: 114,
2590
+ S: void 0,
2591
+ C: (f, a) => f(...a)
2592
+ });
2593
+ }),
2547
2594
  // Theme.
2548
- // TODO(burdon): Make theme configurable.
2549
- EditorView12.baseTheme(defaultTheme),
2550
- EditorView12.theme(theme ?? {}),
2551
- EditorView12.darkTheme.of(themeMode === "dark"),
2595
+ // TODO(burdon): Make configurable.
2596
+ EditorView13.baseTheme(defaultTheme),
2597
+ EditorView13.theme(theme ?? {}),
2598
+ EditorView13.darkTheme.of(themeMode === "dark"),
2599
+ EditorView13.editorAttributes.of({
2600
+ class: slots.editor?.className ?? ""
2601
+ }),
2602
+ EditorView13.contentAttributes.of({
2603
+ class: slots.content?.className ?? ""
2604
+ }),
2605
+ // State.
2606
+ EditorState2.readOnly.of(!!readonly),
2552
2607
  // Storage and replication.
2608
+ // NOTE: This must come before user extensions.
2553
2609
  model.extension,
2610
+ // TODO(burdon): Factor out (requires special handling for Escape/focus).
2611
+ editorMode === "vim" && vim(),
2554
2612
  // Custom.
2555
2613
  ...extensions
2556
2614
  ].filter(Boolean)
2557
2615
  });
2558
- view?.destroy();
2559
- const newView = new EditorView12({
2616
+ const newView = new EditorView13({
2617
+ state,
2560
2618
  parent: rootRef.current,
2561
- state
2619
+ scrollTo,
2620
+ // NOTE: Uncomment to debug/monitor all transactions.
2621
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
2622
+ dispatchTransactions: (trs, view2) => {
2623
+ view2.update(trs);
2624
+ const debug = false;
2625
+ if (debug) {
2626
+ logChanges(trs);
2627
+ }
2628
+ }
2562
2629
  });
2630
+ view?.destroy();
2563
2631
  setView(newView);
2564
2632
  return () => {
2565
2633
  newView?.destroy();
@@ -2575,8 +2643,14 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, read
2575
2643
  const handleKeyUp = useCallback((event) => {
2576
2644
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2577
2645
  switch (key) {
2646
+ case "Enter": {
2647
+ view?.focus();
2648
+ break;
2649
+ }
2578
2650
  case "Escape": {
2579
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
2651
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
2652
+ rootRef.current?.focus();
2653
+ }
2580
2654
  break;
2581
2655
  }
2582
2656
  }
@@ -2587,24 +2661,24 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, read
2587
2661
  return /* @__PURE__ */ React.createElement("div", {
2588
2662
  key: model.id,
2589
2663
  role: "none",
2590
- ref: rootRef,
2591
2664
  tabIndex: 0,
2592
2665
  onKeyUp: handleKeyUp,
2593
2666
  ...slots.root,
2594
- ...editorMode !== "vim" && tabsterDOMAttribute
2667
+ ...editorMode !== "vim" && tabsterDOMAttribute,
2668
+ ref: rootRef
2595
2669
  });
2596
2670
  });
2597
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
2598
- const { themeMode } = useThemeContext();
2671
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, multiline, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
2672
+ const { themeMode } = useThemeContext2();
2599
2673
  const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2600
2674
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2601
2675
  ref: forwardedRef,
2602
2676
  readonly,
2603
2677
  extensions: [
2604
2678
  basicBundle({
2605
- readonly,
2606
2679
  themeMode,
2607
- placeholder: placeholder3
2680
+ placeholder: placeholder3,
2681
+ multiline
2608
2682
  }),
2609
2683
  ...extensions
2610
2684
  ],
@@ -2613,16 +2687,16 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholde
2613
2687
  ...props
2614
2688
  });
2615
2689
  });
2616
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
2617
- const { themeMode } = useThemeContext();
2690
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
2691
+ const { themeMode } = useThemeContext2();
2618
2692
  const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2619
2693
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2620
2694
  ref: forwardedRef,
2621
2695
  readonly,
2622
2696
  extensions: [
2623
2697
  markdownBundle({
2624
- readonly,
2625
2698
  themeMode,
2699
+ readonly,
2626
2700
  placeholder: placeholder3
2627
2701
  }),
2628
2702
  ...extensions
@@ -2634,7 +2708,11 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeh
2634
2708
  });
2635
2709
  var defaultSlots = {
2636
2710
  root: {
2637
- className: mx3("p-2 overflow-y-auto", inputSurface)
2711
+ // TODO(burdon): Add focusRing by default/as property?
2712
+ className: mx3("flex flex-col grow overflow-y-auto", inputSurface)
2713
+ },
2714
+ editor: {
2715
+ className: "h-full p-2"
2638
2716
  }
2639
2717
  };
2640
2718
  var defaultTextSlots = {
@@ -2654,7 +2732,7 @@ import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2654
2732
  import { Context as Context2 } from "@dxos/context";
2655
2733
  import { invariant as invariant3 } from "@dxos/invariant";
2656
2734
  import { log as log3 } from "@dxos/log";
2657
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2735
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2658
2736
  var DEBOUNCE_INTERVAL = 100;
2659
2737
  var SpaceAwarenessProvider = class {
2660
2738
  constructor(params) {
@@ -2695,7 +2773,7 @@ var SpaceAwarenessProvider = class {
2695
2773
  log3.debug("failed to query awareness", {
2696
2774
  err
2697
2775
  }, {
2698
- F: __dxlog_file4,
2776
+ F: __dxlog_file5,
2699
2777
  L: 85,
2700
2778
  S: this,
2701
2779
  C: (f, a) => f(...a)
@@ -2709,7 +2787,7 @@ var SpaceAwarenessProvider = class {
2709
2787
  }
2710
2788
  update(position) {
2711
2789
  invariant3(this._postTask, void 0, {
2712
- F: __dxlog_file4,
2790
+ F: __dxlog_file5,
2713
2791
  L: 96,
2714
2792
  S: this,
2715
2793
  A: [
@@ -2729,7 +2807,7 @@ var SpaceAwarenessProvider = class {
2729
2807
  }
2730
2808
  _handleQueryMessage() {
2731
2809
  invariant3(this._postTask, void 0, {
2732
- F: __dxlog_file4,
2810
+ F: __dxlog_file5,
2733
2811
  L: 111,
2734
2812
  S: this,
2735
2813
  A: [
@@ -2741,7 +2819,7 @@ var SpaceAwarenessProvider = class {
2741
2819
  }
2742
2820
  _handlePostMessage(message) {
2743
2821
  invariant3(message.kind === "post", void 0, {
2744
- F: __dxlog_file4,
2822
+ F: __dxlog_file5,
2745
2823
  L: 116,
2746
2824
  S: this,
2747
2825
  A: [
@@ -2754,10 +2832,50 @@ var SpaceAwarenessProvider = class {
2754
2832
  }
2755
2833
  };
2756
2834
 
2757
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2835
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
2758
2836
  import { StateField as StateField5 } from "@codemirror/state";
2759
- import { useEffect as useEffect2, useState as useState2 } from "react";
2760
- import { isAutomergeObject } from "@dxos/react-client/echo";
2837
+ var modelState = StateField5.define({
2838
+ create: () => void 0,
2839
+ update: (model) => model
2840
+ });
2841
+
2842
+ // packages/ui/react-ui-editor/src/hooks/automerge.ts
2843
+ var createAutomergeModel = ({ space, identity, text }) => {
2844
+ const obj = text;
2845
+ const doc = getRawDoc(obj, [
2846
+ obj.field
2847
+ ]);
2848
+ const awarenessProvider = space && new SpaceAwarenessProvider({
2849
+ space,
2850
+ channel: `automerge.awareness.${obj.id}`,
2851
+ info: {
2852
+ displayName: identity?.profile?.displayName ?? "",
2853
+ color: cursorColor.color,
2854
+ lightColor: cursorColor.light
2855
+ },
2856
+ peerId: identity?.identityKey.toHex() ?? "Anonymous"
2857
+ });
2858
+ const model = {
2859
+ id: obj.id,
2860
+ content: doc,
2861
+ text: () => get4(doc.handle.docSync(), doc.path),
2862
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2863
+ extension: [
2864
+ modelState.init(() => model),
2865
+ automerge3({
2866
+ handle: doc.handle,
2867
+ path: doc.path
2868
+ }),
2869
+ awarenessProvider && AwarenessProvider.of(awarenessProvider),
2870
+ awareness()
2871
+ ].filter(isNotNullOrUndefined),
2872
+ peer: identity ? {
2873
+ id: identity.identityKey.toHex(),
2874
+ name: identity.profile?.displayName
2875
+ } : void 0
2876
+ };
2877
+ return model;
2878
+ };
2761
2879
 
2762
2880
  // packages/ui/react-ui-editor/src/hooks/yjs.ts
2763
2881
  import * as decoding from "lib0/decoding";
@@ -2766,11 +2884,11 @@ import { Observable } from "lib0/observable";
2766
2884
  import * as YP from "y-protocols/awareness";
2767
2885
  import { invariant as invariant4 } from "@dxos/invariant";
2768
2886
  import { log as log4 } from "@dxos/log";
2769
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2887
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2770
2888
  var createYjsModel = ({ identity, space, text }) => {
2771
2889
  invariant4(text?.doc && text?.content, void 0, {
2772
- F: __dxlog_file5,
2773
- L: 21,
2890
+ F: __dxlog_file6,
2891
+ L: 22,
2774
2892
  S: void 0,
2775
2893
  A: [
2776
2894
  "text?.doc && text?.content",
@@ -2834,8 +2952,8 @@ var YAwarenessProvider = class extends Observable {
2834
2952
  removed,
2835
2953
  origin
2836
2954
  }, {
2837
- F: __dxlog_file5,
2838
- L: 100,
2955
+ F: __dxlog_file6,
2956
+ L: 101,
2839
2957
  S: this,
2840
2958
  C: (f, a) => f(...a)
2841
2959
  });
@@ -2847,8 +2965,8 @@ var YAwarenessProvider = class extends Observable {
2847
2965
  }
2848
2966
  _handleSpaceMessage({ payload }) {
2849
2967
  log4("space message", payload, {
2850
- F: __dxlog_file5,
2851
- L: 109,
2968
+ F: __dxlog_file6,
2969
+ L: 110,
2852
2970
  S: this,
2853
2971
  C: (f, a) => f(...a)
2854
2972
  });
@@ -2892,14 +3010,12 @@ var YAwarenessProvider = class extends Observable {
2892
3010
  };
2893
3011
 
2894
3012
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2895
- var modelState = StateField5.define({
2896
- create: () => void 0,
2897
- update: (model) => model
2898
- });
3013
+ import { useEffect as useEffect4, useState as useState2 } from "react";
3014
+ import { isAutomergeObject } from "@dxos/react-client/echo";
2899
3015
  var useTextModel = (props) => {
2900
3016
  const { identity, space, text } = props;
2901
3017
  const [model, setModel] = useState2();
2902
- useEffect2(() => setModel(createModel(props)), [
3018
+ useEffect4(() => setModel(createModel(props)), [
2903
3019
  identity,
2904
3020
  space,
2905
3021
  text
@@ -2916,44 +3032,6 @@ var createModel = (props) => {
2916
3032
  return void 0;
2917
3033
  }
2918
3034
  };
2919
-
2920
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2921
- var createAutomergeModel = ({ space, identity, text }) => {
2922
- const obj = text;
2923
- const doc = getRawDoc(obj, [
2924
- obj.field
2925
- ]);
2926
- const awarenessProvider = space && new SpaceAwarenessProvider({
2927
- space,
2928
- channel: `automerge.awareness.${obj.id}`,
2929
- info: {
2930
- displayName: identity?.profile?.displayName ?? "",
2931
- color: cursorColor.color,
2932
- lightColor: cursorColor.light
2933
- },
2934
- peerId: identity?.identityKey.toHex() ?? "Anonymous"
2935
- });
2936
- const model = {
2937
- id: obj.id,
2938
- content: doc,
2939
- text: () => get4(doc.handle.docSync(), doc.path),
2940
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2941
- extension: [
2942
- modelState.init(() => model),
2943
- automerge3({
2944
- handle: doc.handle,
2945
- path: doc.path
2946
- }),
2947
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
2948
- awareness()
2949
- ].filter(isNotNullOrUndefined),
2950
- peer: identity ? {
2951
- id: identity.identityKey.toHex(),
2952
- name: identity.profile?.displayName
2953
- } : void 0
2954
- };
2955
- return model;
2956
- };
2957
3035
  export {
2958
3036
  AwarenessProvider,
2959
3037
  BaseTextEditor,
@@ -2979,7 +3057,6 @@ export {
2979
3057
  comments,
2980
3058
  createAutomergeModel,
2981
3059
  createYjsModel,
2982
- cursorConverter,
2983
3060
  defaultMarkdownSlots,
2984
3061
  defaultOptions,
2985
3062
  defaultSlots,
@@ -2990,6 +3067,7 @@ export {
2990
3067
  image,
2991
3068
  link,
2992
3069
  listener,
3070
+ logChanges,
2993
3071
  markdownBundle,
2994
3072
  markdownHighlightStyle,
2995
3073
  markdownTags,
@@ -2997,7 +3075,8 @@ export {
2997
3075
  markdownTheme,
2998
3076
  mention,
2999
3077
  modelState,
3000
- setCommentRange,
3078
+ outliner,
3079
+ setComments,
3001
3080
  setFocus,
3002
3081
  setSelection,
3003
3082
  table,
@@ -3005,6 +3084,8 @@ export {
3005
3084
  tasklist,
3006
3085
  textTheme,
3007
3086
  typewriter,
3087
+ useAwareness,
3088
+ useComments,
3008
3089
  useTextModel,
3009
3090
  yjs
3010
3091
  };