@dxos/react-ui-editor 0.4.8-main.c67d72c → 0.4.8-main.dcf422f

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 (47) hide show
  1. package/dist/lib/browser/index.mjs +454 -274
  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/TextEditor.d.ts +2 -2
  5. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +2 -1
  7. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  8. package/dist/types/src/components/Toolbar/Toolbar.d.ts +11 -5
  9. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  10. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +4 -2
  11. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  12. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -0
  13. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  14. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  15. package/dist/types/src/extensions/index.d.ts +1 -0
  16. package/dist/types/src/extensions/index.d.ts.map +1 -1
  17. package/dist/types/src/extensions/markdown/formatting.d.ts +7 -4
  18. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  19. package/dist/types/src/extensions/markdown/image.d.ts +6 -0
  20. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  21. package/dist/types/src/extensions/modes.d.ts +1 -0
  22. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  23. package/dist/types/src/extensions/state.d.ts +20 -0
  24. package/dist/types/src/extensions/state.d.ts.map +1 -0
  25. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
  26. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  27. package/dist/types/src/hooks/useTextEditor.stories.d.ts +1 -0
  28. package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
  29. package/dist/types/src/translations.d.ts +1 -0
  30. package/dist/types/src/translations.d.ts.map +1 -1
  31. package/package.json +25 -24
  32. package/src/components/TextEditor/TextEditor.stories.tsx +16 -4
  33. package/src/components/TextEditor/TextEditor.tsx +13 -12
  34. package/src/components/Toolbar/Toolbar.stories.tsx +23 -24
  35. package/src/components/Toolbar/Toolbar.tsx +101 -35
  36. package/src/extensions/automerge/automerge.ts +4 -4
  37. package/src/extensions/comments.ts +2 -2
  38. package/src/extensions/index.ts +1 -0
  39. package/src/extensions/markdown/formatting.test.ts +13 -13
  40. package/src/extensions/markdown/formatting.ts +91 -83
  41. package/src/extensions/markdown/image.ts +6 -0
  42. package/src/extensions/modes.ts +3 -1
  43. package/src/extensions/state.ts +90 -0
  44. package/src/hooks/useActionHandler.ts +5 -1
  45. package/src/hooks/useTextEditor.stories.tsx +3 -3
  46. package/src/hooks/useTextEditor.ts +3 -0
  47. package/src/translations.ts +1 -0
@@ -15,6 +15,7 @@ var translations_default = [
15
15
  "blockquote label": "Block quote",
16
16
  "codeblock label": "Code block",
17
17
  "comment label": "Create comment",
18
+ "image label": "Insert image",
18
19
  "heading label": "Heading level",
19
20
  "table label": "Create table"
20
21
  }
@@ -23,18 +24,18 @@ var translations_default = [
23
24
  ];
24
25
 
25
26
  // packages/ui/react-ui-editor/src/index.ts
26
- import { keymap as keymap9 } from "@codemirror/view";
27
+ import { keymap as keymap10 } from "@codemirror/view";
27
28
  import { tags as tags2 } from "@lezer/highlight";
28
29
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
29
30
  import { Doc, YText, YXmlFragment } from "@dxos/text-model";
30
31
 
31
32
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
32
33
  import { EditorState as EditorState2 } from "@codemirror/state";
33
- import { EditorView as EditorView16 } from "@codemirror/view";
34
+ import { EditorView as EditorView17 } from "@codemirror/view";
34
35
  import { useFocusableGroup } from "@fluentui/react-tabster";
35
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState as useState2 } from "react";
36
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState } from "react";
36
37
  import { log as log9 } from "@dxos/log";
37
- import { isNotFalsy as isNotFalsy3 } from "@dxos/util";
38
+ import { isNotFalsy as isNotFalsy4 } from "@dxos/util";
38
39
 
39
40
  // packages/ui/react-ui-editor/src/extensions/annotations.ts
40
41
  import { StateField } from "@codemirror/state";
@@ -56,8 +57,8 @@ var Cursor = class _Cursor {
56
57
  });
57
58
  }
