@dxos/react-ui-editor 0.3.11-main.42059c4 → 0.3.11-main.463e5cd

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 +1610 -1059
  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 -41
  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 +22 -18
  101. package/src/extensions/basic.ts +17 -10
  102. package/src/extensions/blast.ts +4 -1
  103. package/src/extensions/comments.ts +253 -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} +16 -7
  114. package/src/extensions/{tasklist.ts → markdown/tasklist.ts} +48 -33
  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 -106
  164. package/src/extensions/hr.ts +0 -73
  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,40 +585,42 @@ 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",
598
- // these should be separate
622
+ borderRadius: "2px",
623
+ // These should be separate.
599
624
  opacity: 0,
600
625
  transitionDelay: "0s",
601
626
  whiteSpace: "nowrap"
@@ -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,167 +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 getDecorations = (view) => {
1063
- const decorations = [];
1064
- if (!view.hasFocus || view.state.readOnly) {
1065
- const text = view.state.doc.sliceString(0);
1066
- const matches = text.matchAll(CODE_REGEX);
1067
- const blocks = view.viewportLineBlocks;
1068
- for (const match of matches) {
1069
- const range = getLineRange(blocks, match.index, match.index + match[0].length);
1070
- for (let i = range[0]; i <= range[1]; i++) {
1071
- const block = blocks[i];
1072
- decorations.push(Decoration2.line({
1073
- class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1074
- }).range(block.from));
1075
- }
1076
- }
1077
- }
1078
- return Decoration2.set(decorations);
1079
- };
1080
- return [
1081
- ViewPlugin3.fromClass(class {
1082
- constructor(view) {
1083
- this.hasFocus = false;
1084
- this.decorations = getDecorations(view);
1085
- }
1086
- update(update2) {
1087
- if (
1088
- // TODO(burdon): Generalize for other extensions.
1089
- this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1090
- ) {
1091
- this.hasFocus = update2.view.hasFocus;
1092
- this.decorations = getDecorations(update2.view);
1093
- }
1094
- }
1095
- }, {
1096
- decorations: (value) => value.decorations
1097
- }),
1098
- styles2
1099
- ];
1100
- };
1101
-
1102
- // packages/ui/react-ui-editor/src/extensions/comments.ts
1103
- import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1104
- import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
1105
- import sortBy from "lodash.sortby";
1106
- import { debounce } from "@dxos/async";
1107
- import { invariant as invariant2 } from "@dxos/invariant";
1108
- import { nonNullable } from "@dxos/util";
1109
-
1110
1040
  // packages/ui/react-ui-editor/src/util.ts
1111
1041
  import { log } from "@dxos/log";
1112
1042
  var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1113
