@dxos/react-ui-editor 0.4.8-main.de30065 → 0.4.8-main.e23f3c5

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 (50) hide show
  1. package/dist/lib/browser/index.mjs +456 -272
  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/useDocAccessor.d.ts +4 -2
  27. package/dist/types/src/hooks/useDocAccessor.d.ts.map +1 -1
  28. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  29. package/dist/types/src/hooks/useTextEditor.stories.d.ts +1 -0
  30. package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
  31. package/dist/types/src/translations.d.ts +1 -0
  32. package/dist/types/src/translations.d.ts.map +1 -1
  33. package/package.json +25 -24
  34. package/src/components/TextEditor/TextEditor.stories.tsx +16 -4
  35. package/src/components/TextEditor/TextEditor.tsx +13 -12
  36. package/src/components/Toolbar/Toolbar.stories.tsx +23 -24
  37. package/src/components/Toolbar/Toolbar.tsx +101 -35
  38. package/src/extensions/automerge/automerge.ts +4 -4
  39. package/src/extensions/comments.ts +2 -2
  40. package/src/extensions/index.ts +1 -0
  41. package/src/extensions/markdown/formatting.test.ts +13 -13
  42. package/src/extensions/markdown/formatting.ts +95 -87
  43. package/src/extensions/markdown/image.ts +6 -0
  44. package/src/extensions/modes.ts +3 -1
  45. package/src/extensions/state.ts +90 -0
  46. package/src/hooks/useActionHandler.ts +5 -1
  47. package/src/hooks/useDocAccessor.ts +8 -2
  48. package/src/hooks/useTextEditor.stories.tsx +3 -3
  49. package/src/hooks/useTextEditor.ts +3 -0
  50. 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
36
  import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState as useState2 } 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,8 @@ 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, useState } from "react";
2801
+ 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
2802
  var Inline;
2802
2803
  (function(Inline2) {
2803
2804
  Inline2[Inline2["Strong"] = 0] = "Strong";
@@ -2811,13 +2812,13 @@ var List;
2811
2812
  List2[List2["Bullet"] = 1] = "Bullet";
2812
2813
  List2[List2["Task"] = 2] = "Task";
2813
2814
  })(List || (List = {}));
