@dxos/react-ui-editor 0.3.10-main.5c4230b → 0.3.10-main.5e11230

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 (148) hide show
  1. package/dist/lib/browser/index.mjs +1644 -791
  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 +56 -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 +7 -3
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +9 -4
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +10 -0
  11. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/extensions/{basic/bundle.d.ts → basic.d.ts} +3 -2
  13. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -0
  14. package/dist/types/src/components/TextEditor/extensions/blast.d.ts +25 -0
  15. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +1 -0
  16. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +18 -0
  17. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -0
  18. package/dist/types/src/components/TextEditor/extensions/demo.d.ts +11 -0
  19. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +1 -0
  20. package/dist/types/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.d.ts → experimental.d.ts} +2 -2
  21. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -0
  22. package/dist/types/src/components/TextEditor/extensions/image.d.ts +4 -0
  23. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +1 -0
  24. package/dist/types/src/components/TextEditor/extensions/index.d.ts +11 -2
  25. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  26. package/dist/types/src/components/TextEditor/extensions/link.d.ts +11 -0
  27. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -0
  28. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +9 -0
  29. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -0
  30. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +9 -1
  31. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  32. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +37 -0
  33. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -0
  34. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +1 -1
  35. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -1
  36. package/dist/types/src/components/TextEditor/extensions/mention.d.ts +6 -0
  37. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +1 -0
  38. package/dist/types/src/components/TextEditor/extensions/table.d.ts +8 -0
  39. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +1 -0
  40. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +3 -0
  41. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -0
  42. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +12 -0
  43. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +1 -0
  44. package/dist/types/src/components/TextEditor/index.d.ts +2 -0
  45. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  46. package/dist/types/src/components/TextEditor/themes/default.d.ts +25 -0
  47. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -0
  48. package/dist/types/src/components/TextEditor/themes/index.d.ts +2 -0
  49. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +1 -0
  50. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.d.ts +3 -0
  51. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +1 -0
  52. package/dist/types/src/hooks/automerge/automerge.stories.d.ts +16 -0
  53. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
  54. package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
  55. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/index.d.ts +1 -1
  56. package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
  57. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
  58. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts +5 -0
  59. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts.map +1 -0
  60. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts +6 -0
  61. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts.map +1 -0
  62. package/dist/types/src/hooks/automerge/translation/index.d.ts +3 -0
  63. package/dist/types/src/hooks/automerge/translation/index.d.ts.map +1 -0
  64. package/dist/types/src/hooks/index.d.ts +0 -1
  65. package/dist/types/src/hooks/index.d.ts.map +1 -1
  66. package/dist/types/src/hooks/useTextModel.d.ts +4 -4
  67. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  68. package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +2 -1
  69. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
  70. package/dist/types/src/styles/markdown.d.ts +14 -11
  71. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  72. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  73. package/package.json +21 -32
  74. package/src/components/TextEditor/MarkdownEditor.stories.tsx +350 -0
  75. package/src/components/TextEditor/TextEditor.stories.tsx +46 -43
  76. package/src/components/TextEditor/TextEditor.tsx +71 -45
  77. package/src/components/TextEditor/extensions/autocomplete.ts +59 -0
  78. package/src/components/TextEditor/extensions/{basic/bundle.ts → basic.ts} +6 -3
  79. package/src/components/TextEditor/extensions/blast.ts +387 -0
  80. package/src/components/TextEditor/extensions/comments.ts +172 -0
  81. package/src/components/TextEditor/extensions/demo.ts +69 -0
  82. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.tsx → experimental.tsx} +15 -20
  83. package/src/components/TextEditor/extensions/image.ts +67 -0
  84. package/src/components/TextEditor/extensions/index.ts +11 -2
  85. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkDecoration.ts → link.ts} +75 -38
  86. package/src/components/TextEditor/extensions/{change.ts → listener.ts} +5 -4
  87. package/src/components/TextEditor/extensions/markdown/bundle.ts +56 -59
  88. package/src/components/TextEditor/extensions/markdown/highlight.ts +207 -0
  89. package/src/components/TextEditor/extensions/markdown/index.ts +1 -1
  90. package/src/components/TextEditor/extensions/mention.ts +31 -0
  91. package/src/components/TextEditor/extensions/table.ts +122 -0
  92. package/src/components/TextEditor/extensions/tasklist.ts +121 -0
  93. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkTooltip.tsx → tooltip.tsx} +9 -9
  94. package/src/components/TextEditor/index.ts +3 -0
  95. package/src/components/TextEditor/themes/default.ts +274 -0
  96. package/src/components/TextEditor/themes/index.ts +5 -0
  97. package/src/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.ts +12 -9
  98. package/src/{automerge → hooks/automerge}/automerge.stories.tsx +5 -3
  99. package/src/{automerge/automerge-plugin → hooks/automerge}/plugin.ts +1 -0
  100. package/src/{automerge/automerge-plugin/amToCodemirror.ts → hooks/automerge/translation/automerge-to-codemirror.ts} +10 -2
  101. package/src/{automerge/automerge-plugin/codeMirrorToAm.ts → hooks/automerge/translation/codemirror-to-automerge.ts} +7 -4
  102. package/src/hooks/automerge/translation/index.ts +6 -0
  103. package/src/hooks/index.ts +0 -1
  104. package/src/hooks/useTextModel.ts +52 -39
  105. package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +2 -1
  106. package/src/styles/markdown.ts +32 -24
  107. package/src/testing/replicator.ts +6 -6
  108. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
  109. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts +0 -6
  110. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
  111. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts +0 -7
  112. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
  113. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
  114. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
  115. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
  116. package/dist/types/src/automerge/automerge.stories.d.ts +0 -9
  117. package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
  118. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
  119. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
  120. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
  121. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
  122. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
  123. package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
  124. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
  125. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
  126. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
  127. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
  128. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
  129. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
  130. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
  131. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
  132. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
  133. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
  134. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
  135. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
  136. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  137. package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
  138. package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
  139. package/src/components/TextEditor/extensions/basic/index.ts +0 -6
  140. package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
  141. package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
  142. package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
  143. package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
  144. package/src/hooks/useCollaboration.ts +0 -45
  145. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/handle.d.ts +0 -0
  146. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/plugin.d.ts +0 -0
  147. package/src/{automerge/automerge-plugin → hooks/automerge}/handle.ts +0 -0
  148. package/src/{automerge/automerge-plugin → hooks/automerge}/index.ts +1 -1
@@ -1,162 +1,1247 @@
1
1
  // packages/ui/react-ui-editor/src/index.ts
2
2
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
3
- import { Doc, YText as YText2, YXmlFragment } from "@dxos/text-model";
3
+ import { Doc, YText, YXmlFragment } from "@dxos/text-model";
4
4
 
5
5
  // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
6
6
  import { tags as tags2 } from "@lezer/highlight";
7
7
 
8
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/bundle.ts
8
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/autocomplete.ts
9
+ import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
10
+ import { keymap } from "@codemirror/view";
11
+ var autocomplete = ({ onSearch }) => {
12
+ return [
13
+ // https://codemirror.net/docs/ref/#view.keymap
14
+ // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
15
+ keymap.of(completionKeymap),
16
+ // https://codemirror.net/examples/autocompletion
17
+ // https://codemirror.net/docs/ref/#autocomplete.autocompletion
18
+ autocompletion({
19
+ // TODO(burdon): Set custom keymap.
20
+ // defaultKeymap: false,
21
+ // Don't start unless key press.
22
+ activateOnTyping: false,
23
+ // TODO(burdon): Option to create new page?
24
+ // TODO(burdon): Optional decoration via addToOptions
25
+ override: [
26
+ (context) => {
27
+ const match = context.matchBefore(/\w*/);
28
+ if (!match || match.from === match.to && !context.explicit) {
29
+ return null;
30
+ }
31
+ return {
32
+ from: match.from,
33
+ options: onSearch(match.text.toLowerCase())
34
+ };
35
+ }
36
+ ]
37
+ })
38
+ ];
39
+ };
40
+
41
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic.ts
9
42
  import { closeBrackets } from "@codemirror/autocomplete";
10
43
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
11
44
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
12
45
  import { EditorView, placeholder } from "@codemirror/view";
13
- var basicBundle = ({ themeMode, placeholder: _placeholder }) => [
46
+ var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
47
+ // TODO(burdon): Compare with minimalSetup.
48
+ // https://codemirror.net/docs/ref/#codemirror.minimalSetup
14
49
  bracketMatching(),
15
50
  closeBrackets(),
16
- _placeholder && placeholder(_placeholder),
17
51
  EditorView.lineWrapping,
52
+ _placeholder && placeholder(_placeholder),
18
53
  // TODO(burdon): Is this required?
19
54
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
20
55
  ].filter(Boolean);
21
56
 
