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

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 +801 -776
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +16 -13
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +20 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -6
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  11. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -1
  13. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  14. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  15. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  16. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  17. package/dist/types/src/extensions/automerge/defs.d.ts +7 -9
  18. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  19. package/dist/types/src/extensions/automerge/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 +124 -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 +40 -46
  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,52 +7,56 @@ 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, ViewPlugin } from "@codemirror/view";
55
58
  import { next as A } from "@dxos/automerge/automerge";
59
+ import { invariant } from "@dxos/invariant";
56
60
 
57
61
  // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
58
62
  import get from "lodash.get";
@@ -93,12 +97,12 @@ var cursorConverter = (handle, path) => ({
93
97
  // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
94
98
  import { Annotation, StateEffect } from "@codemirror/state";
95
99
  var effectType = StateEffect.define({});
100
+ var reconcileAnnotationType = Annotation.define();
101
+ var getPath = (state, field) => state.field(field).path;
102
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
96
103
  var updateHeads = (newHeads) => effectType.of({
97
104
  newHeads
98
105
  });
99
- var getLastHeads = (state, field) => state.field(field).lastHeads;
100
- var getPath = (state, field) => state.field(field).path;
101
- var reconcileAnnotationType = Annotation.define();
102
106
  var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
103
107
 
104
108
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
@@ -236,85 +240,96 @@ var charPath = (textPath, candidatePath) => {
236
240
 
237
241
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
238
242
  var PatchSemaphore = class {
239
- constructor(_field) {
240
- this._field = _field;
243
+ // prettier-ignore
244
+ constructor(_handle, _state) {
245
+ this._handle = _handle;
246
+ this._state = _state;
241
247
  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);
248
+ }
249
+ // NOTE: Cannot destruct view.state.
250
+ reconcile(view) {
251
+ if (this._inReconcile) {
252
+ return;
253
+ }
254
+ this._inReconcile = true;
255
+ const path = getPath(view.state, this._state);
256
+ const oldHeads = getLastHeads(view.state, this._state);
257
+ let selection = view.state.selection;
258
+ const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
259
+ const toInvert = transactions.slice().reverse();
260
+ for (const tx of toInvert) {
261
+ const inverted = tx.changes.invert(tx.startState.doc);
262
+ selection = selection.map(inverted);
267
263
  view.dispatch({
268
- effects: updateHeads(newHeads),
269
- annotations: reconcileAnnotationType.of({})
264
+ changes: inverted,
265
+ annotations: reconcileAnnotationType.of(true)
270
266
  });
271
- this._inReconcile = false;
272
- };
267
+ }
268
+ let newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
269
+ if (newHeads === null || newHeads === void 0) {
270
+ newHeads = automerge2.getHeads(this._handle.docSync());
271
+ }
272
+ const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(this._handle.docSync(), oldHeads, newHeads);
273
+ updateCodeMirror(view, selection, path, diff);
274
+ view.dispatch({
275
+ effects: updateHeads(newHeads),
276
+ annotations: reconcileAnnotationType.of({})
277
+ });
278
+ this._inReconcile = false;
273
279
  }
274
280
  };
275
281
 
276
- // packages/ui/react-ui-editor/src/util/cursor.ts
282
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
277
283
  import { Facet } from "@codemirror/state";
278
- var DEFAULT_CURSOR_CONVERTER = {
284
+ var defaultCursorConverter = {
279
285
  toCursor: (position) => position.toString(),
280
286
  fromCursor: (cursor) => parseInt(cursor)
281
287
  };
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)
288
+ var Cursor = class _Cursor {
289
+ static {
290
+ this.converter = Facet.define({
291
+ combine: (providers) => providers[0] ?? defaultCursorConverter
298
292
  });
299
293
  }
294
+ static {
295
+ this.getCursorFromRange = (state, range) => {
296
+ const cursorConverter3 = state.facet(_Cursor.converter);
297
+ const from = cursorConverter3.toCursor(range.from);
298
+ const to = cursorConverter3.toCursor(range.to, -1);
299
+ return [
300
+ from,
301
+ to
302
+ ].join(":");
303
+ };
304
+ }
305
+ static {
306
+ this.getRangeFromCursor = (state, cursor) => {
307
+ const cursorConverter3 = state.facet(_Cursor.converter);
308
+ const parts = cursor.split(":");
309
+ const from = cursorConverter3.fromCursor(parts[0]);
310
+ const to = cursorConverter3.fromCursor(parts[1]);
311
+ return from !== void 0 && to !== void 0 ? {
312
+ from,
313
+ to
314
+ } : void 0;
315
+ };
316
+ }
300
317
  };
301
318
 
302
319
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
303
- var semaphoreFacet = Facet2.define({
304
- combine: (values) => values.at(-1)
305
- });
320
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts";
306
321
  var automerge3 = ({ handle, path }) => {
307
- const stateField = StateField.define({
322
+ const state = StateField.define({
308
323
  create: () => ({
324
+ path: path.slice(),
309
325
  lastHeads: A.getHeads(handle.docSync()),
310
- unreconciledTransactions: [],
311
- path: path.slice()
326
+ unreconciledTransactions: []
312
327
  }),
313
328
  update: (value, tr) => {
314
329
  const result = {
330
+ path: path.slice(),
315
331
  lastHeads: value.lastHeads,
316
- unreconciledTransactions: value.unreconciledTransactions.slice(),
317
- path: path.slice()
332
+ unreconciledTransactions: value.unreconciledTransactions.slice()
318
333
  };
319
334
  let clearUnreconciled = false;
320
335
  for (const effect of tr.effects) {
@@ -333,41 +348,50 @@ var automerge3 = ({ handle, path }) => {
333
348
  return result;
334
349
  }
335
350
  });
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);
351
+ const semaphore = new PatchSemaphore(handle, state);
352
+ return [
353
+ Cursor.converter.of(cursorConverter(handle, path)),
354
+ // Reconcile external updates.
355
+ ViewPlugin.fromClass(class {
356
+ constructor(_view) {
357
+ this._view = _view;
358
+ invariant(this._view, void 0, {
359
+ F: __dxlog_file,
360
+ L: 66,
361
+ S: this,
362
+ A: [
363
+ "this._view",
364
+ ""
365
+ ]
349
366
  });
367
+ handle.addListener("change", this._handleChange.bind(this));
350
368
  }
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
- };
369
+ destroy() {
370
+ handle.removeListener("change", this._handleChange.bind(this));
371
+ }
372
+ _handleChange() {
373
+ semaphore.reconcile(this._view);
374
+ }
375
+ }),
376
+ // Reconcile local updates.
377
+ EditorView.updateListener.of(({ view, changes }) => {
378
+ if (!changes.empty) {
379
+ semaphore.reconcile(view);
380
+ }
381
+ }),
382
+ state
383
+ ];
364
384
  };
365
385
 
366
386
  // 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";
387
+ import { Annotation as Annotation2, Facet as Facet2, RangeSet } from "@codemirror/state";
388
+ import { Decoration, EditorView as EditorView2, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
389
+ import { useEffect } from "react";
369
390
  import { Event } from "@dxos/async";
370
391
  import { Context } from "@dxos/context";
392
+ import { generateName } from "@dxos/display-name";
393
+ import { useThemeContext } from "@dxos/react-ui";
394
+ import { getColorForValue } from "@dxos/react-ui-theme";
371
395
  var dummyProvider = {
372
396
  remoteStateChange: new Event(),
373
397
  open: () => {
@@ -378,7 +402,7 @@ var dummyProvider = {
378
402
  },
379
403
  getRemoteStates: () => []
380
404
  };
381
- var AwarenessProvider = Facet3.define({
405
+ var AwarenessProvider = Facet2.define({
382
406
  combine: (providers) => providers[0] ?? dummyProvider
383
407
  });
384
408
  var RemoteSelectionChangedAnnotation = Annotation2.define();
@@ -391,13 +415,36 @@ var awareness = (provider = dummyProvider) => {
391
415
  styles
392
416
  ];
393
417
  };
418
+ var useAwareness = ({ awareness: awareness2, peer }) => {
419
+ const { themeMode } = useThemeContext();
420
+ useEffect(() => {
421
+ if (awareness2 && peer) {
422
+ awareness2.setLocalStateField("user", {
423
+ name: peer.name ?? generateName(peer.id),
424
+ color: getColorForValue({
425
+ value: peer.id,
426
+ type: "color"
427
+ }),
428
+ colorLight: getColorForValue({
429
+ value: peer.id,
430
+ themeMode,
431
+ type: "highlight"
432
+ })
433
+ });
434
+ }
435
+ }, [
436
+ awareness2,
437
+ peer,
438
+ themeMode
439
+ ]);
440
+ };
394
441
  var RemoteSelectionsDecorator = class {
395
442
  constructor(view) {
396
443
  this.decorations = RangeSet.of([]);
397
444
  this._ctx = new Context();
398
445
  this._lastAnchor = void 0;
399
446
  this._lastHead = void 0;
400
- this._cursorConverter = view.state.facet(CursorConverter);
447
+ this._cursorConverter = view.state.facet(Cursor.converter);
401
448
  this._provider = view.state.facet(AwarenessProvider);
402
449
  this._provider.open();
403
450
  this._provider.remoteStateChange.on(this._ctx, () => {
@@ -452,7 +499,7 @@ var RemoteSelectionsDecorator = class {
452
499
  attributes: {
453
500
  style: `background-color: ${lightColor}`
454
501
  },
455
- class: "cm-ySelection"
502
+ class: "cm-collab-selection"
456
503
  })
457
504
  });
458
505
  } else {
@@ -463,7 +510,7 @@ var RemoteSelectionsDecorator = class {
463
510
  attributes: {
464
511
  style: `background-color: ${color}`
465
512
  },
466
- class: "cm-ySelection"
513
+ class: "cm-collab-selection"
467
514
  })
468
515
  });
469
516
  decorations.push({
@@ -473,7 +520,7 @@ var RemoteSelectionsDecorator = class {
473
520
  attributes: {
474
521
  style: `background-color: ${color}`
475
522
  },
476
- class: "cm-ySelection"
523
+ class: "cm-collab-selection"
477
524
  })
478
525
  });
479
526
  for (let i = startLine.number + 1; i < endLine.number; i++) {
@@ -484,7 +531,7 @@ var RemoteSelectionsDecorator = class {
484
531
  value: Decoration.line({
485
532
  attributes: {
486
533
  style: `background-color: ${color}`,
487
- class: "cm-yLineSelection"
534
+ class: "cm-collab-selectionLine"
488
535
  }
489
536
  })
490
537
  });
@@ -504,23 +551,21 @@ var RemoteSelectionsDecorator = class {
504
551
  }
505
552
  };
506
553
  var RemoteCaretWidget = class extends WidgetType {
507
- constructor(name, color) {
554
+ constructor(_name, _color) {
508
555
  super();
509
- this.name = name;
510
- this.color = color;
511
- this.name = name;
512
- this.color = color;
556
+ this._name = _name;
557
+ this._color = _color;
513
558
  }
514
559
  toDOM() {
515
560
  const span = document.createElement("span");
516
- span.className = "cm-ySelectionCaret";
517
- span.style.backgroundColor = this.color;
518
- span.style.borderColor = this.color;
561
+ span.className = "cm-collab-selectionCaret";
562
+ span.style.backgroundColor = this._color;
563
+ span.style.borderColor = this._color;
519
564
  const dot = document.createElement("div");
520
- dot.className = "cm-ySelectionCaretDot";
565
+ dot.className = "cm-collab-selectionCaretDot";
521
566
  const info = document.createElement("div");
522
- info.className = "cm-ySelectionInfo";
523
- info.innerText = this.name;
567
+ info.className = "cm-collab-selectionInfo";
568
+ info.innerText = this._name;
524
569
  span.appendChild(document.createTextNode("\u2060"));
525
570
  span.appendChild(dot);
526
571
  span.appendChild(document.createTextNode("\u2060"));
@@ -528,15 +573,12 @@ var RemoteCaretWidget = class extends WidgetType {
528
573
  span.appendChild(document.createTextNode("\u2060"));
529
574
  return span;
530
575
  }
531
- eq(widget) {
532
- return widget.color === this.color;
533
- }
534
- compare(widget) {
535
- return widget.color === this.color;
536
- }
537
576
  updateDOM() {
538
577
  return false;
539
578
  }
579
+ eq(widget) {
580
+ return widget._color === this._color;
581
+ }
540
582
  get estimatedHeight() {
541
583
  return -1;
542
584
  }
@@ -544,13 +586,13 @@ var RemoteCaretWidget = class extends WidgetType {
544
586
  return true;
545
587
  }
546
588
  };
547
- var styles = EditorView.baseTheme({
548
- ".cm-ySelection": {},
549
- ".cm-yLineSelection": {
589
+ var styles = EditorView2.baseTheme({
590
+ ".cm-collab-selection": {},
591
+ ".cm-collab-selectionLine": {
550
592
  padding: 0,
551
593
  margin: "0px 2px 0px 4px"
552
594
  },
553
- ".cm-ySelectionCaret": {
595
+ ".cm-collab-selectionCaret": {
554
596
  position: "relative",
555
597
  borderLeft: "1px solid black",
556
598
  borderRight: "1px solid black",
@@ -559,7 +601,7 @@ var styles = EditorView.baseTheme({
559
601
  boxSizing: "border-box",
560
602
  display: "inline"
561
603
  },
562
- ".cm-ySelectionCaretDot": {
604
+ ".cm-collab-selectionCaretDot": {
563
605
  borderRadius: "50%",
564
606
  position: "absolute",
565
607
  width: ".4em",
@@ -570,11 +612,11 @@ var styles = EditorView.baseTheme({
570
612
  transition: "transform .3s ease-in-out",
571
613
  boxSizing: "border-box"
572
614
  },
573
- ".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot": {
615
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
574
616
  transformOrigin: "bottom center",
575
617
  transform: "scale(0)"
576
618
  },
577
- ".cm-ySelectionInfo": {
619
+ ".cm-collab-selectionInfo": {
578
620
  position: "absolute",
579
621
  top: "-1.05em",
580
622
  left: "-1px",
@@ -590,12 +632,12 @@ var styles = EditorView.baseTheme({
590
632
  zIndex: 101,
591
633
  transition: "opacity .3s ease-in-out",
592
634
  backgroundColor: "inherit",
593
- // these should be separate
635
+ // These should be separate.
594
636
  opacity: 0,
595
637
  transitionDelay: "0s",
596
638
  whiteSpace: "nowrap"
597
639
  },
598
- ".cm-ySelectionCaret:hover > .cm-ySelectionInfo": {
640
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
599
641
  opacity: 1,
600
642
  transitionDelay: "0s"
601
643
  }
@@ -603,25 +645,27 @@ var styles = EditorView.baseTheme({
603
645
 
604
646
  // packages/ui/react-ui-editor/src/extensions/basic.ts
605
647
  import { closeBrackets } from "@codemirror/autocomplete";
648
+ import { history } from "@codemirror/commands";
606
649
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
607
650
  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.
651
+ import { drawSelection, EditorView as EditorView3, highlightActiveLine, placeholder } from "@codemirror/view";
652
+ var basicBundle = ({ themeMode, placeholder: _placeholder, multiline = true }) => [
653
+ multiline ? EditorView3.lineWrapping : [],
612
654
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
613
655
  bracketMatching(),
614
656
  closeBrackets(),
615
657
  drawSelection(),
616
- _placeholder && placeholder(_placeholder),
617
- // TODO(burdon): Is this required?
658
+ highlightActiveLine(),
659
+ history(),
660
+ _placeholder ? placeholder(_placeholder) : [],
661
+ // https://github.com/codemirror/theme-one-dark
618
662
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
619
- ].filter(Boolean);
663
+ ];
620
664
 
621
665
  // packages/ui/react-ui-editor/src/extensions/blast.ts
622
- import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
666
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
623
667
  import defaultsDeep from "lodash.defaultsdeep";
624
- import { invariant } from "@dxos/invariant";
668
+ import { invariant as invariant2 } from "@dxos/invariant";
625
669
  var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
626
670
  var defaultOptions = {
627
671
  effect: 2,
@@ -667,7 +711,7 @@ var blast = (options = defaultOptions) => {
667
711
  };
668
712
  return [
669
713
  // Cursor moved.
670
- EditorView3.updateListener.of((update2) => {
714
+ EditorView4.updateListener.of((update2) => {
671
715
  if (blaster?.node !== update2.view.scrollDOM) {
672
716
  if (blaster) {
673
717
  blaster.destroy();
@@ -748,7 +792,7 @@ var Blaster = class {
748
792
  return this._node;
749
793
  }
750
794
  initialize() {
751
- invariant(!this._canvas && !this._ctx, void 0, {
795
+ invariant2(!this._canvas && !this._ctx, void 0, {
752
796
  F: __dxlog_file2,
753
797
  L: 138,
754
798
  S: this,
@@ -785,7 +829,7 @@ var Blaster = class {
785
829
  }
786
830
  }
787
831
  start() {
788
- invariant(this._canvas && this._ctx, void 0, {
832
+ invariant2(this._canvas && this._ctx, void 0, {
789
833
  F: __dxlog_file2,
790
834
  L: 177,
791
835
  S: this,
@@ -929,7 +973,7 @@ var random = (min, max) => {
929
973
  };
930
974
 
931
975
  // packages/ui/react-ui-editor/src/extensions/code.ts
932
- import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
976
+ import { ViewPlugin as ViewPlugin3, EditorView as EditorView5, Decoration as Decoration2 } from "@codemirror/view";
933
977
  import { mx as mx2 } from "@dxos/react-ui-theme";
934
978
 
935
979
  // packages/ui/react-ui-editor/src/styles/cursors.ts
@@ -1020,7 +1064,7 @@ var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue)
1020
1064
 
1021
1065
  // packages/ui/react-ui-editor/src/extensions/code.ts
1022
1066
  var CODE_REGEX = /```[\s\S]*?```/gs;
1023
- var styles2 = EditorView4.baseTheme({
1067
+ var styles2 = EditorView5.baseTheme({
1024
1068
  "& .cm-codeblock": {
1025
1069
  fontFamily: getToken("fontFamily.mono", []).join(",")
1026
1070
  },
@@ -1054,20 +1098,18 @@ var getLineRange = (lines, from, to) => {
1054
1098
  ];
1055
1099
  };
1056
1100
  var code2 = () => {
1057
- const getDecorations = (view) => {
1101
+ const buildDecorations5 = (view) => {
1058
1102
  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
- }
1103
+ const text = view.state.doc.sliceString(0);
1104
+ const matches = text.matchAll(CODE_REGEX);
1105
+ const blocks = view.viewportLineBlocks;
1106
+ for (const match of matches) {
1107
+ const range = getLineRange(blocks, match.index, match.index + match[0].length);
1108
+ for (let i = range[0]; i <= range[1]; i++) {
1109
+ const block = blocks[i];
1110
+ decorations.push(Decoration2.line({
1111
+ class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1112
+ }).range(block.from));
1071
1113
  }
1072
1114
  }
1073
1115
  return Decoration2.set(decorations);
@@ -1075,11 +1117,16 @@ var code2 = () => {
1075
1117
  return [
1076
1118
  ViewPlugin3.fromClass(class {
1077
1119
  constructor(view) {
1078
- this.decorations = getDecorations(view);
1120
+ this.hasFocus = false;
1121
+ this.decorations = buildDecorations5(view);
1079
1122
  }
1080
1123
  update(update2) {
1081
- if (update2.docChanged || update2.viewportChanged) {
1082
- this.decorations = getDecorations(update2.view);
1124
+ if (
1125
+ // TODO(burdon): Generalize for other extensions.
1126
+ this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1127
+ ) {
1128
+ this.hasFocus = update2.view.hasFocus;
1129
+ this.decorations = buildDecorations5(update2.view);
1083
1130
  }
1084
1131
  }
1085
1132
  }, {
@@ -1091,23 +1138,51 @@ var code2 = () => {
1091
1138
 
1092
1139
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1093
1140
  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";
1141
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView6 } from "@codemirror/view";
1095
1142
  import sortBy from "lodash.sortby";
1143
+ import { useEffect as useEffect2 } from "react";
1096
1144
  import { debounce } from "@dxos/async";
1097
- import { invariant as invariant2 } from "@dxos/invariant";
1098
- import { log as log2 } from "@dxos/log";
1145
+ import { invariant as invariant3 } from "@dxos/invariant";
1099
1146
  import { nonNullable } from "@dxos/util";
1100
- 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
- },
1147
+
1148
+ // packages/ui/react-ui-editor/src/util.ts
1149
+ import { log } from "@dxos/log";
1150
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1151
+ var callbackWrapper = (fn) => (...args) => {
1152
+ try {
1153
+ return fn(...args);
1154
+ } catch (err) {
1155
+ log.catch(err, void 0, {
1156
+ F: __dxlog_file3,
1157
+ L: 18,
1158
+ S: void 0,
1159
+ C: (f, a) => f(...a)
1160
+ });
1161
+ }
1162
+ };
1163
+ var logChanges = (trs) => {
1164
+ const changes = trs.flatMap((tr) => {
1165
+ if (tr.changes.empty) {
1166
+ return void 0;
1167
+ }
1168
+ const changes2 = [];
1169
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => changes2.push(JSON.stringify({
1170
+ fromA,
1171
+ toA,
1172
+ fromB,
1173
+ toB,
1174
+ inserted: inserted.toString()
1175
+ })));
1176
+ return changes2;
1177
+ }).filter(Boolean);
1178
+ if (changes.length) {
1179
+ console.log("changes", changes);
1180
+ }
1181
+ };
1182
+
1183
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
1184
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1185
+ var styles3 = EditorView6.baseTheme({
1111
1186
  "& .cm-comment": {
1112
1187
  backgroundColor: getToken("extend.colors.yellow.50")
1113
1188
  },
@@ -1123,49 +1198,59 @@ var marks = {
1123
1198
  class: "cm-comment-active"
1124
1199
  })
1125
1200
  };
1126
- var setFocus = (view, thread) => {
1127
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1128
- if (!range) {
1201
+ var setFocus = (view, id, center = true) => {
1202
+ const comment = view.state.field(commentsState).comments.find((range2) => range2.comment.id === id);
1203
+ if (!comment?.comment.cursor) {
1129
1204
  return;
1130
1205
  }
1131
- view.dispatch({
1132
- effects: setSelection.of({
1133
- active: thread
1134
- }),
1135
- selection: {
1136
- anchor: range.from
1137
- },
1138
- scrollIntoView: true
1139
- });
1206
+ const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);
1207
+ if (range) {
1208
+ view.dispatch({
1209
+ selection: {
1210
+ anchor: range.from
1211
+ },
1212
+ effects: [
1213
+ //
1214
+ EditorView6.scrollIntoView(range.from, center ? {
1215
+ y: "center"
1216
+ } : void 0),
1217
+ setSelection.of({
1218
+ active: id
1219
+ })
1220
+ ]
1221
+ });
1222
+ }
1140
1223
  };
1141
- var setCommentRange = StateEffect2.define();
1224
+ var setComments = StateEffect2.define();
1142
1225
  var setSelection = StateEffect2.define();
1143
1226
  var setCommentState = StateEffect2.define();
1144
- var commentsStateField = StateField2.define({
1227
+ var commentsState = StateField2.define({
1145
1228
  create: () => ({
1146
- ranges: []
1229
+ comments: [],
1230
+ selection: {}
1147
1231
  }),
1148
1232
  update: (value, tr) => {
1149
- const cursorConverter3 = tr.state.facet(CursorConverter);
1150
1233
  for (const effect of tr.effects) {
1151
1234
  if (effect.is(setSelection)) {
1152
1235
  return {
1153
1236
  ...value,
1154
- ...effect.value
1237
+ selection: effect.value
1155
1238
  };
1156
1239
  }
1157
- if (effect.is(setCommentRange)) {
1158
- const { comments: comments2 } = effect.value;
1159
- const ranges = comments2.map((comment) => {
1160
- const range = getRangeFromCursor(cursorConverter3, comment.cursor);
1240
+ if (effect.is(setComments)) {
1241
+ const comments2 = effect.value.map((comment) => {
1242
+ if (!comment.cursor) {
1243
+ return void 0;
1244
+ }
1245
+ const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);
1161
1246
  return range && {
1162
- ...comment,
1163
- ...range
1247
+ comment,
1248
+ range
1164
1249
  };
1165
1250
  }).filter(nonNullable);
1166
1251
  return {
1167
1252
  ...value,
1168
- ranges
1253
+ comments: comments2
1169
1254
  };
1170
1255
  }
1171
1256
  if (effect.is(setCommentState)) {
@@ -1175,78 +1260,109 @@ var commentsStateField = StateField2.define({
1175
1260
  return value;
1176
1261
  }
1177
1262
  });
1178
- var highlightDecorations = EditorView5.decorations.compute([
1179
- commentsStateField
1263
+ var highlightDecorations = EditorView6.decorations.compute([
1264
+ commentsState
1180
1265
  ], (state) => {
1181
- const { active, ranges } = state.field(commentsStateField);
1182
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1183
- if (selection.from === selection.to) {
1266
+ const { selection: { active }, comments: comments2 } = state.field(commentsState);
1267
+ const decorations = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
1268
+ const range = comment.range;
1269
+ if (!range || range.from === range.to) {
1270
+ console.warn("Invalid range:", range);
1184
1271
  return void 0;
1185
1272
  }
1186
- const range = {
1187
- from: selection.from,
1188
- to: selection.to
1189
- };
1190
- if (selection.id === active) {
1273
+ if (comment.comment.id === active) {
1191
1274
  return marks.highlightActive.range(range.from, range.to);
1192
1275
  } else {
1193
1276
  return marks.highlight.range(range.from, range.to);
1194
1277
  }
1195
- }).filter(nonNullable) ?? [];
1278
+ }).filter(nonNullable);
1196
1279
  return Decoration3.set(decorations);
1197
1280
  });
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();
1281
+ var trackPastedComments = (onUpdate) => {
1282
+ let tracked = null;
1283
+ const handleTrack = (event, view) => {
1284
+ const comments2 = view.state.field(commentsState);
1285
+ const { main } = view.state.selection;
1286
+ const selectedRanges = comments2.comments.filter(({ range }) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1287
+ if (!selectedRanges.length) {
1288
+ tracked = null;
1289
+ } else {
1290
+ tracked = {
1291
+ text: view.state.doc.slice(main.from, main.to),
1292
+ comments: selectedRanges.map(({ comment, range }) => ({
1293
+ id: comment.id,
1294
+ from: range.from - main.from,
1295
+ to: range.to - main.from
1296
+ }))
1297
+ };
1223
1298
  }
1224
- return span;
1225
- }
1299
+ };
1300
+ return [
1301
+ EditorView6.domEventHandlers({
1302
+ cut: handleTrack,
1303
+ copy: handleTrack
1304
+ }),
1305
+ // Handle paste.
1306
+ EditorView6.updateListener.of(({ view, state, transactions }) => {
1307
+ if (tracked) {
1308
+ const paste = transactions.find((tr) => tr.isUserEvent("input.paste"));
1309
+ if (paste) {
1310
+ let found = -1;
1311
+ paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1312
+ if (text.eq(tracked.text)) {
1313
+ for (let i = transactions.indexOf(paste) + 1; i < transactions.length; i++) {
1314
+ fromB = transactions[i].changes.mapPos(fromB);
1315
+ }
1316
+ found = fromB;
1317
+ }
1318
+ });
1319
+ if (found > -1) {
1320
+ for (const moved of tracked.comments) {
1321
+ const range = {
1322
+ from: found + moved.from,
1323
+ to: found + moved.to
1324
+ };
1325
+ const cursor = Cursor.getCursorFromRange(state, range);
1326
+ onUpdate(moved.id, cursor);
1327
+ }
1328
+ }
1329
+ tracked = null;
1330
+ }
1331
+ }
1332
+ })
1333
+ ];
1226
1334
  };
1227
1335
  var comments = (options = {}) => {
1336
+ const { key: shortcut = "meta-'" } = options;
1228
1337
  const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1229
1338
  const createCommentThread = (view) => {
1230
- const cursorConverter3 = view.state.facet(CursorConverter);
1231
- invariant2(options.onCreate, void 0, {
1232
- F: __dxlog_file3,
1233
- L: 224,
1339
+ invariant3(options.onCreate, void 0, {
1340
+ F: __dxlog_file4,
1341
+ L: 259,
1234
1342
  S: void 0,
1235
1343
  A: [
1236
1344
  "options.onCreate",
1237
1345
  ""
1238
1346
  ]
1239
1347
  });
1240
- const { head, from, to } = view.state.selection.main;
1348
+ const { from, to } = view.state.selection.main;
1241
1349
  if (from === to) {
1242
1350
  return false;
1243
1351
  }
1244
- const relPos = getCursorFromRange(cursorConverter3, {
1352
+ if (to === view.state.doc.length) {
1353
+ view.dispatch({
1354
+ changes: {
1355
+ from: to,
1356
+ insert: "\n"
1357
+ }
1358
+ });
1359
+ }
1360
+ const cursor = Cursor.getCursorFromRange(view.state, {
1245
1361
  from,
1246
1362
  to
1247
1363
  });
1248
- if (relPos) {
1249
- const id = callbackWrapper(options.onCreate)(relPos);
1364
+ if (cursor) {
1365
+ const id = options.onCreate?.(cursor, view.coordsAtPos(from));
1250
1366
  if (id) {
1251
1367
  view.dispatch({
1252
1368
  effects: setSelection.of({
@@ -1256,53 +1372,13 @@ var comments = (options = {}) => {
1256
1372
  anchor: from
1257
1373
  }
1258
1374
  });
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
1375
  return true;
1272
1376
  }
1273
1377
  }
1274
1378
  return false;
1275
1379
  };
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
1380
  return [
1304
- bookmarksViewPlugin,
1305
- commentsStateField,
1381
+ commentsState,
1306
1382
  highlightDecorations,
1307
1383
  styles3,
1308
1384
  //
@@ -1310,7 +1386,7 @@ var comments = (options = {}) => {
1310
1386
  //
1311
1387
  options.onCreate ? keymap3.of([
1312
1388
  {
1313
- key: options?.key ?? "meta-'",
1389
+ key: shortcut,
1314
1390
  run: callbackWrapper(createCommentThread)
1315
1391
  }
1316
1392
  ]) : [],
@@ -1327,7 +1403,7 @@ var comments = (options = {}) => {
1327
1403
  above: true,
1328
1404
  create: () => {
1329
1405
  const el = document.createElement("div");
1330
- options.onHover?.(el);
1406
+ options.onHover(el, shortcut);
1331
1407
  return {
1332
1408
  dom: el,
1333
1409
  offset: {
@@ -1340,156 +1416,154 @@ var comments = (options = {}) => {
1340
1416
  }
1341
1417
  return null;
1342
1418
  }, {
1343
- hideOnChange: true,
1419
+ // TODO(burdon): Hide on change triggered immediately?
1420
+ // hideOnChange: true,
1344
1421
  hoverTime: 1e3
1345
1422
  }) : [],
1346
1423
  //
1347
- // Monitor cursor movement and text updates.
1348
- // TODO(burdon): Is there a better (finer grained) way to do this?
1424
+ // Track deleted ranges and update ranges for decorations.
1349
1425
  //
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
- }
1369
- }
1370
- if (from <= range.to) {
1371
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1372
- Object.assign(range, newRange);
1373
- mod = true;
1426
+ EditorView6.updateListener.of(({ view, state, changes }) => {
1427
+ let mod = false;
1428
+ const { comments: comments2, ...value } = state.field(commentsState);
1429
+ changes.iterChanges((from, to, from2, to2) => {
1430
+ comments2.forEach(({ comment, range }) => {
1431
+ if (from2 === to2) {
1432
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1433
+ if (!newRange || newRange.to - newRange.from === 0) {
1434
+ options.onDelete?.(comment.id);
1374
1435
  }
1375
- });
1436
+ }
1437
+ if (from <= range.to) {
1438
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1439
+ Object.assign(range, newRange);
1440
+ mod = true;
1441
+ }
1442
+ });
1443
+ });
1444
+ if (mod) {
1445
+ view.dispatch({
1446
+ effects: setCommentState.of({
1447
+ comments: comments2,
1448
+ ...value
1449
+ })
1376
1450
  });
1377
- if (mod) {
1378
- view.dispatch({
1379
- effects: setCommentState.of({
1380
- active,
1381
- ranges
1382
- })
1383
- });
1384
- }
1385
1451
  }
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
- }
1452
+ }),
1453
+ //
1454
+ // Track selection/proximity.
1455
+ //
1456
+ EditorView6.updateListener.of(({ view, state, changes }) => {
1457
+ let min = Infinity;
1458
+ const { selection: { active, closest }, comments: comments2 } = state.field(commentsState);
1459
+ const { head } = state.selection.main;
1460
+ const selection = {};
1461
+ comments2.forEach(({ comment, range }) => {
1462
+ if (head >= range.from && head <= range.to) {
1463
+ selection.active = comment.id;
1464
+ selection.closest = void 0;
1465
+ }
1466
+ if (!selection.active) {
1467
+ const d = Math.min(Math.abs(head - range.from), Math.abs(head - range.to));
1399
1468
  if (d < min) {
1400
- selected.closest = comment.id;
1469
+ selection.closest = comment.id;
1401
1470
  min = d;
1402
1471
  }
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
1472
  }
1473
+ });
1474
+ if (selection.active !== active || selection.closest !== closest) {
1475
+ view.dispatch({
1476
+ effects: setSelection.of(selection)
1477
+ });
1478
+ handleSelect({
1479
+ selection,
1480
+ comments: comments2.map(({ comment, range }) => ({
1481
+ comment,
1482
+ range,
1483
+ location: view.coordsAtPos(range.from)
1484
+ }))
1485
+ });
1416
1486
  }
1417
- })
1487
+ }),
1488
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
1418
1489
  ];
1419
1490
  };
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;
1491
+ var useComments = (view, comments2) => {
1492
+ useEffect2(() => {
1493
+ if (view && comments2) {
1494
+ view.dispatch({
1495
+ effects: setComments.of(comments2)
1496
+ });
1497
+ }
1498
+ }, [
1499
+ view,
1500
+ comments2
1501
+ ]);
1436
1502
  };
1437
1503
 
1438
1504
  // 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({
1505
+ import { syntaxTree } from "@codemirror/language";
1506
+ import { RangeSetBuilder } from "@codemirror/state";
1507
+ import { Decoration as Decoration4, EditorView as EditorView7, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1508
+ var styles4 = EditorView7.baseTheme({
1441
1509
  "& .cm-hr": {
1442
- // TODO(burdon): ???
1443
1510
  // Note that block-level decorations should not have vertical margins,
1444
- borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1511
+ borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
1445
1512
  }
1446
1513
  });
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) {
1514
+ var HorizontalRuleWidget = class extends WidgetType2 {
1515
+ toDOM() {
1456
1516
  const el = document.createElement("div");
1457
1517
  el.className = "cm-hr";
1458
1518
  return el;
1459
1519
  }
1460
1520
  };
1461
- var placeholderMatcher = new MatchDecorator2({
1462
- regexp: /^---$/g,
1463
- decoration: (match, view, pos) => Decoration4.replace({
1464
- widget: new HorizontalRuleWidget(pos)
1465
- })
1521
+ var decoration = Decoration4.replace({
1522
+ widget: new HorizontalRuleWidget()
1466
1523
  });
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;
1524
+ var buildDecorations = (view) => {
1525
+ const builder = new RangeSetBuilder();
1526
+ const { state } = view;
1527
+ const cursor = state.selection.main.head;
1528
+ for (const { from, to } of view.visibleRanges) {
1529
+ syntaxTree(state).iterate({
1530
+ enter: (node) => {
1531
+ if (node.name === "HorizontalRule") {
1532
+ if (cursor <= node.from || cursor >= node.to) {
1533
+ builder.add(node.from, node.to, decoration);
1534
+ }
1535
+ }
1536
+ },
1537
+ from,
1538
+ to
1539
+ });
1540
+ }
1541
+ return builder.finish();
1542
+ };
1543
+ var hr = () => {
1544
+ return [
1545
+ styles4,
1546
+ ViewPlugin4.fromClass(class {
1547
+ constructor(view) {
1548
+ this.decorations = buildDecorations(view);
1549
+ }
1550
+ update(update2) {
1551
+ this.decorations = buildDecorations(update2.view);
1552
+ }
1553
+ }, {
1554
+ decorations: (v) => v.decorations
1481
1555
  })
1482
- })
1483
- ];
1556
+ ];
1557
+ };
1484
1558
 
1485
1559
  // packages/ui/react-ui-editor/src/extensions/image.ts
1486
- import { syntaxTree } from "@codemirror/language";
1560
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1487
1561
  import { StateField as StateField3 } from "@codemirror/state";
1488
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1562
+ import { Decoration as Decoration5, EditorView as EditorView8, WidgetType as WidgetType3 } from "@codemirror/view";
1489
1563
  var image = (options = {}) => {
1490
1564
  return StateField3.define({
1491
1565
  create: (state) => {
1492
- return Decoration5.set(buildDecorations(0, state.doc.length, state));
1566
+ return Decoration5.set(buildDecorations2(0, state.doc.length, state));
1493
1567
  },
1494
1568
  update: (value, tr) => {
1495
1569
  if (!tr.docChanged && !tr.selection) {
@@ -1509,16 +1583,16 @@ var image = (options = {}) => {
1509
1583
  filterFrom: from,
1510
1584
  filterTo: to,
1511
1585
  filter: () => false,
1512
- add: buildDecorations(from, to, tr.state)
1586
+ add: buildDecorations2(from, to, tr.state)
1513
1587
  });
1514
1588
  },
1515
- provide: (field) => EditorView7.decorations.from(field)
1589
+ provide: (field) => EditorView8.decorations.from(field)
1516
1590
  });
1517
1591
  };
1518
- var buildDecorations = (from, to, state) => {
1592
+ var buildDecorations2 = (from, to, state) => {
1519
1593
  const decorations = [];
1520
1594
  const cursor = state.selection.main.head;
1521
- syntaxTree(state).iterate({
1595
+ syntaxTree2(state).iterate({
1522
1596
  enter: (node) => {
1523
1597
  if (node.name === "Image") {
1524
1598
  const urlNode = node.node.getChild("URL");
@@ -1537,7 +1611,7 @@ var buildDecorations = (from, to, state) => {
1537
1611
  });
1538
1612
  return decorations;
1539
1613
  };
1540
- var ImageWidget = class extends WidgetType4 {
1614
+ var ImageWidget = class extends WidgetType3 {
1541
1615
  constructor(_url) {
1542
1616
  super();
1543
1617
  this._url = _url;
@@ -1555,19 +1629,19 @@ var ImageWidget = class extends WidgetType4 {
1555
1629
  };
1556
1630
 
1557
1631
  // 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";
1632
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1633
+ import { RangeSetBuilder as RangeSetBuilder2 } from "@codemirror/state";
1634
+ import { hoverTooltip as hoverTooltip2, Decoration as Decoration6, ViewPlugin as ViewPlugin5, WidgetType as WidgetType4 } from "@codemirror/view";
1561
1635
  import { tooltipContent } from "@dxos/react-ui-theme";
1562
1636
  var link = (options = {}) => {
1563
1637
  const extensions = [
1564
- ViewPlugin6.fromClass(class {
1638
+ ViewPlugin5.fromClass(class {
1565
1639
  constructor(view) {
1566
- this.decorations = buildDecorations2(view, options);
1640
+ this.decorations = buildDecorations3(view, options);
1567
1641
  }
1568
1642
  update(update2) {
1569
1643
  if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1570
- this.decorations = buildDecorations2(update2.view, options);
1644
+ this.decorations = buildDecorations3(update2.view, options);
1571
1645
  }
1572
1646
  }
1573
1647
  }, {
@@ -1580,7 +1654,7 @@ var link = (options = {}) => {
1580
1654
  // https://codemirror.net/docs/ref/#view.hoverTooltip
1581
1655
  // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1582
1656
  hoverTooltip2((view, pos, side) => {
1583
- const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1657
+ const syntax = syntaxTree3(view.state).resolveInner(pos, side);
1584
1658
  let link2 = null;
1585
1659
  for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1586
1660
  link2 = node.name === "Link" ? node : null;
@@ -1612,7 +1686,7 @@ var link = (options = {}) => {
1612
1686
  }
1613
1687
  return extensions;
1614
1688
  };
1615
- var LinkButton = class extends WidgetType5 {
1689
+ var LinkButton = class extends WidgetType4 {
1616
1690
  constructor(_url, _onAttach) {
1617
1691
  super();
1618
1692
  this._url = _url;
@@ -1627,7 +1701,7 @@ var LinkButton = class extends WidgetType5 {
1627
1701
  return el;
1628
1702
  }
1629
1703
  };
1630
- var LinkText = class extends WidgetType5 {
1704
+ var LinkText = class extends WidgetType4 {
1631
1705
  constructor(_text, _url) {
1632
1706
  super();
1633
1707
  this._text = _text;
@@ -1656,12 +1730,12 @@ var LinkText = class extends WidgetType5 {
1656
1730
  return link2;
1657
1731
  }
1658
1732
  };
1659
- var buildDecorations2 = (view, options) => {
1660
- const builder = new RangeSetBuilder();
1733
+ var buildDecorations3 = (view, options) => {
1734
+ const builder = new RangeSetBuilder2();
1661
1735
  const { state } = view;
1662
1736
  const cursor = state.selection.main.head;
1663
1737
  for (const { from, to } of view.visibleRanges) {
1664
- syntaxTree2(state).iterate({
1738
+ syntaxTree3(state).iterate({
1665
1739
  enter: (node) => {
1666
1740
  if (node.name === "Link") {
1667
1741
  const marks2 = node.node.getChildren("LinkMark");
@@ -1692,14 +1766,14 @@ var buildDecorations2 = (view, options) => {
1692
1766
  };
1693
1767
 
1694
1768
  // packages/ui/react-ui-editor/src/extensions/listener.ts
1695
- import { EditorView as EditorView8 } from "@codemirror/view";
1769
+ import { EditorView as EditorView9 } from "@codemirror/view";
1696
1770
  var listener = ({ onFocus, onChange }) => {
1697
1771
  const extensions = [];
1698
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1772
+ onFocus && extensions.push(EditorView9.focusChangeEffect.of((_, focused) => {
1699
1773
  onFocus(focused);
1700
1774
  return null;
1701
1775
  }));
1702
- onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1776
+ onChange && extensions.push(EditorView9.updateListener.of((update2) => {
1703
1777
  onChange(update2.state.doc.toString());
1704
1778
  }));
1705
1779
  return extensions;
@@ -1707,17 +1781,17 @@ var listener = ({ onFocus, onChange }) => {
1707
1781
 
1708
1782
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1709
1783
  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";
1784
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1785
+ import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1712
1786
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1713
1787
  import { languages } from "@codemirror/language-data";
1714
1788
  import { searchKeymap } from "@codemirror/search";
1715
1789
  import { EditorState } from "@codemirror/state";
1716
1790
  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";
1791
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView10, highlightActiveLine as highlightActiveLine2, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1718
1792
 
1719
1793
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1720
- import { markdownLanguage } from "@codemirror/lang-markdown";
1794
+ import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
1721
1795
  import { HighlightStyle } from "@codemirror/language";
1722
1796
  import { tags, styleTags, Tag } from "@lezer/highlight";
1723
1797
  import { Table } from "@lezer/markdown";
@@ -1727,7 +1801,6 @@ var markdownTags = {
1727
1801
  CodeText: Tag.define(),
1728
1802
  EmphasisMark: Tag.define(),
1729
1803
  HeaderMark: Tag.define(),
1730
- // HorizontalRule: Tag.define(),
1731
1804
  InlineCode: Tag.define(),
1732
1805
  LinkLabel: Tag.define(),
1733
1806
  LinkReference: Tag.define(),
@@ -1891,12 +1964,6 @@ var markdownHighlightStyle = (readonly) => {
1891
1964
  ],
1892
1965
  class: blockquote
1893
1966
  },
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
1967
  {
1901
1968
  tag: [
1902
1969
  markdownTags.TableCell
@@ -1904,7 +1971,7 @@ var markdownHighlightStyle = (readonly) => {
1904
1971
  class: "font-mono"
1905
1972
  }
1906
1973
  ], {
1907
- scope: markdownLanguage,
1974
+ scope: markdownLanguage2,
1908
1975
  all: {
1909
1976
  fontFamily: getToken("fontFamily.body", []).join(",")
1910
1977
  }
@@ -1916,17 +1983,17 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1916
1983
  return [
1917
1984
  EditorState.allowMultipleSelections.of(true),
1918
1985
  EditorState.tabSize.of(2),
1919
- EditorView9.lineWrapping,
1920
- customKeymap(),
1986
+ EditorView10.lineWrapping,
1987
+ // https://github.com/codemirror/basic-setup
1921
1988
  bracketMatching2(),
1922
1989
  closeBrackets2(),
1923
1990
  crosshairCursor(),
1924
1991
  dropCursor(),
1925
1992
  drawSelection2(),
1926
- highlightActiveLine(),
1927
- history(),
1993
+ highlightActiveLine2(),
1994
+ history2(),
1928
1995
  indentOnInput(),
1929
- _placeholder && placeholder2(_placeholder),
1996
+ _placeholder ? placeholder2(_placeholder) : [],
1930
1997
  // Main extension.
1931
1998
  // https://github.com/codemirror/lang-markdown
1932
1999
  // https://codemirror.net/5/mode/markdown/index.html (demo).
@@ -1936,7 +2003,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1936
2003
  // NOTE: This extends the parser; it doesn't affect rendering.
1937
2004
  // https://github.github.com/gfm
1938
2005
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1939
- base: markdownLanguage2,
2006
+ base: markdownLanguage3,
1940
2007
  // Languages for syntax highlighting fenced code blocks.
1941
2008
  codeLanguages: languages,
1942
2009
  // Parser extensions.
@@ -1948,34 +2015,40 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1948
2015
  // https://github.com/codemirror/theme-one-dark
1949
2016
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1950
2017
  // 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
- ]);
2018
+ syntaxHighlighting2(markdownHighlightStyle(readonly)),
2019
+ keymap4.of([
2020
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
2021
+ indentWithTab,
2022
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
2023
+ ...closeBracketsKeymap,
2024
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
2025
+ ...historyKeymap,
2026
+ // https://codemirror.net/docs/ref/#search.searchKeymap
2027
+ ...searchKeymap,
2028
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
2029
+ ...standardKeymap
2030
+ ])
2031
+ ];
2032
+ };
1966
2033
 
1967
2034
  // packages/ui/react-ui-editor/src/extensions/mention.ts
1968
2035
  import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1969
2036
  var mention = ({ onSearch }) => {
1970
2037
  return autocompletion2({
2038
+ // TODO(burdon): Not working.
2039
+ activateOnTyping: true,
2040
+ // activateOnTypingDelay: 100,
2041
+ // selectOnOpen: true,
1971
2042
  closeOnBlur: false,
2043
+ // defaultKeymap: false,
2044
+ icons: false,
1972
2045
  override: [
1973
2046
  (context) => {
2047
+ console.log(context);
1974
2048
  const match = context.matchBefore(/@(\w+)?/);
1975
2049
  if (!match || match.from === match.to && !context.explicit) {
1976
2050
  return null;
1977
2051
  }
1978
- console.log(match);
1979
2052
  return {
1980
2053
  from: match.from,
1981
2054
  options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
@@ -1987,19 +2060,24 @@ var mention = ({ onSearch }) => {
1987
2060
  });
1988
2061
  };
1989
2062
 
2063
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
2064
+ var outliner = (options = {}) => {
2065
+ return [];
2066
+ };
2067
+
1990
2068
  // 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";
2069
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2070
+ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField4 } from "@codemirror/state";
2071
+ import { Decoration as Decoration7, EditorView as EditorView11, WidgetType as WidgetType5 } from "@codemirror/view";
1994
2072
  var table = (options = {}) => {
1995
2073
  return StateField4.define({
1996
2074
  create: (state) => update(state, options),
1997
2075
  update: (_, tr) => update(tr.state, options),
1998
- provide: (field) => EditorView10.decorations.from(field)
2076
+ provide: (field) => EditorView11.decorations.from(field)
1999
2077
  });
2000
2078
  };
2001
2079
  var update = (state, options) => {
2002
- const builder = new RangeSetBuilder2();
2080
+ const builder = new RangeSetBuilder3();
2003
2081
  const cursor = state.selection.main.head;
2004
2082
  const tables = [];
2005
2083
  const getTable = () => tables[tables.length - 1];
@@ -2007,7 +2085,7 @@ var update = (state, options) => {
2007
2085
  const table2 = getTable();
2008
2086
  return table2.rows?.[table2.rows.length - 1];
2009
2087
  };
2010
- syntaxTree3(state).iterate({
2088
+ syntaxTree4(state).iterate({
2011
2089
  enter: (node) => {
2012
2090
  switch (node.name) {
2013
2091
  case "Table": {
@@ -2040,7 +2118,6 @@ var update = (state, options) => {
2040
2118
  tables.forEach((table2) => {
2041
2119
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
2042
2120
  hide && builder.add(table2.from, table2.to, Decoration7.replace({
2043
- block: true,
2044
2121
  widget: new TableWidget(table2)
2045
2122
  }));
2046
2123
  builder.add(table2.from, table2.to, Decoration7.mark({
@@ -2049,7 +2126,7 @@ var update = (state, options) => {
2049
2126
  });
2050
2127
  return builder.finish();
2051
2128
  };
2052
- var TableWidget = class extends WidgetType6 {
2129
+ var TableWidget = class extends WidgetType5 {
2053
2130
  constructor(_table) {
2054
2131
  super();
2055
2132
  this._table = _table;
@@ -2081,88 +2158,104 @@ var TableWidget = class extends WidgetType6 {
2081
2158
  }
2082
2159
  return table2;
2083
2160
  }
2161
+ ignoreEvent(e) {
2162
+ return !/^mouse/.test(e.type);
2163
+ }
2084
2164
  };
2085
2165
 
2086
2166
  // 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"
2167
+ import { syntaxTree as syntaxTree5 } from "@codemirror/language";
2168
+ import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
2169
+ import { EditorView as EditorView12, Decoration as Decoration8, WidgetType as WidgetType6, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2170
+ var styles5 = EditorView12.baseTheme({
2171
+ "& .cm-task": {
2172
+ color: getToken("extend.colors.blue.500")
2173
+ },
2174
+ "& .cm-task-checkbox": {
2175
+ marginLeft: "4px",
2176
+ marginRight: "4px"
2092
2177
  }
2093
2178
  });
2094
- var CheckboxWidget = class extends WidgetType7 {
2095
- constructor(_checked, _indent, _onCheck) {
2179
+ var CheckboxWidget = class extends WidgetType6 {
2180
+ constructor(_checked) {
2096
2181
  super();
2097
2182
  this._checked = _checked;
2098
- this._indent = _indent;
2099
- this._onCheck = _onCheck;
2100
2183
  }
2101
2184
  eq(other) {
2102
- return this._checked === other._checked && this._indent === other._indent;
2185
+ return this._checked === other._checked;
2103
2186
  }
2104
2187
  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"));
2188
+ const input = document.createElement("input");
2189
+ input.className = "cm-task-checkbox";
2110
2190
  input.type = "checkbox";
2111
2191
  input.checked = this._checked;
2112
- if (!this._onCheck) {
2192
+ if (view.state.readOnly) {
2113
2193
  input.setAttribute("disabled", "true");
2114
- }
2115
- if (this._onCheck) {
2116
- input.onchange = (event) => {
2117
- this._onCheck?.(event.target.checked);
2118
- return true;
2194
+ } else {
2195
+ input.onmousedown = (event) => {
2196
+ const pos = view.posAtDOM(input);
2197
+ const text = view.state.sliceDoc(pos, pos + 3);
2198
+ if (text === (this._checked ? "[x]" : "[ ]")) {
2199
+ view.dispatch({
2200
+ changes: {
2201
+ from: pos + 1,
2202
+ to: pos + 2,
2203
+ insert: this._checked ? " " : "x"
2204
+ }
2205
+ });
2206
+ event.preventDefault();
2207
+ }
2119
2208
  };
2120
2209
  }
2121
- return wrap;
2210
+ return input;
2122
2211
  }
2123
2212
  ignoreEvent() {
2124
2213
  return false;
2125
2214
  }
2126
2215
  };
2216
+ var checkedDecoration = Decoration8.replace({
2217
+ widget: new CheckboxWidget(true)
2218
+ });
2219
+ var uncheckedDecoration = Decoration8.replace({
2220
+ widget: new CheckboxWidget(false)
2221
+ });
2222
+ var buildDecorations4 = (view) => {
2223
+ const builder = new RangeSetBuilder4();
2224
+ const { state } = view;
2225
+ const cursor = state.selection.main.head;
2226
+ for (const { from, to } of view.visibleRanges) {
2227
+ syntaxTree5(state).iterate({
2228
+ enter: (node) => {
2229
+ if (node.name === "TaskMarker") {
2230
+ if (cursor < node.from || cursor > node.to) {
2231
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2232
+ builder.add(node.from - 2, node.from - 1, Decoration8.mark({
2233
+ class: "cm-task"
2234
+ }));
2235
+ builder.add(node.from, node.to, checked ? checkedDecoration : uncheckedDecoration);
2236
+ }
2237
+ }
2238
+ },
2239
+ from,
2240
+ to
2241
+ });
2242
+ }
2243
+ return builder.finish();
2244
+ };
2127
2245
  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
2246
  return [
2165
- tasks,
2247
+ ViewPlugin6.fromClass(class {
2248
+ constructor(view) {
2249
+ this.decorations = buildDecorations4(view);
2250
+ }
2251
+ update(update2) {
2252
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2253
+ this.decorations = buildDecorations4(update2.view);
2254
+ }
2255
+ }
2256
+ }, {
2257
+ decorations: (v) => v.decorations
2258
+ }),
2166
2259
  styles5
2167
2260
  ];
2168
2261
  };
@@ -2241,7 +2334,7 @@ var cursorConverter2 = (text) => ({
2241
2334
 
2242
2335
  // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2243
2336
  var yjs = (content, awareness2) => [
2244
- CursorConverter.of(cursorConverter2(content)),
2337
+ Cursor.converter.of(cursorConverter2(content)),
2245
2338
  yCollab(content, awareness2)
2246
2339
  ];
2247
2340
 
@@ -2338,33 +2431,6 @@ var defaultTheme = {
2338
2431
  backgroundColor: get3(tokens, "extend.colors.yellow.700")
2339
2432
  },
2340
2433
  //
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
2434
  // link
2369
2435
  //
2370
2436
  ".cm-link": {
@@ -2401,13 +2467,6 @@ var defaultTheme = {
2401
2467
  textDecoration: "none"
2402
2468
  },
2403
2469
  //
2404
- // widgets
2405
- //
2406
- ".cm-widgetBuffer": {
2407
- display: "none",
2408
- height: 0
2409
- },
2410
- //
2411
2470
  // table
2412
2471
  //
2413
2472
  ".cm-table *": {
@@ -2438,27 +2497,17 @@ var defaultTheme = {
2438
2497
  },
2439
2498
  //
2440
2499
  // 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.
2500
+ // TODO(thure): This appears to be the best or only way to set selection caret heights,
2501
+ // but it's far more verbose than it needs to be.
2442
2502
  //
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
- }, {}),
2503
+ // ...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
2504
+ // const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
2505
+ //
2506
+ // acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
2507
+ // acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
2508
+ // acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
2509
+ // return acc;
2510
+ // }, {}),
2462
2511
  /**
2463
2512
  * Panels
2464
2513
  * TODO(burdon): Needs styling attention (esp. dark mode).
@@ -2509,7 +2558,9 @@ var textTheme = {
2509
2558
  }
2510
2559
  };
2511
2560
  var markdownTheme = {
2512
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2561
+ ".cm-scroller": {
2562
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2563
+ },
2513
2564
  ".cm-placeholder": {
2514
2565
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2515
2566
  }
@@ -2524,95 +2575,90 @@ var codeTheme = {
2524
2575
  };
2525
2576
 
2526
2577
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2578
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
2527
2579
  var EditorModes = [
2528
2580
  "default",
2529
2581
  "vim"
2530
2582
  ];
2531
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
2532
- const { themeMode } = useThemeContext();
2583
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autofocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2533
2584
  const tabsterDOMAttribute = useFocusableGroup({
2534
2585
  tabBehavior: "limited"
2535
2586
  });
2536
- const [root, setRoot] = useState(null);
2537
- const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
2538
- useImperativeHandle(forwardedRef, () => ({
2539
- root,
2540
- state,
2587
+ const { themeMode } = useThemeContext2();
2588
+ const rootRef = useRef(null);
2589
+ const [view, setView] = useState(null);
2590
+ useImperativeHandle(forwardedRef, () => view, [
2541
2591
  view
2542
- }), [
2543
- view,
2544
- state,
2545
- root
2546
2592
  ]);
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
- });
2593
+ useEffect3(() => {
2594
+ if (autofocus) {
2595
+ view?.focus();
2576
2596
  }
2577
2597
  }, [
2578
2598
  view,
2579
- comments2
2599
+ autofocus
2580
2600
  ]);
2581
- useEffect(() => {
2582
- if (!root) {
2601
+ useAwareness(model);
2602
+ useEffect3(() => {
2603
+ if (!model || !rootRef.current) {
2583
2604
  return;
2584
2605
  }
2585
- const state2 = EditorState2.create({
2606
+ const state = EditorState2.create({
2586
2607
  doc: model.text(),
2608
+ selection,
2587
2609
  extensions: [
2588
- readonly && EditorState2.readOnly.of(readonly),
2589
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2590
- editorMode === "vim" && vim(),
2610
+ // TODO(burdon): Doesn't catch errors in keymap functions.
2611
+ EditorView13.exceptionSink.of((err) => {
2612
+ log2.catch(err, void 0, {
2613
+ F: __dxlog_file5,
2614
+ L: 126,
2615
+ S: void 0,
2616
+ C: (f, a) => f(...a)
2617
+ });
2618
+ }),
2591
2619
  // Theme.
2592
- // TODO(burdon): Make theme configurable.
2593
- EditorView12.baseTheme(defaultTheme),
2594
- EditorView12.theme(slots?.editor?.theme ?? {}),
2595
- EditorView12.darkTheme.of(themeMode === "dark"),
2620
+ // TODO(burdon): Make configurable.
2621
+ EditorView13.baseTheme(defaultTheme),
2622
+ EditorView13.theme(theme ?? {}),
2623
+ EditorView13.darkTheme.of(themeMode === "dark"),
2624
+ EditorView13.editorAttributes.of({
2625
+ class: slots.editor?.className ?? ""
2626
+ }),
2627
+ EditorView13.contentAttributes.of({
2628
+ class: slots.content?.className ?? ""
2629
+ }),
2630
+ // State.
2631
+ EditorState2.readOnly.of(!!readonly),
2596
2632
  // Storage and replication.
2633
+ // NOTE: This must come before user extensions.
2597
2634
  model.extension,
2635
+ // TODO(burdon): Factor out (requires special handling for Escape/focus).
2636
+ editorMode === "vim" && vim(),
2598
2637
  // Custom.
2599
2638
  ...extensions
2600
2639
  ].filter(Boolean)
2601
2640
  });
2602
- view?.destroy();
2603
- setEditor({
2604
- state: state2,
2605
- view: new EditorView12({
2606
- state: state2,
2607
- parent: root
2608
- })
2641
+ const newView = new EditorView13({
2642
+ state,
2643
+ parent: rootRef.current,
2644
+ scrollTo,
2645
+ // NOTE: Uncomment to debug/monitor all transactions.
2646
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
2647
+ dispatchTransactions: (trs, view2) => {
2648
+ if (debug) {
2649
+ logChanges(trs);
2650
+ }
2651
+ view2.update(trs);
2652
+ }
2609
2653
  });
2654
+ view?.destroy();
2655
+ setView(newView);
2610
2656
  return () => {
2611
- view?.destroy();
2612
- setEditor(void 0);
2657
+ newView?.destroy();
2658
+ setView(null);
2613
2659
  };
2614
2660
  }, [
2615
- root,
2661
+ rootRef,
2616
2662
  model,
2617
2663
  readonly,
2618
2664
  editorMode,
@@ -2622,11 +2668,13 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2622
2668
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2623
2669
  switch (key) {
2624
2670
  case "Enter": {
2625
- view?.contentDOM.focus();
2671
+ view?.focus();
2626
2672
  break;
2627
2673
  }
2628
2674
  case "Escape": {
2629
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2675
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
2676
+ rootRef.current?.focus();
2677
+ }
2630
2678
  break;
2631
2679
  }
2632
2680
  }
@@ -2636,65 +2684,66 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2636
2684
  ]);
2637
2685
  return /* @__PURE__ */ React.createElement("div", {
2638
2686
  key: model.id,
2639
- ref: setRoot,
2687
+ role: "none",
2640
2688
  tabIndex: 0,
2641
- ...slots?.root,
2689
+ onKeyUp: handleKeyUp,
2690
+ ...slots.root,
2642
2691
  ...editorMode !== "vim" && tabsterDOMAttribute,
2643
- onKeyUp: handleKeyUp
2692
+ ref: rootRef
2644
2693
  });
2645
2694
  });
2646
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2647
- const { themeMode } = useThemeContext();
2648
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2695
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, multiline, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
2696
+ const { themeMode } = useThemeContext2();
2697
+ const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2649
2698
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2650
2699
  ref: forwardedRef,
2651
2700
  readonly,
2652
2701
  extensions: [
2653
2702
  basicBundle({
2654
- readonly,
2655
2703
  themeMode,
2656
- placeholder: slots?.editor?.placeholder
2704
+ placeholder: placeholder3,
2705
+ multiline
2657
2706
  }),
2658
2707
  ...extensions
2659
2708
  ],
2660
- slots,
2709
+ theme,
2710
+ slots: updatedSlots,
2661
2711
  ...props
2662
2712
  });
2663
2713
  });
2664
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2665
- const { themeMode } = useThemeContext();
2666
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
2714
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
2715
+ const { themeMode } = useThemeContext2();
2716
+ const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2667
2717
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2668
2718
  ref: forwardedRef,
2669
2719
  readonly,
2670
2720
  extensions: [
2671
2721
  markdownBundle({
2672
- readonly,
2673
2722
  themeMode,
2674
- placeholder: slots?.editor?.placeholder
2723
+ readonly,
2724
+ placeholder: placeholder3
2675
2725
  }),
2676
2726
  ...extensions
2677
2727
  ],
2678
- slots,
2728
+ theme,
2729
+ slots: updatedSlots,
2679
2730
  ...props
2680
2731
  });
2681
2732
  });
2682
2733
  var defaultSlots = {
2683
2734
  root: {
2684
- className: mx3("p-2", inputSurface)
2735
+ // TODO(burdon): Add focusRing by default/as property?
2736
+ className: mx3("flex flex-col grow overflow-y-auto", inputSurface)
2737
+ },
2738
+ editor: {
2739
+ className: "h-full p-2"
2685
2740
  }
2686
2741
  };
2687
2742
  var defaultTextSlots = {
2688
- ...defaultSlots,
2689
- editor: {
2690
- theme: textTheme
2691
- }
2743
+ ...defaultSlots
2692
2744
  };
2693
2745
  var defaultMarkdownSlots = {
2694
- ...defaultSlots,
2695
- editor: {
2696
- theme: markdownTheme
2697
- }
2746
+ ...defaultSlots
2698
2747
  };
2699
2748
 
2700
2749
  // packages/ui/react-ui-editor/src/hooks/automerge.ts
@@ -2705,9 +2754,9 @@ import { isNotNullOrUndefined } from "@dxos/util";
2705
2754
  // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2706
2755
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2707
2756
  import { Context as Context2 } from "@dxos/context";
2708
- import { invariant as invariant3 } from "@dxos/invariant";
2757
+ import { invariant as invariant4 } from "@dxos/invariant";
2709
2758
  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";
2759
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2711
2760
  var DEBOUNCE_INTERVAL = 100;
2712
2761
  var SpaceAwarenessProvider = class {
2713
2762
  constructor(params) {
@@ -2748,7 +2797,7 @@ var SpaceAwarenessProvider = class {
2748
2797
  log3.debug("failed to query awareness", {
2749
2798
  err
2750
2799
  }, {
2751
- F: __dxlog_file4,
2800
+ F: __dxlog_file6,
2752
2801
  L: 85,
2753
2802
  S: this,
2754
2803
  C: (f, a) => f(...a)
@@ -2761,8 +2810,8 @@ var SpaceAwarenessProvider = class {
2761
2810
  this._postTask = void 0;
2762
2811
  }
2763
2812
  update(position) {
2764
- invariant3(this._postTask, void 0, {
2765
- F: __dxlog_file4,
2813
+ invariant4(this._postTask, void 0, {
2814
+ F: __dxlog_file6,
2766
2815
  L: 96,
2767
2816
  S: this,
2768
2817
  A: [
@@ -2781,8 +2830,8 @@ var SpaceAwarenessProvider = class {
2781
2830
  return Array.from(this._remoteStates.values());
2782
2831
  }
2783
2832
  _handleQueryMessage() {
2784
- invariant3(this._postTask, void 0, {
2785
- F: __dxlog_file4,
2833
+ invariant4(this._postTask, void 0, {
2834
+ F: __dxlog_file6,
2786
2835
  L: 111,
2787
2836
  S: this,
2788
2837
  A: [
@@ -2793,8 +2842,8 @@ var SpaceAwarenessProvider = class {
2793
2842
  this._postTask.schedule();
2794
2843
  }
2795
2844
  _handlePostMessage(message) {
2796
- invariant3(message.kind === "post", void 0, {
2797
- F: __dxlog_file4,
2845
+ invariant4(message.kind === "post", void 0, {
2846
+ F: __dxlog_file6,
2798
2847
  L: 116,
2799
2848
  S: this,
2800
2849
  A: [
@@ -2807,25 +2856,63 @@ var SpaceAwarenessProvider = class {
2807
2856
  }
2808
2857
  };
2809
2858
 
2810
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2859
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
2811
2860
  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";
2861
+ var modelState = StateField5.define({
2862
+ create: () => void 0,
2863
+ update: (model) => model
2864
+ });
2865
+
2866
+ // packages/ui/react-ui-editor/src/hooks/automerge.ts
2867
+ var createAutomergeModel = ({ space, identity, text }) => {
2868
+ const obj = text;
2869
+ const doc = getRawDoc(obj, [
2870
+ obj.field
2871
+ ]);
2872
+ const awarenessProvider = space && new SpaceAwarenessProvider({
2873
+ space,
2874
+ channel: `automerge.awareness.${obj.id}`,
2875
+ info: {
2876
+ displayName: identity?.profile?.displayName ?? "",
2877
+ color: cursorColor.color,
2878
+ lightColor: cursorColor.light
2879
+ },
2880
+ peerId: identity?.identityKey.toHex() ?? "Anonymous"
2881
+ });
2882
+ const model = {
2883
+ id: obj.id,
2884
+ content: doc,
2885
+ text: () => get4(doc.handle.docSync(), doc.path),
2886
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2887
+ extension: [
2888
+ modelState.init(() => model),
2889
+ automerge3({
2890
+ handle: doc.handle,
2891
+ path: doc.path
2892
+ }),
2893
+ awarenessProvider && AwarenessProvider.of(awarenessProvider),
2894
+ awareness()
2895
+ ].filter(isNotNullOrUndefined),
2896
+ peer: identity ? {
2897
+ id: identity.identityKey.toHex(),
2898
+ name: identity.profile?.displayName
2899
+ } : void 0
2900
+ };
2901
+ return model;
2902
+ };
2814
2903
 
2815
2904
  // packages/ui/react-ui-editor/src/hooks/yjs.ts
2816
2905
  import * as decoding from "lib0/decoding";
2817
2906
  import * as encoding from "lib0/encoding";
2818
2907
  import { Observable } from "lib0/observable";
2819
2908
  import * as YP from "y-protocols/awareness";
2820
- import * as Y2 from "yjs";
2821
- import { invariant as invariant4 } from "@dxos/invariant";
2909
+ import { invariant as invariant5 } from "@dxos/invariant";
2822
2910
  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";
2911
+ var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2825
2912
  var createYjsModel = ({ identity, space, text }) => {
2826
- invariant4(text?.doc && text?.content, void 0, {
2827
- F: __dxlog_file5,
2828
- L: 23,
2913
+ invariant5(text?.doc && text?.content, void 0, {
2914
+ F: __dxlog_file7,
2915
+ L: 22,
2829
2916
  S: void 0,
2830
2917
  A: [
2831
2918
  "text?.doc && text?.content",
@@ -2841,34 +2928,6 @@ var createYjsModel = ({ identity, space, text }) => {
2841
2928
  id: text.doc.guid,
2842
2929
  content: text.content,
2843
2930
  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
2931
  extension: [
2873
2932
  modelState.init(() => model),
2874
2933
  yjs(text.content, provider?.awareness)
@@ -2917,8 +2976,8 @@ var YAwarenessProvider = class extends Observable {
2917
2976
  removed,
2918
2977
  origin
2919
2978
  }, {
2920
- F: __dxlog_file5,
2921
- L: 122,
2979
+ F: __dxlog_file7,
2980
+ L: 101,
2922
2981
  S: this,
2923
2982
  C: (f, a) => f(...a)
2924
2983
  });
@@ -2930,8 +2989,8 @@ var YAwarenessProvider = class extends Observable {
2930
2989
  }
2931
2990
  _handleSpaceMessage({ payload }) {
2932
2991
  log4("space message", payload, {
2933
- F: __dxlog_file5,
2934
- L: 131,
2992
+ F: __dxlog_file7,
2993
+ L: 110,
2935
2994
  S: this,
2936
2995
  C: (f, a) => f(...a)
2937
2996
  });
@@ -2975,14 +3034,12 @@ var YAwarenessProvider = class extends Observable {
2975
3034
  };
2976
3035
 
2977
3036
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2978
- var modelState = StateField5.define({
2979
- create: () => void 0,
2980
- update: (model) => model
2981
- });
3037
+ import { useEffect as useEffect4, useState as useState2 } from "react";
3038
+ import { isAutomergeObject } from "@dxos/react-client/echo";
2982
3039
  var useTextModel = (props) => {
2983
3040
  const { identity, space, text } = props;
2984
- const [model, setModel] = useState2(() => createModel(props));
2985
- useEffect2(() => setModel(createModel(props)), [
3041
+ const [model, setModel] = useState2();
3042
+ useEffect4(() => setModel(createModel(props)), [
2986
3043
  identity,
2987
3044
  space,
2988
3045
  text
@@ -2999,47 +3056,10 @@ var createModel = (props) => {
2999
3056
  return void 0;
3000
3057
  }
3001
3058
  };
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
3059
  export {
3041
3060
  AwarenessProvider,
3042
3061
  BaseTextEditor,
3062
+ Cursor,
3043
3063
  Doc,
3044
3064
  EditorModes,
3045
3065
  MarkdownEditor,
@@ -3055,6 +3075,7 @@ export {
3055
3075
  awareness,
3056
3076
  basicBundle,
3057
3077
  blast,
3078
+ callbackWrapper,
3058
3079
  code2 as code,
3059
3080
  codeTheme,
3060
3081
  comments,
@@ -3070,6 +3091,7 @@ export {
3070
3091
  image,
3071
3092
  link,
3072
3093
  listener,
3094
+ logChanges,
3073
3095
  markdownBundle,
3074
3096
  markdownHighlightStyle,
3075
3097
  markdownTags,
@@ -3077,7 +3099,8 @@ export {
3077
3099
  markdownTheme,
3078
3100
  mention,
3079
3101
  modelState,
3080
- setCommentRange,
3102
+ outliner,
3103
+ setComments,
3081
3104
  setFocus,
3082
3105
  setSelection,
3083
3106
  table,
@@ -3085,6 +3108,8 @@ export {
3085
3108
  tasklist,
3086
3109
  textTheme,
3087
3110
  typewriter,
3111
+ useAwareness,
3112
+ useComments,
3088
3113
  useTextModel,
3089
3114
  yjs
3090
3115
  };