@dxos/react-ui-editor 0.4.8-main.8cb190e → 0.4.8-main.932d824

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 (42) hide show
  1. package/dist/lib/browser/index.mjs +410 -256
  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 +10 -4
  9. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  10. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +3 -1
  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/index.d.ts +1 -0
  15. package/dist/types/src/extensions/index.d.ts.map +1 -1
  16. package/dist/types/src/extensions/markdown/formatting.d.ts +4 -1
  17. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  18. package/dist/types/src/extensions/markdown/image.d.ts +6 -0
  19. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  20. package/dist/types/src/extensions/modes.d.ts +1 -0
  21. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  22. package/dist/types/src/extensions/state.d.ts +20 -0
  23. package/dist/types/src/extensions/state.d.ts.map +1 -0
  24. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
  25. package/dist/types/src/hooks/useTextEditor.stories.d.ts +1 -0
  26. package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
  27. package/dist/types/src/translations.d.ts +1 -0
  28. package/dist/types/src/translations.d.ts.map +1 -1
  29. package/package.json +25 -24
  30. package/src/components/TextEditor/TextEditor.stories.tsx +16 -4
  31. package/src/components/TextEditor/TextEditor.tsx +11 -10
  32. package/src/components/Toolbar/Toolbar.stories.tsx +3 -2
  33. package/src/components/Toolbar/Toolbar.tsx +86 -32
  34. package/src/extensions/index.ts +1 -0
  35. package/src/extensions/markdown/formatting.test.ts +13 -13
  36. package/src/extensions/markdown/formatting.ts +80 -76
  37. package/src/extensions/markdown/image.ts +6 -0
  38. package/src/extensions/modes.ts +3 -1
  39. package/src/extensions/state.ts +90 -0
  40. package/src/hooks/useActionHandler.ts +5 -1
  41. package/src/hooks/useTextEditor.stories.tsx +3 -3
  42. 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
  }
@@ -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,
@@ -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
  }
@@ -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,17 @@ var getFormatting = (state) => {
3864
3865
  };
3865
3866
  };