1043
  var callbackWrapper = (fn) => (...args) => {
1114
1044
  try {
1115
1045
  return fn(...args);
1116
- } catch (error) {
1117
- log.catch(error, void 0, {
1046
+ } catch (err) {
1047
+ log.catch(err, void 0, {
1118
1048
  F: __dxlog_file2,
1119
- L: 16,
1049
+ L: 18,
1120
1050
  S: void 0,
1121
1051
  C: (f, a) => f(...a)
1122
1052
  });
1123
1053
  }
1124
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
+ };
1125
1074
 
1126
1075
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1127
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1128
- var styles3 = EditorView5.baseTheme({
1076
+ var styles2 = EditorView5.baseTheme({
1129
1077
  "& .cm-comment": {
1130
1078
  backgroundColor: getToken("extend.colors.yellow.50")
1131
1079
  },
1132
- "& .cm-comment-active": {
1080
+ "& .cm-comment-current": {
1133
1081
  backgroundColor: getToken("extend.colors.yellow.100")
1134
1082
  }
1135
1083
  });
1136
- var marks = {
1137
- highlight: Decoration3.mark({
1138
- class: "cm-comment"
1139
- }),
1140
- highlightActive: Decoration3.mark({
1141
- class: "cm-comment-active"
1142
- })
1143
- };
1144
- var setFocus = (view, thread) => {
1145
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1146
- 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) {
1147
1093
  return;
1148
1094
  }
1149
- view.dispatch({
1150
- effects: setSelection.of({
1151
- active: thread
1152
- }),
1153
- selection: {
1154
- anchor: range.from
1155
- },
1156
- scrollIntoView: true
1157
- });
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
+ }
1158
1112
  };
1159
- var setCommentRange = StateEffect2.define();
1113
+ var setComments = StateEffect2.define();
1160
1114
  var setSelection = StateEffect2.define();
1161
1115
  var setCommentState = StateEffect2.define();
1162
- var commentsStateField = StateField2.define({
1116
+ var commentsState = StateField2.define({
1163
1117
  create: () => ({
1164
- ranges: []
1118
+ comments: [],
1119
+ selection: {}
1165
1120
  }),
1166
1121
  update: (value, tr) => {
1167
- const cursorConverter3 = tr.state.facet(Cursor.converter);
1168
1122
  for (const effect of tr.effects) {
1169
1123
  if (effect.is(setSelection)) {
1170
1124
  return {
1171
1125
  ...value,
1172
- ...effect.value
1126
+ selection: effect.value
1173
1127
  };
1174
1128
  }
1175
- if (effect.is(setCommentRange) && effect.value.comments.length > 0) {
1176
- const { comments: comments2 } = effect.value;
1177
- const ranges = comments2.map((comment) => {
1178
- 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);
1179
1135
  return range && {
1180
- ...comment,
1181
- ...range
1136
+ comment,
1137
+ range
1182
1138
  };
1183
1139
  }).filter(nonNullable);
1184
1140
  return {
1185
1141
  ...value,
1186
- ranges
1142
+ comments: comments2
1187
1143
  };
1188
1144
  }
1189
1145
  if (effect.is(setCommentState)) {
@@ -1193,39 +1149,37 @@ var commentsStateField = StateField2.define({
1193
1149
  return value;
1194
1150
  }
1195
1151
  });
1196
- var highlightDecorations = EditorView5.decorations.compute([
1197
- commentsStateField
1152
+ var commentsDecorations = EditorView5.decorations.compute([
1153
+ commentsState
1198
1154
  ], (state) => {
1199
- const { active, ranges } = state.field(commentsStateField);
1200
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1201
- 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);
1202
1160
  return void 0;
1203
1161
  }
1204
- const range = {
1205
- from: selection.from,
1206
- to: selection.to
1207
- };
1208
- if (selection.id === active) {
1209
- return marks.highlightActive.range(range.from, range.to);
1162
+ if (comment.comment.id === current) {
1163
+ return commentCurrentMark.range(range.from, range.to);
1210
1164
  } else {
1211
- return marks.highlight.range(range.from, range.to);
1165
+ return commentMark.range(range.from, range.to);
1212
1166
  }
1213
- }).filter(nonNullable) ?? [];
1214
- return Decoration3.set(decorations);
1167
+ }).filter(nonNullable);
1168
+ return Decoration2.set(decorations);
1215
1169
  });
1216
1170
  var trackPastedComments = (onUpdate) => {
1217
1171
  let tracked = null;
1218
1172
  const handleTrack = (event, view) => {
1219
- const comments2 = view.state.field(commentsStateField);
1173
+ const comments2 = view.state.field(commentsState);
1220
1174
  const { main } = view.state.selection;
1221
- 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);
1222
1176
  if (!selectedRanges.length) {
1223
1177
  tracked = null;
1224
1178
  } else {
1225
1179
  tracked = {
1226
1180
  text: view.state.doc.slice(main.from, main.to),
1227
- comments: selectedRanges.map((range) => ({
1228
- id: range.id,
1181
+ comments: selectedRanges.map(({ comment, range }) => ({
1182
+ id: comment.id,
1229
1183
  from: range.from - main.from,
1230
1184
  to: range.to - main.from
1231
1185
  }))
@@ -1237,90 +1191,132 @@ var trackPastedComments = (onUpdate) => {
1237
1191
  cut: handleTrack,
1238
1192
  copy: handleTrack
1239
1193
  }),
1240
- // Handle paste.
1241
- 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
+ }
1242
1225
  if (tracked) {
1243
- const paste = transactions.find((tr) => tr.isUserEvent("input.paste"));
1226
+ const paste = update2.transactions.find((tr) => tr.isUserEvent("input.paste"));
1244
1227
  if (paste) {
1245
1228
  let found = -1;
1246
1229
  paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1247
1230
  if (text.eq(tracked.text)) {
1248
- for (let i = transactions.indexOf(paste) + 1; i < transactions.length; i++) {
1249
- 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);
1250
1233
  }
1251
1234
  found = fromB;
1252
1235
  }
1253
1236
  });
1254
1237
  if (found > -1) {
1255
- const comments2 = tracked.comments;
1256
- view.state.facet(semaphoreFacet).reconcile(view);
1257
- const active = state.field(commentsStateField).ranges;
1258
- for (const moved of comments2) {
1259
- if (active.some((range) => range.id === moved.id && range.from === range.to)) {
1260
- const range = {
1261
- from: found + moved.from,
1262
- to: found + moved.to
1263
- };
1264
- const cursor = Cursor.getCursorFromRange(state.facet(Cursor.converter), range);
1265
- onUpdate(moved.id, cursor);
1266
- }
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
+ });
1267
1244
  }
1268
1245
  }
1269
1246
  tracked = null;
1270
1247
  }
1271
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
+ }
1272
1257
  })
1273
1258
  ];
1274
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
+ };
1275
1305
  var comments = (options = {}) => {
1276
1306
  const { key: shortcut = "meta-'" } = options;
1277
1307
  const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1278
- const createCommentThread = (view) => {
1279
- const cursorConverter3 = view.state.facet(Cursor.converter);
1280
- invariant2(options.onCreate, void 0, {
1281
- F: __dxlog_file3,
1282
- L: 262,
1283
- S: void 0,
1284
- A: [
1285
- "options.onCreate",
1286
- ""
1287
- ]
1288
- });
1289
- const { from, to } = view.state.selection.main;
1290
- if (from === to) {
1291
- return false;
1292
- }
1293
- const cursor = Cursor.getCursorFromRange(cursorConverter3, {
1294
- from,
1295
- to
1296
- });
1297
- if (cursor) {
1298
- const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
1299
- if (id) {
1300
- view.dispatch({
1301
- effects: setSelection.of({
1302
- active: id
1303
- }),
1304
- selection: {
1305
- anchor: from
1306
- }
1307
- });
1308
- return true;
1309
- }
1310
- }
1311
- return false;
1312
- };
1313
1308
  return [
1314
- commentsStateField,
1315
- highlightDecorations,
1316
- styles3,
1309
+ optionsFacet.of(options),
1310
+ commentsState,
1311
+ commentsDecorations,
1312
+ styles2,
1317
1313
  //
1318
1314
  // Keymap.
1319
1315
  //
1320
1316
  options.onCreate ? keymap3.of([
1321
1317
  {
1322
1318
  key: shortcut,
1323
- run: callbackWrapper(createCommentThread)
1319
+ run: callbackWrapper(createComment)
1324
1320
  }
1325
1321
  ]) : [],
1326
1322
  //
@@ -1336,7 +1332,7 @@ var comments = (options = {}) => {
1336
1332
  above: true,
1337
1333
  create: () => {
1338
1334
  const el = document.createElement("div");
1339
- options.onHover?.(el, shortcut);
1335
+ options.onHover(el, shortcut);
1340
1336
  return {
1341
1337
  dom: el,
1342
1338
  offset: {
@@ -1349,358 +1345,116 @@ var comments = (options = {}) => {
1349
1345
  }
1350
1346
  return null;
1351
1347
  }, {
1352
- hideOnChange: true,
1348
+ // TODO(burdon): Hide on change triggered immediately?
1349
+ // hideOnChange: true,
1353
1350
  hoverTime: 1e3
1354
1351
  }) : [],
1355
1352
  //
1356
- // Monitor cursor movement and text updates.
1357
- // TODO(burdon): Is there a better (finer grained) way to do this?
1353
+ // Track deleted ranges and update ranges for decorations.
1358
1354
  //
1359
1355
  EditorView5.updateListener.of(({ view, state, changes }) => {
1360
- const cursorConverter3 = view.state.facet(Cursor.converter);
1361
- {
1362
- let mod = false;
1363
- const { active, ranges } = state.field(commentsStateField);
1364
- changes.iterChanges((from, to, from2, to2) => {
1365
- ranges.forEach((range) => {
1366
- if (from2 === to2) {
1367
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1368
- if (!newRange || newRange.to - newRange.from === 0) {
1369
- options.onDelete?.(range.id);
1370
- }
1371
- }
1372
- if (from <= range.to) {
1373
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1374
- Object.assign(range, newRange);
1375
- mod = true;
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);
1376
1364
  }
1377
- });
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
+ })
1378
1379
  });
1379
- if (mod) {
1380
- view.dispatch({
1381
- effects: setCommentState.of({
1382
- active,
1383
- ranges
1384
- })
1385
- });
1386
- }
1387
1380
  }
1388
- {
1389
- const { head } = state.selection.main;
1390
- let min = Infinity;
1391
- const { active, closest, ranges } = state.field(commentsStateField);
1392
- const selected = {
1393
- active: void 0,
1394
- closest: void 0
1395
- };
1396
- ranges.forEach((comment) => {
1397
- const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1398
- if (head >= comment.from && head <= comment.to) {
1399
- selected.active = comment.id;
1400
- }
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));
1401
1397
  if (d < min) {
1402
- selected.closest = comment.id;
1398
+ selection.closest = comment.id;
1403
1399
  min = d;
1404
1400
  }
1405
- });
1406
- if (selected.active !== active || selected.closest !== closest) {
1407
- view.dispatch({
1408
- effects: setSelection.of(selected)
1409
- });
1410
- handleSelect({
1411
- ...selected,
1412
- ranges: ranges.map((range) => ({
1413
- ...range,
1414
- location: view.coordsAtPos(range.from)
1415
- }))
1416
- });
1417
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
+ });
1418
1415
  }
1419
1416
  }),
