@dxos/react-ui-editor 0.3.10-main.90b298e → 0.3.10-main.92d1f2c

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 (65) hide show
  1. package/dist/lib/browser/index.mjs +897 -293
  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 +19 -1
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +2 -1
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -1
  9. package/dist/types/src/components/TextEditor/extensions/basic.d.ts +2 -1
  10. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -1
  11. package/dist/types/src/components/TextEditor/extensions/blast.d.ts +25 -0
  12. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +1 -0
  13. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +1 -1
  14. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -1
  15. package/dist/types/src/components/TextEditor/extensions/demo.d.ts +2 -1
  16. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +1 -1
  17. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts +1 -1
  18. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -1
  19. package/dist/types/src/components/TextEditor/extensions/image.d.ts +4 -0
  20. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +1 -0
  21. package/dist/types/src/components/TextEditor/extensions/index.d.ts +4 -0
  22. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  23. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -1
  24. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -1
  25. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +2 -1
  26. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  27. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +9 -3
  28. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -1
  29. package/dist/types/src/components/TextEditor/extensions/mention.d.ts +6 -0
  30. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +1 -0
  31. package/dist/types/src/components/TextEditor/extensions/table.d.ts +8 -0
  32. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +1 -0
  33. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +2 -1
  34. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -1
  35. package/dist/types/src/components/TextEditor/themes/default.d.ts +5 -0
  36. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -1
  37. package/dist/types/src/hooks/automerge/automerge.stories.d.ts +8 -1
  38. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -1
  39. package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +1 -1
  40. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
  41. package/dist/types/src/styles/markdown.d.ts +3 -2
  42. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  43. package/package.json +20 -20
  44. package/src/components/TextEditor/MarkdownEditor.stories.tsx +102 -27
  45. package/src/components/TextEditor/TextEditor.tsx +13 -23
  46. package/src/components/TextEditor/extensions/autocomplete.ts +5 -20
  47. package/src/components/TextEditor/extensions/basic.ts +2 -1
  48. package/src/components/TextEditor/extensions/blast.ts +387 -0
  49. package/src/components/TextEditor/extensions/comments.ts +21 -39
  50. package/src/components/TextEditor/extensions/demo.ts +4 -1
  51. package/src/components/TextEditor/extensions/experimental.tsx +14 -35
  52. package/src/components/TextEditor/extensions/image.ts +67 -0
  53. package/src/components/TextEditor/extensions/index.ts +4 -0
  54. package/src/components/TextEditor/extensions/link.ts +17 -25
  55. package/src/components/TextEditor/extensions/listener.ts +3 -3
  56. package/src/components/TextEditor/extensions/markdown/bundle.ts +30 -19
  57. package/src/components/TextEditor/extensions/markdown/highlight.ts +147 -108
  58. package/src/components/TextEditor/extensions/mention.ts +31 -0
  59. package/src/components/TextEditor/extensions/table.ts +122 -0
  60. package/src/components/TextEditor/extensions/tasklist.ts +41 -27
  61. package/src/components/TextEditor/themes/default.ts +54 -2
  62. package/src/hooks/automerge/automerge.stories.tsx +3 -2
  63. package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +1 -1
  64. package/src/styles/markdown.ts +14 -8
  65. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
