@dxos/react-ui-editor 0.3.11-main.ec7a2b3 → 0.3.11-main.ee2b64c

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 (185) hide show
  1. package/dist/lib/browser/index.mjs +1702 -1100
  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 +14 -6
  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 +9 -11
  27. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  28. package/dist/types/src/extensions/automerge/index.d.ts +0 -1
  29. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
  30. package/dist/types/src/extensions/automerge/semaphore.d.ts +8 -12
  31. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  32. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
  33. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  34. package/dist/types/src/extensions/awareness.d.ts +2 -2
  35. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  36. package/dist/types/src/extensions/basic.d.ts +4 -5
  37. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  38. package/dist/types/src/extensions/comments.d.ts +35 -18
  39. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  40. package/dist/types/src/extensions/cursor.d.ts +3 -3
  41. package/dist/types/src/extensions/cursor.d.ts.map +1 -1
  42. package/dist/types/src/extensions/index.d.ts +1 -7
  43. package/dist/types/src/extensions/index.d.ts.map +1 -1
  44. package/dist/types/src/extensions/markdown/bundle.d.ts +3 -6
  45. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  46. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
  47. package/dist/types/src/extensions/markdown/formatting.d.ts +13 -0
  48. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  49. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  50. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  51. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  52. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  53. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  54. package/dist/types/src/extensions/markdown/index.d.ts +8 -0
  55. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  56. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  57. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  58. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  59. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  60. package/dist/types/src/extensions/outliner.d.ts +4 -0
  61. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  62. package/dist/types/src/hooks/awareness-provider.d.ts +7 -4
  63. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -1
  64. package/dist/types/src/hooks/defs.d.ts +25 -0
  65. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  66. package/dist/types/src/hooks/index.d.ts +4 -2
  67. package/dist/types/src/hooks/index.d.ts.map +1 -1
  68. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  69. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  70. package/dist/types/src/hooks/useEditorView.d.ts +17 -0
  71. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
  72. package/dist/types/src/hooks/useTextEditor.d.ts +39 -0
  73. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  74. package/dist/types/src/hooks/useTextModel.d.ts +14 -27
  75. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  76. package/dist/types/src/index.d.ts +1 -1
  77. package/dist/types/src/index.d.ts.map +1 -1
  78. package/dist/types/src/styles/markdown.d.ts +2 -2
  79. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  80. package/dist/types/src/themes/default.d.ts.map +1 -1
  81. package/dist/types/src/util.d.ts +2 -0
  82. package/dist/types/src/util.d.ts.map +1 -1
  83. package/package.json +48 -38
  84. package/src/components/TextEditor/MarkdownEditor.stories.tsx +73 -82
  85. package/src/components/TextEditor/TextEditor.tsx +111 -75
  86. package/src/components/TextEditor/automerge.stories.tsx +1 -2
  87. package/src/components/TextEditor/hooks.stories.tsx +106 -0
  88. package/src/components/Toolbar/Toolbar.stories.tsx +90 -0
  89. package/src/components/Toolbar/Toolbar.tsx +166 -0
  90. package/src/components/Toolbar/index.ts +5 -0
  91. package/src/components/index.ts +1 -0
  92. package/src/extensions/autocomplete.ts +6 -3
  93. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  94. package/src/extensions/automerge/automerge.test.ts +13 -0
  95. package/src/extensions/automerge/automerge.ts +54 -45
  96. package/src/extensions/automerge/cursor.ts +3 -3
  97. package/src/extensions/automerge/defs.ts +13 -34
  98. package/src/extensions/automerge/index.ts +0 -1
  99. package/src/extensions/automerge/semaphore.ts +33 -32
  100. package/src/extensions/automerge/update-automerge.ts +14 -8
  101. package/src/extensions/automerge/update-codemirror.ts +5 -5
  102. package/src/extensions/awareness.ts +22 -18
  103. package/src/extensions/basic.ts +17 -10
  104. package/src/extensions/blast.ts +4 -1
  105. package/src/extensions/comments.ts +327 -166
  106. package/src/extensions/cursor.ts +7 -3
  107. package/src/extensions/index.ts +1 -7
  108. package/src/extensions/markdown/bundle.ts +23 -28
  109. package/src/extensions/markdown/code.ts +150 -0
  110. package/src/extensions/markdown/formatting.ts +188 -0
  111. package/src/extensions/markdown/heading.ts +59 -0
  112. package/src/extensions/markdown/highlight.ts +9 -15
  113. package/src/extensions/markdown/hr.ts +71 -0
  114. package/src/extensions/markdown/index.ts +8 -0
  115. package/src/extensions/{link.ts → markdown/link.ts} +16 -7
  116. package/src/extensions/markdown/tasklist.ts +110 -0
  117. package/src/extensions/mention.ts +9 -2
  118. package/src/extensions/outliner.ts +12 -0
  119. package/src/hooks/awareness-provider.ts +24 -20
  120. package/src/hooks/defs.ts +42 -0
  121. package/src/hooks/index.ts +5 -2
  122. package/src/hooks/useActionHandler.ts +62 -0
  123. package/src/hooks/useEditorView.ts +29 -0
  124. package/src/hooks/useTextEditor.ts +153 -0
  125. package/src/hooks/useTextModel.ts +66 -56
  126. package/src/index.ts +1 -1
  127. package/src/styles/markdown.ts +9 -10
  128. package/src/themes/default.ts +11 -8
  129. package/src/util.ts +26 -3
  130. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  131. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  132. package/dist/types/src/components/TextEditor/comments.stories.d.ts +0 -26
  133. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +0 -1
  134. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  135. package/dist/types/src/extensions/code.d.ts.map +0 -1
  136. package/dist/types/src/extensions/hr.d.ts.map +0 -1
  137. package/dist/types/src/extensions/image.d.ts.map +0 -1
  138. package/dist/types/src/extensions/link.d.ts.map +0 -1
  139. package/dist/types/src/extensions/table.d.ts.map +0 -1
  140. package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
  141. package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
  142. package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
  143. package/dist/types/src/extensions/yjs/index.d.ts +0 -2
  144. package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
  145. package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
  146. package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
  147. package/dist/types/src/extensions/yjs/yjs.test.d.ts +0 -2
  148. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
  149. package/dist/types/src/hooks/automerge.d.ts +0 -3
  150. package/dist/types/src/hooks/automerge.d.ts.map +0 -1
  151. package/dist/types/src/hooks/yjs.d.ts +0 -27
  152. package/dist/types/src/hooks/yjs.d.ts.map +0 -1
  153. package/dist/types/src/testing/index.d.ts +0 -3
  154. package/dist/types/src/testing/index.d.ts.map +0 -1
  155. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  156. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  157. package/dist/types/src/testing/proto/index.d.ts +0 -2
  158. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  159. package/dist/types/src/testing/replicator.d.ts +0 -45
  160. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  161. package/src/components/TextEditor/codemirror.stories.ts +0 -115
  162. package/src/components/TextEditor/comments.stories.tsx +0 -315
  163. package/src/components/TextEditor/yjs.stories.tsx +0 -56
  164. package/src/extensions/code.ts +0 -106
  165. package/src/extensions/hr.ts +0 -73
  166. package/src/extensions/tasklist.ts +0 -119
  167. package/src/extensions/yjs/cursor.ts +0 -22
  168. package/src/extensions/yjs/index.ts +0 -5
  169. package/src/extensions/yjs/yjs.test.ts +0 -35
  170. package/src/extensions/yjs/yjs.ts +0 -16
  171. package/src/hooks/automerge.ts +0 -52
  172. package/src/hooks/yjs.ts +0 -156
  173. package/src/testing/index.ts +0 -6
  174. package/src/testing/proto/gen/schema.ts +0 -49
  175. package/src/testing/proto/index.ts +0 -5
  176. package/src/testing/proto/schema.proto +0 -15
  177. package/src/testing/replicator.ts +0 -184
  178. /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
  179. /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
  180. /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
  181. /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
  182. /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
  183. /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.ts} +0 -0
  184. /package/src/extensions/{image.ts → markdown/image.ts} +0 -0
  185. /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
54
- import { Facet as Facet2, StateField } from "@codemirror/state";
55
- import { ViewPlugin } from "@codemirror/view";
56
- import { next as A } from "@dxos/automerge/automerge";
57
+ import { invertedEffects } from "@codemirror/commands";
58
+ import { StateField } from "@codemirror/state";
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,26 +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
99
  import { Annotation, StateEffect } from "@codemirror/state";
96
- var effectType = StateEffect.define({});
97
- var updateHeads = (newHeads) => effectType.of({
100
+ var getPath = (state, field) => state.field(field).path;
101
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
102
+ var updateHeadsEffect = StateEffect.define({});
103
+ var updateHeads = (newHeads) => updateHeadsEffect.of({
98
104
  newHeads
99
105
  });
100
- var getLastHeads = (state, field) => state.field(field).lastHeads;
101
- var getPath = (state, field) => state.field(field).path;
102
- var reconcileAnnotationType = Annotation.define();
103
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
106
+ var reconcileAnnotation = Annotation.define();
107
+ var isReconcile = (tr) => {
108
+ return !!tr.annotation(reconcileAnnotation);
109
+ };
104
110
 
105
111
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
106
- import { next as automerge2 } from "@dxos/automerge/automerge";
112
+ import { next as A3 } from "@dxos/automerge/automerge";
107
113
 
108
114
  // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
109
- import { next as am } from "@dxos/automerge/automerge";
115
+ import { next as A2 } from "@dxos/automerge/automerge";
110
116
  var updateAutomerge = (field, handle, transactions, state) => {
111
117
  const { lastHeads, path } = state.field(field);
112
118
  let hasChanges = false;
@@ -119,11 +125,19 @@ var updateAutomerge = (field, handle, transactions, state) => {
119
125
  return void 0;
120
126
  }
121
127
  const newHeads = handle.changeAt(lastHeads, (doc) => {
128
+ const invertedTransactions = [];
122
129
  for (const tr of transactions) {
123
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
124
- 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
+ });
125
136
  });
126
137
  }
138
+ invertedTransactions.reverse().forEach(({ from, del, insert }) => {
139
+ A2.splice(doc, path.slice(), from, del, insert.toString());
140
+ });
127
141
  });
128
142
  return newHeads ?? void 0;
129
143
  };
@@ -138,13 +152,13 @@ var updateCodeMirror = (view, selection, target, patches) => {
138
152
  selection = selection.map(changeSet, 1);
139
153
  view.dispatch({
140
154
  changes: changeSet,
141
- annotations: reconcileAnnotationType.of({})
155
+ annotations: reconcileAnnotation.of(false)
142
156
  });
143
157
  }
144
158
  }
145
159
  view.dispatch({
146
160
  selection,
147
- annotations: reconcileAnnotationType.of({})
161
+ annotations: reconcileAnnotation.of(false)
148
162
  });
149
163
  };