2814
- var setHeading = (level) => ({ state, dispatch }) => {
2815
- const { selection: { ranges }, doc } = state;
2815
+ var setHeading = (level) => ({ state: state2, dispatch }) => {
2816
+ const { selection: { ranges }, doc } = state2;
2816
2817
  const changes = [];
2817
2818
  let prevBlock = -1;
2818
2819
  for (const range of ranges) {
2819
2820
  let sawBlock = false;
2820
- syntaxTree2(state).iterate({
2821
+ syntaxTree2(state2).iterate({
2821
2822
  from: range.from,
2822
2823
  to: range.to,
2823
2824
  enter: (node) => {
@@ -2870,7 +2871,7 @@ var setHeading = (level) => ({ state, dispatch }) => {
2870
2871
  }
2871
2872
  });
2872
2873
  let line;
2873
- if (!sawBlock && range.empty && level > 0 && !/\S/.test((line = state.doc.lineAt(range.from)).text)) {
2874
+ if (!sawBlock && range.empty && level > 0 && !/\S/.test((line = state2.doc.lineAt(range.from)).text)) {
2874
2875
  changes.push({
2875
2876
  from: line.from,
2876
2877
  to: line.to,
@@ -2881,23 +2882,23 @@ var setHeading = (level) => ({ state, dispatch }) => {
2881
2882
  if (!changes.length) {
2882
2883
  return false;
2883
2884
  }
2884
- const changeSet = state.changes(changes);
2885
- dispatch(state.update({
2885
+ const changeSet = state2.changes(changes);
2886
+ dispatch(state2.update({
2886
2887
  changes: changeSet,
2887
- selection: state.selection.map(changeSet, 1),
2888
+ selection: state2.selection.map(changeSet, 1),
2888
2889
  userEvent: "format.setHeading",
2889
2890
  scrollIntoView: true
2890
2891
  }));
2891
2892
  return true;
2892
2893
  };
2893
- var setStyle = (type, enable) => ({ state, dispatch }) => {
2894
+ var setStyle = (type, enable) => ({ state: state2, dispatch }) => {
2894
2895
  const marker = inlineMarkerText(type);
2895
- const changes = state.changeByRange((range) => {
2896
+ const changes = state2.changeByRange((range) => {
2896
2897
  if (!enable && range.empty) {
2897
- const after = state.doc.sliceString(range.head, range.head + 6);
2898
+ const after = state2.doc.sliceString(range.head, range.head + 6);
2898
2899
  const found = after.indexOf(marker);
2899
2900
  if (found >= 0 && /^[*~`]*$/.test(after.slice(0, found))) {
2900
- const before = state.doc.sliceString(range.head - 6, range.head);
2901
+ const before = state2.doc.sliceString(range.head - 6, range.head);
2901
2902
  if (before.slice(before.length - found - marker.length, before.length - found) === marker && [
2902
2903
  ...before.slice(before.length - found)
2903
2904
  ].reverse().join("") === after.slice(0, found)) {
@@ -2924,14 +2925,14 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
2924
2925
  let startCovered = false;
2925
2926
  let endCovered = false;
2926
2927
  let { from, to } = range;
2927
- syntaxTree2(state).iterate({
2928
+ syntaxTree2(state2).iterate({
2928
2929
  from,
2929
2930
  to,
2930
2931
  enter: (node) => {
2931
2932
  const { name } = node;
2932
2933
  if (Object.hasOwn(Textblocks, name) && Textblocks[name] !== "codeblock") {
2933
2934
  blockStart = blockContentStart(node);
2934
- blockEnd = blockContentEnd(node, state.doc);
2935
+ blockEnd = blockContentEnd(node, state2.doc);
2935
2936
  startCovered = endCovered = false;
2936
2937
  } else if (name === "Link" || name === "Image" && enable) {
2937
2938
  if (from < node.from && to > node.from && to <= node.to) {
@@ -2976,7 +2977,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
2976
2977
  });
2977
2978
  if (markType !== type && closeStart >= to) {
2978
2979
  changesAtEnd.push({
2979
- from: skipSpaces(Math.min(to, blockEnd), state.doc, 1, blockEnd),
2980
+ from: skipSpaces(Math.min(to, blockEnd), state2.doc, 1, blockEnd),
2980
2981
  insert: inlineMarkerText(markType)
2981
2982
  });
2982
2983
  }
@@ -2988,7 +2989,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
2988
2989
  });
2989
2990
  if (markType !== type && openEnd <= from) {
2990
2991
  changes2.push({
2991
- from: skipSpaces(Math.max(from, blockStart), state.doc, -1, blockStart),
2992
+ from: skipSpaces(Math.max(from, blockStart), state2.doc, -1, blockStart),
2992
2993
  insert: inlineMarkerText(markType)
2993
2994
  });
2994
2995
  }
@@ -3018,7 +3019,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3018
3019
  changes2.push(startCovered);
3019
3020
  } else if (startCovered) {
3020
3021
  changes2.push({
3021
- from: skipSpaces(rangeStart, state.doc, -1, blockStart),
3022
+ from: skipSpaces(rangeStart, state2.doc, -1, blockStart),
3022
3023
  insert: marker
3023
3024
  });
3024
3025
  }
@@ -3026,7 +3027,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3026
3027
  changes2.push(endCovered);
3027
3028
  } else if (endCovered) {
3028
3029
  changes2.push({
3029
- from: skipSpaces(rangeEnd, state.doc, 1, blockEnd),
3030
+ from: skipSpaces(rangeEnd, state2.doc, 1, blockEnd),
3030
3031
  insert: marker
3031
3032
  });
3032
3033
  }
@@ -3034,7 +3035,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3034
3035
  }
3035
3036
  }
3036
3037
  });
3037
- if (blockStart < 0 && range.empty && enable && !/\S/.test(state.doc.lineAt(range.from).text)) {
3038
+ if (blockStart < 0 && range.empty && enable && !/\S/.test(state2.doc.lineAt(range.from).text)) {
3038
3039
  return {
3039
3040
  changes: {
3040
3041
  from: range.head,
@@ -3043,13 +3044,13 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3043
3044
  range: EditorSelection.cursor(range.head + marker.length)
3044
3045
  };
3045
3046
  }
3046
- const changeSet = state.changes(changes2.concat(changesAtEnd));
3047
+ const changeSet = state2.changes(changes2.concat(changesAtEnd));
3047
3048
  return {
3048
3049
  changes: changeSet,
3049
3050
  range: range.empty && !changeSet.empty ? EditorSelection.cursor(range.head + marker.length) : EditorSelection.range(changeSet.mapPos(range.from, 1), changeSet.mapPos(range.to, -1))
3050
3051
  };
3051
3052
  });
3052
- dispatch(state.update(changes, {
3053
+ dispatch(state2.update(changes, {
3053
3054
  userEvent: enable ? "format.style.add" : "format.style.remove",
3054
3055
  scrollIntoView: true
3055
3056
  }));
@@ -3123,8 +3124,8 @@ var insertTable = (view) => {
3123
3124
  const { from } = doc.line(number);
3124
3125
  snippets.table(view, null, from, from);
3125
3126
  };
3126
- var removeLinkInner = (from, to, changes, state) => {
3127
- syntaxTree2(state).iterate({
3127
+ var removeLinkInner = (from, to, changes, state2) => {
3128
+ syntaxTree2(state2).iterate({
3128
3129
  from,
3129
3130
  to,
3130
3131
  enter: (node) => {
@@ -3138,8 +3139,8 @@ var removeLinkInner = (from, to, changes, state) => {
3138
3139
  });
3139
3140
  } else if (name === "LinkTitle" || name === "URL") {
3140
3141
  changes.push({
3141
- from: skipSpaces(node2.from, state.doc, -1),
3142
- to: skipSpaces(node2.to, state.doc, 1)
3142
+ from: skipSpaces(node2.from, state2.doc, -1),
3143
+ to: skipSpaces(node2.to, state2.doc, 1)
3143
3144
  });
3144
3145
  }
3145
3146
  });
@@ -3148,32 +3149,32 @@ var removeLinkInner = (from, to, changes, state) => {
3148
3149
  }
3149
3150
  });
3150
3151
  };
3151
- var removeLink = ({ state, dispatch }) => {
3152
+ var removeLink = ({ state: state2, dispatch }) => {
3152
3153
  const changes = [];
3153
- for (const { from, to } of state.selection.ranges) {
3154
- removeLinkInner(from, to, changes, state);
3154
+ for (const { from, to } of state2.selection.ranges) {
3155
+ removeLinkInner(from, to, changes, state2);
3155
3156
  }
3156
3157
  if (!changes) {
3157
3158
  return false;
3158
3159
  }
3159
- dispatch(state.update({
3160
+ dispatch(state2.update({
3160
3161
  changes,
3161
3162
  userEvent: "format.link.remove",
3162
3163
  scrollIntoView: true
3163
3164
  }));
3164
3165
  return true;
3165
3166
  };
3166
- var addLink = ({ state, dispatch }) => {
3167
- const changes = state.changeByRange((range) => {
3167
+ var addLink = ({ url, image: image2 } = {}) => ({ state: state2, dispatch }) => {
3168
+ const changes = state2.changeByRange((range) => {
3168
3169
  let { from, to } = range;
3169
3170
  const cutStyles = [];
3170
3171
  let okay = null;
3171
- syntaxTree2(state).iterate({
3172
+ syntaxTree2(state2).iterate({
3172
3173
  from,
3173
3174
  to,
3174
3175
  enter: (node) => {
3175
3176
  if (Object.hasOwn(Textblocks, node.name)) {
3176
- okay = Textblocks[node.name] !== "codeblock" && from >= blockContentStart(node) && to <= blockContentEnd(node, state.doc);
3177
+ okay = Textblocks[node.name] !== "codeblock" && from >= blockContentStart(node) && to <= blockContentEnd(node, state2.doc);
3177
3178
  } else if (Object.hasOwn(InlineMarker, node.name)) {
3178
3179
  const sNode = node.node;
3179
3180
  if (node.from < from && node.to <= to) {
@@ -3193,7 +3194,7 @@ var addLink = ({ state, dispatch }) => {
3193
3194
  }
3194
3195
  });
3195
3196
  if (okay === null) {
3196
- const line = state.doc.lineAt(from);
3197
+ const line = state2.doc.lineAt(from);
3197
3198
  okay = to <= line.to && !/\S/.test(line.text.slice(from - line.from));
3198
3199
  }
3199
3200
  if (!okay) {
@@ -3203,26 +3204,26 @@ var addLink = ({ state, dispatch }) => {
3203
3204
  }
3204
3205
  const changes2 = [];
3205
3206
  const changesAfter = [];
3206
- removeLinkInner(from, to, changesAfter, state);
3207
+ removeLinkInner(from, to, changesAfter, state2);
3207
3208
  let cursorOffset = 1;
3208
3209
  for (const style of cutStyles) {
3209
3210
  const type = InlineMarker[style.name];
3210
3211
  const mark2 = inlineMarkerText(type);
3211
3212
  if (style.from < from) {
3212
3213
  changes2.push({
3213
- from: skipSpaces(from, state.doc, -1),
3214
+ from: skipSpaces(from, state2.doc, -1),
3214
3215
  insert: mark2
3215
3216
  });
3216
3217
  changesAfter.push({
3217
- from: skipSpaces(from, state.doc, 1, to),
3218
+ from: skipSpaces(from, state2.doc, 1, to),
3218
3219
  insert: mark2
3219
3220
  });
3220
3221
  } else {
3221
3222
  changes2.push({
3222
- from: skipSpaces(to, state.doc, -1, from),
3223
+ from: skipSpaces(to, state2.doc, -1, from),
3223
3224
  insert: mark2
3224
3225
  });
3225
- const after = skipSpaces(to, state.doc, 1);
3226
+ const after = skipSpaces(to, state2.doc, 1);
3226
3227
  if (after === to) {
3227
3228
  cursorOffset += mark2.length;
3228
3229
  }
@@ -3234,34 +3235,34 @@ var addLink = ({ state, dispatch }) => {
3234
3235
  }
3235
3236
  changes2.push({
3236
3237
  from,
3237
- insert: "["
3238
+ insert: image2 ? "![" : "["
3238
3239
  }, {
3239
3240
  from: to,
3240
- insert: "]()"
3241
+ insert: `](${url ?? ""})`
3241
3242
  });
3242
- const changeSet = state.changes(changes2.concat(changesAfter));
3243
+ const changeSet = state2.changes(changes2.concat(changesAfter));
3243
3244
  return {
3244
3245
  changes: changeSet,
3245
- range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset)
3246
+ range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset - (url ? url.length + 2 : 0))
3246
3247
  };
3247
3248
  });
3248
3249
  if (changes.changes.empty) {
3249
3250
  return false;
3250
3251
  }
3251
- dispatch(state.update(changes, {
3252
+ dispatch(state2.update(changes, {
3252
3253
  userEvent: "format.link.add",
3253
3254
  scrollIntoView: true
3254
3255
  }));
3255
3256
  return true;
3256
3257
  };
3257
- var addList = (type) => ({ state, dispatch }) => {
3258
+ var addList = (type) => ({ state: state2, dispatch }) => {
3258
3259
  let lastBlock = -1;
3259
3260
  let counter = 1;
3260
3261
  let first = true;
3261
3262
  let parentColumn = null;
3262
3263
  const blocks = [];
3263
- for (const { from, to } of state.selection.ranges) {
3264
- syntaxTree2(state).iterate({
3264
+ for (const { from, to } of state2.selection.ranges) {
3265
+ syntaxTree2(state2).iterate({
3265
3266
  from,
3266
3267
  to,
3267
3268
  enter: (node) => {
@@ -3273,7 +3274,7 @@ var addList = (type) => ({ state, dispatch }) => {
3273
3274
  }
3274
3275
  if (before?.name === (type === 0 ? "OrderedList" : "BulletList")) {
3275
3276
  const item = before.lastChild;
3276
- const itemLine = state.doc.lineAt(item.from);
3277
+ const itemLine = state2.doc.lineAt(item.from);
3277
3278
  const itemText = itemLine.text.slice(item.from - itemLine.from);
3278
3279
  parentColumn = item.from - itemLine.from + /^\s*/.exec(itemText)[0].length;
3279
3280
  if (type === 0) {
@@ -3308,10 +3309,10 @@ var addList = (type) => ({ state, dispatch }) => {
3308
3309
  });
3309
3310
  }
3310
3311
  if (!blocks.length) {
3311
- const { from, to } = state.doc.lineAt(state.selection.main.anchor);
3312
+ const { from, to } = state2.doc.lineAt(state2.selection.main.anchor);
3312
3313
  if (from === to) {
3313
3314
  const insert = type === 1 ? "- " : type === 0 ? "1. " : "- [ ] ";
3314
- dispatch(state.update({
3315
+ dispatch(state2.update({
3315
3316
  changes: [
3316
3317
  {
3317
3318
  from,
@@ -3332,11 +3333,11 @@ var addList = (type) => ({ state, dispatch }) => {
3332
3333
  for (let i = 0; i < blocks.length; i++) {
3333
3334
  const { node, counter: counter2, parentColumn: parentColumn2 } = blocks[i];
3334
3335
  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;
3336
+ let padding = nodeFrom > 0 && !/\s/.test(state2.doc.sliceString(nodeFrom - 1, nodeFrom)) ? 1 : 0;
3336
3337
  if (type === 0) {
3337
3338
  padding += String(counter2).length;
3338
3339
  }
3339
- let line = state.doc.lineAt(nodeFrom);
3340
+ let line = state2.doc.lineAt(nodeFrom);
3340
3341
  const column = nodeFrom - line.from;
3341
3342
  if (parentColumn2 !== null && parentColumn2 > column) {
3342
3343
  padding = Math.max(padding, parentColumn2 - column);
@@ -3361,7 +3362,7 @@ var addList = (type) => ({ state, dispatch }) => {
3361
3362
  insert: mark2
3362
3363
  });
3363
3364
  while (line.to < node.to) {
3364
- line = state.doc.lineAt(line.to + 1);
3365
+ line = state2.doc.lineAt(line.to + 1);
3365
3366
  const open = /^[\s>]*/.exec(line.text)[0].length;
3366
3367
  changes.push({
3367
3368
  from: line.from + Math.min(open, column),
@@ -3376,25 +3377,25 @@ var addList = (type) => ({ state, dispatch }) => {
3376
3377
  next = next.nextSibling;
3377
3378
  }
3378
3379
  if (next?.name === "OrderedList") {
3379
- renumberListItems(next.firstChild, last.counter + 1, changes, state.doc);
3380
+ renumberListItems(next.firstChild, last.counter + 1, changes, state2.doc);
3380
3381
  }
3381
3382
  }
3382
- const changeSet = state.changes(changes);
3383
- dispatch(state.update({
3383
+ const changeSet = state2.changes(changes);
3384
+ dispatch(state2.update({
3384
3385
  changes: changeSet,
3385
- selection: state.selection.map(changeSet, 1),
3386
+ selection: state2.selection.map(changeSet, 1),
3386
3387
  userEvent: "format.list.add",
3387
3388
  scrollIntoView: true
3388
3389
  }));
3389
3390
  return true;
3390
3391
  };
3391
- var removeList = (type) => ({ state, dispatch }) => {
3392
+ var removeList = (type) => ({ state: state2, dispatch }) => {
3392
3393
  let lastBlock = -1;
3393
3394
  const changes = [];
3394
3395
  const stack = [];
3395
3396
  const targetNodeType = type === 0 ? "OrderedList" : type === 1 ? "BulletList" : "TaskList";
3396
- for (const { from, to } of state.selection.ranges) {
3397
- syntaxTree2(state).iterate({
3397
+ for (const { from, to } of state2.selection.ranges) {
3398
+ syntaxTree2(state2).iterate({
3398
3399
  from,
3399
3400
  to,
3400
3401
  enter: (node) => {
@@ -3411,7 +3412,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3411
3412
  stack.pop();
3412
3413
  } else if (name === "ListItem" && stack[stack.length - 1] === targetNodeType && node.from !== lastBlock) {
3413
3414
  lastBlock = node.from;
3414
- let line = state.doc.lineAt(node.from);
3415
+ let line = state2.doc.lineAt(node.from);
3415
3416
  const mark2 = /^\s*(\d+[.)] |[-*+] (\[[ x]\] )?)/.exec(line.text.slice(node.from - line.from));
3416
3417
  if (!mark2) {
3417
3418
  return false;
@@ -3422,7 +3423,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3422
3423
  to: node.from + mark2[0].length
3423
3424
  });
3424
3425
  while (line.to < node.to) {
3425
- line = state.doc.lineAt(line.to + 1);
3426
+ line = state2.doc.lineAt(line.to + 1);
3426
3427
  const open = /^[\s>]*/.exec(line.text)[0].length;
3427
3428
  if (open > column) {
3428
3429
  changes.push({
@@ -3432,7 +3433,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3432
3433
  }
3433
3434
  }
3434
3435
  if (node.to >= to) {
3435
- renumberListItems(node.node.nextSibling, 1, changes, state.doc);
3436
+ renumberListItems(node.node.nextSibling, 1, changes, state2.doc);
3436
3437
  }
3437
3438
  return false;
3438
3439
  }
@@ -3442,7 +3443,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3442
3443
  if (!changes.length) {
3443
3444
  return false;
3444
3445
  }
3445
- dispatch(state.update({
3446
+ dispatch(state2.update({
3446
3447
  changes,
3447
3448
  userEvent: "format.list.remove",
3448
3449
  scrollIntoView: true
@@ -3472,12 +3473,12 @@ var renumberListItems = (item, counter, changes, doc) => {
3472
3473
  }
3473
3474
  }
3474
3475
  };
3475
- var setBlockquote = (enable) => ({ state, dispatch }) => {
3476
+ var setBlockquote = (enable) => ({ state: state2, dispatch }) => {
3476
3477
  const lines = [];
3477
3478
  let lastBlock = -1;
3478
- for (const { from, to } of state.selection.ranges) {
3479
+ for (const { from, to } of state2.selection.ranges) {
3479
3480
  const sawBlock = false;
3480
- syntaxTree2(state).iterate({
3481
+ syntaxTree2(state2).iterate({
3481
3482
  from,
3482
3483
  to,
3483
3484
  enter: (node) => {
@@ -3486,9 +3487,9 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3486
3487
  return false;
3487
3488
  }
3488
3489
  lastBlock = node.from;
3489
- let line2 = state.doc.lineAt(node.from);
3490
+ let line2 = state2.doc.lineAt(node.from);
3490
3491
  if (line2.number > 1) {
3491
- const prevLine = state.doc.line(line2.number - 1);
3492
+ const prevLine = state2.doc.line(line2.number - 1);
3492
3493
  if (/^[>\s]*$/.test(prevLine.text)) {
3493
3494
  if (!enable || lines.length && lines[lines.length - 1].number === prevLine.number - 1) {
3494
3495
  lines.push(prevLine);
@@ -3500,10 +3501,10 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3500
3501
  if (line2.to >= node.to) {
3501
3502
  break;
3502
3503
  }
3503
- line2 = state.doc.line(line2.number + 1);
3504
+ line2 = state2.doc.line(line2.number + 1);
3504
3505
  }
3505
- if (!enable && line2.number < state.doc.lines) {
3506
- const nextLine = state.doc.line(line2.number + 1);
3506
+ if (!enable && line2.number < state2.doc.lines) {
3507
+ const nextLine = state2.doc.line(line2.number + 1);
3507
3508
  if (/^[>\s]*$/.test(nextLine.text)) {
3508
3509
  lines.push(nextLine);
3509
3510
  }
@@ -3513,7 +3514,7 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3513
3514
  }
3514
3515
  });
3515
3516
  let line;
3516
- if (!sawBlock && enable && from === to && !/\S/.test((line = state.doc.lineAt(from)).text)) {
3517
+ if (!sawBlock && enable && from === to && !/\S/.test((line = state2.doc.lineAt(from)).text)) {
3517
3518
  lines.push(line);
3518
3519
  }
3519
3520
  }
@@ -3537,10 +3538,10 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3537
3538
  if (!changes.length) {
3538
3539
  return false;
3539
3540
  }
3540
- const changeSet = state.changes(changes);
3541
- dispatch(state.update({
3541
+ const changeSet = state2.changes(changes);
3542
+ dispatch(state2.update({
3542
3543
  changes: changeSet,
3543
- selection: state.selection.map(changeSet, 1),
3544
+ selection: state2.selection.map(changeSet, 1),
3544
3545
  userEvent: enable ? "format.blockquote.add" : "format.blockquote.remove",
3545
3546
  scrollIntoView: true
3546
3547
  }));
@@ -3552,11 +3553,11 @@ var toggleBlockquote = (target) => {
3552
3553
  return (getFormatting(target.state).blockQuote ? removeBlockquote : addBlockquote)(target);
3553
3554
  };
3554
3555
  var addCodeblock = (target) => {
3555
- const { state, dispatch } = target;
3556
- const { selection } = state;
3556
+ const { state: state2, dispatch } = target;
3557
+ const { selection } = state2;
3557
3558
  if (selection.ranges.length === 1 && selection.main.empty) {
3558
3559
  const { head } = selection.main;
3559
- const line = state.doc.lineAt(head);
3560
+ const line = state2.doc.lineAt(head);
3560
3561
  if (!/\S/.test(line.text) && head === line.from) {
3561
3562
  snippets.codeblock(target, null, line.from, line.to);
3562
3563
  return true;
@@ -3566,7 +3567,7 @@ var addCodeblock = (target) => {
3566
3567
  for (const { from, to } of selection.ranges) {
3567
3568
  let blockFrom = from;
3568
3569
  let blockTo = to;
3569
- syntaxTree2(state).iterate({
3570
+ syntaxTree2(state2).iterate({
3570
3571
  from,
3571
3572
  to,
3572
3573
  enter: (node) => {
@@ -3575,8 +3576,8 @@ var addCodeblock = (target) => {
3575
3576
  blockFrom = node.from;
3576
3577
  blockTo = node.to;
3577
3578
  } else {
3578
- blockFrom = Math.min(blockFrom, state.doc.lineAt(node.from).from);
3579
- blockTo = Math.max(blockTo, state.doc.lineAt(node.to).to);
3579
+ blockFrom = Math.min(blockFrom, state2.doc.lineAt(node.from).from);
3580
+ blockTo = Math.max(blockTo, state2.doc.lineAt(node.to).to);
3580
3581
  }
3581
3582
  }
3582
3583
  }
@@ -3594,7 +3595,7 @@ var addCodeblock = (target) => {
3594
3595
  return false;
3595
3596
  }
3596
3597
  const changes = ranges.map(({ from, to }) => {
3597
- const column = from - state.doc.lineAt(from).from;
3598
+ const column = from - state2.doc.lineAt(from).from;
3598
3599
  return [
3599
3600
  {
3600
3601
  from,
@@ -3606,30 +3607,30 @@ var addCodeblock = (target) => {
3606
3607
  }
3607
3608
  ];
3608
3609
  });
3609
- dispatch(state.update({
3610
+ dispatch(state2.update({
3610
3611
  changes,
3611
3612
  userEvent: "format.codeblock.add",
3612
3613
  scrollIntoView: true
3613
3614
  }));
3614
3615
  return true;
3615
3616
  };
3616
- var removeCodeblock = ({ state, dispatch }) => {
3617
+ var removeCodeblock = ({ state: state2, dispatch }) => {
3617
3618
  const changes = [];
3618
3619
  let lastBlock = -1;
3619
- for (const { from, to } of state.selection.ranges) {
3620
- syntaxTree2(state).iterate({
3620
+ for (const { from, to } of state2.selection.ranges) {
3621
+ syntaxTree2(state2).iterate({
3621
3622
  from,
3622
3623
  to,
3623
3624
  enter: (node) => {
3624
3625
  if (Textblocks[node.name] === "codeblock" && lastBlock !== node.from) {
3625
3626
  lastBlock = node.from;
3626
- const firstLine = state.doc.lineAt(node.from);
3627
+ const firstLine = state2.doc.lineAt(node.from);
3627
3628
  if (node.name === "FencedCode") {
3628
3629
  changes.push({
3629
3630
  from: node.from,
3630
3631
  to: firstLine.to + 1 + node.from - firstLine.from
3631
3632
  });
3632
- const lastLine = state.doc.lineAt(node.to);
3633
+ const lastLine = state2.doc.lineAt(node.to);
3633
3634
  if (/^([\s>]|[-*+] |\d+[).])*`+$/.test(lastLine.text)) {
3634
3635
  changes.push({
3635
3636
  from: lastLine.from - (lastLine.number === firstLine.number + 1 ? 0 : 1),
@@ -3638,7 +3639,7 @@ var removeCodeblock = ({ state, dispatch }) => {
3638
3639
  }
3639
3640
  } else {
3640
3641
  const column = node.from - firstLine.from;
3641
- for (let line = firstLine; ; line = state.doc.line(line.number + 1)) {
3642
+ for (let line = firstLine; ; line = state2.doc.line(line.number + 1)) {
3642
3643
  changes.push({
3643
3644
  from: line.from + column - 4,
3644
3645
  to: line.from + column
@@ -3655,7 +3656,7 @@ var removeCodeblock = ({ state, dispatch }) => {
3655
3656
  if (!changes.length) {
3656
3657
  return false;
3657
3658
  }
3658
- dispatch(state.update({
3659
+ dispatch(state2.update({
3659
3660
  changes,
3660
3661
  userEvent: "format.codeblock.remove",
3661
3662
  scrollIntoView: true
@@ -3714,7 +3715,7 @@ var Textblocks = {
3714
3715
  TableCell: "tablecell",
3715
3716
  Task: "paragraph"
3716
3717
  };
3717
- var getFormatting = (state) => {
3718
+ var getFormatting = (state2) => {
3718
3719
  let blockType = null;
3719
3720
  const inline = [
3720
3721
  null,
@@ -3740,7 +3741,7 @@ var getFormatting = (state) => {
3740
3741
  continue;
3741
3742
  } else if (currentBlock.active[i]) {
3742
3743
  inline[i] = true;
3743
- } else if (/\S/.test(state.doc.sliceString(currentBlock.pos, upto))) {
3744
+ } else if (/\S/.test(state2.doc.sliceString(currentBlock.pos, upto))) {
3744
3745
  inline[i] = false;
3745
3746
  }
3746
3747
  }
@@ -3751,12 +3752,12 @@ var getFormatting = (state) => {
3751
3752
  currentBlock.pos = Math.min(upto, currentBlock.end);
3752
3753
  }
3753
3754
  };
3754
- const { selection } = state;
3755
+ const { selection } = state2;
3755
3756
  for (const range of selection.ranges) {
3756
3757
  if (range.empty && inline.some((v) => v === null)) {
3757
3758
  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);
3759
+ const contextBefore = state2.doc.sliceString(range.head - contextSize, range.head);
3760
+ let contextAfter = state2.doc.sliceString(range.head, range.head + contextSize);
3760
3761
  for (let i = 0; i < contextSize; i++) {
3761
3762
  const ch = contextAfter[i];
3762
3763
  if (ch !== contextBefore[contextBefore.length - 1 - i] || !/[~`*]/.test(ch)) {
@@ -3775,7 +3776,7 @@ var getFormatting = (state) => {
3775
3776
  }
3776
3777
  }
3777
3778
  }
3778
- syntaxTree2(state).iterate({
3779
+ syntaxTree2(state2).iterate({
3779
3780
  from: range.from,
3780
3781
  to: range.to,
3781
3782
  enter: (node) => {
@@ -3849,7 +3850,7 @@ var getFormatting = (state) => {
3849
3850
  }
3850
3851
  });
3851
3852
  }
3852
- const { from, to } = state.doc.lineAt(selection.main.anchor);
3853
+ const { from, to } = state2.doc.lineAt(selection.main.anchor);
3853
3854
  const blankLine = from === to;
3854
3855
  return {
3855
3856
  blankLine,
@@ -3864,17 +3865,22 @@ var getFormatting = (state) => {
3864
3865
  };
3865
3866
  };
3866
3867
  var useFormattingState = () => {
3867
- const [state, setState] = useState(null);
3868
+ const [state2, setState] = useState();
3868
3869
  const observer = useMemo(() => EditorView13.updateListener.of((update2) => {
3869
3870
  if (update2.docChanged || update2.selectionSet) {
3870
- const newState = getFormatting(update2.state);
3871
- if (!state || !compareFormatting(state, newState)) {
3872
- setState(newState);
3873
- }
3871
+ setState((prevState) => {
3872
+ const newState = getFormatting(update2.state);
3873
+ if (!prevState || !formattingEquals(prevState, newState)) {
3874
+ return newState;
3875
+ }
3876
+ return prevState;
3877
+ });
3874
3878
  }
3875
- }), []);
3879
+ }), [
3880
+ setState
3881
+ ]);
3876
3882
  return [
3877
- state,
3883
+ state2,
3878
3884
  observer
3879
3885
  ];
3880
3886
  };
@@ -3885,8 +3891,8 @@ import { StateField as StateField5 } from "@codemirror/state";
3885
3891
  import { Decoration as Decoration6, EditorView as EditorView14, WidgetType as WidgetType4 } from "@codemirror/view";
3886
3892
  var image = (options = {}) => {
3887
3893
  return StateField5.define({
3888
- create: (state) => {
3889
- return Decoration6.set(buildDecorations2(0, state.doc.length, state));
3894
+ create: (state2) => {
3895
+ return Decoration6.set(buildDecorations2(0, state2.doc.length, state2));
3890
3896
  },
3891
3897
  update: (value, tr) => {
3892
3898
  if (!tr.docChanged && !tr.selection) {
@@ -3920,16 +3926,16 @@ var preloadImage = (url) => {
3920
3926
  preloaded.add(url);
3921
3927
  }
3922
3928
  };
3923
- var buildDecorations2 = (from, to, state) => {
3929
+ var buildDecorations2 = (from, to, state2) => {
3924
3930
  const decorations = [];
3925
- const cursor = state.selection.main.head;
3926
- syntaxTree3(state).iterate({
3931
+ const cursor = state2.selection.main.head;
3932
+ syntaxTree3(state2).iterate({
3927
3933
  enter: (node) => {
3928
3934
  if (node.name === "Image") {
3929
3935
  const urlNode = node.node.getChild("URL");
3930
3936
  if (urlNode) {
3931
- const hide2 = state.readOnly || cursor < node.from || cursor > node.to;
3932
- const url = state.sliceDoc(urlNode.from, urlNode.to);
3937
+ const hide2 = state2.readOnly || cursor < node.from || cursor > node.to;
3938
+ const url = state2.sliceDoc(urlNode.from, urlNode.to);
3933
3939
  preloadImage(url);
3934
3940
  decorations.push(Decoration6.replace({
3935
3941
  block: true,
@@ -3959,6 +3965,8 @@ var ImageWidget = class extends WidgetType4 {
3959
3965
  return img;
3960
3966
  }
3961
3967
  };
3968
+ var imageUpload = (options = {}) => {
3969
+ };
3962
3970
 
3963
3971
  // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
3964
3972
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
@@ -4000,21 +4008,21 @@ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField6 } from "
4000
4008
  import { Decoration as Decoration7, EditorView as EditorView15, WidgetType as WidgetType5 } from "@codemirror/view";
4001
4009
  var table = (options = {}) => {
4002
4010
  return StateField6.define({
4003
- create: (state) => update(state, options),
4011
+ create: (state2) => update(state2, options),
4004
4012
  update: (_, tr) => update(tr.state, options),
4005
4013
  provide: (field) => EditorView15.decorations.from(field)
4006
4014
  });
4007
4015
  };
4008
- var update = (state, options) => {
4016
+ var update = (state2, options) => {
4009
4017
  const builder = new RangeSetBuilder3();
4010
- const cursor = state.selection.main.head;
4018
+ const cursor = state2.selection.main.head;
4011
4019
  const tables = [];
4012
4020
  const getTable = () => tables[tables.length - 1];
4013
4021
  const getRow = () => {
4014
4022
  const table2 = getTable();
4015
4023
  return table2.rows?.[table2.rows.length - 1];
4016
4024
  };
4017
- syntaxTree5(state).iterate({
4025
+ syntaxTree5(state2).iterate({
4018
4026
  enter: (node) => {
4019
4027
  switch (node.name) {
4020
4028
  case "Table": {
@@ -4035,9 +4043,9 @@ var update = (state, options) => {
4035
4043
  case "TableCell": {
4036
4044
  const row = getRow();
4037
4045
  if (row) {
4038
- row.push(state.sliceDoc(node.from, node.to));
4046
+ row.push(state2.sliceDoc(node.from, node.to));
4039
4047
  } else {
4040
- getTable().header?.push(state.sliceDoc(node.from, node.to));
4048
+ getTable().header?.push(state2.sliceDoc(node.from, node.to));
4041
4049
  }
4042
4050
  break;
4043
4051
  }
@@ -4045,7 +4053,7 @@ var update = (state, options) => {
4045
4053
  }
4046
4054
  });
4047
4055
  tables.forEach((table2) => {
4048
- const hide2 = state.readOnly || cursor < table2.from || cursor > table2.to;
4056
+ const hide2 = state2.readOnly || cursor < table2.from || cursor > table2.to;
4049
4057
  if (hide2) {
4050
4058
  builder.add(table2.from, table2.to, Decoration7.replace({
4051
4059
  widget: new TableWidget(table2)
@@ -4136,6 +4144,7 @@ var mention = ({ onSearch }) => {
4136
4144
  import { Facet as Facet6 } from "@codemirror/state";
4137
4145
  import { keymap as keymap7 } from "@codemirror/view";
4138
4146
  import { vim } from "@replit/codemirror-vim";
4147
+ var focusEvent = "focus.container";
4139
4148
  var editorMode = Facet6.define({
4140
4149
  combine: (modes) => modes[0] ?? {}
4141
4150
  });
@@ -4152,7 +4161,7 @@ var EditorModes = {
4152
4161
  key: "Alt-Escape",
4153
4162
  run: (view) => {
4154
4163
  view.dispatch({
4155
- userEvent: "focus.container"
4164
+ userEvent: focusEvent
4156
4165
  });
4157
4166
  return true;
4158
4167
  }
@@ -4161,8 +4170,95 @@ var EditorModes = {
4161
4170
  ]
4162
4171
  };
4163
4172
 
4173
+ // packages/ui/react-ui-editor/src/extensions/state.ts
4174
+ import { Transaction as Transaction2 } from "@codemirror/state";
4175
+ import { EditorView as EditorView16, keymap as keymap8 } from "@codemirror/view";
4176
+ import { debounce as debounce2 } from "@dxos/async";
4177
+ import { invariant as invariant4 } from "@dxos/invariant";
4178
+ import { isNotFalsy as isNotFalsy3 } from "@dxos/util";
4179
+ var __dxlog_file11 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/state.ts";
4180
+ var scrollAnnotation = "dxos.org/cm/scrolling";
4181
+ var keyPrefix = "dxos.org/react-ui-editor/state";
4182
+ var localStorageStateStoreAdapter = {
4183
+ setState: (id, state2) => {
4184
+ invariant4(id, void 0, {
4185
+ F: __dxlog_file11,
4186
+ L: 35,
4187
+ S: void 0,
4188
+ A: [
4189
+ "id",
4190
+ ""
4191
+ ]
4192
+ });
4193
+ localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state2));
4194
+ },
4195
+ getState: (id) => {
4196
+ invariant4(id, void 0, {
4197
+ F: __dxlog_file11,
4198
+ L: 39,
4199
+ S: void 0,
4200
+ A: [
4201
+ "id",
4202
+ ""
4203
+ ]
4204
+ });
4205
+ const state2 = localStorage.getItem(`${keyPrefix}/${id}`);
4206
+ return state2 ? JSON.parse(state2) : void 0;
4207
+ }
4208
+ };
4209
+ var state = ({ getState, setState } = {}) => {
4210
+ const setStateDebounced = debounce2(setState, 1e3);
4211
+ return [
4212
+ // TODO(burdon): Track scrolling (currently only updates when cursor moves).
4213
+ EditorView16.updateListener.of(({ view, changes, transactions }) => {
4214
+ const id = view.state.facet(documentId2);
4215
+ if (!id || transactions.some((tr) => tr.isUserEvent(scrollAnnotation))) {
4216
+ return;
4217
+ }
4218
+ if (setState) {
4219
+ const { top } = view.dom.getBoundingClientRect();
4220
+ const pos = view.posAtCoords({
4221
+ x: 0,
4222
+ y: top
4223
+ });
4224
+ if (pos !== null) {
4225
+ const { anchor, head } = view.state.selection.main;
4226
+ setStateDebounced(id, {
4227
+ scrollTo: {
4228
+ from: pos,
4229
+ yMargin: 0
4230
+ },
4231
+ selection: {
4232
+ anchor,
4233
+ head
4234
+ }
4235
+ });
4236
+ }
4237
+ }
4238
+ }),
4239
+ getState && keymap8.of([
4240
+ {
4241
+ key: "ctrl-r",
4242
+ run: (view) => {
4243
+ const state2 = getState(view.state.facet(documentId2));
4244
+ if (state2) {
4245
+ view.dispatch({
4246
+ effects: EditorView16.scrollIntoView(state2.scrollTo.from, {
4247
+ yMargin: 0
4248
+ }),
4249
+ selection: state2.selection,
4250
+ annotations: Transaction2.userEvent.of(scrollAnnotation)
4251
+ });
4252
+ }
4253
+ return true;
4254
+ }
4255
+ }
4256
+ ])
4257
+ ].filter(isNotFalsy3);
4258
+ };
4259
+
4164
4260
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
4165
- import { keymap as keymap8 } from "@codemirror/view";
4261
+ import { keymap as keymap9 } from "@codemirror/view";
4166
4262
  var defaultItems = [
4167
4263
  "hello world!",
4168
4264
  "this is a test.",
@@ -4172,7 +4268,7 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
4172
4268
  let t;
4173
4269
  let idx = 0;
4174
4270
  return [
4175
- keymap8.of([
4271
+ keymap9.of([
4176
4272
  {
4177
4273
  // Reset.
4178
4274
  key: "alt-meta-'",
@@ -4219,9 +4315,11 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
4219
4315
  };
4220
4316
 
4221
4317
  // 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";
4318
+ var __dxlog_file12 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
4223
4319
  var instanceCount = 0;
4224
- var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, className, autoFocus, scrollTo = EditorView16.scrollIntoView(0), moveToEndOfLine, debug, dataTestId }, forwardedRef) => {
4320
+ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, className, autoFocus, scrollTo = EditorView17.scrollIntoView(0, {
4321
+ yMargin: 0
4322
+ }), moveToEndOfLine, debug, dataTestId }, forwardedRef) => {
4225
4323
  const [instanceId] = useState2(() => `text-editor-${++instanceCount}`);
4226
4324
  const tabsterDOMAttribute = useFocusableGroup({
4227
4325
  tabBehavior: "limited"
@@ -4240,42 +4338,42 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4240
4338
  autoFocus
4241
4339
  ]);
4242
4340
  useEffect2(() => {
4243
- log9("updating", {
4341
+ log9("create", {
4244
4342
  id,
4245
4343
  instanceId
4246
4344
  }, {
4247
- F: __dxlog_file11,
4345
+ F: __dxlog_file12,
4248
4346
  L: 88,
4249
4347
  S: void 0,
4250
4348
  C: (f, a) => f(...a)
4251
4349
  });
4252
- const state = EditorState2.create({
4350
+ const state2 = EditorState2.create({
4253
4351
  doc,
4254
4352
  selection,
4255
4353
  extensions: [
4256
4354
  id && documentId2.of(id),
4257
4355
  // TODO(burdon): NOTE: Doesn't catch errors in keymap functions.
4258
- EditorView16.exceptionSink.of((err) => {
4356
+ EditorView17.exceptionSink.of((err) => {
4259
4357
  log9.catch(err, void 0, {
4260
- F: __dxlog_file11,
4358
+ F: __dxlog_file12,
4261
4359
  L: 101,
4262
4360
  S: void 0,
4263
4361
  C: (f, a) => f(...a)
4264
4362
  });
4265
4363
  }),
4266
4364
  // Focus.
4267
- EditorView16.updateListener.of((update2) => {
4365
+ EditorView17.updateListener.of((update2) => {
4268
4366
  update2.transactions.forEach((transaction) => {
4269
- if (transaction.isUserEvent("focus.container")) {
4367
+ if (transaction.isUserEvent(focusEvent)) {
4270
4368
  rootRef.current?.focus();
4271
4369
  }
4272
4370
  });
4273
4371
  }),
4274
4372
  extensions
4275
- ].filter(isNotFalsy3)
4373
+ ].filter(isNotFalsy4)
4276
4374
  });
4277
- const view2 = new EditorView16({
4278
- state,
4375
+ const view2 = new EditorView17({
4376
+ state: state2,
4279
4377
  parent: rootRef.current,
4280
4378
  scrollTo,
4281
4379
  // NOTE: Uncomment to debug/monitor all transactions.
@@ -4287,21 +4385,29 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4287
4385
  view3.update(trs);
4288
4386
  }
4289
4387
  });
4290
- setView(view2);
4291
- if (moveToEndOfLine) {
4388
+ if (moveToEndOfLine && !(scrollTo || selection)) {
4292
4389
  const { to } = view2.state.doc.lineAt(0);
4293
- view2?.dispatch({
4390
+ view2.dispatch({
4294
4391
  selection: {
4295
4392
  anchor: to
4296
4393
  }
4297
4394
  });
4298
4395
  }
4299
- if (state.facet(editorMode).noTabster) {
4396
+ if (state2.facet(editorMode).noTabster) {
4300
4397
  rootRef.current?.removeAttribute("data-tabster");
4301
4398
  }
4399
+ setView(view2);
4302
4400
  return () => {
4401
+ log9("destroy", {
4402
+ id,
4403
+ instanceId
4404
+ }, {
4405
+ F: __dxlog_file12,
4406
+ L: 150,
4407
+ S: void 0,
4408
+ C: (f, a) => f(...a)
4409
+ });
4303
4410
  view2?.destroy();
4304
- setView(null);
4305
4411
  };
4306
4412
  }, [
4307
4413
  doc,
@@ -4331,9 +4437,10 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4331
4437
  });
4332
4438
 
4333
4439
  // 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";
4440
+ 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
4441
  import { createContext } from "@radix-ui/react-context";
4336
- import React2, { useRef as useRef2, useState as useState3 } from "react";
4442
+ import React2, { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
4443
+ import { useDropzone } from "react-dropzone";
4337
4444
  import { DensityProvider, ElevationProvider, Select, Toolbar as NaturalToolbar, Tooltip, useTranslation } from "@dxos/react-ui";
4338
4445
  import { getSize } from "@dxos/react-ui-theme";
4339
4446
  var tooltipProps = {
@@ -4350,10 +4457,10 @@ var HeadingIcons = {
4350
4457
  "6": TextHSix
4351
4458
  };
4352
4459
  var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
4353
- var ToolbarRoot = ({ children, onAction, classNames, state }) => {
4460
+ var ToolbarRoot = ({ children, onAction, classNames, state: state2 }) => {
4354
4461
  return /* @__PURE__ */ React2.createElement(ToolbarContextProvider, {
4355
4462
  onAction,
4356
- state
4463
+ state: state2
4357
4464
  }, /* @__PURE__ */ React2.createElement(DensityProvider, {
4358
4465
  density: "fine"
4359
4466
  }, /* @__PURE__ */ React2.createElement(ElevationProvider, {
@@ -4367,7 +4474,7 @@ var ToolbarRoot = ({ children, onAction, classNames, state }) => {
4367
4474
  };
4368
4475
  var buttonStyles = "min-bs-0 p-2";
4369
4476
  var iconStyles = getSize(5);
4370
- var ToolbarButton = ({ Icon, children, ...props }) => {
4477
+ var ToolbarToggleButton = ({ Icon, children, ...props }) => {
4371
4478
  return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4372
4479
  asChild: true
4373
4480
  }, /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroupItem, {
@@ -4380,14 +4487,27 @@ var ToolbarButton = ({ Icon, children, ...props }) => {
4380
4487
  className: "sr-only"
4381
4488
  }, children))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, tooltipProps, children, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
4382
4489
  };
4490
+ var ToolbarButton = ({ Icon, children, ...props }) => {
4491
+ return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4492
+ asChild: true
4493
+ }, /* @__PURE__ */ React2.createElement(NaturalToolbar.Button, {
4494
+ variant: "ghost",
4495
+ ...props,
4496
+ classNames: buttonStyles
4497
+ }, /* @__PURE__ */ React2.createElement(Icon, {
4498
+ className: iconStyles
4499
+ }), /* @__PURE__ */ React2.createElement("span", {
4500
+ className: "sr-only"
4501
+ }, children))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, tooltipProps, children, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
4502
+ };
4383
4503
  var ToolbarSeparator = () => /* @__PURE__ */ React2.createElement("div", {
4384
4504
  role: "separator",
4385
4505
  className: "grow"
4386
4506
  });
4387
4507
  var MarkdownHeading = () => {
4388
4508
  const { t } = useTranslation(translationKey);
4389
- const { onAction, state } = useToolbarContext("MarkdownFormatting");
4390
- const blockType = state ? state.blockType : "paragraph";
4509
+ const { onAction, state: state2 } = useToolbarContext("MarkdownFormatting");
4510
+ const blockType = state2 ? state2.blockType : "paragraph";
4391
4511
  const header = blockType && /heading(\d)/.exec(blockType);
4392
4512
  const value = header ? header[1] : blockType === "paragraph" || !blockType ? "0" : void 0;
4393
4513
  const HeadingIcon = HeadingIcons[value ?? "0"];
@@ -4461,43 +4581,43 @@ var markdownStyles = [
4461
4581
  {
4462
4582
  type: "strong",
4463
4583
  Icon: TextB,
4464
- getState: (state) => state.strong
4584
+ getState: (state2) => state2.strong
4465
4585
  },
4466
4586
  {
4467
4587
  type: "emphasis",
4468
4588
  Icon: TextItalic,
4469
- getState: (state) => state.emphasis
4589
+ getState: (state2) => state2.emphasis
4470
4590
  },
4471
4591
  {
4472
4592
  type: "strikethrough",
4473
4593
  Icon: TextStrikethrough,
4474
- getState: (state) => state.strikethrough
4594
+ getState: (state2) => state2.strikethrough
4475
4595
  },
4476
4596
  {
4477
4597
  type: "code",
4478
4598
  Icon: Code,
4479
- getState: (state) => state.code
4599
+ getState: (state2) => state2.code
4480
4600
  },
4481
4601
  {
4482
4602
  type: "link",
4483
4603
  Icon: Link,
4484
- getState: (state) => state.link
4604
+ getState: (state2) => state2.link
4485
4605
  }
4486
4606
  ];
4487
4607
  var MarkdownStyles = () => {
4488
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4608
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4489
4609
  const { t } = useTranslation(translationKey);
4490
4610
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4491
4611
  type: "multiple",
4492
- value: markdownStyles.filter(({ getState }) => state && getState(state)).map(({ type }) => type)
4493
- }, markdownStyles.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4612
+ value: markdownStyles.filter(({ getState }) => state2 && getState(state2)).map(({ type }) => type)
4613
+ }, markdownStyles.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4494
4614
  key: type,
4495
4615
  value: type,
4496
4616
  Icon,
4497
- disabled: state?.blockType === "codeblock",
4498
- onClick: state ? () => onAction?.({
4617
+ disabled: state2?.blockType === "codeblock",
4618
+ onClick: state2 ? () => onAction?.({
4499
4619
  type,
4500
- data: !getState(state)
4620
+ data: !getState(state2)
4501
4621
  }) : void 0
4502
4622
  }, t(`${type} label`))));
4503
4623
  };
@@ -4505,32 +4625,32 @@ var markdownLists = [
4505
4625
  {
4506
4626
  type: "list-bullet",
4507
4627
  Icon: ListBullets,
4508
- getState: (state) => state.listStyle === "bullet"
4628
+ getState: (state2) => state2.listStyle === "bullet"
4509
4629
  },
4510
4630
  {
4511
4631
  type: "list-ordered",
4512
4632
  Icon: ListNumbers,
4513
- getState: (state) => state.listStyle === "ordered"
4633
+ getState: (state2) => state2.listStyle === "ordered"
4514
4634
  },
4515
4635
  {
4516
4636
  type: "list-task",
4517
4637
  Icon: ListChecks,
4518
- getState: (state) => state.listStyle === "task"
4638
+ getState: (state2) => state2.listStyle === "task"
4519
4639
  }
4520
4640
  ];
4521
4641
  var MarkdownLists = () => {
4522
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4642
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4523
4643
  const { t } = useTranslation(translationKey);
4524
4644
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4525
4645
  type: "single",
4526
- value: state?.listStyle ? `list-${state.listStyle}` : ""
4527
- }, markdownLists.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4646
+ value: state2?.listStyle ? `list-${state2.listStyle}` : ""
4647
+ }, markdownLists.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4528
4648
  key: type,
4529
4649
  value: type,
4530
4650
  Icon,
4531
- onClick: state ? () => onAction?.({
4651
+ onClick: state2 ? () => onAction?.({
4532
4652
  type,
4533
- data: !getState(state)
4653
+ data: !getState(state2)
4534
4654
  }) : void 0
4535
4655
  }, t(`${type} label`))));
4536
4656
  };
@@ -4538,63 +4658,101 @@ var markdownBlocks = [
4538
4658
  {
4539
4659
  type: "blockquote",
4540
4660
  Icon: Quotes,
4541
- getState: (state) => state.blockQuote
4661
+ getState: (state2) => state2.blockQuote
4542
4662
  },
4543
4663
  {
4544
4664
  type: "codeblock",
4545
4665
  Icon: CodeBlock,
4546
- getState: (state) => state.blockType === "codeblock"
4666
+ getState: (state2) => state2.blockType === "codeblock"
4547
4667
  },
4548
4668
  {
4549
4669
  type: "table",
4550
4670
  Icon: Table2,
4551
- getState: (state) => state.blockType === "tablecell",
4552
- disabled: (state) => !state.blankLine
4671
+ getState: (state2) => state2.blockType === "tablecell",
4672
+ disabled: (state2) => !state2.blankLine
4553
4673
  }
4554
4674
  ];
4555
4675
  var MarkdownBlocks = () => {
4556
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4676
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4557
4677
  const { t } = useTranslation(translationKey);
4558
- const value = markdownBlocks.find(({ getState }) => state && getState(state));
4678
+ const value = markdownBlocks.find(({ getState }) => state2 && getState(state2));
4559
4679
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4560
4680
  type: "single",
4561
4681
  value: value?.type ?? ""
4562
- }, markdownBlocks.map(({ type, disabled, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4682
+ }, markdownBlocks.map(({ type, disabled, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4563
4683
  key: type,
4564
4684
  value: type,
4565
4685
  Icon,
4566
- disabled: !state || disabled?.(state),
4567
- onClick: state ? () => onAction?.({
4686
+ disabled: !state2 || disabled?.(state2),
4687
+ onClick: state2 ? () => onAction?.({
4568
4688
  type,
4569
- data: !getState(state)
4689
+ data: !getState(state2)
4570
4690
  }) : void 0
4571
4691
  }, t(`${type} label`))));
4572
4692
  };
4573
4693
  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 = () => {
4694
+ var MarkdownCustom = ({ onUpload } = {}) => {
4575
4695
  const { onAction } = useToolbarContext("MarkdownStyles");
4576
4696
  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",
4697
+ const { acceptedFiles, getInputProps, open } = useDropzone({
4698
+ multiple: false,
4699
+ noDrag: true,
4700
+ accept: {
4701
+ "image/*": [
4702
+ ".jpg",
4703
+ ".jpeg",
4704
+ ".png",
4705
+ ".gif"
4706
+ ]
4707
+ }
4708
+ });
4709
+ useEffect3(() => {
4710
+ if (onUpload && acceptedFiles.length) {
4711
+ setTimeout(async () => {
4712
+ const f = acceptedFiles[0];
4713
+ const file = new File([
4714
+ f
4715
+ ], f.name, {
4716
+ type: f.type,
4717
+ lastModified: f.lastModified
4718
+ });
4719
+ const { url } = await onUpload(file);
4720
+ if (url) {
4721
+ onAction?.({
4722
+ type: "image",
4723
+ data: url
4724
+ });
4725
+ }
4726
+ });
4727
+ }
4728
+ }, [
4729
+ acceptedFiles
4730
+ ]);
4731
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("input", getInputProps()), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4732
+ value: "image",
4733
+ Icon: Image,
4734
+ onClick: () => open()
4735
+ }, t("image label")));
4736
+ };
4737
+ var MarkdownActions = () => {
4738
+ const { onAction } = useToolbarContext("MarkdownStyles");
4739
+ const { t } = useTranslation(translationKey);
4740
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(ToolbarButton, {
4741
+ value: "comment",
4742
+ Icon: ChatText,
4581
4743
  "data-testid": "editor.toolbar.comment",
4582
4744
  onClick: () => onAction?.({
4583
4745
  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))));
4746
+ })
4747
+ }, t("comment label")));
4591
4748
  };
4592
4749
  var Toolbar = {
4593
4750
  Root: ToolbarRoot,
4594
- Button: ToolbarButton,
4751
+ Button: ToolbarToggleButton,
4595
4752
  Separator: ToolbarSeparator,
4596
4753
  Markdown: MarkdownStandard,
4597
- Extended: MarkdownExtended
4754
+ Custom: MarkdownCustom,
4755
+ Actions: MarkdownActions
4598
4756
  };
4599
4757
 
4600
4758
  // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
@@ -4631,7 +4789,13 @@ var useActionHandler = (view) => {
4631
4789
  insertTable(view);
4632
4790
  break;
4633
4791
  case "link":
4634
- (action.data === false ? removeLink : addLink)(view);
4792
+ (action.data === false ? removeLink : addLink())(view);
4793
+ break;
4794
+ case "image":
4795
+ addLink({
4796
+ url: action.data,
4797
+ image: true
4798
+ })(view);
4635
4799
  break;
4636
4800
  case "comment":
4637
4801
  createComment(view);
@@ -4660,43 +4824,51 @@ var useDocAccessor = (text) => {
4660
4824
 
4661
4825
  // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
4662
4826
  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";
4827
+ import { EditorView as EditorView18 } from "@codemirror/view";
4828
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
4665
4829
  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";
4830
+ import { isNotFalsy as isNotFalsy5 } from "@dxos/util";
4831
+ var __dxlog_file13 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
4668
4832
  var useTextEditor = (cb = () => ({}), deps = []) => {
4669
4833
  let { id, doc, selection, extensions, autoFocus, scrollTo, debug } = useMemo3(cb, deps ?? []);
4670
4834
  const onUpdate = useRef3();
4671
4835
  const [view, setView] = useState4();
4672
4836
  const parentRef = useRef3(null);
4673
- useEffect3(() => {
4837
+ useEffect4(() => {
4674
4838
  let view2;
4675
4839
  if (parentRef.current) {
4676
- const state = EditorState3.create({
4840
+ log10("create", {
4841
+ id
4842
+ }, {
4843
+ F: __dxlog_file13,
4844
+ L: 36,
4845
+ S: void 0,
4846
+ C: (f, a) => f(...a)
4847
+ });
4848
+ const state2 = EditorState3.create({
4677
4849
  doc,
4678
4850
  selection,
4679
4851
  extensions: [
4680
4852
  id && documentId2.of(id),
4681
4853
  // TODO(burdon): Doesn't catch errors in keymap functions.
4682
- EditorView17.exceptionSink.of((err) => {
4854
+ EditorView18.exceptionSink.of((err) => {
4683
4855
  log10.catch(err, void 0, {
4684
- F: __dxlog_file12,
4685
- L: 44,
4856
+ F: __dxlog_file13,
4857
+ L: 46,
4686
4858
  S: void 0,
4687
4859
  C: (f, a) => f(...a)
4688
4860
  });
4689
4861
  }),
4690
4862
  extensions,
4691
- EditorView17.updateListener.of(() => {
4863
+ EditorView18.updateListener.of(() => {
4692
4864
  onUpdate.current?.();
4693
4865
  })
4694
- ].filter(isNotFalsy4)
4866
+ ].filter(isNotFalsy5)
4695
4867
  });
4696
- view2 = new EditorView17({
4868
+ view2 = new EditorView18({
4697
4869
  parent: parentRef.current,
4698
4870
  scrollTo,
4699
- state,
4871
+ state: state2,
4700
4872
  // NOTE: Uncomment to debug/monitor all transactions.
4701
4873
  // https://codemirror.net/docs/ref/#view.EditorView.dispatch
4702
4874
  dispatchTransactions: (trs, view3) => {
@@ -4709,10 +4881,18 @@ var useTextEditor = (cb = () => ({}), deps = []) => {
4709
4881
  setView(view2);
4710
4882
  }
4711
4883
  return () => {
4884
+ log10("destroy", {
4885
+ id
4886
+ }, {
4887
+ F: __dxlog_file13,
4888
+ L: 74,
4889
+ S: void 0,
4890
+ C: (f, a) => f(...a)
4891
+ });
4712
4892
  view2?.destroy();
4713
4893
  };
4714
4894
  }, deps);
4715
- useEffect3(() => {
4895
+ useEffect4(() => {
4716
4896
  if (view) {
4717
4897
  if (!selection && !view.state.selection.main.anchor) {
4718
4898
  selection = EditorSelection2.single(view.state.doc.line(1).to);
@@ -4771,7 +4951,6 @@ export {
4771
4951
  callbackWrapper,
4772
4952
  command,
4773
4953
  comments,
4774
- compareFormatting,
4775
4954
  createBasicExtensions,
4776
4955
  createComment,
4777
4956
  createDataExtensions,
@@ -4788,14 +4967,18 @@ export {
4788
4967
  editorMode,
4789
4968
  editorWithToolbarLayout,
4790
4969
  focusComment,
4970
+ focusEvent,
4971
+ formattingEquals,
4791
4972
  formattingKeymap,
4792
4973
  getFormatting,
4793
4974
  getToken,
4794
4975
  image,
4976
+ imageUpload,
4795
4977
  insertTable,
4796
- keymap9 as keymap,
4978
+ keymap10 as keymap,
4797
4979
  linkTooltip,
4798
4980
  listener,
4981
+ localStorageStateStoreAdapter,
4799
4982
  logChanges,
4800
4983
  markdownHighlightStyle,
4801
4984
  markdownTags,
@@ -4811,6 +4994,7 @@ export {
4811
4994
  setHeading,
4812
4995
  setSelection,
4813
4996
  setStyle,
4997
+ state,
4814
4998
  table,
4815
4999
  tags2 as tags,
4816
5000
  toggleBlockquote,