@dxos/react-ui-editor 0.3.10-next.ef70620 → 0.3.11-main.0e40963

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