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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/lib/browser/index.mjs +779 -760
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +16 -13
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +20 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -6
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  11. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -1
  13. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  14. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  15. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  16. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  17. package/dist/types/src/extensions/automerge/defs.d.ts +7 -9
  18. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  19. package/dist/types/src/extensions/automerge/index.d.ts +0 -1
  20. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
  21. package/dist/types/src/extensions/automerge/semaphore.d.ts +7 -12
  22. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  23. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
  24. package/dist/types/src/extensions/awareness.d.ts +2 -0
  25. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  26. package/dist/types/src/extensions/basic.d.ts +4 -5
  27. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  28. package/dist/types/src/extensions/code.d.ts.map +1 -1
  29. package/dist/types/src/extensions/comments.d.ts +31 -18
  30. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  31. package/dist/types/src/{util → extensions}/cursor.d.ts +10 -2
  32. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  33. package/dist/types/src/extensions/hr.d.ts.map +1 -1
  34. package/dist/types/src/extensions/index.d.ts +2 -0
  35. package/dist/types/src/extensions/index.d.ts.map +1 -1
  36. package/dist/types/src/extensions/markdown/bundle.d.ts +2 -5
  37. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  38. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  39. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  40. package/dist/types/src/extensions/outliner.d.ts +4 -0
  41. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  42. package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
  43. package/dist/types/src/extensions/yjs/cursor.d.ts +1 -1
  44. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -1
  45. package/dist/types/src/hooks/automerge.d.ts +2 -1
  46. package/dist/types/src/hooks/automerge.d.ts.map +1 -1
  47. package/dist/types/src/hooks/defs.d.ts +28 -0
  48. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  49. package/dist/types/src/hooks/index.d.ts +2 -1
  50. package/dist/types/src/hooks/index.d.ts.map +1 -1
  51. package/dist/types/src/hooks/useTextModel.d.ts +2 -32
  52. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  53. package/dist/types/src/hooks/yjs.d.ts +2 -1
  54. package/dist/types/src/hooks/yjs.d.ts.map +1 -1
  55. package/dist/types/src/index.d.ts +1 -0
  56. package/dist/types/src/index.d.ts.map +1 -1
  57. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  58. package/dist/types/src/themes/default.d.ts.map +1 -1
  59. package/dist/types/src/util.d.ts +7 -0
  60. package/dist/types/src/util.d.ts.map +1 -0
  61. package/package.json +32 -31
  62. package/src/components/TextEditor/MarkdownEditor.stories.tsx +77 -79
  63. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  64. package/src/components/TextEditor/TextEditor.tsx +124 -92
  65. package/src/components/TextEditor/comments.stories.tsx +357 -0
  66. package/src/extensions/autocomplete.ts +6 -3
  67. package/src/extensions/automerge/automerge.ts +40 -46
  68. package/src/extensions/automerge/cursor.ts +2 -1
  69. package/src/extensions/automerge/defs.ts +10 -31
  70. package/src/extensions/automerge/index.ts +0 -1
  71. package/src/extensions/automerge/semaphore.ts +23 -20
  72. package/src/extensions/automerge/update-automerge.ts +2 -2
  73. package/src/extensions/awareness.ts +45 -34
  74. package/src/extensions/basic.ts +22 -17
  75. package/src/extensions/code.ts +24 -18
  76. package/src/extensions/comments.ts +242 -234
  77. package/src/extensions/cursor.ts +50 -0
  78. package/src/extensions/hr.ts +49 -50
  79. package/src/extensions/image.ts +2 -2
  80. package/src/extensions/index.ts +2 -0
  81. package/src/extensions/link.ts +7 -7
  82. package/src/extensions/markdown/bundle.ts +18 -24
  83. package/src/extensions/markdown/highlight.ts +0 -8
  84. package/src/extensions/mention.ts +9 -2
  85. package/src/extensions/outliner.ts +12 -0
  86. package/src/extensions/table.ts +2 -2
  87. package/src/extensions/tasklist.ts +78 -86
  88. package/src/extensions/yjs/cursor.ts +2 -1
  89. package/src/extensions/yjs/yjs.test.ts +1 -1
  90. package/src/extensions/yjs/yjs.ts +2 -2
  91. package/src/hooks/automerge.ts +2 -1
  92. package/src/hooks/defs.ts +47 -0
  93. package/src/hooks/index.ts +3 -1
  94. package/src/hooks/useTextModel.ts +3 -49
  95. package/src/hooks/yjs.ts +2 -23
  96. package/src/index.ts +1 -0
  97. package/src/styles/markdown.ts +0 -2
  98. package/src/themes/default.ts +17 -50
  99. package/src/util.ts +41 -0
  100. package/dist/types/src/util/cursor.d.ts.map +0 -1
  101. package/dist/types/src/util/index.d.ts +0 -3
  102. package/dist/types/src/util/index.d.ts.map +0 -1
  103. package/dist/types/src/util/util.d.ts +0 -5
  104. package/dist/types/src/util/util.d.ts.map +0 -1
  105. package/src/util/cursor.ts +0 -29
  106. package/src/util/index.ts +0 -6
  107. package/src/util/util.ts +0 -18
@@ -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 } 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,85 +240,96 @@ 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
 
277
- // packages/ui/react-ui-editor/src/util/cursor.ts
282
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
278
283
  import { Facet } from "@codemirror/state";
