@dxos/react-ui-editor 0.4.8-main.6d7437e → 0.4.8-main.7d8f6a4

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 (53) hide show
  1. package/dist/lib/browser/index.mjs +457 -276
  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/factories.d.ts.map +1 -1
  16. package/dist/types/src/extensions/index.d.ts +1 -0
  17. package/dist/types/src/extensions/index.d.ts.map +1 -1
  18. package/dist/types/src/extensions/markdown/formatting.d.ts +7 -4
  19. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  20. package/dist/types/src/extensions/markdown/image.d.ts +6 -0
  21. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  22. package/dist/types/src/extensions/modes.d.ts +1 -0
  23. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  24. package/dist/types/src/extensions/state.d.ts +20 -0
  25. package/dist/types/src/extensions/state.d.ts.map +1 -0
  26. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
  27. package/dist/types/src/hooks/useDocAccessor.d.ts +4 -2
  28. package/dist/types/src/hooks/useDocAccessor.d.ts.map +1 -1
  29. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  30. package/dist/types/src/hooks/useTextEditor.stories.d.ts +1 -0
  31. package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
  32. package/dist/types/src/translations.d.ts +1 -0
  33. package/dist/types/src/translations.d.ts.map +1 -1
  34. package/package.json +25 -27
  35. package/src/components/TextEditor/TextEditor.stories.tsx +16 -4
  36. package/src/components/TextEditor/TextEditor.tsx +13 -12
  37. package/src/components/Toolbar/Toolbar.stories.tsx +23 -24
  38. package/src/components/Toolbar/Toolbar.tsx +101 -35
  39. package/src/extensions/automerge/automerge.ts +4 -4
  40. package/src/extensions/comments.ts +2 -2
  41. package/src/extensions/factories.ts +0 -3
  42. package/src/extensions/index.ts +1 -0
  43. package/src/extensions/markdown/decorate.ts +1 -1
  44. package/src/extensions/markdown/formatting.test.ts +13 -13
  45. package/src/extensions/markdown/formatting.ts +95 -87
  46. package/src/extensions/markdown/image.ts +6 -0
  47. package/src/extensions/modes.ts +3 -1
  48. package/src/extensions/state.ts +90 -0
  49. package/src/hooks/useActionHandler.ts +5 -1
  50. package/src/hooks/useDocAccessor.ts +8 -2
  51. package/src/hooks/useTextEditor.stories.tsx +3 -3
  52. package/src/hooks/useTextEditor.ts +3 -0
  53. 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;