22
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/theme.ts
57
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts
58
+ import { StateField } from "@codemirror/state";
59
+ import { EditorView as EditorView2, keymap as keymap2 } from "@codemirror/view";
60
+ import defaultsDeep from "lodash.defaultsdeep";
61
+ import { invariant } from "@dxos/invariant";
62
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts";
63
+ var defaultOptions = {
64
+ effect: 2,
65
+ maxParticles: 200,
66
+ particleGravity: 0.08,
67
+ particleAlphaFadeout: 0.996,
68
+ particleSize: {
69
+ min: 2,
70
+ max: 8
71
+ },
72
+ particleNumRange: {
73
+ min: 5,
74
+ max: 10
75
+ },
76
+ particleVelocityRange: {
77
+ x: [
78
+ -1,
79
+ 1
80
+ ],
81
+ y: [
82
+ -3.5,
83
+ -1.5
84
+ ]
85
+ },
86
+ particleShrinkRate: 0.95,
87
+ shakeIntensity: 5
88
+ };
89
+ var blast = (options) => {
90
+ let blaster;
91
+ let last = 0;
92
+ const getPoint = (view, pos) => {
93
+ const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
94
+ const element = document.elementFromPoint(x, y);
95
+ const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
96
+ const point = {
97
+ x: x - left,
98
+ y: y - top
99
+ };
100
+ return {
101
+ element,
102
+ point
103
+ };
104
+ };
105
+ return [
106
+ // Cursor moved.
107
+ EditorView2.updateListener.of((update4) => {
108
+ if (blaster?.node !== update4.view.scrollDOM) {
109
+ if (blaster) {
110
+ blaster.destroy();
111
+ }
112
+ blaster = new Blaster(update4.view.scrollDOM, defaultsDeep({
113
+ particleGravity: 0.2,
114
+ particleShrinkRate: 0.995,
115
+ color: () => [
116
+ 100 + Math.random() * 100,
117
+ 0,
118
+ 0
119
+ ]
120
+ }, options, defaultOptions));
121
+ blaster.initialize();
122
+ blaster.start();
123
+ } else {
124
+ blaster.resize();
125
+ }
126
+ const current = update4.view.state.selection.main.head;
127
+ if (current !== last) {
128
+ last = current;
129
+ const { element, point } = getPoint(update4.view, current);
130
+ if (element && point) {
131
+ blaster.spawn({
132
+ element,
133
+ point
134
+ });
135
+ }
136
+ }
137
+ }),
138
+ // Document changed.
139
+ StateField.define({
140
+ create: () => null,
141
+ update: (_value, transaction) => {
142
+ if (blaster && transaction.docChanged) {
143
+ const view = EditorView2.findFromDOM(blaster.node);
144
+ const { element, point } = getPoint(view, transaction.selection.main.head);
145
+ if (element && point) {
146
+ blaster.spawn({
147
+ element,
148
+ point
149
+ });
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+ }),
155
+ keymap2.of([
156
+ {
157
+ any: (view, event) => {
158
+ if (blaster && event.key === "Enter" && event.shiftKey) {
159
+ blaster.shake({
160
+ time: 0.4
161
+ });
162
+ }
163
+ return false;
164
+ }
165
+ }
166
+ ])
167
+ ];
168
+ };
169
+ var Blaster = class {
170
+ constructor(_node, _options) {
171
+ this._node = _node;
172
+ this._options = _options;
173
+ this._running = false;
174
+ this._shakeTime = 0;
175
+ this._shakeTimeMax = 0;
176
+ this._particles = [];
177
+ this._particlePointer = 0;
178
+ this._lastPoint = {
179
+ x: 0,
180
+ y: 0
181
+ };
182
+ this.shake = throttle(({ time }) => {
183
+ this._shakeTime = this._shakeTimeMax;
184
+ this._shakeTimeMax = time;
185
+ }, 100);
186
+ this.spawn = throttle(({ element, point }) => {
187
+ const color = getRGBComponents(element, this._options.color);
188
+ const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
189
+ const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
190
+ this._lastPoint = point;
191
+ for (let i = numParticles; i--; i > 0) {
192
+ this._particles[this._particlePointer] = this._effect.create(point.x - dir * 16, point.y, color);
193
+ this._particlePointer = (this._particlePointer + 1) % this._options.maxParticles;
194
+ }
195
+ }, 100);
196
+ this._effect = this._options.effect === 1 ? new Effect1(_options) : new Effect2(_options);
197
+ }
198
+ get node() {
199
+ return this._node;
200
+ }
201
+ initialize() {
202
+ invariant(!this._canvas && !this._ctx, void 0, {
203
+ F: __dxlog_file,
204
+ L: 152,
205
+ S: this,
206
+ A: [
207
+ "!this._canvas && !this._ctx",
208
+ ""
209
+ ]
210
+ });
211
+ this._canvas = document.createElement("canvas");
212
+ this._canvas.id = "code-blast-canvas";
213
+ this._canvas.style.position = "absolute";
214
+ this._canvas.style.zIndex = "0";
215
+ this._canvas.style.pointerEvents = "none";
216
+ this._ctx = this._canvas.getContext("2d");
217
+ const parent = this._node.parentElement?.parentElement;
218
+ parent?.appendChild(this._canvas);
219
+ this.resize();
220
+ }
221
+ destroy() {
222
+ this.stop();
223
+ if (this._canvas) {
224
+ this._canvas.remove();
225
+ this._canvas = void 0;
226
+ this._ctx = void 0;
227
+ }
228
+ }
229
+ resize() {
230
+ if (this._node.parentElement && this._canvas) {
231
+ const { offsetLeft: x, offsetTop: y, offsetWidth: width, offsetHeight: height } = this._node.parentElement;
232
+ this._canvas.style.top = `${y}px`;
233
+ this._canvas.style.left = `${x}px`;
234
+ this._canvas.width = width;
235
+ this._canvas.height = height;
236
+ }
237
+ }
238
+ start() {
239
+ invariant(this._canvas && this._ctx, void 0, {
240
+ F: __dxlog_file,
241
+ L: 191,
242
+ S: this,
243
+ A: [
244
+ "this._canvas && this._ctx",
245
+ ""
246
+ ]
247
+ });
248
+ this._running = true;
249
+ this.loop();
250
+ }
251
+ stop() {
252
+ this._running = false;
253
+ this._node.style.transform = "translate(0px, 0px)";
254
+ }
255
+ loop() {
256
+ if (!this._running || !this._canvas || !this._ctx) {
257
+ return;
258
+ }
259
+ this._ctx.clearRect(0, 0, this._canvas.width ?? 0, this._canvas.height ?? 0);
260
+ const now = (/* @__PURE__ */ new Date()).getTime();
261
+ this._lastTime ??= now;
262
+ const dt = (now - this._lastTime) / 1e3;
263
+ this._lastTime = now;
264
+ if (this._shakeTime > 0) {
265
+ this._shakeTime -= dt;
266
+ const magnitude = this._shakeTime / this._shakeTimeMax * this._options.shakeIntensity;
267
+ const shakeX = random(-magnitude, magnitude);
268
+ const shakeY = random(-magnitude, magnitude);
269
+ this._node.style.transform = `translate(${shakeX}px,${shakeY}px)`;
270
+ }
271
+ this.drawParticles();
272
+ requestAnimationFrame(this.loop.bind(this));
273
+ }
274
+ drawParticles() {
275
+ for (let i = this._particles.length; i--; i > 0) {
276
+ const particle = this._particles[i];
277
+ if (!particle) {
278
+ continue;
279
+ }
280
+ if (particle.alpha < 0.01 || particle.size <= 0.5) {
281
+ continue;
282
+ }
283
+ this._effect.update(this._ctx, particle);
284
+ }
285
+ }
286
+ };
287
+ var Effect = class {
288
+ constructor(_options) {
289
+ this._options = _options;
290
+ }
291
+ };
292
+ var Effect1 = class extends Effect {
293
+ create(x, y, color) {
294
+ return {
295
+ x,
296
+ y: y + 10,
297
+ vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
298
+ vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
299
+ size: random(this._options.particleSize.min, this._options.particleSize.max),
300
+ color,
301
+ alpha: 1
302
+ };
303
+ }
304
+ update(ctx, particle) {
305
+ particle.vy += this._options.particleGravity;
306
+ particle.x += particle.vx;
307
+ particle.y += particle.vy;
308
+ particle.alpha *= this._options.particleAlphaFadeout;
309
+ ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
310
+ ctx.fillRect(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, particle.size);
311
+ }
312
+ };
313
+ var Effect2 = class extends Effect {
314
+ create(x, y, color) {
315
+ return {
316
+ x,
317
+ y: y + 10,
318
+ vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
319
+ vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
320
+ size: random(this._options.particleSize.min, this._options.particleSize.max),
321
+ color,
322
+ alpha: 1,
323
+ theta: random(0, 360) * Math.PI / 180,
324
+ drag: 0.92,
325
+ wander: 0.15
326
+ };
327
+ }
328
+ update(ctx, particle) {
329
+ particle.vy += this._options.particleGravity;
330
+ particle.x += particle.vx;
331
+ particle.y += particle.vy;
332
+ particle.vx *= particle.drag;
333
+ particle.vy *= particle.drag;
334
+ particle.theta += random(-0.5, 0.5);
335
+ particle.vx += Math.sin(particle.theta) * 0.1;
336
+ particle.vy += Math.cos(particle.theta) * 0.1;
337
+ particle.size *= this._options.particleShrinkRate;
338
+ particle.alpha *= this._options.particleAlphaFadeout;
339
+ ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
340
+ ctx.beginPath();
341
+ ctx.arc(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, 0, 2 * Math.PI);
342
+ ctx.fill();
343
+ }
344
+ };
345
+ function throttle(callback, limit) {
346
+ let wait = false;
347
+ return function(...args) {
348
+ if (!wait) {
349
+ callback.apply(this, args);
350
+ wait = true;
351
+ setTimeout(() => {
352
+ wait = false;
353
+ }, limit);
354
+ }
355
+ };
356
+ }
357
+ var getRGBComponents = (node, color) => {
358
+ if (typeof color === "function") {
359
+ return color();
360
+ }
361
+ const bgColor = getComputedStyle(node).color;
362
+ if (bgColor) {
363
+ const x = bgColor.match(/(\d+), (\d+), (\d+)/)?.slice(1);
364
+ if (x) {
365
+ return x;
366
+ }
367
+ }
368
+ return [
369
+ 50,
370
+ 50,
371
+ 50
372
+ ];
373
+ };
374
+ var random = (min, max) => {
375
+ if (!max) {
376
+ max = min;
377
+ min = 0;
378
+ }
379
+ return min + ~~(Math.random() * (max - min + 1));
380
+ };
381
+
382
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/demo.ts
383
+ import { keymap as keymap3 } from "@codemirror/view";
384
+ var defaultItems = [
385
+ "hello world!",
386
+ "this is a test.",
387
+ "this is [DXOS](https://dxos.org)"
388
+ ];
389
+ var demo = ({ delay = 75, items = defaultItems } = {}) => {
390
+ let t;
391
+ let idx = 0;
392
+ return [
393
+ keymap3.of([
394
+ {
395
+ // Reset.
396
+ key: "alt-meta-'",
397
+ run: (view) => {
398
+ clearTimeout(t);
399
+ idx = 0;
400
+ return true;
401
+ }
402
+ },
403
+ {
404
+ // Next prompt.
405
+ // TODO(burdon): Press 1-9 to select prompt?
406
+ key: "shift-meta-'",
407
+ run: (view) => {
408
+ clearTimeout(t);
409
+ const text = items[idx++];
410
+ if (idx === items?.length) {
411
+ idx = 0;
412
+ }
413
+ let i = 0;
414
+ const insert = (d = 0) => {
415
+ t = setTimeout(() => {
416
+ const pos = view.state.selection.main.head;
417
+ view.dispatch({
418
+ changes: {
419
+ from: pos,
420
+ insert: text[i++]
421
+ },
422
+ selection: {
423
+ anchor: pos + 1
424
+ }
425
+ });
426
+ if (i < text.length) {
427
+ insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
428
+ }
429
+ }, d);
430
+ };
431
+ insert();
432
+ return true;
433
+ }
434
+ }
435
+ ])
436
+ ];
437
+ };
438
+
439
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts
440
+ import { keymap as keymap4, Decoration, EditorView as EditorView3, MatchDecorator, ViewPlugin, WidgetType } from "@codemirror/view";
441
+ import { debounce } from "@dxos/async";
442
+ import { invariant as invariant2 } from "@dxos/invariant";
443
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts";
444
+ var styles = EditorView3.baseTheme({
445
+ "& .cm-bookmark": {
446
+ cursor: "pointer",
447
+ margin: "4px",
448
+ padding: "4px",
449
+ backgroundColor: "yellow"
450
+ },
451
+ "& .cm-bookmark-selected": {
452
+ backgroundColor: "orange"
453
+ }
454
+ });
455
+ var BookmarkWidget = class extends WidgetType {
456
+ constructor(_pos, _id, _handleClick) {
457
+ super();
458
+ this._pos = _pos;
459
+ this._id = _id;
460
+ this._handleClick = _handleClick;
461
+ invariant2(this._id, void 0, {
462
+ F: __dxlog_file2,
463
+ L: 37,
464
+ S: this,
465
+ A: [
466
+ "this._id",
467
+ ""
468
+ ]
469
+ });
470
+ }
471
+ eq(other) {
472
+ return this._id === other._id;
473
+ }
474
+ toDOM() {
475
+ const span = document.createElement("span");
476
+ span.className = "cm-bookmark";
477
+ span.textContent = "\xA7";
478
+ span.onclick = () => this._handleClick();
479
+ return span;
480
+ }
481
+ };
482
+ var comments = (options = {}) => {
483
+ let active;
484
+ const bookmarkMatcher = new MatchDecorator({
485
+ regexp: /\[\^(\w+)\]/g,
486
+ decoration: (match, view, pos) => {
487
+ const id = match[1];
488
+ return Decoration.replace({
489
+ id,
490
+ widget: new BookmarkWidget(pos, id, () => handleUpdate({
491
+ active: id
492
+ }))
493
+ });
494
+ }
495
+ });
496
+ const bookmarks = ViewPlugin.fromClass(class {
497
+ constructor(view) {
498
+ this.bookmarks = bookmarkMatcher.createDeco(view);
499
+ }
500
+ update(update4) {
501
+ this.bookmarks = bookmarkMatcher.updateDeco(update4, this.bookmarks);
502
+ }
503
+ }, {
504
+ decorations: (instance) => instance.bookmarks,
505
+ provide: (plugin) => EditorView3.atomicRanges.of((view) => {
506
+ return view.plugin(plugin)?.bookmarks || Decoration.none;
507
+ })
508
+ });
509
+ const handleUpdate = debounce((data) => {
510
+ options.onUpdate?.(data);
511
+ }, 200);
512
+ return [
513
+ keymap4.of([
514
+ {
515
+ key: options?.key ?? "shift-meta-c",
516
+ run: (view) => {
517
+ const id = options.onCreate?.();
518
+ if (id) {
519
+ const pos = view.state.selection.main.head;
520
+ const tag = `[^${id}]`;
521
+ view.dispatch({
522
+ changes: {
523
+ from: pos,
524
+ insert: tag
525
+ },
526
+ selection: {
527
+ anchor: pos + tag.length
528
+ }
529
+ });
530
+ return true;
531
+ }
532
+ return false;
533
+ }
534
+ }
535
+ ]),
536
+ bookmarks,
537
+ // Monitor cursor movement.
538
+ EditorView3.updateListener.of((update4) => {
539
+ active = void 0;
540
+ if (!options.onUpdate) {
541
+ return;
542
+ }
543
+ const view = update4.view;
544
+ const pos = view.state.selection.main.head;
545
+ const decorations = [];
546
+ const rangeSet = view.plugin(bookmarks)?.bookmarks;
547
+ let closest = Infinity;
548
+ const { from, to } = (
549
+ /* view.visibleRanges?.[0] ?? */
550
+ {
551
+ from: 0,
552
+ to: view.state.doc.length
553
+ }
554
+ );
555
+ rangeSet?.between(from, to, (from2, to2, value) => {
556
+ decorations.push({
557
+ from: from2,
558
+ to: to2,
559
+ value
560
+ });
561
+ const d = Math.min(Math.abs(pos - from2), Math.abs(pos - to2));
562
+ if (d < closest) {
563
+ active = value.spec.id;
564
+ closest = d;
565
+ }
566
+ });
567
+ if (decorations.length) {
568
+ handleUpdate({
569
+ active,
570
+ items: decorations.map(({ from: from2, value: { spec: { id } } }) => ({
571
+ id,
572
+ pos: from2,
573
+ location: view.coordsAtPos(from2)
574
+ }))
575
+ });
576
+ }
577
+ }),
578
+ styles
579
+ ];
580
+ };
581
+
582
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/image.ts
583
+ import { syntaxTree } from "@codemirror/language";
584
+ import { RangeSetBuilder, StateField as StateField2 } from "@codemirror/state";
585
+ import { Decoration as Decoration2, EditorView as EditorView4, WidgetType as WidgetType2 } from "@codemirror/view";
586
+ var image = (options = {}) => {
587
+ return StateField2.define({
588
+ create: (state) => update(state, options),
589
+ update: (_, tr) => update(tr.state, options),
590
+ provide: (field) => EditorView4.decorations.from(field)
591
+ });
592
+ };
593
+ var update = (state, options) => {
594
+ const builder = new RangeSetBuilder();
595
+ const cursor = state.selection.main.head;
596
+ syntaxTree(state).iterate({
597
+ enter: (node) => {
598
+ if (node.name === "Image") {
599
+ const urlNode = node.node.getChild("URL");
600
+ if (urlNode) {
601
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
602
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
603
+ builder.add(hide ? node.from : node.to, node.to, Decoration2.replace({
604
+ widget: new ImageWidget(url)
605
+ }));
606
+ }
607
+ }
608
+ }
609
+ });
610
+ return builder.finish();
611
+ };
612
+ var ImageWidget = class extends WidgetType2 {
613
+ constructor(_url) {
614
+ super();
615
+ this._url = _url;
616
+ }
617
+ eq(other) {
618
+ return this._url === other?._url;
619
+ }
620
+ toDOM(view) {
621
+ const img = document.createElement("img");
622
+ img.setAttribute("src", this._url);
623
+ img.setAttribute("class", "cm-image");
624
+ return img;
625
+ }
626
+ };
627
+
628
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
629
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
630
+ import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField3 } from "@codemirror/state";
631
+ import { Decoration as Decoration3, EditorView as EditorView5, WidgetType as WidgetType3 } from "@codemirror/view";
632
+ var link = (options = {}) => {
633
+ return StateField3.define({
634
+ create: (state) => update2(state, options),
635
+ update: (_, tr) => update2(tr.state, options),
636
+ provide: (field) => EditorView5.decorations.from(field)
637
+ });
638
+ };
639
+ var LinkButton = class extends WidgetType3 {
640
+ constructor(_pos, _url, _onAttach) {
641
+ super();
642
+ this._pos = _pos;
643
+ this._url = _url;
644
+ this._onAttach = _onAttach;
645
+ }
646
+ eq(other) {
647
+ return this._pos === other._pos && this._url === other._url;
648
+ }
649
+ toDOM(view) {
650
+ const el = document.createElement("span");
651
+ this._onAttach(el, this._url);
652
+ return el;
653
+ }
654
+ };
655
+ var LinkText = class extends WidgetType3 {
656
+ constructor(_pos, _text, _url) {
657
+ super();
658
+ this._pos = _pos;
659
+ this._text = _text;
660
+ this._url = _url;
661
+ }
662
+ eq(other) {
663
+ return this._pos === other._pos && this._url === other._url;
664
+ }
665
+ toDOM(view) {
666
+ const link2 = document.createElement("a");
667
+ link2.setAttribute("class", "cm-link");
668
+ link2.textContent = this._text;
669
+ if (this._url) {
670
+ link2.setAttribute("rel", "noreferrer");
671
+ link2.setAttribute("target", "_blank");
672
+ link2.href = this._url;
673
+ } else {
674
+ link2.onclick = () => {
675
+ view.dispatch({
676
+ selection: {
677
+ anchor: this._pos
678
+ }
679
+ });
680
+ };
681
+ }
682
+ return link2;
683
+ }
684
+ };
685
+ var update2 = (state, options) => {
686
+ const builder = new RangeSetBuilder2();
687
+ const cursor = state.selection.main.head;
688
+ syntaxTree2(state).iterate({
689
+ enter: (node) => {
690
+ if (node.name === "Link") {
691
+ const marks = node.node.getChildren("LinkMark");
692
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
693
+ const urlNode = node.node.getChild("URL");
694
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
695
+ if (!url) {
696
+ return false;
697
+ }
698
+ if (state.readOnly || cursor < node.from || cursor > node.to) {
699
+ builder.add(node.from, node.to, Decoration3.replace({
700
+ widget: new LinkText(node.from, text, options.link ? url : void 0)
701
+ }));
702
+ }
703
+ if (options.onRender) {
704
+ builder.add(node.to, node.to, Decoration3.replace({
705
+ widget: new LinkButton(node.from, url, options.onRender)
706
+ }));
707
+ }
708
+ return false;
709
+ }
710
+ }
711
+ });
712
+ return builder.finish();
713
+ };
714
+
715
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
716
+ import { StateField as StateField4 } from "@codemirror/state";
717
+ var listener = ({ onChange }) => {
718
+ return StateField4.define({
719
+ create: () => null,
720
+ update: (_value, transaction) => {
721
+ if (transaction.docChanged && onChange) {
722
+ onChange(transaction.newDoc.toString());
723
+ }
724
+ return null;
725
+ }
726
+ });
727
+ };
728
+
729
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
730
+ import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
731
+ import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
732
+ import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
733
+ import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
734
+ import { languages } from "@codemirror/language-data";
735
+ import { searchKeymap } from "@codemirror/search";
736
+ import { EditorState } from "@codemirror/state";
737
+ import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
738
+ import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView6, keymap as keymap5, placeholder as placeholder2 } from "@codemirror/view";
739
+
740
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
741
+ import { markdownLanguage } from "@codemirror/lang-markdown";
742
+ import { HighlightStyle } from "@codemirror/language";
743
+ import { tags, styleTags, Tag } from "@lezer/highlight";
744
+ import { Table } from "@lezer/markdown";
23
745
  import get from "lodash.get";
746
+ import { mx as mx2 } from "@dxos/react-ui-theme";
24
747
 
25
748
  // packages/ui/react-ui-editor/src/styles/markdown.ts
26
749
  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"
750
+ var headings = {
751
+ 1: [
752
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
753
+ "-ml-1.5"
754
+ ],
755
+ 2: [
756
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
757
+ "-ml-1.5"
758
+ ],
759
+ 3: [
760
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
761
+ "-ml-1.5"
762
+ ],
763
+ 4: [
764
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
765
+ "-ml-[5px]"
766
+ ],
767
+ 5: [
768
+ "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
769
+ "-ml-[5px]"
770
+ ],
771
+ 6: [
772
+ "mbs-4 mbe-2 font-medium text-inherit no-underline",
773
+ "-ml-[4px]"
774
+ ]
775
+ };
776
+ var heading = (level, readonly) => {
777
+ const [style, offset] = headings[level];
778
+ return mx(style, readonly && offset);
34
779
  };
780
+ var light = "text-neutral-200 dark:text-neutral-800";
781
+ var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
35
782
  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
783
  var italic = "italic";
39
784
  var strikethrough = "line-through";
40
- var mark = "!font-normal !no-underline !text-inherit opacity-40";
785
+ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
786
+ var codeMark = "font-mono text-primary-500";
787
+ var inlineUrl = mx(code, "px-1");
788
+ var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
789
+ var horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800";
790
+
791
+ // packages/ui/react-ui-editor/src/styles/tokens.ts
792
+ import { tailwindConfig } from "@dxos/react-ui-theme";
793
+ var tokens = tailwindConfig({}).theme;
794
+
795
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
796
+ var markdownTags = {
797
+ Blockquote: Tag.define(),
798
+ CodeMark: Tag.define(),
799
+ CodeText: Tag.define(),
800
+ EmphasisMark: Tag.define(),
801
+ HeaderMark: Tag.define(),
802
+ HorizontalRule: Tag.define(),
803
+ InlineCode: Tag.define(),
804
+ LinkLabel: Tag.define(),
805
+ LinkReference: Tag.define(),
806
+ ListMark: Tag.define(),
807
+ QuoteMark: Tag.define(),
808
+ URL: Tag.define(),
809
+ // Custom.
810
+ TableCell: Tag.define()
811
+ };
812
+ Table.defineNodes?.forEach((node) => {
813
+ switch (node?.name) {
814
+ case "TableCell": {
815
+ node.style = markdownTags.TableCell;
816
+ break;
817
+ }
818
+ }
819
+ });
820
+ var markdownTagsExtensions = [
821
+ Table,
822
+ {
823
+ props: [
824
+ styleTags(markdownTags)
825
+ ]
826
+ }
827
+ ];
828
+ var markdownHighlightStyle = (readonly) => {
829
+ return HighlightStyle.define([
830
+ {
831
+ tag: [
832
+ tags.keyword,
833
+ tags.name,
834
+ tags.deleted,
835
+ tags.character,
836
+ tags.propertyName,
837
+ tags.macroName,
838
+ tags.color,
839
+ tags.constant(tags.name),
840
+ tags.standard(tags.name),
841
+ tags.definition(tags.name),
842
+ tags.separator,
843
+ tags.typeName,
844
+ tags.className,
845
+ tags.number,
846
+ tags.changed,
847
+ tags.annotation,
848
+ tags.modifier,
849
+ tags.self,
850
+ tags.namespace,
851
+ tags.operator,
852
+ tags.operatorKeyword,
853
+ tags.escape,
854
+ tags.regexp,
855
+ tags.special(tags.string),
856
+ tags.meta,
857
+ tags.comment,
858
+ tags.atom,
859
+ tags.bool,
860
+ tags.special(tags.variableName),
861
+ tags.processingInstruction,
862
+ tags.string,
863
+ tags.inserted,
864
+ tags.invalid
865
+ ],
866
+ color: "inherit !important"
867
+ },
868
+ // Markdown marks.
869
+ {
870
+ tag: [
871
+ tags.meta,
872
+ tags.processingInstruction,
873
+ markdownTags.LinkLabel,
874
+ markdownTags.LinkReference,
875
+ markdownTags.ListMark
876
+ ],
877
+ class: mark
878
+ },
879
+ // Markdown marks.
880
+ {
881
+ tag: [
882
+ markdownTags.CodeMark,
883
+ markdownTags.HeaderMark,
884
+ markdownTags.QuoteMark,
885
+ markdownTags.EmphasisMark
886
+ ],
887
+ class: readonly ? "hidden" : mark
888
+ },
889
+ // E.g., code block language (after ```).
890
+ {
891
+ tag: [
892
+ tags.function(tags.variableName),
893
+ tags.labelName
894
+ ],
895
+ class: readonly ? "hidden" : codeMark
896
+ },
897
+ {
898
+ tag: [
899
+ tags.monospace
900
+ ],
901
+ class: "font-mono"
902
+ },
903
+ // Headings.
904
+ {
905
+ tag: tags.heading1,
906
+ class: heading(1, readonly)
907
+ },
908
+ {
909
+ tag: tags.heading2,
910
+ class: heading(2, readonly)
911
+ },
912
+ {
913
+ tag: tags.heading3,
914
+ class: heading(3, readonly)
915
+ },
916
+ {
917
+ tag: tags.heading4,
918
+ class: heading(4, readonly)
919
+ },
920
+ {
921
+ tag: tags.heading5,
922
+ class: heading(5, readonly)
923
+ },
924
+ {
925
+ tag: tags.heading6,
926
+ class: heading(6, readonly)
927
+ },
928
+ // Emphasis.
929
+ {
930
+ tag: tags.emphasis,
931
+ class: italic
932
+ },
933
+ {
934
+ tag: tags.strong,
935
+ class: bold
936
+ },
937
+ {
938
+ tag: tags.strikethrough,
939
+ class: strikethrough
940
+ },
941
+ // Naked URLs.
942
+ {
943
+ tag: [
944
+ markdownTags.URL
945
+ ],
946
+ class: inlineUrl
947
+ },
948
+ // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
949
+ // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
950
+ // when a language is specified. In this case, the syntax highlighting extensions will colorize
951
+ // the code, but all other CSS properties will be inherited.
952
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font.
953
+ {
954
+ tag: [
955
+ markdownTags.CodeText,
956
+ markdownTags.InlineCode
957
+ ],
958
+ class: code
959
+ },
960
+ {
961
+ tag: [
962
+ markdownTags.QuoteMark
963
+ ],
964
+ class: blockquote
965
+ },
966
+ // TODO(burdon): Replace with extension.
967
+ {
968
+ tag: [
969
+ markdownTags.HorizontalRule
970
+ ],
971
+ class: mx2(horizontalRule, readonly && "-indent-[100rem]")
972
+ },
973
+ // TODO(burdon): Only if being edited.
974
+ {
975
+ tag: [
976
+ markdownTags.TableCell
977
+ ],
978
+ class: "font-mono"
979
+ }
980
+ ], {
981
+ scope: markdownLanguage,
982
+ all: {
983
+ fontFamily: get(tokens, "fontFamily.body", []).join(",")
984
+ }
985
+ });
986
+ };
987
+
988
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
989
+ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
990
+ return [
991
+ EditorState.allowMultipleSelections.of(true),
992
+ EditorState.tabSize.of(2),
993
+ EditorView6.lineWrapping,
994
+ customKeymap(),
995
+ bracketMatching2(),
996
+ closeBrackets2(),
997
+ crosshairCursor(),
998
+ dropCursor(),
999
+ drawSelection(),
1000
+ history(),
1001
+ indentOnInput(),
1002
+ _placeholder && placeholder2(_placeholder),
1003
+ // Main extension.
1004
+ // https://github.com/codemirror/lang-markdown
1005
+ // https://codemirror.net/5/mode/markdown/index.html (demo).
1006
+ markdown({
1007
+ // GRM by default (vs strict CommonMark):
1008
+ // Table, TaskList, Strikethrough, and Autolink.
1009
+ // NOTE: This extends the parser; it doesn't affect rendering.
1010
+ // https://github.github.com/gfm
1011
+ // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1012
+ base: markdownLanguage2,
1013
+ // Languages for syntax highlighting fenced code blocks.
1014
+ codeLanguages: languages,
1015
+ // Parser extensions.
1016
+ extensions: [
1017
+ // GFM provided by default.
1018
+ markdownTagsExtensions
1019
+ ]
1020
+ }),
1021
+ // https://github.com/codemirror/theme-one-dark
1022
+ themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1023
+ // Custom styles.
1024
+ syntaxHighlighting2(markdownHighlightStyle(readonly))
1025
+ ].filter(Boolean);
1026
+ };
1027
+ var customKeymap = () => keymap5.of([
1028
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1029
+ indentWithTab,
1030
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
1031
+ ...standardKeymap,
1032
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
1033
+ ...historyKeymap,
1034
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1035
+ ...closeBracketsKeymap,
1036
+ // https://codemirror.net/docs/ref/#search.searchKeymap
1037
+ ...searchKeymap
1038
+ ]);
41
1039
 