1420
1417
  options.onUpdate ? trackPastedComments(options.onUpdate) : []
1421
1418
  ];
1422
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
+ };
1423
1432
 
1424
- // packages/ui/react-ui-editor/src/extensions/hr.ts
1425
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1426
- var styles4 = EditorView6.baseTheme({
1427
- "& .cm-hr": {
1428
- // TODO(burdon): ???
1429
- // Note that block-level decorations should not have vertical margins,
1430
- borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1431
- }
1432
- });
1433
- var HorizontalRuleWidget = class extends WidgetType2 {
1434
- constructor(_pos) {
1435
- super();
1436
- this._pos = _pos;
1437
- }
1438
- eq(other) {
1439
- return this._pos === other._pos;
1440
- }
1441
- toDOM(view) {
1442
- const el = document.createElement("div");
1443
- el.className = "cm-hr";
1444
- return el;
1445
- }
1446
- };
1447
- var placeholderMatcher = new MatchDecorator({
1448
- regexp: /^---$/g,
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
- // TODO(burdon): Is this really atomic (cursor can move into ---).
1465
- provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1466
- return view.plugin(plugin)?.rules || Decoration4.none;
1467
- })
1468
- })
1469
- ];
1470
-
1471
- // packages/ui/react-ui-editor/src/extensions/image.ts
1472
- import { syntaxTree } from "@codemirror/language";
1473
- import { StateField as StateField3 } from "@codemirror/state";
1474
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
1475
- var image = (options = {}) => {
1476
- return StateField3.define({
1477
- create: (state) => {
1478
- return Decoration5.set(buildDecorations(0, state.doc.length, state));
1479
- },
1480
- update: (value, tr) => {
1481
- if (!tr.docChanged && !tr.selection) {
1482
- return value;
1483
- }
1484
- const cursor = tr.state.selection.main.head;
1485
- const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
1486
- let from = Math.min(cursor, oldCursor);
1487
- let to = Math.max(cursor, oldCursor);
1488
- tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
1489
- from = Math.min(from, fromB);
1490
- to = Math.max(to, toB);
1491
- });
1492
- from = tr.state.doc.lineAt(from).from;
1493
- to = tr.state.doc.lineAt(to).to;
1494
- return value.map(tr.changes).update({
1495
- filterFrom: from,
1496
- filterTo: to,
1497
- filter: () => false,
1498
- add: buildDecorations(from, to, tr.state)
1499
- });
1500
- },
1501
- provide: (field) => EditorView7.decorations.from(field)
1502
- });
1503
- };
1504
- var buildDecorations = (from, to, state) => {
1505
- const decorations = [];
1506
- const cursor = state.selection.main.head;
1507
- syntaxTree(state).iterate({
1508
- enter: (node) => {
1509
- if (node.name === "Image") {
1510
- const urlNode = node.node.getChild("URL");
1511
- if (urlNode) {
1512
- const hide = state.readOnly || cursor < node.from || cursor > node.to;
1513
- const url = state.sliceDoc(urlNode.from, urlNode.to);
1514
- decorations.push(Decoration5.replace({
1515
- block: true,
1516
- widget: new ImageWidget(url)
1517
- }).range(hide ? node.from : node.to, node.to));
1518
- }
1519
- }
1520
- },
1521
- from,
1522
- to
1523
- });
1524
- return decorations;
1525
- };
1526
- var ImageWidget = class extends WidgetType3 {
1527
- constructor(_url) {
1528
- super();
1529
- this._url = _url;
1530
- }
1531
- eq(other) {
1532
- return this._url === other._url;
1533
- }
1534
- toDOM(view) {
1535
- const img = document.createElement("img");
1536
- img.setAttribute("src", this._url);
1537
- img.setAttribute("class", "cm-image");
1538
- img.onload = () => img.classList.add("cm-loaded-image");
1539
- return img;
1540
- }
1541
- };
1542
-
1543
- // packages/ui/react-ui-editor/src/extensions/link.ts
1544
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1545
- import { RangeSetBuilder } from "@codemirror/state";
1546
- import { Decoration as Decoration6, WidgetType as WidgetType4, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1547
- import { tooltipContent } from "@dxos/react-ui-theme";
1548
- var link = (options = {}) => {
1549
- const extensions = [
1550
- ViewPlugin5.fromClass(class {
1551
- constructor(view) {
1552
- this.decorations = buildDecorations2(view, options);
1553
- }
1554
- update(update2) {
1555
- if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1556
- this.decorations = buildDecorations2(update2.view, options);
1557
- }
1558
- }
1559
- }, {
1560
- decorations: (v) => v.decorations
1561
- })
1562
- ];
1563
- if (options.onHover) {
1564
- extensions.push(
1565
- // https://codemirror.net/examples/tooltip
1566
- // https://codemirror.net/docs/ref/#view.hoverTooltip
1567
- // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1568
- hoverTooltip2((view, pos, side) => {
1569
- const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1570
- let link2 = null;
1571
- for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1572
- link2 = node.name === "Link" ? node : null;
1573
- }
1574
- const url = link2 && link2.getChild("URL");
1575
- if (!url || !link2) {
1576
- return null;
1577
- }
1578
- const urlText = view.state.sliceDoc(url.from, url.to);
1579
- return {
1580
- pos: link2.from,
1581
- end: link2.to,
1582
- above: true,
1583
- create: () => {
1584
- const el = document.createElement("div");
1585
- el.className = tooltipContent({}, "pli-2 plb-1");
1586
- options.onHover(el, urlText);
1587
- return {
1588
- dom: el,
1589
- offset: {
1590
- x: 0,
1591
- y: 4
1592
- }
1593
- };
1594
- }
1595
- };
1596
- })
1597
- );
1598
- }
1599
- return extensions;
1600
- };
1601
- var LinkButton = class extends WidgetType4 {
1602
- constructor(_url, _onAttach) {
1603
- super();
1604
- this._url = _url;
1605
- this._onAttach = _onAttach;
1606
- }
1607
- eq(other) {
1608
- return this._url === other._url;
1609
- }
1610
- toDOM(view) {
1611
- const el = document.createElement("span");
1612
- this._onAttach(el, this._url);
1613
- return el;
1614
- }
1615
- };
1616
- var LinkText = class extends WidgetType4 {
1617
- constructor(_text, _url) {
1618
- super();
1619
- this._text = _text;
1620
- this._url = _url;
1621
- }
1622
- eq(other) {
1623
- return this._url === other._url;
1624
- }
1625
- toDOM(view) {
1626
- const link2 = document.createElement("a");
1627
- link2.setAttribute("class", "cm-link");
1628
- link2.textContent = this._text;
1629
- if (this._url) {
1630
- link2.setAttribute("rel", "noreferrer");
1631
- link2.setAttribute("target", "_blank");
1632
- link2.href = this._url;
1633
- } else {
1634
- link2.onclick = () => {
1635
- view.dispatch({
1636
- selection: {
1637
- anchor: view.posAtDOM(link2)
1638
- }
1639
- });
1640
- };
1641
- }
1642
- return link2;
1643
- }
1644
- };
1645
- var buildDecorations2 = (view, options) => {
1646
- const builder = new RangeSetBuilder();
1647
- const { state } = view;
1648
- const cursor = state.selection.main.head;
1649
- for (const { from, to } of view.visibleRanges) {
1650
- syntaxTree2(state).iterate({
1651
- enter: (node) => {
1652
- if (node.name === "Link") {
1653
- const marks2 = node.node.getChildren("LinkMark");
1654
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1655
- const urlNode = node.node.getChild("URL");
1656
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1657
- if (!url) {
1658
- return false;
1659
- }
1660
- if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
1661
- builder.add(node.from, node.to, Decoration6.replace({
1662
- widget: new LinkText(text, options.link ? url : void 0)
1663
- }));
1664
- }
1665
- if (options.onRender) {
1666
- builder.add(node.to, node.to, Decoration6.replace({
1667
- widget: new LinkButton(url, options.onRender)
1668
- }));
1669
- }
1670
- return false;
1671
- }
1672
- },
1673
- from,
1674
- to
1675
- });
1676
- }
1677
- return builder.finish();
1678
- };
1679
-
1680
- // packages/ui/react-ui-editor/src/extensions/listener.ts
1681
- import { EditorView as EditorView8 } from "@codemirror/view";
1682
- var listener = ({ onFocus, onChange }) => {
1683
- const extensions = [];
1684
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1685
- onFocus(focused);
1686
- return null;
1687
- }));
1688
- onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1689
- onChange(update2.state.doc.toString());
1690
- }));
1691
- 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;
1692
1445
  };