3866
3867
  var useFormattingState = () => {
3867
- const [state, setState] = useState(null);
3868
+ const [state2, setState] = useState(null);
3868
3869
  const observer = useMemo(() => EditorView13.updateListener.of((update2) => {
3869
3870
  if (update2.docChanged || update2.selectionSet) {
3870
3871
  const newState = getFormatting(update2.state);
3871
- if (!state || !compareFormatting(state, newState)) {
3872
+ if (!state2 || !compareFormatting(state2, newState)) {
3872
3873
  setState(newState);
3873
3874
  }
3874
3875
  }
3875
3876
  }), []);
3876
3877
  return [
3877
- state,
3878
+ state2,
3878
3879
  observer
3879
3880
  ];
3880
3881
  };
@@ -3885,8 +3886,8 @@ import { StateField as StateField5 } from "@codemirror/state";
3885
3886
  import { Decoration as Decoration6, EditorView as EditorView14, WidgetType as WidgetType4 } from "@codemirror/view";
3886
3887
  var image = (options = {}) => {
3887
3888
  return StateField5.define({
3888
- create: (state) => {
3889
- return Decoration6.set(buildDecorations2(0, state.doc.length, state));
3889
+ create: (state2) => {
3890
+ return Decoration6.set(buildDecorations2(0, state2.doc.length, state2));
3890
3891
  },
3891
3892
  update: (value, tr) => {
3892
3893
  if (!tr.docChanged && !tr.selection) {
@@ -3920,16 +3921,16 @@ var preloadImage = (url) => {
3920
3921
  preloaded.add(url);
3921
3922
  }
3922
3923
  };
3923
- var buildDecorations2 = (from, to, state) => {
3924
+ var buildDecorations2 = (from, to, state2) => {
3924
3925
  const decorations = [];
3925
- const cursor = state.selection.main.head;
3926
- syntaxTree3(state).iterate({
3926
+ const cursor = state2.selection.main.head;
3927
+ syntaxTree3(state2).iterate({
3927
3928
  enter: (node) => {
3928
3929
  if (node.name === "Image") {
3929
3930
  const urlNode = node.node.getChild("URL");
3930
3931
  if (urlNode) {
3931
- const hide2 = state.readOnly || cursor < node.from || cursor > node.to;
3932
- const url = state.sliceDoc(urlNode.from, urlNode.to);
3932
+ const hide2 = state2.readOnly || cursor < node.from || cursor > node.to;
3933
+ const url = state2.sliceDoc(urlNode.from, urlNode.to);
3933
3934
  preloadImage(url);
3934
3935
  decorations.push(Decoration6.replace({
3935
3936
  block: true,
@@ -3959,6 +3960,8 @@ var ImageWidget = class extends WidgetType4 {
3959
3960
  return img;
3960
3961
  }
3961
3962
  };
3963
+ var imageUpload = (options = {}) => {
3964
+ };
3962
3965
 
3963
3966
  // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
3964
3967
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
@@ -4000,21 +4003,21 @@ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField6 } from "
4000
4003
  import { Decoration as Decoration7, EditorView as EditorView15, WidgetType as WidgetType5 } from "@codemirror/view";
4001
4004
  var table = (options = {}) => {
4002
4005
  return StateField6.define({
4003
- create: (state) => update(state, options),
4006
+ create: (state2) => update(state2, options),
4004
4007
  update: (_, tr) => update(tr.state, options),
4005
4008
  provide: (field) => EditorView15.decorations.from(field)
4006
4009
  });
4007
4010
  };
4008
- var update = (state, options) => {
4011
+ var update = (state2, options) => {
4009
4012
  const builder = new RangeSetBuilder3();
4010
- const cursor = state.selection.main.head;
4013
+ const cursor = state2.selection.main.head;
4011
4014
  const tables = [];
4012
4015
  const getTable = () => tables[tables.length - 1];
4013
4016
  const getRow = () => {
4014
4017
  const table2 = getTable();
4015
4018
  return table2.rows?.[table2.rows.length - 1];
4016
4019
  };
4017
- syntaxTree5(state).iterate({
4020
+ syntaxTree5(state2).iterate({
4018
4021
  enter: (node) => {
4019
4022
  switch (node.name) {
4020
4023
  case "Table": {
@@ -4035,9 +4038,9 @@ var update = (state, options) => {
4035
4038
  case "TableCell": {
4036
4039
  const row = getRow();
4037
4040
  if (row) {
4038
- row.push(state.sliceDoc(node.from, node.to));
4041
+ row.push(state2.sliceDoc(node.from, node.to));
4039
4042
  } else {
4040
- getTable().header?.push(state.sliceDoc(node.from, node.to));
4043
+ getTable().header?.push(state2.sliceDoc(node.from, node.to));
4041
4044
  }
4042
4045
  break;
4043
4046
  }
@@ -4045,7 +4048,7 @@ var update = (state, options) => {
4045
4048
  }
4046
4049
  });
4047
4050
  tables.forEach((table2) => {
4048
- const hide2 = state.readOnly || cursor < table2.from || cursor > table2.to;
4051
+ const hide2 = state2.readOnly || cursor < table2.from || cursor > table2.to;
4049
4052
  if (hide2) {
4050
4053
  builder.add(table2.from, table2.to, Decoration7.replace({
4051
4054
  widget: new TableWidget(table2)
@@ -4136,6 +4139,7 @@ var mention = ({ onSearch }) => {
4136
4139
  import { Facet as Facet6 } from "@codemirror/state";
4137
4140
  import { keymap as keymap7 } from "@codemirror/view";
4138
4141
  import { vim } from "@replit/codemirror-vim";
4142
+ var focusEvent = "focus.container";
4139
4143
  var editorMode = Facet6.define({
4140
4144
  combine: (modes) => modes[0] ?? {}
4141
4145
  });
@@ -4152,7 +4156,7 @@ var EditorModes = {
4152
4156
  key: "Alt-Escape",
4153
4157
  run: (view) => {
4154
4158
  view.dispatch({
4155
- userEvent: "focus.container"
4159
+ userEvent: focusEvent
4156
4160
  });
4157
4161
  return true;
4158
4162
  }
@@ -4161,8 +4165,95 @@ var EditorModes = {
4161
4165
  ]
4162
4166
  };
4163
4167
 
4168
+ // packages/ui/react-ui-editor/src/extensions/state.ts
4169
+ import { Transaction as Transaction2 } from "@codemirror/state";
4170
+ import { EditorView as EditorView16, keymap as keymap8 } from "@codemirror/view";
4171
+ import { debounce as debounce2 } from "@dxos/async";
4172
+ import { invariant as invariant4 } from "@dxos/invariant";
4173
+ import { isNotFalsy as isNotFalsy3 } from "@dxos/util";
4174
+ var __dxlog_file11 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/state.ts";
4175
+ var scrollAnnotation = "dxos.org/cm/scrolling";
4176
+ var keyPrefix = "dxos.org/react-ui-editor/state";
4177
+ var localStorageStateStoreAdapter = {
4178
+ setState: (id, state2) => {
4179
+ invariant4(id, void 0, {
4180
+ F: __dxlog_file11,
4181
+ L: 35,
4182
+ S: void 0,
4183
+ A: [
4184
+ "id",
4185
+ ""
4186
+ ]
4187
+ });
4188
+ localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state2));
4189
+ },
4190
+ getState: (id) => {
4191
+ invariant4(id, void 0, {
4192
+ F: __dxlog_file11,
4193
+ L: 39,
4194
+ S: void 0,
4195
+ A: [
4196
+ "id",
4197
+ ""
4198
+ ]
4199
+ });
4200
+ const state2 = localStorage.getItem(`${keyPrefix}/${id}`);
4201
+ return state2 ? JSON.parse(state2) : void 0;
4202
+ }
4203
+ };
4204
+ var state = ({ getState, setState } = {}) => {
4205
+ const setStateDebounced = debounce2(setState, 1e3);
4206
+ return [
4207
+ // TODO(burdon): Track scrolling (currently only updates when cursor moves).
4208
+ EditorView16.updateListener.of(({ view, changes, transactions }) => {
4209
+ const id = view.state.facet(documentId2);
4210
+ if (!id || transactions.some((tr) => tr.isUserEvent(scrollAnnotation))) {
4211
+ return;
4212
+ }
4213
+ if (setState) {
4214
+ const { top } = view.dom.getBoundingClientRect();
4215
+ const pos = view.posAtCoords({
4216
+ x: 0,
4217
+ y: top
4218
+ });
4219
+ if (pos !== null) {
4220
+ const { anchor, head } = view.state.selection.main;
4221
+ setStateDebounced(id, {
4222
+ scrollTo: {
4223
+ from: pos,
4224
+ yMargin: 0
4225
+ },
4226
+ selection: {
4227
+ anchor,
4228
+ head
4229
+ }
4230
+ });
4231
+ }
4232
+ }
4233
+ }),
4234
+ getState && keymap8.of([
4235
+ {
4236
+ key: "ctrl-r",
4237
+ run: (view) => {
4238
+ const state2 = getState(view.state.facet(documentId2));
4239
+ if (state2) {
4240
+ view.dispatch({
4241
+ effects: EditorView16.scrollIntoView(state2.scrollTo.from, {
4242
+ yMargin: 0
4243
+ }),
4244
+ selection: state2.selection,
4245
+ annotations: Transaction2.userEvent.of(scrollAnnotation)
4246
+ });
4247
+ }
4248
+ return true;
4249
+ }
4250
+ }
4251
+ ])
4252
+ ].filter(isNotFalsy3);
4253
+ };
4254
+
4164
4255
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
4165
- import { keymap as keymap8 } from "@codemirror/view";
4256
+ import { keymap as keymap9 } from "@codemirror/view";
4166
4257
  var defaultItems = [
4167
4258
  "hello world!",
4168
4259
  "this is a test.",
@@ -4172,7 +4263,7 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
4172
4263
  let t;
4173
4264
  let idx = 0;
4174
4265
  return [
4175
- keymap8.of([
4266
+ keymap9.of([
4176
4267
  {
4177
4268
  // Reset.
4178
4269
  key: "alt-meta-'",
@@ -4219,9 +4310,11 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
4219
4310
  };
4220
4311
 
4221
4312
  // 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";
4313
+ var __dxlog_file12 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
4223
4314
  var instanceCount = 0;
4224
- var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, className, autoFocus, scrollTo = EditorView16.scrollIntoView(0), moveToEndOfLine, debug, dataTestId }, forwardedRef) => {
4315
+ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, className, autoFocus, scrollTo = EditorView17.scrollIntoView(0, {
4316
+ yMargin: 0
4317
+ }), moveToEndOfLine, debug, dataTestId }, forwardedRef) => {
4225
4318
  const [instanceId] = useState2(() => `text-editor-${++instanceCount}`);
4226
4319
  const tabsterDOMAttribute = useFocusableGroup({
4227
4320
  tabBehavior: "limited"
@@ -4244,38 +4337,38 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4244
4337
  id,
4245
4338
  instanceId
4246
4339
  }, {
4247
- F: __dxlog_file11,
4340
+ F: __dxlog_file12,
4248
4341
  L: 88,
4249
4342
  S: void 0,
4250
4343
  C: (f, a) => f(...a)
4251
4344
  });
4252
- const state = EditorState2.create({
4345
+ const state2 = EditorState2.create({
4253
4346
  doc,
4254
4347
  selection,
4255
4348
  extensions: [
4256
4349
  id && documentId2.of(id),
4257
4350
  // TODO(burdon): NOTE: Doesn't catch errors in keymap functions.
4258
- EditorView16.exceptionSink.of((err) => {
4351
+ EditorView17.exceptionSink.of((err) => {
4259
4352
  log9.catch(err, void 0, {
4260
- F: __dxlog_file11,
4353
+ F: __dxlog_file12,
4261
4354
  L: 101,
4262
4355
  S: void 0,
4263
4356
  C: (f, a) => f(...a)
4264
4357
  });
4265
4358
  }),
4266
4359
  // Focus.
4267
- EditorView16.updateListener.of((update2) => {
4360
+ EditorView17.updateListener.of((update2) => {
4268
4361
  update2.transactions.forEach((transaction) => {
4269
- if (transaction.isUserEvent("focus.container")) {
4362
+ if (transaction.isUserEvent(focusEvent)) {
4270
4363
  rootRef.current?.focus();
4271
4364
  }
4272
4365
  });
4273
4366
  }),
4274
4367
  extensions
4275
- ].filter(isNotFalsy3)
4368
+ ].filter(isNotFalsy4)
4276
4369
  });
4277
- const view2 = new EditorView16({
4278
- state,
4370
+ const view2 = new EditorView17({
4371
+ state: state2,
4279
4372
  parent: rootRef.current,
4280
4373
  scrollTo,
4281
4374
  // NOTE: Uncomment to debug/monitor all transactions.
@@ -4287,18 +4380,18 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4287
4380
  view3.update(trs);
4288
4381
  }
4289
4382
  });
4290
- setView(view2);
4291
- if (moveToEndOfLine) {
4383
+ if (moveToEndOfLine && !(scrollTo || selection)) {
4292
4384
  const { to } = view2.state.doc.lineAt(0);
4293
- view2?.dispatch({
4385
+ view2.dispatch({
4294
4386
  selection: {
4295
4387
  anchor: to
4296
4388
  }
4297
4389
  });
4298
4390
  }
4299
- if (state.facet(editorMode).noTabster) {
4391
+ if (state2.facet(editorMode).noTabster) {
4300
4392
  rootRef.current?.removeAttribute("data-tabster");
4301
4393
  }
4394
+ setView(view2);
4302
4395
  return () => {
4303
4396
  view2?.destroy();
4304
4397
  setView(null);
@@ -4331,9 +4424,10 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4331
4424
  });
4332
4425
 
4333
4426
  // 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";
4427
+ import { ChatText, Code, CodeBlock, Image, Link, ListBullets, ListChecks, ListNumbers, Paragraph, TextStrikethrough, Table as Table2, TextB, TextHOne, TextHTwo, TextHThree, TextHFour, TextHFive, TextHSix, TextItalic, Quotes } from "@phosphor-icons/react";
4335
4428
  import { createContext } from "@radix-ui/react-context";
4336
- import React2, { useRef as useRef2, useState as useState3 } from "react";
4429
+ import React2, { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
4430
+ import { useDropzone } from "react-dropzone";
4337
4431
  import { DensityProvider, ElevationProvider, Select, Toolbar as NaturalToolbar, Tooltip, useTranslation } from "@dxos/react-ui";
4338
4432
  import { getSize } from "@dxos/react-ui-theme";
4339
4433
  var tooltipProps = {
@@ -4350,10 +4444,10 @@ var HeadingIcons = {
4350
4444
  "6": TextHSix
4351
4445
  };
4352
4446
  var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
4353
- var ToolbarRoot = ({ children, onAction, classNames, state }) => {
4447
+ var ToolbarRoot = ({ children, onAction, classNames, state: state2 }) => {
4354
4448
  return /* @__PURE__ */ React2.createElement(ToolbarContextProvider, {
4355
4449
  onAction,
4356
- state
4450
+ state: state2
4357
4451
  }, /* @__PURE__ */ React2.createElement(DensityProvider, {
4358
4452
  density: "fine"
4359
4453
  }, /* @__PURE__ */ React2.createElement(ElevationProvider, {
@@ -4367,7 +4461,7 @@ var ToolbarRoot = ({ children, onAction, classNames, state }) => {
4367
4461
  };
4368
4462
  var buttonStyles = "min-bs-0 p-2";
4369
4463
  var iconStyles = getSize(5);
4370
- var ToolbarButton = ({ Icon, children, ...props }) => {
4464
+ var ToolbarToggleButton = ({ Icon, children, ...props }) => {
4371
4465
  return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4372
4466
  asChild: true
4373
4467
  }, /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroupItem, {
@@ -4380,14 +4474,27 @@ var ToolbarButton = ({ Icon, children, ...props }) => {
4380
4474
  className: "sr-only"
4381
4475
  }, children))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, tooltipProps, children, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
4382
4476
  };
4477
+ var ToolbarButton = ({ Icon, children, ...props }) => {
4478
+ return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4479
+ asChild: true
4480
+ }, /* @__PURE__ */ React2.createElement(NaturalToolbar.Button, {
4481
+ variant: "ghost",
4482
+ ...props,
4483
+ classNames: buttonStyles
4484
+ }, /* @__PURE__ */ React2.createElement(Icon, {
4485
+ className: iconStyles
4486
+ }), /* @__PURE__ */ React2.createElement("span", {
4487
+ className: "sr-only"
4488
+ }, children))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, tooltipProps, children, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
4489
+ };
4383
4490
  var ToolbarSeparator = () => /* @__PURE__ */ React2.createElement("div", {
4384
4491
  role: "separator",
4385
4492
  className: "grow"
4386
4493
  });
4387
4494
  var MarkdownHeading = () => {
4388
4495
  const { t } = useTranslation(translationKey);
4389
- const { onAction, state } = useToolbarContext("MarkdownFormatting");
4390
- const blockType = state ? state.blockType : "paragraph";
4496
+ const { onAction, state: state2 } = useToolbarContext("MarkdownFormatting");
4497
+ const blockType = state2 ? state2.blockType : "paragraph";
4391
4498
  const header = blockType && /heading(\d)/.exec(blockType);
4392
4499
  const value = header ? header[1] : blockType === "paragraph" || !blockType ? "0" : void 0;
4393
4500
  const HeadingIcon = HeadingIcons[value ?? "0"];
@@ -4461,43 +4568,43 @@ var markdownStyles = [
4461
4568
  {
4462
4569
  type: "strong",
4463
4570
  Icon: TextB,
4464
- getState: (state) => state.strong
4571
+ getState: (state2) => state2.strong
4465
4572
  },
4466
4573
  {
4467
4574
  type: "emphasis",
4468
4575
  Icon: TextItalic,
4469
- getState: (state) => state.emphasis
4576
+ getState: (state2) => state2.emphasis
4470
4577
  },
4471
4578
  {
4472
4579
  type: "strikethrough",
4473
4580
  Icon: TextStrikethrough,
4474
- getState: (state) => state.strikethrough
4581
+ getState: (state2) => state2.strikethrough
4475
4582
  },
4476
4583
  {
4477
4584
  type: "code",
4478
4585
  Icon: Code,
4479
- getState: (state) => state.code
4586
+ getState: (state2) => state2.code
4480
4587
  },
4481
4588
  {
4482
4589
  type: "link",
4483
4590
  Icon: Link,
4484
- getState: (state) => state.link
4591
+ getState: (state2) => state2.link
4485
4592
  }
4486
4593
  ];
4487
4594
  var MarkdownStyles = () => {
4488
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4595
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4489
4596
  const { t } = useTranslation(translationKey);
4490
4597
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4491
4598
  type: "multiple",
4492
- value: markdownStyles.filter(({ getState }) => state && getState(state)).map(({ type }) => type)
4493
- }, markdownStyles.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4599
+ value: markdownStyles.filter(({ getState }) => state2 && getState(state2)).map(({ type }) => type)
4600
+ }, markdownStyles.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4494
4601
  key: type,
4495
4602
  value: type,
4496
4603
  Icon,
4497
- disabled: state?.blockType === "codeblock",
4498
- onClick: state ? () => onAction?.({
4604
+ disabled: state2?.blockType === "codeblock",
4605
+ onClick: state2 ? () => onAction?.({
4499
4606
  type,
4500
- data: !getState(state)
4607
+ data: !getState(state2)
4501
4608
  }) : void 0
4502
4609
  }, t(`${type} label`))));
4503
4610
  };
@@ -4505,32 +4612,32 @@ var markdownLists = [
4505
4612
  {
4506
4613
  type: "list-bullet",
4507
4614
  Icon: ListBullets,
4508
- getState: (state) => state.listStyle === "bullet"
4615
+ getState: (state2) => state2.listStyle === "bullet"
4509
4616
  },
4510
4617
  {
4511
4618
  type: "list-ordered",
4512
4619
  Icon: ListNumbers,
4513
- getState: (state) => state.listStyle === "ordered"
4620
+ getState: (state2) => state2.listStyle === "ordered"
4514
4621
  },
4515
4622
  {
4516
4623
  type: "list-task",
4517
4624
  Icon: ListChecks,
4518
- getState: (state) => state.listStyle === "task"
4625
+ getState: (state2) => state2.listStyle === "task"
4519
4626
  }
4520
4627
  ];
4521
4628
  var MarkdownLists = () => {
4522
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4629
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4523
4630
  const { t } = useTranslation(translationKey);
4524
4631
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4525
4632
  type: "single",
4526
- value: state?.listStyle ? `list-${state.listStyle}` : ""
4527
- }, markdownLists.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4633
+ value: state2?.listStyle ? `list-${state2.listStyle}` : ""
4634
+ }, markdownLists.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4528
4635
  key: type,
4529
4636
  value: type,
4530
4637
  Icon,
4531
- onClick: state ? () => onAction?.({
4638
+ onClick: state2 ? () => onAction?.({
4532
4639
  type,
4533
- data: !getState(state)
4640
+ data: !getState(state2)
4534
4641
  }) : void 0
4535
4642
  }, t(`${type} label`))));
4536
4643
  };
@@ -4538,63 +4645,100 @@ var markdownBlocks = [
4538
4645
  {
4539
4646
  type: "blockquote",
4540
4647
  Icon: Quotes,
4541
- getState: (state) => state.blockQuote
4648
+ getState: (state2) => state2.blockQuote
4542
4649
  },
4543
4650
  {
4544
4651
  type: "codeblock",
4545
4652
  Icon: CodeBlock,
4546
- getState: (state) => state.blockType === "codeblock"
4653
+ getState: (state2) => state2.blockType === "codeblock"
4547
4654
  },
4548
4655
  {
4549
4656
  type: "table",
4550
4657
  Icon: Table2,
4551
- getState: (state) => state.blockType === "tablecell",
4552
- disabled: (state) => !state.blankLine
4658
+ getState: (state2) => state2.blockType === "tablecell",
4659
+ disabled: (state2) => !state2.blankLine
4553
4660
  }
4554
4661
  ];
4555
4662
  var MarkdownBlocks = () => {
4556
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4663
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4557
4664
  const { t } = useTranslation(translationKey);
4558
- const value = markdownBlocks.find(({ getState }) => state && getState(state));
4665
+ const value = markdownBlocks.find(({ getState }) => state2 && getState(state2));
4559
4666
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4560
4667
  type: "single",
4561
4668
  value: value?.type ?? ""
4562
- }, markdownBlocks.map(({ type, disabled, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4669
+ }, markdownBlocks.map(({ type, disabled, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4563
4670
  key: type,
4564
4671
  value: type,
4565
4672
  Icon,
4566
- disabled: !state || disabled?.(state),
4567
- onClick: state ? () => onAction?.({
4673
+ disabled: !state2 || disabled?.(state2),
4674
+ onClick: state2 ? () => onAction?.({
4568
4675
  type,
4569
- data: !getState(state)
4676
+ data: !getState(state2)
4570
4677
  }) : void 0
4571
4678
  }, t(`${type} label`))));
4572
4679
  };
4573
4680
  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 = () => {
4681
+ var MarkdownCustom = ({ onUpload } = {}) => {
4575
4682
  const { onAction } = useToolbarContext("MarkdownStyles");
4576
4683
  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",
4581
- "data-testid": "editor.toolbar.comment",
4684
+ const { acceptedFiles, getInputProps, open } = useDropzone({
4685
+ multiple: false,
4686
+ noDrag: true,
4687
+ accept: {
4688
+ "image/*": [
4689
+ ".jpg",
4690
+ ".jpeg",
4691
+ ".png",
4692
+ ".gif"
4693
+ ]
4694
+ }
4695
+ });
4696
+ useEffect3(() => {
4697
+ if (onUpload && acceptedFiles.length) {
4698
+ setTimeout(async () => {
4699
+ const f = acceptedFiles[0];
4700
+ const file = new File([
4701
+ f
4702
+ ], f.name, {
4703
+ type: f.type,
4704
+ lastModified: f.lastModified
4705
+ });
4706
+ const { url } = await onUpload(file);
4707
+ if (url) {
4708
+ onAction?.({
4709
+ type: "image",
4710
+ data: url
4711
+ });
4712
+ }
4713
+ });
4714
+ }
4715
+ }, [
4716
+ acceptedFiles
4717
+ ]);
4718
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("input", getInputProps()), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4719
+ value: "image",
4720
+ Icon: Image,
4721
+ onClick: () => open()
4722
+ }, t("image label")));
4723
+ };
4724
+ var MarkdownActions = () => {
4725
+ const { onAction } = useToolbarContext("MarkdownStyles");
4726
+ const { t } = useTranslation(translationKey);
4727
+ return /* @__PURE__ */ React2.createElement(ToolbarButton, {
4728
+ value: "comment",
4729
+ Icon: ChatText,
4582
4730
  onClick: () => onAction?.({
4583
4731
  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))));
4732
+ })
4733
+ }, t("comment label"));
4591
4734
  };
4592
4735
  var Toolbar = {
4593
4736
  Root: ToolbarRoot,
4594
- Button: ToolbarButton,
4737
+ Button: ToolbarToggleButton,
4595
4738
  Separator: ToolbarSeparator,
4596
4739
  Markdown: MarkdownStandard,
4597
- Extended: MarkdownExtended
4740
+ Custom: MarkdownCustom,
4741
+ Actions: MarkdownActions
4598
4742
  };
4599
4743
 
4600
4744
  // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
@@ -4631,7 +4775,13 @@ var useActionHandler = (view) => {
4631
4775
  insertTable(view);
4632
4776
  break;
4633
4777
  case "link":
4634
- (action.data === false ? removeLink : addLink)(view);
4778
+ (action.data === false ? removeLink : addLink())(view);
4779
+ break;
4780
+ case "image":
4781
+ addLink({
4782
+ url: action.data,
4783
+ image: true
4784
+ })(view);
4635
4785
  break;
4636
4786
  case "comment":
4637
4787
  createComment(view);
@@ -4660,43 +4810,43 @@ var useDocAccessor = (text) => {
4660
4810
 
4661
4811
  // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
4662
4812
  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";
4813
+ import { EditorView as EditorView18 } from "@codemirror/view";
4814
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
4665
4815
  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";
4816
+ import { isNotFalsy as isNotFalsy5 } from "@dxos/util";
4817
+ var __dxlog_file13 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
4668
4818
  var useTextEditor = (cb = () => ({}), deps = []) => {
4669
4819
  let { id, doc, selection, extensions, autoFocus, scrollTo, debug } = useMemo3(cb, deps ?? []);
4670
4820
  const onUpdate = useRef3();
4671
4821
  const [view, setView] = useState4();
4672
4822
  const parentRef = useRef3(null);
4673
- useEffect3(() => {
4823
+ useEffect4(() => {
4674
4824
  let view2;
4675
4825
  if (parentRef.current) {
4676
- const state = EditorState3.create({
4826
+ const state2 = EditorState3.create({
4677
4827
  doc,
4678
4828
  selection,
4679
4829
  extensions: [
4680
4830
  id && documentId2.of(id),
4681
4831
  // TODO(burdon): Doesn't catch errors in keymap functions.
4682
- EditorView17.exceptionSink.of((err) => {
4832
+ EditorView18.exceptionSink.of((err) => {
4683
4833
  log10.catch(err, void 0, {
4684
- F: __dxlog_file12,
4834
+ F: __dxlog_file13,
4685
4835
  L: 44,
4686
4836
  S: void 0,
4687
4837
  C: (f, a) => f(...a)
4688
4838
  });
4689
4839
  }),
4690
4840
  extensions,
4691
- EditorView17.updateListener.of(() => {
4841
+ EditorView18.updateListener.of(() => {
4692
4842
  onUpdate.current?.();
4693
4843
  })
4694
- ].filter(isNotFalsy4)
4844
+ ].filter(isNotFalsy5)
4695
4845
  });
4696
- view2 = new EditorView17({
4846
+ view2 = new EditorView18({
4697
4847
  parent: parentRef.current,
4698
4848
  scrollTo,
4699
- state,
4849
+ state: state2,
4700
4850
  // NOTE: Uncomment to debug/monitor all transactions.
4701
4851
  // https://codemirror.net/docs/ref/#view.EditorView.dispatch
4702
4852
  dispatchTransactions: (trs, view3) => {
@@ -4712,7 +4862,7 @@ var useTextEditor = (cb = () => ({}), deps = []) => {
4712
4862
  view2?.destroy();
4713
4863
  };
4714
4864
  }, deps);
4715
- useEffect3(() => {
4865
+ useEffect4(() => {
4716
4866
  if (view) {
4717
4867
  if (!selection && !view.state.selection.main.anchor) {
4718
4868
  selection = EditorSelection2.single(view.state.doc.line(1).to);
@@ -4788,14 +4938,17 @@ export {
4788
4938
  editorMode,
4789
4939
  editorWithToolbarLayout,
4790
4940
  focusComment,
4941
+ focusEvent,
4791
4942
  formattingKeymap,
4792
4943
  getFormatting,
4793
4944
  getToken,
4794
4945
  image,
4946
+ imageUpload,
4795
4947
  insertTable,
4796
- keymap9 as keymap,
4948
+ keymap10 as keymap,
4797
4949
  linkTooltip,
4798
4950
  listener,
4951
+ localStorageStateStoreAdapter,
4799
4952
  logChanges,
4800
4953
  markdownHighlightStyle,
4801
4954
  markdownTags,
@@ -4811,6 +4964,7 @@ export {
4811
4964
  setHeading,
4812
4965
  setSelection,
4813
4966
  setStyle,
4967
+ state,
4814
4968
  table,
4815
4969
  tags2 as tags,
4816
4970
  toggleBlockquote,