@dxos/react-ui-editor 0.3.11-main.9c729f3 → 0.3.11-main.9de2c34

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