1693
1446
 
1694
1447
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1695
1448
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1696
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1449
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1697
1450
  import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1698
1451
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1699
1452
  import { languages } from "@codemirror/language-data";
1700
1453
  import { searchKeymap } from "@codemirror/search";
1701
1454
  import { EditorState } from "@codemirror/state";
1702
1455
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1703
- 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";
1704
1458
 
1705
1459
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1706
1460
  import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
@@ -1797,7 +1551,7 @@ var markdownHighlightStyle = (readonly) => {
1797
1551
  markdownTags.QuoteMark,
1798
1552
  markdownTags.EmphasisMark
1799
1553
  ],
1800
- class: readonly ? "hidden" : mark
1554
+ class: mark
1801
1555
  },
1802
1556
  // E.g., code block language (after ```).
1803
1557
  {
@@ -1805,7 +1559,7 @@ var markdownHighlightStyle = (readonly) => {
1805
1559
  tags.function(tags.variableName),
1806
1560
  tags.labelName
1807
1561
  ],
1808
- class: readonly ? "hidden" : codeMark
1562
+ class: codeMark
1809
1563
  },
1810
1564
  {
1811
1565
  tag: [
@@ -1816,27 +1570,27 @@ var markdownHighlightStyle = (readonly) => {
1816
1570
  // Headings.
1817
1571
  {
1818
1572
  tag: tags.heading1,
1819
- class: heading(1, readonly)
1573
+ class: heading(1)
1820
1574
  },
1821
1575
  {
1822
1576
  tag: tags.heading2,
1823
- class: heading(2, readonly)
1577
+ class: heading(2)
1824
1578
  },
1825
1579
  {
1826
1580
  tag: tags.heading3,
1827
- class: heading(3, readonly)
1581
+ class: heading(3)
1828
1582
  },
1829
1583
  {
1830
1584
  tag: tags.heading4,
1831
- class: heading(4, readonly)
1585
+ class: heading(4)
1832
1586
  },
1833
1587
  {
1834
1588
  tag: tags.heading5,
1835
- class: heading(5, readonly)
1589
+ class: heading(5)
1836
1590
  },
1837
1591
  {
1838
1592
  tag: tags.heading6,
1839
- class: heading(6, readonly)
1593
+ class: heading(6)
1840
1594
  },
1841
1595
  // Emphasis.
1842
1596
  {
@@ -1862,7 +1616,7 @@ var markdownHighlightStyle = (readonly) => {
1862
1616
  // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1863
1617
  // when a language is specified. In this case, the syntax highlighting extensions will colorize
1864
1618
  // the code, but all other CSS properties will be inherited.
1865
- // 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.
1866
1620
  {
1867
1621
  tag: [
1868
1622
  markdownTags.CodeText,
@@ -1891,19 +1645,19 @@ var markdownHighlightStyle = (readonly) => {
1891
1645
  };
1892
1646
 
1893
1647
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1894
- var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1648
+ var createMarkdownExtensions = ({ themeMode, placeholder: _placeholder, lineWrapping = true } = {}) => {
1895
1649
  return [
1650
+ lineWrapping && EditorView7.lineWrapping,
1896
1651
  EditorState.allowMultipleSelections.of(true),
1897
1652
  EditorState.tabSize.of(2),
1898
- EditorView9.lineWrapping,
1899
- customKeymap(),
1653
+ // https://github.com/codemirror/basic-setup
1900
1654
  bracketMatching2(),
1901
1655
  closeBrackets2(),
1902
1656
  crosshairCursor(),
1903
1657
  dropCursor(),
1904
1658
  drawSelection2(),
1905
- highlightActiveLine(),
1906
- history(),
1659
+ highlightActiveLine2(),
1660
+ history2(),
1907
1661
  indentOnInput(),
1908
1662
  _placeholder && placeholder2(_placeholder),
1909
1663
  // Main extension.
@@ -1927,58 +1681,602 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1927
1681
  // https://github.com/codemirror/theme-one-dark
1928
1682
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1929
1683
  // Custom styles.
1930
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1931
- ].filter(Boolean);
1932
- };
1933
- var customKeymap = () => keymap4.of([
1934
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1935
- indentWithTab,
1936
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1937
- ...standardKeymap,
1938
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1939
- ...historyKeymap,
1940
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1941
- ...closeBracketsKeymap,
1942
- // https://codemirror.net/docs/ref/#search.searchKeymap
1943
- ...searchKeymap
1944
- ]);
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);
1698
+ };
1945
1699
 
1946
- // packages/ui/react-ui-editor/src/extensions/mention.ts
1947
- import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1948
- var mention = ({ onSearch }) => {
1949
- return autocompletion2({
1950
- closeOnBlur: false,
1951
- override: [
1952
- (context) => {
1953
- const match = context.matchBefore(/@(\w+)?/);
1954
- if (!match || match.from === match.to && !context.explicit) {
1955
- return null;
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
+ ];
1753
+ };
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;
1772
+ const cursor = state.selection.main.head;
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
+ }
1956
1791
  }
1957
- console.log(match);
1958
- return {
1959
- from: match.from,
1960
- options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1961
- label: `@${value}`
1962
- }))
1963
- };
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);
1964
1804
  }
1965
- ]
1966
- });
1805
+ update(update2) {
1806
+ this.decorations = buildDecorations(update2.view);
1807
+ }
1808
+ }, {
1809
+ decorations: (value) => value.decorations
1810
+ }),
1811
+ styles3
1812
+ ];
1967
1813
  };
1968
1814
 
