@dxos/react-ui-editor 0.3.10-next.ef70620 → 0.3.11-main.1caa3af

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/lib/browser/index.mjs +2174 -935
  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 +66 -0
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -0
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +9 -4
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +12 -4
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +10 -0
  11. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/extensions/{basic/bundle.d.ts → basic.d.ts} +3 -2
  13. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -0
  14. package/dist/types/src/components/TextEditor/extensions/blast.d.ts +25 -0
  15. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +1 -0
  16. package/dist/types/src/components/TextEditor/extensions/code.d.ts +2 -0
  17. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +1 -0
  18. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +48 -0
  19. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -0
  20. package/dist/types/src/components/TextEditor/extensions/hr.d.ts +2 -0
  21. package/dist/types/src/components/TextEditor/extensions/hr.d.ts.map +1 -0
  22. package/dist/types/src/components/TextEditor/extensions/image.d.ts +4 -0
  23. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +1 -0
  24. package/dist/types/src/components/TextEditor/extensions/index.d.ts +14 -2
  25. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  26. package/dist/types/src/components/TextEditor/extensions/link.d.ts +11 -0
  27. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -0
  28. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +9 -0
  29. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -0
  30. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +10 -1
  31. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  32. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +36 -0
  33. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -0
  34. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +1 -1
  35. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -1
  36. package/dist/types/src/components/TextEditor/extensions/mention.d.ts +6 -0
  37. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +1 -0
  38. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts +3 -0
  39. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts.map +1 -0
  40. package/dist/types/src/components/TextEditor/extensions/table.d.ts +8 -0
  41. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +1 -0
  42. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +3 -0
  43. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -0
  44. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +12 -0
  45. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +1 -0
  46. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts +10 -0
  47. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts.map +1 -0
  48. package/dist/types/src/components/TextEditor/extensions/util.d.ts +5 -0
  49. package/dist/types/src/components/TextEditor/extensions/util.d.ts.map +1 -0
  50. package/dist/types/src/components/TextEditor/index.d.ts +2 -0
  51. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  52. package/dist/types/src/components/TextEditor/themes/default.d.ts +17 -0
  53. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -0
  54. package/dist/types/src/components/TextEditor/themes/index.d.ts +2 -0
  55. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +1 -0
  56. package/dist/types/src/hooks/automerge/automerge.stories.d.ts +15 -0
  57. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
  58. package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
  59. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/index.d.ts +1 -1
  60. package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
  61. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
  62. package/dist/types/src/{automerge/automerge-plugin/PatchSemaphore.d.ts → hooks/automerge/semaphore.d.ts} +4 -1
  63. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +1 -0
  64. package/dist/types/src/{automerge/automerge-plugin/codeMirrorToAm.d.ts → hooks/automerge/update-automerge.d.ts} +2 -3
  65. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +1 -0
  66. package/dist/types/src/hooks/automerge/update-codemirror.d.ts +5 -0
  67. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +1 -0
  68. package/dist/types/src/hooks/index.d.ts +0 -1
  69. package/dist/types/src/hooks/index.d.ts.map +1 -1
  70. package/dist/types/src/hooks/useTextModel.d.ts +19 -5
  71. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  72. package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +2 -1
  73. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
  74. package/dist/types/src/hooks/yjs/yjs.test.d.ts +2 -0
  75. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +1 -0
  76. package/dist/types/src/index.d.ts +1 -0
  77. package/dist/types/src/index.d.ts.map +1 -1
  78. package/dist/types/src/styles/markdown.d.ts +14 -11
  79. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  80. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  81. package/package.json +28 -33
  82. package/src/components/TextEditor/MarkdownEditor.stories.tsx +428 -0
  83. package/src/components/TextEditor/TextEditor.stories.tsx +57 -43
  84. package/src/components/TextEditor/TextEditor.tsx +98 -52
  85. package/src/components/TextEditor/extensions/autocomplete.ts +59 -0
  86. package/src/components/TextEditor/extensions/{basic/bundle.ts → basic.ts} +8 -4
  87. package/src/components/TextEditor/extensions/blast.ts +373 -0
  88. package/src/components/TextEditor/extensions/code.ts +99 -0
  89. package/src/components/TextEditor/extensions/comments.ts +388 -0
  90. package/src/components/TextEditor/extensions/hr.ts +74 -0
  91. package/src/components/TextEditor/extensions/image.ts +69 -0
  92. package/src/components/TextEditor/extensions/index.ts +14 -2
  93. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkDecoration.ts → link.ts} +75 -38
  94. package/src/components/TextEditor/extensions/{change.ts → listener.ts} +5 -4
  95. package/src/components/TextEditor/extensions/markdown/bundle.ts +56 -49
  96. package/src/components/TextEditor/extensions/markdown/highlight.ts +204 -0
  97. package/src/components/TextEditor/extensions/markdown/index.ts +1 -1
  98. package/src/components/TextEditor/extensions/mention.ts +31 -0
  99. package/src/components/TextEditor/extensions/mermaid.ts +11 -0
  100. package/src/components/TextEditor/extensions/table.ts +132 -0
  101. package/src/components/TextEditor/extensions/tasklist.ts +121 -0
  102. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkTooltip.tsx → tooltip.ts} +9 -9
  103. package/src/components/TextEditor/extensions/typewriter.ts +68 -0
  104. package/src/components/TextEditor/extensions/util.ts +18 -0
  105. package/src/components/TextEditor/index.ts +3 -0
  106. package/src/components/TextEditor/themes/default.ts +302 -0
  107. package/src/components/TextEditor/themes/index.ts +5 -0
  108. package/src/{automerge → hooks/automerge}/automerge.stories.tsx +7 -5
  109. package/src/{automerge/automerge-plugin → hooks/automerge}/plugin.ts +2 -1
  110. package/src/{automerge/automerge-plugin/PatchSemaphore.ts → hooks/automerge/semaphore.ts} +13 -9
  111. package/src/{automerge/automerge-plugin/codeMirrorToAm.ts → hooks/automerge/update-automerge.ts} +5 -2
  112. package/src/{automerge/automerge-plugin/amToCodemirror.ts → hooks/automerge/update-codemirror.ts} +4 -1
  113. package/src/hooks/index.ts +0 -1
  114. package/src/hooks/useTextModel.ts +101 -42
  115. package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +2 -1
  116. package/src/hooks/yjs/yjs.test.ts +59 -0
  117. package/src/index.ts +1 -0
  118. package/src/styles/markdown.ts +33 -24
  119. package/src/testing/replicator.ts +6 -6
  120. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
  121. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts +0 -6
  122. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
  123. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
  124. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
  125. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
  126. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
  127. package/dist/types/src/automerge/automerge.stories.d.ts +0 -9
  128. package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
  129. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
  130. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
  131. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
  132. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
  133. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
  134. package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
  135. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
  136. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
  137. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
  138. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
  139. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
  140. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts +0 -10
  141. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
  142. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
  143. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
  144. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
  145. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
  146. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
  147. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
  148. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  149. package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
  150. package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
  151. package/src/components/TextEditor/extensions/basic/index.ts +0 -6
  152. package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
  153. package/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx +0 -119
  154. package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
  155. package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
  156. package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
  157. package/src/hooks/useCollaboration.ts +0 -45
  158. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/handle.d.ts +0 -0
  159. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/plugin.d.ts +0 -0
  160. package/src/{automerge/automerge-plugin → hooks/automerge}/handle.ts +0 -0
  161. package/src/{automerge/automerge-plugin → hooks/automerge}/index.ts +1 -1
@@ -1,759 +1,601 @@
1
1
  // packages/ui/react-ui-editor/src/index.ts
2
2
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
3
- import { Doc, YText as YText2, YXmlFragment } from "@dxos/text-model";
3
+ import { Doc, YText, YXmlFragment } from "@dxos/text-model";
4
4
 
5
5
  // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
6
6
  import { tags as tags2 } from "@lezer/highlight";
7
7
 
8
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/bundle.ts
8
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/autocomplete.ts
9
+ import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
10
+ import { keymap } from "@codemirror/view";
11
+ var autocomplete = ({ onSearch }) => {
12
+ return [
13
+ // https://codemirror.net/docs/ref/#view.keymap
14
+ // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
15
+ keymap.of(completionKeymap),
16
+ // https://codemirror.net/examples/autocompletion
17
+ // https://codemirror.net/docs/ref/#autocomplete.autocompletion
18
+ autocompletion({
19
+ // TODO(burdon): Set custom keymap.
20
+ // defaultKeymap: false,
21
+ // Don't start unless key press.
22
+ activateOnTyping: false,
23
+ // TODO(burdon): Option to create new page?
24
+ // TODO(burdon): Optional decoration via addToOptions
25
+ override: [
26
+ (context) => {
27
+ const match = context.matchBefore(/\w*/);
28
+ if (!match || match.from === match.to && !context.explicit) {
29
+ return null;
30
+ }
31
+ return {
32
+ from: match.from,
33
+ options: onSearch(match.text.toLowerCase())
34
+ };
35
+ }
36
+ ]
37
+ })
38
+ ];
39
+ };
40
+
41
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic.ts
9
42
  import { closeBrackets } from "@codemirror/autocomplete";
10
43
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
11
44
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
12
- import { EditorView, placeholder } from "@codemirror/view";
13
- var basicBundle = ({ themeMode, placeholder: _placeholder }) => [
45
+ import { drawSelection, EditorView, placeholder } from "@codemirror/view";
46
+ var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
47
+ EditorView.lineWrapping,
48
+ // TODO(burdon): Compare with minimalSetup.
49
+ // https://codemirror.net/docs/ref/#codemirror.minimalSetup
14
50
  bracketMatching(),
15
51
  closeBrackets(),
52
+ drawSelection(),
16
53
  _placeholder && placeholder(_placeholder),
17
- EditorView.lineWrapping,
18
54
  // TODO(burdon): Is this required?
19
55
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
20
56
  ].filter(Boolean);
21
57
 
22
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/theme.ts
58
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts
59
+ import { EditorView as EditorView2, keymap as keymap2 } from "@codemirror/view";
60
+ import defaultsDeep from "lodash.defaultsdeep";
61
+ import { invariant } from "@dxos/invariant";
62
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts";
63
+ var defaultOptions = {
64
+ effect: 2,
65
+ maxParticles: 200,
66
+ particleGravity: 0.08,
67
+ particleAlphaFadeout: 0.996,
68
+ particleSize: {
69
+ min: 2,
70
+ max: 8
71
+ },
72
+ particleNumRange: {
73
+ min: 5,
74
+ max: 10
75
+ },
76
+ particleVelocityRange: {
77
+ x: [
78
+ -1,
79
+ 1
80
+ ],
81
+ y: [
82
+ -3.5,
83
+ -1.5
84
+ ]
85
+ },
86
+ particleShrinkRate: 0.95,
87
+ shakeIntensity: 5
88
+ };
89
+ var blast = (options = defaultOptions) => {
90
+ let blaster;
91
+ let last = 0;
92
+ const getPoint = (view, pos) => {
93
+ const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
94
+ const element = document.elementFromPoint(x, y);
95
+ const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
96
+ const point = {
97
+ x: x - left,
98
+ y: y - top
99
+ };
100
+ return {
101
+ element,
102
+ point
103
+ };
104
+ };
105
+ return [
106
+ // Cursor moved.
107
+ EditorView2.updateListener.of((update4) => {
108
+ if (blaster?.node !== update4.view.scrollDOM) {
109
+ if (blaster) {
110
+ blaster.destroy();
111
+ }
112
+ blaster = new Blaster(update4.view.scrollDOM, defaultsDeep({
113
+ particleGravity: 0.2,
114
+ particleShrinkRate: 0.995,
115
+ color: () => [
116
+ 100 + Math.random() * 100,
117
+ 0,
118
+ 0
119
+ ]
120
+ }, options, defaultOptions));
121
+ blaster.initialize();
122
+ blaster.start();
123
+ } else {
124
+ blaster.resize();
125
+ }
126
+ const current = update4.state.selection.main.head;
127
+ if (current !== last) {
128
+ last = current;
129
+ const { element, point } = getPoint(update4.view, current - 1);
130
+ if (element && point) {
131
+ blaster.spawn({
132
+ element,
133
+ point
134
+ });
135
+ }
136
+ }
137
+ }),
138
+ keymap2.of([
139
+ {
140
+ any: (_, event) => {
141
+ if (blaster) {
142
+ if (event.key === "Enter" && event.shiftKey) {
143
+ blaster.shake({
144
+ time: 0.8
145
+ });
146
+ return true;
147
+ }
148
+ }
149
+ return false;
150
+ }
151
+ }
152
+ ])
153
+ ];
154
+ };
155
+ var Blaster = class {
156
+ constructor(_node, _options) {
157
+ this._node = _node;
158
+ this._options = _options;
159
+ this._running = false;
160
+ this._shakeTime = 0;
161
+ this._shakeTimeMax = 0;
162
+ this._particles = [];
163
+ this._particlePointer = 0;
164
+ this._lastPoint = {
165
+ x: 0,
166
+ y: 0
167
+ };
168
+ this.shake = throttle(({ time }) => {
169
+ this._shakeTime = this._shakeTimeMax || time;
170
+ this._shakeTimeMax = time;
171
+ }, 100);
172
+ this.spawn = throttle(({ element, point }) => {
173
+ const color = getRGBComponents(element, this._options.color);
174
+ const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
175
+ const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
176
+ this._lastPoint = point;
177
+ for (let i = numParticles; i--; i > 0) {
178
+ this._particles[this._particlePointer] = this._effect.create(point.x - dir * 16, point.y, color);
179
+ this._particlePointer = (this._particlePointer + 1) % this._options.maxParticles;
180
+ }
181
+ }, 100);
182
+ this._effect = this._options.effect === 1 ? new Effect1(_options) : new Effect2(_options);
183
+ }
184
+ get node() {
185
+ return this._node;
186
+ }
187
+ initialize() {
188
+ invariant(!this._canvas && !this._ctx, void 0, {
189
+ F: __dxlog_file,
190
+ L: 138,
191
+ S: this,
192
+ A: [
193
+ "!this._canvas && !this._ctx",
194
+ ""
195
+ ]
196
+ });
197
+ this._canvas = document.createElement("canvas");
198
+ this._canvas.id = "code-blast-canvas";
199
+ this._canvas.style.position = "absolute";
200
+ this._canvas.style.zIndex = "0";
201
+ this._canvas.style.pointerEvents = "none";
202
+ this._ctx = this._canvas.getContext("2d");
203
+ const parent = this._node.parentElement?.parentElement;
204
+ parent?.appendChild(this._canvas);
205
+ this.resize();
206
+ }
207
+ destroy() {
208
+ this.stop();
209
+ if (this._canvas) {
210
+ this._canvas.remove();
211
+ this._canvas = void 0;
212
+ this._ctx = void 0;
213
+ }
214
+ }
215
+ resize() {
216
+ if (this._node.parentElement && this._canvas) {
217
+ const { offsetLeft: x, offsetTop: y, offsetWidth: width, offsetHeight: height } = this._node.parentElement;
218
+ this._canvas.style.top = `${y}px`;
219
+ this._canvas.style.left = `${x}px`;
220
+ this._canvas.width = width;
221
+ this._canvas.height = height;
222
+ }
223
+ }
224
+ start() {
225
+ invariant(this._canvas && this._ctx, void 0, {
226
+ F: __dxlog_file,
227
+ L: 177,
228
+ S: this,
229
+ A: [
230
+ "this._canvas && this._ctx",
231
+ ""
232
+ ]
233
+ });
234
+ this._running = true;
235
+ this.loop();
236
+ }
237
+ stop() {
238
+ this._running = false;
239
+ this._node.style.transform = "translate(0px, 0px)";
240
+ }
241
+ loop() {
242
+ if (!this._running || !this._canvas || !this._ctx) {
243
+ return;
244
+ }
245
+ this._ctx.clearRect(0, 0, this._canvas.width ?? 0, this._canvas.height ?? 0);
246
+ const now = (/* @__PURE__ */ new Date()).getTime();
247
+ this._lastTime ??= now;
248
+ const dt = (now - this._lastTime) / 1e3;
249
+ this._lastTime = now;
250
+ if (this._shakeTime > 0) {
251
+ this._shakeTime -= dt;
252
+ const magnitude = this._shakeTime / this._shakeTimeMax * this._options.shakeIntensity;
253
+ const shakeX = random(-magnitude, magnitude);
254
+ const shakeY = random(-magnitude, magnitude);
255
+ this._node.style.transform = `translate(${shakeX}px,${shakeY}px)`;
256
+ }
257
+ this.drawParticles();
258
+ requestAnimationFrame(this.loop.bind(this));
259
+ }
260
+ drawParticles() {
261
+ for (let i = this._particles.length; i--; i > 0) {
262
+ const particle = this._particles[i];
263
+ if (!particle) {
264
+ continue;
265
+ }
266
+ if (particle.alpha < 0.01 || particle.size <= 0.5) {
267
+ continue;
268
+ }
269
+ this._effect.update(this._ctx, particle);
270
+ }
271
+ }
272
+ };
273
+ var Effect = class {
274
+ constructor(_options) {
275
+ this._options = _options;
276
+ }
277
+ };
278
+ var Effect1 = class extends Effect {
279
+ create(x, y, color) {
280
+ return {
281
+ x,
282
+ y: y + 10,
283
+ vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
284
+ vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
285
+ size: random(this._options.particleSize.min, this._options.particleSize.max),
286
+ color,
287
+ alpha: 1
288
+ };
289
+ }
290
+ update(ctx, particle) {
291
+ particle.vy += this._options.particleGravity;
292
+ particle.x += particle.vx;
293
+ particle.y += particle.vy;
294
+ particle.alpha *= this._options.particleAlphaFadeout;
295
+ ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
296
+ ctx.fillRect(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, particle.size);
297
+ }
298
+ };
299
+ var Effect2 = class extends Effect {
300
+ create(x, y, color) {
301
+ return {
302
+ x,
303
+ y: y + 10,
304
+ vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
305
+ vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
306
+ size: random(this._options.particleSize.min, this._options.particleSize.max),
307
+ color,
308
+ alpha: 1,
309
+ theta: random(0, 360) * Math.PI / 180,
310
+ drag: 0.92,
311
+ wander: 0.15
312
+ };
313
+ }
314
+ update(ctx, particle) {
315
+ particle.vy += this._options.particleGravity;
316
+ particle.x += particle.vx;
317
+ particle.y += particle.vy;
318
+ particle.vx *= particle.drag;
319
+ particle.vy *= particle.drag;
320
+ particle.theta += random(-0.5, 0.5);
321
+ particle.vx += Math.sin(particle.theta) * 0.1;
322
+ particle.vy += Math.cos(particle.theta) * 0.1;
323
+ particle.size *= this._options.particleShrinkRate;
324
+ particle.alpha *= this._options.particleAlphaFadeout;
325
+ ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
326
+ ctx.beginPath();
327
+ ctx.arc(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, 0, 2 * Math.PI);
328
+ ctx.fill();
329
+ }
330
+ };
331
+ function throttle(callback, limit) {
332
+ let wait = false;
333
+ return function(...args) {
334
+ if (!wait) {
335
+ callback.apply(this, args);
336
+ wait = true;
337
+ setTimeout(() => {
338
+ wait = false;
339
+ }, limit);
340
+ }
341
+ };
342
+ }
343
+ var getRGBComponents = (node, color) => {
344
+ if (typeof color === "function") {
345
+ return color();
346
+ }
347
+ const bgColor = getComputedStyle(node).color;
348
+ if (bgColor) {
349
+ const x = bgColor.match(/(\d+), (\d+), (\d+)/)?.slice(1);
350
+ if (x) {
351
+ return x;
352
+ }
353
+ }
354
+ return [
355
+ 50,
356
+ 50,
357
+ 50
358
+ ];
359
+ };
360
+ var random = (min, max) => {
361
+ if (!max) {
362
+ max = min;
363
+ min = 0;
364
+ }
365
+ return min + ~~(Math.random() * (max - min + 1));
366
+ };
367
+
368
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/code.ts
369
+ import { ViewPlugin, EditorView as EditorView3, Decoration } from "@codemirror/view";
23
370
  import get from "lodash.get";
