@dxos/react-ui-editor 0.3.10-main.3cfd481 → 0.3.10-main.4fbcffb

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 (43) hide show
  1. package/dist/lib/browser/index.mjs +345 -125
  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 +3 -0
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +1 -0
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +3 -2
  9. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +18 -0
  11. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/extensions/demo.d.ts +10 -0
  13. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +1 -0
  14. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -1
  15. package/dist/types/src/components/TextEditor/extensions/index.d.ts +2 -0
  16. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  17. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -1
  18. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +4 -2
  19. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -1
  20. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  21. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -1
  22. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +1 -4
  23. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -1
  24. package/dist/types/src/components/TextEditor/themes/default.d.ts +3 -0
  25. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -1
  26. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  27. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  28. package/package.json +20 -19
  29. package/src/components/TextEditor/MarkdownEditor.stories.tsx +41 -8
  30. package/src/components/TextEditor/TextEditor.tsx +32 -11
  31. package/src/components/TextEditor/extensions/autocomplete.ts +5 -3
  32. package/src/components/TextEditor/extensions/comments.ts +190 -0
  33. package/src/components/TextEditor/extensions/demo.ts +66 -0
  34. package/src/components/TextEditor/extensions/experimental.tsx +1 -0
  35. package/src/components/TextEditor/extensions/index.ts +2 -0
  36. package/src/components/TextEditor/extensions/link.ts +10 -2
  37. package/src/components/TextEditor/extensions/listener.ts +3 -2
  38. package/src/components/TextEditor/extensions/markdown/bundle.ts +6 -14
  39. package/src/components/TextEditor/extensions/markdown/highlight.ts +2 -1
  40. package/src/components/TextEditor/extensions/tasklist.ts +73 -79
  41. package/src/components/TextEditor/themes/default.ts +15 -0
  42. package/src/hooks/useTextModel.ts +11 -8
  43. package/src/testing/replicator.ts +5 -5
@@ -8,7 +8,7 @@ import { tags as tags2 } from "@lezer/highlight";
8
8
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/autocomplete.ts
9
9
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
10
10
  import { keymap } from "@codemirror/view";
