@dxos/react-ui-editor 0.3.11-main.008b7c6 → 0.3.11-main.00a28cb

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 (92) hide show
  1. package/dist/lib/browser/index.mjs +558 -629
  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 +12 -5
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +12 -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 +4 -4
  18. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  19. package/dist/types/src/extensions/automerge/semaphore.d.ts +4 -9
  20. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  21. package/dist/types/src/extensions/code.d.ts.map +1 -1
  22. package/dist/types/src/extensions/comments.d.ts +14 -5
  23. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  24. package/dist/types/src/{util → extensions}/cursor.d.ts +9 -1
  25. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  26. package/dist/types/src/extensions/hr.d.ts.map +1 -1
  27. package/dist/types/src/extensions/image.d.ts.map +1 -1
  28. package/dist/types/src/extensions/index.d.ts +2 -1
  29. package/dist/types/src/extensions/index.d.ts.map +1 -1
  30. package/dist/types/src/extensions/link.d.ts.map +1 -1
  31. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  32. package/dist/types/src/extensions/outliner.d.ts +4 -0
  33. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  34. package/dist/types/src/extensions/tasklist.d.ts +5 -1
  35. package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
  36. package/dist/types/src/extensions/yjs/cursor.d.ts +1 -1
  37. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -1
  38. package/dist/types/src/hooks/index.d.ts +1 -1
  39. package/dist/types/src/hooks/index.d.ts.map +1 -1
  40. package/dist/types/src/hooks/useTextModel.d.ts +0 -5
  41. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  42. package/dist/types/src/hooks/yjs.d.ts.map +1 -1
  43. package/dist/types/src/index.d.ts +3 -0
  44. package/dist/types/src/index.d.ts.map +1 -1
  45. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  46. package/dist/types/src/styles/tokens.d.ts +1 -0
  47. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  48. package/dist/types/src/themes/default.d.ts.map +1 -1
  49. package/dist/types/src/util.d.ts.map +1 -0
  50. package/package.json +25 -24
  51. package/src/components/TextEditor/MarkdownEditor.stories.tsx +41 -37
  52. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  53. package/src/components/TextEditor/TextEditor.tsx +71 -67
  54. package/src/components/TextEditor/comments.stories.tsx +355 -0
  55. package/src/extensions/autocomplete.ts +18 -17
  56. package/src/extensions/automerge/automerge.ts +12 -14
  57. package/src/extensions/automerge/cursor.ts +2 -1
  58. package/src/extensions/automerge/defs.ts +13 -9
  59. package/src/extensions/automerge/semaphore.ts +10 -12
  60. package/src/extensions/awareness.ts +27 -33
  61. package/src/extensions/code.ts +28 -23
  62. package/src/extensions/comments.ts +131 -144
  63. package/src/extensions/cursor.ts +46 -0
  64. package/src/extensions/hr.ts +10 -12
  65. package/src/extensions/image.ts +40 -22
  66. package/src/extensions/index.ts +2 -1
  67. package/src/extensions/link.ts +86 -88
  68. package/src/extensions/markdown/highlight.ts +3 -12
  69. package/src/extensions/outliner.ts +12 -0
  70. package/src/extensions/table.ts +10 -4
  71. package/src/extensions/tasklist.ts +54 -79
  72. package/src/extensions/yjs/cursor.ts +2 -1
  73. package/src/extensions/yjs/yjs.test.ts +1 -1
  74. package/src/extensions/yjs/yjs.ts +2 -2
  75. package/src/hooks/index.ts +2 -1
  76. package/src/hooks/useTextModel.ts +4 -7
  77. package/src/hooks/yjs.ts +1 -23
  78. package/src/index.ts +3 -0
  79. package/src/styles/markdown.ts +0 -2
  80. package/src/styles/tokens.ts +3 -0
  81. package/src/themes/default.ts +51 -78
  82. package/dist/types/src/extensions/mermaid.d.ts +0 -3
  83. package/dist/types/src/extensions/mermaid.d.ts.map +0 -1
  84. package/dist/types/src/util/cursor.d.ts.map +0 -1
  85. package/dist/types/src/util/index.d.ts +0 -3
  86. package/dist/types/src/util/index.d.ts.map +0 -1
  87. package/dist/types/src/util/util.d.ts.map +0 -1
  88. package/src/extensions/mermaid.ts +0 -11
  89. package/src/util/cursor.ts +0 -29
  90. package/src/util/index.ts +0 -6
  91. /package/dist/types/src/{util/util.d.ts → util.d.ts} +0 -0
  92. /package/src/{util/util.ts → util.ts} +0 -0
@@ -7,50 +7,51 @@ 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 EditorView13 } from "@codemirror/view";
10
+ import { EditorView as EditorView12 } 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";
14
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState, useRef } from "react";
15
15
  import { generateName } from "@dxos/display-name";
16
16
  import { useThemeContext } from "@dxos/react-ui";
