@dxos/react-ui-editor 0.3.11-main.c01834e → 0.3.11-main.c86e105

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 (70) hide show
  1. package/dist/lib/browser/index.mjs +301 -384
  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 +6 -2
  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/cursor.d.ts +1 -1
  15. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  16. package/dist/types/src/extensions/code.d.ts.map +1 -1
  17. package/dist/types/src/extensions/comments.d.ts +6 -2
  18. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  19. package/dist/types/src/{util → extensions}/cursor.d.ts +9 -1
  20. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  21. package/dist/types/src/extensions/index.d.ts +1 -0
  22. package/dist/types/src/extensions/index.d.ts.map +1 -1
  23. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  24. package/dist/types/src/extensions/tasklist.d.ts +5 -1
  25. package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
  26. package/dist/types/src/extensions/yjs/cursor.d.ts +1 -1
  27. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -1
  28. package/dist/types/src/hooks/useTextModel.d.ts +0 -5
  29. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  30. package/dist/types/src/hooks/yjs.d.ts.map +1 -1
  31. package/dist/types/src/index.d.ts +2 -0
  32. package/dist/types/src/index.d.ts.map +1 -1
  33. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  34. package/dist/types/src/themes/default.d.ts.map +1 -1
  35. package/dist/types/src/util.d.ts.map +1 -0
  36. package/package.json +25 -24
  37. package/src/components/TextEditor/MarkdownEditor.stories.tsx +19 -16
  38. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  39. package/src/components/TextEditor/TextEditor.tsx +68 -64
  40. package/src/components/TextEditor/comments.stories.tsx +329 -0
  41. package/src/extensions/autocomplete.ts +18 -17
  42. package/src/extensions/automerge/automerge.ts +2 -2
  43. package/src/extensions/automerge/cursor.ts +2 -1
  44. package/src/extensions/awareness.ts +25 -32
  45. package/src/extensions/code.ts +10 -2
  46. package/src/extensions/comments.ts +86 -116
  47. package/src/extensions/cursor.ts +46 -0
  48. package/src/extensions/hr.ts +2 -2
  49. package/src/extensions/image.ts +2 -2
  50. package/src/extensions/index.ts +1 -0
  51. package/src/extensions/link.ts +4 -4
  52. package/src/extensions/markdown/highlight.ts +0 -8
  53. package/src/extensions/table.ts +2 -2
  54. package/src/extensions/tasklist.ts +53 -77
  55. package/src/extensions/yjs/cursor.ts +2 -1
  56. package/src/extensions/yjs/yjs.test.ts +1 -1
  57. package/src/extensions/yjs/yjs.ts +2 -2
  58. package/src/hooks/useTextModel.ts +4 -7
  59. package/src/hooks/yjs.ts +1 -23
  60. package/src/index.ts +2 -0
  61. package/src/styles/markdown.ts +0 -2
  62. package/src/themes/default.ts +17 -50
  63. package/dist/types/src/util/cursor.d.ts.map +0 -1
  64. package/dist/types/src/util/index.d.ts +0 -3
  65. package/dist/types/src/util/index.d.ts.map +0 -1
  66. package/dist/types/src/util/util.d.ts.map +0 -1
  67. package/src/util/cursor.ts +0 -29
  68. package/src/util/index.ts +0 -6
  69. /package/dist/types/src/{util/util.d.ts → util.d.ts} +0 -0
  70. /package/src/{util/util.ts → util.ts} +0 -0
@@ -11,40 +11,41 @@ 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
  };
@@ -273,30 +274,39 @@ var PatchSemaphore = class {
273
274
  }
274
275
  };
275
276
 
276
- // packages/ui/react-ui-editor/src/util/cursor.ts
277
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
277
278
  import { Facet } from "@codemirror/state";
278
- var DEFAULT_CURSOR_CONVERTER = {
279
+ var defaultCursorConverter = {
279
280
  toCursor: (position) => position.toString(),
280
281
  fromCursor: (cursor) => parseInt(cursor)
281
282
  };
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)
283
+ var Cursor = class {
284
+ static {
285
+ this.converter = Facet.define({
286
+ combine: (providers) => providers[0] ?? defaultCursorConverter
298
287
  });
299
288
  }
289
+ static {
290
+ this.getCursorFromRange = (cursorConverter3, range) => {
291
+ const from = cursorConverter3.toCursor(range.from);
292
+ const to = cursorConverter3.toCursor(range.to, -1);
293
+ return [
294
+ from,
295
+ to
296
+ ].join(":");
297
+ };
298
+ }
299
+ static {
300
+ this.getRangeFromCursor = (cursorConverter3, cursor) => {
301
+ const parts = cursor.split(":");
302
+ const from = cursorConverter3.fromCursor(parts[0]);
303
+ const to = cursorConverter3.fromCursor(parts[1]);
304
+ return from !== void 0 && to !== void 0 ? {
305
+ from,
306
+ to
307
+ } : void 0;
308
+ };
309
+ }
300
310
  };
301
311
 
