@dxos/react-ui-editor 0.3.11-main.c8e9429 → 0.3.11-main.cb8120f

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