@dxos/react-ui-editor 0.3.11-main.008b7c6 → 0.3.11-main.08776d7

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