302
312
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
@@ -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, () => {
@@ -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,7 +483,7 @@ 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++) {
@@ -484,7 +494,7 @@ var RemoteSelectionsDecorator = class {
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",
@@ -595,7 +600,7 @@ var styles = EditorView.baseTheme({
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,
@@ -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: [
@@ -1056,7 +1061,7 @@ var getLineRange = (lines, from, to) => {
1056
1061
  var code2 = () => {
1057
1062
  const getDecorations = (view) => {
1058
1063
  const decorations = [];
1059
- if (view.state.readOnly) {
1064
+ if (!view.hasFocus || view.state.readOnly) {
1060
1065
  const text = view.state.doc.sliceString(0);
1061
1066
  const matches = text.matchAll(CODE_REGEX);
1062
1067
  const blocks = view.viewportLineBlocks;
@@ -1075,10 +1080,15 @@ var code2 = () => {
1075
1080
  return [
1076
1081
  ViewPlugin3.fromClass(class {
1077
1082
  constructor(view) {
1083
+ this.hasFocus = false;
1078
1084
  this.decorations = getDecorations(view);
1079
1085
  }
1080
1086
  update(update2) {
1081
- if (update2.docChanged || update2.viewportChanged) {
1087
+ if (
1088
+ // TODO(burdon): Generalize for other extensions.
1089
+ this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1090
+ ) {
1091
+ this.hasFocus = update2.view.hasFocus;
1082
1092
  this.decorations = getDecorations(update2.view);
1083
1093
  }
1084
1094
  }
@@ -1091,23 +1101,32 @@ var code2 = () => {
1091
1101
 
1092
1102
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1093
1103
  import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1094
- import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1104
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
1095
1105
  import sortBy from "lodash.sortby";
1096
1106
  import { debounce } from "@dxos/async";
1097
1107
  import { invariant as invariant2 } from "@dxos/invariant";
1098
1108
  import { log as log2 } from "@dxos/log";
1099
1109
  import { nonNullable } from "@dxos/util";
1110
+
1111
+ // packages/ui/react-ui-editor/src/util.ts
1112
+ import { log } from "@dxos/log";
1113
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1114
+ var callbackWrapper = (fn) => (...args) => {
1115
+ try {
1116
+ return fn(...args);
1117
+ } catch (error) {
1118
+ log.catch(error, void 0, {
1119
+ F: __dxlog_file2,
1120
+ L: 16,
1121
+ S: void 0,
1122
+ C: (f, a) => f(...a)
1123
+ });
1124
+ }
1125
+ };
1126
+
1127
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
1100
1128
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1101
1129
  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
1130
  "& .cm-comment": {
1112
1131
  backgroundColor: getToken("extend.colors.yellow.50")
1113
1132
  },
@@ -1146,7 +1165,7 @@ var commentsStateField = StateField2.define({
1146
1165
  ranges: []
1147
1166
  }),
1148
1167
  update: (value, tr) => {
1149
- const cursorConverter3 = tr.state.facet(CursorConverter);
1168
+ const cursorConverter3 = tr.state.facet(Cursor.converter);
1150
1169
  for (const effect of tr.effects) {
1151
1170
  if (effect.is(setSelection)) {
1152
1171
  return {
@@ -1157,7 +1176,7 @@ var commentsStateField = StateField2.define({
1157
1176
  if (effect.is(setCommentRange)) {
1158
1177
  const { comments: comments2 } = effect.value;
1159
1178
  const ranges = comments2.map((comment) => {
1160
- const range = getRangeFromCursor(cursorConverter3, comment.cursor);
1179
+ const range = Cursor.getRangeFromCursor(cursorConverter3, comment.cursor);
1161
1180
  return range && {
1162
1181
  ...comment,
1163
1182
  ...range
@@ -1195,42 +1214,68 @@ var highlightDecorations = EditorView5.decorations.compute([
1195
1214
  }).filter(nonNullable) ?? [];
1196
1215
  return Decoration3.set(decorations);
1197
1216
  });
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: 168,
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();
1217
+ var trackPastedComments = (onMove) => {
1218
+ let tracked = null;
1219
+ const registerCopy = (event, view) => {
1220
+ const comments2 = view.state.field(commentsStateField);
1221
+ const { main } = view.state.selection;
1222
+ const inSel = comments2.ranges.filter((range) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1223
+ if (!inSel.length) {
1224
+ tracked = null;
1225
+ } else {
1226
+ tracked = {
1227
+ text: view.state.doc.slice(main.from, main.to),
1228
+ comments: inSel.map((range) => ({
1229
+ from: range.from - main.from,
1230
+ to: range.to - main.from,
1231
+ id: range.id
1232
+ }))
1233
+ };
1223
1234
  }
1224
- return span;
1225
- }
1235
+ };
1236
+ return [
1237
+ EditorView5.domEventHandlers({
1238
+ cut: registerCopy,
1239
+ copy: registerCopy
1240
+ }),
1241
+ EditorView5.updateListener.of((update2) => {
1242
+ if (tracked) {
1243
+ const paste = update2.transactions.find((tr) => tr.isUserEvent("input.paste"));
1244
+ if (paste) {
1245
+ let found = -1;
1246
+ paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1247
+ if (text.eq(tracked.text)) {
1248
+ for (let i = update2.transactions.indexOf(paste) + 1; i < update2.transactions.length; i++) {
1249
+ fromB = update2.transactions[i].changes.mapPos(fromB);
1250
+ }
1251
+ found = fromB;
1252
+ }
1253
+ });
1254
+ if (found > -1) {
1255
+ const active = update2.view.state.field(commentsStateField).ranges;
1256
+ for (const moved of tracked.comments) {
1257
+ if (active.some((range) => range.id === moved.id && range.from === range.to)) {
1258
+ onMove(moved.id, Cursor.getCursorFromRange(update2.view.state.facet(Cursor.converter), {
1259
+ from: found + moved.from,
1260
+ to: found + moved.to
1261
+ }));
1262
+ }
1263
+ }
1264
+ }
1265
+ tracked = null;
1266
+ }
1267
+ }
1268
+ })
1269
+ ];
1226
1270
  };
1227
1271
  var comments = (options = {}) => {
1272
+ const { key: shortcut = "meta-'" } = options;
1228
1273
  const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1229
1274
  const createCommentThread = (view) => {
1230
- const cursorConverter3 = view.state.facet(CursorConverter);
1275
+ const cursorConverter3 = view.state.facet(Cursor.converter);
1231
1276
  invariant2(options.onCreate, void 0, {
1232
1277
  F: __dxlog_file3,
1233
- L: 224,
1278
+ L: 240,
1234
1279
  S: void 0,
1235
1280
  A: [
1236
1281
  "options.onCreate",
@@ -1241,12 +1286,12 @@ var comments = (options = {}) => {
1241
1286
  if (from === to) {
1242
1287
  return false;
1243
1288
  }
1244
- const relPos = getCursorFromRange(cursorConverter3, {
1289
+ const cursor = Cursor.getCursorFromRange(cursorConverter3, {
1245
1290
  from,
1246
1291
  to
1247
1292
  });
1248
- if (relPos) {
1249
- const id = callbackWrapper(options.onCreate)(relPos);
1293
+ if (cursor) {
1294
+ const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
1250
1295
  if (id) {
1251
1296
  view.dispatch({
1252
1297
  effects: setSelection.of({
@@ -1273,35 +1318,7 @@ var comments = (options = {}) => {
1273
1318
  }
1274
1319
  return false;
1275
1320
  };
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(update2) {
1295
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update2, 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
1321
  return [
1304
- bookmarksViewPlugin,
1305
1322
  commentsStateField,
1306
1323
  highlightDecorations,
1307
1324
  styles3,
@@ -1310,7 +1327,7 @@ var comments = (options = {}) => {
1310
1327
  //
1311
1328
  options.onCreate ? keymap3.of([
1312
1329
  {
1313
- key: options?.key ?? "meta-'",
1330
+ key: shortcut,
1314
1331
  run: callbackWrapper(createCommentThread)
1315
1332
  }
1316
1333
  ]) : [],
@@ -1327,7 +1344,7 @@ var comments = (options = {}) => {
1327
1344
  above: true,
1328
1345
  create: () => {
1329
1346
  const el = document.createElement("div");
1330
- options.onHover?.(el);
1347
+ options.onHover?.(el, shortcut);
1331
1348
  return {
1332
1349
  dom: el,
1333
1350
  offset: {
@@ -1348,27 +1365,27 @@ var comments = (options = {}) => {
1348
1365
  // TODO(burdon): Is there a better (finer grained) way to do this?
1349
1366
  //
1350
1367
  EditorView5.updateListener.of(({ view, state, changes }) => {
1351
- const cursorConverter3 = view.state.facet(CursorConverter);
1368
+ const cursorConverter3 = view.state.facet(Cursor.converter);
1352
1369
  {
1353
1370
  let mod = false;
1354
1371
  const { active, ranges } = state.field(commentsStateField);
1355
1372
  changes.iterChanges((from, to, from2, to2) => {
1356
1373
  ranges.forEach((range) => {
1357
1374
  if (from2 === to2) {
1358
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1375
+ const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1359
1376
  if (!newRange || newRange.to - newRange.from === 0) {
1360
1377
  log2.info("deleted comment", {
1361
1378
  thread: range.id
1362
1379
  }, {
1363
1380
  F: __dxlog_file3,
1364
- L: 358,
1381
+ L: 337,
1365
1382
  S: void 0,
1366
1383
  C: (f, a) => f(...a)
1367
1384
  });
1368
1385
  }
1369
1386
  }
1370
1387
  if (from <= range.to) {
1371
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1388
+ const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1372
1389
  Object.assign(range, newRange);
1373
1390
  mod = true;
1374
1391
  }
@@ -1414,29 +1431,13 @@ var comments = (options = {}) => {
1414
1431
  });
1415
1432
  }
1416
1433
  }
1417
- })
1434
+ }),
1435
+ options.onMove ? trackPastedComments(options.onMove) : []
1418
1436
  ];
1419
1437
  };
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 !== void 0 && to !== void 0 ? {
1433
- from,
1434
- to
1435
- } : void 0;
1436
- };
1437
1438
 
1438
1439
  // 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 { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1440
1441
  var styles4 = EditorView6.baseTheme({
1441
1442
  "& .cm-hr": {
1442
1443
  // TODO(burdon): ???
@@ -1444,7 +1445,7 @@ var styles4 = EditorView6.baseTheme({
1444
1445
  borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1445
1446
  }
1446
1447
  });
1447
- var HorizontalRuleWidget = class extends WidgetType3 {
1448
+ var HorizontalRuleWidget = class extends WidgetType2 {
1448
1449
  constructor(_pos) {
1449
1450
  super();
1450
1451
  this._pos = _pos;
@@ -1458,7 +1459,7 @@ var HorizontalRuleWidget = class extends WidgetType3 {
1458
1459
  return el;
1459
1460
  }
1460
1461
  };
1461
- var placeholderMatcher = new MatchDecorator2({
1462
+ var placeholderMatcher = new MatchDecorator({
1462
1463
  regexp: /^---$/g,
1463
1464
  decoration: (match, view, pos) => Decoration4.replace({
1464
1465
  widget: new HorizontalRuleWidget(pos)
@@ -1466,7 +1467,7 @@ var placeholderMatcher = new MatchDecorator2({
1466
1467
  });
1467
1468
  var hr = () => [
1468
1469
  styles4,
1469
- ViewPlugin5.fromClass(class {
1470
+ ViewPlugin4.fromClass(class {
1470
1471
  constructor(view) {
1471
1472
  this.rules = placeholderMatcher.createDeco(view);
1472
1473
  }
@@ -1485,7 +1486,7 @@ var hr = () => [
1485
1486
  // packages/ui/react-ui-editor/src/extensions/image.ts
1486
1487
  import { syntaxTree } from "@codemirror/language";
1487
1488
  import { StateField as StateField3 } from "@codemirror/state";
1488
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1489
+ import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
1489
1490
  var image = (options = {}) => {
1490
1491
  return StateField3.define({
1491
1492
  create: (state) => {
@@ -1537,7 +1538,7 @@ var buildDecorations = (from, to, state) => {
1537
1538
  });
1538
1539
  return decorations;
1539
1540
  };
1540
- var ImageWidget = class extends WidgetType4 {
1541
+ var ImageWidget = class extends WidgetType3 {
1541
1542
  constructor(_url) {
1542
1543
  super();
1543
1544
  this._url = _url;
@@ -1557,11 +1558,11 @@ var ImageWidget = class extends WidgetType4 {
1557
1558
  // packages/ui/react-ui-editor/src/extensions/link.ts
1558
1559
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1559
1560
  import { RangeSetBuilder } from "@codemirror/state";
1560
- import { Decoration as Decoration6, WidgetType as WidgetType5, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
1561
+ import { Decoration as Decoration6, WidgetType as WidgetType4, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1561
1562
  import { tooltipContent } from "@dxos/react-ui-theme";
1562
1563
  var link = (options = {}) => {
1563
1564
  const extensions = [
1564
- ViewPlugin6.fromClass(class {
1565
+ ViewPlugin5.fromClass(class {
1565
1566
  constructor(view) {
1566
1567
  this.decorations = buildDecorations2(view, options);
1567
1568
  }
@@ -1612,7 +1613,7 @@ var link = (options = {}) => {
1612
1613
  }
1613
1614
  return extensions;
1614
1615
  };
1615
- var LinkButton = class extends WidgetType5 {
1616
+ var LinkButton = class extends WidgetType4 {
1616
1617
  constructor(_url, _onAttach) {
1617
1618
  super();
1618
1619
  this._url = _url;
@@ -1627,7 +1628,7 @@ var LinkButton = class extends WidgetType5 {
1627
1628
  return el;
1628
1629
  }
1629
1630
  };
1630
- var LinkText = class extends WidgetType5 {
1631
+ var LinkText = class extends WidgetType4 {
1631
1632
  constructor(_text, _url) {
1632
1633
  super();
1633
1634
  this._text = _text;
@@ -1708,7 +1709,7 @@ var listener = ({ onFocus, onChange }) => {
1708
1709
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1709
1710
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1710
1711
  import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1711
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
1712
+ import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1712
1713
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1713
1714
  import { languages } from "@codemirror/language-data";
1714
1715
  import { searchKeymap } from "@codemirror/search";
@@ -1717,7 +1718,7 @@ import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/the
1717
1718
  import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1718
1719
 
1719
1720
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1720
- import { markdownLanguage } from "@codemirror/lang-markdown";
1721
+ import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
1721
1722
  import { HighlightStyle } from "@codemirror/language";
1722
1723
  import { tags, styleTags, Tag } from "@lezer/highlight";
1723
1724
  import { Table } from "@lezer/markdown";
@@ -1727,7 +1728,6 @@ var markdownTags = {
1727
1728
  CodeText: Tag.define(),
1728
1729
  EmphasisMark: Tag.define(),
1729
1730
  HeaderMark: Tag.define(),
1730
- // HorizontalRule: Tag.define(),
1731
1731
  InlineCode: Tag.define(),
1732
1732
  LinkLabel: Tag.define(),
1733
1733
  LinkReference: Tag.define(),
@@ -1891,12 +1891,6 @@ var markdownHighlightStyle = (readonly) => {
1891
1891
  ],
1892
1892
  class: blockquote
1893
1893
  },
1894
- // TODO(burdon): Replace with extension.
1895
- // {
1896
- // tag: [markdownTags.HorizontalRule],
1897
- // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1898
- // },
1899
- // TODO(burdon): Only if being edited.
1900
1894
  {
1901
1895
  tag: [
1902
1896
  markdownTags.TableCell
@@ -1904,7 +1898,7 @@ var markdownHighlightStyle = (readonly) => {
1904
1898
  class: "font-mono"
1905
1899
  }
1906
1900
  ], {
1907
- scope: markdownLanguage,
1901
+ scope: markdownLanguage2,
1908
1902
  all: {
1909
1903
  fontFamily: getToken("fontFamily.body", []).join(",")
1910
1904
  }
@@ -1936,7 +1930,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1936
1930
  // NOTE: This extends the parser; it doesn't affect rendering.
1937
1931
  // https://github.github.com/gfm
1938
1932
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1939
- base: markdownLanguage2,
1933
+ base: markdownLanguage3,
1940
1934
  // Languages for syntax highlighting fenced code blocks.
1941
1935
  codeLanguages: languages,
1942
1936
  // Parser extensions.
@@ -1990,7 +1984,7 @@ var mention = ({ onSearch }) => {
1990
1984
  // packages/ui/react-ui-editor/src/extensions/table.ts
1991
1985
  import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1992
1986
  import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1993
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType6 } from "@codemirror/view";
1987
+ import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
1994
1988
  var table = (options = {}) => {
1995
1989
  return StateField4.define({
1996
1990
  create: (state) => update(state, options),
@@ -2048,7 +2042,7 @@ var update = (state, options) => {
2048
2042
  });
2049
2043
  return builder.finish();
2050
2044
  };
2051
- var TableWidget = class extends WidgetType6 {
2045
+ var TableWidget = class extends WidgetType5 {
2052
2046
  constructor(_table) {
2053
2047
  super();
2054
2048
  this._table = _table;
@@ -2086,87 +2080,85 @@ var TableWidget = class extends WidgetType6 {
2086
2080
  };
2087
2081
 
2088
2082
  // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2089
- import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType7, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin7 } from "@codemirror/view";
2090
- var styles5 = EditorView11.baseTheme({
2091
- "& .cm-task-item": {
2092
- paddingLeft: "4px",
2093
- paddingRight: "8px"
2094
- }
2095
- });
2096
- var CheckboxWidget = class extends WidgetType7 {
2097
- constructor(_checked, _indent, _onCheck) {
2083
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2084
+ import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
2085
+ import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType6, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2086
+ var CheckboxWidget = class extends WidgetType6 {
2087
+ constructor(_checked) {
2098
2088
  super();
2099
2089
  this._checked = _checked;
2100
- this._indent = _indent;
2101
- this._onCheck = _onCheck;
2102
2090
  }
2103
2091
  eq(other) {
2104
- return this._checked === other._checked && this._indent === other._indent;
2092
+ return this._checked === other._checked;
2105
2093
  }
2106
2094
  toDOM(view) {
2107
- const wrap = document.createElement("span");
2108
- wrap.className = "cm-task-item";
2109
- wrap.setAttribute("aria-hidden", "true");
2110
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
2111
- const input = wrap.appendChild(document.createElement("input"));
2095
+ const input = document.createElement("input");
2112
2096
  input.type = "checkbox";
2113
2097
  input.checked = this._checked;
2114
- if (!this._onCheck) {
2098
+ if (view.state.readOnly) {
2115
2099
  input.setAttribute("disabled", "true");
2116
- }
2117
- if (this._onCheck) {
2118
- input.onchange = (event) => {
2119
- this._onCheck?.(event.target.checked);
2120
- return true;
2100
+ } else {
2101
+ input.onmousedown = (event) => {
2102
+ const pos = view.posAtDOM(input);
2103
+ const text = view.state.sliceDoc(pos, pos + 3);
2104
+ if (text === (this._checked ? "[x]" : "[ ]")) {
2105
+ view.dispatch({
2106
+ changes: {
2107
+ from: pos + 1,
2108
+ to: pos + 2,
2109
+ insert: this._checked ? " " : "x"
2110
+ }
2111
+ });
2112
+ event.preventDefault();
2113
+ }
2121
2114
  };
2122
2115
  }
2123
- return wrap;
2116
+ return input;
2124
2117
  }
2125
2118
  ignoreEvent() {
2126
2119
  return false;
2127
2120
  }
2128
2121
  };
2122
+ var checkedDeco = Decoration8.replace({
2123
+ widget: new CheckboxWidget(true)
2124
+ });
2125
+ var uncheckedDeco = Decoration8.replace({
2126
+ widget: new CheckboxWidget(false)
2127
+ });
2129
2128
  var tasklist = (options = {}) => {
2130
- const taskMatcher = new MatchDecorator3({
2131
- regexp: /^(\s*)- \[([ xX])\]\s/g,
2132
- decoration: (match, view, pos) => {
2133
- const indent = Math.floor(match[1].length / 2);
2134
- const checked = match[2] === "x" || match[2] === "X";
2135
- return Decoration8.replace({
2136
- widget: new CheckboxWidget(checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2137
- const idx = pos + match[0].indexOf("[") + 1;
2138
- view.dispatch({
2139
- changes: {
2140
- from: idx,
2141
- to: idx + 1,
2142
- insert: checked2 ? "x" : " "
2143
- },
2144
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
2145
- selection: {
2146
- anchor: idx + 3
2147
- }
2148
- });
2149
- })
2150
- });
2151
- }
2152
- });
2153
- const tasks = ViewPlugin7.fromClass(class {
2129
+ return ViewPlugin6.fromClass(class {
2154
2130
  constructor(view) {
2155
- this.tasks = taskMatcher.createDeco(view);
2131
+ this.decorations = buildDecorations3(view);
2156
2132
  }
2157
2133
  update(update2) {
2158
- this.tasks = taskMatcher.updateDeco(update2, this.tasks);
2134
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2135
+ this.decorations = buildDecorations3(update2.view);
2136
+ }
2159
2137
  }
2160
2138
  }, {
2161
- decorations: (value) => value.tasks,
2139
+ decorations: (v) => v.decorations,
2162
2140
  provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2163
- return view.plugin(plugin)?.tasks || Decoration8.none;
2141
+ return view.plugin(plugin)?.decorations || Decoration8.none;
2164
2142
  })
2165
2143
  });
2166
- return [
2167
- tasks,
2168
- styles5
2169
- ];
2144
+ };
2145
+ var buildDecorations3 = (view) => {
2146
+ const builder = new RangeSetBuilder3();
2147
+ const { state } = view;
2148
+ const cursor = state.selection.main.head;
2149
+ for (const { from, to } of view.visibleRanges) {
2150
+ syntaxTree4(state).iterate({
2151
+ enter: (node) => {
2152
+ if (node.name === "TaskMarker" && (cursor < node.from || cursor > node.to)) {
2153
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2154
+ builder.add(node.from, node.to, checked ? checkedDeco : uncheckedDeco);
2155
+ }
2156
+ },
2157
+ from,
2158
+ to
2159
+ });
2160
+ }
2161
+ return builder.finish();
2170
2162
  };
2171
2163
 
2172
2164
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
@@ -2243,7 +2235,7 @@ var cursorConverter2 = (text) => ({
2243
2235
 
2244
2236
  // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2245
2237
  var yjs = (content, awareness2) => [
2246
- CursorConverter.of(cursorConverter2(content)),
2238
+ Cursor.converter.of(cursorConverter2(content)),
2247
2239
  yCollab(content, awareness2)
2248
2240
  ];
2249
2241
 
@@ -2340,33 +2332,6 @@ var defaultTheme = {
2340
2332
  backgroundColor: get3(tokens, "extend.colors.yellow.700")
2341
2333
  },
2342
2334
  //
2343
- // collaboration
2344
- // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2345
- //
2346
- "&light .cm-ySelection, &light .cm-yLineSelection": {
2347
- mixBlendMode: "multiply"
2348
- },
2349
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2350
- mixBlendMode: "screen"
2351
- },
2352
- ".cm-ySelectionInfo": {
2353
- padding: "2px 4px",
2354
- marginBlockStart: "-4px"
2355
- },
2356
- ".cm-ySelection, & .cm-selectionMatch": {
2357
- paddingBlockStart: ".15em",
2358
- paddingBlockEnd: ".15em"
2359
- },
2360
- ".cm-ySelectionCaret": {
2361
- display: "inline-block",
2362
- insetBlockStart: ".1em",
2363
- blockSize: "1.4em",
2364
- verticalAlign: "top"
2365
- },
2366
- ".cm-yLineSelection": {
2367
- margin: 0
2368
- },
2369
- //
2370
2335
  // link
2371
2336
  //
2372
2337
  ".cm-link": {
@@ -2403,13 +2368,6 @@ var defaultTheme = {
2403
2368
  textDecoration: "none"
2404
2369
  },
2405
2370
  //
2406
- // widgets
2407
- //
2408
- ".cm-widgetBuffer": {
2409
- display: "none",
2410
- height: 0
2411
- },
2412
- //
2413
2371
  // table
2414
2372
  //
2415
2373
  ".cm-table *": {
@@ -2440,27 +2398,17 @@ var defaultTheme = {
2440
2398
  },
2441
2399
  //
2442
2400
  // font size
2443
- // 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.
2401
+ // TODO(thure): This appears to be the best or only way to set selection caret heights,
2402
+ // but it's far more verbose than it needs to be.
2444
2403
  //
2445
- ...Object.keys(get3(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2446
- const height = get3(tokens, [
2447
- "extend",
2448
- "fontSize",
2449
- fontSize,
2450
- 1,
2451
- "lineHeight"
2452
- ]);
2453
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2454
- height
2455
- };
2456
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2457
- height
2458
- };
2459
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2460
- height
2461
- };
2462
- return acc;
2463
- }, {}),
2404
+ // ...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
2405
+ // const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
2406
+ //
2407
+ // acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
2408
+ // acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
2409
+ // acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
2410
+ // return acc;
2411
+ // }, {}),
2464
2412
  /**
2465
2413
  * Panels
2466
2414
  * TODO(burdon): Needs styling attention (esp. dark mode).
@@ -2511,7 +2459,9 @@ var textTheme = {
2511
2459
  }
2512
2460
  };
2513
2461
  var markdownTheme = {
2514
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2462
+ ".cm-scroller": {
2463
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2464
+ },
2515
2465
  ".cm-placeholder": {
2516
2466
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2517
2467
  }
@@ -2530,21 +2480,23 @@ var EditorModes = [
2530
2480
  "default",
2531
2481
  "vim"
2532
2482
  ];
2533
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
2534
- const { themeMode } = useThemeContext();
2483
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, readonly, comments: comments2, extensions = [], editorMode, theme, slots = defaultSlots }, forwardedRef) => {
2535
2484
  const tabsterDOMAttribute = useFocusableGroup({
2536
2485
  tabBehavior: "limited"
2537
2486
  });
2538
- const [root, setRoot] = useState(null);
2539
- const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
2540
- useImperativeHandle(forwardedRef, () => ({
2541
- root,
2542
- state,
2487
+ const { themeMode } = useThemeContext();
2488
+ const rootRef = useRef(null);
2489
+ const [view, setView] = useState(null);
2490
+ useImperativeHandle(forwardedRef, () => view, [
2543
2491
  view
2544
- }), [
2492
+ ]);
2493
+ useEffect(() => {
2494
+ if (view && focus) {
2495
+ view.focus();
2496
+ }
2497
+ }, [
2545
2498
  view,
2546
- state,
2547
- root
2499
+ focus
2548
2500
  ]);
2549
2501
  const { awareness: awareness2, peer } = model;
2550
2502
  useEffect(() => {
@@ -2568,7 +2520,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2568
2520
  themeMode
2569
2521
  ]);
2570
2522
  useEffect(() => {
2571
- if (view && comments2?.length) {
2523
+ if (view && comments2 !== void 0) {
2572
2524
  view.dispatch({
2573
2525
  effects: setCommentRange.of({
2574
2526
  model,
@@ -2581,11 +2533,13 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2581
2533
  comments2
2582
2534
  ]);
2583
2535
  useEffect(() => {
2584
- if (!root) {
2536
+ if (!model || !rootRef.current) {
2585
2537
  return;
2586
2538
  }
2587
- const state2 = EditorState2.create({
2539
+ const state = EditorState2.create({
2588
2540
  doc: model.text(),
2541
+ // TODO(burdon): Composer should store and set selection when switching documents.
2542
+ selection,
2589
2543
  extensions: [
2590
2544
  readonly && EditorState2.readOnly.of(readonly),
2591
2545
  // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
@@ -2593,7 +2547,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2593
2547
  // Theme.
2594
2548
  // TODO(burdon): Make theme configurable.
2595
2549
  EditorView12.baseTheme(defaultTheme),
2596
- EditorView12.theme(slots?.editor?.theme ?? {}),
2550
+ EditorView12.theme(theme ?? {}),
2597
2551
  EditorView12.darkTheme.of(themeMode === "dark"),
2598
2552
  // Storage and replication.
2599
2553
  model.extension,
@@ -2602,19 +2556,17 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2602
2556
  ].filter(Boolean)
2603
2557
  });
2604
2558
  view?.destroy();
2605
- setEditor({
2606
- state: state2,
2607
- view: new EditorView12({
2608
- state: state2,
2609
- parent: root
2610
- })
2559
+ const newView = new EditorView12({
2560
+ parent: rootRef.current,
2561
+ state
2611
2562
  });
2563
+ setView(newView);
2612
2564
  return () => {
2613
- view?.destroy();
2614
- setEditor(void 0);
2565
+ newView?.destroy();
2566
+ setView(null);
2615
2567
  };
2616
2568
  }, [
2617
- root,
2569
+ rootRef,
2618
2570
  model,
2619
2571
  readonly,
2620
2572
  editorMode,
@@ -2623,12 +2575,8 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2623
2575
  const handleKeyUp = useCallback((event) => {
2624
2576
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2625
2577
  switch (key) {
2626
- case "Enter": {
2627
- view?.contentDOM.focus();
2628
- break;
2629
- }
2630
2578
  case "Escape": {
2631
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2579
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
2632
2580
  break;
2633
2581
  }
2634
2582
  }
@@ -2638,16 +2586,17 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2638
2586
  ]);
2639
2587
  return /* @__PURE__ */ React.createElement("div", {
2640
2588
  key: model.id,
2641
- ref: setRoot,
2589
+ role: "none",
2590
+ ref: rootRef,
2642
2591
  tabIndex: 0,
2643
- ...slots?.root,
2644
- ...editorMode !== "vim" && tabsterDOMAttribute,
2645
- onKeyUp: handleKeyUp
2592
+ onKeyUp: handleKeyUp,
2593
+ ...slots.root,
2594
+ ...editorMode !== "vim" && tabsterDOMAttribute
2646
2595
  });
2647
2596
  });
2648
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2597
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
2649
2598
  const { themeMode } = useThemeContext();
2650
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2599
+ const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2651
2600
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2652
2601
  ref: forwardedRef,
2653
2602
  readonly,
@@ -2655,17 +2604,18 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots:
2655
2604
  basicBundle({
2656
2605
  readonly,
2657
2606
  themeMode,
2658
- placeholder: slots?.editor?.placeholder
2607
+ placeholder: placeholder3
2659
2608
  }),
2660
2609
  ...extensions
2661
2610
  ],
2662
- slots,
2611
+ theme,
2612
+ slots: updatedSlots,
2663
2613
  ...props
2664
2614
  });
2665
2615
  });
2666
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2616
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
2667
2617
  const { themeMode } = useThemeContext();
2668
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
2618
+ const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2669
2619
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2670
2620
  ref: forwardedRef,
2671
2621
  readonly,
@@ -2673,30 +2623,25 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], sl
2673
2623
  markdownBundle({
2674
2624
  readonly,
2675
2625
  themeMode,
2676
- placeholder: slots?.editor?.placeholder
2626
+ placeholder: placeholder3
2677
2627
  }),
2678
2628
  ...extensions
2679
2629
  ],
2680
- slots,
2630
+ theme,
2631
+ slots: updatedSlots,
2681
2632
  ...props
2682
2633
  });
2683
2634
  });
2684
2635
  var defaultSlots = {
2685
2636
  root: {
2686
- className: mx3("p-2", inputSurface)
2637
+ className: mx3("p-2 overflow-y-auto", inputSurface)
2687
2638
  }
2688
2639
  };
2689
2640
  var defaultTextSlots = {
2690
- ...defaultSlots,
2691
- editor: {
2692
- theme: textTheme
2693
- }
2641
+ ...defaultSlots
2694
2642
  };
2695
2643
  var defaultMarkdownSlots = {
2696
- ...defaultSlots,
2697
- editor: {
2698
- theme: markdownTheme
2699
- }
2644
+ ...defaultSlots
2700
2645
  };
2701
2646
 
2702
2647
  // packages/ui/react-ui-editor/src/hooks/automerge.ts
@@ -2819,15 +2764,13 @@ import * as decoding from "lib0/decoding";
2819
2764
  import * as encoding from "lib0/encoding";
2820
2765
  import { Observable } from "lib0/observable";
2821
2766
  import * as YP from "y-protocols/awareness";
2822
- import * as Y2 from "yjs";
2823
2767
  import { invariant as invariant4 } from "@dxos/invariant";
2824
2768
  import { log as log4 } from "@dxos/log";
2825
- import { arrayToString as arrayToString2, stringToArray as stringToArray2 } from "@dxos/util";
2826
2769
  var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2827
2770
  var createYjsModel = ({ identity, space, text }) => {
2828
2771
  invariant4(text?.doc && text?.content, void 0, {
2829
2772
  F: __dxlog_file5,
2830
- L: 23,
2773
+ L: 21,
2831
2774
  S: void 0,
2832
2775
  A: [
2833
2776
  "text?.doc && text?.content",
@@ -2843,34 +2786,6 @@ var createYjsModel = ({ identity, space, text }) => {
2843
2786
  id: text.doc.guid,
2844
2787
  content: text.content,
2845
2788
  text: () => text.content.toString(),
2846
- // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
2847
- // TODO(dmaretskyi): Refactor as cursor-converter.
2848
- getCursorFromRange: (range) => {
2849
- const from = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.from));
2850
- const to = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.to, -1));
2851
- return [
2852
- arrayToString2(from),
2853
- arrayToString2(to)
2854
- ].join(":");
2855
- },
2856
- getRangeFromCursor: (cursor) => {
2857
- invariant4(text.doc, void 0, {
2858
- F: __dxlog_file5,
2859
- L: 40,
2860
- S: void 0,
2861
- A: [
2862
- "text.doc",
2863
- ""
2864
- ]
2865
- });
2866
- const parts = cursor.split(":");
2867
- const from = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[0])), text.doc);
2868
- const to = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[1])), text.doc);
2869
- return from && to ? {
2870
- from: from.index,
2871
- to: to.index
2872
- } : void 0;
2873
- },
2874
2789
  extension: [
2875
2790
  modelState.init(() => model),
2876
2791
  yjs(text.content, provider?.awareness)
@@ -2920,7 +2835,7 @@ var YAwarenessProvider = class extends Observable {
2920
2835
  origin
2921
2836
  }, {
2922
2837
  F: __dxlog_file5,
2923
- L: 122,
2838
+ L: 100,
2924
2839
  S: this,
2925
2840
  C: (f, a) => f(...a)
2926
2841
  });
@@ -2933,7 +2848,7 @@ var YAwarenessProvider = class extends Observable {
2933
2848
  _handleSpaceMessage({ payload }) {
2934
2849
  log4("space message", payload, {
2935
2850
  F: __dxlog_file5,
2936
- L: 131,
2851
+ L: 109,
2937
2852
  S: this,
2938
2853
  C: (f, a) => f(...a)
2939
2854
  });
@@ -2983,7 +2898,7 @@ var modelState = StateField5.define({
2983
2898
  });
2984
2899
  var useTextModel = (props) => {
2985
2900
  const { identity, space, text } = props;
2986
- const [model, setModel] = useState2(() => createModel(props));
2901
+ const [model, setModel] = useState2();
2987
2902
  useEffect2(() => setModel(createModel(props)), [
2988
2903
  identity,
2989
2904
  space,
@@ -3042,6 +2957,7 @@ var createAutomergeModel = ({ space, identity, text }) => {
3042
2957
  export {
3043
2958
  AwarenessProvider,
3044
2959
  BaseTextEditor,
2960
+ Cursor,
3045
2961
  Doc,
3046
2962
  EditorModes,
3047
2963
  MarkdownEditor,
@@ -3057,6 +2973,7 @@ export {
3057
2973
  awareness,
3058
2974
  basicBundle,
3059
2975
  blast,
2976
+ callbackWrapper,
3060
2977
  code2 as code,
3061
2978
  codeTheme,
3062
2979
  comments,