@dxos/react-ui-editor 0.3.11-main.279d9e6 → 0.3.11-main.2e1a8e1

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 (104) hide show
  1. package/dist/lib/browser/index.mjs +770 -769
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +16 -13
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +19 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -6
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  11. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -1
  13. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  14. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  15. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  16. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  17. package/dist/types/src/extensions/automerge/defs.d.ts +7 -9
  18. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  19. package/dist/types/src/extensions/automerge/semaphore.d.ts +7 -12
  20. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  21. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
  22. package/dist/types/src/extensions/awareness.d.ts +2 -0
  23. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  24. package/dist/types/src/extensions/basic.d.ts +4 -5
  25. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  26. package/dist/types/src/extensions/code.d.ts.map +1 -1
  27. package/dist/types/src/extensions/comments.d.ts +31 -18
  28. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  29. package/dist/types/src/{util → extensions}/cursor.d.ts +10 -2
  30. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  31. package/dist/types/src/extensions/hr.d.ts.map +1 -1
  32. package/dist/types/src/extensions/index.d.ts +2 -0
  33. package/dist/types/src/extensions/index.d.ts.map +1 -1
  34. package/dist/types/src/extensions/markdown/bundle.d.ts +2 -5
  35. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  36. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  37. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  38. package/dist/types/src/extensions/outliner.d.ts +4 -0
  39. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  40. package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
  41. package/dist/types/src/extensions/yjs/cursor.d.ts +1 -1
  42. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -1
  43. package/dist/types/src/hooks/automerge.d.ts +2 -1
  44. package/dist/types/src/hooks/automerge.d.ts.map +1 -1
  45. package/dist/types/src/hooks/defs.d.ts +28 -0
  46. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  47. package/dist/types/src/hooks/index.d.ts +2 -1
  48. package/dist/types/src/hooks/index.d.ts.map +1 -1
  49. package/dist/types/src/hooks/useTextModel.d.ts +2 -32
  50. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  51. package/dist/types/src/hooks/yjs.d.ts +2 -1
  52. package/dist/types/src/hooks/yjs.d.ts.map +1 -1
  53. package/dist/types/src/index.d.ts +2 -0
  54. package/dist/types/src/index.d.ts.map +1 -1
  55. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  56. package/dist/types/src/themes/default.d.ts.map +1 -1
  57. package/dist/types/src/util.d.ts +7 -0
  58. package/dist/types/src/util.d.ts.map +1 -0
  59. package/package.json +32 -31
  60. package/src/components/TextEditor/MarkdownEditor.stories.tsx +77 -79
  61. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  62. package/src/components/TextEditor/TextEditor.tsx +113 -92
  63. package/src/components/TextEditor/comments.stories.tsx +357 -0
  64. package/src/extensions/autocomplete.ts +23 -19
  65. package/src/extensions/automerge/automerge.ts +24 -49
  66. package/src/extensions/automerge/cursor.ts +2 -1
  67. package/src/extensions/automerge/defs.ts +10 -31
  68. package/src/extensions/automerge/semaphore.ts +23 -20
  69. package/src/extensions/automerge/update-automerge.ts +2 -2
  70. package/src/extensions/awareness.ts +45 -34
  71. package/src/extensions/basic.ts +22 -17
  72. package/src/extensions/code.ts +24 -18
  73. package/src/extensions/comments.ts +242 -234
  74. package/src/extensions/cursor.ts +50 -0
  75. package/src/extensions/hr.ts +49 -50
  76. package/src/extensions/image.ts +2 -2
  77. package/src/extensions/index.ts +2 -0
  78. package/src/extensions/link.ts +7 -7
  79. package/src/extensions/markdown/bundle.ts +18 -24
  80. package/src/extensions/markdown/highlight.ts +0 -8
  81. package/src/extensions/mention.ts +9 -2
  82. package/src/extensions/outliner.ts +12 -0
  83. package/src/extensions/table.ts +6 -3
  84. package/src/extensions/tasklist.ts +78 -86
  85. package/src/extensions/yjs/cursor.ts +2 -1
  86. package/src/extensions/yjs/yjs.test.ts +1 -1
  87. package/src/extensions/yjs/yjs.ts +2 -2
  88. package/src/hooks/automerge.ts +2 -1
  89. package/src/hooks/defs.ts +47 -0
  90. package/src/hooks/index.ts +3 -1
  91. package/src/hooks/useTextModel.ts +3 -49
  92. package/src/hooks/yjs.ts +2 -23
  93. package/src/index.ts +2 -0
  94. package/src/styles/markdown.ts +0 -2
  95. package/src/themes/default.ts +17 -50
  96. package/src/util.ts +41 -0
  97. package/dist/types/src/util/cursor.d.ts.map +0 -1
  98. package/dist/types/src/util/index.d.ts +0 -3
  99. package/dist/types/src/util/index.d.ts.map +0 -1
  100. package/dist/types/src/util/util.d.ts +0 -5
  101. package/dist/types/src/util/util.d.ts.map +0 -1
  102. package/src/util/cursor.ts +0 -29
  103. package/src/util/index.ts +0 -6
  104. package/src/util/util.ts +0 -18
@@ -7,51 +7,54 @@ import { tags as tags2 } from "@lezer/highlight";
7
7
 
8
8
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
9
9
  import { EditorState as EditorState2 } from "@codemirror/state";
10
- import { EditorView as EditorView12 } from "@codemirror/view";
10
+ import { EditorView as EditorView13 } from "@codemirror/view";
11
11
  import { useFocusableGroup } from "@fluentui/react-tabster";
12
12
  import { vim } from "@replit/codemirror-vim";
13
13
  import defaultsDeep2 from "lodash.defaultsdeep";
14
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
15
- import { generateName } from "@dxos/display-name";
16
- import { useThemeContext } from "@dxos/react-ui";
17
- import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
14
+ import React, { forwardRef, useCallback, useEffect as useEffect3, useImperativeHandle, useRef, useState } from "react";
15
+ import { log as log2 } from "@dxos/log";
16
+ import { useThemeContext as useThemeContext2 } from "@dxos/react-ui";
17
+ import { inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
18
18
 
19
19
  // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
20
20
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
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.
32
+ activateOnTyping: true,
33
+ // closeOnBlur: false,
31
34
  // defaultKeymap: false,
32
- // 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
- };
35
+ // TODO(burdon): Styles/fragments.
36
+ tooltipClass: () => "shadow rounded"
37
+ }),
38
+ // TODO(burdon): Option to create new page?
39
+ // TODO(burdon): Optional decoration via addToOptions
40
+ markdownLanguage.data.of({
41
+ autocomplete: (context) => {
42
+ const match = context.matchBefore(/\w*/);
43
+ if (!match || match.from === match.to && !context.explicit) {
44
+ return null;
46
45
  }
47
- ]
46
+ return {
47
+ from: match.from,
48
+ options: onSearch(match.text.toLowerCase())
49
+ };
50
+ }
48
51
  })
49
52
  ];
50
53
  };
51
54
 
52
55
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
53
- import { Facet as Facet2, StateField } from "@codemirror/state";
54
- import { ViewPlugin } from "@codemirror/view";
56
+ import { StateField } from "@codemirror/state";
57
+ import { EditorView } from "@codemirror/view";
55
58
  import { next as A } from "@dxos/automerge/automerge";
56
59
 
57
60
  // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
