@dxos/react-ui-editor 0.3.11-main.a996413 → 0.3.11-main.b225571

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 (145) hide show
  1. package/dist/lib/browser/index.mjs +1393 -1052
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +9 -5
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +12 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -3
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -1
  11. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  14. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  15. package/dist/types/src/{hooks/yjs → components/TextEditor}/yjs.stories.d.ts +1 -1
  16. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
  17. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  18. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  19. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  20. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  21. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  22. package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
  23. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  24. package/dist/types/src/extensions/automerge/index.d.ts +4 -0
  25. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  26. package/dist/types/src/{hooks → extensions}/automerge/semaphore.d.ts +4 -4
  27. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  28. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  29. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  30. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  31. package/dist/types/src/extensions/awareness.d.ts +46 -0
  32. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  33. package/dist/types/src/extensions/code.d.ts.map +1 -1
  34. package/dist/types/src/extensions/comments.d.ts +2 -2
  35. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  36. package/dist/types/src/extensions/cursor.d.ts +25 -0
  37. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  38. package/dist/types/src/extensions/hr.d.ts.map +1 -1
  39. package/dist/types/src/extensions/image.d.ts.map +1 -1
  40. package/dist/types/src/extensions/index.d.ts +4 -1
  41. package/dist/types/src/extensions/index.d.ts.map +1 -1
  42. package/dist/types/src/extensions/link.d.ts.map +1 -1
  43. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  44. package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
  45. package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
  46. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
  47. package/dist/types/src/extensions/yjs/index.d.ts +2 -0
  48. package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
  49. package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
  50. package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
  51. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
  52. package/dist/types/src/hooks/automerge.d.ts +3 -0
  53. package/dist/types/src/hooks/automerge.d.ts.map +1 -0
  54. package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
  55. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  56. package/dist/types/src/hooks/index.d.ts +2 -0
  57. package/dist/types/src/hooks/index.d.ts.map +1 -1
  58. package/dist/types/src/hooks/useTextModel.d.ts +4 -8
  59. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  60. package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
  61. package/dist/types/src/hooks/yjs.d.ts.map +1 -0
  62. package/dist/types/src/index.d.ts +3 -0
  63. package/dist/types/src/index.d.ts.map +1 -1
  64. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  65. package/dist/types/src/styles/index.d.ts +1 -0
  66. package/dist/types/src/styles/index.d.ts.map +1 -1
  67. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  68. package/dist/types/src/styles/tokens.d.ts +1 -0
  69. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  70. package/dist/types/src/testing/replicator.d.ts +2 -2
  71. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  72. package/dist/types/src/themes/default.d.ts.map +1 -1
  73. package/dist/types/src/util.d.ts.map +1 -0
  74. package/package.json +23 -21
  75. package/src/components/TextEditor/MarkdownEditor.stories.tsx +25 -23
  76. package/src/components/TextEditor/TextEditor.stories.tsx +13 -34
  77. package/src/components/TextEditor/TextEditor.tsx +69 -65
  78. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +66 -6
  79. package/src/components/TextEditor/codemirror.stories.ts +3 -2
  80. package/src/components/TextEditor/comments.stories.tsx +315 -0
  81. package/src/{hooks/yjs → components/TextEditor}/yjs.stories.tsx +1 -1
  82. package/src/extensions/autocomplete.ts +18 -17
  83. package/src/{hooks/automerge/plugin.ts → extensions/automerge/automerge.ts} +20 -58
  84. package/src/extensions/automerge/cursor.ts +52 -0
  85. package/src/extensions/automerge/defs.ts +62 -0
  86. package/src/extensions/automerge/index.ts +7 -0
  87. package/src/{hooks → extensions}/automerge/semaphore.ts +14 -12
  88. package/src/{hooks → extensions}/automerge/update-automerge.ts +5 -6
  89. package/src/{hooks → extensions}/automerge/update-codemirror.ts +13 -5
  90. package/src/extensions/awareness.ts +309 -0
  91. package/src/extensions/code.ts +14 -7
  92. package/src/extensions/comments.ts +29 -109
  93. package/src/extensions/cursor.ts +46 -0
  94. package/src/extensions/hr.ts +4 -5
  95. package/src/extensions/image.ts +40 -22
  96. package/src/extensions/index.ts +4 -1
  97. package/src/extensions/link.ts +86 -88
  98. package/src/extensions/markdown/highlight.ts +3 -12
  99. package/src/extensions/table.ts +10 -4
  100. package/src/extensions/tasklist.ts +3 -5
  101. package/src/extensions/yjs/cursor.ts +22 -0
  102. package/src/extensions/yjs/index.ts +5 -0
  103. package/src/{hooks → extensions}/yjs/yjs.test.ts +1 -25
  104. package/src/extensions/yjs/yjs.ts +16 -0
  105. package/src/hooks/automerge.ts +52 -0
  106. package/src/hooks/awareness-provider.ts +120 -0
  107. package/src/hooks/index.ts +2 -0
  108. package/src/hooks/useTextModel.ts +20 -97
  109. package/src/hooks/{yjs/space-provider.ts → yjs.ts} +49 -19
  110. package/src/index.ts +3 -0
  111. package/src/styles/index.ts +1 -0
  112. package/src/styles/markdown.ts +0 -2
  113. package/src/styles/tokens.ts +3 -0
  114. package/src/testing/replicator.ts +10 -15
  115. package/src/themes/default.ts +52 -71
  116. package/dist/types/src/extensions/mermaid.d.ts +0 -3
  117. package/dist/types/src/extensions/mermaid.d.ts.map +0 -1
  118. package/dist/types/src/extensions/util.d.ts.map +0 -1
  119. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  120. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  121. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  122. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  123. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  124. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  125. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  126. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
  127. package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
  128. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
  129. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
  130. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  131. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  132. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  133. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  134. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  135. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
  136. package/src/extensions/mermaid.ts +0 -11
  137. package/src/hooks/automerge/handle.ts +0 -14
  138. package/src/hooks/automerge/index.ts +0 -6
  139. package/src/hooks/yjs/index.ts +0 -6
  140. /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
  141. /package/dist/types/src/{hooks → extensions}/yjs/yjs.test.d.ts +0 -0
  142. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  143. /package/dist/types/src/{extensions/util.d.ts → util.d.ts} +0 -0
  144. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  145. /package/src/{extensions/util.ts → util.ts} +0 -0
@@ -11,51 +11,608 @@ import { EditorView as EditorView12 } from "@codemirror/view";
11
11
  import { useFocusableGroup } from "@fluentui/react-tabster";
12
12
  import { vim } from "@replit/codemirror-vim";
13
13
  import defaultsDeep2 from "lodash.defaultsdeep";
14
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState as useState2 } from "react";
14
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState, useRef } from "react";
15
15
  import { generateName } from "@dxos/display-name";
16
16
  import { useThemeContext } from "@dxos/react-ui";
17
17
  import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
18
18
 
19
19
  // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
20
20
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
21
+ import { markdownLanguage } from "@codemirror/lang-markdown";
21
22
  import { keymap } from "@codemirror/view";
