@dxos/react-ui-editor 0.3.11-main.00a28cb → 0.3.11-main.0999ea0

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