@dxos/react-ui-editor 0.3.11-main.b929e1d → 0.3.11-main.bc6b68b

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