22
23
  var autocomplete = ({ onSearch }) => {
23
24
  return [
24
25
  // https://codemirror.net/docs/ref/#view.keymap
25
26
  // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
27
+ // TODO(burdon): Set custom keymap.
26
28
  keymap.of(completionKeymap),
27
29
  // https://codemirror.net/examples/autocompletion
28
30
  // https://codemirror.net/docs/ref/#autocomplete.autocompletion
29
31
  autocompletion({
30
- // TODO(burdon): Set custom keymap.
31
- // defaultKeymap: false,
32
+ defaultKeymap: false,
32
33
  // Don't start unless key press.
33
- activateOnTyping: false,
34
- // TODO(burdon): Option to create new page?
35
- // TODO(burdon): Optional decoration via addToOptions
36
- override: [
37
- (context) => {
38
- const match = context.matchBefore(/\w*/);
39
- if (!match || match.from === match.to && !context.explicit) {
40
- return null;
41
- }
42
- return {
43
- from: match.from,
44
- options: onSearch(match.text.toLowerCase())
45
- };
34
+ activateOnTyping: false
35
+ }),
36
+ // TODO(burdon): Option to create new page?
37
+ // TODO(burdon): Optional decoration via addToOptions
38
+ markdownLanguage.data.of({
39
+ autocomplete: (context) => {
40
+ const match = context.matchBefore(/\w*/);
41
+ if (!match || match.from === match.to && !context.explicit) {
42
+ return null;
46
43
  }
47
- ]
44
+ return {
45
+ from: match.from,
46
+ options: onSearch(match.text.toLowerCase())
47
+ };
48
+ }
48
49
  })
49
50
  ];
50
51
  };
51
52
 
53
+ // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
54
+ import { Facet as Facet2, StateField } from "@codemirror/state";
55
+ import { ViewPlugin } from "@codemirror/view";
56
+ import { next as A } from "@dxos/automerge/automerge";
57
+
58
+ // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
59
+ import get from "lodash.get";
60
+ import { next as automerge } from "@dxos/automerge/automerge";
61
+ var cursorConverter = (handle, path) => ({
62
+ // TODO(burdon): Handle assoc to associate with a previous character.
63
+ toCursor: (pos) => {
64
+ const doc = handle.docSync();
65
+ if (!doc) {
66
+ return "";
67
+ }
68
+ const value = get(doc, path);
69
+ if (typeof value === "string" && value.length <= pos) {
70
+ return "end";
71
+ }
72
+ return automerge.getCursor(doc, path.slice(), pos);
73
+ },
74
+ fromCursor: (cursor) => {
75
+ if (cursor === "") {
76
+ return 0;
77
+ }
78
+ const doc = handle.docSync();
79
+ if (!doc) {
80
+ return 0;
81
+ }
82
+ if (cursor === "end") {
83
+ const value = get(doc, path);
84
+ if (typeof value === "string") {
85
+ return value.length;
86
+ } else {
87
+ return 0;
88
+ }
89
+ }
90
+ return automerge.getCursorPosition(doc, path.slice(), cursor);
91
+ }
92
+ });
93
+
94
+ // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
95
+ import { Annotation, StateEffect } from "@codemirror/state";
96
+ var effectType = StateEffect.define({});
97
+ var updateHeads = (newHeads) => effectType.of({
98
+ newHeads
99
+ });
100
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
101
+ var getPath = (state, field) => state.field(field).path;
102
+ var reconcileAnnotationType = Annotation.define();
103
+ var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
104
+
105
+ // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
106
+ import { next as automerge2 } from "@dxos/automerge/automerge";
107
+
108
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
109
+ import { next as am } from "@dxos/automerge/automerge";
110
+ var updateAutomerge = (field, handle, transactions, state) => {
111
+ const { lastHeads, path } = state.field(field);
112
+ let hasChanges = false;
113
+ for (const tr of transactions) {
114
+ tr.changes.iterChanges(() => {
115
+ hasChanges = true;
116
+ });
117
+ }
118
+ if (!hasChanges) {
119
+ return void 0;
120
+ }
121
+ const newHeads = handle.changeAt(lastHeads, (doc) => {
122
+ for (const tr of transactions) {
123
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
124
+ am.splice(doc, path, fromA, toA - fromA, inserted.toString());
125
+ });
126
+ }
127
+ });
128
+ return newHeads ?? void 0;
129
+ };
130
+
131
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-codemirror.ts
132
+ import { ChangeSet } from "@codemirror/state";
133
+ var updateCodeMirror = (view, selection, target, patches) => {
134
+ for (const patch of patches) {
135
+ const changeSpec = handlePatch(patch, target, view.state);
136
+ if (changeSpec != null) {
137
+ const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
138
+ selection = selection.map(changeSet, 1);
139
+ view.dispatch({
140
+ changes: changeSet,
141
+ annotations: reconcileAnnotationType.of({})
142
+ });
143
+ }
144
+ }
145
+ view.dispatch({
146
+ selection,
147
+ annotations: reconcileAnnotationType.of({})
148
+ });
149
+ };
150
+ var handlePatch = (patch, target, state) => {
151
+ if (patch.action === "insert") {
152
+ return handleInsert(target, patch);
153
+ } else if (patch.action === "splice") {
154
+ return handleSplice(target, patch);
155
+ } else if (patch.action === "del") {
156
+ return handleDel(target, patch);
157
+ } else if (patch.action === "put") {
158
+ return handlePut(target, patch, state);
159
+ } else {
160
+ return null;
161
+ }
162
+ };
163
+ var handleInsert = (target, patch) => {
164
+ const index = charPath(target, patch.path);
165
+ if (index == null) {
166
+ return [];
167
+ }
168
+ const text = patch.values.map((v) => v ? v.toString() : "").join("");
169
+ return [
170
+ {
171
+ from: index,
172
+ to: index,
173
+ insert: text
174
+ }
175
+ ];
176
+ };
177
+ var handleSplice = (target, patch) => {
178
+ const index = charPath(target, patch.path);
179
+ if (index == null) {
180
+ return [];
181
+ }
182
+ return [
183
+ {
184
+ from: index,
185
+ insert: patch.value
186
+ }
187
+ ];
188
+ };
189
+ var handleDel = (target, patch) => {
190
+ const index = charPath(target, patch.path);
191
+ if (index == null) {
192
+ return [];
193
+ }
194
+ const length = patch.length || 1;
195
+ return [
196
+ {
197
+ from: index,
198
+ to: index + length
199
+ }
200
+ ];
201
+ };
202
+ var handlePut = (target, patch, state) => {
203
+ const index = charPath(target, [
204
+ ...patch.path,
205
+ 0
206
+ ]);
207
+ if (index == null) {
208
+ return [];
209
+ }
210
+ const length = state.doc.length;
211
+ if (typeof patch.value !== "string") {
212
+ return [];
213
+ }
214
+ return [
215
+ {
216
+ from: 0,
217
+ to: length,
218
+ insert: patch.value
219
+ }
220
+ ];
221
+ };
222
+ var charPath = (textPath, candidatePath) => {
223
+ if (candidatePath.length !== textPath.length + 1) {
224
+ return null;
225
+ }
226
+ for (let i = 0; i < textPath.length; i++) {
227
+ if (textPath[i] !== candidatePath[i]) {
228
+ return null;
229
+ }
230
+ }
231
+ const index = candidatePath[candidatePath.length - 1];
232
+ if (typeof index === "number") {
233
+ return index;
234
+ }
235
+ return null;
236
+ };
237
+
238
+ // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
239
+ var PatchSemaphore = class {
240
+ constructor(_field) {
241
+ this._field = _field;
242
+ this._inReconcile = false;
243
+ this._queue = [];
244
+ this.reconcile = (handle, view) => {
245
+ if (this._inReconcile) {
246
+ return;
247
+ }
248
+ this._inReconcile = true;
249
+ const path = getPath(view.state, this._field);
250
+ const oldHeads = getLastHeads(view.state, this._field);
251
+ let selection = view.state.selection;
252
+ const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
253
+ const toInvert = transactions.slice().reverse();
254
+ for (const tx of toInvert) {
255
+ const inverted = tx.changes.invert(tx.startState.doc);
256
+ selection = selection.map(inverted);
257
+ view.dispatch({
258
+ changes: inverted,
259
+ annotations: reconcileAnnotationType.of(true)
260
+ });
261
+ }
262
+ let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
263
+ if (newHeads === null || newHeads === void 0) {
264
+ newHeads = automerge2.getHeads(handle.docSync());
265
+ }
266
+ const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(handle.docSync(), oldHeads, newHeads);
267
+ updateCodeMirror(view, selection, path, diff);
268
+ view.dispatch({
269
+ effects: updateHeads(newHeads),
270
+ annotations: reconcileAnnotationType.of({})
271
+ });
272
+ this._inReconcile = false;
273
+ };
274
+ }
275
+ };
276
+
277
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
278
+ import { Facet } from "@codemirror/state";
279
+ var defaultCursorConverter = {
280
+ toCursor: (position) => position.toString(),
281
+ fromCursor: (cursor) => parseInt(cursor)
282
+ };
283
+ var Cursor = class {
284
+ static {
285
+ this.converter = Facet.define({
286
+ combine: (providers) => providers[0] ?? defaultCursorConverter
287
+ });
288
+ }
289
+ static {
290
+ this.getCursorFromRange = (cursorConverter3, range) => {
291
+ const from = cursorConverter3.toCursor(range.from);
292
+ const to = cursorConverter3.toCursor(range.to, -1);
293
+ return [
294
+ from,
295
+ to
296
+ ].join(":");
297
+ };
298
+ }
299
+ static {
300
+ this.getRangeFromCursor = (cursorConverter3, cursor) => {
301
+ const parts = cursor.split(":");
302
+ const from = cursorConverter3.fromCursor(parts[0]);
303
+ const to = cursorConverter3.fromCursor(parts[1]);
304
+ return from !== void 0 && to !== void 0 ? {
305
+ from,
306
+ to
307
+ } : void 0;
308
+ };
309
+ }
310
+ };
311
+
312
+ // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
313
+ var semaphoreFacet = Facet2.define({
314
+ combine: (values) => values.at(-1)
315
+ });
316
+ var automerge3 = ({ handle, path }) => {
317
+ const stateField = StateField.define({
318
+ create: () => ({
319
+ lastHeads: A.getHeads(handle.docSync()),
320
+ unreconciledTransactions: [],
321
+ path: path.slice()
322
+ }),
323
+ update: (value, tr) => {
324
+ const result = {
325
+ lastHeads: value.lastHeads,
326
+ unreconciledTransactions: value.unreconciledTransactions.slice(),
327
+ path: path.slice()
328
+ };
329
+ let clearUnreconciled = false;
330
+ for (const effect of tr.effects) {
331
+ if (effect.is(effectType)) {
332
+ result.lastHeads = effect.value.newHeads;
333
+ clearUnreconciled = true;
334
+ }
335
+ }
336
+ if (clearUnreconciled) {
337
+ result.unreconciledTransactions = [];
338
+ } else {
339
+ if (!isReconcileTx(tr)) {
340
+ result.unreconciledTransactions.push(tr);
341
+ }
342
+ }
343
+ return result;
344
+ }
345
+ });
346
+ const semaphore = new PatchSemaphore(stateField);
347
+ const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
348
+ constructor(_view) {
349
+ this._view = _view;
350
+ this._handleChange = () => {
351
+ this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
352
+ };
353
+ handle.addListener("change", this._handleChange);
354
+ }
355
+ update(update2) {
356
+ if (update2.transactions.length > 0 && update2.transactions.some((tr) => !isReconcileTx(tr))) {
357
+ queueMicrotask(() => {
358
+ this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
359
+ });
360
+ }
361
+ }
362
+ destroy() {
363
+ handle.addListener("change", this._handleChange);
364
+ }
365
+ });
366
+ return {
367
+ extension: [
368
+ Cursor.converter.of(cursorConverter(handle, path)),
369
+ semaphoreFacet.of(semaphore),
370
+ stateField,
371
+ viewPlugin
372
+ ]
373
+ };
374
+ };
375
+
376
+ // packages/ui/react-ui-editor/src/extensions/awareness.ts
377
+ import { Annotation as Annotation2, Facet as Facet3, RangeSet } from "@codemirror/state";
378
+ import { Decoration, EditorView, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
379
+ import { Event } from "@dxos/async";
380
+ import { Context } from "@dxos/context";
381
+ var dummyProvider = {
382
+ remoteStateChange: new Event(),
383
+ open: () => {
384
+ },
385
+ close: () => {
386
+ },
387
+ update: () => {
388
+ },
389
+ getRemoteStates: () => []
390
+ };
391
+ var AwarenessProvider = Facet3.define({
392
+ combine: (providers) => providers[0] ?? dummyProvider
393
+ });
394
+ var RemoteSelectionChangedAnnotation = Annotation2.define();
395
+ var awareness = (provider = dummyProvider) => {
396
+ return [
397
+ AwarenessProvider.of(provider),
398
+ ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
399
+ decorations: (value) => value.decorations
400
+ }),
401
+ styles
402
+ ];
403
+ };
404
+ var RemoteSelectionsDecorator = class {
405
+ constructor(view) {
406
+ this.decorations = RangeSet.of([]);
407
+ this._ctx = new Context();
408
+ this._lastAnchor = void 0;
409
+ this._lastHead = void 0;
410
+ this._cursorConverter = view.state.facet(Cursor.converter);
411
+ this._provider = view.state.facet(AwarenessProvider);
412
+ this._provider.open();
413
+ this._provider.remoteStateChange.on(this._ctx, () => {
414
+ view.dispatch({
415
+ annotations: [
416
+ RemoteSelectionChangedAnnotation.of([])
417
+ ]
418
+ });
419
+ });
420
+ }
421
+ destroy() {
422
+ void this._ctx.dispose();
423
+ this._provider.close();
424
+ }
425
+ update(update2) {
426
+ this._updateLocalSelection(update2);
427
+ this._updateRemoteSelections(update2);
428
+ }
429
+ _updateLocalSelection(update2) {
430
+ const hasFocus = update2.view.hasFocus && update2.view.dom.ownerDocument.hasFocus();
431
+ const { anchor = void 0, head = void 0 } = hasFocus ? update2.state.selection.main : {};
432
+ if (this._lastAnchor === anchor && this._lastHead === head) {
433
+ return;
434
+ }
435
+ this._lastAnchor = anchor;
436
+ this._lastHead = head;
437
+ this._provider.update(anchor !== void 0 && head !== void 0 ? {
438
+ anchor: this._cursorConverter.toCursor(anchor),
439
+ head: this._cursorConverter.toCursor(head)
440
+ } : void 0);
441
+ }
442
+ _updateRemoteSelections(update2) {
443
+ const decorations = [];
444
+ const awarenessStates = this._provider.getRemoteStates();
445
+ for (const state of awarenessStates) {
446
+ const anchor = state.position?.anchor ? this._cursorConverter.fromCursor(state.position.anchor) : null;
447
+ const head = state.position?.head ? this._cursorConverter.fromCursor(state.position.head) : null;
448
+ if (anchor == null || head == null) {
449
+ continue;
450
+ }
451
+ const start = Math.min(Math.min(anchor, head), update2.view.state.doc.length);
452
+ const end = Math.min(Math.max(anchor, head), update2.view.state.doc.length);
453
+ const startLine = update2.view.state.doc.lineAt(start);
454
+ const endLine = update2.view.state.doc.lineAt(end);
455
+ const color = state.info.color ?? "#30bced";
456
+ const lightColor = state.info.lightColor ?? color + "33";
457
+ if (startLine.number === endLine.number) {
458
+ decorations.push({
459
+ from: start,
460
+ to: end,
461
+ value: Decoration.mark({
462
+ attributes: {
463
+ style: `background-color: ${lightColor}`
464
+ },
465
+ class: "cm-collab-selection"
466
+ })
467
+ });
468
+ } else {
469
+ decorations.push({
470
+ from: start,
471
+ to: startLine.from + startLine.length,
472
+ value: Decoration.mark({
473
+ attributes: {
474
+ style: `background-color: ${color}`
475
+ },
476
+ class: "cm-collab-selection"
477
+ })
478
+ });
479
+ decorations.push({
480
+ from: endLine.from,
481
+ to: end,
482
+ value: Decoration.mark({
483
+ attributes: {
484
+ style: `background-color: ${color}`
485
+ },
486
+ class: "cm-collab-selection"
487
+ })
488
+ });
489
+ for (let i = startLine.number + 1; i < endLine.number; i++) {
490
+ const linePos = update2.view.state.doc.line(i).from;
491
+ decorations.push({
492
+ from: linePos,
493
+ to: linePos,
494
+ value: Decoration.line({
495
+ attributes: {
496
+ style: `background-color: ${color}`,
497
+ class: "cm-collab-selectionLine"
498
+ }
499
+ })
500
+ });
501
+ }
502
+ }
503
+ decorations.push({
504
+ from: head,
505
+ to: head,
506
+ value: Decoration.widget({
507
+ side: head - anchor > 0 ? -1 : 1,
508
+ block: false,
509
+ widget: new RemoteCaretWidget(state.info.displayName ?? "Anonymous", color)
510
+ })
511
+ });
512
+ }
513
+ this.decorations = Decoration.set(decorations, true);
514
+ }
515
+ };
516
+ var RemoteCaretWidget = class extends WidgetType {
517
+ constructor(_name, _color) {
518
+ super();
519
+ this._name = _name;
520
+ this._color = _color;
521
+ }
522
+ toDOM() {
523
+ const span = document.createElement("span");
524
+ span.className = "cm-collab-selectionCaret";
525
+ span.style.backgroundColor = this._color;
526
+ span.style.borderColor = this._color;
527
+ const dot = document.createElement("div");
528
+ dot.className = "cm-collab-selectionCaretDot";
529
+ const info = document.createElement("div");
530
+ info.className = "cm-collab-selectionInfo";
531
+ info.innerText = this._name;
532
+ span.appendChild(document.createTextNode("\u2060"));
533
+ span.appendChild(dot);
534
+ span.appendChild(document.createTextNode("\u2060"));
535
+ span.appendChild(info);
536
+ span.appendChild(document.createTextNode("\u2060"));
537
+ return span;
538
+ }
539
+ updateDOM() {
540
+ return false;
541
+ }
542
+ eq(widget) {
543
+ return widget._color === this._color;
544
+ }
545
+ get estimatedHeight() {
546
+ return -1;
547
+ }
548
+ ignoreEvent() {
549
+ return true;
550
+ }
551
+ };
552
+ var styles = EditorView.baseTheme({
553
+ ".cm-collab-selection": {},
554
+ ".cm-collab-selectionLine": {
555
+ padding: 0,
556
+ margin: "0px 2px 0px 4px"
557
+ },
558
+ ".cm-collab-selectionCaret": {
559
+ position: "relative",
560
+ borderLeft: "1px solid black",
561
+ borderRight: "1px solid black",
562
+ marginLeft: "-1px",
563
+ marginRight: "-1px",
564
+ boxSizing: "border-box",
565
+ display: "inline"
566
+ },
567
+ ".cm-collab-selectionCaretDot": {
568
+ borderRadius: "50%",
569
+ position: "absolute",
570
+ width: ".4em",
571
+ height: ".4em",
572
+ top: "-.2em",
573
+ left: "-.2em",
574
+ backgroundColor: "inherit",
575
+ transition: "transform .3s ease-in-out",
576
+ boxSizing: "border-box"
577
+ },
578
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
579
+ transformOrigin: "bottom center",
580
+ transform: "scale(0)"
581
+ },
582
+ ".cm-collab-selectionInfo": {
583
+ position: "absolute",
584
+ top: "-1.05em",
585
+ left: "-1px",
586
+ fontSize: ".75em",
587
+ fontFamily: "serif",
588
+ fontStyle: "normal",
589
+ fontWeight: "normal",
590
+ lineHeight: "normal",
591
+ userSelect: "none",
592
+ color: "white",
593
+ paddingLeft: "2px",
594
+ paddingRight: "2px",
595
+ zIndex: 101,
596
+ transition: "opacity .3s ease-in-out",
597
+ backgroundColor: "inherit",
598
+ // these should be separate
599
+ opacity: 0,
600
+ transitionDelay: "0s",
601
+ whiteSpace: "nowrap"
602
+ },
603
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
604
+ opacity: 1,
605
+ transitionDelay: "0s"
606
+ }
607
+ });
608
+
52
609
  // packages/ui/react-ui-editor/src/extensions/basic.ts
