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

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