@dxos/react-ui-editor 0.3.11-main.870164f → 0.3.11-main.8d54a30

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 (108) hide show
  1. package/dist/lib/browser/index.mjs +1405 -960
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -1
  5. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
  6. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
  7. package/dist/types/src/{hooks/yjs → components/TextEditor}/yjs.stories.d.ts +1 -1
  8. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
  9. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  10. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  11. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  12. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  13. package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
  14. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  15. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  16. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  17. package/dist/types/src/{hooks → extensions}/automerge/semaphore.d.ts +4 -4
  18. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  19. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  20. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  21. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  22. package/dist/types/src/extensions/awareness.d.ts +46 -0
  23. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  24. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  25. package/dist/types/src/extensions/index.d.ts +3 -0
  26. package/dist/types/src/extensions/index.d.ts.map +1 -1
  27. package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
  28. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
  29. package/dist/types/src/extensions/yjs/index.d.ts +2 -0
  30. package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
  31. package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
  32. package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
  33. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
  34. package/dist/types/src/hooks/automerge.d.ts +3 -0
  35. package/dist/types/src/hooks/automerge.d.ts.map +1 -0
  36. package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
  37. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  38. package/dist/types/src/hooks/index.d.ts +2 -0
  39. package/dist/types/src/hooks/index.d.ts.map +1 -1
  40. package/dist/types/src/hooks/useTextModel.d.ts +8 -7
  41. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  42. package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
  43. package/dist/types/src/hooks/yjs.d.ts.map +1 -0
  44. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  45. package/dist/types/src/styles/index.d.ts +1 -0
  46. package/dist/types/src/styles/index.d.ts.map +1 -1
  47. package/dist/types/src/testing/replicator.d.ts +2 -2
  48. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  49. package/dist/types/src/util/cursor.d.ts +17 -0
  50. package/dist/types/src/util/cursor.d.ts.map +1 -0
  51. package/dist/types/src/util/index.d.ts +3 -0
  52. package/dist/types/src/util/index.d.ts.map +1 -0
  53. package/dist/types/src/util/util.d.ts.map +1 -0
  54. package/package.json +22 -21
  55. package/src/components/TextEditor/TextEditor.tsx +1 -1
  56. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +66 -6
  57. package/src/components/TextEditor/codemirror.stories.ts +3 -2
  58. package/src/{hooks/yjs → components/TextEditor}/yjs.stories.tsx +1 -1
  59. package/src/{hooks/automerge/plugin.ts → extensions/automerge/automerge.ts} +20 -58
  60. package/src/extensions/automerge/cursor.ts +51 -0
  61. package/src/extensions/automerge/defs.ts +62 -0
  62. package/src/extensions/automerge/index.ts +6 -0
  63. package/src/{hooks → extensions}/automerge/semaphore.ts +14 -12
  64. package/src/{hooks → extensions}/automerge/update-automerge.ts +5 -6
  65. package/src/{hooks → extensions}/automerge/update-codemirror.ts +13 -5
  66. package/src/extensions/awareness.ts +316 -0
  67. package/src/extensions/comments.ts +27 -11
  68. package/src/extensions/index.ts +3 -0
  69. package/src/extensions/yjs/cursor.ts +21 -0
  70. package/src/extensions/yjs/index.ts +5 -0
  71. package/src/{hooks → extensions}/yjs/yjs.test.ts +1 -25
  72. package/src/extensions/yjs/yjs.ts +16 -0
  73. package/src/hooks/automerge.ts +52 -0
  74. package/src/hooks/awareness-provider.ts +120 -0
  75. package/src/hooks/index.ts +2 -0
  76. package/src/hooks/useTextModel.ts +19 -93
  77. package/src/hooks/{yjs/space-provider.ts → yjs.ts} +71 -19
  78. package/src/styles/index.ts +1 -0
  79. package/src/testing/replicator.ts +10 -15
  80. package/src/util/cursor.ts +29 -0
  81. package/src/util/index.ts +6 -0
  82. package/dist/types/src/extensions/util.d.ts.map +0 -1
  83. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  84. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  85. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  86. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  87. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  88. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  89. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  90. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
  91. package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
  92. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
  93. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
  94. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  95. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  96. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  97. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  98. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  99. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
  100. package/src/hooks/automerge/handle.ts +0 -14
  101. package/src/hooks/automerge/index.ts +0 -6
  102. package/src/hooks/yjs/index.ts +0 -6
  103. /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
  104. /package/dist/types/src/{hooks → extensions}/yjs/yjs.test.d.ts +0 -0
  105. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  106. /package/dist/types/src/{extensions → util}/util.d.ts +0 -0
  107. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  108. /package/src/{extensions → util}/util.ts +0 -0
@@ -7,11 +7,11 @@ import { tags as tags2 } from "@lezer/highlight";
7
7
 
8
8
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
9
9
  import { EditorState as EditorState2 } from "@codemirror/state";
10
- import { EditorView as EditorView12 } from "@codemirror/view";
10
+ import { EditorView as EditorView13 } from "@codemirror/view";
11
11
  import { useFocusableGroup } from "@fluentui/react-tabster";
12
12
  import { vim } from "@replit/codemirror-vim";
13
13
  import defaultsDeep2 from "lodash.defaultsdeep";
14
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState as useState2 } from "react";
14
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
15
15
  import { generateName } from "@dxos/display-name";
16
16
  import { useThemeContext } from "@dxos/react-ui";
17
17
  import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
@@ -49,742 +49,891 @@ var autocomplete = ({ onSearch }) => {
49
49
  ];
50
50
  };
51
51
 
52
- // packages/ui/react-ui-editor/src/extensions/basic.ts
53
- import { closeBrackets } from "@codemirror/autocomplete";
54
- import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
55
- import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
56
- import { drawSelection, EditorView, placeholder } from "@codemirror/view";
57
- var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
58
- EditorView.lineWrapping,
59
- // TODO(burdon): Compare with minimalSetup.
60
- // https://codemirror.net/docs/ref/#codemirror.minimalSetup
61
- bracketMatching(),
62
- closeBrackets(),
63
- drawSelection(),
64
- _placeholder && placeholder(_placeholder),
65
- // TODO(burdon): Is this required?
66
- themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
67
- ].filter(Boolean);
52
+ // 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";
55
+ import { next as A } from "@dxos/automerge/automerge";
68
56
 