279
- var DEFAULT_CURSOR_CONVERTER = {
284
+ var defaultCursorConverter = {
280
285
  toCursor: (position) => position.toString(),
281
286
  fromCursor: (cursor) => parseInt(cursor)
282
287
  };
283
- var CursorConverter = Facet.define({
284
- combine: (providers) => providers[0] ?? DEFAULT_CURSOR_CONVERTER
285
- });
286
-
287
- // packages/ui/react-ui-editor/src/util/util.ts
288
- import { log } from "@dxos/log";
289
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util/util.ts";
290
- var callbackWrapper = (fn) => (...args) => {
291
- try {
292
- return fn(...args);
293
- } catch (error) {
294
- log.catch(error, void 0, {
295
- F: __dxlog_file,
296
- L: 16,
297
- S: void 0,
298
- C: (f, a) => f(...a)
288
+ var Cursor = class _Cursor {
289
+ static {
290
+ this.converter = Facet.define({
291
+ combine: (providers) => providers[0] ?? defaultCursorConverter
299
292
  });
300
293
  }
294
+ static {
295
+ this.getCursorFromRange = (state, range) => {
296
+ const cursorConverter3 = state.facet(_Cursor.converter);
297
+ const from = cursorConverter3.toCursor(range.from);
298
+ const to = cursorConverter3.toCursor(range.to, -1);
299
+ return [
300
+ from,
301
+ to
302
+ ].join(":");
303
+ };
304
+ }
305
+ static {
306
+ this.getRangeFromCursor = (state, cursor) => {
307
+ const cursorConverter3 = state.facet(_Cursor.converter);
308
+ const parts = cursor.split(":");
309
+ const from = cursorConverter3.fromCursor(parts[0]);
310
+ const to = cursorConverter3.fromCursor(parts[1]);
311
+ return from !== void 0 && to !== void 0 ? {
312
+ from,
313
+ to
314
+ } : void 0;
315
+ };
316
+ }
301
317
  };
302
318
 
303
319
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
304
- var semaphoreFacet = Facet2.define({
305
- combine: (values) => values.at(-1)
306
- });
320
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts";
307
321
  var automerge3 = ({ handle, path }) => {
308
- const stateField = StateField.define({
322
+ const state = StateField.define({
309
323
  create: () => ({
324
+ path: path.slice(),
310
325
  lastHeads: A.getHeads(handle.docSync()),
311
- unreconciledTransactions: [],
312
- path: path.slice()
326
+ unreconciledTransactions: []
313
327
  }),
314
328
  update: (value, tr) => {
315
329
  const result = {
330
+ path: path.slice(),
316
331
  lastHeads: value.lastHeads,
317
- unreconciledTransactions: value.unreconciledTransactions.slice(),
318
- path: path.slice()
332
+ unreconciledTransactions: value.unreconciledTransactions.slice()
319
333
  };
320
334
  let clearUnreconciled = false;
321
335
  for (const effect of tr.effects) {
@@ -334,41 +348,50 @@ var automerge3 = ({ handle, path }) => {
334
348
  return result;
335
349
  }
336
350
  });
337
- const semaphore = new PatchSemaphore(stateField);
338
- const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
339
- constructor(_view) {
340
- this._view = _view;
341
- this._handleChange = () => {
342
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
343
- };
344
- handle.addListener("change", this._handleChange);
345
- }
346
- update(update2) {
347
- if (update2.transactions.length > 0 && update2.transactions.some((tr) => !isReconcileTx(tr))) {
348
- queueMicrotask(() => {
349
- 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
+ ]
350
366
  });
367
+ handle.addListener("change", this._handleChange.bind(this));
351
368
  }
352
- }
353
- destroy() {
354
- handle.addListener("change", this._handleChange);
355
- }
356
- });
357
- return {
358
- extension: [
359
- CursorConverter.of(cursorConverter(handle, path)),
360
- semaphoreFacet.of(semaphore),
361
- stateField,
362
- viewPlugin
363
- ]
364
- };
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
+ ];
365
384
  };
366
385
 
367
386
  // packages/ui/react-ui-editor/src/extensions/awareness.ts
368
- import { Annotation as Annotation2, Facet as Facet3, RangeSet } from "@codemirror/state";
369
- 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";
370
390
  import { Event } from "@dxos/async";
371
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";
372
395
  var dummyProvider = {
373
396
  remoteStateChange: new Event(),
374
397
  open: () => {
@@ -379,7 +402,7 @@ var dummyProvider = {
379
402
  },
380
403
  getRemoteStates: () => []
381
404
  };
382
- var AwarenessProvider = Facet3.define({
405
+ var AwarenessProvider = Facet2.define({
383
406
  combine: (providers) => providers[0] ?? dummyProvider
384
407
  });
385
408
  var RemoteSelectionChangedAnnotation = Annotation2.define();
@@ -392,13 +415,36 @@ var awareness = (provider = dummyProvider) => {
392
415
  styles
393
416
  ];
394
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
+ };
395
441
  var RemoteSelectionsDecorator = class {
396
442
  constructor(view) {
397
443
  this.decorations = RangeSet.of([]);
398
444
  this._ctx = new Context();
399
445
  this._lastAnchor = void 0;
400
446
  this._lastHead = void 0;
401
- this._cursorConverter = view.state.facet(CursorConverter);
447
+ this._cursorConverter = view.state.facet(Cursor.converter);
402
448
  this._provider = view.state.facet(AwarenessProvider);
403
449
  this._provider.open();
404
450
  this._provider.remoteStateChange.on(this._ctx, () => {
@@ -453,7 +499,7 @@ var RemoteSelectionsDecorator = class {
453
499
  attributes: {
454
500
  style: `background-color: ${lightColor}`
455
501
  },
456
- class: "cm-ySelection"
502
+ class: "cm-collab-selection"
457
503
  })
458
504
  });
459
505
  } else {
@@ -464,7 +510,7 @@ var RemoteSelectionsDecorator = class {
464
510
  attributes: {
465
511
  style: `background-color: ${color}`
466
512
  },
467
- class: "cm-ySelection"
513
+ class: "cm-collab-selection"
468
514
  })
469
515
  });
470
516
  decorations.push({
@@ -474,7 +520,7 @@ var RemoteSelectionsDecorator = class {
474
520
  attributes: {
475
521
  style: `background-color: ${color}`
476
522
  },
477
- class: "cm-ySelection"
523
+ class: "cm-collab-selection"
478
524
  })
479
525
  });
480
526
  for (let i = startLine.number + 1; i < endLine.number; i++) {
@@ -485,7 +531,7 @@ var RemoteSelectionsDecorator = class {
485
531
  value: Decoration.line({
486
532
  attributes: {
487
533
  style: `background-color: ${color}`,
488
- class: "cm-yLineSelection"
534
+ class: "cm-collab-selectionLine"
489
535
  }
490
536
  })
491
537
  });
@@ -505,23 +551,21 @@ var RemoteSelectionsDecorator = class {
505
551
  }
506
552
  };
507
553
  var RemoteCaretWidget = class extends WidgetType {
508
- constructor(name, color) {
554
+ constructor(_name, _color) {
509
555
  super();
510
- this.name = name;
511
- this.color = color;
512
- this.name = name;
513
- this.color = color;
556
+ this._name = _name;
557
+ this._color = _color;
514
558
  }
515
559
  toDOM() {
516
560
  const span = document.createElement("span");
517
- span.className = "cm-ySelectionCaret";
518
- span.style.backgroundColor = this.color;
519
- span.style.borderColor = this.color;
561
+ span.className = "cm-collab-selectionCaret";
562
+ span.style.backgroundColor = this._color;
563
+ span.style.borderColor = this._color;
520
564
  const dot = document.createElement("div");
521
- dot.className = "cm-ySelectionCaretDot";
565
+ dot.className = "cm-collab-selectionCaretDot";
522
566
  const info = document.createElement("div");
523
- info.className = "cm-ySelectionInfo";
524
- info.innerText = this.name;
567
+ info.className = "cm-collab-selectionInfo";
568
+ info.innerText = this._name;
525
569
  span.appendChild(document.createTextNode("\u2060"));
526
570
  span.appendChild(dot);
527
571
  span.appendChild(document.createTextNode("\u2060"));
@@ -529,15 +573,12 @@ var RemoteCaretWidget = class extends WidgetType {
529
573
  span.appendChild(document.createTextNode("\u2060"));
530
574
  return span;
531
575
  }
532
- eq(widget) {
533
- return widget.color === this.color;
534
- }
535
- compare(widget) {
536
- return widget.color === this.color;
537
- }
538
576
  updateDOM() {
539
577
  return false;
540
578
  }
579
+ eq(widget) {
580
+ return widget._color === this._color;
581
+ }
541
582
  get estimatedHeight() {
542
583
  return -1;
543
584
  }
@@ -545,13 +586,13 @@ var RemoteCaretWidget = class extends WidgetType {
545
586
  return true;
546
587
  }
547
588
  };
548
- var styles = EditorView.baseTheme({
549
- ".cm-ySelection": {},
550
- ".cm-yLineSelection": {
589
+ var styles = EditorView2.baseTheme({
590
+ ".cm-collab-selection": {},
591
+ ".cm-collab-selectionLine": {
551
592
  padding: 0,
552
593
  margin: "0px 2px 0px 4px"
553
594
  },
554
- ".cm-ySelectionCaret": {
595
+ ".cm-collab-selectionCaret": {
555
596
  position: "relative",
556
597
  borderLeft: "1px solid black",
557
598
  borderRight: "1px solid black",
@@ -560,7 +601,7 @@ var styles = EditorView.baseTheme({
560
601
  boxSizing: "border-box",
561
602
  display: "inline"
562
603
  },
563
- ".cm-ySelectionCaretDot": {
604
+ ".cm-collab-selectionCaretDot": {
564
605
  borderRadius: "50%",
565
606
  position: "absolute",
566
607
  width: ".4em",
@@ -571,11 +612,11 @@ var styles = EditorView.baseTheme({
571
612
  transition: "transform .3s ease-in-out",
572
613
  boxSizing: "border-box"
573
614
  },
574
- ".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot": {
615
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
575
616
  transformOrigin: "bottom center",
576
617
  transform: "scale(0)"
577
618
  },
578
- ".cm-ySelectionInfo": {
619
+ ".cm-collab-selectionInfo": {
579
620
  position: "absolute",
580
621
  top: "-1.05em",
581
622
  left: "-1px",
@@ -591,12 +632,12 @@ var styles = EditorView.baseTheme({
591
632
  zIndex: 101,
592
633
  transition: "opacity .3s ease-in-out",
593
634
  backgroundColor: "inherit",
594
- // these should be separate
635
+ // These should be separate.
595
636
  opacity: 0,
596
637
  transitionDelay: "0s",
597
638
  whiteSpace: "nowrap"
598
639
  },
599
- ".cm-ySelectionCaret:hover > .cm-ySelectionInfo": {
640
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
600
641
  opacity: 1,
601
642
  transitionDelay: "0s"
602
643
  }
@@ -604,25 +645,27 @@ var styles = EditorView.baseTheme({
604
645
 
605
646
  // packages/ui/react-ui-editor/src/extensions/basic.ts
606
647
  import { closeBrackets } from "@codemirror/autocomplete";
648
+ import { history } from "@codemirror/commands";
607
649
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
608
650
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
609
- import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
610
- var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
611
- EditorView2.lineWrapping,
612
- // 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 : [],
613
654
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
614
655
  bracketMatching(),
615
656
  closeBrackets(),
616
657
  drawSelection(),
617
- _placeholder && placeholder(_placeholder),
618
- // TODO(burdon): Is this required?
658
+ highlightActiveLine(),
659
+ history(),
660
+ _placeholder ? placeholder(_placeholder) : [],
661
+ // https://github.com/codemirror/theme-one-dark
619
662
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
620
- ].filter(Boolean);
663
+ ];
621
664
 
622
665
  // packages/ui/react-ui-editor/src/extensions/blast.ts
623
- import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
666
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
624
667
  import defaultsDeep from "lodash.defaultsdeep";
625
- import { invariant } from "@dxos/invariant";
668
+ import { invariant as invariant2 } from "@dxos/invariant";
626
669
  var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
627
670
  var defaultOptions = {
628
671
  effect: 2,
@@ -668,7 +711,7 @@ var blast = (options = defaultOptions) => {
668
711
  };
669
712
  return [
670
713
  // Cursor moved.
671
- EditorView3.updateListener.of((update2) => {
714
+ EditorView4.updateListener.of((update2) => {
672
715
  if (blaster?.node !== update2.view.scrollDOM) {
673
716
  if (blaster) {
674
717
  blaster.destroy();
@@ -749,7 +792,7 @@ var Blaster = class {
749
792
  return this._node;
750
793
  }
751
794
  initialize() {
752
- invariant(!this._canvas && !this._ctx, void 0, {
795
+ invariant2(!this._canvas && !this._ctx, void 0, {
753
796
  F: __dxlog_file2,
754
797
  L: 138,
755
798
  S: this,
@@ -786,7 +829,7 @@ var Blaster = class {
786
829
  }
787
830
  }
788
831
  start() {
789
- invariant(this._canvas && this._ctx, void 0, {
832
+ invariant2(this._canvas && this._ctx, void 0, {
790
833
  F: __dxlog_file2,
791
834
  L: 177,
792
835
  S: this,
@@ -930,7 +973,7 @@ var random = (min, max) => {
930
973
  };
931
974
 
932
975
  // packages/ui/react-ui-editor/src/extensions/code.ts
933
- 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";
934
977
  import { mx as mx2 } from "@dxos/react-ui-theme";
935
978
 
936
979
  // packages/ui/react-ui-editor/src/styles/cursors.ts
@@ -1021,7 +1064,7 @@ var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue)
1021
1064
 
1022
1065
  // packages/ui/react-ui-editor/src/extensions/code.ts
1023
1066
  var CODE_REGEX = /```[\s\S]*?```/gs;
1024
- var styles2 = EditorView4.baseTheme({
1067
+ var styles2 = EditorView5.baseTheme({
1025
1068
  "& .cm-codeblock": {
1026
1069
  fontFamily: getToken("fontFamily.mono", []).join(",")
1027
1070
  },
@@ -1055,20 +1098,18 @@ var getLineRange = (lines, from, to) => {
1055
1098
  ];
1056
1099
  };
1057
1100
  var code2 = () => {
1058
- const getDecorations = (view) => {
1101
+ const buildDecorations5 = (view) => {
1059
1102
  const decorations = [];
1060
- if (view.state.readOnly) {
1061
- const text = view.state.doc.sliceString(0);
1062
- const matches = text.matchAll(CODE_REGEX);
1063
- const blocks = view.viewportLineBlocks;
1064
- for (const match of matches) {
1065
- const range = getLineRange(blocks, match.index, match.index + match[0].length);
1066
- for (let i = range[0]; i <= range[1]; i++) {
1067
- const block = blocks[i];
1068
- decorations.push(Decoration2.line({
1069
- class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1070
- }).range(block.from));
1071
- }
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));
1072
1113
  }
1073
1114
  }
1074
1115
  return Decoration2.set(decorations);
@@ -1076,11 +1117,16 @@ var code2 = () => {
1076
1117
  return [
1077
1118
  ViewPlugin3.fromClass(class {
1078
1119
  constructor(view) {
1079
- this.decorations = getDecorations(view);
1120
+ this.hasFocus = false;
1121
+ this.decorations = buildDecorations5(view);
1080
1122
  }
1081
1123
  update(update2) {
1082
- if (update2.docChanged || update2.viewportChanged) {
1083
- this.decorations = getDecorations(update2.view);
1124
+ if (
1125
+ // TODO(burdon): Generalize for other extensions.
1126
+ this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1127
+ ) {
1128
+ this.hasFocus = update2.view.hasFocus;
1129
+ this.decorations = buildDecorations5(update2.view);
1084
1130
  }
1085
1131
  }
1086
1132
  }, {
@@ -1092,23 +1138,51 @@ var code2 = () => {
1092
1138
 
1093
1139
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1094
1140
  import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1095
- import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1141
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView6 } from "@codemirror/view";
1096
1142
  import sortBy from "lodash.sortby";
1143
+ import { useEffect as useEffect2 } from "react";
1097
1144
  import { debounce } from "@dxos/async";
1098
- import { invariant as invariant2 } from "@dxos/invariant";
1099
- import { log as log2 } from "@dxos/log";
1145
+ import { invariant as invariant3 } from "@dxos/invariant";
1100
1146
  import { nonNullable } from "@dxos/util";
1101
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1102
- var styles3 = EditorView5.baseTheme({
1103
- "& .cm-bookmark": {
1104
- cursor: "pointer",
1105
- margin: "4px",
1106
- padding: "4px",
1107
- backgroundColor: "yellow"
1108
- },
1109
- "& .cm-bookmark-selected": {
1110
- backgroundColor: "orange"
1111
- },
1147
+
1148
+ // packages/ui/react-ui-editor/src/util.ts
1149
+ import { log } from "@dxos/log";
1150
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1151
+ var callbackWrapper = (fn) => (...args) => {
1152
+ try {
1153
+ return fn(...args);
1154
+ } catch (err) {
1155
+ log.catch(err, void 0, {
1156
+ F: __dxlog_file3,
1157
+ L: 18,
1158
+ S: void 0,
1159
+ C: (f, a) => f(...a)
1160
+ });
1161
+ }
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
+ };
1182
+
1183
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
1184
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1185
+ var styles3 = EditorView6.baseTheme({
1112
1186
  "& .cm-comment": {
1113
1187
  backgroundColor: getToken("extend.colors.yellow.50")
1114
1188
  },
@@ -1124,49 +1198,59 @@ var marks = {
1124
1198
  class: "cm-comment-active"
1125
1199
  })
1126
1200
  };
1127
- var setFocus = (view, thread) => {
1128
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1129
- 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) {
1130
1204
  return;
1131
1205
  }
1132
- view.dispatch({
1133
- effects: setSelection.of({
1134
- active: thread
1135
- }),
1136
- selection: {
1137
- anchor: range.from
1138
- },
1139
- scrollIntoView: true
1140
- });
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
+ }
1141
1223
  };
1142
- var setCommentRange = StateEffect2.define();
1224
+ var setComments = StateEffect2.define();
1143
1225
  var setSelection = StateEffect2.define();
1144
1226
  var setCommentState = StateEffect2.define();
1145
- var commentsStateField = StateField2.define({
1227
+ var commentsState = StateField2.define({
1146
1228
  create: () => ({
1147
- ranges: []
1229
+ comments: [],
1230
+ selection: {}
1148
1231
  }),
1149
1232
  update: (value, tr) => {
1150
- const cursorConverter3 = tr.state.facet(CursorConverter);
1151
1233
  for (const effect of tr.effects) {
1152
1234
  if (effect.is(setSelection)) {
1153
1235
  return {
1154
1236
  ...value,
1155
- ...effect.value
1237
+ selection: effect.value
1156
1238
  };
1157
1239
  }
1158
- if (effect.is(setCommentRange)) {
1159
- const { comments: comments2 } = effect.value;
1160
- const ranges = comments2.map((comment) => {
1161
- const range = 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);
1162
1246
  return range && {
1163
- ...comment,
1164
- ...range
1247
+ comment,
1248
+ range
1165
1249
  };
1166
1250
  }).filter(nonNullable);
1167
1251
  return {
1168
1252
  ...value,
1169
- ranges
1253
+ comments: comments2
1170
1254
  };
1171
1255
  }
1172
1256
  if (effect.is(setCommentState)) {
@@ -1176,78 +1260,109 @@ var commentsStateField = StateField2.define({
1176
1260
  return value;
1177
1261
  }
1178
1262
  });
1179
- var highlightDecorations = EditorView5.decorations.compute([
1180
- commentsStateField
1263
+ var highlightDecorations = EditorView6.decorations.compute([
1264
+ commentsState
1181
1265
  ], (state) => {
1182
- const { active, ranges } = state.field(commentsStateField);
1183
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1184
- 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);
1185
1271
  return void 0;
1186
1272
  }
1187
- const range = {
1188
- from: selection.from,
1189
- to: selection.to
1190
- };
1191
- if (selection.id === active) {
1273
+ if (comment.comment.id === active) {
1192
1274
  return marks.highlightActive.range(range.from, range.to);
1193
1275
  } else {
1194
1276
  return marks.highlight.range(range.from, range.to);
1195
1277
  }
1196
- }).filter(nonNullable) ?? [];
1278
+ }).filter(nonNullable);
1197
1279
  return Decoration3.set(decorations);
1198
1280
  });
1199
- var BookmarkWidget = class extends WidgetType2 {
1200
- constructor(_anchor, _id, _handleClick) {
1201
- super();
1202
- this._anchor = _anchor;
1203
- this._id = _id;
1204
- this._handleClick = _handleClick;
1205
- invariant2(this._id, void 0, {
1206
- F: __dxlog_file3,
1207
- L: 168,
1208
- S: this,
1209
- A: [
1210
- "this._id",
1211
- ""
1212
- ]
1213
- });
1214
- }
1215
- eq(other) {
1216
- return this._anchor === other._anchor && this._id === other._id;
1217
- }
1218
- toDOM() {
1219
- const span = document.createElement("span");
1220
- span.className = "cm-bookmark";
1221
- span.textContent = "\u203B";
1222
- if (this._handleClick) {
1223
- span.onclick = () => this._handleClick();
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
+ };
1224
1298
  }
1225
- return span;
1226
- }
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
+ ];
1227
1334
  };
1228
1335
  var comments = (options = {}) => {
1336
+ const { key: shortcut = "meta-'" } = options;
1229
1337
  const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1230
1338
  const createCommentThread = (view) => {
1231
- const cursorConverter3 = view.state.facet(CursorConverter);
1232
- invariant2(options.onCreate, void 0, {
1233
- F: __dxlog_file3,
1234
- L: 224,
1339
+ invariant3(options.onCreate, void 0, {
1340
+ F: __dxlog_file4,
1341
+ L: 259,
1235
1342
  S: void 0,
1236
1343
  A: [
1237
1344
  "options.onCreate",
1238
1345
  ""
1239
1346
  ]
1240
1347
  });
1241
- const { head, from, to } = view.state.selection.main;
1348
+ const { from, to } = view.state.selection.main;
1242
1349
  if (from === to) {
1243
1350
  return false;
1244
1351
  }
1245
- const relPos = 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, {
1246
1361
  from,
1247
1362
  to
1248
1363
  });
1249
- if (relPos) {
1250
- const id = callbackWrapper(options.onCreate)(relPos);
1364
+ if (cursor) {
1365
+ const id = options.onCreate?.(cursor, view.coordsAtPos(from));
1251
1366
  if (id) {
1252
1367
  view.dispatch({
1253
1368
  effects: setSelection.of({
@@ -1257,53 +1372,13 @@ var comments = (options = {}) => {
1257
1372
  anchor: from
1258
1373
  }
1259
1374
  });
1260
- if (options.footnote) {
1261
- const tag = `[^${id}]`;
1262
- view.dispatch({
1263
- changes: {
1264
- from: head,
1265
- insert: tag
1266
- },
1267
- selection: {
1268
- anchor: head + tag.length
1269
- }
1270
- });
1271
- }
1272
1375
  return true;
1273
1376
  }
1274
1377
  }
1275
1378
  return false;
1276
1379
  };
1277
- const bookmarksViewPlugin = ViewPlugin4.fromClass(class BookmarkViewPlugin {
1278
- static {
1279
- // Match markdown footnotes (option).
1280
- // https://www.markdownguide.org/extended-syntax/#footnotes
1281
- this.bookmarkMatcher = new MatchDecorator({
1282
- regexp: /\[\^(\w+)\]/g,
1283
- decoration: (match, view, pos) => {
1284
- const id = match[1];
1285
- return Decoration3.replace({
1286
- id,
1287
- widget: new BookmarkWidget(pos, id)
1288
- });
1289
- }
1290
- });
1291
- }
1292
- constructor(view) {
1293
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
1294
- }
1295
- update(update2) {
1296
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update2, this.bookmarks);
1297
- }
1298
- }, {
1299
- decorations: (instance) => instance.bookmarks,
1300
- provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1301
- return view.plugin(plugin)?.bookmarks || Decoration3.none;
1302
- })
1303
- });
1304
1380
  return [
1305
- bookmarksViewPlugin,
1306
- commentsStateField,
1381
+ commentsState,
1307
1382
  highlightDecorations,
1308
1383
  styles3,
1309
1384
  //
@@ -1311,7 +1386,7 @@ var comments = (options = {}) => {
1311
1386
  //
1312
1387
  options.onCreate ? keymap3.of([
1313
1388
  {
1314
- key: options?.key ?? "meta-'",
1389
+ key: shortcut,
1315
1390
  run: callbackWrapper(createCommentThread)
1316
1391
  }
1317
1392
  ]) : [],
@@ -1328,7 +1403,7 @@ var comments = (options = {}) => {
1328
1403
  above: true,
1329
1404
  create: () => {
1330
1405
  const el = document.createElement("div");
1331
- options.onHover?.(el);
1406
+ options.onHover(el, shortcut);
1332
1407
  return {
1333
1408
  dom: el,
1334
1409
  offset: {
@@ -1341,156 +1416,154 @@ var comments = (options = {}) => {
1341
1416
  }
1342
1417
  return null;
1343
1418
  }, {
1344
- hideOnChange: true,
1419
+ // TODO(burdon): Hide on change triggered immediately?
1420
+ // hideOnChange: true,
1345
1421
  hoverTime: 1e3
1346
1422
  }) : [],
1347
1423
  //
1348
- // Monitor cursor movement and text updates.
1349
- // TODO(burdon): Is there a better (finer grained) way to do this?
1424
+ // Track deleted ranges and update ranges for decorations.
1350
1425
  //
1351
- EditorView5.updateListener.of(({ view, state, changes }) => {
1352
- const cursorConverter3 = view.state.facet(CursorConverter);
1353
- {
1354
- let mod = false;
1355
- const { active, ranges } = state.field(commentsStateField);
1356
- changes.iterChanges((from, to, from2, to2) => {
1357
- ranges.forEach((range) => {
1358
- if (from2 === to2) {
1359
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1360
- if (!newRange || newRange.to - newRange.from === 0) {
1361
- log2.info("deleted comment", {
1362
- thread: range.id
1363
- }, {
1364
- F: __dxlog_file3,
1365
- L: 358,
1366
- S: void 0,
1367
- C: (f, a) => f(...a)
1368
- });
1369
- }
1370
- }
1371
- if (from <= range.to) {
1372
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1373
- Object.assign(range, newRange);
1374
- 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);
1375
1435
  }
1376
- });
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
+ })
1377
1450
  });
1378
- if (mod) {
1379
- view.dispatch({
1380
- effects: setCommentState.of({
1381
- active,
1382
- ranges
1383
- })
1384
- });
1385
- }
1386
1451
  }
1387
- {
1388
- const { head } = state.selection.main;
1389
- let min = Infinity;
1390
- const { active, closest, ranges } = state.field(commentsStateField);
1391
- const selected = {
1392
- active: void 0,
1393
- closest: void 0
1394
- };
1395
- ranges.forEach((comment) => {
1396
- const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1397
- if (head >= comment.from && head <= comment.to) {
1398
- selected.active = comment.id;
1399
- }
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));
1400
1468
  if (d < min) {
1401
- selected.closest = comment.id;
1469
+ selection.closest = comment.id;
1402
1470
  min = d;
1403
1471
  }
1404
- });
1405
- if (selected.active !== active || selected.closest !== closest) {
1406
- view.dispatch({
1407
- effects: setSelection.of(selected)
1408
- });
1409
- handleSelect({
1410
- ...selected,
1411
- ranges: ranges.map((range) => ({
1412
- ...range,
1413
- location: view.coordsAtPos(range.from)
1414
- }))
1415
- });
1416
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
+ });
1417
1486
  }
1418
- })
1487
+ }),
1488
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
1419
1489
  ];
1420
1490
  };
1421
- var getCursorFromRange = (cursorConverter3, range) => {
1422
- const from = cursorConverter3.toCursor(range.from);
1423
- const to = cursorConverter3.toCursor(range.to, -1);
1424
- return [
1425
- from,
1426
- to
1427
- ].join(":");
1428
- };
1429
- var getRangeFromCursor = (cursorConverter3, cursor) => {
1430
- const parts = cursor.split(":");
1431
- const from = cursorConverter3.fromCursor(parts[0]);
1432
- const to = cursorConverter3.fromCursor(parts[1]);
1433
- return from !== void 0 && to !== void 0 ? {
1434
- from,
1435
- to
1436
- } : void 0;
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
+ ]);
1437
1502
  };
1438
1503
 
1439
1504
  // packages/ui/react-ui-editor/src/extensions/hr.ts
1440
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin5, WidgetType as WidgetType3 } from "@codemirror/view";
1441
- 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({
1442
1509
  "& .cm-hr": {
1443
- // TODO(burdon): ???
1444
1510
  // Note that block-level decorations should not have vertical margins,
1445
- borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1511
+ borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
1446
1512
  }
1447
1513
  });
1448
- var HorizontalRuleWidget = class extends WidgetType3 {
1449
- constructor(_pos) {
1450
- super();
1451
- this._pos = _pos;
1452
- }
1453
- eq(other) {
1454
- return this._pos === other._pos;
1455
- }
1456
- toDOM(view) {
1514
+ var HorizontalRuleWidget = class extends WidgetType2 {
1515
+ toDOM() {
1457
1516
  const el = document.createElement("div");
1458
1517
  el.className = "cm-hr";
1459
1518
  return el;
1460
1519
  }
1461
1520
  };
1462
- var placeholderMatcher = new MatchDecorator2({
1463
- regexp: /^---$/g,
1464
- decoration: (match, view, pos) => Decoration4.replace({
1465
- widget: new HorizontalRuleWidget(pos)
1466
- })
1521
+ var decoration = Decoration4.replace({
1522
+ widget: new HorizontalRuleWidget()
1467
1523
  });
1468
- var hr = () => [
1469
- styles4,
1470
- ViewPlugin5.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;
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
1482
1555
  })
1483
- })
1484
- ];
1556
+ ];
1557
+ };
1485
1558
 
1486
1559
  // packages/ui/react-ui-editor/src/extensions/image.ts
1487
- import { syntaxTree } from "@codemirror/language";
1560
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1488
1561
  import { StateField as StateField3 } from "@codemirror/state";
1489
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1562
+ import { Decoration as Decoration5, EditorView as EditorView8, WidgetType as WidgetType3 } from "@codemirror/view";
1490
1563
  var image = (options = {}) => {
1491
1564
  return StateField3.define({
1492
1565
  create: (state) => {
1493
- return Decoration5.set(buildDecorations(0, state.doc.length, state));
1566
+ return Decoration5.set(buildDecorations2(0, state.doc.length, state));
1494
1567
  },
1495
1568
  update: (value, tr) => {
1496
1569
  if (!tr.docChanged && !tr.selection) {
@@ -1510,16 +1583,16 @@ var image = (options = {}) => {
1510
1583
  filterFrom: from,
1511
1584
  filterTo: to,
1512
1585
  filter: () => false,
1513
- add: buildDecorations(from, to, tr.state)
1586
+ add: buildDecorations2(from, to, tr.state)
1514
1587
  });
1515
1588
  },
1516
- provide: (field) => EditorView7.decorations.from(field)
1589
+ provide: (field) => EditorView8.decorations.from(field)
1517
1590
  });
1518
1591
  };
1519
- var buildDecorations = (from, to, state) => {
1592
+ var buildDecorations2 = (from, to, state) => {
1520
1593
  const decorations = [];
1521
1594
  const cursor = state.selection.main.head;
1522
- syntaxTree(state).iterate({
1595
+ syntaxTree2(state).iterate({
1523
1596
  enter: (node) => {
1524
1597
  if (node.name === "Image") {
1525
1598
  const urlNode = node.node.getChild("URL");
@@ -1538,7 +1611,7 @@ var buildDecorations = (from, to, state) => {
1538
1611
  });
1539
1612
  return decorations;
1540
1613
  };
1541
- var ImageWidget = class extends WidgetType4 {
1614
+ var ImageWidget = class extends WidgetType3 {
1542
1615
  constructor(_url) {
1543
1616
  super();
1544
1617
  this._url = _url;
@@ -1556,19 +1629,19 @@ var ImageWidget = class extends WidgetType4 {
1556
1629
  };
1557
1630
 
1558
1631
  // 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 WidgetType5, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin6 } 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";
1562
1635
  import { tooltipContent } from "@dxos/react-ui-theme";
1563
1636
  var link = (options = {}) => {
1564
1637
  const extensions = [
1565
- ViewPlugin6.fromClass(class {
1638
+ ViewPlugin5.fromClass(class {
1566
1639
  constructor(view) {
1567
- this.decorations = buildDecorations2(view, options);
1640
+ this.decorations = buildDecorations3(view, options);
1568
1641
  }
1569
1642
  update(update2) {
1570
1643
  if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1571
- this.decorations = buildDecorations2(update2.view, options);
1644
+ this.decorations = buildDecorations3(update2.view, options);
1572
1645
  }
1573
1646
  }
1574
1647
  }, {
@@ -1581,7 +1654,7 @@ var link = (options = {}) => {
1581
1654
  // https://codemirror.net/docs/ref/#view.hoverTooltip
1582
1655
  // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1583
1656
  hoverTooltip2((view, pos, side) => {
1584
- const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1657
+ const syntax = syntaxTree3(view.state).resolveInner(pos, side);
1585
1658
  let link2 = null;
1586
1659
  for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1587
1660
  link2 = node.name === "Link" ? node : null;
@@ -1613,7 +1686,7 @@ var link = (options = {}) => {
1613
1686
  }
1614
1687
  return extensions;
1615
1688
  };
1616
- var LinkButton = class extends WidgetType5 {
1689
+ var LinkButton = class extends WidgetType4 {
1617
1690
  constructor(_url, _onAttach) {
1618
1691
  super();
1619
1692
  this._url = _url;
@@ -1628,7 +1701,7 @@ var LinkButton = class extends WidgetType5 {
1628
1701
  return el;
1629
1702
  }
1630
1703
  };
1631
- var LinkText = class extends WidgetType5 {
1704
+ var LinkText = class extends WidgetType4 {
1632
1705
  constructor(_text, _url) {
1633
1706
  super();
1634
1707
  this._text = _text;
@@ -1657,12 +1730,12 @@ var LinkText = class extends WidgetType5 {
1657
1730
  return link2;
1658
1731
  }
1659
1732
  };
1660
- var buildDecorations2 = (view, options) => {
1661
- const builder = new RangeSetBuilder();
1733
+ var buildDecorations3 = (view, options) => {
1734
+ const builder = new RangeSetBuilder2();
1662
1735
  const { state } = view;
1663
1736
  const cursor = state.selection.main.head;
1664
1737
  for (const { from, to } of view.visibleRanges) {
1665
- syntaxTree2(state).iterate({
1738
+ syntaxTree3(state).iterate({
1666
1739
  enter: (node) => {
1667
1740
  if (node.name === "Link") {
1668
1741
  const marks2 = node.node.getChildren("LinkMark");
@@ -1693,14 +1766,14 @@ var buildDecorations2 = (view, options) => {
1693
1766
  };
1694
1767
 
1695
1768
  // packages/ui/react-ui-editor/src/extensions/listener.ts
1696
- import { EditorView as EditorView8 } from "@codemirror/view";
1769
+ import { EditorView as EditorView9 } from "@codemirror/view";
1697
1770
  var listener = ({ onFocus, onChange }) => {
1698
1771
  const extensions = [];
1699
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1772
+ onFocus && extensions.push(EditorView9.focusChangeEffect.of((_, focused) => {
1700
1773
  onFocus(focused);
1701
1774
  return null;
1702
1775
  }));
1703
- onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1776
+ onChange && extensions.push(EditorView9.updateListener.of((update2) => {
1704
1777
  onChange(update2.state.doc.toString());
1705
1778
  }));
1706
1779
  return extensions;
@@ -1708,14 +1781,14 @@ var listener = ({ onFocus, onChange }) => {
1708
1781
 
1709
1782
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1710
1783
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1711
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1784
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1712
1785
  import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1713
1786
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1714
1787
  import { languages } from "@codemirror/language-data";
1715
1788
  import { searchKeymap } from "@codemirror/search";
1716
1789
  import { EditorState } from "@codemirror/state";
1717
1790
  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";
1791
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView10, highlightActiveLine as highlightActiveLine2, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1719
1792
 
1720
1793
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1721
1794
  import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
@@ -1728,7 +1801,6 @@ var markdownTags = {
1728
1801
  CodeText: Tag.define(),
1729
1802
  EmphasisMark: Tag.define(),
1730
1803
  HeaderMark: Tag.define(),
1731
- // HorizontalRule: Tag.define(),
1732
1804
  InlineCode: Tag.define(),
1733
1805
  LinkLabel: Tag.define(),
1734
1806
  LinkReference: Tag.define(),
@@ -1892,12 +1964,6 @@ var markdownHighlightStyle = (readonly) => {
1892
1964
  ],
1893
1965
  class: blockquote
1894
1966
  },
1895
- // TODO(burdon): Replace with extension.
1896
- // {
1897
- // tag: [markdownTags.HorizontalRule],
1898
- // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1899
- // },
1900
- // TODO(burdon): Only if being edited.
1901
1967
  {
1902
1968
  tag: [
1903
1969
  markdownTags.TableCell
@@ -1917,17 +1983,17 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1917
1983
  return [
1918
1984
  EditorState.allowMultipleSelections.of(true),
1919
1985
  EditorState.tabSize.of(2),
1920
- EditorView9.lineWrapping,
1921
- customKeymap(),
1986
+ EditorView10.lineWrapping,
1987
+ // https://github.com/codemirror/basic-setup
1922
1988
  bracketMatching2(),
1923
1989
  closeBrackets2(),
1924
1990
  crosshairCursor(),
1925
1991
  dropCursor(),
1926
1992
  drawSelection2(),
1927
- highlightActiveLine(),
1928
- history(),
1993
+ highlightActiveLine2(),
1994
+ history2(),
1929
1995
  indentOnInput(),
1930
- _placeholder && placeholder2(_placeholder),
1996
+ _placeholder ? placeholder2(_placeholder) : [],
1931
1997
  // Main extension.
1932
1998
  // https://github.com/codemirror/lang-markdown
1933
1999
  // https://codemirror.net/5/mode/markdown/index.html (demo).
@@ -1949,34 +2015,40 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1949
2015
  // https://github.com/codemirror/theme-one-dark
1950
2016
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1951
2017
  // Custom styles.
1952
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1953
- ].filter(Boolean);
1954
- };
1955
- var customKeymap = () => keymap4.of([
1956
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1957
- indentWithTab,
1958
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1959
- ...standardKeymap,
1960
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1961
- ...historyKeymap,
1962
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1963
- ...closeBracketsKeymap,
1964
- // https://codemirror.net/docs/ref/#search.searchKeymap
1965
- ...searchKeymap
1966
- ]);
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
+ };
1967
2033
 
1968
2034
  // packages/ui/react-ui-editor/src/extensions/mention.ts
1969
2035
  import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1970
2036
  var mention = ({ onSearch }) => {
1971
2037
  return autocompletion2({
2038
+ // TODO(burdon): Not working.
2039
+ activateOnTyping: true,
2040
+ // activateOnTypingDelay: 100,
2041
+ // selectOnOpen: true,
1972
2042
  closeOnBlur: false,
2043
+ // defaultKeymap: false,
2044
+ icons: false,
1973
2045
  override: [
1974
2046
  (context) => {
2047
+ console.log(context);
1975
2048
  const match = context.matchBefore(/@(\w+)?/);
1976
2049
  if (!match || match.from === match.to && !context.explicit) {
1977
2050
  return null;
1978
2051
  }
1979
- console.log(match);
1980
2052
  return {
1981
2053
  from: match.from,
1982
2054
  options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
@@ -1988,19 +2060,24 @@ var mention = ({ onSearch }) => {
1988
2060
  });
1989
2061
  };
1990
2062
 
2063
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
2064
+ var outliner = (options = {}) => {
2065
+ return [];
2066
+ };
2067
+
1991
2068
  // packages/ui/react-ui-editor/src/extensions/table.ts
1992
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1993
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1994
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType6 } 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";
1995
2072
  var table = (options = {}) => {
1996
2073
  return StateField4.define({
1997
2074
  create: (state) => update(state, options),
1998
2075
  update: (_, tr) => update(tr.state, options),
1999
- provide: (field) => EditorView10.decorations.from(field)
2076
+ provide: (field) => EditorView11.decorations.from(field)
2000
2077
  });
2001
2078
  };
2002
2079
  var update = (state, options) => {
2003
- const builder = new RangeSetBuilder2();
2080
+ const builder = new RangeSetBuilder3();
2004
2081
  const cursor = state.selection.main.head;
2005
2082
  const tables = [];
2006
2083
  const getTable = () => tables[tables.length - 1];
@@ -2008,7 +2085,7 @@ var update = (state, options) => {
2008
2085
  const table2 = getTable();
2009
2086
  return table2.rows?.[table2.rows.length - 1];
2010
2087
  };
2011
- syntaxTree3(state).iterate({
2088
+ syntaxTree4(state).iterate({
2012
2089
  enter: (node) => {
2013
2090
  switch (node.name) {
2014
2091
  case "Table": {
@@ -2049,7 +2126,7 @@ var update = (state, options) => {
2049
2126
  });
2050
2127
  return builder.finish();
2051
2128
  };
2052
- var TableWidget = class extends WidgetType6 {
2129
+ var TableWidget = class extends WidgetType5 {
2053
2130
  constructor(_table) {
2054
2131
  super();
2055
2132
  this._table = _table;
@@ -2087,85 +2164,98 @@ var TableWidget = class extends WidgetType6 {
2087
2164
  };
2088
2165
 
2089
2166
  // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2090
- import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType7, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin7 } from "@codemirror/view";
2091
- var styles5 = EditorView11.baseTheme({
2092
- "& .cm-task-item": {
2093
- paddingLeft: "4px",
2094
- 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"
2095
2177
  }
2096
2178
  });
2097
- var CheckboxWidget = class extends WidgetType7 {
2098
- constructor(_checked, _indent, _onCheck) {
2179
+ var CheckboxWidget = class extends WidgetType6 {
2180
+ constructor(_checked) {
2099
2181
  super();
2100
2182
  this._checked = _checked;
2101
- this._indent = _indent;
2102
- this._onCheck = _onCheck;
2103
2183
  }
2104
2184
  eq(other) {
2105
- return this._checked === other._checked && this._indent === other._indent;
2185
+ return this._checked === other._checked;
2106
2186
  }
2107
2187
  toDOM(view) {
2108
- const wrap = document.createElement("span");
2109
- wrap.className = "cm-task-item";
2110
- wrap.setAttribute("aria-hidden", "true");
2111
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
2112
- const input = wrap.appendChild(document.createElement("input"));
2188
+ const input = document.createElement("input");
2189
+ input.className = "cm-task-checkbox";
2113
2190
  input.type = "checkbox";
2114
2191
  input.checked = this._checked;
2115
- if (!this._onCheck) {
2192
+ if (view.state.readOnly) {
2116
2193
  input.setAttribute("disabled", "true");
2117
- }
2118
- if (this._onCheck) {
2119
- input.onchange = (event) => {
2120
- this._onCheck?.(event.target.checked);
2121
- 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
+ }
2122
2208
  };
2123
2209
  }
2124
- return wrap;
2210
+ return input;
2125
2211
  }
2126
2212
  ignoreEvent() {
2127
2213
  return false;
2128
2214
  }
2129
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
+ };
2130
2245
  var tasklist = (options = {}) => {
2131
- const taskMatcher = new MatchDecorator3({
2132
- regexp: /^(\s*)- \[([ xX])\]\s/g,
2133
- decoration: (match, view, pos) => {
2134
- const indent = Math.floor(match[1].length / 2);
2135
- const checked = match[2] === "x" || match[2] === "X";
2136
- return Decoration8.replace({
2137
- widget: new CheckboxWidget(checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2138
- const idx = pos + match[0].indexOf("[") + 1;
2139
- view.dispatch({
2140
- changes: {
2141
- from: idx,
2142
- to: idx + 1,
2143
- insert: checked2 ? "x" : " "
2144
- },
2145
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
2146
- selection: {
2147
- anchor: idx + 3
2148
- }
2149
- });
2150
- })
2151
- });
2152
- }
2153
- });
2154
- const tasks = ViewPlugin7.fromClass(class {
2155
- constructor(view) {
2156
- this.tasks = taskMatcher.createDeco(view);
2157
- }
2158
- update(update2) {
2159
- this.tasks = taskMatcher.updateDeco(update2, this.tasks);
2160
- }
2161
- }, {
2162
- decorations: (value) => value.tasks,
2163
- provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2164
- return view.plugin(plugin)?.tasks || Decoration8.none;
2165
- })
2166
- });
2167
2246
  return [
2168
- 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
+ }),
2169
2259
  styles5
2170
2260
  ];
2171
2261
  };
@@ -2244,7 +2334,7 @@ var cursorConverter2 = (text) => ({
2244
2334
 
2245
2335
  // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2246
2336
  var yjs = (content, awareness2) => [
2247
- CursorConverter.of(cursorConverter2(content)),
2337
+ Cursor.converter.of(cursorConverter2(content)),
2248
2338
  yCollab(content, awareness2)
2249
2339
  ];
2250
2340
 
@@ -2341,33 +2431,6 @@ var defaultTheme = {
2341
2431
  backgroundColor: get3(tokens, "extend.colors.yellow.700")
2342
2432
  },
2343
2433
  //
2344
- // collaboration
2345
- // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2346
- //
2347
- "&light .cm-ySelection, &light .cm-yLineSelection": {
2348
- mixBlendMode: "multiply"
2349
- },
2350
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2351
- mixBlendMode: "screen"
2352
- },
2353
- ".cm-ySelectionInfo": {
2354
- padding: "2px 4px",
2355
- marginBlockStart: "-4px"
2356
- },
2357
- ".cm-ySelection, & .cm-selectionMatch": {
2358
- paddingBlockStart: ".15em",
2359
- paddingBlockEnd: ".15em"
2360
- },
2361
- ".cm-ySelectionCaret": {
2362
- display: "inline-block",
2363
- insetBlockStart: ".1em",
2364
- blockSize: "1.4em",
2365
- verticalAlign: "top"
2366
- },
2367
- ".cm-yLineSelection": {
2368
- margin: 0
2369
- },
2370
- //
2371
2434
  // link
2372
2435
  //
2373
2436
  ".cm-link": {
@@ -2404,13 +2467,6 @@ var defaultTheme = {
2404
2467
  textDecoration: "none"
2405
2468
  },
2406
2469
  //
2407
- // widgets
2408
- //
2409
- ".cm-widgetBuffer": {
2410
- display: "none",
2411
- height: 0
2412
- },
2413
- //
2414
2470
  // table
2415
2471
  //
2416
2472
  ".cm-table *": {
@@ -2441,27 +2497,17 @@ var defaultTheme = {
2441
2497
  },
2442
2498
  //
2443
2499
  // font size
2444
- // TODO(thure): This appears to be the best or only way to set selection caret heights, but it's far more verbose than it needs to be.
2500
+ // TODO(thure): This appears to be the best or only way to set selection caret heights,
2501
+ // but it's far more verbose than it needs to be.
2445
2502
  //
2446
- ...Object.keys(get3(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2447
- const height = get3(tokens, [
2448
- "extend",
2449
- "fontSize",
2450
- fontSize,
2451
- 1,
2452
- "lineHeight"
2453
- ]);
2454
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2455
- height
2456
- };
2457
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2458
- height
2459
- };
2460
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2461
- height
2462
- };
2463
- return acc;
2464
- }, {}),
2503
+ // ...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
2504
+ // const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
2505
+ //
2506
+ // acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
2507
+ // acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
2508
+ // acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
2509
+ // return acc;
2510
+ // }, {}),
2465
2511
  /**
2466
2512
  * Panels
2467
2513
  * TODO(burdon): Needs styling attention (esp. dark mode).
@@ -2512,7 +2558,9 @@ var textTheme = {
2512
2558
  }
2513
2559
  };
2514
2560
  var markdownTheme = {
2515
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2561
+ ".cm-scroller": {
2562
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2563
+ },
2516
2564
  ".cm-placeholder": {
2517
2565
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2518
2566
  }
@@ -2527,95 +2575,90 @@ var codeTheme = {
2527
2575
  };
2528
2576
 
2529
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";
2530
2579
  var EditorModes = [
2531
2580
  "default",
2532
2581
  "vim"
2533
2582
  ];
2534
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
2535
- const { themeMode } = useThemeContext();
2583
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autofocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2536
2584
  const tabsterDOMAttribute = useFocusableGroup({
2537
2585
  tabBehavior: "limited"
2538
2586
  });
2539
- const [root, setRoot] = useState(null);
2540
- const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
2541
- useImperativeHandle(forwardedRef, () => ({
2542
- root,
2543
- state,
2587
+ const { themeMode } = useThemeContext2();
2588
+ const rootRef = useRef(null);
2589
+ const [view, setView] = useState(null);
2590
+ useImperativeHandle(forwardedRef, () => view, [
2544
2591
  view
2545
- }), [
2546
- view,
2547
- state,
2548
- root
2549
- ]);
2550
- const { awareness: awareness2, peer } = model;
2551
- useEffect(() => {
2552
- if (awareness2 && peer) {
2553
- awareness2.setLocalStateField("user", {
2554
- name: peer.name ?? generateName(peer.id),
2555
- color: getColorForValue({
2556
- value: peer.id,
2557
- type: "color"
2558
- }),
2559
- colorLight: getColorForValue({
2560
- value: peer.id,
2561
- themeMode,
2562
- type: "highlight"
2563
- })
2564
- });
2565
- }
2566
- }, [
2567
- awareness2,
2568
- peer,
2569
- themeMode
2570
2592
  ]);
2571
- useEffect(() => {
2572
- if (view && comments2?.length) {
2573
- view.dispatch({
2574
- effects: setCommentRange.of({
2575
- model,
2576
- comments: comments2
2577
- })
2578
- });
2593
+ useEffect3(() => {
2594
+ if (autofocus) {
2595
+ view?.focus();
2579
2596
  }
2580
2597
  }, [
2581
2598
  view,
2582
- comments2
2599
+ autofocus
2583
2600
  ]);
2584
- useEffect(() => {
2585
- if (!root) {
2601
+ useAwareness(model);
2602
+ useEffect3(() => {
2603
+ if (!model || !rootRef.current) {
2586
2604
  return;
2587
2605
  }
2588
- const state2 = EditorState2.create({
2606
+ const state = EditorState2.create({
2589
2607
  doc: model.text(),
2608
+ selection,
2590
2609
  extensions: [
2591
- readonly && EditorState2.readOnly.of(readonly),
2592
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2593
- 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
+ }),
2594
2619
  // Theme.
2595
- // TODO(burdon): Make theme configurable.
2596
- EditorView12.baseTheme(defaultTheme),
2597
- EditorView12.theme(slots?.editor?.theme ?? {}),
2598
- 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),
2599
2632
  // Storage and replication.
2633
+ // NOTE: This must come before user extensions.
2600
2634
  model.extension,
2635
+ // TODO(burdon): Factor out (requires special handling for Escape/focus).
2636
+ editorMode === "vim" && vim(),
2601
2637
  // Custom.
2602
2638
  ...extensions
2603
2639
  ].filter(Boolean)
2604
2640
  });
2605
- view?.destroy();
2606
- setEditor({
2607
- state: state2,
2608
- view: new EditorView12({
2609
- state: state2,
2610
- parent: root
2611
- })
2641
+ const newView = new EditorView13({
2642
+ state,
2643
+ parent: rootRef.current,
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
+ }
2612
2653
  });
2654
+ view?.destroy();
2655
+ setView(newView);
2613
2656
  return () => {
2614
- view?.destroy();
2615
- setEditor(void 0);
2657
+ newView?.destroy();
2658
+ setView(null);
2616
2659
  };
2617
2660
  }, [
2618
- root,
2661
+ rootRef,
2619
2662
  model,
2620
2663
  readonly,
2621
2664
  editorMode,
@@ -2625,11 +2668,13 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2625
2668
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2626
2669
  switch (key) {
2627
2670
  case "Enter": {
2628
- view?.contentDOM.focus();
2671
+ view?.focus();
2629
2672
  break;
2630
2673
  }
2631
2674
  case "Escape": {
2632
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2675
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
2676
+ rootRef.current?.focus();
2677
+ }
2633
2678
  break;
2634
2679
  }
2635
2680
  }
@@ -2639,65 +2684,66 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2639
2684
  ]);
2640
2685
  return /* @__PURE__ */ React.createElement("div", {
2641
2686
  key: model.id,
2642
- ref: setRoot,
2687
+ role: "none",
2643
2688
  tabIndex: 0,
2644
- ...slots?.root,
2689
+ onKeyUp: handleKeyUp,
2690
+ ...slots.root,
2645
2691
  ...editorMode !== "vim" && tabsterDOMAttribute,
2646
- onKeyUp: handleKeyUp
2692
+ ref: rootRef
2647
2693
  });
2648
2694
  });
2649
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2650
- const { themeMode } = useThemeContext();
2651
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2695
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, multiline, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
2696
+ const { themeMode } = useThemeContext2();
2697
+ const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2652
2698
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2653
2699
  ref: forwardedRef,
2654
2700
  readonly,
2655
2701
  extensions: [
2656
2702
  basicBundle({
2657
- readonly,
2658
2703
  themeMode,
2659
- placeholder: slots?.editor?.placeholder
2704
+ placeholder: placeholder3,
2705
+ multiline
2660
2706
  }),
2661
2707
  ...extensions
2662
2708
  ],
2663
- slots,
2709
+ theme,
2710
+ slots: updatedSlots,
2664
2711
  ...props
2665
2712
  });
2666
2713
  });
2667
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2668
- const { themeMode } = useThemeContext();
2669
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
2714
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
2715
+ const { themeMode } = useThemeContext2();
2716
+ const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2670
2717
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2671
2718
  ref: forwardedRef,
2672
2719
  readonly,
2673
2720
  extensions: [
2674
2721
  markdownBundle({
2675
- readonly,
2676
2722
  themeMode,
2677
- placeholder: slots?.editor?.placeholder
2723
+ readonly,
2724
+ placeholder: placeholder3
2678
2725
  }),
2679
2726
  ...extensions
2680
2727
  ],
2681
- slots,
2728
+ theme,
2729
+ slots: updatedSlots,
2682
2730
  ...props
2683
2731
  });
2684
2732
  });
2685
2733
  var defaultSlots = {
2686
2734
  root: {
2687
- className: mx3("p-2", 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"
2688
2740
  }
2689
2741
  };
2690
2742
  var defaultTextSlots = {
2691
- ...defaultSlots,
2692
- editor: {
2693
- theme: textTheme
2694
- }
2743
+ ...defaultSlots
2695
2744
  };
2696
2745
  var defaultMarkdownSlots = {
2697
- ...defaultSlots,
2698
- editor: {
2699
- theme: markdownTheme
2700
- }
2746
+ ...defaultSlots
2701
2747
  };
2702
2748
 
2703
2749
  // packages/ui/react-ui-editor/src/hooks/automerge.ts
@@ -2708,9 +2754,9 @@ import { isNotNullOrUndefined } from "@dxos/util";
2708
2754
  // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2709
2755
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2710
2756
  import { Context as Context2 } from "@dxos/context";
2711
- import { invariant as invariant3 } from "@dxos/invariant";
2757
+ import { invariant as invariant4 } from "@dxos/invariant";
2712
2758
  import { log as log3 } from "@dxos/log";
2713
- 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";
2714
2760
  var DEBOUNCE_INTERVAL = 100;
2715
2761
  var SpaceAwarenessProvider = class {
2716
2762
  constructor(params) {
@@ -2751,7 +2797,7 @@ var SpaceAwarenessProvider = class {
2751
2797
  log3.debug("failed to query awareness", {
2752
2798
  err
2753
2799
  }, {
2754
- F: __dxlog_file4,
2800
+ F: __dxlog_file6,
2755
2801
  L: 85,
2756
2802
  S: this,
2757
2803
  C: (f, a) => f(...a)
@@ -2764,8 +2810,8 @@ var SpaceAwarenessProvider = class {
2764
2810
  this._postTask = void 0;
2765
2811
  }
2766
2812
  update(position) {
2767
- invariant3(this._postTask, void 0, {
2768
- F: __dxlog_file4,
2813
+ invariant4(this._postTask, void 0, {
2814
+ F: __dxlog_file6,
2769
2815
  L: 96,
2770
2816
  S: this,
2771
2817
  A: [
@@ -2784,8 +2830,8 @@ var SpaceAwarenessProvider = class {
2784
2830
  return Array.from(this._remoteStates.values());
2785
2831
  }
2786
2832
  _handleQueryMessage() {
2787
- invariant3(this._postTask, void 0, {
2788
- F: __dxlog_file4,
2833
+ invariant4(this._postTask, void 0, {
2834
+ F: __dxlog_file6,
2789
2835
  L: 111,
2790
2836
  S: this,
2791
2837
  A: [
@@ -2796,8 +2842,8 @@ var SpaceAwarenessProvider = class {
2796
2842
  this._postTask.schedule();
2797
2843
  }
2798
2844
  _handlePostMessage(message) {
2799
- invariant3(message.kind === "post", void 0, {
2800
- F: __dxlog_file4,
2845
+ invariant4(message.kind === "post", void 0, {
2846
+ F: __dxlog_file6,
2801
2847
  L: 116,
2802
2848
  S: this,
2803
2849
  A: [
@@ -2810,25 +2856,63 @@ var SpaceAwarenessProvider = class {
2810
2856
  }
2811
2857
  };
2812
2858
 
2813
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2859
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
2814
2860
  import { StateField as StateField5 } from "@codemirror/state";
2815
- import { useEffect as useEffect2, useState as useState2 } from "react";
2816
- 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
+ };
2817
2903
 
2818
2904
  // packages/ui/react-ui-editor/src/hooks/yjs.ts
2819
2905
  import * as decoding from "lib0/decoding";
2820
2906
  import * as encoding from "lib0/encoding";
2821
2907
  import { Observable } from "lib0/observable";
2822
2908
  import * as YP from "y-protocols/awareness";
2823
- import * as Y2 from "yjs";
2824
- import { invariant as invariant4 } from "@dxos/invariant";
2909
+ import { invariant as invariant5 } from "@dxos/invariant";
2825
2910
  import { log as log4 } from "@dxos/log";
2826
- import { arrayToString as arrayToString2, stringToArray as stringToArray2 } from "@dxos/util";
2827
- 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";
2828
2912
  var createYjsModel = ({ identity, space, text }) => {
2829
- invariant4(text?.doc && text?.content, void 0, {
2830
- F: __dxlog_file5,
2831
- L: 23,
2913
+ invariant5(text?.doc && text?.content, void 0, {
2914
+ F: __dxlog_file7,
2915
+ L: 22,
2832
2916
  S: void 0,
2833
2917
  A: [
2834
2918
  "text?.doc && text?.content",
@@ -2844,34 +2928,6 @@ var createYjsModel = ({ identity, space, text }) => {
2844
2928
  id: text.doc.guid,
2845
2929
  content: text.content,
2846
2930
  text: () => text.content.toString(),
2847
- // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
2848
- // TODO(dmaretskyi): Refactor as cursor-converter.
2849
- getCursorFromRange: (range) => {
2850
- const from = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.from));
2851
- const to = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.to, -1));
2852
- return [
2853
- arrayToString2(from),
2854
- arrayToString2(to)
2855
- ].join(":");
2856
- },
2857
- getRangeFromCursor: (cursor) => {
2858
- invariant4(text.doc, void 0, {
2859
- F: __dxlog_file5,
2860
- L: 40,
2861
- S: void 0,
2862
- A: [
2863
- "text.doc",
2864
- ""
2865
- ]
2866
- });
2867
- const parts = cursor.split(":");
2868
- const from = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[0])), text.doc);
2869
- const to = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[1])), text.doc);
2870
- return from && to ? {
2871
- from: from.index,
2872
- to: to.index
2873
- } : void 0;
2874
- },
2875
2931
  extension: [
2876
2932
  modelState.init(() => model),
2877
2933
  yjs(text.content, provider?.awareness)
@@ -2920,8 +2976,8 @@ var YAwarenessProvider = class extends Observable {
2920
2976
  removed,
2921
2977
  origin
2922
2978
  }, {
2923
- F: __dxlog_file5,
2924
- L: 122,
2979
+ F: __dxlog_file7,
2980
+ L: 101,
2925
2981
  S: this,
2926
2982
  C: (f, a) => f(...a)
2927
2983
  });
@@ -2933,8 +2989,8 @@ var YAwarenessProvider = class extends Observable {
2933
2989
  }
2934
2990
  _handleSpaceMessage({ payload }) {
2935
2991
  log4("space message", payload, {
2936
- F: __dxlog_file5,
2937
- L: 131,
2992
+ F: __dxlog_file7,
2993
+ L: 110,
2938
2994
  S: this,
2939
2995
  C: (f, a) => f(...a)
2940
2996
  });
@@ -2978,14 +3034,12 @@ var YAwarenessProvider = class extends Observable {
2978
3034
  };
2979
3035
 
2980
3036
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2981
- var modelState = StateField5.define({
2982
- create: () => void 0,
2983
- update: (model) => model
2984
- });
3037
+ import { useEffect as useEffect4, useState as useState2 } from "react";
3038
+ import { isAutomergeObject } from "@dxos/react-client/echo";
2985
3039
  var useTextModel = (props) => {
2986
3040
  const { identity, space, text } = props;
2987
- const [model, setModel] = useState2(() => createModel(props));
2988
- useEffect2(() => setModel(createModel(props)), [
3041
+ const [model, setModel] = useState2();
3042
+ useEffect4(() => setModel(createModel(props)), [
2989
3043
  identity,
2990
3044
  space,
2991
3045
  text
@@ -3002,48 +3056,10 @@ var createModel = (props) => {
3002
3056
  return void 0;
3003
3057
  }
3004
3058
  };
3005
-
3006
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
3007
- var createAutomergeModel = ({ space, identity, text }) => {
3008
- const obj = text;
3009
- const doc = getRawDoc(obj, [
3010
- obj.field
3011
- ]);
3012
- const awarenessProvider = space && new SpaceAwarenessProvider({
3013
- space,
3014
- channel: `automerge.awareness.${obj.id}`,
3015
- info: {
3016
- displayName: identity?.profile?.displayName ?? "",
3017
- color: cursorColor.color,
3018
- lightColor: cursorColor.light
3019
- },
3020
- peerId: identity?.identityKey.toHex() ?? "Anonymous"
3021
- });
3022
- const model = {
3023
- id: obj.id,
3024
- content: doc,
3025
- text: () => get4(doc.handle.docSync(), doc.path),
3026
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
3027
- extension: [
3028
- modelState.init(() => model),
3029
- automerge3({
3030
- handle: doc.handle,
3031
- path: doc.path
3032
- }),
3033
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
3034
- awareness()
3035
- ].filter(isNotNullOrUndefined),
3036
- peer: identity ? {
3037
- id: identity.identityKey.toHex(),
3038
- name: identity.profile?.displayName
3039
- } : void 0
3040
- };
3041
- return model;
3042
- };
3043
3059
  export {
3044
3060
  AwarenessProvider,
3045
3061
  BaseTextEditor,
3046
- CursorConverter,
3062
+ Cursor,
3047
3063
  Doc,
3048
3064
  EditorModes,
3049
3065
  MarkdownEditor,
@@ -3065,7 +3081,6 @@ export {
3065
3081
  comments,
3066
3082
  createAutomergeModel,
3067
3083
  createYjsModel,
3068
- cursorConverter,
3069
3084
  defaultMarkdownSlots,
3070
3085
  defaultOptions,
3071
3086
  defaultSlots,
@@ -3076,6 +3091,7 @@ export {
3076
3091
  image,
3077
3092
  link,
3078
3093
  listener,
3094
+ logChanges,
3079
3095
  markdownBundle,
3080
3096
  markdownHighlightStyle,
3081
3097
  markdownTags,
@@ -3083,7 +3099,8 @@ export {
3083
3099
  markdownTheme,
3084
3100
  mention,
3085
3101
  modelState,
3086
- setCommentRange,
3102
+ outliner,
3103
+ setComments,
3087
3104
  setFocus,
3088
3105
  setSelection,
3089
3106
  table,
@@ -3091,6 +3108,8 @@ export {
3091
3108
  tasklist,
3092
3109
  textTheme,
3093
3110
  typewriter,
3111
+ useAwareness,
3112
+ useComments,
3094
3113
  useTextModel,
3095
3114
  yjs
3096
3115
  };