@@ -93,12 +96,12 @@ var cursorConverter = (handle, path) => ({
93
96
  // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
94
97
  import { Annotation, StateEffect } from "@codemirror/state";
95
98
  var effectType = StateEffect.define({});
99
+ var reconcileAnnotationType = Annotation.define();
100
+ var getPath = (state, field) => state.field(field).path;
101
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
96
102
  var updateHeads = (newHeads) => effectType.of({
97
103
  newHeads
98
104
  });
99
- var getLastHeads = (state, field) => state.field(field).lastHeads;
100
- var getPath = (state, field) => state.field(field).path;
101
- var reconcileAnnotationType = Annotation.define();
102
105
  var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
103
106
 
104
107
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
@@ -236,85 +239,95 @@ var charPath = (textPath, candidatePath) => {
236
239
 
237
240
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
238
241
  var PatchSemaphore = class {
239
- constructor(_field) {
240
- this._field = _field;
242
+ // prettier-ignore
243
+ constructor(_handle, _state) {
244
+ this._handle = _handle;
245
+ this._state = _state;
241
246
  this._inReconcile = false;
242
- this._queue = [];
243
- this.reconcile = (handle, view) => {
244
- if (this._inReconcile) {
245
- return;
246
- }
247
- this._inReconcile = true;
248
- const path = getPath(view.state, this._field);
249
- const oldHeads = getLastHeads(view.state, this._field);
250
- let selection = view.state.selection;
251
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
252
- const toInvert = transactions.slice().reverse();
253
- for (const tx of toInvert) {
254
- const inverted = tx.changes.invert(tx.startState.doc);
255
- selection = selection.map(inverted);
256
- view.dispatch({
257
- changes: inverted,
258
- annotations: reconcileAnnotationType.of(true)
259
- });
260
- }
261
- let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
262
- if (newHeads === null || newHeads === void 0) {
263
- newHeads = automerge2.getHeads(handle.docSync());
264
- }
265
- const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(handle.docSync(), oldHeads, newHeads);
266
- updateCodeMirror(view, selection, path, diff);
247
+ }
248
+ // NOTE: Cannot destruct view.state.
249
+ reconcile(view) {
250
+ if (this._inReconcile) {
251
+ return;
252
+ }
253
+ this._inReconcile = true;
254
+ const path = getPath(view.state, this._state);
255
+ const oldHeads = getLastHeads(view.state, this._state);
256
+ let selection = view.state.selection;
257
+ const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
258
+ const toInvert = transactions.slice().reverse();
259
+ for (const tx of toInvert) {
260
+ const inverted = tx.changes.invert(tx.startState.doc);
261
+ selection = selection.map(inverted);
267
262
  view.dispatch({
268
- effects: updateHeads(newHeads),
269
- annotations: reconcileAnnotationType.of({})
263
+ changes: inverted,
264
+ annotations: reconcileAnnotationType.of(true)
270
265
  });
271
- this._inReconcile = false;
272
- };
266
+ }
267
+ let newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
268
+ if (newHeads === null || newHeads === void 0) {
269
+ newHeads = automerge2.getHeads(this._handle.docSync());
270
+ }
271
+ const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(this._handle.docSync(), oldHeads, newHeads);
272
+ updateCodeMirror(view, selection, path, diff);
273
+ view.dispatch({
274
+ effects: updateHeads(newHeads),
275
+ annotations: reconcileAnnotationType.of({})
276
+ });
277
+ this._inReconcile = false;
273
278
  }
274
279
  };
275
280
 
276
- // packages/ui/react-ui-editor/src/util/cursor.ts
281
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
277
282
  import { Facet } from "@codemirror/state";
278
- var DEFAULT_CURSOR_CONVERTER = {
283
+ var defaultCursorConverter = {
279
284
  toCursor: (position) => position.toString(),
280
285
  fromCursor: (cursor) => parseInt(cursor)
281
286
  };
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)
287
+ var Cursor = class _Cursor {
288
+ static {
289
+ this.converter = Facet.define({
290
+ combine: (providers) => providers[0] ?? defaultCursorConverter
298
291
  });
299
292
  }
293
+ static {
294
+ this.getCursorFromRange = (state, range) => {
295
+ const cursorConverter3 = state.facet(_Cursor.converter);
296
+ const from = cursorConverter3.toCursor(range.from);
297
+ const to = cursorConverter3.toCursor(range.to, -1);
298
+ return [
299
+ from,
300
+ to
301
+ ].join(":");
302
+ };
303
+ }
304
+ static {
305
+ this.getRangeFromCursor = (state, cursor) => {
306
+ const cursorConverter3 = state.facet(_Cursor.converter);
307
+ const parts = cursor.split(":");
308
+ const from = cursorConverter3.fromCursor(parts[0]);
309
+ const to = cursorConverter3.fromCursor(parts[1]);
310
+ return from !== void 0 && to !== void 0 ? {
311
+ from,
312
+ to
313
+ } : void 0;
314
+ };
315
+ }
300
316
  };
301
317
 
302
318
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
303
- var semaphoreFacet = Facet2.define({
304
- combine: (values) => values.at(-1)
305
- });
306
319
  var automerge3 = ({ handle, path }) => {
307
- const stateField = StateField.define({
320
+ const state = StateField.define({
308
321
  create: () => ({
322
+ path: path.slice(),
309
323
  lastHeads: A.getHeads(handle.docSync()),
310
- unreconciledTransactions: [],
311
- path: path.slice()
324
+ unreconciledTransactions: []
312
325
  }),
313
326
  update: (value, tr) => {
314
327
  const result = {
328
+ path: path.slice(),
315
329
  lastHeads: value.lastHeads,
316
- unreconciledTransactions: value.unreconciledTransactions.slice(),
317
- path: path.slice()
330
+ unreconciledTransactions: value.unreconciledTransactions.slice()
318
331
  };
319
332
  let clearUnreconciled = false;
320
333
  for (const effect of tr.effects) {
@@ -333,41 +346,27 @@ var automerge3 = ({ handle, path }) => {
333
346
  return result;
334
347
  }
335
348
  });
336
- const semaphore = new PatchSemaphore(stateField);
337
- const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
338
- constructor(_view) {
339
- this._view = _view;
340
- this._handleChange = () => {
341
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
342
- };
343
- handle.addListener("change", this._handleChange);
344
- }
345
- update(update2) {
346
- if (update2.transactions.length > 0 && update2.transactions.some((tr) => !isReconcileTx(tr))) {
347
- queueMicrotask(() => {
348
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
349
- });
349
+ const semaphore = new PatchSemaphore(handle, state);
350
+ return [
351
+ Cursor.converter.of(cursorConverter(handle, path)),
352
+ EditorView.updateListener.of(({ view, changes }) => {
353
+ if (!changes.empty) {
354
+ semaphore.reconcile(view);
350
355
  }
351
- }
352
- destroy() {
353
- handle.addListener("change", this._handleChange);
354
- }
355
- });
356
- return {
357
- extension: [
358
- CursorConverter.of(cursorConverter(handle, path)),
359
- semaphoreFacet.of(semaphore),
360
- stateField,
361
- viewPlugin
362
- ]
363
- };
356
+ }),
357
+ state
358
+ ];
364
359
  };
365
360
 
366
361
  // packages/ui/react-ui-editor/src/extensions/awareness.ts
367
- import { Annotation as Annotation2, Facet as Facet3, RangeSet } from "@codemirror/state";
368
- import { Decoration, EditorView, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
362
+ import { Annotation as Annotation2, Facet as Facet2, RangeSet } from "@codemirror/state";
363
+ import { Decoration, EditorView as EditorView2, ViewPlugin, WidgetType } from "@codemirror/view";
364
+ import { useEffect } from "react";
369
365
  import { Event } from "@dxos/async";
370
366
  import { Context } from "@dxos/context";
367
+ import { generateName } from "@dxos/display-name";
368
+ import { useThemeContext } from "@dxos/react-ui";
369
+ import { getColorForValue } from "@dxos/react-ui-theme";
371
370
  var dummyProvider = {
372
371
  remoteStateChange: new Event(),
373
372
  open: () => {
@@ -378,26 +377,49 @@ var dummyProvider = {
378
377
  },
379
378
  getRemoteStates: () => []
380
379
  };
381
- var AwarenessProvider = Facet3.define({
380
+ var AwarenessProvider = Facet2.define({
382
381
  combine: (providers) => providers[0] ?? dummyProvider
383
382
  });
384
383
  var RemoteSelectionChangedAnnotation = Annotation2.define();
385
384
  var awareness = (provider = dummyProvider) => {
386
385
  return [
387
386
  AwarenessProvider.of(provider),
388
- ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
387
+ ViewPlugin.fromClass(RemoteSelectionsDecorator, {
389
388
  decorations: (value) => value.decorations
390
389
  }),
391
390
  styles
392
391
  ];
393
392
  };
393
+ var useAwareness = ({ awareness: awareness2, peer }) => {
394
+ const { themeMode } = useThemeContext();
395
+ useEffect(() => {
396
+ if (awareness2 && peer) {
397
+ awareness2.setLocalStateField("user", {
398
+ name: peer.name ?? generateName(peer.id),
399
+ color: getColorForValue({
400
+ value: peer.id,
401
+ type: "color"
402
+ }),
403
+ colorLight: getColorForValue({
404
+ value: peer.id,
405
+ themeMode,
406
+ type: "highlight"
407
+ })
408
+ });
409
+ }
410
+ }, [
411
+ awareness2,
412
+ peer,
413
+ themeMode
414
+ ]);
415
+ };
394
416
  var RemoteSelectionsDecorator = class {
395
417
  constructor(view) {
396
418
  this.decorations = RangeSet.of([]);
397
419
  this._ctx = new Context();
398
420
  this._lastAnchor = void 0;
399
421
  this._lastHead = void 0;
400
- this._cursorConverter = view.state.facet(CursorConverter);
422
+ this._cursorConverter = view.state.facet(Cursor.converter);
401
423
  this._provider = view.state.facet(AwarenessProvider);
402
424
  this._provider.open();
403
425
  this._provider.remoteStateChange.on(this._ctx, () => {
@@ -452,7 +474,7 @@ var RemoteSelectionsDecorator = class {
452
474
  attributes: {
453
475
  style: `background-color: ${lightColor}`
454
476
  },
455
- class: "cm-ySelection"
477
+ class: "cm-collab-selection"
456
478
  })
457
479
  });
458
480
  } else {
@@ -463,7 +485,7 @@ var RemoteSelectionsDecorator = class {
463
485
  attributes: {
464
486
  style: `background-color: ${color}`
465
487
  },
466
- class: "cm-ySelection"
488
+ class: "cm-collab-selection"
467
489
  })
468
490
  });
469
491
  decorations.push({
@@ -473,7 +495,7 @@ var RemoteSelectionsDecorator = class {
473
495
  attributes: {
474
496
  style: `background-color: ${color}`
475
497
  },
476
- class: "cm-ySelection"
498
+ class: "cm-collab-selection"
477
499
  })
478
500
  });
479
501
  for (let i = startLine.number + 1; i < endLine.number; i++) {
@@ -484,7 +506,7 @@ var RemoteSelectionsDecorator = class {
484
506
  value: Decoration.line({
485
507
  attributes: {
486
508
  style: `background-color: ${color}`,
487
- class: "cm-yLineSelection"
509
+ class: "cm-collab-selectionLine"
488
510
  }
489
511
  })
490
512
  });
@@ -504,23 +526,21 @@ var RemoteSelectionsDecorator = class {
504
526
  }
505
527
  };
506
528
  var RemoteCaretWidget = class extends WidgetType {
507
- constructor(name, color) {
529
+ constructor(_name, _color) {
508
530
  super();
509
- this.name = name;
510
- this.color = color;
511
- this.name = name;
512
- this.color = color;
531
+ this._name = _name;
532
+ this._color = _color;
513
533
  }
514
534
  toDOM() {
515
535
  const span = document.createElement("span");
516
- span.className = "cm-ySelectionCaret";
517
- span.style.backgroundColor = this.color;
518
- span.style.borderColor = this.color;
536
+ span.className = "cm-collab-selectionCaret";
537
+ span.style.backgroundColor = this._color;
538
+ span.style.borderColor = this._color;
519
539
  const dot = document.createElement("div");
520
- dot.className = "cm-ySelectionCaretDot";
540
+ dot.className = "cm-collab-selectionCaretDot";
521
541
  const info = document.createElement("div");
522
- info.className = "cm-ySelectionInfo";
523
- info.innerText = this.name;
542
+ info.className = "cm-collab-selectionInfo";
543
+ info.innerText = this._name;
524
544
  span.appendChild(document.createTextNode("\u2060"));
525
545
  span.appendChild(dot);
526
546
  span.appendChild(document.createTextNode("\u2060"));
@@ -528,15 +548,12 @@ var RemoteCaretWidget = class extends WidgetType {
528
548
  span.appendChild(document.createTextNode("\u2060"));
529
549
  return span;
530
550
  }
531
- eq(widget) {
532
- return widget.color === this.color;
533
- }
534
- compare(widget) {
535
- return widget.color === this.color;
536
- }
537
551
  updateDOM() {
538
552
  return false;
539
553
  }
554
+ eq(widget) {
555
+ return widget._color === this._color;
556
+ }
540
557
  get estimatedHeight() {
541
558
  return -1;
542
559
  }
@@ -544,13 +561,13 @@ var RemoteCaretWidget = class extends WidgetType {
544
561
  return true;
545
562
  }
546
563
  };
547
- var styles = EditorView.baseTheme({
548
- ".cm-ySelection": {},
549
- ".cm-yLineSelection": {
564
+ var styles = EditorView2.baseTheme({
565
+ ".cm-collab-selection": {},
566
+ ".cm-collab-selectionLine": {
550
567
  padding: 0,
551
568
  margin: "0px 2px 0px 4px"
552
569
  },
553
- ".cm-ySelectionCaret": {
570
+ ".cm-collab-selectionCaret": {
554
571
  position: "relative",
555
572
  borderLeft: "1px solid black",
556
573
  borderRight: "1px solid black",
@@ -559,7 +576,7 @@ var styles = EditorView.baseTheme({
559
576
  boxSizing: "border-box",
560
577
  display: "inline"
561
578
  },
562
- ".cm-ySelectionCaretDot": {
579
+ ".cm-collab-selectionCaretDot": {
563
580
  borderRadius: "50%",
564
581
  position: "absolute",
565
582
  width: ".4em",
@@ -570,11 +587,11 @@ var styles = EditorView.baseTheme({
570
587
  transition: "transform .3s ease-in-out",
571
588
  boxSizing: "border-box"
572
589
  },
573
- ".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot": {
590
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
574
591
  transformOrigin: "bottom center",
575
592
  transform: "scale(0)"
576
593
  },
577
- ".cm-ySelectionInfo": {
594
+ ".cm-collab-selectionInfo": {
578
595
  position: "absolute",
579
596
  top: "-1.05em",
580
597
  left: "-1px",
@@ -590,12 +607,12 @@ var styles = EditorView.baseTheme({
590
607
  zIndex: 101,
591
608
  transition: "opacity .3s ease-in-out",
592
609
  backgroundColor: "inherit",
593
- // these should be separate
610
+ // These should be separate.
594
611
  opacity: 0,
595
612
  transitionDelay: "0s",
596
613
  whiteSpace: "nowrap"
597
614
  },
598
- ".cm-ySelectionCaret:hover > .cm-ySelectionInfo": {
615
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
599
616
  opacity: 1,
600
617
  transitionDelay: "0s"
601
618
  }
@@ -603,26 +620,28 @@ var styles = EditorView.baseTheme({
603
620
 
604
621
  // packages/ui/react-ui-editor/src/extensions/basic.ts
605
622
  import { closeBrackets } from "@codemirror/autocomplete";
623
+ import { history } from "@codemirror/commands";
606
624
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
607
625
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
608
- import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
609
- var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
610
- EditorView2.lineWrapping,
611
- // TODO(burdon): Compare with minimalSetup.
626
+ import { drawSelection, EditorView as EditorView3, highlightActiveLine, placeholder } from "@codemirror/view";
627
+ var basicBundle = ({ themeMode, placeholder: _placeholder, multiline = true }) => [
628
+ multiline ? EditorView3.lineWrapping : [],
612
629
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
613
630
  bracketMatching(),
614
631
  closeBrackets(),
615
632
  drawSelection(),
616
- _placeholder && placeholder(_placeholder),
617
- // TODO(burdon): Is this required?
633
+ highlightActiveLine(),
634
+ history(),
635
+ _placeholder ? placeholder(_placeholder) : [],
636
+ // https://github.com/codemirror/theme-one-dark
618
637
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
619
- ].filter(Boolean);
638
+ ];
620
639
 
621
640
  // packages/ui/react-ui-editor/src/extensions/blast.ts
622
- import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
641
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
623
642
  import defaultsDeep from "lodash.defaultsdeep";
624
643
  import { invariant } from "@dxos/invariant";
625
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
644
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
626
645
  var defaultOptions = {
627
646
  effect: 2,
628
647
  maxParticles: 200,
@@ -667,7 +686,7 @@ var blast = (options = defaultOptions) => {
667
686
  };
668
687
  return [
669
688
  // Cursor moved.
670
- EditorView3.updateListener.of((update2) => {
689
+ EditorView4.updateListener.of((update2) => {
671
690
  if (blaster?.node !== update2.view.scrollDOM) {
672
691
  if (blaster) {
673
692
  blaster.destroy();
@@ -749,7 +768,7 @@ var Blaster = class {
749
768
  }
750
769
  initialize() {
751
770
  invariant(!this._canvas && !this._ctx, void 0, {
752
- F: __dxlog_file2,
771
+ F: __dxlog_file,
753
772
  L: 138,
754
773
  S: this,
755
774
  A: [
@@ -786,7 +805,7 @@ var Blaster = class {
786
805
  }
787
806
  start() {
788
807
  invariant(this._canvas && this._ctx, void 0, {
789
- F: __dxlog_file2,
808
+ F: __dxlog_file,
790
809
  L: 177,
791
810
  S: this,
792
811
  A: [
@@ -929,7 +948,7 @@ var random = (min, max) => {
929
948
  };
930
949
 
931
950
  // packages/ui/react-ui-editor/src/extensions/code.ts
932
- import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
951
+ import { ViewPlugin as ViewPlugin2, EditorView as EditorView5, Decoration as Decoration2 } from "@codemirror/view";
933
952
  import { mx as mx2 } from "@dxos/react-ui-theme";
934
953
 
935
954
  // packages/ui/react-ui-editor/src/styles/cursors.ts
@@ -1020,7 +1039,7 @@ var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue)
1020
1039
 
1021
1040
  // packages/ui/react-ui-editor/src/extensions/code.ts
1022
1041
  var CODE_REGEX = /```[\s\S]*?```/gs;
1023
- var styles2 = EditorView4.baseTheme({
1042
+ var styles2 = EditorView5.baseTheme({
1024
1043
  "& .cm-codeblock": {
1025
1044
  fontFamily: getToken("fontFamily.mono", []).join(",")
1026
1045
  },
@@ -1054,32 +1073,35 @@ var getLineRange = (lines, from, to) => {
1054
1073
  ];
1055
1074
  };
1056
1075
  var code2 = () => {
1057
- const getDecorations = (view) => {
1076
+ const buildDecorations5 = (view) => {
1058
1077
  const decorations = [];
1059
- if (view.state.readOnly) {
1060
- const text = view.state.doc.sliceString(0);
1061
- const matches = text.matchAll(CODE_REGEX);
1062
- const blocks = view.viewportLineBlocks;
1063
- for (const match of matches) {
1064
- const range = getLineRange(blocks, match.index, match.index + match[0].length);
1065
- for (let i = range[0]; i <= range[1]; i++) {
1066
- const block = blocks[i];
1067
- decorations.push(Decoration2.line({
1068
- class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1069
- }).range(block.from));
1070
- }
1078
+ const text = view.state.doc.sliceString(0);
1079
+ const matches = text.matchAll(CODE_REGEX);
1080
+ const blocks = view.viewportLineBlocks;
1081
+ for (const match of matches) {
1082
+ const range = getLineRange(blocks, match.index, match.index + match[0].length);
1083
+ for (let i = range[0]; i <= range[1]; i++) {
1084
+ const block = blocks[i];
1085
+ decorations.push(Decoration2.line({
1086
+ class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1087
+ }).range(block.from));
1071
1088
  }
1072
1089
  }
1073
1090
  return Decoration2.set(decorations);
1074
1091
  };
1075
1092
  return [
1076
- ViewPlugin3.fromClass(class {
1093
+ ViewPlugin2.fromClass(class {
1077
1094
  constructor(view) {
1078
- this.decorations = getDecorations(view);
1095
+ this.hasFocus = false;
1096
+ this.decorations = buildDecorations5(view);
1079
1097
  }
1080
1098
  update(update2) {
1081
- if (update2.docChanged || update2.viewportChanged) {
1082
- this.decorations = getDecorations(update2.view);
1099
+ if (
1100
+ // TODO(burdon): Generalize for other extensions.
1101
+ this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1102
+ ) {
1103
+ this.hasFocus = update2.view.hasFocus;
1104
+ this.decorations = buildDecorations5(update2.view);
1083
1105
  }
1084
1106
  }
1085
1107
  }, {
@@ -1091,23 +1113,51 @@ var code2 = () => {
1091
1113
 
1092
1114
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1093
1115
  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";
1116
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView6 } from "@codemirror/view";
1095
1117
  import sortBy from "lodash.sortby";
1118
+ import { useEffect as useEffect2 } from "react";
1096
1119
  import { debounce } from "@dxos/async";
1097
1120
  import { invariant as invariant2 } from "@dxos/invariant";
1098
- import { log as log2 } from "@dxos/log";
1099
1121
  import { nonNullable } from "@dxos/util";
1122
+
1123
+ // packages/ui/react-ui-editor/src/util.ts
1124
+ import { log } from "@dxos/log";
1125
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1126
+ var callbackWrapper = (fn) => (...args) => {
1127
+ try {
1128
+ return fn(...args);
1129
+ } catch (err) {
1130
+ log.catch(err, void 0, {
1131
+ F: __dxlog_file2,
1132
+ L: 18,
1133
+ S: void 0,
1134
+ C: (f, a) => f(...a)
1135
+ });
1136
+ }
1137
+ };
1138
+ var logChanges = (trs) => {
1139
+ const changes = trs.flatMap((tr) => {
1140
+ if (tr.changes.empty) {
1141
+ return void 0;
1142
+ }
1143
+ const changes2 = [];
1144
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => changes2.push(JSON.stringify({
1145
+ fromA,
1146
+ toA,
1147
+ fromB,
1148
+ toB,
1149
+ inserted: inserted.toString()
1150
+ })));
1151
+ return changes2;
1152
+ }).filter(Boolean);
1153
+ if (changes.length) {
1154
+ console.log("changes", changes);
1155
+ }
1156
+ };
1157
+
1158
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
1100
1159
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1101
- 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
- },
1160
+ var styles3 = EditorView6.baseTheme({
1111
1161
  "& .cm-comment": {
1112
1162
  backgroundColor: getToken("extend.colors.yellow.50")
1113
1163
  },
@@ -1123,49 +1173,59 @@ var marks = {
1123
1173
  class: "cm-comment-active"
1124
1174
  })
1125
1175
  };
1126
- var setFocus = (view, thread) => {
1127
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1128
- if (!range) {
1176
+ var setFocus = (view, id, center = true) => {
1177
+ const comment = view.state.field(commentsState).comments.find((range2) => range2.comment.id === id);
1178
+ if (!comment?.comment.cursor) {
1129
1179
  return;
1130
1180
  }
1131
- view.dispatch({
1132
- effects: setSelection.of({
1133
- active: thread
1134
- }),
1135
- selection: {
1136
- anchor: range.from
1137
- },
1138
- scrollIntoView: true
1139
- });
1181
+ const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);
1182
+ if (range) {
1183
+ view.dispatch({
1184
+ selection: {
1185
+ anchor: range.from
1186
+ },
1187
+ effects: [
1188
+ //
1189
+ EditorView6.scrollIntoView(range.from, center ? {
1190
+ y: "center"
1191
+ } : void 0),
1192
+ setSelection.of({
1193
+ active: id
1194
+ })
1195
+ ]
1196
+ });
1197
+ }
1140
1198
  };
1141
- var setCommentRange = StateEffect2.define();
1199
+ var setComments = StateEffect2.define();
1142
1200
  var setSelection = StateEffect2.define();
1143
1201
  var setCommentState = StateEffect2.define();
1144
- var commentsStateField = StateField2.define({
1202
+ var commentsState = StateField2.define({
1145
1203
  create: () => ({
1146
- ranges: []
1204
+ comments: [],
1205
+ selection: {}
1147
1206
  }),
1148
1207
  update: (value, tr) => {
1149
- const cursorConverter3 = tr.state.facet(CursorConverter);
1150
1208
  for (const effect of tr.effects) {
1151
1209
  if (effect.is(setSelection)) {
1152
1210
  return {
1153
1211
  ...value,
1154
- ...effect.value
1212
+ selection: effect.value
1155
1213
  };
1156
1214
  }
1157
- if (effect.is(setCommentRange)) {
1158
- const { comments: comments2 } = effect.value;
1159
- const ranges = comments2.map((comment) => {
1160
- const range = getRangeFromCursor(cursorConverter3, comment.cursor);
1215
+ if (effect.is(setComments)) {
1216
+ const comments2 = effect.value.map((comment) => {
1217
+ if (!comment.cursor) {
1218
+ return void 0;
1219
+ }
1220
+ const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);
1161
1221
  return range && {
1162
- ...comment,
1163
- ...range
1222
+ comment,
1223
+ range
1164
1224
  };
1165
1225
  }).filter(nonNullable);
1166
1226
  return {
1167
1227
  ...value,
1168
- ranges
1228
+ comments: comments2
1169
1229
  };
1170
1230
  }
1171
1231
  if (effect.is(setCommentState)) {
@@ -1175,78 +1235,109 @@ var commentsStateField = StateField2.define({
1175
1235
  return value;
1176
1236
  }
1177
1237
  });
1178
- var highlightDecorations = EditorView5.decorations.compute([
1179
- commentsStateField
1238
+ var highlightDecorations = EditorView6.decorations.compute([
1239
+ commentsState
1180
1240
  ], (state) => {
1181
- const { active, ranges } = state.field(commentsStateField);
1182
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1183
- if (selection.from === selection.to) {
1241
+ const { selection: { active }, comments: comments2 } = state.field(commentsState);
1242
+ const decorations = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
1243
+ const range = comment.range;
1244
+ if (!range || range.from === range.to) {
1245
+ console.warn("Invalid range:", range);
1184
1246
  return void 0;
1185
1247
  }
1186
- const range = {
1187
- from: selection.from,
1188
- to: selection.to
1189
- };
1190
- if (selection.id === active) {
1248
+ if (comment.comment.id === active) {
1191
1249
  return marks.highlightActive.range(range.from, range.to);
1192
1250
  } else {
1193
1251
  return marks.highlight.range(range.from, range.to);
1194
1252
  }
1195
- }).filter(nonNullable) ?? [];
1253
+ }).filter(nonNullable);
1196
1254
  return Decoration3.set(decorations);
1197
1255
  });
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();
1256
+ var trackPastedComments = (onUpdate) => {
1257
+ let tracked = null;
1258
+ const handleTrack = (event, view) => {
1259
+ const comments2 = view.state.field(commentsState);
1260
+ const { main } = view.state.selection;
1261
+ const selectedRanges = comments2.comments.filter(({ range }) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1262
+ if (!selectedRanges.length) {
1263
+ tracked = null;
1264
+ } else {
1265
+ tracked = {
1266
+ text: view.state.doc.slice(main.from, main.to),
1267
+ comments: selectedRanges.map(({ comment, range }) => ({
1268
+ id: comment.id,
1269
+ from: range.from - main.from,
1270
+ to: range.to - main.from
1271
+ }))
1272
+ };
1223
1273
  }
1224
- return span;
1225
- }
1274
+ };
1275
+ return [
1276
+ EditorView6.domEventHandlers({
1277
+ cut: handleTrack,
1278
+ copy: handleTrack
1279
+ }),
1280
+ // Handle paste.
1281
+ EditorView6.updateListener.of(({ view, state, transactions }) => {
1282
+ if (tracked) {
1283
+ const paste = transactions.find((tr) => tr.isUserEvent("input.paste"));
1284
+ if (paste) {
1285
+ let found = -1;
1286
+ paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1287
+ if (text.eq(tracked.text)) {
1288
+ for (let i = transactions.indexOf(paste) + 1; i < transactions.length; i++) {
1289
+ fromB = transactions[i].changes.mapPos(fromB);
1290
+ }
1291
+ found = fromB;
1292
+ }
1293
+ });
1294
+ if (found > -1) {
1295
+ for (const moved of tracked.comments) {
1296
+ const range = {
1297
+ from: found + moved.from,
1298
+ to: found + moved.to
1299
+ };
1300
+ const cursor = Cursor.getCursorFromRange(state, range);
1301
+ onUpdate(moved.id, cursor);
1302
+ }
1303
+ }
1304
+ tracked = null;
1305
+ }
1306
+ }
1307
+ })
1308
+ ];
1226
1309
  };
1227
1310
  var comments = (options = {}) => {
1311
+ const { key: shortcut = "meta-'" } = options;
1228
1312
  const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1229
1313
  const createCommentThread = (view) => {
1230
- const cursorConverter3 = view.state.facet(CursorConverter);
1231
1314
  invariant2(options.onCreate, void 0, {
1232
1315
  F: __dxlog_file3,
1233
- L: 224,
1316
+ L: 259,
1234
1317
  S: void 0,
1235
1318
  A: [
1236
1319
  "options.onCreate",
1237
1320
  ""
1238
1321
  ]
1239
1322
  });
1240
- const { head, from, to } = view.state.selection.main;
1323
+ const { from, to } = view.state.selection.main;
1241
1324
  if (from === to) {
1242
1325
  return false;
1243
1326
  }
1244
- const relPos = getCursorFromRange(cursorConverter3, {
1327
+ if (to === view.state.doc.length) {
1328
+ view.dispatch({
1329
+ changes: {
1330
+ from: to,
1331
+ insert: "\n"
1332
+ }
1333
+ });
1334
+ }
1335
+ const cursor = Cursor.getCursorFromRange(view.state, {
1245
1336
  from,
1246
1337
  to
1247
1338
  });
1248
- if (relPos) {
1249
- const id = callbackWrapper(options.onCreate)(relPos);
1339
+ if (cursor) {
1340
+ const id = options.onCreate?.(cursor, view.coordsAtPos(from));
1250
1341
  if (id) {
1251
1342
  view.dispatch({
1252
1343
  effects: setSelection.of({
@@ -1256,53 +1347,13 @@ var comments = (options = {}) => {
1256
1347
  anchor: from
1257
1348
  }
1258
1349
  });
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
1350
  return true;
1272
1351
  }
1273
1352
  }
1274
1353
  return false;
1275
1354
  };
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
1355
  return [
1304
- bookmarksViewPlugin,
1305
- commentsStateField,
1356
+ commentsState,
1306
1357
  highlightDecorations,
1307
1358
  styles3,
1308
1359
  //
@@ -1310,7 +1361,7 @@ var comments = (options = {}) => {
1310
1361
  //
1311
1362
  options.onCreate ? keymap3.of([
1312
1363
  {
1313
- key: options?.key ?? "meta-'",
1364
+ key: shortcut,
1314
1365
  run: callbackWrapper(createCommentThread)
1315
1366
  }
1316
1367
  ]) : [],
@@ -1327,7 +1378,7 @@ var comments = (options = {}) => {
1327
1378
  above: true,
1328
1379
  create: () => {
1329
1380
  const el = document.createElement("div");
1330
- options.onHover?.(el);
1381
+ options.onHover(el, shortcut);
1331
1382
  return {
1332
1383
  dom: el,
1333
1384
  offset: {
@@ -1340,156 +1391,154 @@ var comments = (options = {}) => {
1340
1391
  }
1341
1392
  return null;
1342
1393
  }, {
1343
- hideOnChange: true,
1394
+ // TODO(burdon): Hide on change triggered immediately?
1395
+ // hideOnChange: true,
1344
1396
  hoverTime: 1e3
1345
1397
  }) : [],
1346
1398
  //
1347
- // Monitor cursor movement and text updates.
1348
- // TODO(burdon): Is there a better (finer grained) way to do this?
1399
+ // Track deleted ranges and update ranges for decorations.
1349
1400
  //
1350
- EditorView5.updateListener.of(({ view, state, changes }) => {
1351
- const cursorConverter3 = view.state.facet(CursorConverter);
1352
- {
1353
- let mod = false;
1354
- const { active, ranges } = state.field(commentsStateField);
1355
- changes.iterChanges((from, to, from2, to2) => {
1356
- ranges.forEach((range) => {
1357
- if (from2 === to2) {
1358
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1359
- if (!newRange || newRange.to - newRange.from === 0) {
1360
- log2.info("deleted comment", {
1361
- thread: range.id
1362
- }, {
1363
- F: __dxlog_file3,
1364
- L: 358,
1365
- S: void 0,
1366
- C: (f, a) => f(...a)
1367
- });
1368
- }
1401
+ EditorView6.updateListener.of(({ view, state, changes }) => {
1402
+ let mod = false;
1403
+ const { comments: comments2, ...value } = state.field(commentsState);
1404
+ changes.iterChanges((from, to, from2, to2) => {
1405
+ comments2.forEach(({ comment, range }) => {
1406
+ if (from2 === to2) {
1407
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1408
+ if (!newRange || newRange.to - newRange.from === 0) {
1409
+ options.onDelete?.(comment.id);
1369
1410
  }
1370
- if (from <= range.to) {
1371
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1372
- Object.assign(range, newRange);
1373
- mod = true;
1374
- }
1375
- });
1411
+ }
1412
+ if (from <= range.to) {
1413
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1414
+ Object.assign(range, newRange);
1415
+ mod = true;
1416
+ }
1417
+ });
1418
+ });
1419
+ if (mod) {
1420
+ view.dispatch({
1421
+ effects: setCommentState.of({
1422
+ comments: comments2,
1423
+ ...value
1424
+ })
1376
1425
  });
1377
- if (mod) {
1378
- view.dispatch({
1379
- effects: setCommentState.of({
1380
- active,
1381
- ranges
1382
- })
1383
- });
1384
- }
1385
1426
  }
1386
- {
1387
- const { head } = state.selection.main;
1388
- let min = Infinity;
1389
- const { active, closest, ranges } = state.field(commentsStateField);
1390
- const selected = {
1391
- active: void 0,
1392
- closest: void 0
1393
- };
1394
- ranges.forEach((comment) => {
1395
- const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1396
- if (head >= comment.from && head <= comment.to) {
1397
- selected.active = comment.id;
1398
- }
1427
+ }),
1428
+ //
1429
+ // Track selection/proximity.
1430
+ //
1431
+ EditorView6.updateListener.of(({ view, state, changes }) => {
1432
+ let min = Infinity;
1433
+ const { selection: { active, closest }, comments: comments2 } = state.field(commentsState);
1434
+ const { head } = state.selection.main;
1435
+ const selection = {};
1436
+ comments2.forEach(({ comment, range }) => {
1437
+ if (head >= range.from && head <= range.to) {
1438
+ selection.active = comment.id;
1439
+ selection.closest = void 0;
1440
+ }
1441
+ if (!selection.active) {
1442
+ const d = Math.min(Math.abs(head - range.from), Math.abs(head - range.to));
1399
1443
  if (d < min) {
1400
- selected.closest = comment.id;
1444
+ selection.closest = comment.id;
1401
1445
  min = d;
1402
1446
  }
1403
- });
1404
- if (selected.active !== active || selected.closest !== closest) {
1405
- view.dispatch({
1406
- effects: setSelection.of(selected)
1407
- });
1408
- handleSelect({
1409
- ...selected,
1410
- ranges: ranges.map((range) => ({
1411
- ...range,
1412
- location: view.coordsAtPos(range.from)
1413
- }))
1414
- });
1415
1447
  }
1448
+ });
1449
+ if (selection.active !== active || selection.closest !== closest) {
1450
+ view.dispatch({
1451
+ effects: setSelection.of(selection)
1452
+ });
1453
+ handleSelect({
1454
+ selection,
1455
+ comments: comments2.map(({ comment, range }) => ({
1456
+ comment,
1457
+ range,
1458
+ location: view.coordsAtPos(range.from)
1459
+ }))
1460
+ });
1416
1461
  }
1417
- })
1462
+ }),
1463
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
1418
1464
  ];
1419
1465
  };
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;
1466
+ var useComments = (view, comments2) => {
1467
+ useEffect2(() => {
1468
+ if (view && comments2) {
1469
+ view.dispatch({
1470
+ effects: setComments.of(comments2)
1471
+ });
1472
+ }
1473
+ }, [
1474
+ view,
1475
+ comments2
1476
+ ]);
1436
1477
  };
1437
1478
 
1438
1479
  // 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
- var styles4 = EditorView6.baseTheme({
1480
+ import { syntaxTree } from "@codemirror/language";
1481
+ import { RangeSetBuilder } from "@codemirror/state";
1482
+ import { Decoration as Decoration4, EditorView as EditorView7, ViewPlugin as ViewPlugin3, WidgetType as WidgetType2 } from "@codemirror/view";
1483
+ var styles4 = EditorView7.baseTheme({
1441
1484
  "& .cm-hr": {
1442
- // TODO(burdon): ???
1443
1485
  // Note that block-level decorations should not have vertical margins,
1444
- borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1486
+ borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
1445
1487
  }
1446
1488
  });
1447
- var HorizontalRuleWidget = class extends WidgetType3 {
1448
- constructor(_pos) {
1449
- super();
1450
- this._pos = _pos;
1451
- }
1452
- eq(other) {
1453
- return this._pos === other._pos;
1454
- }
1455
- toDOM(view) {
1489
+ var HorizontalRuleWidget = class extends WidgetType2 {
1490
+ toDOM() {
1456
1491
  const el = document.createElement("div");
1457
1492
  el.className = "cm-hr";
1458
1493
  return el;
1459
1494
  }
1460
1495
  };
1461
- var placeholderMatcher = new MatchDecorator2({
1462
- regexp: /^---$/g,
1463
- decoration: (match, view, pos) => Decoration4.replace({
1464
- widget: new HorizontalRuleWidget(pos)
1465
- })
1496
+ var decoration = Decoration4.replace({
1497
+ widget: new HorizontalRuleWidget()
1466
1498
  });
1467
- var hr = () => [
1468
- styles4,
1469
- ViewPlugin5.fromClass(class {
1470
- constructor(view) {
1471
- this.rules = placeholderMatcher.createDeco(view);
1472
- }
1473
- update(update2) {
1474
- this.rules = placeholderMatcher.updateDeco(update2, this.rules);
1475
- }
1476
- }, {
1477
- decorations: (instance) => instance.rules,
1478
- // TODO(burdon): Is this really atomic (cursor can move into ---).
1479
- provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1480
- return view.plugin(plugin)?.rules || Decoration4.none;
1499
+ var buildDecorations = (view) => {
1500
+ const builder = new RangeSetBuilder();
1501
+ const { state } = view;
1502
+ const cursor = state.selection.main.head;
1503
+ for (const { from, to } of view.visibleRanges) {
1504
+ syntaxTree(state).iterate({
1505
+ enter: (node) => {
1506
+ if (node.name === "HorizontalRule") {
1507
+ if (cursor <= node.from || cursor >= node.to) {
1508
+ builder.add(node.from, node.to, decoration);
1509
+ }
1510
+ }
1511
+ },
1512
+ from,
1513
+ to
1514
+ });
1515
+ }
1516
+ return builder.finish();
1517
+ };
1518
+ var hr = () => {
1519
+ return [
1520
+ styles4,
1521
+ ViewPlugin3.fromClass(class {
1522
+ constructor(view) {
1523
+ this.decorations = buildDecorations(view);
1524
+ }
1525
+ update(update2) {
1526
+ this.decorations = buildDecorations(update2.view);
1527
+ }
1528
+ }, {
1529
+ decorations: (v) => v.decorations
1481
1530
  })
1482
- })
1483
- ];
1531
+ ];
1532
+ };
1484
1533
 
1485
1534
  // packages/ui/react-ui-editor/src/extensions/image.ts
1486
- import { syntaxTree } from "@codemirror/language";
1535
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1487
1536
  import { StateField as StateField3 } from "@codemirror/state";
1488
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1537
+ import { Decoration as Decoration5, EditorView as EditorView8, WidgetType as WidgetType3 } from "@codemirror/view";
1489
1538
  var image = (options = {}) => {
1490
1539
  return StateField3.define({
1491
1540
  create: (state) => {
1492
- return Decoration5.set(buildDecorations(0, state.doc.length, state));
1541
+ return Decoration5.set(buildDecorations2(0, state.doc.length, state));
1493
1542
  },
1494
1543
  update: (value, tr) => {
1495
1544
  if (!tr.docChanged && !tr.selection) {
@@ -1509,16 +1558,16 @@ var image = (options = {}) => {
1509
1558
  filterFrom: from,
1510
1559
  filterTo: to,
1511
1560
  filter: () => false,
1512
- add: buildDecorations(from, to, tr.state)
1561
+ add: buildDecorations2(from, to, tr.state)
1513
1562
  });
1514
1563
  },
1515
- provide: (field) => EditorView7.decorations.from(field)
1564
+ provide: (field) => EditorView8.decorations.from(field)
1516
1565
  });
1517
1566
  };
1518
- var buildDecorations = (from, to, state) => {
1567
+ var buildDecorations2 = (from, to, state) => {
1519
1568
  const decorations = [];
1520
1569
  const cursor = state.selection.main.head;
1521
- syntaxTree(state).iterate({
1570
+ syntaxTree2(state).iterate({
1522
1571
  enter: (node) => {
1523
1572
  if (node.name === "Image") {
1524
1573
  const urlNode = node.node.getChild("URL");
@@ -1537,7 +1586,7 @@ var buildDecorations = (from, to, state) => {
1537
1586
  });
1538
1587
  return decorations;
1539
1588
  };
1540
- var ImageWidget = class extends WidgetType4 {
1589
+ var ImageWidget = class extends WidgetType3 {
1541
1590
  constructor(_url) {
1542
1591
  super();
1543
1592
  this._url = _url;
@@ -1555,19 +1604,19 @@ var ImageWidget = class extends WidgetType4 {
1555
1604
  };
1556
1605
 
1557
1606
  // packages/ui/react-ui-editor/src/extensions/link.ts
1558
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1559
- import { RangeSetBuilder } from "@codemirror/state";
1560
- import { Decoration as Decoration6, WidgetType as WidgetType5, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
1607
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1608
+ import { RangeSetBuilder as RangeSetBuilder2 } from "@codemirror/state";
1609
+ import { hoverTooltip as hoverTooltip2, Decoration as Decoration6, ViewPlugin as ViewPlugin4, WidgetType as WidgetType4 } from "@codemirror/view";
1561
1610
  import { tooltipContent } from "@dxos/react-ui-theme";
1562
1611
  var link = (options = {}) => {
1563
1612
  const extensions = [
1564
- ViewPlugin6.fromClass(class {
1613
+ ViewPlugin4.fromClass(class {
1565
1614
  constructor(view) {
1566
- this.decorations = buildDecorations2(view, options);
1615
+ this.decorations = buildDecorations3(view, options);
1567
1616
  }
1568
1617
  update(update2) {
1569
1618
  if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1570
- this.decorations = buildDecorations2(update2.view, options);
1619
+ this.decorations = buildDecorations3(update2.view, options);
1571
1620
  }
1572
1621
  }
1573
1622
  }, {
@@ -1580,7 +1629,7 @@ var link = (options = {}) => {
1580
1629
  // https://codemirror.net/docs/ref/#view.hoverTooltip
1581
1630
  // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1582
1631
  hoverTooltip2((view, pos, side) => {
1583
- const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1632
+ const syntax = syntaxTree3(view.state).resolveInner(pos, side);
1584
1633
  let link2 = null;
1585
1634
  for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1586
1635
  link2 = node.name === "Link" ? node : null;
@@ -1612,7 +1661,7 @@ var link = (options = {}) => {
1612
1661
  }
1613
1662
  return extensions;
1614
1663
  };
1615
- var LinkButton = class extends WidgetType5 {
1664
+ var LinkButton = class extends WidgetType4 {
1616
1665
  constructor(_url, _onAttach) {
1617
1666
  super();
1618
1667
  this._url = _url;
@@ -1627,7 +1676,7 @@ var LinkButton = class extends WidgetType5 {
1627
1676
  return el;
1628
1677
  }
1629
1678
  };
1630
- var LinkText = class extends WidgetType5 {
1679
+ var LinkText = class extends WidgetType4 {
1631
1680
  constructor(_text, _url) {
1632
1681
  super();
1633
1682
  this._text = _text;
@@ -1656,12 +1705,12 @@ var LinkText = class extends WidgetType5 {
1656
1705
  return link2;
1657
1706
  }
1658
1707
  };
1659
- var buildDecorations2 = (view, options) => {
1660
- const builder = new RangeSetBuilder();
1708
+ var buildDecorations3 = (view, options) => {
1709
+ const builder = new RangeSetBuilder2();
1661
1710
  const { state } = view;
1662
1711
  const cursor = state.selection.main.head;
1663
1712
  for (const { from, to } of view.visibleRanges) {
1664
- syntaxTree2(state).iterate({
1713
+ syntaxTree3(state).iterate({
1665
1714
  enter: (node) => {
1666
1715
  if (node.name === "Link") {
1667
1716
  const marks2 = node.node.getChildren("LinkMark");
@@ -1692,14 +1741,14 @@ var buildDecorations2 = (view, options) => {
1692
1741
  };
1693
1742
 
1694
1743
  // packages/ui/react-ui-editor/src/extensions/listener.ts
1695
- import { EditorView as EditorView8 } from "@codemirror/view";
1744
+ import { EditorView as EditorView9 } from "@codemirror/view";
1696
1745
  var listener = ({ onFocus, onChange }) => {
1697
1746
  const extensions = [];
1698
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1747
+ onFocus && extensions.push(EditorView9.focusChangeEffect.of((_, focused) => {
1699
1748
  onFocus(focused);
1700
1749
  return null;
1701
1750
  }));
1702
- onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1751
+ onChange && extensions.push(EditorView9.updateListener.of((update2) => {
1703
1752
  onChange(update2.state.doc.toString());
1704
1753
  }));
1705
1754
  return extensions;
@@ -1707,17 +1756,17 @@ var listener = ({ onFocus, onChange }) => {
1707
1756
 
1708
1757
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1709
1758
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1710
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1711
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
1759
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1760
+ import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1712
1761
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1713
1762
  import { languages } from "@codemirror/language-data";
1714
1763
  import { searchKeymap } from "@codemirror/search";
1715
1764
  import { EditorState } from "@codemirror/state";
1716
1765
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1717
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1766
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView10, highlightActiveLine as highlightActiveLine2, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1718
1767
 
1719
1768
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1720
- import { markdownLanguage } from "@codemirror/lang-markdown";
1769
+ import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
1721
1770
  import { HighlightStyle } from "@codemirror/language";
1722
1771
  import { tags, styleTags, Tag } from "@lezer/highlight";
1723
1772
  import { Table } from "@lezer/markdown";
@@ -1727,7 +1776,6 @@ var markdownTags = {
1727
1776
  CodeText: Tag.define(),
1728
1777
  EmphasisMark: Tag.define(),
1729
1778
  HeaderMark: Tag.define(),
1730
- // HorizontalRule: Tag.define(),
1731
1779
  InlineCode: Tag.define(),
1732
1780
  LinkLabel: Tag.define(),
1733
1781
  LinkReference: Tag.define(),
@@ -1891,12 +1939,6 @@ var markdownHighlightStyle = (readonly) => {
1891
1939
  ],
1892
1940
  class: blockquote
1893
1941
  },
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
1942
  {
1901
1943
  tag: [
1902
1944
  markdownTags.TableCell
@@ -1904,7 +1946,7 @@ var markdownHighlightStyle = (readonly) => {
1904
1946
  class: "font-mono"
1905
1947
  }
1906
1948
  ], {
1907
- scope: markdownLanguage,
1949
+ scope: markdownLanguage2,
1908
1950
  all: {
1909
1951
  fontFamily: getToken("fontFamily.body", []).join(",")
1910
1952
  }
@@ -1916,17 +1958,17 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1916
1958
  return [
1917
1959
  EditorState.allowMultipleSelections.of(true),
1918
1960
  EditorState.tabSize.of(2),
1919
- EditorView9.lineWrapping,
1920
- customKeymap(),
1961
+ EditorView10.lineWrapping,
1962
+ // https://github.com/codemirror/basic-setup
1921
1963
  bracketMatching2(),
1922
1964
  closeBrackets2(),
1923
1965
  crosshairCursor(),
1924
1966
  dropCursor(),
1925
1967
  drawSelection2(),
1926
- highlightActiveLine(),
1927
- history(),
1968
+ highlightActiveLine2(),
1969
+ history2(),
1928
1970
  indentOnInput(),
1929
- _placeholder && placeholder2(_placeholder),
1971
+ _placeholder ? placeholder2(_placeholder) : [],
1930
1972
  // Main extension.
1931
1973
  // https://github.com/codemirror/lang-markdown
1932
1974
  // https://codemirror.net/5/mode/markdown/index.html (demo).
@@ -1936,7 +1978,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1936
1978
  // NOTE: This extends the parser; it doesn't affect rendering.
1937
1979
  // https://github.github.com/gfm
1938
1980
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1939
- base: markdownLanguage2,
1981
+ base: markdownLanguage3,
1940
1982
  // Languages for syntax highlighting fenced code blocks.
1941
1983
  codeLanguages: languages,
1942
1984
  // Parser extensions.
@@ -1948,34 +1990,40 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1948
1990
  // https://github.com/codemirror/theme-one-dark
1949
1991
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1950
1992
  // Custom styles.
1951
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1952
- ].filter(Boolean);
1953
- };
1954
- var customKeymap = () => keymap4.of([
1955
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1956
- indentWithTab,
1957
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1958
- ...standardKeymap,
1959
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1960
- ...historyKeymap,
1961
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1962
- ...closeBracketsKeymap,
1963
- // https://codemirror.net/docs/ref/#search.searchKeymap
1964
- ...searchKeymap
1965
- ]);
1993
+ syntaxHighlighting2(markdownHighlightStyle(readonly)),
1994
+ keymap4.of([
1995
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1996
+ indentWithTab,
1997
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1998
+ ...closeBracketsKeymap,
1999
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
2000
+ ...historyKeymap,
2001
+ // https://codemirror.net/docs/ref/#search.searchKeymap
2002
+ ...searchKeymap,
2003
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
2004
+ ...standardKeymap
2005
+ ])
2006
+ ];
2007
+ };
1966
2008
 
1967
2009
  // packages/ui/react-ui-editor/src/extensions/mention.ts
1968
2010
  import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1969
2011
  var mention = ({ onSearch }) => {
1970
2012
  return autocompletion2({
2013
+ // TODO(burdon): Not working.
2014
+ activateOnTyping: true,
2015
+ // activateOnTypingDelay: 100,
2016
+ // selectOnOpen: true,
1971
2017
  closeOnBlur: false,
2018
+ // defaultKeymap: false,
2019
+ icons: false,
1972
2020
  override: [
1973
2021
  (context) => {
2022
+ console.log(context);
1974
2023
  const match = context.matchBefore(/@(\w+)?/);
1975
2024
  if (!match || match.from === match.to && !context.explicit) {
1976
2025
  return null;
1977
2026
  }
1978
- console.log(match);
1979
2027
  return {
1980
2028
  from: match.from,
1981
2029
  options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
@@ -1987,19 +2035,24 @@ var mention = ({ onSearch }) => {
1987
2035
  });
1988
2036
  };
1989
2037
 
2038
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
2039
+ var outliner = (options = {}) => {
2040
+ return [];
2041
+ };
2042
+
1990
2043
  // packages/ui/react-ui-editor/src/extensions/table.ts
1991
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1992
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1993
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType6 } from "@codemirror/view";
2044
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2045
+ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField4 } from "@codemirror/state";
2046
+ import { Decoration as Decoration7, EditorView as EditorView11, WidgetType as WidgetType5 } from "@codemirror/view";
1994
2047
  var table = (options = {}) => {
1995
2048
  return StateField4.define({
1996
2049
  create: (state) => update(state, options),
1997
2050
  update: (_, tr) => update(tr.state, options),
1998
- provide: (field) => EditorView10.decorations.from(field)
2051
+ provide: (field) => EditorView11.decorations.from(field)
1999
2052
  });
2000
2053
  };
2001
2054
  var update = (state, options) => {
2002
- const builder = new RangeSetBuilder2();
2055
+ const builder = new RangeSetBuilder3();
2003
2056
  const cursor = state.selection.main.head;
2004
2057
  const tables = [];
2005
2058
  const getTable = () => tables[tables.length - 1];
@@ -2007,7 +2060,7 @@ var update = (state, options) => {
2007
2060
  const table2 = getTable();
2008
2061
  return table2.rows?.[table2.rows.length - 1];
2009
2062
  };
2010
- syntaxTree3(state).iterate({
2063
+ syntaxTree4(state).iterate({
2011
2064
  enter: (node) => {
2012
2065
  switch (node.name) {
2013
2066
  case "Table": {
@@ -2040,7 +2093,6 @@ var update = (state, options) => {
2040
2093
  tables.forEach((table2) => {
2041
2094
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
2042
2095
  hide && builder.add(table2.from, table2.to, Decoration7.replace({
2043
- block: true,
2044
2096
  widget: new TableWidget(table2)
2045
2097
  }));
2046
2098
  builder.add(table2.from, table2.to, Decoration7.mark({
@@ -2049,7 +2101,7 @@ var update = (state, options) => {
2049
2101
  });
2050
2102
  return builder.finish();
2051
2103
  };
2052
- var TableWidget = class extends WidgetType6 {
2104
+ var TableWidget = class extends WidgetType5 {
2053
2105
  constructor(_table) {
2054
2106
  super();
2055
2107
  this._table = _table;
@@ -2081,88 +2133,104 @@ var TableWidget = class extends WidgetType6 {
2081
2133
  }
2082
2134
  return table2;
2083
2135
  }
2136
+ ignoreEvent(e) {
2137
+ return !/^mouse/.test(e.type);
2138
+ }
2084
2139
  };
2085
2140
 
2086
2141
  // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2087
- import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType7, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin7 } from "@codemirror/view";
2088
- var styles5 = EditorView11.baseTheme({
2089
- "& .cm-task-item": {
2090
- paddingLeft: "4px",
2091
- paddingRight: "8px"
2142
+ import { syntaxTree as syntaxTree5 } from "@codemirror/language";
2143
+ import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
2144
+ import { EditorView as EditorView12, Decoration as Decoration8, WidgetType as WidgetType6, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
2145
+ var styles5 = EditorView12.baseTheme({
2146
+ "& .cm-task": {
2147
+ color: getToken("extend.colors.blue.500")
2148
+ },
2149
+ "& .cm-task-checkbox": {
2150
+ marginLeft: "4px",
2151
+ marginRight: "4px"
2092
2152
  }
2093
2153
  });
2094
- var CheckboxWidget = class extends WidgetType7 {
2095
- constructor(_checked, _indent, _onCheck) {
2154
+ var CheckboxWidget = class extends WidgetType6 {
2155
+ constructor(_checked) {
2096
2156
  super();
2097
2157
  this._checked = _checked;
2098
- this._indent = _indent;
2099
- this._onCheck = _onCheck;
2100
2158
  }
2101
2159
  eq(other) {
2102
- return this._checked === other._checked && this._indent === other._indent;
2160
+ return this._checked === other._checked;
2103
2161
  }
2104
2162
  toDOM(view) {
2105
- const wrap = document.createElement("span");
2106
- wrap.className = "cm-task-item";
2107
- wrap.setAttribute("aria-hidden", "true");
2108
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
2109
- const input = wrap.appendChild(document.createElement("input"));
2163
+ const input = document.createElement("input");
2164
+ input.className = "cm-task-checkbox";
2110
2165
  input.type = "checkbox";
2111
2166
  input.checked = this._checked;
2112
- if (!this._onCheck) {
2167
+ if (view.state.readOnly) {
2113
2168
  input.setAttribute("disabled", "true");
2114
- }
2115
- if (this._onCheck) {
2116
- input.onchange = (event) => {
2117
- this._onCheck?.(event.target.checked);
2118
- return true;
2169
+ } else {
2170
+ input.onmousedown = (event) => {
2171
+ const pos = view.posAtDOM(input);
2172
+ const text = view.state.sliceDoc(pos, pos + 3);
2173
+ if (text === (this._checked ? "[x]" : "[ ]")) {
2174
+ view.dispatch({
2175
+ changes: {
2176
+ from: pos + 1,
2177
+ to: pos + 2,
2178
+ insert: this._checked ? " " : "x"
2179
+ }
2180
+ });
2181
+ event.preventDefault();
2182
+ }
2119
2183
  };
2120
2184
  }
2121
- return wrap;
2185
+ return input;
2122
2186
  }
2123
2187
  ignoreEvent() {
2124
2188
  return false;
2125
2189
  }
2126
2190
  };
2191
+ var checkedDecoration = Decoration8.replace({
2192
+ widget: new CheckboxWidget(true)
2193
+ });
2194
+ var uncheckedDecoration = Decoration8.replace({
2195
+ widget: new CheckboxWidget(false)
2196
+ });
2197
+ var buildDecorations4 = (view) => {
2198
+ const builder = new RangeSetBuilder4();
2199
+ const { state } = view;
2200
+ const cursor = state.selection.main.head;
2201
+ for (const { from, to } of view.visibleRanges) {
2202
+ syntaxTree5(state).iterate({
2203
+ enter: (node) => {
2204
+ if (node.name === "TaskMarker") {
2205
+ if (cursor < node.from || cursor > node.to) {
2206
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2207
+ builder.add(node.from - 2, node.from - 1, Decoration8.mark({
2208
+ class: "cm-task"
2209
+ }));
2210
+ builder.add(node.from, node.to, checked ? checkedDecoration : uncheckedDecoration);
2211
+ }
2212
+ }
2213
+ },
2214
+ from,
2215
+ to
2216
+ });
2217
+ }
2218
+ return builder.finish();
2219
+ };
2127
2220
  var tasklist = (options = {}) => {
2128
- const taskMatcher = new MatchDecorator3({
2129
- regexp: /^(\s*)- \[([ xX])\]\s/g,
2130
- decoration: (match, view, pos) => {
2131
- const indent = Math.floor(match[1].length / 2);
2132
- const checked = match[2] === "x" || match[2] === "X";
2133
- return Decoration8.replace({
2134
- widget: new CheckboxWidget(checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2135
- const idx = pos + match[0].indexOf("[") + 1;
2136
- view.dispatch({
2137
- changes: {
2138
- from: idx,
2139
- to: idx + 1,
2140
- insert: checked2 ? "x" : " "
2141
- },
2142
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
2143
- selection: {
2144
- anchor: idx + 3
2145
- }
2146
- });
2147
- })
2148
- });
2149
- }
2150
- });
2151
- const tasks = ViewPlugin7.fromClass(class {
2152
- constructor(view) {
2153
- this.tasks = taskMatcher.createDeco(view);
2154
- }
2155
- update(update2) {
2156
- this.tasks = taskMatcher.updateDeco(update2, this.tasks);
2157
- }
2158
- }, {
2159
- decorations: (value) => value.tasks,
2160
- provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2161
- return view.plugin(plugin)?.tasks || Decoration8.none;
2162
- })
2163
- });
2164
2221
  return [
2165
- tasks,
2222
+ ViewPlugin5.fromClass(class {
2223
+ constructor(view) {
2224
+ this.decorations = buildDecorations4(view);
2225
+ }
2226
+ update(update2) {
2227
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2228
+ this.decorations = buildDecorations4(update2.view);
2229
+ }
2230
+ }
2231
+ }, {
2232
+ decorations: (v) => v.decorations
2233
+ }),
2166
2234
  styles5
2167
2235
  ];
2168
2236
  };
@@ -2241,7 +2309,7 @@ var cursorConverter2 = (text) => ({
2241
2309
 
2242
2310
  // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2243
2311
  var yjs = (content, awareness2) => [
2244
- CursorConverter.of(cursorConverter2(content)),
2312
+ Cursor.converter.of(cursorConverter2(content)),
2245
2313
  yCollab(content, awareness2)
2246
2314
  ];
2247
2315
 
@@ -2338,33 +2406,6 @@ var defaultTheme = {
2338
2406
  backgroundColor: get3(tokens, "extend.colors.yellow.700")
2339
2407
  },
2340
2408
  //
2341
- // collaboration
2342
- // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2343
- //
2344
- "&light .cm-ySelection, &light .cm-yLineSelection": {
2345
- mixBlendMode: "multiply"
2346
- },
2347
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2348
- mixBlendMode: "screen"
2349
- },
2350
- ".cm-ySelectionInfo": {
2351
- padding: "2px 4px",
2352
- marginBlockStart: "-4px"
2353
- },
2354
- ".cm-ySelection, & .cm-selectionMatch": {
2355
- paddingBlockStart: ".15em",
2356
- paddingBlockEnd: ".15em"
2357
- },
2358
- ".cm-ySelectionCaret": {
2359
- display: "inline-block",
2360
- insetBlockStart: ".1em",
2361
- blockSize: "1.4em",
2362
- verticalAlign: "top"
2363
- },
2364
- ".cm-yLineSelection": {
2365
- margin: 0
2366
- },
2367
- //
2368
2409
  // link
2369
2410
  //
2370
2411
  ".cm-link": {
@@ -2401,13 +2442,6 @@ var defaultTheme = {
2401
2442
  textDecoration: "none"
2402
2443
  },
2403
2444
  //
2404
- // widgets
2405
- //
2406
- ".cm-widgetBuffer": {
2407
- display: "none",
2408
- height: 0
2409
- },
2410
- //
2411
2445
  // table
2412
2446
  //
2413
2447
  ".cm-table *": {
@@ -2438,27 +2472,17 @@ var defaultTheme = {
2438
2472
  },
2439
2473
  //
2440
2474
  // font size
2441
- // 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.
2475
+ // TODO(thure): This appears to be the best or only way to set selection caret heights,
2476
+ // but it's far more verbose than it needs to be.
2442
2477
  //
2443
- ...Object.keys(get3(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2444
- const height = get3(tokens, [
2445
- "extend",
2446
- "fontSize",
2447
- fontSize,
2448
- 1,
2449
- "lineHeight"
2450
- ]);
2451
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2452
- height
2453
- };
2454
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2455
- height
2456
- };
2457
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2458
- height
2459
- };
2460
- return acc;
2461
- }, {}),
2478
+ // ...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
2479
+ // const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
2480
+ //
2481
+ // acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
2482
+ // acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
2483
+ // acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
2484
+ // return acc;
2485
+ // }, {}),
2462
2486
  /**
2463
2487
  * Panels
2464
2488
  * TODO(burdon): Needs styling attention (esp. dark mode).
@@ -2509,7 +2533,9 @@ var textTheme = {
2509
2533
  }
2510
2534
  };
2511
2535
  var markdownTheme = {
2512
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2536
+ ".cm-scroller": {
2537
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2538
+ },
2513
2539
  ".cm-placeholder": {
2514
2540
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2515
2541
  }
@@ -2524,95 +2550,91 @@ var codeTheme = {
2524
2550
  };
2525
2551
 
2526
2552
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2553
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
2527
2554
  var EditorModes = [
2528
2555
  "default",
2529
2556
  "vim"
2530
2557
  ];
2531
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
2532
- const { themeMode } = useThemeContext();
2558
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autofocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [] }, forwardedRef) => {
2533
2559
  const tabsterDOMAttribute = useFocusableGroup({
2534
2560
  tabBehavior: "limited"
2535
2561
  });
2536
- const [root, setRoot] = useState(null);
2537
- const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
2538
- useImperativeHandle(forwardedRef, () => ({
2539
- root,
2540
- state,
2562
+ const { themeMode } = useThemeContext2();
2563
+ const rootRef = useRef(null);
2564
+ const [view, setView] = useState(null);
2565
+ useImperativeHandle(forwardedRef, () => view, [
2541
2566
  view
2542
- }), [
2543
- view,
2544
- state,
2545
- root
2546
2567
  ]);
2547
- const { awareness: awareness2, peer } = model;
2548
- useEffect(() => {
2549
- if (awareness2 && peer) {
2550
- awareness2.setLocalStateField("user", {
2551
- name: peer.name ?? generateName(peer.id),
2552
- color: getColorForValue({
2553
- value: peer.id,
2554
- type: "color"
2555
- }),
2556
- colorLight: getColorForValue({
2557
- value: peer.id,
2558
- themeMode,
2559
- type: "highlight"
2560
- })
2561
- });
2562
- }
2563
- }, [
2564
- awareness2,
2565
- peer,
2566
- themeMode
2567
- ]);
2568
- useEffect(() => {
2569
- if (view && comments2?.length) {
2570
- view.dispatch({
2571
- effects: setCommentRange.of({
2572
- model,
2573
- comments: comments2
2574
- })
2575
- });
2568
+ useEffect3(() => {
2569
+ if (autofocus) {
2570
+ view?.focus();
2576
2571
  }
2577
2572
  }, [
2578
2573
  view,
2579
- comments2
2574
+ autofocus
2580
2575
  ]);
2581
- useEffect(() => {
2582
- if (!root) {
2576
+ useAwareness(model);
2577
+ useEffect3(() => {
2578
+ if (!model || !rootRef.current) {
2583
2579
  return;
2584
2580
  }
2585
- const state2 = EditorState2.create({
2581
+ const state = EditorState2.create({
2586
2582
  doc: model.text(),
2583
+ selection,
2587
2584
  extensions: [
2588
- readonly && EditorState2.readOnly.of(readonly),
2589
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2590
- editorMode === "vim" && vim(),
2585
+ // TODO(burdon): Doesn't catch errors in keymap functions.
2586
+ EditorView13.exceptionSink.of((err) => {
2587
+ log2.catch(err, void 0, {
2588
+ F: __dxlog_file4,
2589
+ L: 114,
2590
+ S: void 0,
2591
+ C: (f, a) => f(...a)
2592
+ });
2593
+ }),
2591
2594
  // Theme.
2592
- // TODO(burdon): Make theme configurable.
2593
- EditorView12.baseTheme(defaultTheme),
2594
- EditorView12.theme(slots?.editor?.theme ?? {}),
2595
- EditorView12.darkTheme.of(themeMode === "dark"),
2595
+ // TODO(burdon): Make configurable.
2596
+ EditorView13.baseTheme(defaultTheme),
2597
+ EditorView13.theme(theme ?? {}),
2598
+ EditorView13.darkTheme.of(themeMode === "dark"),
2599
+ EditorView13.editorAttributes.of({
2600
+ class: slots.editor?.className ?? ""
2601
+ }),
2602
+ EditorView13.contentAttributes.of({
2603
+ class: slots.content?.className ?? ""
2604
+ }),
2605
+ // State.
2606
+ EditorState2.readOnly.of(!!readonly),
2596
2607
  // Storage and replication.
2608
+ // NOTE: This must come before user extensions.
2597
2609
  model.extension,
2610
+ // TODO(burdon): Factor out (requires special handling for Escape/focus).
2611
+ editorMode === "vim" && vim(),
2598
2612
  // Custom.
2599
2613
  ...extensions
2600
2614
  ].filter(Boolean)
2601
2615
  });
2602
- view?.destroy();
2603
- setEditor({
2604
- state: state2,
2605
- view: new EditorView12({
2606
- state: state2,
2607
- parent: root
2608
- })
2616
+ const newView = new EditorView13({
2617
+ state,
2618
+ parent: rootRef.current,
2619
+ scrollTo,
2620
+ // NOTE: Uncomment to debug/monitor all transactions.
2621
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
2622
+ dispatchTransactions: (trs, view2) => {
2623
+ view2.update(trs);
2624
+ const debug = false;
2625
+ if (debug) {
2626
+ logChanges(trs);
2627
+ }
2628
+ }
2609
2629
  });
2630
+ view?.destroy();
2631
+ setView(newView);
2610
2632
  return () => {
2611
- view?.destroy();
2612
- setEditor(void 0);
2633
+ newView?.destroy();
2634
+ setView(null);
2613
2635
  };
2614
2636
  }, [
2615
- root,
2637
+ rootRef,
2616
2638
  model,
2617
2639
  readonly,
2618
2640
  editorMode,
@@ -2622,11 +2644,13 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2622
2644
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2623
2645
  switch (key) {
2624
2646
  case "Enter": {
2625
- view?.contentDOM.focus();
2647
+ view?.focus();
2626
2648
  break;
2627
2649
  }
2628
2650
  case "Escape": {
2629
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2651
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
2652
+ rootRef.current?.focus();
2653
+ }
2630
2654
  break;
2631
2655
  }
2632
2656
  }
@@ -2636,65 +2660,66 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2636
2660
  ]);
2637
2661
  return /* @__PURE__ */ React.createElement("div", {
2638
2662
  key: model.id,
2639
- ref: setRoot,
2663
+ role: "none",
2640
2664
  tabIndex: 0,
2641
- ...slots?.root,
2665
+ onKeyUp: handleKeyUp,
2666
+ ...slots.root,
2642
2667
  ...editorMode !== "vim" && tabsterDOMAttribute,
2643
- onKeyUp: handleKeyUp
2668
+ ref: rootRef
2644
2669
  });
2645
2670
  });
2646
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2647
- const { themeMode } = useThemeContext();
2648
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2671
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, multiline, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
2672
+ const { themeMode } = useThemeContext2();
2673
+ const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2649
2674
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2650
2675
  ref: forwardedRef,
2651
2676
  readonly,
2652
2677
  extensions: [
2653
2678
  basicBundle({
2654
- readonly,
2655
2679
  themeMode,
2656
- placeholder: slots?.editor?.placeholder
2680
+ placeholder: placeholder3,
2681
+ multiline
2657
2682
  }),
2658
2683
  ...extensions
2659
2684
  ],
2660
- slots,
2685
+ theme,
2686
+ slots: updatedSlots,
2661
2687
  ...props
2662
2688
  });
2663
2689
  });
2664
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2665
- const { themeMode } = useThemeContext();
2666
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
2690
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
2691
+ const { themeMode } = useThemeContext2();
2692
+ const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2667
2693
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2668
2694
  ref: forwardedRef,
2669
2695
  readonly,
2670
2696
  extensions: [
2671
2697
  markdownBundle({
2672
- readonly,
2673
2698
  themeMode,
2674
- placeholder: slots?.editor?.placeholder
2699
+ readonly,
2700
+ placeholder: placeholder3
2675
2701
  }),
2676
2702
  ...extensions
2677
2703
  ],
2678
- slots,
2704
+ theme,
2705
+ slots: updatedSlots,
2679
2706
  ...props
2680
2707
  });
2681
2708
  });
2682
2709
  var defaultSlots = {
2683
2710
  root: {
2684
- className: mx3("p-2", inputSurface)
2711
+ // TODO(burdon): Add focusRing by default/as property?
2712
+ className: mx3("flex flex-col grow overflow-y-auto", inputSurface)
2713
+ },
2714
+ editor: {
2715
+ className: "h-full p-2"
2685
2716
  }
2686
2717
  };
2687
2718
  var defaultTextSlots = {
2688
- ...defaultSlots,
2689
- editor: {
2690
- theme: textTheme
2691
- }
2719
+ ...defaultSlots
2692
2720
  };
2693
2721
  var defaultMarkdownSlots = {
2694
- ...defaultSlots,
2695
- editor: {
2696
- theme: markdownTheme
2697
- }
2722
+ ...defaultSlots
2698
2723
  };
2699
2724
 
2700
2725
  // packages/ui/react-ui-editor/src/hooks/automerge.ts
@@ -2707,7 +2732,7 @@ import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2707
2732
  import { Context as Context2 } from "@dxos/context";
2708
2733
  import { invariant as invariant3 } from "@dxos/invariant";
2709
2734
  import { log as log3 } from "@dxos/log";
2710
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2735
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2711
2736
  var DEBOUNCE_INTERVAL = 100;
2712
2737
  var SpaceAwarenessProvider = class {
2713
2738
  constructor(params) {
@@ -2748,7 +2773,7 @@ var SpaceAwarenessProvider = class {
2748
2773
  log3.debug("failed to query awareness", {
2749
2774
  err
2750
2775
  }, {
2751
- F: __dxlog_file4,
2776
+ F: __dxlog_file5,
2752
2777
  L: 85,
2753
2778
  S: this,
2754
2779
  C: (f, a) => f(...a)
@@ -2762,7 +2787,7 @@ var SpaceAwarenessProvider = class {
2762
2787
  }
2763
2788
  update(position) {
2764
2789
  invariant3(this._postTask, void 0, {
2765
- F: __dxlog_file4,
2790
+ F: __dxlog_file5,
2766
2791
  L: 96,
2767
2792
  S: this,
2768
2793
  A: [
@@ -2782,7 +2807,7 @@ var SpaceAwarenessProvider = class {
2782
2807
  }
2783
2808
  _handleQueryMessage() {
2784
2809
  invariant3(this._postTask, void 0, {
2785
- F: __dxlog_file4,
2810
+ F: __dxlog_file5,
2786
2811
  L: 111,
2787
2812
  S: this,
2788
2813
  A: [
@@ -2794,7 +2819,7 @@ var SpaceAwarenessProvider = class {
2794
2819
  }
2795
2820
  _handlePostMessage(message) {
2796
2821
  invariant3(message.kind === "post", void 0, {
2797
- F: __dxlog_file4,
2822
+ F: __dxlog_file5,
2798
2823
  L: 116,
2799
2824
  S: this,
2800
2825
  A: [
@@ -2807,25 +2832,63 @@ var SpaceAwarenessProvider = class {
2807
2832
  }
2808
2833
  };
2809
2834
 
2810
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2835
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
2811
2836
  import { StateField as StateField5 } from "@codemirror/state";
2812
- import { useEffect as useEffect2, useState as useState2 } from "react";
2813
- import { isAutomergeObject } from "@dxos/react-client/echo";
2837
+ var modelState = StateField5.define({
2838
+ create: () => void 0,
2839
+ update: (model) => model
2840
+ });
2841
+
2842
+ // packages/ui/react-ui-editor/src/hooks/automerge.ts
2843
+ var createAutomergeModel = ({ space, identity, text }) => {
2844
+ const obj = text;
2845
+ const doc = getRawDoc(obj, [
2846
+ obj.field
2847
+ ]);
2848
+ const awarenessProvider = space && new SpaceAwarenessProvider({
2849
+ space,
2850
+ channel: `automerge.awareness.${obj.id}`,
2851
+ info: {
2852
+ displayName: identity?.profile?.displayName ?? "",
2853
+ color: cursorColor.color,
2854
+ lightColor: cursorColor.light
2855
+ },
2856
+ peerId: identity?.identityKey.toHex() ?? "Anonymous"
2857
+ });
2858
+ const model = {
2859
+ id: obj.id,
2860
+ content: doc,
2861
+ text: () => get4(doc.handle.docSync(), doc.path),
2862
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2863
+ extension: [
2864
+ modelState.init(() => model),
2865
+ automerge3({
2866
+ handle: doc.handle,
2867
+ path: doc.path
2868
+ }),
2869
+ awarenessProvider && AwarenessProvider.of(awarenessProvider),
2870
+ awareness()
2871
+ ].filter(isNotNullOrUndefined),
2872
+ peer: identity ? {
2873
+ id: identity.identityKey.toHex(),
2874
+ name: identity.profile?.displayName
2875
+ } : void 0
2876
+ };
2877
+ return model;
2878
+ };
2814
2879
 
2815
2880
  // packages/ui/react-ui-editor/src/hooks/yjs.ts
2816
2881
  import * as decoding from "lib0/decoding";
2817
2882
  import * as encoding from "lib0/encoding";
2818
2883
  import { Observable } from "lib0/observable";
2819
2884
  import * as YP from "y-protocols/awareness";
2820
- import * as Y2 from "yjs";
2821
2885
  import { invariant as invariant4 } from "@dxos/invariant";
2822
2886
  import { log as log4 } from "@dxos/log";
2823
- import { arrayToString as arrayToString2, stringToArray as stringToArray2 } from "@dxos/util";
2824
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2887
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2825
2888
  var createYjsModel = ({ identity, space, text }) => {
2826
2889
  invariant4(text?.doc && text?.content, void 0, {
2827
- F: __dxlog_file5,
2828
- L: 23,
2890
+ F: __dxlog_file6,
2891
+ L: 22,
2829
2892
  S: void 0,
2830
2893
  A: [
2831
2894
  "text?.doc && text?.content",
@@ -2841,34 +2904,6 @@ var createYjsModel = ({ identity, space, text }) => {
2841
2904
  id: text.doc.guid,
2842
2905
  content: text.content,
2843
2906
  text: () => text.content.toString(),
2844
- // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
2845
- // TODO(dmaretskyi): Refactor as cursor-converter.
2846
- getCursorFromRange: (range) => {
2847
- const from = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.from));
2848
- const to = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.to, -1));
2849
- return [
2850
- arrayToString2(from),
2851
- arrayToString2(to)
2852
- ].join(":");
2853
- },
2854
- getRangeFromCursor: (cursor) => {
2855
- invariant4(text.doc, void 0, {
2856
- F: __dxlog_file5,
2857
- L: 40,
2858
- S: void 0,
2859
- A: [
2860
- "text.doc",
2861
- ""
2862
- ]
2863
- });
2864
- const parts = cursor.split(":");
2865
- const from = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[0])), text.doc);
2866
- const to = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[1])), text.doc);
2867
- return from && to ? {
2868
- from: from.index,
2869
- to: to.index
2870
- } : void 0;
2871
- },
2872
2907
  extension: [
2873
2908
  modelState.init(() => model),
2874
2909
  yjs(text.content, provider?.awareness)
@@ -2917,8 +2952,8 @@ var YAwarenessProvider = class extends Observable {
2917
2952
  removed,
2918
2953
  origin
2919
2954
  }, {
2920
- F: __dxlog_file5,
2921
- L: 122,
2955
+ F: __dxlog_file6,
2956
+ L: 101,
2922
2957
  S: this,
2923
2958
  C: (f, a) => f(...a)
2924
2959
  });
@@ -2930,8 +2965,8 @@ var YAwarenessProvider = class extends Observable {
2930
2965
  }
2931
2966
  _handleSpaceMessage({ payload }) {
2932
2967
  log4("space message", payload, {
2933
- F: __dxlog_file5,
2934
- L: 131,
2968
+ F: __dxlog_file6,
2969
+ L: 110,
2935
2970
  S: this,
2936
2971
  C: (f, a) => f(...a)
2937
2972
  });
@@ -2975,14 +3010,12 @@ var YAwarenessProvider = class extends Observable {
2975
3010
  };
2976
3011
 
2977
3012
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2978
- var modelState = StateField5.define({
2979
- create: () => void 0,
2980
- update: (model) => model
2981
- });
3013
+ import { useEffect as useEffect4, useState as useState2 } from "react";
3014
+ import { isAutomergeObject } from "@dxos/react-client/echo";
2982
3015
  var useTextModel = (props) => {
2983
3016
  const { identity, space, text } = props;
2984
- const [model, setModel] = useState2(() => createModel(props));
2985
- useEffect2(() => setModel(createModel(props)), [
3017
+ const [model, setModel] = useState2();
3018
+ useEffect4(() => setModel(createModel(props)), [
2986
3019
  identity,
2987
3020
  space,
2988
3021
  text
@@ -2999,47 +3032,10 @@ var createModel = (props) => {
2999
3032
  return void 0;
3000
3033
  }
3001
3034
  };
3002
-
3003
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
3004
- var createAutomergeModel = ({ space, identity, text }) => {
3005
- const obj = text;
3006
- const doc = getRawDoc(obj, [
3007
- obj.field
3008
- ]);
3009
- const awarenessProvider = space && new SpaceAwarenessProvider({
3010
- space,
3011
- channel: `automerge.awareness.${obj.id}`,
3012
- info: {
3013
- displayName: identity?.profile?.displayName ?? "",
3014
- color: cursorColor.color,
3015
- lightColor: cursorColor.light
3016
- },
3017
- peerId: identity?.identityKey.toHex() ?? "Anonymous"
3018
- });
3019
- const model = {
3020
- id: obj.id,
3021
- content: doc,
3022
- text: () => get4(doc.handle.docSync(), doc.path),
3023
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
3024
- extension: [
3025
- modelState.init(() => model),
3026
- automerge3({
3027
- handle: doc.handle,
3028
- path: doc.path
3029
- }),
3030
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
3031
- awareness()
3032
- ].filter(isNotNullOrUndefined),
3033
- peer: identity ? {
3034
- id: identity.identityKey.toHex(),
3035
- name: identity.profile?.displayName
3036
- } : void 0
3037
- };
3038
- return model;
3039
- };
3040
3035
  export {
3041
3036
  AwarenessProvider,
3042
3037
  BaseTextEditor,
3038
+ Cursor,
3043
3039
  Doc,
3044
3040
  EditorModes,
3045
3041
  MarkdownEditor,
@@ -3055,6 +3051,7 @@ export {
3055
3051
  awareness,
3056
3052
  basicBundle,
3057
3053
  blast,
3054
+ callbackWrapper,
3058
3055
  code2 as code,
3059
3056
  codeTheme,
3060
3057
  comments,
@@ -3070,6 +3067,7 @@ export {
3070
3067
  image,
3071
3068
  link,
3072
3069
  listener,
3070
+ logChanges,
3073
3071
  markdownBundle,
3074
3072
  markdownHighlightStyle,
3075
3073
  markdownTags,
@@ -3077,7 +3075,8 @@ export {
3077
3075
  markdownTheme,
3078
3076
  mention,
3079
3077
  modelState,
3080
- setCommentRange,
3078
+ outliner,
3079
+ setComments,
3081
3080
  setFocus,
3082
3081
  setSelection,
3083
3082
  table,
@@ -3085,6 +3084,8 @@ export {
3085
3084
  tasklist,
3086
3085
  textTheme,
3087
3086
  typewriter,
3087
+ useAwareness,
3088
+ useComments,
3088
3089
  useTextModel,
3089
3090
  yjs
3090
3091
  };