11
- var autocomplete = ({ getOptions }) => {
11
+ var autocomplete = ({ onSearch }) => {
12
12
  return [
13
13
  // https://codemirror.net/docs/ref/#view.keymap
14
14
  // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
@@ -30,7 +30,7 @@ var autocomplete = ({ getOptions }) => {
30
30
  }
31
31
  return {
32
32
  from: word.from,
33
- options: getOptions(word.text.toLowerCase())
33
+ options: onSearch(word.text.toLowerCase())
34
34
  };
35
35
  }
36
36
  ]
@@ -54,11 +54,204 @@ 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/demo.ts
58
+ import { keymap as keymap2 } from "@codemirror/view";
59
+ var defaultItems = [
60
+ "hello world!",
61
+ "this is a test.",
62
+ "this is [DXOS](https://dxos.org)"
63
+ ];
64
+ var demo = ({ delay = 75, items = defaultItems } = {}) => {
65
+ let t;
66
+ let idx = 0;
67
+ return [
68
+ keymap2.of([
69
+ {
70
+ // Reset.
71
+ key: "alt-meta-'",
72
+ run: (view) => {
73
+ clearTimeout(t);
74
+ idx = 0;
75
+ return true;
76
+ }
77
+ },
78
+ {
79
+ // Next prompt.
80
+ // TODO(burdon): Press 1-9 to select prompt?
81
+ key: "shift-meta-'",
82
+ run: (view) => {
83
+ clearTimeout(t);
84
+ const text = items[idx++];
85
+ if (idx === items?.length) {
86
+ idx = 0;
87
+ }
88
+ let i = 0;
89
+ const insert = (d = 0) => {
90
+ t = setTimeout(() => {
91
+ const pos = view.state.selection.main.head;
92
+ view.dispatch({
93
+ changes: {
94
+ from: pos,
95
+ insert: text[i++]
96
+ },
97
+ selection: {
98
+ anchor: pos + 1
99
+ }
100
+ });
101
+ if (i < text.length) {
102
+ insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
103
+ }
104
+ }, d);
105
+ };
106
+ insert();
107
+ return true;
108
+ }
109
+ }
110
+ ])
111
+ ];
112
+ };
113
+
114
+ // 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";
116
+ 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";
119
+ var BookmarkWidget = class extends WidgetType {
120
+ constructor(_pos, _id) {
121
+ super();
122
+ this._pos = _pos;
123
+ this._id = _id;
124
+ invariant(this._id, void 0, {
125
+ F: __dxlog_file,
126
+ L: 41,
127
+ S: this,
128
+ A: [
129
+ "this._id",
130
+ ""
131
+ ]
132
+ });
133
+ }
134
+ eq(other) {
135
+ return this._id === other._id;
136
+ }
137
+ toDOM() {
138
+ const span = document.createElement("span");
139
+ span.className = "cm-bookmark";
140
+ span.textContent = "\u{1F4AC}";
141
+ return span;
142
+ }
143
+ };
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
+ var comments = (options = {}) => {
156
+ let active;
157
+ const bookmarkMatcher = new MatchDecorator({
158
+ regexp: /\[\^(\w+)\]/g,
159
+ decoration: (match, view, pos) => {
160
+ const id = match[1];
161
+ return Decoration.replace({
162
+ id,
163
+ widget: new BookmarkWidget(pos, id)
164
+ });
165
+ }
166
+ });
167
+ const bookmarks = ViewPlugin.fromClass(class {
168
+ constructor(view) {
169
+ this.bookmarks = bookmarkMatcher.createDeco(view);
170
+ }
171
+ update(update2) {
172
+ this.bookmarks = bookmarkMatcher.updateDeco(update2, this.bookmarks);
173
+ }
174
+ }, {
175
+ decorations: (instance) => instance.bookmarks,
176
+ provide: (plugin) => EditorView2.atomicRanges.of((view) => {
177
+ return view.plugin(plugin)?.bookmarks || Decoration.none;
178
+ })
179
+ });
180
+ const doUpdate = debounce((data) => {
181
+ options.onUpdate?.(data);
182
+ }, 200);
183
+ return [
184
+ keymap3.of([
185
+ {
186
+ key: options?.key ?? "shift-meta-c",
187
+ run: (view) => {
188
+ const id = options.onCreate?.();
189
+ if (id) {
190
+ const pos = view.state.selection.main.head;
191
+ const tag = `[^${id}]`;
192
+ view.dispatch({
193
+ changes: {
194
+ from: pos,
195
+ insert: tag
196
+ },
197
+ selection: {
198
+ anchor: pos + tag.length
199
+ }
200
+ });
201
+ return true;
202
+ }
203
+ return false;
204
+ }
205
+ }
206
+ ]),
207
+ bookmarks,
208
+ // Monitor cursor movement.
209
+ EditorView2.updateListener.of((update2) => {
210
+ active = void 0;
211
+ if (!options.onUpdate) {
212
+ return;
213
+ }
214
+ const view = update2.view;
215
+ const pos = view.state.selection.main.head;
216
+ const decorations = [];
217
+ const rangeSet = view.plugin(bookmarks)?.bookmarks;
218
+ let closest = Infinity;
219
+ const { from, to } = view.visibleRanges?.[0] ?? {
220
+ from: 0,
221
+ to: view.state.doc.length
222
+ };
223
+ rangeSet?.between(from, to, (from2, to2, value) => {
224
+ decorations.push({
225
+ from: from2,
226
+ to: to2,
227
+ value
228
+ });
229
+ const d = Math.min(Math.abs(pos - from2), Math.abs(pos - to2));
230
+ if (d < closest) {
231
+ active = value.spec.id;
232
+ closest = d;
233
+ }
234
+ });
235
+ if (decorations.length) {
236
+ doUpdate({
237
+ active,
238
+ items: decorations.map(({ from: from2, value: { spec: { id } } }) => ({
239
+ id,
240
+ pos: from2,
241
+ location: view.coordsAtPos(from2)
242
+ }))
243
+ });
244
+ }
245
+ }),
246
+ styles
247
+ ];
248
+ };
249
+
57
250
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
58
251
  import { syntaxTree } from "@codemirror/language";
59
252
  import { RangeSetBuilder, StateField } from "@codemirror/state";
60
- import { Decoration, EditorView as EditorView2, WidgetType } from "@codemirror/view";
61
- var LinkButton = class extends WidgetType {
253
+ import { Decoration as Decoration2, EditorView as EditorView3, WidgetType as WidgetType2 } from "@codemirror/view";
254
+ var LinkButton = class extends WidgetType2 {
62
255
  constructor(_pos, _url, _onAttach) {
63
256
  super();
64
257
  this._pos = _pos;
@@ -74,7 +267,7 @@ var LinkButton = class extends WidgetType {
74
267
  return el;
75
268
  }
76
269
  };
77
- var LinkText = class extends WidgetType {
270
+ var LinkText = class extends WidgetType2 {
78
271
  constructor(_pos, _text, _url) {
79
272
  super();
80
273
  this._pos = _pos;
@@ -114,18 +307,21 @@ var update = (state, options) => {
114
307
  const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
115
308
  const urlNode = node.node.getChild("URL");
116
309
  const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
310
+ if (!url) {
311
+ return false;
312
+ }
117
313
  if (cursor < node.from || cursor > node.to) {
118
- builder.add(node.from, node.to, Decoration.replace({
314
+ builder.add(node.from, node.to, Decoration2.replace({
119
315
  widget: new LinkText(node.from, text, options.link ? url : void 0)
120
316
  }));
121
317
  }
122
318
  if (options.onRender) {
123
- builder.add(node.to, node.to, Decoration.replace({
319
+ builder.add(node.to, node.to, Decoration2.replace({
124
320
  widget: new LinkButton(node.from, url, options.onRender)
125
321
  }));
126
322
  }
323
+ return false;
127
324
  }
128
- return true;
129
325
  }
130
326
  });
131
327
  return builder.finish();
@@ -134,13 +330,13 @@ var link = (options = {}) => {
134
330
  return StateField.define({
135
331
  create: (state) => update(state, options),
136
332
  update: (_, tr) => update(tr.state, options),
137
- provide: (field) => EditorView2.decorations.from(field)
333
+ provide: (field) => EditorView3.decorations.from(field)
138
334
  });
139
335
  };
140
336
 
141
337
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
142
338
  import { StateField as StateField2 } from "@codemirror/state";
143
- var listener = (onChange) => StateField2.define({
339
+ var listener = ({ onChange }) => StateField2.define({
144
340
  create: () => null,
145
341
  update: (_value, transaction) => {
146
342
  if (transaction.docChanged && onChange) {
@@ -152,15 +348,14 @@ var listener = (onChange) => StateField2.define({
152
348
 
153
349
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
154
350
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
155
- import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
351
+ import { defaultKeymap, history, indentWithTab } from "@codemirror/commands";
156
352
  import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
157
- import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, foldKeymap, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
353
+ import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
158
354
  import { languages } from "@codemirror/language-data";
159
- import { lintKeymap } from "@codemirror/lint";
160
355
  import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
161
356
  import { EditorState } from "@codemirror/state";
162
357
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
163
- import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView3, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap as keymap2, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
358
+ import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView4, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap as keymap4, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
164
359
 
165
360
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
166
361
  import { markdownLanguage } from "@codemirror/lang-markdown";
@@ -320,10 +515,11 @@ var markdownHighlightStyle = HighlightStyle.define([
320
515
  ],
321
516
  class: codeMark
322
517
  },
323
- // The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
518
+ // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
324
519
  // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
325
520
  // when a language is specified. In this case, the syntax highlighting extensions will colorize
326
521
  // the code, but all other CSS properties will be inherited.
522
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font.
327
523
  {
328
524
  tag: [
329
525
  markdownTags.CodeText,
@@ -357,8 +553,7 @@ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
357
553
  closeBrackets2(),
358
554
  _placeholder && placeholder2(_placeholder),
359
555
  EditorState.allowMultipleSelections.of(true),
360
- EditorView3.lineWrapping,
361
- // autocompletion(),
556
+ EditorView4.lineWrapping,
362
557
  crosshairCursor(),
363
558
  dropCursor(),
364
559
  drawSelection(),
@@ -369,13 +564,13 @@ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
369
564
  history(),
370
565
  indentOnInput(),
371
566
  rectangularSelection(),
372
- keymap2.of([
567
+ // TODO(burdon): Review.
568
+ keymap4.of([
373
569
  ...closeBracketsKeymap,
374
- // ...completionKeymap,
375
570
  ...defaultKeymap,
376
- ...foldKeymap,
377
- ...historyKeymap,
378
- ...lintKeymap,
571
+ // ...foldKeymap,
572
+ // ...historyKeymap,
573
+ // ...lintKeymap,
379
574
  ...searchKeymap,
380
575
  indentWithTab
381
576
  ]),
@@ -405,90 +600,84 @@ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
405
600
  };
406
601
 
407
602
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
408
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
409
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField3 } from "@codemirror/state";
410
- import { EditorView as EditorView4, Decoration as Decoration2, WidgetType as WidgetType2 } from "@codemirror/view";
411
- var CheckboxWidget = class extends WidgetType2 {
412
- constructor(_pos, _getCursor, _checked) {
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 {
605
+ constructor(_pos, _checked, _indent, _onCheck) {
413
606
  super();
414
607
  this._pos = _pos;
415
- this._getCursor = _getCursor;
416
608
  this._checked = _checked;
609
+ this._indent = _indent;
610
+ this._onCheck = _onCheck;
417
611
  }
418
612
  eq(other) {
419
- return this._pos === other._pos && this._checked === other._checked;
613
+ return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
420
614
  }
421
615
  toDOM(view) {
422
616
  const wrap = document.createElement("span");
423
- wrap.setAttribute("aria-hidden", "true");
424
617
  wrap.className = "cm-task-item";
425
- const box = wrap.appendChild(document.createElement("input"));
426
- box.type = "checkbox";
427
- box.checked = this._checked;
428
- box.onclick = (event) => {
429
- this._checked = !isChecked(view.state, this._pos);
430
- view.dispatch({
431
- changes: {
432
- from: this._pos + 1,
433
- to: this._pos + 2,
434
- insert: this._checked ? "x" : " "
435
- },
436
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
437
- selection: {
438
- anchor: this._pos + 4
439
- }
440
- });
618
+ wrap.setAttribute("aria-hidden", "true");
619
+ wrap.style.setProperty("margin-left", this._indent * 24 + "px");
620
+ const input = wrap.appendChild(document.createElement("input"));
621
+ input.type = "checkbox";
622
+ input.checked = this._checked;
623
+ input.onchange = (event) => {
624
+ this._onCheck(event.target.checked);
441
625
  return true;
442
626
  };
443
627
  return wrap;
444
628
  }
445
- // TODO(burdon): Remove listener?
446
- // override destroy() {
447
- // console.log('destroy', this._pos);
448
- // }
449
629
  ignoreEvent() {
450
630
  return false;
451
631
  }
452
632
  };
453
- var isChecked = (state, pos) => {
454
- return state.sliceDoc(pos, pos + 3).toLowerCase() === "[x]";
455
- };
456
- var statefield = () => {
457
- let lastPosition = 0;
458
- const listener2 = EditorView4.updateListener.of((update2) => {
459
- lastPosition = update2.startState.selection.main.head;
633
+ var styles2 = EditorView5.baseTheme({
634
+ "& .cm-task-item": {
635
+ paddingLeft: "4px",
636
+ paddingRight: "8px"
637
+ }
638
+ });
639
+ var tasklist = () => {
640
+ const taskMatcher = new MatchDecorator2({
641
+ regexp: /^(\s*)- \[([ xX])\]\s/g,
642
+ decoration: (match, view, pos) => {
643
+ const indent = Math.floor(match[1].length / 2);
644
+ const checked = match[2] === "x" || match[2] === "X";
645
+ return Decoration3.replace({
646
+ widget: new CheckboxWidget(pos, checked, indent, (checked2) => {
647
+ const idx = pos + match[0].indexOf("[") + 1;
648
+ view.dispatch({
649
+ changes: {
650
+ from: idx,
651
+ to: idx + 1,
652
+ insert: checked2 ? "x" : " "
653
+ },
654
+ // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
655
+ selection: {
656
+ anchor: idx + 3
657
+ }
658
+ });
659
+ })
660
+ });
661
+ }
460
662
  });
461
- const checkbox = (state) => {
462
- const builder = new RangeSetBuilder2();
463
- syntaxTree2(state).iterate({
464
- enter: (node) => {
465
- if (node.name === "TaskMarker") {
466
- builder.add(node.from, node.to, Decoration2.replace({
467
- widget: new CheckboxWidget(node.from, () => lastPosition, isChecked(state, node.from))
468
- }));
469
- }
470
- }
471
- });
472
- return builder.finish();
473
- };
474
- return [
475
- listener2,
476
- StateField3.define({
477
- create: (state) => checkbox(state),
478
- update: (_, tr) => checkbox(tr.state),
479
- provide: (field) => EditorView4.decorations.from(field)
663
+ const tasks = ViewPlugin2.fromClass(class {
664
+ constructor(view) {
665
+ this.tasks = taskMatcher.createDeco(view);
666
+ }
667
+ update(update2) {
668
+ this.tasks = taskMatcher.updateDeco(update2, this.tasks);
669
+ }
670
+ }, {
671
+ decorations: (instance) => instance.tasks,
672
+ provide: (plugin) => EditorView5.atomicRanges.of((view) => {
673
+ return view.plugin(plugin)?.tasks || Decoration3.none;
480
674
  })
675
+ });
676
+ return [
677
+ tasks,
678
+ styles2
481
679
  ];
482
680
  };
483
- var styles = EditorView4.baseTheme({
484
- "& .cm-task-item": {
485
- padding: "0 4px"
486
- }
487
- });
488
- var tasklist = () => [
489
- statefield(),
490
- styles
491
- ];
492
681
 
493
682
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.tsx
494
683
  import { hoverTooltip } from "@codemirror/view";
@@ -677,17 +866,29 @@ var defaultTheme = {
677
866
  var textTheme = {
678
867
  "& .cm-scroller": {
679
868
  fontFamily: get2(tokens, "fontFamily.body", []).join(",")
869
+ },
870
+ "& .cm-placeholder": {
871
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
872
+ }
873
+ };
874
+ var markdownTheme = {
875
+ // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
876
+ "& .cm-placeholder": {
877
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
680
878
  }
681
879
  };
682
880
  var codeTheme = {
683
881
  "& .cm-scroller": {
684
882
  fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
883
+ },
884
+ "& .cm-placeholder": {
885
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
685
886
  }
686
887
  };
687
888
 
688
889
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
689
890
  import { EditorState as EditorState2 } from "@codemirror/state";
690
- import { EditorView as EditorView5 } from "@codemirror/view";
891
+ import { EditorView as EditorView6 } from "@codemirror/view";
691
892
  import { useFocusableGroup } from "@fluentui/react-tabster";
692
893
  import { vim } from "@replit/codemirror-vim";
693
894
  import defaultsDeep from "lodash.defaultsdeep";
@@ -704,6 +905,18 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots
704
905
  const tabsterDOMAttribute = useFocusableGroup({
705
906
  tabBehavior: "limited"
706
907
  });
908
+ const [root, setRoot] = useState(null);
909
+ const [state, setState] = useState();
910
+ const [view, setView] = useState();
911
+ useImperativeHandle(forwardedRef, () => ({
912
+ root,
913
+ state,
914
+ view
915
+ }), [
916
+ view,
917
+ state,
918
+ root
919
+ ]);
707
920
  const { awareness, peer } = model;
708
921
  useEffect(() => {
709
922
  if (awareness && peer) {
@@ -725,18 +938,6 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots
725
938
  peer,
726
939
  themeMode
727
940
  ]);
728
- const [root, setRoot] = useState(null);
729
- const [state, setState] = useState();
730
- const [view, setView] = useState();
731
- useImperativeHandle(forwardedRef, () => ({
732
- root,
733
- state,
734
- view
735
- }), [
736
- view,
737
- state,
738
- root
739
- ]);
740
941
  useEffect(() => {
741
942
  if (!root) {
742
943
  return;
@@ -747,10 +948,10 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots
747
948
  // TODO(burdon): Factor out VIM mode?
748
949
  editorMode === "vim" && vim(),
749
950
  // Theme.
750
- EditorView5.baseTheme(defaultTheme),
751
- EditorView5.theme(slots?.editor?.theme ?? {}),
951
+ EditorView6.baseTheme(defaultTheme),
952
+ EditorView6.theme(slots?.editor?.theme ?? {}),
752
953
  // TODO(burdon): themeMode doesn't change in storybooks.
753
- EditorView5.darkTheme.of(themeMode === "dark"),
954
+ EditorView6.darkTheme.of(themeMode === "dark"),
754
955
  // Storage and replication.
755
956
  model.extension,
756
957
  // Custom.
@@ -758,7 +959,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots
758
959
  ].filter(Boolean)
759
960
  });
760
961
  view?.destroy();
761
- setView(new EditorView5({
962
+ setView(new EditorView6({
762
963
  state: state2,
763
964
  parent: root
764
965
  }));
@@ -799,6 +1000,15 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots
799
1000
  onKeyUp: handleKeyUp
800
1001
  });
801
1002
  });
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
+ };
802
1012
  var TextEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
803
1013
  const { themeMode } = useThemeContext();
804
1014
  const slots = defaultsDeep({}, _slots, defaultTextSlots);
@@ -809,6 +1019,7 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, .
809
1019
  themeMode,
810
1020
  placeholder: slots?.editor?.placeholder
811
1021
  }),
1022
+ maybeDebug(),
812
1023
  ...extensions
813
1024
  ],
814
1025
  slots,
@@ -817,7 +1028,7 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, .
817
1028
  });
818
1029
  var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
819
1030
  const { themeMode } = useThemeContext();
820
- const slots = defaultsDeep({}, _slots, defaultSlots);
1031
+ const slots = defaultsDeep({}, _slots, defaultMarkdownSlots);
821
1032
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
822
1033
  ref: forwardedRef,
823
1034
  extensions: [
@@ -825,6 +1036,7 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slot
825
1036
  themeMode,
826
1037
  placeholder: slots?.editor?.placeholder
827
1038
  }),
1039
+ maybeDebug(),
828
1040
  ...extensions
829
1041
  ],
830
1042
  slots,
@@ -842,17 +1054,23 @@ var defaultTextSlots = {
842
1054
  theme: textTheme
843
1055
  }
844
1056
  };
1057
+ var defaultMarkdownSlots = {
1058
+ ...defaultSlots,
1059
+ editor: {
1060
+ theme: markdownTheme
1061
+ }
1062
+ };
845
1063
 
846
1064
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
847
1065
  import get3 from "lodash.get";
848
1066
  import { useEffect as useEffect2, useState as useState2 } from "react";
849
1067
  import { yCollab } from "y-codemirror.next";
850
- import { invariant } from "@dxos/invariant";
1068
+ import { invariant as invariant2 } from "@dxos/invariant";
851
1069
  import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
852
1070
 
853
1071
  // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
854
- import { Annotation, Facet, StateEffect, StateField as StateField4 } from "@codemirror/state";
855
- import { ViewPlugin } from "@codemirror/view";
1072
+ import { Annotation, Facet, StateEffect, StateField as StateField3 } from "@codemirror/state";
1073
+ import { ViewPlugin as ViewPlugin3 } from "@codemirror/view";
856
1074
  import * as automerge2 from "@dxos/automerge/automerge";
857
1075
 
858
1076
  // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
@@ -1040,7 +1258,7 @@ var semaphoreFacet = Facet.define({
1040
1258
  combine: (values) => values.at(-1)
1041
1259
  });
1042
1260
  var automergePlugin = (handle, path) => {
1043
- const stateField = StateField4.define({
1261
+ const stateField = StateField3.define({
1044
1262
  create: () => ({
1045
1263
  lastHeads: automerge2.getHeads(handle.docSync()),
1046
1264
  unreconciledTransactions: [],
@@ -1070,7 +1288,7 @@ var automergePlugin = (handle, path) => {
1070
1288
  }
1071
1289
  });
1072
1290
  const semaphore = new PatchSemaphore(stateField);
1073
- const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
1291
+ const viewPlugin = ViewPlugin3.fromClass(class AutomergeCodemirrorViewPlugin {
1074
1292
  constructor(view) {
1075
1293
  this._handleChange = () => {
1076
1294
  this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
@@ -1144,7 +1362,7 @@ import * as encoding from "lib0/encoding";
1144
1362
  import { Observable } from "lib0/observable";
1145
1363
  import * as awarenessProtocol from "y-protocols/awareness";
1146
1364
  import { log } from "@dxos/log";
1147
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1365
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1148
1366
  var messageAwareness = 1;
1149
1367
  var messageQueryAwareness = 3;
1150
1368
  var SpaceAwarenessProvider = class extends Observable {
@@ -1181,7 +1399,7 @@ var SpaceAwarenessProvider = class extends Observable {
1181
1399
  removed,
1182
1400
  origin
1183
1401
  }, {
1184
- F: __dxlog_file,
1402
+ F: __dxlog_file2,
1185
1403
  L: 67,
1186
1404
  S: this,
1187
1405
  C: (f, a) => f(...a)
@@ -1194,7 +1412,7 @@ var SpaceAwarenessProvider = class extends Observable {
1194
1412
  }
1195
1413
  _handleSpaceMessage({ payload }) {
1196
1414
  log("space message", payload, {
1197
- F: __dxlog_file,
1415
+ F: __dxlog_file2,
1198
1416
  L: 79,
1199
1417
  S: this,
1200
1418
  C: (f, a) => f(...a)
@@ -1239,7 +1457,7 @@ var SpaceAwarenessProvider = class extends Observable {
1239
1457
  };
1240
1458
 
1241
1459
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1242
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/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";
1243
1461
  var useTextModel = ({ identity, space, text }) => {
1244
1462
  const [model, setModel] = useState2(() => createModel({
1245
1463
  identity,
@@ -1260,44 +1478,44 @@ var useTextModel = ({ identity, space, text }) => {
1260
1478
  return model;
1261
1479
  };
1262
1480
  var createModel = (options) => {
1263
- const { space, text } = options;
1481
+ const { text } = options;
1264
1482
  if (isActualAutomergeObject(text)) {
1265
1483
  return createAutomergeModel(options);
1266
1484
  } else {
1267
- if (!space || !text?.doc || !text?.content) {
1485
+ if (!text?.doc) {
1268
1486
  return void 0;
1269
1487
  }
1270
1488
  return createYjsModel(options);
1271
1489
  }
1272
1490
  };
1273
1491
  var createYjsModel = ({ identity, space, text }) => {
1274
- invariant(space && text?.doc && text?.content, void 0, {
1275
- F: __dxlog_file2,
1276
- L: 73,
1492
+ invariant2(text?.doc && text?.content, void 0, {
1493
+ F: __dxlog_file3,
1494
+ L: 74,
1277
1495
  S: void 0,
1278
1496
  A: [
1279
- "space && text?.doc && text?.content",
1497
+ "text?.doc && text?.content",
1280
1498
  ""
1281
1499
  ]
1282
1500
  });
1283
- const provider = new SpaceAwarenessProvider({
1501
+ const provider = space ? new SpaceAwarenessProvider({
1284
1502
  space,
1285
1503
  doc: text.doc,
1286
1504
  channel: `yjs.awareness.${text.id}`
1287
- });
1505
+ }) : void 0;
1288
1506
  return {
1289
1507
  id: text.doc.guid,
1290
1508
  content: text.content,
1291
1509
  text: () => text.content.toString(),
1292
- extension: yCollab(text.content, provider.awareness),
1293
- awareness: provider.awareness,
1510
+ extension: yCollab(text.content, provider?.awareness),
1511
+ awareness: provider?.awareness,
1294
1512
  peer: identity ? {
1295
1513
  id: identity.identityKey.toHex(),
1296
1514
  name: identity.profile?.displayName
1297
1515
  } : void 0
1298
1516
  };
1299
1517
  };
1300
- var createAutomergeModel = ({ identity, space, text }) => {
1518
+ var createAutomergeModel = ({ identity, text }) => {
1301
1519
  const obj = text;
1302
1520
  const doc = getRawDoc(obj, [
1303
1521
  obj.field
@@ -1326,18 +1544,20 @@ export {
1326
1544
  autocomplete,
1327
1545
  basicBundle,
1328
1546
  codeTheme,
1547
+ comments,
1329
1548
  cursorColor,
1549
+ defaultMarkdownSlots,
1330
1550
  defaultSlots,
1331
1551
  defaultTextSlots,
1332
1552
  defaultTheme,
1553
+ demo,
1333
1554
  link,
1334
1555
  listener,
1335
1556
  markdownBundle,
1336
1557
  markdownHighlightStyle,
1337
1558
  markdownTags,
1338
1559
  markdownTagsExtension,
1339
- statefield,
1340
- styles,
1560
+ markdownTheme,
1341
1561
  tags2 as tags,
1342
1562
  tasklist,
1343
1563
  textTheme,