@dxos/react-ui-editor 0.3.11-main.162de7b → 0.3.11-main.1a15df3

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