53
610
  import { closeBrackets } from "@codemirror/autocomplete";
54
611
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
55
612
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
56
- import { drawSelection, EditorView, placeholder } from "@codemirror/view";
613
+ import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
57
614
  var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
58
- EditorView.lineWrapping,
615
+ EditorView2.lineWrapping,
59
616
  // TODO(burdon): Compare with minimalSetup.
60
617
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
61
618
  bracketMatching(),
@@ -67,7 +624,7 @@ var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
67
624
  ].filter(Boolean);
68
625
 
69
626
  // packages/ui/react-ui-editor/src/extensions/blast.ts
70
- import { EditorView as EditorView2, keymap as keymap2 } from "@codemirror/view";
627
+ import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
71
628
  import defaultsDeep from "lodash.defaultsdeep";
72
629
  import { invariant } from "@dxos/invariant";
73
630
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
@@ -115,12 +672,12 @@ var blast = (options = defaultOptions) => {
115
672
  };
116
673
  return [
117
674
  // Cursor moved.
118
- EditorView2.updateListener.of((update4) => {
119
- if (blaster?.node !== update4.view.scrollDOM) {
675
+ EditorView3.updateListener.of((update2) => {
676
+ if (blaster?.node !== update2.view.scrollDOM) {
120
677
  if (blaster) {
121
678
  blaster.destroy();
122
679
  }
123
- blaster = new Blaster(update4.view.scrollDOM, defaultsDeep({
680
+ blaster = new Blaster(update2.view.scrollDOM, defaultsDeep({
124
681
  particleGravity: 0.2,
125
682
  particleShrinkRate: 0.995,
126
683
  color: () => [
@@ -134,10 +691,10 @@ var blast = (options = defaultOptions) => {
134
691
  } else {
135
692
  blaster.resize();
136
693
  }
137
- const current = update4.state.selection.main.head;
694
+ const current = update2.state.selection.main.head;
138
695
  if (current !== last) {
139
696
  last = current;
140
- const { element, point } = getPoint(update4.view, current - 1);
697
+ const { element, point } = getPoint(update2.view, current - 1);
141
698
  if (element && point) {
142
699
  blaster.spawn({
143
700
  element,
@@ -345,446 +902,42 @@ function throttle(callback, limit) {
345
902
  if (!wait) {
346
903
  callback.apply(this, args);
347
904
  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();
357
- }
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
- }
364
- }
365
- return [
366
- 50,
367
- 50,
368
- 50
369
- ];
370
- };
371
- var random = (min, max) => {
372
- if (!max) {
373
- max = min;
374
- min = 0;
375
- }
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
- };
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(",")
435
- },
436
- '&light [aria-readonly="true"] .cm-codeblock': {
437
- background: get(tokens, "extend.colors.neutral.50")
438
- },
439
- '&dark [aria-readonly="true"] .cm-codeblock': {
440
- background: get(tokens, "extend.colors.neutral.850")
441
- },
442
- '& [aria-readonly="true"] .cm-codeblock': {
443
- display: "block",
444
- paddingInline: "4px !important"
445
- },
446
- '& [aria-readonly="true"] .cm-codeblock-first': {
447
- paddingTop: "4px !important",
448
- borderTopLeftRadius: "4px",
449
- borderTopRightRadius: "4px"
450
- },
451
- '& [aria-readonly="true"] .cm-codeblock-last': {
452
- paddingBottom: "4px !important",
453
- borderBottomLeftRadius: "4px",
454
- borderBottomRightRadius: "4px"
455
- }
456
- });
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
- ];
464
- };
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);
483
- };
484
- return [
485
- ViewPlugin.fromClass(class {
486
- constructor(view) {
487
- this.decorations = getDecorations(view);
488
- }
489
- update(update4) {
490
- if (update4.docChanged || update4.viewportChanged) {
491
- this.decorations = getDecorations(update4.view);
492
- }
493
- }
494
- }, {
495
- decorations: (value) => value.decorations
496
- }),
497
- styles
498
- ];
499
- };
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, {
519
- F: __dxlog_file2,
520
- L: 16,
521
- S: void 0,
522
- C: (f, a) => f(...a)
523
- });
524
- }
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;
553
- });
554
- }
555
- if (!hasChanges) {
556
- return void 0;
557
- }
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
- });
563
- }
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
- });
580
- }
581
- }
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
- };
600
- var handleInsert = (target, patch) => {
601
- const index = charPath(target, patch.path);
602
- if (index == null) {
603
- return [];
604
- }
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
- };
614
- var handleSplice = (target, patch) => {
615
- const index = charPath(target, patch.path);
616
- if (index == null) {
617
- return [];
618
- }
619
- return [
620
- {
621
- from: index,
622
- insert: patch.value
905
+ setTimeout(() => {
906
+ wait = false;
907
+ }, limit);
623
908
  }
624
- ];
625
- };
626
- var handleDel = (target, patch) => {
627
- const index = charPath(target, patch.path);
628
- if (index == null) {
629
- return [];
909
+ };
910
+ }
911
+ var getRGBComponents = (node, color) => {
912
+ if (typeof color === "function") {
913
+ return color();
630
914
  }
631
- const length = patch.length || 1;
632
- return [
633
- {
634
- from: index,
635
- to: index + length
915
+ const bgColor = getComputedStyle(node).color;
916
+ if (bgColor) {
917
+ const x = bgColor.match(/(\d+), (\d+), (\d+)/)?.slice(1);
918
+ if (x) {
919
+ return x;
636
920
  }
637
- ];
638
- };
639
- var handlePut = (target, patch, state) => {
640
- const index = charPath(target, [
641
- ...patch.path,
642
- 0
643
- ]);
644
- if (index == null) {
645
- return [];
646
- }
647
- const length = state.doc.length;
648
- if (typeof patch.value !== "string") {
649
- return [];
650
921
  }
651
922
  return [
652
- {
653
- from: 0,
654
- to: length,
655
- insert: patch.value
656
- }
923
+ 50,
924
+ 50,
925
+ 50
657
926
  ];
658
927
  };
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;
666
- }
667
- }
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;
712
- }
928
+ var random = (min, max) => {
929
+ if (!max) {
930
+ max = min;
931
+ min = 0;
713
932
  }
933
+ return min + ~~(Math.random() * (max - min + 1));
714
934
  };
715
935
 
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
- };
783
- };
784
- var reconcileAnnotationType = Annotation.define();
785
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
936
+ // packages/ui/react-ui-editor/src/extensions/code.ts
937
+ import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
938
+ import { mx as mx2 } from "@dxos/react-ui-theme";
786
939
 
787
- // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
940
+ // packages/ui/react-ui-editor/src/styles/cursors.ts
788
941
  import * as random2 from "lib0/random";
789
942
  var cursorColors = [
790
943
  {
@@ -811,248 +964,181 @@ var cursorColors = [
811
964
  color: "#9ac2c9",
812
965
  light: "#9ac2c933"
813
966
  },
814
- {
815
- color: "#8acb88",
816
- light: "#8acb8833"
817
- },
818
- {
819
- color: "#1be7ff",
820
- light: "#1be7ff33"
821
- }
822
- ];
823
- var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
824
-
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");
967
+ {
968
+ color: "#8acb88",
969
+ light: "#8acb8833"
970
+ },
971
+ {
972
+ color: "#1be7ff",
973
+ light: "#1be7ff33"
922
974
  }
975
+ ];
976
+ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
977
+
978
+ // packages/ui/react-ui-editor/src/styles/markdown.ts
979
+ import { mx } from "@dxos/react-ui-theme";
980
+ var headings = {
981
+ 1: [
982
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
983
+ "-ml-[10px]"
984
+ ],
985
+ 2: [
986
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
987
+ "-ml-[8px]"
988
+ ],
989
+ 3: [
990
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
991
+ "-ml-[7px]"
992
+ ],
993
+ 4: [
994
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
995
+ "-ml-[6px]"
996
+ ],
997
+ 5: [
998
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
999
+ "-ml-[5px]"
1000
+ ],
1001
+ 6: [
1002
+ "mbs-4 mbe-2 font-medium text-inherit no-underline",
1003
+ "-ml-[4px]"
1004
+ ]
1005
+ };
1006
+ var heading = (level, readonly) => {
1007
+ const [style, offset] = headings[level];
1008
+ return mx(style, readonly && offset);
923
1009
  };
1010
+ var light = "text-neutral-200 dark:text-neutral-800";
1011
+ var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
1012
+ var bold = "font-bold";
1013
+ var italic = "italic";
1014
+ var strikethrough = "line-through";
1015
+ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
1016
+ var codeMark = "font-mono text-primary-500";
1017
+ var inlineUrl = mx(code, "px-1");
1018
+ var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
924
1019
 
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
1020
+ // packages/ui/react-ui-editor/src/styles/tokens.ts
1021
+ import get2 from "lodash.get";
1022
+ import { tailwindConfig } from "@dxos/react-ui-theme";
1023
+ var tokens = tailwindConfig({}).theme;
1024
+ var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue);
1025
+
1026
+ // packages/ui/react-ui-editor/src/extensions/code.ts
1027
+ var CODE_REGEX = /```[\s\S]*?```/gs;
1028
+ var styles2 = EditorView4.baseTheme({
1029
+ "& .cm-codeblock": {
1030
+ fontFamily: getToken("fontFamily.mono", []).join(",")
1031
+ },
1032
+ '&light [aria-readonly="true"] .cm-codeblock': {
1033
+ background: getToken("extend.colors.neutral.50")
1034
+ },
1035
+ '&dark [aria-readonly="true"] .cm-codeblock': {
1036
+ background: getToken("extend.colors.neutral.850")
1037
+ },
1038
+ '& [aria-readonly="true"] .cm-codeblock': {
1039
+ display: "block",
1040
+ paddingInline: "4px !important"
1041
+ },
1042
+ '& [aria-readonly="true"] .cm-codeblock-first': {
1043
+ paddingTop: "4px !important",
1044
+ borderTopLeftRadius: "4px",
1045
+ borderTopRightRadius: "4px"
1046
+ },
1047
+ '& [aria-readonly="true"] .cm-codeblock-last': {
1048
+ paddingBottom: "4px !important",
1049
+ borderBottomLeftRadius: "4px",
1050
+ borderBottomRightRadius: "4px"
1051
+ }
930
1052
  });
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;
1053
+ var getLineRange = (lines, from, to) => {
1054
+ const start = lines.findIndex((line) => line.from >= from);
1055
+ const end = lines.findIndex((line) => line.to >= to);
1056
+ return [
1057
+ start,
1058
+ end
1059
+ ];
940
1060
  };
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;
1061
+ var code2 = () => {
1062
+ const getDecorations = (view) => {
1063
+ const decorations = [];
1064
+ if (!view.hasFocus || view.state.readOnly) {
1065
+ const text = view.state.doc.sliceString(0);
1066
+ const matches = text.matchAll(CODE_REGEX);
1067
+ const blocks = view.viewportLineBlocks;
1068
+ for (const match of matches) {
1069
+ const range = getLineRange(blocks, match.index, match.index + match[0].length);
1070
+ for (let i = range[0]; i <= range[1]; i++) {
1071
+ const block = blocks[i];
1072
+ decorations.push(Decoration2.line({
1073
+ class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1074
+ }).range(block.from));
1075
+ }
1076
+ }
948
1077
  }
949
- return createYjsModel(props);
950
- }
951
- };
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
1078
+ return Decoration2.set(decorations);
1007
1079
  };