69
- // packages/ui/react-ui-editor/src/extensions/blast.ts
70
- import { EditorView as EditorView2, keymap as keymap2 } from "@codemirror/view";
71
- import defaultsDeep from "lodash.defaultsdeep";
72
- import { invariant } from "@dxos/invariant";
73
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
74
- var defaultOptions = {
75
- effect: 2,
76
- maxParticles: 200,
77
- particleGravity: 0.08,
78
- particleAlphaFadeout: 0.996,
79
- particleSize: {
80
- min: 2,
81
- max: 8
82
- },
83
- particleNumRange: {
84
- min: 5,
85
- max: 10
86
- },
87
- particleVelocityRange: {
88
- x: [
89
- -1,
90
- 1
91
- ],
92
- y: [
93
- -3.5,
94
- -1.5
95
- ]
57
+ // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
58
+ import get from "lodash.get";
59
+ import { next as automerge } from "@dxos/automerge/automerge";
60
+ var cursorConverter = (handle, path) => ({
61
+ // TODO(burdon): Handle assoc to associate with a previous character.
62
+ toCursor: (pos) => {
63
+ const doc = handle.docSync();
64
+ if (!doc) {
65
+ return "";
66
+ }
67
+ const value = get(doc, path);
68
+ if (typeof value === "string" && value.length <= pos) {
69
+ return "end";
70
+ }
71
+ return automerge.getCursor(doc, path.slice(), pos);
96
72
  },
97
- particleShrinkRate: 0.95,
98
- shakeIntensity: 5
99
- };
100
- var blast = (options = defaultOptions) => {
101
- let blaster;
102
- let last = 0;
103
- const getPoint = (view, pos) => {
104
- const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
105
- const element = document.elementFromPoint(x, y);
106
- const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
107
- const point = {
108
- x: x - left,
109
- y: y - top
110
- };
111
- return {
112
- element,
113
- point
114
- };
115
- };
116
- return [
117
- // Cursor moved.
118
- EditorView2.updateListener.of((update4) => {
119
- if (blaster?.node !== update4.view.scrollDOM) {
120
- if (blaster) {
121
- blaster.destroy();
122
- }
123
- blaster = new Blaster(update4.view.scrollDOM, defaultsDeep({
124
- particleGravity: 0.2,
125
- particleShrinkRate: 0.995,
126
- color: () => [
127
- 100 + Math.random() * 100,
128
- 0,
129
- 0
130
- ]
131
- }, options, defaultOptions));
132
- blaster.initialize();
133
- blaster.start();
73
+ fromCursor: (cursor) => {
74
+ if (cursor === "") {
75
+ return 0;
76
+ }
77
+ const doc = handle.docSync();
78
+ if (!doc) {
79
+ return 0;
80
+ }
81
+ if (cursor === "end") {
82
+ const value = get(doc, path);
83
+ if (typeof value === "string") {
84
+ return value.length;
134
85
  } else {
135
- blaster.resize();
136
- }
137
- const current = update4.state.selection.main.head;
138
- if (current !== last) {
139
- last = current;
140
- const { element, point } = getPoint(update4.view, current - 1);
141
- if (element && point) {
142
- blaster.spawn({
143
- element,
144
- point
145
- });
146
- }
147
- }
148
- }),
149
- keymap2.of([
150
- {
151
- any: (_, event) => {
152
- if (blaster) {
153
- if (event.key === "Enter" && event.shiftKey) {
154
- blaster.shake({
155
- time: 0.8
156
- });
157
- return true;
158
- }
159
- }
160
- return false;
161
- }
86
+ return 0;
162
87
  }
163
- ])
88
+ }
89
+ return automerge.getCursorPosition(doc, path.slice(), cursor);
90
+ }
91
+ });
92
+
93
+ // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
94
+ import { Annotation, StateEffect } from "@codemirror/state";
95
+ var effectType = StateEffect.define({});
96
+ var updateHeads = (newHeads) => effectType.of({
97
+ newHeads
98
+ });
99
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
100
+ var getPath = (state, field) => state.field(field).path;
101
+ var reconcileAnnotationType = Annotation.define();
102
+ var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
103
+
104
+ // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
105
+ import { next as automerge2 } from "@dxos/automerge/automerge";
106
+
107
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
108
+ import { next as am } from "@dxos/automerge/automerge";
109
+ var updateAutomerge = (field, handle, transactions, state) => {
110
+ const { lastHeads, path } = state.field(field);
111
+ let hasChanges = false;
112
+ for (const tr of transactions) {
113
+ tr.changes.iterChanges(() => {
114
+ hasChanges = true;
115
+ });
116
+ }
117
+ if (!hasChanges) {
118
+ return void 0;
119
+ }
120
+ const newHeads = handle.changeAt(lastHeads, (doc) => {
121
+ for (const tr of transactions) {
122
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
123
+ am.splice(doc, path, fromA, toA - fromA, inserted.toString());
124
+ });
125
+ }
126
+ });
127
+ return newHeads ?? void 0;
128
+ };
129
+
130
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-codemirror.ts
131
+ import { ChangeSet } from "@codemirror/state";
132
+ var updateCodeMirror = (view, selection, target, patches) => {
133
+ for (const patch of patches) {
134
+ const changeSpec = handlePatch(patch, target, view.state);
135
+ if (changeSpec != null) {
136
+ const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
137
+ selection = selection.map(changeSet, 1);
138
+ view.dispatch({
139
+ changes: changeSet,
140
+ annotations: reconcileAnnotationType.of({})
141
+ });
142
+ }
143
+ }
144
+ view.dispatch({
145
+ selection,
146
+ annotations: reconcileAnnotationType.of({})
147
+ });
148
+ };
149
+ var handlePatch = (patch, target, state) => {
150
+ if (patch.action === "insert") {
151
+ return handleInsert(target, patch);
152
+ } else if (patch.action === "splice") {
153
+ return handleSplice(target, patch);
154
+ } else if (patch.action === "del") {
155
+ return handleDel(target, patch);
156
+ } else if (patch.action === "put") {
157
+ return handlePut(target, patch, state);
158
+ } else {
159
+ return null;
160
+ }
161
+ };
162
+ var handleInsert = (target, patch) => {
163
+ const index = charPath(target, patch.path);
164
+ if (index == null) {
165
+ return [];
166
+ }
167
+ const text = patch.values.map((v) => v ? v.toString() : "").join("");
168
+ return [
169
+ {
170
+ from: index,
171
+ to: index,
172
+ insert: text
173
+ }
164
174
  ];
165
175
  };
166
- var Blaster = class {
167
- constructor(_node, _options) {
168
- this._node = _node;
169
- this._options = _options;
170
- this._running = false;
171
- this._shakeTime = 0;
172
- this._shakeTimeMax = 0;
173
- this._particles = [];
174
- this._particlePointer = 0;
175
- this._lastPoint = {
176
- x: 0,
177
- y: 0
178
- };
179
- this.shake = throttle(({ time }) => {
180
- this._shakeTime = this._shakeTimeMax || time;
181
- this._shakeTimeMax = time;
182
- }, 100);
183
- this.spawn = throttle(({ element, point }) => {
184
- const color = getRGBComponents(element, this._options.color);
185
- const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
186
- const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
187
- this._lastPoint = point;
188
- for (let i = numParticles; i--; i > 0) {
189
- this._particles[this._particlePointer] = this._effect.create(point.x - dir * 16, point.y, color);
190
- this._particlePointer = (this._particlePointer + 1) % this._options.maxParticles;
191
- }
192
- }, 100);
193
- this._effect = this._options.effect === 1 ? new Effect1(_options) : new Effect2(_options);
176
+ var handleSplice = (target, patch) => {
177
+ const index = charPath(target, patch.path);
178
+ if (index == null) {
179
+ return [];
194
180
  }
195
- get node() {
196
- return this._node;
181
+ return [
182
+ {
183
+ from: index,
184
+ insert: patch.value
185
+ }
186
+ ];
187
+ };
188
+ var handleDel = (target, patch) => {
189
+ const index = charPath(target, patch.path);
190
+ if (index == null) {
191
+ return [];
197
192
  }
198
- initialize() {
199
- invariant(!this._canvas && !this._ctx, void 0, {
200
- F: __dxlog_file,
201
- L: 138,
202
- S: this,
203
- A: [
204
- "!this._canvas && !this._ctx",
205
- ""
206
- ]
207
- });
208
- this._canvas = document.createElement("canvas");
209
- this._canvas.id = "code-blast-canvas";
210
- this._canvas.style.position = "absolute";
211
- this._canvas.style.zIndex = "0";
212
- this._canvas.style.pointerEvents = "none";
213
- this._ctx = this._canvas.getContext("2d");
214
- const parent = this._node.parentElement?.parentElement;
215
- parent?.appendChild(this._canvas);
216
- this.resize();
193
+ const length = patch.length || 1;
194
+ return [
195
+ {
196
+ from: index,
197
+ to: index + length
198
+ }
199
+ ];
200
+ };
201
+ var handlePut = (target, patch, state) => {
202
+ const index = charPath(target, [
203
+ ...patch.path,
204
+ 0
205
+ ]);
206
+ if (index == null) {
207
+ return [];
217
208
  }
218
- destroy() {
219
- this.stop();
220
- if (this._canvas) {
221
- this._canvas.remove();
222
- this._canvas = void 0;
223
- this._ctx = void 0;
209
+ const length = state.doc.length;
210
+ if (typeof patch.value !== "string") {
211
+ return [];
212
+ }
213
+ return [
214
+ {
215
+ from: 0,
216
+ to: length,
217
+ insert: patch.value
224
218
  }
219
+ ];
220
+ };
221
+ var charPath = (textPath, candidatePath) => {
222
+ if (candidatePath.length !== textPath.length + 1) {
223
+ return null;
225
224
  }
226
- resize() {
227
- if (this._node.parentElement && this._canvas) {
228
- const { offsetLeft: x, offsetTop: y, offsetWidth: width, offsetHeight: height } = this._node.parentElement;
229
- this._canvas.style.top = `${y}px`;
230
- this._canvas.style.left = `${x}px`;
231
- this._canvas.width = width;
232
- this._canvas.height = height;
225
+ for (let i = 0; i < textPath.length; i++) {
226
+ if (textPath[i] !== candidatePath[i]) {
227
+ return null;
233
228
  }
234
229
  }
235
- start() {
236
- invariant(this._canvas && this._ctx, void 0, {
230
+ const index = candidatePath[candidatePath.length - 1];
231
+ if (typeof index === "number") {
232
+ return index;
233
+ }
234
+ return null;
235
+ };
236
+
237
+ // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
238
+ var PatchSemaphore = class {
239
+ constructor(_field) {
240
+ this._field = _field;
241
+ 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);
267
+ view.dispatch({
268
+ effects: updateHeads(newHeads),
269
+ annotations: reconcileAnnotationType.of({})
270
+ });
271
+ this._inReconcile = false;
272
+ };
273
+ }
274
+ };
275
+
276
+ // packages/ui/react-ui-editor/src/util/cursor.ts
277
+ import { Facet } from "@codemirror/state";
278
+ var DEFAULT_CURSOR_CONVERTER = {
279
+ toCursor: (position) => position.toString(),
280
+ fromCursor: (cursor) => parseInt(cursor)
281
+ };
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, {
237
294
  F: __dxlog_file,
238
- L: 177,
239
- S: this,
240
- A: [
241
- "this._canvas && this._ctx",
242
- ""
243
- ]
295
+ L: 16,
296
+ S: void 0,
297
+ C: (f, a) => f(...a)
298
+ });
299
+ }
300
+ };
301
+
302
+ // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
303
+ var semaphoreFacet = Facet2.define({
304
+ combine: (values) => values.at(-1)
305
+ });
306
+ var automerge3 = ({ handle, path }) => {
307
+ const stateField = StateField.define({
308
+ create: () => ({
309
+ lastHeads: A.getHeads(handle.docSync()),
310
+ unreconciledTransactions: [],
311
+ path: path.slice()
312
+ }),
313
+ update: (value, tr) => {
314
+ const result = {
315
+ lastHeads: value.lastHeads,
316
+ unreconciledTransactions: value.unreconciledTransactions.slice(),
317
+ path: path.slice()
318
+ };
319
+ let clearUnreconciled = false;
320
+ for (const effect of tr.effects) {
321
+ if (effect.is(effectType)) {
322
+ result.lastHeads = effect.value.newHeads;
323
+ clearUnreconciled = true;
324
+ }
325
+ }
326
+ if (clearUnreconciled) {
327
+ result.unreconciledTransactions = [];
328
+ } else {
329
+ if (!isReconcileTx(tr)) {
330
+ result.unreconciledTransactions.push(tr);
331
+ }
332
+ }
333
+ return result;
334
+ }
335
+ });
336
+ const semaphore = new PatchSemaphore(stateField);
337
+ const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
338
+ constructor(_view) {
339
+ this._view = _view;
340
+ this._handleChange = () => {
341
+ this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
342
+ };
343
+ handle.addListener("change", this._handleChange);
344
+ }
345
+ update(update4) {
346
+ if (update4.transactions.length > 0 && update4.transactions.some((tr) => !isReconcileTx(tr))) {
347
+ queueMicrotask(() => {
348
+ this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
349
+ });
350
+ }
351
+ }
352
+ destroy() {
353
+ handle.addListener("change", this._handleChange);
354
+ }
355
+ });
356
+ return {
357
+ extension: [
358
+ CursorConverter.of(cursorConverter(handle, path)),
359
+ semaphoreFacet.of(semaphore),
360
+ stateField,
361
+ viewPlugin
362
+ ]
363
+ };
364
+ };
365
+
366
+ // 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";
369
+ import { Event } from "@dxos/async";
370
+ import { Context } from "@dxos/context";
371
+ var dummyProvider = {
372
+ remoteStateChange: new Event(),
373
+ open: () => {
374
+ },
375
+ close: () => {
376
+ },
377
+ update: () => {
378
+ },
379
+ getRemoteStates: () => []
380
+ };
381
+ var AwarenessProvider = Facet3.define({
382
+ combine: (providers) => providers[0] ?? dummyProvider
383
+ });
384
+ var RemoteSelectionChangedAnnotation = Annotation2.define();
385
+ var awareness = (provider = dummyProvider) => {
386
+ return [
387
+ AwarenessProvider.of(provider),
388
+ ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
389
+ decorations: (value) => value.decorations
390
+ }),
391
+ styles
392
+ ];
393
+ };
394
+ var RemoteSelectionsDecorator = class {
395
+ constructor(view) {
396
+ this.decorations = RangeSet.of([]);
397
+ this._ctx = new Context();
398
+ this._lastAnchor = void 0;
399
+ this._lastHead = void 0;
400
+ this._cursorConverter = view.state.facet(CursorConverter);
401
+ this._provider = view.state.facet(AwarenessProvider);
402
+ this._provider.open();
403
+ this._provider.remoteStateChange.on(this._ctx, () => {
404
+ view.dispatch({
405
+ annotations: [
406
+ RemoteSelectionChangedAnnotation.of([])
407
+ ]
408
+ });
244
409
  });
245
- this._running = true;
246
- this.loop();
247
410
  }
248
- stop() {
249
- this._running = false;
250
- this._node.style.transform = "translate(0px, 0px)";
411
+ destroy() {
412
+ void this._ctx.dispose();
413
+ this._provider.close();
251
414
  }
252
- loop() {
253
- if (!this._running || !this._canvas || !this._ctx) {
415
+ update(update4) {
416
+ this._updateLocalSelection(update4);
417
+ this._updateRemoteSelections(update4);
418
+ }
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 : {};
422
+ if (this._lastAnchor === anchor && this._lastHead === head) {
254
423
  return;
255
424
  }
256
- this._ctx.clearRect(0, 0, this._canvas.width ?? 0, this._canvas.height ?? 0);
257
- const now = (/* @__PURE__ */ new Date()).getTime();
258
- this._lastTime ??= now;
259
- const dt = (now - this._lastTime) / 1e3;
260
- this._lastTime = now;
261
- if (this._shakeTime > 0) {
262
- this._shakeTime -= dt;
263
- const magnitude = this._shakeTime / this._shakeTimeMax * this._options.shakeIntensity;
264
- const shakeX = random(-magnitude, magnitude);
265
- const shakeY = random(-magnitude, magnitude);
266
- this._node.style.transform = `translate(${shakeX}px,${shakeY}px)`;
267
- }
268
- this.drawParticles();
269
- requestAnimationFrame(this.loop.bind(this));
270
- }
271
- drawParticles() {
272
- for (let i = this._particles.length; i--; i > 0) {
273
- const particle = this._particles[i];
274
- if (!particle) {
425
+ this._lastAnchor = anchor;
426
+ this._lastHead = head;
427
+ this._provider.update(anchor !== void 0 && head !== void 0 ? {
428
+ anchor: this._cursorConverter.toCursor(anchor),
429
+ head: this._cursorConverter.toCursor(head)
430
+ } : void 0);
431
+ }
432
+ _updateRemoteSelections(update4) {
433
+ const decorations = [];
434
+ const awarenessStates = this._provider.getRemoteStates();
435
+ for (const state of awarenessStates) {
436
+ const anchor = state.position?.anchor ? this._cursorConverter.fromCursor(state.position.anchor) : null;
437
+ const head = state.position?.head ? this._cursorConverter.fromCursor(state.position.head) : null;
438
+ if (anchor == null || head == null) {
275
439
  continue;
276
440
  }
277
- if (particle.alpha < 0.01 || particle.size <= 0.5) {
278
- continue;
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);
445
+ const color = state.info.color ?? "#30bced";
446
+ const lightColor = state.info.lightColor ?? color + "33";
447
+ if (startLine.number === endLine.number) {
448
+ decorations.push({
449
+ from: start,
450
+ to: end,
451
+ value: Decoration.mark({
452
+ attributes: {
453
+ style: `background-color: ${lightColor}`
454
+ },
455
+ class: "cm-ySelection"
456
+ })
457
+ });
458
+ } else {
459
+ decorations.push({
460
+ from: start,
461
+ to: startLine.from + startLine.length,
462
+ value: Decoration.mark({
463
+ attributes: {
464
+ style: `background-color: ${color}`
465
+ },
466
+ class: "cm-ySelection"
467
+ })
468
+ });
469
+ decorations.push({
470
+ from: endLine.from,
471
+ to: end,
472
+ value: Decoration.mark({
473
+ attributes: {
474
+ style: `background-color: ${color}`
475
+ },
476
+ class: "cm-ySelection"
477
+ })
478
+ });
479
+ for (let i = startLine.number + 1; i < endLine.number; i++) {
480
+ const linePos = update4.view.state.doc.line(i).from;
481
+ decorations.push({
482
+ from: linePos,
483
+ to: linePos,
484
+ value: Decoration.line({
485
+ attributes: {
486
+ style: `background-color: ${color}`,
487
+ class: "cm-yLineSelection"
488
+ }
489
+ })
490
+ });
491
+ }
279
492
  }
280
- this._effect.update(this._ctx, particle);
493
+ decorations.push({
494
+ from: head,
495
+ to: head,
496
+ value: Decoration.widget({
497
+ side: head - anchor > 0 ? -1 : 1,
498
+ block: false,
499
+ widget: new RemoteCaretWidget(state.info.displayName ?? "Anonymous", color)
500
+ })
501
+ });
281
502
  }
503
+ this.decorations = Decoration.set(decorations, true);
282
504
  }
283
505
  };
284
- var Effect = class {
285
- constructor(_options) {
286
- this._options = _options;
287
- }
288
- };
289
- var Effect1 = class extends Effect {
290
- create(x, y, color) {
291
- return {
292
- x,
293
- y: y + 10,
294
- vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
295
- vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
296
- size: random(this._options.particleSize.min, this._options.particleSize.max),
297
- color,
298
- alpha: 1
299
- };
506
+ var RemoteCaretWidget = class extends WidgetType {
507
+ constructor(name, color) {
508
+ super();
509
+ this.name = name;
510
+ this.color = color;
511
+ this.name = name;
512
+ this.color = color;
300
513
  }
301
- update(ctx, particle) {
302
- particle.vy += this._options.particleGravity;
303
- particle.x += particle.vx;
304
- particle.y += particle.vy;
305
- particle.alpha *= this._options.particleAlphaFadeout;
306
- ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
307
- ctx.fillRect(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, particle.size);
514
+ toDOM() {
515
+ const span = document.createElement("span");
516
+ span.className = "cm-ySelectionCaret";
517
+ span.style.backgroundColor = this.color;
518
+ span.style.borderColor = this.color;
519
+ const dot = document.createElement("div");
520
+ dot.className = "cm-ySelectionCaretDot";
521
+ const info = document.createElement("div");
522
+ info.className = "cm-ySelectionInfo";
523
+ info.innerText = this.name;
524
+ span.appendChild(document.createTextNode("\u2060"));
525
+ span.appendChild(dot);
526
+ span.appendChild(document.createTextNode("\u2060"));
527
+ span.appendChild(info);
528
+ span.appendChild(document.createTextNode("\u2060"));
529
+ return span;
308
530
  }
309
- };
310
- var Effect2 = class extends Effect {
311
- create(x, y, color) {
312
- return {
313
- x,
314
- y: y + 10,
315
- vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
316
- vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
317
- size: random(this._options.particleSize.min, this._options.particleSize.max),
318
- color,
319
- alpha: 1,
320
- theta: random(0, 360) * Math.PI / 180,
321
- drag: 0.92,
322
- wander: 0.15
323
- };
531
+ eq(widget) {
532
+ return widget.color === this.color;
324
533
  }
325
- update(ctx, particle) {
326
- particle.vy += this._options.particleGravity;
327
- particle.x += particle.vx;
328
- particle.y += particle.vy;
329
- particle.vx *= particle.drag;
330
- particle.vy *= particle.drag;
331
- particle.theta += random(-0.5, 0.5);
332
- particle.vx += Math.sin(particle.theta) * 0.1;
333
- particle.vy += Math.cos(particle.theta) * 0.1;
334
- particle.size *= this._options.particleShrinkRate;
335
- particle.alpha *= this._options.particleAlphaFadeout;
336
- ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
337
- ctx.beginPath();
338
- ctx.arc(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, 0, 2 * Math.PI);
339
- ctx.fill();
534
+ compare(widget) {
535
+ return widget.color === this.color;
340
536
  }
341
- };
342
- function throttle(callback, limit) {
343
- let wait = false;
344
- return function(...args) {
345
- if (!wait) {
346
- callback.apply(this, args);
347
- wait = true;
348
- setTimeout(() => {
349
- wait = false;
350
- }, limit);
351
- }
352
- };
353
- }
354
- var getRGBComponents = (node, color) => {
355
- if (typeof color === "function") {
356
- return color();
537
+ updateDOM() {
538
+ return false;
357
539
  }
358
- const bgColor = getComputedStyle(node).color;
359
- if (bgColor) {
360
- const x = bgColor.match(/(\d+), (\d+), (\d+)/)?.slice(1);
361
- if (x) {
362
- return x;
363
- }
540
+ get estimatedHeight() {
541
+ return -1;
364
542
  }
365
- return [
366
- 50,
367
- 50,
368
- 50
369
- ];
370
- };
371
- var random = (min, max) => {
372
- if (!max) {
373
- max = min;
374
- min = 0;
543
+ ignoreEvent() {
544
+ return true;
375
545
  }
376
- return min + ~~(Math.random() * (max - min + 1));
377
- };
378
-
379
- // packages/ui/react-ui-editor/src/extensions/code.ts
380
- import { ViewPlugin, EditorView as EditorView3, Decoration } from "@codemirror/view";
381
- import get from "lodash.get";
382
- import { mx as mx2 } from "@dxos/react-ui-theme";
383
-
384
- // packages/ui/react-ui-editor/src/styles/markdown.ts
385
- import { mx } from "@dxos/react-ui-theme";
386
- var headings = {
387
- 1: [
388
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
389
- "-ml-[10px]"
390
- ],
391
- 2: [
392
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
393
- "-ml-[8px]"
394
- ],
395
- 3: [
396
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
397
- "-ml-[7px]"
398
- ],
399
- 4: [
400
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
401
- "-ml-[6px]"
402
- ],
403
- 5: [
404
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
405
- "-ml-[5px]"
406
- ],
407
- 6: [
408
- "mbs-4 mbe-2 font-medium text-inherit no-underline",
409
- "-ml-[4px]"
410
- ]
411
- };
412
- var heading = (level, readonly) => {
413
- const [style, offset] = headings[level];
414
- return mx(style, readonly && offset);
415
546
  };
416
- var light = "text-neutral-200 dark:text-neutral-800";
417
- var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
418
- var bold = "font-bold";
419
- var italic = "italic";
420
- var strikethrough = "line-through";
421
- var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
422
- var codeMark = "font-mono text-primary-500";
423
- var inlineUrl = mx(code, "px-1");
424
- var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
425
-
426
- // packages/ui/react-ui-editor/src/styles/tokens.ts
427
- import { tailwindConfig } from "@dxos/react-ui-theme";
428
- var tokens = tailwindConfig({}).theme;
429
-
430
- // packages/ui/react-ui-editor/src/extensions/code.ts
431
- var CODE_REGEX = /```[\s\S]*?```/gs;
432
- var styles = EditorView3.baseTheme({
433
- "& .cm-codeblock": {
434
- fontFamily: get(tokens, "fontFamily.mono", []).join(",")
547
+ var styles = EditorView.baseTheme({
548
+ ".cm-ySelection": {},
549
+ ".cm-yLineSelection": {
550
+ padding: 0,
551
+ margin: "0px 2px 0px 4px"
435
552
  },
436
- '&light [aria-readonly="true"] .cm-codeblock': {
437
- background: get(tokens, "extend.colors.neutral.50")
553
+ ".cm-ySelectionCaret": {
554
+ position: "relative",
555
+ borderLeft: "1px solid black",
556
+ borderRight: "1px solid black",
557
+ marginLeft: "-1px",
558
+ marginRight: "-1px",
559
+ boxSizing: "border-box",
560
+ display: "inline"
438
561
  },
439
- '&dark [aria-readonly="true"] .cm-codeblock': {
440
- background: get(tokens, "extend.colors.neutral.850")
562
+ ".cm-ySelectionCaretDot": {
563
+ borderRadius: "50%",
564
+ position: "absolute",
565
+ width: ".4em",
566
+ height: ".4em",
567
+ top: "-.2em",
568
+ left: "-.2em",
569
+ backgroundColor: "inherit",
570
+ transition: "transform .3s ease-in-out",
571
+ boxSizing: "border-box"
441
572
  },
442
- '& [aria-readonly="true"] .cm-codeblock': {
443
- display: "block",
444
- paddingInline: "4px !important"
573
+ ".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot": {
574
+ transformOrigin: "bottom center",
575
+ transform: "scale(0)"
445
576
  },
446
- '& [aria-readonly="true"] .cm-codeblock-first': {
447
- paddingTop: "4px !important",
448
- borderTopLeftRadius: "4px",
449
- borderTopRightRadius: "4px"
577
+ ".cm-ySelectionInfo": {
578
+ position: "absolute",
579
+ top: "-1.05em",
580
+ left: "-1px",
581
+ fontSize: ".75em",
582
+ fontFamily: "serif",
583
+ fontStyle: "normal",
584
+ fontWeight: "normal",
585
+ lineHeight: "normal",
586
+ userSelect: "none",
587
+ color: "white",
588
+ paddingLeft: "2px",
589
+ paddingRight: "2px",
590
+ zIndex: 101,
591
+ transition: "opacity .3s ease-in-out",
592
+ backgroundColor: "inherit",
593
+ // these should be separate
594
+ opacity: 0,
595
+ transitionDelay: "0s",
596
+ whiteSpace: "nowrap"
450
597
  },
451
- '& [aria-readonly="true"] .cm-codeblock-last': {
452
- paddingBottom: "4px !important",
453
- borderBottomLeftRadius: "4px",
454
- borderBottomRightRadius: "4px"
598
+ ".cm-ySelectionCaret:hover > .cm-ySelectionInfo": {
599
+ opacity: 1,
600
+ transitionDelay: "0s"
455
601
  }
456
602
  });
457
- var getLineRange = (lines, from, to) => {
458
- const start = lines.findIndex((line) => line.from >= from);
459
- const end = lines.findIndex((line) => line.to >= to);
460
- return [
461
- start,
462
- end
463
- ];
603
+
604
+ // packages/ui/react-ui-editor/src/extensions/basic.ts
605
+ import { closeBrackets } from "@codemirror/autocomplete";
606
+ import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
607
+ 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.
612
+ // https://codemirror.net/docs/ref/#codemirror.minimalSetup
613
+ bracketMatching(),
614
+ closeBrackets(),
615
+ drawSelection(),
616
+ _placeholder && placeholder(_placeholder),
617
+ // TODO(burdon): Is this required?
618
+ themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
619
+ ].filter(Boolean);
620
+
621
+ // packages/ui/react-ui-editor/src/extensions/blast.ts
622
+ import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
623
+ import defaultsDeep from "lodash.defaultsdeep";
624
+ import { invariant } from "@dxos/invariant";
625
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
626
+ var defaultOptions = {
627
+ effect: 2,
628
+ maxParticles: 200,
629
+ particleGravity: 0.08,
630
+ particleAlphaFadeout: 0.996,
631
+ particleSize: {
632
+ min: 2,
633
+ max: 8
634
+ },
635
+ particleNumRange: {
636
+ min: 5,
637
+ max: 10
638
+ },
639
+ particleVelocityRange: {
640
+ x: [
641
+ -1,
642
+ 1
643
+ ],
644
+ y: [
645
+ -3.5,
646
+ -1.5
647
+ ]
648
+ },
649
+ particleShrinkRate: 0.95,
650
+ shakeIntensity: 5
464
651
  };
465
- var code2 = () => {
466
- const getDecorations = (view) => {
467
- const decorations = [];
468
- if (view.state.readOnly) {
469
- const text = view.state.doc.sliceString(0);
470
- const matches = text.matchAll(CODE_REGEX);
471
- const blocks = view.viewportLineBlocks;
472
- for (const match of matches) {
473
- const range = getLineRange(blocks, match.index, match.index + match[0].length);
474
- for (let i = range[0]; i <= range[1]; i++) {
475
- const block = blocks[i];
476
- decorations.push(Decoration.line({
477
- class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
478
- }).range(block.from));
479
- }
480
- }
481
- }
482
- return Decoration.set(decorations);
652
+ var blast = (options = defaultOptions) => {
653
+ let blaster;
654
+ let last = 0;
655
+ const getPoint = (view, pos) => {
656
+ const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
657
+ const element = document.elementFromPoint(x, y);
658
+ const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
659
+ const point = {
660
+ x: x - left,
661
+ y: y - top
662
+ };
663
+ return {
664
+ element,
665
+ point
666
+ };
483
667
  };
484
668
  return [
485
- ViewPlugin.fromClass(class {
486
- constructor(view) {
487
- this.decorations = getDecorations(view);
669
+ // Cursor moved.
670
+ EditorView3.updateListener.of((update4) => {
671
+ if (blaster?.node !== update4.view.scrollDOM) {
672
+ if (blaster) {
673
+ blaster.destroy();
674
+ }
675
+ blaster = new Blaster(update4.view.scrollDOM, defaultsDeep({
676
+ particleGravity: 0.2,
677
+ particleShrinkRate: 0.995,
678
+ color: () => [
679
+ 100 + Math.random() * 100,
680
+ 0,
681
+ 0
682
+ ]
683
+ }, options, defaultOptions));
684
+ blaster.initialize();
685
+ blaster.start();
686
+ } else {
687
+ blaster.resize();
488
688
  }
489
- update(update4) {
490
- if (update4.docChanged || update4.viewportChanged) {
491
- this.decorations = getDecorations(update4.view);
689
+ const current = update4.state.selection.main.head;
690
+ if (current !== last) {
691
+ last = current;
692
+ const { element, point } = getPoint(update4.view, current - 1);
693
+ if (element && point) {
694
+ blaster.spawn({
695
+ element,
696
+ point
697
+ });
492
698
  }
493
699
  }
494
- }, {
495
- decorations: (value) => value.decorations
496
700
  }),
497
- styles
701
+ keymap2.of([
702
+ {
703
+ any: (_, event) => {
704
+ if (blaster) {
705
+ if (event.key === "Enter" && event.shiftKey) {
706
+ blaster.shake({
707
+ time: 0.8
708
+ });
709
+ return true;
710
+ }
711
+ }
712
+ return false;
713
+ }
714
+ }
715
+ ])
498
716
  ];
499
717
  };
500
-
501
- // packages/ui/react-ui-editor/src/extensions/comments.ts
502
- import { StateEffect as StateEffect2, StateField as StateField3 } from "@codemirror/state";
503
- import { hoverTooltip, keymap as keymap3, Decoration as Decoration2, EditorView as EditorView4, MatchDecorator, ViewPlugin as ViewPlugin3, WidgetType } from "@codemirror/view";
504
- import get3 from "lodash.get";
505
- import sortBy from "lodash.sortby";
506
- import { debounce } from "@dxos/async";
507
- import { invariant as invariant3 } from "@dxos/invariant";
508
- import { log as log3 } from "@dxos/log";
509
- import { nonNullable } from "@dxos/util";
510
-
511
- // packages/ui/react-ui-editor/src/extensions/util.ts
512
- import { log } from "@dxos/log";
513
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/util.ts";
514
- var callbackWrapper = (fn) => (...args) => {
515
- try {
516
- return fn(...args);
517
- } catch (error) {
518
- log.catch(error, void 0, {
718
+ var Blaster = class {
719
+ constructor(_node, _options) {
720
+ this._node = _node;
721
+ this._options = _options;
722
+ this._running = false;
723
+ this._shakeTime = 0;
724
+ this._shakeTimeMax = 0;
725
+ this._particles = [];
726
+ this._particlePointer = 0;
727
+ this._lastPoint = {
728
+ x: 0,
729
+ y: 0
730
+ };
731
+ this.shake = throttle(({ time }) => {
732
+ this._shakeTime = this._shakeTimeMax || time;
733
+ this._shakeTimeMax = time;
734
+ }, 100);
735
+ this.spawn = throttle(({ element, point }) => {
736
+ const color = getRGBComponents(element, this._options.color);
737
+ const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
738
+ const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
739
+ this._lastPoint = point;
740
+ for (let i = numParticles; i--; i > 0) {
741
+ this._particles[this._particlePointer] = this._effect.create(point.x - dir * 16, point.y, color);
742
+ this._particlePointer = (this._particlePointer + 1) % this._options.maxParticles;
743
+ }
744
+ }, 100);
745
+ this._effect = this._options.effect === 1 ? new Effect1(_options) : new Effect2(_options);
746
+ }
747
+ get node() {
748
+ return this._node;
749
+ }
750
+ initialize() {
751
+ invariant(!this._canvas && !this._ctx, void 0, {
519
752
  F: __dxlog_file2,
520
- L: 16,
521
- S: void 0,
522
- C: (f, a) => f(...a)
753
+ L: 138,
754
+ S: this,
755
+ A: [
756
+ "!this._canvas && !this._ctx",
757
+ ""
758
+ ]
523
759
  });
760
+ this._canvas = document.createElement("canvas");
761
+ this._canvas.id = "code-blast-canvas";
762
+ this._canvas.style.position = "absolute";
763
+ this._canvas.style.zIndex = "0";
764
+ this._canvas.style.pointerEvents = "none";
765
+ this._ctx = this._canvas.getContext("2d");
766
+ const parent = this._node.parentElement?.parentElement;
767
+ parent?.appendChild(this._canvas);
768
+ this.resize();
524
769
  }
525
- };
526
-
527
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
528
- import { StateField as StateField2 } from "@codemirror/state";
529
- import get2 from "lodash.get";
530
- import { useEffect, useState } from "react";
531
- import { yCollab } from "y-codemirror.next";
532
- import * as Y from "yjs";
533
- import { invariant as invariant2 } from "@dxos/invariant";
534
- import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
535
- import { arrayToString, stringToArray } from "@dxos/util";
536
-
537
- // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
538
- import { Annotation, Facet, StateEffect, StateField } from "@codemirror/state";
539
- import { ViewPlugin as ViewPlugin2 } from "@codemirror/view";
540
- import * as automerge2 from "@dxos/automerge/automerge";
541
-
542
- // packages/ui/react-ui-editor/src/hooks/automerge/semaphore.ts
543
- import { next as automerge } from "@dxos/automerge/automerge";
544
-
545
- // packages/ui/react-ui-editor/src/hooks/automerge/update-automerge.ts
546
- import { next as am } from "@dxos/automerge/automerge";
547
- var updateAutomerge = (field, handle, transactions, state) => {
548
- const { lastHeads, path } = state.field(field);
549
- let hasChanges = false;
550
- for (const tr of transactions) {
551
- tr.changes.iterChanges(() => {
552
- hasChanges = true;
770
+ destroy() {
771
+ this.stop();
772
+ if (this._canvas) {
773
+ this._canvas.remove();
774
+ this._canvas = void 0;
775
+ this._ctx = void 0;
776
+ }
777
+ }
778
+ resize() {
779
+ if (this._node.parentElement && this._canvas) {
780
+ const { offsetLeft: x, offsetTop: y, offsetWidth: width, offsetHeight: height } = this._node.parentElement;
781
+ this._canvas.style.top = `${y}px`;
782
+ this._canvas.style.left = `${x}px`;
783
+ this._canvas.width = width;
784
+ this._canvas.height = height;
785
+ }
786
+ }
787
+ start() {
788
+ invariant(this._canvas && this._ctx, void 0, {
789
+ F: __dxlog_file2,
790
+ L: 177,
791
+ S: this,
792
+ A: [
793
+ "this._canvas && this._ctx",
794
+ ""
795
+ ]
553
796
  });
797
+ this._running = true;
798
+ this.loop();
554
799
  }
555
- if (!hasChanges) {
556
- return void 0;
800
+ stop() {
801
+ this._running = false;
802
+ this._node.style.transform = "translate(0px, 0px)";
557
803
  }
558
- const newHeads = handle.changeAt(lastHeads, (doc) => {
559
- for (const tr of transactions) {
560
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
561
- am.splice(doc, path, fromA, toA - fromA, inserted.toString());
562
- });
804
+ loop() {
805
+ if (!this._running || !this._canvas || !this._ctx) {
806
+ return;
563
807
  }
564
- });
565
- return newHeads ?? void 0;
566
- };
567
-
568
- // packages/ui/react-ui-editor/src/hooks/automerge/update-codemirror.ts
569
- import { ChangeSet } from "@codemirror/state";
570
- var updateCodeMirror = (view, selection, target, patches) => {
571
- for (const patch of patches) {
572
- const changeSpec = handlePatch(patch, target, view.state);
573
- if (changeSpec != null) {
574
- const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
575
- selection = selection.map(changeSet, 1);
576
- view.dispatch({
577
- changes: changeSet,
578
- annotations: reconcileAnnotationType.of({})
579
- });
808
+ this._ctx.clearRect(0, 0, this._canvas.width ?? 0, this._canvas.height ?? 0);
809
+ const now = (/* @__PURE__ */ new Date()).getTime();
810
+ this._lastTime ??= now;
811
+ const dt = (now - this._lastTime) / 1e3;
812
+ this._lastTime = now;
813
+ if (this._shakeTime > 0) {
814
+ this._shakeTime -= dt;
815
+ const magnitude = this._shakeTime / this._shakeTimeMax * this._options.shakeIntensity;
816
+ const shakeX = random(-magnitude, magnitude);
817
+ const shakeY = random(-magnitude, magnitude);
818
+ this._node.style.transform = `translate(${shakeX}px,${shakeY}px)`;
819
+ }
820
+ this.drawParticles();
821
+ requestAnimationFrame(this.loop.bind(this));
822
+ }
823
+ drawParticles() {
824
+ for (let i = this._particles.length; i--; i > 0) {
825
+ const particle = this._particles[i];
826
+ if (!particle) {
827
+ continue;
828
+ }
829
+ if (particle.alpha < 0.01 || particle.size <= 0.5) {
830
+ continue;
831
+ }
832
+ this._effect.update(this._ctx, particle);
580
833
  }
581
834
  }
582
- view.dispatch({
583
- selection,
584
- annotations: reconcileAnnotationType.of({})
585
- });
586
- };
587
- var handlePatch = (patch, target, state) => {
588
- if (patch.action === "insert") {
589
- return handleInsert(target, patch);
590
- } else if (patch.action === "splice") {
591
- return handleSplice(target, patch);
592
- } else if (patch.action === "del") {
593
- return handleDel(target, patch);
594
- } else if (patch.action === "put") {
595
- return handlePut(target, patch, state);
596
- } else {
597
- return null;
598
- }
599
835
  };
600
- var handleInsert = (target, patch) => {
601
- const index = charPath(target, patch.path);
602
- if (index == null) {
603
- return [];
836
+ var Effect = class {
837
+ constructor(_options) {
838
+ this._options = _options;
604
839
  }
605
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
606
- return [
607
- {
608
- from: index,
609
- to: index,
610
- insert: text
611
- }
612
- ];
613
840
  };
614
- var handleSplice = (target, patch) => {
615
- const index = charPath(target, patch.path);
616
- if (index == null) {
617
- return [];
841
+ var Effect1 = class extends Effect {
842
+ create(x, y, color) {
843
+ return {
844
+ x,
845
+ y: y + 10,
846
+ vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
847
+ vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
848
+ size: random(this._options.particleSize.min, this._options.particleSize.max),
849
+ color,
850
+ alpha: 1
851
+ };
618
852
  }
619
- return [
620
- {
621
- from: index,
622
- insert: patch.value
623
- }
624
- ];
625
- };
626
- var handleDel = (target, patch) => {
627
- const index = charPath(target, patch.path);
628
- if (index == null) {
629
- return [];
853
+ update(ctx, particle) {
854
+ particle.vy += this._options.particleGravity;
855
+ particle.x += particle.vx;
856
+ particle.y += particle.vy;
857
+ particle.alpha *= this._options.particleAlphaFadeout;
858
+ ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
859
+ ctx.fillRect(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, particle.size);
630
860
  }
631
- const length = patch.length || 1;
632
- return [
633
- {
634
- from: index,
635
- to: index + length
636
- }
637
- ];
638
861
  };
639
- var handlePut = (target, patch, state) => {
640
- const index = charPath(target, [
641
- ...patch.path,
642
- 0
643
- ]);
644
- if (index == null) {
645
- return [];
862
+ var Effect2 = class extends Effect {
863
+ create(x, y, color) {
864
+ return {
865
+ x,
866
+ y: y + 10,
867
+ vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
868
+ vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
869
+ size: random(this._options.particleSize.min, this._options.particleSize.max),
870
+ color,
871
+ alpha: 1,
872
+ theta: random(0, 360) * Math.PI / 180,
873
+ drag: 0.92,
874
+ wander: 0.15
875
+ };
646
876
  }
647
- const length = state.doc.length;
648
- if (typeof patch.value !== "string") {
649
- return [];
877
+ update(ctx, particle) {
878
+ particle.vy += this._options.particleGravity;
879
+ particle.x += particle.vx;
880
+ particle.y += particle.vy;
881
+ particle.vx *= particle.drag;
882
+ particle.vy *= particle.drag;
883
+ particle.theta += random(-0.5, 0.5);
884
+ particle.vx += Math.sin(particle.theta) * 0.1;
885
+ particle.vy += Math.cos(particle.theta) * 0.1;
886
+ particle.size *= this._options.particleShrinkRate;
887
+ particle.alpha *= this._options.particleAlphaFadeout;
888
+ ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
889
+ ctx.beginPath();
890
+ ctx.arc(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, 0, 2 * Math.PI);
891
+ ctx.fill();
650
892
  }
651
- return [
652
- {
653
- from: 0,
654
- to: length,
655
- insert: patch.value
656
- }
657
- ];
658
893
  };
659
- var charPath = (textPath, candidatePath) => {
660
- if (candidatePath.length !== textPath.length + 1) {
661
- return null;
662
- }
663
- for (let i = 0; i < textPath.length; i++) {
664
- if (textPath[i] !== candidatePath[i]) {
665
- return null;
894
+ function throttle(callback, limit) {
895
+ let wait = false;
896
+ return function(...args) {
897
+ if (!wait) {
898
+ callback.apply(this, args);
899
+ wait = true;
900
+ setTimeout(() => {
901
+ wait = false;
902
+ }, limit);
666
903
  }
904
+ };
905
+ }
906
+ var getRGBComponents = (node, color) => {
907
+ if (typeof color === "function") {
908
+ return color();
667
909
  }
668
- const index = candidatePath[candidatePath.length - 1];
669
- if (typeof index === "number") {
670
- return index;
671
- }
672
- return null;
673
- };
674
-
675
- // packages/ui/react-ui-editor/src/hooks/automerge/semaphore.ts
676
- var PatchSemaphore = class {
677
- constructor(field) {
678
- this._inReconcile = false;
679
- this._queue = [];
680
- this.reconcile = (handle, view) => {
681
- if (this._inReconcile) {
682
- return;
683
- }
684
- this._inReconcile = true;
685
- const path = getPath(view.state, this._field);
686
- const oldHeads = getLastHeads(view.state, this._field);
687
- let selection = view.state.selection;
688
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
689
- const toInvert = transactions.slice().reverse();
690
- for (const tx of toInvert) {
691
- const inverted = tx.changes.invert(tx.startState.doc);
692
- selection = selection.map(inverted);
693
- view.dispatch({
694
- changes: inverted,
695
- annotations: reconcileAnnotationType.of(true)
696
- });
697
- }
698
- let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
699
- if (newHeads === null || newHeads === void 0) {
700
- newHeads = automerge.getHeads(handle.docSync());
701
- }
702
- const diff = automerge.equals(oldHeads, newHeads) ? [] : automerge.diff(handle.docSync(), oldHeads, newHeads);
703
- updateCodeMirror(view, selection, path, diff);
704
- view.dispatch({
705
- effects: updateHeads(newHeads),
706
- annotations: reconcileAnnotationType.of({})
707
- });
708
- this._inReconcile = false;
709
- };
710
- if (field !== void 0) {
711
- this._field = field;
910
+ const bgColor = getComputedStyle(node).color;
911
+ if (bgColor) {
912
+ const x = bgColor.match(/(\d+), (\d+), (\d+)/)?.slice(1);
913
+ if (x) {
914
+ return x;
712
915
  }
713
916
  }
917
+ return [
918
+ 50,
919
+ 50,
920
+ 50
921
+ ];
714
922
  };
715
-
716
- // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
717
- var effectType = StateEffect.define({});
718
- var updateHeads = (newHeads) => effectType.of({
719
- newHeads
720
- });
721
- var getLastHeads = (state, field) => state.field(field).lastHeads;
722
- var getPath = (state, field) => state.field(field).path;
723
- var semaphoreFacet = Facet.define({
724
- combine: (values) => values.at(-1)
725
- });
726
- var automergePlugin = (handle, path) => {
727
- const stateField = StateField.define({
728
- create: () => ({
729
- lastHeads: automerge2.getHeads(handle.docSync()),
730
- unreconciledTransactions: [],
731
- path: path.slice()
732
- }),
733
- update: (value, tr) => {
734
- const result = {
735
- lastHeads: value.lastHeads,
736
- unreconciledTransactions: value.unreconciledTransactions.slice(),
737
- path: path.slice()
738
- };
739
- let clearUnreconciled = false;
740
- for (const effect of tr.effects) {
741
- if (effect.is(effectType)) {
742
- result.lastHeads = effect.value.newHeads;
743
- clearUnreconciled = true;
744
- }
745
- }
746
- if (clearUnreconciled) {
747
- result.unreconciledTransactions = [];
748
- } else {
749
- if (!isReconcileTx(tr)) {
750
- result.unreconciledTransactions.push(tr);
751
- }
752
- }
753
- return result;
754
- }
755
- });
756
- const semaphore = new PatchSemaphore(stateField);
757
- const viewPlugin = ViewPlugin2.fromClass(class AutomergeCodemirrorViewPlugin {
758
- constructor(_view) {
759
- this._view = _view;
760
- this._handleChange = () => {
761
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
762
- };
763
- handle.addListener("change", this._handleChange);
764
- }
765
- update(update4) {
766
- if (update4.transactions.length > 0 && update4.transactions.some((tr) => !isReconcileTx(tr))) {
767
- queueMicrotask(() => {
768
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
769
- });
770
- }
771
- }
772
- destroy() {
773
- handle.addListener("change", this._handleChange);
774
- }
775
- });
776
- return {
777
- extension: [
778
- stateField,
779
- semaphoreFacet.of(semaphore),
780
- viewPlugin
781
- ]
782
- };
923
+ var random = (min, max) => {
924
+ if (!max) {
925
+ max = min;
926
+ min = 0;
927
+ }
928
+ return min + ~~(Math.random() * (max - min + 1));
783
929
  };
784
- var reconcileAnnotationType = Annotation.define();
785
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
786
930
 
787
- // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
931
+ // 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";
934
+ import { mx as mx2 } from "@dxos/react-ui-theme";
935
+
936
+ // packages/ui/react-ui-editor/src/styles/cursors.ts
788
937
  import * as random2 from "lib0/random";
789
938
  var cursorColors = [
790
939
  {
@@ -822,216 +971,134 @@ var cursorColors = [
822
971
  ];
823
972
  var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
824
973
 
825
- // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
826
- import * as decoding from "lib0/decoding";
827
- import * as encoding from "lib0/encoding";
828
- import { Observable } from "lib0/observable";
829
- import * as awarenessProtocol from "y-protocols/awareness";
830
- import { log as log2 } from "@dxos/log";
831
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
832
- var messageAwareness = 1;
833
- var messageQueryAwareness = 3;
834
- var SpaceAwarenessProvider = class extends Observable {
835
- constructor({ space, doc, channel, awareness }) {
836
- super();
837
- this._space = space;
838
- this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
839
- this._channel = channel;
840
- this._clientId = doc.clientID;
841
- this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
842
- this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
843
- if (typeof window !== "undefined") {
844
- window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
845
- } else if (typeof process !== "undefined") {
846
- process.on("exit", this._handleBeforeUnload.bind(this));
847
- }
848
- const encoderAwarenessQuery = encoding.createEncoder();
849
- encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
850
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
851
- const encoderAwarenessState = encoding.createEncoder();
852
- encoding.writeVarUint(encoderAwarenessState, messageAwareness);
853
- encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
854
- this._clientId
855
- ]));
856
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
857
- }
858
- get awareness() {
859
- return this._awareness;
860
- }
861
- _handleAwarenessUpdate({ added, updated, removed }, origin) {
862
- log2("awareness update", {
863
- added,
864
- updated,
865
- removed,
866
- origin
867
- }, {
868
- F: __dxlog_file3,
869
- L: 67,
870
- S: this,
871
- C: (f, a) => f(...a)
872
- });
873
- const changedClients = added.concat(updated).concat(removed);
874
- const encoderAwareness = encoding.createEncoder();
875
- encoding.writeVarUint(encoderAwareness, messageAwareness);
876
- encoding.writeVarUint8Array(encoderAwareness, awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients));
877
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
878
- }
879
- _handleSpaceMessage({ payload }) {
880
- log2("space message", payload, {
881
- F: __dxlog_file3,
882
- L: 79,
883
- S: this,
884
- C: (f, a) => f(...a)
885
- });
886
- const data = new Uint8Array(Array.from(Object.values(payload)));
887
- const encoder = this._readMessage(data);
888
- if (encoder) {
889
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
890
- }
891
- }
892
- _readMessage(message) {
893
- const decoder = decoding.createDecoder(message);
894
- const encoder = encoding.createEncoder();
895
- const messageType = decoding.readVarUint(decoder);
896
- let sendReply = false;
897
- switch (messageType) {
898
- case messageAwareness: {
899
- awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
900
- break;
901
- }
902
- case messageQueryAwareness: {
903
- encoding.writeVarUint(encoder, messageAwareness);
904
- encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
905
- sendReply = true;
906
- break;
907
- }
908
- default: {
909
- console.error("Invalid message:", messageType);
910
- return encoder;
911
- }
912
- }
913
- if (!sendReply) {
914
- return null;
915
- }
916
- return encoder;
917
- }
918
- _handleBeforeUnload() {
919
- awarenessProtocol.removeAwarenessStates(this._awareness, [
920
- this._clientId
921
- ], "window unload");
922
- }
974
+ // packages/ui/react-ui-editor/src/styles/markdown.ts
975
+ import { mx } from "@dxos/react-ui-theme";
976
+ var headings = {
977
+ 1: [
978
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
979
+ "-ml-[10px]"
980
+ ],
981
+ 2: [
982
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
983
+ "-ml-[8px]"
984
+ ],
985
+ 3: [
986
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
987
+ "-ml-[7px]"
988
+ ],
989
+ 4: [
990
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
991
+ "-ml-[6px]"
992
+ ],
993
+ 5: [
994
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
995
+ "-ml-[5px]"
996
+ ],
997
+ 6: [
998
+ "mbs-4 mbe-2 font-medium text-inherit no-underline",
999
+ "-ml-[4px]"
1000
+ ]
923
1001
  };
924
-
925
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
926
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
927
- var modelState = StateField2.define({
928
- create: () => void 0,
929
- update: (model) => model
930
- });
931
- var useTextModel = (props) => {
932
- const { identity, space, text } = props;
933
- const [model, setModel] = useState(() => createModel(props));
934
- useEffect(() => setModel(createModel(props)), [
935
- identity,
936
- space,
937
- text
938
- ]);
939
- return model;
1002
+ var heading = (level, readonly) => {
1003
+ const [style, offset] = headings[level];
1004
+ return mx(style, readonly && offset);
940
1005
  };
941
- var createModel = (props) => {
942
- const { text } = props;
943
- if (isActualAutomergeObject(text)) {
944
- return createAutomergeModel(props);
945
- } else {
946
- if (!text?.doc) {
947
- return void 0;
948
- }
949
- return createYjsModel(props);
1006
+ var light = "text-neutral-200 dark:text-neutral-800";
1007
+ var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
1008
+ var bold = "font-bold";
1009
+ var italic = "italic";
1010
+ var strikethrough = "line-through";
1011
+ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
1012
+ var codeMark = "font-mono text-primary-500";
1013
+ var inlineUrl = mx(code, "px-1");
1014
+ var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
1015
+
1016
+ // packages/ui/react-ui-editor/src/styles/tokens.ts
1017
+ import { tailwindConfig } from "@dxos/react-ui-theme";
1018
+ var tokens = tailwindConfig({}).theme;
1019
+
1020
+ // packages/ui/react-ui-editor/src/extensions/code.ts
1021
+ var CODE_REGEX = /```[\s\S]*?```/gs;
1022
+ var styles2 = EditorView4.baseTheme({
1023
+ "& .cm-codeblock": {
1024
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
1025
+ },
1026
+ '&light [aria-readonly="true"] .cm-codeblock': {
1027
+ background: get2(tokens, "extend.colors.neutral.50")
1028
+ },
1029
+ '&dark [aria-readonly="true"] .cm-codeblock': {
1030
+ background: get2(tokens, "extend.colors.neutral.850")
1031
+ },
1032
+ '& [aria-readonly="true"] .cm-codeblock': {
1033
+ display: "block",
1034
+ paddingInline: "4px !important"
1035
+ },
1036
+ '& [aria-readonly="true"] .cm-codeblock-first': {
1037
+ paddingTop: "4px !important",
1038
+ borderTopLeftRadius: "4px",
1039
+ borderTopRightRadius: "4px"
1040
+ },
1041
+ '& [aria-readonly="true"] .cm-codeblock-last': {
1042
+ paddingBottom: "4px !important",
1043
+ borderBottomLeftRadius: "4px",
1044
+ borderBottomRightRadius: "4px"
950
1045
  }
1046
+ });
1047
+ var getLineRange = (lines, from, to) => {
1048
+ const start = lines.findIndex((line) => line.from >= from);
1049
+ const end = lines.findIndex((line) => line.to >= to);
1050
+ return [
1051
+ start,
1052
+ end
1053
+ ];
951
1054
  };
952
- var createYjsModel = ({ identity, space, text }) => {
953
- invariant2(text?.doc && text?.content, void 0, {
954
- F: __dxlog_file4,
955
- L: 97,
956
- S: void 0,
957
- A: [
958
- "text?.doc && text?.content",
959
- ""
960
- ]
961
- });
962
- const provider = space ? new SpaceAwarenessProvider({
963
- space,
964
- doc: text.doc,
965
- channel: `yjs.awareness.${text.id}`
966
- }) : void 0;
967
- const model = {
968
- id: text.doc.guid,
969
- content: text.content,
970
- text: () => text.content.toString(),
971
- // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
972
- getCursorFromRange: (range) => {
973
- const from = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content, range.from));
974
- const to = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content, range.to, -1));
975
- return [
976
- arrayToString(from),
977
- arrayToString(to)
978
- ].join(":");
979
- },
980
- getRangeFromCursor: (cursor) => {
981
- invariant2(text.doc, void 0, {
982
- F: __dxlog_file4,
983
- L: 113,
984
- S: void 0,
985
- A: [
986
- "text.doc",
987
- ""
988
- ]
989
- });
990
- const parts = cursor.split(":");
991
- const from = Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(parts[0])), text.doc);
992
- const to = Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(parts[1])), text.doc);
993
- return from && to ? {
994
- from: from.index,
995
- to: to.index
996
- } : void 0;
997
- },
998
- extension: [
999
- yCollab(text.content, provider?.awareness),
1000
- modelState.init(() => model)
1001
- ],
1002
- awareness: provider?.awareness,
1003
- peer: identity ? {
1004
- id: identity.identityKey.toHex(),
1005
- name: identity.profile?.displayName
1006
- } : void 0
1007
- };
1008
- return model;
1009
- };
1010
- var createAutomergeModel = ({ identity, text }) => {
1011
- const obj = text;
1012
- const doc = getRawDoc(obj, [
1013
- obj.field
1014
- ]);
1015
- const model = {
1016
- id: obj.id,
1017
- content: doc,
1018
- text: () => get2(doc.handle.docSync(), doc.path),
1019
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
1020
- extension: [
1021
- automergePlugin(doc.handle, doc.path),
1022
- modelState.init(() => model)
1023
- ],
1024
- peer: identity ? {
1025
- id: identity.identityKey.toHex(),
1026
- name: identity.profile?.displayName
1027
- } : void 0
1055
+ var code2 = () => {
1056
+ const getDecorations = (view) => {
1057
+ 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
+ }
1070
+ }
1071
+ }
1072
+ return Decoration2.set(decorations);
1028
1073
  };
1029
- return model;
1074
+ return [
1075
+ ViewPlugin3.fromClass(class {
1076
+ constructor(view) {
1077
+ this.decorations = getDecorations(view);
1078
+ }
1079
+ update(update4) {
1080
+ if (update4.docChanged || update4.viewportChanged) {
1081
+ this.decorations = getDecorations(update4.view);
1082
+ }
1083
+ }
1084
+ }, {
1085
+ decorations: (value) => value.decorations
1086
+ }),
1087
+ styles2
1088
+ ];
1030
1089
  };
1031
1090
 
1032
1091
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1033
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1034
- var styles2 = EditorView4.baseTheme({
1092
+ 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";
1095
+ import sortBy from "lodash.sortby";
1096
+ import { debounce } from "@dxos/async";
1097
+ import { invariant as invariant2 } from "@dxos/invariant";
1098
+ import { log as log2 } from "@dxos/log";
1099
+ 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({
1035
1102
  "& .cm-bookmark": {
1036
1103
  cursor: "pointer",
1037
1104
  margin: "4px",
@@ -1049,10 +1116,10 @@ var styles2 = EditorView4.baseTheme({
1049
1116
  }
1050
1117
  });
1051
1118
  var marks = {
1052
- highlight: Decoration2.mark({
1119
+ highlight: Decoration3.mark({
1053
1120
  class: "cm-comment"
1054
1121
  }),
1055
- highlightActive: Decoration2.mark({
1122
+ highlightActive: Decoration3.mark({
1056
1123
  class: "cm-comment-active"
1057
1124
  })
1058
1125
  };
@@ -1074,11 +1141,12 @@ var setFocus = (view, thread) => {
1074
1141
  var setCommentRange = StateEffect2.define();
1075
1142
  var setSelection = StateEffect2.define();
1076
1143
  var setCommentState = StateEffect2.define();
1077
- var commentsStateField = StateField3.define({
1144
+ var commentsStateField = StateField2.define({
1078
1145
  create: () => ({
1079
1146
  ranges: []
1080
1147
  }),
1081
1148
  update: (value, tr) => {
1149
+ const cursorConverter3 = tr.state.facet(CursorConverter);
1082
1150
  for (const effect of tr.effects) {
1083
1151
  if (effect.is(setSelection)) {
1084
1152
  return {
@@ -1087,9 +1155,9 @@ var commentsStateField = StateField3.define({
1087
1155
  };
1088
1156
  }
1089
1157
  if (effect.is(setCommentRange)) {
1090
- const { model, comments: comments2 } = effect.value;
1158
+ const { comments: comments2 } = effect.value;
1091
1159
  const ranges = comments2.map((comment) => {
1092
- const range = model.getRangeFromCursor(comment.cursor);
1160
+ const range = getRangeFromCursor(cursorConverter3, comment.cursor);
1093
1161
  return range && {
1094
1162
  ...comment,
1095
1163
  ...range
@@ -1107,7 +1175,7 @@ var commentsStateField = StateField3.define({
1107
1175
  return value;
1108
1176
  }
1109
1177
  });
1110
- var highlightDecorations = EditorView4.decorations.compute([
1178
+ var highlightDecorations = EditorView5.decorations.compute([
1111
1179
  commentsStateField
1112
1180
  ], (state) => {
1113
1181
  const { active, ranges } = state.field(commentsStateField);
@@ -1125,17 +1193,17 @@ var highlightDecorations = EditorView4.decorations.compute([
1125
1193
  return marks.highlight.range(range.from, range.to);
1126
1194
  }
1127
1195
  }).filter(nonNullable) ?? [];
1128
- return Decoration2.set(decorations);
1196
+ return Decoration3.set(decorations);
1129
1197
  });
1130
- var BookmarkWidget = class extends WidgetType {
1198
+ var BookmarkWidget = class extends WidgetType2 {
1131
1199
  constructor(_anchor, _id, _handleClick) {
1132
1200
  super();
1133
1201
  this._anchor = _anchor;
1134
1202
  this._id = _id;
1135
1203
  this._handleClick = _handleClick;
1136
- invariant3(this._id, void 0, {
1137
- F: __dxlog_file5,
1138
- L: 167,
1204
+ invariant2(this._id, void 0, {
1205
+ F: __dxlog_file3,
1206
+ L: 169,
1139
1207
  S: this,
1140
1208
  A: [
1141
1209
  "this._id",
@@ -1159,9 +1227,10 @@ var BookmarkWidget = class extends WidgetType {
1159
1227
  var comments = (options = {}) => {
1160
1228
  const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1161
1229
  const createCommentThread = (view) => {
1162
- invariant3(options.onCreate, void 0, {
1163
- F: __dxlog_file5,
1164
- L: 221,
1230
+ const cursorConverter3 = view.state.facet(CursorConverter);
1231
+ invariant2(options.onCreate, void 0, {
1232
+ F: __dxlog_file3,
1233
+ L: 225,
1165
1234
  S: void 0,
1166
1235
  A: [
1167
1236
  "options.onCreate",
@@ -1172,8 +1241,7 @@ var comments = (options = {}) => {
1172
1241
  if (from === to) {
1173
1242
  return false;
1174
1243
  }
1175
- const model = view.state.field(modelState);
1176
- const relPos = model?.getCursorFromRange?.({
1244
+ const relPos = getCursorFromRange(cursorConverter3, {
1177
1245
  from,
1178
1246
  to
1179
1247
  });
@@ -1205,7 +1273,7 @@ var comments = (options = {}) => {
1205
1273
  }
1206
1274
  return false;
1207
1275
  };
1208
- const bookmarksViewPlugin = ViewPlugin3.fromClass(class BookmarkViewPlugin {
1276
+ const bookmarksViewPlugin = ViewPlugin4.fromClass(class BookmarkViewPlugin {
1209
1277
  static {
1210
1278
  // Match markdown footnotes (option).
1211
1279
  // https://www.markdownguide.org/extended-syntax/#footnotes
@@ -1213,7 +1281,7 @@ var comments = (options = {}) => {
1213
1281
  regexp: /\[\^(\w+)\]/g,
1214
1282
  decoration: (match, view, pos) => {
1215
1283
  const id = match[1];
1216
- return Decoration2.replace({
1284
+ return Decoration3.replace({
1217
1285
  id,
1218
1286
  widget: new BookmarkWidget(pos, id)
1219
1287
  });
@@ -1228,15 +1296,15 @@ var comments = (options = {}) => {
1228
1296
  }
1229
1297
  }, {
1230
1298
  decorations: (instance) => instance.bookmarks,
1231
- provide: (plugin) => EditorView4.atomicRanges.of((view) => {
1232
- return view.plugin(plugin)?.bookmarks || Decoration2.none;
1299
+ provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1300
+ return view.plugin(plugin)?.bookmarks || Decoration3.none;
1233
1301
  })
1234
1302
  });
1235
1303
  return [
1236
1304
  bookmarksViewPlugin,
1237
1305
  commentsStateField,
1238
1306
  highlightDecorations,
1239
- styles2,
1307
+ styles3,
1240
1308
  //
1241
1309
  // Keymap.
1242
1310
  //
@@ -1279,37 +1347,28 @@ var comments = (options = {}) => {
1279
1347
  // Monitor cursor movement and text updates.
1280
1348
  // TODO(burdon): Is there a better (finer grained) way to do this?
1281
1349
  //
1282
- EditorView4.updateListener.of(({ view, state, startState, changes }) => {
1350
+ EditorView5.updateListener.of(({ view, state, changes }) => {
1351
+ const cursorConverter3 = view.state.facet(CursorConverter);
1283
1352
  {
1284
1353
  let mod = false;
1285
- const model = state.field(modelState);
1286
1354
  const { active, ranges } = state.field(commentsStateField);
1287
1355
  changes.iterChanges((from, to, from2, to2) => {
1288
- invariant3(model, void 0, {
1289
- F: __dxlog_file5,
1290
- L: 350,
1291
- S: void 0,
1292
- A: [
1293
- "model",
1294
- ""
1295
- ]
1296
- });
1297
1356
  ranges.forEach((range) => {
1298
1357
  if (from2 === to2) {
1299
- const newRange = model.getRangeFromCursor(range.cursor);
1358
+ const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1300
1359
  if (!newRange || newRange.to - newRange.from === 0) {
1301
- log3.info("deleted comment", {
1360
+ log2.info("deleted comment", {
1302
1361
  thread: range.id
1303
1362
  }, {
1304
- F: __dxlog_file5,
1305
- L: 356,
1363
+ F: __dxlog_file3,
1364
+ L: 359,
1306
1365
  S: void 0,
1307
1366
  C: (f, a) => f(...a)
1308
1367
  });
1309
1368
  }
1310
1369
  }
1311
1370
  if (from <= range.to) {
1312
- const newRange = model.getRangeFromCursor(range.cursor);
1371
+ const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1313
1372
  Object.assign(range, newRange);
1314
1373
  mod = true;
1315
1374
  }
@@ -1358,18 +1417,35 @@ var comments = (options = {}) => {
1358
1417
  })
1359
1418
  ];
1360
1419
  };
1420
+ var getCursorFromRange = (cursorConverter3, range) => {
1421
+ const from = cursorConverter3.toCursor(range.from);
1422
+ const to = cursorConverter3.toCursor(range.to, -1);
1423
+ return [
1424
+ from,
1425
+ to
1426
+ ].join(":");
1427
+ };
1428
+ var getRangeFromCursor = (cursorConverter3, cursor) => {
1429
+ const parts = cursor.split(":");
1430
+ const from = cursorConverter3.fromCursor(parts[0]);
1431
+ const to = cursorConverter3.fromCursor(parts[1]);
1432
+ return from && to ? {
1433
+ from,
1434
+ to
1435
+ } : void 0;
1436
+ };
1361
1437
 
1362
1438
  // packages/ui/react-ui-editor/src/extensions/hr.ts
1363
- import { Decoration as Decoration3, EditorView as EditorView5, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1439
+ import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin5, WidgetType as WidgetType3 } from "@codemirror/view";
1364
1440
  import get4 from "lodash.get";
1365
- var styles3 = EditorView5.baseTheme({
1441
+ var styles4 = EditorView6.baseTheme({
1366
1442
  "& .cm-hr": {
1367
1443
  // TODO(burdon): ???
1368
1444
  // Note that block-level decorations should not have vertical margins,
1369
1445
  borderBottom: `1px solid ${get4(tokens, "extend.colors.neutral.200")}`
1370
1446
  }
1371
1447
  });
1372
- var HorizontalRuleWidget = class extends WidgetType2 {
1448
+ var HorizontalRuleWidget = class extends WidgetType3 {
1373
1449
  constructor(_pos) {
1374
1450
  super();
1375
1451
  this._pos = _pos;
@@ -1385,13 +1461,13 @@ var HorizontalRuleWidget = class extends WidgetType2 {
1385
1461
  };
1386
1462
  var placeholderMatcher = new MatchDecorator2({
1387
1463
  regexp: /^---$/g,
1388
- decoration: (match, view, pos) => Decoration3.replace({
1464
+ decoration: (match, view, pos) => Decoration4.replace({
1389
1465
  widget: new HorizontalRuleWidget(pos)
1390
1466
  })
1391
1467
  });
1392
1468
  var hr = () => [
1393
- styles3,
1394
- ViewPlugin4.fromClass(class {
1469
+ styles4,
1470
+ ViewPlugin5.fromClass(class {
1395
1471
  constructor(view) {
1396
1472
  this.rules = placeholderMatcher.createDeco(view);
1397
1473
  }
@@ -1401,21 +1477,21 @@ var hr = () => [
1401
1477
  }, {
1402
1478
  decorations: (instance) => instance.rules,
1403
1479
  // TODO(burdon): Is this really atomic (cursor can move into ---).
1404
- provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1405
- return view.plugin(plugin)?.rules || Decoration3.none;
1480
+ provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1481
+ return view.plugin(plugin)?.rules || Decoration4.none;
1406
1482
  })
1407
1483
  })
1408
1484
  ];
1409
1485
 
1410
1486
  // packages/ui/react-ui-editor/src/extensions/image.ts
1411
1487
  import { syntaxTree } from "@codemirror/language";
1412
- import { RangeSetBuilder, StateField as StateField4 } from "@codemirror/state";
1413
- import { Decoration as Decoration4, EditorView as EditorView6, WidgetType as WidgetType3 } from "@codemirror/view";
1488
+ import { RangeSetBuilder, StateField as StateField3 } from "@codemirror/state";
1489
+ import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1414
1490
  var image = (options = {}) => {
1415
- return StateField4.define({
1491
+ return StateField3.define({
1416
1492
  create: (state) => update(state, options),
1417
1493
  update: (_, tr) => update(tr.state, options),
1418
- provide: (field) => EditorView6.decorations.from(field)
1494
+ provide: (field) => EditorView7.decorations.from(field)
1419
1495
  });
1420
1496
  };
1421
1497
  var update = (state, options) => {
@@ -1428,7 +1504,7 @@ var update = (state, options) => {
1428
1504
  if (urlNode) {
1429
1505
  const hide = state.readOnly || cursor < node.from || cursor > node.to;
1430
1506
  const url = state.sliceDoc(urlNode.from, urlNode.to);
1431
- builder.add(hide ? node.from : node.to, node.to, Decoration4.replace({
1507
+ builder.add(hide ? node.from : node.to, node.to, Decoration5.replace({
1432
1508
  block: true,
1433
1509
  widget: new ImageWidget(url)
1434
1510
  }));
@@ -1438,7 +1514,7 @@ var update = (state, options) => {
1438
1514
  });
1439
1515
  return builder.finish();
1440
1516
  };
1441
- var ImageWidget = class extends WidgetType3 {
1517
+ var ImageWidget = class extends WidgetType4 {
1442
1518
  constructor(_url) {
1443
1519
  super();
1444
1520
  this._url = _url;
@@ -1457,16 +1533,16 @@ var ImageWidget = class extends WidgetType3 {
1457
1533
 
1458
1534
  // packages/ui/react-ui-editor/src/extensions/link.ts
1459
1535
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1460
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField5 } from "@codemirror/state";
1461
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4, hoverTooltip as hoverTooltip2 } from "@codemirror/view";
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";
1462
1538
  import { tooltipContent } from "@dxos/react-ui-theme";
1463
1539
  var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1464
1540
  var link = (options = {}) => {
1465
1541
  const extensions = [
1466
- StateField5.define({
1542
+ StateField4.define({
1467
1543
  create: (state) => update2(state, options),
1468
1544
  update: (_, tr) => update2(tr.state, options),
1469
- provide: (field) => EditorView7.decorations.from(field)
1545
+ provide: (field) => EditorView8.decorations.from(field)
1470
1546
  })
1471
1547
  ];
1472
1548
  if (options.onHover) {
@@ -1517,7 +1593,7 @@ var link = (options = {}) => {
1517
1593
  }
1518
1594
  return extensions;
1519
1595
  };
1520
- var LinkButton = class extends WidgetType4 {
1596
+ var LinkButton = class extends WidgetType5 {
1521
1597
  constructor(_pos, _url, _onAttach) {
1522
1598
  super();
1523
1599
  this._pos = _pos;
@@ -1533,7 +1609,7 @@ var LinkButton = class extends WidgetType4 {
1533
1609
  return el;
1534
1610
  }
1535
1611
  };
1536
- var LinkText = class extends WidgetType4 {
1612
+ var LinkText = class extends WidgetType5 {
1537
1613
  constructor(_text, _url) {
1538
1614
  super();
1539
1615
  this._text = _text;
@@ -1576,12 +1652,12 @@ var update2 = (state, options) => {
1576
1652
  return false;
1577
1653
  }
1578
1654
  if (state.readOnly || cursor < node.from || cursor > node.to) {
1579
- builder.add(node.from, node.to, Decoration5.replace({
1655
+ builder.add(node.from, node.to, Decoration6.replace({
1580
1656
  widget: new LinkText(text, options.link ? url : void 0)
1581
1657
  }));
1582
1658
  }
1583
1659
  if (options.onRender) {
1584
- builder.add(node.to, node.to, Decoration5.replace({
1660
+ builder.add(node.to, node.to, Decoration6.replace({
1585
1661
  widget: new LinkButton(node.from, url, options.onRender)
1586
1662
  }));
1587
1663
  }
@@ -1593,14 +1669,14 @@ var update2 = (state, options) => {
1593
1669
  };
1594
1670
 
1595
1671
  // packages/ui/react-ui-editor/src/extensions/listener.ts
1596
- import { EditorView as EditorView8 } from "@codemirror/view";
1672
+ import { EditorView as EditorView9 } from "@codemirror/view";
1597
1673
  var listener = ({ onFocus, onChange }) => {
1598
1674
  const extensions = [];
1599
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1675
+ onFocus && extensions.push(EditorView9.focusChangeEffect.of((_, focused) => {
1600
1676
  onFocus(focused);
1601
1677
  return null;
1602
1678
  }));
1603
- onChange && extensions.push(EditorView8.updateListener.of((update4) => {
1679
+ onChange && extensions.push(EditorView9.updateListener.of((update4) => {
1604
1680
  onChange(update4.state.doc.toString());
1605
1681
  }));
1606
1682
  return extensions;
@@ -1615,7 +1691,7 @@ import { languages } from "@codemirror/language-data";
1615
1691
  import { searchKeymap } from "@codemirror/search";
1616
1692
  import { EditorState } from "@codemirror/state";
1617
1693
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1618
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1694
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView10, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1619
1695
 
1620
1696
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1621
1697
  import { markdownLanguage } from "@codemirror/lang-markdown";
@@ -1818,7 +1894,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1818
1894
  return [
1819
1895
  EditorState.allowMultipleSelections.of(true),
1820
1896
  EditorState.tabSize.of(2),
1821
- EditorView9.lineWrapping,
1897
+ EditorView10.lineWrapping,
1822
1898
  customKeymap(),
1823
1899
  bracketMatching2(),
1824
1900
  closeBrackets2(),
@@ -1896,13 +1972,13 @@ var mermaid = () => {
1896
1972
 
1897
1973
  // packages/ui/react-ui-editor/src/extensions/table.ts
1898
1974
  import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1899
- import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField6 } from "@codemirror/state";
1900
- import { Decoration as Decoration6, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
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";
1901
1977
  var table = (options = {}) => {
1902
- return StateField6.define({
1978
+ return StateField5.define({
1903
1979
  create: (state) => update3(state, options),
1904
1980
  update: (_, tr) => update3(tr.state, options),
1905
- provide: (field) => EditorView10.decorations.from(field)
1981
+ provide: (field) => EditorView11.decorations.from(field)
1906
1982
  });
1907
1983
  };
1908
1984
  var update3 = (state, options) => {
@@ -1946,17 +2022,17 @@ var update3 = (state, options) => {
1946
2022
  });
1947
2023
  tables.forEach((table2) => {
1948
2024
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1949
- hide && builder.add(table2.from, table2.to, Decoration6.replace({
2025
+ hide && builder.add(table2.from, table2.to, Decoration7.replace({
1950
2026
  block: true,
1951
2027
  widget: new TableWidget(table2)
1952
2028
  }));
1953
- builder.add(table2.from, table2.to, Decoration6.mark({
2029
+ builder.add(table2.from, table2.to, Decoration7.mark({
1954
2030
  class: "cm-table"
1955
2031
  }));
1956
2032
  });
1957
2033
  return builder.finish();
1958
2034
  };
1959
- var TableWidget = class extends WidgetType5 {
2035
+ var TableWidget = class extends WidgetType6 {
1960
2036
  constructor(_table) {
1961
2037
  super();
1962
2038
  this._table = _table;
@@ -1991,14 +2067,14 @@ var TableWidget = class extends WidgetType5 {
1991
2067
  };
1992
2068
 
1993
2069
  // packages/ui/react-ui-editor/src/extensions/tasklist.ts
1994
- import { EditorView as EditorView11, Decoration as Decoration7, WidgetType as WidgetType6, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1995
- var styles4 = EditorView11.baseTheme({
2070
+ import { EditorView as EditorView12, Decoration as Decoration8, WidgetType as WidgetType7, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2071
+ var styles5 = EditorView12.baseTheme({
1996
2072
  "& .cm-task-item": {
1997
2073
  paddingLeft: "4px",
1998
2074
  paddingRight: "8px"
1999
2075
  }
2000
2076
  });
2001
- var CheckboxWidget = class extends WidgetType6 {
2077
+ var CheckboxWidget = class extends WidgetType7 {
2002
2078
  constructor(_pos, _checked, _indent, _onCheck) {
2003
2079
  super();
2004
2080
  this._pos = _pos;
@@ -2038,7 +2114,7 @@ var tasklist = (options = {}) => {
2038
2114
  decoration: (match, view, pos) => {
2039
2115
  const indent = Math.floor(match[1].length / 2);
2040
2116
  const checked = match[2] === "x" || match[2] === "X";
2041
- return Decoration7.replace({
2117
+ return Decoration8.replace({
2042
2118
  widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2043
2119
  const idx = pos + match[0].indexOf("[") + 1;
2044
2120
  view.dispatch({
@@ -2056,7 +2132,7 @@ var tasklist = (options = {}) => {
2056
2132
  });
2057
2133
  }
2058
2134
  });
2059
- const tasks = ViewPlugin5.fromClass(class {
2135
+ const tasks = ViewPlugin6.fromClass(class {
2060
2136
  constructor(view) {
2061
2137
  this.tasks = taskMatcher.createDeco(view);
2062
2138
  }
@@ -2065,13 +2141,13 @@ var tasklist = (options = {}) => {
2065
2141
  }
2066
2142
  }, {
2067
2143
  decorations: (value) => value.tasks,
2068
- provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2069
- return view.plugin(plugin)?.tasks || Decoration7.none;
2144
+ provide: (plugin) => EditorView12.atomicRanges.of((view) => {
2145
+ return view.plugin(plugin)?.tasks || Decoration8.none;
2070
2146
  })
2071
2147
  });
2072
2148
  return [
2073
2149
  tasks,
2074
- styles4
2150
+ styles5
2075
2151
  ];
2076
2152
  };
2077
2153
 
@@ -2132,6 +2208,27 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2132
2208
  ];
2133
2209
  };
2134
2210
 
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
+
2135
2232
  // packages/ui/react-ui-editor/src/themes/default.ts
2136
2233
  import get6 from "lodash.get";
2137
2234
  var defaultTheme = {
@@ -2414,8 +2511,8 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2414
2511
  const tabsterDOMAttribute = useFocusableGroup({
2415
2512
  tabBehavior: "limited"
2416
2513
  });
2417
- const [root, setRoot] = useState2(null);
2418
- const [{ state = void 0, view = void 0 } = {}, setEditor] = useState2();
2514
+ const [root, setRoot] = useState(null);
2515
+ const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
2419
2516
  useImperativeHandle(forwardedRef, () => ({
2420
2517
  root,
2421
2518
  state,
@@ -2425,10 +2522,10 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2425
2522
  state,
2426
2523
  root
2427
2524
  ]);
2428
- const { awareness, peer } = model;
2429
- useEffect2(() => {
2430
- if (awareness && peer) {
2431
- awareness.setLocalStateField("user", {
2525
+ const { awareness: awareness2, peer } = model;
2526
+ useEffect(() => {
2527
+ if (awareness2 && peer) {
2528
+ awareness2.setLocalStateField("user", {
2432
2529
  name: peer.name ?? generateName(peer.id),
2433
2530
  color: getColorForValue({
2434
2531
  value: peer.id,
@@ -2442,11 +2539,11 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2442
2539
  });
2443
2540
  }
2444
2541
  }, [
2445
- awareness,
2542
+ awareness2,
2446
2543
  peer,
2447
2544
  themeMode
2448
2545
  ]);
2449
- useEffect2(() => {
2546
+ useEffect(() => {
2450
2547
  if (view && comments2?.length) {
2451
2548
  view.dispatch({
2452
2549
  effects: setCommentRange.of({
@@ -2459,7 +2556,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2459
2556
  view,
2460
2557
  comments2
2461
2558
  ]);
2462
- useEffect2(() => {
2559
+ useEffect(() => {
2463
2560
  if (!root) {
2464
2561
  return;
2465
2562
  }
@@ -2471,9 +2568,9 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2471
2568
  editorMode === "vim" && vim(),
2472
2569
  // Theme.
2473
2570
  // TODO(burdon): Make theme configurable.
2474
- EditorView12.baseTheme(defaultTheme),
2475
- EditorView12.theme(slots?.editor?.theme ?? {}),
2476
- EditorView12.darkTheme.of(themeMode === "dark"),
2571
+ EditorView13.baseTheme(defaultTheme),
2572
+ EditorView13.theme(slots?.editor?.theme ?? {}),
2573
+ EditorView13.darkTheme.of(themeMode === "dark"),
2477
2574
  // Storage and replication.
2478
2575
  model.extension,
2479
2576
  // Custom.
@@ -2483,7 +2580,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2483
2580
  view?.destroy();
2484
2581
  setEditor({
2485
2582
  state: state2,
2486
- view: new EditorView12({
2583
+ view: new EditorView13({
2487
2584
  state: state2,
2488
2585
  parent: root
2489
2586
  })
@@ -2577,23 +2674,370 @@ var defaultMarkdownSlots = {
2577
2674
  theme: markdownTheme
2578
2675
  }
2579
2676
  };
2677
+
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
+ // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2684
+ import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2685
+ import { Context as Context2 } from "@dxos/context";
2686
+ import { invariant as invariant3 } from "@dxos/invariant";
2687
+ 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";
2689
+ var DEBOUNCE_INTERVAL = 100;
2690
+ var SpaceAwarenessProvider = class {
2691
+ constructor(params) {
2692
+ this.remoteStateChange = new Event2();
2693
+ this._remoteStates = /* @__PURE__ */ new Map();
2694
+ this._space = params.space;
2695
+ this._channel = params.channel;
2696
+ this._peerId = params.peerId;
2697
+ this._info = params.info;
2698
+ }
2699
+ open() {
2700
+ this._ctx = new Context2();
2701
+ this._postTask = new DeferredTask(this._ctx, async () => {
2702
+ if (this._localState) {
2703
+ await this._space.postMessage(this._channel, {
2704
+ kind: "post",
2705
+ state: this._localState
2706
+ });
2707
+ await sleep(DEBOUNCE_INTERVAL);
2708
+ }
2709
+ });
2710
+ const unsubscribe = this._space.listen(this._channel, (message) => {
2711
+ switch (message.payload.kind) {
2712
+ case "query": {
2713
+ this._handleQueryMessage();
2714
+ break;
2715
+ }
2716
+ case "post": {
2717
+ this._handlePostMessage(message.payload);
2718
+ break;
2719
+ }
2720
+ }
2721
+ });
2722
+ this._ctx.onDispose(unsubscribe);
2723
+ void this._space.postMessage(this._channel, {
2724
+ kind: "query"
2725
+ }).catch((err) => {
2726
+ log3.debug("failed to query awareness", {
2727
+ err
2728
+ }, {
2729
+ F: __dxlog_file4,
2730
+ L: 85,
2731
+ S: this,
2732
+ C: (f, a) => f(...a)
2733
+ });
2734
+ });
2735
+ }
2736
+ close() {
2737
+ void this._ctx?.dispose();
2738
+ this._ctx = void 0;
2739
+ this._postTask = void 0;
2740
+ }
2741
+ update(position) {
2742
+ invariant3(this._postTask, void 0, {
2743
+ F: __dxlog_file4,
2744
+ L: 96,
2745
+ S: this,
2746
+ A: [
2747
+ "this._postTask",
2748
+ ""
2749
+ ]
2750
+ });
2751
+ this._localState = {
2752
+ peerId: this._peerId,
2753
+ position,
2754
+ info: this._info
2755
+ };
2756
+ this._postTask.schedule();
2757
+ }
2758
+ getRemoteStates() {
2759
+ return Array.from(this._remoteStates.values());
2760
+ }
2761
+ _handleQueryMessage() {
2762
+ invariant3(this._postTask, void 0, {
2763
+ F: __dxlog_file4,
2764
+ L: 111,
2765
+ S: this,
2766
+ A: [
2767
+ "this._postTask",
2768
+ ""
2769
+ ]
2770
+ });
2771
+ this._postTask.schedule();
2772
+ }
2773
+ _handlePostMessage(message) {
2774
+ invariant3(message.kind === "post", void 0, {
2775
+ F: __dxlog_file4,
2776
+ L: 116,
2777
+ S: this,
2778
+ A: [
2779
+ "message.kind === 'post'",
2780
+ ""
2781
+ ]
2782
+ });
2783
+ this._remoteStates.set(message.state.peerId, message.state);
2784
+ this.remoteStateChange.emit();
2785
+ }
2786
+ };
2787
+
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";
2792
+
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;
2849
+ },
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
+ }
2953
+ };
2954
+
2955
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2956
+ var modelState = StateField6.define({
2957
+ create: () => void 0,
2958
+ update: (model) => model
2959
+ });
2960
+ var useTextModel = (props) => {
2961
+ const { identity, space, text } = props;
2962
+ const [model, setModel] = useState2(() => createModel(props));
2963
+ useEffect2(() => setModel(createModel(props)), [
2964
+ identity,
2965
+ space,
2966
+ text
2967
+ ]);
2968
+ return model;
2969
+ };
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 }) => {
2983
+ const obj = text;
2984
+ const doc = getRawDoc(obj, [
2985
+ obj.field
2986
+ ]);
2987
+ const awarenessProvider = space && new SpaceAwarenessProvider({
2988
+ space,
2989
+ channel: `automerge.awareness.${obj.id}`,
2990
+ info: {
2991
+ displayName: identity?.profile?.displayName ?? "",
2992
+ color: cursorColor.color,
2993
+ lightColor: cursorColor.light
2994
+ },
2995
+ peerId: identity?.identityKey.toHex() ?? "Anonymous"
2996
+ });
2997
+ const model = {
2998
+ id: obj.id,
2999
+ content: doc,
3000
+ text: () => get7(doc.handle.docSync(), doc.path),
3001
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
3002
+ extension: [
3003
+ modelState.init(() => model),
3004
+ automerge3({
3005
+ handle: doc.handle,
3006
+ path: doc.path
3007
+ }),
3008
+ awarenessProvider && AwarenessProvider.of(awarenessProvider),
3009
+ awareness()
3010
+ ].filter(isNotNullOrUndefined),
3011
+ peer: identity ? {
3012
+ id: identity.identityKey.toHex(),
3013
+ name: identity.profile?.displayName
3014
+ } : void 0
3015
+ };
3016
+ return model;
3017
+ };
2580
3018
  export {
3019
+ AwarenessProvider,
2581
3020
  BaseTextEditor,
2582
3021
  Doc,
2583
3022
  EditorModes,
2584
3023
  MarkdownEditor,
3024
+ RemoteSelectionsDecorator,
2585
3025
  SpaceAwarenessProvider,
2586
3026
  TextEditor,
2587
3027
  TextKind,
3028
+ YAwarenessProvider,
2588
3029
  YText,
2589
3030
  YXmlFragment,
2590
3031
  autocomplete,
3032
+ automerge3 as automerge,
3033
+ awareness,
2591
3034
  basicBundle,
2592
3035
  blast,
2593
3036
  code2 as code,
2594
3037
  codeTheme,
2595
3038
  comments,
2596
- cursorColor,
3039
+ createAutomergeModel,
3040
+ createYjsModel,
2597
3041
  defaultMarkdownSlots,
2598
3042
  defaultOptions,
2599
3043
  defaultSlots,
@@ -2619,6 +3063,7 @@ export {
2619
3063
  tasklist,
2620
3064
  textTheme,
2621
3065
  typewriter,
2622
- useTextModel
3066
+ useTextModel,
3067
+ yjs
2623
3068
  };
2624
3069
  //# sourceMappingURL=index.mjs.map