371
+ import { mx as mx2 } from "@dxos/react-ui-theme";
24
372
 
25
373
  // packages/ui/react-ui-editor/src/styles/markdown.ts
26
374
  import { mx } from "@dxos/react-ui-theme";
27
- var heading = {
28
- 1: "mbs-4 mbe-2 text-4xl font-semibold text-inherit no-underline",
29
- 2: "mbs-4 mbe-2 text-3xl font-bold text-inherit no-underline",
30
- 3: "mbs-4 mbe-2 text-2xl font-bold text-inherit no-underline",
31
- 4: "mbs-4 mbe-2 text-xl font-extrabold text-inherit no-underline",
32
- 5: "mbs-4 mbe-2 text-lg font-extrabold text-inherit no-underline",
33
- 6: "mbs-4 mbe-2 font-black text-inherit no-underline"
375
+ var headings = {
376
+ 1: [
377
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
378
+ "-ml-[10px]"
379
+ ],
380
+ 2: [
381
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
382
+ "-ml-[8px]"
383
+ ],
384
+ 3: [
385
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
386
+ "-ml-[7px]"
387
+ ],
388
+ 4: [
389
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
390
+ "-ml-[6px]"
391
+ ],
392
+ 5: [
393
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
394
+ "-ml-[5px]"
395
+ ],
396
+ 6: [
397
+ "mbs-4 mbe-2 font-medium text-inherit no-underline",
398
+ "-ml-[4px]"
399
+ ]
400
+ };
401
+ var heading = (level, readonly) => {
402
+ const [style, offset] = headings[level];
403
+ return mx(style, readonly && offset);
34
404
  };
405
+ var light = "text-neutral-200 dark:text-neutral-800";
406
+ var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
35
407
  var bold = "font-bold";
36
- var code = "font-mono bg-neutral-500/10 rounded pli-0.5 plb-0.5 -mlb-0.5";
37
- var codeWithoutMarks = mx(code, "pli-1.5 mli-0.5");
38
408
  var italic = "italic";
39
409
  var strikethrough = "line-through";
40
- var mark = "!font-normal !no-underline !text-inherit opacity-40";
410
+ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
411
+ var codeMark = "font-mono text-primary-500";
412
+ var inlineUrl = mx(code, "px-1");
413
+ var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
41
414
 
42
415
  // packages/ui/react-ui-editor/src/styles/tokens.ts
43
416
  import { tailwindConfig } from "@dxos/react-ui-theme";
44
417
  var tokens = tailwindConfig({}).theme;
45
418
 
46
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/theme.ts
47
- var basicTheme = {
48
- "&.cm-focused": {
49
- outline: "none"
50
- },
51
- "& .cm-line": {
52
- paddingInline: 0,
53
- lineHeight: 1.6,
54
- minBlockSize: "1.6em"
419
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/code.ts
420
+ var CODE_REGEX = /```[\s\S]*?```/gs;
421
+ var styles = EditorView3.baseTheme({
422
+ "& .cm-codeblock": {
423
+ fontFamily: get(tokens, "fontFamily.mono", []).join(",")
55
424
  },
56
- "& .cm-line *": {
57
- lineHeight: 1.6
425
+ '&light [aria-readonly="true"] .cm-codeblock': {
426
+ background: get(tokens, "extend.colors.neutral.50")
58
427
  },
59
- "& .cm-scroller": {
60
- fontFamily: get(tokens, "fontFamily.body", []).join(","),
61
- overflow: "visible"
62
- },
63
- "& .cm-content": {
64
- padding: 0,
65
- caretColor: "black"
428
+ '&dark [aria-readonly="true"] .cm-codeblock': {
429
+ background: get(tokens, "extend.colors.neutral.850")
66
430
  },
67
- ".dark & .cm-content": {
68
- caretColor: "white"
431
+ '& [aria-readonly="true"] .cm-codeblock': {
432
+ display: "block",
433
+ paddingInline: "4px !important"
69
434
  },
70
- "& .cm-tooltip": {
71
- backgroundColor: "transparent",
72
- border: "none"
435
+ '& [aria-readonly="true"] .cm-codeblock-first': {
436
+ paddingTop: "4px !important",
437
+ borderTopLeftRadius: "4px",
438
+ borderTopRightRadius: "4px"
73
439
  },
74
- "& .cm-link": {
75
- color: get(tokens, "extend.colors.primary.500"),
76
- textDecoration: "underline"
440
+ '& [aria-readonly="true"] .cm-codeblock-last': {
441
+ paddingBottom: "4px !important",
442
+ borderBottomLeftRadius: "4px",
443
+ borderBottomRightRadius: "4px"
77
444
  }
445
+ });
446
+ var getLineRange = (lines, from, to) => {
447
+ const start = lines.findIndex((line) => line.from >= from);
448
+ const end = lines.findIndex((line) => line.to >= to);
449
+ return [
450
+ start,
451
+ end
452
+ ];
78
453
  };
79
-
80
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/change.ts
81
- import { StateField } from "@codemirror/state";
82
- var onChangeExtension = (onChange) => StateField.define({
83
- create: () => null,
84
- update: (_value, transaction) => {
85
- if (transaction.docChanged && onChange) {
86
- onChange(transaction.newDoc.toString());
454
+ var code2 = () => {
455
+ const getDecorations = (view) => {
456
+ const decorations = [];
457
+ if (view.state.readOnly) {
458
+ const text = view.state.doc.sliceString(0);
459
+ const matches = text.matchAll(CODE_REGEX);
460
+ const blocks = view.viewportLineBlocks;
461
+ for (const match of matches) {
462
+ const range = getLineRange(blocks, match.index, match.index + match[0].length);
463
+ for (let i = range[0]; i <= range[1]; i++) {
464
+ const block = blocks[i];
465
+ decorations.push(Decoration.line({
466
+ class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
467
+ }).range(block.from));
468
+ }
469
+ }
87
470
  }
88
- return null;
471
+ return Decoration.set(decorations);
472
+ };
473
+ return [
474
+ ViewPlugin.fromClass(class {
475
+ constructor(view) {
476
+ this.decorations = getDecorations(view);
477
+ }
478
+ update(update4) {
479
+ if (update4.docChanged || update4.viewportChanged) {
480
+ this.decorations = getDecorations(update4.view);
481
+ }
482
+ }
483
+ }, {
484
+ decorations: (value) => value.decorations
485
+ }),
486
+ styles
487
+ ];
488
+ };
489
+
490
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts
491
+ import { StateEffect as StateEffect2, StateField as StateField3 } from "@codemirror/state";
492
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration2, EditorView as EditorView4, MatchDecorator, ViewPlugin as ViewPlugin3, WidgetType } from "@codemirror/view";
493
+ import sortBy from "lodash.sortby";
494
+ import { debounce } from "@dxos/async";
495
+ import { invariant as invariant3 } from "@dxos/invariant";
496
+ import { log as log3 } from "@dxos/log";
497
+ import { nonNullable } from "@dxos/util";
498
+
499
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/util.ts
500
+ import { log } from "@dxos/log";
501
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/extensions/util.ts";
502
+ var callbackWrapper = (fn) => (...args) => {
503
+ try {
504
+ return fn(...args);
505
+ } catch (error) {
506
+ log.catch(error, void 0, {
507
+ F: __dxlog_file2,
508
+ L: 16,
509
+ S: void 0,
510
+ C: (f, a) => f(...a)
511
+ });
89
512
  }
90
- });
513
+ };
91
514
 
92
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts
93
- import { syntaxTree } from "@codemirror/language";
94
- import { RangeSetBuilder, StateField as StateField2 } from "@codemirror/state";
95
- import { Decoration, EditorView as EditorView2, WidgetType } from "@codemirror/view";
96
- var Link = class extends WidgetType {
97
- constructor(_anchor, _text, _url) {
98
- super();
99
- this._anchor = _anchor;
100
- this._text = _text;
101
- this._url = _url;
515
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
516
+ import { StateField as StateField2 } from "@codemirror/state";
517
+ import get2 from "lodash.get";
518
+ import { useEffect, useState } from "react";
519
+ import { yCollab } from "y-codemirror.next";
520
+ import * as Y from "yjs";
521
+ import { invariant as invariant2 } from "@dxos/invariant";
522
+ import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
523
+ import { arrayToString, stringToArray } from "@dxos/util";
524
+
525
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
526
+ import { Annotation, Facet, StateEffect, StateField } from "@codemirror/state";
527
+ import { ViewPlugin as ViewPlugin2 } from "@codemirror/view";
528
+ import * as automerge2 from "@dxos/automerge/automerge";
529
+
530
+ // packages/ui/react-ui-editor/src/hooks/automerge/semaphore.ts
531
+ import { next as automerge } from "@dxos/automerge/automerge";
532
+
533
+ // packages/ui/react-ui-editor/src/hooks/automerge/update-automerge.ts
534
+ import { next as am } from "@dxos/automerge/automerge";
535
+ var updateAutomerge = (field, handle, transactions, state) => {
536
+ const { lastHeads, path } = state.field(field);
537
+ let hasChanges = false;
538
+ for (const tr of transactions) {
539
+ tr.changes.iterChanges(() => {
540
+ hasChanges = true;
541
+ });
102
542
  }
103
- eq(other) {
104
- return this._text === other.text && this._url === other.url;
543
+ if (!hasChanges) {
544
+ return void 0;
105
545
  }
106
- toDOM(view) {
107
- const link = document.createElement("a");
108
- link.setAttribute("class", "cm-link");
109
- link.textContent = this._text;
110
- if (this._url) {
111
- link.setAttribute("target", "_blank");
112
- link.href = this._url;
113
- } else {
114
- link.onclick = () => {
115
- view.dispatch({
116
- selection: {
117
- anchor: this._anchor
118
- }
119
- });
120
- };
546
+ const newHeads = handle.changeAt(lastHeads, (doc) => {
547
+ for (const tr of transactions) {
548
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
549
+ am.splice(doc, path, fromA, toA - fromA, inserted.toString());
550
+ });
121
551
  }
122
- return link;
123
- }
124
- };
125
- var hyperlinkDecoration = ({ link } = {}) => {
126
- return StateField2.define({
127
- create: (state) => update(state, link),
128
- update: (_, tr) => update(tr.state, link),
129
- provide: (field) => EditorView2.decorations.from(field)
130
552
  });
553
+ return newHeads ?? void 0;
131
554
  };
132
- var update = (state, link) => {
133
- const builder = new RangeSetBuilder();
134
- const cursor = state.selection.main.head;
135
- syntaxTree(state).iterate({
136
- enter: (node) => {
137
- if (cursor < node.from || cursor > node.to) {
138
- if (node.name === "Link") {
139
- const marks = node.node.getChildren("LinkMark");
140
- const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
141
- const urlNode = node.node.getChild("URL");
142
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
143
- builder.add(node.from, node.to, Decoration.replace({
144
- widget: new Link(node.from, text, link ? url : void 0)
145
- }));
146
- return false;
147
- }
148
- }
149
- return true;
555
+
556
+ // packages/ui/react-ui-editor/src/hooks/automerge/update-codemirror.ts
557
+ import { ChangeSet } from "@codemirror/state";
558
+ var updateCodeMirror = (view, selection, target, patches) => {
559
+ for (const patch of patches) {
560
+ const changeSpec = handlePatch(patch, target, view.state);
561
+ if (changeSpec != null) {
562
+ const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
563
+ selection = selection.map(changeSet, 1);
564
+ view.dispatch({
565
+ changes: changeSet,
566
+ annotations: reconcileAnnotationType.of({})
567
+ });
150
568
  }
569
+ }
570
+ view.dispatch({
571
+ selection,
572
+ annotations: reconcileAnnotationType.of({})
151
573
  });
152
- return builder.finish();
153
574
  };
154
-
155
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.tsx
156
- import { hoverTooltip } from "@codemirror/view";
157
- import { tooltipContent } from "@dxos/react-ui-theme";
158
- var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
159
- var createHyperlinkTooltip = (onHover, regexp = markdownLinkRegexp) => hoverTooltip((view, pos) => {
160
- const { from, text } = view.state.doc.lineAt(pos);
161
- const p = pos - from;
162
- let idx = 0;
163
- let match;
164
- do {
165
- match = text.substring(idx).match(regexp);
166
- if (!match) {
167
- match = void 0;
168
- break;
169
- }
170
- idx = text.indexOf(match[0]);
171
- if (p >= idx && p < idx + match[0].length) {
172
- break;
173
- }
174
- idx += match[0].length;
175
- } while (true);
176
- if (!match) {
575
+ var handlePatch = (patch, target, state) => {
576
+ if (patch.action === "insert") {
577
+ return handleInsert(target, patch);
578
+ } else if (patch.action === "splice") {
579
+ return handleSplice(target, patch);
580
+ } else if (patch.action === "del") {
581
+ return handleDel(target, patch);
582
+ } else if (patch.action === "put") {
583
+ return handlePut(target, patch, state);
584
+ } else {
177
585
  return null;
178
586
  }
179
- const [, , url] = match ?? [];
180
- return {
181
- pos: idx + from,
182
- end: idx + from + match[0].length,
183
- above: true,
184
- create: () => {
185
- const el = document.createElement("a");
186
- el.innerText = "_";
187
- el.className = tooltipContent({}, "mb-2 p-1");
188
- el.setAttribute("target", "_blank");
189
- el.setAttribute("href", url);
190
- onHover(el, url);
191
- return {
192
- dom: el
193
- };
194
- }
195
- };
196
- });
197
-
198
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx
199
- import { Decoration as Decoration2, EditorView as EditorView3, MatchDecorator, ViewPlugin } from "@codemirror/view";
200
- var markdownLinkRegexp2 = /\[([^\]]+)]\(([^)]+)\)(!?)/gi;
201
- var linkDecorator = () => new MatchDecorator({
202
- regexp: markdownLinkRegexp2,
203
- decorate: (add, from, to, match, view) => {
204
- const [_, label, url] = match;
205
- const p0 = {
206
- start: from,
207
- end: from + 1
208
- };
209
- const p1 = {
210
- start: from + 1,
211
- end: from + 1 + label.length
212
- };
213
- const p2 = {
214
- start: p1.end,
215
- end: p1.end + 1
216
- };
217
- const p3 = {
218
- start: p1.end + 1,
219
- end: p1.end + 1 + url.length + 2
220
- };
221
- add(p0.start, p0.end, Decoration2.mark({
222
- class: "cm-hyperlink-bracket",
223
- _attributes: {
224
- style: "display: none"
225
- }
226
- }));
227
- add(p1.start, p1.end, Decoration2.mark({
228
- class: "cm-hyperlink-label"
229
- }));
230
- add(p2.start, p2.end, Decoration2.mark({
231
- class: "cm-hyperlink-bracket",
232
- _attributes: {
233
- style: "display: none"
234
- }
235
- }));
236
- add(p3.start, p3.end, Decoration2.mark({
237
- class: "cm-hyperlink-url",
238
- _attributes: {
239
- style: "display: none"
240
- }
241
- }));
587
+ };
588
+ var handleInsert = (target, patch) => {
589
+ const index = charPath(target, patch.path);
590
+ if (index == null) {
591
+ return [];
242
592
  }
243
- });
244
- function hyperLinkExtension() {
245
- return ViewPlugin.fromClass(class HyperLinkView {
246
- constructor(view) {
247
- this.decorator = linkDecorator();
248
- this.decorations = this.decorator.createDeco(view);
249
- }
250
- update(update2) {
251
- if (update2.docChanged || update2.viewportChanged) {
252
- this.decorations = this.decorator.updateDeco(update2, this.decorations);
253
- }
254
- }
255
- }, {
256
- decorations: (view) => view.decorations,
257
- eventHandlers: {
258
- // TODO(burdon): CLick to expand link.
259
- // https://discuss.codemirror.net/t/decorator-iteration-on-click-event/4226/2
260
- mousedown: function(event, view) {
261
- const pos = view.posAtDOM(event.target);
262
- if (pos !== 0) {
263
- console.log(this);
264
- }
265
- },
266
- // TODO(burdon): Hide when cursor moves away.
267
- keydown: (event, view) => {
268
- view.update([]);
269
- }
270
- }
271
- });
272
- }
273
- var hyperlinkStyle = EditorView3.baseTheme({
274
- ".cm-hyperlink-label": {
275
- cursor: "pointer",
276
- textDecoration: "underline"
277
- },
278
- ".cm-hyperlink-bracket": {
279
- opacity: 0.2
280
- },
281
- ".cm-hyperlink-url": {
282
- opacity: 0.5,
283
- fontFamily: "monospace"
284
- }
285
- });
286
- var hyperlinkWidget = [
287
- hyperLinkExtension(),
288
- hyperlinkStyle
289
- ];
290
-
291
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
292
- import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
293
- import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
294
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
295
- import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, foldKeymap, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
296
- import { languages } from "@codemirror/language-data";
297
- import { lintKeymap } from "@codemirror/lint";
298
- import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
299
- import { EditorState } from "@codemirror/state";
300
- import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
301
- import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView4, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
302
-
303
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/tags.ts
304
- import { styleTags, Tag } from "@lezer/highlight";
305
- var markdownTags = {
306
- headingMark: Tag.define(),
307
- quoteMark: Tag.define(),
308
- listMark: Tag.define(),
309
- linkMark: Tag.define(),
310
- emphasisMark: Tag.define(),
311
- codeMark: Tag.define(),
312
- codeText: Tag.define(),
313
- inlineCode: Tag.define(),
314
- url: Tag.define(),
315
- linkReference: Tag.define(),
316
- linkLabel: Tag.define()
317
- };
318
- var markdownTagsExtension = {
319
- props: [
320
- styleTags({
321
- HeaderMark: markdownTags.headingMark,
322
- QuoteMark: markdownTags.quoteMark,
323
- ListMark: markdownTags.listMark,
324
- LinkMark: markdownTags.linkMark,
325
- EmphasisMark: markdownTags.emphasisMark,
326
- CodeMark: markdownTags.codeMark,
327
- CodeText: markdownTags.codeText,
328
- InlineCode: markdownTags.inlineCode,
329
- URL: markdownTags.url,
330
- LinkReference: markdownTags.linkReference,
331
- LinkLabel: markdownTags.linkLabel
332
- })
333
- ]
334
- };
335
-
336
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/theme.ts
337
- import { markdownLanguage } from "@codemirror/lang-markdown";
338
- import { HighlightStyle } from "@codemirror/language";
339
- import { tags } from "@lezer/highlight";
340
- import get2 from "lodash.get";
341
- var chalky = "#e5c07b";
342
- var coral = "#e06c75";
343
- var cyan = "#56b6c2";
344
- var invalid = "#ffffff";
345
- var ivory = "#abb2bf";
346
- var stone = "#7d8799";
347
- var malibu = "#61afef";
348
- var sage = "#98c379";
349
- var whiskey = "#d19a66";
350
- var violet = "#c678dd";
351
- var highlightBackground = "#2c313a";
352
- var tooltipBackground = "#353a42";
353
- var monospace = get2(tokens, "fontFamily.mono", [
354
- "monospace"
355
- ]).join(",");
356
- var markdownTheme = {
357
- // TODO(thure): Consider whether these commented-out rules from one-dark-theme should be integrated.
358
- // '&': {
359
- // color: ivory,
360
- // backgroundColor: background
361
- // },
362
- // '.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
363
- // '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
364
- // backgroundColor: selection
365
- // },
366
- // '.cm-panels': { backgroundColor: darkBackground, color: ivory },
367
- // '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
368
- // '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
369
- // '.cm-searchMatch': {
370
- // backgroundColor: '#72a1ff59',
371
- // outline: '1px solid #457dff'
372
- // },
373
- // '.cm-searchMatch.cm-searchMatch-selected': {
374
- // backgroundColor: '#6199ff2f'
375
- // },
376
- // '.cm-activeLine': { backgroundColor: '#6699ff0b' },
377
- // '.cm-selectionMatch': { backgroundColor: '#aafe661a' },
378
- // '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
379
- // backgroundColor: '#bad0f847'
380
- // },
381
- // '.cm-gutters': {
382
- // backgroundColor: background,
383
- // color: stone,
384
- // border: 'none'
385
- // },
386
- // '.cm-activeLineGutter': {
387
- // backgroundColor: highlightBackground
388
- // },
389
- // '.cm-foldPlaceholder': {
390
- // backgroundColor: 'transparent',
391
- // border: 'none',
392
- // color: '#ddd'
393
- // },
394
- "& .cm-tooltip": {
395
- backgroundColor: "transparent",
396
- border: "none"
397
- },
398
- // '.dark & .cm-tooltip': {
399
- // border: 'none',
400
- // backgroundColor: tooltipBackground,
401
- // },
402
- // '.dark & .cm-tooltip .cm-tooltip-arrow:before': {
403
- // borderTopColor: 'transparent',
404
- // borderBottomColor: 'transparent',
405
- // },
406
- // '.dark & .cm-tooltip .cm-tooltip-arrow:after': {
407
- // borderTopColor: tooltipBackground,
408
- // borderBottomColor: tooltipBackground,
409
- // },
410
- // '.dark & .cm-tooltip-autocomplete': {
411
- // '& > ul > li[aria-selected]': {
412
- // backgroundColor: highlightBackground,
413
- // color: ivory,
414
- // },
415
- // },
416
- "&.cm-focused": {
417
- outline: "none"
418
- },
419
- "& .cm-line": {
420
- paddingInline: 0,
421
- lineHeight: 1.6,
422
- minBlockSize: "1.6em"
423
- },
424
- "& .cm-line *": {
425
- lineHeight: 1.6
426
- },
427
- "&.cm-focused .cm-selectionBackground, & .cm-selectionBackground": {
428
- background: get2(tokens, "extend.colors.primary.150", "#00ffff")
429
- },
430
- ".dark & .cm-selectionBackground, .dark &.cm-focused .cm-selectionBackground": {
431
- background: get2(tokens, "extend.colors.primary.850", "#00ffff")
432
- },
433
- "& .cm-selectionMatch": {
434
- background: get2(tokens, "extend.colors.primary.250", "#00ffff") + "44"
435
- },
436
- ".dark & .cm-selectionMatch": {
437
- background: get2(tokens, "extend.colors.primary.600", "#00ffff") + "44"
438
- },
439
- "& .cm-content": {
440
- padding: 0,
441
- caretColor: "black"
442
- },
443
- ".dark & .cm-content": {
444
- caretColor: "white"
445
- },
446
- "& .cm-cursor": {
447
- borderLeftColor: "black"
448
- },
449
- ".dark & .cm-cursor": {
450
- borderLeftColor: "white"
451
- },
452
- ".cm-placeholder": {
453
- fontFamily: get2(tokens, "fontFamily.body", []).join(",")
454
- },
455
- "& .cm-scroller": {
456
- fontFamily: get2(tokens, "fontFamily.mono", []).join(","),
457
- overflow: "visible"
458
- },
459
- "& .cm-activeLine": {
460
- backgroundColor: "transparent"
461
- },
462
- ".dark & .cm-activeLine": {
463
- backgroundColor: "transparent"
464
- },
465
- "& .cm-ySelectionInfo": {
466
- fontFamily: get2(tokens, "fontFamily.body", []).join(","),
467
- padding: "2px 4px",
468
- marginBlockStart: "-4px"
469
- },
470
- "& .cm-ySelection, & .cm-selectionMatch": {
471
- paddingBlockStart: ".15em",
472
- paddingBlockEnd: ".15em"
473
- },
474
- "& .cm-ySelection, & .cm-yLineSelection": {
475
- mixBlendMode: "multiply"
476
- },
477
- ".dark & .cm-ySelection, .dark & .cm-yLineSelection": {
478
- mixBlendMode: "screen"
479
- },
480
- "& .cm-ySelectionCaret": {
481
- display: "inline-block",
482
- insetBlockStart: ".1em",
483
- blockSize: "1.4em",
484
- verticalAlign: "top"
485
- },
486
- "& .cm-yLineSelection": {
487
- margin: "0"
488
- },
489
- ".cm-link": {
490
- color: "rgb(20 89 208)",
491
- textDecorationLine: "underline",
492
- textDecorationThickness: "1px",
493
- textUnderlineOffset: "2px",
494
- borderRadius: ".125rem"
495
- },
496
- // TODO(burdon): Document.
497
- ...Object.keys(get2(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
498
- const height = get2(tokens, [
499
- "extend",
500
- "fontSize",
501
- fontSize,
502
- 1,
503
- "lineHeight"
504
- ]);
505
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
506
- height
507
- };
508
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
509
- height
510
- };
511
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
512
- height
513
- };
514
- return acc;
515
- }, {})
516
- };
517
- var markdownHighlightStyle = HighlightStyle.define([
518
- {
519
- tag: [
520
- tags.keyword,
521
- tags.name,
522
- tags.deleted,
523
- tags.character,
524
- tags.propertyName,
525
- tags.macroName,
526
- tags.color,
527
- tags.constant(tags.name),
528
- tags.standard(tags.name),
529
- tags.definition(tags.name),
530
- tags.separator,
531
- tags.typeName,
532
- tags.className,
533
- tags.number,
534
- tags.changed,
535
- tags.annotation,
536
- tags.modifier,
537
- tags.self,
538
- tags.namespace,
539
- tags.operator,
540
- tags.operatorKeyword,
541
- tags.escape,
542
- tags.regexp,
543
- tags.special(tags.string),
544
- tags.meta,
545
- tags.comment,
546
- tags.atom,
547
- tags.bool,
548
- tags.special(tags.variableName),
549
- tags.processingInstruction,
550
- tags.string,
551
- tags.inserted,
552
- tags.invalid
553
- ],
554
- color: "inherit !important"
555
- },
556
- {
557
- tag: [
558
- tags.link,
559
- tags.url
560
- ],
561
- color: "inherit !important",
562
- textDecoration: "none !important"
563
- },
564
- {
565
- tag: [
566
- tags.function(tags.variableName),
567
- tags.labelName
568
- ],
569
- color: malibu,
570
- fontFamily: monospace
571
- },
572
- {
573
- tag: [
574
- markdownTags.codeMark,
575
- markdownTags.emphasisMark,
576
- markdownTags.headingMark,
577
- markdownTags.linkLabel,
578
- markdownTags.linkReference,
579
- markdownTags.listMark,
580
- markdownTags.quoteMark,
581
- markdownTags.url,
582
- tags.meta,
583
- tags.processingInstruction
584
- ],
585
- class: mark
586
- },
587
- {
588
- tag: [
589
- markdownTags.codeText,
590
- markdownTags.inlineCode
591
- ],
592
- class: "font-mono"
593
- },
594
- {
595
- tag: tags.emphasis,
596
- class: italic
597
- },
598
- {
599
- tag: tags.heading1,
600
- class: heading[1]
601
- },
602
- {
603
- tag: tags.heading2,
604
- class: heading[2]
605
- },
606
- {
607
- tag: tags.heading3,
608
- class: heading[3]
609
- },
610
- {
611
- tag: tags.heading4,
612
- class: heading[4]
613
- },
614
- {
615
- tag: tags.heading5,
616
- class: heading[5]
617
- },
618
- {
619
- tag: tags.heading6,
620
- class: heading[6]
621
- },
622
- {
623
- tag: tags.strikethrough,
624
- class: strikethrough
625
- },
626
- {
627
- tag: tags.strong,
628
- class: bold
629
- }
630
- ], {
631
- scope: markdownLanguage,
632
- all: {
633
- fontFamily: get2(tokens, "fontFamily.body", []).join(",")
634
- }
635
- });
636
-
637
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
638
- var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
639
- return [
640
- bracketMatching2(),
641
- closeBrackets2(),
642
- _placeholder && placeholder2(_placeholder),
643
- EditorState.allowMultipleSelections.of(true),
644
- EditorView4.lineWrapping,
645
- // autocompletion(),
646
- crosshairCursor(),
647
- dropCursor(),
648
- drawSelection(),
649
- highlightActiveLine(),
650
- highlightActiveLineGutter(),
651
- highlightSelectionMatches(),
652
- highlightSpecialChars(),
653
- history(),
654
- indentOnInput(),
655
- rectangularSelection(),
656
- keymap.of([
657
- ...closeBracketsKeymap,
658
- // ...completionKeymap,
659
- ...defaultKeymap,
660
- ...foldKeymap,
661
- ...historyKeymap,
662
- ...lintKeymap,
663
- ...searchKeymap,
664
- indentWithTab
665
- ]),
666
- // Main extension.
667
- // https://github.com/codemirror/lang-markdown
668
- markdown({
669
- base: markdownLanguage2,
670
- codeLanguages: languages,
671
- extensions: [
672
- // TODO(burdon): This seems to upgrade the parser.
673
- // GitHub flavored markdown bundle: Table, TaskList, Strikethrough, and Autolink.
674
- // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
675
- // https://github.github.com/gfm
676
- // GFM,
677
- // Custom styling.
678
- markdownTagsExtension
679
- ]
680
- }),
681
- // TODO(thure): All but one rule here apply to both themes; rename or refactor.
682
- syntaxHighlighting2(markdownHighlightStyle),
683
- themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2)
684
- ].filter(Boolean);
685
- };
686
-
687
- // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
688
- import { EditorState as EditorState2 } from "@codemirror/state";
689
- import { EditorView as EditorView5 } from "@codemirror/view";
690
- import { useFocusableGroup } from "@fluentui/react-tabster";
691
- import { vim } from "@replit/codemirror-vim";
692
- import defaultsDeep from "lodash.defaultsdeep";
693
- import get3 from "lodash.get";
694
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState } from "react";
695
- import { isDocAccessor as isDocAccessor2 } from "@dxos/echo-schema";
696
- import { useThemeContext } from "@dxos/react-ui";
697
-
698
- // packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
699
- import { useEffect, useMemo } from "react";
700
- import { yCollab } from "y-codemirror.next";
701
- import { generateName } from "@dxos/display-name";
702
- import { isDocAccessor } from "@dxos/echo-schema";
703
- import { getColorForValue } from "@dxos/react-ui-theme";
704
- import { YText } from "@dxos/text-model";
705
-
706
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/plugin.ts
707
- import { Annotation, Facet, StateEffect, StateField as StateField3 } from "@codemirror/state";
708
- import { ViewPlugin as ViewPlugin2 } from "@codemirror/view";
709
- import * as automerge2 from "@dxos/automerge/automerge";
710
-
711
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
712
- import { next as automerge } from "@dxos/automerge/automerge";
713
-
714
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/amToCodemirror.ts
715
- import { ChangeSet } from "@codemirror/state";
716
- var amToCodemirror_default = (view, selection, target, patches) => {
717
- for (const patch of patches) {
718
- const changeSpec = handlePatch(patch, target, view.state);
719
- if (changeSpec != null) {
720
- const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
721
- selection = selection.map(changeSet, 1);
722
- view.dispatch({
723
- changes: changeSet,
724
- annotations: reconcileAnnotationType.of({})
725
- });
726
- }
727
- }
728
- view.dispatch({
729
- selection,
730
- annotations: reconcileAnnotationType.of({})
731
- });
732
- };
733
- var handlePatch = (patch, target, state) => {
734
- if (patch.action === "insert") {
735
- return handleInsert(target, patch);
736
- } else if (patch.action === "splice") {
737
- return handleSplice(target, patch);
738
- } else if (patch.action === "del") {
739
- return handleDel(target, patch);
740
- } else if (patch.action === "put") {
741
- return handlePut(target, patch, state);
742
- } else {
743
- return null;
744
- }
745
- };
746
- var handleInsert = (target, patch) => {
747
- const index = charPath(target, patch.path);
748
- if (index == null) {
749
- return [];
750
- }
751
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
752
- return [
753
- {
754
- from: index,
755
- to: index,
756
- insert: text
593
+ const text = patch.values.map((v) => v ? v.toString() : "").join("");
594
+ return [
595
+ {
596
+ from: index,
597
+ to: index,
598
+ insert: text
757
599
  }
758
600
  ];
759
601
  };
@@ -818,30 +660,7 @@ var charPath = (textPath, candidatePath) => {
818
660
  return null;
819
661
  };
820
662
 
821
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/codeMirrorToAm.ts
822
- import { next as am } from "@dxos/automerge/automerge";
823
- var codeMirrorToAm_default = (field, handle, transactions, state) => {
824
- const { lastHeads, path } = state.field(field);
825
- let hasChanges = false;
826
- for (const tr of transactions) {
827
- tr.changes.iterChanges(() => {
828
- hasChanges = true;
829
- });
830
- }
831
- if (!hasChanges) {
832
- return void 0;
833
- }
834
- const newHeads = handle.changeAt(lastHeads, (doc) => {
835
- for (const tr of transactions) {
836
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
837
- am.splice(doc, path, fromA, toA - fromA, inserted.toString());
838
- });
839
- }
840
- });
841
- return newHeads ?? void 0;
842
- };
843
-
844
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
663
+ // packages/ui/react-ui-editor/src/hooks/automerge/semaphore.ts
845
664
  var PatchSemaphore = class {
846
665
  constructor(field) {
847
666
  this._inReconcile = false;
@@ -864,12 +683,12 @@ var PatchSemaphore = class {
864
683
  annotations: reconcileAnnotationType.of(true)
865
684
  });
866
685
  }
867
- let newHeads = codeMirrorToAm_default(this._field, handle, transactions, view.state);
686
+ let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
868
687
  if (newHeads === null || newHeads === void 0) {
869
688
  newHeads = automerge.getHeads(handle.docSync());
870
689
  }
871
690
  const diff = automerge.equals(oldHeads, newHeads) ? [] : automerge.diff(handle.docSync(), oldHeads, newHeads);
872
- amToCodemirror_default(view, selection, path, diff);
691
+ updateCodeMirror(view, selection, path, diff);
873
692
  view.dispatch({
874
693
  effects: updateHeads(newHeads),
875
694
  annotations: reconcileAnnotationType.of({})
@@ -882,7 +701,7 @@ var PatchSemaphore = class {
882
701
  }
883
702
  };
884
703
 
885
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/plugin.ts
704
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
886
705
  var effectType = StateEffect.define({});
887
706
  var updateHeads = (newHeads) => effectType.of({
888
707
  newHeads
@@ -893,7 +712,7 @@ var semaphoreFacet = Facet.define({
893
712
  combine: (values) => values.at(-1)
894
713
  });
895
714
  var automergePlugin = (handle, path) => {
896
- const stateField = StateField3.define({
715
+ const stateField = StateField.define({
897
716
  create: () => ({
898
717
  lastHeads: automerge2.getHeads(handle.docSync()),
899
718
  unreconciledTransactions: [],
@@ -931,8 +750,8 @@ var automergePlugin = (handle, path) => {
931
750
  this._view = view;
932
751
  handle.addListener("change", this._handleChange);
933
752
  }
934
- update(update2) {
935
- if (update2.transactions.length > 0 && update2.transactions.some((t) => !isReconcileTx(t))) {
753
+ update(update4) {
754
+ if (update4.transactions.length > 0 && update4.transactions.some((t) => !isReconcileTx(t))) {
936
755
  queueMicrotask(() => {
937
756
  this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
938
757
  });
@@ -953,47 +772,8 @@ var automergePlugin = (handle, path) => {
953
772
  var reconcileAnnotationType = Annotation.define();
954
773
  var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
955
774
 
956
- // packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
957
- var useCollaboration = (model, themeMode) => {
958
- const { provider, peer, content } = model;
959
- const extension = useMemo(() => {
960
- if (content instanceof YText) {
961
- return yCollab(content, provider?.awareness);
962
- } else if (isDocAccessor(content)) {
963
- return automergePlugin(content.handle, content.path);
964
- } else {
965
- return void 0;
966
- }
967
- }, []);
968
- useEffect(() => {
969
- if (provider && peer) {
970
- provider.awareness.setLocalStateField("user", {
971
- name: peer.name ?? generateName(peer.id),
972
- color: getColorForValue({
973
- value: peer.id,
974
- type: "color"
975
- }),
976
- colorLight: getColorForValue({
977
- value: peer.id,
978
- themeMode,
979
- type: "highlight"
980
- })
981
- });
982
- }
983
- }, [
984
- provider,
985
- peer,
986
- themeMode
987
- ]);
988
- return extension;
989
- };
990
-
991
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
992
- import { useMemo as useMemo2 } from "react";
993
- import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
994
-
995
775
  // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
996
- import * as random from "lib0/random";
776
+ import * as random2 from "lib0/random";
997
777
  var cursorColors = [
998
778
  {
999
779
  color: "#30bced",
@@ -1028,15 +808,15 @@ var cursorColors = [
1028
808
  light: "#1be7ff33"
1029
809
  }
1030
810
  ];
1031
- var cursorColor = cursorColors[random.uint32() % cursorColors.length];
811
+ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
1032
812
 
1033
813
  // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
1034
814
  import * as decoding from "lib0/decoding";
1035
815
  import * as encoding from "lib0/encoding";
1036
816
  import { Observable } from "lib0/observable";
1037
817
  import * as awarenessProtocol from "y-protocols/awareness";
1038
- import { log } from "@dxos/log";
1039
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
818
+ import { log as log2 } from "@dxos/log";
819
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1040
820
  var messageAwareness = 1;
1041
821
  var messageQueryAwareness = 3;
1042
822
  var SpaceAwarenessProvider = class extends Observable {
@@ -1067,13 +847,13 @@ var SpaceAwarenessProvider = class extends Observable {
1067
847
  return this._awareness;
1068
848
  }
1069
849
  _handleAwarenessUpdate({ added, updated, removed }, origin) {
1070
- log("awareness update", {
850
+ log2("awareness update", {
1071
851
  added,
1072
852
  updated,
1073
853
  removed,
1074
854
  origin
1075
855
  }, {
1076
- F: __dxlog_file,
856
+ F: __dxlog_file3,
1077
857
  L: 67,
1078
858
  S: this,
1079
859
  C: (f, a) => f(...a)
@@ -1085,8 +865,8 @@ var SpaceAwarenessProvider = class extends Observable {
1085
865
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
1086
866
  }
1087
867
  _handleSpaceMessage({ payload }) {
1088
- log("space message", payload, {
1089
- F: __dxlog_file,
868
+ log2("space message", payload, {
869
+ F: __dxlog_file3,
1090
870
  L: 79,
1091
871
  S: this,
1092
872
  C: (f, a) => f(...a)
@@ -1097,154 +877,1594 @@ var SpaceAwarenessProvider = class extends Observable {
1097
877
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
1098
878
  }
1099
879
  }
1100
- _readMessage(message) {
1101
- const decoder = decoding.createDecoder(message);
1102
- const encoder = encoding.createEncoder();
1103
- const messageType = decoding.readVarUint(decoder);
1104
- let sendReply = false;
1105
- switch (messageType) {
1106
- case messageAwareness: {
1107
- awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
1108
- break;
1109
- }
1110
- case messageQueryAwareness: {
1111
- encoding.writeVarUint(encoder, messageAwareness);
1112
- encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
1113
- sendReply = true;
1114
- break;
1115
- }
1116
- default: {
1117
- console.error("Invalid message:", messageType);
1118
- return encoder;
1119
- }
880
+ _readMessage(message) {
881
+ const decoder = decoding.createDecoder(message);
882
+ const encoder = encoding.createEncoder();
883
+ const messageType = decoding.readVarUint(decoder);
884
+ let sendReply = false;
885
+ switch (messageType) {
886
+ case messageAwareness: {
887
+ awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
888
+ break;
889
+ }
890
+ case messageQueryAwareness: {
891
+ encoding.writeVarUint(encoder, messageAwareness);
892
+ encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
893
+ sendReply = true;
894
+ break;
895
+ }
896
+ default: {
897
+ console.error("Invalid message:", messageType);
898
+ return encoder;
899
+ }
900
+ }
901
+ if (!sendReply) {
902
+ return null;
903
+ }
904
+ return encoder;
905
+ }
906
+ _handleBeforeUnload() {
907
+ awarenessProtocol.removeAwarenessStates(this._awareness, [
908
+ this._clientId
909
+ ], "window unload");
910
+ }
911
+ };
912
+
913
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
914
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
915
+ var modelState = StateField2.define({
916
+ create: () => void 0,
917
+ update: (model) => model
918
+ });
919
+ var useTextModel = (props) => {
920
+ const { identity, space, text } = props;
921
+ const [model, setModel] = useState(() => createModel(props));
922
+ useEffect(() => setModel(createModel(props)), [
923
+ identity,
924
+ space,
925
+ text
926
+ ]);
927
+ return model;
928
+ };
929
+ var createModel = (props) => {
930
+ const { text } = props;
931
+ if (isActualAutomergeObject(text)) {
932
+ return createAutomergeModel(props);
933
+ } else {
934
+ if (!text?.doc) {
935
+ return void 0;
936
+ }
937
+ return createYjsModel(props);
938
+ }
939
+ };
940
+ var createYjsModel = ({ identity, space, text }) => {
941
+ invariant2(text?.doc && text?.content, void 0, {
942
+ F: __dxlog_file4,
943
+ L: 97,
944
+ S: void 0,
945
+ A: [
946
+ "text?.doc && text?.content",
947
+ ""
948
+ ]
949
+ });
950
+ const provider = space ? new SpaceAwarenessProvider({
951
+ space,
952
+ doc: text.doc,
953
+ channel: `yjs.awareness.${text.id}`
954
+ }) : void 0;
955
+ const model = {
956
+ id: text.doc.guid,
957
+ content: text.content,
958
+ text: () => text.content.toString(),
959
+ // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
960
+ getCursorFromRange: (range) => {
961
+ const from = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content, range.from));
962
+ const to = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content, range.to, -1));
963
+ return [
964
+ arrayToString(from),
965
+ arrayToString(to)
966
+ ].join(":");
967
+ },
968
+ getRangeFromCursor: (cursor) => {
969
+ const parts = cursor.split(":");
970
+ const from = Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(parts[0])), text.doc);
971
+ const to = Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(parts[1])), text.doc);
972
+ return {
973
+ from: from.index,
974
+ to: to.index
975
+ };
976
+ },
977
+ extension: [
978
+ yCollab(text.content, provider?.awareness),
979
+ modelState.init(() => model)
980
+ ],
981
+ awareness: provider?.awareness,
982
+ peer: identity ? {
983
+ id: identity.identityKey.toHex(),
984
+ name: identity.profile?.displayName
985
+ } : void 0
986
+ };
987
+ return model;
988
+ };
989
+ var createAutomergeModel = ({ identity, text }) => {
990
+ const obj = text;
991
+ const doc = getRawDoc(obj, [
992
+ obj.field
993
+ ]);
994
+ const model = {
995
+ id: obj.id,
996
+ content: doc,
997
+ text: () => get2(doc.handle.docSync(), doc.path),
998
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
999
+ extension: [
1000
+ automergePlugin(doc.handle, doc.path),
1001
+ modelState.init(() => model)
1002
+ ],
1003
+ peer: identity ? {
1004
+ id: identity.identityKey.toHex(),
1005
+ name: identity.profile?.displayName
1006
+ } : void 0
1007
+ };
1008
+ return model;
1009
+ };
1010
+
1011
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts
1012
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts";
1013
+ var styles2 = EditorView4.baseTheme({
1014
+ "& .cm-bookmark": {
1015
+ cursor: "pointer",
1016
+ margin: "4px",
1017
+ padding: "4px",
1018
+ backgroundColor: "yellow"
1019
+ },
1020
+ "& .cm-bookmark-selected": {
1021
+ backgroundColor: "orange"
1022
+ },
1023
+ "& .cm-comment": {
1024
+ backgroundColor: "yellow"
1025
+ },
1026
+ "& .cm-comment-active": {
1027
+ backgroundColor: "orange"
1028
+ }
1029
+ });
1030
+ var marks = {
1031
+ highlight: Decoration2.mark({
1032
+ class: "cm-comment"
1033
+ }),
1034
+ highlightActive: Decoration2.mark({
1035
+ class: "cm-comment-active"
1036
+ })
1037
+ };
1038
+ var setCommentRange = StateEffect2.define();
1039
+ var setSelection = StateEffect2.define();
1040
+ var setCommentState = StateEffect2.define();
1041
+ var commentsStateField = StateField3.define({
1042
+ create: () => ({
1043
+ ranges: []
1044
+ }),
1045
+ update: (value, tr) => {
1046
+ for (const effect of tr.effects) {
1047
+ if (effect.is(setSelection)) {
1048
+ return {
1049
+ ...value,
1050
+ ...effect.value
1051
+ };
1052
+ }
1053
+ if (effect.is(setCommentRange)) {
1054
+ const { model, comments: comments2 } = effect.value;
1055
+ const ranges = comments2.map((comment) => {
1056
+ const range = model.getRangeFromCursor(comment.cursor);
1057
+ return {
1058
+ ...comment,
1059
+ ...range
1060
+ };
1061
+ });
1062
+ return {
1063
+ ...value,
1064
+ ranges
1065
+ };
1066
+ }
1067
+ if (effect.is(setCommentState)) {
1068
+ return effect.value;
1069
+ }
1070
+ }
1071
+ return value;
1072
+ }
1073
+ });
1074
+ var highlightDecorations = EditorView4.decorations.compute([
1075
+ commentsStateField
1076
+ ], (state) => {
1077
+ const { active, ranges } = state.field(commentsStateField);
1078
+ const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1079
+ if (selection.from === selection.to) {
1080
+ return void 0;
1081
+ }
1082
+ const range = {
1083
+ from: selection.from,
1084
+ to: selection.to
1085
+ };
1086
+ if (selection.id === active) {
1087
+ return marks.highlightActive.range(range.from, range.to);
1088
+ } else {
1089
+ return marks.highlight.range(range.from, range.to);
1090
+ }
1091
+ }).filter(nonNullable) ?? [];
1092
+ return Decoration2.set(decorations);
1093
+ });
1094
+ var BookmarkWidget = class extends WidgetType {
1095
+ constructor(_anchor, _id, _handleClick) {
1096
+ super();
1097
+ this._anchor = _anchor;
1098
+ this._id = _id;
1099
+ this._handleClick = _handleClick;
1100
+ invariant3(this._id, void 0, {
1101
+ F: __dxlog_file5,
1102
+ L: 150,
1103
+ S: this,
1104
+ A: [
1105
+ "this._id",
1106
+ ""
1107
+ ]
1108
+ });
1109
+ }
1110
+ eq(other) {
1111
+ return this._anchor === other._anchor && this._id === other._id;
1112
+ }
1113
+ toDOM() {
1114
+ const span = document.createElement("span");
1115
+ span.className = "cm-bookmark";
1116
+ span.textContent = "\u203B";
1117
+ if (this._handleClick) {
1118
+ span.onclick = () => this._handleClick();
1119
+ }
1120
+ return span;
1121
+ }
1122
+ };
1123
+ var comments = (options = {}) => {
1124
+ const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1125
+ const createCommentThread = (view) => {
1126
+ invariant3(options.onCreate, void 0, {
1127
+ F: __dxlog_file5,
1128
+ L: 204,
1129
+ S: void 0,
1130
+ A: [
1131
+ "options.onCreate",
1132
+ ""
1133
+ ]
1134
+ });
1135
+ const { head, from, to } = view.state.selection.main;
1136
+ if (from === to) {
1137
+ return false;
1138
+ }
1139
+ const model = view.state.field(modelState);
1140
+ const relPos = model?.getCursorFromRange?.({
1141
+ from,
1142
+ to
1143
+ });
1144
+ if (relPos) {
1145
+ const id = callbackWrapper(options.onCreate)(relPos);
1146
+ if (id) {
1147
+ view.dispatch({
1148
+ effects: setSelection.of({
1149
+ active: id
1150
+ }),
1151
+ selection: {
1152
+ anchor: from
1153
+ }
1154
+ });
1155
+ if (options.footnote) {
1156
+ const tag = `[^${id}]`;
1157
+ view.dispatch({
1158
+ changes: {
1159
+ from: head,
1160
+ insert: tag
1161
+ },
1162
+ selection: {
1163
+ anchor: head + tag.length
1164
+ }
1165
+ });
1166
+ }
1167
+ return true;
1168
+ }
1169
+ }
1170
+ return false;
1171
+ };
1172
+ const bookmarksViewPlugin = ViewPlugin3.fromClass(class BookmarkViewPlugin {
1173
+ static {
1174
+ // Match markdown footnotes (option).
1175
+ // https://www.markdownguide.org/extended-syntax/#footnotes
1176
+ this.bookmarkMatcher = new MatchDecorator({
1177
+ regexp: /\[\^(\w+)\]/g,
1178
+ decoration: (match, view, pos) => {
1179
+ const id = match[1];
1180
+ return Decoration2.replace({
1181
+ id,
1182
+ widget: new BookmarkWidget(pos, id)
1183
+ });
1184
+ }
1185
+ });
1186
+ }
1187
+ constructor(view) {
1188
+ this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
1189
+ }
1190
+ update(update4) {
1191
+ this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update4, this.bookmarks);
1192
+ }
1193
+ }, {
1194
+ decorations: (instance) => instance.bookmarks,
1195
+ provide: (plugin) => EditorView4.atomicRanges.of((view) => {
1196
+ return view.plugin(plugin)?.bookmarks || Decoration2.none;
1197
+ })
1198
+ });
1199
+ return [
1200
+ bookmarksViewPlugin,
1201
+ commentsStateField,
1202
+ highlightDecorations,
1203
+ styles2,
1204
+ //
1205
+ // Keymap.
1206
+ //
1207
+ options.onCreate ? keymap3.of([
1208
+ {
1209
+ key: options?.key ?? "meta-'",
1210
+ run: createCommentThread
1211
+ }
1212
+ ]) : [],
1213
+ //
1214
+ // Hover tooltip (for key shortcut hints, etc.)
1215
+ // TODO(burdon): Factor out to generic hints extension for current selection/line.
1216
+ //
1217
+ options.onHover ? hoverTooltip((view, pos) => {
1218
+ const selection = view.state.selection.main;
1219
+ if (selection && pos >= selection.from && pos <= selection.to) {
1220
+ return {
1221
+ pos: selection.from,
1222
+ end: selection.to,
1223
+ above: true,
1224
+ create: () => {
1225
+ const el = document.createElement("div");
1226
+ options.onHover?.(el);
1227
+ return {
1228
+ dom: el,
1229
+ offset: {
1230
+ x: 0,
1231
+ y: 8
1232
+ }
1233
+ };
1234
+ }
1235
+ };
1236
+ }
1237
+ return null;
1238
+ }, {
1239
+ hideOnChange: true,
1240
+ hoverTime: 1e3
1241
+ }) : [],
1242
+ //
1243
+ // Monitor cursor movement and text updates.
1244
+ // TODO(burdon): Is there a better (finer grained) way to do this?
1245
+ //
1246
+ EditorView4.updateListener.of(({ view, state, startState, changes }) => {
1247
+ {
1248
+ let mod = false;
1249
+ const model = state.field(modelState);
1250
+ const { active, ranges } = state.field(commentsStateField);
1251
+ changes.iterChanges((from, to, from2, to2) => {
1252
+ invariant3(model, void 0, {
1253
+ F: __dxlog_file5,
1254
+ L: 333,
1255
+ S: void 0,
1256
+ A: [
1257
+ "model",
1258
+ ""
1259
+ ]
1260
+ });
1261
+ ranges.forEach((range) => {
1262
+ if (from2 === to2) {
1263
+ const newRange = model.getRangeFromCursor(range.cursor);
1264
+ if (newRange.to - newRange.from === 0) {
1265
+ log3.info("deleted comment", {
1266
+ thread: range.id
1267
+ }, {
1268
+ F: __dxlog_file5,
1269
+ L: 339,
1270
+ S: void 0,
1271
+ C: (f, a) => f(...a)
1272
+ });
1273
+ }
1274
+ }
1275
+ if (from <= range.to) {
1276
+ const newRange = model.getRangeFromCursor(range.cursor);
1277
+ Object.assign(range, newRange);
1278
+ mod = true;
1279
+ }
1280
+ });
1281
+ });
1282
+ if (mod) {
1283
+ view.dispatch({
1284
+ effects: setCommentState.of({
1285
+ active,
1286
+ ranges
1287
+ })
1288
+ });
1289
+ }
1290
+ }
1291
+ {
1292
+ const { head } = state.selection.main;
1293
+ let min = Infinity;
1294
+ const { active, closest, ranges } = state.field(commentsStateField);
1295
+ const selected = {
1296
+ active: void 0,
1297
+ closest: void 0
1298
+ };
1299
+ ranges.forEach((comment) => {
1300
+ const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1301
+ if (head >= comment.from && head <= comment.to) {
1302
+ selected.active = comment.id;
1303
+ }
1304
+ if (d < min) {
1305
+ selected.closest = comment.id;
1306
+ min = d;
1307
+ }
1308
+ });
1309
+ if (selected.active !== active || selected.closest !== closest) {
1310
+ view.dispatch({
1311
+ effects: setSelection.of(selected)
1312
+ });
1313
+ handleSelect({
1314
+ ...selected,
1315
+ ranges: ranges.map((range) => ({
1316
+ ...range,
1317
+ location: view.coordsAtPos(range.from)
1318
+ }))
1319
+ });
1320
+ }
1321
+ }
1322
+ })
1323
+ ];
1324
+ };
1325
+
1326
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hr.ts
1327
+ import { Decoration as Decoration3, EditorView as EditorView5, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1328
+ import get3 from "lodash.get";
1329
+ var styles3 = EditorView5.baseTheme({
1330
+ "& .cm-hr": {
1331
+ // TODO(burdon): ???
1332
+ // Note that block-level decorations should not have vertical margins,
1333
+ borderBottom: `1px solid ${get3(tokens, "extend.colors.neutral.200")}`
1334
+ }
1335
+ });
1336
+ var HorizontalRuleWidget = class extends WidgetType2 {
1337
+ constructor(_pos) {
1338
+ super();
1339
+ this._pos = _pos;
1340
+ }
1341
+ eq(other) {
1342
+ return this._pos === other._pos;
1343
+ }
1344
+ toDOM(view) {
1345
+ const el = document.createElement("div");
1346
+ el.className = "cm-hr";
1347
+ return el;
1348
+ }
1349
+ };
1350
+ var placeholderMatcher = new MatchDecorator2({
1351
+ regexp: /^---$/g,
1352
+ decoration: (match, view, pos) => Decoration3.replace({
1353
+ widget: new HorizontalRuleWidget(pos)
1354
+ })
1355
+ });
1356
+ var hr = () => [
1357
+ styles3,
1358
+ ViewPlugin4.fromClass(class {
1359
+ constructor(view) {
1360
+ this.rules = placeholderMatcher.createDeco(view);
1361
+ }
1362
+ update(update4) {
1363
+ this.rules = placeholderMatcher.updateDeco(update4, this.rules);
1364
+ }
1365
+ }, {
1366
+ decorations: (instance) => instance.rules,
1367
+ // TODO(burdon): Is this really atomic (cursor can move into ---).
1368
+ provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1369
+ return view.plugin(plugin)?.rules || Decoration3.none;
1370
+ })
1371
+ })
1372
+ ];
1373
+
1374
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/image.ts
1375
+ import { syntaxTree } from "@codemirror/language";
1376
+ import { RangeSetBuilder, StateField as StateField4 } from "@codemirror/state";
1377
+ import { Decoration as Decoration4, EditorView as EditorView6, WidgetType as WidgetType3 } from "@codemirror/view";
1378
+ var image = (options = {}) => {
1379
+ return StateField4.define({
1380
+ create: (state) => update(state, options),
1381
+ update: (_, tr) => update(tr.state, options),
1382
+ provide: (field) => EditorView6.decorations.from(field)
1383
+ });
1384
+ };
1385
+ var update = (state, options) => {
1386
+ const builder = new RangeSetBuilder();
1387
+ const cursor = state.selection.main.head;
1388
+ syntaxTree(state).iterate({
1389
+ enter: (node) => {
1390
+ if (node.name === "Image") {
1391
+ const urlNode = node.node.getChild("URL");
1392
+ if (urlNode) {
1393
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
1394
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
1395
+ builder.add(hide ? node.from : node.to, node.to, Decoration4.replace({
1396
+ block: true,
1397
+ widget: new ImageWidget(url)
1398
+ }));
1399
+ }
1400
+ }
1401
+ }
1402
+ });
1403
+ return builder.finish();
1404
+ };
1405
+ var ImageWidget = class extends WidgetType3 {
1406
+ constructor(_url) {
1407
+ super();
1408
+ this._url = _url;
1409
+ }
1410
+ // TODO(burdon): Does this need to be unique for each position?
1411
+ eq(other) {
1412
+ return this._url === other._url;
1413
+ }
1414
+ toDOM(view) {
1415
+ const img = document.createElement("img");
1416
+ img.setAttribute("src", this._url);
1417
+ img.setAttribute("class", "cm-image");
1418
+ return img;
1419
+ }
1420
+ };
1421
+
1422
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
1423
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1424
+ import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField5 } from "@codemirror/state";
1425
+ import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1426
+ var link = (options = {}) => {
1427
+ return StateField5.define({
1428
+ create: (state) => update2(state, options),
1429
+ update: (_, tr) => update2(tr.state, options),
1430
+ provide: (field) => EditorView7.decorations.from(field)
1431
+ });
1432
+ };
1433
+ var LinkButton = class extends WidgetType4 {
1434
+ constructor(_pos, _url, _onAttach) {
1435
+ super();
1436
+ this._pos = _pos;
1437
+ this._url = _url;
1438
+ this._onAttach = _onAttach;
1439
+ }
1440
+ eq(other) {
1441
+ return this._pos === other._pos && this._url === other._url;
1442
+ }
1443
+ toDOM(view) {
1444
+ const el = document.createElement("span");
1445
+ this._onAttach(el, this._url);
1446
+ return el;
1447
+ }
1448
+ };
1449
+ var LinkText = class extends WidgetType4 {
1450
+ constructor(_pos, _text, _url) {
1451
+ super();
1452
+ this._pos = _pos;
1453
+ this._text = _text;
1454
+ this._url = _url;
1455
+ }
1456
+ eq(other) {
1457
+ return this._pos === other._pos && this._url === other._url;
1458
+ }
1459
+ toDOM(view) {
1460
+ const link2 = document.createElement("a");
1461
+ link2.setAttribute("class", "cm-link");
1462
+ link2.textContent = this._text;
1463
+ if (this._url) {
1464
+ link2.setAttribute("rel", "noreferrer");
1465
+ link2.setAttribute("target", "_blank");
1466
+ link2.href = this._url;
1467
+ } else {
1468
+ link2.onclick = () => {
1469
+ view.dispatch({
1470
+ selection: {
1471
+ anchor: this._pos
1472
+ }
1473
+ });
1474
+ };
1475
+ }
1476
+ return link2;
1477
+ }
1478
+ };
1479
+ var update2 = (state, options) => {
1480
+ const builder = new RangeSetBuilder2();
1481
+ const cursor = state.selection.main.head;
1482
+ syntaxTree2(state).iterate({
1483
+ enter: (node) => {
1484
+ if (node.name === "Link") {
1485
+ const marks2 = node.node.getChildren("LinkMark");
1486
+ const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1487
+ const urlNode = node.node.getChild("URL");
1488
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1489
+ if (!url) {
1490
+ return false;
1491
+ }
1492
+ if (state.readOnly || cursor < node.from || cursor > node.to) {
1493
+ builder.add(node.from, node.to, Decoration5.replace({
1494
+ widget: new LinkText(node.from, text, options.link ? url : void 0)
1495
+ }));
1496
+ }
1497
+ if (options.onRender) {
1498
+ builder.add(node.to, node.to, Decoration5.replace({
1499
+ widget: new LinkButton(node.from, url, options.onRender)
1500
+ }));
1501
+ }
1502
+ return false;
1503
+ }
1504
+ }
1505
+ });
1506
+ return builder.finish();
1507
+ };
1508
+
1509
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
1510
+ import { StateField as StateField6 } from "@codemirror/state";
1511
+ var listener = ({ onChange }) => {
1512
+ return StateField6.define({
1513
+ create: () => null,
1514
+ update: (_value, transaction) => {
1515
+ if (transaction.docChanged && onChange) {
1516
+ onChange(transaction.newDoc.toString());
1517
+ }
1518
+ return null;
1519
+ }
1520
+ });
1521
+ };
1522
+
1523
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
1524
+ import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1525
+ import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1526
+ import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
1527
+ import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1528
+ import { languages } from "@codemirror/language-data";
1529
+ import { searchKeymap } from "@codemirror/search";
1530
+ import { EditorState } from "@codemirror/state";
1531
+ import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1532
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView8, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1533
+
1534
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
1535
+ import { markdownLanguage } from "@codemirror/lang-markdown";
1536
+ import { HighlightStyle } from "@codemirror/language";
1537
+ import { tags, styleTags, Tag } from "@lezer/highlight";
1538
+ import { Table } from "@lezer/markdown";
1539
+ import get4 from "lodash.get";
1540
+ var markdownTags = {
1541
+ Blockquote: Tag.define(),
1542
+ CodeMark: Tag.define(),
1543
+ CodeText: Tag.define(),
1544
+ EmphasisMark: Tag.define(),
1545
+ HeaderMark: Tag.define(),
1546
+ // HorizontalRule: Tag.define(),
1547
+ InlineCode: Tag.define(),
1548
+ LinkLabel: Tag.define(),
1549
+ LinkReference: Tag.define(),
1550
+ ListMark: Tag.define(),
1551
+ QuoteMark: Tag.define(),
1552
+ URL: Tag.define(),
1553
+ // Custom.
1554
+ TableCell: Tag.define()
1555
+ };
1556
+ Table.defineNodes?.forEach((node) => {
1557
+ switch (node?.name) {
1558
+ case "TableCell": {
1559
+ node.style = markdownTags.TableCell;
1560
+ break;
1561
+ }
1562
+ }
1563
+ });
1564
+ var markdownTagsExtensions = [
1565
+ Table,
1566
+ {
1567
+ props: [
1568
+ styleTags(markdownTags)
1569
+ ]
1570
+ }
1571
+ ];
1572
+ var markdownHighlightStyle = (readonly) => {
1573
+ return HighlightStyle.define([
1574
+ {
1575
+ tag: [
1576
+ tags.keyword,
1577
+ tags.name,
1578
+ tags.deleted,
1579
+ tags.character,
1580
+ tags.propertyName,
1581
+ tags.macroName,
1582
+ tags.color,
1583
+ tags.constant(tags.name),
1584
+ tags.standard(tags.name),
1585
+ tags.definition(tags.name),
1586
+ tags.separator,
1587
+ tags.typeName,
1588
+ tags.className,
1589
+ tags.number,
1590
+ tags.changed,
1591
+ tags.annotation,
1592
+ tags.modifier,
1593
+ tags.self,
1594
+ tags.namespace,
1595
+ tags.operator,
1596
+ tags.operatorKeyword,
1597
+ tags.escape,
1598
+ tags.regexp,
1599
+ tags.special(tags.string),
1600
+ tags.meta,
1601
+ tags.comment,
1602
+ tags.atom,
1603
+ tags.bool,
1604
+ tags.special(tags.variableName),
1605
+ tags.processingInstruction,
1606
+ tags.string,
1607
+ tags.inserted,
1608
+ tags.invalid
1609
+ ],
1610
+ color: "inherit !important"
1611
+ },
1612
+ // Markdown marks.
1613
+ {
1614
+ tag: [
1615
+ tags.meta,
1616
+ tags.processingInstruction,
1617
+ markdownTags.LinkLabel,
1618
+ markdownTags.LinkReference,
1619
+ markdownTags.ListMark
1620
+ ],
1621
+ class: mark
1622
+ },
1623
+ // Markdown marks.
1624
+ {
1625
+ tag: [
1626
+ markdownTags.CodeMark,
1627
+ markdownTags.HeaderMark,
1628
+ markdownTags.QuoteMark,
1629
+ markdownTags.EmphasisMark
1630
+ ],
1631
+ class: readonly ? "hidden" : mark
1632
+ },
1633
+ // E.g., code block language (after ```).
1634
+ {
1635
+ tag: [
1636
+ tags.function(tags.variableName),
1637
+ tags.labelName
1638
+ ],
1639
+ class: readonly ? "hidden" : codeMark
1640
+ },
1641
+ {
1642
+ tag: [
1643
+ tags.monospace
1644
+ ],
1645
+ class: "font-mono"
1646
+ },
1647
+ // Headings.
1648
+ {
1649
+ tag: tags.heading1,
1650
+ class: heading(1, readonly)
1651
+ },
1652
+ {
1653
+ tag: tags.heading2,
1654
+ class: heading(2, readonly)
1655
+ },
1656
+ {
1657
+ tag: tags.heading3,
1658
+ class: heading(3, readonly)
1659
+ },
1660
+ {
1661
+ tag: tags.heading4,
1662
+ class: heading(4, readonly)
1663
+ },
1664
+ {
1665
+ tag: tags.heading5,
1666
+ class: heading(5, readonly)
1667
+ },
1668
+ {
1669
+ tag: tags.heading6,
1670
+ class: heading(6, readonly)
1671
+ },
1672
+ // Emphasis.
1673
+ {
1674
+ tag: tags.emphasis,
1675
+ class: italic
1676
+ },
1677
+ {
1678
+ tag: tags.strong,
1679
+ class: bold
1680
+ },
1681
+ {
1682
+ tag: tags.strikethrough,
1683
+ class: strikethrough
1684
+ },
1685
+ // Naked URLs.
1686
+ {
1687
+ tag: [
1688
+ markdownTags.URL
1689
+ ],
1690
+ class: inlineUrl
1691
+ },
1692
+ // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
1693
+ // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1694
+ // when a language is specified. In this case, the syntax highlighting extensions will colorize
1695
+ // the code, but all other CSS properties will be inherited.
1696
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font.
1697
+ {
1698
+ tag: [
1699
+ markdownTags.CodeText,
1700
+ markdownTags.InlineCode
1701
+ ],
1702
+ class: code
1703
+ },
1704
+ {
1705
+ tag: [
1706
+ markdownTags.QuoteMark
1707
+ ],
1708
+ class: blockquote
1709
+ },
1710
+ // TODO(burdon): Replace with extension.
1711
+ // {
1712
+ // tag: [markdownTags.HorizontalRule],
1713
+ // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1714
+ // },
1715
+ // TODO(burdon): Only if being edited.
1716
+ {
1717
+ tag: [
1718
+ markdownTags.TableCell
1719
+ ],
1720
+ class: "font-mono"
1721
+ }
1722
+ ], {
1723
+ scope: markdownLanguage,
1724
+ all: {
1725
+ fontFamily: get4(tokens, "fontFamily.body", []).join(",")
1726
+ }
1727
+ });
1728
+ };
1729
+
1730
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
1731
+ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1732
+ return [
1733
+ EditorState.allowMultipleSelections.of(true),
1734
+ EditorState.tabSize.of(2),
1735
+ EditorView8.lineWrapping,
1736
+ customKeymap(),
1737
+ bracketMatching2(),
1738
+ closeBrackets2(),
1739
+ crosshairCursor(),
1740
+ dropCursor(),
1741
+ drawSelection2(),
1742
+ highlightActiveLine(),
1743
+ history(),
1744
+ indentOnInput(),
1745
+ _placeholder && placeholder2(_placeholder),
1746
+ // Main extension.
1747
+ // https://github.com/codemirror/lang-markdown
1748
+ // https://codemirror.net/5/mode/markdown/index.html (demo).
1749
+ markdown({
1750
+ // GRM by default (vs strict CommonMark):
1751
+ // Table, TaskList, Strikethrough, and Autolink.
1752
+ // NOTE: This extends the parser; it doesn't affect rendering.
1753
+ // https://github.github.com/gfm
1754
+ // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1755
+ base: markdownLanguage2,
1756
+ // Languages for syntax highlighting fenced code blocks.
1757
+ codeLanguages: languages,
1758
+ // Parser extensions.
1759
+ extensions: [
1760
+ // GFM provided by default.
1761
+ markdownTagsExtensions
1762
+ ]
1763
+ }),
1764
+ // https://github.com/codemirror/theme-one-dark
1765
+ themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1766
+ // Custom styles.
1767
+ syntaxHighlighting2(markdownHighlightStyle(readonly))
1768
+ ].filter(Boolean);
1769
+ };
1770
+ var customKeymap = () => keymap4.of([
1771
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1772
+ indentWithTab,
1773
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
1774
+ ...standardKeymap,
1775
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
1776
+ ...historyKeymap,
1777
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1778
+ ...closeBracketsKeymap,
1779
+ // https://codemirror.net/docs/ref/#search.searchKeymap
1780
+ ...searchKeymap
1781
+ ]);
1782
+
1783
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/mention.ts
1784
+ import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1785
+ var mention = ({ onSearch }) => {
1786
+ return autocompletion2({
1787
+ closeOnBlur: false,
1788
+ override: [
1789
+ (context) => {
1790
+ const match = context.matchBefore(/@(\w+)?/);
1791
+ if (!match || match.from === match.to && !context.explicit) {
1792
+ return null;
1793
+ }
1794
+ console.log(match);
1795
+ return {
1796
+ from: match.from,
1797
+ options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1798
+ label: `@${value}`
1799
+ }))
1800
+ };
1801
+ }
1802
+ ]
1803
+ });
1804
+ };
1805
+
1806
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/mermaid.ts
1807
+ var mermaid = () => {
1808
+ return [];
1809
+ };
1810
+
1811
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/table.ts
1812
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1813
+ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField7 } from "@codemirror/state";
1814
+ import { Decoration as Decoration6, EditorView as EditorView9, WidgetType as WidgetType5 } from "@codemirror/view";
1815
+ var table = (options = {}) => {
1816
+ return StateField7.define({
1817
+ create: (state) => update3(state, options),
1818
+ update: (_, tr) => update3(tr.state, options),
1819
+ provide: (field) => EditorView9.decorations.from(field)
1820
+ });
1821
+ };
1822
+ var update3 = (state, options) => {
1823
+ const builder = new RangeSetBuilder3();
1824
+ const cursor = state.selection.main.head;
1825
+ const tables = [];
1826
+ const getTable = () => tables[tables.length - 1];
1827
+ const getRow = () => {
1828
+ const table2 = getTable();
1829
+ return table2.rows?.[table2.rows.length - 1];
1830
+ };
1831
+ syntaxTree3(state).iterate({
1832
+ enter: (node) => {
1833
+ switch (node.name) {
1834
+ case "Table": {
1835
+ tables.push({
1836
+ from: node.from,
1837
+ to: node.to
1838
+ });
1839
+ break;
1840
+ }
1841
+ case "TableHeader": {
1842
+ getTable().header = [];
1843
+ break;
1844
+ }
1845
+ case "TableRow": {
1846
+ (getTable().rows ??= []).push([]);
1847
+ break;
1848
+ }
1849
+ case "TableCell": {
1850
+ const row = getRow();
1851
+ if (row) {
1852
+ row.push(state.sliceDoc(node.from, node.to));
1853
+ } else {
1854
+ getTable().header?.push(state.sliceDoc(node.from, node.to));
1855
+ }
1856
+ break;
1857
+ }
1858
+ }
1859
+ }
1860
+ });
1861
+ tables.forEach((table2) => {
1862
+ const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1863
+ hide && builder.add(table2.from, table2.to, Decoration6.replace({
1864
+ block: true,
1865
+ widget: new TableWidget(table2)
1866
+ }));
1867
+ builder.add(table2.from, table2.to, Decoration6.mark({
1868
+ class: "cm-table"
1869
+ }));
1870
+ });
1871
+ return builder.finish();
1872
+ };
1873
+ var TableWidget = class extends WidgetType5 {
1874
+ constructor(_table) {
1875
+ super();
1876
+ this._table = _table;
1877
+ }
1878
+ eq(other) {
1879
+ return this._table.from === other?._table?.from;
1880
+ }
1881
+ toDOM(view) {
1882
+ const table2 = document.createElement("table");
1883
+ {
1884
+ const header = table2.appendChild(document.createElement("thead"));
1885
+ const tr = header.appendChild(document.createElement("tr"));
1886
+ this._table.header?.forEach((cell) => {
1887
+ const th = document.createElement("th");
1888
+ th.setAttribute("class", "cm-table-head");
1889
+ tr.appendChild(th).textContent = cell;
1890
+ });
1891
+ }
1892
+ {
1893
+ const body = table2.appendChild(document.createElement("tbody"));
1894
+ this._table.rows?.forEach((row) => {
1895
+ const tr = body.appendChild(document.createElement("tr"));
1896
+ row.forEach((cell) => {
1897
+ const td = document.createElement("td");
1898
+ td.setAttribute("class", "cm-table-cell");
1899
+ tr.appendChild(td).textContent = cell;
1900
+ });
1901
+ });
1902
+ }
1903
+ return table2;
1904
+ }
1905
+ };
1906
+
1907
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
1908
+ import { EditorView as EditorView10, Decoration as Decoration7, WidgetType as WidgetType6, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1909
+ var styles4 = EditorView10.baseTheme({
1910
+ "& .cm-task-item": {
1911
+ paddingLeft: "4px",
1912
+ paddingRight: "8px"
1913
+ }
1914
+ });
1915
+ var CheckboxWidget = class extends WidgetType6 {
1916
+ constructor(_pos, _checked, _indent, _onCheck) {
1917
+ super();
1918
+ this._pos = _pos;
1919
+ this._checked = _checked;
1920
+ this._indent = _indent;
1921
+ this._onCheck = _onCheck;
1922
+ }
1923
+ eq(other) {
1924
+ return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
1925
+ }
1926
+ toDOM(view) {
1927
+ const wrap = document.createElement("span");
1928
+ wrap.className = "cm-task-item";
1929
+ wrap.setAttribute("aria-hidden", "true");
1930
+ wrap.style.setProperty("margin-left", this._indent * 24 + "px");
1931
+ const input = wrap.appendChild(document.createElement("input"));
1932
+ input.type = "checkbox";
1933
+ input.checked = this._checked;
1934
+ if (!this._onCheck) {
1935
+ input.setAttribute("disabled", "true");
1120
1936
  }
1121
- if (!sendReply) {
1122
- return null;
1937
+ if (this._onCheck) {
1938
+ input.onchange = (event) => {
1939
+ this._onCheck?.(event.target.checked);
1940
+ return true;
1941
+ };
1123
1942
  }
1124
- return encoder;
1943
+ return wrap;
1125
1944
  }
1126
- _handleBeforeUnload() {
1127
- awarenessProtocol.removeAwarenessStates(this._awareness, [
1128
- this._clientId
1129
- ], "window unload");
1945
+ ignoreEvent() {
1946
+ return false;
1130
1947
  }
1131
1948
  };
1949
+ var tasklist = (options = {}) => {
1950
+ const taskMatcher = new MatchDecorator3({
1951
+ regexp: /^(\s*)- \[([ xX])\]\s/g,
1952
+ decoration: (match, view, pos) => {
1953
+ const indent = Math.floor(match[1].length / 2);
1954
+ const checked = match[2] === "x" || match[2] === "X";
1955
+ return Decoration7.replace({
1956
+ widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
1957
+ const idx = pos + match[0].indexOf("[") + 1;
1958
+ view.dispatch({
1959
+ changes: {
1960
+ from: idx,
1961
+ to: idx + 1,
1962
+ insert: checked2 ? "x" : " "
1963
+ },
1964
+ // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
1965
+ selection: {
1966
+ anchor: idx + 3
1967
+ }
1968
+ });
1969
+ })
1970
+ });
1971
+ }
1972
+ });
1973
+ const tasks = ViewPlugin5.fromClass(class {
1974
+ constructor(view) {
1975
+ this.tasks = taskMatcher.createDeco(view);
1976
+ }
1977
+ update(update4) {
1978
+ this.tasks = taskMatcher.updateDeco(update4, this.tasks);
1979
+ }
1980
+ }, {
1981
+ decorations: (value) => value.tasks,
1982
+ provide: (plugin) => EditorView10.atomicRanges.of((view) => {
1983
+ return view.plugin(plugin)?.tasks || Decoration7.none;
1984
+ })
1985
+ });
1986
+ return [
1987
+ tasks,
1988
+ styles4
1989
+ ];
1990
+ };
1132
1991
 
1133
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1134
- var useTextModel = ({ identity, space, text }) => {
1135
- const provider = useMemo2(() => {
1136
- if (isActualAutomergeObject(text)) {
1137
- return void 0;
1992
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.ts
1993
+ import { hoverTooltip as hoverTooltip2 } from "@codemirror/view";
1994
+ import { tooltipContent } from "@dxos/react-ui-theme";
1995
+ var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1996
+ var tooltip = ({ onHover, regexp = markdownLinkRegexp }) => hoverTooltip2((view, pos) => {
1997
+ const { from, text } = view.state.doc.lineAt(pos);
1998
+ const p = pos - from;
1999
+ let idx = 0;
2000
+ let match;
2001
+ do {
2002
+ match = text.substring(idx).match(regexp);
2003
+ if (!match) {
2004
+ match = void 0;
2005
+ break;
1138
2006
  }
1139
- if (!space || !text?.doc) {
1140
- return void 0;
2007
+ idx = text.indexOf(match[0]);
2008
+ if (p >= idx && p < idx + match[0].length) {
2009
+ break;
1141
2010
  }
1142
- return new SpaceAwarenessProvider({
1143
- space,
1144
- doc: text.doc,
1145
- channel: `yjs.awareness.${text.id}`
1146
- });
1147
- }, [
1148
- identity,
1149
- space,
1150
- text,
1151
- text?.doc
1152
- ]);
1153
- const content = useMemo2(() => {
1154
- if (isActualAutomergeObject(text)) {
1155
- const obj = text;
1156
- return getRawDoc(obj, [
1157
- obj.field
1158
- ]);
1159
- } else {
1160
- return text?.content;
2011
+ idx += match[0].length;
2012
+ } while (true);
2013
+ if (!match) {
2014
+ return null;
2015
+ }
2016
+ const [, , url] = match ?? [];
2017
+ return {
2018
+ pos: idx + from,
2019
+ end: idx + from + match[0].length,
2020
+ above: true,
2021
+ create: () => {
2022
+ const el = document.createElement("div");
2023
+ el.className = tooltipContent({}, "pli-2 plb-1");
2024
+ onHover(el, url);
2025
+ return {
2026
+ dom: el,
2027
+ offset: {
2028
+ x: 0,
2029
+ y: 4
2030
+ }
2031
+ };
1161
2032
  }
1162
- }, [
1163
- text
1164
- ]);
1165
- if (isActualAutomergeObject(text)) {
1166
- const obj = text;
1167
- return {
1168
- id: obj.id,
1169
- content,
1170
- provider: void 0,
1171
- peer: identity ? {
1172
- id: identity.identityKey.toHex(),
1173
- name: identity.profile?.displayName
1174
- } : void 0
2033
+ };
2034
+ });
2035
+
2036
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/typewriter.ts
2037
+ import { keymap as keymap5 } from "@codemirror/view";
2038
+ var defaultItems = [
2039
+ "hello world!",
2040
+ "this is a test.",
2041
+ "this is [DXOS](https://dxos.org)"
2042
+ ];
2043
+ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2044
+ let t;
2045
+ let idx = 0;
2046
+ return [
2047
+ keymap5.of([
2048
+ {
2049
+ // Reset.
2050
+ key: "alt-meta-'",
2051
+ run: (view) => {
2052
+ clearTimeout(t);
2053
+ idx = 0;
2054
+ return true;
2055
+ }
2056
+ },
2057
+ {
2058
+ // Next prompt.
2059
+ // TODO(burdon): Press 1-9 to select prompt?
2060
+ key: "shift-meta-'",
2061
+ run: (view) => {
2062
+ clearTimeout(t);
2063
+ const text = items[idx++];
2064
+ if (idx === items?.length) {
2065
+ idx = 0;
2066
+ }
2067
+ let i = 0;
2068
+ const insert = (d = 0) => {
2069
+ t = setTimeout(() => {
2070
+ const pos = view.state.selection.main.head;
2071
+ view.dispatch({
2072
+ changes: {
2073
+ from: pos,
2074
+ insert: text[i++]
2075
+ },
2076
+ selection: {
2077
+ anchor: pos + 1
2078
+ }
2079
+ });
2080
+ if (i < text.length) {
2081
+ insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
2082
+ }
2083
+ }, d);
2084
+ };
2085
+ insert();
2086
+ return true;
2087
+ }
2088
+ }
2089
+ ])
2090
+ ];
2091
+ };
2092
+
2093
+ // packages/ui/react-ui-editor/src/components/TextEditor/themes/default.ts
2094
+ import get5 from "lodash.get";
2095
+ var defaultTheme = {
2096
+ //
2097
+ // Main layout:
2098
+ // https://codemirror.net/examples/styling
2099
+ //
2100
+ // <div class="cm-editor [cm-focused] [generated classes]">
2101
+ // <div class="cm-scroller">
2102
+ // <div class="cm-gutters">
2103
+ // <div class="cm-gutter [...]">
2104
+ // <div class="cm-gutterElement">...</div>
2105
+ // </div>
2106
+ // </div>
2107
+ // <div class="cm-content" role="textbox" contenteditable="true">
2108
+ // <div class="cm-line"></div>
2109
+ // </div>
2110
+ // <div class="cm-selectionLayer">
2111
+ // <div class="cm-selectionBackground"></div>
2112
+ // </div>
2113
+ // <div class="cm-cursorLayer">
2114
+ // <div class="cm-cursor"></div>
2115
+ // </div>
2116
+ // </div>
2117
+ // </div>
2118
+ //
2119
+ "&": {},
2120
+ "&.cm-focused": {
2121
+ outline: "none"
2122
+ },
2123
+ "& .cm-scroller": {
2124
+ overflow: "visible",
2125
+ fontFamily: get5(tokens, "fontFamily.mono", []).join(",")
2126
+ },
2127
+ "& .cm-content": {
2128
+ padding: 0,
2129
+ // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
2130
+ fontSize: "16px"
2131
+ },
2132
+ "&light .cm-content": {
2133
+ color: get5(tokens, "extend.colors.neutral.900", "black"),
2134
+ caretColor: "black"
2135
+ },
2136
+ "&dark .cm-content": {
2137
+ color: get5(tokens, "extend.colors.neutral.100", "white"),
2138
+ caretColor: "white"
2139
+ },
2140
+ //
2141
+ // Cursor
2142
+ //
2143
+ "&light .cm-cursor, &light .cm-dropCursor": {
2144
+ borderLeft: "2px solid black"
2145
+ },
2146
+ "&dark .cm-cursor, &dark .cm-dropCursor": {
2147
+ borderLeft: "2px solid white"
2148
+ },
2149
+ "& .cm-placeholder": {
2150
+ fontWeight: 100
2151
+ },
2152
+ //
2153
+ // line
2154
+ //
2155
+ "& .cm-line": {
2156
+ paddingInline: 0
2157
+ },
2158
+ "& .cm-activeLine": {
2159
+ background: "inherit"
2160
+ },
2161
+ //
2162
+ // Selection
2163
+ //
2164
+ "&light .cm-selectionBackground": {
2165
+ background: get5(tokens, "extend.colors.primary.100")
2166
+ },
2167
+ "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2168
+ background: get5(tokens, "extend.colors.primary.200")
2169
+ },
2170
+ "&dark .cm-selectionBackground": {
2171
+ background: get5(tokens, "extend.colors.primary.700")
2172
+ },
2173
+ "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2174
+ background: get5(tokens, "extend.colors.primary.600")
2175
+ },
2176
+ //
2177
+ // Search
2178
+ //
2179
+ "&light .cm-searchMatch": {
2180
+ backgroundColor: get5(tokens, "extend.colors.yellow.100")
2181
+ },
2182
+ "&dark .cm-searchMatch": {
2183
+ backgroundColor: get5(tokens, "extend.colors.yellow.700")
2184
+ },
2185
+ //
2186
+ // collaboration
2187
+ // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2188
+ //
2189
+ "&light .cm-ySelection, &light .cm-yLineSelection": {
2190
+ mixBlendMode: "multiply"
2191
+ },
2192
+ "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2193
+ mixBlendMode: "screen"
2194
+ },
2195
+ "& .cm-ySelectionInfo": {
2196
+ padding: "2px 4px",
2197
+ marginBlockStart: "-4px"
2198
+ },
2199
+ "& .cm-ySelection, & .cm-selectionMatch": {
2200
+ paddingBlockStart: ".15em",
2201
+ paddingBlockEnd: ".15em"
2202
+ },
2203
+ "& .cm-ySelectionCaret": {
2204
+ display: "inline-block",
2205
+ insetBlockStart: ".1em",
2206
+ blockSize: "1.4em",
2207
+ verticalAlign: "top"
2208
+ },
2209
+ "& .cm-yLineSelection": {
2210
+ margin: 0
2211
+ },
2212
+ //
2213
+ // link
2214
+ //
2215
+ "& .cm-link": {
2216
+ color: get5(tokens, "extend.colors.primary.500"),
2217
+ textDecorationLine: "underline",
2218
+ textDecorationThickness: "1px",
2219
+ textUnderlineOffset: "2px",
2220
+ borderRadius: ".125rem",
2221
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2222
+ },
2223
+ //
2224
+ // tooltip
2225
+ //
2226
+ "& .cm-tooltip": {
2227
+ border: "none"
2228
+ },
2229
+ "& .cm-tooltip-below": {},
2230
+ //
2231
+ // autocomplete
2232
+ //
2233
+ "& .cm-tooltip-autocomplete": {
2234
+ marginTop: "4px",
2235
+ marginLeft: "-3px"
2236
+ },
2237
+ "& .cm-tooltip-autocomplete ul li": {},
2238
+ "& .cm-tooltip-autocomplete ul li[aria-selected]": {},
2239
+ "& .cm-completionIcon": {
2240
+ display: "none"
2241
+ },
2242
+ "& .cm-completionLabel": {
2243
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2244
+ },
2245
+ "& .cm-completionMatchedText": {
2246
+ textDecoration: "none"
2247
+ },
2248
+ //
2249
+ // widgets
2250
+ //
2251
+ "& .cm-widgetBuffer": {
2252
+ display: "none",
2253
+ height: 0
2254
+ },
2255
+ //
2256
+ // table
2257
+ //
2258
+ "& .cm-table *": {
2259
+ fontFamily: `${get5(tokens, "fontFamily.mono", []).join(",")} !important`,
2260
+ textDecoration: "none !important"
2261
+ },
2262
+ "& .cm-table-head": {
2263
+ padding: "2px 16px 2px 0px",
2264
+ borderBottom: `1px solid ${get5(tokens, "extend.colors.neutral.500")}`,
2265
+ fontWeight: 100,
2266
+ textAlign: "left",
2267
+ color: get5(tokens, "extend.colors.neutral.500")
2268
+ },
2269
+ "& .cm-table-cell": {
2270
+ padding: "2px 16px 2px 0px"
2271
+ },
2272
+ //
2273
+ // image
2274
+ //
2275
+ "& .cm-image": {
2276
+ margin: "0.5rem 0"
2277
+ },
2278
+ //
2279
+ // font size
2280
+ // TODO(thure): This appears to be the best or only way to set selection caret heights, but it's far more verbose than it needs to be.
2281
+ //
2282
+ ...Object.keys(get5(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2283
+ const height = get5(tokens, [
2284
+ "extend",
2285
+ "fontSize",
2286
+ fontSize,
2287
+ 1,
2288
+ "lineHeight"
2289
+ ]);
2290
+ acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2291
+ height
1175
2292
  };
2293
+ acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2294
+ height
2295
+ };
2296
+ acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2297
+ height
2298
+ };
2299
+ return acc;
2300
+ }, {}),
2301
+ /**
2302
+ * Panels
2303
+ * TODO(burdon): Needs styling attention (esp. dark mode).
2304
+ * https://github.com/codemirror/search/blob/main/src/search.ts#L745
2305
+ *
2306
+ * Find/replace panel.
2307
+ * <div class="cm-announced">...</div>
2308
+ * <div class="cm-scroller">...</div>
2309
+ * <div class="cm-panels cm-panels-bottom">
2310
+ * <div class="cm-search cm-panel">
2311
+ * <input class="cm-textfield" />
2312
+ * <button class="cm-button">...</button>
2313
+ * <label><input type="checkbox" />...</label>
2314
+ * </div>
2315
+ * </div
2316
+ */
2317
+ "& .cm-panels": {
2318
+ border: `1px solid ${get5(tokens, "extend.colors.neutral.200")}`
2319
+ },
2320
+ "& .cm-panel": {
2321
+ background: get5(tokens, "extend.colors.neutral.50"),
2322
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2323
+ },
2324
+ "& .cm-button": {
2325
+ margin: "4px",
2326
+ fontFamily: get5(tokens, "fontFamily.body", []).join(","),
2327
+ background: get5(tokens, "extend.colors.neutral.100"),
2328
+ backgroundImage: "none",
2329
+ border: "none",
2330
+ "&:hover": {
2331
+ background: get5(tokens, "extend.colors.neutral.200")
2332
+ },
2333
+ "&:active": {
2334
+ background: get5(tokens, "extend.colors.neutral.300"),
2335
+ backgroundImage: "none"
2336
+ }
2337
+ },
2338
+ "& .cm-panel input[type=checkbox]": {
2339
+ marginRight: "0.4rem !important"
1176
2340
  }
1177
- if (!text?.doc || !text?.content) {
1178
- return void 0;
2341
+ };
2342
+ var textTheme = {
2343
+ "& .cm-scroller": {
2344
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2345
+ },
2346
+ "& .cm-placeholder": {
2347
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2348
+ }
2349
+ };
2350
+ var markdownTheme = {
2351
+ // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2352
+ "& .cm-placeholder": {
2353
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2354
+ }
2355
+ };
2356
+ var codeTheme = {
2357
+ "& .cm-scroller": {
2358
+ fontFamily: get5(tokens, "fontFamily.mono", []).join(",")
2359
+ },
2360
+ "& .cm-placeholder": {
2361
+ fontFamily: get5(tokens, "fontFamily.mono", []).join(",")
1179
2362
  }
1180
- return {
1181
- id: text.doc.guid,
1182
- content: text.content,
1183
- provider,
1184
- peer: identity ? {
1185
- id: identity.identityKey.toHex(),
1186
- name: identity.profile?.displayName
1187
- } : void 0
1188
- };
1189
2363
  };
1190
2364
 
1191
2365
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2366
+ import { EditorState as EditorState2 } from "@codemirror/state";
2367
+ import { EditorView as EditorView11 } from "@codemirror/view";
2368
+ import { useFocusableGroup } from "@fluentui/react-tabster";
2369
+ import { vim } from "@replit/codemirror-vim";
2370
+ import defaultsDeep2 from "lodash.defaultsdeep";
2371
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState as useState2 } from "react";
2372
+ import { generateName } from "@dxos/display-name";
2373
+ import { useThemeContext } from "@dxos/react-ui";
2374
+ import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
1192
2375
  var EditorModes = [
1193
2376
  "default",
1194
2377
  "vim"
1195
2378
  ];
1196
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots, editorMode }, forwardedRef) => {
2379
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, comments: comments2, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
1197
2380
  const { themeMode } = useThemeContext();
1198
2381
  const tabsterDOMAttribute = useFocusableGroup({
1199
2382
  tabBehavior: "limited"
1200
2383
  });
1201
- const [parent, setParent] = useState(null);
1202
- const [state, setState] = useState();
1203
- const [view, setView] = useState();
2384
+ const [root, setRoot] = useState2(null);
2385
+ const [{ state = void 0, view = void 0 } = {}, setEditor] = useState2();
1204
2386
  useImperativeHandle(forwardedRef, () => ({
1205
- editor: parent,
2387
+ root,
1206
2388
  state,
1207
2389
  view
1208
2390
  }), [
1209
2391
  view,
1210
2392
  state,
1211
- parent
2393
+ root
2394
+ ]);
2395
+ const { awareness, peer } = model;
2396
+ useEffect2(() => {
2397
+ if (awareness && peer) {
2398
+ awareness.setLocalStateField("user", {
2399
+ name: peer.name ?? generateName(peer.id),
2400
+ color: getColorForValue({
2401
+ value: peer.id,
2402
+ type: "color"
2403
+ }),
2404
+ colorLight: getColorForValue({
2405
+ value: peer.id,
2406
+ themeMode,
2407
+ type: "highlight"
2408
+ })
2409
+ });
2410
+ }
2411
+ }, [
2412
+ awareness,
2413
+ peer,
2414
+ themeMode
2415
+ ]);
2416
+ useEffect2(() => {
2417
+ if (view && comments2?.length) {
2418
+ view.dispatch({
2419
+ effects: setCommentRange.of({
2420
+ model,
2421
+ comments: comments2
2422
+ })
2423
+ });
2424
+ }
2425
+ }, [
2426
+ view,
2427
+ comments2
1212
2428
  ]);
1213
- const collaboration = useCollaboration(model, themeMode);
1214
2429
  useEffect2(() => {
1215
- if (!parent) {
2430
+ if (!root) {
1216
2431
  return;
1217
2432
  }
1218
- const initialText = isDocAccessor2(model.content) ? get3(model.content.handle.docSync(), model.content.path) : model.content?.toString();
1219
2433
  const state2 = EditorState2.create({
1220
- doc: initialText,
2434
+ doc: model.text(),
1221
2435
  extensions: [
1222
- // TODO(burdon): Factor out VIM mode?
2436
+ readonly && EditorState2.readOnly.of(readonly),
2437
+ // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
1223
2438
  editorMode === "vim" && vim(),
1224
- // Replication.
1225
- collaboration,
1226
2439
  // Theme.
1227
- slots?.editor?.theme && EditorView5.theme(slots?.editor?.theme),
2440
+ // TODO(burdon): Make theme configurable.
2441
+ EditorView11.baseTheme(defaultTheme),
2442
+ EditorView11.theme(slots?.editor?.theme ?? {}),
2443
+ EditorView11.darkTheme.of(themeMode === "dark"),
2444
+ // Storage and replication.
2445
+ model.extension,
1228
2446
  // Custom.
1229
2447
  ...extensions
1230
2448
  ].filter(Boolean)
1231
2449
  });
1232
- setState(state2);
1233
2450
  view?.destroy();
1234
- setView(new EditorView5({
2451
+ setEditor({
1235
2452
  state: state2,
1236
- parent
1237
- }));
2453
+ view: new EditorView11({
2454
+ state: state2,
2455
+ parent: root
2456
+ })
2457
+ });
1238
2458
  return () => {
1239
2459
  view?.destroy();
1240
- setView(void 0);
1241
- setState(void 0);
2460
+ setEditor(void 0);
1242
2461
  };
1243
2462
  }, [
1244
- parent,
1245
- model.content,
1246
- themeMode,
1247
- editorMode
2463
+ root,
2464
+ model,
2465
+ readonly,
2466
+ editorMode,
2467
+ themeMode
1248
2468
  ]);
1249
2469
  const handleKeyUp = useCallback((event) => {
1250
2470
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
@@ -1254,7 +2474,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots
1254
2474
  break;
1255
2475
  }
1256
2476
  case "Escape": {
1257
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && parent?.focus();
2477
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
1258
2478
  break;
1259
2479
  }
1260
2480
  }
@@ -1264,52 +2484,66 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots
1264
2484
  ]);
1265
2485
  return /* @__PURE__ */ React.createElement("div", {
1266
2486
  key: model.id,
1267
- ref: setParent,
2487
+ ref: setRoot,
1268
2488
  tabIndex: 0,
1269
2489
  ...slots?.root,
1270
2490
  ...editorMode !== "vim" && tabsterDOMAttribute,
1271
2491
  onKeyUp: handleKeyUp
1272
2492
  });
1273
2493
  });
1274
- var TextEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
2494
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
1275
2495
  const { themeMode } = useThemeContext();
1276
- const extensions = [
1277
- ..._extensions ?? [],
1278
- basicBundle({
1279
- themeMode,
1280
- placeholder: slots?.editor?.placeholder
1281
- })
1282
- ];
2496
+ const slots = defaultsDeep2({}, _slots, defaultTextSlots);
1283
2497
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1284
2498
  ref: forwardedRef,
1285
- extensions,
1286
- slots: defaultsDeep({}, slots, {
1287
- editor: {
1288
- theme: basicTheme
1289
- }
1290
- }),
2499
+ readonly,
2500
+ extensions: [
2501
+ basicBundle({
2502
+ readonly,
2503
+ themeMode,
2504
+ placeholder: slots?.editor?.placeholder
2505
+ }),
2506
+ ...extensions
2507
+ ],
2508
+ slots,
1291
2509
  ...props
1292
2510
  });
1293
2511
  });
1294
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
2512
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
1295
2513
  const { themeMode } = useThemeContext();
1296
- const extensions = [
1297
- ..._extensions ?? [],
1298
- markdownBundle({
1299
- themeMode
1300
- })
1301
- ];
2514
+ const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
1302
2515
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1303
2516
  ref: forwardedRef,
1304
- extensions,
1305
- slots: defaultsDeep({}, slots, {
1306
- editor: {
1307
- theme: markdownTheme
1308
- }
1309
- }),
2517
+ readonly,
2518
+ extensions: [
2519
+ markdownBundle({
2520
+ readonly,
2521
+ themeMode,
2522
+ placeholder: slots?.editor?.placeholder
2523
+ }),
2524
+ ...extensions
2525
+ ],
2526
+ slots,
1310
2527
  ...props
1311
2528
  });
1312
2529
  });
2530
+ var defaultSlots = {
2531
+ root: {
2532
+ className: mx3("p-2", inputSurface)
2533
+ }
2534
+ };
2535
+ var defaultTextSlots = {
2536
+ ...defaultSlots,
2537
+ editor: {
2538
+ theme: textTheme
2539
+ }
2540
+ };
2541
+ var defaultMarkdownSlots = {
2542
+ ...defaultSlots,
2543
+ editor: {
2544
+ theme: markdownTheme
2545
+ }
2546
+ };
1313
2547
  export {
1314
2548
  BaseTextEditor,
1315
2549
  Doc,
@@ -1318,34 +2552,39 @@ export {
1318
2552
  SpaceAwarenessProvider,
1319
2553
  TextEditor,
1320
2554
  TextKind,
1321
- YText2 as YText,
2555
+ YText,
1322
2556
  YXmlFragment,
2557
+ autocomplete,
1323
2558
  basicBundle,
1324
- basicTheme,
1325
- chalky,
1326
- coral,
1327
- createHyperlinkTooltip,
2559
+ blast,
2560
+ code2 as code,
2561
+ codeTheme,
2562
+ comments,
1328
2563
  cursorColor,
1329
- cyan,
1330
- highlightBackground,
1331
- hyperLinkExtension,
1332
- hyperlinkDecoration,
1333
- hyperlinkStyle,
1334
- hyperlinkWidget,
1335
- invalid,
1336
- ivory,
1337
- malibu,
2564
+ defaultMarkdownSlots,
2565
+ defaultOptions,
2566
+ defaultSlots,
2567
+ defaultTextSlots,
2568
+ defaultTheme,
2569
+ hr,
2570
+ image,
2571
+ link,
2572
+ listener,
1338
2573
  markdownBundle,
1339
2574
  markdownHighlightStyle,
2575
+ markdownTags,
2576
+ markdownTagsExtensions,
1340
2577
  markdownTheme,
1341
- onChangeExtension,
1342
- sage,
1343
- stone,
2578
+ mention,
2579
+ mermaid,
2580
+ modelState,
2581
+ setCommentRange,
2582
+ table,
1344
2583
  tags2 as tags,
1345
- tooltipBackground,
1346
- useCollaboration,
1347
- useTextModel,
1348
- violet,
1349
- whiskey
2584
+ tasklist,
2585
+ textTheme,
2586
+ tooltip,
2587
+ typewriter,
2588
+ useTextModel
1350
2589
  };
1351
2590
  //# sourceMappingURL=index.mjs.map