1969
- // packages/ui/react-ui-editor/src/extensions/table.ts
1970
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1971
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1972
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
1973
- var table = (options = {}) => {
1974
- return StateField4.define({
1975
- create: (state) => update(state, options),
1976
- update: (_, tr) => update(tr.state, options),
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
+ },
1977
2088
  provide: (field) => EditorView10.decorations.from(field)
1978
2089
  });
1979
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
+ };
1980
2278
  var update = (state, options) => {
1981
- const builder = new RangeSetBuilder2();
2279
+ const builder = new RangeSetBuilder6();
1982
2280
  const cursor = state.selection.main.head;
1983
2281
  const tables = [];
1984
2282
  const getTable = () => tables[tables.length - 1];
@@ -1986,7 +2284,7 @@ var update = (state, options) => {
1986
2284
  const table2 = getTable();
1987
2285
  return table2.rows?.[table2.rows.length - 1];
1988
2286
  };
1989
- syntaxTree3(state).iterate({
2287
+ syntaxTree7(state).iterate({
1990
2288
  enter: (node) => {
1991
2289
  switch (node.name) {
1992
2290
  case "Table": {
@@ -2018,16 +2316,16 @@ var update = (state, options) => {
2018
2316
  });
2019
2317
  tables.forEach((table2) => {
2020
2318
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
2021
- hide && builder.add(table2.from, table2.to, Decoration7.replace({
2319
+ hide && builder.add(table2.from, table2.to, Decoration9.replace({
2022
2320
  widget: new TableWidget(table2)
2023
2321
  }));
2024
- builder.add(table2.from, table2.to, Decoration7.mark({
2322
+ builder.add(table2.from, table2.to, Decoration9.mark({
2025
2323
  class: "cm-table"
2026
2324
  }));
2027
2325
  });
2028
2326
  return builder.finish();
2029
2327
  };
2030
- var TableWidget = class extends WidgetType5 {
2328
+ var TableWidget = class extends WidgetType6 {
2031
2329
  constructor(_table) {
2032
2330
  super();
2033
2331
  this._table = _table;
@@ -2064,11 +2362,20 @@ var TableWidget = class extends WidgetType5 {
2064
2362
  }
2065
2363
  };
2066
2364
 
2067
- // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2068
- import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2069
- import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
2070
- import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType6, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2071
- 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 {
2072
2379
  constructor(_checked) {
2073
2380
  super();
2074
2381
  this._checked = _checked;
@@ -2078,6 +2385,7 @@ var CheckboxWidget = class extends WidgetType6 {
2078
2385
  }
2079
2386
  toDOM(view) {
2080
2387
  const input = document.createElement("input");
2388
+ input.className = "cm-task-checkbox";
2081
2389
  input.type = "checkbox";
2082
2390
  input.checked = this._checked;
2083
2391
  if (view.state.readOnly) {
@@ -2104,39 +2412,27 @@ var CheckboxWidget = class extends WidgetType6 {
2104
2412
  return false;
2105
2413
  }
2106
2414
  };
2107
- var checkedDeco = Decoration8.replace({
2415
+ var checkedDecoration = Decoration10.replace({
2108
2416
  widget: new CheckboxWidget(true)
2109
2417
  });
2110
- var uncheckedDeco = Decoration8.replace({
2418
+ var uncheckedDecoration = Decoration10.replace({
2111
2419
  widget: new CheckboxWidget(false)
2112
2420
  });
2113
- var tasklist = (options = {}) => {
2114
- return ViewPlugin6.fromClass(class {
2115
- constructor(view) {
2116
- this.decorations = buildDecorations3(view);
2117
- }
2118
- update(update2) {
2119
- if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2120
- this.decorations = buildDecorations3(update2.view);
2121
- }
2122
- }
2123
- }, {
2124
- decorations: (v) => v.decorations,
2125
- provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2126
- return view.plugin(plugin)?.decorations || Decoration8.none;
2127
- })
2128
- });
2129
- };
2130
- var buildDecorations3 = (view) => {
2131
- const builder = new RangeSetBuilder3();
2421
+ var buildDecorations6 = (view) => {
2422
+ const builder = new RangeSetBuilder7();
2132
2423
  const { state } = view;
2133
2424
  const cursor = state.selection.main.head;
2134
2425
  for (const { from, to } of view.visibleRanges) {
2135
- syntaxTree4(state).iterate({
2426
+ syntaxTree8(state).iterate({
2136
2427
  enter: (node) => {
2137
- if (node.name === "TaskMarker" && (cursor < node.from || cursor > node.to)) {
2138
- const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2139
- 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
+ }
2140
2436
  }
2141
2437
  },
2142
2438
  from,
@@ -2145,9 +2441,60 @@ var buildDecorations3 = (view) => {
2145
2441
  }
2146
2442
  return builder.finish();
2147
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
+ };
2148
2495
 
2149
2496
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
2150
- import { keymap as keymap5 } from "@codemirror/view";
2497
+ import { keymap as keymap6 } from "@codemirror/view";
2151
2498
  var defaultItems = [
2152
2499
  "hello world!",
2153
2500
  "this is a test.",
@@ -2157,7 +2504,7 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2157
2504
  let t;
2158
2505
  let idx = 0;
2159
2506
  return [
2160
- keymap5.of([
2507
+ keymap6.of([
2161
2508
  {
2162
2509
  // Reset.
2163
2510
  key: "alt-meta-'",
@@ -2203,27 +2550,6 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2203
2550
  ];
2204
2551
  };
2205
2552
 
2206
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2207
- import { yCollab } from "y-codemirror.next";
2208
-
2209
- // packages/ui/react-ui-editor/src/extensions/yjs/cursor.ts
2210
- import * as Y from "yjs";
2211
- import { arrayToString, stringToArray } from "@dxos/util";
2212
- var cursorConverter2 = (text) => ({
2213
- toCursor: (index, assoc) => {
2214
- return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
2215
- },
2216
- fromCursor: (cursor) => {
2217
- return Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc)?.index ?? 0;
2218
- }
2219
- });
2220
-
2221
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2222
- var yjs = (content, awareness2) => [
2223
- Cursor.converter.of(cursorConverter2(content)),
2224
- yCollab(content, awareness2)
2225
- ];
2226
-
2227
2553
  // packages/ui/react-ui-editor/src/themes/default.ts
2228
2554
  import get3 from "lodash.get";
2229
2555
  var defaultTheme = {
@@ -2255,8 +2581,9 @@ var defaultTheme = {
2255
2581
  outline: "none"
2256
2582
  },
2257
2583
  ".cm-scroller": {
2258
- overflow: "visible",
2259
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2584
+ // overflow: 'visible',
2585
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(","),
2586
+ lineHeight: 1.4
2260
2587
  },
2261
2588
  ".cm-content": {
2262
2589
  padding: 0,
@@ -2280,8 +2607,11 @@ var defaultTheme = {
2280
2607
  "&dark .cm-cursor, &dark .cm-dropCursor": {
2281
2608
  borderLeft: "2px solid white"
2282
2609
  },
2283
- ".cm-placeholder": {
2284
- 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)")
2285
2615
  },
2286
2616
  //
2287
2617
  // line
@@ -2290,7 +2620,7 @@ var defaultTheme = {
2290
2620
  paddingInline: 0
2291
2621
  },
2292
2622
  ".cm-activeLine": {
2293
- background: "inherit"
2623
+ background: "transparent"
2294
2624
  },
2295
2625
  //
2296
2626
  // Selection
@@ -2461,90 +2791,88 @@ var codeTheme = {
2461
2791
  };
2462
2792
 
2463
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";
2464
2795
  var EditorModes = [
2465
2796
  "default",
2466
2797
  "vim"
2467
2798
  ];
2468
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, autofocus, readonly, selection, comments: comments2, extensions = [], editorMode, theme, slots = defaultSlots }, forwardedRef) => {
2469
- const tabsterDOMAttribute = useFocusableGroup({
2470
- tabBehavior: "limited"
2471
- });
2799
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2472
2800
  const { themeMode } = useThemeContext();
2473
2801
  const rootRef = useRef(null);
2474
2802
  const [view, setView] = useState(null);
2475
2803
  useImperativeHandle(forwardedRef, () => view, [
2476
2804
  view
2477
2805
  ]);
2478
- useEffect(() => {
2479
- 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
+ }
2480
2816
  view?.focus();
2481
2817
  }
2482
- }, [
2483
- autofocus,
2484
- view
2485
- ]);
2486
- const { awareness: awareness2, peer } = model;
2487
- useEffect(() => {
2488
- if (awareness2 && peer) {
2489
- awareness2.setLocalStateField("user", {
2490
- name: peer.name ?? generateName(peer.id),
2491
- color: getColorForValue({
2492
- value: peer.id,
2493
- type: "color"
2494
- }),
2495
- colorLight: getColorForValue({
2496
- value: peer.id,
2497
- themeMode,
2498
- type: "highlight"
2499
- })
2500
- });
2501
- }
2502
- }, [
2503
- awareness2,
2504
- peer,
2505
- themeMode
2506
- ]);
2507
- useEffect(() => {
2508
- if (view && comments2 !== void 0) {
2509
- view.dispatch({
2510
- effects: setCommentRange.of({
2511
- model,
2512
- comments: comments2
2513
- })
2514
- });
2515
- }
2516
2818
  }, [
2517
2819
  view,
2518
- comments2
2820
+ autoFocus
2519
2821
  ]);
2520
- useEffect(() => {
2822
+ useEffect2(() => {
2521
2823
  if (!model || !rootRef.current) {
2522
2824
  return;
2523
2825
  }
2524
2826
  const state = EditorState2.create({
2525
2827
  doc: model.text(),
2526
- // TODO(burdon): Composer should store and set selection when switching documents.
2527
2828
  selection,
2528
2829
  extensions: [
2529
- readonly && EditorState2.readOnly.of(readonly),
2530
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2531
- 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
+ }),
2532
2839
  // Theme.
2533
- // TODO(burdon): Make theme configurable.
2534
- EditorView12.baseTheme(defaultTheme),
2535
- EditorView12.theme(theme ?? {}),
2536
- 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),
2537
2853
  // Storage and replication.
2854
+ // NOTE: This must come before user extensions.
2538
2855
  model.extension,
2856
+ // TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
2857
+ editorMode === "vim" && vim(),
2539
2858
  // Custom.
2540
2859
  ...extensions
2541
- ].filter(Boolean)
2860
+ ].filter(isNotFalsy3)
2542
2861
  });
2543
- view?.destroy();
2544
- const newView = new EditorView12({
2862
+ const newView = new EditorView13({
2863
+ state,
2545
2864
  parent: rootRef.current,
2546
- 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
+ }
2547
2874
  });
2875
+ view?.destroy();
2548
2876
  setView(newView);
2549
2877
  return () => {
2550
2878
  newView?.destroy();
@@ -2560,8 +2888,14 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, autofocus, readonly, s
2560
2888
  const handleKeyUp = useCallback((event) => {
2561
2889
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2562
2890
  switch (key) {
2891
+ case "Enter": {
2892
+ view?.focus();
2893
+ break;
2894
+ }
2563
2895
  case "Escape": {
2564
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
2896
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
2897
+ view?.focus();
2898
+ }
2565
2899
  break;
2566
2900
  }
2567
2901
  }
@@ -2573,23 +2907,23 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, autofocus, readonly, s
2573
2907
  key: model.id,
2574
2908
  role: "none",
2575
2909
  tabIndex: 0,
2576
- onKeyUp: handleKeyUp,
2577
2910
  ...slots.root,
2578
- ...editorMode !== "vim" && tabsterDOMAttribute,
2911
+ // {...(editorMode !== 'vim' && tabsterDOMAttribute)}
2912
+ onKeyUp: handleKeyUp,
2579
2913
  ref: rootRef
2580
2914
  });
2581
2915
  });
2582
- 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) => {
2583
2917
  const { themeMode } = useThemeContext();
2584
2918
  const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2585
2919
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2586
2920
  ref: forwardedRef,
2587
2921
  readonly,
2588
2922
  extensions: [
2589
- basicBundle({
2590
- readonly,
2923
+ createBasicBundle({
2591
2924
  themeMode,
2592
- placeholder: placeholder3
2925
+ placeholder: placeholder3,
2926
+ lineWrapping
2593
2927
  }),
2594
2928
  ...extensions
2595
2929
  ],
@@ -2598,15 +2932,14 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholde
2598
2932
  ...props
2599
2933
  });
2600
2934
  });
2601
- 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) => {
2602
2936
  const { themeMode } = useThemeContext();
2603
2937
  const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2604
2938
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2605
2939
  ref: forwardedRef,
2606
2940
  readonly,
2607
2941
  extensions: [
2608
- markdownBundle({
2609
- readonly,
2942
+ createMarkdownExtensions({
2610
2943
  themeMode,
2611
2944
  placeholder: placeholder3
2612
2945
  }),
@@ -2619,7 +2952,10 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeh
2619
2952
  });
2620
2953
  var defaultSlots = {
2621
2954
  root: {
2622
- className: mx3("w-full p-2 overflow-y-auto", inputSurface)
2955
+ className: mx3("grow", focusRing)
2956
+ },
2957
+ editor: {
2958
+ className: "bs-full"
2623
2959
  }
2624
2960
  };
2625
2961
  var defaultTextSlots = {
@@ -2629,22 +2965,178 @@ var defaultMarkdownSlots = {
2629
2965
  ...defaultSlots
2630
2966
  };
2631
2967
 
2632
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2633
- import get4 from "lodash.get";
2634
- import { getRawDoc } from "@dxos/echo-schema";
2635
- 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
+ };
2636
3128
 
2637
3129
  // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2638
3130
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2639
3131
  import { Context as Context2 } from "@dxos/context";
2640
- import { invariant as invariant3 } from "@dxos/invariant";
2641
- import { log as log2 } from "@dxos/log";
3132
+ import { invariant as invariant2 } from "@dxos/invariant";
3133
+ import { log as log3 } from "@dxos/log";
2642
3134
  var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2643
3135
  var DEBOUNCE_INTERVAL = 100;
2644
3136
  var SpaceAwarenessProvider = class {
2645
3137
  constructor(params) {
2646
- this.remoteStateChange = new Event2();
2647
3138
  this._remoteStates = /* @__PURE__ */ new Map();
3139
+ this.remoteStateChange = new Event2();
2648
3140
  this._space = params.space;
2649
3141
  this._channel = params.channel;
2650
3142
  this._peerId = params.peerId;
@@ -2661,7 +3153,7 @@ var SpaceAwarenessProvider = class {
2661
3153
  await sleep(DEBOUNCE_INTERVAL);
2662
3154
  }
2663
3155
  });
2664
- const unsubscribe = this._space.listen(this._channel, (message) => {
3156
+ this._ctx.onDispose(this._space.listen(this._channel, (message) => {
2665
3157
  switch (message.payload.kind) {
2666
3158
  case "query": {
2667
3159
  this._handleQueryMessage();
@@ -2672,16 +3164,15 @@ var SpaceAwarenessProvider = class {
2672
3164
  break;
2673
3165
  }
2674
3166
  }
2675
- });
2676
- this._ctx.onDispose(unsubscribe);
3167
+ }));
2677
3168
  void this._space.postMessage(this._channel, {
2678
3169
  kind: "query"
2679
3170
  }).catch((err) => {
2680
- log2.debug("failed to query awareness", {
3171
+ log3.debug("failed to query awareness", {
2681
3172
  err
2682
3173
  }, {
2683
3174
  F: __dxlog_file4,
2684
- L: 85,
3175
+ L: 89,
2685
3176
  S: this,
2686
3177
  C: (f, a) => f(...a)
2687
3178
  });
@@ -2692,10 +3183,13 @@ var SpaceAwarenessProvider = class {
2692
3183
  this._ctx = void 0;
2693
3184
  this._postTask = void 0;
2694
3185
  }
3186
+ getRemoteStates() {
3187
+ return Array.from(this._remoteStates.values());
3188
+ }
2695
3189
  update(position) {
2696
- invariant3(this._postTask, void 0, {
3190
+ invariant2(this._postTask, void 0, {
2697
3191
  F: __dxlog_file4,
2698
- L: 96,
3192
+ L: 104,
2699
3193
  S: this,
2700
3194
  A: [
2701
3195
  "this._postTask",
@@ -2709,13 +3203,10 @@ var SpaceAwarenessProvider = class {
2709
3203
  };
2710
3204
  this._postTask.schedule();
2711
3205
  }
2712
- getRemoteStates() {
2713
- return Array.from(this._remoteStates.values());
2714
- }
2715
3206
  _handleQueryMessage() {
2716
- invariant3(this._postTask, void 0, {
3207
+ invariant2(this._postTask, void 0, {
2717
3208
  F: __dxlog_file4,
2718
- L: 111,
3209
+ L: 115,
2719
3210
  S: this,
2720
3211
  A: [
2721
3212
  "this._postTask",
@@ -2725,9 +3216,9 @@ var SpaceAwarenessProvider = class {
2725
3216
  this._postTask.schedule();
2726
3217
  }
2727
3218
  _handlePostMessage(message) {
2728
- invariant3(message.kind === "post", void 0, {
3219
+ invariant2(message.kind === "post", void 0, {
2729
3220
  F: __dxlog_file4,
2730
- L: 116,
3221
+ L: 120,
2731
3222
  S: this,
2732
3223
  A: [
2733
3224
  "message.kind === 'post'",
@@ -2739,199 +3230,240 @@ var SpaceAwarenessProvider = class {
2739
3230
  }
2740
3231
  };
2741
3232
 
2742
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
3233
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
2743
3234
  import { StateField as StateField5 } from "@codemirror/state";
2744
- import { useEffect as useEffect2, useState as useState2 } from "react";
2745
- import { isAutomergeObject } from "@dxos/react-client/echo";
3235
+ var modelState = StateField5.define({
3236
+ create: () => void 0,
3237
+ update: (model) => model
3238
+ });
2746
3239
 
2747
- // packages/ui/react-ui-editor/src/hooks/yjs.ts
2748
- import * as decoding from "lib0/decoding";
2749
- import * as encoding from "lib0/encoding";
2750
- import { Observable } from "lib0/observable";
2751
- import * as YP from "y-protocols/awareness";
2752
- import { invariant as invariant4 } from "@dxos/invariant";
2753
- import { log as log3 } from "@dxos/log";
2754
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2755
- var createYjsModel = ({ identity, space, text }) => {
2756
- invariant4(text?.doc && text?.content, void 0, {
2757
- F: __dxlog_file5,
2758
- L: 21,
2759
- S: void 0,
2760
- A: [
2761
- "text?.doc && text?.content",
2762
- ""
2763
- ]
2764
- });
2765
- const provider = space ? new YAwarenessProvider({
2766
- space,
2767
- doc: text.doc,
2768
- channel: `yjs.awareness.${text.id}`
2769
- }) : void 0;
2770
- const model = {
2771
- id: text.doc.guid,
2772
- content: text.content,
2773
- text: () => text.content.toString(),
2774
- extension: [
2775
- modelState.init(() => model),
2776
- yjs(text.content, provider?.awareness)
2777
- ],
2778
- awareness: provider?.awareness,
2779
- peer: identity ? {
2780
- id: identity.identityKey.toHex(),
2781
- name: identity.profile?.displayName
2782
- } : void 0
2783
- };
2784
- return model;
2785
- };
2786
- var messageAwareness = 1;
2787
- var messageQueryAwareness = 3;
2788
- var YAwarenessProvider = class extends Observable {
2789
- constructor({ space, doc, channel, awareness: awareness2 }) {
2790
- super();
2791
- this._space = space;
2792
- this._awareness = awareness2 ?? new YP.Awareness(doc);
2793
- this._channel = channel;
2794
- this._clientId = doc.clientID;
2795
- this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
2796
- this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
2797
- if (typeof window !== "undefined") {
2798
- window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
2799
- } else if (typeof process !== "undefined") {
2800
- 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;
2801
3245
  }
2802
- const encoderAwarenessQuery = encoding.createEncoder();
2803
- encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
2804
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
2805
- const encoderAwarenessState = encoding.createEncoder();
2806
- encoding.writeVarUint(encoderAwarenessState, messageAwareness);
2807
- encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [
2808
- this._clientId
2809
- ]));
2810
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
2811
- }
2812
- get awareness() {
2813
- return this._awareness;
2814
- }
2815
- _handleAwarenessUpdate({ added, updated, removed }, origin) {
2816
- log3("awareness update", {
2817
- added,
2818
- updated,
2819
- removed,
2820
- origin
2821
- }, {
2822
- F: __dxlog_file5,
2823
- L: 100,
2824
- S: this,
2825
- C: (f, a) => f(...a)
2826
- });
2827
- const changedClients = added.concat(updated).concat(removed);
2828
- const encoderAwareness = encoding.createEncoder();
2829
- encoding.writeVarUint(encoderAwareness, messageAwareness);
2830
- encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
2831
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2832
- }
2833
- _handleSpaceMessage({ payload }) {
2834
- log3("space message", payload, {
2835
- F: __dxlog_file5,
2836
- L: 109,
2837
- S: this,
2838
- 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
+ });
2839
3277
  });
2840
- const data = new Uint8Array(Array.from(Object.values(payload)));
2841
- const encoder = this._readMessage(data);
2842
- if (encoder) {
2843
- 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);
2844
3339
  }
2845
- }
2846
- _readMessage(message) {
2847
- const decoder = decoding.createDecoder(message);
2848
- const encoder = encoding.createEncoder();
2849
- const messageType = decoding.readVarUint(decoder);
2850
- let sendReply = false;
2851
- switch (messageType) {
2852
- case messageAwareness: {
2853
- YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2854
- 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);
2855
3350
  }
2856
- case messageQueryAwareness: {
2857
- encoding.writeVarUint(encoder, messageAwareness);
2858
- encoding.writeVarUint8Array(encoder, YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2859
- sendReply = true;
2860
- 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
+ };
2861
3362
  }
2862
- default: {
2863
- console.error("Invalid message:", messageType);
2864
- return encoder;
3363
+ if (autoFocus) {
3364
+ view.focus();
2865
3365
  }
2866
3366
  }
2867
- if (!sendReply) {
2868
- return null;
2869
- }
2870
- return encoder;
2871
- }
2872
- _handleBeforeUnload() {
2873
- YP.removeAwarenessStates(this._awareness, [
2874
- this._clientId
2875
- ], "window unload");
2876
- }
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);
2877
3398
  };
2878
3399
 
2879
3400
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2880
- var modelState = StateField5.define({
2881
- create: () => void 0,
2882
- update: (model) => model
2883
- });
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";
2884
3408
  var useTextModel = (props) => {
2885
3409
  const { identity, space, text } = props;
2886
- const [model, setModel] = useState2();
2887
- useEffect2(() => setModel(createModel(props)), [
3410
+ const [model, setModel] = useState4();
3411
+ useEffect4(() => setModel(createModel(props)), [
2888
3412
  identity,
2889
3413
  space,
2890
3414
  text
2891
3415
  ]);
2892
3416
  return model;
2893
3417
  };
2894
- var createModel = (props) => {
2895
- const { text } = props;
2896
- if (isAutomergeObject(text)) {
2897
- return createAutomergeModel(props);
2898
- } else if (text?.doc) {
2899
- return createYjsModel(props);
2900
- } else {
2901
- return void 0;
2902
- }
3418
+ var useInMemoryTextModel = ({ id, defaultContent }) => {
3419
+ const [content, setContent] = useState4(defaultContent ?? "");
3420
+ return {
3421
+ id,
3422
+ content,
3423
+ setContent,
3424
+ text: () => content
3425
+ };
2903
3426
  };
2904
-
2905
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2906
- 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
+ });
2907
3437
  const obj = text;
2908
3438
  const doc = getRawDoc(obj, [
2909
3439
  obj.field
2910
3440
  ]);
2911
- const awarenessProvider = space && new SpaceAwarenessProvider({
3441
+ const awarenessProvider2 = space && new SpaceAwarenessProvider({
2912
3442
  space,
2913
3443
  channel: `automerge.awareness.${obj.id}`,
2914
3444
  info: {
2915
- displayName: identity?.profile?.displayName ?? "",
3445
+ displayName: identity ? identity.profile?.displayName ?? generateName(identity.identityKey.toHex()) : void 0,
2916
3446
  color: cursorColor.color,
2917
3447
  lightColor: cursorColor.light
2918
3448
  },
2919
3449
  peerId: identity?.identityKey.toHex() ?? "Anonymous"
2920
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
+ }
2921
3462
  const model = {
2922
3463
  id: obj.id,
2923
3464
  content: doc,
2924
3465
  text: () => get4(doc.handle.docSync(), doc.path),
2925
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2926
- extension: [
2927
- modelState.init(() => model),
2928
- automerge3({
2929
- handle: doc.handle,
2930
- path: doc.path
2931
- }),
2932
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
2933
- awareness()
2934
- ].filter(isNotNullOrUndefined),
3466
+ extension: extensions,
2935
3467
  peer: identity ? {
2936
3468
  id: identity.identityKey.toHex(),
2937
3469
  name: identity.profile?.displayName
@@ -2940,7 +3472,6 @@ var createAutomergeModel = ({ space, identity, text }) => {
2940
3472
  return model;
2941
3473
  };
2942
3474
  export {
2943
- AwarenessProvider,
2944
3475
  BaseTextEditor,
2945
3476
  Cursor,
2946
3477
  Doc,
@@ -2950,46 +3481,66 @@ export {
2950
3481
  SpaceAwarenessProvider,
2951
3482
  TextEditor,
2952
3483
  TextKind,
2953
- YAwarenessProvider,
3484
+ Toolbar,
2954
3485
  YText,
2955
3486
  YXmlFragment,
2956
3487
  autocomplete,
2957
- automerge3 as automerge,
3488
+ automerge,
2958
3489
  awareness,
2959
- basicBundle,
3490
+ awarenessProvider,
2960
3491
  blast,
2961
3492
  callbackWrapper,
2962
3493
  code2 as code,
2963
3494
  codeTheme,
2964
3495
  comments,
2965
- createAutomergeModel,
2966
- createYjsModel,
3496
+ createBasicBundle,
3497
+ createComment,
3498
+ createDataExtensions,
3499
+ createMarkdownExtensions,
3500
+ createThemeExtensions,
2967
3501
  defaultMarkdownSlots,
2968
3502
  defaultOptions,
2969
3503
  defaultSlots,
2970
3504
  defaultTextSlots,
2971
3505
  defaultTheme,
3506
+ focusComment,
3507
+ formatting,
2972
3508
  getToken,
3509
+ heading2 as heading,
2973
3510
  hr,
2974
3511
  image,
3512
+ insertCodeblock,
3513
+ insertTable,
3514
+ keymap7 as keymap,
2975
3515
  link,
2976
3516
  listener,
2977
- markdownBundle,
3517
+ logChanges,
2978
3518
  markdownHighlightStyle,
2979
3519
  markdownTags,
2980
3520
  markdownTagsExtensions,
2981
3521
  markdownTheme,
2982
3522
  mention,
2983
3523
  modelState,
2984
- setCommentRange,
2985
- setFocus,
3524
+ outliner,
3525
+ setComments,
3526
+ setHeading,
2986
3527
  setSelection,
2987
3528
  table,
2988
3529
  tags2 as tags,
2989
3530
  tasklist,
2990
3531
  textTheme,
3532
+ toggleBold,
3533
+ toggleItalic,
3534
+ toggleList,
3535
+ toggleStrikethrough,
3536
+ toggleStyle,
2991
3537
  typewriter,
3538
+ useActionHandler,
3539
+ useComments,
3540
+ useEditorView,
3541
+ useInMemoryTextModel,
3542
+ useTextEditor,
2992
3543
  useTextModel,
2993
- yjs
3544
+ useToolbarContext
2994
3545
  };
2995
3546
  //# sourceMappingURL=index.mjs.map