@@ -24,13 +24,13 @@ var autocomplete = ({ onSearch }) => {
24
24
  // TODO(burdon): Optional decoration via addToOptions
25
25
  override: [
26
26
  (context) => {
27
- const word = context.matchBefore(/\w*/);
28
- if (!word || word.from === word.to && !context.explicit) {
27
+ const match = context.matchBefore(/\w*/);
28
+ if (!match || match.from === match.to && !context.explicit) {
29
29
  return null;
30
30
  }
31
31
  return {
32
- from: word.from,
33
- options: onSearch(word.text.toLowerCase())
32
+ from: match.from,
33
+ options: onSearch(match.text.toLowerCase())
34
34
  };
35
35
  }
36
36
  ]
@@ -43,7 +43,7 @@ import { closeBrackets } from "@codemirror/autocomplete";
43
43
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
44
44
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
45
45
  import { EditorView, placeholder } from "@codemirror/view";
46
- var basicBundle = ({ themeMode, placeholder: _placeholder }) => [
46
+ var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
47
47
  // TODO(burdon): Compare with minimalSetup.
48
48
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
49
49
  bracketMatching(),
@@ -54,8 +54,333 @@ var basicBundle = ({ themeMode, placeholder: _placeholder }) => [
54
54
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
55
55
  ].filter(Boolean);
56
56
 
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
+
57
382
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/demo.ts
58
- import { keymap as keymap2 } from "@codemirror/view";
383
+ import { keymap as keymap3 } from "@codemirror/view";
59
384
  var defaultItems = [
60
385
  "hello world!",
61
386
  "this is a test.",
@@ -65,7 +390,7 @@ var demo = ({ delay = 75, items = defaultItems } = {}) => {
65
390
  let t;
66
391
  let idx = 0;
67
392
  return [
68
- keymap2.of([
393
+ keymap3.of([
69
394
  {
70
395
  // Reset.
71
396
  key: "alt-meta-'",
@@ -112,18 +437,30 @@ var demo = ({ delay = 75, items = defaultItems } = {}) => {
112
437
  };
113
438
 
114
439
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts
115
- import { keymap as keymap3, Decoration, EditorView as EditorView2, MatchDecorator, ViewPlugin, WidgetType } from "@codemirror/view";
440
+ import { keymap as keymap4, Decoration, EditorView as EditorView3, MatchDecorator, ViewPlugin, WidgetType } from "@codemirror/view";
116
441
  import { debounce } from "@dxos/async";
117
- import { invariant } from "@dxos/invariant";
118
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts";
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
+ });
119
455
  var BookmarkWidget = class extends WidgetType {
120
- constructor(_pos, _id) {
456
+ constructor(_pos, _id, _handleClick) {
121
457
  super();
122
458
  this._pos = _pos;
123
459
  this._id = _id;
124
- invariant(this._id, void 0, {
125
- F: __dxlog_file,
126
- L: 41,
460
+ this._handleClick = _handleClick;
461
+ invariant2(this._id, void 0, {
462
+ F: __dxlog_file2,
463
+ L: 37,
127
464
  S: this,
128
465
  A: [
129
466
  "this._id",
@@ -137,21 +474,11 @@ var BookmarkWidget = class extends WidgetType {
137
474
  toDOM() {
138
475
  const span = document.createElement("span");
139
476
  span.className = "cm-bookmark";
140
- span.textContent = "\u{1F4AC}";
477
+ span.textContent = "\xA7";
478
+ span.onclick = () => this._handleClick();
141
479
  return span;
142
480
  }
143
481
  };
144
- var styles = EditorView2.baseTheme({
145
- "& .cm-bookmark": {
146
- cursor: "pointer",
147
- margin: "4px",
148
- padding: "4px",
149
- backgroundColor: "yellow"
150
- },
151
- "& .cm-bookmark-selected": {
152
- backgroundColor: "orange"
153
- }
154
- });
155
482
  var comments = (options = {}) => {
156
483
  let active;
157
484
  const bookmarkMatcher = new MatchDecorator({
@@ -160,7 +487,9 @@ var comments = (options = {}) => {
160
487
  const id = match[1];
161
488
  return Decoration.replace({
162
489
  id,
163
- widget: new BookmarkWidget(pos, id)
490
+ widget: new BookmarkWidget(pos, id, () => handleUpdate({
491
+ active: id
492
+ }))
164
493
  });
165
494
  }
166
495
  });
@@ -168,20 +497,20 @@ var comments = (options = {}) => {
168
497
  constructor(view) {
169
498
  this.bookmarks = bookmarkMatcher.createDeco(view);
170
499
  }
171
- update(update2) {
172
- this.bookmarks = bookmarkMatcher.updateDeco(update2, this.bookmarks);
500
+ update(update4) {
501
+ this.bookmarks = bookmarkMatcher.updateDeco(update4, this.bookmarks);
173
502
  }
174
503
  }, {
175
504
  decorations: (instance) => instance.bookmarks,
176
- provide: (plugin) => EditorView2.atomicRanges.of((view) => {
505
+ provide: (plugin) => EditorView3.atomicRanges.of((view) => {
177
506
  return view.plugin(plugin)?.bookmarks || Decoration.none;
178
507
  })
179
508
  });
180
- const doUpdate = debounce((data) => {
509
+ const handleUpdate = debounce((data) => {
181
510
  options.onUpdate?.(data);
182
511
  }, 200);
183
512
  return [
184
- keymap3.of([
513
+ keymap4.of([
185
514
  {
186
515
  key: options?.key ?? "shift-meta-c",
187
516
  run: (view) => {
@@ -206,20 +535,23 @@ var comments = (options = {}) => {
206
535
  ]),
207
536
  bookmarks,
208
537
  // Monitor cursor movement.
209
- EditorView2.updateListener.of((update2) => {
538
+ EditorView3.updateListener.of((update4) => {
210
539
  active = void 0;
211
540
  if (!options.onUpdate) {
212
541
  return;
213
542
  }
214
- const view = update2.view;
543
+ const view = update4.view;
215
544
  const pos = view.state.selection.main.head;
216
545
  const decorations = [];
217
546
  const rangeSet = view.plugin(bookmarks)?.bookmarks;
218
547
  let closest = Infinity;
219
- const { from, to } = view.visibleRanges?.[0] ?? {
220
- from: 0,
221
- to: view.state.doc.length
222
- };
548
+ const { from, to } = (
549
+ /* view.visibleRanges?.[0] ?? */
550
+ {
551
+ from: 0,
552
+ to: view.state.doc.length
553
+ }
554
+ );
223
555
  rangeSet?.between(from, to, (from2, to2, value) => {
224
556
  decorations.push({
225
557
  from: from2,
@@ -233,7 +565,7 @@ var comments = (options = {}) => {
233
565
  }
234
566
  });
235
567
  if (decorations.length) {
236
- doUpdate({
568
+ handleUpdate({
237
569
  active,
238
570
  items: decorations.map(({ from: from2, value: { spec: { id } } }) => ({
239
571
  id,
@@ -247,11 +579,64 @@ var comments = (options = {}) => {
247
579
  ];
248
580
  };
249
581
 
250
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
582
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/image.ts
251
583
  import { syntaxTree } from "@codemirror/language";
252
- import { RangeSetBuilder, StateField } from "@codemirror/state";
253
- import { Decoration as Decoration2, EditorView as EditorView3, WidgetType as WidgetType2 } from "@codemirror/view";
254
- var LinkButton = class extends WidgetType2 {
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 {
255
640
  constructor(_pos, _url, _onAttach) {
256
641
  super();
257
642
  this._pos = _pos;
@@ -267,7 +652,7 @@ var LinkButton = class extends WidgetType2 {
267
652
  return el;
268
653
  }
269
654
  };
270
- var LinkText = class extends WidgetType2 {
655
+ var LinkText = class extends WidgetType3 {
271
656
  constructor(_pos, _text, _url) {
272
657
  super();
273
658
  this._pos = _pos;
@@ -297,10 +682,10 @@ var LinkText = class extends WidgetType2 {
297
682
  return link2;
298
683
  }
299
684
  };
300
- var update = (state, options) => {
301
- const builder = new RangeSetBuilder();
685
+ var update2 = (state, options) => {
686
+ const builder = new RangeSetBuilder2();
302
687
  const cursor = state.selection.main.head;
303
- syntaxTree(state).iterate({
688
+ syntaxTree2(state).iterate({
304
689
  enter: (node) => {
305
690
  if (node.name === "Link") {
306
691
  const marks = node.node.getChildren("LinkMark");
@@ -310,13 +695,13 @@ var update = (state, options) => {
310
695
  if (!url) {
311
696
  return false;
312
697
  }
313
- if (cursor < node.from || cursor > node.to) {
314
- builder.add(node.from, node.to, Decoration2.replace({
698
+ if (state.readOnly || cursor < node.from || cursor > node.to) {
699
+ builder.add(node.from, node.to, Decoration3.replace({
315
700
  widget: new LinkText(node.from, text, options.link ? url : void 0)
316
701
  }));
317
702
  }
318
703
  if (options.onRender) {
319
- builder.add(node.to, node.to, Decoration2.replace({
704
+ builder.add(node.to, node.to, Decoration3.replace({
320
705
  widget: new LinkButton(node.from, url, options.onRender)
321
706
  }));
322
707
  }
@@ -326,52 +711,71 @@ var update = (state, options) => {
326
711
  });
327
712
  return builder.finish();
328
713
  };
329
- var link = (options = {}) => {
330
- return StateField.define({
331
- create: (state) => update(state, options),
332
- update: (_, tr) => update(tr.state, options),
333
- provide: (field) => EditorView3.decorations.from(field)
334
- });
335
- };
336
714
 
337
715
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
338
- import { StateField as StateField2 } from "@codemirror/state";
339
- var listener = ({ onChange }) => StateField2.define({
340
- create: () => null,
341
- update: (_value, transaction) => {
342
- if (transaction.docChanged && onChange) {
343
- onChange(transaction.newDoc.toString());
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;
344
725
  }
345
- return null;
346
- }
347
- });
726
+ });
727
+ };
348
728
 
349
729
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
350
730
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
351
- import { defaultKeymap, history, indentWithTab } from "@codemirror/commands";
731
+ import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
352
732
  import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
353
733
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
354
734
  import { languages } from "@codemirror/language-data";
355
735
  import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
356
736
  import { EditorState } from "@codemirror/state";
357
737
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
358
- import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView4, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap as keymap4, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
738
+ import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView6, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap as keymap5, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
359
739
 
360
740
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
361
741
  import { markdownLanguage } from "@codemirror/lang-markdown";
362
742
  import { HighlightStyle } from "@codemirror/language";
363
743
  import { tags, styleTags, Tag } from "@lezer/highlight";
744
+ import { Table } from "@lezer/markdown";
364
745
  import get from "lodash.get";
746
+ import { mx as mx2 } from "@dxos/react-ui-theme";
365
747
 
366
748
  // packages/ui/react-ui-editor/src/styles/markdown.ts
367
749
  import { mx } from "@dxos/react-ui-theme";
368
- var heading = {
369
- 1: "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
370
- 2: "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
371
- 3: "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
372
- 4: "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
373
- 5: "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
374
- 6: "mbs-4 mbe-2 font-medium 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);
375
779
  };
376
780
  var light = "text-neutral-200 dark:text-neutral-800";
377
781
  var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
@@ -382,7 +786,7 @@ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
382
786
  var codeMark = "font-mono text-primary-500";
383
787
  var inlineUrl = mx(code, "px-1");
384
788
  var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
385
- var horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-500/50";
789
+ var horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800";
386
790
 
387
791
  // packages/ui/react-ui-editor/src/styles/tokens.ts
388
792
  import { tailwindConfig } from "@dxos/react-ui-theme";
@@ -401,159 +805,193 @@ var markdownTags = {
401
805
  LinkReference: Tag.define(),
402
806
  ListMark: Tag.define(),
403
807
  QuoteMark: Tag.define(),
404
- URL: Tag.define()
808
+ URL: Tag.define(),
809
+ // Custom.
810
+ TableCell: Tag.define()
405
811
  };
406
- var markdownTagsExtension = {
407
- props: [
408
- styleTags(markdownTags)
409
- ]
410
- };
411
- var markdownHighlightStyle = HighlightStyle.define([
412
- {
413
- tag: [
414
- tags.keyword,
415
- tags.name,
416
- tags.deleted,
417
- tags.character,
418
- tags.propertyName,
419
- tags.macroName,
420
- tags.color,
421
- tags.constant(tags.name),
422
- tags.standard(tags.name),
423
- tags.definition(tags.name),
424
- tags.separator,
425
- tags.typeName,
426
- tags.className,
427
- tags.number,
428
- tags.changed,
429
- tags.annotation,
430
- tags.modifier,
431
- tags.self,
432
- tags.namespace,
433
- tags.operator,
434
- tags.operatorKeyword,
435
- tags.escape,
436
- tags.regexp,
437
- tags.special(tags.string),
438
- tags.meta,
439
- tags.comment,
440
- tags.atom,
441
- tags.bool,
442
- tags.special(tags.variableName),
443
- tags.processingInstruction,
444
- tags.string,
445
- tags.inserted,
446
- tags.invalid
447
- ],
448
- color: "inherit !important"
449
- },
450
- // Markdown marks.
451
- {
452
- tag: [
453
- tags.meta,
454
- tags.processingInstruction,
455
- markdownTags.CodeMark,
456
- markdownTags.EmphasisMark,
457
- markdownTags.HeaderMark,
458
- markdownTags.LinkLabel,
459
- markdownTags.LinkReference,
460
- markdownTags.ListMark,
461
- markdownTags.QuoteMark
462
- ],
463
- class: mark
464
- },
465
- // Headings.
466
- {
467
- tag: tags.heading1,
468
- class: heading[1]
469
- },
470
- {
471
- tag: tags.heading2,
472
- class: heading[2]
473
- },
474
- {
475
- tag: tags.heading3,
476
- class: heading[3]
477
- },
478
- {
479
- tag: tags.heading4,
480
- class: heading[4]
481
- },
482
- {
483
- tag: tags.heading5,
484
- class: heading[5]
485
- },
486
- {
487
- tag: tags.heading6,
488
- class: heading[6]
489
- },
490
- // Emphasis.
491
- {
492
- tag: tags.emphasis,
493
- class: italic
494
- },
495
- {
496
- tag: tags.strong,
497
- class: bold
498
- },
499
- {
500
- tag: tags.strikethrough,
501
- class: strikethrough
502
- },
503
- // Naked URLs.
504
- {
505
- tag: [
506
- markdownTags.URL
507
- ],
508
- class: inlineUrl
509
- },
510
- // E.g., code block language (after ```).
511
- {
512
- tag: [
513
- tags.function(tags.variableName),
514
- tags.labelName
515
- ],
516
- class: codeMark
517
- },
518
- // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
519
- // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
520
- // when a language is specified. In this case, the syntax highlighting extensions will colorize
521
- // the code, but all other CSS properties will be inherited.
522
- // IMPORTANT: Therefore, the fenced code block will use the base editor font.
523
- {
524
- tag: [
525
- markdownTags.CodeText,
526
- markdownTags.InlineCode
527
- ],
528
- class: code
529
- },
530
- {
531
- tag: [
532
- markdownTags.QuoteMark
533
- ],
534
- class: blockquote
535
- },
536
- {
537
- tag: [
538
- markdownTags.HorizontalRule
539
- ],
540
- class: horizontalRule
541
- }
542
- ], {
543
- scope: markdownLanguage,
544
- all: {
545
- fontFamily: get(tokens, "fontFamily.body", []).join(",")
812
+ Table.defineNodes?.forEach((node) => {
813
+ switch (node?.name) {
814
+ case "TableCell": {
815
+ node.style = markdownTags.TableCell;
816
+ break;
817
+ }
546
818
  }
547
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
+ {
967
+ tag: [
968
+ markdownTags.HorizontalRule
969
+ ],
970
+ class: mx2(horizontalRule, readonly && "-indent-[100rem]")
971
+ },
972
+ // TODO(burdon): Only if being edited.
973
+ {
974
+ tag: [
975
+ markdownTags.TableCell
976
+ ],
977
+ class: "font-mono"
978
+ }
979
+ ], {
980
+ scope: markdownLanguage,
981
+ all: {
982
+ fontFamily: get(tokens, "fontFamily.body", []).join(",")
983
+ }
984
+ });
985
+ };
548
986
 
549
987
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
550
- var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
988
+ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
551
989
  return [
552
990
  bracketMatching2(),
553
991
  closeBrackets2(),
554
992
  _placeholder && placeholder2(_placeholder),
555
993
  EditorState.allowMultipleSelections.of(true),
556
- EditorView4.lineWrapping,
994
+ EditorView6.lineWrapping,
557
995
  crosshairCursor(),
558
996
  dropCursor(),
559
997
  drawSelection(),
@@ -564,16 +1002,7 @@ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
564
1002
  history(),
565
1003
  indentOnInput(),
566
1004
  rectangularSelection(),
567
- // TODO(burdon): Review.
568
- keymap4.of([
569
- ...closeBracketsKeymap,
570
- ...defaultKeymap,
571
- // ...foldKeymap,
572
- // ...historyKeymap,
573
- // ...lintKeymap,
574
- ...searchKeymap,
575
- indentWithTab
576
- ]),
1005
+ customkeymap(),
577
1006
  // Main extension.
578
1007
  // https://github.com/codemirror/lang-markdown
579
1008
  // https://codemirror.net/5/mode/markdown/index.html (demo).
@@ -588,20 +1017,153 @@ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
588
1017
  codeLanguages: languages,
589
1018
  // Parser extensions.
590
1019
  extensions: [
591
- // GFM, // TODO(burdon): Provided by default?
592
- markdownTagsExtension
1020
+ // GFM provided by default.
1021
+ markdownTagsExtensions
593
1022
  ]
594
1023
  }),
1024
+ // https://github.com/codemirror/theme-one-dark
1025
+ themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
595
1026
  // Custom styles.
596
- syntaxHighlighting2(markdownHighlightStyle),
597
- // TODO(thure): All but one rule here apply to both themes; rename or refactor.
598
- themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2)
1027
+ syntaxHighlighting2(markdownHighlightStyle(readonly))
599
1028
  ].filter(Boolean);
600
1029
  };
1030
+ var customkeymap = () => keymap5.of([
1031
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1032
+ indentWithTab,
1033
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
1034
+ ...standardKeymap,
1035
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
1036
+ ...historyKeymap,
1037
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1038
+ ...closeBracketsKeymap,
1039
+ // https://codemirror.net/docs/ref/#search.searchKeymap
1040
+ ...searchKeymap
1041
+ ]);
1042
+
1043
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/mention.ts
1044
+ import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1045
+ var mention = ({ onSearch }) => {
1046
+ return autocompletion2({
1047
+ closeOnBlur: false,
1048
+ override: [
1049
+ (context) => {
1050
+ const match = context.matchBefore(/@(\w+)?/);
1051
+ if (!match || match.from === match.to && !context.explicit) {
1052
+ return null;
1053
+ }
1054
+ console.log(match);
1055
+ return {
1056
+ from: match.from,
1057
+ options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1058
+ label: `@${value}`
1059
+ }))
1060
+ };
1061
+ }
1062
+ ]
1063
+ });
1064
+ };
1065
+
1066
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/table.ts
1067
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1068
+ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField5 } from "@codemirror/state";
1069
+ import { Decoration as Decoration4, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1070
+ var table = (options = {}) => {
1071
+ return StateField5.define({
1072
+ create: (state) => update3(state, options),
1073
+ update: (_, tr) => update3(tr.state, options),
1074
+ provide: (field) => EditorView7.decorations.from(field)
1075
+ });
1076
+ };
1077
+ var update3 = (state, options) => {
1078
+ const builder = new RangeSetBuilder3();
1079
+ const cursor = state.selection.main.head;
1080
+ const tables = [];
1081
+ const getTable = () => tables[tables.length - 1];
1082
+ const getRow = () => {
1083
+ const table2 = getTable();
1084
+ return table2.rows?.[table2.rows.length - 1];
1085
+ };
1086
+ syntaxTree3(state).iterate({
1087
+ enter: (node) => {
1088
+ switch (node.name) {
1089
+ case "Table": {
1090
+ tables.push({
1091
+ from: node.from,
1092
+ to: node.to
1093
+ });
1094
+ break;
1095
+ }
1096
+ case "TableHeader": {
1097
+ getTable().header = [];
1098
+ break;
1099
+ }
1100
+ case "TableRow": {
1101
+ (getTable().rows ??= []).push([]);
1102
+ break;
1103
+ }
1104
+ case "TableCell": {
1105
+ const row = getRow();
1106
+ if (row) {
1107
+ row.push(state.sliceDoc(node.from, node.to));
1108
+ } else {
1109
+ getTable().header?.push(state.sliceDoc(node.from, node.to));
1110
+ }
1111
+ break;
1112
+ }
1113
+ }
1114
+ }
1115
+ });
1116
+ tables.forEach((table2) => {
1117
+ const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1118
+ hide && builder.add(table2.from, table2.to, Decoration4.replace({
1119
+ widget: new TableWidget(table2)
1120
+ }));
1121
+ });
1122
+ return builder.finish();
1123
+ };
1124
+ var TableWidget = class extends WidgetType4 {
1125
+ constructor(_table) {
1126
+ super();
1127
+ this._table = _table;
1128
+ }
1129
+ eq(other) {
1130
+ return this._table.from === other?._table?.from;
1131
+ }
1132
+ toDOM(view) {
1133
+ const table2 = document.createElement("table");
1134
+ {
1135
+ const header = table2.appendChild(document.createElement("thead"));
1136
+ const tr = header.appendChild(document.createElement("tr"));
1137
+ this._table.header?.forEach((cell) => {
1138
+ const th = document.createElement("th");
1139
+ th.setAttribute("class", "cm-table-head");
1140
+ tr.appendChild(th).textContent = cell;
1141
+ });
1142
+ }
1143
+ {
1144
+ const body = table2.appendChild(document.createElement("tbody"));
1145
+ this._table.rows?.forEach((row) => {
1146
+ const tr = body.appendChild(document.createElement("tr"));
1147
+ row.forEach((cell) => {
1148
+ const td = document.createElement("td");
1149
+ td.setAttribute("class", "cm-table-cell");
1150
+ tr.appendChild(td).textContent = cell;
1151
+ });
1152
+ });
1153
+ }
1154
+ return table2;
1155
+ }
1156
+ };
601
1157
 
602
1158
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
603
- import { EditorView as EditorView5, Decoration as Decoration3, WidgetType as WidgetType3, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin2 } from "@codemirror/view";
604
- var CheckboxWidget = class extends WidgetType3 {
1159
+ import { EditorView as EditorView8, Decoration as Decoration5, WidgetType as WidgetType5, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin2 } from "@codemirror/view";
1160
+ var styles2 = EditorView8.baseTheme({
1161
+ "& .cm-task-item": {
1162
+ paddingLeft: "4px",
1163
+ paddingRight: "8px"
1164
+ }
1165
+ });
1166
+ var CheckboxWidget = class extends WidgetType5 {
605
1167
  constructor(_pos, _checked, _indent, _onCheck) {
606
1168
  super();
607
1169
  this._pos = _pos;
@@ -620,30 +1182,29 @@ var CheckboxWidget = class extends WidgetType3 {
620
1182
  const input = wrap.appendChild(document.createElement("input"));
621
1183
  input.type = "checkbox";
622
1184
  input.checked = this._checked;
623
- input.onchange = (event) => {
624
- this._onCheck(event.target.checked);
625
- return true;
626
- };
1185
+ if (!this._onCheck) {
1186
+ input.setAttribute("disabled", "true");
1187
+ }
1188
+ if (this._onCheck) {
1189
+ input.onchange = (event) => {
1190
+ this._onCheck?.(event.target.checked);
1191
+ return true;
1192
+ };
1193
+ }
627
1194
  return wrap;
628
1195
  }
629
1196
  ignoreEvent() {
630
1197
  return false;
631
1198
  }
632
1199
  };
633
- var styles2 = EditorView5.baseTheme({
634
- "& .cm-task-item": {
635
- paddingLeft: "4px",
636
- paddingRight: "8px"
637
- }
638
- });
639
- var tasklist = () => {
1200
+ var tasklist = (options = {}) => {
640
1201
  const taskMatcher = new MatchDecorator2({
641
1202
  regexp: /^(\s*)- \[([ xX])\]\s/g,
642
1203
  decoration: (match, view, pos) => {
643
1204
  const indent = Math.floor(match[1].length / 2);
644
1205
  const checked = match[2] === "x" || match[2] === "X";
645
- return Decoration3.replace({
646
- widget: new CheckboxWidget(pos, checked, indent, (checked2) => {
1206
+ return Decoration5.replace({
1207
+ widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
647
1208
  const idx = pos + match[0].indexOf("[") + 1;
648
1209
  view.dispatch({
649
1210
  changes: {
@@ -664,13 +1225,13 @@ var tasklist = () => {
664
1225
  constructor(view) {
665
1226
  this.tasks = taskMatcher.createDeco(view);
666
1227
  }
667
- update(update2) {
668
- this.tasks = taskMatcher.updateDeco(update2, this.tasks);
1228
+ update(update4) {
1229
+ this.tasks = taskMatcher.updateDeco(update4, this.tasks);
669
1230
  }
670
1231
  }, {
671
1232
  decorations: (instance) => instance.tasks,
672
- provide: (plugin) => EditorView5.atomicRanges.of((view) => {
673
- return view.plugin(plugin)?.tasks || Decoration3.none;
1233
+ provide: (plugin) => EditorView8.atomicRanges.of((view) => {
1234
+ return view.plugin(plugin)?.tasks || Decoration5.none;
674
1235
  })
675
1236
  });
676
1237
  return [
@@ -837,8 +1398,51 @@ var defaultTheme = {
837
1398
  "& .cm-tooltip": {
838
1399
  border: "none"
839
1400
  },
840
- // '& .cm-tooltip-below': {},
841
- // '& .cm-tooltip-autocomplete': {},
1401
+ "& .cm-tooltip-below": {},
1402
+ //
1403
+ // autocomplete
1404
+ //
1405
+ "& .cm-tooltip-autocomplete": {
1406
+ marginTop: "4px",
1407
+ marginLeft: "-3px"
1408
+ },
1409
+ "& .cm-tooltip-autocomplete ul li": {},
1410
+ "& .cm-tooltip-autocomplete ul li[aria-selected]": {},
1411
+ "& .cm-completionIcon": {
1412
+ display: "none"
1413
+ },
1414
+ "& .cm-completionLabel": {
1415
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
1416
+ },
1417
+ "& .cm-completionMatchedText": {
1418
+ textDecoration: "none"
1419
+ },
1420
+ //
1421
+ // widgets
1422
+ //
1423
+ "& .cm-widgetBuffer": {
1424
+ display: "none",
1425
+ height: 0
1426
+ },
1427
+ //
1428
+ // table
1429
+ //
1430
+ "& .cm-table-head": {
1431
+ padding: "2px 16px 2px 0px",
1432
+ borderBottom: `1px solid ${get2(tokens, "extend.colors.neutral.500")}`,
1433
+ fontWeight: 100,
1434
+ textAlign: "left",
1435
+ color: get2(tokens, "extend.colors.neutral.500")
1436
+ },
1437
+ "& .cm-table-cell": {
1438
+ padding: "2px 16px 2px 0px"
1439
+ },
1440
+ //
1441
+ // image
1442
+ //
1443
+ "& .cm-image": {
1444
+ margin: "0.5rem 0"
1445
+ },
842
1446
  //
843
1447
  // font size
844
1448
  // 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.
@@ -888,19 +1492,19 @@ var codeTheme = {
888
1492
 
889
1493
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
890
1494
  import { EditorState as EditorState2 } from "@codemirror/state";
891
- import { EditorView as EditorView6 } from "@codemirror/view";
1495
+ import { EditorView as EditorView9 } from "@codemirror/view";
892
1496
  import { useFocusableGroup } from "@fluentui/react-tabster";
893
1497
  import { vim } from "@replit/codemirror-vim";
894
- import defaultsDeep from "lodash.defaultsdeep";
1498
+ import defaultsDeep2 from "lodash.defaultsdeep";
895
1499
  import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
896
1500
  import { generateName } from "@dxos/display-name";
897
1501
  import { useThemeContext } from "@dxos/react-ui";
898
- import { getColorForValue, inputSurface, mx as mx2 } from "@dxos/react-ui-theme";
1502
+ import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
899
1503
  var EditorModes = [
900
1504
  "default",
901
1505
  "vim"
902
1506
  ];
903
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots = defaultSlots, editorMode }, forwardedRef) => {
1507
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
904
1508
  const { themeMode } = useThemeContext();
905
1509
  const tabsterDOMAttribute = useFocusableGroup({
906
1510
  tabBehavior: "limited"
@@ -945,13 +1549,14 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots
945
1549
  const state2 = EditorState2.create({
946
1550
  doc: model.text(),
947
1551
  extensions: [
1552
+ readonly && EditorState2.readOnly.of(readonly),
948
1553
  // TODO(burdon): Factor out VIM mode?
949
1554
  editorMode === "vim" && vim(),
950
1555
  // Theme.
951
- EditorView6.baseTheme(defaultTheme),
952
- EditorView6.theme(slots?.editor?.theme ?? {}),
1556
+ EditorView9.baseTheme(defaultTheme),
1557
+ EditorView9.theme(slots?.editor?.theme ?? {}),
953
1558
  // TODO(burdon): themeMode doesn't change in storybooks.
954
- EditorView6.darkTheme.of(themeMode === "dark"),
1559
+ EditorView9.darkTheme.of(themeMode === "dark"),
955
1560
  // Storage and replication.
956
1561
  model.extension,
957
1562
  // Custom.
@@ -959,7 +1564,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots
959
1564
  ].filter(Boolean)
960
1565
  });
961
1566
  view?.destroy();
962
- setView(new EditorView6({
1567
+ setView(new EditorView9({
963
1568
  state: state2,
964
1569
  parent: root
965
1570
  }));
@@ -973,6 +1578,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots
973
1578
  root,
974
1579
  model,
975
1580
  themeMode,
1581
+ readonly,
976
1582
  editorMode
977
1583
  ]);
978
1584
  const handleKeyUp = useCallback((event) => {
@@ -1000,43 +1606,36 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots
1000
1606
  onKeyUp: handleKeyUp
1001
1607
  });
1002
1608
  });
1003
- var maybeDebug = () => {
1004
- const items = localStorage.getItem("dxos.composer.demo");
1005
- if (items) {
1006
- return demo({
1007
- items: items.split(",")
1008
- });
1009
- }
1010
- return [];
1011
- };
1012
- var TextEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
1609
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
1013
1610
  const { themeMode } = useThemeContext();
1014
- const slots = defaultsDeep({}, _slots, defaultTextSlots);
1611
+ const slots = defaultsDeep2({}, _slots, defaultTextSlots);
1015
1612
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1016
1613
  ref: forwardedRef,
1614
+ readonly,
1017
1615
  extensions: [
1018
1616
  basicBundle({
1617
+ readonly,
1019
1618
  themeMode,
1020
1619
  placeholder: slots?.editor?.placeholder
1021
1620
  }),
1022
- maybeDebug(),
1023
1621
  ...extensions
1024
1622
  ],
1025
1623
  slots,
1026
1624
  ...props
1027
1625
  });
1028
1626
  });
1029
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
1627
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
1030
1628
  const { themeMode } = useThemeContext();
1031
- const slots = defaultsDeep({}, _slots, defaultMarkdownSlots);
1629
+ const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
1032
1630
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1033
1631
  ref: forwardedRef,
1632
+ readonly,
1034
1633
  extensions: [
1035
1634
  markdownBundle({
1635
+ readonly,
1036
1636
  themeMode,
1037
1637
  placeholder: slots?.editor?.placeholder
1038
1638
  }),
1039
- maybeDebug(),
1040
1639
  ...extensions
1041
1640
  ],
1042
1641
  slots,
@@ -1045,7 +1644,7 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slot
1045
1644
  });
1046
1645
  var defaultSlots = {
1047
1646
  root: {
1048
- className: mx2("p-2", inputSurface)
1647
+ className: mx3("p-2", inputSurface)
1049
1648
  }
1050
1649
  };
1051
1650
  var defaultTextSlots = {
@@ -1065,11 +1664,11 @@ var defaultMarkdownSlots = {
1065
1664
  import get3 from "lodash.get";
1066
1665
  import { useEffect as useEffect2, useState as useState2 } from "react";
1067
1666
  import { yCollab } from "y-codemirror.next";
1068
- import { invariant as invariant2 } from "@dxos/invariant";
1667
+ import { invariant as invariant3 } from "@dxos/invariant";
1069
1668
  import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
1070
1669
 
1071
1670
  // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
1072
- import { Annotation, Facet, StateEffect, StateField as StateField3 } from "@codemirror/state";
1671
+ import { Annotation, Facet, StateEffect, StateField as StateField6 } from "@codemirror/state";
1073
1672
  import { ViewPlugin as ViewPlugin3 } from "@codemirror/view";
1074
1673
  import * as automerge2 from "@dxos/automerge/automerge";
1075
1674
 
@@ -1258,7 +1857,7 @@ var semaphoreFacet = Facet.define({
1258
1857
  combine: (values) => values.at(-1)
1259
1858
  });
1260
1859
  var automergePlugin = (handle, path) => {
1261
- const stateField = StateField3.define({
1860
+ const stateField = StateField6.define({
1262
1861
  create: () => ({
1263
1862
  lastHeads: automerge2.getHeads(handle.docSync()),
1264
1863
  unreconciledTransactions: [],
@@ -1296,8 +1895,8 @@ var automergePlugin = (handle, path) => {
1296
1895
  this._view = view;
1297
1896
  handle.addListener("change", this._handleChange);
1298
1897
  }
1299
- update(update2) {
1300
- if (update2.transactions.length > 0 && update2.transactions.some((t) => !isReconcileTx(t))) {
1898
+ update(update4) {
1899
+ if (update4.transactions.length > 0 && update4.transactions.some((t) => !isReconcileTx(t))) {
1301
1900
  queueMicrotask(() => {
1302
1901
  this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
1303
1902
  });
@@ -1319,7 +1918,7 @@ var reconcileAnnotationType = Annotation.define();
1319
1918
  var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
1320
1919
 
1321
1920
  // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
1322
- import * as random from "lib0/random";
1921
+ import * as random2 from "lib0/random";
1323
1922
  var cursorColors = [
1324
1923
  {
1325
1924
  color: "#30bced",
@@ -1354,7 +1953,7 @@ var cursorColors = [
1354
1953
  light: "#1be7ff33"
1355
1954
  }
1356
1955
  ];
1357
- var cursorColor = cursorColors[random.uint32() % cursorColors.length];
1956
+ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
1358
1957
 
1359
1958
  // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
1360
1959
  import * as decoding from "lib0/decoding";
@@ -1362,7 +1961,7 @@ import * as encoding from "lib0/encoding";
1362
1961
  import { Observable } from "lib0/observable";
1363
1962
  import * as awarenessProtocol from "y-protocols/awareness";
1364
1963
  import { log } from "@dxos/log";
1365
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1964
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1366
1965
  var messageAwareness = 1;
1367
1966
  var messageQueryAwareness = 3;
1368
1967
  var SpaceAwarenessProvider = class extends Observable {
@@ -1399,7 +1998,7 @@ var SpaceAwarenessProvider = class extends Observable {
1399
1998
  removed,
1400
1999
  origin
1401
2000
  }, {
1402
- F: __dxlog_file2,
2001
+ F: __dxlog_file3,
1403
2002
  L: 67,
1404
2003
  S: this,
1405
2004
  C: (f, a) => f(...a)
@@ -1412,7 +2011,7 @@ var SpaceAwarenessProvider = class extends Observable {
1412
2011
  }
1413
2012
  _handleSpaceMessage({ payload }) {
1414
2013
  log("space message", payload, {
1415
- F: __dxlog_file2,
2014
+ F: __dxlog_file3,
1416
2015
  L: 79,
1417
2016
  S: this,
1418
2017
  C: (f, a) => f(...a)
@@ -1457,7 +2056,7 @@ var SpaceAwarenessProvider = class extends Observable {
1457
2056
  };
1458
2057
 
1459
2058
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1460
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
2059
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
1461
2060
  var useTextModel = ({ identity, space, text }) => {
1462
2061
  const [model, setModel] = useState2(() => createModel({
1463
2062
  identity,
@@ -1489,8 +2088,8 @@ var createModel = (options) => {
1489
2088
  }
1490
2089
  };
1491
2090
  var createYjsModel = ({ identity, space, text }) => {
1492
- invariant2(text?.doc && text?.content, void 0, {
1493
- F: __dxlog_file3,
2091
+ invariant3(text?.doc && text?.content, void 0, {
2092
+ F: __dxlog_file4,
1494
2093
  L: 74,
1495
2094
  S: void 0,
1496
2095
  A: [
@@ -1543,21 +2142,26 @@ export {
1543
2142
  YXmlFragment,
1544
2143
  autocomplete,
1545
2144
  basicBundle,
2145
+ blast,
1546
2146
  codeTheme,
1547
2147
  comments,
1548
2148
  cursorColor,
1549
2149
  defaultMarkdownSlots,
2150
+ defaultOptions,
1550
2151
  defaultSlots,
1551
2152
  defaultTextSlots,
1552
2153
  defaultTheme,
1553
2154
  demo,
2155
+ image,
1554
2156
  link,
1555
2157
  listener,
1556
2158
  markdownBundle,
1557
2159
  markdownHighlightStyle,
1558
2160
  markdownTags,
1559
- markdownTagsExtension,
2161
+ markdownTagsExtensions,
1560
2162
  markdownTheme,
2163
+ mention,
2164
+ table,
1561
2165
  tags2 as tags,
1562
2166
  tasklist,
1563
2167
  textTheme,