42
- // packages/ui/react-ui-editor/src/styles/tokens.ts
43
- import { tailwindConfig } from "@dxos/react-ui-theme";
44
- var tokens = tailwindConfig({}).theme;
1040
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/mention.ts
1041
+ import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1042
+ var mention = ({ onSearch }) => {
1043
+ return autocompletion2({
1044
+ closeOnBlur: false,
1045
+ override: [
1046
+ (context) => {
1047
+ const match = context.matchBefore(/@(\w+)?/);
1048
+ if (!match || match.from === match.to && !context.explicit) {
1049
+ return null;
1050
+ }
1051
+ console.log(match);
1052
+ return {
1053
+ from: match.from,
1054
+ options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1055
+ label: `@${value}`
1056
+ }))
1057
+ };
1058
+ }
1059
+ ]
1060
+ });
1061
+ };
45
1062
 
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
58
- },
59
- "& .cm-scroller": {
60
- fontFamily: get(tokens, "fontFamily.body", []).join(","),
61
- overflow: "visible"
62
- },
63
- "& .cm-content": {
64
- padding: 0,
65
- caretColor: "black"
66
- },
67
- ".dark & .cm-content": {
68
- caretColor: "white"
69
- },
70
- "& .cm-tooltip": {
71
- backgroundColor: "transparent",
72
- border: "none"
73
- },
74
- "& .cm-link": {
75
- color: get(tokens, "extend.colors.primary.500"),
76
- textDecoration: "underline"
1063
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/table.ts
1064
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1065
+ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField5 } from "@codemirror/state";
1066
+ import { Decoration as Decoration4, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1067
+ var table = (options = {}) => {
1068
+ return StateField5.define({
1069
+ create: (state) => update3(state, options),
1070
+ update: (_, tr) => update3(tr.state, options),
1071
+ provide: (field) => EditorView7.decorations.from(field)
1072
+ });
1073
+ };
1074
+ var update3 = (state, options) => {
1075
+ const builder = new RangeSetBuilder3();
1076
+ const cursor = state.selection.main.head;
1077
+ const tables = [];
1078
+ const getTable = () => tables[tables.length - 1];
1079
+ const getRow = () => {
1080
+ const table2 = getTable();
1081
+ return table2.rows?.[table2.rows.length - 1];
1082
+ };
1083
+ syntaxTree3(state).iterate({
1084
+ enter: (node) => {
1085
+ switch (node.name) {
1086
+ case "Table": {
1087
+ tables.push({
1088
+ from: node.from,
1089
+ to: node.to
1090
+ });
1091
+ break;
1092
+ }
1093
+ case "TableHeader": {
1094
+ getTable().header = [];
1095
+ break;
1096
+ }
1097
+ case "TableRow": {
1098
+ (getTable().rows ??= []).push([]);
1099
+ break;
1100
+ }
1101
+ case "TableCell": {
1102
+ const row = getRow();
1103
+ if (row) {
1104
+ row.push(state.sliceDoc(node.from, node.to));
1105
+ } else {
1106
+ getTable().header?.push(state.sliceDoc(node.from, node.to));
1107
+ }
1108
+ break;
1109
+ }
1110
+ }
1111
+ }
1112
+ });
1113
+ tables.forEach((table2) => {
1114
+ const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1115
+ hide && builder.add(table2.from, table2.to, Decoration4.replace({
1116
+ widget: new TableWidget(table2)
1117
+ }));
1118
+ });
1119
+ return builder.finish();
1120
+ };
1121
+ var TableWidget = class extends WidgetType4 {
1122
+ constructor(_table) {
1123
+ super();
1124
+ this._table = _table;
1125
+ }
1126
+ eq(other) {
1127
+ return this._table.from === other?._table?.from;
1128
+ }
1129
+ toDOM(view) {
1130
+ const table2 = document.createElement("table");
1131
+ {
1132
+ const header = table2.appendChild(document.createElement("thead"));
1133
+ const tr = header.appendChild(document.createElement("tr"));
1134
+ this._table.header?.forEach((cell) => {
1135
+ const th = document.createElement("th");
1136
+ th.setAttribute("class", "cm-table-head");
1137
+ tr.appendChild(th).textContent = cell;
1138
+ });
1139
+ }
1140
+ {
1141
+ const body = table2.appendChild(document.createElement("tbody"));
1142
+ this._table.rows?.forEach((row) => {
1143
+ const tr = body.appendChild(document.createElement("tr"));
1144
+ row.forEach((cell) => {
1145
+ const td = document.createElement("td");
1146
+ td.setAttribute("class", "cm-table-cell");
1147
+ tr.appendChild(td).textContent = cell;
1148
+ });
1149
+ });
1150
+ }
1151
+ return table2;
77
1152
  }
78
1153
  };
79
1154
 
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());
87
- }
88
- return null;
1155
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
1156
+ import { EditorView as EditorView8, Decoration as Decoration5, WidgetType as WidgetType5, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin2 } from "@codemirror/view";
1157
+ var styles2 = EditorView8.baseTheme({
1158
+ "& .cm-task-item": {
1159
+ paddingLeft: "4px",
1160
+ paddingRight: "8px"
89
1161
  }
90
1162
  });