150
164
  var handlePatch = (patch, target, state) => {
@@ -165,7 +179,7 @@ var handleInsert = (target, patch) => {
165
179
  if (index == null) {
166
180
  return [];
167
181
  }
168
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
182
+ const text = patch.values.map((value) => value ? value.toString() : "").join("");
169
183
  return [
170
184
  {
171
185
  from: index,
@@ -237,40 +251,43 @@ var charPath = (textPath, candidatePath) => {
237
251
 
238
252
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
239
253
  var PatchSemaphore = class {
240
- constructor(_field) {
241
- this._field = _field;
254
+ // prettier-ignore
255
+ constructor(_handle, _state) {
256
+ this._handle = _handle;
257
+ this._state = _state;
242
258
  this._inReconcile = false;
243
- this._queue = [];
244
- this.reconcile = (handle, view) => {
245
- if (this._inReconcile) {
246
- return;
247
- }
259
+ }
260
+ reconcile(view) {
261
+ if (!this._inReconcile) {
248
262
  this._inReconcile = true;
249
- const path = getPath(view.state, this._field);
250
- const oldHeads = getLastHeads(view.state, this._field);
251
- let selection = view.state.selection;
252
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
253
- const toInvert = transactions.slice().reverse();
254
- for (const tx of toInvert) {
255
- const inverted = tx.changes.invert(tx.startState.doc);
256
- selection = selection.map(inverted);
257
- view.dispatch({
258
- changes: inverted,
259
- annotations: reconcileAnnotationType.of(true)
260
- });
261
- }
262
- let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
263
- if (newHeads === null || newHeads === void 0) {
264
- newHeads = automerge2.getHeads(handle.docSync());
265
- }
266
- const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(handle.docSync(), oldHeads, newHeads);
267
- updateCodeMirror(view, selection, path, diff);
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);
268
276
  view.dispatch({
269
- effects: updateHeads(newHeads),
270
- annotations: reconcileAnnotationType.of({})
277
+ changes: inverted,
278
+ annotations: reconcileAnnotation.of(true)
271
279
  });
272
- this._inReconcile = false;
273
- };
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
+ });
274
291
  }
275
292
  };
276
293
 
@@ -280,16 +297,17 @@ var defaultCursorConverter = {
280
297
  toCursor: (position) => position.toString(),
281
298
  fromCursor: (cursor) => parseInt(cursor)
282
299
  };
283
- var Cursor = class {
300
+ var Cursor = class _Cursor {
284
301
  static {
285
302
  this.converter = Facet.define({
286
303
  combine: (providers) => providers[0] ?? defaultCursorConverter
287
304
  });
288
305
  }
289
306
  static {
290
- this.getCursorFromRange = (cursorConverter3, range) => {
291
- const from = cursorConverter3.toCursor(range.from);
292
- 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);
293
311
  return [
294
312
  from,
295
313
  to
@@ -297,10 +315,11 @@ var Cursor = class {
297
315
  };
298
316
  }
299
317
  static {
300
- this.getRangeFromCursor = (cursorConverter3, cursor) => {
318
+ this.getRangeFromCursor = (state, cursor) => {
319
+ const cursorConverter2 = state.facet(_Cursor.converter);
301
320
  const parts = cursor.split(":");
302
- const from = cursorConverter3.fromCursor(parts[0]);
303
- const to = cursorConverter3.fromCursor(parts[1]);
321
+ const from = cursorConverter2.fromCursor(parts[0]);
322
+ const to = cursorConverter2.fromCursor(parts[1]);
304
323
  return from !== void 0 && to !== void 0 ? {
305
324
  from,
306
325
  to
@@ -310,25 +329,22 @@ var Cursor = class {
310
329
  };
311
330
 
312
331
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
313
- var semaphoreFacet = Facet2.define({
314
- combine: (values) => values.at(-1)
315
- });
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(stateField);
347
- const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
348
- constructor(_view) {
349
- this._view = _view;
350
- this._handleChange = () => {
351
- this._view.state.facet(semaphoreFacet).reconcile(handle, 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(handle, 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,168 +1037,123 @@ 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 { log as log2 } from "@dxos/log";
1109
- import { nonNullable } from "@dxos/util";
1110
-
1111
1040
  // packages/ui/react-ui-editor/src/util.ts
1112
1041
  import { log } from "@dxos/log";
1113
1042
  var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1114
1043
  var callbackWrapper = (fn) => (...args) => {
1115
1044
  try {
1116
1045
  return fn(...args);
1117
- } catch (error) {
1118
- log.catch(error, void 0, {
1046
+ } catch (err) {
1047
+ log.catch(err, void 0, {
1119
1048
  F: __dxlog_file2,
1120
- L: 16,
1049
+ L: 18,
1121
1050
  S: void 0,
1122
1051
  C: (f, a) => f(...a)
1123
1052
  });
1124
1053
  }
1125
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
+ };
1126
1074
 
1127
1075
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1128
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1129
- var styles3 = EditorView5.baseTheme({
1130
- "& .cm-comment": {
1076
+ var styles2 = EditorView5.baseTheme({
1077
+ "&light .cm-comment, &light .cm-comment-current": {
1078
+ mixBlendMode: "darken"
1079
+ },
1080
+ "&dark .cm-comment, &dark .cm-comment-current": {
1081
+ mixBlendMode: "plus-lighter"
1082
+ },
1083
+ "&light .cm-comment": {
1131
1084
  backgroundColor: getToken("extend.colors.yellow.50")
1132
1085
  },
1133
- "& .cm-comment-active": {
1086
+ "&light .cm-comment-current": {
1134
1087
  backgroundColor: getToken("extend.colors.yellow.100")
1088
+ },
1089
+ "&dark .cm-comment": {
1090
+ color: getToken("extend.colors.yellow.50"),
1091
+ backgroundColor: getToken("extend.colors.yellow.900")
1092
+ },
1093
+ "&dark .cm-comment-current": {
1094
+ color: getToken("extend.colors.yellow.100"),
1095
+ backgroundColor: getToken("extend.colors.yellow.950")
1135
1096
  }
1136
1097
  });
1137
- var marks = {
1138
- highlight: Decoration3.mark({
1139
- class: "cm-comment"
1140
- }),
1141
- highlightActive: Decoration3.mark({
1142
- class: "cm-comment-active"
1143
- })
1144
- };
1145
- var setFocus = (view, thread) => {
1146
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1147
- if (!range) {
1098
+ var commentMark = Decoration2.mark({
1099
+ class: "cm-comment"
1100
+ });
1101
+ var commentCurrentMark = Decoration2.mark({
1102
+ class: "cm-comment-current"
1103
+ });
1104
+ var focusComment = (view, id, center = true) => {
1105
+ const comment = view.state.field(commentsState).comments.find((range2) => range2.comment.id === id);
1106
+ if (!comment?.comment.cursor) {
1148
1107
  return;
1149
1108
  }
1150
- view.dispatch({
1151
- effects: setSelection.of({
1152
- active: thread
1153
- }),
1154
- selection: {
1155
- anchor: range.from
1156
- },
1157
- scrollIntoView: true
1158
- });
1109
+ const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);
1110
+ if (range) {
1111
+ view.dispatch({
1112
+ selection: {
1113
+ anchor: range.from
1114
+ },
1115
+ effects: [
1116
+ //
1117
+ EditorView5.scrollIntoView(range.from, center ? {
1118
+ y: "center"
1119
+ } : void 0),
1120
+ setSelection.of({
1121
+ current: id
1122
+ })
1123
+ ]
1124
+ });
1125
+ }
1159
1126
  };
1160
- var setCommentRange = StateEffect2.define();
1127
+ var setComments = StateEffect2.define();
1161
1128
  var setSelection = StateEffect2.define();
1162
1129
  var setCommentState = StateEffect2.define();
1163
- var commentsStateField = StateField2.define({
1130
+ var commentsState = StateField2.define({
1164
1131
  create: () => ({
1165
- ranges: []
1132
+ comments: [],
1133
+ selection: {}
1166
1134
  }),
1167
1135
  update: (value, tr) => {
1168
- const cursorConverter3 = tr.state.facet(Cursor.converter);
1169
1136
  for (const effect of tr.effects) {
1170
1137
  if (effect.is(setSelection)) {
1171
1138
  return {
1172
1139
  ...value,
1173
- ...effect.value
1140
+ selection: effect.value
1174
1141
  };
1175
1142
  }
1176
- if (effect.is(setCommentRange)) {
1177
- const { comments: comments2 } = effect.value;
1178
- const ranges = comments2.map((comment) => {
1179
- const range = Cursor.getRangeFromCursor(cursorConverter3, comment.cursor);
1143
+ if (effect.is(setComments)) {
1144
+ const comments2 = effect.value.map((comment) => {
1145
+ if (!comment.cursor) {
1146
+ return void 0;
1147
+ }
1148
+ const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);
1180
1149
  return range && {
1181
- ...comment,
1182
- ...range
1150
+ comment,
1151
+ range
1183
1152
  };
1184
1153
  }).filter(nonNullable);
1185
1154
  return {
1186
1155
  ...value,
1187
- ranges
1156
+ comments: comments2
1188
1157
  };
1189
1158
  }
1190
1159
  if (effect.is(setCommentState)) {
@@ -1194,87 +1163,174 @@ var commentsStateField = StateField2.define({
1194
1163
  return value;
1195
1164
  }
1196
1165
  });
1197
- var highlightDecorations = EditorView5.decorations.compute([
1198
- commentsStateField
1166
+ var commentsDecorations = EditorView5.decorations.compute([
1167
+ commentsState
1199
1168
  ], (state) => {
1200
- const { active, ranges } = state.field(commentsStateField);
1201
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1202
- if (selection.from === selection.to) {
1169
+ const { selection: { current }, comments: comments2 } = state.field(commentsState);
1170
+ const decorations = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
1171
+ const range = comment.range;
1172
+ if (!range || range.from === range.to) {
1173
+ console.warn("Invalid range:", range);
1203
1174
  return void 0;
1204
1175
  }
1205
- const range = {
1206
- from: selection.from,
1207
- to: selection.to
1208
- };
1209
- if (selection.id === active) {
1210
- return marks.highlightActive.range(range.from, range.to);
1176
+ if (comment.comment.id === current) {
1177
+ return commentCurrentMark.range(range.from, range.to);
1211
1178
  } else {
1212
- return marks.highlight.range(range.from, range.to);
1179
+ return commentMark.range(range.from, range.to);
1213
1180
  }
1214
- }).filter(nonNullable) ?? [];
1215
- return Decoration3.set(decorations);
1181
+ }).filter(nonNullable);
1182
+ return Decoration2.set(decorations);
1216
1183
  });
1217
- var comments = (options = {}) => {
1218
- const { key: shortcut = "meta-'" } = options;
1219
- const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1220
- const createCommentThread = (view) => {
1221
- const cursorConverter3 = view.state.facet(Cursor.converter);
1222
- invariant2(options.onCreate, void 0, {
1223
- F: __dxlog_file3,
1224
- L: 177,
1225
- S: void 0,
1226
- A: [
1227
- "options.onCreate",
1228
- ""
1229
- ]
1230
- });
1231
- const { head, from, to } = view.state.selection.main;
1232
- if (from === to) {
1233
- return false;
1184
+ var trackPastedComments = (onUpdate) => {
1185
+ let tracked = null;
1186
+ const handleTrack = (event, view) => {
1187
+ const comments2 = view.state.field(commentsState);
1188
+ const { main } = view.state.selection;
1189
+ const selectedRanges = comments2.comments.filter(({ range }) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1190
+ if (!selectedRanges.length) {
1191
+ tracked = null;
1192
+ } else {
1193
+ tracked = {
1194
+ text: view.state.doc.slice(main.from, main.to),
1195
+ comments: selectedRanges.map(({ comment, range }) => ({
1196
+ id: comment.id,
1197
+ from: range.from - main.from,
1198
+ to: range.to - main.from
1199
+ }))
1200
+ };
1234
1201
  }
1235
- const cursor = Cursor.getCursorFromRange(cursorConverter3, {
1236
- from,
1237
- to
1238
- });
1239
- if (cursor) {
1240
- const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
1241
- if (id) {
1242
- view.dispatch({
1243
- effects: setSelection.of({
1244
- active: id
1245
- }),
1246
- selection: {
1247
- anchor: from
1202
+ };
1203
+ return [
1204
+ EditorView5.domEventHandlers({
1205
+ cut: handleTrack,
1206
+ copy: handleTrack
1207
+ }),
1208
+ // Track deleted comments.
1209
+ invertedEffects2.of((tr) => {
1210
+ const { comments: comments2 } = tr.startState.field(commentsState);
1211
+ const effects = [];
1212
+ tr.changes.iterChangedRanges((fromA, toA) => {
1213
+ for (const { comment: { id }, range: { from, to } } of comments2) {
1214
+ if (from < to && from >= fromA && to <= toA) {
1215
+ effects.push(restoreCommentEffect.of({
1216
+ id,
1217
+ from,
1218
+ to
1219
+ }));
1248
1220
  }
1249
- });
1250
- if (options.footnote) {
1251
- const tag = `[^${id}]`;
1252
- view.dispatch({
1253
- changes: {
1254
- from: head,
1255
- insert: tag
1256
- },
1257
- selection: {
1258
- anchor: head + tag.length
1221
+ }
1222
+ });
1223
+ return effects;
1224
+ }),
1225
+ // Handle paste or the undo of comment deletion.
1226
+ EditorView5.updateListener.of((update2) => {
1227
+ const restore = [];
1228
+ for (let i = 0; i < update2.transactions.length; i++) {
1229
+ const tr = update2.transactions[i];
1230
+ for (let j = 0; j < restore.length; j++) {
1231
+ restore[j] = mapTrackedComment(restore[j], tr.changes);
1232
+ }
1233
+ for (const effect of tr.effects) {
1234
+ if (effect.is(restoreCommentEffect)) {
1235
+ restore.push(effect.value);
1236
+ }
1237
+ }
1238
+ }
1239
+ if (tracked) {
1240
+ const paste = update2.transactions.find((tr) => tr.isUserEvent("input.paste"));
1241
+ if (paste) {
1242
+ let found = -1;
1243
+ paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1244
+ if (text.eq(tracked.text)) {
1245
+ for (let i = update2.transactions.indexOf(paste) + 1; i < update2.transactions.length; i++) {
1246
+ fromB = update2.transactions[i].changes.mapPos(fromB);
1247
+ }
1248
+ found = fromB;
1259
1249
  }
1260
1250
  });
1251
+ if (found > -1) {
1252
+ for (const moved of tracked.comments) {
1253
+ restore.push({
1254
+ id: moved.id,
1255
+ from: found + moved.from,
1256
+ to: found + moved.to
1257
+ });
1258
+ }
1259
+ }
1260
+ tracked = null;
1261
1261
  }
1262
- return true;
1263
1262
  }
1264
- }
1263
+ for (const comment of restore) {
1264
+ const { comments: comments2 } = update2.startState.field(commentsState);
1265
+ const exists = comments2.some((c) => c.comment.id === comment.id && c.range.from < c.range.to);
1266
+ if (!exists) {
1267
+ const cursor = Cursor.getCursorFromRange(update2.state, comment);
1268
+ onUpdate(comment.id, cursor);
1269
+ }
1270
+ }
1271
+ })
1272
+ ];
1273
+ };
1274
+ var mapTrackedComment = (comment, changes) => ({
1275
+ id: comment.id,
1276
+ from: changes.mapPos(comment.from, 1),
1277
+ to: changes.mapPos(comment.to, 1)
1278
+ });
1279
+ var restoreCommentEffect = StateEffect2.define({
1280
+ map: mapTrackedComment
1281
+ });
1282
+ var optionsFacet = Facet3.define({
1283
+ combine: (providers) => providers[0]
1284
+ });
1285
+ var createComment = (view) => {
1286
+ const options = view.state.facet(optionsFacet);
1287
+ const { from, to } = view.state.selection.main;
1288
+ if (from === to) {
1265
1289
  return false;
1266
- };
1290
+ }
1291
+ if (to === view.state.doc.length) {
1292
+ view.dispatch({
1293
+ changes: {
1294
+ from: to,
1295
+ insert: "\n"
1296
+ }
1297
+ });
1298
+ }
1299
+ const cursor = Cursor.getCursorFromRange(view.state, {
1300
+ from,
1301
+ to
1302
+ });
1303
+ if (cursor) {
1304
+ const id = options.onCreate?.(cursor, view.coordsAtPos(from));
1305
+ if (id) {
1306
+ view.dispatch({
1307
+ effects: setSelection.of({
1308
+ current: id
1309
+ }),
1310
+ selection: {
1311
+ anchor: to
1312
+ }
1313
+ });
1314
+ return true;
1315
+ }
1316
+ }
1317
+ return false;
1318
+ };
1319
+ var comments = (options = {}) => {
1320
+ const { key: shortcut = "meta-'" } = options;
1321
+ const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1267
1322
  return [
1268
- commentsStateField,
1269
- highlightDecorations,
1270
- styles3,
1323
+ optionsFacet.of(options),
1324
+ commentsState,
1325
+ commentsDecorations,
1326
+ styles2,
1271
1327
  //
1272
1328
  // Keymap.
1273
1329
  //
1274
1330
  options.onCreate ? keymap3.of([
1275
1331
  {
1276
1332
  key: shortcut,
1277
- run: callbackWrapper(createCommentThread)
1333
+ run: callbackWrapper(createComment)
1278
1334
  }
1279
1335
  ]) : [],
1280
1336
  //
@@ -1290,7 +1346,7 @@ var comments = (options = {}) => {
1290
1346
  above: true,
1291
1347
  create: () => {
1292
1348
  const el = document.createElement("div");
1293
- options.onHover?.(el, shortcut);
1349
+ options.onHover(el, shortcut);
1294
1350
  return {
1295
1351
  dom: el,
1296
1352
  offset: {
@@ -1303,364 +1359,116 @@ var comments = (options = {}) => {
1303
1359
  }
1304
1360
  return null;
1305
1361
  }, {
1306
- hideOnChange: true,
1362
+ // TODO(burdon): Hide on change triggered immediately?
1363
+ // hideOnChange: true,
1307
1364
  hoverTime: 1e3
1308
1365
  }) : [],
1309
1366
  //
1310
- // Monitor cursor movement and text updates.
1311
- // TODO(burdon): Is there a better (finer grained) way to do this?
1367
+ // Track deleted ranges and update ranges for decorations.
1312
1368
  //
1313
1369
  EditorView5.updateListener.of(({ view, state, changes }) => {
1314
- const cursorConverter3 = view.state.facet(Cursor.converter);
1315
- {
1316
- let mod = false;
1317
- const { active, ranges } = state.field(commentsStateField);
1318
- changes.iterChanges((from, to, from2, to2) => {
1319
- ranges.forEach((range) => {
1320
- if (from2 === to2) {
1321
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1322
- if (!newRange || newRange.to - newRange.from === 0) {
1323
- log2.info("deleted comment", {
1324
- thread: range.id
1325
- }, {
1326
- F: __dxlog_file3,
1327
- L: 274,
1328
- S: void 0,
1329
- C: (f, a) => f(...a)
1330
- });
1331
- }
1332
- }
1333
- if (from <= range.to) {
1334
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1335
- Object.assign(range, newRange);
1336
- mod = true;
1370
+ let mod = false;
1371
+ const { comments: comments2, ...value } = state.field(commentsState);
1372
+ changes.iterChanges((from, to, from2, to2) => {
1373
+ comments2.forEach(({ comment, range }) => {
1374
+ if (from2 === to2) {
1375
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1376
+ if (!newRange || newRange.to - newRange.from === 0) {
1377
+ options.onDelete?.(comment.id);
1337
1378
  }
1338
- });
1379
+ }
1380
+ if (from <= range.to) {
1381
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1382
+ Object.assign(range, newRange);
1383
+ mod = true;
1384
+ }
1385
+ });
1386
+ });
1387
+ if (mod) {
1388
+ view.dispatch({
1389
+ effects: setCommentState.of({
1390
+ comments: comments2,
1391
+ ...value
1392
+ })
1339
1393
  });
1340
- if (mod) {
1341
- view.dispatch({
1342
- effects: setCommentState.of({
1343
- active,
1344
- ranges
1345
- })
1346
- });
1347
- }
1348
1394
  }
1349
- {
1350
- const { head } = state.selection.main;
1351
- let min = Infinity;
1352
- const { active, closest, ranges } = state.field(commentsStateField);
1353
- const selected = {
1354
- active: void 0,
1355
- closest: void 0
1356
- };
1357
- ranges.forEach((comment) => {
1358
- const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1359
- if (head >= comment.from && head <= comment.to) {
1360
- selected.active = comment.id;
1361
- }
1395
+ }),
1396
+ //
1397
+ // Track selection/proximity.
1398
+ //
1399
+ EditorView5.updateListener.of(({ view, state }) => {
1400
+ let min = Infinity;
1401
+ const { selection: { current, closest }, comments: comments2 } = state.field(commentsState);
1402
+ const { head } = state.selection.main;
1403
+ const selection = {};
1404
+ comments2.forEach(({ comment, range }) => {
1405
+ if (head >= range.from && head <= range.to) {
1406
+ selection.current = comment.id;
1407
+ selection.closest = void 0;
1408
+ }
1409
+ if (!selection.current) {
1410
+ const d = Math.min(Math.abs(head - range.from), Math.abs(head - range.to));
1362
1411
  if (d < min) {
1363
- selected.closest = comment.id;
1412
+ selection.closest = comment.id;
1364
1413
  min = d;
1365
1414
  }
1366
- });
1367
- if (selected.active !== active || selected.closest !== closest) {
1368
- view.dispatch({
1369
- effects: setSelection.of(selected)
1370
- });
1371
- handleSelect({
1372
- ...selected,
1373
- ranges: ranges.map((range) => ({
1374
- ...range,
1375
- location: view.coordsAtPos(range.from)
1376
- }))
1377
- });
1378
1415
  }
1416
+ });
1417
+ if (selection.current !== current || selection.closest !== closest) {
1418
+ view.dispatch({
1419
+ effects: setSelection.of(selection)
1420
+ });
1421
+ handleSelect({
1422
+ selection,
1423
+ comments: comments2.map(({ comment, range }) => ({
1424
+ comment,
1425
+ range,
1426
+ location: view.coordsAtPos(range.from)
1427
+ }))
1428
+ });
1379
1429
  }
1380
- })
1430
+ }),
1431
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
1381
1432
  ];
1382
1433
  };
1434
+ var useComments = (view, comments2 = []) => {
1435
+ useEffect(() => {
1436
+ if (view) {
1437
+ view.dispatch({
1438
+ effects: setComments.of(comments2)
1439
+ });
1440
+ }
1441
+ }, [
1442
+ view,
1443
+ comments2
1444
+ ]);
1445
+ };
1383
1446
 
1384
- // packages/ui/react-ui-editor/src/extensions/hr.ts
1385
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1386
- var styles4 = EditorView6.baseTheme({
1387
- "& .cm-hr": {
1388
- // TODO(burdon): ???
1389
- // Note that block-level decorations should not have vertical margins,
1390
- borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1391
- }
1392
- });
1393
- var HorizontalRuleWidget = class extends WidgetType2 {
1394
- constructor(_pos) {
1395
- super();
1396
- this._pos = _pos;
1397
- }
1398
- eq(other) {
1399
- return this._pos === other._pos;
1400
- }
1401
- toDOM(view) {
1402
- const el = document.createElement("div");
1403
- el.className = "cm-hr";
1404
- return el;
1405
- }
1406
- };
1407
- var placeholderMatcher = new MatchDecorator({
1408
- regexp: /^---$/g,
1409
- decoration: (match, view, pos) => Decoration4.replace({
1410
- widget: new HorizontalRuleWidget(pos)
1411
- })
1412
- });
1413
- var hr = () => [
1414
- styles4,
1415
- ViewPlugin4.fromClass(class {
1416
- constructor(view) {
1417
- this.rules = placeholderMatcher.createDeco(view);
1418
- }
1419
- update(update2) {
1420
- this.rules = placeholderMatcher.updateDeco(update2, this.rules);
1421
- }
1422
- }, {
1423
- decorations: (instance) => instance.rules,
1424
- // TODO(burdon): Is this really atomic (cursor can move into ---).
1425
- provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1426
- return view.plugin(plugin)?.rules || Decoration4.none;
1427
- })
1428
- })
1429
- ];
1430
-
1431
- // packages/ui/react-ui-editor/src/extensions/image.ts
1432
- import { syntaxTree } from "@codemirror/language";
1433
- import { StateField as StateField3 } from "@codemirror/state";
1434
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
1435
- var image = (options = {}) => {
1436
- return StateField3.define({
1437
- create: (state) => {
1438
- return Decoration5.set(buildDecorations(0, state.doc.length, state));
1439
- },
1440
- update: (value, tr) => {
1441
- if (!tr.docChanged && !tr.selection) {
1442
- return value;
1443
- }
1444
- const cursor = tr.state.selection.main.head;
1445
- const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
1446
- let from = Math.min(cursor, oldCursor);
1447
- let to = Math.max(cursor, oldCursor);
1448
- tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
1449
- from = Math.min(from, fromB);
1450
- to = Math.max(to, toB);
1451
- });
1452
- from = tr.state.doc.lineAt(from).from;
1453
- to = tr.state.doc.lineAt(to).to;
1454
- return value.map(tr.changes).update({
1455
- filterFrom: from,
1456
- filterTo: to,
1457
- filter: () => false,
1458
- add: buildDecorations(from, to, tr.state)
1459
- });
1460
- },
1461
- provide: (field) => EditorView7.decorations.from(field)
1462
- });
1463
- };
1464
- var buildDecorations = (from, to, state) => {
1465
- const decorations = [];
1466
- const cursor = state.selection.main.head;
1467
- syntaxTree(state).iterate({
1468
- enter: (node) => {
1469
- if (node.name === "Image") {
1470
- const urlNode = node.node.getChild("URL");
1471
- if (urlNode) {
1472
- const hide = state.readOnly || cursor < node.from || cursor > node.to;
1473
- const url = state.sliceDoc(urlNode.from, urlNode.to);
1474
- decorations.push(Decoration5.replace({
1475
- block: true,
1476
- widget: new ImageWidget(url)
1477
- }).range(hide ? node.from : node.to, node.to));
1478
- }
1479
- }
1480
- },
1481
- from,
1482
- to
1483
- });
1484
- return decorations;
1485
- };
1486
- var ImageWidget = class extends WidgetType3 {
1487
- constructor(_url) {
1488
- super();
1489
- this._url = _url;
1490
- }
1491
- eq(other) {
1492
- return this._url === other._url;
1493
- }
1494
- toDOM(view) {
1495
- const img = document.createElement("img");
1496
- img.setAttribute("src", this._url);
1497
- img.setAttribute("class", "cm-image");
1498
- img.onload = () => img.classList.add("cm-loaded-image");
1499
- return img;
1500
- }
1501
- };
1502
-
1503
- // packages/ui/react-ui-editor/src/extensions/link.ts
1504
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1505
- import { RangeSetBuilder } from "@codemirror/state";
1506
- import { Decoration as Decoration6, WidgetType as WidgetType4, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1507
- import { tooltipContent } from "@dxos/react-ui-theme";
1508
- var link = (options = {}) => {
1509
- const extensions = [
1510
- ViewPlugin5.fromClass(class {
1511
- constructor(view) {
1512
- this.decorations = buildDecorations2(view, options);
1513
- }
1514
- update(update2) {
1515
- if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1516
- this.decorations = buildDecorations2(update2.view, options);
1517
- }
1518
- }
1519
- }, {
1520
- decorations: (v) => v.decorations
1521
- })
1522
- ];
1523
- if (options.onHover) {
1524
- extensions.push(
1525
- // https://codemirror.net/examples/tooltip
1526
- // https://codemirror.net/docs/ref/#view.hoverTooltip
1527
- // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1528
- hoverTooltip2((view, pos, side) => {
1529
- const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1530
- let link2 = null;
1531
- for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1532
- link2 = node.name === "Link" ? node : null;
1533
- }
1534
- const url = link2 && link2.getChild("URL");
1535
- if (!url || !link2) {
1536
- return null;
1537
- }
1538
- const urlText = view.state.sliceDoc(url.from, url.to);
1539
- return {
1540
- pos: link2.from,
1541
- end: link2.to,
1542
- above: true,
1543
- create: () => {
1544
- const el = document.createElement("div");
1545
- el.className = tooltipContent({}, "pli-2 plb-1");
1546
- options.onHover(el, urlText);
1547
- return {
1548
- dom: el,
1549
- offset: {
1550
- x: 0,
1551
- y: 4
1552
- }
1553
- };
1554
- }
1555
- };
1556
- })
1557
- );
1558
- }
1559
- return extensions;
1560
- };
1561
- var LinkButton = class extends WidgetType4 {
1562
- constructor(_url, _onAttach) {
1563
- super();
1564
- this._url = _url;
1565
- this._onAttach = _onAttach;
1566
- }
1567
- eq(other) {
1568
- return this._url === other._url;
1569
- }
1570
- toDOM(view) {
1571
- const el = document.createElement("span");
1572
- this._onAttach(el, this._url);
1573
- return el;
1574
- }
1575
- };
1576
- var LinkText = class extends WidgetType4 {
1577
- constructor(_text, _url) {
1578
- super();
1579
- this._text = _text;
1580
- this._url = _url;
1581
- }
1582
- eq(other) {
1583
- return this._url === other._url;
1584
- }
1585
- toDOM(view) {
1586
- const link2 = document.createElement("a");
1587
- link2.setAttribute("class", "cm-link");
1588
- link2.textContent = this._text;
1589
- if (this._url) {
1590
- link2.setAttribute("rel", "noreferrer");
1591
- link2.setAttribute("target", "_blank");
1592
- link2.href = this._url;
1593
- } else {
1594
- link2.onclick = () => {
1595
- view.dispatch({
1596
- selection: {
1597
- anchor: view.posAtDOM(link2)
1598
- }
1599
- });
1600
- };
1601
- }
1602
- return link2;
1603
- }
1604
- };
1605
- var buildDecorations2 = (view, options) => {
1606
- const builder = new RangeSetBuilder();
1607
- const { state } = view;
1608
- const cursor = state.selection.main.head;
1609
- for (const { from, to } of view.visibleRanges) {
1610
- syntaxTree2(state).iterate({
1611
- enter: (node) => {
1612
- if (node.name === "Link") {
1613
- const marks2 = node.node.getChildren("LinkMark");
1614
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1615
- const urlNode = node.node.getChild("URL");
1616
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1617
- if (!url) {
1618
- return false;
1619
- }
1620
- if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
1621
- builder.add(node.from, node.to, Decoration6.replace({
1622
- widget: new LinkText(text, options.link ? url : void 0)
1623
- }));
1624
- }
1625
- if (options.onRender) {
1626
- builder.add(node.to, node.to, Decoration6.replace({
1627
- widget: new LinkButton(url, options.onRender)
1628
- }));
1629
- }
1630
- return false;
1631
- }
1632
- },
1633
- from,
1634
- to
1635
- });
1636
- }
1637
- return builder.finish();
1638
- };
1639
-
1640
- // packages/ui/react-ui-editor/src/extensions/listener.ts
1641
- import { EditorView as EditorView8 } from "@codemirror/view";
1642
- var listener = ({ onFocus, onChange }) => {
1643
- const extensions = [];
1644
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1645
- onFocus(focused);
1646
- return null;
1647
- }));
1648
- onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1649
- onChange(update2.state.doc.toString());
1650
- }));
1651
- return extensions;
1447
+ // packages/ui/react-ui-editor/src/extensions/listener.ts
1448
+ import { EditorView as EditorView6 } from "@codemirror/view";
1449
+ var listener = ({ onFocus, onChange }) => {
1450
+ const extensions = [];
1451
+ onFocus && extensions.push(EditorView6.focusChangeEffect.of((_, focused) => {
1452
+ onFocus(focused);
1453
+ return null;
1454
+ }));
1455
+ onChange && extensions.push(EditorView6.updateListener.of((update2) => {
1456
+ onChange(update2.state.doc.toString());
1457
+ }));
1458
+ return extensions;
1652
1459
  };
1653
1460
 
1654
1461
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1655
1462
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1656
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1463
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1657
1464
  import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1658
1465
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1659
1466
  import { languages } from "@codemirror/language-data";
1660
1467
  import { searchKeymap } from "@codemirror/search";
1661
1468
  import { EditorState } from "@codemirror/state";
1662
1469
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1663
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1470
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView7, highlightActiveLine as highlightActiveLine2, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1471
+ import { isNotFalsy as isNotFalsy2 } from "@dxos/util";
1664
1472
 
1665
1473
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1666
1474
  import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
@@ -1757,7 +1565,7 @@ var markdownHighlightStyle = (readonly) => {
1757
1565
  markdownTags.QuoteMark,
1758
1566
  markdownTags.EmphasisMark
1759
1567
  ],
1760
- class: readonly ? "hidden" : mark
1568
+ class: mark
1761
1569
  },
1762
1570
  // E.g., code block language (after ```).
1763
1571
  {
@@ -1765,7 +1573,7 @@ var markdownHighlightStyle = (readonly) => {
1765
1573
  tags.function(tags.variableName),
1766
1574
  tags.labelName
1767
1575
  ],
1768
- class: readonly ? "hidden" : codeMark
1576
+ class: codeMark
1769
1577
  },
1770
1578
  {
1771
1579
  tag: [
@@ -1776,27 +1584,27 @@ var markdownHighlightStyle = (readonly) => {
1776
1584
  // Headings.
1777
1585
  {
1778
1586
  tag: tags.heading1,
1779
- class: heading(1, readonly)
1587
+ class: heading(1)
1780
1588
  },
1781
1589
  {
1782
1590
  tag: tags.heading2,
1783
- class: heading(2, readonly)
1591
+ class: heading(2)
1784
1592
  },
1785
1593
  {
1786
1594
  tag: tags.heading3,
1787
- class: heading(3, readonly)
1595
+ class: heading(3)
1788
1596
  },
1789
1597
  {
1790
1598
  tag: tags.heading4,
1791
- class: heading(4, readonly)
1599
+ class: heading(4)
1792
1600
  },
1793
1601
  {
1794
1602
  tag: tags.heading5,
1795
- class: heading(5, readonly)
1603
+ class: heading(5)
1796
1604
  },
1797
1605
  {
1798
1606
  tag: tags.heading6,
1799
- class: heading(6, readonly)
1607
+ class: heading(6)
1800
1608
  },
1801
1609
  // Emphasis.
1802
1610
  {
@@ -1822,7 +1630,7 @@ var markdownHighlightStyle = (readonly) => {
1822
1630
  // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1823
1631
  // when a language is specified. In this case, the syntax highlighting extensions will colorize
1824
1632
  // the code, but all other CSS properties will be inherited.
1825
- // IMPORTANT: Therefore, the fenced code block will use the base editor font.
1633
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font unless changed by an extension.
1826
1634
  {
1827
1635
  tag: [
1828
1636
  markdownTags.CodeText,
@@ -1851,19 +1659,19 @@ var markdownHighlightStyle = (readonly) => {
1851
1659
  };
1852
1660
 
1853
1661
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1854
- var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1662
+ var createMarkdownExtensions = ({ themeMode, placeholder: _placeholder, lineWrapping = true } = {}) => {
1855
1663
  return [
1664
+ lineWrapping && EditorView7.lineWrapping,
1856
1665
  EditorState.allowMultipleSelections.of(true),
1857
1666
  EditorState.tabSize.of(2),
1858
- EditorView9.lineWrapping,
1859
- customKeymap(),
1667
+ // https://github.com/codemirror/basic-setup
1860
1668
  bracketMatching2(),
1861
1669
  closeBrackets2(),
1862
1670
  crosshairCursor(),
1863
1671
  dropCursor(),
1864
1672
  drawSelection2(),
1865
- highlightActiveLine(),
1866
- history(),
1673
+ highlightActiveLine2(),
1674
+ history2(),
1867
1675
  indentOnInput(),
1868
1676
  _placeholder && placeholder2(_placeholder),
1869
1677
  // Main extension.
@@ -1887,66 +1695,610 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1887
1695
  // https://github.com/codemirror/theme-one-dark
1888
1696
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1889
1697
  // Custom styles.
1890
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1891
- ].filter(Boolean);
1892
- };
1893
- var customKeymap = () => keymap4.of([
1894
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1895
- indentWithTab,
1896
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1897
- ...standardKeymap,
1898
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1899
- ...historyKeymap,
1900
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1901
- ...closeBracketsKeymap,
1902
- // https://codemirror.net/docs/ref/#search.searchKeymap
1903
- ...searchKeymap
1904
- ]);
1905
-
1906
- // packages/ui/react-ui-editor/src/extensions/mention.ts
1907
- import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1908
- var mention = ({ onSearch }) => {
1909
- return autocompletion2({
1910
- closeOnBlur: false,
1911
- override: [
1912
- (context) => {
1913
- const match = context.matchBefore(/@(\w+)?/);
1914
- if (!match || match.from === match.to && !context.explicit) {
1915
- return null;
1916
- }
1917
- console.log(match);
1918
- return {
1919
- from: match.from,
1920
- options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1921
- label: `@${value}`
1922
- }))
1923
- };
1924
- }
1925
- ]
1926
- });
1698
+ syntaxHighlighting2(markdownHighlightStyle()),
1699
+ keymap4.of([
1700
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1701
+ indentWithTab,
1702
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1703
+ ...closeBracketsKeymap,
1704
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
1705
+ ...historyKeymap,
1706
+ // https://codemirror.net/docs/ref/#search.searchKeymap
1707
+ ...searchKeymap,
1708
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
1709
+ ...standardKeymap
1710
+ ])
1711
+ ].filter(isNotFalsy2);
1927
1712
  };
1928
1713
 
1929
- // packages/ui/react-ui-editor/src/extensions/table.ts
1930
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1931
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1932
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
1933
- var table = (options = {}) => {
1934
- return StateField4.define({
1935
- create: (state) => update(state, options),
1936
- update: (_, tr) => update(tr.state, options),
1937
- provide: (field) => EditorView10.decorations.from(field)
1938
- });
1714
+ // packages/ui/react-ui-editor/src/extensions/markdown/code.ts
1715
+ import { syntaxTree } from "@codemirror/language";
1716
+ import { RangeSetBuilder } from "@codemirror/state";
1717
+ import { ViewPlugin as ViewPlugin3, EditorView as EditorView8, Decoration as Decoration3, WidgetType as WidgetType2 } from "@codemirror/view";
1718
+ import { mx as mx2 } from "@dxos/react-ui-theme";
1719
+ var styles3 = EditorView8.baseTheme({
1720
+ "& .cm-code": {
1721
+ fontFamily: getToken("fontFamily.mono", []).join(",")
1722
+ },
1723
+ "& .cm-codeblock": {
1724
+ width: "100%",
1725
+ paddingInline: "1rem !important",
1726
+ position: "relative",
1727
+ "&::after": {
1728
+ content: '""',
1729
+ position: "absolute",
1730
+ inset: 0
1731
+ }
1732
+ },
1733
+ "&light .cm-codeblock": {
1734
+ "&::after": {
1735
+ background: getToken("extend.semanticColors.input.light"),
1736
+ mixBlendMode: "darken"
1737
+ }
1738
+ },
1739
+ "&dark .cm-codeblock": {
1740
+ "&::after": {
1741
+ background: getToken("extend.semanticColors.input.dark"),
1742
+ mixBlendMode: "lighten"
1743
+ }
1744
+ },
1745
+ "& .cm-codeblock-first": {
1746
+ display: "inline-block",
1747
+ "&::after": {
1748
+ borderTopLeftRadius: ".5rem",
1749
+ borderTopRightRadius: ".5rem"
1750
+ }
1751
+ },
1752
+ "& .cm-codeblock-last": {
1753
+ display: "inline-block",
1754
+ "&::after": {
1755
+ borderBottomLeftRadius: ".5rem",
1756
+ borderBottomRightRadius: ".5rem"
1757
+ }
1758
+ }
1759
+ });
1760
+ var getLineRange = (lines, from, to) => {
1761
+ const start = lines.findIndex((line) => line.from >= from);
1762
+ const end = lines.findIndex((line) => line.to >= to);
1763
+ return [
1764
+ start,
1765
+ end
1766
+ ];
1939
1767
  };
1940
- var update = (state, options) => {
1941
- const builder = new RangeSetBuilder2();
1942
- const cursor = state.selection.main.head;
1943
- const tables = [];
1944
- const getTable = () => tables[tables.length - 1];
1945
- const getRow = () => {
1946
- const table2 = getTable();
1947
- return table2.rows?.[table2.rows.length - 1];
1768
+ var LineWidget = class extends WidgetType2 {
1769
+ constructor(_className) {
1770
+ super();
1771
+ this._className = _className;
1772
+ }
1773
+ toDOM() {
1774
+ const el = document.createElement("span");
1775
+ el.innerHTML = "&nbsp;";
1776
+ el.className = mx2("cm-code cm-codeblock", this._className);
1777
+ return el;
1778
+ }
1779
+ };
1780
+ var top = new LineWidget("cm-codeblock-first");
1781
+ var bottom = new LineWidget("cm-codeblock-last");
1782
+ var buildDecorations = (view) => {
1783
+ const builder = new RangeSetBuilder();
1784
+ const { state } = view;
1785
+ const blocks = view.viewportLineBlocks;
1786
+ const cursor = state.selection.main.head;
1787
+ for (const { from, to } of view.visibleRanges) {
1788
+ syntaxTree(state).iterate({
1789
+ enter: (node) => {
1790
+ if (node.name === "FencedCode") {
1791
+ const edit = !view.state.readOnly && cursor >= node.from && cursor <= node.to;
1792
+ const range = getLineRange(blocks, node.from, node.to);
1793
+ for (let i = range[0]; i <= range[1]; i++) {
1794
+ const block = blocks[i];
1795
+ if (!edit && (i === range[0] || i === range[1])) {
1796
+ builder.add(block.from, block.to, Decoration3.replace({
1797
+ widget: i === range[0] ? top : bottom
1798
+ }));
1799
+ } else {
1800
+ builder.add(block.from, block.from, Decoration3.line({
1801
+ class: mx2("cm-code cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1802
+ }));
1803
+ }
1804
+ }
1805
+ }
1806
+ },
1807
+ from,
1808
+ to
1809
+ });
1810
+ }
1811
+ return builder.finish();
1812
+ };
1813
+ var code2 = () => {
1814
+ return [
1815
+ ViewPlugin3.fromClass(class {
1816
+ constructor(view) {
1817
+ this.decorations = buildDecorations(view);
1818
+ }
1819
+ update(update2) {
1820
+ this.decorations = buildDecorations(update2.view);
1821
+ }
1822
+ }, {
1823
+ decorations: (value) => value.decorations
1824
+ }),
1825
+ styles3
1826
+ ];
1827
+ };
1828
+
1829
+ // packages/ui/react-ui-editor/src/extensions/markdown/formatting.ts
1830
+ import { snippet } from "@codemirror/autocomplete";
1831
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1832
+ import { RangeSetBuilder as RangeSetBuilder2 } from "@codemirror/state";
1833
+ import { Decoration as Decoration4, keymap as keymap5, ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1834
+ var setHeading = (level) => (view) => {
1835
+ const { selection: { ranges }, doc } = view.state;
1836
+ const changes = [];
1837
+ for (const range of ranges) {
1838
+ const { number } = doc.lineAt(range.anchor);
1839
+ const { from, to } = doc.line(number);
1840
+ const line = doc.sliceString(from, to);
1841
+ const [_, marks, spaces] = line.match(/(#+)(\s+)/) ?? [];
1842
+ const current = marks?.length ?? 0;
1843
+ if (level !== current) {
1844
+ changes.push({
1845
+ from,
1846
+ to: from + current + (spaces?.length ?? 0),
1847
+ insert: "#".repeat(level) + (level > 0 ? " " : "")
1848
+ });
1849
+ }
1850
+ }
1851
+ if (changes.length) {
1852
+ view.dispatch({
1853
+ changes
1854
+ });
1855
+ }
1856
+ return true;
1857
+ };
1858
+ var toggleStyle = (mark2) => (view) => {
1859
+ const { ranges } = view.state.selection;
1860
+ for (const range of ranges) {
1861
+ if (range.from === range.to) {
1862
+ return false;
1863
+ }
1864
+ view.dispatch({
1865
+ changes: [
1866
+ {
1867
+ from: range.from,
1868
+ insert: mark2
1869
+ },
1870
+ {
1871
+ from: range.to,
1872
+ insert: mark2
1873
+ }
1874
+ ]
1875
+ });
1876
+ }
1877
+ return true;
1878
+ };
1879
+ var snippets = {
1880
+ codeblock: snippet([
1881
+ "```#{lang}",
1882
+ " #{}",
1883
+ "```"
1884
+ ].join("\n")),
1885
+ table: snippet([
1886
+ "| #{col1} | #{col2} |",
1887
+ "| ---- | ---- |",
1888
+ "| #{val1} | #{val2} |",
1889
+ "| #{val3} | #{val4} |"
1890
+ ].join("\n"))
1891
+ };
1892
+ var insertCodeblock = (view) => {
1893
+ const { selection: { main }, doc } = view.state;
1894
+ const { number } = doc.lineAt(main.anchor);
1895
+ const { from } = doc.line(number);
1896
+ snippets.codeblock(view, null, from, from);
1897
+ };
1898
+ var insertTable = (view) => {
1899
+ const { selection: { main }, doc } = view.state;
1900
+ const { number } = doc.lineAt(main.anchor);
1901
+ const { from } = doc.line(number);
1902
+ snippets.table(view, null, from, from);
1903
+ };
1904
+ var toggleBold = toggleStyle("**");
1905
+ var toggleItalic = toggleStyle("_");
1906
+ var toggleStrikethrough = toggleStyle("~~");
1907
+ var toggleList = (view) => {
1908
+ };
1909
+ var formatting = (options = {}) => {
1910
+ return [
1911
+ keymap5.of([
1912
+ {
1913
+ key: "meta-b",
1914
+ run: toggleBold
1915
+ }
1916
+ ]),
1917
+ styling()
1918
+ ];
1919
+ };
1920
+ var styling = () => {
1921
+ const buildDecorations7 = (view) => {
1922
+ const builder = new RangeSetBuilder2();
1923
+ const { state } = view;
1924
+ const cursor = state.selection.main.head;
1925
+ const replace = (node, marks) => {
1926
+ if (cursor <= node.from || cursor >= node.to) {
1927
+ for (const mark2 of marks) {
1928
+ builder.add(mark2.from, mark2.to, Decoration4.replace({}));
1929
+ }
1930
+ }
1931
+ };
1932
+ for (const { from, to } of view.visibleRanges) {
1933
+ syntaxTree2(state).iterate({
1934
+ enter: (node) => {
1935
+ switch (node.name) {
1936
+ case "Emphasis":
1937
+ case "StrongEmphasis": {
1938
+ const marks = node.node.getChildren("EmphasisMark");
1939
+ replace(node, marks);
1940
+ break;
1941
+ }
1942
+ case "Strikethrough": {
1943
+ const marks = node.node.getChildren("StrikethroughMark");
1944
+ replace(node, marks);
1945
+ break;
1946
+ }
1947
+ }
1948
+ },
1949
+ from,
1950
+ to
1951
+ });
1952
+ }
1953
+ return builder.finish();
1954
+ };
1955
+ return [
1956
+ ViewPlugin4.fromClass(class {
1957
+ constructor(view) {
1958
+ this.decorations = buildDecorations7(view);
1959
+ }
1960
+ update(update2) {
1961
+ this.decorations = buildDecorations7(update2.view);
1962
+ }
1963
+ }, {
1964
+ decorations: (value) => value.decorations
1965
+ })
1966
+ ];
1967
+ };
1968
+
1969
+ // packages/ui/react-ui-editor/src/extensions/markdown/heading.ts
1970
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1971
+ import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
1972
+ import { Decoration as Decoration5, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1973
+ var buildDecorations2 = (view) => {
1974
+ const builder = new RangeSetBuilder3();
1975
+ const { state } = view;
1976
+ const cursor = state.selection.main.head;
1977
+ for (const { from, to } of view.visibleRanges) {
1978
+ syntaxTree3(state).iterate({
1979
+ enter: (node) => {
1980
+ switch (node.name) {
1981
+ case "ATXHeading1":
1982
+ case "ATXHeading2":
1983
+ case "ATXHeading3":
1984
+ case "ATXHeading4":
1985
+ case "ATXHeading5":
1986
+ case "ATXHeading6": {
1987
+ const mark2 = node.node.getChild("HeaderMark");
1988
+ if (mark2) {
1989
+ if (!view.hasFocus || view.state.readOnly || cursor < node.from || cursor > node.to) {
1990
+ builder.add(mark2.from, mark2.to + 1, Decoration5.replace({}));
1991
+ }
1992
+ }
1993
+ }
1994
+ }
1995
+ },
1996
+ from,
1997
+ to
1998
+ });
1999
+ }
2000
+ return builder.finish();
2001
+ };
2002
+ var heading2 = () => {
2003
+ return [
2004
+ ViewPlugin5.fromClass(class {
2005
+ constructor(view) {
2006
+ this.decorations = buildDecorations2(view);
2007
+ }
2008
+ update(update2) {
2009
+ this.decorations = buildDecorations2(update2.view);
2010
+ }
2011
+ }, {
2012
+ decorations: (value) => value.decorations
2013
+ })
2014
+ ];
2015
+ };
2016
+
2017
+ // packages/ui/react-ui-editor/src/extensions/markdown/hr.ts
2018
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2019
+ import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
2020
+ import { Decoration as Decoration6, EditorView as EditorView9, ViewPlugin as ViewPlugin6, WidgetType as WidgetType3 } from "@codemirror/view";
2021
+ var styles4 = EditorView9.baseTheme({
2022
+ "& .cm-hr": {
2023
+ // Note that block-level decorations should not have vertical margins,
2024
+ borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
2025
+ }
2026
+ });
2027
+ var HorizontalRuleWidget = class extends WidgetType3 {
2028
+ toDOM() {
2029
+ const el = document.createElement("div");
2030
+ el.className = "cm-hr";
2031
+ return el;
2032
+ }
2033
+ };
2034
+ var decoration = Decoration6.replace({
2035
+ widget: new HorizontalRuleWidget()
2036
+ });
2037
+ var buildDecorations3 = (view) => {
2038
+ const builder = new RangeSetBuilder4();
2039
+ const { state } = view;
2040
+ const cursor = state.selection.main.head;
2041
+ for (const { from, to } of view.visibleRanges) {
2042
+ syntaxTree4(state).iterate({
2043
+ enter: (node) => {
2044
+ if (node.name === "HorizontalRule") {
2045
+ if (cursor <= node.from || cursor >= node.to) {
2046
+ builder.add(node.from, node.to, decoration);
2047
+ }
2048
+ }
2049
+ },
2050
+ from,
2051
+ to
2052
+ });
2053
+ }
2054
+ return builder.finish();
2055
+ };
2056
+ var hr = () => {
2057
+ return [
2058
+ ViewPlugin6.fromClass(class {
2059
+ constructor(view) {
2060
+ this.decorations = buildDecorations3(view);
2061
+ }
2062
+ update(update2) {
2063
+ this.decorations = buildDecorations3(update2.view);
2064
+ }
2065
+ }, {
2066
+ decorations: (value) => value.decorations
2067
+ }),
2068
+ styles4
2069
+ ];
2070
+ };
2071
+
2072
+ // packages/ui/react-ui-editor/src/extensions/markdown/image.ts
2073
+ import { syntaxTree as syntaxTree5 } from "@codemirror/language";
2074
+ import { StateField as StateField3 } from "@codemirror/state";
2075
+ import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType4 } from "@codemirror/view";
2076
+ var image = (options = {}) => {
2077
+ return StateField3.define({
2078
+ create: (state) => {
2079
+ return Decoration7.set(buildDecorations4(0, state.doc.length, state));
2080
+ },
2081
+ update: (value, tr) => {
2082
+ if (!tr.docChanged && !tr.selection) {
2083
+ return value;
2084
+ }
2085
+ const cursor = tr.state.selection.main.head;
2086
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
2087
+ let from = Math.min(cursor, oldCursor);
2088
+ let to = Math.max(cursor, oldCursor);
2089
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
2090
+ from = Math.min(from, fromB);
2091
+ to = Math.max(to, toB);
2092
+ });
2093
+ from = tr.state.doc.lineAt(from).from;
2094
+ to = tr.state.doc.lineAt(to).to;
2095
+ return value.map(tr.changes).update({
2096
+ filterFrom: from,
2097
+ filterTo: to,
2098
+ filter: () => false,
2099
+ add: buildDecorations4(from, to, tr.state)
2100
+ });
2101
+ },
2102
+ provide: (field) => EditorView10.decorations.from(field)
2103
+ });
2104
+ };
2105
+ var buildDecorations4 = (from, to, state) => {
2106
+ const decorations = [];
2107
+ const cursor = state.selection.main.head;
2108
+ syntaxTree5(state).iterate({
2109
+ enter: (node) => {
2110
+ if (node.name === "Image") {
2111
+ const urlNode = node.node.getChild("URL");
2112
+ if (urlNode) {
2113
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
2114
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
2115
+ decorations.push(Decoration7.replace({
2116
+ block: true,
2117
+ widget: new ImageWidget(url)
2118
+ }).range(hide ? node.from : node.to, node.to));
2119
+ }
2120
+ }
2121
+ },
2122
+ from,
2123
+ to
2124
+ });
2125
+ return decorations;
2126
+ };
2127
+ var ImageWidget = class extends WidgetType4 {
2128
+ constructor(_url) {
2129
+ super();
2130
+ this._url = _url;
2131
+ }
2132
+ eq(other) {
2133
+ return this._url === other._url;
2134
+ }
2135
+ toDOM(view) {
2136
+ const img = document.createElement("img");
2137
+ img.setAttribute("src", this._url);
2138
+ img.setAttribute("class", "cm-image");
2139
+ img.onload = () => img.classList.add("cm-loaded-image");
2140
+ return img;
2141
+ }
2142
+ };
2143
+
2144
+ // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
2145
+ import { syntaxTree as syntaxTree6 } from "@codemirror/language";
2146
+ import { RangeSetBuilder as RangeSetBuilder5 } from "@codemirror/state";
2147
+ import { hoverTooltip as hoverTooltip2, Decoration as Decoration8, ViewPlugin as ViewPlugin7, WidgetType as WidgetType5 } from "@codemirror/view";
2148
+ import { tooltipContent } from "@dxos/react-ui-theme";
2149
+ var link = (options = {}) => {
2150
+ const extensions = [
2151
+ ViewPlugin7.fromClass(class {
2152
+ constructor(view) {
2153
+ this.decorations = buildDecorations5(view, options);
2154
+ }
2155
+ update(update2) {
2156
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2157
+ this.decorations = buildDecorations5(update2.view, options);
2158
+ }
2159
+ }
2160
+ }, {
2161
+ decorations: (value) => value.decorations
2162
+ })
2163
+ ];
2164
+ if (options.onHover) {
2165
+ extensions.push(
2166
+ // https://codemirror.net/examples/tooltip
2167
+ // https://codemirror.net/docs/ref/#view.hoverTooltip
2168
+ // https://github.com/codemirror/view/blob/main/src/tooltip.ts
2169
+ hoverTooltip2((view, pos, side) => {
2170
+ const syntax = syntaxTree6(view.state).resolveInner(pos, side);
2171
+ let link2 = null;
2172
+ for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
2173
+ link2 = node.name === "Link" ? node : null;
2174
+ }
2175
+ const url = link2 && link2.getChild("URL");
2176
+ if (!url || !link2) {
2177
+ return null;
2178
+ }
2179
+ const urlText = view.state.sliceDoc(url.from, url.to);
2180
+ return {
2181
+ pos: link2.from,
2182
+ end: link2.to,
2183
+ above: true,
2184
+ create: () => {
2185
+ const el = document.createElement("div");
2186
+ el.className = tooltipContent({}, "pli-2 plb-1");
2187
+ options.onHover(el, urlText);
2188
+ return {
2189
+ dom: el,
2190
+ offset: {
2191
+ x: 0,
2192
+ y: 4
2193
+ }
2194
+ };
2195
+ }
2196
+ };
2197
+ })
2198
+ );
2199
+ }
2200
+ return extensions;
2201
+ };
2202
+ var LinkButton = class extends WidgetType5 {
2203
+ constructor(_url, _onAttach) {
2204
+ super();
2205
+ this._url = _url;
2206
+ this._onAttach = _onAttach;
2207
+ }
2208
+ eq(other) {
2209
+ return this._url === other._url;
2210
+ }
2211
+ toDOM(view) {
2212
+ const el = document.createElement("span");
2213
+ this._onAttach(el, this._url);
2214
+ return el;
2215
+ }
2216
+ };
2217
+ var LinkText = class extends WidgetType5 {
2218
+ constructor(_text, _url) {
2219
+ super();
2220
+ this._text = _text;
2221
+ this._url = _url;
2222
+ }
2223
+ eq(other) {
2224
+ return this._url === other._url;
2225
+ }
2226
+ toDOM(view) {
2227
+ const link2 = document.createElement("a");
2228
+ link2.setAttribute("class", "cm-link");
2229
+ link2.textContent = this._text;
2230
+ if (this._url) {
2231
+ link2.setAttribute("rel", "noreferrer");
2232
+ link2.setAttribute("target", "_blank");
2233
+ link2.href = this._url;
2234
+ } else {
2235
+ link2.onclick = () => {
2236
+ view.dispatch({
2237
+ selection: {
2238
+ anchor: view.posAtDOM(link2)
2239
+ }
2240
+ });
2241
+ };
2242
+ }
2243
+ return link2;
2244
+ }
2245
+ };
2246
+ var buildDecorations5 = (view, options) => {
2247
+ const builder = new RangeSetBuilder5();
2248
+ const { state } = view;
2249
+ const cursor = state.selection.main.head;
2250
+ for (const { from, to } of view.visibleRanges) {
2251
+ syntaxTree6(state).iterate({
2252
+ enter: (node) => {
2253
+ if (node.name === "Link") {
2254
+ const marks = node.node.getChildren("LinkMark");
2255
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
2256
+ const urlNode = node.node.getChild("URL");
2257
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
2258
+ if (!url) {
2259
+ return false;
2260
+ }
2261
+ if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
2262
+ builder.add(node.from, node.to, Decoration8.replace({
2263
+ widget: new LinkText(text, options.link ? url : void 0)
2264
+ }));
2265
+ }
2266
+ if (options.onRender) {
2267
+ builder.add(node.to, node.to, Decoration8.replace({
2268
+ widget: new LinkButton(url, options.onRender)
2269
+ }));
2270
+ }
2271
+ return false;
2272
+ }
2273
+ },
2274
+ from,
2275
+ to
2276
+ });
2277
+ }
2278
+ return builder.finish();
2279
+ };
2280
+
2281
+ // packages/ui/react-ui-editor/src/extensions/markdown/table.ts
2282
+ import { syntaxTree as syntaxTree7 } from "@codemirror/language";
2283
+ import { RangeSetBuilder as RangeSetBuilder6, StateField as StateField4 } from "@codemirror/state";
2284
+ import { Decoration as Decoration9, EditorView as EditorView11, WidgetType as WidgetType6 } from "@codemirror/view";
2285
+ var table = (options = {}) => {
2286
+ return StateField4.define({
2287
+ create: (state) => update(state, options),
2288
+ update: (_, tr) => update(tr.state, options),
2289
+ provide: (field) => EditorView11.decorations.from(field)
2290
+ });
2291
+ };
2292
+ var update = (state, options) => {
2293
+ const builder = new RangeSetBuilder6();
2294
+ const cursor = state.selection.main.head;
2295
+ const tables = [];
2296
+ const getTable = () => tables[tables.length - 1];
2297
+ const getRow = () => {
2298
+ const table2 = getTable();
2299
+ return table2.rows?.[table2.rows.length - 1];
1948
2300
  };
1949
- syntaxTree3(state).iterate({
2301
+ syntaxTree7(state).iterate({
1950
2302
  enter: (node) => {
1951
2303
  switch (node.name) {
1952
2304
  case "Table": {
@@ -1978,16 +2330,16 @@ var update = (state, options) => {
1978
2330
  });
1979
2331
  tables.forEach((table2) => {
1980
2332
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1981
- hide && builder.add(table2.from, table2.to, Decoration7.replace({
2333
+ hide && builder.add(table2.from, table2.to, Decoration9.replace({
1982
2334
  widget: new TableWidget(table2)
1983
2335
  }));
1984
- builder.add(table2.from, table2.to, Decoration7.mark({
2336
+ builder.add(table2.from, table2.to, Decoration9.mark({
1985
2337
  class: "cm-table"
1986
2338
  }));
1987
2339
  });
1988
2340
  return builder.finish();
1989
2341
  };
1990
- var TableWidget = class extends WidgetType5 {
2342
+ var TableWidget = class extends WidgetType6 {
1991
2343
  constructor(_table) {
1992
2344
  super();
1993
2345
  this._table = _table;
@@ -2024,92 +2376,139 @@ var TableWidget = class extends WidgetType5 {
2024
2376
  }
2025
2377
  };
2026
2378
 
2027
- // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2028
- import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2029
- var styles5 = EditorView11.baseTheme({
2030
- "& .cm-task-item": {
2031
- paddingLeft: "4px",
2032
- paddingRight: "8px"
2379
+ // packages/ui/react-ui-editor/src/extensions/markdown/tasklist.ts
2380
+ import { syntaxTree as syntaxTree8 } from "@codemirror/language";
2381
+ import { RangeSetBuilder as RangeSetBuilder7 } from "@codemirror/state";
2382
+ import { EditorView as EditorView12, Decoration as Decoration10, WidgetType as WidgetType7, ViewPlugin as ViewPlugin8 } from "@codemirror/view";
2383
+ var styles5 = EditorView12.baseTheme({
2384
+ "& .cm-task": {
2385
+ color: getToken("extend.colors.blue.500")
2386
+ },
2387
+ "& .cm-task-checkbox": {
2388
+ marginLeft: "4px",
2389
+ marginRight: "4px"
2033
2390
  }
2034
2391
  });
2035
- var CheckboxWidget = class extends WidgetType6 {
2036
- constructor(_checked, _indent, _onCheck) {
2392
+ var CheckboxWidget = class extends WidgetType7 {
2393
+ constructor(_checked) {
2037
2394
  super();
2038
2395
  this._checked = _checked;
2039
- this._indent = _indent;
2040
- this._onCheck = _onCheck;
2041
2396
  }
2042
2397
  eq(other) {
2043
- return this._checked === other._checked && this._indent === other._indent;
2398
+ return this._checked === other._checked;
2044
2399
  }
2045
2400
  toDOM(view) {
2046
- const wrap = document.createElement("span");
2047
- wrap.className = "cm-task-item";
2048
- wrap.setAttribute("aria-hidden", "true");
2049
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
2050
- const input = wrap.appendChild(document.createElement("input"));
2401
+ const input = document.createElement("input");
2402
+ input.className = "cm-task-checkbox";
2051
2403
  input.type = "checkbox";
2052
2404
  input.checked = this._checked;
2053
- if (!this._onCheck) {
2405
+ if (view.state.readOnly) {
2054
2406
  input.setAttribute("disabled", "true");
2055
- }
2056
- if (this._onCheck) {
2057
- input.onchange = (event) => {
2058
- this._onCheck?.(event.target.checked);
2059
- return true;
2407
+ } else {
2408
+ input.onmousedown = (event) => {
2409
+ const pos = view.posAtDOM(input);
2410
+ const text = view.state.sliceDoc(pos, pos + 3);
2411
+ if (text === (this._checked ? "[x]" : "[ ]")) {
2412
+ view.dispatch({
2413
+ changes: {
2414
+ from: pos + 1,
2415
+ to: pos + 2,
2416
+ insert: this._checked ? " " : "x"
2417
+ }
2418
+ });
2419
+ event.preventDefault();
2420
+ }
2060
2421
  };
2061
2422
  }
2062
- return wrap;
2423
+ return input;
2063
2424
  }
2064
2425
  ignoreEvent() {
2065
2426
  return false;
2066
2427
  }
2067
2428
  };
2429
+ var checkedDecoration = Decoration10.replace({
2430
+ widget: new CheckboxWidget(true)
2431
+ });
2432
+ var uncheckedDecoration = Decoration10.replace({
2433
+ widget: new CheckboxWidget(false)
2434
+ });
2435
+ var buildDecorations6 = (view) => {
2436
+ const builder = new RangeSetBuilder7();
2437
+ const { state } = view;
2438
+ const cursor = state.selection.main.head;
2439
+ for (const { from, to } of view.visibleRanges) {
2440
+ syntaxTree8(state).iterate({
2441
+ enter: (node) => {
2442
+ if (node.name === "TaskMarker") {
2443
+ if (cursor < node.from || cursor > node.to) {
2444
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2445
+ builder.add(node.from - 2, node.from - 1, Decoration10.mark({
2446
+ class: "cm-task"
2447
+ }));
2448
+ builder.add(node.from, node.to, checked ? checkedDecoration : uncheckedDecoration);
2449
+ }
2450
+ }
2451
+ },
2452
+ from,
2453
+ to
2454
+ });
2455
+ }
2456
+ return builder.finish();
2457
+ };
2068
2458
  var tasklist = (options = {}) => {
2069
- const taskMatcher = new MatchDecorator2({
2070
- regexp: /^(\s*)- \[([ xX])\]\s/g,
2071
- decoration: (match, view, pos) => {
2072
- const indent = Math.floor(match[1].length / 2);
2073
- const checked = match[2] === "x" || match[2] === "X";
2074
- return Decoration8.replace({
2075
- widget: new CheckboxWidget(checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2076
- const idx = pos + match[0].indexOf("[") + 1;
2077
- view.dispatch({
2078
- changes: {
2079
- from: idx,
2080
- to: idx + 1,
2081
- insert: checked2 ? "x" : " "
2082
- },
2083
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
2084
- selection: {
2085
- anchor: idx + 3
2086
- }
2087
- });
2088
- })
2089
- });
2090
- }
2091
- });
2092
- const tasks = ViewPlugin6.fromClass(class {
2093
- constructor(view) {
2094
- this.tasks = taskMatcher.createDeco(view);
2095
- }
2096
- update(update2) {
2097
- this.tasks = taskMatcher.updateDeco(update2, this.tasks);
2098
- }
2099
- }, {
2100
- decorations: (value) => value.tasks,
2101
- provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2102
- return view.plugin(plugin)?.tasks || Decoration8.none;
2103
- })
2104
- });
2105
2459
  return [
2106
- tasks,
2460
+ ViewPlugin8.fromClass(class {
2461
+ constructor(view) {
2462
+ this.decorations = buildDecorations6(view);
2463
+ }
2464
+ update(update2) {
2465
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2466
+ this.decorations = buildDecorations6(update2.view);
2467
+ }
2468
+ }
2469
+ }, {
2470
+ decorations: (value) => value.decorations
2471
+ }),
2107
2472
  styles5
2108
2473
  ];
2109
2474
  };
2110
2475
 
2476
+ // packages/ui/react-ui-editor/src/extensions/mention.ts
2477
+ import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
2478
+ var mention = ({ onSearch }) => {
2479
+ return autocompletion2({
2480
+ // TODO(burdon): Not working.
2481
+ activateOnTyping: true,
2482
+ // activateOnTypingDelay: 100,
2483
+ // selectOnOpen: true,
2484
+ closeOnBlur: false,
2485
+ // defaultKeymap: false,
2486
+ icons: false,
2487
+ override: [
2488
+ (context) => {
2489
+ console.log(context);
2490
+ const match = context.matchBefore(/@(\w+)?/);
2491
+ if (!match || match.from === match.to && !context.explicit) {
2492
+ return null;
2493
+ }
2494
+ return {
2495
+ from: match.from,
2496
+ options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
2497
+ label: `@${value}`
2498
+ }))
2499
+ };
2500
+ }
2501
+ ]
2502
+ });
2503
+ };
2504
+
2505
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
2506
+ var outliner = (options = {}) => {
2507
+ return [];
2508
+ };
2509
+
2111
2510
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
2112
- import { keymap as keymap5 } from "@codemirror/view";
2511
+ import { keymap as keymap6 } from "@codemirror/view";
2113
2512
  var defaultItems = [
2114
2513
  "hello world!",
2115
2514
  "this is a test.",
@@ -2119,7 +2518,7 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2119
2518
  let t;
2120
2519
  let idx = 0;
2121
2520
  return [
2122
- keymap5.of([
2521
+ keymap6.of([
2123
2522
  {
2124
2523
  // Reset.
2125
2524
  key: "alt-meta-'",
@@ -2165,27 +2564,6 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2165
2564
  ];
2166
2565
  };
2167
2566
 
2168
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2169
- import { yCollab } from "y-codemirror.next";
2170
-
2171
- // packages/ui/react-ui-editor/src/extensions/yjs/cursor.ts
2172
- import * as Y from "yjs";
2173
- import { arrayToString, stringToArray } from "@dxos/util";
2174
- var cursorConverter2 = (text) => ({
2175
- toCursor: (index, assoc) => {
2176
- return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
2177
- },
2178
- fromCursor: (cursor) => {
2179
- return Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc)?.index ?? 0;
2180
- }
2181
- });
2182
-
2183
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2184
- var yjs = (content, awareness2) => [
2185
- Cursor.converter.of(cursorConverter2(content)),
2186
- yCollab(content, awareness2)
2187
- ];
2188
-
2189
2567
  // packages/ui/react-ui-editor/src/themes/default.ts
2190
2568
  import get3 from "lodash.get";
2191
2569
  var defaultTheme = {
@@ -2217,8 +2595,9 @@ var defaultTheme = {
2217
2595
  outline: "none"
2218
2596
  },
2219
2597
  ".cm-scroller": {
2220
- overflow: "visible",
2221
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2598
+ // overflow: 'visible',
2599
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(","),
2600
+ lineHeight: 1.4
2222
2601
  },
2223
2602
  ".cm-content": {
2224
2603
  padding: 0,
@@ -2226,11 +2605,11 @@ var defaultTheme = {
2226
2605
  fontSize: "16px"
2227
2606
  },
2228
2607
  "&light .cm-content": {
2229
- color: get3(tokens, "extend.colors.neutral.900", "black"),
2608
+ color: get3(tokens, "extend.semanticColors.base.fg.light", "black"),
2230
2609
  caretColor: "black"
2231
2610
  },
2232
2611
  "&dark .cm-content": {
2233
- color: get3(tokens, "extend.colors.neutral.100", "white"),
2612
+ color: get3(tokens, "extend.semanticColors.base.fg.dark", "white"),
2234
2613
  caretColor: "white"
2235
2614
  },
2236
2615
  //
@@ -2242,8 +2621,11 @@ var defaultTheme = {
2242
2621
  "&dark .cm-cursor, &dark .cm-dropCursor": {
2243
2622
  borderLeft: "2px solid white"
2244
2623
  },
2245
- ".cm-placeholder": {
2246
- fontWeight: 100
2624
+ "&light .cm-placeholder": {
2625
+ color: get3(tokens, "extend.semanticColors.description.light", "rgba(0,0,0,.2)")
2626
+ },
2627
+ "&dark .cm-placeholder": {
2628
+ color: get3(tokens, "extend.semanticColors.description.dark", "rgba(255,255,255,.2)")
2247
2629
  },
2248
2630
  //
2249
2631
  // line
@@ -2252,7 +2634,7 @@ var defaultTheme = {
2252
2634
  paddingInline: 0
2253
2635
  },
2254
2636
  ".cm-activeLine": {
2255
- background: "inherit"
2637
+ background: "transparent"
2256
2638
  },
2257
2639
  //
2258
2640
  // Selection
@@ -2423,90 +2805,88 @@ var codeTheme = {
2423
2805
  };
2424
2806
 
2425
2807
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2808
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
2426
2809
  var EditorModes = [
2427
2810
  "default",
2428
2811
  "vim"
2429
2812
  ];
2430
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, readonly, comments: comments2, extensions = [], editorMode, theme, slots = defaultSlots }, forwardedRef) => {
2431
- const tabsterDOMAttribute = useFocusableGroup({
2432
- tabBehavior: "limited"
2433
- });
2813
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2434
2814
  const { themeMode } = useThemeContext();
2435
2815
  const rootRef = useRef(null);
2436
2816
  const [view, setView] = useState(null);
2437
2817
  useImperativeHandle(forwardedRef, () => view, [
2438
2818
  view
2439
2819
  ]);
2440
- useEffect(() => {
2441
- if (view && focus) {
2442
- view.focus();
2443
- }
2444
- }, [
2445
- view,
2446
- focus
2447
- ]);
2448
- const { awareness: awareness2, peer } = model;
2449
- useEffect(() => {
2450
- if (awareness2 && peer) {
2451
- awareness2.setLocalStateField("user", {
2452
- name: peer.name ?? generateName(peer.id),
2453
- color: getColorForValue({
2454
- value: peer.id,
2455
- type: "color"
2456
- }),
2457
- colorLight: getColorForValue({
2458
- value: peer.id,
2459
- themeMode,
2460
- type: "highlight"
2461
- })
2462
- });
2463
- }
2464
- }, [
2465
- awareness2,
2466
- peer,
2467
- themeMode
2468
- ]);
2469
- useEffect(() => {
2470
- if (view && comments2 !== void 0) {
2471
- view.dispatch({
2472
- effects: setCommentRange.of({
2473
- model,
2474
- comments: comments2
2475
- })
2476
- });
2820
+ useEffect2(() => {
2821
+ if (autoFocus && !view?.hasFocus) {
2822
+ if (view?.state.selection.main?.from === 0) {
2823
+ const { to } = view.state.doc.lineAt(0);
2824
+ view?.dispatch({
2825
+ selection: {
2826
+ anchor: to
2827
+ }
2828
+ });
2829
+ }
2830
+ view?.focus();
2477
2831
  }
2478
2832
  }, [
2479
2833
  view,
2480
- comments2
2834
+ autoFocus
2481
2835
  ]);
2482
- useEffect(() => {
2836
+ useEffect2(() => {
2483
2837
  if (!model || !rootRef.current) {
2484
2838
  return;
2485
2839
  }
2486
2840
  const state = EditorState2.create({
2487
2841
  doc: model.text(),
2488
- // TODO(burdon): Composer should store and set selection when switching documents.
2489
2842
  selection,
2490
2843
  extensions: [
2491
- readonly && EditorState2.readOnly.of(readonly),
2492
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2493
- editorMode === "vim" && vim(),
2844
+ // TODO(burdon): Doesn't catch errors in keymap functions.
2845
+ EditorView13.exceptionSink.of((err) => {
2846
+ log2.catch(err, void 0, {
2847
+ F: __dxlog_file3,
2848
+ L: 134,
2849
+ S: void 0,
2850
+ C: (f, a) => f(...a)
2851
+ });
2852
+ }),
2494
2853
  // Theme.
2495
- // TODO(burdon): Make theme configurable.
2496
- EditorView12.baseTheme(defaultTheme),
2497
- EditorView12.theme(theme ?? {}),
2498
- EditorView12.darkTheme.of(themeMode === "dark"),
2854
+ // TODO(burdon): Make configurable.
2855
+ EditorView13.baseTheme(defaultTheme),
2856
+ EditorView13.theme(theme ?? {}),
2857
+ EditorView13.darkTheme.of(themeMode === "dark"),
2858
+ EditorView13.editorAttributes.of({
2859
+ class: slots.editor?.className ?? ""
2860
+ }),
2861
+ EditorView13.contentAttributes.of({
2862
+ class: slots.content?.className ?? ""
2863
+ }),
2864
+ // State.
2865
+ EditorView13.editable.of(!readonly),
2866
+ EditorState2.readOnly.of(!!readonly),
2499
2867
  // Storage and replication.
2868
+ // NOTE: This must come before user extensions.
2500
2869
  model.extension,
2870
+ // TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
2871
+ editorMode === "vim" && vim(),
2501
2872
  // Custom.
2502
2873
  ...extensions
2503
- ].filter(Boolean)
2874
+ ].filter(isNotFalsy3)
2504
2875
  });
2505
- view?.destroy();
2506
- const newView = new EditorView12({
2876
+ const newView = new EditorView13({
2877
+ state,
2507
2878
  parent: rootRef.current,
2508
- state
2879
+ scrollTo,
2880
+ // NOTE: Uncomment to debug/monitor all transactions.
2881
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
2882
+ dispatchTransactions: (trs, view2) => {
2883
+ if (debug) {
2884
+ logChanges(trs);
2885
+ }
2886
+ view2.update(trs);
2887
+ }
2509
2888
  });
2889
+ view?.destroy();
2510
2890
  setView(newView);
2511
2891
  return () => {
2512
2892
  newView?.destroy();
@@ -2522,8 +2902,14 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, read
2522
2902
  const handleKeyUp = useCallback((event) => {
2523
2903
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2524
2904
  switch (key) {
2905
+ case "Enter": {
2906
+ view?.focus();
2907
+ break;
2908
+ }
2525
2909
  case "Escape": {
2526
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
2910
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
2911
+ view?.focus();
2912
+ }
2527
2913
  break;
2528
2914
  }
2529
2915
  }
@@ -2534,24 +2920,24 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, read
2534
2920
  return /* @__PURE__ */ React.createElement("div", {
2535
2921
  key: model.id,
2536
2922
  role: "none",
2537
- ref: rootRef,
2538
2923
  tabIndex: 0,
2539
- onKeyUp: handleKeyUp,
2540
2924
  ...slots.root,
2541
- ...editorMode !== "vim" && tabsterDOMAttribute
2925
+ // {...(editorMode !== 'vim' && tabsterDOMAttribute)}
2926
+ onKeyUp: handleKeyUp,
2927
+ ref: rootRef
2542
2928
  });
2543
2929
  });
2544
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
2930
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, lineWrapping, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
2545
2931
  const { themeMode } = useThemeContext();
2546
2932
  const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2547
2933
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2548
2934
  ref: forwardedRef,
2549
2935
  readonly,
2550
2936
  extensions: [
2551
- basicBundle({
2552
- readonly,
2937
+ createBasicBundle({
2553
2938
  themeMode,
2554
- placeholder: placeholder3
2939
+ placeholder: placeholder3,
2940
+ lineWrapping
2555
2941
  }),
2556
2942
  ...extensions
2557
2943
  ],
@@ -2560,15 +2946,14 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholde
2560
2946
  ...props
2561
2947
  });
2562
2948
  });
2563
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
2949
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
2564
2950
  const { themeMode } = useThemeContext();
2565
2951
  const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2566
2952
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2567
2953
  ref: forwardedRef,
2568
2954
  readonly,
2569
2955
  extensions: [
2570
- markdownBundle({
2571
- readonly,
2956
+ createMarkdownExtensions({
2572
2957
  themeMode,
2573
2958
  placeholder: placeholder3
2574
2959
  }),
@@ -2581,7 +2966,10 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeh
2581
2966
  });
2582
2967
  var defaultSlots = {
2583
2968
  root: {
2584
- className: mx3("p-2 overflow-y-auto", inputSurface)
2969
+ className: mx3("grow", focusRing)
2970
+ },
2971
+ editor: {
2972
+ className: "bs-full"
2585
2973
  }
2586
2974
  };
2587
2975
  var defaultTextSlots = {
@@ -2591,22 +2979,178 @@ var defaultMarkdownSlots = {
2591
2979
  ...defaultSlots
2592
2980
  };
2593
2981
 
2594
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2595
- import get4 from "lodash.get";
2596
- import { getRawDoc } from "@dxos/echo-schema";
2597
- import { isNotNullOrUndefined } from "@dxos/util";
2982
+ // packages/ui/react-ui-editor/src/components/Toolbar/Toolbar.tsx
2983
+ import { At, CaretRight, ChatText, Code, Image, Link, ListBullets, ListChecks, ListNumbers, TextStrikethrough, Table as Table2, TextB, TextItalic } from "@phosphor-icons/react";
2984
+ import { createContext } from "@radix-ui/react-context";
2985
+ import React2 from "react";
2986
+ import { Button, DensityProvider, Select } from "@dxos/react-ui";
2987
+ import { getSize } from "@dxos/react-ui-theme";
2988
+ var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
2989
+ var ToolbarRoot = ({ children, onAction }) => {
2990
+ return /* @__PURE__ */ React2.createElement(ToolbarContextProvider, {
2991
+ onAction
2992
+ }, /* @__PURE__ */ React2.createElement(DensityProvider, {
2993
+ density: "fine"
2994
+ }, /* @__PURE__ */ React2.createElement("div", {
2995
+ role: "toolbar",
2996
+ className: "flex w-full shrink-0 p-2 gap-4 items-center whitespace-nowrap overflow-hidden"
2997
+ }, children)));
2998
+ };
2999
+ var ToolbarButton = ({ Icon, onClick, title }) => {
3000
+ const { onAction } = useToolbarContext("ToolbarButton");
3001
+ const handleClick = () => {
3002
+ const action = onClick();
3003
+ if (action) {
3004
+ onAction?.(action);
3005
+ }
3006
+ };
3007
+ return /* @__PURE__ */ React2.createElement(Button, {
3008
+ variant: "ghost",
3009
+ classNames: "p-2",
3010
+ onClick: handleClick,
3011
+ title
3012
+ }, /* @__PURE__ */ React2.createElement(Icon, {
3013
+ className: getSize(5)
3014
+ }));
3015
+ };
3016
+ var ToolbarSeparator = () => /* @__PURE__ */ React2.createElement("div", {
3017
+ className: "grow"
3018
+ });
3019
+ var MarkdownHeading = () => {
3020
+ const { onAction } = useToolbarContext("MarkdownFormatting");
3021
+ return /* @__PURE__ */ React2.createElement(Select.Root, {
3022
+ defaultValue: "0",
3023
+ onValueChange: (value) => onAction?.({
3024
+ type: "heading",
3025
+ data: parseInt(value)
3026
+ })
3027
+ }, /* @__PURE__ */ React2.createElement(Select.TriggerButton, {
3028
+ classNames: "w-[8rem]"
3029
+ }), /* @__PURE__ */ React2.createElement(Select.Portal, null, /* @__PURE__ */ React2.createElement(Select.Content, null, /* @__PURE__ */ React2.createElement(Select.Viewport, null, /* @__PURE__ */ React2.createElement(Select.Option, {
3030
+ value: "0"
3031
+ }, "Paragraph"), [
3032
+ 1,
3033
+ 2,
3034
+ 3,
3035
+ 4,
3036
+ 5,
3037
+ 6
3038
+ ].map((level) => /* @__PURE__ */ React2.createElement(Select.Option, {
3039
+ key: level,
3040
+ value: String(level)
3041
+ }, "Heading ", level))))));
3042
+ };
3043
+ var MarkdownStyles = () => /* @__PURE__ */ React2.createElement("div", {
3044
+ role: "none"
3045
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3046
+ Icon: TextB,
3047
+ title: "Bold",
3048
+ onClick: () => ({
3049
+ type: "bold"
3050
+ })
3051
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3052
+ Icon: TextItalic,
3053
+ title: "Italic",
3054
+ onClick: () => ({
3055
+ type: "italic"
3056
+ })
3057
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3058
+ Icon: TextStrikethrough,
3059
+ title: "Strike-through",
3060
+ onClick: () => ({
3061
+ type: "strikethrough"
3062
+ })
3063
+ }));
3064
+ var MarkdownLists = () => /* @__PURE__ */ React2.createElement("div", {
3065
+ role: "none"
3066
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3067
+ Icon: ListBullets,
3068
+ title: "Bullet list",
3069
+ onClick: () => ({
3070
+ type: "list"
3071
+ })
3072
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3073
+ Icon: ListNumbers,
3074
+ title: "Numbered list",
3075
+ onClick: () => ({
3076
+ type: "list-ordered"
3077
+ })
3078
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3079
+ Icon: ListChecks,
3080
+ title: "Task list",
3081
+ onClick: () => ({
3082
+ type: "list-tasks"
3083
+ })
3084
+ }));
3085
+ var MarkdownBlocks = () => /* @__PURE__ */ React2.createElement("div", {
3086
+ role: "none"
3087
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3088
+ Icon: CaretRight,
3089
+ title: "Block quite",
3090
+ onClick: () => ({
3091
+ type: "blockquote"
3092
+ })
3093
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3094
+ Icon: Code,
3095
+ title: "Code block",
3096
+ onClick: () => ({
3097
+ type: "codeblock"
3098
+ })
3099
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3100
+ Icon: Table2,
3101
+ title: "Table",
3102
+ onClick: () => ({
3103
+ type: "table"
3104
+ })
3105
+ }));
3106
+ var MarkdownLinks = () => /* @__PURE__ */ React2.createElement("div", {
3107
+ role: "none"
3108
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3109
+ Icon: Link,
3110
+ title: "Link",
3111
+ onClick: () => ({
3112
+ type: "link"
3113
+ })
3114
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3115
+ Icon: At,
3116
+ title: "Mention",
3117
+ onClick: () => ({
3118
+ type: "mention"
3119
+ })
3120
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3121
+ Icon: Image,
3122
+ title: "Image",
3123
+ onClick: () => ({
3124
+ type: "image"
3125
+ })
3126
+ }));
3127
+ 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));
3128
+ var MarkdownExtended = () => /* @__PURE__ */ React2.createElement(Toolbar.Button, {
3129
+ Icon: ChatText,
3130
+ title: "Create comment",
3131
+ onClick: () => ({
3132
+ type: "comment"
3133
+ })
3134
+ });
3135
+ var Toolbar = {
3136
+ Root: ToolbarRoot,
3137
+ Button: ToolbarButton,
3138
+ Separator: ToolbarSeparator,
3139
+ Markdown: MarkdownStandard,
3140
+ Extended: MarkdownExtended
3141
+ };
2598
3142
 
2599
3143
  // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2600
3144
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2601
3145
  import { Context as Context2 } from "@dxos/context";
2602
- import { invariant as invariant3 } from "@dxos/invariant";
3146
+ import { invariant as invariant2 } from "@dxos/invariant";
2603
3147
  import { log as log3 } from "@dxos/log";
2604
3148
  var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2605
3149
  var DEBOUNCE_INTERVAL = 100;
2606
3150
  var SpaceAwarenessProvider = class {
2607
3151
  constructor(params) {
2608
- this.remoteStateChange = new Event2();
2609
3152
  this._remoteStates = /* @__PURE__ */ new Map();
3153
+ this.remoteStateChange = new Event2();
2610
3154
  this._space = params.space;
2611
3155
  this._channel = params.channel;
2612
3156
  this._peerId = params.peerId;
@@ -2623,7 +3167,7 @@ var SpaceAwarenessProvider = class {
2623
3167
  await sleep(DEBOUNCE_INTERVAL);
2624
3168
  }
2625
3169
  });
2626
- const unsubscribe = this._space.listen(this._channel, (message) => {
3170
+ this._ctx.onDispose(this._space.listen(this._channel, (message) => {
2627
3171
  switch (message.payload.kind) {
2628
3172
  case "query": {
2629
3173
  this._handleQueryMessage();
@@ -2634,8 +3178,7 @@ var SpaceAwarenessProvider = class {
2634
3178
  break;
2635
3179
  }
2636
3180
  }
2637
- });
2638
- this._ctx.onDispose(unsubscribe);
3181
+ }));
2639
3182
  void this._space.postMessage(this._channel, {
2640
3183
  kind: "query"
2641
3184
  }).catch((err) => {
@@ -2643,7 +3186,7 @@ var SpaceAwarenessProvider = class {
2643
3186
  err
2644
3187
  }, {
2645
3188
  F: __dxlog_file4,
2646
- L: 85,
3189
+ L: 89,
2647
3190
  S: this,
2648
3191
  C: (f, a) => f(...a)
2649
3192
  });
@@ -2654,10 +3197,13 @@ var SpaceAwarenessProvider = class {
2654
3197
  this._ctx = void 0;
2655
3198
  this._postTask = void 0;
2656
3199
  }
3200
+ getRemoteStates() {
3201
+ return Array.from(this._remoteStates.values());
3202
+ }
2657
3203
  update(position) {
2658
- invariant3(this._postTask, void 0, {
3204
+ invariant2(this._postTask, void 0, {
2659
3205
  F: __dxlog_file4,
2660
- L: 96,
3206
+ L: 104,
2661
3207
  S: this,
2662
3208
  A: [
2663
3209
  "this._postTask",
@@ -2671,13 +3217,10 @@ var SpaceAwarenessProvider = class {
2671
3217
  };
2672
3218
  this._postTask.schedule();
2673
3219
  }
2674
- getRemoteStates() {
2675
- return Array.from(this._remoteStates.values());
2676
- }
2677
3220
  _handleQueryMessage() {
2678
- invariant3(this._postTask, void 0, {
3221
+ invariant2(this._postTask, void 0, {
2679
3222
  F: __dxlog_file4,
2680
- L: 111,
3223
+ L: 115,
2681
3224
  S: this,
2682
3225
  A: [
2683
3226
  "this._postTask",
@@ -2687,9 +3230,9 @@ var SpaceAwarenessProvider = class {
2687
3230
  this._postTask.schedule();
2688
3231
  }
2689
3232
  _handlePostMessage(message) {
2690
- invariant3(message.kind === "post", void 0, {
3233
+ invariant2(message.kind === "post", void 0, {
2691
3234
  F: __dxlog_file4,
2692
- L: 116,
3235
+ L: 120,
2693
3236
  S: this,
2694
3237
  A: [
2695
3238
  "message.kind === 'post'",
@@ -2701,199 +3244,240 @@ var SpaceAwarenessProvider = class {
2701
3244
  }
2702
3245
  };
2703
3246
 
2704
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
3247
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
2705
3248
  import { StateField as StateField5 } from "@codemirror/state";
2706
- import { useEffect as useEffect2, useState as useState2 } from "react";
2707
- import { isAutomergeObject } from "@dxos/react-client/echo";
3249
+ var modelState = StateField5.define({
3250
+ create: () => void 0,
3251
+ update: (model) => model
3252
+ });
2708
3253
 
2709
- // packages/ui/react-ui-editor/src/hooks/yjs.ts
2710
- import * as decoding from "lib0/decoding";
2711
- import * as encoding from "lib0/encoding";
2712
- import { Observable } from "lib0/observable";
2713
- import * as YP from "y-protocols/awareness";
2714
- import { invariant as invariant4 } from "@dxos/invariant";
2715
- import { log as log4 } from "@dxos/log";
2716
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2717
- var createYjsModel = ({ identity, space, text }) => {
2718
- invariant4(text?.doc && text?.content, void 0, {
2719
- F: __dxlog_file5,
2720
- L: 21,
2721
- S: void 0,
2722
- A: [
2723
- "text?.doc && text?.content",
2724
- ""
2725
- ]
2726
- });
2727
- const provider = space ? new YAwarenessProvider({
2728
- space,
2729
- doc: text.doc,
2730
- channel: `yjs.awareness.${text.id}`
2731
- }) : void 0;
2732
- const model = {
2733
- id: text.doc.guid,
2734
- content: text.content,
2735
- text: () => text.content.toString(),
2736
- extension: [
2737
- modelState.init(() => model),
2738
- yjs(text.content, provider?.awareness)
2739
- ],
2740
- awareness: provider?.awareness,
2741
- peer: identity ? {
2742
- id: identity.identityKey.toHex(),
2743
- name: identity.profile?.displayName
2744
- } : void 0
2745
- };
2746
- return model;
2747
- };
2748
- var messageAwareness = 1;
2749
- var messageQueryAwareness = 3;
2750
- var YAwarenessProvider = class extends Observable {
2751
- constructor({ space, doc, channel, awareness: awareness2 }) {
2752
- super();
2753
- this._space = space;
2754
- this._awareness = awareness2 ?? new YP.Awareness(doc);
2755
- this._channel = channel;
2756
- this._clientId = doc.clientID;
2757
- this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
2758
- this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
2759
- if (typeof window !== "undefined") {
2760
- window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
2761
- } else if (typeof process !== "undefined") {
2762
- process.on("exit", this._handleBeforeUnload.bind(this));
3254
+ // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
3255
+ var useActionHandler = (view) => {
3256
+ return (action) => {
3257
+ if (!view) {
3258
+ return;
2763
3259
  }
2764
- const encoderAwarenessQuery = encoding.createEncoder();
2765
- encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
2766
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
2767
- const encoderAwarenessState = encoding.createEncoder();
2768
- encoding.writeVarUint(encoderAwarenessState, messageAwareness);
2769
- encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [
2770
- this._clientId
2771
- ]));
2772
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
2773
- }
2774
- get awareness() {
2775
- return this._awareness;
2776
- }
2777
- _handleAwarenessUpdate({ added, updated, removed }, origin) {
2778
- log4("awareness update", {
2779
- added,
2780
- updated,
2781
- removed,
2782
- origin
2783
- }, {
2784
- F: __dxlog_file5,
2785
- L: 100,
2786
- S: this,
2787
- C: (f, a) => f(...a)
2788
- });
2789
- const changedClients = added.concat(updated).concat(removed);
2790
- const encoderAwareness = encoding.createEncoder();
2791
- encoding.writeVarUint(encoderAwareness, messageAwareness);
2792
- encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
2793
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2794
- }
2795
- _handleSpaceMessage({ payload }) {
2796
- log4("space message", payload, {
2797
- F: __dxlog_file5,
2798
- L: 109,
2799
- S: this,
2800
- C: (f, a) => f(...a)
3260
+ switch (action.type) {
3261
+ case "heading":
3262
+ setHeading(parseInt(action.data))(view);
3263
+ break;
3264
+ case "bold":
3265
+ toggleBold(view);
3266
+ break;
3267
+ case "italic":
3268
+ toggleItalic(view);
3269
+ break;
3270
+ case "strikethrough":
3271
+ toggleStrikethrough(view);
3272
+ break;
3273
+ case "list":
3274
+ toggleList(view);
3275
+ break;
3276
+ case "codeblock":
3277
+ insertCodeblock(view);
3278
+ break;
3279
+ case "table":
3280
+ insertTable(view);
3281
+ break;
3282
+ case "comment":
3283
+ createComment(view);
3284
+ break;
3285
+ }
3286
+ setTimeout(() => {
3287
+ view.focus();
3288
+ view.dispatch({
3289
+ selection: view.state.selection
3290
+ });
2801
3291
  });
2802
- const data = new Uint8Array(Array.from(Object.values(payload)));
2803
- const encoder = this._readMessage(data);
2804
- if (encoder) {
2805
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
3292
+ };
3293
+ };
3294
+
3295
+ // packages/ui/react-ui-editor/src/hooks/useEditorView.ts
3296
+ import { useState as useState2 } from "react";
3297
+ var useEditorView = () => {
3298
+ const [view, setView] = useState2(null);
3299
+ return [
3300
+ (ref) => {
3301
+ setView(ref);
3302
+ },
3303
+ view
3304
+ ];
3305
+ };
3306
+
3307
+ // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
3308
+ import { EditorSelection, EditorState as EditorState3 } from "@codemirror/state";
3309
+ import { EditorView as EditorView14 } from "@codemirror/view";
3310
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
3311
+ import { log as log4 } from "@dxos/log";
3312
+ import { isNotFalsy as isNotFalsy4 } from "@dxos/util";
3313
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
3314
+ var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions } = {}) => {
3315
+ const onUpdate = useRef2();
3316
+ const [view, setView] = useState3();
3317
+ const parentRef = useRef2(null);
3318
+ useEffect3(() => {
3319
+ if (parentRef.current) {
3320
+ const state = EditorState3.create({
3321
+ doc,
3322
+ selection,
3323
+ extensions: [
3324
+ // TODO(burdon): Doesn't catch errors in keymap functions.
3325
+ EditorView14.exceptionSink.of((err) => {
3326
+ log4.catch(err, void 0, {
3327
+ F: __dxlog_file5,
3328
+ L: 57,
3329
+ S: void 0,
3330
+ C: (f, a) => f(...a)
3331
+ });
3332
+ }),
3333
+ extensions,
3334
+ EditorView14.updateListener.of(() => {
3335
+ onUpdate.current?.();
3336
+ })
3337
+ ].filter(isNotFalsy4)
3338
+ });
3339
+ const view2 = new EditorView14({
3340
+ parent: parentRef.current,
3341
+ scrollTo,
3342
+ state,
3343
+ // NOTE: Uncomment to debug/monitor all transactions.
3344
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
3345
+ dispatchTransactions: (trs, view3) => {
3346
+ if (debug) {
3347
+ logChanges(trs);
3348
+ }
3349
+ view3.update(trs);
3350
+ }
3351
+ });
3352
+ setView(view2);
2806
3353
  }
2807
- }
2808
- _readMessage(message) {
2809
- const decoder = decoding.createDecoder(message);
2810
- const encoder = encoding.createEncoder();
2811
- const messageType = decoding.readVarUint(decoder);
2812
- let sendReply = false;
2813
- switch (messageType) {
2814
- case messageAwareness: {
2815
- YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2816
- break;
3354
+ return () => {
3355
+ view?.destroy();
3356
+ };
3357
+ }, [
3358
+ parentRef
3359
+ ]);
3360
+ useEffect3(() => {
3361
+ if (view) {
3362
+ if (!selection && !view.state.selection.main.anchor) {
3363
+ selection = EditorSelection.single(view.state.doc.line(1).to);
2817
3364
  }
2818
- case messageQueryAwareness: {
2819
- encoding.writeVarUint(encoder, messageAwareness);
2820
- encoding.writeVarUint8Array(encoder, YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2821
- sendReply = true;
2822
- break;
3365
+ if (selection || scrollTo) {
3366
+ onUpdate.current = () => {
3367
+ onUpdate.current = void 0;
3368
+ view.dispatch({
3369
+ selection,
3370
+ effects: scrollTo && [
3371
+ scrollTo
3372
+ ],
3373
+ scrollIntoView: !scrollTo
3374
+ });
3375
+ };
2823
3376
  }
2824
- default: {
2825
- console.error("Invalid message:", messageType);
2826
- return encoder;
3377
+ if (autoFocus) {
3378
+ view.focus();
2827
3379
  }
2828
3380
  }
2829
- if (!sendReply) {
2830
- return null;
2831
- }
2832
- return encoder;
2833
- }
2834
- _handleBeforeUnload() {
2835
- YP.removeAwarenessStates(this._awareness, [
2836
- this._clientId
2837
- ], "window unload");
2838
- }
3381
+ }, [
3382
+ view,
3383
+ autoFocus,
3384
+ selection,
3385
+ scrollTo
3386
+ ]);
3387
+ return {
3388
+ parentRef,
3389
+ view
3390
+ };
3391
+ };
3392
+ var createDataExtensions = ({ readonly = false } = {}) => {
3393
+ return [
3394
+ //
3395
+ EditorState3.readOnly.of(readonly),
3396
+ EditorView14.editable.of(!readonly)
3397
+ ];
3398
+ };
3399
+ var createThemeExtensions = ({ theme, themeMode, slots } = {}) => {
3400
+ return [
3401
+ //
3402
+ EditorView14.baseTheme(defaultTheme),
3403
+ theme && EditorView14.theme(theme),
3404
+ EditorView14.darkTheme.of(themeMode === "dark"),
3405
+ EditorView14.editorAttributes.of({
3406
+ class: slots?.editor?.className ?? ""
3407
+ }),
3408
+ EditorView14.contentAttributes.of({
3409
+ class: slots?.content?.className ?? ""
3410
+ })
3411
+ ].filter(isNotFalsy4);
2839
3412
  };
2840
3413
 
2841
3414
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2842
- var modelState = StateField5.define({
2843
- create: () => void 0,
2844
- update: (model) => model
2845
- });
3415
+ import get4 from "lodash.get";
3416
+ import { useEffect as useEffect4, useState as useState4 } from "react";
3417
+ import { generateName } from "@dxos/display-name";
3418
+ import { getRawDoc } from "@dxos/echo-schema";
3419
+ import { invariant as invariant3 } from "@dxos/invariant";
3420
+ import { isAutomergeObject } from "@dxos/react-client/echo";
3421
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
2846
3422
  var useTextModel = (props) => {
2847
3423
  const { identity, space, text } = props;
2848
- const [model, setModel] = useState2();
2849
- useEffect2(() => setModel(createModel(props)), [
3424
+ const [model, setModel] = useState4();
3425
+ useEffect4(() => setModel(createModel(props)), [
2850
3426
  identity,
2851
3427
  space,
2852
3428
  text
2853
3429
  ]);
2854
3430
  return model;
2855
3431
  };
2856
- var createModel = (props) => {
2857
- const { text } = props;
2858
- if (isAutomergeObject(text)) {
2859
- return createAutomergeModel(props);
2860
- } else if (text?.doc) {
2861
- return createYjsModel(props);
2862
- } else {
2863
- return void 0;
2864
- }
3432
+ var useInMemoryTextModel = ({ id, defaultContent }) => {
3433
+ const [content, setContent] = useState4(defaultContent ?? "");
3434
+ return {
3435
+ id,
3436
+ content,
3437
+ setContent,
3438
+ text: () => content
3439
+ };
2865
3440
  };
2866
-
2867
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2868
- var createAutomergeModel = ({ space, identity, text }) => {
3441
+ var createModel = ({ space, identity, text }) => {
3442
+ invariant3(isAutomergeObject(text), void 0, {
3443
+ F: __dxlog_file6,
3444
+ L: 50,
3445
+ S: void 0,
3446
+ A: [
3447
+ "isAutomergeObject(text)",
3448
+ ""
3449
+ ]
3450
+ });
2869
3451
  const obj = text;
2870
3452
  const doc = getRawDoc(obj, [
2871
3453
  obj.field
2872
3454
  ]);
2873
- const awarenessProvider = space && new SpaceAwarenessProvider({
3455
+ const awarenessProvider2 = space && new SpaceAwarenessProvider({
2874
3456
  space,
2875
3457
  channel: `automerge.awareness.${obj.id}`,
2876
3458
  info: {
2877
- displayName: identity?.profile?.displayName ?? "",
3459
+ displayName: identity ? identity.profile?.displayName ?? generateName(identity.identityKey.toHex()) : void 0,
2878
3460
  color: cursorColor.color,
2879
3461
  lightColor: cursorColor.light
2880
3462
  },
2881
3463
  peerId: identity?.identityKey.toHex() ?? "Anonymous"
2882
3464
  });
3465
+ const extensions = [
3466
+ //
3467
+ modelState.init(() => model),
3468
+ automerge({
3469
+ handle: doc.handle,
3470
+ path: doc.path
3471
+ })
3472
+ ];
3473
+ if (awarenessProvider2) {
3474
+ extensions.push(awareness(awarenessProvider2));
3475
+ }
2883
3476
  const model = {
2884
3477
  id: obj.id,
2885
3478
  content: doc,
2886
3479
  text: () => get4(doc.handle.docSync(), doc.path),
2887
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2888
- extension: [
2889
- modelState.init(() => model),
2890
- automerge3({
2891
- handle: doc.handle,
2892
- path: doc.path
2893
- }),
2894
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
2895
- awareness()
2896
- ].filter(isNotNullOrUndefined),
3480
+ extension: extensions,
2897
3481
  peer: identity ? {
2898
3482
  id: identity.identityKey.toHex(),
2899
3483
  name: identity.profile?.displayName
@@ -2902,7 +3486,6 @@ var createAutomergeModel = ({ space, identity, text }) => {
2902
3486
  return model;
2903
3487
  };
2904
3488
  export {
2905
- AwarenessProvider,
2906
3489
  BaseTextEditor,
2907
3490
  Cursor,
2908
3491
  Doc,
@@ -2912,47 +3495,66 @@ export {
2912
3495
  SpaceAwarenessProvider,
2913
3496
  TextEditor,
2914
3497
  TextKind,
2915
- YAwarenessProvider,
3498
+ Toolbar,
2916
3499
  YText,
2917
3500
  YXmlFragment,
2918
3501
  autocomplete,
2919
- automerge3 as automerge,
3502
+ automerge,
2920
3503
  awareness,
2921
- basicBundle,
3504
+ awarenessProvider,
2922
3505
  blast,
2923
3506
  callbackWrapper,
2924
3507
  code2 as code,
2925
3508
  codeTheme,
2926
3509
  comments,
2927
- createAutomergeModel,
2928
- createYjsModel,
2929
- cursorConverter,
3510
+ createBasicBundle,
3511
+ createComment,
3512
+ createDataExtensions,
3513
+ createMarkdownExtensions,
3514
+ createThemeExtensions,
2930
3515
  defaultMarkdownSlots,
2931
3516
  defaultOptions,
2932
3517
  defaultSlots,
2933
3518
  defaultTextSlots,
2934
3519
  defaultTheme,
3520
+ focusComment,
3521
+ formatting,
2935
3522
  getToken,
3523
+ heading2 as heading,
2936
3524
  hr,
2937
3525
  image,
3526
+ insertCodeblock,
3527
+ insertTable,
3528
+ keymap7 as keymap,
2938
3529
  link,
2939
3530
  listener,
2940
- markdownBundle,
3531
+ logChanges,
2941
3532
  markdownHighlightStyle,
2942
3533
  markdownTags,
2943
3534
  markdownTagsExtensions,
2944
3535
  markdownTheme,
2945
3536
  mention,
2946
3537
  modelState,
2947
- setCommentRange,
2948
- setFocus,
3538
+ outliner,
3539
+ setComments,
3540
+ setHeading,
2949
3541
  setSelection,
2950
3542
  table,
2951
3543
  tags2 as tags,
2952
3544
  tasklist,
2953
3545
  textTheme,
3546
+ toggleBold,
3547
+ toggleItalic,
3548
+ toggleList,
3549
+ toggleStrikethrough,
3550
+ toggleStyle,
2954
3551
  typewriter,
3552
+ useActionHandler,
3553
+ useComments,
3554
+ useEditorView,
3555
+ useInMemoryTextModel,
3556
+ useTextEditor,
2955
3557
  useTextModel,
2956
- yjs
3558
+ useToolbarContext
2957
3559
  };
2958
3560
  //# sourceMappingURL=index.mjs.map