@@ -2233,9 +2234,6 @@ var createBasicExtensions = (_props) => {
2233
2234
  var defaultSlots = {
2234
2235
  editor: {
2235
2236
  className: "w-full bs-full"
2236
- },
2237
- content: {
2238
- className: "!p-2"
2239
2237
  }
2240
2238
  };
2241
2239
  var createThemeExtensions = ({ theme, themeMode, slots: _slots } = {}) => {
@@ -2563,7 +2561,7 @@ var CheckboxWidget = class extends WidgetType3 {
2563
2561
  }
2564
2562
  toDOM(view) {
2565
2563
  const input = document.createElement("input");
2566
- input.className = "cm-task-checkbox";
2564
+ input.className = "cm-task-checkbox ch-checkbox ch-focus-ring -mbs-0.5";
2567
2565
  input.type = "checkbox";
2568
2566
  input.checked = this._checked;
2569
2567
  if (view.state.readOnly) {
@@ -2609,8 +2607,8 @@ var checkedTask = Decoration5.replace({
2609
2607
  var uncheckedTask = Decoration5.replace({
2610
2608
  widget: new CheckboxWidget(false)
2611
2609
  });
2612
- var editingRange = (state, range, focus) => {
2613
- const { readOnly, selection: { main: { head } } } = state;
2610
+ var editingRange = (state2, range, focus) => {
2611
+ const { readOnly, selection: { main: { head } } } = state2;
2614
2612
  return focus && !readOnly && head >= range.from && head <= range.to;
2615
2613
  };
2616
2614
  var MarksByParent = /* @__PURE__ */ new Set([
@@ -2623,14 +2621,14 @@ var MarksByParent = /* @__PURE__ */ new Set([
2623
2621
  var buildDecorations = (view, options, focus) => {
2624
2622
  const deco = new RangeSetBuilder2();
2625
2623
  const atomicDeco = new RangeSetBuilder2();
2626
- const { state } = view;
2624
+ const { state: state2 } = view;
2627
2625
  for (const { from, to } of view.visibleRanges) {
2628
- syntaxTree(state).iterate({
2626
+ syntaxTree(state2).iterate({
2629
2627
  from,
2630
2628
  to,
2631
2629
  enter: (node) => {
2632
2630
  if (node.name === "FencedCode") {
2633
- const editing = editingRange(state, node, focus);
2631
+ const editing = editingRange(state2, node, focus);
2634
2632
  for (const block of view.viewportLineBlocks) {
2635
2633
  if (block.to < node.from) {
2636
2634
  continue;
@@ -2639,7 +2637,7 @@ var buildDecorations = (view, options, focus) => {
2639
2637
  break;
2640
2638
  }
2641
2639
  const first = block.from <= node.from;
2642
- 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));
2643
2641
  deco.add(block.from, block.from, first ? fencedCodeLineFirst : last ? fencedCodeLineLast : fencedCodeLine);
2644
2642
  if (!editing && (first || last)) {
2645
2643
  atomicDeco.add(block.from, block.to, hide);
@@ -2649,9 +2647,9 @@ var buildDecorations = (view, options, focus) => {
2649
2647
  } else if (node.name === "Link") {
2650
2648
  const marks = node.node.getChildren("LinkMark");
2651
2649
  const urlNode = node.node.getChild("URL");
2652
- const editing = editingRange(state, node, focus);
2650
+ const editing = editingRange(state2, node, focus);
2653
2651
  if (urlNode && marks.length >= 2) {
2654
- const url = state.sliceDoc(urlNode.from, urlNode.to);
2652
+ const url = state2.sliceDoc(urlNode.from, urlNode.to);
2655
2653
  if (!editing) {
2656
2654
  atomicDeco.add(node.from, marks[0].to, hide);
2657
2655
  }
@@ -2672,24 +2670,24 @@ var buildDecorations = (view, options, focus) => {
2672
2670
  }
2673
2671
  } else if (node.name === "HeaderMark") {
2674
2672
  const parent = node.node.parent;
2675
- if (/^ATX/.test(parent.name) && !editingRange(state, state.doc.lineAt(node.from), focus)) {
2676
- 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);
2677
2675
  atomicDeco.add(node.from, node.to + (next === " " ? 1 : 0), hide);
2678
2676
  }
2679
2677
  } else if (node.name === "HorizontalRule") {
2680
- if (!editingRange(state, node, focus)) {
2678
+ if (!editingRange(state2, node, focus)) {
2681
2679
  deco.add(node.from, node.to, horizontalRule);
2682
2680
  }
2683
2681
  } else if (node.name === "TaskMarker") {
2684
- if (!editingRange(state, node, focus)) {
2685
- 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";
2686
2684
  atomicDeco.add(node.from - 2, node.from - 1, Decoration5.mark({
2687
2685
  class: "cm-task"
2688
2686
  }));
2689
2687
  atomicDeco.add(node.from, node.to, checked ? checkedTask : uncheckedTask);
2690
2688
  }
2691
2689
  } else if (MarksByParent.has(node.name)) {
2692
- if (!editingRange(state, node.node.parent, focus)) {
2690
+ if (!editingRange(state2, node.node.parent, focus)) {
2693
2691
  atomicDeco.add(node.from, node.to, hide);
2694
2692
  }
2695
2693
  }
@@ -2799,8 +2797,8 @@ import { snippet } from "@codemirror/autocomplete";
2799
2797
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
2800
2798
  import { EditorSelection } from "@codemirror/state";
2801
2799
  import { EditorView as EditorView13, keymap as keymap6 } from "@codemirror/view";
2802
- import { useState, useMemo } from "react";
2803
- 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;
2804
2802
  var Inline;
2805
2803
  (function(Inline2) {
2806
2804
  Inline2[Inline2["Strong"] = 0] = "Strong";
@@ -2814,13 +2812,13 @@ var List;
2814
2812
  List2[List2["Bullet"] = 1] = "Bullet";
2815
2813
  List2[List2["Task"] = 2] = "Task";
2816
2814
  })(List || (List = {}));
2817
- var setHeading = (level) => ({ state, dispatch }) => {
2818
- const { selection: { ranges }, doc } = state;
2815
+ var setHeading = (level) => ({ state: state2, dispatch }) => {
2816
+ const { selection: { ranges }, doc } = state2;
2819
2817
  const changes = [];
2820
2818
  let prevBlock = -1;
2821
2819
  for (const range of ranges) {
2822
2820
  let sawBlock = false;
2823
- syntaxTree2(state).iterate({
2821
+ syntaxTree2(state2).iterate({
2824
2822
  from: range.from,
2825
2823
  to: range.to,
2826
2824
  enter: (node) => {
@@ -2873,7 +2871,7 @@ var setHeading = (level) => ({ state, dispatch }) => {
2873
2871
  }
2874
2872
  });
2875
2873
  let line;
2876
- 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)) {
2877
2875
  changes.push({
2878
2876
  from: line.from,
2879
2877
  to: line.to,
@@ -2884,23 +2882,23 @@ var setHeading = (level) => ({ state, dispatch }) => {
2884
2882
  if (!changes.length) {
2885
2883
  return false;
2886
2884
  }
2887
- const changeSet = state.changes(changes);
2888
- dispatch(state.update({
2885
+ const changeSet = state2.changes(changes);
2886
+ dispatch(state2.update({
2889
2887
  changes: changeSet,
2890
- selection: state.selection.map(changeSet, 1),
2888
+ selection: state2.selection.map(changeSet, 1),
2891
2889
  userEvent: "format.setHeading",
2892
2890
  scrollIntoView: true
2893
2891
  }));
2894
2892
  return true;
2895
2893
  };
2896
- var setStyle = (type, enable) => ({ state, dispatch }) => {
2894
+ var setStyle = (type, enable) => ({ state: state2, dispatch }) => {
2897
2895
  const marker = inlineMarkerText(type);
2898
- const changes = state.changeByRange((range) => {
2896
+ const changes = state2.changeByRange((range) => {
2899
2897
  if (!enable && range.empty) {
2900
- const after = state.doc.sliceString(range.head, range.head + 6);
2898
+ const after = state2.doc.sliceString(range.head, range.head + 6);
2901
2899
  const found = after.indexOf(marker);
2902
2900
  if (found >= 0 && /^[*~`]*$/.test(after.slice(0, found))) {
2903
- const before = state.doc.sliceString(range.head - 6, range.head);
2901
+ const before = state2.doc.sliceString(range.head - 6, range.head);
2904
2902
  if (before.slice(before.length - found - marker.length, before.length - found) === marker && [
2905
2903
  ...before.slice(before.length - found)
2906
2904
  ].reverse().join("") === after.slice(0, found)) {
@@ -2927,14 +2925,14 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
2927
2925
  let startCovered = false;
2928
2926
  let endCovered = false;
2929
2927
  let { from, to } = range;
2930
- syntaxTree2(state).iterate({
2928
+ syntaxTree2(state2).iterate({
2931
2929
  from,
2932
2930
  to,
2933
2931
  enter: (node) => {
2934
2932
  const { name } = node;
2935
2933
  if (Object.hasOwn(Textblocks, name) && Textblocks[name] !== "codeblock") {
2936
2934
  blockStart = blockContentStart(node);
2937
- blockEnd = blockContentEnd(node, state.doc);
2935
+ blockEnd = blockContentEnd(node, state2.doc);
2938
2936
  startCovered = endCovered = false;
2939
2937
  } else if (name === "Link" || name === "Image" && enable) {
2940
2938
  if (from < node.from && to > node.from && to <= node.to) {
@@ -2979,7 +2977,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
2979
2977
  });
2980
2978
  if (markType !== type && closeStart >= to) {
2981
2979
  changesAtEnd.push({
2982
- from: skipSpaces(Math.min(to, blockEnd), state.doc, 1, blockEnd),
2980
+ from: skipSpaces(Math.min(to, blockEnd), state2.doc, 1, blockEnd),
2983
2981
  insert: inlineMarkerText(markType)
2984
2982
  });
2985
2983
  }
@@ -2991,7 +2989,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
2991
2989
  });
2992
2990
  if (markType !== type && openEnd <= from) {
2993
2991
  changes2.push({
2994
- from: skipSpaces(Math.max(from, blockStart), state.doc, -1, blockStart),
2992
+ from: skipSpaces(Math.max(from, blockStart), state2.doc, -1, blockStart),
2995
2993
  insert: inlineMarkerText(markType)
2996
2994
  });
2997
2995
  }
@@ -3021,7 +3019,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3021
3019
  changes2.push(startCovered);
3022
3020
  } else if (startCovered) {
3023
3021
  changes2.push({
3024
- from: skipSpaces(rangeStart, state.doc, -1, blockStart),
3022
+ from: skipSpaces(rangeStart, state2.doc, -1, blockStart),
3025
3023
  insert: marker
3026
3024
  });
3027
3025
  }
@@ -3029,7 +3027,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3029
3027
  changes2.push(endCovered);
3030
3028
  } else if (endCovered) {
3031
3029
  changes2.push({
3032
- from: skipSpaces(rangeEnd, state.doc, 1, blockEnd),
3030
+ from: skipSpaces(rangeEnd, state2.doc, 1, blockEnd),
3033
3031
  insert: marker
3034
3032
  });
3035
3033
  }
@@ -3037,7 +3035,7 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3037
3035
  }
3038
3036
  }
3039
3037
  });
3040
- 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)) {
3041
3039
  return {
3042
3040
  changes: {
3043
3041
  from: range.head,
@@ -3046,13 +3044,13 @@ var setStyle = (type, enable) => ({ state, dispatch }) => {
3046
3044
  range: EditorSelection.cursor(range.head + marker.length)
3047
3045
  };
3048
3046
  }
3049
- const changeSet = state.changes(changes2.concat(changesAtEnd));
3047
+ const changeSet = state2.changes(changes2.concat(changesAtEnd));
3050
3048
  return {
3051
3049
  changes: changeSet,
3052
3050
  range: range.empty && !changeSet.empty ? EditorSelection.cursor(range.head + marker.length) : EditorSelection.range(changeSet.mapPos(range.from, 1), changeSet.mapPos(range.to, -1))
3053
3051
  };
3054
3052
  });
3055
- dispatch(state.update(changes, {
3053
+ dispatch(state2.update(changes, {
3056
3054
  userEvent: enable ? "format.style.add" : "format.style.remove",
3057
3055
  scrollIntoView: true
3058
3056
  }));
@@ -3126,8 +3124,8 @@ var insertTable = (view) => {
3126
3124
  const { from } = doc.line(number);
3127
3125
  snippets.table(view, null, from, from);
3128
3126
  };
3129
- var removeLinkInner = (from, to, changes, state) => {
3130
- syntaxTree2(state).iterate({
3127
+ var removeLinkInner = (from, to, changes, state2) => {
3128
+ syntaxTree2(state2).iterate({
3131
3129
  from,
3132
3130
  to,
3133
3131
  enter: (node) => {
@@ -3141,8 +3139,8 @@ var removeLinkInner = (from, to, changes, state) => {
3141
3139
  });
3142
3140
  } else if (name === "LinkTitle" || name === "URL") {
3143
3141
  changes.push({
3144
- from: skipSpaces(node2.from, state.doc, -1),
3145
- to: skipSpaces(node2.to, state.doc, 1)
3142
+ from: skipSpaces(node2.from, state2.doc, -1),
3143
+ to: skipSpaces(node2.to, state2.doc, 1)
3146
3144
  });
3147
3145
  }
3148
3146
  });
@@ -3151,32 +3149,32 @@ var removeLinkInner = (from, to, changes, state) => {
3151
3149
  }
3152
3150
  });
3153
3151
  };
3154
- var removeLink = ({ state, dispatch }) => {
3152
+ var removeLink = ({ state: state2, dispatch }) => {
3155
3153
  const changes = [];
3156
- for (const { from, to } of state.selection.ranges) {
3157
- removeLinkInner(from, to, changes, state);
3154
+ for (const { from, to } of state2.selection.ranges) {
3155
+ removeLinkInner(from, to, changes, state2);
3158
3156
  }
3159
3157
  if (!changes) {
3160
3158
  return false;
3161
3159
  }
3162
- dispatch(state.update({
3160
+ dispatch(state2.update({
3163
3161
  changes,
3164
3162
  userEvent: "format.link.remove",
3165
3163
  scrollIntoView: true
3166
3164
  }));
3167
3165
  return true;
3168
3166
  };
3169
- var addLink = ({ state, dispatch }) => {
3170
- const changes = state.changeByRange((range) => {
3167
+ var addLink = ({ url, image: image2 } = {}) => ({ state: state2, dispatch }) => {
3168
+ const changes = state2.changeByRange((range) => {
3171
3169
  let { from, to } = range;
3172
3170
  const cutStyles = [];
3173
3171
  let okay = null;
3174
- syntaxTree2(state).iterate({
3172
+ syntaxTree2(state2).iterate({
3175
3173
  from,
3176
3174
  to,
3177
3175
  enter: (node) => {
3178
3176
  if (Object.hasOwn(Textblocks, node.name)) {
3179
- 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);
3180
3178
  } else if (Object.hasOwn(InlineMarker, node.name)) {
3181
3179
  const sNode = node.node;
3182
3180
  if (node.from < from && node.to <= to) {
@@ -3196,7 +3194,7 @@ var addLink = ({ state, dispatch }) => {
3196
3194
  }
3197
3195
  });
3198
3196
  if (okay === null) {
3199
- const line = state.doc.lineAt(from);
3197
+ const line = state2.doc.lineAt(from);
3200
3198
  okay = to <= line.to && !/\S/.test(line.text.slice(from - line.from));
3201
3199
  }
3202
3200
  if (!okay) {
@@ -3206,26 +3204,26 @@ var addLink = ({ state, dispatch }) => {
3206
3204
  }
3207
3205
  const changes2 = [];
3208
3206
  const changesAfter = [];
3209
- removeLinkInner(from, to, changesAfter, state);
3207
+ removeLinkInner(from, to, changesAfter, state2);
3210
3208
  let cursorOffset = 1;
3211
3209
  for (const style of cutStyles) {
3212
3210
  const type = InlineMarker[style.name];
3213
3211
  const mark2 = inlineMarkerText(type);
3214
3212
  if (style.from < from) {
3215
3213
  changes2.push({
3216
- from: skipSpaces(from, state.doc, -1),
3214
+ from: skipSpaces(from, state2.doc, -1),
3217
3215
  insert: mark2
3218
3216
  });
3219
3217
  changesAfter.push({
3220
- from: skipSpaces(from, state.doc, 1, to),
3218
+ from: skipSpaces(from, state2.doc, 1, to),
3221
3219
  insert: mark2
3222
3220
  });
3223
3221
  } else {
3224
3222
  changes2.push({
3225
- from: skipSpaces(to, state.doc, -1, from),
3223
+ from: skipSpaces(to, state2.doc, -1, from),
3226
3224
  insert: mark2
3227
3225
  });
3228
- const after = skipSpaces(to, state.doc, 1);
3226
+ const after = skipSpaces(to, state2.doc, 1);
3229
3227
  if (after === to) {
3230
3228
  cursorOffset += mark2.length;
3231
3229
  }
@@ -3237,34 +3235,34 @@ var addLink = ({ state, dispatch }) => {
3237
3235
  }
3238
3236
  changes2.push({
3239
3237
  from,
3240
- insert: "["
3238
+ insert: image2 ? "![" : "["
3241
3239
  }, {
3242
3240
  from: to,
3243
- insert: "]()"
3241
+ insert: `](${url ?? ""})`
3244
3242
  });
3245
- const changeSet = state.changes(changes2.concat(changesAfter));
3243
+ const changeSet = state2.changes(changes2.concat(changesAfter));
3246
3244
  return {
3247
3245
  changes: changeSet,
3248
- range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset)
3246
+ range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset - (url ? url.length + 2 : 0))
3249
3247
  };
3250
3248
  });
3251
3249
  if (changes.changes.empty) {
3252
3250
  return false;
3253
3251
  }
3254
- dispatch(state.update(changes, {
3252
+ dispatch(state2.update(changes, {
3255
3253
  userEvent: "format.link.add",
3256
3254
  scrollIntoView: true
3257
3255
  }));
3258
3256
  return true;
3259
3257
  };
3260
- var addList = (type) => ({ state, dispatch }) => {
3258
+ var addList = (type) => ({ state: state2, dispatch }) => {
3261
3259
  let lastBlock = -1;
3262
3260
  let counter = 1;
3263
3261
  let first = true;
3264
3262
  let parentColumn = null;
3265
3263
  const blocks = [];
3266
- for (const { from, to } of state.selection.ranges) {
3267
- syntaxTree2(state).iterate({
3264
+ for (const { from, to } of state2.selection.ranges) {
3265
+ syntaxTree2(state2).iterate({
3268
3266
  from,
3269
3267
  to,
3270
3268
  enter: (node) => {
@@ -3276,7 +3274,7 @@ var addList = (type) => ({ state, dispatch }) => {
3276
3274
  }
3277
3275
  if (before?.name === (type === 0 ? "OrderedList" : "BulletList")) {
3278
3276
  const item = before.lastChild;
3279
- const itemLine = state.doc.lineAt(item.from);
3277
+ const itemLine = state2.doc.lineAt(item.from);
3280
3278
  const itemText = itemLine.text.slice(item.from - itemLine.from);
3281
3279
  parentColumn = item.from - itemLine.from + /^\s*/.exec(itemText)[0].length;
3282
3280
  if (type === 0) {
@@ -3311,10 +3309,10 @@ var addList = (type) => ({ state, dispatch }) => {
3311
3309
  });
3312
3310
  }
3313
3311
  if (!blocks.length) {
3314
- const { from, to } = state.doc.lineAt(state.selection.main.anchor);
3312
+ const { from, to } = state2.doc.lineAt(state2.selection.main.anchor);
3315
3313
  if (from === to) {
3316
3314
  const insert = type === 1 ? "- " : type === 0 ? "1. " : "- [ ] ";
3317
- dispatch(state.update({
3315
+ dispatch(state2.update({
3318
3316
  changes: [
3319
3317
  {
3320
3318
  from,
@@ -3335,11 +3333,11 @@ var addList = (type) => ({ state, dispatch }) => {
3335
3333
  for (let i = 0; i < blocks.length; i++) {
3336
3334
  const { node, counter: counter2, parentColumn: parentColumn2 } = blocks[i];
3337
3335
  const nodeFrom = node.name === "CodeBlock" ? node.from - 4 : node.from;
3338
- 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;
3339
3337
  if (type === 0) {
3340
3338
  padding += String(counter2).length;
3341
3339
  }
3342
- let line = state.doc.lineAt(nodeFrom);
3340
+ let line = state2.doc.lineAt(nodeFrom);
3343
3341
  const column = nodeFrom - line.from;
3344
3342
  if (parentColumn2 !== null && parentColumn2 > column) {
3345
3343
  padding = Math.max(padding, parentColumn2 - column);
@@ -3364,7 +3362,7 @@ var addList = (type) => ({ state, dispatch }) => {
3364
3362
  insert: mark2
3365
3363
  });
3366
3364
  while (line.to < node.to) {
3367
- line = state.doc.lineAt(line.to + 1);
3365
+ line = state2.doc.lineAt(line.to + 1);
3368
3366
  const open = /^[\s>]*/.exec(line.text)[0].length;
3369
3367
  changes.push({
3370
3368
  from: line.from + Math.min(open, column),
@@ -3379,25 +3377,25 @@ var addList = (type) => ({ state, dispatch }) => {
3379
3377
  next = next.nextSibling;
3380
3378
  }
3381
3379
  if (next?.name === "OrderedList") {
3382
- renumberListItems(next.firstChild, last.counter + 1, changes, state.doc);
3380
+ renumberListItems(next.firstChild, last.counter + 1, changes, state2.doc);
3383
3381
  }
3384
3382
  }
3385
- const changeSet = state.changes(changes);
3386
- dispatch(state.update({
3383
+ const changeSet = state2.changes(changes);
3384
+ dispatch(state2.update({
3387
3385
  changes: changeSet,
3388
- selection: state.selection.map(changeSet, 1),
3386
+ selection: state2.selection.map(changeSet, 1),
3389
3387
  userEvent: "format.list.add",
3390
3388
  scrollIntoView: true
3391
3389
  }));
3392
3390
  return true;
3393
3391
  };
3394
- var removeList = (type) => ({ state, dispatch }) => {
3392
+ var removeList = (type) => ({ state: state2, dispatch }) => {
3395
3393
  let lastBlock = -1;
3396
3394
  const changes = [];
3397
3395
  const stack = [];
3398
3396
  const targetNodeType = type === 0 ? "OrderedList" : type === 1 ? "BulletList" : "TaskList";
3399
- for (const { from, to } of state.selection.ranges) {
3400
- syntaxTree2(state).iterate({
3397
+ for (const { from, to } of state2.selection.ranges) {
3398
+ syntaxTree2(state2).iterate({
3401
3399
  from,
3402
3400
  to,
3403
3401
  enter: (node) => {
@@ -3414,7 +3412,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3414
3412
  stack.pop();
3415
3413
  } else if (name === "ListItem" && stack[stack.length - 1] === targetNodeType && node.from !== lastBlock) {
3416
3414
  lastBlock = node.from;
3417
- let line = state.doc.lineAt(node.from);
3415
+ let line = state2.doc.lineAt(node.from);
3418
3416
  const mark2 = /^\s*(\d+[.)] |[-*+] (\[[ x]\] )?)/.exec(line.text.slice(node.from - line.from));
3419
3417
  if (!mark2) {
3420
3418
  return false;
@@ -3425,7 +3423,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3425
3423
  to: node.from + mark2[0].length
3426
3424
  });
3427
3425
  while (line.to < node.to) {
3428
- line = state.doc.lineAt(line.to + 1);
3426
+ line = state2.doc.lineAt(line.to + 1);
3429
3427
  const open = /^[\s>]*/.exec(line.text)[0].length;
3430
3428
  if (open > column) {
3431
3429
  changes.push({
@@ -3435,7 +3433,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3435
3433
  }
3436
3434
  }
3437
3435
  if (node.to >= to) {
3438
- renumberListItems(node.node.nextSibling, 1, changes, state.doc);
3436
+ renumberListItems(node.node.nextSibling, 1, changes, state2.doc);
3439
3437
  }
3440
3438
  return false;
3441
3439
  }
@@ -3445,7 +3443,7 @@ var removeList = (type) => ({ state, dispatch }) => {
3445
3443
  if (!changes.length) {
3446
3444
  return false;
3447
3445
  }
3448
- dispatch(state.update({
3446
+ dispatch(state2.update({
3449
3447
  changes,
3450
3448
  userEvent: "format.list.remove",
3451
3449
  scrollIntoView: true
@@ -3475,12 +3473,12 @@ var renumberListItems = (item, counter, changes, doc) => {
3475
3473
  }
3476
3474
  }
3477
3475
  };
3478
- var setBlockquote = (enable) => ({ state, dispatch }) => {
3476
+ var setBlockquote = (enable) => ({ state: state2, dispatch }) => {
3479
3477
  const lines = [];
3480
3478
  let lastBlock = -1;
3481
- for (const { from, to } of state.selection.ranges) {
3479
+ for (const { from, to } of state2.selection.ranges) {
3482
3480
  const sawBlock = false;
3483
- syntaxTree2(state).iterate({
3481
+ syntaxTree2(state2).iterate({
3484
3482
  from,
3485
3483
  to,
3486
3484
  enter: (node) => {
@@ -3489,9 +3487,9 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3489
3487
  return false;
3490
3488
  }
3491
3489
  lastBlock = node.from;
3492
- let line2 = state.doc.lineAt(node.from);
3490
+ let line2 = state2.doc.lineAt(node.from);
3493
3491
  if (line2.number > 1) {
3494
- const prevLine = state.doc.line(line2.number - 1);
3492
+ const prevLine = state2.doc.line(line2.number - 1);
3495
3493
  if (/^[>\s]*$/.test(prevLine.text)) {
3496
3494
  if (!enable || lines.length && lines[lines.length - 1].number === prevLine.number - 1) {
3497
3495
  lines.push(prevLine);
@@ -3503,10 +3501,10 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3503
3501
  if (line2.to >= node.to) {
3504
3502
  break;
3505
3503
  }
3506
- line2 = state.doc.line(line2.number + 1);
3504
+ line2 = state2.doc.line(line2.number + 1);
3507
3505
  }
3508
- if (!enable && line2.number < state.doc.lines) {
3509
- 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);
3510
3508
  if (/^[>\s]*$/.test(nextLine.text)) {
3511
3509
  lines.push(nextLine);
3512
3510
  }
@@ -3516,7 +3514,7 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3516
3514
  }
3517
3515
  });
3518
3516
  let line;
3519
- 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)) {
3520
3518
  lines.push(line);
3521
3519
  }
3522
3520
  }
@@ -3540,10 +3538,10 @@ var setBlockquote = (enable) => ({ state, dispatch }) => {
3540
3538
  if (!changes.length) {
3541
3539
  return false;
3542
3540
  }
3543
- const changeSet = state.changes(changes);
3544
- dispatch(state.update({
3541
+ const changeSet = state2.changes(changes);
3542
+ dispatch(state2.update({
3545
3543
  changes: changeSet,
3546
- selection: state.selection.map(changeSet, 1),
3544
+ selection: state2.selection.map(changeSet, 1),
3547
3545
  userEvent: enable ? "format.blockquote.add" : "format.blockquote.remove",
3548
3546
  scrollIntoView: true
3549
3547
  }));
@@ -3555,11 +3553,11 @@ var toggleBlockquote = (target) => {
3555
3553
  return (getFormatting(target.state).blockQuote ? removeBlockquote : addBlockquote)(target);
3556
3554
  };
3557
3555
  var addCodeblock = (target) => {
3558
- const { state, dispatch } = target;
3559
- const { selection } = state;
3556
+ const { state: state2, dispatch } = target;
3557
+ const { selection } = state2;
3560
3558
  if (selection.ranges.length === 1 && selection.main.empty) {
3561
3559
  const { head } = selection.main;
3562
- const line = state.doc.lineAt(head);
3560
+ const line = state2.doc.lineAt(head);
3563
3561
  if (!/\S/.test(line.text) && head === line.from) {
3564
3562
  snippets.codeblock(target, null, line.from, line.to);
3565
3563
  return true;
@@ -3569,7 +3567,7 @@ var addCodeblock = (target) => {
3569
3567
  for (const { from, to } of selection.ranges) {
3570
3568
  let blockFrom = from;
3571
3569
  let blockTo = to;
3572
- syntaxTree2(state).iterate({
3570
+ syntaxTree2(state2).iterate({
3573
3571
  from,
3574
3572
  to,
3575
3573
  enter: (node) => {
@@ -3578,8 +3576,8 @@ var addCodeblock = (target) => {
3578
3576
  blockFrom = node.from;
3579
3577
  blockTo = node.to;
3580
3578
  } else {
3581
- blockFrom = Math.min(blockFrom, state.doc.lineAt(node.from).from);
3582
- 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);
3583
3581
  }
3584
3582
  }
3585
3583
  }
@@ -3597,7 +3595,7 @@ var addCodeblock = (target) => {
3597
3595
  return false;
3598
3596
  }
3599
3597
  const changes = ranges.map(({ from, to }) => {
3600
- const column = from - state.doc.lineAt(from).from;
3598
+ const column = from - state2.doc.lineAt(from).from;
3601
3599
  return [
3602
3600
  {
3603
3601
  from,
@@ -3609,30 +3607,30 @@ var addCodeblock = (target) => {
3609
3607
  }
3610
3608
  ];
3611
3609
  });
3612
- dispatch(state.update({
3610
+ dispatch(state2.update({
3613
3611
  changes,
3614
3612
  userEvent: "format.codeblock.add",
3615
3613
  scrollIntoView: true
3616
3614
  }));
3617
3615
  return true;
3618
3616
  };
3619
- var removeCodeblock = ({ state, dispatch }) => {
3617
+ var removeCodeblock = ({ state: state2, dispatch }) => {
3620
3618
  const changes = [];
3621
3619
  let lastBlock = -1;
3622
- for (const { from, to } of state.selection.ranges) {
3623
- syntaxTree2(state).iterate({
3620
+ for (const { from, to } of state2.selection.ranges) {
3621
+ syntaxTree2(state2).iterate({
3624
3622
  from,
3625
3623
  to,
3626
3624
  enter: (node) => {
3627
3625
  if (Textblocks[node.name] === "codeblock" && lastBlock !== node.from) {
3628
3626
  lastBlock = node.from;
3629
- const firstLine = state.doc.lineAt(node.from);
3627
+ const firstLine = state2.doc.lineAt(node.from);
3630
3628
  if (node.name === "FencedCode") {
3631
3629
  changes.push({
3632
3630
  from: node.from,
3633
3631
  to: firstLine.to + 1 + node.from - firstLine.from
3634
3632
  });
3635
- const lastLine = state.doc.lineAt(node.to);
3633
+ const lastLine = state2.doc.lineAt(node.to);
3636
3634
  if (/^([\s>]|[-*+] |\d+[).])*`+$/.test(lastLine.text)) {
3637
3635
  changes.push({
3638
3636
  from: lastLine.from - (lastLine.number === firstLine.number + 1 ? 0 : 1),
@@ -3641,7 +3639,7 @@ var removeCodeblock = ({ state, dispatch }) => {
3641
3639
  }
3642
3640
  } else {
3643
3641
  const column = node.from - firstLine.from;
3644
- for (let line = firstLine; ; line = state.doc.line(line.number + 1)) {
3642
+ for (let line = firstLine; ; line = state2.doc.line(line.number + 1)) {
3645
3643
  changes.push({
3646
3644
  from: line.from + column - 4,
3647
3645
  to: line.from + column
@@ -3658,7 +3656,7 @@ var removeCodeblock = ({ state, dispatch }) => {
3658
3656
  if (!changes.length) {
3659
3657
  return false;
3660
3658
  }
3661
- dispatch(state.update({
3659
+ dispatch(state2.update({
3662
3660
  changes,
3663
3661
  userEvent: "format.codeblock.remove",
3664
3662
  scrollIntoView: true
@@ -3717,7 +3715,7 @@ var Textblocks = {
3717
3715
  TableCell: "tablecell",
3718
3716
  Task: "paragraph"
3719
3717
  };
3720
- var getFormatting = (state) => {
3718
+ var getFormatting = (state2) => {
3721
3719
  let blockType = null;
3722
3720
  const inline = [
3723
3721
  null,
@@ -3743,7 +3741,7 @@ var getFormatting = (state) => {
3743
3741
  continue;
3744
3742
  } else if (currentBlock.active[i]) {
3745
3743
  inline[i] = true;
3746
- } else if (/\S/.test(state.doc.sliceString(currentBlock.pos, upto))) {
3744
+ } else if (/\S/.test(state2.doc.sliceString(currentBlock.pos, upto))) {
3747
3745
  inline[i] = false;
3748
3746
  }
3749
3747
  }
@@ -3754,12 +3752,12 @@ var getFormatting = (state) => {
3754
3752
  currentBlock.pos = Math.min(upto, currentBlock.end);
3755
3753
  }
3756
3754
  };
3757
- const { selection } = state;
3755
+ const { selection } = state2;
3758
3756
  for (const range of selection.ranges) {
3759
3757
  if (range.empty && inline.some((v) => v === null)) {
3760
3758
  const contextSize = Math.min(range.head, 6);
3761
- const contextBefore = state.doc.sliceString(range.head - contextSize, range.head);
3762
- 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);
3763
3761
  for (let i = 0; i < contextSize; i++) {
3764
3762
  const ch = contextAfter[i];
3765
3763
  if (ch !== contextBefore[contextBefore.length - 1 - i] || !/[~`*]/.test(ch)) {
@@ -3778,7 +3776,7 @@ var getFormatting = (state) => {
3778
3776
  }
3779
3777
  }
3780
3778
  }
3781
- syntaxTree2(state).iterate({
3779
+ syntaxTree2(state2).iterate({
3782
3780
  from: range.from,
3783
3781
  to: range.to,
3784
3782
  enter: (node) => {
@@ -3852,7 +3850,7 @@ var getFormatting = (state) => {
3852
3850
  }
3853
3851
  });
3854
3852
  }
3855
- const { from, to } = state.doc.lineAt(selection.main.anchor);
3853
+ const { from, to } = state2.doc.lineAt(selection.main.anchor);
3856
3854
  const blankLine = from === to;
3857
3855
  return {
3858
3856
  blankLine,
@@ -3867,17 +3865,22 @@ var getFormatting = (state) => {
3867
3865
  };
3868
3866
  };
3869
3867
  var useFormattingState = () => {
3870
- const [state, setState] = useState(null);
3868
+ const [state2, setState] = useState();
3871
3869
  const observer = useMemo(() => EditorView13.updateListener.of((update2) => {
3872
3870
  if (update2.docChanged || update2.selectionSet) {
3873
- const newState = getFormatting(update2.state);
3874
- if (!state || !compareFormatting(state, newState)) {
3875
- setState(newState);
3876
- }
3871
+ setState((prevState) => {
3872
+ const newState = getFormatting(update2.state);
3873
+ if (!prevState || !formattingEquals(prevState, newState)) {
3874
+ return newState;
3875
+ }
3876
+ return prevState;
3877
+ });
3877
3878
  }
3878
- }), []);
3879
+ }), [
3880
+ setState
3881
+ ]);
3879
3882
  return [
3880
- state,
3883
+ state2,
3881
3884
  observer
3882
3885
  ];
3883
3886
  };
@@ -3888,8 +3891,8 @@ import { StateField as StateField5 } from "@codemirror/state";
3888
3891
  import { Decoration as Decoration6, EditorView as EditorView14, WidgetType as WidgetType4 } from "@codemirror/view";
3889
3892
  var image = (options = {}) => {
3890
3893
  return StateField5.define({
3891
- create: (state) => {
3892
- return Decoration6.set(buildDecorations2(0, state.doc.length, state));
3894
+ create: (state2) => {
3895
+ return Decoration6.set(buildDecorations2(0, state2.doc.length, state2));
3893
3896
  },
3894
3897
  update: (value, tr) => {
3895
3898
  if (!tr.docChanged && !tr.selection) {
@@ -3923,16 +3926,16 @@ var preloadImage = (url) => {
3923
3926
  preloaded.add(url);
3924
3927
  }
3925
3928
  };
3926
- var buildDecorations2 = (from, to, state) => {
3929
+ var buildDecorations2 = (from, to, state2) => {
3927
3930
  const decorations = [];
3928
- const cursor = state.selection.main.head;
3929
- syntaxTree3(state).iterate({
3931
+ const cursor = state2.selection.main.head;
3932
+ syntaxTree3(state2).iterate({
3930
3933
  enter: (node) => {
3931
3934
  if (node.name === "Image") {
3932
3935
  const urlNode = node.node.getChild("URL");
3933
3936
  if (urlNode) {
3934
- const hide2 = state.readOnly || cursor < node.from || cursor > node.to;
3935
- 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);
3936
3939
  preloadImage(url);
3937
3940
  decorations.push(Decoration6.replace({
3938
3941
  block: true,
@@ -3962,6 +3965,8 @@ var ImageWidget = class extends WidgetType4 {
3962
3965
  return img;
3963
3966
  }
3964
3967
  };
3968
+ var imageUpload = (options = {}) => {
3969
+ };
3965
3970
 
3966
3971
  // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
3967
3972
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
@@ -4003,21 +4008,21 @@ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField6 } from "
4003
4008
  import { Decoration as Decoration7, EditorView as EditorView15, WidgetType as WidgetType5 } from "@codemirror/view";
4004
4009
  var table = (options = {}) => {
4005
4010
  return StateField6.define({
4006
- create: (state) => update(state, options),
4011
+ create: (state2) => update(state2, options),
4007
4012
  update: (_, tr) => update(tr.state, options),
4008
4013
  provide: (field) => EditorView15.decorations.from(field)
4009
4014
  });
4010
4015
  };
4011
- var update = (state, options) => {
4016
+ var update = (state2, options) => {
4012
4017
  const builder = new RangeSetBuilder3();
4013
- const cursor = state.selection.main.head;
4018
+ const cursor = state2.selection.main.head;
4014
4019
  const tables = [];
4015
4020
  const getTable = () => tables[tables.length - 1];
4016
4021
  const getRow = () => {
4017
4022
  const table2 = getTable();
4018
4023
  return table2.rows?.[table2.rows.length - 1];
4019
4024
  };
4020
- syntaxTree5(state).iterate({
4025
+ syntaxTree5(state2).iterate({
4021
4026
  enter: (node) => {
4022
4027
  switch (node.name) {
4023
4028
  case "Table": {
@@ -4038,9 +4043,9 @@ var update = (state, options) => {
4038
4043
  case "TableCell": {
4039
4044
  const row = getRow();
4040
4045
  if (row) {
4041
- row.push(state.sliceDoc(node.from, node.to));
4046
+ row.push(state2.sliceDoc(node.from, node.to));
4042
4047
  } else {
4043
- getTable().header?.push(state.sliceDoc(node.from, node.to));
4048
+ getTable().header?.push(state2.sliceDoc(node.from, node.to));
4044
4049
  }
4045
4050
  break;
4046
4051
  }
@@ -4048,7 +4053,7 @@ var update = (state, options) => {
4048
4053
  }
4049
4054
  });
4050
4055
  tables.forEach((table2) => {
4051
- const hide2 = state.readOnly || cursor < table2.from || cursor > table2.to;
4056
+ const hide2 = state2.readOnly || cursor < table2.from || cursor > table2.to;
4052
4057
  if (hide2) {
4053
4058
  builder.add(table2.from, table2.to, Decoration7.replace({
4054
4059
  widget: new TableWidget(table2)
@@ -4139,6 +4144,7 @@ var mention = ({ onSearch }) => {
4139
4144
  import { Facet as Facet6 } from "@codemirror/state";
4140
4145
  import { keymap as keymap7 } from "@codemirror/view";
4141
4146
  import { vim } from "@replit/codemirror-vim";
4147
+ var focusEvent = "focus.container";
4142
4148
  var editorMode = Facet6.define({
4143
4149
  combine: (modes) => modes[0] ?? {}
4144
4150
  });
@@ -4155,7 +4161,7 @@ var EditorModes = {
4155
4161
  key: "Alt-Escape",
4156
4162
  run: (view) => {
4157
4163
  view.dispatch({
4158
- userEvent: "focus.container"
4164
+ userEvent: focusEvent
4159
4165
  });
4160
4166
  return true;
4161
4167
  }
@@ -4164,8 +4170,95 @@ var EditorModes = {
4164
4170
  ]
4165
4171
  };
4166
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
+
4167
4260
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
4168
- import { keymap as keymap8 } from "@codemirror/view";
4261
+ import { keymap as keymap9 } from "@codemirror/view";
4169
4262
  var defaultItems = [
4170
4263
  "hello world!",
4171
4264
  "this is a test.",
@@ -4175,7 +4268,7 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
4175
4268
  let t;
4176
4269
  let idx = 0;
4177
4270
  return [
4178
- keymap8.of([
4271
+ keymap9.of([
4179
4272
  {
4180
4273
  // Reset.
4181
4274
  key: "alt-meta-'",
@@ -4222,9 +4315,11 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
4222
4315
  };
4223
4316
 
4224
4317
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
4225
- 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";
4226
4319
  var instanceCount = 0;
4227
- 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) => {
4228
4323
  const [instanceId] = useState2(() => `text-editor-${++instanceCount}`);
4229
4324
  const tabsterDOMAttribute = useFocusableGroup({
4230
4325
  tabBehavior: "limited"
@@ -4243,42 +4338,42 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4243
4338
  autoFocus
4244
4339
  ]);
4245
4340
  useEffect2(() => {
4246
- log9("updating", {
4341
+ log9("create", {
4247
4342
  id,
4248
4343
  instanceId
4249
4344
  }, {
4250
- F: __dxlog_file11,
4345
+ F: __dxlog_file12,
4251
4346
  L: 88,
4252
4347
  S: void 0,
4253
4348
  C: (f, a) => f(...a)
4254
4349
  });
4255
- const state = EditorState2.create({
4350
+ const state2 = EditorState2.create({
4256
4351
  doc,
4257
4352
  selection,
4258
4353
  extensions: [
4259
4354
  id && documentId2.of(id),
4260
4355
  // TODO(burdon): NOTE: Doesn't catch errors in keymap functions.
4261
- EditorView16.exceptionSink.of((err) => {
4356
+ EditorView17.exceptionSink.of((err) => {
4262
4357
  log9.catch(err, void 0, {
4263
- F: __dxlog_file11,
4358
+ F: __dxlog_file12,
4264
4359
  L: 101,
4265
4360
  S: void 0,
4266
4361
  C: (f, a) => f(...a)
4267
4362
  });
4268
4363
  }),
4269
4364
  // Focus.
4270
- EditorView16.updateListener.of((update2) => {
4365
+ EditorView17.updateListener.of((update2) => {
4271
4366
  update2.transactions.forEach((transaction) => {
4272
- if (transaction.isUserEvent("focus.container")) {
4367
+ if (transaction.isUserEvent(focusEvent)) {
4273
4368
  rootRef.current?.focus();
4274
4369
  }
4275
4370
  });
4276
4371
  }),
4277
4372
  extensions
4278
- ].filter(isNotFalsy3)
4373
+ ].filter(isNotFalsy4)
4279
4374
  });
4280
- const view2 = new EditorView16({
4281
- state,
4375
+ const view2 = new EditorView17({
4376
+ state: state2,
4282
4377
  parent: rootRef.current,
4283
4378
  scrollTo,
4284
4379
  // NOTE: Uncomment to debug/monitor all transactions.
@@ -4290,21 +4385,29 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4290
4385
  view3.update(trs);
4291
4386
  }
4292
4387
  });
4293
- setView(view2);
4294
- if (moveToEndOfLine) {
4388
+ if (moveToEndOfLine && !(scrollTo || selection)) {
4295
4389
  const { to } = view2.state.doc.lineAt(0);
4296
- view2?.dispatch({
4390
+ view2.dispatch({
4297
4391
  selection: {
4298
4392
  anchor: to
4299
4393
  }
4300
4394
  });
4301
4395
  }
4302
- if (state.facet(editorMode).noTabster) {
4396
+ if (state2.facet(editorMode).noTabster) {
4303
4397
  rootRef.current?.removeAttribute("data-tabster");
4304
4398
  }
4399
+ setView(view2);
4305
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
+ });
4306
4410
  view2?.destroy();
4307
- setView(null);
4308
4411
  };
4309
4412
  }, [
4310
4413
  doc,
@@ -4334,9 +4437,10 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4334
4437
  });
4335
4438
 
4336
4439
  // packages/ui/react-ui-editor/src/components/Toolbar/Toolbar.tsx
4337
- 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";
4338
4441
  import { createContext } from "@radix-ui/react-context";
4339
- 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";
4340
4444
  import { DensityProvider, ElevationProvider, Select, Toolbar as NaturalToolbar, Tooltip, useTranslation } from "@dxos/react-ui";
4341
4445
  import { getSize } from "@dxos/react-ui-theme";
4342
4446
  var tooltipProps = {
@@ -4353,10 +4457,10 @@ var HeadingIcons = {
4353
4457
  "6": TextHSix
4354
4458
  };
4355
4459
  var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
4356
- var ToolbarRoot = ({ children, onAction, classNames, state }) => {
4460
+ var ToolbarRoot = ({ children, onAction, classNames, state: state2 }) => {
4357
4461
  return /* @__PURE__ */ React2.createElement(ToolbarContextProvider, {
4358
4462
  onAction,
4359
- state
4463
+ state: state2
4360
4464
  }, /* @__PURE__ */ React2.createElement(DensityProvider, {
4361
4465
  density: "fine"
4362
4466
  }, /* @__PURE__ */ React2.createElement(ElevationProvider, {
@@ -4370,7 +4474,7 @@ var ToolbarRoot = ({ children, onAction, classNames, state }) => {
4370
4474
  };
4371
4475
  var buttonStyles = "min-bs-0 p-2";
4372
4476
  var iconStyles = getSize(5);
4373
- var ToolbarButton = ({ Icon, children, ...props }) => {
4477
+ var ToolbarToggleButton = ({ Icon, children, ...props }) => {
4374
4478
  return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4375
4479
  asChild: true
4376
4480
  }, /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroupItem, {
@@ -4383,14 +4487,27 @@ var ToolbarButton = ({ Icon, children, ...props }) => {
4383
4487
  className: "sr-only"
4384
4488
  }, children))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, tooltipProps, children, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
4385
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
+ };
4386
4503
  var ToolbarSeparator = () => /* @__PURE__ */ React2.createElement("div", {
4387
4504
  role: "separator",
4388
4505
  className: "grow"
4389
4506
  });
4390
4507
  var MarkdownHeading = () => {
4391
4508
  const { t } = useTranslation(translationKey);
4392
- const { onAction, state } = useToolbarContext("MarkdownFormatting");
4393
- const blockType = state ? state.blockType : "paragraph";
4509
+ const { onAction, state: state2 } = useToolbarContext("MarkdownFormatting");
4510
+ const blockType = state2 ? state2.blockType : "paragraph";
4394
4511
  const header = blockType && /heading(\d)/.exec(blockType);
4395
4512
  const value = header ? header[1] : blockType === "paragraph" || !blockType ? "0" : void 0;
4396
4513
  const HeadingIcon = HeadingIcons[value ?? "0"];
@@ -4464,43 +4581,43 @@ var markdownStyles = [
4464
4581
  {
4465
4582
  type: "strong",
4466
4583
  Icon: TextB,
4467
- getState: (state) => state.strong
4584
+ getState: (state2) => state2.strong
4468
4585
  },
4469
4586
  {
4470
4587
  type: "emphasis",
4471
4588
  Icon: TextItalic,
4472
- getState: (state) => state.emphasis
4589
+ getState: (state2) => state2.emphasis
4473
4590
  },
4474
4591
  {
4475
4592
  type: "strikethrough",
4476
4593
  Icon: TextStrikethrough,
4477
- getState: (state) => state.strikethrough
4594
+ getState: (state2) => state2.strikethrough
4478
4595
  },
4479
4596
  {
4480
4597
  type: "code",
4481
4598
  Icon: Code,
4482
- getState: (state) => state.code
4599
+ getState: (state2) => state2.code
4483
4600
  },
4484
4601
  {
4485
4602
  type: "link",
4486
4603
  Icon: Link,
4487
- getState: (state) => state.link
4604
+ getState: (state2) => state2.link
4488
4605
  }
4489
4606
  ];
4490
4607
  var MarkdownStyles = () => {
4491
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4608
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4492
4609
  const { t } = useTranslation(translationKey);
4493
4610
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4494
4611
  type: "multiple",
4495
- value: markdownStyles.filter(({ getState }) => state && getState(state)).map(({ type }) => type)
4496
- }, 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, {
4497
4614
  key: type,
4498
4615
  value: type,
4499
4616
  Icon,
4500
- disabled: state?.blockType === "codeblock",
4501
- onClick: state ? () => onAction?.({
4617
+ disabled: state2?.blockType === "codeblock",
4618
+ onClick: state2 ? () => onAction?.({
4502
4619
  type,
4503
- data: !getState(state)
4620
+ data: !getState(state2)
4504
4621
  }) : void 0
4505
4622
  }, t(`${type} label`))));
4506
4623
  };
@@ -4508,32 +4625,32 @@ var markdownLists = [
4508
4625
  {
4509
4626
  type: "list-bullet",
4510
4627
  Icon: ListBullets,
4511
- getState: (state) => state.listStyle === "bullet"
4628
+ getState: (state2) => state2.listStyle === "bullet"
4512
4629
  },
4513
4630
  {
4514
4631
  type: "list-ordered",
4515
4632
  Icon: ListNumbers,
4516
- getState: (state) => state.listStyle === "ordered"
4633
+ getState: (state2) => state2.listStyle === "ordered"
4517
4634
  },
4518
4635
  {
4519
4636
  type: "list-task",
4520
4637
  Icon: ListChecks,
4521
- getState: (state) => state.listStyle === "task"
4638
+ getState: (state2) => state2.listStyle === "task"
4522
4639
  }
4523
4640
  ];
4524
4641
  var MarkdownLists = () => {
4525
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4642
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4526
4643
  const { t } = useTranslation(translationKey);
4527
4644
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4528
4645
  type: "single",
4529
- value: state?.listStyle ? `list-${state.listStyle}` : ""
4530
- }, 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, {
4531
4648
  key: type,
4532
4649
  value: type,
4533
4650
  Icon,
4534
- onClick: state ? () => onAction?.({
4651
+ onClick: state2 ? () => onAction?.({
4535
4652
  type,
4536
- data: !getState(state)
4653
+ data: !getState(state2)
4537
4654
  }) : void 0
4538
4655
  }, t(`${type} label`))));
4539
4656
  };
@@ -4541,63 +4658,101 @@ var markdownBlocks = [
4541
4658
  {
4542
4659
  type: "blockquote",
4543
4660
  Icon: Quotes,
4544
- getState: (state) => state.blockQuote
4661
+ getState: (state2) => state2.blockQuote
4545
4662
  },
4546
4663
  {
4547
4664
  type: "codeblock",
4548
4665
  Icon: CodeBlock,
4549
- getState: (state) => state.blockType === "codeblock"
4666
+ getState: (state2) => state2.blockType === "codeblock"
4550
4667
  },
4551
4668
  {
4552
4669
  type: "table",
4553
4670
  Icon: Table2,
4554
- getState: (state) => state.blockType === "tablecell",
4555
- disabled: (state) => !state.blankLine
4671
+ getState: (state2) => state2.blockType === "tablecell",
4672
+ disabled: (state2) => !state2.blankLine
4556
4673
  }
4557
4674
  ];
4558
4675
  var MarkdownBlocks = () => {
4559
- const { onAction, state } = useToolbarContext("MarkdownStyles");
4676
+ const { onAction, state: state2 } = useToolbarContext("MarkdownStyles");
4560
4677
  const { t } = useTranslation(translationKey);
4561
- const value = markdownBlocks.find(({ getState }) => state && getState(state));
4678
+ const value = markdownBlocks.find(({ getState }) => state2 && getState(state2));
4562
4679
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4563
4680
  type: "single",
4564
4681
  value: value?.type ?? ""
4565
- }, markdownBlocks.map(({ type, disabled, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4682
+ }, markdownBlocks.map(({ type, disabled, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4566
4683
  key: type,
4567
4684
  value: type,
4568
4685
  Icon,
4569
- disabled: !state || disabled?.(state),
4570
- onClick: state ? () => onAction?.({
4686
+ disabled: !state2 || disabled?.(state2),
4687
+ onClick: state2 ? () => onAction?.({
4571
4688
  type,
4572
- data: !getState(state)
4689
+ data: !getState(state2)
4573
4690
  }) : void 0
4574
4691
  }, t(`${type} label`))));
4575
4692
  };
4576
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));
4577
- var MarkdownExtended = () => {
4694
+ var MarkdownCustom = ({ onUpload } = {}) => {
4578
4695
  const { onAction } = useToolbarContext("MarkdownStyles");
4579
4696
  const { t } = useTranslation(translationKey);
4580
- return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4581
- asChild: true
4582
- }, /* @__PURE__ */ React2.createElement(NaturalToolbar.Button, {
4583
- 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,
4584
4743
  "data-testid": "editor.toolbar.comment",
4585
4744
  onClick: () => onAction?.({
4586
4745
  type: "comment"
4587
- }),
4588
- classNames: buttonStyles
4589
- }, /* @__PURE__ */ React2.createElement(ChatText, {
4590
- className: iconStyles
4591
- }), /* @__PURE__ */ React2.createElement("span", {
4592
- className: "sr-only"
4593
- }, 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")));
4594
4748
  };
4595
4749
  var Toolbar = {
4596
4750
  Root: ToolbarRoot,
4597
- Button: ToolbarButton,
4751
+ Button: ToolbarToggleButton,
4598
4752
  Separator: ToolbarSeparator,
4599
4753
  Markdown: MarkdownStandard,
4600
- Extended: MarkdownExtended
4754
+ Custom: MarkdownCustom,
4755
+ Actions: MarkdownActions
4601
4756
  };
4602
4757
 
4603
4758
  // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
@@ -4634,7 +4789,13 @@ var useActionHandler = (view) => {
4634
4789
  insertTable(view);
4635
4790
  break;
4636
4791
  case "link":
4637
- (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);
4638
4799
  break;
4639
4800
  case "comment":
4640
4801
  createComment(view);
@@ -4663,43 +4824,51 @@ var useDocAccessor = (text) => {
4663
4824
 
4664
4825
  // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
4665
4826
  import { EditorSelection as EditorSelection2, EditorState as EditorState3 } from "@codemirror/state";
4666
- import { EditorView as EditorView17 } from "@codemirror/view";
4667
- 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";
4668
4829
  import { log as log10 } from "@dxos/log";
4669
- import { isNotFalsy as isNotFalsy4 } from "@dxos/util";
4670
- 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";
4671
4832
  var useTextEditor = (cb = () => ({}), deps = []) => {
4672
4833
  let { id, doc, selection, extensions, autoFocus, scrollTo, debug } = useMemo3(cb, deps ?? []);
4673
4834
  const onUpdate = useRef3();
4674
4835
  const [view, setView] = useState4();
4675
4836
  const parentRef = useRef3(null);
4676
- useEffect3(() => {
4837
+ useEffect4(() => {
4677
4838
  let view2;
4678
4839
  if (parentRef.current) {
4679
- 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({
4680
4849
  doc,
4681
4850
  selection,
4682
4851
  extensions: [
4683
4852
  id && documentId2.of(id),
4684
4853
  // TODO(burdon): Doesn't catch errors in keymap functions.
4685
- EditorView17.exceptionSink.of((err) => {
4854
+ EditorView18.exceptionSink.of((err) => {
4686
4855
  log10.catch(err, void 0, {
4687
- F: __dxlog_file12,
4688
- L: 44,
4856
+ F: __dxlog_file13,
4857
+ L: 46,
4689
4858
  S: void 0,
4690
4859
  C: (f, a) => f(...a)
4691
4860
  });
4692
4861
  }),
4693
4862
  extensions,
4694
- EditorView17.updateListener.of(() => {
4863
+ EditorView18.updateListener.of(() => {
4695
4864
  onUpdate.current?.();
4696
4865
  })
4697
- ].filter(isNotFalsy4)
4866
+ ].filter(isNotFalsy5)
4698
4867
  });
4699
- view2 = new EditorView17({
4868
+ view2 = new EditorView18({
4700
4869
  parent: parentRef.current,
4701
4870
  scrollTo,
4702
- state,
4871
+ state: state2,
4703
4872
  // NOTE: Uncomment to debug/monitor all transactions.
4704
4873
  // https://codemirror.net/docs/ref/#view.EditorView.dispatch
4705
4874
  dispatchTransactions: (trs, view3) => {
@@ -4712,10 +4881,18 @@ var useTextEditor = (cb = () => ({}), deps = []) => {
4712
4881
  setView(view2);
4713
4882
  }
4714
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
+ });
4715
4892
  view2?.destroy();
4716
4893
  };
4717
4894
  }, deps);
4718
- useEffect3(() => {
4895
+ useEffect4(() => {
4719
4896
  if (view) {
4720
4897
  if (!selection && !view.state.selection.main.anchor) {
4721
4898
  selection = EditorSelection2.single(view.state.doc.line(1).to);
@@ -4774,7 +4951,6 @@ export {
4774
4951
  callbackWrapper,
4775
4952
  command,
4776
4953
  comments,
4777
- compareFormatting,
4778
4954
  createBasicExtensions,
4779
4955
  createComment,
4780
4956
  createDataExtensions,
@@ -4791,14 +4967,18 @@ export {
4791
4967
  editorMode,
4792
4968
  editorWithToolbarLayout,
4793
4969
  focusComment,
4970
+ focusEvent,
4971
+ formattingEquals,
4794
4972
  formattingKeymap,
4795
4973
  getFormatting,
4796
4974
  getToken,
4797
4975
  image,
4976
+ imageUpload,
4798
4977
  insertTable,
4799
- keymap9 as keymap,
4978
+ keymap10 as keymap,
4800
4979
  linkTooltip,
4801
4980
  listener,
4981
+ localStorageStateStoreAdapter,
4802
4982
  logChanges,
4803
4983
  markdownHighlightStyle,
4804
4984
  markdownTags,
@@ -4814,6 +4994,7 @@ export {
4814
4994
  setHeading,
4815
4995
  setSelection,
4816
4996
  setStyle,
4997
+ state,
4817
4998
  table,
4818
4999
  tags2 as tags,
4819
5000
  toggleBlockquote,