91
-
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) {
1163
+ var CheckboxWidget = class extends WidgetType5 {
1164
+ constructor(_pos, _checked, _indent, _onCheck) {
98
1165
  super();
99
- this._anchor = _anchor;
100
- this._text = _text;
101
- this._url = _url;
1166
+ this._pos = _pos;
1167
+ this._checked = _checked;
1168
+ this._indent = _indent;
1169
+ this._onCheck = _onCheck;
102
1170
  }
103
1171
  eq(other) {
104
- return this._text === other.text && this._url === other.url;
1172
+ return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
105
1173
  }
106
1174
  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
- });
1175
+ const wrap = document.createElement("span");
1176
+ wrap.className = "cm-task-item";
1177
+ wrap.setAttribute("aria-hidden", "true");
1178
+ wrap.style.setProperty("margin-left", this._indent * 24 + "px");
1179
+ const input = wrap.appendChild(document.createElement("input"));
1180
+ input.type = "checkbox";
1181
+ input.checked = this._checked;
1182
+ if (!this._onCheck) {
1183
+ input.setAttribute("disabled", "true");
1184
+ }
1185
+ if (this._onCheck) {
1186
+ input.onchange = (event) => {
1187
+ this._onCheck?.(event.target.checked);
1188
+ return true;
120
1189
  };
121
1190
  }