17
17
  import { getColorForValue, 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";
21
+ import { markdownLanguage } from "@codemirror/lang-markdown";
21
22
  import { keymap } from "@codemirror/view";
22
23
  var autocomplete = ({ onSearch }) => {
23
24
  return [
24
25
  // https://codemirror.net/docs/ref/#view.keymap
25
26
  // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
27
+ // TODO(burdon): Set custom keymap.
26
28
  keymap.of(completionKeymap),
27
29
  // https://codemirror.net/examples/autocompletion
28
30
  // https://codemirror.net/docs/ref/#autocomplete.autocompletion
29
31
  autocompletion({
30
- // TODO(burdon): Set custom keymap.
31
- // defaultKeymap: false,
32
+ defaultKeymap: false,
32
33
  // Don't start unless key press.
33
- activateOnTyping: false,
34
- // TODO(burdon): Option to create new page?
35
- // TODO(burdon): Optional decoration via addToOptions
36
- override: [
37
- (context) => {
38
- const match = context.matchBefore(/\w*/);
39
- if (!match || match.from === match.to && !context.explicit) {
40
- return null;
41
- }
42
- return {
43
- from: match.from,
44
- options: onSearch(match.text.toLowerCase())
45
- };
34
+ activateOnTyping: false
35
+ }),
36
+ // TODO(burdon): Option to create new page?
37
+ // TODO(burdon): Optional decoration via addToOptions
38
+ markdownLanguage.data.of({
39
+ autocomplete: (context) => {
40
+ const match = context.matchBefore(/\w*/);
41
+ if (!match || match.from === match.to && !context.explicit) {
42
+ return null;
46
43
  }
47
- ]
44
+ return {
45
+ from: match.from,
46
+ options: onSearch(match.text.toLowerCase())
47
+ };
48
+ }
48
49
  })
49
50
  ];
50
51
  };
51
52
 
52
53
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
53
- import { Facet as Facet2, StateField } from "@codemirror/state";
54
+ import { StateField } from "@codemirror/state";
54
55
  import { ViewPlugin } from "@codemirror/view";
55
56
  import { next as A } from "@dxos/automerge/automerge";
56
57
 
@@ -91,7 +92,7 @@ var cursorConverter = (handle, path) => ({
91
92
  });
92
93
 
93
94
  // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
94
- import { Annotation, StateEffect } from "@codemirror/state";
95
+ import { Annotation, StateEffect, Facet } from "@codemirror/state";
95
96
  var effectType = StateEffect.define({});
96
97
  var updateHeads = (newHeads) => effectType.of({
97
98
  newHeads
@@ -100,6 +101,9 @@ var getLastHeads = (state, field) => state.field(field).lastHeads;
100
101
  var getPath = (state, field) => state.field(field).path;
101
102
  var reconcileAnnotationType = Annotation.define();
102
103
  var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
104
+ var semaphoreFacet = Facet.define({
105
+ combine: (values) => values.at(-1)
106
+ });
103
107
 
104
108
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
105
109
  import { next as automerge2 } from "@dxos/automerge/automerge";
@@ -236,11 +240,11 @@ var charPath = (textPath, candidatePath) => {
236
240
 
237
241
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
238
242
  var PatchSemaphore = class {
239
- constructor(_field) {
243
+ constructor(_handle, _field) {
244
+ this._handle = _handle;
240
245
  this._field = _field;
241
246
  this._inReconcile = false;
242
- this._queue = [];
243
- this.reconcile = (handle, view) => {
247
+ this.reconcile = (view) => {
244
248
  if (this._inReconcile) {
245
249
  return;
246
250
  }
@@ -258,11 +262,11 @@ var PatchSemaphore = class {
258
262
  annotations: reconcileAnnotationType.of(true)
259
263
  });
260
264
  }
261
- let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
265
+ let newHeads = updateAutomerge(this._field, this._handle, transactions, view.state);
262
266
  if (newHeads === null || newHeads === void 0) {
263
- newHeads = automerge2.getHeads(handle.docSync());
267
+ newHeads = automerge2.getHeads(this._handle.docSync());
264
268
  }
265
- const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(handle.docSync(), oldHeads, newHeads);
269
+ const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(this._handle.docSync(), oldHeads, newHeads);
266
270
  updateCodeMirror(view, selection, path, diff);
267
271
  view.dispatch({
268
272
  effects: updateHeads(newHeads),
@@ -273,36 +277,42 @@ var PatchSemaphore = class {
273
277
  }
274
278
  };
275
279
 
276
- // packages/ui/react-ui-editor/src/util/cursor.ts
277
- import { Facet } from "@codemirror/state";
278
- var DEFAULT_CURSOR_CONVERTER = {
280
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
281
+ import { Facet as Facet2 } from "@codemirror/state";
282
+ var defaultCursorConverter = {
279
283
  toCursor: (position) => position.toString(),
280
284
  fromCursor: (cursor) => parseInt(cursor)
281
285
  };
282
- var CursorConverter = Facet.define({
283
- combine: (providers) => providers[0] ?? DEFAULT_CURSOR_CONVERTER
284
- });
285
-
286
- // packages/ui/react-ui-editor/src/util/util.ts
287
- import { log } from "@dxos/log";
288
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util/util.ts";
289
- var callbackWrapper = (fn) => (...args) => {
290
- try {
291
- return fn(...args);
292
- } catch (error) {
293
- log.catch(error, void 0, {
294
- F: __dxlog_file,
295
- L: 16,
296
- S: void 0,
297
- C: (f, a) => f(...a)
286
+ var Cursor = class {
287
+ static {
288
+ this.converter = Facet2.define({
289
+ combine: (providers) => providers[0] ?? defaultCursorConverter
298
290
  });
299
291
  }
292
+ static {
293
+ this.getCursorFromRange = (cursorConverter3, range) => {
294
+ const from = cursorConverter3.toCursor(range.from);
295
+ const to = cursorConverter3.toCursor(range.to, -1);
296
+ return [
297
+ from,
298
+ to
299
+ ].join(":");
300
+ };
301
+ }
302
+ static {
303
+ this.getRangeFromCursor = (cursorConverter3, cursor) => {
304
+ const parts = cursor.split(":");
305
+ const from = cursorConverter3.fromCursor(parts[0]);
306
+ const to = cursorConverter3.fromCursor(parts[1]);
307
+ return from !== void 0 && to !== void 0 ? {
308
+ from,
309
+ to
310
+ } : void 0;
311
+ };
312
+ }
300
313
  };
301
314
 
302
315
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
303
- var semaphoreFacet = Facet2.define({
304
- combine: (values) => values.at(-1)
305
- });
306
316
  var automerge3 = ({ handle, path }) => {
307
317
  const stateField = StateField.define({
308
318
  create: () => ({
@@ -333,19 +343,19 @@ var automerge3 = ({ handle, path }) => {
333
343
  return result;
334
344
  }
335
345
  });
336
- const semaphore = new PatchSemaphore(stateField);
337
- const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
346
+ const semaphore = new PatchSemaphore(handle, stateField);
347
+ const viewPlugin = ViewPlugin.fromClass(class AutomergeViewPlugin {
338
348
  constructor(_view) {
339
349
  this._view = _view;
340
350
  this._handleChange = () => {
341
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
351
+ this._view.state.facet(semaphoreFacet).reconcile(this._view);
342
352
  };
343
353
  handle.addListener("change", this._handleChange);
344
354
  }
345
- update(update4) {
346
- if (update4.transactions.length > 0 && update4.transactions.some((tr) => !isReconcileTx(tr))) {
355
+ update(update2) {
356
+ if (update2.transactions.length > 0 && update2.transactions.some((tr) => !isReconcileTx(tr))) {
347
357
  queueMicrotask(() => {
348
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
358
+ this._view.state.facet(semaphoreFacet).reconcile(this._view);
349
359
  });
350
360
  }
351
361
  }
@@ -355,7 +365,7 @@ var automerge3 = ({ handle, path }) => {
355
365
  });
356
366
  return {
357
367
  extension: [
358
- CursorConverter.of(cursorConverter(handle, path)),
368
+ Cursor.converter.of(cursorConverter(handle, path)),
359
369
  semaphoreFacet.of(semaphore),
360
370
  stateField,
361
371
  viewPlugin
@@ -397,7 +407,7 @@ var RemoteSelectionsDecorator = class {
397
407
  this._ctx = new Context();
398
408
  this._lastAnchor = void 0;
399
409
  this._lastHead = void 0;
400
- this._cursorConverter = view.state.facet(CursorConverter);
410
+ this._cursorConverter = view.state.facet(Cursor.converter);
401
411
  this._provider = view.state.facet(AwarenessProvider);
402
412
  this._provider.open();
403
413
  this._provider.remoteStateChange.on(this._ctx, () => {
@@ -412,13 +422,13 @@ var RemoteSelectionsDecorator = class {
412
422
  void this._ctx.dispose();
413
423
  this._provider.close();
414
424
  }
415
- update(update4) {
416
- this._updateLocalSelection(update4);
417
- this._updateRemoteSelections(update4);
425
+ update(update2) {
426
+ this._updateLocalSelection(update2);
427
+ this._updateRemoteSelections(update2);
418
428
  }
419
- _updateLocalSelection(update4) {
420
- const hasFocus = update4.view.hasFocus && update4.view.dom.ownerDocument.hasFocus();
421
- const { anchor = void 0, head = void 0 } = hasFocus ? update4.state.selection.main : {};
429
+ _updateLocalSelection(update2) {
430
+ const hasFocus = update2.view.hasFocus && update2.view.dom.ownerDocument.hasFocus();
431
+ const { anchor = void 0, head = void 0 } = hasFocus ? update2.state.selection.main : {};
422
432
  if (this._lastAnchor === anchor && this._lastHead === head) {
423
433
  return;
424
434
  }
@@ -429,7 +439,7 @@ var RemoteSelectionsDecorator = class {
429
439
  head: this._cursorConverter.toCursor(head)
430
440
  } : void 0);
431
441
  }
432
- _updateRemoteSelections(update4) {
442
+ _updateRemoteSelections(update2) {
433
443
  const decorations = [];
434
444
  const awarenessStates = this._provider.getRemoteStates();
435
445
  for (const state of awarenessStates) {
@@ -438,10 +448,10 @@ var RemoteSelectionsDecorator = class {
438
448
  if (anchor == null || head == null) {
439
449
  continue;
440
450
  }
441
- const start = Math.min(Math.min(anchor, head), update4.view.state.doc.length);
442
- const end = Math.min(Math.max(anchor, head), update4.view.state.doc.length);
443
- const startLine = update4.view.state.doc.lineAt(start);
444
- const endLine = update4.view.state.doc.lineAt(end);
451
+ const start = Math.min(Math.min(anchor, head), update2.view.state.doc.length);
452
+ const end = Math.min(Math.max(anchor, head), update2.view.state.doc.length);
453
+ const startLine = update2.view.state.doc.lineAt(start);
454
+ const endLine = update2.view.state.doc.lineAt(end);
445
455
  const color = state.info.color ?? "#30bced";
446
456
  const lightColor = state.info.lightColor ?? color + "33";
447
457
  if (startLine.number === endLine.number) {
@@ -452,7 +462,7 @@ var RemoteSelectionsDecorator = class {
452
462
  attributes: {
453
463
  style: `background-color: ${lightColor}`
454
464
  },
455
- class: "cm-ySelection"
465
+ class: "cm-collab-selection"
456
466
  })
457
467
  });
458
468
  } else {
@@ -463,7 +473,7 @@ var RemoteSelectionsDecorator = class {
463
473
  attributes: {
464
474
  style: `background-color: ${color}`
465
475
  },
466
- class: "cm-ySelection"
476
+ class: "cm-collab-selection"
467
477
  })
468
478
  });
469
479
  decorations.push({
@@ -473,18 +483,18 @@ var RemoteSelectionsDecorator = class {
473
483
  attributes: {
474
484
  style: `background-color: ${color}`
475
485
  },
476
- class: "cm-ySelection"
486
+ class: "cm-collab-selection"
477
487
  })
478
488
  });
479
489
  for (let i = startLine.number + 1; i < endLine.number; i++) {
480
- const linePos = update4.view.state.doc.line(i).from;
490
+ const linePos = update2.view.state.doc.line(i).from;
481
491
  decorations.push({
482
492
  from: linePos,
483
493
  to: linePos,
484
494
  value: Decoration.line({
485
495
  attributes: {
486
496
  style: `background-color: ${color}`,
487
- class: "cm-yLineSelection"
497
+ class: "cm-collab-selectionLine"
488
498
  }
489
499
  })
490
500
  });
@@ -504,23 +514,21 @@ var RemoteSelectionsDecorator = class {
504
514
  }
505
515
  };
506
516
  var RemoteCaretWidget = class extends WidgetType {
507
- constructor(name, color) {
517
+ constructor(_name, _color) {
508
518
  super();
509
- this.name = name;
510
- this.color = color;
511
- this.name = name;
512
- this.color = color;
519
+ this._name = _name;
520
+ this._color = _color;
513
521
  }
514
522
  toDOM() {
515
523
  const span = document.createElement("span");
516
- span.className = "cm-ySelectionCaret";
517
- span.style.backgroundColor = this.color;
518
- span.style.borderColor = this.color;
524
+ span.className = "cm-collab-selectionCaret";
525
+ span.style.backgroundColor = this._color;
526
+ span.style.borderColor = this._color;
519
527
  const dot = document.createElement("div");
520
- dot.className = "cm-ySelectionCaretDot";
528
+ dot.className = "cm-collab-selectionCaretDot";
521
529
  const info = document.createElement("div");
522
- info.className = "cm-ySelectionInfo";
523
- info.innerText = this.name;
530
+ info.className = "cm-collab-selectionInfo";
531
+ info.innerText = this._name;
524
532
  span.appendChild(document.createTextNode("\u2060"));
525
533
  span.appendChild(dot);
526
534
  span.appendChild(document.createTextNode("\u2060"));
@@ -528,15 +536,12 @@ var RemoteCaretWidget = class extends WidgetType {
528
536
  span.appendChild(document.createTextNode("\u2060"));
529
537
  return span;
530
538
  }
531
- eq(widget) {
532
- return widget.color === this.color;
533
- }
534
- compare(widget) {
535
- return widget.color === this.color;
536
- }
537
539
  updateDOM() {
538
540
  return false;
539
541
  }
542
+ eq(widget) {
543
+ return widget._color === this._color;
544
+ }
540
545
  get estimatedHeight() {
541
546
  return -1;
542
547
  }
@@ -545,12 +550,12 @@ var RemoteCaretWidget = class extends WidgetType {
545
550
  }
546
551
  };
547
552
  var styles = EditorView.baseTheme({
548
- ".cm-ySelection": {},
549
- ".cm-yLineSelection": {
553
+ ".cm-collab-selection": {},
554
+ ".cm-collab-selectionLine": {
550
555
  padding: 0,
551
556
  margin: "0px 2px 0px 4px"
552
557
  },
553
- ".cm-ySelectionCaret": {
558
+ ".cm-collab-selectionCaret": {
554
559
  position: "relative",
555
560
  borderLeft: "1px solid black",
556
561
  borderRight: "1px solid black",
@@ -559,7 +564,7 @@ var styles = EditorView.baseTheme({
559
564
  boxSizing: "border-box",
560
565
  display: "inline"
561
566
  },
562
- ".cm-ySelectionCaretDot": {
567
+ ".cm-collab-selectionCaretDot": {
563
568
  borderRadius: "50%",
564
569
  position: "absolute",
565
570
  width: ".4em",
@@ -570,11 +575,11 @@ var styles = EditorView.baseTheme({
570
575
  transition: "transform .3s ease-in-out",
571
576
  boxSizing: "border-box"
572
577
  },
573
- ".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot": {
578
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
574
579
  transformOrigin: "bottom center",
575
580
  transform: "scale(0)"
576
581
  },
577
- ".cm-ySelectionInfo": {
582
+ ".cm-collab-selectionInfo": {
578
583
  position: "absolute",
579
584
  top: "-1.05em",
580
585
  left: "-1px",
@@ -590,12 +595,12 @@ var styles = EditorView.baseTheme({
590
595
  zIndex: 101,
591
596
  transition: "opacity .3s ease-in-out",
592
597
  backgroundColor: "inherit",
593
- // these should be separate
598
+ // These should be separate.
594
599
  opacity: 0,
595
600
  transitionDelay: "0s",
596
601
  whiteSpace: "nowrap"
597
602
  },
598
- ".cm-ySelectionCaret:hover > .cm-ySelectionInfo": {
603
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
599
604
  opacity: 1,
600
605
  transitionDelay: "0s"
601
606
  }
@@ -622,7 +627,7 @@ var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
622
627
  import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
623
628
  import defaultsDeep from "lodash.defaultsdeep";
624
629
  import { invariant } from "@dxos/invariant";
625
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
630
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
626
631
  var defaultOptions = {
627
632
  effect: 2,
628
633
  maxParticles: 200,
@@ -667,12 +672,12 @@ var blast = (options = defaultOptions) => {
667
672
  };
668
673
  return [
669
674
  // Cursor moved.
670
- EditorView3.updateListener.of((update4) => {
671
- if (blaster?.node !== update4.view.scrollDOM) {
675
+ EditorView3.updateListener.of((update2) => {
676
+ if (blaster?.node !== update2.view.scrollDOM) {
672
677
  if (blaster) {
673
678
  blaster.destroy();
674
679
  }
675
- blaster = new Blaster(update4.view.scrollDOM, defaultsDeep({
680
+ blaster = new Blaster(update2.view.scrollDOM, defaultsDeep({
676
681
  particleGravity: 0.2,
677
682
  particleShrinkRate: 0.995,
678
683
  color: () => [
@@ -686,10 +691,10 @@ var blast = (options = defaultOptions) => {
686
691
  } else {
687
692
  blaster.resize();
688
693
  }
689
- const current = update4.state.selection.main.head;
694
+ const current = update2.state.selection.main.head;
690
695
  if (current !== last) {
691
696
  last = current;
692
- const { element, point } = getPoint(update4.view, current - 1);
697
+ const { element, point } = getPoint(update2.view, current - 1);
693
698
  if (element && point) {
694
699
  blaster.spawn({
695
700
  element,
@@ -749,7 +754,7 @@ var Blaster = class {
749
754
  }
750
755
  initialize() {
751
756
  invariant(!this._canvas && !this._ctx, void 0, {
752
- F: __dxlog_file2,
757
+ F: __dxlog_file,
753
758
  L: 138,
754
759
  S: this,
755
760
  A: [
@@ -786,7 +791,7 @@ var Blaster = class {
786
791
  }
787
792
  start() {
788
793
  invariant(this._canvas && this._ctx, void 0, {
789
- F: __dxlog_file2,
794
+ F: __dxlog_file,
790
795
  L: 177,
791
796
  S: this,
792
797
  A: [
@@ -930,7 +935,6 @@ var random = (min, max) => {
930
935
 
931
936
  // packages/ui/react-ui-editor/src/extensions/code.ts
932
937
  import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
933
- import get2 from "lodash.get";
934
938
  import { mx as mx2 } from "@dxos/react-ui-theme";
935
939
 
936
940
  // packages/ui/react-ui-editor/src/styles/cursors.ts
@@ -1014,20 +1018,22 @@ var inlineUrl = mx(code, "px-1");
1014
1018
  var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
1015
1019
 
1016
1020
  // packages/ui/react-ui-editor/src/styles/tokens.ts
1021
+ import get2 from "lodash.get";
1017
1022
  import { tailwindConfig } from "@dxos/react-ui-theme";
1018
1023
  var tokens = tailwindConfig({}).theme;
1024
+ var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue);
1019
1025
 
1020
1026
  // packages/ui/react-ui-editor/src/extensions/code.ts
1021
1027
  var CODE_REGEX = /```[\s\S]*?```/gs;
1022
1028
  var styles2 = EditorView4.baseTheme({
1023
1029
  "& .cm-codeblock": {
1024
- fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
1030
+ fontFamily: getToken("fontFamily.mono", []).join(",")
1025
1031
  },
1026
1032
  '&light [aria-readonly="true"] .cm-codeblock': {
1027
- background: get2(tokens, "extend.colors.neutral.50")
1033
+ background: getToken("extend.colors.neutral.50")
1028
1034
  },
1029
1035
  '&dark [aria-readonly="true"] .cm-codeblock': {
1030
- background: get2(tokens, "extend.colors.neutral.850")
1036
+ background: getToken("extend.colors.neutral.850")
1031
1037
  },
1032
1038
  '& [aria-readonly="true"] .cm-codeblock': {
1033
1039
  display: "block",
@@ -1053,20 +1059,18 @@ var getLineRange = (lines, from, to) => {
1053
1059
  ];
1054
1060
  };
1055
1061
  var code2 = () => {
1056
- const getDecorations = (view) => {
1062
+ const buildDecorations4 = (view) => {
1057
1063
  const decorations = [];
1058
- if (view.state.readOnly) {
1059
- const text = view.state.doc.sliceString(0);
1060
- const matches = text.matchAll(CODE_REGEX);
1061
- const blocks = view.viewportLineBlocks;
1062
- for (const match of matches) {
1063
- const range = getLineRange(blocks, match.index, match.index + match[0].length);
1064
- for (let i = range[0]; i <= range[1]; i++) {
1065
- const block = blocks[i];
1066
- decorations.push(Decoration2.line({
1067
- class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1068
- }).range(block.from));
1069
- }
1064
+ const text = view.state.doc.sliceString(0);
1065
+ const matches = text.matchAll(CODE_REGEX);
1066
+ const blocks = view.viewportLineBlocks;
1067
+ for (const match of matches) {
1068
+ const range = getLineRange(blocks, match.index, match.index + match[0].length);
1069
+ for (let i = range[0]; i <= range[1]; i++) {
1070
+ const block = blocks[i];
1071
+ decorations.push(Decoration2.line({
1072
+ class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1073
+ }).range(block.from));
1070
1074
  }
1071
1075
  }
1072
1076
  return Decoration2.set(decorations);
@@ -1074,11 +1078,16 @@ var code2 = () => {
1074
1078
  return [
1075
1079
  ViewPlugin3.fromClass(class {
1076
1080
  constructor(view) {
1077
- this.decorations = getDecorations(view);
1081
+ this.hasFocus = false;
1082
+ this.decorations = buildDecorations4(view);
1078
1083
  }
1079
- update(update4) {
1080
- if (update4.docChanged || update4.viewportChanged) {
1081
- this.decorations = getDecorations(update4.view);
1084
+ update(update2) {
1085
+ if (
1086
+ // TODO(burdon): Generalize for other extensions.
1087
+ this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1088
+ ) {
1089
+ this.hasFocus = update2.view.hasFocus;
1090
+ this.decorations = buildDecorations4(update2.view);
1082
1091
  }
1083
1092
  }
1084
1093
  }, {
@@ -1090,29 +1099,36 @@ var code2 = () => {
1090
1099
 
1091
1100
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1092
1101
  import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1093
- import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1094
- import get3 from "lodash.get";
1102
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
1095
1103
  import sortBy from "lodash.sortby";
1096
1104
  import { debounce } from "@dxos/async";
1097
1105
  import { invariant as invariant2 } from "@dxos/invariant";
1098
- import { log as log2 } from "@dxos/log";
1099
1106
  import { nonNullable } from "@dxos/util";
1107
+
1108
+ // packages/ui/react-ui-editor/src/util.ts
1109
+ import { log } from "@dxos/log";
1110
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1111
+ var callbackWrapper = (fn) => (...args) => {
1112
+ try {
1113
+ return fn(...args);
1114
+ } catch (error) {
1115
+ log.catch(error, void 0, {
1116
+ F: __dxlog_file2,
1117
+ L: 16,
1118
+ S: void 0,
1119
+ C: (f, a) => f(...a)
1120
+ });
1121
+ }
1122
+ };
1123
+
1124
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
1100
1125
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1101
1126
  var styles3 = EditorView5.baseTheme({
1102
- "& .cm-bookmark": {
1103
- cursor: "pointer",
1104
- margin: "4px",
1105
- padding: "4px",
1106
- backgroundColor: "yellow"
1107
- },
1108
- "& .cm-bookmark-selected": {
1109
- backgroundColor: "orange"
1110
- },
1111
1127
  "& .cm-comment": {
1112
- backgroundColor: get3(tokens, "extend.colors.yellow.50")
1128
+ backgroundColor: getToken("extend.colors.yellow.50")
1113
1129
  },
1114
1130
  "& .cm-comment-active": {
1115
- backgroundColor: get3(tokens, "extend.colors.yellow.100")
1131
+ backgroundColor: getToken("extend.colors.yellow.100")
1116
1132
  }
1117
1133
  });
1118
1134
  var marks = {
@@ -1146,7 +1162,7 @@ var commentsStateField = StateField2.define({
1146
1162
  ranges: []
1147
1163
  }),
1148
1164
  update: (value, tr) => {
1149
- const cursorConverter3 = tr.state.facet(CursorConverter);
1165
+ const cursorConverter3 = tr.state.facet(Cursor.converter);
1150
1166
  for (const effect of tr.effects) {
1151
1167
  if (effect.is(setSelection)) {
1152
1168
  return {
@@ -1157,7 +1173,7 @@ var commentsStateField = StateField2.define({
1157
1173
  if (effect.is(setCommentRange)) {
1158
1174
  const { comments: comments2 } = effect.value;
1159
1175
  const ranges = comments2.map((comment) => {
1160
- const range = getRangeFromCursor(cursorConverter3, comment.cursor);
1176
+ const range = Cursor.getRangeFromCursor(cursorConverter3, comment.cursor);
1161
1177
  return range && {
1162
1178
  ...comment,
1163
1179
  ...range
@@ -1195,58 +1211,89 @@ var highlightDecorations = EditorView5.decorations.compute([
1195
1211
  }).filter(nonNullable) ?? [];
1196
1212
  return Decoration3.set(decorations);
1197
1213
  });
1198
- var BookmarkWidget = class extends WidgetType2 {
1199
- constructor(_anchor, _id, _handleClick) {
1200
- super();
1201
- this._anchor = _anchor;
1202
- this._id = _id;
1203
- this._handleClick = _handleClick;
1204
- invariant2(this._id, void 0, {
1205
- F: __dxlog_file3,
1206
- L: 169,
1207
- S: this,
1208
- A: [
1209
- "this._id",
1210
- ""
1211
- ]
1212
- });
1213
- }
1214
- eq(other) {
1215
- return this._anchor === other._anchor && this._id === other._id;
1216
- }
1217
- toDOM() {
1218
- const span = document.createElement("span");
1219
- span.className = "cm-bookmark";
1220
- span.textContent = "\u203B";
1221
- if (this._handleClick) {
1222
- span.onclick = () => this._handleClick();
1214
+ var trackPastedComments = (onUpdate) => {
1215
+ let tracked = null;
1216
+ const handleTrack = (event, view) => {
1217
+ const comments2 = view.state.field(commentsStateField);
1218
+ const { main } = view.state.selection;
1219
+ const selectedRanges = comments2.ranges.filter((range) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1220
+ if (!selectedRanges.length) {
1221
+ tracked = null;
1222
+ } else {
1223
+ tracked = {
1224
+ text: view.state.doc.slice(main.from, main.to),
1225
+ comments: selectedRanges.map((range) => ({
1226
+ id: range.id,
1227
+ from: range.from - main.from,
1228
+ to: range.to - main.from
1229
+ }))
1230
+ };
1223
1231
  }
1224
- return span;
1225
- }
1232
+ };
1233
+ return [
1234
+ EditorView5.domEventHandlers({
1235
+ cut: handleTrack,
1236
+ copy: handleTrack
1237
+ }),
1238
+ // Handle paste.
1239
+ EditorView5.updateListener.of(({ view, state, transactions }) => {
1240
+ if (tracked) {
1241
+ const paste = transactions.find((tr) => tr.isUserEvent("input.paste"));
1242
+ if (paste) {
1243
+ let found = -1;
1244
+ paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1245
+ if (text.eq(tracked.text)) {
1246
+ for (let i = transactions.indexOf(paste) + 1; i < transactions.length; i++) {
1247
+ fromB = transactions[i].changes.mapPos(fromB);
1248
+ }
1249
+ found = fromB;
1250
+ }
1251
+ });
1252
+ if (found > -1) {
1253
+ const comments2 = tracked.comments;
1254
+ view.state.facet(semaphoreFacet).reconcile(view);
1255
+ const active = state.field(commentsStateField).ranges;
1256
+ for (const moved of comments2) {
1257
+ if (active.some((range) => range.id === moved.id && range.from === range.to)) {
1258
+ const range = {
1259
+ from: found + moved.from,
1260
+ to: found + moved.to
1261
+ };
1262
+ const cursor = Cursor.getCursorFromRange(state.facet(Cursor.converter), range);
1263
+ onUpdate(moved.id, cursor);
1264
+ }
1265
+ }
1266
+ }
1267
+ tracked = null;
1268
+ }
1269
+ }
1270
+ })
1271
+ ];
1226
1272
  };
1227
1273
  var comments = (options = {}) => {
1274
+ const { key: shortcut = "meta-'" } = options;
1228
1275
  const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1229
1276
  const createCommentThread = (view) => {
1230
- const cursorConverter3 = view.state.facet(CursorConverter);
1277
+ const cursorConverter3 = view.state.facet(Cursor.converter);
1231
1278
  invariant2(options.onCreate, void 0, {
1232
1279
  F: __dxlog_file3,
1233
- L: 225,
1280
+ L: 264,
1234
1281
  S: void 0,
1235
1282
  A: [
1236
1283
  "options.onCreate",
1237
1284
  ""
1238
1285
  ]
1239
1286
  });
1240
- const { head, from, to } = view.state.selection.main;
1287
+ const { from, to } = view.state.selection.main;
1241
1288
  if (from === to) {
1242
1289
  return false;
1243
1290
  }
1244
- const relPos = getCursorFromRange(cursorConverter3, {
1291
+ const cursor = Cursor.getCursorFromRange(cursorConverter3, {
1245
1292
  from,
1246
1293
  to
1247
1294
  });
1248
- if (relPos) {
1249
- const id = callbackWrapper(options.onCreate)(relPos);
1295
+ if (cursor) {
1296
+ const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
1250
1297
  if (id) {
1251
1298
  view.dispatch({
1252
1299
  effects: setSelection.of({
@@ -1256,52 +1303,12 @@ var comments = (options = {}) => {
1256
1303
  anchor: from
1257
1304
  }
1258
1305
  });
1259
- if (options.footnote) {
1260
- const tag = `[^${id}]`;
1261
- view.dispatch({
1262
- changes: {
1263
- from: head,
1264
- insert: tag
1265
- },
1266
- selection: {
1267
- anchor: head + tag.length
1268
- }
1269
- });
1270
- }
1271
1306
  return true;
1272
1307
  }
1273
1308
  }
1274
1309
  return false;
1275
1310
  };
1276
- const bookmarksViewPlugin = ViewPlugin4.fromClass(class BookmarkViewPlugin {
1277
- static {
1278
- // Match markdown footnotes (option).
1279
- // https://www.markdownguide.org/extended-syntax/#footnotes
1280
- this.bookmarkMatcher = new MatchDecorator({
1281
- regexp: /\[\^(\w+)\]/g,
1282
- decoration: (match, view, pos) => {
1283
- const id = match[1];
1284
- return Decoration3.replace({
1285
- id,
1286
- widget: new BookmarkWidget(pos, id)
1287
- });
1288
- }
1289
- });
1290
- }
1291
- constructor(view) {
1292
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
1293
- }
1294
- update(update4) {
1295
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update4, this.bookmarks);
1296
- }
1297
- }, {
1298
- decorations: (instance) => instance.bookmarks,
1299
- provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1300
- return view.plugin(plugin)?.bookmarks || Decoration3.none;
1301
- })
1302
- });
1303
1311
  return [
1304
- bookmarksViewPlugin,
1305
1312
  commentsStateField,
1306
1313
  highlightDecorations,
1307
1314
  styles3,
@@ -1310,7 +1317,7 @@ var comments = (options = {}) => {
1310
1317
  //
1311
1318
  options.onCreate ? keymap3.of([
1312
1319
  {
1313
- key: options?.key ?? "meta-'",
1320
+ key: shortcut,
1314
1321
  run: callbackWrapper(createCommentThread)
1315
1322
  }
1316
1323
  ]) : [],
@@ -1327,7 +1334,7 @@ var comments = (options = {}) => {
1327
1334
  above: true,
1328
1335
  create: () => {
1329
1336
  const el = document.createElement("div");
1330
- options.onHover?.(el);
1337
+ options.onHover(el, shortcut);
1331
1338
  return {
1332
1339
  dom: el,
1333
1340
  offset: {
@@ -1340,7 +1347,8 @@ var comments = (options = {}) => {
1340
1347
  }
1341
1348
  return null;
1342
1349
  }, {
1343
- hideOnChange: true,
1350
+ // TODO(burdon): Hide on change triggered immediately?
1351
+ // hideOnChange: true,
1344
1352
  hoverTime: 1e3
1345
1353
  }) : [],
1346
1354
  //
@@ -1348,27 +1356,20 @@ var comments = (options = {}) => {
1348
1356
  // TODO(burdon): Is there a better (finer grained) way to do this?
1349
1357
  //
1350
1358
  EditorView5.updateListener.of(({ view, state, changes }) => {
1351
- const cursorConverter3 = view.state.facet(CursorConverter);
1359
+ const cursorConverter3 = view.state.facet(Cursor.converter);
1352
1360
  {
1353
1361
  let mod = false;
1354
1362
  const { active, ranges } = state.field(commentsStateField);
1355
1363
  changes.iterChanges((from, to, from2, to2) => {
1356
1364
  ranges.forEach((range) => {
1357
1365
  if (from2 === to2) {
1358
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1366
+ const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1359
1367
  if (!newRange || newRange.to - newRange.from === 0) {
1360
- log2.info("deleted comment", {
1361
- thread: range.id
1362
- }, {
1363
- F: __dxlog_file3,
1364
- L: 359,
1365
- S: void 0,
1366
- C: (f, a) => f(...a)
1367
- });
1368
+ options.onDelete?.(range.id);
1368
1369
  }
1369
1370
  }
1370
1371
  if (from <= range.to) {
1371
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1372
+ const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1372
1373
  Object.assign(range, newRange);
1373
1374
  mod = true;
1374
1375
  }
@@ -1414,38 +1415,21 @@ var comments = (options = {}) => {
1414
1415
  });
1415
1416
  }
1416
1417
  }
1417
- })
1418
+ }),
1419
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
1418
1420
  ];
1419
1421
  };
1420
- var getCursorFromRange = (cursorConverter3, range) => {
1421
- const from = cursorConverter3.toCursor(range.from);
1422
- const to = cursorConverter3.toCursor(range.to, -1);
1423
- return [
1424
- from,
1425
- to
1426
- ].join(":");
1427
- };
1428
- var getRangeFromCursor = (cursorConverter3, cursor) => {
1429
- const parts = cursor.split(":");
1430
- const from = cursorConverter3.fromCursor(parts[0]);
1431
- const to = cursorConverter3.fromCursor(parts[1]);
1432
- return from && to ? {
1433
- from,
1434
- to
1435
- } : void 0;
1436
- };
1437
1422
 
1438
1423
  // packages/ui/react-ui-editor/src/extensions/hr.ts
1439
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin5, WidgetType as WidgetType3 } from "@codemirror/view";
1440
- import get4 from "lodash.get";
1424
+ import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1441
1425
  var styles4 = EditorView6.baseTheme({
1442
1426
  "& .cm-hr": {
1443
1427
  // TODO(burdon): ???
1444
1428
  // Note that block-level decorations should not have vertical margins,
1445
- borderBottom: `1px solid ${get4(tokens, "extend.colors.neutral.200")}`
1429
+ borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1446
1430
  }
1447
1431
  });
1448
- var HorizontalRuleWidget = class extends WidgetType3 {
1432
+ var HorizontalRuleWidget = class extends WidgetType2 {
1449
1433
  constructor(_pos) {
1450
1434
  super();
1451
1435
  this._pos = _pos;
@@ -1459,43 +1443,62 @@ var HorizontalRuleWidget = class extends WidgetType3 {
1459
1443
  return el;
1460
1444
  }
1461
1445
  };
1462
- var placeholderMatcher = new MatchDecorator2({
1463
- regexp: /^---$/g,
1446
+ var placeholderMatcher = new MatchDecorator({
1447
+ // regexp: /(?<=\n\n)---/gs,
1448
+ regexp: /^---$/gs,
1464
1449
  decoration: (match, view, pos) => Decoration4.replace({
1465
1450
  widget: new HorizontalRuleWidget(pos)
1466
1451
  })
1467
1452
  });
1468
1453
  var hr = () => [
1469
1454
  styles4,
1470
- ViewPlugin5.fromClass(class {
1455
+ ViewPlugin4.fromClass(class {
1471
1456
  constructor(view) {
1472
1457
  this.rules = placeholderMatcher.createDeco(view);
1473
1458
  }
1474
- update(update4) {
1475
- this.rules = placeholderMatcher.updateDeco(update4, this.rules);
1459
+ update(update2) {
1460
+ this.rules = placeholderMatcher.updateDeco(update2, this.rules);
1476
1461
  }
1477
1462
  }, {
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;
1482
- })
1463
+ decorations: (instance) => instance.rules
1483
1464
  })
1484
1465
  ];
1485
1466
 
1486
1467
  // packages/ui/react-ui-editor/src/extensions/image.ts
1487
1468
  import { syntaxTree } from "@codemirror/language";
1488
- import { RangeSetBuilder, StateField as StateField3 } from "@codemirror/state";
1489
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1469
+ import { StateField as StateField3 } from "@codemirror/state";
1470
+ import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
1490
1471
  var image = (options = {}) => {
1491
1472
  return StateField3.define({
1492
- create: (state) => update(state, options),
1493
- update: (_, tr) => update(tr.state, options),
1473
+ create: (state) => {
1474
+ return Decoration5.set(buildDecorations(0, state.doc.length, state));
1475
+ },
1476
+ update: (value, tr) => {
1477
+ if (!tr.docChanged && !tr.selection) {
1478
+ return value;
1479
+ }
1480
+ const cursor = tr.state.selection.main.head;
1481
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
1482
+ let from = Math.min(cursor, oldCursor);
1483
+ let to = Math.max(cursor, oldCursor);
1484
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
1485
+ from = Math.min(from, fromB);
1486
+ to = Math.max(to, toB);
1487
+ });
1488
+ from = tr.state.doc.lineAt(from).from;
1489
+ to = tr.state.doc.lineAt(to).to;
1490
+ return value.map(tr.changes).update({
1491
+ filterFrom: from,
1492
+ filterTo: to,
1493
+ filter: () => false,
1494
+ add: buildDecorations(from, to, tr.state)
1495
+ });
1496
+ },
1494
1497
  provide: (field) => EditorView7.decorations.from(field)
1495
1498
  });
1496
1499
  };
1497
- var update = (state, options) => {
1498
- const builder = new RangeSetBuilder();
1500
+ var buildDecorations = (from, to, state) => {
1501
+ const decorations = [];
1499
1502
  const cursor = state.selection.main.head;
1500
1503
  syntaxTree(state).iterate({
1501
1504
  enter: (node) => {
@@ -1504,22 +1507,23 @@ var update = (state, options) => {
1504
1507
  if (urlNode) {
1505
1508
  const hide = state.readOnly || cursor < node.from || cursor > node.to;
1506
1509
  const url = state.sliceDoc(urlNode.from, urlNode.to);
1507
- builder.add(hide ? node.from : node.to, node.to, Decoration5.replace({
1510
+ decorations.push(Decoration5.replace({
1508
1511
  block: true,
1509
1512
  widget: new ImageWidget(url)
1510
- }));
1513
+ }).range(hide ? node.from : node.to, node.to));
1511
1514
  }
1512
1515
  }
1513
- }
1516
+ },
1517
+ from,
1518
+ to
1514
1519
  });
1515
- return builder.finish();
1520
+ return decorations;
1516
1521
  };
1517
- var ImageWidget = class extends WidgetType4 {
1522
+ var ImageWidget = class extends WidgetType3 {
1518
1523
  constructor(_url) {
1519
1524
  super();
1520
1525
  this._url = _url;
1521
1526
  }
1522
- // TODO(burdon): Does this need to be unique for each position?
1523
1527
  eq(other) {
1524
1528
  return this._url === other._url;
1525
1529
  }
@@ -1527,22 +1531,29 @@ var ImageWidget = class extends WidgetType4 {
1527
1531
  const img = document.createElement("img");
1528
1532
  img.setAttribute("src", this._url);
1529
1533
  img.setAttribute("class", "cm-image");
1534
+ img.onload = () => img.classList.add("cm-loaded-image");
1530
1535
  return img;
1531
1536
  }
1532
1537
  };
1533
1538
 
1534
1539
  // packages/ui/react-ui-editor/src/extensions/link.ts
1535
1540
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1536
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1537
- import { Decoration as Decoration6, EditorView as EditorView8, WidgetType as WidgetType5, hoverTooltip as hoverTooltip2 } from "@codemirror/view";
1541
+ import { RangeSetBuilder } from "@codemirror/state";
1542
+ import { hoverTooltip as hoverTooltip2, Decoration as Decoration6, ViewPlugin as ViewPlugin5, WidgetType as WidgetType4 } from "@codemirror/view";
1538
1543
  import { tooltipContent } from "@dxos/react-ui-theme";
1539
- var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1540
1544
  var link = (options = {}) => {
1541
1545
  const extensions = [
1542
- StateField4.define({
1543
- create: (state) => update2(state, options),
1544
- update: (_, tr) => update2(tr.state, options),
1545
- provide: (field) => EditorView8.decorations.from(field)
1546
+ ViewPlugin5.fromClass(class {
1547
+ constructor(view) {
1548
+ this.decorations = buildDecorations2(view, options);
1549
+ }
1550
+ update(update2) {
1551
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1552
+ this.decorations = buildDecorations2(update2.view, options);
1553
+ }
1554
+ }
1555
+ }, {
1556
+ decorations: (v) => v.decorations
1546
1557
  })
1547
1558
  ];
1548
1559
  if (options.onHover) {
@@ -1550,35 +1561,25 @@ var link = (options = {}) => {
1550
1561
  // https://codemirror.net/examples/tooltip
1551
1562
  // https://codemirror.net/docs/ref/#view.hoverTooltip
1552
1563
  // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1553
- hoverTooltip2((view, pos) => {
1554
- const { from, text } = view.state.doc.lineAt(pos);
1555
- const p = pos - from;
1556
- let idx = 0;
1557
- let match;
1558
- do {
1559
- match = text.substring(idx).match(markdownLinkRegexp);
1560
- if (!match) {
1561
- match = void 0;
1562
- break;
1563
- }
1564
- idx = text.indexOf(match[0]);
1565
- if (p >= idx && p < idx + match[0].length) {
1566
- break;
1567
- }
1568
- idx += match[0].length;
1569
- } while (true);
1570
- if (!match) {
1564
+ hoverTooltip2((view, pos, side) => {
1565
+ const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1566
+ let link2 = null;
1567
+ for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1568
+ link2 = node.name === "Link" ? node : null;
1569
+ }
1570
+ const url = link2 && link2.getChild("URL");
1571
+ if (!url || !link2) {
1571
1572
  return null;
1572
1573
  }
1573
- const [, , url] = match ?? [];
1574
+ const urlText = view.state.sliceDoc(url.from, url.to);
1574
1575
  return {
1575
- pos: idx + from,
1576
- end: idx + from + match[0].length,
1576
+ pos: link2.from,
1577
+ end: link2.to,
1577
1578
  above: true,
1578
1579
  create: () => {
1579
1580
  const el = document.createElement("div");
1580
1581
  el.className = tooltipContent({}, "pli-2 plb-1");
1581
- options.onHover(el, url);
1582
+ options.onHover(el, urlText);
1582
1583
  return {
1583
1584
  dom: el,
1584
1585
  offset: {
@@ -1593,15 +1594,14 @@ var link = (options = {}) => {
1593
1594
  }
1594
1595
  return extensions;
1595
1596
  };
1596
- var LinkButton = class extends WidgetType5 {
1597
- constructor(_pos, _url, _onAttach) {
1597
+ var LinkButton = class extends WidgetType4 {
1598
+ constructor(_url, _onAttach) {
1598
1599
  super();
1599
- this._pos = _pos;
1600
1600
  this._url = _url;
1601
1601
  this._onAttach = _onAttach;
1602
1602
  }
1603
1603
  eq(other) {
1604
- return this._pos === other._pos && this._url === other._url;
1604
+ return this._url === other._url;
1605
1605
  }
1606
1606
  toDOM(view) {
1607
1607
  const el = document.createElement("span");
@@ -1609,7 +1609,7 @@ var LinkButton = class extends WidgetType5 {
1609
1609
  return el;
1610
1610
  }
1611
1611
  };
1612
- var LinkText = class extends WidgetType5 {
1612
+ var LinkText = class extends WidgetType4 {
1613
1613
  constructor(_text, _url) {
1614
1614
  super();
1615
1615
  this._text = _text;
@@ -1638,46 +1638,51 @@ var LinkText = class extends WidgetType5 {
1638
1638
  return link2;
1639
1639
  }
1640
1640
  };
1641
- var update2 = (state, options) => {
1642
- const builder = new RangeSetBuilder2();
1641
+ var buildDecorations2 = (view, options) => {
1642
+ const builder = new RangeSetBuilder();
1643
+ const { state } = view;
1643
1644
  const cursor = state.selection.main.head;
1644
- syntaxTree2(state).iterate({
1645
- enter: (node) => {
1646
- if (node.name === "Link") {
1647
- const marks2 = node.node.getChildren("LinkMark");
1648
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1649
- const urlNode = node.node.getChild("URL");
1650
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1651
- if (!url) {
1645
+ for (const { from, to } of view.visibleRanges) {
1646
+ syntaxTree2(state).iterate({
1647
+ enter: (node) => {
1648
+ if (node.name === "Link") {
1649
+ const marks2 = node.node.getChildren("LinkMark");
1650
+ const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1651
+ const urlNode = node.node.getChild("URL");
1652
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1653
+ if (!url) {
1654
+ return false;
1655
+ }
1656
+ if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
1657
+ builder.add(node.from, node.to, Decoration6.replace({
1658
+ widget: new LinkText(text, options.link ? url : void 0)
1659
+ }));
1660
+ }
1661
+ if (options.onRender) {
1662
+ builder.add(node.to, node.to, Decoration6.replace({
1663
+ widget: new LinkButton(url, options.onRender)
1664
+ }));
1665
+ }
1652
1666
  return false;
1653
1667
  }
1654
- if (state.readOnly || cursor < node.from || cursor > node.to) {
1655
- builder.add(node.from, node.to, Decoration6.replace({
1656
- widget: new LinkText(text, options.link ? url : void 0)
1657
- }));
1658
- }
1659
- if (options.onRender) {
1660
- builder.add(node.to, node.to, Decoration6.replace({
1661
- widget: new LinkButton(node.from, url, options.onRender)
1662
- }));
1663
- }
1664
- return false;
1665
- }
1666
- }
1667
- });
1668
+ },
1669
+ from,
1670
+ to
1671
+ });
1672
+ }
1668
1673
  return builder.finish();
1669
1674
  };
1670
1675
 
1671
1676
  // packages/ui/react-ui-editor/src/extensions/listener.ts
1672
- import { EditorView as EditorView9 } from "@codemirror/view";
1677
+ import { EditorView as EditorView8 } from "@codemirror/view";
1673
1678
  var listener = ({ onFocus, onChange }) => {
1674
1679
  const extensions = [];
1675
- onFocus && extensions.push(EditorView9.focusChangeEffect.of((_, focused) => {
1680
+ onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1676
1681
  onFocus(focused);
1677
1682
  return null;
1678
1683
  }));
1679
- onChange && extensions.push(EditorView9.updateListener.of((update4) => {
1680
- onChange(update4.state.doc.toString());
1684
+ onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1685
+ onChange(update2.state.doc.toString());
1681
1686
  }));
1682
1687
  return extensions;
1683
1688
  };
@@ -1685,27 +1690,25 @@ var listener = ({ onFocus, onChange }) => {
1685
1690
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1686
1691
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1687
1692
  import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1688
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
1693
+ import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1689
1694
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1690
1695
  import { languages } from "@codemirror/language-data";
1691
1696
  import { searchKeymap } from "@codemirror/search";
1692
1697
  import { EditorState } from "@codemirror/state";
1693
1698
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1694
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView10, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1699
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1695
1700
 
1696
1701
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1697
- import { markdownLanguage } from "@codemirror/lang-markdown";
1702
+ import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
1698
1703
  import { HighlightStyle } from "@codemirror/language";
1699
1704
  import { tags, styleTags, Tag } from "@lezer/highlight";
1700
1705
  import { Table } from "@lezer/markdown";
1701
- import get5 from "lodash.get";
1702
1706
  var markdownTags = {
1703
1707
  Blockquote: Tag.define(),
1704
1708
  CodeMark: Tag.define(),
1705
1709
  CodeText: Tag.define(),
1706
1710
  EmphasisMark: Tag.define(),
1707
1711
  HeaderMark: Tag.define(),
1708
- // HorizontalRule: Tag.define(),
1709
1712
  InlineCode: Tag.define(),
1710
1713
  LinkLabel: Tag.define(),
1711
1714
  LinkReference: Tag.define(),
@@ -1869,12 +1872,6 @@ var markdownHighlightStyle = (readonly) => {
1869
1872
  ],
1870
1873
  class: blockquote
1871
1874
  },
1872
- // TODO(burdon): Replace with extension.
1873
- // {
1874
- // tag: [markdownTags.HorizontalRule],
1875
- // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1876
- // },
1877
- // TODO(burdon): Only if being edited.
1878
1875
  {
1879
1876
  tag: [
1880
1877
  markdownTags.TableCell
@@ -1882,9 +1879,9 @@ var markdownHighlightStyle = (readonly) => {
1882
1879
  class: "font-mono"
1883
1880
  }
1884
1881
  ], {
1885
- scope: markdownLanguage,
1882
+ scope: markdownLanguage2,
1886
1883
  all: {
1887
- fontFamily: get5(tokens, "fontFamily.body", []).join(",")
1884
+ fontFamily: getToken("fontFamily.body", []).join(",")
1888
1885
  }
1889
1886
  });
1890
1887
  };
@@ -1894,7 +1891,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1894
1891
  return [
1895
1892
  EditorState.allowMultipleSelections.of(true),
1896
1893
  EditorState.tabSize.of(2),
1897
- EditorView10.lineWrapping,
1894
+ EditorView9.lineWrapping,
1898
1895
  customKeymap(),
1899
1896
  bracketMatching2(),
1900
1897
  closeBrackets2(),
@@ -1914,7 +1911,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1914
1911
  // NOTE: This extends the parser; it doesn't affect rendering.
1915
1912
  // https://github.github.com/gfm
1916
1913
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1917
- base: markdownLanguage2,
1914
+ base: markdownLanguage3,
1918
1915
  // Languages for syntax highlighting fenced code blocks.
1919
1916
  codeLanguages: languages,
1920
1917
  // Parser extensions.
@@ -1965,24 +1962,24 @@ var mention = ({ onSearch }) => {
1965
1962
  });
1966
1963
  };
1967
1964
 
1968
- // packages/ui/react-ui-editor/src/extensions/mermaid.ts
1969
- var mermaid = () => {
1965
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
1966
+ var outliner = (options = {}) => {
1970
1967
  return [];
1971
1968
  };
1972
1969
 
1973
1970
  // packages/ui/react-ui-editor/src/extensions/table.ts
1974
1971
  import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1975
- import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField5 } from "@codemirror/state";
1976
- import { Decoration as Decoration7, EditorView as EditorView11, WidgetType as WidgetType6 } from "@codemirror/view";
1972
+ import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1973
+ import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
1977
1974
  var table = (options = {}) => {
1978
- return StateField5.define({
1979
- create: (state) => update3(state, options),
1980
- update: (_, tr) => update3(tr.state, options),
1981
- provide: (field) => EditorView11.decorations.from(field)
1975
+ return StateField4.define({
1976
+ create: (state) => update(state, options),
1977
+ update: (_, tr) => update(tr.state, options),
1978
+ provide: (field) => EditorView10.decorations.from(field)
1982
1979
  });
1983
1980
  };
1984
- var update3 = (state, options) => {
1985
- const builder = new RangeSetBuilder3();
1981
+ var update = (state, options) => {
1982
+ const builder = new RangeSetBuilder2();
1986
1983
  const cursor = state.selection.main.head;
1987
1984
  const tables = [];
1988
1985
  const getTable = () => tables[tables.length - 1];
@@ -2023,7 +2020,6 @@ var update3 = (state, options) => {
2023
2020
  tables.forEach((table2) => {
2024
2021
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
2025
2022
  hide && builder.add(table2.from, table2.to, Decoration7.replace({
2026
- block: true,
2027
2023
  widget: new TableWidget(table2)
2028
2024
  }));
2029
2025
  builder.add(table2.from, table2.to, Decoration7.mark({
@@ -2032,13 +2028,13 @@ var update3 = (state, options) => {
2032
2028
  });
2033
2029
  return builder.finish();
2034
2030
  };
2035
- var TableWidget = class extends WidgetType6 {
2031
+ var TableWidget = class extends WidgetType5 {
2036
2032
  constructor(_table) {
2037
2033
  super();
2038
2034
  this._table = _table;
2039
2035
  }
2040
2036
  eq(other) {
2041
- return this._table.from === other?._table?.from;
2037
+ return this._table.header?.join() === other._table.header?.join() && this._table.rows?.join() === other._table.rows?.join();
2042
2038
  }
2043
2039
  toDOM(view) {
2044
2040
  const table2 = document.createElement("table");
@@ -2064,91 +2060,92 @@ var TableWidget = class extends WidgetType6 {
2064
2060
  }
2065
2061
  return table2;
2066
2062
  }
2063
+ ignoreEvent(e) {
2064
+ return !/^mouse/.test(e.type);
2065
+ }
2067
2066
  };
2068
2067
 
2069
2068
  // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2070
- import { EditorView as EditorView12, Decoration as Decoration8, WidgetType as WidgetType7, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2071
- var styles5 = EditorView12.baseTheme({
2072
- "& .cm-task-item": {
2073
- paddingLeft: "4px",
2074
- paddingRight: "8px"
2075
- }
2076
- });
2077
- var CheckboxWidget = class extends WidgetType7 {
2078
- constructor(_pos, _checked, _indent, _onCheck) {
2069
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2070
+ import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
2071
+ import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType6, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2072
+ var CheckboxWidget = class extends WidgetType6 {
2073
+ constructor(_checked) {
2079
2074
  super();
2080
- this._pos = _pos;
2081
2075
  this._checked = _checked;
2082
- this._indent = _indent;
2083
- this._onCheck = _onCheck;
2084
2076
  }
2085
2077
  eq(other) {
2086
- return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
2078
+ return this._checked === other._checked;
2087
2079
  }
2088
2080
  toDOM(view) {
2089
- const wrap = document.createElement("span");
2090
- wrap.className = "cm-task-item";
2091
- wrap.setAttribute("aria-hidden", "true");
2092
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
2093
- const input = wrap.appendChild(document.createElement("input"));
2081
+ const input = document.createElement("input");
2094
2082
  input.type = "checkbox";
2095
2083
  input.checked = this._checked;
2096
- if (!this._onCheck) {
2084
+ if (view.state.readOnly) {
2097
2085
  input.setAttribute("disabled", "true");
2098
- }
2099
- if (this._onCheck) {
2100
- input.onchange = (event) => {
2101
- this._onCheck?.(event.target.checked);
2102
- return true;
2086
+ } else {
2087
+ input.onmousedown = (event) => {
2088
+ const pos = view.posAtDOM(input);
2089
+ const text = view.state.sliceDoc(pos, pos + 3);
2090
+ if (text === (this._checked ? "[x]" : "[ ]")) {
2091
+ view.dispatch({
2092
+ changes: {
2093
+ from: pos + 1,
2094
+ to: pos + 2,
2095
+ insert: this._checked ? " " : "x"
2096
+ }
2097
+ });
2098
+ event.preventDefault();
2099
+ }
2103
2100
  };
2104
2101
  }
2105
- return wrap;
2102
+ return input;
2106
2103
  }
2107
2104
  ignoreEvent() {
2108
2105
  return false;
2109
2106
  }
2110
2107
  };
2108
+ var checkedDeco = Decoration8.replace({
2109
+ widget: new CheckboxWidget(true)
2110
+ });
2111
+ var uncheckedDeco = Decoration8.replace({
2112
+ widget: new CheckboxWidget(false)
2113
+ });
2111
2114
  var tasklist = (options = {}) => {
2112
- const taskMatcher = new MatchDecorator3({
2113
- regexp: /^(\s*)- \[([ xX])\]\s/g,
2114
- decoration: (match, view, pos) => {
2115
- const indent = Math.floor(match[1].length / 2);
2116
- const checked = match[2] === "x" || match[2] === "X";
2117
- return Decoration8.replace({
2118
- widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2119
- const idx = pos + match[0].indexOf("[") + 1;
2120
- view.dispatch({
2121
- changes: {
2122
- from: idx,
2123
- to: idx + 1,
2124
- insert: checked2 ? "x" : " "
2125
- },
2126
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
2127
- selection: {
2128
- anchor: idx + 3
2129
- }
2130
- });
2131
- })
2132
- });
2133
- }
2134
- });
2135
- const tasks = ViewPlugin6.fromClass(class {
2115
+ return ViewPlugin6.fromClass(class {
2136
2116
  constructor(view) {
2137
- this.tasks = taskMatcher.createDeco(view);
2117
+ this.decorations = buildDecorations3(view);
2138
2118
  }
2139
- update(update4) {
2140
- this.tasks = taskMatcher.updateDeco(update4, this.tasks);
2119
+ update(update2) {
2120
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2121
+ this.decorations = buildDecorations3(update2.view);
2122
+ }
2141
2123
  }
2142
2124
  }, {
2143
- decorations: (value) => value.tasks,
2144
- provide: (plugin) => EditorView12.atomicRanges.of((view) => {
2145
- return view.plugin(plugin)?.tasks || Decoration8.none;
2125
+ decorations: (v) => v.decorations,
2126
+ // TODO(burdon): Is this still required?
2127
+ provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2128
+ return view.plugin(plugin)?.decorations || Decoration8.none;
2146
2129
  })
2147
2130
  });
2148
- return [
2149
- tasks,
2150
- styles5
2151
- ];
2131
+ };
2132
+ var buildDecorations3 = (view) => {
2133
+ const builder = new RangeSetBuilder3();
2134
+ const { state } = view;
2135
+ const cursor = state.selection.main.head;
2136
+ for (const { from, to } of view.visibleRanges) {
2137
+ syntaxTree4(state).iterate({
2138
+ enter: (node) => {
2139
+ if (node.name === "TaskMarker" && (cursor < node.from || cursor > node.to)) {
2140
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2141
+ builder.add(node.from, node.to, checked ? checkedDeco : uncheckedDeco);
2142
+ }
2143
+ },
2144
+ from,
2145
+ to
2146
+ });
2147
+ }
2148
+ return builder.finish();
2152
2149
  };
2153
2150
 
2154
2151
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
@@ -2225,12 +2222,12 @@ var cursorConverter2 = (text) => ({
2225
2222
 
2226
2223
  // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2227
2224
  var yjs = (content, awareness2) => [
2228
- CursorConverter.of(cursorConverter2(content)),
2225
+ Cursor.converter.of(cursorConverter2(content)),
2229
2226
  yCollab(content, awareness2)
2230
2227
  ];
2231
2228
 
2232
2229
  // packages/ui/react-ui-editor/src/themes/default.ts
2233
- import get6 from "lodash.get";
2230
+ import get3 from "lodash.get";
2234
2231
  var defaultTheme = {
2235
2232
  //
2236
2233
  // Main layout:
@@ -2259,21 +2256,21 @@ var defaultTheme = {
2259
2256
  "&.cm-focused": {
2260
2257
  outline: "none"
2261
2258
  },
2262
- "& .cm-scroller": {
2259
+ ".cm-scroller": {
2263
2260
  overflow: "visible",
2264
- fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
2261
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2265
2262
  },
2266
- "& .cm-content": {
2263
+ ".cm-content": {
2267
2264
  padding: 0,
2268
2265
  // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
2269
2266
  fontSize: "16px"
2270
2267
  },
2271
2268
  "&light .cm-content": {
2272
- color: get6(tokens, "extend.colors.neutral.900", "black"),
2269
+ color: get3(tokens, "extend.colors.neutral.900", "black"),
2273
2270
  caretColor: "black"
2274
2271
  },
2275
2272
  "&dark .cm-content": {
2276
- color: get6(tokens, "extend.colors.neutral.100", "white"),
2273
+ color: get3(tokens, "extend.colors.neutral.100", "white"),
2277
2274
  caretColor: "white"
2278
2275
  },
2279
2276
  //
@@ -2285,158 +2282,120 @@ var defaultTheme = {
2285
2282
  "&dark .cm-cursor, &dark .cm-dropCursor": {
2286
2283
  borderLeft: "2px solid white"
2287
2284
  },
2288
- "& .cm-placeholder": {
2285
+ ".cm-placeholder": {
2289
2286
  fontWeight: 100
2290
2287
  },
2291
2288
  //
2292
2289
  // line
2293
2290
  //
2294
- "& .cm-line": {
2291
+ ".cm-line": {
2295
2292
  paddingInline: 0
2296
2293
  },
2297
- "& .cm-activeLine": {
2294
+ ".cm-activeLine": {
2298
2295
  background: "inherit"
2299
2296
  },
2300
2297
  //
2301
2298
  // Selection
2302
2299
  //
2303
2300
  "&light .cm-selectionBackground": {
2304
- background: get6(tokens, "extend.colors.primary.100")
2301
+ background: get3(tokens, "extend.colors.primary.100")
2305
2302
  },
2306
2303
  "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2307
- background: get6(tokens, "extend.colors.primary.200")
2304
+ background: get3(tokens, "extend.colors.primary.200")
2308
2305
  },
2309
2306
  "&dark .cm-selectionBackground": {
2310
- background: get6(tokens, "extend.colors.primary.700")
2307
+ background: get3(tokens, "extend.colors.primary.700")
2311
2308
  },
2312
2309
  "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2313
- background: get6(tokens, "extend.colors.primary.600")
2310
+ background: get3(tokens, "extend.colors.primary.600")
2314
2311
  },
2315
2312
  //
2316
2313
  // Search
2317
2314
  //
2318
2315
  "&light .cm-searchMatch": {
2319
- backgroundColor: get6(tokens, "extend.colors.yellow.100")
2316
+ backgroundColor: get3(tokens, "extend.colors.yellow.100")
2320
2317
  },
2321
2318
  "&dark .cm-searchMatch": {
2322
- backgroundColor: get6(tokens, "extend.colors.yellow.700")
2323
- },
2324
- //
2325
- // collaboration
2326
- // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2327
- //
2328
- "&light .cm-ySelection, &light .cm-yLineSelection": {
2329
- mixBlendMode: "multiply"
2330
- },
2331
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2332
- mixBlendMode: "screen"
2333
- },
2334
- "& .cm-ySelectionInfo": {
2335
- padding: "2px 4px",
2336
- marginBlockStart: "-4px"
2337
- },
2338
- "& .cm-ySelection, & .cm-selectionMatch": {
2339
- paddingBlockStart: ".15em",
2340
- paddingBlockEnd: ".15em"
2341
- },
2342
- "& .cm-ySelectionCaret": {
2343
- display: "inline-block",
2344
- insetBlockStart: ".1em",
2345
- blockSize: "1.4em",
2346
- verticalAlign: "top"
2347
- },
2348
- "& .cm-yLineSelection": {
2349
- margin: 0
2319
+ backgroundColor: get3(tokens, "extend.colors.yellow.700")
2350
2320
  },
2351
2321
  //
2352
2322
  // link
2353
2323
  //
2354
- "& .cm-link": {
2355
- color: get6(tokens, "extend.colors.primary.500"),
2324
+ ".cm-link": {
2325
+ color: get3(tokens, "extend.colors.primary.500"),
2356
2326
  textDecorationLine: "underline",
2357
2327
  textDecorationThickness: "1px",
2358
2328
  textUnderlineOffset: "2px",
2359
2329
  borderRadius: ".125rem",
2360
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2330
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2361
2331
  },
2362
2332
  //
2363
2333
  // tooltip
2364
2334
  //
2365
- "& .cm-tooltip": {
2335
+ ".cm-tooltip": {
2366
2336
  border: "none"
2367
2337
  },
2368
- "& .cm-tooltip-below": {},
2338
+ ".cm-tooltip-below": {},
2369
2339
  //
2370
2340
  // autocomplete
2371
2341
  //
2372
- "& .cm-tooltip-autocomplete": {
2342
+ ".cm-tooltip-autocomplete": {
2373
2343
  marginTop: "4px",
2374
2344
  marginLeft: "-3px"
2375
2345
  },
2376
- "& .cm-tooltip-autocomplete ul li": {},
2377
- "& .cm-tooltip-autocomplete ul li[aria-selected]": {},
2378
- "& .cm-completionIcon": {
2346
+ ".cm-tooltip-autocomplete ul li": {},
2347
+ ".cm-tooltip-autocomplete ul li[aria-selected]": {},
2348
+ ".cm-completionIcon": {
2379
2349
  display: "none"
2380
2350
  },
2381
- "& .cm-completionLabel": {
2382
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2351
+ ".cm-completionLabel": {
2352
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2383
2353
  },
2384
- "& .cm-completionMatchedText": {
2354
+ ".cm-completionMatchedText": {
2385
2355
  textDecoration: "none"
2386
2356
  },
2387
2357
  //
2388
- // widgets
2389
- //
2390
- "& .cm-widgetBuffer": {
2391
- display: "none",
2392
- height: 0
2393
- },
2394
- //
2395
2358
  // table
2396
2359
  //
2397
- "& .cm-table *": {
2398
- fontFamily: `${get6(tokens, "fontFamily.mono", []).join(",")} !important`,
2360
+ ".cm-table *": {
2361
+ fontFamily: `${get3(tokens, "fontFamily.mono", []).join(",")} !important`,
2399
2362
  textDecoration: "none !important"
2400
2363
  },
2401
- "& .cm-table-head": {
2364
+ ".cm-table-head": {
2402
2365
  padding: "2px 16px 2px 0px",
2403
- borderBottom: `1px solid ${get6(tokens, "extend.colors.neutral.500")}`,
2366
+ borderBottom: `1px solid ${get3(tokens, "extend.colors.neutral.500")}`,
2404
2367
  fontWeight: 100,
2405
2368
  textAlign: "left",
2406
- color: get6(tokens, "extend.colors.neutral.500")
2369
+ color: get3(tokens, "extend.colors.neutral.500")
2407
2370
  },
2408
- "& .cm-table-cell": {
2371
+ ".cm-table-cell": {
2409
2372
  padding: "2px 16px 2px 0px"
2410
2373
  },
2411
2374
  //
2412
2375
  // image
2413
2376
  //
2414
- "& .cm-image": {
2415
- margin: "0.5rem 0"
2377
+ ".cm-image": {
2378
+ display: "block",
2379
+ height: "0"
2380
+ },
2381
+ ".cm-image.cm-loaded-image": {
2382
+ height: "auto",
2383
+ borderTop: "0.5rem solid transparent",
2384
+ borderBottom: "0.5rem solid transparent"
2416
2385
  },
2417
2386
  //
2418
2387
  // font size
2419
- // 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.
2388
+ // TODO(thure): This appears to be the best or only way to set selection caret heights,
2389
+ // but it's far more verbose than it needs to be.
2420
2390
  //
2421
- ...Object.keys(get6(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2422
- const height = get6(tokens, [
2423
- "extend",
2424
- "fontSize",
2425
- fontSize,
2426
- 1,
2427
- "lineHeight"
2428
- ]);
2429
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2430
- height
2431
- };
2432
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2433
- height
2434
- };
2435
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2436
- height
2437
- };
2438
- return acc;
2439
- }, {}),
2391
+ // ...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
2392
+ // const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
2393
+ //
2394
+ // acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
2395
+ // acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
2396
+ // acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
2397
+ // return acc;
2398
+ // }, {}),
2440
2399
  /**
2441
2400
  * Panels
2442
2401
  * TODO(burdon): Needs styling attention (esp. dark mode).
@@ -2453,51 +2412,53 @@ var defaultTheme = {
2453
2412
  * </div>
2454
2413
  * </div
2455
2414
  */
2456
- "& .cm-panels": {
2457
- border: `1px solid ${get6(tokens, "extend.colors.neutral.200")}`
2415
+ ".cm-panels": {
2416
+ border: `1px solid ${get3(tokens, "extend.colors.neutral.200")}`
2458
2417
  },
2459
- "& .cm-panel": {
2460
- background: get6(tokens, "extend.colors.neutral.50"),
2461
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2418
+ ".cm-panel": {
2419
+ background: get3(tokens, "extend.colors.neutral.50"),
2420
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2462
2421
  },
2463
- "& .cm-button": {
2422
+ ".cm-button": {
2464
2423
  margin: "4px",
2465
- fontFamily: get6(tokens, "fontFamily.body", []).join(","),
2466
- background: get6(tokens, "extend.colors.neutral.100"),
2424
+ fontFamily: get3(tokens, "fontFamily.body", []).join(","),
2425
+ background: get3(tokens, "extend.colors.neutral.100"),
2467
2426
  backgroundImage: "none",
2468
2427
  border: "none",
2469
2428
  "&:hover": {
2470
- background: get6(tokens, "extend.colors.neutral.200")
2429
+ background: get3(tokens, "extend.colors.neutral.200")
2471
2430
  },
2472
2431
  "&:active": {
2473
- background: get6(tokens, "extend.colors.neutral.300"),
2432
+ background: get3(tokens, "extend.colors.neutral.300"),
2474
2433
  backgroundImage: "none"
2475
2434
  }
2476
2435
  },
2477
- "& .cm-panel input[type=checkbox]": {
2436
+ ".cm-panel input[type=checkbox]": {
2478
2437
  marginRight: "0.4rem !important"
2479
2438
  }
2480
2439
  };
2481
2440
  var textTheme = {
2482
- "& .cm-scroller": {
2483
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2441
+ ".cm-scroller": {
2442
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2484
2443
  },
2485
- "& .cm-placeholder": {
2486
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2444
+ ".cm-placeholder": {
2445
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2487
2446
  }
2488
2447
  };
2489
2448
  var markdownTheme = {
2490
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2491
- "& .cm-placeholder": {
2492
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2449
+ ".cm-scroller": {
2450
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2451
+ },
2452
+ ".cm-placeholder": {
2453
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2493
2454
  }
2494
2455
  };
2495
2456
  var codeTheme = {
2496
- "& .cm-scroller": {
2497
- fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
2457
+ ".cm-scroller": {
2458
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2498
2459
  },
2499
- "& .cm-placeholder": {
2500
- fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
2460
+ ".cm-placeholder": {
2461
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2501
2462
  }
2502
2463
  };
2503
2464
 
@@ -2506,21 +2467,23 @@ var EditorModes = [
2506
2467
  "default",
2507
2468
  "vim"
2508
2469
  ];
2509
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
2510
- const { themeMode } = useThemeContext();
2470
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, autofocus, readonly, selection, comments: comments2, extensions = [], editorMode, theme, slots = defaultSlots }, forwardedRef) => {
2511
2471
  const tabsterDOMAttribute = useFocusableGroup({
2512
2472
  tabBehavior: "limited"
2513
2473
  });
2514
- const [root, setRoot] = useState(null);
2515
- const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
2516
- useImperativeHandle(forwardedRef, () => ({
2517
- root,
2518
- state,
2474
+ const { themeMode } = useThemeContext();
2475
+ const rootRef = useRef(null);
2476
+ const [view, setView] = useState(null);
2477
+ useImperativeHandle(forwardedRef, () => view, [
2478
+ view
2479
+ ]);
2480
+ useEffect(() => {
2481
+ if (autofocus) {
2482
+ view?.focus();
2483
+ }
2484
+ }, [
2485
+ autofocus,
2519
2486
  view
2520
- }), [
2521
- view,
2522
- state,
2523
- root
2524
2487
  ]);
2525
2488
  const { awareness: awareness2, peer } = model;
2526
2489
  useEffect(() => {
@@ -2544,7 +2507,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2544
2507
  themeMode
2545
2508
  ]);
2546
2509
  useEffect(() => {
2547
- if (view && comments2?.length) {
2510
+ if (view && comments2 !== void 0) {
2548
2511
  view.dispatch({
2549
2512
  effects: setCommentRange.of({
2550
2513
  model,
@@ -2557,20 +2520,22 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2557
2520
  comments2
2558
2521
  ]);
2559
2522
  useEffect(() => {
2560
- if (!root) {
2523
+ if (!model || !rootRef.current) {
2561
2524
  return;
2562
2525
  }
2563
- const state2 = EditorState2.create({
2526
+ const state = EditorState2.create({
2564
2527
  doc: model.text(),
2528
+ // TODO(burdon): Composer should store and set selection when switching documents.
2529
+ selection,
2565
2530
  extensions: [
2566
2531
  readonly && EditorState2.readOnly.of(readonly),
2567
2532
  // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2568
2533
  editorMode === "vim" && vim(),
2569
2534
  // Theme.
2570
2535
  // TODO(burdon): Make theme configurable.
2571
- EditorView13.baseTheme(defaultTheme),
2572
- EditorView13.theme(slots?.editor?.theme ?? {}),
2573
- EditorView13.darkTheme.of(themeMode === "dark"),
2536
+ EditorView12.baseTheme(defaultTheme),
2537
+ EditorView12.theme(theme ?? {}),
2538
+ EditorView12.darkTheme.of(themeMode === "dark"),
2574
2539
  // Storage and replication.
2575
2540
  model.extension,
2576
2541
  // Custom.
@@ -2578,19 +2543,17 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2578
2543
  ].filter(Boolean)
2579
2544
  });
2580
2545
  view?.destroy();
2581
- setEditor({
2582
- state: state2,
2583
- view: new EditorView13({
2584
- state: state2,
2585
- parent: root
2586
- })
2546
+ const newView = new EditorView12({
2547
+ parent: rootRef.current,
2548
+ state
2587
2549
  });
2550
+ setView(newView);
2588
2551
  return () => {
2589
- view?.destroy();
2590
- setEditor(void 0);
2552
+ newView?.destroy();
2553
+ setView(null);
2591
2554
  };
2592
2555
  }, [
2593
- root,
2556
+ rootRef,
2594
2557
  model,
2595
2558
  readonly,
2596
2559
  editorMode,
@@ -2599,12 +2562,8 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2599
2562
  const handleKeyUp = useCallback((event) => {
2600
2563
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2601
2564
  switch (key) {
2602
- case "Enter": {
2603
- view?.contentDOM.focus();
2604
- break;
2605
- }
2606
2565
  case "Escape": {
2607
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2566
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
2608
2567
  break;
2609
2568
  }
2610
2569
  }
@@ -2614,16 +2573,17 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2614
2573
  ]);
2615
2574
  return /* @__PURE__ */ React.createElement("div", {
2616
2575
  key: model.id,
2617
- ref: setRoot,
2576
+ role: "none",
2618
2577
  tabIndex: 0,
2619
- ...slots?.root,
2578
+ onKeyUp: handleKeyUp,
2579
+ ...slots.root,
2620
2580
  ...editorMode !== "vim" && tabsterDOMAttribute,
2621
- onKeyUp: handleKeyUp
2581
+ ref: rootRef
2622
2582
  });
2623
2583
  });
2624
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2584
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
2625
2585
  const { themeMode } = useThemeContext();
2626
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2586
+ const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2627
2587
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2628
2588
  ref: forwardedRef,
2629
2589
  readonly,
@@ -2631,17 +2591,18 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots:
2631
2591
  basicBundle({
2632
2592
  readonly,
2633
2593
  themeMode,
2634
- placeholder: slots?.editor?.placeholder
2594
+ placeholder: placeholder3
2635
2595
  }),
2636
2596
  ...extensions
2637
2597
  ],
2638
- slots,
2598
+ theme,
2599
+ slots: updatedSlots,
2639
2600
  ...props
2640
2601
  });
2641
2602
  });
2642
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2603
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
2643
2604
  const { themeMode } = useThemeContext();
2644
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
2605
+ const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2645
2606
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2646
2607
  ref: forwardedRef,
2647
2608
  readonly,
@@ -2649,34 +2610,29 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], sl
2649
2610
  markdownBundle({
2650
2611
  readonly,
2651
2612
  themeMode,
2652
- placeholder: slots?.editor?.placeholder
2613
+ placeholder: placeholder3
2653
2614
  }),
2654
2615
  ...extensions
2655
2616
  ],
2656
- slots,
2617
+ theme,
2618
+ slots: updatedSlots,
2657
2619
  ...props
2658
2620
  });
2659
2621
  });
2660
2622
  var defaultSlots = {
2661
2623
  root: {
2662
- className: mx3("p-2", inputSurface)
2624
+ className: mx3("w-full p-2 overflow-y-auto", inputSurface)
2663
2625
  }
2664
2626
  };
2665
2627
  var defaultTextSlots = {
2666
- ...defaultSlots,
2667
- editor: {
2668
- theme: textTheme
2669
- }
2628
+ ...defaultSlots
2670
2629
  };
2671
2630
  var defaultMarkdownSlots = {
2672
- ...defaultSlots,
2673
- editor: {
2674
- theme: markdownTheme
2675
- }
2631
+ ...defaultSlots
2676
2632
  };
2677
2633
 
2678
2634
  // packages/ui/react-ui-editor/src/hooks/automerge.ts
2679
- import get7 from "lodash.get";
2635
+ import get4 from "lodash.get";
2680
2636
  import { getRawDoc } from "@dxos/echo-schema";
2681
2637
  import { isNotNullOrUndefined } from "@dxos/util";
2682
2638
 
@@ -2684,7 +2640,7 @@ import { isNotNullOrUndefined } from "@dxos/util";
2684
2640
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2685
2641
  import { Context as Context2 } from "@dxos/context";
2686
2642
  import { invariant as invariant3 } from "@dxos/invariant";
2687
- import { log as log3 } from "@dxos/log";
2643
+ import { log as log2 } from "@dxos/log";
2688
2644
  var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2689
2645
  var DEBOUNCE_INTERVAL = 100;
2690
2646
  var SpaceAwarenessProvider = class {
@@ -2723,7 +2679,7 @@ var SpaceAwarenessProvider = class {
2723
2679
  void this._space.postMessage(this._channel, {
2724
2680
  kind: "query"
2725
2681
  }).catch((err) => {
2726
- log3.debug("failed to query awareness", {
2682
+ log2.debug("failed to query awareness", {
2727
2683
  err
2728
2684
  }, {
2729
2685
  F: __dxlog_file4,
@@ -2786,7 +2742,7 @@ var SpaceAwarenessProvider = class {
2786
2742
  };
2787
2743
 
2788
2744
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2789
- import { StateField as StateField6 } from "@codemirror/state";
2745
+ import { StateField as StateField5 } from "@codemirror/state";
2790
2746
  import { useEffect as useEffect2, useState as useState2 } from "react";
2791
2747
  import { isAutomergeObject } from "@dxos/react-client/echo";
2792
2748
 
@@ -2795,15 +2751,13 @@ import * as decoding from "lib0/decoding";
2795
2751
  import * as encoding from "lib0/encoding";
2796
2752
  import { Observable } from "lib0/observable";
2797
2753
  import * as YP from "y-protocols/awareness";
2798
- import * as Y2 from "yjs";
2799
2754
  import { invariant as invariant4 } from "@dxos/invariant";
2800
- import { log as log4 } from "@dxos/log";
2801
- import { arrayToString as arrayToString2, stringToArray as stringToArray2 } from "@dxos/util";
2755
+ import { log as log3 } from "@dxos/log";
2802
2756
  var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2803
2757
  var createYjsModel = ({ identity, space, text }) => {
2804
2758
  invariant4(text?.doc && text?.content, void 0, {
2805
2759
  F: __dxlog_file5,
2806
- L: 23,
2760
+ L: 21,
2807
2761
  S: void 0,
2808
2762
  A: [
2809
2763
  "text?.doc && text?.content",
@@ -2819,34 +2773,6 @@ var createYjsModel = ({ identity, space, text }) => {
2819
2773
  id: text.doc.guid,
2820
2774
  content: text.content,
2821
2775
  text: () => text.content.toString(),
2822
- // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
2823
- // TODO(dmaretskyi): Refactor as cursor-converter.
2824
- getCursorFromRange: (range) => {
2825
- const from = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.from));
2826
- const to = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.to, -1));
2827
- return [
2828
- arrayToString2(from),
2829
- arrayToString2(to)
2830
- ].join(":");
2831
- },
2832
- getRangeFromCursor: (cursor) => {
2833
- invariant4(text.doc, void 0, {
2834
- F: __dxlog_file5,
2835
- L: 40,
2836
- S: void 0,
2837
- A: [
2838
- "text.doc",
2839
- ""
2840
- ]
2841
- });
2842
- const parts = cursor.split(":");
2843
- const from = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[0])), text.doc);
2844
- const to = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[1])), text.doc);
2845
- return from && to ? {
2846
- from: from.index,
2847
- to: to.index
2848
- } : void 0;
2849
- },
2850
2776
  extension: [
2851
2777
  modelState.init(() => model),
2852
2778
  yjs(text.content, provider?.awareness)
@@ -2889,14 +2815,14 @@ var YAwarenessProvider = class extends Observable {
2889
2815
  return this._awareness;
2890
2816
  }
2891
2817
  _handleAwarenessUpdate({ added, updated, removed }, origin) {
2892
- log4("awareness update", {
2818
+ log3("awareness update", {
2893
2819
  added,
2894
2820
  updated,
2895
2821
  removed,
2896
2822
  origin
2897
2823
  }, {
2898
2824
  F: __dxlog_file5,
2899
- L: 122,
2825
+ L: 100,
2900
2826
  S: this,
2901
2827
  C: (f, a) => f(...a)
2902
2828
  });
@@ -2907,9 +2833,9 @@ var YAwarenessProvider = class extends Observable {
2907
2833
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2908
2834
  }
2909
2835
  _handleSpaceMessage({ payload }) {
2910
- log4("space message", payload, {
2836
+ log3("space message", payload, {
2911
2837
  F: __dxlog_file5,
2912
- L: 131,
2838
+ L: 109,
2913
2839
  S: this,
2914
2840
  C: (f, a) => f(...a)
2915
2841
  });
@@ -2953,13 +2879,13 @@ var YAwarenessProvider = class extends Observable {
2953
2879
  };
2954
2880
 
2955
2881
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2956
- var modelState = StateField6.define({
2882
+ var modelState = StateField5.define({
2957
2883
  create: () => void 0,
2958
2884
  update: (model) => model
2959
2885
  });
2960
2886
  var useTextModel = (props) => {
2961
2887
  const { identity, space, text } = props;
2962
- const [model, setModel] = useState2(() => createModel(props));
2888
+ const [model, setModel] = useState2();
2963
2889
  useEffect2(() => setModel(createModel(props)), [
2964
2890
  identity,
2965
2891
  space,
@@ -2997,7 +2923,7 @@ var createAutomergeModel = ({ space, identity, text }) => {
2997
2923
  const model = {
2998
2924
  id: obj.id,
2999
2925
  content: doc,
3000
- text: () => get7(doc.handle.docSync(), doc.path),
2926
+ text: () => get4(doc.handle.docSync(), doc.path),
3001
2927
  // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
3002
2928
  extension: [
3003
2929
  modelState.init(() => model),
@@ -3018,6 +2944,7 @@ var createAutomergeModel = ({ space, identity, text }) => {
3018
2944
  export {
3019
2945
  AwarenessProvider,
3020
2946
  BaseTextEditor,
2947
+ Cursor,
3021
2948
  Doc,
3022
2949
  EditorModes,
3023
2950
  MarkdownEditor,
@@ -3033,6 +2960,7 @@ export {
3033
2960
  awareness,
3034
2961
  basicBundle,
3035
2962
  blast,
2963
+ callbackWrapper,
3036
2964
  code2 as code,
3037
2965
  codeTheme,
3038
2966
  comments,
@@ -3043,6 +2971,7 @@ export {
3043
2971
  defaultSlots,
3044
2972
  defaultTextSlots,
3045
2973
  defaultTheme,
2974
+ getToken,
3046
2975
  hr,
3047
2976
  image,
3048
2977
  link,
@@ -3053,8 +2982,8 @@ export {
3053
2982
  markdownTagsExtensions,
3054
2983
  markdownTheme,
3055
2984
  mention,
3056
- mermaid,
3057
2985
  modelState,
2986
+ outliner,
3058
2987
  setCommentRange,
3059
2988
  setFocus,
3060
2989
  setSelection,