1008
- return model;
1080
+ return [
1081
+ ViewPlugin3.fromClass(class {
1082
+ constructor(view) {
1083
+ this.hasFocus = false;
1084
+ this.decorations = getDecorations(view);
1085
+ }
1086
+ update(update2) {
1087
+ if (
1088
+ // TODO(burdon): Generalize for other extensions.
1089
+ this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1090
+ ) {
1091
+ this.hasFocus = update2.view.hasFocus;
1092
+ this.decorations = getDecorations(update2.view);
1093
+ }
1094
+ }
1095
+ }, {
1096
+ decorations: (value) => value.decorations
1097
+ }),
1098
+ styles2
1099
+ ];
1009
1100
  };
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
1028
- };
1029
- return model;
1101
+
1102
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
1103
+ import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1104
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
1105
+ import sortBy from "lodash.sortby";
1106
+ import { debounce } from "@dxos/async";
1107
+ import { invariant as invariant2 } from "@dxos/invariant";
1108
+ import { log as log2 } from "@dxos/log";
1109
+ import { nonNullable } from "@dxos/util";
1110
+
1111
+ // packages/ui/react-ui-editor/src/util.ts
1112
+ import { log } from "@dxos/log";
1113
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1114
+ var callbackWrapper = (fn) => (...args) => {
1115
+ try {
1116
+ return fn(...args);
1117
+ } catch (error) {
1118
+ log.catch(error, void 0, {
1119
+ F: __dxlog_file2,
1120
+ L: 16,
1121
+ S: void 0,
1122
+ C: (f, a) => f(...a)
1123
+ });
1124
+ }
1030
1125
  };
1031
1126
 
1032
1127
  // 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({
1035
- "& .cm-bookmark": {
1036
- cursor: "pointer",
1037
- margin: "4px",
1038
- padding: "4px",
1039
- backgroundColor: "yellow"
1040
- },
1041
- "& .cm-bookmark-selected": {
1042
- backgroundColor: "orange"
1043
- },
1128
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1129
+ var styles3 = EditorView5.baseTheme({
1044
1130
  "& .cm-comment": {
1045
- backgroundColor: get3(tokens, "extend.colors.yellow.50")
1131
+ backgroundColor: getToken("extend.colors.yellow.50")
1046
1132
  },
1047
1133
  "& .cm-comment-active": {
1048
- backgroundColor: get3(tokens, "extend.colors.yellow.100")
1134
+ backgroundColor: getToken("extend.colors.yellow.100")
1049
1135
  }
1050
1136
  });
