@dxos/react-ui-editor 0.3.11-main.6fccc32 → 0.3.11-main.73ee990

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 (160) hide show
  1. package/dist/lib/browser/index.mjs +1259 -1060
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +13 -14
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +14 -6
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +1 -1
  9. package/dist/types/src/components/TextEditor/comments.stories.d.ts +1 -1
  10. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/Toolbar/Toolbar.d.ts +9 -0
  12. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
  13. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +14 -0
  14. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
  15. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  16. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  17. package/dist/types/src/extensions/automerge/defs.d.ts +7 -9
  18. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  19. package/dist/types/src/extensions/automerge/index.d.ts +0 -1
  20. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
  21. package/dist/types/src/extensions/automerge/semaphore.d.ts +7 -12
  22. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  23. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
  24. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  25. package/dist/types/src/extensions/awareness.d.ts +1 -1
  26. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  27. package/dist/types/src/extensions/basic.d.ts +4 -5
  28. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  29. package/dist/types/src/extensions/comments.d.ts +35 -18
  30. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  31. package/dist/types/src/extensions/cursor.d.ts +3 -3
  32. package/dist/types/src/extensions/cursor.d.ts.map +1 -1
  33. package/dist/types/src/extensions/index.d.ts +1 -7
  34. package/dist/types/src/extensions/index.d.ts.map +1 -1
  35. package/dist/types/src/extensions/markdown/bundle.d.ts +3 -6
  36. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  37. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
  38. package/dist/types/src/extensions/markdown/formatting.d.ts +11 -0
  39. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  40. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  41. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  42. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  43. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  44. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  45. package/dist/types/src/extensions/markdown/index.d.ts +8 -0
  46. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  47. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  48. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  49. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  50. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  51. package/dist/types/src/extensions/outliner.d.ts +4 -0
  52. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  53. package/dist/types/src/hooks/awareness-provider.d.ts +7 -4
  54. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -1
  55. package/dist/types/src/hooks/defs.d.ts +25 -0
  56. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  57. package/dist/types/src/hooks/index.d.ts +3 -2
  58. package/dist/types/src/hooks/index.d.ts.map +1 -1
  59. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  60. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  61. package/dist/types/src/hooks/useTextEditor.d.ts +17 -0
  62. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  63. package/dist/types/src/hooks/useTextModel.d.ts +2 -27
  64. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  65. package/dist/types/src/styles/markdown.d.ts +2 -2
  66. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  67. package/dist/types/src/themes/default.d.ts.map +1 -1
  68. package/dist/types/src/util.d.ts +2 -0
  69. package/dist/types/src/util.d.ts.map +1 -1
  70. package/package.json +34 -37
  71. package/src/components/TextEditor/MarkdownEditor.stories.tsx +72 -80
  72. package/src/components/TextEditor/TextEditor.tsx +96 -70
  73. package/src/components/TextEditor/comments.stories.tsx +113 -71
  74. package/src/components/Toolbar/Toolbar.stories.tsx +86 -0
  75. package/src/components/Toolbar/Toolbar.tsx +115 -0
  76. package/src/extensions/autocomplete.ts +6 -3
  77. package/src/extensions/automerge/automerge.ts +39 -46
  78. package/src/extensions/automerge/defs.ts +10 -31
  79. package/src/extensions/automerge/index.ts +0 -1
  80. package/src/extensions/automerge/semaphore.ts +23 -20
  81. package/src/extensions/automerge/update-automerge.ts +14 -8
  82. package/src/extensions/awareness.ts +15 -14
  83. package/src/extensions/basic.ts +22 -17
  84. package/src/extensions/comments.ts +269 -162
  85. package/src/extensions/cursor.ts +7 -3
  86. package/src/extensions/index.ts +1 -7
  87. package/src/extensions/markdown/bundle.ts +20 -30
  88. package/src/extensions/markdown/code.ts +142 -0
  89. package/src/extensions/markdown/formatting.ts +159 -0
  90. package/src/extensions/markdown/heading.ts +59 -0
  91. package/src/extensions/markdown/highlight.ts +9 -15
  92. package/src/extensions/markdown/hr.ts +71 -0
  93. package/src/extensions/markdown/index.ts +8 -0
  94. package/src/extensions/{link.ts → markdown/link.ts} +8 -5
  95. package/src/extensions/markdown/tasklist.ts +110 -0
  96. package/src/extensions/mention.ts +9 -2
  97. package/src/extensions/outliner.ts +12 -0
  98. package/src/hooks/awareness-provider.ts +24 -20
  99. package/src/hooks/defs.ts +42 -0
  100. package/src/hooks/index.ts +4 -2
  101. package/src/hooks/useActionHandler.ts +45 -0
  102. package/src/hooks/useTextEditor.ts +29 -0
  103. package/src/hooks/useTextModel.ts +47 -56
  104. package/src/styles/markdown.ts +9 -10
  105. package/src/themes/default.ts +1 -11
  106. package/src/util.ts +26 -3
  107. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  108. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  109. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +0 -12
  110. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  111. package/dist/types/src/extensions/code.d.ts.map +0 -1
  112. package/dist/types/src/extensions/hr.d.ts.map +0 -1
  113. package/dist/types/src/extensions/image.d.ts.map +0 -1
  114. package/dist/types/src/extensions/link.d.ts.map +0 -1
  115. package/dist/types/src/extensions/table.d.ts.map +0 -1
  116. package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
  117. package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
  118. package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
  119. package/dist/types/src/extensions/yjs/index.d.ts +0 -2
  120. package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
  121. package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
  122. package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
  123. package/dist/types/src/extensions/yjs/yjs.test.d.ts +0 -2
  124. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
  125. package/dist/types/src/hooks/automerge.d.ts +0 -3
  126. package/dist/types/src/hooks/automerge.d.ts.map +0 -1
  127. package/dist/types/src/hooks/yjs.d.ts +0 -27
  128. package/dist/types/src/hooks/yjs.d.ts.map +0 -1
  129. package/dist/types/src/testing/index.d.ts +0 -3
  130. package/dist/types/src/testing/index.d.ts.map +0 -1
  131. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  132. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  133. package/dist/types/src/testing/proto/index.d.ts +0 -2
  134. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  135. package/dist/types/src/testing/replicator.d.ts +0 -45
  136. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  137. package/src/components/TextEditor/codemirror.stories.ts +0 -115
  138. package/src/components/TextEditor/yjs.stories.tsx +0 -56
  139. package/src/extensions/code.ts +0 -106
  140. package/src/extensions/hr.ts +0 -73
  141. package/src/extensions/tasklist.ts +0 -119
  142. package/src/extensions/yjs/cursor.ts +0 -22
  143. package/src/extensions/yjs/index.ts +0 -5
  144. package/src/extensions/yjs/yjs.test.ts +0 -35
  145. package/src/extensions/yjs/yjs.ts +0 -16
  146. package/src/hooks/automerge.ts +0 -52
  147. package/src/hooks/yjs.ts +0 -156
  148. package/src/testing/index.ts +0 -6
  149. package/src/testing/proto/gen/schema.ts +0 -49
  150. package/src/testing/proto/index.ts +0 -5
  151. package/src/testing/proto/schema.proto +0 -15
  152. package/src/testing/replicator.ts +0 -184
  153. /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
  154. /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
  155. /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
  156. /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
  157. /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
  158. /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.ts} +0 -0
  159. /package/src/extensions/{image.ts → markdown/image.ts} +0 -0
  160. /package/src/extensions/{table.ts → markdown/table.ts} +0 -0
@@ -7,14 +7,14 @@ import { tags as tags2 } from "@lezer/highlight";
7
7
 
8
8
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
9
9
  import { EditorState as EditorState2 } from "@codemirror/state";
10
- import { EditorView as EditorView12 } from "@codemirror/view";
10
+ import { EditorView as EditorView13 } from "@codemirror/view";
11
11
  import { useFocusableGroup } from "@fluentui/react-tabster";
12
12
  import { vim } from "@replit/codemirror-vim";
13
13
  import defaultsDeep2 from "lodash.defaultsdeep";
14
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState, useRef } from "react";
15
- import { generateName } from "@dxos/display-name";
14
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState } from "react";
15
+ import { log as log2 } from "@dxos/log";
16
16
  import { useThemeContext } from "@dxos/react-ui";
17
- import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
17
+ import { attentionSurface, mx as mx3 } from "@dxos/react-ui-theme";
18
18
 
19
19
  // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