122
- return link;
1191
+ return wrap;
1192
+ }
1193
+ ignoreEvent() {
1194
+ return false;
123
1195
  }
124
1196
  };
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)
1197
+ var tasklist = (options = {}) => {
1198
+ const taskMatcher = new MatchDecorator2({
1199
+ regexp: /^(\s*)- \[([ xX])\]\s/g,
1200
+ decoration: (match, view, pos) => {
1201
+ const indent = Math.floor(match[1].length / 2);
1202
+ const checked = match[2] === "x" || match[2] === "X";
1203
+ return Decoration5.replace({
1204
+ widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
1205
+ const idx = pos + match[0].indexOf("[") + 1;
1206
+ view.dispatch({
1207
+ changes: {
1208
+ from: idx,
1209
+ to: idx + 1,
1210
+ insert: checked2 ? "x" : " "
1211
+ },
1212
+ // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
1213
+ selection: {
1214
+ anchor: idx + 3
1215
+ }
1216
+ });
1217
+ })
1218
+ });
1219
+ }
130
1220
  });
131
- };
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;
1221
+ const tasks = ViewPlugin2.fromClass(class {
1222
+ constructor(view) {
1223
+ this.tasks = taskMatcher.createDeco(view);
1224
+ }
1225
+ update(update4) {
1226
+ this.tasks = taskMatcher.updateDeco(update4, this.tasks);
150
1227
  }
1228
+ }, {
1229
+ decorations: (instance) => instance.tasks,
1230
+ provide: (plugin) => EditorView8.atomicRanges.of((view) => {
1231
+ return view.plugin(plugin)?.tasks || Decoration5.none;
1232
+ })
151
1233
  });
152
- return builder.finish();
1234
+ return [
1235
+ tasks,
1236
+ styles2
1237
+ ];
153
1238
  };
154
1239
 
155
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.tsx
1240
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.tsx
156
1241
  import { hoverTooltip } from "@codemirror/view";
157
1242
  import { tooltipContent } from "@dxos/react-ui-theme";
158
1243
  var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
159
- var createHyperlinkTooltip = (onHover, regexp = markdownLinkRegexp) => hoverTooltip((view, pos) => {
1244
+ var tooltip = ({ onHover, regexp = markdownLinkRegexp }) => hoverTooltip((view, pos) => {
160
1245
  const { from, text } = view.state.doc.lineAt(pos);
161
1246
  const p = pos - from;
162
1247
  let idx = 0;
@@ -182,288 +1267,101 @@ var createHyperlinkTooltip = (onHover, regexp = markdownLinkRegexp) => hoverTool
182
1267
  end: idx + from + match[0].length,
183
1268
  above: true,
184
1269
  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);
1270
+ const el = document.createElement("div");
1271
+ el.className = tooltipContent({}, "pli-2 plb-1");
190
1272
  onHover(el, url);
191
1273
  return {
192
- dom: el
1274
+ dom: el,
1275
+ offset: {
1276
+ x: 0,
1277
+ y: 12
1278
+ }
193
1279
  };
194
1280
  }
195
1281
  };
196
1282
  });
197
1283
 
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
- }));
242
- }
243
- });
244
- function hyperLinkExtension() {
245
- return ViewPlugin.fromClass(class HyperLinkView {
246
- constructor(view) {
247
- this.decorator = linkDecorator();
248
- this.decorations = this.decorator.createDeco(view);
249
- }
250
- update(update2) {
251
- if (update2.docChanged || update2.viewportChanged) {
252
- this.decorations = this.decorator.updateDeco(update2, this.decorations);
253
- }
254
- }
255
- }, {
256
- decorations: (view) => view.decorations,
257
- eventHandlers: {
258
- // TODO(burdon): CLick to expand link.
259
- // https://discuss.codemirror.net/t/decorator-iteration-on-click-event/4226/2
260
- mousedown: function(event, view) {
261
- const pos = view.posAtDOM(event.target);
262
- if (pos !== 0) {
263
- console.log(this);
264
- }
265
- },
266
- // TODO(burdon): Hide when cursor moves away.
267
- keydown: (event, view) => {
268
- view.update([]);
269
- }
270
- }
271
- });
272
- }
273
- var hyperlinkStyle = EditorView3.baseTheme({
274
- ".cm-hyperlink-label": {
275
- cursor: "pointer",
276
- textDecoration: "underline"
277
- },
278
- ".cm-hyperlink-bracket": {
279
- opacity: 0.2
280
- },
281
- ".cm-hyperlink-url": {
282
- opacity: 0.5,
283
- fontFamily: "monospace"
284
- }
285
- });
286
- var hyperlinkWidget = [
287
- hyperLinkExtension(),
288
- hyperlinkStyle
289
- ];
290
-
291
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
292
- import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
293
- import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
294
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
295
- import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, foldKeymap, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
296
- import { languages } from "@codemirror/language-data";
297
- import { lintKeymap } from "@codemirror/lint";
298
- import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
299
- import { EditorState } from "@codemirror/state";
300
- import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
301
- import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView4, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
302
-
303
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/tags.ts
304
- import { styleTags, Tag } from "@lezer/highlight";
305
- var markdownTags = {
306
- headingMark: Tag.define(),
307
- quoteMark: Tag.define(),
308
- listMark: Tag.define(),
309
- linkMark: Tag.define(),
310
- emphasisMark: Tag.define(),
311
- codeMark: Tag.define(),
312
- codeText: Tag.define(),
313
- inlineCode: Tag.define(),
314
- url: Tag.define(),
315
- linkReference: Tag.define(),
316
- linkLabel: Tag.define()
317
- };
318
- var markdownTagsExtension = {
319
- props: [
320
- styleTags({
321
- HeaderMark: markdownTags.headingMark,
322
- QuoteMark: markdownTags.quoteMark,
323
- ListMark: markdownTags.listMark,
324
- LinkMark: markdownTags.linkMark,
325
- EmphasisMark: markdownTags.emphasisMark,
326
- CodeMark: markdownTags.codeMark,
327
- CodeText: markdownTags.codeText,
328
- InlineCode: markdownTags.inlineCode,
329
- URL: markdownTags.url,
330
- LinkReference: markdownTags.linkReference,
331
- LinkLabel: markdownTags.linkLabel
332
- })
333
- ]
334
- };
335
-
336
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/theme.ts
337
- import { markdownLanguage } from "@codemirror/lang-markdown";
338
- import { HighlightStyle } from "@codemirror/language";
339
- import { tags } from "@lezer/highlight";
1284
+ // packages/ui/react-ui-editor/src/components/TextEditor/themes/default.ts
340
1285
  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