58
59
  static {
59
- this.getCursorFromRange = (state, range) => {
60
- const cursorConverter2 = state.facet(_Cursor.converter);
60
+ this.getCursorFromRange = (state2, range) => {
61
+ const cursorConverter2 = state2.facet(_Cursor.converter);
61
62
  const from = cursorConverter2.toCursor(range.from);
62
63
  const to = cursorConverter2.toCursor(range.to, -1);
63
64
  return [
@@ -67,8 +68,8 @@ var Cursor = class _Cursor {
67
68
  };
68
69
  }
69
70
  static {
70
- this.getRangeFromCursor = (state, cursor) => {
71
- const cursorConverter2 = state.facet(_Cursor.converter);
71
+ this.getRangeFromCursor = (state2, cursor) => {
72
+ const cursorConverter2 = state2.facet(_Cursor.converter);
72
73
  const parts = cursor.split(":");
73
74
  const from = cursorConverter2.fromCursor(parts[0]);
74
75
  const to = cursorConverter2.fromCursor(parts[1]);
@@ -85,15 +86,15 @@ var annotationMark = Decoration.mark({
85
86
  class: "cm-annotation"
86
87
  });
87
88
  var annotations = (options = {}) => {
88
- const match = (state) => {
89
+ const match = (state2) => {
89
90
  const annotations2 = [];
90
- const text = state.doc.toString();
91
+ const text = state2.doc.toString();
91
92
  if (options.match) {
92
93
  const matches = text.matchAll(options.match);
93
94
  for (const match2 of matches) {
94
95
  const from = match2.index;
95
96
  const to = from + match2[0].length;
96
- const cursor = Cursor.getCursorFromRange(state, {
97
+ const cursor = Cursor.getCursorFromRange(state2, {
97
98
  from,
98
99
  to
99
100
  });
@@ -105,8 +106,8 @@ var annotations = (options = {}) => {
105
106
  return annotations2;
106
107
  };
107
108
  const annotationsState = StateField.define({
108
- create: (state) => {
109
- return match(state);
109
+ create: (state2) => {
110
+ return match(state2);
110
111
  },
111
112
  update: (value, tr) => {
112
113
  if (!tr.changes.empty) {
@@ -119,10 +120,10 @@ var annotations = (options = {}) => {
119
120
  annotationsState,
120
121
  EditorView.decorations.compute([
121
122
  annotationsState
122
- ], (state) => {
123
- const annotations2 = state.field(annotationsState);
123
+ ], (state2) => {
124
+ const annotations2 = state2.field(annotationsState);
124
125
  const decorations = annotations2.map((annotation2) => {
125
- const range = Cursor.getRangeFromCursor(state, annotation2.cursor);
126
+ const range = Cursor.getRangeFromCursor(state2, annotation2.cursor);
126
127
  return range && annotationMark.range(range.from, range.to);
127
128
  }).filter(isNotFalsy);
128
129
  return Decoration.set(decorations);
@@ -239,8 +240,8 @@ var cursorConverter = ({ handle, path }) => ({
239
240
 
240
241
  // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
241
242
  import { Annotation, StateEffect } from "@codemirror/state";
242
- var getPath = (state, field) => state.field(field).path;
243
- var getLastHeads = (state, field) => state.field(field).lastHeads;
243
+ var getPath = (state2, field) => state2.field(field).path;
244
+ var getLastHeads = (state2, field) => state2.field(field).lastHeads;
244
245
  var updateHeadsEffect = StateEffect.define({});
245
246
  var updateHeads = (newHeads) => updateHeadsEffect.of({
246
247
  newHeads
@@ -255,8 +256,8 @@ import { next as A3 } from "@dxos/automerge/automerge";
255
256
 
256
257
  // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
257
258
  import { next as A2 } from "@dxos/automerge/automerge";
258
- var updateAutomerge = (field, handle, transactions, state) => {
259
- const { lastHeads, path } = state.field(field);
259
+ var updateAutomerge = (field, handle, transactions, state2) => {
260
+ const { lastHeads, path } = state2.field(field);
260
261
  let hasChanges = false;
261
262
  for (const tr of transactions) {
262
263
  tr.changes.iterChanges(() => {
@@ -303,7 +304,7 @@ var updateCodeMirror = (view, selection, target, patches) => {
303
304
  annotations: reconcileAnnotation.of(false)
304
305
  });
305
306
  };
306
- var handlePatch = (patch, target, state) => {
307
+ var handlePatch = (patch, target, state2) => {
307
308
  if (patch.action === "insert") {
308
309
  return handleInsert(target, patch);
309
310
  } else if (patch.action === "splice") {
@@ -311,7 +312,7 @@ var handlePatch = (patch, target, state) => {
311
312
  } else if (patch.action === "del") {
312
313
  return handleDel(target, patch);
313
314
  } else if (patch.action === "put") {
314
- return handlePut(target, patch, state);
315
+ return handlePut(target, patch, state2);
315
316
  } else {
316
317
  return null;
317
318
  }
@@ -355,7 +356,7 @@ var handleDel = (target, patch) => {
355
356
  }
356
357
  ];
357
358
  };
358
- var handlePut = (target, patch, state) => {
359
+ var handlePut = (target, patch, state2) => {
359
360
  const index = charPath(target, [
360
361
  ...patch.path,
361
362
  0
@@ -363,7 +364,7 @@ var handlePut = (target, patch, state) => {
363
364
  if (index == null) {
364
365
  return [];
365
366
  }
366
- const length = state.doc.length;
367
+ const length = state2.doc.length;
367
368
  if (typeof patch.value !== "string") {
368
369
  return [];
369
370
  }
@@ -474,13 +475,13 @@ var automerge = (accessor) => {
474
475
  ViewPlugin.fromClass(class {
475
476
  constructor(_view) {
476
477
  this._view = _view;
477
- accessor.handle.addListener("change", this._handleChange.bind(this));
478
+ this._handleChange = () => {
479
+ syncer.reconcile(this._view, false);
480
+ };
481
+ accessor.handle.addListener("change", this._handleChange);
478
482
  }
479
483
  destroy() {
480
- accessor.handle.removeListener("change", this._handleChange.bind(this));
481
- }
482
- _handleChange() {
483
- syncer.reconcile(this._view, false);
484
+ accessor.handle.removeListener("change", this._handleChange);
484
485
  }
485
486
  }),
486
487
  // Reconcile local updates.
@@ -561,9 +562,9 @@ var RemoteSelectionsDecorator = class {
561
562
  _updateRemoteSelections(update2) {
562
563
  const decorations = [];
563
564
  const awarenessStates = this._provider.getRemoteStates();
564
- for (const state of awarenessStates) {
565
- const anchor = state.position?.anchor ? this._cursorConverter.fromCursor(state.position.anchor) : null;
566
- const head = state.position?.head ? this._cursorConverter.fromCursor(state.position.head) : null;
565
+ for (const state2 of awarenessStates) {
566
+ const anchor = state2.position?.anchor ? this._cursorConverter.fromCursor(state2.position.anchor) : null;
567
+ const head = state2.position?.head ? this._cursorConverter.fromCursor(state2.position.head) : null;
567
568
  if (anchor == null || head == null) {
568
569
  continue;
569
570
  }
@@ -571,8 +572,8 @@ var RemoteSelectionsDecorator = class {
571
572
  const end = Math.min(Math.max(anchor, head), update2.view.state.doc.length);
572
573
  const startLine = update2.view.state.doc.lineAt(start);
573
574
  const endLine = update2.view.state.doc.lineAt(end);
574
- const color = state.info.color ?? "#30bced";
575
- const lightColor = state.info.lightColor ?? color + "33";
575
+ const color = state2.info.color ?? "#30bced";
576
+ const lightColor = state2.info.lightColor ?? color + "33";
576
577
  if (startLine.number === endLine.number) {
577
578
  decorations.push({
578
579
  from: start,
@@ -625,7 +626,7 @@ var RemoteSelectionsDecorator = class {
625
626
  value: Decoration2.widget({
626
627
  side: head - anchor > 0 ? -1 : 1,
627
628
  block: false,
628
- widget: new RemoteCaretWidget(state.info.displayName ?? "Anonymous", color)
629
+ widget: new RemoteCaretWidget(state2.info.displayName ?? "Anonymous", color)
629
630
  })
630
631
  });
631
632
  }
@@ -1156,7 +1157,7 @@ var commandConfig = Facet3.define({
1156
1157
  });
1157
1158
  var commandState = StateField3.define({
1158
1159
  create: () => ({}),
1159
- update: (state, tr) => {
1160
+ update: (state2, tr) => {
1160
1161
  for (const effect of tr.effects) {
1161
1162
  if (effect.is(closeEffect)) {
1162
1163
  return {};
@@ -1207,7 +1208,7 @@ var commandState = StateField3.define({
1207
1208
  };
1208
1209
  }
1209
1210
  }
1210
- return state;
1211
+ return state2;
1211
1212
  },
1212
1213
  provide: (field) => [
1213
1214
  showTooltip.from(field, (value) => value.tooltip ?? null)
@@ -1453,8 +1454,8 @@ var setComments = StateEffect3.define();
1453
1454
  var setSelection = StateEffect3.define();
1454
1455
  var setCommentState = StateEffect3.define();
1455
1456
  var commentsState = StateField4.define({
1456
- create: (state) => ({
1457
- id: state.facet(documentId),
1457
+ create: (state2) => ({
1458
+ id: state2.facet(documentId),
1458
1459
  comments: [],
1459
1460
  selection: {}
1460
1461
  }),
@@ -1526,8 +1527,8 @@ var commentCurrentMark = Decoration4.mark({
1526
1527
  });
1527
1528
  var commentsDecorations = EditorView7.decorations.compute([
1528
1529
  commentsState
1529
- ], (state) => {
1530
- const { selection: { current }, comments: comments2 } = state.field(commentsState);
1530
+ ], (state2) => {
1531
+ const { selection: { current }, comments: comments2 } = state2.field(commentsState);
1531
1532
  const decorations = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
1532
1533
  const range = comment.range;
1533
1534
  if (!range || range.from === range.to) {
@@ -1691,7 +1692,7 @@ var optionsFacet = Facet4.define({
1691
1692
  });
1692
1693
  var comments = (options = {}) => {
1693
1694
  const { key: shortcut = "meta-'" } = options;
1694
- const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1695
+ const handleSelect = debounce((state2) => options.onSelect?.(state2), 200);
1695
1696
  return [
1696
1697
  optionsFacet.of(options),
1697
1698
  documentId.of(options.id),
@@ -1740,9 +1741,9 @@ var comments = (options = {}) => {
1740
1741
  //
1741
1742
  // Track deleted ranges and update ranges for decorations.
1742
1743
  //
1743
- EditorView7.updateListener.of(({ view, state, changes }) => {
1744
+ EditorView7.updateListener.of(({ view, state: state2, changes }) => {
1744
1745
  let mod = false;
1745
- const { comments: comments2, ...value } = state.field(commentsState);
1746
+ const { comments: comments2, ...value } = state2.field(commentsState);
1746
1747
  changes.iterChanges((from, to, from2, to2) => {
1747
1748
  comments2.forEach(({ comment, range }) => {
1748
1749
  if (from2 === to2) {
@@ -1772,10 +1773,10 @@ var comments = (options = {}) => {
1772
1773
  //
1773
1774
  // Track selection/proximity.
1774
1775
  //
1775
- EditorView7.updateListener.of(({ view, state }) => {
1776
+ EditorView7.updateListener.of(({ view, state: state2 }) => {
1776
1777
  let min = Infinity;
1777
- const { selection: { current, closest }, comments: comments2 } = state.field(commentsState);
1778
- const { head } = state.selection.main;
1778
+ const { selection: { current, closest }, comments: comments2 } = state2.field(commentsState);
1779
+ const { head } = state2.selection.main;
1779
1780
  const selection = {};
1780
1781
  comments2.forEach(({ comment, range }) => {
1781
1782
  if (head >= range.from && head <= range.to) {
@@ -1796,7 +1797,7 @@ var comments = (options = {}) => {
1796
1797
  });
1797
1798
  handleSelect({
1798
1799
  selection,
1799
- id: state.facet(documentId),
1800
+ id: state2.facet(documentId),
1800
1801
  comments: comments2.map(({ comment, range }) => ({
1801
1802
  comment,
1802
1803
  range,
@@ -1831,14 +1832,14 @@ var focusComment = (view, id, center = true) => {
1831
1832
  });
1832
1833
  }
1833
1834
  };
1834
- var useComments = (view, id, comments2 = []) => {
1835
+ var useComments = (view, id, comments2) => {
1835
1836
  useEffect(() => {
1836
1837
  if (view) {
1837
1838
  if (id === view.state.facet(documentId)) {
1838
1839
  view.dispatch({
1839
1840
  effects: setComments.of({
1840
1841
  id,
1841
- comments: comments2
1842
+ comments: comments2 ?? []
1842
1843
  })
1843
1844
  });
1844
1845
  }
@@ -1864,11 +1865,11 @@ var cursorLineMargin = (options = {
1864
1865
  if (!lines || lines <= 0) {
1865
1866
  return [];
1866
1867
  }
1867
- return EditorView8.updateListener.of(({ transactions, state, selectionSet, startState, view }) => {
1868
+ return EditorView8.updateListener.of(({ transactions, state: state2, selectionSet, startState, view }) => {
1868
1869
  if (transactions.length < 1 || transactions.some((tr) => tr.isUserEvent(annotation))) {
1869
1870
  return;
1870
1871
  }
1871
- const s = state;
1872
+ const s = state2;
1872
1873
  const { main } = s.selection;
1873
1874
  const rect = view.dom.getBoundingClientRect();
1874
1875
  const viewportTop = rect.top - view.documentTop;
@@ -2606,8 +2607,8 @@ var checkedTask = Decoration5.replace({
2606
2607
  var uncheckedTask = Decoration5.replace({
2607
2608
  widget: new CheckboxWidget(false)
2608
2609
  });
2609
- var editingRange = (state, range, focus) => {
2610
- const { readOnly, selection: { main: { head } } } = state;
2610
+ var editingRange = (state2, range, focus) => {
2611
+ const { readOnly, selection: { main: { head } } } = state2;
2611
2612
  return focus && !readOnly && head >= range.from && head <= range.to;
2612
2613
  };
2613
2614
  var MarksByParent = /* @__PURE__ */ new Set([
@@ -2620,14 +2621,14 @@ var MarksByParent = /* @__PURE__ */ new Set([
2620
2621
  var buildDecorations = (view, options, focus) => {
2621
2622
  const deco = new RangeSetBuilder2();
2622
2623
  const atomicDeco = new RangeSetBuilder2();
2623
- const { state } = view;
2624
+ const { state: state2 } = view;
2624
2625
  for (const { from, to } of view.visibleRanges) {
2625
- syntaxTree(state).iterate({
2626
+ syntaxTree(state2).iterate({
2626
2627
  from,
2627
2628
  to,
2628
2629
  enter: (node) => {
2629
2630
  if (node.name === "FencedCode") {
2630
- const editing = editingRange(state, node, focus);
2631
+ const editing = editingRange(state2, node, focus);
2631
2632
  for (const block of view.viewportLineBlocks) {
2632
2633
  if (block.to < node.from) {
2633
2634
  continue;
@@ -2636,7 +2637,7 @@ var buildDecorations = (view, options, focus) => {
2636
2637
  break;
2637
2638
  }
2638
2639
  const first = block.from <= node.from;
2639
- const last = block.to >= node.to && /^(\s>)*```$/.test(state.doc.sliceString(block.from, block.to));
2640
+ const last = block.to >= node.to && /^(\s>)*```$/.test(state2.doc.sliceString(block.from, block.to));
2640
2641
  deco.add(block.from, block.from, first ? fencedCodeLineFirst : last ? fencedCodeLineLast : fencedCodeLine);
2641
2642
  if (!editing && (first || last)) {
2642
2643
  atomicDeco.add(block.from, block.to, hide);
@@ -2646,9 +2647,9 @@ var buildDecorations = (view, options, focus) => {
2646
2647
  } else if (node.name === "Link") {
2647
2648
  const marks = node.node.getChildren("LinkMark");
2648
2649
  const urlNode = node.node.getChild("URL");
2649
- const editing = editingRange(state, node, focus);
2650
+ const editing = editingRange(state2, node, focus);
2650
2651
  if (urlNode && marks.length >= 2) {
2651
- const url = state.sliceDoc(urlNode.from, urlNode.to);
2652
+ const url = state2.sliceDoc(urlNode.from, urlNode.to);
2652
2653
  if (!editing) {
2653
2654
  atomicDeco.add(node.from, marks[0].to, hide);
2654
2655
  }
@@ -2669,24 +2670,24 @@ var buildDecorations = (view, options, focus) => {
2669
2670
  }
2670
2671
  } else if (node.name === "HeaderMark") {
2671
2672
  const parent = node.node.parent;
2672
- if (/^ATX/.test(parent.name) && !editingRange(state, state.doc.lineAt(node.from), focus)) {
2673
- const next = state.doc.sliceString(node.to, node.to + 1);
2673
+ if (/^ATX/.test(parent.name) && !editingRange(state2, state2.doc.lineAt(node.from), focus)) {
2674
+ const next = state2.doc.sliceString(node.to, node.to + 1);
2674
2675
  atomicDeco.add(node.from, node.to + (next === " " ? 1 : 0), hide);
2675
2676
  }
2676
2677
  } else if (node.name === "HorizontalRule") {
2677
- if (!editingRange(state, node, focus)) {
2678
+ if (!editingRange(state2, node, focus)) {
2678
2679
  deco.add(node.from, node.to, horizontalRule);
2679
2680
  }
2680
2681
  } else if (node.name === "TaskMarker") {
2681
- if (!editingRange(state, node, focus)) {
2682
- const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2682
+ if (!editingRange(state2, node, focus)) {
2683
+ const checked = state2.doc.sliceString(node.from + 1, node.to - 1) === "x";
2683
2684
  atomicDeco.add(node.from - 2, node.from - 1, Decoration5.mark({
2684
2685
  class: "cm-task"
2685
2686
  }));
2686
2687
  atomicDeco.add(node.from, node.to, checked ? checkedTask : uncheckedTask);
2687
2688
  }
2688
2689
  } else if (MarksByParent.has(node.name)) {
2689
- if (!editingRange(state, node.node.parent, focus)) {
2690
+ if (!editingRange(state2, node.node.parent, focus)) {
2690
2691
  atomicDeco.add(node.from, node.to, hide);
2691
2692
  }
2692
2693
  }
@@ -2796,8 +2797,9 @@ import { snippet } from "@codemirror/autocomplete";
2796
2797
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
2797
2798
  import { EditorSelection } from "@codemirror/state";
2798
2799
  import { EditorView as EditorView13, keymap as keymap6 } from "@codemirror/view";
2799
- import { useState, useMemo } from "react";
2800
- var compareFormatting = (a, b) => a.blockType === b.blockType && a.strong === b.strong && a.emphasis === b.emphasis && a.strikethrough === b.strikethrough && a.code === b.code && a.link === b.link && a.listStyle === b.listStyle && a.blockQuote === b.blockQuote;
2800
+ import { useMemo } from "react";
2801
+ import { useStateRef } from "@dxos/react-async";
2802
+ var formattingEquals = (a, b) => a.blockType === b.blockType && a.strong === b.strong && a.emphasis === b.emphasis && a.strikethrough === b.strikethrough && a.code === b.code && a.link === b.link && a.listStyle === b.listStyle && a.blockQuote === b.blockQuote;
2801
2803
  var Inline;
2802
2804
  (function(Inline2) {
2803
2805
  Inline2[Inline2["Strong"] = 0] = "Strong";
@@ -2811,13 +2813,13 @@ var List;
2811
2813
  List2[List2["Bullet"] = 1] = "Bullet";
2812
2814
  List2[List2["Task"] = 2] = "Task";
2813
2815
  })(List || (List = {}));
2814
- var setHeading = (level) => ({ state, dispatch }) => {
2815
- const { selection: { ranges }, doc } = state;
2816
+ var setHeading = (level) => ({ state: state2, dispatch }) => {
2817
+ const { selection: { ranges }, doc } = state2;
2816
2818
  const changes = [];
2817
2819
  let prevBlock = -1;
2818
2820
  for (const range of ranges) {
2819
2821
  let sawBlock = false;
2820
- syntaxTree2(state).iterate({
2822
+ syntaxTree2(state2).iterate({
2821
2823
  from: range.from,
2822
2824
  to: range.to,
2823
2825
  enter: (node) => {
@@ -2870,7 +2872,7 @@ var setHeading = (level) => ({ state, dispatch }) => {
2870
2872
  }
2871
2873
  });
2872
2874
  let line;
2873
- if (!sawBlock && range.empty && level > 0 && !/\S/.test((line = state.doc.lineAt(range.from)).text)) {
2875
+ if (!sawBlock && range.empty && level > 0 && !/\S/.test((line = state2.doc.lineAt(range.from)).text)) {
2874
2876
  changes.push({
2875
2877
  from: line.from,
2876
2878
  to: line.to,
@@ -2881,23 +2883,23 @@ var setHeading = (level) => ({ state, dispatch }) => {
2881
2883
  if (!changes.length) {
2882
2884
  return false;
2883
2885
  }
2884
- const changeSet = state.changes(changes);
2885
- dispatch(state.update({
2886
+ const changeSet = state2.changes(changes);
2887
+ dispatch(state2.update({
2886
2888
  changes: changeSet,
2887
- selection: state.selection.map(changeSet, 1),
2889
+ selection: state2.selection.map(changeSet, 1),
2888
2890
  userEvent: "format.setHeading",
2889
2891
  scrollIntoView: true
2890
2892
  }));
2891
2893
  return true;
2892
2894
  };
2893
- var setStyle = (type, enable) => ({ state, dispatch }) => {
2895
+ var setStyle = (type, enable) => ({ state: state2, dispatch }) => {
2894
2896
  const marker = inlineMarkerText(type);
2895
- const changes = state.changeByRange((range) => {
2897
+ const changes = state2.changeByRange((range) => {
2896
2898
  if (!enable && range.empty) {
2897
- const after = state.doc.sliceString(range.head, range.head + 6);
2899
+ const after = state2.doc.sliceString(range.head, range.head + 6);
2898
2900
  const found = after.indexOf(marker);
2899
2901
  if (found >= 0 && /^[*~`]*$/.test(after.slice(0, found))) {
2900
- const before = state.doc.sliceString(range.head - 6, range.head);
2902
+ const before = state2.doc.sliceString(range.head - 6, range.head);
2901
2903
  if (before.slice(before.length - found - marker.length, before.length - found) === marker && [
2902
2904
  ...before.slice(before.length - found)
2903
2905
  ].reverse().join("") === after.slice(0, found)) {
@@ -2924,14 +2926,14 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
2924
2926
  let startCovered = false;
2925
2927
  let endCovered = false;
2926
2928
  let { from, to } = range;
2927
- syntaxTree2(state).iterate({
2929
+ syntaxTree2(state2).iterate({
2928
2930
  from,
2929
2931
  to,
2930
2932
  enter: (node) => {
2931
2933
  const { name } = node;
2932
2934
  if (Object.hasOwn(Textblocks, name) && Textblocks[name] !== "codeblock") {
2933
2935
  blockStart = blockContentStart(node);
2934
- blockEnd = blockContentEnd(node, state.doc);
2936
+ blockEnd = blockContentEnd(node, state2.doc);
2935
2937
  startCovered = endCovered = false;
2936
2938
  } else if (name === "Link" || name === "Image" && enable) {
2937
2939
  if (from < node.from && to > node.from && to <= node.to) {
@@ -2976,7 +2978,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
2976
2978
  });
2977
2979
  if (markType !== type && closeStart >= to) {
2978
2980
  changesAtEnd.push({
2979
- from: skipSpaces(Math.min(to, blockEnd), state.doc, 1, blockEnd),
2981
+ from: skipSpaces(Math.min(to, blockEnd), state2.doc, 1, blockEnd),
2980
2982
  insert: inlineMarkerText(markType)
2981
2983
  });
2982
2984
  }
@@ -2988,7 +2990,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
2988
2990
  });
2989
2991
  if (markType !== type && openEnd <= from) {
2990
2992
  changes2.push({
2991
- from: skipSpaces(Math.max(from, blockStart), state.doc, -1, blockStart),
2993
+ from: skipSpaces(Math.max(from, blockStart), state2.doc, -1, blockStart),
2992
2994
  insert: inlineMarkerText(markType)
2993
2995
  });
2994
2996
  }
@@ -3018,7 +3020,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3018
3020
  changes2.push(startCovered);
3019
3021
  } else if (startCovered) {
3020
3022
  changes2.push({
3021
- from: skipSpaces(rangeStart, state.doc, -1, blockStart),
3023
+ from: skipSpaces(rangeStart, state2.doc, -1, blockStart),
3022
3024
  insert: marker
3023
3025
  });
3024
3026
  }
@@ -3026,7 +3028,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3026
3028
  changes2.push(endCovered);
3027
3029
  } else if (endCovered) {
3028
3030
  changes2.push({
3029
- from: skipSpaces(rangeEnd, state.doc, 1, blockEnd),
3031
+ from: skipSpaces(rangeEnd, state2.doc, 1, blockEnd),
3030
3032
  insert: marker
3031
3033
  });
3032
3034
  }
@@ -3034,7 +3036,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3034
3036
  }
3035
3037
  }
3036
3038
  });
3037
- if (blockStart < 0 && range.empty && enable && !/\S/.test(state.doc.lineAt(range.from).text)) {
3039
+ if (blockStart < 0 && range.empty && enable && !/\S/.test(state2.doc.lineAt(range.from).text)) {
3038
3040
  return {
3039
3041
  changes: {
3040
3042
  from: range.head,
@@ -3043,13 +3045,13 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3043
3045
  range: EditorSelection.cursor(range.head + marker.length)
3044
3046
  };
3045
3047
  }
3046
- const changeSet = state.changes(changes2.concat(changesAtEnd));
3048
+ const changeSet = state2.changes(changes2.concat(changesAtEnd));
3047
3049
  return {
3048
3050
  changes: changeSet,
3049
3051
  range: range.empty && !changeSet.empty ? EditorSelection.cursor(range.head + marker.length) : EditorSelection.range(changeSet.mapPos(range.from, 1), changeSet.mapPos(range.to, -1))
3050
3052
  };
3051
3053
  });
3052
- dispatch(state.update(changes, {
3054
+ dispatch(state2.update(changes, {
3053
3055
  userEvent: enable ? "format.style.add" : "format.style.remove",
3054
3056
  scrollIntoView: true
3055
3057
  }));
@@ -3123,8 +3125,8 @@ var insertTable = (view) => {
3123
3125
  const { from } = doc.line(number);
3124
3126
  snippets.table(view, null, from, from);
3125
3127
  };
3126
- var removeLinkInner = (from, to, changes, state) => {
3127
- syntaxTree2(state).iterate({
3128
+ var removeLinkInner = (from, to, changes, state2) => {
3129
+ syntaxTree2(state2).iterate({
3128
3130
  from,
3129
3131
  to,
3130
3132
  enter: (node) => {
@@ -3138,8 +3140,8 @@ var removeLinkInner = (from, to, changes, state) => {
3138
3140
  });
3139
3141
  } else if (name === "LinkTitle" || name === "URL") {
3140
3142
  changes.push({
3141
- from: skipSpaces(node2.from, state.doc, -1),
3142
- to: skipSpaces(node2.to, state.doc, 1)
3143
+ from: skipSpaces(node2.from, state2.doc, -1),
3144
+ to: skipSpaces(node2.to, state2.doc, 1)
3143
3145
  });
3144
3146
  }
3145
3147
  });
@@ -3148,32 +3150,32 @@ var removeLinkInner = (from, to, changes, state) => {
3148
3150
  }
3149
3151
  });
3150
3152
  };
3151
- var removeLink = ({ state, dispatch }) => {
3153
+ var removeLink = ({ state: state2, dispatch }) => {
3152
3154
  const changes = [];
3153
- for (const { from, to } of state.selection.ranges) {
3154
- removeLinkInner(from, to, changes, state);
3155
+ for (const { from, to } of state2.selection.ranges) {
3156
+ removeLinkInner(from, to, changes, state2);
3155
3157
  }
3156
3158
  if (!changes) {
3157
3159
  return false;
3158
3160
  }
3159
- dispatch(state.update({
3161
+ dispatch(state2.update({
3160
3162
  changes,
3161
3163
  userEvent: "format.link.remove",
3162
3164
  scrollIntoView: true
3163
3165
  }));
3164
3166
  return true;
3165
3167
  };
3166
- var addLink = ({ state, dispatch }) => {
3167
- const changes = state.changeByRange((range) => {
3168
+ var addLink = ({ url, image: image2 } = {}) => ({ state: state2, dispatch }) => {
3169
+ const changes = state2.changeByRange((range) => {
3168
3170
  let { from, to } = range;
3169
3171
  const cutStyles = [];
3170
3172
  let okay = null;
3171
- syntaxTree2(state).iterate({
3173
+ syntaxTree2(state2).iterate({
3172
3174
  from,
3173
3175
  to,
3174
3176
  enter: (node) => {
3175
3177
  if (Object.hasOwn(Textblocks, node.name)) {
3176
- okay = Textblocks[node.name] !== "codeblock" && from >= blockContentStart(node) && to <= blockContentEnd(node, state.doc);
3178
+ okay = Textblocks[node.name] !== "codeblock" && from >= blockContentStart(node) && to <= blockContentEnd(node, state2.doc);
3177
3179
  } else if (Object.hasOwn(InlineMarker, node.name)) {
3178
3180
  const sNode = node.node;
3179
3181
  if (node.from < from && node.to <= to) {
@@ -3193,7 +3195,7 @@ var addLink = ({ state, dispatch }) => {
3193
3195
  }
3194
3196
  });
3195
3197
  if (okay === null) {
3196
- const line = state.doc.lineAt(from);
3198
+ const line = state2.doc.lineAt(from);
3197
3199
  okay = to <= line.to && !/\S/.test(line.text.slice(from - line.from));
3198
3200
  }
3199
3201
  if (!okay) {
@@ -3203,26 +3205,26 @@ var addLink = ({ state, dispatch }) => {
3203
3205
  }
3204
3206
  const changes2 = [];
3205
3207
  const changesAfter = [];
3206
- removeLinkInner(from, to, changesAfter, state);
3208
+ removeLinkInner(from, to, changesAfter, state2);
3207
3209
  let cursorOffset = 1;
3208
3210
  for (const style of cutStyles) {
3209
3211
  const type = InlineMarker[style.name];
3210
3212
  const mark2 = inlineMarkerText(type);
3211
3213
  if (style.from < from) {
3212
3214
  changes2.push({
3213
- from: skipSpaces(from, state.doc, -1),
3215
+ from: skipSpaces(from, state2.doc, -1),
3214
3216
  insert: mark2
3215
3217
  });
3216
3218
  changesAfter.push({
3217
- from: skipSpaces(from, state.doc, 1, to),
3219
+ from: skipSpaces(from, state2.doc, 1, to),
3218
3220
  insert: mark2
3219
3221
  });
3220
3222
  } else {
3221
3223
  changes2.push({
3222
- from: skipSpaces(to, state.doc, -1, from),
3224
+ from: skipSpaces(to, state2.doc, -1, from),
3223
3225
  insert: mark2
3224
3226
  });
3225
- const after = skipSpaces(to, state.doc, 1);
3227
+ const after = skipSpaces(to, state2.doc, 1);
3226
3228
  if (after === to) {
3227
3229
  cursorOffset += mark2.length;
3228
3230
  }
@@ -3234,34 +3236,34 @@ var addLink = ({ state, dispatch }) => {
3234
3236
  }
3235
3237
  changes2.push({
3236
3238
  from,
3237
- insert: "["
3239
+ insert: image2 ? "![" : "["
3238
3240
  }, {
3239
3241
  from: to,
3240
- insert: "]()"
3242
+ insert: `](${url ?? ""})`
3241
3243
  });
3242
- const changeSet = state.changes(changes2.concat(changesAfter));
3244
+ const changeSet = state2.changes(changes2.concat(changesAfter));
3243
3245
  return {
3244
3246
  changes: changeSet,
3245
- range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset)
3247
+ range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset - (url ? url.length + 2 : 0))
3246
3248
  };
3247
3249
  });
3248
3250
  if (changes.changes.empty) {
3249
3251
  return false;
3250
3252
  }
3251
- dispatch(state.update(changes, {
3253
+ dispatch(state2.update(changes, {
3252
3254
  userEvent: "format.link.add",
3253
3255
  scrollIntoView: true
3254
3256
  }));
3255
3257
  return true;
3256
3258
  };
3257
- var addList = (type) => ({ state, dispatch }) => {
3259
+ var addList = (type) => ({ state: state2, dispatch }) => {
3258
3260
  let lastBlock = -1;
3259
3261
  let counter = 1;
3260
3262
  let first = true;
3261
3263
  let parentColumn = null;
3262
3264
  const blocks = [];
3263
- for (const { from, to } of state.selection.ranges) {
3264
- syntaxTree2(state).iterate({
3265
+ for (const { from, to } of state2.selection.ranges) {
3266
+ syntaxTree2(state2).iterate({
3265
3267
  from,
3266
3268
  to,
3267
3269
  enter: (node) => {
@@ -3273,7 +3275,7 @@ var addList = (type) => ({ state, dispatch }) => {
3273
3275
  }
3274
3276
  if (before?.name === (type === 0 ? "OrderedList" : "BulletList")) {
3275
3277
  const item = before.lastChild;
3276
- const itemLine = state.doc.lineAt(item.from);
3278
+ const itemLine = state2.doc.lineAt(item.from);
3277
3279
  const itemText = itemLine.text.slice(item.from - itemLine.from);
3278
3280
  parentColumn = item.from - itemLine.from + /^\s*/.exec(itemText)[0].length;
3279
3281
  if (type === 0) {
@@ -3308,10 +3310,10 @@ var addList = (type) => ({ state, dispatch }) => {
3308
3310
  });
3309
3311
  }
3310
3312
  if (!blocks.length) {
3311
- const { from, to } = state.doc.lineAt(state.selection.main.anchor);
3313
+ const { from, to } = state2.doc.lineAt(state2.selection.main.anchor);
3312
3314
  if (from === to) {
3313
3315
  const insert = type === 1 ? "- " : type === 0 ? "1. " : "- [ ] ";
3314
- dispatch(state.update({
3316
+ dispatch(state2.update({
3315
3317
  changes: [
3316
3318
  {
3317
3319
  from,
@@ -3332,11 +3334,11 @@ var addList = (type) => ({ state, dispatch }) => {
3332
3334
  for (let i = 0; i < blocks.length; i++) {
3333
3335
  const { node, counter: counter2, parentColumn: parentColumn2 } = blocks[i];
3334
3336
  const nodeFrom = node.name === "CodeBlock" ? node.from - 4 : node.from;
3335
- let padding = nodeFrom > 0 && !/\s/.test(state.doc.sliceString(nodeFrom - 1, nodeFrom)) ? 1 : 0;
3337
+ let padding = nodeFrom > 0 && !/\s/.test(state2.doc.sliceString(nodeFrom - 1, nodeFrom)) ? 1 : 0;
3336
3338
  if (type === 0) {
3337
3339
  padding += String(counter2).length;
3338
3340
  }
3339
- let line = state.doc.lineAt(nodeFrom);
3341
+ let line = state2.doc.lineAt(nodeFrom);
3340
3342
  const column = nodeFrom - line.from;
3341
3343
  if (parentColumn2 !== null && parentColumn2 > column) {
3342
3344
  padding = Math.max(padding, parentColumn2 - column);
@@ -3361,7 +3363,7 @@ var addList = (type) => ({ state, dispatch }) => {
3361
3363
  insert: mark2
3362
3364
  });
3363
3365
  while (line.to < node.to) {
3364
- line = state.doc.lineAt(line.to + 1);
3366
+ line = state2.doc.lineAt(line.to + 1);
3365
3367
  const open = /^[\s>]*/.exec(line.text)[0].length;
3366
3368
  changes.push({
3367
3369
  from: line.from + Math.min(open, column),
@@ -3376,25 +3378,25 @@ var addList = (type) => ({ state, dispatch }) => {
3376
3378
  next = next.nextSibling;
3377
3379
  }
3378
3380
  if (next?.name === "OrderedList") {
3379
- renumberListItems(next.firstChild, last.counter + 1, changes, state.doc);
3381
+ renumberListItems(next.firstChild, last.counter + 1, changes, state2.doc);
3380
3382
  }
3381
3383
  }
3382
- const changeSet = state.changes(changes);
3383
- dispatch(state.update({
3384
+ const changeSet = state2.changes(changes);
3385
+ dispatch(state2.update({
3384
3386
  changes: changeSet,
3385
- selection: state.selection.map(changeSet, 1),
3387
+ selection: state2.selection.map(changeSet, 1),
3386
3388
  userEvent: "format.list.add",
3387
3389
  scrollIntoView: true
3388
3390
  }));
3389
3391
  return true;
3390
3392
  };
3391
- var removeList = (type) => ({ state, dispatch }) => {
3393
+ var removeList = (type) => ({ state: state2, dispatch }) => {
3392
3394
  let lastBlock = -1;
3393
3395
  const changes = [];
3394
3396
  const stack = [];
3395
3397
  const targetNodeType = type === 0 ? "OrderedList" : type === 1 ? "BulletList" : "TaskList";
3396
- for (const { from, to } of state.selection.ranges) {
3397
- syntaxTree2(state).iterate({
3398
+ for (const { from, to } of state2.selection.ranges) {
3399
+ syntaxTree2(state2).iterate({
3398
3400
  from,
3399
3401
  to,
3400
3402
  enter: (node) => {
@@ -3411,7 +3413,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3411
3413
  stack.pop();
3412
3414
  } else if (name === "ListItem" && stack[stack.length - 1] === targetNodeType && node.from !== lastBlock) {
3413
3415
  lastBlock = node.from;
3414
- let line = state.doc.lineAt(node.from);
3416
+ let line = state2.doc.lineAt(node.from);
3415
3417
  const mark2 = /^\s*(\d+[.)] |[-*+] (\[[ x]\] )?)/.exec(line.text.slice(node.from - line.from));
3416
3418
  if (!mark2) {
3417
3419
  return false;
@@ -3422,7 +3424,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3422
3424
  to: node.from + mark2[0].length
3423
3425
  });
3424
3426
  while (line.to < node.to) {
3425
- line = state.doc.lineAt(line.to + 1);
3427
+ line = state2.doc.lineAt(line.to + 1);
3426
3428
  const open = /^[\s>]*/.exec(line.text)[0].length;
3427
3429
  if (open > column) {
3428
3430
  changes.push({
@@ -3432,7 +3434,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3432
3434
  }
3433
3435
  }
3434
3436
  if (node.to >= to) {
3435
- renumberListItems(node.node.nextSibling, 1, changes, state.doc);
3437
+ renumberListItems(node.node.nextSibling, 1, changes, state2.doc);
3436
3438
  }
3437
3439
  return false;
3438
3440
  }
@@ -3442,7 +3444,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3442
3444
  if (!changes.length) {
3443
3445
  return false;
3444
3446
  }
3445
- dispatch(state.update({
3447
+ dispatch(state2.update({
3446
3448
  changes,
3447
3449
  userEvent: "format.list.remove",
3448
3450
  scrollIntoView: true
@@ -3472,12 +3474,12 @@ var renumberListItems = (item, counter, changes, doc) => {
3472
3474
  }
3473
3475
  }
3474
3476
  };
3475
- var setBlockquote = (enable) => ({ state, dispatch }) => {
3477
+ var setBlockquote = (enable) => ({ state: state2, dispatch }) => {
3476
3478
  const lines = [];
3477
3479
  let lastBlock = -1;
3478
- for (const { from, to } of state.selection.ranges) {
3480
+ for (const { from, to } of state2.selection.ranges) {
3479
3481
  const sawBlock = false;
3480
- syntaxTree2(state).iterate({
3482
+ syntaxTree2(state2).iterate({
3481
3483
  from,
3482
3484
  to,
3483
3485
  enter: (node) => {
@@ -3486,9 +3488,9 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3486
3488
  return false;
3487
3489
  }
3488
3490
  lastBlock = node.from;
3489
- let line2 = state.doc.lineAt(node.from);
3491
+ let line2 = state2.doc.lineAt(node.from);
3490
3492
  if (line2.number > 1) {
3491
- const prevLine = state.doc.line(line2.number - 1);
3493
+ const prevLine = state2.doc.line(line2.number - 1);
3492
3494
  if (/^[>\s]*$/.test(prevLine.text)) {
3493
3495
  if (!enable || lines.length && lines[lines.length - 1].number === prevLine.number - 1) {
3494
3496
  lines.push(prevLine);
@@ -3500,10 +3502,10 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3500
3502
  if (line2.to >= node.to) {
3501
3503
  break;
3502
3504
  }
3503
- line2 = state.doc.line(line2.number + 1);
3505
+ line2 = state2.doc.line(line2.number + 1);
3504
3506
  }
3505
- if (!enable && line2.number < state.doc.lines) {
3506
- const nextLine = state.doc.line(line2.number + 1);
3507
+ if (!enable && line2.number < state2.doc.lines) {
3508
+ const nextLine = state2.doc.line(line2.number + 1);
3507
3509
  if (/^[>\s]*$/.test(nextLine.text)) {
3508
3510
  lines.push(nextLine);
3509
3511
  }
@@ -3513,7 +3515,7 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3513
3515
  }
3514
3516
  });
3515
3517
  let line;
3516
- if (!sawBlock && enable && from === to && !/\S/.test((line = state.doc.lineAt(from)).text)) {
3518
+ if (!sawBlock && enable && from === to && !/\S/.test((line = state2.doc.lineAt(from)).text)) {
3517
3519
  lines.push(line);
3518
3520
  }
3519
3521
  }
@@ -3537,10 +3539,10 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3537
3539
  if (!changes.length) {
3538
3540
  return false;
3539
3541
  }
3540
- const changeSet = state.changes(changes);
3541
- dispatch(state.update({
3542
+ const changeSet = state2.changes(changes);
3543
+ dispatch(state2.update({
3542
3544
  changes: changeSet,
3543
- selection: state.selection.map(changeSet, 1),
3545
+ selection: state2.selection.map(changeSet, 1),
3544
3546
  userEvent: enable ? "format.blockquote.add" : "format.blockquote.remove",
3545
3547
  scrollIntoView: true
3546
3548
  }));
@@ -3552,11 +3554,11 @@ var toggleBlockquote = (target) => {
3552
3554
  return (getFormatting(target.state).blockQuote ? removeBlockquote : addBlockquote)(target);
3553
3555
  };
3554
3556
  var addCodeblock = (target) => {
3555
- const { state, dispatch } = target;
3556
- const { selection } = state;
3557
+ const { state: state2, dispatch } = target;
3558
+ const { selection } = state2;
3557
3559
  if (selection.ranges.length === 1 && selection.main.empty) {
3558
3560
  const { head } = selection.main;
3559
- const line = state.doc.lineAt(head);
3561
+ const line = state2.doc.lineAt(head);
3560
3562
  if (!/\S/.test(line.text) && head === line.from) {
3561
3563
  snippets.codeblock(target, null, line.from, line.to);
3562
3564
  return true;
@@ -3566,7 +3568,7 @@ var addCodeblock = (target) => {
3566
3568
  for (const { from, to } of selection.ranges) {
3567
3569
  let blockFrom = from;
3568
3570
  let blockTo = to;
3569
- syntaxTree2(state).iterate({
3571
+ syntaxTree2(state2).iterate({
3570
3572
  from,
3571
3573
  to,
3572
3574
  enter: (node) => {
@@ -3575,8 +3577,8 @@ var addCodeblock = (target) => {
3575
3577
  blockFrom = node.from;
3576
3578
  blockTo = node.to;
3577
3579
  } else {
3578
- blockFrom = Math.min(blockFrom, state.doc.lineAt(node.from).from);
3579
- blockTo = Math.max(blockTo, state.doc.lineAt(node.to).to);
3580
+ blockFrom = Math.min(blockFrom, state2.doc.lineAt(node.from).from);
3581
+ blockTo = Math.max(blockTo, state2.doc.lineAt(node.to).to);
3580
3582
  }
3581
3583
  }
3582
3584
  }
@@ -3594,7 +3596,7 @@ var addCodeblock = (target) => {
3594
3596
  return false;
3595
3597
  }
3596
3598
  const changes = ranges.map(({ from, to }) => {
3597
- const column = from - state.doc.lineAt(from).from;
3599
+ const column = from - state2.doc.lineAt(from).from;
3598
3600
  return [
3599
3601
  {
3600
3602
  from,
@@ -3606,30 +3608,30 @@ var addCodeblock = (target) => {
3606
3608
  }
3607
3609
  ];
3608
3610
  });
3609
- dispatch(state.update({
3611
+ dispatch(state2.update({
3610
3612
  changes,
3611
3613
  userEvent: "format.codeblock.add",
3612
3614
  scrollIntoView: true
3613
3615
  }));
3614
3616
  return true;
3615
3617
  };
3616
- var removeCodeblock = ({ state, dispatch }) => {
3618
+ var removeCodeblock = ({ state: state2, dispatch }) => {
3617
3619
  const changes = [];
3618
3620
  let lastBlock = -1;
3619
- for (const { from, to } of state.selection.ranges) {
3620
- syntaxTree2(state).iterate({
3621
+ for (const { from, to } of state2.selection.ranges) {
3622
+ syntaxTree2(state2).iterate({
3621
3623
  from,
3622
3624
  to,
3623
3625
  enter: (node) => {
3624
3626
  if (Textblocks[node.name] === "codeblock" && lastBlock !== node.from) {
3625
3627
  lastBlock = node.from;
3626
- const firstLine = state.doc.lineAt(node.from);
3628
+ const firstLine = state2.doc.lineAt(node.from);
3627
3629
  if (node.name === "FencedCode") {
3628
3630
  changes.push({
3629
3631
  from: node.from,
3630
3632
  to: firstLine.to + 1 + node.from - firstLine.from
3631
3633
  });
3632
- const lastLine = state.doc.lineAt(node.to);
3634
+ const lastLine = state2.doc.lineAt(node.to);
3633
3635
  if (/^([\s>]|[-*+] |\d+[).])*`+$/.test(lastLine.text)) {
3634
3636
  changes.push({
3635
3637
  from: lastLine.from - (lastLine.number === firstLine.number + 1 ? 0 : 1),
@@ -3638,7 +3640,7 @@ var removeCodeblock = ({ state, dispatch }) => {
3638
3640
  }
3639
3641
  } else {
3640
3642
  const column = node.from - firstLine.from;
3641
- for (let line = firstLine; ; line = state.doc.line(line.number + 1)) {
3643
+ for (let line = firstLine; ; line = state2.doc.line(line.number + 1)) {
3642
3644
  changes.push({
3643
3645
  from: line.from + column - 4,
3644
3646
  to: line.from + column
@@ -3655,7 +3657,7 @@ var removeCodeblock = ({ state, dispatch }) => {
3655
3657
  if (!changes.length) {
3656
3658
  return false;
3657
3659
  }
3658
- dispatch(state.update({
3660
+ dispatch(state2.update({
3659
3661
  changes,
3660
3662
  userEvent: "format.codeblock.remove",
3661
3663
  scrollIntoView: true
@@ -3714,7 +3716,7 @@ var Textblocks = {
3714
3716
  TableCell: "tablecell",
3715
3717
  Task: "paragraph"
3716
3718
  };
3717
- var getFormatting = (state) => {
3719
+ var getFormatting = (state2) => {
3718
3720
  let blockType = null;
3719
3721
  const inline = [
3720
3722
  null,
@@ -3740,7 +3742,7 @@ var getFormatting = (state) => {
3740
3742
  continue;
3741
3743
  } else if (currentBlock.active[i]) {
3742
3744
  inline[i] = true;
3743
- } else if (/\S/.test(state.doc.sliceString(currentBlock.pos, upto))) {
3745
+ } else if (/\S/.test(state2.doc.sliceString(currentBlock.pos, upto))) {
3744
3746
  inline[i] = false;
3745
3747
  }
3746
3748
  }
@@ -3751,12 +3753,12 @@ var getFormatting = (state) => {
3751
3753
  currentBlock.pos = Math.min(upto, currentBlock.end);
3752
3754
  }
3753
3755
  };
3754
- const { selection } = state;
3756
+ const { selection } = state2;
3755
3757
  for (const range of selection.ranges) {
3756
3758
  if (range.empty && inline.some((v) => v === null)) {
3757
3759
  const contextSize = Math.min(range.head, 6);
3758
- const contextBefore = state.doc.sliceString(range.head - contextSize, range.head);
3759
- let contextAfter = state.doc.sliceString(range.head, range.head + contextSize);
3760
+ const contextBefore = state2.doc.sliceString(range.head - contextSize, range.head);
3761
+ let contextAfter = state2.doc.sliceString(range.head, range.head + contextSize);
3760
3762
  for (let i = 0; i < contextSize; i++) {
3761
3763
  const ch = contextAfter[i];
3762
3764
  if (ch !== contextBefore[contextBefore.length - 1 - i] || !/[~`*]/.test(ch)) {
@@ -3775,7 +3777,7 @@ var getFormatting = (state) => {
3775
3777
  }
3776
3778
  }
3777
3779
  }
3778
- syntaxTree2(state).iterate({
3780
+ syntaxTree2(state2).iterate({
3779
3781
  from: range.from,
3780
3782
  to: range.to,
3781
3783
  enter: (node) => {
@@ -3849,7 +3851,7 @@ var getFormatting = (state) => {
3849
3851
  }
3850
3852
  });
3851
3853
  }
3852
- const { from, to } = state.doc.lineAt(selection.main.anchor);
3854
+ const { from, to } = state2.doc.lineAt(selection.main.anchor);
3853
3855
  const blankLine = from === to;
3854
3856
  return {
3855
3857
  blankLine,
@@ -3864,17 +3866,17 @@ var getFormatting = (state) => {
3864
3866
  };
3865
3867
  };
3866
3868
  var useFormattingState = () => {
3867
- const [state, setState] = useState(null);
3869
+ const [state2, setState, stateRef] = useStateRef();
3868
3870
  const observer = useMemo(() => EditorView13.updateListener.of((update2) => {
3869
3871
  if (update2.docChanged || update2.selectionSet) {
3870
3872
  const newState = getFormatting(update2.state);
3871
- if (!state || !compareFormatting(state, newState)) {
3873
+ if (!stateRef.current || !formattingEquals(stateRef.current, newState)) {
3872
3874
  setState(newState);
3873
3875
  }
3874
3876
  }
3875
3877
  }), []);
3876
3878
  return [
3877
- state,
3879
+ state2,
3878
3880
  observer
3879
3881
  ];
3880
3882
  };
@@ -3885,8 +3887,8 @@ import { StateField as StateField5 } from "@codemirror/state";
3885
3887
  import { Decoration as Decoration6, EditorView as EditorView14, WidgetType as WidgetType4 } from "@codemirror/view";
3886
3888
  var image = (options = {}) => {
3887
3889
  return StateField5.define({
3888
- create: (state) => {
3889
- return Decoration6.set(buildDecorations2(0, state.doc.length, state));
3890
+ create: (state2) => {
3891
+ return Decoration6.set(buildDecorations2(0, state2.doc.length, state2));
3890
3892
  },
3891
3893
  update: (value, tr) => {
3892
3894
  if (!tr.docChanged && !tr.selection) {
@@ -3920,16 +3922,16 @@ var preloadImage = (url) => {
3920
3922
  preloaded.add(url);
3921
3923
  }
3922
3924
  };
3923
- var buildDecorations2 = (from, to, state) => {
3925
+ var buildDecorations2 = (from, to, state2) => {
3924
3926
  const decorations = [];
3925
- const cursor = state.selection.main.head;
3926
- syntaxTree3(state).iterate({
3927
+ const cursor = state2.selection.main.head;
3928
+ syntaxTree3(state2).iterate({
3927
3929
  enter: (node) => {
3928
3930
  if (node.name === "Image") {
3929
3931
  const urlNode = node.node.getChild("URL");
3930
3932
  if (urlNode) {
3931
- const hide2 = state.readOnly || cursor < node.from || cursor > node.to;
3932
- const url = state.sliceDoc(urlNode.from, urlNode.to);
3933
+ const hide2 = state2.readOnly || cursor < node.from || cursor > node.to;
3934
+ const url = state2.sliceDoc(urlNode.from, urlNode.to);
3933
3935
  preloadImage(url);
3934
3936
  decorations.push(Decoration6.replace({
3935
3937
  block: true,
@@ -3959,6 +3961,8 @@ var ImageWidget = class extends WidgetType4 {
3959
3961
  return img;
3960
3962
  }
3961
3963
  };
3964
+ var imageUpload = (options = {}) => {
3965
+ };
3962
3966
 
3963
3967
  // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
3964
3968
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
@@ -4000,21 +4004,21 @@ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField6 } from "
4000
4004
  import { Decoration as Decoration7, EditorView as EditorView15, WidgetType as WidgetType5 } from "@codemirror/view";
4001
4005
  var table = (options = {}) => {
4002
4006
  return StateField6.define({
4003
- create: (state) => update(state, options),
4007
+ create: (state2) => update(state2, options),
4004
4008
  update: (_, tr) => update(tr.state, options),
4005
4009
  provide: (field) => EditorView15.decorations.from(field)
4006
4010
  });
4007
4011
  };
4008
- var update = (state, options) => {
4012
+ var update = (state2, options) => {
4009
4013
  const builder = new RangeSetBuilder3();
4010
- const cursor = state.selection.main.head;
4014
+ const cursor = state2.selection.main.head;
4011
4015
  const tables = [];
4012
4016
  const getTable = () => tables[tables.length - 1];
4013
4017
  const getRow = () => {
4014
4018
  const table2 = getTable();
4015
4019
  return table2.rows?.[table2.rows.length - 1];
4016
4020
  };
4017
- syntaxTree5(state).iterate({
4021
+ syntaxTree5(state2).iterate({
4018
4022
  enter: (node) => {
4019
4023
  switch (node.name) {
4020
4024
  case "Table": {
@@ -4035,9 +4039,9 @@ var update = (state, options) => {
4035
4039
  case "TableCell": {
4036
4040
  const row = getRow();
4037
4041
  if (row) {
4038
- row.push(state.sliceDoc(node.from, node.to));
4042
+ row.push(state2.sliceDoc(node.from, node.to));
4039
4043
  } else {
4040
- getTable().header?.push(state.sliceDoc(node.from, node.to));
4044
+ getTable().header?.push(state2.sliceDoc(node.from, node.to));
4041
4045
  }
4042
4046
  break;
4043
4047
  }
@@ -4045,7 +4049,7 @@ var update = (state, options) => {
4045
4049
  }
4046
4050
  });
4047
4051
  tables.forEach((table2) => {
4048
- const hide2 = state.readOnly || cursor < table2.from || cursor > table2.to;
4052
+ const hide2 = state2.readOnly || cursor < table2.from || cursor > table2.to;
4049
4053
  if (hide2) {
4050
4054
  builder.add(table2.from, table2.to, Decoration7.replace({
4051
4055
  widget: new TableWidget(table2)
@@ -4136,6 +4140,7 @@ var mention = ({ onSearch }) => {
4136
4140
  import { Facet as Facet6 } from "@codemirror/state";
4137
4141
  import { keymap as keymap7 } from "@codemirror/view";
4138
4142
  import { vim } from "@replit/codemirror-vim";
4143
+ var focusEvent = "focus.container";
4139
4144
  var editorMode = Facet6.define({
4140
4145
  combine: (modes) => modes[0] ?? {}
4141
4146
  });
@@ -4152,7 +4157,7 @@ var EditorModes = {
4152
4157
  key: "Alt-Escape",
4153
4158
  run: (view) => {
4154
4159
  view.dispatch({
4155
- userEvent: "focus.container"
4160
+ userEvent: focusEvent
4156
4161
  });
4157
4162
  return true;
4158
4163
  }
@@ -4161,8 +4166,95 @@ var EditorModes = {
4161
4166
  ]
4162
4167
  };
4163
4168
 
4169
+ // packages/ui/react-ui-editor/src/extensions/state.ts
4170
+ import { Transaction as Transaction2 } from "@codemirror/state";
4171
+ import { EditorView as EditorView16, keymap as keymap8 } from "@codemirror/view";
4172
+ import { debounce as debounce2 } from "@dxos/async";
4173
+ import { invariant as invariant4 } from "@dxos/invariant";
4174
+ import { isNotFalsy as isNotFalsy3 } from "@dxos/util";
4175
+ var __dxlog_file11 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/state.ts";
4176
+ var scrollAnnotation = "dxos.org/cm/scrolling";
4177
+ var keyPrefix = "dxos.org/react-ui-editor/state";
4178
+ var localStorageStateStoreAdapter = {
4179
+ setState: (id, state2) => {
4180
+ invariant4(id, void 0, {
4181
+ F: __dxlog_file11,
4182
+ L: 35,
4183
+ S: void 0,
4184
+ A: [
4185
+ "id",
4186
+ ""
4187
+ ]
4188
+ });
4189
+ localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state2));
4190
+ },
4191
+ getState: (id) => {
4192
+ invariant4(id, void 0, {
4193
+ F: __dxlog_file11,
4194
+ L: 39,
4195
+ S: void 0,
4196
+ A: [
4197
+ "id",
4198
+ ""
4199
+ ]
4200
+ });
4201
+ const state2 = localStorage.getItem(`${keyPrefix}/${id}`);
4202
+ return state2 ? JSON.parse(state2) : void 0;
4203
+ }
4204
+ };
4205
+ var state = ({ getState, setState } = {}) => {
4206
+ const setStateDebounced = debounce2(setState, 1e3);
4207
+ return [
4208
+ // TODO(burdon): Track scrolling (currently only updates when cursor moves).
4209
+ EditorView16.updateListener.of(({ view, changes, transactions }) => {
4210
+ const id = view.state.facet(documentId2);
4211
+ if (!id || transactions.some((tr) => tr.isUserEvent(scrollAnnotation))) {
4212
+ return;
4213
+ }
4214
+ if (setState) {
4215
+ const { top } = view.dom.getBoundingClientRect();
4216
+ const pos = view.posAtCoords({
4217
+ x: 0,
4218
+ y: top
4219
+ });
4220
+ if (pos !== null) {
4221
+ const { anchor, head } = view.state.selection.main;
4222
+ setStateDebounced(id, {
4223
+ scrollTo: {
4224
+ from: pos,
4225
+ yMargin: 0
4226
+ },
4227
+ selection: {
4228
+ anchor,
4229
+ head
4230
+ }
4231
+ });
4232
+ }
4233
+ }
4234
+ }),
4235
+ getState && keymap8.of([
4236
+ {
4237
+ key: "ctrl-r",
4238
+ run: (view) => {
4239
+ const state2 = getState(view.state.facet(documentId2));
4240
+ if (state2) {
4241
+ view.dispatch({
4242
+ effects: EditorView16.scrollIntoView(state2.scrollTo.from, {
4243
+ yMargin: 0
4244
+ }),
4245
+ selection: state2.selection,
4246
+ annotations: Transaction2.userEvent.of(scrollAnnotation)
4247
+ });
4248
+ }
4249
+ return true;
4250
+ }
4251
+ }
4252
+ ])
4253
+ ].filter(isNotFalsy3);
4254
+ };
4255
+
4164
4256
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
4165
- import { keymap as keymap8 } from "@codemirror/view";
4257
+ import { keymap as keymap9 } from "@codemirror/view";
4166
4258
  var defaultItems = [
4167
4259
  "hello world!",
4168
4260
  "this is a test.",
@@ -4172,7 +4264,7 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
4172
4264
  let t;
4173
4265
  let idx = 0;
4174
4266
  return [
4175
- keymap8.of([
4267
+ keymap9.of([
4176
4268
  {
4177
4269
  // Reset.
4178
4270
  key: "alt-meta-'",
@@ -4219,15 +4311,17 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
4219
4311
  };
4220
4312
 
4221
4313
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
4222
- var __dxlog_file11 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
4314
+ var __dxlog_file12 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
4223
4315
  var instanceCount = 0;
4224
- var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, className, autoFocus, scrollTo = EditorView16.scrollIntoView(0), moveToEndOfLine, debug, dataTestId }, forwardedRef) => {
4225
- const [instanceId] = useState2(() => `text-editor-${++instanceCount}`);
4316
+ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, className, autoFocus, scrollTo = EditorView17.scrollIntoView(0, {
4317
+ yMargin: 0
4318
+ }), moveToEndOfLine, debug, dataTestId }, forwardedRef) => {
4319
+ const [instanceId] = useState(() => `text-editor-${++instanceCount}`);
4226
4320
  const tabsterDOMAttribute = useFocusableGroup({
4227
4321
  tabBehavior: "limited"
4228
4322
  });
4229
4323
  const rootRef = useRef(null);
4230
- const [view, setView] = useState2(null);
4324
+ const [view, setView] = useState(null);
4231
4325
  useImperativeHandle(forwardedRef, () => view, [
4232
4326
  view
4233
4327
  ]);
@@ -4240,42 +4334,42 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4240
4334
  autoFocus
4241
4335
  ]);
4242
4336
  useEffect2(() => {
4243
- log9("updating", {
4337
+ log9("create", {
4244
4338
  id,
4245
4339
  instanceId
4246
4340
  }, {
4247
- F: __dxlog_file11,
4341
+ F: __dxlog_file12,
4248
4342
  L: 88,
4249
4343
  S: void 0,
4250
4344
  C: (f, a) => f(...a)
4251
4345
  });
4252
- const state = EditorState2.create({
4346
+ const state2 = EditorState2.create({
4253
4347
  doc,
4254
4348
  selection,
4255
4349
  extensions: [
4256
4350
  id && documentId2.of(id),
4257
4351
  // TODO(burdon): NOTE: Doesn't catch errors in keymap functions.
4258
- EditorView16.exceptionSink.of((err) => {
4352
+ EditorView17.exceptionSink.of((err) => {
4259
4353
  log9.catch(err, void 0, {
4260
- F: __dxlog_file11,
4354
+ F: __dxlog_file12,
4261
4355
  L: 101,
4262
4356
  S: void 0,
4263
4357
  C: (f, a) => f(...a)
4264
4358
  });
4265
4359
  }),
4266
4360
  // Focus.
4267
- EditorView16.updateListener.of((update2) => {
4361
+ EditorView17.updateListener.of((update2) => {
4268
4362
  update2.transactions.forEach((transaction) => {
4269
- if (transaction.isUserEvent("focus.container")) {
4363
+ if (transaction.isUserEvent(focusEvent)) {
4270
4364
  rootRef.current?.focus();
4271
4365
  }
4272
4366
  });
4273
4367
  }),
4274
4368
  extensions
4275
- ].filter(isNotFalsy3)
4369
+ ].filter(isNotFalsy4)
4276
4370
  });
4277
- const view2 = new EditorView16({
4278
- state,
4371
+ const view2 = new EditorView17({
4372
+ state: state2,
4279
4373
  parent: rootRef.current,
4280
4374
  scrollTo,
4281
4375
  // NOTE: Uncomment to debug/monitor all transactions.
@@ -4287,21 +4381,29 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4287
4381
  view3.update(trs);
4288
4382
  }
4289
4383
  });
4290
- setView(view2);
4291
- if (moveToEndOfLine) {
4384
+ if (moveToEndOfLine && !(scrollTo || selection)) {
4292
4385
  const { to } = view2.state.doc.lineAt(0);
4293
- view2?.dispatch({
4386
+ view2.dispatch({
4294
4387
  selection: {
4295
4388
  anchor: to
4296
4389
  }
4297
4390
  });
4298
4391
  }
4299
- if (state.facet(editorMode).noTabster) {
4392
+ if (state2.facet(editorMode).noTabster) {
4300
4393
  rootRef.current?.removeAttribute("data-tabster");
4301
4394
  }
4395
+ setView(view2);
4302
4396
  return () => {
4397
+ log9("destroy", {
4398
+ id,
4399
+ instanceId
4400
+ }, {
4401
+ F: __dxlog_file12,
4402
+ L: 150,
4403
+ S: void 0,
4404
+ C: (f, a) => f(...a)
4405
+ });
4303
4406
  view2?.destroy();
4304
- setView(null);
4305
4407
  };
4306
4408
  }, [
4307
4409
  doc,
@@ -4331,9 +4433,10 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4331
4433
  });
4332
4434
 
4333
4435
  // packages/ui/react-ui-editor/src/components/Toolbar/Toolbar.tsx
4334
- import { ChatText, Code, CodeBlock, Link, ListBullets, ListChecks, ListNumbers, Paragraph, TextStrikethrough, Table as Table2, TextB, TextHOne, TextHTwo, TextHThree, TextHFour, TextHFive, TextHSix, TextItalic, Quotes } from "@phosphor-icons/react";
4436
+ import { ChatText, Code, CodeBlock, Image, Link, ListBullets, ListChecks, ListNumbers, Paragraph, Quotes, TextStrikethrough, Table as Table2, TextB, TextHOne, TextHTwo, TextHThree, TextHFour, TextHFive, TextHSix, TextItalic } from "@phosphor-icons/react";
4335
4437
  import { createContext } from "@radix-ui/react-context";
4336
- import React2, { useRef as useRef2, useState as useState3 } from "react";
4438
+ import React2, { useEffect as useEffect3, useRef as useRef2, useState as useState2 } from "react";
4439
+ import { useDropzone } from "react-dropzone";
4337
4440
  import { DensityProvider, ElevationProvider, Select, Toolbar as NaturalToolbar, Tooltip, useTranslation } from "@dxos/react-ui";
4338
4441
  import { getSize } from "@dxos/react-ui-theme";
4339
4442
  var tooltipProps = {
@@ -4350,10 +4453,10 @@ var HeadingIcons = {
4350
4453
  "6": TextHSix
4351
4454
  };
4352
4455
  var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
4353
- var ToolbarRoot = ({ children, onAction, classNames, state }) => {
4456
+ var ToolbarRoot = ({ children, onAction, classNames, state: state2 }) => {
4354
4457
  return /* @__PURE__ */ React2.createElement(ToolbarContextProvider, {
4355
4458
  onAction,
4356
- state
4459
+ state: state2
4357
4460
  }, /* @__PURE__ */ React2.createElement(DensityProvider, {
4358
4461
  density: "fine"
4359
4462
  }, /* @__PURE__ */ React2.createElement(ElevationProvider, {
@@ -4367,7 +4470,7 @@ var ToolbarRoot = ({ children, onAction, classNames, state }) => {
4367
4470
  };
4368
4471
  var buttonStyles = "min-bs-0 p-2";
4369
4472
  var iconStyles = getSize(5);
4370
- var ToolbarButton = ({ Icon, children, ...props }) => {
4473
+ var ToolbarToggleButton = ({ Icon, children, ...props }) => {
4371
4474
  return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4372
4475
  asChild: true
4373
4476
  }, /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroupItem, {
@@ -4380,20 +4483,33 @@ var ToolbarButton = ({ Icon, children, ...props }) => {
4380
4483
  className: "sr-only"
4381
4484
  }, children))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, tooltipProps, children, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
4382
4485
  };
4486
+ var ToolbarButton = ({ Icon, children, ...props }) => {
4487
+ return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4488
+ asChild: true
4489
+ }, /* @__PURE__ */ React2.createElement(NaturalToolbar.Button, {
4490
+ variant: "ghost",
4491
+ ...props,
4492
+ classNames: buttonStyles
4493
+ }, /* @__PURE__ */ React2.createElement(Icon, {
4494
+ className: iconStyles
4495
+ }), /* @__PURE__ */ React2.createElement("span", {
4496
+ className: "sr-only"
4497
+ }, children))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, tooltipProps, children, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
4498
+ };
4383
4499
  var ToolbarSeparator = () => /* @__PURE__ */ React2.createElement("div", {
4384
4500
  role: "separator",
4385
4501
  className: "grow"
4386
4502
  });
4387
4503
  var MarkdownHeading = () => {
4388
4504
  const { t } = useTranslation(translationKey);
4389
- const { onAction, state } = useToolbarContext("MarkdownFormatting");
4390
- const blockType = state ? state.blockType : "paragraph";
4505
+ const { onAction, state: state2 } = useToolbarContext("MarkdownFormatting");
4506
+ const blockType = state2 ? state2.blockType : "paragraph";
4391
4507
  const header = blockType && /heading(\d)/.exec(blockType);
4392
4508
  const value = header ? header[1] : blockType === "paragraph" || !blockType ? "0" : void 0;
4393
4509
  const HeadingIcon = HeadingIcons[value ?? "0"];
4394
4510
  const suppressNextTooltip = useRef2(false);
4395
- const [tooltipOpen, setTooltipOpen] = useState3(false);
4396
- const [selectOpen, setSelectOpen] = useState3(false);
4511
+ const [tooltipOpen, setTooltipOpen] = useState2(false);
4512
+ const [selectOpen, setSelectOpen] = useState2(false);
4397
4513
  return /* @__PURE__ */ React2.createElement(Tooltip.Root, {
4398
4514
  open: tooltipOpen,
4399
4515
  onOpenChange: (nextOpen) => {
@@ -4461,43 +4577,43 @@ var markdownStyles = [
4461
4577
  {
4462
4578
  type: "strong",
4463
4579
  Icon: TextB,
4464
- getState: (state) => state.strong
4580
+ getState: (state2) => state2.strong
4465
4581
  },
4466
4582
  {
4467
4583
  type: "emphasis",
4468
4584
  Icon: TextItalic,
4469
- getState: (state) => state.emphasis
4585
+ getState: (state2) => state2.emphasis
4470
4586
  },
4471
4587
  {
4472
4588
  type: "strikethrough",
4473
4589
  Icon: TextStrikethrough,
4474
- getState: (state) => state.strikethrough
4590
+ getState: (state2) => state2.strikethrough
4475
4591
  },
4476
4592
  {
4477
4593
  type: "code",
4478
4594
  Icon: Code,
4479
- getState: (state) => state.code
4595
+ getState: (state2) => state2.code
4480
4596
  },
4481
4597
  {
4482
4598
  type: "link",
4483
4599
  Icon: Link,
4484
- getState: (state) => state.link
4600
+ getState: (state2) => state2.link
4485
4601
  }
4486
4602
  ];
4487
4603
  var MarkdownStyles = () => {
4488
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4604
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4489
4605
  const { t } = useTranslation(translationKey);
4490
4606
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4491
4607
  type: "multiple",
4492
- value: markdownStyles.filter(({ getState }) => state && getState(state)).map(({ type }) => type)
4493
- }, markdownStyles.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4608
+ value: markdownStyles.filter(({ getState }) => state2 && getState(state2)).map(({ type }) => type)
4609
+ }, markdownStyles.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4494
4610
  key: type,
4495
4611
  value: type,
4496
4612
  Icon,
4497
- disabled: state?.blockType === "codeblock",
4498
- onClick: state ? () => onAction?.({
4613
+ disabled: state2?.blockType === "codeblock",
4614
+ onClick: state2 ? () => onAction?.({
4499
4615
  type,
4500
- data: !getState(state)
4616
+ data: !getState(state2)
4501
4617
  }) : void 0
4502
4618
  }, t(`${type} label`))));
4503
4619
  };
@@ -4505,32 +4621,32 @@ var markdownLists = [
4505
4621
  {
4506
4622
  type: "list-bullet",
4507
4623
  Icon: ListBullets,
4508
- getState: (state) => state.listStyle === "bullet"
4624
+ getState: (state2) => state2.listStyle === "bullet"
4509
4625
  },
4510
4626
  {
4511
4627
  type: "list-ordered",
4512
4628
  Icon: ListNumbers,
4513
- getState: (state) => state.listStyle === "ordered"
4629
+ getState: (state2) => state2.listStyle === "ordered"
4514
4630
  },
4515
4631
  {
4516
4632
  type: "list-task",
4517
4633
  Icon: ListChecks,
4518
- getState: (state) => state.listStyle === "task"
4634
+ getState: (state2) => state2.listStyle === "task"
4519
4635
  }
4520
4636
  ];
4521
4637
  var MarkdownLists = () => {
4522
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4638
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4523
4639
  const { t } = useTranslation(translationKey);
4524
4640
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4525
4641
  type: "single",
4526
- value: state?.listStyle ? `list-${state.listStyle}` : ""
4527
- }, markdownLists.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4642
+ value: state2?.listStyle ? `list-${state2.listStyle}` : ""
4643
+ }, markdownLists.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4528
4644
  key: type,
4529
4645
  value: type,
4530
4646
  Icon,
4531
- onClick: state ? () => onAction?.({
4647
+ onClick: state2 ? () => onAction?.({
4532
4648
  type,
4533
- data: !getState(state)
4649
+ data: !getState(state2)
4534
4650
  }) : void 0
4535
4651
  }, t(`${type} label`))));
4536
4652
  };
@@ -4538,63 +4654,101 @@ var markdownBlocks = [
4538
4654
  {
4539
4655
  type: "blockquote",
4540
4656
  Icon: Quotes,
4541
- getState: (state) => state.blockQuote
4657
+ getState: (state2) => state2.blockQuote
4542
4658
  },
4543
4659
  {
4544
4660
  type: "codeblock",
4545
4661
  Icon: CodeBlock,
4546
- getState: (state) => state.blockType === "codeblock"
4662
+ getState: (state2) => state2.blockType === "codeblock"
4547
4663
  },
4548
4664
  {
4549
4665
  type: "table",
4550
4666
  Icon: Table2,
4551
- getState: (state) => state.blockType === "tablecell",
4552
- disabled: (state) => !state.blankLine
4667
+ getState: (state2) => state2.blockType === "tablecell",
4668
+ disabled: (state2) => !state2.blankLine
4553
4669
  }
4554
4670
  ];
4555
4671
  var MarkdownBlocks = () => {
4556
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4672
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4557
4673
  const { t } = useTranslation(translationKey);
4558
- const value = markdownBlocks.find(({ getState }) => state && getState(state));
4674
+ const value = markdownBlocks.find(({ getState }) => state2 && getState(state2));
4559
4675
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4560
4676
  type: "single",
4561
4677
  value: value?.type ?? ""
4562
- }, markdownBlocks.map(({ type, disabled, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4678
+ }, markdownBlocks.map(({ type, disabled, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4563
4679
  key: type,
4564
4680
  value: type,
4565
4681
  Icon,
4566
- disabled: !state || disabled?.(state),
4567
- onClick: state ? () => onAction?.({
4682
+ disabled: !state2 || disabled?.(state2),
4683
+ onClick: state2 ? () => onAction?.({
4568
4684
  type,
4569
- data: !getState(state)
4685
+ data: !getState(state2)
4570
4686
  }) : void 0
4571
4687
  }, t(`${type} label`))));
4572
4688
  };
4573
4689
  var MarkdownStandard = () => /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(MarkdownHeading, null), /* @__PURE__ */ React2.createElement(MarkdownStyles, null), /* @__PURE__ */ React2.createElement(MarkdownLists, null), /* @__PURE__ */ React2.createElement(MarkdownBlocks, null));
4574
- var MarkdownExtended = () => {
4690
+ var MarkdownCustom = ({ onUpload } = {}) => {
4575
4691
  const { onAction } = useToolbarContext("MarkdownStyles");
4576
4692
  const { t } = useTranslation(translationKey);
4577
- return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4578
- asChild: true
4579
- }, /* @__PURE__ */ React2.createElement(NaturalToolbar.Button, {
4580
- variant: "ghost",
4693
+ const { acceptedFiles, getInputProps, open } = useDropzone({
4694
+ multiple: false,
4695
+ noDrag: true,
4696
+ accept: {
4697
+ "image/*": [
4698
+ ".jpg",
4699
+ ".jpeg",
4700
+ ".png",
4701
+ ".gif"
4702
+ ]
4703
+ }
4704
+ });
4705
+ useEffect3(() => {
4706
+ if (onUpload && acceptedFiles.length) {
4707
+ setTimeout(async () => {
4708
+ const f = acceptedFiles[0];
4709
+ const file = new File([
4710
+ f
4711
+ ], f.name, {
4712
+ type: f.type,
4713
+ lastModified: f.lastModified
4714
+ });
4715
+ const { url } = await onUpload(file);
4716
+ if (url) {
4717
+ onAction?.({
4718
+ type: "image",
4719
+ data: url
4720
+ });
4721
+ }
4722
+ });
4723
+ }
4724
+ }, [
4725
+ acceptedFiles
4726
+ ]);
4727
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("input", getInputProps()), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4728
+ value: "image",
4729
+ Icon: Image,
4730
+ onClick: () => open()
4731
+ }, t("image label")));
4732
+ };
4733
+ var MarkdownActions = () => {
4734
+ const { onAction } = useToolbarContext("MarkdownStyles");
4735
+ const { t } = useTranslation(translationKey);
4736
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(ToolbarButton, {
4737
+ value: "comment",
4738
+ Icon: ChatText,
4581
4739
  "data-testid": "editor.toolbar.comment",
4582
4740
  onClick: () => onAction?.({
4583
4741
  type: "comment"
4584
- }),
4585
- classNames: buttonStyles
4586
- }, /* @__PURE__ */ React2.createElement(ChatText, {
4587
- className: iconStyles
4588
- }), /* @__PURE__ */ React2.createElement("span", {
4589
- className: "sr-only"
4590
- }, t("comment label")))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, tooltipProps, t("comment label"), /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
4742
+ })
4743
+ }, t("comment label")));
4591
4744
  };
4592
4745
  var Toolbar = {
4593
4746
  Root: ToolbarRoot,
4594
- Button: ToolbarButton,
4747
+ Button: ToolbarToggleButton,
4595
4748
  Separator: ToolbarSeparator,
4596
4749
  Markdown: MarkdownStandard,
4597
- Extended: MarkdownExtended
4750
+ Custom: MarkdownCustom,
4751
+ Actions: MarkdownActions
4598
4752
  };
4599
4753
 
4600
4754
  // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
@@ -4631,7 +4785,13 @@ var useActionHandler = (view) => {
4631
4785
  insertTable(view);
4632
4786
  break;
4633
4787
  case "link":
4634
- (action.data === false ? removeLink : addLink)(view);
4788
+ (action.data === false ? removeLink : addLink())(view);
4789
+ break;
4790
+ case "image":
4791
+ addLink({
4792
+ url: action.data,
4793
+ image: true
4794
+ })(view);
4635
4795
  break;
4636
4796
  case "comment":
4637
4797
  createComment(view);
@@ -4660,43 +4820,51 @@ var useDocAccessor = (text) => {
4660
4820
 
4661
4821
  // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
4662
4822
  import { EditorSelection as EditorSelection2, EditorState as EditorState3 } from "@codemirror/state";
4663
- import { EditorView as EditorView17 } from "@codemirror/view";
4664
- import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
4823
+ import { EditorView as EditorView18 } from "@codemirror/view";
4824
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
4665
4825
  import { log as log10 } from "@dxos/log";
4666
- import { isNotFalsy as isNotFalsy4 } from "@dxos/util";
4667
- var __dxlog_file12 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
4826
+ import { isNotFalsy as isNotFalsy5 } from "@dxos/util";
4827
+ var __dxlog_file13 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
4668
4828
  var useTextEditor = (cb = () => ({}), deps = []) => {
4669
4829
  let { id, doc, selection, extensions, autoFocus, scrollTo, debug } = useMemo3(cb, deps ?? []);
4670
4830
  const onUpdate = useRef3();
4671
- const [view, setView] = useState4();
4831
+ const [view, setView] = useState3();
4672
4832
  const parentRef = useRef3(null);
4673
- useEffect3(() => {
4833
+ useEffect4(() => {
4674
4834
  let view2;
4675
4835
  if (parentRef.current) {
4676
- const state = EditorState3.create({
4836
+ log10("create", {
4837
+ id
4838
+ }, {
4839
+ F: __dxlog_file13,
4840
+ L: 36,
4841
+ S: void 0,
4842
+ C: (f, a) => f(...a)
4843
+ });
4844
+ const state2 = EditorState3.create({
4677
4845
  doc,
4678
4846
  selection,
4679
4847
  extensions: [
4680
4848
  id && documentId2.of(id),
4681
4849
  // TODO(burdon): Doesn't catch errors in keymap functions.
4682
- EditorView17.exceptionSink.of((err) => {
4850
+ EditorView18.exceptionSink.of((err) => {
4683
4851
  log10.catch(err, void 0, {
4684
- F: __dxlog_file12,
4685
- L: 44,
4852
+ F: __dxlog_file13,
4853
+ L: 46,
4686
4854
  S: void 0,
4687
4855
  C: (f, a) => f(...a)
4688
4856
  });
4689
4857
  }),
4690
4858
  extensions,
4691
- EditorView17.updateListener.of(() => {
4859
+ EditorView18.updateListener.of(() => {
4692
4860
  onUpdate.current?.();
4693
4861
  })
4694
- ].filter(isNotFalsy4)
4862
+ ].filter(isNotFalsy5)
4695
4863
  });
4696
- view2 = new EditorView17({
4864
+ view2 = new EditorView18({
4697
4865
  parent: parentRef.current,
4698
4866
  scrollTo,
4699
- state,
4867
+ state: state2,
4700
4868
  // NOTE: Uncomment to debug/monitor all transactions.
4701
4869
  // https://codemirror.net/docs/ref/#view.EditorView.dispatch
4702
4870
  dispatchTransactions: (trs, view3) => {
@@ -4709,10 +4877,18 @@ var useTextEditor = (cb = () => ({}), deps = []) => {
4709
4877
  setView(view2);
4710
4878
  }
4711
4879
  return () => {
4880
+ log10("destroy", {
4881
+ id
4882
+ }, {
4883
+ F: __dxlog_file13,
4884
+ L: 74,
4885
+ S: void 0,
4886
+ C: (f, a) => f(...a)
4887
+ });
4712
4888
  view2?.destroy();
4713
4889
  };
4714
4890
  }, deps);
4715
- useEffect3(() => {
4891
+ useEffect4(() => {
4716
4892
  if (view) {
4717
4893
  if (!selection && !view.state.selection.main.anchor) {
4718
4894
  selection = EditorSelection2.single(view.state.doc.line(1).to);
@@ -4771,7 +4947,6 @@ export {
4771
4947
  callbackWrapper,
4772
4948
  command,
4773
4949
  comments,
4774
- compareFormatting,
4775
4950
  createBasicExtensions,
4776
4951
  createComment,
4777
4952
  createDataExtensions,
@@ -4788,14 +4963,18 @@ export {
4788
4963
  editorMode,
4789
4964
  editorWithToolbarLayout,
4790
4965
  focusComment,
4966
+ focusEvent,
4967
+ formattingEquals,
4791
4968
  formattingKeymap,
4792
4969
  getFormatting,
4793
4970
  getToken,
4794
4971
  image,
4972
+ imageUpload,
4795
4973
  insertTable,
4796
- keymap9 as keymap,
4974
+ keymap10 as keymap,
4797
4975
  linkTooltip,
4798
4976
  listener,
4977
+ localStorageStateStoreAdapter,
4799
4978
  logChanges,
4800
4979
  markdownHighlightStyle,
4801
4980
  markdownTags,
@@ -4811,6 +4990,7 @@ export {
4811
4990
  setHeading,
4812
4991
  setSelection,
4813
4992
  setStyle,
4993
+ state,
4814
4994
  table,
4815
4995
  tags2 as tags,
4816
4996
  toggleBlockquote,