1051
1137
  var marks = {
1052
- highlight: Decoration2.mark({
1138
+ highlight: Decoration3.mark({
1053
1139
  class: "cm-comment"
1054
1140
  }),
1055
- highlightActive: Decoration2.mark({
1141
+ highlightActive: Decoration3.mark({
1056
1142
  class: "cm-comment-active"
1057
1143
  })
1058
1144
  };
@@ -1074,11 +1160,12 @@ var setFocus = (view, thread) => {
1074
1160
  var setCommentRange = StateEffect2.define();
1075
1161
  var setSelection = StateEffect2.define();
1076
1162
  var setCommentState = StateEffect2.define();
1077
- var commentsStateField = StateField3.define({
1163
+ var commentsStateField = StateField2.define({
1078
1164
  create: () => ({
1079
1165
  ranges: []
1080
1166
  }),
1081
1167
  update: (value, tr) => {
1168
+ const cursorConverter3 = tr.state.facet(Cursor.converter);
1082
1169
  for (const effect of tr.effects) {
1083
1170
  if (effect.is(setSelection)) {
1084
1171
  return {
@@ -1087,9 +1174,9 @@ var commentsStateField = StateField3.define({
1087
1174
  };
1088
1175
  }
1089
1176
  if (effect.is(setCommentRange)) {
1090
- const { model, comments: comments2 } = effect.value;
1177
+ const { comments: comments2 } = effect.value;
1091
1178
  const ranges = comments2.map((comment) => {
1092
- const range = model.getRangeFromCursor(comment.cursor);
1179
+ const range = Cursor.getRangeFromCursor(cursorConverter3, comment.cursor);
1093
1180
  return range && {
1094
1181
  ...comment,
1095
1182
  ...range
@@ -1107,7 +1194,7 @@ var commentsStateField = StateField3.define({
1107
1194
  return value;
1108
1195
  }
1109
1196
  });
1110
- var highlightDecorations = EditorView4.decorations.compute([
1197
+ var highlightDecorations = EditorView5.decorations.compute([
1111
1198
  commentsStateField
1112
1199
  ], (state) => {
1113
1200
  const { active, ranges } = state.field(commentsStateField);
@@ -1125,43 +1212,16 @@ var highlightDecorations = EditorView4.decorations.compute([
1125
1212
  return marks.highlight.range(range.from, range.to);
1126
1213
  }
1127
1214
  }).filter(nonNullable) ?? [];
1128
- return Decoration2.set(decorations);
1215
+ return Decoration3.set(decorations);
1129
1216
  });
1130
- var BookmarkWidget = class extends WidgetType {
1131
- constructor(_anchor, _id, _handleClick) {
1132
- super();
1133
- this._anchor = _anchor;
1134
- this._id = _id;
1135
- this._handleClick = _handleClick;
1136
- invariant3(this._id, void 0, {
1137
- F: __dxlog_file5,
1138
- L: 167,
1139
- S: this,
1140
- A: [
1141
- "this._id",
1142
- ""
1143
- ]
1144
- });
1145
- }
1146
- eq(other) {
1147
- return this._anchor === other._anchor && this._id === other._id;
1148
- }
1149
- toDOM() {
1150
- const span = document.createElement("span");
1151
- span.className = "cm-bookmark";
1152
- span.textContent = "\u203B";
1153
- if (this._handleClick) {
1154
- span.onclick = () => this._handleClick();
1155
- }
1156
- return span;
1157
- }
1158
- };
1159
1217
  var comments = (options = {}) => {
1218
+ const { key: shortcut = "meta-'" } = options;
1160
1219
  const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1161
1220
  const createCommentThread = (view) => {
1162
- invariant3(options.onCreate, void 0, {
1163
- F: __dxlog_file5,
1164
- L: 221,
1221
+ const cursorConverter3 = view.state.facet(Cursor.converter);
1222
+ invariant2(options.onCreate, void 0, {
1223
+ F: __dxlog_file3,
1224
+ L: 177,
1165
1225
  S: void 0,
1166
1226
  A: [
1167
1227
  "options.onCreate",
@@ -1172,13 +1232,12 @@ var comments = (options = {}) => {
1172
1232
  if (from === to) {
1173
1233
  return false;
1174
1234
  }
1175
- const model = view.state.field(modelState);
1176
- const relPos = model?.getCursorFromRange?.({
1235
+ const cursor = Cursor.getCursorFromRange(cursorConverter3, {
1177
1236
  from,
1178
1237
  to
1179
1238
  });
1180
- if (relPos) {
1181
- const id = callbackWrapper(options.onCreate)(relPos);
1239
+ if (cursor) {
1240
+ const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
1182
1241
  if (id) {
1183
1242
  view.dispatch({
1184
1243
  effects: setSelection.of({
@@ -1205,44 +1264,16 @@ var comments = (options = {}) => {
1205
1264
  }
1206
1265
  return false;
1207
1266
  };
1208
- const bookmarksViewPlugin = ViewPlugin3.fromClass(class BookmarkViewPlugin {
1209
- static {
1210
- // Match markdown footnotes (option).
1211
- // https://www.markdownguide.org/extended-syntax/#footnotes
1212
- this.bookmarkMatcher = new MatchDecorator({
1213
- regexp: /\[\^(\w+)\]/g,
1214
- decoration: (match, view, pos) => {
1215
- const id = match[1];
1216
- return Decoration2.replace({
1217
- id,
1218
- widget: new BookmarkWidget(pos, id)
1219
- });
1220
- }
1221
- });
1222
- }
1223
- constructor(view) {
1224
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
1225
- }
1226
- update(update4) {
1227
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update4, this.bookmarks);
1228
- }
1229
- }, {
1230
- decorations: (instance) => instance.bookmarks,
1231
- provide: (plugin) => EditorView4.atomicRanges.of((view) => {
1232
- return view.plugin(plugin)?.bookmarks || Decoration2.none;
1233
- })
1234
- });
1235
1267
  return [
1236
- bookmarksViewPlugin,
1237
1268
  commentsStateField,
1238
1269
  highlightDecorations,
1239
- styles2,
1270
+ styles3,
1240
1271
  //
1241
1272
  // Keymap.
1242
1273
  //
1243
1274
  options.onCreate ? keymap3.of([
1244
1275
  {
1245
- key: options?.key ?? "meta-'",
1276
+ key: shortcut,
1246
1277
  run: callbackWrapper(createCommentThread)
1247
1278
  }
1248
1279
  ]) : [],
@@ -1259,7 +1290,7 @@ var comments = (options = {}) => {
1259
1290
  above: true,
1260
1291
  create: () => {
1261
1292
  const el = document.createElement("div");
1262
- options.onHover?.(el);
1293
+ options.onHover?.(el, shortcut);
1263
1294
  return {
1264
1295
  dom: el,
1265
1296
  offset: {
@@ -1279,37 +1310,28 @@ var comments = (options = {}) => {
1279
1310
  // Monitor cursor movement and text updates.
1280
1311
  // TODO(burdon): Is there a better (finer grained) way to do this?
1281
1312
  //
1282
- EditorView4.updateListener.of(({ view, state, startState, changes }) => {
1313
+ EditorView5.updateListener.of(({ view, state, changes }) => {
1314
+ const cursorConverter3 = view.state.facet(Cursor.converter);
1283
1315
  {
1284
1316
  let mod = false;
1285
- const model = state.field(modelState);
1286
1317
  const { active, ranges } = state.field(commentsStateField);
1287
1318
  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
1319
  ranges.forEach((range) => {
1298
1320
  if (from2 === to2) {
1299
- const newRange = model.getRangeFromCursor(range.cursor);
1321
+ const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1300
1322
  if (!newRange || newRange.to - newRange.from === 0) {
1301
- log3.info("deleted comment", {
1323
+ log2.info("deleted comment", {
1302
1324
  thread: range.id
1303
1325
  }, {
1304
- F: __dxlog_file5,
1305
- L: 356,
1326
+ F: __dxlog_file3,
1327
+ L: 274,
1306
1328
  S: void 0,
1307
1329
  C: (f, a) => f(...a)
1308
1330
  });
1309
1331
  }
1310
1332
  }
1311
1333
  if (from <= range.to) {
1312
- const newRange = model.getRangeFromCursor(range.cursor);
1334
+ const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1313
1335
  Object.assign(range, newRange);
1314
1336
  mod = true;
1315
1337
  }
@@ -1360,13 +1382,12 @@ var comments = (options = {}) => {
1360
1382
  };
1361
1383
 
1362
1384
  // 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";
1364
- import get4 from "lodash.get";
1365
- var styles3 = EditorView5.baseTheme({
1385
+ import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1386
+ var styles4 = EditorView6.baseTheme({
1366
1387
  "& .cm-hr": {
1367
1388
  // TODO(burdon): ???
1368
1389
  // Note that block-level decorations should not have vertical margins,
1369
- borderBottom: `1px solid ${get4(tokens, "extend.colors.neutral.200")}`
1390
+ borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1370
1391
  }
1371
1392
  });
1372
1393
  var HorizontalRuleWidget = class extends WidgetType2 {
@@ -1383,43 +1404,65 @@ var HorizontalRuleWidget = class extends WidgetType2 {
1383
1404
  return el;
1384
1405
  }
1385
1406
  };
1386
- var placeholderMatcher = new MatchDecorator2({
1407
+ var placeholderMatcher = new MatchDecorator({
1387
1408
  regexp: /^---$/g,
1388
- decoration: (match, view, pos) => Decoration3.replace({
1409
+ decoration: (match, view, pos) => Decoration4.replace({
1389
1410
  widget: new HorizontalRuleWidget(pos)
1390
1411
  })
1391
1412
  });
1392
1413
  var hr = () => [
1393
- styles3,
1414
+ styles4,
1394
1415
  ViewPlugin4.fromClass(class {
1395
1416
  constructor(view) {
1396
1417
  this.rules = placeholderMatcher.createDeco(view);
1397
1418
  }
1398
- update(update4) {
1399
- this.rules = placeholderMatcher.updateDeco(update4, this.rules);
1419
+ update(update2) {
1420
+ this.rules = placeholderMatcher.updateDeco(update2, this.rules);
1400
1421
  }
1401
1422
  }, {
1402
1423
  decorations: (instance) => instance.rules,
1403
1424
  // 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;
1425
+ provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1426
+ return view.plugin(plugin)?.rules || Decoration4.none;
1406
1427
  })
1407
1428
  })
1408
1429
  ];
1409
1430
 
1410
1431
  // packages/ui/react-ui-editor/src/extensions/image.ts
1411
1432
  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";
1433
+ import { StateField as StateField3 } from "@codemirror/state";
1434
+ import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
1414
1435
  var image = (options = {}) => {
1415
- return StateField4.define({
1416
- create: (state) => update(state, options),
1417
- update: (_, tr) => update(tr.state, options),
1418
- provide: (field) => EditorView6.decorations.from(field)
1436
+ return StateField3.define({
1437
+ create: (state) => {
1438
+ return Decoration5.set(buildDecorations(0, state.doc.length, state));
1439
+ },
1440
+ update: (value, tr) => {
1441
+ if (!tr.docChanged && !tr.selection) {
1442
+ return value;
1443
+ }
1444
+ const cursor = tr.state.selection.main.head;
1445
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
1446
+ let from = Math.min(cursor, oldCursor);
1447
+ let to = Math.max(cursor, oldCursor);
1448
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
1449
+ from = Math.min(from, fromB);
1450
+ to = Math.max(to, toB);
1451
+ });
1452
+ from = tr.state.doc.lineAt(from).from;
1453
+ to = tr.state.doc.lineAt(to).to;
1454
+ return value.map(tr.changes).update({
1455
+ filterFrom: from,
1456
+ filterTo: to,
1457
+ filter: () => false,
1458
+ add: buildDecorations(from, to, tr.state)
1459
+ });
1460
+ },
1461
+ provide: (field) => EditorView7.decorations.from(field)
1419
1462
  });
1420
1463
  };
1421
- var update = (state, options) => {
1422
- const builder = new RangeSetBuilder();
1464
+ var buildDecorations = (from, to, state) => {
1465
+ const decorations = [];
1423
1466
  const cursor = state.selection.main.head;
1424
1467
  syntaxTree(state).iterate({
1425
1468
  enter: (node) => {
@@ -1428,22 +1471,23 @@ var update = (state, options) => {
1428
1471
  if (urlNode) {
1429
1472
  const hide = state.readOnly || cursor < node.from || cursor > node.to;
1430
1473
  const url = state.sliceDoc(urlNode.from, urlNode.to);
1431
- builder.add(hide ? node.from : node.to, node.to, Decoration4.replace({
1474
+ decorations.push(Decoration5.replace({
1432
1475
  block: true,
1433
1476
  widget: new ImageWidget(url)
1434
- }));
1477
+ }).range(hide ? node.from : node.to, node.to));
1435
1478
  }
1436
1479
  }
1437
- }
1480
+ },
1481
+ from,
1482
+ to
1438
1483
  });
1439
- return builder.finish();
1484
+ return decorations;
1440
1485
  };
1441
1486
  var ImageWidget = class extends WidgetType3 {
1442
1487
  constructor(_url) {
1443
1488
  super();
1444
1489
  this._url = _url;
1445
1490
  }
1446
- // TODO(burdon): Does this need to be unique for each position?
1447
1491
  eq(other) {
1448
1492
  return this._url === other._url;
1449
1493
  }
@@ -1451,22 +1495,29 @@ var ImageWidget = class extends WidgetType3 {
1451
1495
  const img = document.createElement("img");
1452
1496
  img.setAttribute("src", this._url);
1453
1497
  img.setAttribute("class", "cm-image");
1498
+ img.onload = () => img.classList.add("cm-loaded-image");
1454
1499
  return img;
1455
1500
  }
1456
1501
  };
1457
1502
 
1458
1503
  // packages/ui/react-ui-editor/src/extensions/link.ts
1459
1504
  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";
1505
+ import { RangeSetBuilder } from "@codemirror/state";
1506
+ import { Decoration as Decoration6, WidgetType as WidgetType4, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1462
1507
  import { tooltipContent } from "@dxos/react-ui-theme";
1463
- var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1464
1508
  var link = (options = {}) => {
1465
1509
  const extensions = [
1466
- StateField5.define({
1467
- create: (state) => update2(state, options),
1468
- update: (_, tr) => update2(tr.state, options),
1469
- provide: (field) => EditorView7.decorations.from(field)
1510
+ ViewPlugin5.fromClass(class {
1511
+ constructor(view) {
1512
+ this.decorations = buildDecorations2(view, options);
1513
+ }
1514
+ update(update2) {
1515
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1516
+ this.decorations = buildDecorations2(update2.view, options);
1517
+ }
1518
+ }
1519
+ }, {
1520
+ decorations: (v) => v.decorations
1470
1521
  })
1471
1522
  ];
1472
1523
  if (options.onHover) {
@@ -1474,35 +1525,25 @@ var link = (options = {}) => {
1474
1525
  // https://codemirror.net/examples/tooltip
1475
1526
  // https://codemirror.net/docs/ref/#view.hoverTooltip
1476
1527
  // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1477
- hoverTooltip2((view, pos) => {
1478
- const { from, text } = view.state.doc.lineAt(pos);
1479
- const p = pos - from;
1480
- let idx = 0;
1481
- let match;
1482
- do {
1483
- match = text.substring(idx).match(markdownLinkRegexp);
1484
- if (!match) {
1485
- match = void 0;
1486
- break;
1487
- }
1488
- idx = text.indexOf(match[0]);
1489
- if (p >= idx && p < idx + match[0].length) {
1490
- break;
1491
- }
1492
- idx += match[0].length;
1493
- } while (true);
1494
- if (!match) {
1528
+ hoverTooltip2((view, pos, side) => {
1529
+ const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1530
+ let link2 = null;
1531
+ for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1532
+ link2 = node.name === "Link" ? node : null;
1533
+ }
1534
+ const url = link2 && link2.getChild("URL");
1535
+ if (!url || !link2) {
1495
1536
  return null;
1496
1537
  }
1497
- const [, , url] = match ?? [];
1538
+ const urlText = view.state.sliceDoc(url.from, url.to);
1498
1539
  return {
1499
- pos: idx + from,
1500
- end: idx + from + match[0].length,
1540
+ pos: link2.from,
1541
+ end: link2.to,
1501
1542
  above: true,
1502
1543
  create: () => {
1503
1544
  const el = document.createElement("div");
1504
1545
  el.className = tooltipContent({}, "pli-2 plb-1");
1505
- options.onHover(el, url);
1546
+ options.onHover(el, urlText);
1506
1547
  return {
1507
1548
  dom: el,
1508
1549
  offset: {
@@ -1518,14 +1559,13 @@ var link = (options = {}) => {
1518
1559
  return extensions;
1519
1560
  };
1520
1561
  var LinkButton = class extends WidgetType4 {
1521
- constructor(_pos, _url, _onAttach) {
1562
+ constructor(_url, _onAttach) {
1522
1563
  super();
1523
- this._pos = _pos;
1524
1564
  this._url = _url;
1525
1565
  this._onAttach = _onAttach;
1526
1566
  }
1527
1567
  eq(other) {
1528
- return this._pos === other._pos && this._url === other._url;
1568
+ return this._url === other._url;
1529
1569
  }
1530
1570
  toDOM(view) {
1531
1571
  const el = document.createElement("span");
@@ -1562,33 +1602,38 @@ var LinkText = class extends WidgetType4 {
1562
1602
  return link2;
1563
1603
  }
1564
1604
  };
1565
- var update2 = (state, options) => {
1566
- const builder = new RangeSetBuilder2();
1605
+ var buildDecorations2 = (view, options) => {
1606
+ const builder = new RangeSetBuilder();
1607
+ const { state } = view;
1567
1608
  const cursor = state.selection.main.head;
1568
- syntaxTree2(state).iterate({
1569
- enter: (node) => {
1570
- if (node.name === "Link") {
1571
- const marks2 = node.node.getChildren("LinkMark");
1572
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1573
- const urlNode = node.node.getChild("URL");
1574
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1575
- if (!url) {
1609
+ for (const { from, to } of view.visibleRanges) {
1610
+ syntaxTree2(state).iterate({
1611
+ enter: (node) => {
1612
+ if (node.name === "Link") {
1613
+ const marks2 = node.node.getChildren("LinkMark");
1614
+ const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1615
+ const urlNode = node.node.getChild("URL");
1616
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1617
+ if (!url) {
1618
+ return false;
1619
+ }
1620
+ if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
1621
+ builder.add(node.from, node.to, Decoration6.replace({
1622
+ widget: new LinkText(text, options.link ? url : void 0)
1623
+ }));
1624
+ }
1625
+ if (options.onRender) {
1626
+ builder.add(node.to, node.to, Decoration6.replace({
1627
+ widget: new LinkButton(url, options.onRender)
1628
+ }));
1629
+ }
1576
1630
  return false;
1577
1631
  }
1578
- if (state.readOnly || cursor < node.from || cursor > node.to) {
1579
- builder.add(node.from, node.to, Decoration5.replace({
1580
- widget: new LinkText(text, options.link ? url : void 0)
1581
- }));
1582
- }
1583
- if (options.onRender) {
1584
- builder.add(node.to, node.to, Decoration5.replace({
1585
- widget: new LinkButton(node.from, url, options.onRender)
1586
- }));
1587
- }
1588
- return false;
1589
- }
1590
- }
1591
- });
1632
+ },
1633
+ from,
1634
+ to
1635
+ });
1636
+ }
1592
1637
  return builder.finish();
1593
1638
  };
1594
1639
 
@@ -1600,8 +1645,8 @@ var listener = ({ onFocus, onChange }) => {
1600
1645
  onFocus(focused);
1601
1646
  return null;
1602
1647
  }));
1603
- onChange && extensions.push(EditorView8.updateListener.of((update4) => {
1604
- onChange(update4.state.doc.toString());
1648
+ onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1649
+ onChange(update2.state.doc.toString());
1605
1650
  }));
1606
1651
  return extensions;
1607
1652
  };
@@ -1609,7 +1654,7 @@ var listener = ({ onFocus, onChange }) => {
1609
1654
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1610
1655
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1611
1656
  import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1612
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
1657
+ import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1613
1658
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1614
1659
  import { languages } from "@codemirror/language-data";
1615
1660
  import { searchKeymap } from "@codemirror/search";
@@ -1618,18 +1663,16 @@ import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/the
1618
1663
  import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1619
1664
 
1620
1665
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1621
- import { markdownLanguage } from "@codemirror/lang-markdown";
1666
+ import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
1622
1667
  import { HighlightStyle } from "@codemirror/language";
1623
1668
  import { tags, styleTags, Tag } from "@lezer/highlight";
1624
1669
  import { Table } from "@lezer/markdown";
1625
- import get5 from "lodash.get";
1626
1670
  var markdownTags = {
1627
1671
  Blockquote: Tag.define(),
1628
1672
  CodeMark: Tag.define(),
1629
1673
  CodeText: Tag.define(),
1630
1674
  EmphasisMark: Tag.define(),
1631
1675
  HeaderMark: Tag.define(),
1632
- // HorizontalRule: Tag.define(),
1633
1676
  InlineCode: Tag.define(),
1634
1677
  LinkLabel: Tag.define(),
1635
1678
  LinkReference: Tag.define(),
@@ -1793,12 +1836,6 @@ var markdownHighlightStyle = (readonly) => {
1793
1836
  ],
1794
1837
  class: blockquote
1795
1838
  },
1796
- // TODO(burdon): Replace with extension.
1797
- // {
1798
- // tag: [markdownTags.HorizontalRule],
1799
- // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1800
- // },
1801
- // TODO(burdon): Only if being edited.
1802
1839
  {
1803
1840
  tag: [
1804
1841
  markdownTags.TableCell
@@ -1806,9 +1843,9 @@ var markdownHighlightStyle = (readonly) => {
1806
1843
  class: "font-mono"
1807
1844
  }
1808
1845
  ], {
1809
- scope: markdownLanguage,
1846
+ scope: markdownLanguage2,
1810
1847
  all: {
1811
- fontFamily: get5(tokens, "fontFamily.body", []).join(",")
1848
+ fontFamily: getToken("fontFamily.body", []).join(",")
1812
1849
  }
1813
1850
  });
1814
1851
  };
@@ -1838,7 +1875,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1838
1875
  // NOTE: This extends the parser; it doesn't affect rendering.
1839
1876
  // https://github.github.com/gfm
1840
1877
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1841
- base: markdownLanguage2,
1878
+ base: markdownLanguage3,
1842
1879
  // Languages for syntax highlighting fenced code blocks.
1843
1880
  codeLanguages: languages,
1844
1881
  // Parser extensions.
@@ -1889,24 +1926,19 @@ var mention = ({ onSearch }) => {
1889
1926
  });
1890
1927
  };
1891
1928
 
1892
- // packages/ui/react-ui-editor/src/extensions/mermaid.ts
1893
- var mermaid = () => {
1894
- return [];
1895
- };
1896
-
1897
1929
  // packages/ui/react-ui-editor/src/extensions/table.ts
1898
1930
  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";
1931
+ import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1932
+ import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
1901
1933
  var table = (options = {}) => {
1902
- return StateField6.define({
1903
- create: (state) => update3(state, options),
1904
- update: (_, tr) => update3(tr.state, options),
1934
+ return StateField4.define({
1935
+ create: (state) => update(state, options),
1936
+ update: (_, tr) => update(tr.state, options),
1905
1937
  provide: (field) => EditorView10.decorations.from(field)
1906
1938
  });
1907
1939
  };
1908
- var update3 = (state, options) => {
1909
- const builder = new RangeSetBuilder3();
1940
+ var update = (state, options) => {
1941
+ const builder = new RangeSetBuilder2();
1910
1942
  const cursor = state.selection.main.head;
1911
1943
  const tables = [];
1912
1944
  const getTable = () => tables[tables.length - 1];
@@ -1946,11 +1978,10 @@ var update3 = (state, options) => {
1946
1978
  });
1947
1979
  tables.forEach((table2) => {
1948
1980
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1949
- hide && builder.add(table2.from, table2.to, Decoration6.replace({
1950
- block: true,
1981
+ hide && builder.add(table2.from, table2.to, Decoration7.replace({
1951
1982
  widget: new TableWidget(table2)
1952
1983
  }));
1953
- builder.add(table2.from, table2.to, Decoration6.mark({
1984
+ builder.add(table2.from, table2.to, Decoration7.mark({
1954
1985
  class: "cm-table"
1955
1986
  }));
1956
1987
  });
@@ -1962,7 +1993,7 @@ var TableWidget = class extends WidgetType5 {
1962
1993
  this._table = _table;
1963
1994
  }
1964
1995
  eq(other) {
1965
- return this._table.from === other?._table?.from;
1996
+ return this._table.header?.join() === other._table.header?.join() && this._table.rows?.join() === other._table.rows?.join();
1966
1997
  }
1967
1998
  toDOM(view) {
1968
1999
  const table2 = document.createElement("table");
@@ -1988,26 +2019,28 @@ var TableWidget = class extends WidgetType5 {
1988
2019
  }
1989
2020
  return table2;
1990
2021
  }
2022
+ ignoreEvent(e) {
2023
+ return !/^mouse/.test(e.type);
2024
+ }
1991
2025
  };
1992
2026
 
1993
2027
  // 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({
2028
+ import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2029
+ var styles5 = EditorView11.baseTheme({
1996
2030
  "& .cm-task-item": {
1997
2031
  paddingLeft: "4px",
1998
2032
  paddingRight: "8px"
1999
2033
  }
2000
2034
  });
2001
2035
  var CheckboxWidget = class extends WidgetType6 {
2002
- constructor(_pos, _checked, _indent, _onCheck) {
2036
+ constructor(_checked, _indent, _onCheck) {
2003
2037
  super();
2004
- this._pos = _pos;
2005
2038
  this._checked = _checked;
2006
2039
  this._indent = _indent;
2007
2040
  this._onCheck = _onCheck;
2008
2041
  }
2009
2042
  eq(other) {
2010
- return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
2043
+ return this._checked === other._checked && this._indent === other._indent;
2011
2044
  }
2012
2045
  toDOM(view) {
2013
2046
  const wrap = document.createElement("span");
@@ -2033,13 +2066,13 @@ var CheckboxWidget = class extends WidgetType6 {
2033
2066
  }
2034
2067
  };
2035
2068
  var tasklist = (options = {}) => {
2036
- const taskMatcher = new MatchDecorator3({
2069
+ const taskMatcher = new MatchDecorator2({
2037
2070
  regexp: /^(\s*)- \[([ xX])\]\s/g,
2038
2071
  decoration: (match, view, pos) => {
2039
2072
  const indent = Math.floor(match[1].length / 2);
2040
2073
  const checked = match[2] === "x" || match[2] === "X";
2041
- return Decoration7.replace({
2042
- widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2074
+ return Decoration8.replace({
2075
+ widget: new CheckboxWidget(checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2043
2076
  const idx = pos + match[0].indexOf("[") + 1;
2044
2077
  view.dispatch({
2045
2078
  changes: {
@@ -2056,22 +2089,22 @@ var tasklist = (options = {}) => {
2056
2089
  });
2057
2090
  }
2058
2091
  });
2059
- const tasks = ViewPlugin5.fromClass(class {
2092
+ const tasks = ViewPlugin6.fromClass(class {
2060
2093
  constructor(view) {
2061
2094
  this.tasks = taskMatcher.createDeco(view);
2062
2095
  }
2063
- update(update4) {
2064
- this.tasks = taskMatcher.updateDeco(update4, this.tasks);
2096
+ update(update2) {
2097
+ this.tasks = taskMatcher.updateDeco(update2, this.tasks);
2065
2098
  }
2066
2099
  }, {
2067
2100
  decorations: (value) => value.tasks,
2068
2101
  provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2069
- return view.plugin(plugin)?.tasks || Decoration7.none;
2102
+ return view.plugin(plugin)?.tasks || Decoration8.none;
2070
2103
  })
2071
2104
  });
2072
2105
  return [
2073
2106
  tasks,
2074
- styles4
2107
+ styles5
2075
2108
  ];
2076
2109
  };
2077
2110
 
@@ -2132,8 +2165,29 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2132
2165
  ];
2133
2166
  };
2134
2167
 
2168
+ // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2169
+ import { yCollab } from "y-codemirror.next";
2170
+
2171
+ // packages/ui/react-ui-editor/src/extensions/yjs/cursor.ts
2172
+ import * as Y from "yjs";
2173
+ import { arrayToString, stringToArray } from "@dxos/util";
2174
+ var cursorConverter2 = (text) => ({
2175
+ toCursor: (index, assoc) => {
2176
+ return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
2177
+ },
2178
+ fromCursor: (cursor) => {
2179
+ return Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc)?.index ?? 0;
2180
+ }
2181
+ });
2182
+
2183
+ // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2184
+ var yjs = (content, awareness2) => [
2185
+ Cursor.converter.of(cursorConverter2(content)),
2186
+ yCollab(content, awareness2)
2187
+ ];
2188
+
2135
2189
  // packages/ui/react-ui-editor/src/themes/default.ts
2136
- import get6 from "lodash.get";
2190
+ import get3 from "lodash.get";
2137
2191
  var defaultTheme = {
2138
2192
  //
2139
2193
  // Main layout:
@@ -2162,21 +2216,21 @@ var defaultTheme = {
2162
2216
  "&.cm-focused": {
2163
2217
  outline: "none"
2164
2218
  },
2165
- "& .cm-scroller": {
2219
+ ".cm-scroller": {
2166
2220
  overflow: "visible",
2167
- fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
2221
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2168
2222
  },
2169
- "& .cm-content": {
2223
+ ".cm-content": {
2170
2224
  padding: 0,
2171
2225
  // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
2172
2226
  fontSize: "16px"
2173
2227
  },
2174
2228
  "&light .cm-content": {
2175
- color: get6(tokens, "extend.colors.neutral.900", "black"),
2229
+ color: get3(tokens, "extend.colors.neutral.900", "black"),
2176
2230
  caretColor: "black"
2177
2231
  },
2178
2232
  "&dark .cm-content": {
2179
- color: get6(tokens, "extend.colors.neutral.100", "white"),
2233
+ color: get3(tokens, "extend.colors.neutral.100", "white"),
2180
2234
  caretColor: "white"
2181
2235
  },
2182
2236
  //
@@ -2188,158 +2242,127 @@ var defaultTheme = {
2188
2242
  "&dark .cm-cursor, &dark .cm-dropCursor": {
2189
2243
  borderLeft: "2px solid white"
2190
2244
  },
2191
- "& .cm-placeholder": {
2245
+ ".cm-placeholder": {
2192
2246
  fontWeight: 100
2193
2247
  },
2194
2248
  //
2195
2249
  // line
2196
2250
  //
2197
- "& .cm-line": {
2251
+ ".cm-line": {
2198
2252
  paddingInline: 0
2199
2253
  },
2200
- "& .cm-activeLine": {
2254
+ ".cm-activeLine": {
2201
2255
  background: "inherit"
2202
2256
  },
2203
2257
  //
2204
2258
  // Selection
2205
2259
  //
2206
2260
  "&light .cm-selectionBackground": {
2207
- background: get6(tokens, "extend.colors.primary.100")
2261
+ background: get3(tokens, "extend.colors.primary.100")
2208
2262
  },
2209
2263
  "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2210
- background: get6(tokens, "extend.colors.primary.200")
2264
+ background: get3(tokens, "extend.colors.primary.200")
2211
2265
  },
2212
2266
  "&dark .cm-selectionBackground": {
2213
- background: get6(tokens, "extend.colors.primary.700")
2267
+ background: get3(tokens, "extend.colors.primary.700")
2214
2268
  },
2215
2269
  "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2216
- background: get6(tokens, "extend.colors.primary.600")
2270
+ background: get3(tokens, "extend.colors.primary.600")
2217
2271
  },
2218
2272
  //
2219
2273
  // Search
2220
2274
  //
2221
2275
  "&light .cm-searchMatch": {
2222
- backgroundColor: get6(tokens, "extend.colors.yellow.100")
2276
+ backgroundColor: get3(tokens, "extend.colors.yellow.100")
2223
2277
  },
2224
2278
  "&dark .cm-searchMatch": {
2225
- backgroundColor: get6(tokens, "extend.colors.yellow.700")
2226
- },
2227
- //
2228
- // collaboration
2229
- // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2230
- //
2231
- "&light .cm-ySelection, &light .cm-yLineSelection": {
2232
- mixBlendMode: "multiply"
2233
- },
2234
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2235
- mixBlendMode: "screen"
2236
- },
2237
- "& .cm-ySelectionInfo": {
2238
- padding: "2px 4px",
2239
- marginBlockStart: "-4px"
2240
- },
2241
- "& .cm-ySelection, & .cm-selectionMatch": {
2242
- paddingBlockStart: ".15em",
2243
- paddingBlockEnd: ".15em"
2244
- },
2245
- "& .cm-ySelectionCaret": {
2246
- display: "inline-block",
2247
- insetBlockStart: ".1em",
2248
- blockSize: "1.4em",
2249
- verticalAlign: "top"
2250
- },
2251
- "& .cm-yLineSelection": {
2252
- margin: 0
2279
+ backgroundColor: get3(tokens, "extend.colors.yellow.700")
2253
2280
  },
2254
2281
  //
2255
2282
  // link
2256
2283
  //
2257
- "& .cm-link": {
2258
- color: get6(tokens, "extend.colors.primary.500"),
2284
+ ".cm-link": {
2285
+ color: get3(tokens, "extend.colors.primary.500"),
2259
2286
  textDecorationLine: "underline",
2260
2287
  textDecorationThickness: "1px",
2261
2288
  textUnderlineOffset: "2px",
2262
2289
  borderRadius: ".125rem",
2263
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2290
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2264
2291
  },
2265
2292
  //
2266
2293
  // tooltip
2267
2294
  //
2268
- "& .cm-tooltip": {
2295
+ ".cm-tooltip": {
2269
2296
  border: "none"
2270
2297
  },
2271
- "& .cm-tooltip-below": {},
2298
+ ".cm-tooltip-below": {},
2272
2299
  //
2273
2300
  // autocomplete
2274
2301
  //
2275
- "& .cm-tooltip-autocomplete": {
2302
+ ".cm-tooltip-autocomplete": {
2276
2303
  marginTop: "4px",
2277
2304
  marginLeft: "-3px"
2278
2305
  },
2279
- "& .cm-tooltip-autocomplete ul li": {},
2280
- "& .cm-tooltip-autocomplete ul li[aria-selected]": {},
2281
- "& .cm-completionIcon": {
2306
+ ".cm-tooltip-autocomplete ul li": {},
2307
+ ".cm-tooltip-autocomplete ul li[aria-selected]": {},
2308
+ ".cm-completionIcon": {
2282
2309
  display: "none"
2283
2310
  },
2284
- "& .cm-completionLabel": {
2285
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2311
+ ".cm-completionLabel": {
2312
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2286
2313
  },
2287
- "& .cm-completionMatchedText": {
2314
+ ".cm-completionMatchedText": {
2288
2315
  textDecoration: "none"
2289
2316
  },
2290
2317
  //
2291
2318
  // widgets
2292
2319
  //
2293
- "& .cm-widgetBuffer": {
2320
+ ".cm-widgetBuffer": {
2294
2321
  display: "none",
2295
2322
  height: 0
2296
2323
  },
2297
2324
  //
2298
2325
  // table
2299
2326
  //
2300
- "& .cm-table *": {
2301
- fontFamily: `${get6(tokens, "fontFamily.mono", []).join(",")} !important`,
2327
+ ".cm-table *": {
2328
+ fontFamily: `${get3(tokens, "fontFamily.mono", []).join(",")} !important`,
2302
2329
  textDecoration: "none !important"
2303
2330
  },
2304
- "& .cm-table-head": {
2331
+ ".cm-table-head": {
2305
2332
  padding: "2px 16px 2px 0px",
2306
- borderBottom: `1px solid ${get6(tokens, "extend.colors.neutral.500")}`,
2333
+ borderBottom: `1px solid ${get3(tokens, "extend.colors.neutral.500")}`,
2307
2334
  fontWeight: 100,
2308
2335
  textAlign: "left",
2309
- color: get6(tokens, "extend.colors.neutral.500")
2336
+ color: get3(tokens, "extend.colors.neutral.500")
2310
2337
  },
2311
- "& .cm-table-cell": {
2338
+ ".cm-table-cell": {
2312
2339
  padding: "2px 16px 2px 0px"
2313
2340
  },
2314
2341
  //
2315
2342
  // image
2316
2343
  //
2317
- "& .cm-image": {
2318
- margin: "0.5rem 0"
2344
+ ".cm-image": {
2345
+ display: "block",
2346
+ height: "0"
2347
+ },
2348
+ ".cm-image.cm-loaded-image": {
2349
+ height: "auto",
2350
+ borderTop: "0.5rem solid transparent",
2351
+ borderBottom: "0.5rem solid transparent"
2319
2352
  },
2320
2353
  //
2321
2354
  // font size
2322
- // TODO(thure): This appears to be the best or only way to set selection caret heights, but it's far more verbose than it needs to be.
2355
+ // TODO(thure): This appears to be the best or only way to set selection caret heights,
2356
+ // but it's far more verbose than it needs to be.
2323
2357
  //
2324
- ...Object.keys(get6(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2325
- const height = get6(tokens, [
2326
- "extend",
2327
- "fontSize",
2328
- fontSize,
2329
- 1,
2330
- "lineHeight"
2331
- ]);
2332
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2333
- height
2334
- };
2335
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2336
- height
2337
- };
2338
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2339
- height
2340
- };
2341
- return acc;
2342
- }, {}),
2358
+ // ...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
2359
+ // const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
2360
+ //
2361
+ // acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
2362
+ // acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
2363
+ // acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
2364
+ // return acc;
2365
+ // }, {}),
2343
2366
  /**
2344
2367
  * Panels
2345
2368
  * TODO(burdon): Needs styling attention (esp. dark mode).
@@ -2356,51 +2379,53 @@ var defaultTheme = {
2356
2379
  * </div>
2357
2380
  * </div
2358
2381
  */
2359
- "& .cm-panels": {
2360
- border: `1px solid ${get6(tokens, "extend.colors.neutral.200")}`
2382
+ ".cm-panels": {
2383
+ border: `1px solid ${get3(tokens, "extend.colors.neutral.200")}`
2361
2384
  },
2362
- "& .cm-panel": {
2363
- background: get6(tokens, "extend.colors.neutral.50"),
2364
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2385
+ ".cm-panel": {
2386
+ background: get3(tokens, "extend.colors.neutral.50"),
2387
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2365
2388
  },
2366
- "& .cm-button": {
2389
+ ".cm-button": {
2367
2390
  margin: "4px",
2368
- fontFamily: get6(tokens, "fontFamily.body", []).join(","),
2369
- background: get6(tokens, "extend.colors.neutral.100"),
2391
+ fontFamily: get3(tokens, "fontFamily.body", []).join(","),
2392
+ background: get3(tokens, "extend.colors.neutral.100"),
2370
2393
  backgroundImage: "none",
2371
2394
  border: "none",
2372
2395
  "&:hover": {
2373
- background: get6(tokens, "extend.colors.neutral.200")
2396
+ background: get3(tokens, "extend.colors.neutral.200")
2374
2397
  },
2375
2398
  "&:active": {
2376
- background: get6(tokens, "extend.colors.neutral.300"),
2399
+ background: get3(tokens, "extend.colors.neutral.300"),
2377
2400
  backgroundImage: "none"
2378
2401
  }
2379
2402
  },
2380
- "& .cm-panel input[type=checkbox]": {
2403
+ ".cm-panel input[type=checkbox]": {
2381
2404
  marginRight: "0.4rem !important"
2382
2405
  }
2383
2406
  };
2384
2407
  var textTheme = {
2385
- "& .cm-scroller": {
2386
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2408
+ ".cm-scroller": {
2409
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2387
2410
  },
2388
- "& .cm-placeholder": {
2389
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2411
+ ".cm-placeholder": {
2412
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2390
2413
  }
2391
2414
  };
2392
2415
  var markdownTheme = {
2393
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2394
- "& .cm-placeholder": {
2395
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2416
+ ".cm-scroller": {
2417
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2418
+ },
2419
+ ".cm-placeholder": {
2420
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2396
2421
  }
2397
2422
  };
2398
2423
  var codeTheme = {
2399
- "& .cm-scroller": {
2400
- fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
2424
+ ".cm-scroller": {
2425
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2401
2426
  },
2402
- "& .cm-placeholder": {
2403
- fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
2427
+ ".cm-placeholder": {
2428
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2404
2429
  }
2405
2430
  };
2406
2431
 
@@ -2409,26 +2434,28 @@ var EditorModes = [
2409
2434
  "default",
2410
2435
  "vim"
2411
2436
  ];
2412
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
2413
- const { themeMode } = useThemeContext();
2437
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, readonly, comments: comments2, extensions = [], editorMode, theme, slots = defaultSlots }, forwardedRef) => {
2414
2438
  const tabsterDOMAttribute = useFocusableGroup({
2415
2439
  tabBehavior: "limited"
2416
2440
  });
2417
- const [root, setRoot] = useState2(null);
2418
- const [{ state = void 0, view = void 0 } = {}, setEditor] = useState2();
2419
- useImperativeHandle(forwardedRef, () => ({
2420
- root,
2421
- state,
2441
+ const { themeMode } = useThemeContext();
2442
+ const rootRef = useRef(null);
2443
+ const [view, setView] = useState(null);
2444
+ useImperativeHandle(forwardedRef, () => view, [
2422
2445
  view
2423
- }), [
2446
+ ]);
2447
+ useEffect(() => {
2448
+ if (view && focus) {
2449
+ view.focus();
2450
+ }
2451
+ }, [
2424
2452
  view,
2425
- state,
2426
- root
2453
+ focus
2427
2454
  ]);
2428
- const { awareness, peer } = model;
2429
- useEffect2(() => {
2430
- if (awareness && peer) {
2431
- awareness.setLocalStateField("user", {
2455
+ const { awareness: awareness2, peer } = model;
2456
+ useEffect(() => {
2457
+ if (awareness2 && peer) {
2458
+ awareness2.setLocalStateField("user", {
2432
2459
  name: peer.name ?? generateName(peer.id),
2433
2460
  color: getColorForValue({
2434
2461
  value: peer.id,
@@ -2442,12 +2469,12 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2442
2469
  });
2443
2470
  }
2444
2471
  }, [
2445
- awareness,
2472
+ awareness2,
2446
2473
  peer,
2447
2474
  themeMode
2448
2475
  ]);
2449
- useEffect2(() => {
2450
- if (view && comments2?.length) {
2476
+ useEffect(() => {
2477
+ if (view && comments2 !== void 0) {
2451
2478
  view.dispatch({
2452
2479
  effects: setCommentRange.of({
2453
2480
  model,
@@ -2459,12 +2486,14 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2459
2486
  view,
2460
2487
  comments2
2461
2488
  ]);
2462
- useEffect2(() => {
2463
- if (!root) {
2489
+ useEffect(() => {
2490
+ if (!model || !rootRef.current) {
2464
2491
  return;
2465
2492
  }
2466
- const state2 = EditorState2.create({
2493
+ const state = EditorState2.create({
2467
2494
  doc: model.text(),
2495
+ // TODO(burdon): Composer should store and set selection when switching documents.
2496
+ selection,
2468
2497
  extensions: [
2469
2498
  readonly && EditorState2.readOnly.of(readonly),
2470
2499
  // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
@@ -2472,7 +2501,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2472
2501
  // Theme.
2473
2502
  // TODO(burdon): Make theme configurable.
2474
2503
  EditorView12.baseTheme(defaultTheme),
2475
- EditorView12.theme(slots?.editor?.theme ?? {}),
2504
+ EditorView12.theme(theme ?? {}),
2476
2505
  EditorView12.darkTheme.of(themeMode === "dark"),
2477
2506
  // Storage and replication.
2478
2507
  model.extension,
@@ -2481,19 +2510,17 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2481
2510
  ].filter(Boolean)
2482
2511
  });
2483
2512
  view?.destroy();
2484
- setEditor({
2485
- state: state2,
2486
- view: new EditorView12({
2487
- state: state2,
2488
- parent: root
2489
- })
2513
+ const newView = new EditorView12({
2514
+ parent: rootRef.current,
2515
+ state
2490
2516
  });
2517
+ setView(newView);
2491
2518
  return () => {
2492
- view?.destroy();
2493
- setEditor(void 0);
2519
+ newView?.destroy();
2520
+ setView(null);
2494
2521
  };
2495
2522
  }, [
2496
- root,
2523
+ rootRef,
2497
2524
  model,
2498
2525
  readonly,
2499
2526
  editorMode,
@@ -2502,12 +2529,8 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2502
2529
  const handleKeyUp = useCallback((event) => {
2503
2530
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2504
2531
  switch (key) {
2505
- case "Enter": {
2506
- view?.contentDOM.focus();
2507
- break;
2508
- }
2509
2532
  case "Escape": {
2510
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2533
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
2511
2534
  break;
2512
2535
  }
2513
2536
  }
@@ -2517,16 +2540,17 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2517
2540
  ]);
2518
2541
  return /* @__PURE__ */ React.createElement("div", {
2519
2542
  key: model.id,
2520
- ref: setRoot,
2543
+ role: "none",
2544
+ ref: rootRef,
2521
2545
  tabIndex: 0,
2522
- ...slots?.root,
2523
- ...editorMode !== "vim" && tabsterDOMAttribute,
2524
- onKeyUp: handleKeyUp
2546
+ onKeyUp: handleKeyUp,
2547
+ ...slots.root,
2548
+ ...editorMode !== "vim" && tabsterDOMAttribute
2525
2549
  });
2526
2550
  });
2527
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2551
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
2528
2552
  const { themeMode } = useThemeContext();
2529
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2553
+ const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2530
2554
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2531
2555
  ref: forwardedRef,
2532
2556
  readonly,
@@ -2534,17 +2558,18 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots:
2534
2558
  basicBundle({
2535
2559
  readonly,
2536
2560
  themeMode,
2537
- placeholder: slots?.editor?.placeholder
2561
+ placeholder: placeholder3
2538
2562
  }),
2539
2563
  ...extensions
2540
2564
  ],
2541
- slots,
2565
+ theme,
2566
+ slots: updatedSlots,
2542
2567
  ...props
2543
2568
  });
2544
2569
  });
2545
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2570
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
2546
2571
  const { themeMode } = useThemeContext();
2547
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
2572
+ const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2548
2573
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2549
2574
  ref: forwardedRef,
2550
2575
  readonly,
@@ -2552,53 +2577,369 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], sl
2552
2577
  markdownBundle({
2553
2578
  readonly,
2554
2579
  themeMode,
2555
- placeholder: slots?.editor?.placeholder
2580
+ placeholder: placeholder3
2556
2581
  }),
2557
2582
  ...extensions
2558
2583
  ],
2559
- slots,
2584
+ theme,
2585
+ slots: updatedSlots,
2560
2586
  ...props
2561
2587
  });
2562
2588
  });
2563
2589
  var defaultSlots = {
2564
2590
  root: {
2565
- className: mx3("p-2", inputSurface)
2591
+ className: mx3("p-2 overflow-y-auto", inputSurface)
2566
2592
  }
2567
2593
  };
2568
2594
  var defaultTextSlots = {
2569
- ...defaultSlots,
2570
- editor: {
2571
- theme: textTheme
2572
- }
2595
+ ...defaultSlots
2573
2596
  };
2574
2597
  var defaultMarkdownSlots = {
2575
- ...defaultSlots,
2576
- editor: {
2577
- theme: markdownTheme
2598
+ ...defaultSlots
2599
+ };
2600
+
2601
+ // packages/ui/react-ui-editor/src/hooks/automerge.ts
2602
+ import get4 from "lodash.get";
2603
+ import { getRawDoc } from "@dxos/echo-schema";
2604
+ import { isNotNullOrUndefined } from "@dxos/util";
2605
+
2606
+ // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2607
+ import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2608
+ import { Context as Context2 } from "@dxos/context";
2609
+ import { invariant as invariant3 } from "@dxos/invariant";
2610
+ import { log as log3 } from "@dxos/log";
2611
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2612
+ var DEBOUNCE_INTERVAL = 100;
2613
+ var SpaceAwarenessProvider = class {
2614
+ constructor(params) {
2615
+ this.remoteStateChange = new Event2();
2616
+ this._remoteStates = /* @__PURE__ */ new Map();
2617
+ this._space = params.space;
2618
+ this._channel = params.channel;
2619
+ this._peerId = params.peerId;
2620
+ this._info = params.info;
2621
+ }
2622
+ open() {
2623
+ this._ctx = new Context2();
2624
+ this._postTask = new DeferredTask(this._ctx, async () => {
2625
+ if (this._localState) {
2626
+ await this._space.postMessage(this._channel, {
2627
+ kind: "post",
2628
+ state: this._localState
2629
+ });
2630
+ await sleep(DEBOUNCE_INTERVAL);
2631
+ }
2632
+ });
2633
+ const unsubscribe = this._space.listen(this._channel, (message) => {
2634
+ switch (message.payload.kind) {
2635
+ case "query": {
2636
+ this._handleQueryMessage();
2637
+ break;
2638
+ }
2639
+ case "post": {
2640
+ this._handlePostMessage(message.payload);
2641
+ break;
2642
+ }
2643
+ }
2644
+ });
2645
+ this._ctx.onDispose(unsubscribe);
2646
+ void this._space.postMessage(this._channel, {
2647
+ kind: "query"
2648
+ }).catch((err) => {
2649
+ log3.debug("failed to query awareness", {
2650
+ err
2651
+ }, {
2652
+ F: __dxlog_file4,
2653
+ L: 85,
2654
+ S: this,
2655
+ C: (f, a) => f(...a)
2656
+ });
2657
+ });
2658
+ }
2659
+ close() {
2660
+ void this._ctx?.dispose();
2661
+ this._ctx = void 0;
2662
+ this._postTask = void 0;
2663
+ }
2664
+ update(position) {
2665
+ invariant3(this._postTask, void 0, {
2666
+ F: __dxlog_file4,
2667
+ L: 96,
2668
+ S: this,
2669
+ A: [
2670
+ "this._postTask",
2671
+ ""
2672
+ ]
2673
+ });
2674
+ this._localState = {
2675
+ peerId: this._peerId,
2676
+ position,
2677
+ info: this._info
2678
+ };
2679
+ this._postTask.schedule();
2680
+ }
2681
+ getRemoteStates() {
2682
+ return Array.from(this._remoteStates.values());
2683
+ }
2684
+ _handleQueryMessage() {
2685
+ invariant3(this._postTask, void 0, {
2686
+ F: __dxlog_file4,
2687
+ L: 111,
2688
+ S: this,
2689
+ A: [
2690
+ "this._postTask",
2691
+ ""
2692
+ ]
2693
+ });
2694
+ this._postTask.schedule();
2695
+ }
2696
+ _handlePostMessage(message) {
2697
+ invariant3(message.kind === "post", void 0, {
2698
+ F: __dxlog_file4,
2699
+ L: 116,
2700
+ S: this,
2701
+ A: [
2702
+ "message.kind === 'post'",
2703
+ ""
2704
+ ]
2705
+ });
2706
+ this._remoteStates.set(message.state.peerId, message.state);
2707
+ this.remoteStateChange.emit();
2708
+ }
2709
+ };
2710
+
2711
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2712
+ import { StateField as StateField5 } from "@codemirror/state";
2713
+ import { useEffect as useEffect2, useState as useState2 } from "react";
2714
+ import { isAutomergeObject } from "@dxos/react-client/echo";
2715
+
2716
+ // packages/ui/react-ui-editor/src/hooks/yjs.ts
2717
+ import * as decoding from "lib0/decoding";
2718
+ import * as encoding from "lib0/encoding";
2719
+ import { Observable } from "lib0/observable";
2720
+ import * as YP from "y-protocols/awareness";
2721
+ import { invariant as invariant4 } from "@dxos/invariant";
2722
+ import { log as log4 } from "@dxos/log";
2723
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2724
+ var createYjsModel = ({ identity, space, text }) => {
2725
+ invariant4(text?.doc && text?.content, void 0, {
2726
+ F: __dxlog_file5,
2727
+ L: 21,
2728
+ S: void 0,
2729
+ A: [
2730
+ "text?.doc && text?.content",
2731
+ ""
2732
+ ]
2733
+ });
2734
+ const provider = space ? new YAwarenessProvider({
2735
+ space,
2736
+ doc: text.doc,
2737
+ channel: `yjs.awareness.${text.id}`
2738
+ }) : void 0;
2739
+ const model = {
2740
+ id: text.doc.guid,
2741
+ content: text.content,
2742
+ text: () => text.content.toString(),
2743
+ extension: [
2744
+ modelState.init(() => model),
2745
+ yjs(text.content, provider?.awareness)
2746
+ ],
2747
+ awareness: provider?.awareness,
2748
+ peer: identity ? {
2749
+ id: identity.identityKey.toHex(),
2750
+ name: identity.profile?.displayName
2751
+ } : void 0
2752
+ };
2753
+ return model;
2754
+ };
2755
+ var messageAwareness = 1;
2756
+ var messageQueryAwareness = 3;
2757
+ var YAwarenessProvider = class extends Observable {
2758
+ constructor({ space, doc, channel, awareness: awareness2 }) {
2759
+ super();
2760
+ this._space = space;
2761
+ this._awareness = awareness2 ?? new YP.Awareness(doc);
2762
+ this._channel = channel;
2763
+ this._clientId = doc.clientID;
2764
+ this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
2765
+ this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
2766
+ if (typeof window !== "undefined") {
2767
+ window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
2768
+ } else if (typeof process !== "undefined") {
2769
+ process.on("exit", this._handleBeforeUnload.bind(this));
2770
+ }
2771
+ const encoderAwarenessQuery = encoding.createEncoder();
2772
+ encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
2773
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
2774
+ const encoderAwarenessState = encoding.createEncoder();
2775
+ encoding.writeVarUint(encoderAwarenessState, messageAwareness);
2776
+ encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [
2777
+ this._clientId
2778
+ ]));
2779
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
2780
+ }
2781
+ get awareness() {
2782
+ return this._awareness;
2783
+ }
2784
+ _handleAwarenessUpdate({ added, updated, removed }, origin) {
2785
+ log4("awareness update", {
2786
+ added,
2787
+ updated,
2788
+ removed,
2789
+ origin
2790
+ }, {
2791
+ F: __dxlog_file5,
2792
+ L: 100,
2793
+ S: this,
2794
+ C: (f, a) => f(...a)
2795
+ });
2796
+ const changedClients = added.concat(updated).concat(removed);
2797
+ const encoderAwareness = encoding.createEncoder();
2798
+ encoding.writeVarUint(encoderAwareness, messageAwareness);
2799
+ encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
2800
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2801
+ }
2802
+ _handleSpaceMessage({ payload }) {
2803
+ log4("space message", payload, {
2804
+ F: __dxlog_file5,
2805
+ L: 109,
2806
+ S: this,
2807
+ C: (f, a) => f(...a)
2808
+ });
2809
+ const data = new Uint8Array(Array.from(Object.values(payload)));
2810
+ const encoder = this._readMessage(data);
2811
+ if (encoder) {
2812
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
2813
+ }
2814
+ }
2815
+ _readMessage(message) {
2816
+ const decoder = decoding.createDecoder(message);
2817
+ const encoder = encoding.createEncoder();
2818
+ const messageType = decoding.readVarUint(decoder);
2819
+ let sendReply = false;
2820
+ switch (messageType) {
2821
+ case messageAwareness: {
2822
+ YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2823
+ break;
2824
+ }
2825
+ case messageQueryAwareness: {
2826
+ encoding.writeVarUint(encoder, messageAwareness);
2827
+ encoding.writeVarUint8Array(encoder, YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2828
+ sendReply = true;
2829
+ break;
2830
+ }
2831
+ default: {
2832
+ console.error("Invalid message:", messageType);
2833
+ return encoder;
2834
+ }
2835
+ }
2836
+ if (!sendReply) {
2837
+ return null;
2838
+ }
2839
+ return encoder;
2840
+ }
2841
+ _handleBeforeUnload() {
2842
+ YP.removeAwarenessStates(this._awareness, [
2843
+ this._clientId
2844
+ ], "window unload");
2845
+ }
2846
+ };
2847
+
2848
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2849
+ var modelState = StateField5.define({
2850
+ create: () => void 0,
2851
+ update: (model) => model
2852
+ });
2853
+ var useTextModel = (props) => {
2854
+ const { identity, space, text } = props;
2855
+ const [model, setModel] = useState2();
2856
+ useEffect2(() => setModel(createModel(props)), [
2857
+ identity,
2858
+ space,
2859
+ text
2860
+ ]);
2861
+ return model;
2862
+ };
2863
+ var createModel = (props) => {
2864
+ const { text } = props;
2865
+ if (isAutomergeObject(text)) {
2866
+ return createAutomergeModel(props);
2867
+ } else if (text?.doc) {
2868
+ return createYjsModel(props);
2869
+ } else {
2870
+ return void 0;
2578
2871
  }
2579
2872
  };
2873
+
2874
+ // packages/ui/react-ui-editor/src/hooks/automerge.ts
2875
+ var createAutomergeModel = ({ space, identity, text }) => {
2876
+ const obj = text;
2877
+ const doc = getRawDoc(obj, [
2878
+ obj.field
2879
+ ]);
2880
+ const awarenessProvider = space && new SpaceAwarenessProvider({
2881
+ space,
2882
+ channel: `automerge.awareness.${obj.id}`,
2883
+ info: {
2884
+ displayName: identity?.profile?.displayName ?? "",
2885
+ color: cursorColor.color,
2886
+ lightColor: cursorColor.light
2887
+ },
2888
+ peerId: identity?.identityKey.toHex() ?? "Anonymous"
2889
+ });
2890
+ const model = {
2891
+ id: obj.id,
2892
+ content: doc,
2893
+ text: () => get4(doc.handle.docSync(), doc.path),
2894
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2895
+ extension: [
2896
+ modelState.init(() => model),
2897
+ automerge3({
2898
+ handle: doc.handle,
2899
+ path: doc.path
2900
+ }),
2901
+ awarenessProvider && AwarenessProvider.of(awarenessProvider),
2902
+ awareness()
2903
+ ].filter(isNotNullOrUndefined),
2904
+ peer: identity ? {
2905
+ id: identity.identityKey.toHex(),
2906
+ name: identity.profile?.displayName
2907
+ } : void 0
2908
+ };
2909
+ return model;
2910
+ };
2580
2911
  export {
2912
+ AwarenessProvider,
2581
2913
  BaseTextEditor,
2914
+ Cursor,
2582
2915
  Doc,
2583
2916
  EditorModes,
2584
2917
  MarkdownEditor,
2918
+ RemoteSelectionsDecorator,
2585
2919
  SpaceAwarenessProvider,
2586
2920
  TextEditor,
2587
2921
  TextKind,
2922
+ YAwarenessProvider,
2588
2923
  YText,
2589
2924
  YXmlFragment,
2590
2925
  autocomplete,
2926
+ automerge3 as automerge,
2927
+ awareness,
2591
2928
  basicBundle,
2592
2929
  blast,
2930
+ callbackWrapper,
2593
2931
  code2 as code,
2594
2932
  codeTheme,
2595
2933
  comments,
2596
- cursorColor,
2934
+ createAutomergeModel,
2935
+ createYjsModel,
2936
+ cursorConverter,
2597
2937
  defaultMarkdownSlots,
2598
2938
  defaultOptions,
2599
2939
  defaultSlots,
2600
2940
  defaultTextSlots,
2601
2941
  defaultTheme,
2942
+ getToken,
2602
2943
  hr,
2603
2944
  image,
2604
2945
  link,
@@ -2609,7 +2950,6 @@ export {
2609
2950
  markdownTagsExtensions,
2610
2951
  markdownTheme,
2611
2952
  mention,
2612
- mermaid,
2613
2953
  modelState,
2614
2954
  setCommentRange,
2615
2955
  setFocus,
@@ -2619,6 +2959,7 @@ export {
2619
2959
  tasklist,
2620
2960
  textTheme,
2621
2961
  typewriter,
2622
- useTextModel
2962
+ useTextModel,
2963
+ yjs
2623
2964
  };
2624
2965
  //# sourceMappingURL=index.mjs.map