- // },
1286
+ var defaultTheme = {
1287
+ //
1288
+ // Main layout:
1289
+ // <div class=".cm-editor .cm-focused">
1290
+ // <div class=".cm-scroller">
1291
+ // <div class=".cm-content" role="textbox" contenteditable="true">
1292
+ // <div class=".cm-line" />
1293
+ // </div>
1294
+ // </div>
1295
+ // </div>
1296
+ //
1297
+ "&": {},
416
1298
  "&.cm-focused": {
417
1299
  outline: "none"
418
1300
  },
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"
1301
+ "& .cm-scroller": {
1302
+ overflow: "visible"
438
1303
  },
439
1304
  "& .cm-content": {
440
1305
  padding: 0,
1306
+ // Base font size (otherwise defined by HTML tag, which might be different for storybook).
1307
+ fontSize: "16px"
1308
+ },
1309
+ "&light .cm-content": {
1310
+ color: get2(tokens, "extend.colors.neutral.900", "black"),
441
1311
  caretColor: "black"
442
1312
  },
443
- ".dark & .cm-content": {
1313
+ "&dark .cm-content": {
1314
+ color: get2(tokens, "extend.colors.neutral.100", "white"),
444
1315
  caretColor: "white"
445
1316
  },
446
- "& .cm-cursor": {
447
- borderLeftColor: "black"
1317
+ //
1318
+ // Cursor
1319
+ //
1320
+ "&light .cm-cursor": {
1321
+ borderLeft: "2px solid black"
448
1322
  },
449
- ".dark & .cm-cursor": {
450
- borderLeftColor: "white"
1323
+ "&dark .cm-cursor": {
1324
+ borderLeft: "2px solid white"
451
1325
  },
452
- ".cm-placeholder": {
453
- fontFamily: get2(tokens, "fontFamily.body", []).join(",")
1326
+ "& .cm-placeholder": {
1327
+ fontWeight: 100
454
1328
  },
455
- "& .cm-scroller": {
456
- fontFamily: get2(tokens, "fontFamily.mono", []).join(","),
457
- overflow: "visible"
1329
+ //
1330
+ // line
1331
+ //
1332
+ "& .cm-line": {
1333
+ paddingInline: 0
458
1334
  },
1335
+ "& .cm-line *": {},
459
1336
  "& .cm-activeLine": {
460
1337
  backgroundColor: "transparent"
461
1338
  },
462
- ".dark & .cm-activeLine": {
463
- backgroundColor: "transparent"
1339
+ //
1340
+ // selection
1341
+ //
1342
+ "&light .cm-selectionBackground, &light.cm-focused .cm-selectionBackground": {
1343
+ background: get2(tokens, "extend.colors.primary.150", "#00ffff")
1344
+ },
1345
+ "&dark .cm-selectionBackground, &dark.cm-focused .cm-selectionBackground": {
1346
+ background: get2(tokens, "extend.colors.primary.850", "#00ffff")
1347
+ },
1348
+ "&light .cm-selectionMatch": {
1349
+ background: get2(tokens, "extend.colors.primary.250", "#00ffff") + "44"
1350
+ },
1351
+ "&dark .cm-selectionMatch": {
1352
+ background: get2(tokens, "extend.colors.primary.600", "#00ffff") + "44"
1353
+ },
1354
+ //
1355
+ // collaboration
1356
+ // TODO(burdon): Review classnames (YJS dependent?)
1357
+ //
1358
+ "&light .cm-ySelection, &light .cm-yLineSelection": {
1359
+ mixBlendMode: "multiply"
1360
+ },
1361
+ "&dark .cm-ySelection, &dark .cm-yLineSelection": {
1362
+ mixBlendMode: "screen"
464
1363
  },
465
1364
  "& .cm-ySelectionInfo": {
466
- fontFamily: get2(tokens, "fontFamily.body", []).join(","),
467
1365
  padding: "2px 4px",
468
1366
  marginBlockStart: "-4px"
469
1367
  },
@@ -471,12 +1369,6 @@ var markdownTheme = {
471
1369
  paddingBlockStart: ".15em",
472
1370
  paddingBlockEnd: ".15em"
473
1371
  },
474
- "& .cm-ySelection, & .cm-yLineSelection": {
475
- mixBlendMode: "multiply"
476
- },
477
- ".dark & .cm-ySelection, .dark & .cm-yLineSelection": {
478
- mixBlendMode: "screen"
479
- },
480
1372
  "& .cm-ySelectionCaret": {
481
1373
  display: "inline-block",
482
1374
  insetBlockStart: ".1em",
@@ -484,16 +1376,74 @@ var markdownTheme = {
484
1376
  verticalAlign: "top"
485
1377
  },
486
1378
  "& .cm-yLineSelection": {
487
- margin: "0"
1379
+ margin: 0
488
1380
  },
489
- ".cm-link": {
490
- color: "rgb(20 89 208)",
1381
+ //
1382
+ // link
1383
+ //
1384
+ "& .cm-link": {
1385
+ color: get2(tokens, "extend.colors.primary.500"),
491
1386
  textDecorationLine: "underline",
492
1387
  textDecorationThickness: "1px",
493
1388
  textUnderlineOffset: "2px",
494
- borderRadius: ".125rem"
1389
+ borderRadius: ".125rem",
1390
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
1391
+ },
1392
+ //
1393
+ // tooltip
1394
+ //
1395
+ "& .cm-tooltip": {
1396
+ border: "none"
1397
+ },
1398
+ "& .cm-tooltip-below": {},
1399
+ //
1400
+ // autocomplete
1401
+ //
1402
+ "& .cm-tooltip-autocomplete": {
1403
+ marginTop: "4px",
1404
+ marginLeft: "-3px"
1405
+ },
1406
+ "& .cm-tooltip-autocomplete ul li": {},
1407
+ "& .cm-tooltip-autocomplete ul li[aria-selected]": {},
1408
+ "& .cm-completionIcon": {
1409
+ display: "none"
1410
+ },
1411
+ "& .cm-completionLabel": {
1412
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
1413
+ },
1414
+ "& .cm-completionMatchedText": {
1415
+ textDecoration: "none"
1416
+ },
1417
+ //
1418
+ // widgets
1419
+ //
1420
+ "& .cm-widgetBuffer": {
1421
+ display: "none",
1422
+ height: 0
1423
+ },
1424
+ //
1425
+ // table
1426
+ //
1427
+ "& .cm-table-head": {
1428
+ padding: "2px 16px 2px 0px",
1429
+ borderBottom: `1px solid ${get2(tokens, "extend.colors.neutral.500")}`,
1430
+ fontWeight: 100,
1431
+ textAlign: "left",
1432
+ color: get2(tokens, "extend.colors.neutral.500")
1433
+ },
1434
+ "& .cm-table-cell": {
1435
+ padding: "2px 16px 2px 0px"
1436
+ },
1437
+ //
1438
+ // image
1439
+ //
1440
+ "& .cm-image": {
1441
+ margin: "0.5rem 0"
495
1442
  },
496
- // TODO(burdon): Document.
1443
+ //
1444
+ // font size
1445
+ // 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.
1446
+ //
497
1447
  ...Object.keys(get2(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
498
1448
  const height = get2(tokens, [
499
1449
  "extend",
@@ -512,208 +1462,255 @@ var markdownTheme = {
512
1462
  height
513
1463
  };
514
1464
  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]
1465
+ }, {}),
1466
+ /**
1467
+ * Panels
1468
+ * https://github.com/codemirror/search/blob/main/src/search.ts#L745
1469
+ *
1470
+ * Find/replace panel.
1471
+ * <div class="cm-announced">...</div>
1472
+ * <div class="cm-scroller">...</div>
1473
+ * <div class="cm-panels cm-panels-bottom">
1474
+ * <div class="cm-search cm-panel">
1475
+ * <input class="cm-textfield" />
1476
+ * <button class="cm-button">...</button>
1477
+ * <label><input type="checkbox" />...</label>
1478
+ * </div>
1479
+ * </div
1480
+ */
1481
+ "& .cm-panels": {
1482
+ border: `1px solid ${get2(tokens, "extend.colors.neutral.200")}`,
1483
+ borderBottom: "none"
609
1484
  },
610
- {
611
- tag: tags.heading4,
612
- class: heading[4]
1485
+ "& .cm-panel": {
1486
+ background: get2(tokens, "extend.colors.neutral.50"),
1487
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
613
1488
  },
614
- {
615
- tag: tags.heading5,
616
- class: heading[5]
1489
+ "& .cm-button": {
1490
+ margin: "4px",
1491
+ fontFamily: get2(tokens, "fontFamily.body", []).join(","),
1492
+ background: get2(tokens, "extend.colors.neutral.100"),
1493
+ border: "none"
617
1494
  },
618
- {
619
- tag: tags.heading6,
620
- class: heading[6]
1495
+ "& .cm-searchMatch": {
1496
+ backgroundColor: get2(tokens, "extend.colors.yellow.100")
621
1497
  },
622
- {
623
- tag: tags.strikethrough,
624
- class: strikethrough
1498
+ "& .cm-searchMatch.cm-searchMatch-selected": {
1499
+ backgroundColor: get2(tokens, "extend.colors.primary.100")
1500
+ }
1501
+ };
1502
+ var textTheme = {
1503
+ "& .cm-scroller": {
1504
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
625
1505
  },
626
- {
627
- tag: tags.strong,
628
- class: bold
1506
+ "& .cm-placeholder": {
1507
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
629
1508
  }
630
- ], {
631
- scope: markdownLanguage,
632
- all: {
1509
+ };
1510
+ var markdownTheme = {
1511
+ // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
1512
+ "& .cm-placeholder": {
633
1513
  fontFamily: get2(tokens, "fontFamily.body", []).join(",")
634
1514
  }
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);
1515
+ };
1516
+ var codeTheme = {
1517
+ "& .cm-scroller": {
1518
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
1519
+ },
1520
+ "& .cm-placeholder": {
1521
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
1522
+ }
685
1523
  };
686
1524
 
687
1525
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
688
1526
  import { EditorState as EditorState2 } from "@codemirror/state";
689
- import { EditorView as EditorView5 } from "@codemirror/view";
1527
+ import { EditorView as EditorView9 } from "@codemirror/view";
690
1528
  import { useFocusableGroup } from "@fluentui/react-tabster";
691
1529
  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";
1530
+ import defaultsDeep2 from "lodash.defaultsdeep";
1531
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
1532
+ import { generateName } from "@dxos/display-name";
696
1533
  import { useThemeContext } from "@dxos/react-ui";
1534
+ import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
1535
+ var EditorModes = [
1536
+ "default",
1537
+ "vim"
1538
+ ];
1539
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
1540
+ const { themeMode } = useThemeContext();
1541
+ const tabsterDOMAttribute = useFocusableGroup({
1542
+ tabBehavior: "limited"
1543
+ });
1544
+ const [root, setRoot] = useState(null);
1545
+ const [state, setState] = useState();
1546
+ const [view, setView] = useState();
1547
+ useImperativeHandle(forwardedRef, () => ({
1548
+ root,
1549
+ state,
1550
+ view
1551
+ }), [
1552
+ view,
1553
+ state,
1554
+ root
1555
+ ]);
1556
+ const { awareness, peer } = model;
1557
+ useEffect(() => {
1558
+ if (awareness && peer) {
1559
+ awareness.setLocalStateField("user", {
1560
+ name: peer.name ?? generateName(peer.id),
1561
+ color: getColorForValue({
1562
+ value: peer.id,
1563
+ type: "color"
1564
+ }),
1565
+ colorLight: getColorForValue({
1566
+ value: peer.id,
1567
+ themeMode,
1568
+ type: "highlight"
1569
+ })
1570
+ });
1571
+ }
1572
+ }, [
1573
+ awareness,
1574
+ peer,
1575
+ themeMode
1576
+ ]);
1577
+ useEffect(() => {
1578
+ if (!root) {
1579
+ return;
1580
+ }
1581
+ const state2 = EditorState2.create({
1582
+ doc: model.text(),
1583
+ extensions: [
1584
+ readonly && EditorState2.readOnly.of(readonly),
1585
+ // TODO(burdon): Factor out VIM mode?
1586
+ editorMode === "vim" && vim(),
1587
+ // Theme.
1588
+ // TODO(burdon): Make optional.
1589
+ EditorView9.baseTheme(defaultTheme),
1590
+ EditorView9.theme(slots?.editor?.theme ?? {}),
1591
+ // TODO(burdon): themeMode doesn't change in storybooks.
1592
+ EditorView9.darkTheme.of(themeMode === "dark"),
1593
+ // Storage and replication.
1594
+ model.extension,
1595
+ // Custom.
1596
+ ...extensions
1597
+ ].filter(Boolean)
1598
+ });
1599
+ view?.destroy();
1600
+ setView(new EditorView9({
1601
+ state: state2,
1602
+ parent: root
1603
+ }));
1604
+ setState(state2);
1605
+ return () => {
1606
+ view?.destroy();
1607
+ setView(void 0);
1608
+ setState(void 0);
1609
+ };
1610
+ }, [
1611
+ root,
1612
+ model,
1613
+ themeMode,
1614
+ readonly,
1615
+ editorMode
1616
+ ]);
1617
+ const handleKeyUp = useCallback((event) => {
1618
+ const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
1619
+ switch (key) {
1620
+ case "Enter": {
1621
+ view?.contentDOM.focus();
1622
+ break;
1623
+ }
1624
+ case "Escape": {
1625
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
1626
+ break;
1627
+ }
1628
+ }
1629
+ }, [
1630
+ view,
1631
+ editorMode
1632
+ ]);
1633
+ return /* @__PURE__ */ React.createElement("div", {
1634
+ key: model.id,
1635
+ ref: setRoot,
1636
+ tabIndex: 0,
1637
+ ...slots?.root,
1638
+ ...editorMode !== "vim" && tabsterDOMAttribute,
1639
+ onKeyUp: handleKeyUp
1640
+ });
1641
+ });
1642
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
1643
+ const { themeMode } = useThemeContext();
1644
+ const slots = defaultsDeep2({}, _slots, defaultTextSlots);
1645
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1646
+ ref: forwardedRef,
1647
+ readonly,
1648
+ extensions: [
1649
+ basicBundle({
1650
+ readonly,
1651
+ themeMode,
1652
+ placeholder: slots?.editor?.placeholder
1653
+ }),
1654
+ ...extensions
1655
+ ],
1656
+ slots,
1657
+ ...props
1658
+ });
1659
+ });
1660
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
1661
+ const { themeMode } = useThemeContext();
1662
+ const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
1663
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1664
+ ref: forwardedRef,
1665
+ readonly,
1666
+ extensions: [
1667
+ markdownBundle({
1668
+ readonly,
1669
+ themeMode,
1670
+ placeholder: slots?.editor?.placeholder
1671
+ }),
1672
+ ...extensions
1673
+ ],
1674
+ slots,
1675
+ ...props
1676
+ });
1677
+ });
1678
+ var defaultSlots = {
1679
+ root: {
1680
+ className: mx3("p-2", inputSurface)
1681
+ }
1682
+ };
1683
+ var defaultTextSlots = {
1684
+ ...defaultSlots,
1685
+ editor: {
1686
+ theme: textTheme
1687
+ }
1688
+ };
1689
+ var defaultMarkdownSlots = {
1690
+ ...defaultSlots,
1691
+ editor: {
1692
+ theme: markdownTheme
1693
+ }
1694
+ };
697
1695
 
698
- // packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
699
- import { useEffect, useMemo } from "react";
1696
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1697
+ import get3 from "lodash.get";
1698
+ import { useEffect as useEffect2, useState as useState2 } from "react";
700
1699
  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";
1700
+ import { invariant as invariant3 } from "@dxos/invariant";
1701
+ import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
705
1702
 
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";
1703
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
1704
+ import { Annotation, Facet, StateEffect, StateField as StateField6 } from "@codemirror/state";
1705
+ import { ViewPlugin as ViewPlugin3 } from "@codemirror/view";
709
1706
  import * as automerge2 from "@dxos/automerge/automerge";
710
1707
 
711
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
1708
+ // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
712
1709
  import { next as automerge } from "@dxos/automerge/automerge";
713
1710
 
714
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/amToCodemirror.ts
1711
+ // packages/ui/react-ui-editor/src/hooks/automerge/translation/automerge-to-codemirror.ts
715
1712
  import { ChangeSet } from "@codemirror/state";
716
- var amToCodemirror_default = (view, selection, target, patches) => {
1713
+ var automergeToCodemirror = (view, selection, target, patches) => {
717
1714
  for (const patch of patches) {
718
1715
  const changeSpec = handlePatch(patch, target, view.state);
719
1716
  if (changeSpec != null) {
@@ -818,9 +1815,9 @@ var charPath = (textPath, candidatePath) => {
818
1815
  return null;
819
1816
  };
820
1817
 
821
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/codeMirrorToAm.ts
1818
+ // packages/ui/react-ui-editor/src/hooks/automerge/translation/codemirror-to-automerge.ts
822
1819
  import { next as am } from "@dxos/automerge/automerge";
823
- var codeMirrorToAm_default = (field, handle, transactions, state) => {
1820
+ var codemirrorToAutomerge = (field, handle, transactions, state) => {
824
1821
  const { lastHeads, path } = state.field(field);
825
1822
  let hasChanges = false;
826
1823
  for (const tr of transactions) {
@@ -841,7 +1838,7 @@ var codeMirrorToAm_default = (field, handle, transactions, state) => {
841
1838
  return newHeads ?? void 0;
842
1839
  };
843
1840
 
844
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
1841
+ // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
845
1842
  var PatchSemaphore = class {
846
1843
  constructor(field) {
847
1844
  this._inReconcile = false;
@@ -864,12 +1861,12 @@ var PatchSemaphore = class {
864
1861
  annotations: reconcileAnnotationType.of(true)
865
1862
  });
866
1863
  }
867
- let newHeads = codeMirrorToAm_default(this._field, handle, transactions, view.state);
1864
+ let newHeads = codemirrorToAutomerge(this._field, handle, transactions, view.state);
868
1865
  if (newHeads === null || newHeads === void 0) {
869
1866
  newHeads = automerge.getHeads(handle.docSync());
870
1867
  }
871
1868
  const diff = automerge.equals(oldHeads, newHeads) ? [] : automerge.diff(handle.docSync(), oldHeads, newHeads);
872
- amToCodemirror_default(view, selection, path, diff);
1869
+ automergeToCodemirror(view, selection, path, diff);
873
1870
  view.dispatch({
874
1871
  effects: updateHeads(newHeads),
875
1872
  annotations: reconcileAnnotationType.of({})
@@ -882,7 +1879,7 @@ var PatchSemaphore = class {
882
1879
  }
883
1880
  };
884
1881
 
885
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/plugin.ts
1882
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
886
1883
  var effectType = StateEffect.define({});
887
1884
  var updateHeads = (newHeads) => effectType.of({
888
1885
  newHeads
@@ -893,7 +1890,7 @@ var semaphoreFacet = Facet.define({
893
1890
  combine: (values) => values.at(-1)
894
1891
  });
895
1892
  var automergePlugin = (handle, path) => {
896
- const stateField = StateField3.define({
1893
+ const stateField = StateField6.define({
897
1894
  create: () => ({
898
1895
  lastHeads: automerge2.getHeads(handle.docSync()),
899
1896
  unreconciledTransactions: [],
@@ -923,7 +1920,7 @@ var automergePlugin = (handle, path) => {
923
1920
  }
924
1921
  });
925
1922
  const semaphore = new PatchSemaphore(stateField);
926
- const viewPlugin = ViewPlugin2.fromClass(class AutomergeCodemirrorViewPlugin {
1923
+ const viewPlugin = ViewPlugin3.fromClass(class AutomergeCodemirrorViewPlugin {
927
1924
  constructor(view) {
928
1925
  this._handleChange = () => {
929
1926
  this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
@@ -931,8 +1928,8 @@ var automergePlugin = (handle, path) => {
931
1928
  this._view = view;
932
1929
  handle.addListener("change", this._handleChange);
933
1930
  }
934
- update(update2) {
935
- if (update2.transactions.length > 0 && update2.transactions.some((t) => !isReconcileTx(t))) {
1931
+ update(update4) {
1932
+ if (update4.transactions.length > 0 && update4.transactions.some((t) => !isReconcileTx(t))) {
936
1933
  queueMicrotask(() => {
937
1934
  this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
938
1935
  });
@@ -953,47 +1950,8 @@ var automergePlugin = (handle, path) => {
953
1950
  var reconcileAnnotationType = Annotation.define();
954
1951
  var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
955
1952
 
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
1953
  // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
996
- import * as random from "lib0/random";
1954
+ import * as random2 from "lib0/random";
997
1955
  var cursorColors = [
998
1956
  {
999
1957
  color: "#30bced",
@@ -1028,7 +1986,7 @@ var cursorColors = [
1028
1986
  light: "#1be7ff33"
1029
1987
  }
1030
1988
  ];
1031
- var cursorColor = cursorColors[random.uint32() % cursorColors.length];
1989
+ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
1032
1990
 
1033
1991
  // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
1034
1992
  import * as decoding from "lib0/decoding";
@@ -1036,7 +1994,7 @@ import * as encoding from "lib0/encoding";
1036
1994
  import { Observable } from "lib0/observable";
1037
1995
  import * as awarenessProtocol from "y-protocols/awareness";
1038
1996
  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";
1997
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1040
1998
  var messageAwareness = 1;
1041
1999
  var messageQueryAwareness = 3;
1042
2000
  var SpaceAwarenessProvider = class extends Observable {
@@ -1073,7 +2031,7 @@ var SpaceAwarenessProvider = class extends Observable {
1073
2031
  removed,
1074
2032
  origin
1075
2033
  }, {
1076
- F: __dxlog_file,
2034
+ F: __dxlog_file3,
1077
2035
  L: 67,
1078
2036
  S: this,
1079
2037
  C: (f, a) => f(...a)
@@ -1086,7 +2044,7 @@ var SpaceAwarenessProvider = class extends Observable {
1086
2044
  }
1087
2045
  _handleSpaceMessage({ payload }) {
1088
2046
  log("space message", payload, {
1089
- F: __dxlog_file,
2047
+ F: __dxlog_file3,
1090
2048
  L: 79,
1091
2049
  S: this,
1092
2050
  C: (f, a) => f(...a)
@@ -1131,185 +2089,80 @@ var SpaceAwarenessProvider = class extends Observable {
1131
2089
  };
1132
2090
 
1133
2091
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2092
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
1134
2093
  var useTextModel = ({ identity, space, text }) => {
1135
- const provider = useMemo2(() => {
1136
- if (isActualAutomergeObject(text)) {
1137
- return void 0;
1138
- }
1139
- if (!space || !text?.doc) {
1140
- return void 0;
1141
- }
1142
- return new SpaceAwarenessProvider({
2094
+ const [model, setModel] = useState2(() => createModel({
2095
+ identity,
2096
+ space,
2097
+ text
2098
+ }));
2099
+ useEffect2(() => {
2100
+ setModel(createModel({
2101
+ identity,
1143
2102
  space,
1144
- doc: text.doc,
1145
- channel: `yjs.awareness.${text.id}`
1146
- });
2103
+ text
2104
+ }));
1147
2105
  }, [
1148
2106
  identity,
1149
2107
  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;
1161
- }
1162
- }, [
1163
2108
  text
1164
2109
  ]);
2110
+ return model;
2111
+ };
2112
+ var createModel = (options) => {
2113
+ const { text } = options;
1165
2114
  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
1175
- };
1176
- }
1177
- if (!text?.doc || !text?.content) {
1178
- return void 0;
2115
+ return createAutomergeModel(options);
2116
+ } else {
2117
+ if (!text?.doc) {
2118
+ return void 0;
2119
+ }
2120
+ return createYjsModel(options);
1179
2121
  }
2122
+ };
2123
+ var createYjsModel = ({ identity, space, text }) => {
2124
+ invariant3(text?.doc && text?.content, void 0, {
2125
+ F: __dxlog_file4,
2126
+ L: 74,
2127
+ S: void 0,
2128
+ A: [
2129
+ "text?.doc && text?.content",
2130
+ ""
2131
+ ]
2132
+ });
2133
+ const provider = space ? new SpaceAwarenessProvider({
2134
+ space,
2135
+ doc: text.doc,
2136
+ channel: `yjs.awareness.${text.id}`
2137
+ }) : void 0;
1180
2138
  return {
1181
2139
  id: text.doc.guid,
1182
2140
  content: text.content,
1183
- provider,
2141
+ text: () => text.content.toString(),
2142
+ extension: yCollab(text.content, provider?.awareness),
2143
+ awareness: provider?.awareness,
1184
2144
  peer: identity ? {
1185
2145
  id: identity.identityKey.toHex(),
1186
2146
  name: identity.profile?.displayName
1187
2147
  } : void 0
1188
2148
  };
1189
2149
  };
1190
-
1191
- // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
1192
- var EditorModes = [
1193
- "default",
1194
- "vim"
1195
- ];
1196
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots, editorMode }, forwardedRef) => {
1197
- const { themeMode } = useThemeContext();
1198
- const tabsterDOMAttribute = useFocusableGroup({
1199
- tabBehavior: "limited"
1200
- });
1201
- const [parent, setParent] = useState(null);
1202
- const [state, setState] = useState();
1203
- const [view, setView] = useState();
1204
- useImperativeHandle(forwardedRef, () => ({
1205
- editor: parent,
1206
- state,
1207
- view
1208
- }), [
1209
- view,
1210
- state,
1211
- parent
1212
- ]);
1213
- const collaboration = useCollaboration(model, themeMode);
1214
- useEffect2(() => {
1215
- if (!parent) {
1216
- return;
1217
- }
1218
- const initialText = isDocAccessor2(model.content) ? get3(model.content.handle.docSync(), model.content.path) : model.content?.toString();
1219
- const state2 = EditorState2.create({
1220
- doc: initialText,
1221
- extensions: [
1222
- // TODO(burdon): Factor out VIM mode?
1223
- editorMode === "vim" && vim(),
1224
- // Replication.
1225
- collaboration,
1226
- // Theme.
1227
- slots?.editor?.theme && EditorView5.theme(slots?.editor?.theme),
1228
- // Custom.
1229
- ...extensions
1230
- ].filter(Boolean)
1231
- });
1232
- setState(state2);
1233
- view?.destroy();
1234
- setView(new EditorView5({
1235
- state: state2,
1236
- parent
1237
- }));
1238
- return () => {
1239
- view?.destroy();
1240
- setView(void 0);
1241
- setState(void 0);
1242
- };
1243
- }, [
1244
- parent,
1245
- model.content,
1246
- themeMode,
1247
- editorMode
1248
- ]);
1249
- const handleKeyUp = useCallback((event) => {
1250
- const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
1251
- switch (key) {
1252
- case "Enter": {
1253
- view?.contentDOM.focus();
1254
- break;
1255
- }
1256
- case "Escape": {
1257
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && parent?.focus();
1258
- break;
1259
- }
1260
- }
1261
- }, [
1262
- view,
1263
- editorMode
2150
+ var createAutomergeModel = ({ identity, text }) => {
2151
+ const obj = text;
2152
+ const doc = getRawDoc(obj, [
2153
+ obj.field
1264
2154
  ]);
1265
- return /* @__PURE__ */ React.createElement("div", {
1266
- key: model.id,
1267
- ref: setParent,
1268
- tabIndex: 0,
1269
- ...slots?.root,
1270
- ...editorMode !== "vim" && tabsterDOMAttribute,
1271
- onKeyUp: handleKeyUp
1272
- });
1273
- });
1274
- var TextEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
1275
- const { themeMode } = useThemeContext();
1276
- const extensions = [
1277
- ..._extensions ?? [],
1278
- basicBundle({
1279
- themeMode,
1280
- placeholder: slots?.editor?.placeholder
1281
- })
1282
- ];
1283
- return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1284
- ref: forwardedRef,
1285
- extensions,
1286
- slots: defaultsDeep({}, slots, {
1287
- editor: {
1288
- theme: basicTheme
1289
- }
1290
- }),
1291
- ...props
1292
- });
1293
- });
1294
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
1295
- const { themeMode } = useThemeContext();
1296
- const extensions = [
1297
- ..._extensions ?? [],
1298
- markdownBundle({
1299
- themeMode
1300
- })
1301
- ];
1302
- return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1303
- ref: forwardedRef,
1304
- extensions,
1305
- slots: defaultsDeep({}, slots, {
1306
- editor: {
1307
- theme: markdownTheme
1308
- }
1309
- }),
1310
- ...props
1311
- });
1312
- });
2155
+ return {
2156
+ id: obj.id,
2157
+ content: doc,
2158
+ text: () => get3(doc.handle.docSync(), doc.path),
2159
+ extension: automergePlugin(doc.handle, doc.path),
2160
+ peer: identity ? {
2161
+ id: identity.identityKey.toHex(),
2162
+ name: identity.profile?.displayName
2163
+ } : void 0
2164
+ };
2165
+ };
1313
2166
  export {
1314
2167
  BaseTextEditor,
1315
2168
  Doc,
@@ -1318,34 +2171,34 @@ export {
1318
2171
  SpaceAwarenessProvider,
1319
2172
  TextEditor,
1320
2173
  TextKind,
1321
- YText2 as YText,
2174
+ YText,
1322
2175
  YXmlFragment,
2176
+ autocomplete,
1323
2177
  basicBundle,
1324
- basicTheme,
1325
- chalky,
1326
- coral,
1327
- createHyperlinkTooltip,
2178
+ blast,
2179
+ codeTheme,
2180
+ comments,
1328
2181
  cursorColor,
1329
- cyan,
1330
- highlightBackground,
1331
- hyperLinkExtension,
1332
- hyperlinkDecoration,
1333
- hyperlinkStyle,
1334
- hyperlinkWidget,
1335
- invalid,
1336
- ivory,
1337
- malibu,
2182
+ defaultMarkdownSlots,
2183
+ defaultOptions,
2184
+ defaultSlots,
2185
+ defaultTextSlots,
2186
+ defaultTheme,
2187
+ demo,
2188
+ image,
2189
+ link,
2190
+ listener,
1338
2191
  markdownBundle,
1339
2192
  markdownHighlightStyle,
2193
+ markdownTags,
2194
+ markdownTagsExtensions,
1340
2195
  markdownTheme,
1341
- onChangeExtension,
1342
- sage,
1343
- stone,
2196
+ mention,
2197
+ table,
1344
2198
  tags2 as tags,
1345
- tooltipBackground,
1346
- useCollaboration,
1347
- useTextModel,
1348
- violet,
1349
- whiskey
2199
+ tasklist,
2200
+ textTheme,
2201
+ tooltip,
2202
+ useTextModel
1350
2203
  };
1351
2204
  //# sourceMappingURL=index.mjs.map