20
20
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
@@ -29,9 +29,11 @@ var autocomplete = ({ onSearch }) => {
29
29
  // https://codemirror.net/examples/autocompletion
30
30
  // https://codemirror.net/docs/ref/#autocomplete.autocompletion
31
31
  autocompletion({
32
- defaultKeymap: false,
33
- // Don't start unless key press.
34
- activateOnTyping: false
32
+ activateOnTyping: true,
33
+ // closeOnBlur: false,
34
+ // defaultKeymap: false,
35
+ // TODO(burdon): Styles/fragments.
36
+ tooltipClass: () => "shadow rounded"
35
37
  }),
36
38
  // TODO(burdon): Option to create new page?
37
39
  // TODO(burdon): Optional decoration via addToOptions
@@ -51,9 +53,10 @@ var autocomplete = ({ onSearch }) => {
51
53
  };
52
54
 
53
55
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
54
- import { Facet as Facet2, StateField } from "@codemirror/state";
55
- import { ViewPlugin } from "@codemirror/view";
56
- import { next as A } from "@dxos/automerge/automerge";
56
+ import { StateField } from "@codemirror/state";
57
+ import { EditorView, ViewPlugin } from "@codemirror/view";
58
+ import { next as _automerge } from "@dxos/automerge/automerge";
59
+ import { invariant } from "@dxos/invariant";
57
60
 
58
61
  // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
59
62
  import get from "lodash.get";
@@ -94,19 +97,19 @@ var cursorConverter = (handle, path) => ({
94
97
  // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
95
98
  import { Annotation, StateEffect } from "@codemirror/state";
96
99
  var effectType = StateEffect.define({});
100
+ var reconcileAnnotationType = Annotation.define();
101
+ var getPath = (state, field) => state.field(field).path;
102
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
97
103
  var updateHeads = (newHeads) => effectType.of({
98
104
  newHeads
99
105
  });
100
- var getLastHeads = (state, field) => state.field(field).lastHeads;
101
- var getPath = (state, field) => state.field(field).path;
102
- var reconcileAnnotationType = Annotation.define();
103
106
  var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
104
107
 
105
108
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
106
- import { next as automerge2 } from "@dxos/automerge/automerge";
109
+ import { next as automerge3 } from "@dxos/automerge/automerge";
107
110
 
108
111
  // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
109
- import { next as am } from "@dxos/automerge/automerge";
112
+ import { next as automerge2 } from "@dxos/automerge/automerge";
110
113
  var updateAutomerge = (field, handle, transactions, state) => {
111
114
  const { lastHeads, path } = state.field(field);
112
115
  let hasChanges = false;
@@ -119,11 +122,19 @@ var updateAutomerge = (field, handle, transactions, state) => {
119
122
  return void 0;
120
123
  }
121
124
  const newHeads = handle.changeAt(lastHeads, (doc) => {
125
+ const t = [];
122
126
  for (const tr of transactions) {
123
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
124
- am.splice(doc, path, fromA, toA - fromA, inserted.toString());
127
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
128
+ t.push({
129
+ fromA,
130
+ toA,
131
+ insert
132
+ });
125
133
  });
126
134
  }
135
+ t.reverse().forEach(({ fromA, toA, insert }) => {
136
+ automerge2.splice(doc, path.slice(), fromA, toA - fromA, insert.toString());
137
+ });
127
138
  });
128
139
  return newHeads ?? void 0;
129
140
  };
@@ -237,40 +248,42 @@ var charPath = (textPath, candidatePath) => {
237
248
 
238
249
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
239
250
  var PatchSemaphore = class {
240
- constructor(_field) {
241
- this._field = _field;
251
+ // prettier-ignore
252
+ constructor(_handle, _state) {
253
+ this._handle = _handle;
254
+ this._state = _state;
242
255
  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);
256
+ }
257
+ // NOTE: Cannot destruct view.state.
258
+ reconcile(view) {
259
+ if (this._inReconcile) {
260
+ return;
261
+ }
262
+ this._inReconcile = true;
263
+ const path = getPath(view.state, this._state);
264
+ const oldHeads = getLastHeads(view.state, this._state);
265
+ let selection = view.state.selection;
266
+ const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
267
+ const toInvert = transactions.slice().reverse();
268
+ for (const tx of toInvert) {
269
+ const inverted = tx.changes.invert(tx.startState.doc);
270
+ selection = selection.map(inverted);
268
271
  view.dispatch({
269
- effects: updateHeads(newHeads),
270
- annotations: reconcileAnnotationType.of({})
272
+ changes: inverted,
273
+ annotations: reconcileAnnotationType.of(true)
271
274
  });
272
- this._inReconcile = false;
273
- };
275
+ }
276
+ let newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
277
+ if (newHeads === null || newHeads === void 0) {
278
+ newHeads = automerge3.getHeads(this._handle.docSync());
279
+ }
280
+ const diff = automerge3.equals(oldHeads, newHeads) ? [] : automerge3.diff(this._handle.docSync(), oldHeads, newHeads);
281
+ updateCodeMirror(view, selection, path, diff);
282
+ view.dispatch({
283
+ effects: updateHeads(newHeads),
284
+ annotations: reconcileAnnotationType.of({})
285
+ });
286
+ this._inReconcile = false;
274
287
  }
275
288
  };
276
289
 
@@ -280,16 +293,17 @@ var defaultCursorConverter = {
280
293
  toCursor: (position) => position.toString(),
281
294
  fromCursor: (cursor) => parseInt(cursor)
282
295
  };
283
- var Cursor = class {
296
+ var Cursor = class _Cursor {
284
297
  static {
285
298
  this.converter = Facet.define({
286
299
  combine: (providers) => providers[0] ?? defaultCursorConverter
287
300
  });
288
301
  }
289
302
  static {
290
- this.getCursorFromRange = (cursorConverter3, range) => {
291
- const from = cursorConverter3.toCursor(range.from);
292
- const to = cursorConverter3.toCursor(range.to, -1);
303
+ this.getCursorFromRange = (state, range) => {
304
+ const cursorConverter2 = state.facet(_Cursor.converter);
305
+ const from = cursorConverter2.toCursor(range.from);
306
+ const to = cursorConverter2.toCursor(range.to, -1);
293
307
  return [
294
308
  from,
295
309
  to
@@ -297,10 +311,11 @@ var Cursor = class {
297
311
  };
298
312
  }
299
313
  static {
300
- this.getRangeFromCursor = (cursorConverter3, cursor) => {
314
+ this.getRangeFromCursor = (state, cursor) => {
315
+ const cursorConverter2 = state.facet(_Cursor.converter);
301
316
  const parts = cursor.split(":");
302
- const from = cursorConverter3.fromCursor(parts[0]);
303
- const to = cursorConverter3.fromCursor(parts[1]);
317
+ const from = cursorConverter2.fromCursor(parts[0]);
318
+ const to = cursorConverter2.fromCursor(parts[1]);
304
319
  return from !== void 0 && to !== void 0 ? {
305
320
  from,
306
321
  to
@@ -310,21 +325,19 @@ var Cursor = class {
310
325
  };
311
326
 
312
327
  // 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({
328
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts";
329
+ var automerge4 = ({ handle, path }) => {
330
+ const state = StateField.define({
318
331
  create: () => ({
319
- lastHeads: A.getHeads(handle.docSync()),
320
- unreconciledTransactions: [],
321
- path: path.slice()
332
+ path: path.slice(),
333
+ lastHeads: _automerge.getHeads(handle.docSync()),
334
+ unreconciledTransactions: []
322
335
  }),
323
336
  update: (value, tr) => {
324
337
  const result = {
338
+ path: path.slice(),
325
339
  lastHeads: value.lastHeads,
326
- unreconciledTransactions: value.unreconciledTransactions.slice(),
327
- path: path.slice()
340
+ unreconciledTransactions: value.unreconciledTransactions.slice()
328
341
  };
329
342
  let clearUnreconciled = false;
330
343
  for (const effect of tr.effects) {
@@ -343,39 +356,44 @@ var automerge3 = ({ handle, path }) => {
343
356
  return result;
344
357
  }
345
358
  });
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
+ const semaphore = new PatchSemaphore(handle, state);
360
+ return [
361
+ Cursor.converter.of(cursorConverter(handle, path)),
362
+ // Reconcile external updates.
363
+ ViewPlugin.fromClass(class {
364
+ constructor(_view) {
365
+ this._view = _view;
366
+ invariant(this._view, void 0, {
367
+ F: __dxlog_file,
368
+ L: 66,
369
+ S: this,
370
+ A: [
371
+ "this._view",
372
+ ""
373
+ ]
359
374
  });
375
+ handle.addListener("change", this._handleChange.bind(this));
360
376
  }
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
- };
377
+ destroy() {
378
+ handle.removeListener("change", this._handleChange.bind(this));
379
+ }
380
+ _handleChange() {
381
+ semaphore.reconcile(this._view);
382
+ }
383
+ }),
384
+ // Reconcile local updates.
385
+ EditorView.updateListener.of(({ view, changes }) => {
386
+ if (!changes.empty) {
387
+ semaphore.reconcile(view);
388
+ }
389
+ }),
390
+ state
391
+ ];
374
392
  };
375
393
 
376
394
  // 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";
395
+ import { Annotation as Annotation2, Facet as Facet2, RangeSet } from "@codemirror/state";
396
+ import { Decoration, EditorView as EditorView2, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
379
397
  import { Event } from "@dxos/async";
380
398
  import { Context } from "@dxos/context";
381
399
  var dummyProvider = {
@@ -384,11 +402,11 @@ var dummyProvider = {
384
402
  },
385
403
  close: () => {
386
404
  },
405
+ getRemoteStates: () => [],
387
406
  update: () => {
388
- },
389
- getRemoteStates: () => []
407
+ }
390
408
  };
391
- var AwarenessProvider = Facet3.define({
409
+ var AwarenessProvider = Facet2.define({
392
410
  combine: (providers) => providers[0] ?? dummyProvider
393
411
  });
394
412
  var RemoteSelectionChangedAnnotation = Annotation2.define();
@@ -549,7 +567,7 @@ var RemoteCaretWidget = class extends WidgetType {
549
567
  return true;
550
568
  }
551
569
  };
552
- var styles = EditorView.baseTheme({
570
+ var styles = EditorView2.baseTheme({
553
571
  ".cm-collab-selection": {},
554
572
  ".cm-collab-selectionLine": {
555
573
  padding: 0,
@@ -562,40 +580,42 @@ var styles = EditorView.baseTheme({
562
580
  marginLeft: "-1px",
563
581
  marginRight: "-1px",
564
582
  boxSizing: "border-box",
565
- display: "inline"
583
+ display: "inline",
584
+ cursor: "pointer"
566
585
  },
567
586
  ".cm-collab-selectionCaretDot": {
568
587
  borderRadius: "50%",
569
588
  position: "absolute",
570
- width: ".4em",
571
- height: ".4em",
572
- top: "-.2em",
573
- left: "-.2em",
589
+ width: ".5em",
590
+ height: ".5em",
591
+ top: "-.25em",
592
+ left: "-.25em",
574
593
  backgroundColor: "inherit",
575
594
  transition: "transform .3s ease-in-out",
576
595
  boxSizing: "border-box"
577
596
  },
578
597
  ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
579
- transformOrigin: "bottom center",
580
- transform: "scale(0)"
598
+ transform: "scale(0)",
599
+ transformOrigin: "center"
581
600
  },
582
601
  ".cm-collab-selectionInfo": {
583
602
  position: "absolute",
584
- top: "-1.05em",
585
- left: "-1px",
603
+ transform: "translate(-50%, 0)",
604
+ top: "-20px",
605
+ left: 0,
586
606
  fontSize: ".75em",
587
- fontFamily: "serif",
607
+ fontFamily: "sans-serif",
588
608
  fontStyle: "normal",
589
609
  fontWeight: "normal",
590
610
  lineHeight: "normal",
591
611
  userSelect: "none",
592
612
  color: "white",
593
- paddingLeft: "2px",
594
- paddingRight: "2px",
613
+ padding: "2px",
595
614
  zIndex: 101,
596
615
  transition: "opacity .3s ease-in-out",
597
616
  backgroundColor: "inherit",
598
- // these should be separate
617
+ borderRadius: "2px",
618
+ // These should be separate.
599
619
  opacity: 0,
600
620
  transitionDelay: "0s",
601
621
  whiteSpace: "nowrap"
@@ -608,26 +628,28 @@ var styles = EditorView.baseTheme({
608
628
 
609
629
  // packages/ui/react-ui-editor/src/extensions/basic.ts
610
630
  import { closeBrackets } from "@codemirror/autocomplete";
631
+ import { history } from "@codemirror/commands";
611
632
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
612
633
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
613
- import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
614
- var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
615
- EditorView2.lineWrapping,
616
- // TODO(burdon): Compare with minimalSetup.
634
+ import { drawSelection, EditorView as EditorView3, highlightActiveLine, placeholder } from "@codemirror/view";
635
+ var basicBundle = ({ themeMode, placeholder: _placeholder, lineWrapping = true }) => [
636
+ lineWrapping ? EditorView3.lineWrapping : [],
617
637
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
618
638
  bracketMatching(),
619
639
  closeBrackets(),
620
640
  drawSelection(),
621
- _placeholder && placeholder(_placeholder),
622
- // TODO(burdon): Is this required?
641
+ highlightActiveLine(),
642
+ history(),
643
+ _placeholder ? placeholder(_placeholder) : [],
644
+ // https://github.com/codemirror/theme-one-dark
623
645
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
624
- ].filter(Boolean);
646
+ ];
625
647
 
626
648
  // packages/ui/react-ui-editor/src/extensions/blast.ts
627
- import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
649
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
628
650
  import defaultsDeep from "lodash.defaultsdeep";
629
- import { invariant } from "@dxos/invariant";
630
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
651
+ import { invariant as invariant2 } from "@dxos/invariant";
652
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
631
653
  var defaultOptions = {
632
654
  effect: 2,
633
655
  maxParticles: 200,
@@ -660,10 +682,10 @@ var blast = (options = defaultOptions) => {
660
682
  const getPoint = (view, pos) => {
661
683
  const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
662
684
  const element = document.elementFromPoint(x, y);
663
- const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
685
+ const { offsetLeft: left = 0, offsetTop: top2 = 0 } = view.scrollDOM.parentElement ?? {};
664
686
  const point = {
665
687
  x: x - left,
666
- y: y - top
688
+ y: y - top2
667
689
  };
668
690
  return {
669
691
  element,
@@ -672,7 +694,7 @@ var blast = (options = defaultOptions) => {
672
694
  };
673
695
  return [
674
696
  // Cursor moved.
675
- EditorView3.updateListener.of((update2) => {
697
+ EditorView4.updateListener.of((update2) => {
676
698
  if (blaster?.node !== update2.view.scrollDOM) {
677
699
  if (blaster) {
678
700
  blaster.destroy();
@@ -753,8 +775,8 @@ var Blaster = class {
753
775
  return this._node;
754
776
  }
755
777
  initialize() {
756
- invariant(!this._canvas && !this._ctx, void 0, {
757
- F: __dxlog_file,
778
+ invariant2(!this._canvas && !this._ctx, void 0, {
779
+ F: __dxlog_file2,
758
780
  L: 138,
759
781
  S: this,
760
782
  A: [
@@ -790,8 +812,8 @@ var Blaster = class {
790
812
  }
791
813
  }
792
814
  start() {
793
- invariant(this._canvas && this._ctx, void 0, {
794
- F: __dxlog_file,
815
+ invariant2(this._canvas && this._ctx, void 0, {
816
+ F: __dxlog_file2,
795
817
  L: 177,
796
818
  S: this,
797
819
  A: [
@@ -933,9 +955,13 @@ var random = (min, max) => {
933
955
  return min + ~~(Math.random() * (max - min + 1));
934
956
  };
935
957
 
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";
958
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
959
+ import { Facet as Facet3, StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
960
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration2, EditorView as EditorView5 } from "@codemirror/view";
961
+ import sortBy from "lodash.sortby";
962
+ import { useEffect } from "react";
963
+ import { debounce } from "@dxos/async";
964
+ import { nonNullable } from "@dxos/util";
939
965
 
940
966
  // packages/ui/react-ui-editor/src/styles/cursors.ts
941
967
  import * as random2 from "lib0/random";
@@ -978,34 +1004,15 @@ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
978
1004
  // packages/ui/react-ui-editor/src/styles/markdown.ts
979
1005
  import { mx } from "@dxos/react-ui-theme";
980
1006
  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);
1007
+ 1: "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
1008
+ 2: "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
1009
+ 3: "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
1010
+ 4: "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
1011
+ 5: "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
1012
+ 6: "mbs-4 mbe-2 font-medium text-inherit no-underline"
1013
+ };
1014
+ var heading = (level) => {
1015
+ return headings[level];
1009
1016
  };
1010
1017
  var light = "text-neutral-200 dark:text-neutral-800";
1011
1018
  var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
@@ -1023,168 +1030,111 @@ import { tailwindConfig } from "@dxos/react-ui-theme";
1023
1030
  var tokens = tailwindConfig({}).theme;
1024
1031
  var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue);
1025
1032
 
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
- }
1052
- });
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
- ];
1060
- };
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
- }
1077
- }
1078
- return Decoration2.set(decorations);
1079
- };
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
- ];
1100
- };
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
1033
  // packages/ui/react-ui-editor/src/util.ts
1112
1034
  import { log } from "@dxos/log";
1113
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1035
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1114
1036
  var callbackWrapper = (fn) => (...args) => {
1115
1037
  try {
1116
1038
  return fn(...args);
1117
- } catch (error) {
1118
- log.catch(error, void 0, {
1119
- F: __dxlog_file2,
1120
- L: 16,
1039
+ } catch (err) {
1040
+ log.catch(err, void 0, {
1041
+ F: __dxlog_file3,
1042
+ L: 18,
1121
1043
  S: void 0,
1122
1044
  C: (f, a) => f(...a)
1123
1045
  });
1124
1046
  }
1125
1047
  };
1048
+ var logChanges = (trs) => {
1049
+ const changes = trs.flatMap((tr) => {
1050
+ if (tr.changes.empty) {
1051
+ return void 0;
1052
+ }
1053
+ const changes2 = [];
1054
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => changes2.push(JSON.stringify({
1055
+ fromA,
1056
+ toA,
1057
+ fromB,
1058
+ toB,
1059
+ inserted: inserted.toString()
1060
+ })));
1061
+ return changes2;
1062
+ }).filter(Boolean);
1063
+ if (changes.length) {
1064
+ console.log("changes", changes);
1065
+ }
1066
+ };
1126
1067
 
1127
1068
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1128
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1129
- var styles3 = EditorView5.baseTheme({
1069
+ var styles2 = EditorView5.baseTheme({
1130
1070
  "& .cm-comment": {
1131
1071
  backgroundColor: getToken("extend.colors.yellow.50")
1132
1072
  },
1133
- "& .cm-comment-active": {
1073
+ "& .cm-comment-current": {
1134
1074
  backgroundColor: getToken("extend.colors.yellow.100")
1135
1075
  }
1136
1076
  });
1137
1077
  var marks = {
1138
- highlight: Decoration3.mark({
1078
+ highlight: Decoration2.mark({
1139
1079
  class: "cm-comment"
1140
1080
  }),
1141
- highlightActive: Decoration3.mark({
1142
- class: "cm-comment-active"
1081
+ highlightActive: Decoration2.mark({
1082
+ class: "cm-comment-current"
1143
1083
  })
1144
1084
  };
1145
- var setFocus = (view, thread) => {
1146
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1147
- if (!range) {
1085
+ var setFocus = (view, id, center = true) => {
1086
+ const comment = view.state.field(commentsState).comments.find((range2) => range2.comment.id === id);
1087
+ if (!comment?.comment.cursor) {
1148
1088
  return;
1149
1089
  }
1150
- view.dispatch({
1151
- effects: setSelection.of({
1152
- active: thread
1153
- }),
1154
- selection: {
1155
- anchor: range.from
1156
- },
1157
- scrollIntoView: true
1158
- });
1090
+ const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);
1091
+ if (range) {
1092
+ view.dispatch({
1093
+ selection: {
1094
+ anchor: range.from
1095
+ },
1096
+ effects: [
1097
+ //
1098
+ EditorView5.scrollIntoView(range.from, center ? {
1099
+ y: "center"
1100
+ } : void 0),
1101
+ setSelection.of({
1102
+ current: id
1103
+ })
1104
+ ]
1105
+ });
1106
+ }
1159
1107
  };
1160
- var setCommentRange = StateEffect2.define();
1108
+ var setComments = StateEffect2.define();
1161
1109
  var setSelection = StateEffect2.define();
1162
1110
  var setCommentState = StateEffect2.define();
1163
- var commentsStateField = StateField2.define({
1111
+ var commentsState = StateField2.define({
1164
1112
  create: () => ({
1165
- ranges: []
1113
+ comments: [],
1114
+ selection: {}
1166
1115
  }),
1167
1116
  update: (value, tr) => {
1168
- const cursorConverter3 = tr.state.facet(Cursor.converter);
1169
1117
  for (const effect of tr.effects) {
1170
1118
  if (effect.is(setSelection)) {
1171
1119
  return {
1172
1120
  ...value,
1173
- ...effect.value
1121
+ selection: effect.value
1174
1122
  };
1175
1123
  }
1176
- if (effect.is(setCommentRange)) {
1177
- const { comments: comments2 } = effect.value;
1178
- const ranges = comments2.map((comment) => {
1179
- const range = Cursor.getRangeFromCursor(cursorConverter3, comment.cursor);
1124
+ if (effect.is(setComments)) {
1125
+ const comments2 = effect.value.map((comment) => {
1126
+ if (!comment.cursor) {
1127
+ return void 0;
1128
+ }
1129
+ const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);
1180
1130
  return range && {
1181
- ...comment,
1182
- ...range
1131
+ comment,
1132
+ range
1183
1133
  };
1184
1134
  }).filter(nonNullable);
1185
1135
  return {
1186
1136
  ...value,
1187
- ranges
1137
+ comments: comments2
1188
1138
  };
1189
1139
  }
1190
1140
  if (effect.is(setCommentState)) {
@@ -1195,86 +1145,129 @@ var commentsStateField = StateField2.define({
1195
1145
  }
1196
1146
  });
1197
1147
  var highlightDecorations = EditorView5.decorations.compute([
1198
- commentsStateField
1148
+ commentsState
1199
1149
  ], (state) => {
1200
- const { active, ranges } = state.field(commentsStateField);
1201
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1202
- if (selection.from === selection.to) {
1150
+ const { selection: { current }, comments: comments2 } = state.field(commentsState);
1151
+ const decorations = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
1152
+ const range = comment.range;
1153
+ if (!range || range.from === range.to) {
1154
+ console.warn("Invalid range:", range);
1203
1155
  return void 0;
1204
1156
  }
1205
- const range = {
1206
- from: selection.from,
1207
- to: selection.to
1208
- };
1209
- if (selection.id === active) {
1157
+ if (comment.comment.id === current) {
1210
1158
  return marks.highlightActive.range(range.from, range.to);
1211
1159
  } else {
1212
1160
  return marks.highlight.range(range.from, range.to);
1213
1161
  }
1214
- }).filter(nonNullable) ?? [];
1215
- return Decoration3.set(decorations);
1162
+ }).filter(nonNullable);
1163
+ return Decoration2.set(decorations);
1216
1164
  });
1217
- var comments = (options = {}) => {
1218
- const { key: shortcut = "meta-'" } = options;
1219
- const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1220
- const createCommentThread = (view) => {
1221
- const cursorConverter3 = view.state.facet(Cursor.converter);
1222
- invariant2(options.onCreate, void 0, {
1223
- F: __dxlog_file3,
1224
- L: 177,
1225
- S: void 0,
1226
- A: [
1227
- "options.onCreate",
1228
- ""
1229
- ]
1230
- });
1231
- const { head, from, to } = view.state.selection.main;
1232
- if (from === to) {
1233
- return false;
1165
+ var trackPastedComments = (onUpdate) => {
1166
+ let tracked = null;
1167
+ const handleTrack = (event, view) => {
1168
+ const comments2 = view.state.field(commentsState);
1169
+ const { main } = view.state.selection;
1170
+ const selectedRanges = comments2.comments.filter(({ range }) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1171
+ if (!selectedRanges.length) {
1172
+ tracked = null;
1173
+ } else {
1174
+ tracked = {
1175
+ text: view.state.doc.slice(main.from, main.to),
1176
+ comments: selectedRanges.map(({ comment, range }) => ({
1177
+ id: comment.id,
1178
+ from: range.from - main.from,
1179
+ to: range.to - main.from
1180
+ }))
1181
+ };
1234
1182
  }
1235
- const cursor = Cursor.getCursorFromRange(cursorConverter3, {
1236
- from,
1237
- to
1238
- });
1239
- if (cursor) {
1240
- const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
1241
- if (id) {
1242
- view.dispatch({
1243
- effects: setSelection.of({
1244
- active: id
1245
- }),
1246
- selection: {
1247
- anchor: from
1248
- }
1249
- });
1250
- if (options.footnote) {
1251
- const tag = `[^${id}]`;
1252
- view.dispatch({
1253
- changes: {
1254
- from: head,
1255
- insert: tag
1256
- },
1257
- selection: {
1258
- anchor: head + tag.length
1183
+ };
1184
+ return [
1185
+ EditorView5.domEventHandlers({
1186
+ cut: handleTrack,
1187
+ copy: handleTrack
1188
+ }),
1189
+ // Handle paste.
1190
+ EditorView5.updateListener.of(({ view, state, transactions }) => {
1191
+ if (tracked) {
1192
+ const paste = transactions.find((tr) => tr.isUserEvent("input.paste"));
1193
+ if (paste) {
1194
+ let found = -1;
1195
+ paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1196
+ if (text.eq(tracked.text)) {
1197
+ for (let i = transactions.indexOf(paste) + 1; i < transactions.length; i++) {
1198
+ fromB = transactions[i].changes.mapPos(fromB);
1199
+ }
1200
+ found = fromB;
1259
1201
  }
1260
1202
  });
1203
+ if (found > -1) {
1204
+ for (const moved of tracked.comments) {
1205
+ const range = {
1206
+ from: found + moved.from,
1207
+ to: found + moved.to
1208
+ };
1209
+ const cursor = Cursor.getCursorFromRange(state, range);
1210
+ onUpdate(moved.id, cursor);
1211
+ }
1212
+ }
1213
+ tracked = null;
1261
1214
  }
1262
- return true;
1263
1215
  }
1264
- }
1216
+ })
1217
+ ];
1218
+ };
1219
+ var optionsFacet = Facet3.define({
1220
+ combine: (providers) => providers[0]
1221
+ });
1222
+ var createComment = (view) => {
1223
+ const options = view.state.facet(optionsFacet);
1224
+ const { from, to } = view.state.selection.main;
1225
+ if (from === to) {
1265
1226
  return false;
1266
- };
1227
+ }
1228
+ if (to === view.state.doc.length) {
1229
+ view.dispatch({
1230
+ changes: {
1231
+ from: to,
1232
+ insert: "\n"
1233
+ }
1234
+ });
1235
+ }
1236
+ const cursor = Cursor.getCursorFromRange(view.state, {
1237
+ from,
1238
+ to
1239
+ });
1240
+ if (cursor) {
1241
+ const id = options.onCreate?.(cursor, view.coordsAtPos(from));
1242
+ if (id) {
1243
+ view.dispatch({
1244
+ effects: setSelection.of({
1245
+ current: id
1246
+ }),
1247
+ selection: {
1248
+ anchor: from
1249
+ }
1250
+ });
1251
+ return true;
1252
+ }
1253
+ }
1254
+ return false;
1255
+ };
1256
+ var comments = (options = {}) => {
1257
+ const { key: shortcut = "meta-'" } = options;
1258
+ const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1267
1259
  return [
1268
- commentsStateField,
1260
+ optionsFacet.of(options),
1261
+ commentsState,
1269
1262
  highlightDecorations,
1270
- styles3,
1263
+ styles2,
1271
1264
  //
1272
1265
  // Keymap.
1273
1266
  //
1274
1267
  options.onCreate ? keymap3.of([
1275
1268
  {
1276
1269
  key: shortcut,
1277
- run: callbackWrapper(createCommentThread)
1270
+ run: callbackWrapper(createComment)
1278
1271
  }
1279
1272
  ]) : [],
1280
1273
  //
@@ -1290,7 +1283,7 @@ var comments = (options = {}) => {
1290
1283
  above: true,
1291
1284
  create: () => {
1292
1285
  const el = document.createElement("div");
1293
- options.onHover?.(el, shortcut);
1286
+ options.onHover(el, shortcut);
1294
1287
  return {
1295
1288
  dom: el,
1296
1289
  offset: {
@@ -1303,364 +1296,115 @@ var comments = (options = {}) => {
1303
1296
  }
1304
1297
  return null;
1305
1298
  }, {
1306
- hideOnChange: true,
1299
+ // TODO(burdon): Hide on change triggered immediately?
1300
+ // hideOnChange: true,
1307
1301
  hoverTime: 1e3
1308
1302
  }) : [],
1309
1303
  //
1310
- // Monitor cursor movement and text updates.
1311
- // TODO(burdon): Is there a better (finer grained) way to do this?
1304
+ // Track deleted ranges and update ranges for decorations.
1312
1305
  //
1313
1306
  EditorView5.updateListener.of(({ view, state, changes }) => {
1314
- const cursorConverter3 = view.state.facet(Cursor.converter);
1315
- {
1316
- let mod = false;
1317
- const { active, ranges } = state.field(commentsStateField);
1318
- changes.iterChanges((from, to, from2, to2) => {
1319
- ranges.forEach((range) => {
1320
- if (from2 === to2) {
1321
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1322
- if (!newRange || newRange.to - newRange.from === 0) {
1323
- log2.info("deleted comment", {
1324
- thread: range.id
1325
- }, {
1326
- F: __dxlog_file3,
1327
- L: 274,
1328
- S: void 0,
1329
- C: (f, a) => f(...a)
1330
- });
1331
- }
1332
- }
1333
- if (from <= range.to) {
1334
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1335
- Object.assign(range, newRange);
1336
- mod = true;
1307
+ let mod = false;
1308
+ const { comments: comments2, ...value } = state.field(commentsState);
1309
+ changes.iterChanges((from, to, from2, to2) => {
1310
+ comments2.forEach(({ comment, range }) => {
1311
+ if (from2 === to2) {
1312
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1313
+ if (!newRange || newRange.to - newRange.from === 0) {
1314
+ options.onDelete?.(comment.id);
1337
1315
  }
1338
- });
1316
+ }
1317
+ if (from <= range.to) {
1318
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1319
+ Object.assign(range, newRange);
1320
+ mod = true;
1321
+ }
1322
+ });
1323
+ });
1324
+ if (mod) {
1325
+ view.dispatch({
1326
+ effects: setCommentState.of({
1327
+ comments: comments2,
1328
+ ...value
1329
+ })
1339
1330
  });
1340
- if (mod) {
1341
- view.dispatch({
1342
- effects: setCommentState.of({
1343
- active,
1344
- ranges
1345
- })
1346
- });
1347
- }
1348
1331
  }
1349
- {
1350
- const { head } = state.selection.main;
1351
- let min = Infinity;
1352
- const { active, closest, ranges } = state.field(commentsStateField);
1353
- const selected = {
1354
- active: void 0,
1355
- closest: void 0
1356
- };
1357
- ranges.forEach((comment) => {
1358
- const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1359
- if (head >= comment.from && head <= comment.to) {
1360
- selected.active = comment.id;
1361
- }
1332
+ }),
1333
+ //
1334
+ // Track selection/proximity.
1335
+ //
1336
+ EditorView5.updateListener.of(({ view, state }) => {
1337
+ let min = Infinity;
1338
+ const { selection: { current, closest }, comments: comments2 } = state.field(commentsState);
1339
+ const { head } = state.selection.main;
1340
+ const selection = {};
1341
+ comments2.forEach(({ comment, range }) => {
1342
+ if (head >= range.from && head <= range.to) {
1343
+ selection.current = comment.id;
1344
+ selection.closest = void 0;
1345
+ }
1346
+ if (!selection.current) {
1347
+ const d = Math.min(Math.abs(head - range.from), Math.abs(head - range.to));
1362
1348
  if (d < min) {
1363
- selected.closest = comment.id;
1349
+ selection.closest = comment.id;
1364
1350
  min = d;
1365
1351
  }
1366
- });
1367
- if (selected.active !== active || selected.closest !== closest) {
1368
- view.dispatch({
1369
- effects: setSelection.of(selected)
1370
- });
1371
- handleSelect({
1372
- ...selected,
1373
- ranges: ranges.map((range) => ({
1374
- ...range,
1375
- location: view.coordsAtPos(range.from)
1376
- }))
1377
- });
1378
1352
  }
1353
+ });
1354
+ if (selection.current !== current || selection.closest !== closest) {
1355
+ view.dispatch({
1356
+ effects: setSelection.of(selection)
1357
+ });
1358
+ handleSelect({
1359
+ selection,
1360
+ comments: comments2.map(({ comment, range }) => ({
1361
+ comment,
1362
+ range,
1363
+ location: view.coordsAtPos(range.from)
1364
+ }))
1365
+ });
1379
1366
  }
1380
- })
1367
+ }),
1368
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
1381
1369
  ];
1382
1370
  };
1371
+ var useComments = (view, comments2 = []) => {
1372
+ useEffect(() => {
1373
+ if (view) {
1374
+ view.dispatch({
1375
+ effects: setComments.of(comments2)
1376
+ });
1377
+ }
1378
+ }, [
1379
+ view,
1380
+ comments2
1381
+ ]);
1382
+ };
1383
1383
 
1384
- // packages/ui/react-ui-editor/src/extensions/hr.ts
1385
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1386
- var styles4 = EditorView6.baseTheme({
1387
- "& .cm-hr": {
1388
- // TODO(burdon): ???
1389
- // Note that block-level decorations should not have vertical margins,
1390
- borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1391
- }
1392
- });
1393
- var HorizontalRuleWidget = class extends WidgetType2 {
1394
- constructor(_pos) {
1395
- super();
1396
- this._pos = _pos;
1397
- }
1398
- eq(other) {
1399
- return this._pos === other._pos;
1400
- }
1401
- toDOM(view) {
1402
- const el = document.createElement("div");
1403
- el.className = "cm-hr";
1404
- return el;
1405
- }
1406
- };
1407
- var placeholderMatcher = new MatchDecorator({
1408
- regexp: /^---$/g,
1409
- decoration: (match, view, pos) => Decoration4.replace({
1410
- widget: new HorizontalRuleWidget(pos)
1411
- })
1412
- });
1413
- var hr = () => [
1414
- styles4,
1415
- ViewPlugin4.fromClass(class {
1416
- constructor(view) {
1417
- this.rules = placeholderMatcher.createDeco(view);
1418
- }
1419
- update(update2) {
1420
- this.rules = placeholderMatcher.updateDeco(update2, this.rules);
1421
- }
1422
- }, {
1423
- decorations: (instance) => instance.rules,
1424
- // TODO(burdon): Is this really atomic (cursor can move into ---).
1425
- provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1426
- return view.plugin(plugin)?.rules || Decoration4.none;
1427
- })
1428
- })
1429
- ];
1430
-
1431
- // packages/ui/react-ui-editor/src/extensions/image.ts
1432
- import { syntaxTree } from "@codemirror/language";
1433
- import { StateField as StateField3 } from "@codemirror/state";
1434
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
1435
- var image = (options = {}) => {
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)
1462
- });
1463
- };
1464
- var buildDecorations = (from, to, state) => {
1465
- const decorations = [];
1466
- const cursor = state.selection.main.head;
1467
- syntaxTree(state).iterate({
1468
- enter: (node) => {
1469
- if (node.name === "Image") {
1470
- const urlNode = node.node.getChild("URL");
1471
- if (urlNode) {
1472
- const hide = state.readOnly || cursor < node.from || cursor > node.to;
1473
- const url = state.sliceDoc(urlNode.from, urlNode.to);
1474
- decorations.push(Decoration5.replace({
1475
- block: true,
1476
- widget: new ImageWidget(url)
1477
- }).range(hide ? node.from : node.to, node.to));
1478
- }
1479
- }
1480
- },
1481
- from,
1482
- to
1483
- });
1484
- return decorations;
1485
- };
1486
- var ImageWidget = class extends WidgetType3 {
1487
- constructor(_url) {
1488
- super();
1489
- this._url = _url;
1490
- }
1491
- eq(other) {
1492
- return this._url === other._url;
1493
- }
1494
- toDOM(view) {
1495
- const img = document.createElement("img");
1496
- img.setAttribute("src", this._url);
1497
- img.setAttribute("class", "cm-image");
1498
- img.onload = () => img.classList.add("cm-loaded-image");
1499
- return img;
1500
- }
1501
- };
1502
-
1503
- // packages/ui/react-ui-editor/src/extensions/link.ts
1504
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1505
- import { RangeSetBuilder } from "@codemirror/state";
1506
- import { Decoration as Decoration6, WidgetType as WidgetType4, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1507
- import { tooltipContent } from "@dxos/react-ui-theme";
1508
- var link = (options = {}) => {
1509
- const extensions = [
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
1521
- })
1522
- ];
1523
- if (options.onHover) {
1524
- extensions.push(
1525
- // https://codemirror.net/examples/tooltip
1526
- // https://codemirror.net/docs/ref/#view.hoverTooltip
1527
- // https://github.com/codemirror/view/blob/main/src/tooltip.ts
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) {
1536
- return null;
1537
- }
1538
- const urlText = view.state.sliceDoc(url.from, url.to);
1539
- return {
1540
- pos: link2.from,
1541
- end: link2.to,
1542
- above: true,
1543
- create: () => {
1544
- const el = document.createElement("div");
1545
- el.className = tooltipContent({}, "pli-2 plb-1");
1546
- options.onHover(el, urlText);
1547
- return {
1548
- dom: el,
1549
- offset: {
1550
- x: 0,
1551
- y: 4
1552
- }
1553
- };
1554
- }
1555
- };
1556
- })
1557
- );
1558
- }
1559
- return extensions;
1560
- };
1561
- var LinkButton = class extends WidgetType4 {
1562
- constructor(_url, _onAttach) {
1563
- super();
1564
- this._url = _url;
1565
- this._onAttach = _onAttach;
1566
- }
1567
- eq(other) {
1568
- return this._url === other._url;
1569
- }
1570
- toDOM(view) {
1571
- const el = document.createElement("span");
1572
- this._onAttach(el, this._url);
1573
- return el;
1574
- }
1575
- };
1576
- var LinkText = class extends WidgetType4 {
1577
- constructor(_text, _url) {
1578
- super();
1579
- this._text = _text;
1580
- this._url = _url;
1581
- }
1582
- eq(other) {
1583
- return this._url === other._url;
1584
- }
1585
- toDOM(view) {
1586
- const link2 = document.createElement("a");
1587
- link2.setAttribute("class", "cm-link");
1588
- link2.textContent = this._text;
1589
- if (this._url) {
1590
- link2.setAttribute("rel", "noreferrer");
1591
- link2.setAttribute("target", "_blank");
1592
- link2.href = this._url;
1593
- } else {
1594
- link2.onclick = () => {
1595
- view.dispatch({
1596
- selection: {
1597
- anchor: view.posAtDOM(link2)
1598
- }
1599
- });
1600
- };
1601
- }
1602
- return link2;
1603
- }
1604
- };
1605
- var buildDecorations2 = (view, options) => {
1606
- const builder = new RangeSetBuilder();
1607
- const { state } = view;
1608
- const cursor = state.selection.main.head;
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
- }
1630
- return false;
1631
- }
1632
- },
1633
- from,
1634
- to
1635
- });
1636
- }
1637
- return builder.finish();
1638
- };
1639
-
1640
- // packages/ui/react-ui-editor/src/extensions/listener.ts
1641
- import { EditorView as EditorView8 } from "@codemirror/view";
1642
- var listener = ({ onFocus, onChange }) => {
1643
- const extensions = [];
1644
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1645
- onFocus(focused);
1646
- return null;
1647
- }));
1648
- onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1649
- onChange(update2.state.doc.toString());
1650
- }));
1651
- return extensions;
1384
+ // packages/ui/react-ui-editor/src/extensions/listener.ts
1385
+ import { EditorView as EditorView6 } from "@codemirror/view";
1386
+ var listener = ({ onFocus, onChange }) => {
1387
+ const extensions = [];
1388
+ onFocus && extensions.push(EditorView6.focusChangeEffect.of((_, focused) => {
1389
+ onFocus(focused);
1390
+ return null;
1391
+ }));
1392
+ onChange && extensions.push(EditorView6.updateListener.of((update2) => {
1393
+ onChange(update2.state.doc.toString());
1394
+ }));
1395
+ return extensions;
1652
1396
  };
1653
1397
 
1654
1398
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1655
1399
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1656
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1400
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1657
1401
  import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1658
1402
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1659
1403
  import { languages } from "@codemirror/language-data";
1660
1404
  import { searchKeymap } from "@codemirror/search";
1661
1405
  import { EditorState } from "@codemirror/state";
1662
1406
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1663
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1407
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView7, highlightActiveLine as highlightActiveLine2, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1664
1408
 
1665
1409
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1666
1410
  import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
@@ -1757,7 +1501,7 @@ var markdownHighlightStyle = (readonly) => {
1757
1501
  markdownTags.QuoteMark,
1758
1502
  markdownTags.EmphasisMark
1759
1503
  ],
1760
- class: readonly ? "hidden" : mark
1504
+ class: mark
1761
1505
  },
1762
1506
  // E.g., code block language (after ```).
1763
1507
  {
@@ -1765,7 +1509,7 @@ var markdownHighlightStyle = (readonly) => {
1765
1509
  tags.function(tags.variableName),
1766
1510
  tags.labelName
1767
1511
  ],
1768
- class: readonly ? "hidden" : codeMark
1512
+ class: codeMark
1769
1513
  },
1770
1514
  {
1771
1515
  tag: [
@@ -1776,27 +1520,27 @@ var markdownHighlightStyle = (readonly) => {
1776
1520
  // Headings.
1777
1521
  {
1778
1522
  tag: tags.heading1,
1779
- class: heading(1, readonly)
1523
+ class: heading(1)
1780
1524
  },
1781
1525
  {
1782
1526
  tag: tags.heading2,
1783
- class: heading(2, readonly)
1527
+ class: heading(2)
1784
1528
  },
1785
1529
  {
1786
1530
  tag: tags.heading3,
1787
- class: heading(3, readonly)
1531
+ class: heading(3)
1788
1532
  },
1789
1533
  {
1790
1534
  tag: tags.heading4,
1791
- class: heading(4, readonly)
1535
+ class: heading(4)
1792
1536
  },
1793
1537
  {
1794
1538
  tag: tags.heading5,
1795
- class: heading(5, readonly)
1539
+ class: heading(5)
1796
1540
  },
1797
1541
  {
1798
1542
  tag: tags.heading6,
1799
- class: heading(6, readonly)
1543
+ class: heading(6)
1800
1544
  },
1801
1545
  // Emphasis.
1802
1546
  {
@@ -1822,7 +1566,7 @@ var markdownHighlightStyle = (readonly) => {
1822
1566
  // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1823
1567
  // when a language is specified. In this case, the syntax highlighting extensions will colorize
1824
1568
  // the code, but all other CSS properties will be inherited.
1825
- // IMPORTANT: Therefore, the fenced code block will use the base editor font.
1569
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font unless changed by an extension.
1826
1570
  {
1827
1571
  tag: [
1828
1572
  markdownTags.CodeText,
@@ -1851,21 +1595,21 @@ var markdownHighlightStyle = (readonly) => {
1851
1595
  };
1852
1596
 
1853
1597
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1854
- var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1598
+ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
1855
1599
  return [
1856
1600
  EditorState.allowMultipleSelections.of(true),
1857
1601
  EditorState.tabSize.of(2),
1858
- EditorView9.lineWrapping,
1859
- customKeymap(),
1602
+ EditorView7.lineWrapping,
1603
+ // https://github.com/codemirror/basic-setup
1860
1604
  bracketMatching2(),
1861
1605
  closeBrackets2(),
1862
1606
  crosshairCursor(),
1863
1607
  dropCursor(),
1864
1608
  drawSelection2(),
1865
- highlightActiveLine(),
1866
- history(),
1609
+ highlightActiveLine2(),
1610
+ history2(),
1867
1611
  indentOnInput(),
1868
- _placeholder && placeholder2(_placeholder),
1612
+ _placeholder ? placeholder2(_placeholder) : [],
1869
1613
  // Main extension.
1870
1614
  // https://github.com/codemirror/lang-markdown
1871
1615
  // https://codemirror.net/5/mode/markdown/index.html (demo).
@@ -1887,58 +1631,575 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1887
1631
  // https://github.com/codemirror/theme-one-dark
1888
1632
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1889
1633
  // Custom styles.
1890
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1891
- ].filter(Boolean);
1892
- };
1893
- var customKeymap = () => keymap4.of([
1894
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1895
- indentWithTab,
1896
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1897
- ...standardKeymap,
1898
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1899
- ...historyKeymap,
1900
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1901
- ...closeBracketsKeymap,
1902
- // https://codemirror.net/docs/ref/#search.searchKeymap
1903
- ...searchKeymap
1904
- ]);
1634
+ syntaxHighlighting2(markdownHighlightStyle()),
1635
+ keymap4.of([
1636
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1637
+ indentWithTab,
1638
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1639
+ ...closeBracketsKeymap,
1640
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
1641
+ ...historyKeymap,
1642
+ // https://codemirror.net/docs/ref/#search.searchKeymap
1643
+ ...searchKeymap,
1644
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
1645
+ ...standardKeymap
1646
+ ])
1647
+ ];
1648
+ };
1905
1649
 
1906
- // packages/ui/react-ui-editor/src/extensions/mention.ts
1907
- import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1908
- var mention = ({ onSearch }) => {
1909
- return autocompletion2({
1910
- closeOnBlur: false,
1911
- override: [
1912
- (context) => {
1913
- const match = context.matchBefore(/@(\w+)?/);
1914
- if (!match || match.from === match.to && !context.explicit) {
1915
- return null;
1650
+ // packages/ui/react-ui-editor/src/extensions/markdown/code.ts
1651
+ import { syntaxTree } from "@codemirror/language";
1652
+ import { RangeSetBuilder } from "@codemirror/state";
1653
+ import { ViewPlugin as ViewPlugin3, EditorView as EditorView8, Decoration as Decoration3, WidgetType as WidgetType2 } from "@codemirror/view";
1654
+ import { mx as mx2 } from "@dxos/react-ui-theme";
1655
+ var styles3 = EditorView8.baseTheme({
1656
+ "& .cm-code": {
1657
+ paddingInline: "1rem !important",
1658
+ fontFamily: getToken("fontFamily.mono", []).join(",")
1659
+ },
1660
+ "& .cm-codeblock": {
1661
+ display: "inline-block",
1662
+ width: "100%",
1663
+ position: "relative",
1664
+ "&::after": {
1665
+ content: '""',
1666
+ position: "absolute",
1667
+ inset: 0
1668
+ }
1669
+ },
1670
+ "&light .cm-codeblock": {
1671
+ "&::after": {
1672
+ background: getToken("extend.semanticColors.input.light"),
1673
+ mixBlendMode: "darken"
1674
+ }
1675
+ },
1676
+ "&dark .cm-codeblock": {
1677
+ "&::after": {
1678
+ background: getToken("extend.semanticColors.input.dark"),
1679
+ mixBlendMode: "lighten"
1680
+ }
1681
+ },
1682
+ "& .cm-codeblock-first": {
1683
+ "&::after": {
1684
+ borderTopLeftRadius: ".5rem",
1685
+ borderTopRightRadius: ".5rem"
1686
+ }
1687
+ },
1688
+ "& .cm-codeblock-last": {
1689
+ "&::after": {
1690
+ borderBottomLeftRadius: ".5rem",
1691
+ borderBottomRightRadius: ".5rem"
1692
+ }
1693
+ }
1694
+ });
1695
+ var getLineRange = (lines, from, to) => {
1696
+ const start = lines.findIndex((line) => line.from >= from);
1697
+ const end = lines.findIndex((line) => line.to >= to);
1698
+ return [
1699
+ start,
1700
+ end
1701
+ ];
1702
+ };
1703
+ var LineWidget = class extends WidgetType2 {
1704
+ constructor(_className) {
1705
+ super();
1706
+ this._className = _className;
1707
+ }
1708
+ toDOM() {
1709
+ const el = document.createElement("span");
1710
+ el.innerHTML = "&nbsp;";
1711
+ el.className = mx2("cm-code cm-codeblock", this._className);
1712
+ return el;
1713
+ }
1714
+ };
1715
+ var top = new LineWidget("cm-codeblock-first");
1716
+ var bottom = new LineWidget("cm-codeblock-last");
1717
+ var buildDecorations = (view) => {
1718
+ const builder = new RangeSetBuilder();
1719
+ const { state } = view;
1720
+ const blocks = view.viewportLineBlocks;
1721
+ const cursor = state.selection.main.head;
1722
+ for (const { from, to } of view.visibleRanges) {
1723
+ syntaxTree(state).iterate({
1724
+ enter: (node) => {
1725
+ if (node.name === "FencedCode") {
1726
+ const edit = !view.state.readOnly && cursor >= node.from && cursor <= node.to;
1727
+ const range = getLineRange(blocks, node.from, node.to);
1728
+ for (let i = range[0]; i <= range[1]; i++) {
1729
+ const block = blocks[i];
1730
+ if (!edit && (i === range[0] || i === range[1])) {
1731
+ builder.add(block.from, block.to, Decoration3.replace({
1732
+ widget: i === range[0] ? top : bottom
1733
+ }));
1734
+ } else {
1735
+ builder.add(block.from, block.from, Decoration3.line({
1736
+ class: mx2("cm-code cm-codeblock")
1737
+ }));
1738
+ }
1739
+ }
1740
+ }
1741
+ },
1742
+ from,
1743
+ to
1744
+ });
1745
+ }
1746
+ return builder.finish();
1747
+ };
1748
+ var code2 = () => {
1749
+ return [
1750
+ ViewPlugin3.fromClass(class {
1751
+ constructor(view) {
1752
+ this.decorations = buildDecorations(view);
1753
+ }
1754
+ update(update2) {
1755
+ this.decorations = buildDecorations(update2.view);
1756
+ }
1757
+ }, {
1758
+ decorations: (value) => value.decorations
1759
+ }),
1760
+ styles3
1761
+ ];
1762
+ };
1763
+
1764
+ // packages/ui/react-ui-editor/src/extensions/markdown/formatting.ts
1765
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1766
+ import { RangeSetBuilder as RangeSetBuilder2 } from "@codemirror/state";
1767
+ import { Decoration as Decoration4, keymap as keymap5, ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1768
+ var setHeading = (level) => (view) => {
1769
+ const { selection: { ranges }, doc } = view.state;
1770
+ const changes = [];
1771
+ for (const range of ranges) {
1772
+ const { number } = doc.lineAt(range.anchor);
1773
+ const { from } = doc.line(number);
1774
+ const line = doc.sliceString(from, from + 7);
1775
+ const [_, marks2, spaces] = line.match(/(#+)(\s+)/) ?? [];
1776
+ const current = marks2?.length ?? 0;
1777
+ if (level !== current) {
1778
+ changes.push({
1779
+ from,
1780
+ to: from + current + (spaces?.length ?? 0),
1781
+ insert: "#".repeat(level) + (level > 0 ? " " : "")
1782
+ });
1783
+ }
1784
+ }
1785
+ if (changes.length) {
1786
+ view.dispatch({
1787
+ changes
1788
+ });
1789
+ }
1790
+ return true;
1791
+ };
1792
+ var toggleStyle = (mark2) => (view) => {
1793
+ const { ranges } = view.state.selection;
1794
+ for (const range of ranges) {
1795
+ if (range.from === range.to) {
1796
+ return false;
1797
+ }
1798
+ view.dispatch({
1799
+ changes: [
1800
+ {
1801
+ from: range.from,
1802
+ insert: mark2
1803
+ },
1804
+ {
1805
+ from: range.to,
1806
+ insert: mark2
1807
+ }
1808
+ ]
1809
+ });
1810
+ }
1811
+ return true;
1812
+ };
1813
+ var toggleBold = toggleStyle("**");
1814
+ var toggleItalic = toggleStyle("_");
1815
+ var toggleStrikethrough = toggleStyle("~~");
1816
+ var toggleList = (view) => {
1817
+ };
1818
+ var formatting = (options = {}) => {
1819
+ return [
1820
+ keymap5.of([
1821
+ {
1822
+ key: "meta-b",
1823
+ run: toggleBold
1824
+ }
1825
+ ]),
1826
+ styling()
1827
+ ];
1828
+ };
1829
+ var styling = () => {
1830
+ const buildDecorations7 = (view) => {
1831
+ const builder = new RangeSetBuilder2();
1832
+ const { state } = view;
1833
+ const cursor = state.selection.main.head;
1834
+ const replace = (node, marks2) => {
1835
+ if (cursor <= node.from || cursor >= node.to) {
1836
+ for (const mark2 of marks2) {
1837
+ builder.add(mark2.from, mark2.to, Decoration4.replace({}));
1838
+ }
1839
+ }
1840
+ };
1841
+ for (const { from, to } of view.visibleRanges) {
1842
+ syntaxTree2(state).iterate({
1843
+ enter: (node) => {
1844
+ switch (node.name) {
1845
+ case "Emphasis":
1846
+ case "StrongEmphasis": {
1847
+ const marks2 = node.node.getChildren("EmphasisMark");
1848
+ replace(node, marks2);
1849
+ break;
1850
+ }
1851
+ case "Strikethrough": {
1852
+ const marks2 = node.node.getChildren("StrikethroughMark");
1853
+ replace(node, marks2);
1854
+ break;
1855
+ }
1856
+ }
1857
+ },
1858
+ from,
1859
+ to
1860
+ });
1861
+ }
1862
+ return builder.finish();
1863
+ };
1864
+ return [
1865
+ ViewPlugin4.fromClass(class {
1866
+ constructor(view) {
1867
+ this.decorations = buildDecorations7(view);
1868
+ }
1869
+ update(update2) {
1870
+ this.decorations = buildDecorations7(update2.view);
1871
+ }
1872
+ }, {
1873
+ decorations: (value) => value.decorations
1874
+ })
1875
+ ];
1876
+ };
1877
+
1878
+ // packages/ui/react-ui-editor/src/extensions/markdown/heading.ts
1879
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1880
+ import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
1881
+ import { Decoration as Decoration5, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1882
+ var buildDecorations2 = (view) => {
1883
+ const builder = new RangeSetBuilder3();
1884
+ const { state } = view;
1885
+ const cursor = state.selection.main.head;
1886
+ for (const { from, to } of view.visibleRanges) {
1887
+ syntaxTree3(state).iterate({
1888
+ enter: (node) => {
1889
+ switch (node.name) {
1890
+ case "ATXHeading1":
1891
+ case "ATXHeading2":
1892
+ case "ATXHeading3":
1893
+ case "ATXHeading4":
1894
+ case "ATXHeading5":
1895
+ case "ATXHeading6": {
1896
+ const mark2 = node.node.getChild("HeaderMark");
1897
+ if (mark2) {
1898
+ if (!view.hasFocus || view.state.readOnly || cursor < node.from || cursor > node.to) {
1899
+ builder.add(mark2.from, mark2.to + 1, Decoration5.replace({}));
1900
+ }
1901
+ }
1902
+ }
1903
+ }
1904
+ },
1905
+ from,
1906
+ to
1907
+ });
1908
+ }
1909
+ return builder.finish();
1910
+ };
1911
+ var heading2 = () => {
1912
+ return [
1913
+ ViewPlugin5.fromClass(class {
1914
+ constructor(view) {
1915
+ this.decorations = buildDecorations2(view);
1916
+ }
1917
+ update(update2) {
1918
+ this.decorations = buildDecorations2(update2.view);
1919
+ }
1920
+ }, {
1921
+ decorations: (value) => value.decorations
1922
+ })
1923
+ ];
1924
+ };
1925
+
1926
+ // packages/ui/react-ui-editor/src/extensions/markdown/hr.ts
1927
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
1928
+ import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
1929
+ import { Decoration as Decoration6, EditorView as EditorView9, ViewPlugin as ViewPlugin6, WidgetType as WidgetType3 } from "@codemirror/view";
1930
+ var styles4 = EditorView9.baseTheme({
1931
+ "& .cm-hr": {
1932
+ // Note that block-level decorations should not have vertical margins,
1933
+ borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
1934
+ }
1935
+ });
1936
+ var HorizontalRuleWidget = class extends WidgetType3 {
1937
+ toDOM() {
1938
+ const el = document.createElement("div");
1939
+ el.className = "cm-hr";
1940
+ return el;
1941
+ }
1942
+ };
1943
+ var decoration = Decoration6.replace({
1944
+ widget: new HorizontalRuleWidget()
1945
+ });
1946
+ var buildDecorations3 = (view) => {
1947
+ const builder = new RangeSetBuilder4();
1948
+ const { state } = view;
1949
+ const cursor = state.selection.main.head;
1950
+ for (const { from, to } of view.visibleRanges) {
1951
+ syntaxTree4(state).iterate({
1952
+ enter: (node) => {
1953
+ if (node.name === "HorizontalRule") {
1954
+ if (cursor <= node.from || cursor >= node.to) {
1955
+ builder.add(node.from, node.to, decoration);
1956
+ }
1957
+ }
1958
+ },
1959
+ from,
1960
+ to
1961
+ });
1962
+ }
1963
+ return builder.finish();
1964
+ };
1965
+ var hr = () => {
1966
+ return [
1967
+ ViewPlugin6.fromClass(class {
1968
+ constructor(view) {
1969
+ this.decorations = buildDecorations3(view);
1970
+ }
1971
+ update(update2) {
1972
+ this.decorations = buildDecorations3(update2.view);
1973
+ }
1974
+ }, {
1975
+ decorations: (value) => value.decorations
1976
+ }),
1977
+ styles4
1978
+ ];
1979
+ };
1980
+
1981
+ // packages/ui/react-ui-editor/src/extensions/markdown/image.ts
1982
+ import { syntaxTree as syntaxTree5 } from "@codemirror/language";
1983
+ import { StateField as StateField3 } from "@codemirror/state";
1984
+ import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType4 } from "@codemirror/view";
1985
+ var image = (options = {}) => {
1986
+ return StateField3.define({
1987
+ create: (state) => {
1988
+ return Decoration7.set(buildDecorations4(0, state.doc.length, state));
1989
+ },
1990
+ update: (value, tr) => {
1991
+ if (!tr.docChanged && !tr.selection) {
1992
+ return value;
1993
+ }
1994
+ const cursor = tr.state.selection.main.head;
1995
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
1996
+ let from = Math.min(cursor, oldCursor);
1997
+ let to = Math.max(cursor, oldCursor);
1998
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
1999
+ from = Math.min(from, fromB);
2000
+ to = Math.max(to, toB);
2001
+ });
2002
+ from = tr.state.doc.lineAt(from).from;
2003
+ to = tr.state.doc.lineAt(to).to;
2004
+ return value.map(tr.changes).update({
2005
+ filterFrom: from,
2006
+ filterTo: to,
2007
+ filter: () => false,
2008
+ add: buildDecorations4(from, to, tr.state)
2009
+ });
2010
+ },
2011
+ provide: (field) => EditorView10.decorations.from(field)
2012
+ });
2013
+ };
2014
+ var buildDecorations4 = (from, to, state) => {
2015
+ const decorations = [];
2016
+ const cursor = state.selection.main.head;
2017
+ syntaxTree5(state).iterate({
2018
+ enter: (node) => {
2019
+ if (node.name === "Image") {
2020
+ const urlNode = node.node.getChild("URL");
2021
+ if (urlNode) {
2022
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
2023
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
2024
+ decorations.push(Decoration7.replace({
2025
+ block: true,
2026
+ widget: new ImageWidget(url)
2027
+ }).range(hide ? node.from : node.to, node.to));
2028
+ }
2029
+ }
2030
+ },
2031
+ from,
2032
+ to
2033
+ });
2034
+ return decorations;
2035
+ };
2036
+ var ImageWidget = class extends WidgetType4 {
2037
+ constructor(_url) {
2038
+ super();
2039
+ this._url = _url;
2040
+ }
2041
+ eq(other) {
2042
+ return this._url === other._url;
2043
+ }
2044
+ toDOM(view) {
2045
+ const img = document.createElement("img");
2046
+ img.setAttribute("src", this._url);
2047
+ img.setAttribute("class", "cm-image");
2048
+ img.onload = () => img.classList.add("cm-loaded-image");
2049
+ return img;
2050
+ }
2051
+ };
2052
+
2053
+ // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
2054
+ import { syntaxTree as syntaxTree6 } from "@codemirror/language";
2055
+ import { RangeSetBuilder as RangeSetBuilder5 } from "@codemirror/state";
2056
+ import { hoverTooltip as hoverTooltip2, Decoration as Decoration8, ViewPlugin as ViewPlugin7, WidgetType as WidgetType5 } from "@codemirror/view";
2057
+ import { tooltipContent } from "@dxos/react-ui-theme";
2058
+ var link = (options = {}) => {
2059
+ const extensions = [
2060
+ ViewPlugin7.fromClass(class {
2061
+ constructor(view) {
2062
+ this.decorations = buildDecorations5(view, options);
2063
+ }
2064
+ update(update2) {
2065
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2066
+ this.decorations = buildDecorations5(update2.view, options);
2067
+ }
2068
+ }
2069
+ }, {
2070
+ decorations: (value) => value.decorations
2071
+ })
2072
+ ];
2073
+ if (options.onHover) {
2074
+ extensions.push(
2075
+ // https://codemirror.net/examples/tooltip
2076
+ // https://codemirror.net/docs/ref/#view.hoverTooltip
2077
+ // https://github.com/codemirror/view/blob/main/src/tooltip.ts
2078
+ hoverTooltip2((view, pos, side) => {
2079
+ const syntax = syntaxTree6(view.state).resolveInner(pos, side);
2080
+ let link2 = null;
2081
+ for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
2082
+ link2 = node.name === "Link" ? node : null;
2083
+ }
2084
+ const url = link2 && link2.getChild("URL");
2085
+ if (!url || !link2) {
2086
+ return null;
2087
+ }
2088
+ const urlText = view.state.sliceDoc(url.from, url.to);
2089
+ return {
2090
+ pos: link2.from,
2091
+ end: link2.to,
2092
+ above: true,
2093
+ create: () => {
2094
+ const el = document.createElement("div");
2095
+ el.className = tooltipContent({}, "pli-2 plb-1");
2096
+ options.onHover(el, urlText);
2097
+ return {
2098
+ dom: el,
2099
+ offset: {
2100
+ x: 0,
2101
+ y: 4
2102
+ }
2103
+ };
2104
+ }
2105
+ };
2106
+ })
2107
+ );
2108
+ }
2109
+ return extensions;
2110
+ };
2111
+ var LinkButton = class extends WidgetType5 {
2112
+ constructor(_url, _onAttach) {
2113
+ super();
2114
+ this._url = _url;
2115
+ this._onAttach = _onAttach;
2116
+ }
2117
+ eq(other) {
2118
+ return this._url === other._url;
2119
+ }
2120
+ toDOM(view) {
2121
+ const el = document.createElement("span");
2122
+ this._onAttach(el, this._url);
2123
+ return el;
2124
+ }
2125
+ };
2126
+ var LinkText = class extends WidgetType5 {
2127
+ constructor(_text, _url) {
2128
+ super();
2129
+ this._text = _text;
2130
+ this._url = _url;
2131
+ }
2132
+ eq(other) {
2133
+ return this._url === other._url;
2134
+ }
2135
+ toDOM(view) {
2136
+ const link2 = document.createElement("a");
2137
+ link2.setAttribute("class", "cm-link");
2138
+ link2.textContent = this._text;
2139
+ if (this._url) {
2140
+ link2.setAttribute("rel", "noreferrer");
2141
+ link2.setAttribute("target", "_blank");
2142
+ link2.href = this._url;
2143
+ } else {
2144
+ link2.onclick = () => {
2145
+ view.dispatch({
2146
+ selection: {
2147
+ anchor: view.posAtDOM(link2)
2148
+ }
2149
+ });
2150
+ };
2151
+ }
2152
+ return link2;
2153
+ }
2154
+ };
2155
+ var buildDecorations5 = (view, options) => {
2156
+ const builder = new RangeSetBuilder5();
2157
+ const { state } = view;
2158
+ const cursor = state.selection.main.head;
2159
+ for (const { from, to } of view.visibleRanges) {
2160
+ syntaxTree6(state).iterate({
2161
+ enter: (node) => {
2162
+ if (node.name === "Link") {
2163
+ const marks2 = node.node.getChildren("LinkMark");
2164
+ const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
2165
+ const urlNode = node.node.getChild("URL");
2166
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
2167
+ if (!url) {
2168
+ return false;
2169
+ }
2170
+ if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
2171
+ builder.add(node.from, node.to, Decoration8.replace({
2172
+ widget: new LinkText(text, options.link ? url : void 0)
2173
+ }));
2174
+ }
2175
+ if (options.onRender) {
2176
+ builder.add(node.to, node.to, Decoration8.replace({
2177
+ widget: new LinkButton(url, options.onRender)
2178
+ }));
2179
+ }
2180
+ return false;
1916
2181
  }
1917
- console.log(match);
1918
- return {
1919
- from: match.from,
1920
- options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1921
- label: `@${value}`
1922
- }))
1923
- };
1924
- }
1925
- ]
1926
- });
2182
+ },
2183
+ from,
2184
+ to
2185
+ });
2186
+ }
2187
+ return builder.finish();
1927
2188
  };
1928
2189
 
1929
- // packages/ui/react-ui-editor/src/extensions/table.ts
1930
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
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";
2190
+ // packages/ui/react-ui-editor/src/extensions/markdown/table.ts
2191
+ import { syntaxTree as syntaxTree7 } from "@codemirror/language";
2192
+ import { RangeSetBuilder as RangeSetBuilder6, StateField as StateField4 } from "@codemirror/state";
2193
+ import { Decoration as Decoration9, EditorView as EditorView11, WidgetType as WidgetType6 } from "@codemirror/view";
1933
2194
  var table = (options = {}) => {
1934
2195
  return StateField4.define({
1935
2196
  create: (state) => update(state, options),
1936
2197
  update: (_, tr) => update(tr.state, options),
1937
- provide: (field) => EditorView10.decorations.from(field)
2198
+ provide: (field) => EditorView11.decorations.from(field)
1938
2199
  });
1939
2200
  };
1940
2201
  var update = (state, options) => {
1941
- const builder = new RangeSetBuilder2();
2202
+ const builder = new RangeSetBuilder6();
1942
2203
  const cursor = state.selection.main.head;
1943
2204
  const tables = [];
1944
2205
  const getTable = () => tables[tables.length - 1];
@@ -1946,7 +2207,7 @@ var update = (state, options) => {
1946
2207
  const table2 = getTable();
1947
2208
  return table2.rows?.[table2.rows.length - 1];
1948
2209
  };
1949
- syntaxTree3(state).iterate({
2210
+ syntaxTree7(state).iterate({
1950
2211
  enter: (node) => {
1951
2212
  switch (node.name) {
1952
2213
  case "Table": {
@@ -1978,16 +2239,16 @@ var update = (state, options) => {
1978
2239
  });
1979
2240
  tables.forEach((table2) => {
1980
2241
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1981
- hide && builder.add(table2.from, table2.to, Decoration7.replace({
2242
+ hide && builder.add(table2.from, table2.to, Decoration9.replace({
1982
2243
  widget: new TableWidget(table2)
1983
2244
  }));
1984
- builder.add(table2.from, table2.to, Decoration7.mark({
2245
+ builder.add(table2.from, table2.to, Decoration9.mark({
1985
2246
  class: "cm-table"
1986
2247
  }));
1987
2248
  });
1988
2249
  return builder.finish();
1989
2250
  };
1990
- var TableWidget = class extends WidgetType5 {
2251
+ var TableWidget = class extends WidgetType6 {
1991
2252
  constructor(_table) {
1992
2253
  super();
1993
2254
  this._table = _table;
@@ -2024,92 +2285,139 @@ var TableWidget = class extends WidgetType5 {
2024
2285
  }
2025
2286
  };
2026
2287
 
2027
- // packages/ui/react-ui-editor/src/extensions/tasklist.ts
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({
2030
- "& .cm-task-item": {
2031
- paddingLeft: "4px",
2032
- paddingRight: "8px"
2288
+ // packages/ui/react-ui-editor/src/extensions/markdown/tasklist.ts
2289
+ import { syntaxTree as syntaxTree8 } from "@codemirror/language";
2290
+ import { RangeSetBuilder as RangeSetBuilder7 } from "@codemirror/state";
2291
+ import { EditorView as EditorView12, Decoration as Decoration10, WidgetType as WidgetType7, ViewPlugin as ViewPlugin8 } from "@codemirror/view";
2292
+ var styles5 = EditorView12.baseTheme({
2293
+ "& .cm-task": {
2294
+ color: getToken("extend.colors.blue.500")
2295
+ },
2296
+ "& .cm-task-checkbox": {
2297
+ marginLeft: "4px",
2298
+ marginRight: "4px"
2033
2299
  }
2034
2300
  });
2035
- var CheckboxWidget = class extends WidgetType6 {
2036
- constructor(_checked, _indent, _onCheck) {
2301
+ var CheckboxWidget = class extends WidgetType7 {
2302
+ constructor(_checked) {
2037
2303
  super();
2038
2304
  this._checked = _checked;
2039
- this._indent = _indent;
2040
- this._onCheck = _onCheck;
2041
2305
  }
2042
2306
  eq(other) {
2043
- return this._checked === other._checked && this._indent === other._indent;
2307
+ return this._checked === other._checked;
2044
2308
  }
2045
2309
  toDOM(view) {
2046
- const wrap = document.createElement("span");
2047
- wrap.className = "cm-task-item";
2048
- wrap.setAttribute("aria-hidden", "true");
2049
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
2050
- const input = wrap.appendChild(document.createElement("input"));
2310
+ const input = document.createElement("input");
2311
+ input.className = "cm-task-checkbox";
2051
2312
  input.type = "checkbox";
2052
2313
  input.checked = this._checked;
2053
- if (!this._onCheck) {
2314
+ if (view.state.readOnly) {
2054
2315
  input.setAttribute("disabled", "true");
2055
- }
2056
- if (this._onCheck) {
2057
- input.onchange = (event) => {
2058
- this._onCheck?.(event.target.checked);
2059
- return true;
2316
+ } else {
2317
+ input.onmousedown = (event) => {
2318
+ const pos = view.posAtDOM(input);
2319
+ const text = view.state.sliceDoc(pos, pos + 3);
2320
+ if (text === (this._checked ? "[x]" : "[ ]")) {
2321
+ view.dispatch({
2322
+ changes: {
2323
+ from: pos + 1,
2324
+ to: pos + 2,
2325
+ insert: this._checked ? " " : "x"
2326
+ }
2327
+ });
2328
+ event.preventDefault();
2329
+ }
2060
2330
  };
2061
2331
  }
2062
- return wrap;
2332
+ return input;
2063
2333
  }
2064
2334
  ignoreEvent() {
2065
2335
  return false;
2066
2336
  }
2067
2337
  };
2338
+ var checkedDecoration = Decoration10.replace({
2339
+ widget: new CheckboxWidget(true)
2340
+ });
2341
+ var uncheckedDecoration = Decoration10.replace({
2342
+ widget: new CheckboxWidget(false)
2343
+ });
2344
+ var buildDecorations6 = (view) => {
2345
+ const builder = new RangeSetBuilder7();
2346
+ const { state } = view;
2347
+ const cursor = state.selection.main.head;
2348
+ for (const { from, to } of view.visibleRanges) {
2349
+ syntaxTree8(state).iterate({
2350
+ enter: (node) => {
2351
+ if (node.name === "TaskMarker") {
2352
+ if (cursor < node.from || cursor > node.to) {
2353
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2354
+ builder.add(node.from - 2, node.from - 1, Decoration10.mark({
2355
+ class: "cm-task"
2356
+ }));
2357
+ builder.add(node.from, node.to, checked ? checkedDecoration : uncheckedDecoration);
2358
+ }
2359
+ }
2360
+ },
2361
+ from,
2362
+ to
2363
+ });
2364
+ }
2365
+ return builder.finish();
2366
+ };
2068
2367
  var tasklist = (options = {}) => {
2069
- const taskMatcher = new MatchDecorator2({
2070
- regexp: /^(\s*)- \[([ xX])\]\s/g,
2071
- decoration: (match, view, pos) => {
2072
- const indent = Math.floor(match[1].length / 2);
2073
- const checked = match[2] === "x" || match[2] === "X";
2074
- return Decoration8.replace({
2075
- widget: new CheckboxWidget(checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2076
- const idx = pos + match[0].indexOf("[") + 1;
2077
- view.dispatch({
2078
- changes: {
2079
- from: idx,
2080
- to: idx + 1,
2081
- insert: checked2 ? "x" : " "
2082
- },
2083
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
2084
- selection: {
2085
- anchor: idx + 3
2086
- }
2087
- });
2088
- })
2089
- });
2090
- }
2091
- });
2092
- const tasks = ViewPlugin6.fromClass(class {
2093
- constructor(view) {
2094
- this.tasks = taskMatcher.createDeco(view);
2095
- }
2096
- update(update2) {
2097
- this.tasks = taskMatcher.updateDeco(update2, this.tasks);
2098
- }
2099
- }, {
2100
- decorations: (value) => value.tasks,
2101
- provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2102
- return view.plugin(plugin)?.tasks || Decoration8.none;
2103
- })
2104
- });
2105
2368
  return [
2106
- tasks,
2369
+ ViewPlugin8.fromClass(class {
2370
+ constructor(view) {
2371
+ this.decorations = buildDecorations6(view);
2372
+ }
2373
+ update(update2) {
2374
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2375
+ this.decorations = buildDecorations6(update2.view);
2376
+ }
2377
+ }
2378
+ }, {
2379
+ decorations: (value) => value.decorations
2380
+ }),
2107
2381
  styles5
2108
2382
  ];
2109
2383
  };
2110
2384
 
2385
+ // packages/ui/react-ui-editor/src/extensions/mention.ts
2386
+ import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
2387
+ var mention = ({ onSearch }) => {
2388
+ return autocompletion2({
2389
+ // TODO(burdon): Not working.
2390
+ activateOnTyping: true,
2391
+ // activateOnTypingDelay: 100,
2392
+ // selectOnOpen: true,
2393
+ closeOnBlur: false,
2394
+ // defaultKeymap: false,
2395
+ icons: false,
2396
+ override: [
2397
+ (context) => {
2398
+ console.log(context);
2399
+ const match = context.matchBefore(/@(\w+)?/);
2400
+ if (!match || match.from === match.to && !context.explicit) {
2401
+ return null;
2402
+ }
2403
+ return {
2404
+ from: match.from,
2405
+ options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
2406
+ label: `@${value}`
2407
+ }))
2408
+ };
2409
+ }
2410
+ ]
2411
+ });
2412
+ };
2413
+
2414
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
2415
+ var outliner = (options = {}) => {
2416
+ return [];
2417
+ };
2418
+
2111
2419
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
2112
- import { keymap as keymap5 } from "@codemirror/view";
2420
+ import { keymap as keymap6 } from "@codemirror/view";
2113
2421
  var defaultItems = [
2114
2422
  "hello world!",
2115
2423
  "this is a test.",
@@ -2119,7 +2427,7 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2119
2427
  let t;
2120
2428
  let idx = 0;
2121
2429
  return [
2122
- keymap5.of([
2430
+ keymap6.of([
2123
2431
  {
2124
2432
  // Reset.
2125
2433
  key: "alt-meta-'",
@@ -2165,27 +2473,6 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2165
2473
  ];
2166
2474
  };
2167
2475
 
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
-
2189
2476
  // packages/ui/react-ui-editor/src/themes/default.ts
2190
2477
  import get3 from "lodash.get";
2191
2478
  var defaultTheme = {
@@ -2252,7 +2539,7 @@ var defaultTheme = {
2252
2539
  paddingInline: 0
2253
2540
  },
2254
2541
  ".cm-activeLine": {
2255
- background: "inherit"
2542
+ background: "transparent"
2256
2543
  },
2257
2544
  //
2258
2545
  // Selection
@@ -2315,13 +2602,6 @@ var defaultTheme = {
2315
2602
  textDecoration: "none"
2316
2603
  },
2317
2604
  //
2318
- // widgets
2319
- //
2320
- ".cm-widgetBuffer": {
2321
- display: "none",
2322
- height: 0
2323
- },
2324
- //
2325
2605
  // table
2326
2606
  //
2327
2607
  ".cm-table *": {
@@ -2430,11 +2710,12 @@ var codeTheme = {
2430
2710
  };
2431
2711
 
2432
2712
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2713
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
2433
2714
  var EditorModes = [
2434
2715
  "default",
2435
2716
  "vim"
2436
2717
  ];
2437
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, readonly, comments: comments2, extensions = [], editorMode, theme, slots = defaultSlots }, forwardedRef) => {
2718
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2438
2719
  const tabsterDOMAttribute = useFocusableGroup({
2439
2720
  tabBehavior: "limited"
2440
2721
  });
@@ -2444,76 +2725,68 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, read
2444
2725
  useImperativeHandle(forwardedRef, () => view, [
2445
2726
  view
2446
2727
  ]);
2447
- useEffect(() => {
2448
- if (view && focus) {
2449
- view.focus();
2450
- }
2451
- }, [
2452
- view,
2453
- focus
2454
- ]);
2455
- const { awareness: awareness2, peer } = model;
2456
- useEffect(() => {
2457
- if (awareness2 && peer) {
2458
- awareness2.setLocalStateField("user", {
2459
- name: peer.name ?? generateName(peer.id),
2460
- color: getColorForValue({
2461
- value: peer.id,
2462
- type: "color"
2463
- }),
2464
- colorLight: getColorForValue({
2465
- value: peer.id,
2466
- themeMode,
2467
- type: "highlight"
2468
- })
2469
- });
2470
- }
2471
- }, [
2472
- awareness2,
2473
- peer,
2474
- themeMode
2475
- ]);
2476
- useEffect(() => {
2477
- if (view && comments2 !== void 0) {
2478
- view.dispatch({
2479
- effects: setCommentRange.of({
2480
- model,
2481
- comments: comments2
2482
- })
2483
- });
2728
+ useEffect2(() => {
2729
+ if (autoFocus) {
2730
+ view?.focus();
2484
2731
  }
2485
2732
  }, [
2486
2733
  view,
2487
- comments2
2734
+ autoFocus
2488
2735
  ]);
2489
- useEffect(() => {
2736
+ useEffect2(() => {
2490
2737
  if (!model || !rootRef.current) {
2491
2738
  return;
2492
2739
  }
2493
2740
  const state = EditorState2.create({
2494
2741
  doc: model.text(),
2495
- // TODO(burdon): Composer should store and set selection when switching documents.
2496
2742
  selection,
2497
2743
  extensions: [
2498
- readonly && EditorState2.readOnly.of(readonly),
2499
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2500
- editorMode === "vim" && vim(),
2744
+ // TODO(burdon): Doesn't catch errors in keymap functions.
2745
+ EditorView13.exceptionSink.of((err) => {
2746
+ log2.catch(err, void 0, {
2747
+ F: __dxlog_file4,
2748
+ L: 123,
2749
+ S: void 0,
2750
+ C: (f, a) => f(...a)
2751
+ });
2752
+ }),
2501
2753
  // Theme.
2502
- // TODO(burdon): Make theme configurable.
2503
- EditorView12.baseTheme(defaultTheme),
2504
- EditorView12.theme(theme ?? {}),
2505
- EditorView12.darkTheme.of(themeMode === "dark"),
2754
+ // TODO(burdon): Make configurable.
2755
+ EditorView13.baseTheme(defaultTheme),
2756
+ EditorView13.theme(theme ?? {}),
2757
+ EditorView13.darkTheme.of(themeMode === "dark"),
2758
+ EditorView13.editorAttributes.of({
2759
+ class: slots.editor?.className ?? ""
2760
+ }),
2761
+ EditorView13.contentAttributes.of({
2762
+ class: slots.content?.className ?? ""
2763
+ }),
2764
+ // State.
2765
+ EditorView13.editable.of(!readonly),
2766
+ EditorState2.readOnly.of(!!readonly),
2506
2767
  // Storage and replication.
2768
+ // NOTE: This must come before user extensions.
2507
2769
  model.extension,
2770
+ // TODO(burdon): Factor out (requires special handling for Escape/focus).
2771
+ editorMode === "vim" && vim(),
2508
2772
  // Custom.
2509
2773
  ...extensions
2510
2774
  ].filter(Boolean)
2511
2775
  });
2512
- view?.destroy();
2513
- const newView = new EditorView12({
2776
+ const newView = new EditorView13({
2777
+ state,
2514
2778
  parent: rootRef.current,
2515
- state
2779
+ scrollTo,
2780
+ // NOTE: Uncomment to debug/monitor all transactions.
2781
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
2782
+ dispatchTransactions: (trs, view2) => {
2783
+ if (debug) {
2784
+ logChanges(trs);
2785
+ }
2786
+ view2.update(trs);
2787
+ }
2516
2788
  });
2789
+ view?.destroy();
2517
2790
  setView(newView);
2518
2791
  return () => {
2519
2792
  newView?.destroy();
@@ -2529,8 +2802,14 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, read
2529
2802
  const handleKeyUp = useCallback((event) => {
2530
2803
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2531
2804
  switch (key) {
2805
+ case "Enter": {
2806
+ view?.focus();
2807
+ break;
2808
+ }
2532
2809
  case "Escape": {
2533
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
2810
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
2811
+ rootRef.current?.focus();
2812
+ }
2534
2813
  break;
2535
2814
  }
2536
2815
  }
@@ -2541,14 +2820,14 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, read
2541
2820
  return /* @__PURE__ */ React.createElement("div", {
2542
2821
  key: model.id,
2543
2822
  role: "none",
2544
- ref: rootRef,
2545
2823
  tabIndex: 0,
2546
2824
  onKeyUp: handleKeyUp,
2547
2825
  ...slots.root,
2548
- ...editorMode !== "vim" && tabsterDOMAttribute
2826
+ ...editorMode !== "vim" && tabsterDOMAttribute,
2827
+ ref: rootRef
2549
2828
  });
2550
2829
  });
2551
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
2830
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, lineWrapping, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
2552
2831
  const { themeMode } = useThemeContext();
2553
2832
  const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2554
2833
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
@@ -2556,9 +2835,9 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholde
2556
2835
  readonly,
2557
2836
  extensions: [
2558
2837
  basicBundle({
2559
- readonly,
2560
2838
  themeMode,
2561
- placeholder: placeholder3
2839
+ placeholder: placeholder3,
2840
+ lineWrapping
2562
2841
  }),
2563
2842
  ...extensions
2564
2843
  ],
@@ -2567,7 +2846,7 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholde
2567
2846
  ...props
2568
2847
  });
2569
2848
  });
2570
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
2849
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
2571
2850
  const { themeMode } = useThemeContext();
2572
2851
  const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2573
2852
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
@@ -2575,7 +2854,6 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeh
2575
2854
  readonly,
2576
2855
  extensions: [
2577
2856
  markdownBundle({
2578
- readonly,
2579
2857
  themeMode,
2580
2858
  placeholder: placeholder3
2581
2859
  }),
@@ -2588,7 +2866,11 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeh
2588
2866
  });
2589
2867
  var defaultSlots = {
2590
2868
  root: {
2591
- className: mx3("p-2 overflow-y-auto", inputSurface)
2869
+ // TODO(burdon): Add focusRing by default/as property?
2870
+ className: mx3("flex flex-col grow overflow-y-auto", attentionSurface)
2871
+ },
2872
+ editor: {
2873
+ className: "h-full p-2"
2592
2874
  }
2593
2875
  };
2594
2876
  var defaultTextSlots = {
@@ -2598,22 +2880,17 @@ var defaultMarkdownSlots = {
2598
2880
  ...defaultSlots
2599
2881
  };
2600
2882
 
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
2883
  // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2607
2884
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2608
2885
  import { Context as Context2 } from "@dxos/context";
2609
2886
  import { invariant as invariant3 } from "@dxos/invariant";
2610
2887
  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";
2888
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2612
2889
  var DEBOUNCE_INTERVAL = 100;
2613
2890
  var SpaceAwarenessProvider = class {
2614
2891
  constructor(params) {
2615
- this.remoteStateChange = new Event2();
2616
2892
  this._remoteStates = /* @__PURE__ */ new Map();
2893
+ this.remoteStateChange = new Event2();
2617
2894
  this._space = params.space;
2618
2895
  this._channel = params.channel;
2619
2896
  this._peerId = params.peerId;
@@ -2630,7 +2907,7 @@ var SpaceAwarenessProvider = class {
2630
2907
  await sleep(DEBOUNCE_INTERVAL);
2631
2908
  }
2632
2909
  });
2633
- const unsubscribe = this._space.listen(this._channel, (message) => {
2910
+ this._ctx.onDispose(this._space.listen(this._channel, (message) => {
2634
2911
  switch (message.payload.kind) {
2635
2912
  case "query": {
2636
2913
  this._handleQueryMessage();
@@ -2641,16 +2918,15 @@ var SpaceAwarenessProvider = class {
2641
2918
  break;
2642
2919
  }
2643
2920
  }
2644
- });
2645
- this._ctx.onDispose(unsubscribe);
2921
+ }));
2646
2922
  void this._space.postMessage(this._channel, {
2647
2923
  kind: "query"
2648
2924
  }).catch((err) => {
2649
2925
  log3.debug("failed to query awareness", {
2650
2926
  err
2651
2927
  }, {
2652
- F: __dxlog_file4,
2653
- L: 85,
2928
+ F: __dxlog_file5,
2929
+ L: 89,
2654
2930
  S: this,
2655
2931
  C: (f, a) => f(...a)
2656
2932
  });
@@ -2661,10 +2937,13 @@ var SpaceAwarenessProvider = class {
2661
2937
  this._ctx = void 0;
2662
2938
  this._postTask = void 0;
2663
2939
  }
2940
+ getRemoteStates() {
2941
+ return Array.from(this._remoteStates.values());
2942
+ }
2664
2943
  update(position) {
2665
2944
  invariant3(this._postTask, void 0, {
2666
- F: __dxlog_file4,
2667
- L: 96,
2945
+ F: __dxlog_file5,
2946
+ L: 104,
2668
2947
  S: this,
2669
2948
  A: [
2670
2949
  "this._postTask",
@@ -2678,13 +2957,10 @@ var SpaceAwarenessProvider = class {
2678
2957
  };
2679
2958
  this._postTask.schedule();
2680
2959
  }
2681
- getRemoteStates() {
2682
- return Array.from(this._remoteStates.values());
2683
- }
2684
2960
  _handleQueryMessage() {
2685
2961
  invariant3(this._postTask, void 0, {
2686
- F: __dxlog_file4,
2687
- L: 111,
2962
+ F: __dxlog_file5,
2963
+ L: 115,
2688
2964
  S: this,
2689
2965
  A: [
2690
2966
  "this._postTask",
@@ -2695,8 +2971,8 @@ var SpaceAwarenessProvider = class {
2695
2971
  }
2696
2972
  _handlePostMessage(message) {
2697
2973
  invariant3(message.kind === "post", void 0, {
2698
- F: __dxlog_file4,
2699
- L: 116,
2974
+ F: __dxlog_file5,
2975
+ L: 120,
2700
2976
  S: this,
2701
2977
  A: [
2702
2978
  "message.kind === 'post'",
@@ -2708,171 +2984,86 @@ var SpaceAwarenessProvider = class {
2708
2984
  }
2709
2985
  };
2710
2986
 
2711
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2987
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
2712
2988
  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";
2989
+ var modelState = StateField5.define({
2990
+ create: () => void 0,
2991
+ update: (model) => model
2992
+ });
2715
2993
 
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));
2994
+ // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
2995
+ var useActionHandler = (view) => {
2996
+ return (action) => {
2997
+ if (!view) {
2998
+ return;
2813
2999
  }
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);
3000
+ switch (action.type) {
3001
+ case "heading":
3002
+ setHeading(parseInt(action.data))(view);
2823
3003
  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;
3004
+ case "bold":
3005
+ toggleBold(view);
3006
+ break;
3007
+ case "italic":
3008
+ toggleItalic(view);
3009
+ break;
3010
+ case "strikethrough":
3011
+ toggleStrikethrough(view);
3012
+ break;
3013
+ case "list":
3014
+ toggleList(view);
3015
+ break;
3016
+ case "comment":
3017
+ createComment(view);
2829
3018
  break;
2830
- }
2831
- default: {
2832
- console.error("Invalid message:", messageType);
2833
- return encoder;
2834
- }
2835
- }
2836
- if (!sendReply) {
2837
- return null;
2838
3019
  }
2839
- return encoder;
2840
- }
2841
- _handleBeforeUnload() {
2842
- YP.removeAwarenessStates(this._awareness, [
2843
- this._clientId
2844
- ], "window unload");
2845
- }
3020
+ setTimeout(() => {
3021
+ view.focus();
3022
+ });
3023
+ };
3024
+ };
3025
+
3026
+ // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
3027
+ import { useState as useState2 } from "react";
3028
+ var useTextEditor = () => {
3029
+ const [view, setView] = useState2(null);
3030
+ return [
3031
+ (ref) => {
3032
+ setView(ref);
3033
+ },
3034
+ view
3035
+ ];
2846
3036
  };
2847
3037
 
2848
3038
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2849
- var modelState = StateField5.define({
2850
- create: () => void 0,
2851
- update: (model) => model
2852
- });
3039
+ import get4 from "lodash.get";
3040
+ import { useEffect as useEffect3, useState as useState3 } from "react";
3041
+ import { generateName } from "@dxos/display-name";
3042
+ import { getRawDoc } from "@dxos/echo-schema";
3043
+ import { invariant as invariant4 } from "@dxos/invariant";
3044
+ import { isAutomergeObject } from "@dxos/react-client/echo";
3045
+ import { isNotNullOrUndefined } from "@dxos/util";
3046
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
2853
3047
  var useTextModel = (props) => {
2854
3048
  const { identity, space, text } = props;
2855
- const [model, setModel] = useState2();
2856
- useEffect2(() => setModel(createModel(props)), [
3049
+ const [model, setModel] = useState3();
3050
+ useEffect3(() => setModel(createModel(props)), [
2857
3051
  identity,
2858
3052
  space,
2859
3053
  text
2860
3054
  ]);
2861
3055
  return model;
2862
3056
  };
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;
2871
- }
2872
- };
2873
-
2874
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2875
- var createAutomergeModel = ({ space, identity, text }) => {
3057
+ var createModel = ({ space, identity, text }) => {
3058
+ invariant4(isAutomergeObject(text), void 0, {
3059
+ F: __dxlog_file6,
3060
+ L: 35,
3061
+ S: void 0,
3062
+ A: [
3063
+ "isAutomergeObject(text)",
3064
+ ""
3065
+ ]
3066
+ });
2876
3067
  const obj = text;
2877
3068
  const doc = getRawDoc(obj, [
2878
3069
  obj.field
@@ -2881,7 +3072,7 @@ var createAutomergeModel = ({ space, identity, text }) => {
2881
3072
  space,
2882
3073
  channel: `automerge.awareness.${obj.id}`,
2883
3074
  info: {
2884
- displayName: identity?.profile?.displayName ?? "",
3075
+ displayName: identity ? identity.profile?.displayName ?? generateName(identity.identityKey.toHex()) : void 0,
2885
3076
  color: cursorColor.color,
2886
3077
  lightColor: cursorColor.light
2887
3078
  },
@@ -2891,10 +3082,9 @@ var createAutomergeModel = ({ space, identity, text }) => {
2891
3082
  id: obj.id,
2892
3083
  content: doc,
2893
3084
  text: () => get4(doc.handle.docSync(), doc.path),
2894
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2895
3085
  extension: [
2896
3086
  modelState.init(() => model),
2897
- automerge3({
3087
+ automerge4({
2898
3088
  handle: doc.handle,
2899
3089
  path: doc.path
2900
3090
  }),
@@ -2919,11 +3109,10 @@ export {
2919
3109
  SpaceAwarenessProvider,
2920
3110
  TextEditor,
2921
3111
  TextKind,
2922
- YAwarenessProvider,
2923
3112
  YText,
2924
3113
  YXmlFragment,
2925
3114
  autocomplete,
2926
- automerge3 as automerge,
3115
+ automerge4 as automerge,
2927
3116
  awareness,
2928
3117
  basicBundle,
2929
3118
  blast,
@@ -2931,19 +3120,20 @@ export {
2931
3120
  code2 as code,
2932
3121
  codeTheme,
2933
3122
  comments,
2934
- createAutomergeModel,
2935
- createYjsModel,
2936
- cursorConverter,
3123
+ createComment,
2937
3124
  defaultMarkdownSlots,
2938
3125
  defaultOptions,
2939
3126
  defaultSlots,
2940
3127
  defaultTextSlots,
2941
3128
  defaultTheme,
3129
+ formatting,
2942
3130
  getToken,
3131
+ heading2 as heading,
2943
3132
  hr,
2944
3133
  image,
2945
3134
  link,
2946
3135
  listener,
3136
+ logChanges,
2947
3137
  markdownBundle,
2948
3138
  markdownHighlightStyle,
2949
3139
  markdownTags,
@@ -2951,15 +3141,24 @@ export {
2951
3141
  markdownTheme,
2952
3142
  mention,
2953
3143
  modelState,
2954
- setCommentRange,
3144
+ outliner,
3145
+ setComments,
2955
3146
  setFocus,
3147
+ setHeading,
2956
3148
  setSelection,
2957
3149
  table,
2958
3150
  tags2 as tags,
2959
3151
  tasklist,
2960
3152
  textTheme,
3153
+ toggleBold,
3154
+ toggleItalic,
3155
+ toggleList,
3156
+ toggleStrikethrough,
3157
+ toggleStyle,
2961
3158
  typewriter,
2962
- useTextModel,
2963
- yjs
3159
+ useActionHandler,
3160
+ useComments,
3161
+ useTextEditor,
3162
+ useTextModel
2964
3163
  };
2965
3164
  //# sourceMappingURL=index.mjs.map