@dxos/react-ui-editor 0.3.11-main.73ee990 → 0.3.11-main.764a9c1

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 (88) hide show
  1. package/dist/lib/browser/index.mjs +1649 -234
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +1 -2
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  7. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +1 -1
  8. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/TextEditor/{comments.stories.d.ts → hooks.stories.d.ts} +9 -12
  10. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -0
  11. package/dist/types/src/components/Toolbar/Toolbar.d.ts +27 -7
  12. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  13. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +12 -1
  14. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/Toolbar/index.d.ts +2 -0
  16. package/dist/types/src/components/Toolbar/index.d.ts.map +1 -0
  17. package/dist/types/src/components/index.d.ts +1 -0
  18. package/dist/types/src/components/index.d.ts.map +1 -1
  19. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  20. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
  21. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
  22. package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
  23. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
  24. package/dist/types/src/extensions/automerge/defs.d.ts +5 -5
  25. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  26. package/dist/types/src/extensions/automerge/semaphore.d.ts +2 -1
  27. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  28. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  29. package/dist/types/src/extensions/awareness.d.ts +1 -1
  30. package/dist/types/src/extensions/basic.d.ts +1 -1
  31. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  32. package/dist/types/src/extensions/comments.d.ts +6 -2
  33. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  34. package/dist/types/src/extensions/markdown/bundle.d.ts +2 -2
  35. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  36. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -1
  37. package/dist/types/src/extensions/markdown/formatting.d.ts +55 -8
  38. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  39. package/dist/types/src/extensions/markdown/formatting.test.d.ts +3 -0
  40. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -0
  41. package/dist/types/src/hooks/index.d.ts +1 -0
  42. package/dist/types/src/hooks/index.d.ts.map +1 -1
  43. package/dist/types/src/hooks/useActionHandler.d.ts +2 -2
  44. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
  45. package/dist/types/src/hooks/useEditorView.d.ts +17 -0
  46. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
  47. package/dist/types/src/hooks/useTextEditor.d.ts +36 -14
  48. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  49. package/dist/types/src/hooks/useTextModel.d.ts +12 -0
  50. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  51. package/dist/types/src/index.d.ts +1 -1
  52. package/dist/types/src/index.d.ts.map +1 -1
  53. package/dist/types/src/themes/default.d.ts.map +1 -1
  54. package/package.json +39 -26
  55. package/src/components/TextEditor/MarkdownEditor.stories.tsx +5 -6
  56. package/src/components/TextEditor/TextEditor.tsx +26 -16
  57. package/src/components/TextEditor/automerge.stories.tsx +1 -2
  58. package/src/components/TextEditor/hooks.stories.tsx +111 -0
  59. package/src/components/Toolbar/Toolbar.stories.tsx +22 -6
  60. package/src/components/Toolbar/Toolbar.tsx +224 -75
  61. package/src/components/Toolbar/index.ts +5 -0
  62. package/src/components/index.ts +1 -0
  63. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  64. package/src/extensions/automerge/automerge.test.ts +13 -0
  65. package/src/extensions/automerge/automerge.ts +27 -11
  66. package/src/extensions/automerge/cursor.ts +3 -3
  67. package/src/extensions/automerge/defs.ts +9 -9
  68. package/src/extensions/automerge/semaphore.ts +17 -19
  69. package/src/extensions/automerge/update-automerge.ts +9 -9
  70. package/src/extensions/automerge/update-codemirror.ts +5 -5
  71. package/src/extensions/awareness.ts +7 -4
  72. package/src/extensions/basic.ts +15 -13
  73. package/src/extensions/blast.ts +4 -1
  74. package/src/extensions/comments.ts +84 -30
  75. package/src/extensions/markdown/bundle.ts +10 -5
  76. package/src/extensions/markdown/code.ts +12 -4
  77. package/src/extensions/markdown/formatting.test.ts +482 -0
  78. package/src/extensions/markdown/formatting.ts +1133 -55
  79. package/src/extensions/markdown/link.ts +8 -2
  80. package/src/hooks/index.ts +1 -0
  81. package/src/hooks/useActionHandler.ts +61 -13
  82. package/src/hooks/useEditorView.ts +29 -0
  83. package/src/hooks/useTextEditor.ts +143 -19
  84. package/src/hooks/useTextModel.ts +28 -9
  85. package/src/index.ts +1 -1
  86. package/src/themes/default.ts +10 -5
  87. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +0 -1
  88. package/src/components/TextEditor/comments.stories.tsx +0 -357
@@ -1,20 +1,21 @@
1
1
  // packages/ui/react-ui-editor/src/index.ts
2
2
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
3
3
  import { Doc, YText, YXmlFragment } from "@dxos/text-model";
4
+ import { keymap as keymap7 } from "@codemirror/view";
4
5
 
5
6
  // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
6
7
  import { tags as tags2 } from "@lezer/highlight";
7
8
 
8
9
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
9
10
  import { EditorState as EditorState2 } from "@codemirror/state";
10
- import { EditorView as EditorView13 } from "@codemirror/view";
11
- import { useFocusableGroup } from "@fluentui/react-tabster";
11
+ import { EditorView as EditorView14 } from "@codemirror/view";
12
12
  import { vim } from "@replit/codemirror-vim";
13
13
  import defaultsDeep2 from "lodash.defaultsdeep";
14
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState } from "react";
14
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState as useState2 } from "react";
15
15
  import { log as log2 } from "@dxos/log";
16
16
  import { useThemeContext } from "@dxos/react-ui";
17
- import { attentionSurface, mx as mx3 } from "@dxos/react-ui-theme";
17
+ import { focusRing, mx as mx3 } from "@dxos/react-ui-theme";
18
+ import { isNotFalsy as isNotFalsy3 } from "@dxos/util";
18
19
 
19
20
  // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
20
21
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
@@ -53,14 +54,14 @@ var autocomplete = ({ onSearch }) => {
53
54
  };
54
55
 
55
56
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
57
+ import { invertedEffects } from "@codemirror/commands";
56
58
  import { StateField } from "@codemirror/state";
57
59
  import { EditorView, ViewPlugin } from "@codemirror/view";
58
- import { next as _automerge } from "@dxos/automerge/automerge";
59
- import { invariant } from "@dxos/invariant";
60
+ import { next as A4 } from "@dxos/automerge/automerge";
60
61
 
61
62
  // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
62
63
  import get from "lodash.get";
63
- import { next as automerge } from "@dxos/automerge/automerge";
64
+ import { next as A } from "@dxos/automerge/automerge";
64
65
  var cursorConverter = (handle, path) => ({
65
66
  // TODO(burdon): Handle assoc to associate with a previous character.
66
67
  toCursor: (pos) => {
@@ -72,7 +73,7 @@ var cursorConverter = (handle, path) => ({
72
73
  if (typeof value === "string" && value.length <= pos) {
73
74
  return "end";
74
75
  }
75
- return automerge.getCursor(doc, path.slice(), pos);
76
+ return A.getCursor(doc, path.slice(), pos);
76
77
  },
77
78
  fromCursor: (cursor) => {
78
79
  if (cursor === "") {
@@ -90,26 +91,28 @@ var cursorConverter = (handle, path) => ({
90
91
  return 0;
91
92
  }
92
93
  }
93
- return automerge.getCursorPosition(doc, path.slice(), cursor);
94
+ return A.getCursorPosition(doc, path.slice(), cursor);
94
95
  }
95
96
  });
96
97
 
97
98
  // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
98
99
  import { Annotation, StateEffect } from "@codemirror/state";
99
- var effectType = StateEffect.define({});
100
- var reconcileAnnotationType = Annotation.define();
101
100
  var getPath = (state, field) => state.field(field).path;
102
101
  var getLastHeads = (state, field) => state.field(field).lastHeads;
103
- var updateHeads = (newHeads) => effectType.of({
102
+ var updateHeadsEffect = StateEffect.define({});
103
+ var updateHeads = (newHeads) => updateHeadsEffect.of({
104
104
  newHeads
105
105
  });
106
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
106
+ var reconcileAnnotation = Annotation.define();
107
+ var isReconcile = (tr) => {
108
+ return !!tr.annotation(reconcileAnnotation);
109
+ };
107
110
 
108
111
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
109
- import { next as automerge3 } from "@dxos/automerge/automerge";
112
+ import { next as A3 } from "@dxos/automerge/automerge";
110
113
 
111
114
  // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
112
- import { next as automerge2 } from "@dxos/automerge/automerge";
115
+ import { next as A2 } from "@dxos/automerge/automerge";
113
116
  var updateAutomerge = (field, handle, transactions, state) => {
114
117
  const { lastHeads, path } = state.field(field);
115
118
  let hasChanges = false;
@@ -122,18 +125,18 @@ var updateAutomerge = (field, handle, transactions, state) => {
122
125
  return void 0;
123
126
  }
124
127
  const newHeads = handle.changeAt(lastHeads, (doc) => {
125
- const t = [];
128
+ const invertedTransactions = [];
126
129
  for (const tr of transactions) {
127
130
  tr.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
128
- t.push({
129
- fromA,
130
- toA,
131
+ invertedTransactions.push({
132
+ from: fromA,
133
+ del: toA - fromA,
131
134
  insert
132
135
  });
133
136
  });
134
137
  }
135
- t.reverse().forEach(({ fromA, toA, insert }) => {
136
- automerge2.splice(doc, path.slice(), fromA, toA - fromA, insert.toString());
138
+ invertedTransactions.reverse().forEach(({ from, del, insert }) => {
139
+ A2.splice(doc, path.slice(), from, del, insert.toString());
137
140
  });
138
141
  });
139
142
  return newHeads ?? void 0;
@@ -149,13 +152,13 @@ var updateCodeMirror = (view, selection, target, patches) => {
149
152
  selection = selection.map(changeSet, 1);
150
153
  view.dispatch({
151
154
  changes: changeSet,
152
- annotations: reconcileAnnotationType.of({})
155
+ annotations: reconcileAnnotation.of(false)
153
156
  });
154
157
  }
155
158
  }
156
159
  view.dispatch({
157
160
  selection,
158
- annotations: reconcileAnnotationType.of({})
161
+ annotations: reconcileAnnotation.of(false)
159
162
  });
160
163
  };
161
164
  var handlePatch = (patch, target, state) => {
@@ -176,7 +179,7 @@ var handleInsert = (target, patch) => {
176
179
  if (index == null) {
177
180
  return [];
178
181
  }
179
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
182
+ const text = patch.values.map((value) => value ? value.toString() : "").join("");
180
183
  return [
181
184
  {
182
185
  from: index,
@@ -254,36 +257,37 @@ var PatchSemaphore = class {
254
257
  this._state = _state;
255
258
  this._inReconcile = false;
256
259
  }
257
- // NOTE: Cannot destruct view.state.
258
260
  reconcile(view) {
259
- if (this._inReconcile) {
260
- return;
261
+ if (!this._inReconcile) {
262
+ this._inReconcile = true;
263
+ this.doReconcile(view);
264
+ this._inReconcile = false;
261
265
  }
262
- this._inReconcile = true;
266
+ }
267
+ doReconcile(view) {
263
268
  const path = getPath(view.state, this._state);
264
269
  const oldHeads = getLastHeads(view.state, this._state);
265
270
  let selection = view.state.selection;
266
- const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
271
+ const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
267
272
  const toInvert = transactions.slice().reverse();
268
- for (const tx of toInvert) {
269
- const inverted = tx.changes.invert(tx.startState.doc);
273
+ for (const tr of toInvert) {
274
+ const inverted = tr.changes.invert(tr.startState.doc);
270
275
  selection = selection.map(inverted);
271
276
  view.dispatch({
272
277
  changes: inverted,
273
- annotations: reconcileAnnotationType.of(true)
278
+ annotations: reconcileAnnotation.of(true)
274
279
  });
275
280
  }
276
281
  let newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
277
282
  if (newHeads === null || newHeads === void 0) {
278
- newHeads = automerge3.getHeads(this._handle.docSync());
283
+ newHeads = A3.getHeads(this._handle.docSync());
279
284
  }
280
- const diff = automerge3.equals(oldHeads, newHeads) ? [] : automerge3.diff(this._handle.docSync(), oldHeads, newHeads);
285
+ const diff = A3.equals(oldHeads, newHeads) ? [] : A3.diff(this._handle.docSync(), oldHeads, newHeads);
281
286
  updateCodeMirror(view, selection, path, diff);
282
287
  view.dispatch({
283
288
  effects: updateHeads(newHeads),
284
- annotations: reconcileAnnotationType.of({})
289
+ annotations: reconcileAnnotation.of(false)
285
290
  });
286
- this._inReconcile = false;
287
291
  }
288
292
  };
289
293
 
@@ -325,12 +329,11 @@ var Cursor = class _Cursor {
325
329
  };
326
330
 
327
331
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
328
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts";
329
- var automerge4 = ({ handle, path }) => {
330
- const state = StateField.define({
332
+ var automerge = ({ handle, path }) => {
333
+ const syncState = StateField.define({
331
334
  create: () => ({
332
335
  path: path.slice(),
333
- lastHeads: _automerge.getHeads(handle.docSync()),
336
+ lastHeads: A4.getHeads(handle.docSync()),
334
337
  unreconciledTransactions: []
335
338
  }),
336
339
  update: (value, tr) => {
@@ -341,7 +344,7 @@ var automerge4 = ({ handle, path }) => {
341
344
  };
342
345
  let clearUnreconciled = false;
343
346
  for (const effect of tr.effects) {
344
- if (effect.is(effectType)) {
347
+ if (effect.is(updateHeadsEffect)) {
345
348
  result.lastHeads = effect.value.newHeads;
346
349
  clearUnreconciled = true;
347
350
  }
@@ -349,29 +352,21 @@ var automerge4 = ({ handle, path }) => {
349
352
  if (clearUnreconciled) {
350
353
  result.unreconciledTransactions = [];
351
354
  } else {
352
- if (!isReconcileTx(tr)) {
355
+ if (!isReconcile(tr)) {
353
356
  result.unreconciledTransactions.push(tr);
354
357
  }
355
358
  }
356
359
  return result;
357
360
  }
358
361
  });
359
- const semaphore = new PatchSemaphore(handle, state);
362
+ const semaphore = new PatchSemaphore(handle, syncState);
360
363
  return [
361
364
  Cursor.converter.of(cursorConverter(handle, path)),
365
+ syncState,
362
366
  // Reconcile external updates.
363
367
  ViewPlugin.fromClass(class {
364
368
  constructor(_view) {
365
369
  this._view = _view;
366
- invariant(this._view, void 0, {
367
- F: __dxlog_file,
368
- L: 66,
369
- S: this,
370
- A: [
371
- "this._view",
372
- ""
373
- ]
374
- });
375
370
  handle.addListener("change", this._handleChange.bind(this));
376
371
  }
377
372
  destroy() {
@@ -387,7 +382,17 @@ var automerge4 = ({ handle, path }) => {
387
382
  semaphore.reconcile(view);
388
383
  }
389
384
  }),
390
- state
385
+ // TODO(burdon): Record undo transactions.
386
+ // See https://github.com/yjs/y-codemirror.next/tree/main
387
+ // https://codemirror.net/examples/inverted-effect
388
+ invertedEffects.of((tr) => {
389
+ const effects = [];
390
+ if (!tr.changes.empty) {
391
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
392
+ });
393
+ }
394
+ return effects;
395
+ })
391
396
  ];
392
397
  };
393
398
 
@@ -406,13 +411,13 @@ var dummyProvider = {
406
411
  update: () => {
407
412
  }
408
413
  };
409
- var AwarenessProvider = Facet2.define({
414
+ var awarenessProvider = Facet2.define({
410
415
  combine: (providers) => providers[0] ?? dummyProvider
411
416
  });
412
417
  var RemoteSelectionChangedAnnotation = Annotation2.define();
413
418
  var awareness = (provider = dummyProvider) => {
414
419
  return [
415
- AwarenessProvider.of(provider),
420
+ awarenessProvider.of(provider),
416
421
  ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
417
422
  decorations: (value) => value.decorations
418
423
  }),
@@ -426,7 +431,7 @@ var RemoteSelectionsDecorator = class {
426
431
  this._lastAnchor = void 0;
427
432
  this._lastHead = void 0;
428
433
  this._cursorConverter = view.state.facet(Cursor.converter);
429
- this._provider = view.state.facet(AwarenessProvider);
434
+ this._provider = view.state.facet(awarenessProvider);
430
435
  this._provider.open();
431
436
  this._provider.remoteStateChange.on(this._ctx, () => {
432
437
  view.dispatch({
@@ -632,24 +637,25 @@ import { history } from "@codemirror/commands";
632
637
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
633
638
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
634
639
  import { drawSelection, EditorView as EditorView3, highlightActiveLine, placeholder } from "@codemirror/view";
635
- var basicBundle = ({ themeMode, placeholder: _placeholder, lineWrapping = true }) => [
636
- lineWrapping ? EditorView3.lineWrapping : [],
640
+ import { isNotFalsy } from "@dxos/util";
641
+ var createBasicBundle = ({ themeMode, placeholder: _placeholder, lineWrapping = true } = {}) => [
642
+ lineWrapping && EditorView3.lineWrapping,
637
643
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
638
644
  bracketMatching(),
639
645
  closeBrackets(),
640
646
  drawSelection(),
641
647
  highlightActiveLine(),
642
648
  history(),
643
- _placeholder ? placeholder(_placeholder) : [],
649
+ _placeholder && placeholder(_placeholder),
644
650
  // https://github.com/codemirror/theme-one-dark
645
651
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
646
- ];
652
+ ].filter(isNotFalsy);
647
653
 
648
654
  // packages/ui/react-ui-editor/src/extensions/blast.ts
649
655
  import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
650
656
  import defaultsDeep from "lodash.defaultsdeep";
651
- import { invariant as invariant2 } from "@dxos/invariant";
652
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
657
+ import { invariant } from "@dxos/invariant";
658
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
653
659
  var defaultOptions = {
654
660
  effect: 2,
655
661
  maxParticles: 200,
@@ -775,9 +781,9 @@ var Blaster = class {
775
781
  return this._node;
776
782
  }
777
783
  initialize() {
778
- invariant2(!this._canvas && !this._ctx, void 0, {
779
- F: __dxlog_file2,
780
- L: 138,
784
+ invariant(!this._canvas && !this._ctx, void 0, {
785
+ F: __dxlog_file,
786
+ L: 141,
781
787
  S: this,
782
788
  A: [
783
789
  "!this._canvas && !this._ctx",
@@ -812,9 +818,9 @@ var Blaster = class {
812
818
  }
813
819
  }
814
820
  start() {
815
- invariant2(this._canvas && this._ctx, void 0, {
816
- F: __dxlog_file2,
817
- L: 177,
821
+ invariant(this._canvas && this._ctx, void 0, {
822
+ F: __dxlog_file,
823
+ L: 180,
818
824
  S: this,
819
825
  A: [
820
826
  "this._canvas && this._ctx",
@@ -956,6 +962,7 @@ var random = (min, max) => {
956
962
  };
957
963
 
958
964
  // packages/ui/react-ui-editor/src/extensions/comments.ts
965
+ import { invertedEffects as invertedEffects2 } from "@codemirror/commands";
959
966
  import { Facet as Facet3, StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
960
967
  import { hoverTooltip, keymap as keymap3, Decoration as Decoration2, EditorView as EditorView5 } from "@codemirror/view";
961
968
  import sortBy from "lodash.sortby";
@@ -1032,13 +1039,13 @@ var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue)
1032
1039
 
1033
1040
  // packages/ui/react-ui-editor/src/util.ts
1034
1041
  import { log } from "@dxos/log";
1035
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1042
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1036
1043
  var callbackWrapper = (fn) => (...args) => {
1037
1044
  try {
1038
1045
  return fn(...args);
1039
1046
  } catch (err) {
1040
1047
  log.catch(err, void 0, {
1041
- F: __dxlog_file3,
1048
+ F: __dxlog_file2,
1042
1049
  L: 18,
1043
1050
  S: void 0,
1044
1051
  C: (f, a) => f(...a)
@@ -1067,22 +1074,34 @@ var logChanges = (trs) => {
1067
1074
 
1068
1075
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1069
1076
  var styles2 = EditorView5.baseTheme({
1070
- "& .cm-comment": {
1077
+ "&light .cm-comment, &light .cm-comment-current": {
1078
+ mixBlendMode: "darken"
1079
+ },
1080
+ "&dark .cm-comment, &dark .cm-comment-current": {
1081
+ mixBlendMode: "plus-lighter"
1082
+ },
1083
+ "&light .cm-comment": {
1071
1084
  backgroundColor: getToken("extend.colors.yellow.50")
1072
1085
  },
1073
- "& .cm-comment-current": {
1086
+ "&light .cm-comment-current": {
1074
1087
  backgroundColor: getToken("extend.colors.yellow.100")
1088
+ },
1089
+ "&dark .cm-comment": {
1090
+ color: getToken("extend.colors.yellow.50"),
1091
+ backgroundColor: getToken("extend.colors.yellow.900")
1092
+ },
1093
+ "&dark .cm-comment-current": {
1094
+ color: getToken("extend.colors.yellow.100"),
1095
+ backgroundColor: getToken("extend.colors.yellow.950")
1075
1096
  }
1076
1097
  });
1077
- var marks = {
1078
- highlight: Decoration2.mark({
1079
- class: "cm-comment"
1080
- }),
1081
- highlightActive: Decoration2.mark({
1082
- class: "cm-comment-current"
1083
- })
1084
- };
1085
- var setFocus = (view, id, center = true) => {
1098
+ var commentMark = Decoration2.mark({
1099
+ class: "cm-comment"
1100
+ });
1101
+ var commentCurrentMark = Decoration2.mark({
1102
+ class: "cm-comment-current"
1103
+ });
1104
+ var focusComment = (view, id, center = true) => {
1086
1105
  const comment = view.state.field(commentsState).comments.find((range2) => range2.comment.id === id);
1087
1106
  if (!comment?.comment.cursor) {
1088
1107
  return;
@@ -1144,7 +1163,7 @@ var commentsState = StateField2.define({
1144
1163
  return value;
1145
1164
  }
1146
1165
  });
1147
- var highlightDecorations = EditorView5.decorations.compute([
1166
+ var commentsDecorations = EditorView5.decorations.compute([
1148
1167
  commentsState
1149
1168
  ], (state) => {
1150
1169
  const { selection: { current }, comments: comments2 } = state.field(commentsState);
@@ -1155,9 +1174,9 @@ var highlightDecorations = EditorView5.decorations.compute([
1155
1174
  return void 0;
1156
1175
  }
1157
1176
  if (comment.comment.id === current) {
1158
- return marks.highlightActive.range(range.from, range.to);
1177
+ return commentCurrentMark.range(range.from, range.to);
1159
1178
  } else {
1160
- return marks.highlight.range(range.from, range.to);
1179
+ return commentMark.range(range.from, range.to);
1161
1180
  }
1162
1181
  }).filter(nonNullable);
1163
1182
  return Decoration2.set(decorations);
@@ -1186,36 +1205,80 @@ var trackPastedComments = (onUpdate) => {
1186
1205
  cut: handleTrack,
1187
1206
  copy: handleTrack
1188
1207
  }),
1189
- // Handle paste.
1190
- EditorView5.updateListener.of(({ view, state, transactions }) => {
1208
+ // Track deleted comments.
1209
+ invertedEffects2.of((tr) => {
1210
+ const { comments: comments2 } = tr.startState.field(commentsState);
1211
+ const effects = [];
1212
+ tr.changes.iterChangedRanges((fromA, toA) => {
1213
+ for (const { comment: { id }, range: { from, to } } of comments2) {
1214
+ if (from < to && from >= fromA && to <= toA) {
1215
+ effects.push(restoreCommentEffect.of({
1216
+ id,
1217
+ from,
1218
+ to
1219
+ }));
1220
+ }
1221
+ }
1222
+ });
1223
+ return effects;
1224
+ }),
1225
+ // Handle paste or the undo of comment deletion.
1226
+ EditorView5.updateListener.of((update2) => {
1227
+ const restore = [];
1228
+ for (let i = 0; i < update2.transactions.length; i++) {
1229
+ const tr = update2.transactions[i];
1230
+ for (let j = 0; j < restore.length; j++) {
1231
+ restore[j] = mapTrackedComment(restore[j], tr.changes);
1232
+ }
1233
+ for (const effect of tr.effects) {
1234
+ if (effect.is(restoreCommentEffect)) {
1235
+ restore.push(effect.value);
1236
+ }
1237
+ }
1238
+ }
1191
1239
  if (tracked) {
1192
- const paste = transactions.find((tr) => tr.isUserEvent("input.paste"));
1240
+ const paste = update2.transactions.find((tr) => tr.isUserEvent("input.paste"));
1193
1241
  if (paste) {
1194
1242
  let found = -1;
1195
1243
  paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1196
1244
  if (text.eq(tracked.text)) {
1197
- for (let i = transactions.indexOf(paste) + 1; i < transactions.length; i++) {
1198
- fromB = transactions[i].changes.mapPos(fromB);
1245
+ for (let i = update2.transactions.indexOf(paste) + 1; i < update2.transactions.length; i++) {
1246
+ fromB = update2.transactions[i].changes.mapPos(fromB);
1199
1247
  }
1200
1248
  found = fromB;
1201
1249
  }
1202
1250
  });
1203
1251
  if (found > -1) {
1204
1252
  for (const moved of tracked.comments) {
1205
- const range = {
1253
+ restore.push({
1254
+ id: moved.id,
1206
1255
  from: found + moved.from,
1207
1256
  to: found + moved.to
1208
- };
1209
- const cursor = Cursor.getCursorFromRange(state, range);
1210
- onUpdate(moved.id, cursor);
1257
+ });
1211
1258
  }
1212
1259
  }
1213
1260
  tracked = null;
1214
1261
  }
1215
1262
  }
1263
+ for (const comment of restore) {
1264
+ const { comments: comments2 } = update2.startState.field(commentsState);
1265
+ const exists = comments2.some((c) => c.comment.id === comment.id && c.range.from < c.range.to);
1266
+ if (!exists) {
1267
+ const cursor = Cursor.getCursorFromRange(update2.state, comment);
1268
+ onUpdate(comment.id, cursor);
1269
+ }
1270
+ }
1216
1271
  })
1217
1272
  ];
1218
1273
  };
1274
+ var mapTrackedComment = (comment, changes) => ({
1275
+ id: comment.id,
1276
+ from: changes.mapPos(comment.from, 1),
1277
+ to: changes.mapPos(comment.to, 1)
1278
+ });
1279
+ var restoreCommentEffect = StateEffect2.define({
1280
+ map: mapTrackedComment
1281
+ });
1219
1282
  var optionsFacet = Facet3.define({
1220
1283
  combine: (providers) => providers[0]
1221
1284
  });
@@ -1238,14 +1301,18 @@ var createComment = (view) => {
1238
1301
  to
1239
1302
  });
1240
1303
  if (cursor) {
1241
- const id = options.onCreate?.(cursor, view.coordsAtPos(from));
1304
+ const id = options.onCreate?.({
1305
+ cursor,
1306
+ from,
1307
+ location: view.coordsAtPos(from)
1308
+ });
1242
1309
  if (id) {
1243
1310
  view.dispatch({
1244
1311
  effects: setSelection.of({
1245
1312
  current: id
1246
1313
  }),
1247
1314
  selection: {
1248
- anchor: from
1315
+ anchor: to
1249
1316
  }
1250
1317
  });
1251
1318
  return true;
@@ -1259,7 +1326,7 @@ var comments = (options = {}) => {
1259
1326
  return [
1260
1327
  optionsFacet.of(options),
1261
1328
  commentsState,
1262
- highlightDecorations,
1329
+ commentsDecorations,
1263
1330
  styles2,
1264
1331
  //
1265
1332
  // Keymap.
@@ -1405,6 +1472,7 @@ import { searchKeymap } from "@codemirror/search";
1405
1472
  import { EditorState } from "@codemirror/state";
1406
1473
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1407
1474
  import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView7, highlightActiveLine as highlightActiveLine2, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1475
+ import { isNotFalsy as isNotFalsy2 } from "@dxos/util";
1408
1476
 
1409
1477
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1410
1478
  import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
@@ -1595,11 +1663,11 @@ var markdownHighlightStyle = (readonly) => {
1595
1663
  };
1596
1664
 
1597
1665
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1598
- var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
1666
+ var createMarkdownExtensions = ({ themeMode, placeholder: _placeholder, lineWrapping = true } = {}) => {
1599
1667
  return [
1668
+ lineWrapping && EditorView7.lineWrapping,
1600
1669
  EditorState.allowMultipleSelections.of(true),
1601
1670
  EditorState.tabSize.of(2),
1602
- EditorView7.lineWrapping,
1603
1671
  // https://github.com/codemirror/basic-setup
1604
1672
  bracketMatching2(),
1605
1673
  closeBrackets2(),
@@ -1609,7 +1677,7 @@ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
1609
1677
  highlightActiveLine2(),
1610
1678
  history2(),
1611
1679
  indentOnInput(),
1612
- _placeholder ? placeholder2(_placeholder) : [],
1680
+ _placeholder && placeholder2(_placeholder),
1613
1681
  // Main extension.
1614
1682
  // https://github.com/codemirror/lang-markdown
1615
1683
  // https://codemirror.net/5/mode/markdown/index.html (demo).
@@ -1644,7 +1712,7 @@ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
1644
1712
  // https://codemirror.net/docs/ref/#commands.standardKeymap
1645
1713
  ...standardKeymap
1646
1714
  ])
1647
- ];
1715
+ ].filter(isNotFalsy2);
1648
1716
  };
1649
1717
 
1650
1718
  // packages/ui/react-ui-editor/src/extensions/markdown/code.ts
@@ -1654,12 +1722,11 @@ import { ViewPlugin as ViewPlugin3, EditorView as EditorView8, Decoration as Dec
1654
1722
  import { mx as mx2 } from "@dxos/react-ui-theme";
1655
1723
  var styles3 = EditorView8.baseTheme({
1656
1724
  "& .cm-code": {
1657
- paddingInline: "1rem !important",
1658
1725
  fontFamily: getToken("fontFamily.mono", []).join(",")
1659
1726
  },
1660
1727
  "& .cm-codeblock": {
1661
- display: "inline-block",
1662
1728
  width: "100%",
1729
+ paddingInline: "1rem !important",
1663
1730
  position: "relative",
1664
1731
  "&::after": {
1665
1732
  content: '""',
@@ -1680,12 +1747,14 @@ var styles3 = EditorView8.baseTheme({
1680
1747
  }
1681
1748
  },
1682
1749
  "& .cm-codeblock-first": {
1750
+ display: "inline-block",
1683
1751
  "&::after": {
1684
1752
  borderTopLeftRadius: ".5rem",
1685
1753
  borderTopRightRadius: ".5rem"
1686
1754
  }
1687
1755
  },
1688
1756
  "& .cm-codeblock-last": {
1757
+ display: "inline-block",
1689
1758
  "&::after": {
1690
1759
  borderBottomLeftRadius: ".5rem",
1691
1760
  borderBottomRightRadius: ".5rem"
@@ -1733,7 +1802,7 @@ var buildDecorations = (view) => {
1733
1802
  }));
1734
1803
  } else {
1735
1804
  builder.add(block.from, block.from, Decoration3.line({
1736
- class: mx2("cm-code cm-codeblock")
1805
+ class: mx2("cm-code cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1737
1806
  }));
1738
1807
  }
1739
1808
  }
@@ -1762,65 +1831,838 @@ var code2 = () => {
1762
1831
  };
1763
1832
 
1764
1833
  // packages/ui/react-ui-editor/src/extensions/markdown/formatting.ts
1834
+ import { snippet } from "@codemirror/autocomplete";
1765
1835
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1766
- import { RangeSetBuilder as RangeSetBuilder2 } from "@codemirror/state";
1767
- import { Decoration as Decoration4, keymap as keymap5, ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1768
- var setHeading = (level) => (view) => {
1769
- const { selection: { ranges }, doc } = view.state;
1836
+ import { RangeSetBuilder as RangeSetBuilder2, EditorSelection } from "@codemirror/state";
1837
+ import { Decoration as Decoration4, EditorView as EditorView9, keymap as keymap5, ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1838
+ import { useState, useMemo } from "react";
1839
+ 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;
1840
+ var Inline;
1841
+ (function(Inline2) {
1842
+ Inline2[Inline2["Strong"] = 0] = "Strong";
1843
+ Inline2[Inline2["Emphasis"] = 1] = "Emphasis";
1844
+ Inline2[Inline2["Strikethrough"] = 2] = "Strikethrough";
1845
+ Inline2[Inline2["Code"] = 3] = "Code";
1846
+ })(Inline || (Inline = {}));
1847
+ var List;
1848
+ (function(List2) {
1849
+ List2[List2["Ordered"] = 0] = "Ordered";
1850
+ List2[List2["Bullet"] = 1] = "Bullet";
1851
+ List2[List2["Task"] = 2] = "Task";
1852
+ })(List || (List = {}));
1853
+ var setHeading = (level) => ({ state, dispatch }) => {
1854
+ const { selection: { ranges }, doc } = state;
1770
1855
  const changes = [];
1856
+ let prevBlock = -1;
1771
1857
  for (const range of ranges) {
1772
- const { number } = doc.lineAt(range.anchor);
1773
- const { from } = doc.line(number);
1774
- const line = doc.sliceString(from, from + 7);
1775
- const [_, marks2, spaces] = line.match(/(#+)(\s+)/) ?? [];
1776
- const current = marks2?.length ?? 0;
1777
- if (level !== current) {
1858
+ syntaxTree2(state).iterate({
1859
+ from: range.from,
1860
+ to: range.to,
1861
+ enter: (node) => {
1862
+ if (!Object.hasOwn(Textblocks, node.name) || prevBlock === node.from) {
1863
+ return;
1864
+ }
1865
+ prevBlock = node.from;
1866
+ const blockType = Textblocks[node.name];
1867
+ const isHeading = /heading(\d)/.exec(blockType);
1868
+ const curLevel = isHeading ? +isHeading[1] : node.name === "Paragraph" ? 0 : -1;
1869
+ if (curLevel < 0 || curLevel === level) {
1870
+ return;
1871
+ }
1872
+ if (curLevel === 0) {
1873
+ changes.push({
1874
+ from: node.from,
1875
+ insert: "#".repeat(level) + " "
1876
+ });
1877
+ } else if (node.name === "SetextHeading1" || node.name === "SetextHeading2") {
1878
+ const nextLine = doc.lineAt(node.to);
1879
+ if (level) {
1880
+ changes.push({
1881
+ from: node.from,
1882
+ insert: "#".repeat(level) + " "
1883
+ });
1884
+ }
1885
+ changes.push({
1886
+ from: nextLine.from - 1,
1887
+ to: nextLine.to
1888
+ });
1889
+ } else {
1890
+ if (level === 0) {
1891
+ changes.push({
1892
+ from: node.from,
1893
+ to: Math.min(node.to, node.from + curLevel + 1)
1894
+ });
1895
+ } else if (level < curLevel) {
1896
+ changes.push({
1897
+ from: node.from,
1898
+ to: node.from + (curLevel - level)
1899
+ });
1900
+ } else {
1901
+ changes.push({
1902
+ from: node.from,
1903
+ insert: "#".repeat(level - curLevel)
1904
+ });
1905
+ }
1906
+ }
1907
+ }
1908
+ });
1909
+ }
1910
+ if (!changes.length) {
1911
+ return false;
1912
+ }
1913
+ dispatch(state.update({
1914
+ changes,
1915
+ userEvent: "format.setHeading",
1916
+ scrollIntoView: true
1917
+ }));
1918
+ return true;
1919
+ };
1920
+ var setStyle = (type, enable) => ({ state, dispatch }) => {
1921
+ const marker = inlineMarkerText(type);
1922
+ const changes = state.changeByRange((range) => {
1923
+ if (!enable && range.empty) {
1924
+ const after = state.doc.sliceString(range.head, range.head + 6);
1925
+ const found = after.indexOf(marker);
1926
+ if (found >= 0 && /^[*~`]*$/.test(after.slice(0, found))) {
1927
+ const before = state.doc.sliceString(range.head - 6, range.head);
1928
+ if (before.slice(before.length - found - marker.length, before.length - found) === marker && [
1929
+ ...before.slice(before.length - found)
1930
+ ].reverse().join("") === after.slice(0, found)) {
1931
+ return {
1932
+ changes: [
1933
+ {
1934
+ from: range.head - marker.length - found,
1935
+ to: range.head - found
1936
+ },
1937
+ {
1938
+ from: range.head + found,
1939
+ to: range.head + found + marker.length
1940
+ }
1941
+ ],
1942
+ range: EditorSelection.cursor(range.from - marker.length)
1943
+ };
1944
+ }
1945
+ }
1946
+ }
1947
+ const changes2 = [];
1948
+ const changesAtEnd = [];
1949
+ let blockStart = -1;
1950
+ let blockEnd = -1;
1951
+ let startCovered = false;
1952
+ let endCovered = false;
1953
+ let { from, to } = range;
1954
+ syntaxTree2(state).iterate({
1955
+ from,
1956
+ to,
1957
+ enter: (node) => {
1958
+ const { name } = node;
1959
+ if (Object.hasOwn(Textblocks, name) && Textblocks[name] !== "codeblock") {
1960
+ blockStart = blockContentStart(node);
1961
+ blockEnd = blockContentEnd(node, state.doc);
1962
+ startCovered = endCovered = false;
1963
+ } else if (name === "Link" || name === "Image" && enable) {
1964
+ if (from < node.from && to > node.from && to <= node.to) {
1965
+ to = node.to;
1966
+ } else if (to > node.to && from >= node.from && from < node.to) {
1967
+ from = node.from;
1968
+ }
1969
+ } else if (IgnoreInline.has(name) && enable) {
1970
+ if (node.from < from && node.to > from) {
1971
+ if (to === from) {
1972
+ to = node.to;
1973
+ }
1974
+ from = node.to;
1975
+ }
1976
+ if (node.from < to && node.to > to) {
1977
+ to = node.from;
1978
+ }
1979
+ } else if (Object.hasOwn(InlineMarker, name)) {
1980
+ const markType = InlineMarker[name];
1981
+ const size = inlineMarkerText(markType).length;
1982
+ const openEnd = node.from + size;
1983
+ const closeStart = node.to - size;
1984
+ if (markType === type) {
1985
+ if (openEnd <= from && closeStart >= from) {
1986
+ startCovered = openEnd === from ? "adjacent" : true;
1987
+ }
1988
+ if (openEnd <= to && closeStart >= to) {
1989
+ endCovered = closeStart === to ? "adjacent" : true;
1990
+ }
1991
+ }
1992
+ if (markType === type || type === 3 && enable) {
1993
+ if (node.from >= from && openEnd <= to) {
1994
+ changes2.push({
1995
+ from: node.from,
1996
+ to: openEnd
1997
+ });
1998
+ if (markType !== type && closeStart >= to) {
1999
+ changesAtEnd.push({
2000
+ from: skipSpaces(Math.min(to, blockEnd), state.doc, 1, blockEnd),
2001
+ insert: inlineMarkerText(markType)
2002
+ });
2003
+ }
2004
+ }
2005
+ if (closeStart >= from && node.to <= to) {
2006
+ changes2.push({
2007
+ from: closeStart,
2008
+ to: node.to
2009
+ });
2010
+ if (markType !== type && openEnd <= from) {
2011
+ changes2.push({
2012
+ from: skipSpaces(Math.max(from, blockStart), state.doc, -1, blockStart),
2013
+ insert: inlineMarkerText(markType)
2014
+ });
2015
+ }
2016
+ }
2017
+ }
2018
+ }
2019
+ },
2020
+ leave: (node) => {
2021
+ if (Object.hasOwn(Textblocks, node.name) && Textblocks[node.name] !== "codeblock") {
2022
+ const rangeStart = Math.max(from, blockStart);
2023
+ const rangeEnd = Math.min(to, blockEnd);
2024
+ if (enable) {
2025
+ if (!startCovered) {
2026
+ changes2.push({
2027
+ from: rangeStart,
2028
+ insert: marker
2029
+ });
2030
+ }
2031
+ if (!endCovered) {
2032
+ changes2.push({
2033
+ from: rangeEnd,
2034
+ insert: marker
2035
+ });
2036
+ }
2037
+ } else {
2038
+ if (startCovered === "adjacent") {
2039
+ changes2.push({
2040
+ from: from - marker.length,
2041
+ to: from
2042
+ });
2043
+ } else if (startCovered) {
2044
+ changes2.push({
2045
+ from: skipSpaces(rangeStart, state.doc, -1, blockStart),
2046
+ insert: marker
2047
+ });
2048
+ }
2049
+ if (endCovered === "adjacent") {
2050
+ changes2.push({
2051
+ from: to,
2052
+ to: to + marker.length
2053
+ });
2054
+ } else if (endCovered) {
2055
+ changes2.push({
2056
+ from: skipSpaces(rangeEnd, state.doc, 1, blockEnd),
2057
+ insert: marker
2058
+ });
2059
+ }
2060
+ }
2061
+ }
2062
+ }
2063
+ });
2064
+ const changeSet = state.changes(changes2.concat(changesAtEnd));
2065
+ return {
2066
+ changes: changeSet,
2067
+ range: range.empty && !changeSet.empty ? EditorSelection.cursor(range.head + marker.length) : EditorSelection.range(changeSet.mapPos(range.from, 1), changeSet.mapPos(range.to, -1))
2068
+ };
2069
+ });
2070
+ dispatch(state.update(changes, {
2071
+ userEvent: enable ? "format.style.add" : "format.style.remove",
2072
+ scrollIntoView: true
2073
+ }));
2074
+ return true;
2075
+ };
2076
+ var addStyle = (style) => setStyle(style, true);
2077
+ var removeStyle = (style) => setStyle(style, false);
2078
+ var toggleStyle = (style) => (arg) => {
2079
+ const form = getFormatting(arg.state);
2080
+ return setStyle(style, style === 0 ? !form.strong : style === 1 ? !form.emphasis : style === 2 ? !form.strikethrough : !form.code)(arg);
2081
+ };
2082
+ var toggleStrong = toggleStyle(0);
2083
+ var toggleEmphasis = toggleStyle(1);
2084
+ var toggleStrikethrough = toggleStyle(2);
2085
+ var toggleInlineCode = toggleStyle(3);
2086
+ var inlineMarkerText = (type) => type === 0 ? "**" : type === 2 ? "~~" : type === 1 ? "*" : "`";
2087
+ var blockContentStart = (node) => {
2088
+ const atx = /^ATXHeading(\d)/.exec(node.name);
2089
+ if (atx) {
2090
+ return Math.min(node.to, node.from + +atx[1] + 1);
2091
+ }
2092
+ return node.from;
2093
+ };
2094
+ var blockContentEnd = (node, doc) => {
2095
+ const setext = /^SetextHeading(\d)/.exec(node.name);
2096
+ const lastLine = doc.lineAt(node.to);
2097
+ if (setext || /^[\s>]*$/.exec(lastLine.text)) {
2098
+ return lastLine.from - 1;
2099
+ }
2100
+ return node.to;
2101
+ };
2102
+ var skipSpaces = (pos, doc, dir, limit) => {
2103
+ const line = doc.lineAt(pos);
2104
+ while (pos !== limit && line.text[pos - line.from - (dir < 0 ? 1 : 0)] === " ") {
2105
+ pos += dir;
2106
+ }
2107
+ return pos;
2108
+ };
2109
+ var snippets = {
2110
+ codeblock: snippet([
2111
+ //
2112
+ "```#{}",
2113
+ "#{}",
2114
+ "```"
2115
+ ].join("\n")),
2116
+ table: snippet([
2117
+ //
2118
+ "| #{col1} | #{col2} |",
2119
+ "| ---- | ---- |",
2120
+ "| #{val1} | #{val2} |",
2121
+ "| #{val3} | #{val4} |",
2122
+ ""
2123
+ ].join("\n"))
2124
+ };
2125
+ var insertTable = (view) => {
2126
+ const { selection: { main }, doc } = view.state;
2127
+ const { number } = doc.lineAt(main.anchor);
2128
+ const { from } = doc.line(number);
2129
+ snippets.table(view, null, from, from);
2130
+ };
2131
+ var removeLinkInner = (from, to, changes, state) => {
2132
+ syntaxTree2(state).iterate({
2133
+ from,
2134
+ to,
2135
+ enter: (node) => {
2136
+ if (node.name === "Link" && node.from < to && node.to > from) {
2137
+ node.node.cursor().iterate((node2) => {
2138
+ const { name } = node2;
2139
+ if (name === "LinkMark" || name === "LinkLabel") {
2140
+ changes.push({
2141
+ from: node2.from,
2142
+ to: node2.to
2143
+ });
2144
+ } else if (name === "LinkTitle" || name === "URL") {
2145
+ changes.push({
2146
+ from: skipSpaces(node2.from, state.doc, -1),
2147
+ to: skipSpaces(node2.to, state.doc, 1)
2148
+ });
2149
+ }
2150
+ });
2151
+ return false;
2152
+ }
2153
+ }
2154
+ });
2155
+ };
2156
+ var removeLink = ({ state, dispatch }) => {
2157
+ const changes = [];
2158
+ for (const { from, to } of state.selection.ranges) {
2159
+ removeLinkInner(from, to, changes, state);
2160
+ }
2161
+ if (!changes) {
2162
+ return false;
2163
+ }
2164
+ dispatch(state.update({
2165
+ changes,
2166
+ userEvent: "format.link.remove",
2167
+ scrollIntoView: true
2168
+ }));
2169
+ return true;
2170
+ };
2171
+ var addLink = ({ state, dispatch }) => {
2172
+ const changes = state.changeByRange((range) => {
2173
+ let { from, to } = range;
2174
+ const cutStyles = [];
2175
+ let okay = null;
2176
+ syntaxTree2(state).iterate({
2177
+ from,
2178
+ to,
2179
+ enter: (node) => {
2180
+ if (Object.hasOwn(Textblocks, node.name)) {
2181
+ okay = Textblocks[node.name] !== "codeblock" && from >= blockContentStart(node) && to <= blockContentEnd(node, state.doc);
2182
+ } else if (Object.hasOwn(InlineMarker, node.name)) {
2183
+ const sNode = node.node;
2184
+ if (node.from < from && node.to <= to) {
2185
+ if (sNode.firstChild.to === from) {
2186
+ from = node.from;
2187
+ } else {
2188
+ cutStyles.push(sNode);
2189
+ }
2190
+ } else if (node.from >= from && node.to > to) {
2191
+ if (sNode.lastChild.from === to) {
2192
+ to = node.to;
2193
+ } else {
2194
+ cutStyles.push(sNode);
2195
+ }
2196
+ }
2197
+ }
2198
+ }
2199
+ });
2200
+ if (okay === null) {
2201
+ const line = state.doc.lineAt(from);
2202
+ okay = to <= line.to && !/\S/.test(line.text.slice(from - line.from));
2203
+ }
2204
+ if (!okay) {
2205
+ return {
2206
+ range
2207
+ };
2208
+ }
2209
+ const changes2 = [];
2210
+ const changesAfter = [];
2211
+ removeLinkInner(from, to, changesAfter, state);
2212
+ let cursorOffset = 1;
2213
+ for (const style of cutStyles) {
2214
+ const type = InlineMarker[style.name];
2215
+ const mark2 = inlineMarkerText(type);
2216
+ if (style.from < from) {
2217
+ changes2.push({
2218
+ from: skipSpaces(from, state.doc, -1),
2219
+ insert: mark2
2220
+ });
2221
+ changesAfter.push({
2222
+ from: skipSpaces(from, state.doc, 1, to),
2223
+ insert: mark2
2224
+ });
2225
+ } else {
2226
+ changes2.push({
2227
+ from: skipSpaces(to, state.doc, -1, from),
2228
+ insert: mark2
2229
+ });
2230
+ const after = skipSpaces(to, state.doc, 1);
2231
+ if (after === to) {
2232
+ cursorOffset += mark2.length;
2233
+ }
2234
+ changesAfter.push({
2235
+ from: after,
2236
+ insert: mark2
2237
+ });
2238
+ }
2239
+ }
2240
+ changes2.push({
2241
+ from,
2242
+ insert: "["
2243
+ }, {
2244
+ from: to,
2245
+ insert: "]()"
2246
+ });
2247
+ const changeSet = state.changes(changes2.concat(changesAfter));
2248
+ return {
2249
+ changes: changeSet,
2250
+ range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset)
2251
+ };
2252
+ });
2253
+ if (changes.changes.empty) {
2254
+ return false;
2255
+ }
2256
+ dispatch(state.update(changes, {
2257
+ userEvent: "format.link.add",
2258
+ scrollIntoView: true
2259
+ }));
2260
+ return true;
2261
+ };
2262
+ var addList = (type) => ({ state, dispatch }) => {
2263
+ let lastBlock = -1;
2264
+ let counter = 1;
2265
+ let first = true;
2266
+ let parentColumn = null;
2267
+ const blocks = [];
2268
+ for (const { from, to } of state.selection.ranges) {
2269
+ syntaxTree2(state).iterate({
2270
+ from,
2271
+ to,
2272
+ enter: (node) => {
2273
+ if (Object.hasOwn(Textblocks, node.name) && node.name !== "TableCell" || node.name === "Table") {
2274
+ if (first) {
2275
+ let before = node.node.prevSibling;
2276
+ while (before && /Mark$/.test(before.name)) {
2277
+ before = before.prevSibling;
2278
+ }
2279
+ if (before?.name === (type === 0 ? "OrderedList" : "BulletList")) {
2280
+ const item = before.lastChild;
2281
+ const itemLine = state.doc.lineAt(item.from);
2282
+ const itemText = itemLine.text.slice(item.from - itemLine.from);
2283
+ parentColumn = item.from - itemLine.from + /^\s*/.exec(itemText)[0].length;
2284
+ if (type === 0) {
2285
+ const mark2 = /^\s*(\d+)[.)]/.exec(itemText);
2286
+ if (mark2) {
2287
+ parentColumn += mark2[1].length;
2288
+ counter = +mark2[1] + 1;
2289
+ }
2290
+ }
2291
+ }
2292
+ first = false;
2293
+ }
2294
+ if (node.from === lastBlock) {
2295
+ return;
2296
+ }
2297
+ lastBlock = node.from;
2298
+ blocks.push({
2299
+ node: node.node,
2300
+ counter,
2301
+ parentColumn
2302
+ });
2303
+ counter++;
2304
+ return false;
2305
+ }
2306
+ },
2307
+ leave: (node) => {
2308
+ if (node.name === "BulletList" || node.name === "OrderedList" || node.name === "Blockquote") {
2309
+ counter = 1;
2310
+ parentColumn = null;
2311
+ }
2312
+ }
2313
+ });
2314
+ }
2315
+ if (!blocks.length) {
2316
+ const { from, to } = state.doc.lineAt(state.selection.main.anchor);
2317
+ if (from === to) {
2318
+ dispatch(state.update({
2319
+ changes: [
2320
+ {
2321
+ from,
2322
+ insert: type === 1 ? "- " : type === 0 ? "1. " : "- [ ] "
2323
+ }
2324
+ ],
2325
+ userEvent: "format.list.add",
2326
+ scrollIntoView: true
2327
+ }));
2328
+ return true;
2329
+ }
2330
+ return false;
2331
+ }
2332
+ const changes = [];
2333
+ for (let i = 0; i < blocks.length; i++) {
2334
+ const { node, counter: counter2, parentColumn: parentColumn2 } = blocks[i];
2335
+ const nodeFrom = node.name === "CodeBlock" ? node.from - 4 : node.from;
2336
+ let padding = nodeFrom > 0 && !/\s/.test(state.doc.sliceString(nodeFrom - 1, nodeFrom)) ? 1 : 0;
2337
+ if (type === 0) {
2338
+ padding += String(counter2).length;
2339
+ }
2340
+ let line = state.doc.lineAt(nodeFrom);
2341
+ const column = nodeFrom - line.from;
2342
+ if (parentColumn2 !== null && parentColumn2 > column) {
2343
+ padding = Math.max(padding, parentColumn2 - column);
2344
+ }
2345
+ let mark2;
2346
+ if (type === 0) {
2347
+ let max = counter2;
2348
+ for (let j = i + 1; j < blocks.length; j++) {
2349
+ if (blocks[j].counter !== max + 1) {
2350
+ break;
2351
+ }
2352
+ max++;
2353
+ }
2354
+ const num = String(counter2);
2355
+ padding = Math.max(String(max).length, padding);
2356
+ mark2 = " ".repeat(Math.max(0, padding - num.length)) + num + ". ";
2357
+ } else {
2358
+ mark2 = " ".repeat(padding) + "- " + (type === 2 ? "[ ] " : "");
2359
+ }
2360
+ changes.push({
2361
+ from: nodeFrom,
2362
+ insert: mark2
2363
+ });
2364
+ while (line.to < node.to) {
2365
+ line = state.doc.lineAt(line.to + 1);
2366
+ const open = /^[\s>]*/.exec(line.text)[0].length;
1778
2367
  changes.push({
1779
- from,
1780
- to: from + current + (spaces?.length ?? 0),
1781
- insert: "#".repeat(level) + (level > 0 ? " " : "")
2368
+ from: line.from + Math.min(open, column),
2369
+ insert: " ".repeat(mark2.length)
1782
2370
  });
1783
2371
  }
1784
2372
  }
1785
- if (changes.length) {
1786
- view.dispatch({
1787
- changes
2373
+ if (type === 0) {
2374
+ const last = blocks[blocks.length - 1];
2375
+ let next = last.node.nextSibling;
2376
+ while (next && /Mark$/.test(next.name)) {
2377
+ next = next.nextSibling;
2378
+ }
2379
+ if (next?.name === "OrderedList") {
2380
+ renumberListItems(next.firstChild, last.counter + 1, changes, state.doc);
2381
+ }
2382
+ }
2383
+ dispatch(state.update({
2384
+ changes,
2385
+ userEvent: "format.list.add",
2386
+ scrollIntoView: true
2387
+ }));
2388
+ return true;
2389
+ };
2390
+ var removeList = (type) => ({ state, dispatch }) => {
2391
+ let lastBlock = -1;
2392
+ const changes = [];
2393
+ const stack = [];
2394
+ const targetNodeType = type === 0 ? "OrderedList" : type === 1 ? "BulletList" : "TaskList";
2395
+ for (const { from, to } of state.selection.ranges) {
2396
+ syntaxTree2(state).iterate({
2397
+ from,
2398
+ to,
2399
+ enter: (node) => {
2400
+ const { name } = node;
2401
+ if (name === "BulletList" || name === "OrderedList" || name === "Blockquote") {
2402
+ stack.push(name);
2403
+ } else if (name === "Task" && stack[stack.length - 1] === "BulletList") {
2404
+ stack[stack.length - 1] = "TaskList";
2405
+ }
2406
+ },
2407
+ leave: (node) => {
2408
+ const { name } = node;
2409
+ if (name === "BulletList" || name === "OrderedList" || name === "Blockquote") {
2410
+ stack.pop();
2411
+ } else if (name === "ListItem" && stack[stack.length - 1] === targetNodeType && node.from !== lastBlock) {
2412
+ lastBlock = node.from;
2413
+ let line = state.doc.lineAt(node.from);
2414
+ const mark2 = /^\s*(\d+[.)] |[-*+] (\[[ x]\] )?)/.exec(line.text.slice(node.from - line.from));
2415
+ if (!mark2) {
2416
+ return false;
2417
+ }
2418
+ const column = node.from - line.from;
2419
+ changes.push({
2420
+ from: node.from,
2421
+ to: node.from + mark2[0].length
2422
+ });
2423
+ while (line.to < node.to) {
2424
+ line = state.doc.lineAt(line.to + 1);
2425
+ const open = /^[\s>]*/.exec(line.text)[0].length;
2426
+ if (open > column) {
2427
+ changes.push({
2428
+ from: line.from + column,
2429
+ to: line.from + Math.min(column + mark2[0].length, open)
2430
+ });
2431
+ }
2432
+ }
2433
+ if (node.to >= to) {
2434
+ renumberListItems(node.node.nextSibling, 1, changes, state.doc);
2435
+ }
2436
+ return false;
2437
+ }
2438
+ }
1788
2439
  });
1789
2440
  }
2441
+ if (!changes.length) {
2442
+ return false;
2443
+ }
2444
+ dispatch(state.update({
2445
+ changes,
2446
+ userEvent: "format.list.remove",
2447
+ scrollIntoView: true
2448
+ }));
1790
2449
  return true;
1791
2450
  };
1792
- var toggleStyle = (mark2) => (view) => {
1793
- const { ranges } = view.state.selection;
1794
- for (const range of ranges) {
1795
- if (range.from === range.to) {
1796
- return false;
2451
+ var toggleList = (type) => (target) => {
2452
+ const formatting2 = getFormatting(target.state);
2453
+ const active = formatting2.listStyle === (type === 1 ? "bullet" : type === 0 ? "ordered" : "task");
2454
+ return (active ? removeList(type) : addList(type))(target);
2455
+ };
2456
+ var renumberListItems = (item, counter, changes, doc) => {
2457
+ for (; item; item = item.nextSibling) {
2458
+ if (item.name === "ListItem") {
2459
+ const number = /(\s*)(\d+)[.)]/.exec(doc.sliceString(item.from, item.from + 10));
2460
+ if (!number || +number[2] === counter) {
2461
+ break;
2462
+ }
2463
+ const size = number[1].length + number[2].length;
2464
+ const newNum = String(counter);
2465
+ changes.push({
2466
+ from: item.from + Math.max(0, size - newNum.length),
2467
+ to: item.from + size,
2468
+ insert: newNum
2469
+ });
2470
+ counter++;
1797
2471
  }
1798
- view.dispatch({
1799
- changes: [
1800
- {
1801
- from: range.from,
1802
- insert: mark2
1803
- },
1804
- {
1805
- from: range.to,
1806
- insert: mark2
2472
+ }
2473
+ };
2474
+ var setBlockquote = (enable) => ({ state, dispatch }) => {
2475
+ const lines = [];
2476
+ let lastBlock = -1;
2477
+ for (const { from, to } of state.selection.ranges) {
2478
+ syntaxTree2(state).iterate({
2479
+ from,
2480
+ to,
2481
+ enter: (node) => {
2482
+ if (Object.hasOwn(Textblocks, node.name) || node.name === "Table") {
2483
+ if (node.from === lastBlock) {
2484
+ return false;
2485
+ }
2486
+ lastBlock = node.from;
2487
+ let line = state.doc.lineAt(node.from);
2488
+ if (line.number > 1) {
2489
+ const prevLine = state.doc.line(line.number - 1);
2490
+ if (/^[>\s]*$/.test(prevLine.text)) {
2491
+ if (!enable || lines.length && lines[lines.length - 1].number === prevLine.number - 1) {
2492
+ lines.push(prevLine);
2493
+ }
2494
+ }
2495
+ }
2496
+ for (; ; ) {
2497
+ lines.push(line);
2498
+ if (line.to >= node.to) {
2499
+ break;
2500
+ }
2501
+ line = state.doc.line(line.number + 1);
2502
+ }
2503
+ if (!enable && line.number < state.doc.lines) {
2504
+ const nextLine = state.doc.line(line.number + 1);
2505
+ if (/^[>\s]*$/.test(nextLine.text)) {
2506
+ lines.push(nextLine);
2507
+ }
2508
+ }
2509
+ return false;
1807
2510
  }
1808
- ]
2511
+ }
1809
2512
  });
1810
2513
  }
2514
+ const changes = [];
2515
+ for (const line of lines) {
2516
+ if (enable) {
2517
+ changes.push({
2518
+ from: line.from,
2519
+ insert: /\S/.test(line.text) ? "> " : ">"
2520
+ });
2521
+ } else {
2522
+ const quote = /((?:[\s>\-+*]|\d+[.)])*?)> ?/.exec(line.text);
2523
+ if (quote) {
2524
+ changes.push({
2525
+ from: line.from + quote[1].length,
2526
+ to: line.from + quote[0].length
2527
+ });
2528
+ }
2529
+ }
2530
+ }
2531
+ if (!changes.length) {
2532
+ return false;
2533
+ }
2534
+ dispatch(state.update({
2535
+ changes,
2536
+ userEvent: enable ? "format.blockquote.add" : "format.blockquote.remove",
2537
+ scrollIntoView: true
2538
+ }));
1811
2539
  return true;
1812
2540
  };
1813
- var toggleBold = toggleStyle("**");
1814
- var toggleItalic = toggleStyle("_");
1815
- var toggleStrikethrough = toggleStyle("~~");
1816
- var toggleList = (view) => {
2541
+ var addBlockquote = setBlockquote(true);
2542
+ var removeBlockquote = setBlockquote(false);
2543
+ var toggleBlockquote = (target) => {
2544
+ return (getFormatting(target.state).blockQuote ? removeBlockquote : addBlockquote)(target);
2545
+ };
2546
+ var addCodeblock = (target) => {
2547
+ const { state, dispatch } = target;
2548
+ const { selection } = state;
2549
+ if (selection.ranges.length === 1 && selection.main.empty) {
2550
+ const { head } = selection.main;
2551
+ const line = state.doc.lineAt(head);
2552
+ if (!/\S/.test(line.text) && head === line.from) {
2553
+ snippets.codeblock(target, null, line.from, line.to);
2554
+ return true;
2555
+ }
2556
+ }
2557
+ const ranges = [];
2558
+ for (const { from, to } of selection.ranges) {
2559
+ let blockFrom = from;
2560
+ let blockTo = to;
2561
+ syntaxTree2(state).iterate({
2562
+ from,
2563
+ to,
2564
+ enter: (node) => {
2565
+ if (Object.hasOwn(Textblocks, node.name)) {
2566
+ if (from >= node.from && to <= node.to) {
2567
+ blockFrom = node.from;
2568
+ blockTo = node.to;
2569
+ } else {
2570
+ blockFrom = Math.min(blockFrom, state.doc.lineAt(node.from).from);
2571
+ blockTo = Math.max(blockTo, state.doc.lineAt(node.to).to);
2572
+ }
2573
+ }
2574
+ }
2575
+ });
2576
+ if (ranges.length && ranges[ranges.length - 1].to >= blockFrom - 1) {
2577
+ ranges[ranges.length - 1].to = blockTo;
2578
+ } else {
2579
+ ranges.push({
2580
+ from: blockFrom,
2581
+ to: blockTo
2582
+ });
2583
+ }
2584
+ }
2585
+ if (!ranges.length) {
2586
+ return false;
2587
+ }
2588
+ const changes = ranges.map(({ from, to }) => {
2589
+ const column = from - state.doc.lineAt(from).from;
2590
+ return [
2591
+ {
2592
+ from,
2593
+ insert: "```\n" + " ".repeat(column)
2594
+ },
2595
+ {
2596
+ from: to,
2597
+ insert: "\n" + " ".repeat(column) + "```"
2598
+ }
2599
+ ];
2600
+ });
2601
+ dispatch(state.update({
2602
+ changes,
2603
+ userEvent: "format.codeblock.add",
2604
+ scrollIntoView: true
2605
+ }));
2606
+ return true;
2607
+ };
2608
+ var removeCodeblock = ({ state, dispatch }) => {
2609
+ const changes = [];
2610
+ let lastBlock = -1;
2611
+ for (const { from, to } of state.selection.ranges) {
2612
+ syntaxTree2(state).iterate({
2613
+ from,
2614
+ to,
2615
+ enter: (node) => {
2616
+ if (Textblocks[node.name] === "codeblock" && lastBlock !== node.from) {
2617
+ lastBlock = node.from;
2618
+ const firstLine = state.doc.lineAt(node.from);
2619
+ if (node.name === "FencedCode") {
2620
+ changes.push({
2621
+ from: node.from,
2622
+ to: firstLine.to + 1 + node.from - firstLine.from
2623
+ });
2624
+ const lastLine = state.doc.lineAt(node.to);
2625
+ if (/^([\s>]|[-*+] |\d+[).])*`+$/.test(lastLine.text)) {
2626
+ changes.push({
2627
+ from: lastLine.from - (lastLine.number === firstLine.number + 1 ? 0 : 1),
2628
+ to: lastLine.to
2629
+ });
2630
+ }
2631
+ } else {
2632
+ const column = node.from - firstLine.from;
2633
+ for (let line = firstLine; ; line = state.doc.line(line.number + 1)) {
2634
+ changes.push({
2635
+ from: line.from + column - 4,
2636
+ to: line.from + column
2637
+ });
2638
+ if (line.to >= node.to) {
2639
+ break;
2640
+ }
2641
+ }
2642
+ }
2643
+ }
2644
+ }
2645
+ });
2646
+ }
2647
+ if (!changes.length) {
2648
+ return false;
2649
+ }
2650
+ dispatch(state.update({
2651
+ changes,
2652
+ userEvent: "format.codeblock.remove",
2653
+ scrollIntoView: true
2654
+ }));
2655
+ return true;
2656
+ };
2657
+ var toggleCodeblock = (target) => {
2658
+ return (getFormatting(target.state).blockType === "codeblock" ? removeCodeblock : addCodeblock)(target);
1817
2659
  };
1818
2660
  var formatting = (options = {}) => {
1819
2661
  return [
1820
2662
  keymap5.of([
1821
2663
  {
1822
2664
  key: "meta-b",
1823
- run: toggleBold
2665
+ run: toggleStrong
1824
2666
  }
1825
2667
  ]),
1826
2668
  styling()
@@ -1831,9 +2673,9 @@ var styling = () => {
1831
2673
  const builder = new RangeSetBuilder2();
1832
2674
  const { state } = view;
1833
2675
  const cursor = state.selection.main.head;
1834
- const replace = (node, marks2) => {
2676
+ const replace = (node, marks) => {
1835
2677
  if (cursor <= node.from || cursor >= node.to) {
1836
- for (const mark2 of marks2) {
2678
+ for (const mark2 of marks) {
1837
2679
  builder.add(mark2.from, mark2.to, Decoration4.replace({}));
1838
2680
  }
1839
2681
  }
@@ -1844,13 +2686,13 @@ var styling = () => {
1844
2686
  switch (node.name) {
1845
2687
  case "Emphasis":
1846
2688
  case "StrongEmphasis": {
1847
- const marks2 = node.node.getChildren("EmphasisMark");
1848
- replace(node, marks2);
2689
+ const marks = node.node.getChildren("EmphasisMark");
2690
+ replace(node, marks);
1849
2691
  break;
1850
2692
  }
1851
2693
  case "Strikethrough": {
1852
- const marks2 = node.node.getChildren("StrikethroughMark");
1853
- replace(node, marks2);
2694
+ const marks = node.node.getChildren("StrikethroughMark");
2695
+ replace(node, marks);
1854
2696
  break;
1855
2697
  }
1856
2698
  }
@@ -1874,6 +2716,209 @@ var styling = () => {
1874
2716
  })
1875
2717
  ];
1876
2718
  };
2719
+ var InlineMarker = {
2720
+ Emphasis: 1,
2721
+ StrongEmphasis: 0,
2722
+ InlineCode: 3,
2723
+ Strikethrough: 2
2724
+ };
2725
+ var IgnoreInline = /* @__PURE__ */ new Set([
2726
+ "Autolink",
2727
+ "CodeMark",
2728
+ "CodeText",
2729
+ "Comment",
2730
+ "EmphasisMark",
2731
+ "Hardbreak",
2732
+ "HeaderMark",
2733
+ "HTMLTag",
2734
+ "LinkMark",
2735
+ "ListMark",
2736
+ "ProcessingInstruction",
2737
+ "QuoteMark",
2738
+ "StrikethroughMark",
2739
+ "SubscriptMark",
2740
+ "SuperscriptMark",
2741
+ "TaskMarker"
2742
+ ]);
2743
+ var Textblocks = {
2744
+ ATXHeading1: "heading1",
2745
+ ATXHeading2: "heading2",
2746
+ ATXHeading3: "heading3",
2747
+ ATXHeading4: "heading4",
2748
+ ATXHeading5: "heading5",
2749
+ ATXHeading6: "heading6",
2750
+ CodeBlock: "codeblock",
2751
+ FencedCode: "codeblock",
2752
+ Paragraph: "paragraph",
2753
+ SetextHeading1: "heading1",
2754
+ SetextHeading2: "heading2",
2755
+ TableCell: "tablecell",
2756
+ Task: "paragraph"
2757
+ };
2758
+ var getFormatting = (state) => {
2759
+ let blockType = null;
2760
+ const inline = [
2761
+ null,
2762
+ null,
2763
+ null,
2764
+ null
2765
+ ];
2766
+ let link2 = false;
2767
+ let blockQuote = null;
2768
+ let listStyle = null;
2769
+ const stack = [];
2770
+ let currentBlock = null;
2771
+ const advanceInline = (upto) => {
2772
+ if (!currentBlock) {
2773
+ return;
2774
+ }
2775
+ upto = Math.min(upto, currentBlock.end);
2776
+ if (upto <= currentBlock.pos) {
2777
+ return;
2778
+ }
2779
+ for (let i = 0; i < currentBlock.active.length; i++) {
2780
+ if (inline[i] === false) {
2781
+ continue;
2782
+ } else if (currentBlock.active[i]) {
2783
+ inline[i] = true;
2784
+ } else if (/\S/.test(state.doc.sliceString(currentBlock.pos, upto))) {
2785
+ inline[i] = false;
2786
+ }
2787
+ }
2788
+ currentBlock.pos = upto;
2789
+ };
2790
+ const skipInline = (upto) => {
2791
+ if (currentBlock && upto > currentBlock.pos) {
2792
+ currentBlock.pos = Math.min(upto, currentBlock.end);
2793
+ }
2794
+ };
2795
+ const { selection } = state;
2796
+ for (const range of selection.ranges) {
2797
+ if (range.empty && inline.some((v) => v === null)) {
2798
+ const contextSize = Math.min(range.head, 6);
2799
+ const contextBefore = state.doc.sliceString(range.head - contextSize, range.head);
2800
+ let contextAfter = state.doc.sliceString(range.head, range.head + contextSize);
2801
+ for (let i = 0; i < contextSize; i++) {
2802
+ const ch = contextAfter[i];
2803
+ if (ch !== contextBefore[contextBefore.length - 1 - i] || !/[~`*]/.test(ch)) {
2804
+ contextAfter = contextAfter.slice(0, i);
2805
+ break;
2806
+ }
2807
+ }
2808
+ for (let i = 0; i < inline.length; i++) {
2809
+ const mark2 = inlineMarkerText(i);
2810
+ const found = contextAfter.indexOf(mark2);
2811
+ if (found > -1) {
2812
+ contextAfter = contextAfter.slice(0, found) + contextAfter.slice(found + mark2.length);
2813
+ if (inline[i] === null) {
2814
+ inline[i] = true;
2815
+ }
2816
+ }
2817
+ }
2818
+ }
2819
+ syntaxTree2(state).iterate({
2820
+ from: range.from,
2821
+ to: range.to,
2822
+ enter: (node) => {
2823
+ advanceInline(node.from);
2824
+ const { name } = node;
2825
+ if (name === "BulletList" || name === "OrderedList" || name === "Blockquote") {
2826
+ stack.push(name);
2827
+ } else if (name === "Link") {
2828
+ link2 = true;
2829
+ } else if (Object.hasOwn(Textblocks, name) && (range.empty || node.to > range.from || node.from < range.to)) {
2830
+ if (name === "Task" && stack[stack.length - 1] === "BulletList") {
2831
+ stack[stack.length - 1] = "TaskList";
2832
+ }
2833
+ const blockCode = Textblocks[name];
2834
+ if (blockType === null) {
2835
+ blockType = blockCode;
2836
+ } else if (blockType !== blockCode) {
2837
+ blockType = false;
2838
+ }
2839
+ if (blockCode !== "codeblock" && inline.some((i) => i !== false)) {
2840
+ currentBlock = {
2841
+ pos: Math.max(range.from, node.from),
2842
+ end: Math.min(range.to, node.to),
2843
+ active: [
2844
+ false,
2845
+ false,
2846
+ false,
2847
+ false
2848
+ ]
2849
+ };
2850
+ }
2851
+ } else if (Object.hasOwn(InlineMarker, name) && currentBlock) {
2852
+ const index = InlineMarker[name];
2853
+ if (range.empty && inline[index] === null) {
2854
+ inline[index] = true;
2855
+ }
2856
+ currentBlock.active[index] = true;
2857
+ } else if (IgnoreInline.has(name)) {
2858
+ skipInline(node.to);
2859
+ }
2860
+ },
2861
+ leave: (node) => {
2862
+ advanceInline(node.to);
2863
+ const { name } = node;
2864
+ if (name === "BulletList" || name === "OrderedList" || name === "Blockquote") {
2865
+ stack.pop();
2866
+ } else if (Object.hasOwn(Textblocks, name)) {
2867
+ let hasList = false;
2868
+ let hasQuote = false;
2869
+ for (let i = stack.length - 1; i >= 0; i--) {
2870
+ if (stack[i] === "Blockquote") {
2871
+ hasQuote = true;
2872
+ } else if (!hasList) {
2873
+ hasList = stack[i] === "TaskList" ? "task" : stack[i] === "BulletList" ? "bullet" : "ordered";
2874
+ }
2875
+ }
2876
+ if (blockQuote === null) {
2877
+ blockQuote = hasQuote;
2878
+ } else if (!hasQuote && blockQuote) {
2879
+ blockQuote = false;
2880
+ }
2881
+ if (listStyle === null) {
2882
+ listStyle = hasList;
2883
+ } else if (listStyle !== hasList) {
2884
+ listStyle = false;
2885
+ }
2886
+ currentBlock = null;
2887
+ } else if (Object.hasOwn(InlineMarker, name) && currentBlock) {
2888
+ currentBlock.active[InlineMarker[name]] = false;
2889
+ }
2890
+ }
2891
+ });
2892
+ }
2893
+ const { from, to } = state.doc.lineAt(selection.main.anchor);
2894
+ const blankLine = from === to;
2895
+ return {
2896
+ blankLine,
2897
+ blockType: blockType || null,
2898
+ blockQuote: blockQuote ?? false,
2899
+ code: inline[3] ?? false,
2900
+ emphasis: inline[1] ?? false,
2901
+ strong: inline[0] ?? false,
2902
+ strikethrough: inline[2] ?? false,
2903
+ link: link2,
2904
+ listStyle: listStyle || null
2905
+ };
2906
+ };
2907
+ var useFormattingState = () => {
2908
+ const [state, setState] = useState(null);
2909
+ const observer = useMemo(() => EditorView9.updateListener.of((update2) => {
2910
+ if (update2.docChanged || update2.selectionSet) {
2911
+ const newState = getFormatting(update2.state);
2912
+ if (!state || !compareFormatting(state, newState)) {
2913
+ setState(newState);
2914
+ }
2915
+ }
2916
+ }), []);
2917
+ return [
2918
+ state,
2919
+ observer
2920
+ ];
2921
+ };
1877
2922
 
1878
2923
  // packages/ui/react-ui-editor/src/extensions/markdown/heading.ts
1879
2924
  import { syntaxTree as syntaxTree3 } from "@codemirror/language";
@@ -1926,8 +2971,8 @@ var heading2 = () => {
1926
2971
  // packages/ui/react-ui-editor/src/extensions/markdown/hr.ts
1927
2972
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
1928
2973
  import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
1929
- import { Decoration as Decoration6, EditorView as EditorView9, ViewPlugin as ViewPlugin6, WidgetType as WidgetType3 } from "@codemirror/view";
1930
- var styles4 = EditorView9.baseTheme({
2974
+ import { Decoration as Decoration6, EditorView as EditorView10, ViewPlugin as ViewPlugin6, WidgetType as WidgetType3 } from "@codemirror/view";
2975
+ var styles4 = EditorView10.baseTheme({
1931
2976
  "& .cm-hr": {
1932
2977
  // Note that block-level decorations should not have vertical margins,
1933
2978
  borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
@@ -1981,7 +3026,7 @@ var hr = () => {
1981
3026
  // packages/ui/react-ui-editor/src/extensions/markdown/image.ts
1982
3027
  import { syntaxTree as syntaxTree5 } from "@codemirror/language";
1983
3028
  import { StateField as StateField3 } from "@codemirror/state";
1984
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType4 } from "@codemirror/view";
3029
+ import { Decoration as Decoration7, EditorView as EditorView11, WidgetType as WidgetType4 } from "@codemirror/view";
1985
3030
  var image = (options = {}) => {
1986
3031
  return StateField3.define({
1987
3032
  create: (state) => {
@@ -2008,7 +3053,7 @@ var image = (options = {}) => {
2008
3053
  add: buildDecorations4(from, to, tr.state)
2009
3054
  });
2010
3055
  },
2011
- provide: (field) => EditorView10.decorations.from(field)
3056
+ provide: (field) => EditorView11.decorations.from(field)
2012
3057
  });
2013
3058
  };
2014
3059
  var buildDecorations4 = (from, to, state) => {
@@ -2160,8 +3205,8 @@ var buildDecorations5 = (view, options) => {
2160
3205
  syntaxTree6(state).iterate({
2161
3206
  enter: (node) => {
2162
3207
  if (node.name === "Link") {
2163
- const marks2 = node.node.getChildren("LinkMark");
2164
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
3208
+ const marks = node.node.getChildren("LinkMark");
3209
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
2165
3210
  const urlNode = node.node.getChild("URL");
2166
3211
  const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
2167
3212
  if (!url) {
@@ -2190,12 +3235,12 @@ var buildDecorations5 = (view, options) => {
2190
3235
  // packages/ui/react-ui-editor/src/extensions/markdown/table.ts
2191
3236
  import { syntaxTree as syntaxTree7 } from "@codemirror/language";
2192
3237
  import { RangeSetBuilder as RangeSetBuilder6, StateField as StateField4 } from "@codemirror/state";
2193
- import { Decoration as Decoration9, EditorView as EditorView11, WidgetType as WidgetType6 } from "@codemirror/view";
3238
+ import { Decoration as Decoration9, EditorView as EditorView12, WidgetType as WidgetType6 } from "@codemirror/view";
2194
3239
  var table = (options = {}) => {
2195
3240
  return StateField4.define({
2196
3241
  create: (state) => update(state, options),
2197
3242
  update: (_, tr) => update(tr.state, options),
2198
- provide: (field) => EditorView11.decorations.from(field)
3243
+ provide: (field) => EditorView12.decorations.from(field)
2199
3244
  });
2200
3245
  };
2201
3246
  var update = (state, options) => {
@@ -2288,8 +3333,8 @@ var TableWidget = class extends WidgetType6 {
2288
3333
  // packages/ui/react-ui-editor/src/extensions/markdown/tasklist.ts
2289
3334
  import { syntaxTree as syntaxTree8 } from "@codemirror/language";
2290
3335
  import { RangeSetBuilder as RangeSetBuilder7 } from "@codemirror/state";
2291
- import { EditorView as EditorView12, Decoration as Decoration10, WidgetType as WidgetType7, ViewPlugin as ViewPlugin8 } from "@codemirror/view";
2292
- var styles5 = EditorView12.baseTheme({
3336
+ import { EditorView as EditorView13, Decoration as Decoration10, WidgetType as WidgetType7, ViewPlugin as ViewPlugin8 } from "@codemirror/view";
3337
+ var styles5 = EditorView13.baseTheme({
2293
3338
  "& .cm-task": {
2294
3339
  color: getToken("extend.colors.blue.500")
2295
3340
  },
@@ -2504,8 +3549,9 @@ var defaultTheme = {
2504
3549
  outline: "none"
2505
3550
  },
2506
3551
  ".cm-scroller": {
2507
- overflow: "visible",
2508
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
3552
+ // overflow: 'visible',
3553
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(","),
3554
+ lineHeight: 1.4
2509
3555
  },
2510
3556
  ".cm-content": {
2511
3557
  padding: 0,
@@ -2513,11 +3559,11 @@ var defaultTheme = {
2513
3559
  fontSize: "16px"
2514
3560
  },
2515
3561
  "&light .cm-content": {
2516
- color: get3(tokens, "extend.colors.neutral.900", "black"),
3562
+ color: get3(tokens, "extend.semanticColors.base.fg.light", "black"),
2517
3563
  caretColor: "black"
2518
3564
  },
2519
3565
  "&dark .cm-content": {
2520
- color: get3(tokens, "extend.colors.neutral.100", "white"),
3566
+ color: get3(tokens, "extend.semanticColors.base.fg.dark", "white"),
2521
3567
  caretColor: "white"
2522
3568
  },
2523
3569
  //
@@ -2529,8 +3575,11 @@ var defaultTheme = {
2529
3575
  "&dark .cm-cursor, &dark .cm-dropCursor": {
2530
3576
  borderLeft: "2px solid white"
2531
3577
  },
2532
- ".cm-placeholder": {
2533
- fontWeight: 100
3578
+ "&light .cm-placeholder": {
3579
+ color: get3(tokens, "extend.semanticColors.description.light", "rgba(0,0,0,.2)")
3580
+ },
3581
+ "&dark .cm-placeholder": {
3582
+ color: get3(tokens, "extend.semanticColors.description.dark", "rgba(255,255,255,.2)")
2534
3583
  },
2535
3584
  //
2536
3585
  // line
@@ -2710,23 +3759,28 @@ var codeTheme = {
2710
3759
  };
2711
3760
 
2712
3761
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2713
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
3762
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
2714
3763
  var EditorModes = [
2715
3764
  "default",
2716
3765
  "vim"
2717
3766
  ];
2718
3767
  var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2719
- const tabsterDOMAttribute = useFocusableGroup({
2720
- tabBehavior: "limited"
2721
- });
2722
3768
  const { themeMode } = useThemeContext();
2723
3769
  const rootRef = useRef(null);
2724
- const [view, setView] = useState(null);
3770
+ const [view, setView] = useState2(null);
2725
3771
  useImperativeHandle(forwardedRef, () => view, [
2726
3772
  view
2727
3773
  ]);
2728
3774
  useEffect2(() => {
2729
- if (autoFocus) {
3775
+ if (autoFocus && !view?.hasFocus) {
3776
+ if (view?.state.selection.main?.from === 0) {
3777
+ const { to } = view.state.doc.lineAt(0);
3778
+ view?.dispatch({
3779
+ selection: {
3780
+ anchor: to
3781
+ }
3782
+ });
3783
+ }
2730
3784
  view?.focus();
2731
3785
  }
2732
3786
  }, [
@@ -2742,38 +3796,38 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, s
2742
3796
  selection,
2743
3797
  extensions: [
2744
3798
  // TODO(burdon): Doesn't catch errors in keymap functions.
2745
- EditorView13.exceptionSink.of((err) => {
3799
+ EditorView14.exceptionSink.of((err) => {
2746
3800
  log2.catch(err, void 0, {
2747
- F: __dxlog_file4,
2748
- L: 123,
3801
+ F: __dxlog_file3,
3802
+ L: 134,
2749
3803
  S: void 0,
2750
3804
  C: (f, a) => f(...a)
2751
3805
  });
2752
3806
  }),
2753
3807
  // Theme.
2754
3808
  // TODO(burdon): Make configurable.
2755
- EditorView13.baseTheme(defaultTheme),
2756
- EditorView13.theme(theme ?? {}),
2757
- EditorView13.darkTheme.of(themeMode === "dark"),
2758
- EditorView13.editorAttributes.of({
3809
+ EditorView14.baseTheme(defaultTheme),
3810
+ EditorView14.theme(theme ?? {}),
3811
+ EditorView14.darkTheme.of(themeMode === "dark"),
3812
+ EditorView14.editorAttributes.of({
2759
3813
  class: slots.editor?.className ?? ""
2760
3814
  }),
2761
- EditorView13.contentAttributes.of({
3815
+ EditorView14.contentAttributes.of({
2762
3816
  class: slots.content?.className ?? ""
2763
3817
  }),
2764
3818
  // State.
2765
- EditorView13.editable.of(!readonly),
3819
+ EditorView14.editable.of(!readonly),
2766
3820
  EditorState2.readOnly.of(!!readonly),
2767
3821
  // Storage and replication.
2768
3822
  // NOTE: This must come before user extensions.
2769
3823
  model.extension,
2770
- // TODO(burdon): Factor out (requires special handling for Escape/focus).
3824
+ // TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
2771
3825
  editorMode === "vim" && vim(),
2772
3826
  // Custom.
2773
3827
  ...extensions
2774
- ].filter(Boolean)
3828
+ ].filter(isNotFalsy3)
2775
3829
  });
2776
- const newView = new EditorView13({
3830
+ const newView = new EditorView14({
2777
3831
  state,
2778
3832
  parent: rootRef.current,
2779
3833
  scrollTo,
@@ -2808,7 +3862,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, s
2808
3862
  }
2809
3863
  case "Escape": {
2810
3864
  if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
2811
- rootRef.current?.focus();
3865
+ view?.focus();
2812
3866
  }
2813
3867
  break;
2814
3868
  }
@@ -2821,9 +3875,9 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, s
2821
3875
  key: model.id,
2822
3876
  role: "none",
2823
3877
  tabIndex: 0,
2824
- onKeyUp: handleKeyUp,
2825
3878
  ...slots.root,
2826
- ...editorMode !== "vim" && tabsterDOMAttribute,
3879
+ // {...(editorMode !== 'vim' && tabsterDOMAttribute)}
3880
+ onKeyUp: handleKeyUp,
2827
3881
  ref: rootRef
2828
3882
  });
2829
3883
  });
@@ -2834,7 +3888,7 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholde
2834
3888
  ref: forwardedRef,
2835
3889
  readonly,
2836
3890
  extensions: [
2837
- basicBundle({
3891
+ createBasicBundle({
2838
3892
  themeMode,
2839
3893
  placeholder: placeholder3,
2840
3894
  lineWrapping
@@ -2853,7 +3907,7 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeh
2853
3907
  ref: forwardedRef,
2854
3908
  readonly,
2855
3909
  extensions: [
2856
- markdownBundle({
3910
+ createMarkdownExtensions({
2857
3911
  themeMode,
2858
3912
  placeholder: placeholder3
2859
3913
  }),
@@ -2866,11 +3920,10 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeh
2866
3920
  });
2867
3921
  var defaultSlots = {
2868
3922
  root: {
2869
- // TODO(burdon): Add focusRing by default/as property?
2870
- className: mx3("flex flex-col grow overflow-y-auto", attentionSurface)
3923
+ className: mx3("grow", focusRing)
2871
3924
  },
2872
3925
  editor: {
2873
- className: "h-full p-2"
3926
+ className: "bs-full"
2874
3927
  }
2875
3928
  };
2876
3929
  var defaultTextSlots = {
@@ -2880,12 +3933,212 @@ var defaultMarkdownSlots = {
2880
3933
  ...defaultSlots
2881
3934
  };
2882
3935
 
3936
+ // packages/ui/react-ui-editor/src/components/Toolbar/Toolbar.tsx
3937
+ import { CaretRight, ChatText, Code, CodeBlock, Link, ListBullets, ListChecks, ListNumbers, Paragraph, TextStrikethrough, Table as Table2, TextB, TextHOne, TextHTwo, TextHThree, TextHFour, TextHFive, TextHSix, TextItalic } from "@phosphor-icons/react";
3938
+ import { createContext } from "@radix-ui/react-context";
3939
+ import React2 from "react";
3940
+ import { Button, DensityProvider, Select } from "@dxos/react-ui";
3941
+ import { getSize, mx as mx4 } from "@dxos/react-ui-theme";
3942
+ var HeadingIcons = {
3943
+ "0": Paragraph,
3944
+ "1": TextHOne,
3945
+ "2": TextHTwo,
3946
+ "3": TextHThree,
3947
+ "4": TextHFour,
3948
+ "5": TextHFive,
3949
+ "6": TextHSix
3950
+ };
3951
+ var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
3952
+ var ToolbarRoot = ({ children, onAction, state }) => {
3953
+ return /* @__PURE__ */ React2.createElement(ToolbarContextProvider, {
3954
+ onAction,
3955
+ state
3956
+ }, /* @__PURE__ */ React2.createElement(DensityProvider, {
3957
+ density: "fine"
3958
+ }, /* @__PURE__ */ React2.createElement("div", {
3959
+ role: "toolbar",
3960
+ className: "flex w-full shrink-0 p-1 gap-4 items-center whitespace-nowrap overflow-hidden"
3961
+ }, children)));
3962
+ };
3963
+ var ToolbarButton = ({ Icon, onClick, title, getState, disable }) => {
3964
+ const { onAction, state } = useToolbarContext("ToolbarButton");
3965
+ const active = getState && state ? getState(state) : false;
3966
+ const disabled = disable && state ? disable(state) : false;
3967
+ const handleClick = (event) => {
3968
+ const action = onClick(state);
3969
+ if (action) {
3970
+ onAction?.(action);
3971
+ event.preventDefault();
3972
+ }
3973
+ };
3974
+ return /* @__PURE__ */ React2.createElement(Button, {
3975
+ variant: "ghost",
3976
+ classNames: mx4("p-2", active && "ring-[1px]"),
3977
+ onMouseDown: handleClick,
3978
+ title,
3979
+ disabled
3980
+ }, /* @__PURE__ */ React2.createElement(Icon, {
3981
+ className: getSize(5)
3982
+ }));
3983
+ };
3984
+ var ToolbarSeparator = () => /* @__PURE__ */ React2.createElement("div", {
3985
+ className: "grow"
3986
+ });
3987
+ var MarkdownHeading = () => {
3988
+ const { onAction, state } = useToolbarContext("MarkdownFormatting");
3989
+ const blockType = state ? state.blockType : "paragraph";
3990
+ const header = blockType && /heading(\d)/.exec(blockType);
3991
+ const value = header ? header[1] : blockType === "paragraph" || !blockType ? "0" : void 0;
3992
+ const HeadingIcon = HeadingIcons[value ?? "0"];
3993
+ return /* @__PURE__ */ React2.createElement(Select.Root, {
3994
+ disabled: value === null,
3995
+ value: value ?? "0",
3996
+ onValueChange: (value2) => onAction?.({
3997
+ type: "heading",
3998
+ data: parseInt(value2)
3999
+ })
4000
+ }, /* @__PURE__ */ React2.createElement(Select.TriggerButton, null, /* @__PURE__ */ React2.createElement(HeadingIcon, {
4001
+ className: getSize(5)
4002
+ })), /* @__PURE__ */ React2.createElement(Select.Portal, null, /* @__PURE__ */ React2.createElement(Select.Content, null, /* @__PURE__ */ React2.createElement(Select.Viewport, null, /* @__PURE__ */ React2.createElement(Select.Option, {
4003
+ value: "0"
4004
+ }, "Paragraph"), [
4005
+ 1,
4006
+ 2,
4007
+ 3,
4008
+ 4,
4009
+ 5,
4010
+ 6
4011
+ ].map((level) => /* @__PURE__ */ React2.createElement(Select.Option, {
4012
+ key: level,
4013
+ value: String(level)
4014
+ }, "Heading ", level))))));
4015
+ };
4016
+ var MarkdownStyles = () => /* @__PURE__ */ React2.createElement("div", {
4017
+ role: "none"
4018
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
4019
+ Icon: TextB,
4020
+ title: "String",
4021
+ disable: (s) => s.blockType === "codeblock",
4022
+ getState: (s) => s.strong,
4023
+ onClick: (s) => ({
4024
+ type: "strong",
4025
+ data: s ? !s.strong : null
4026
+ })
4027
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4028
+ Icon: TextItalic,
4029
+ title: "Emphasis",
4030
+ disable: (s) => s.blockType === "codeblock",
4031
+ getState: (s) => s.emphasis,
4032
+ onClick: (s) => ({
4033
+ type: "emphasis",
4034
+ data: s ? !s.emphasis : null
4035
+ })
4036
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4037
+ Icon: TextStrikethrough,
4038
+ title: "Strike-through",
4039
+ disable: (s) => s.blockType === "codeblock",
4040
+ getState: (s) => s.strikethrough,
4041
+ onClick: (s) => ({
4042
+ type: "strikethrough",
4043
+ data: s ? !s.strikethrough : null
4044
+ })
4045
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4046
+ Icon: Code,
4047
+ title: "Inline code",
4048
+ disable: (s) => s.blockType === "codeblock",
4049
+ getState: (s) => s.code,
4050
+ onClick: (s) => ({
4051
+ type: "code",
4052
+ data: s ? !s.code : null
4053
+ })
4054
+ }));
4055
+ var MarkdownLists = () => /* @__PURE__ */ React2.createElement("div", {
4056
+ role: "none"
4057
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
4058
+ Icon: ListBullets,
4059
+ title: "Bullet list",
4060
+ getState: (s) => s.listStyle === "bullet",
4061
+ onClick: (s) => ({
4062
+ type: "list-bullet",
4063
+ data: s ? s.listStyle !== "bullet" : null
4064
+ })
4065
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4066
+ Icon: ListNumbers,
4067
+ title: "Numbered list",
4068
+ getState: (s) => s.listStyle === "ordered",
4069
+ onClick: (s) => ({
4070
+ type: "list-ordered",
4071
+ data: s ? s.listStyle !== "ordered" : null
4072
+ })
4073
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4074
+ Icon: ListChecks,
4075
+ title: "Task list",
4076
+ getState: (s) => s.listStyle === "task",
4077
+ onClick: (s) => ({
4078
+ type: "list-tasks",
4079
+ data: s ? s.listStyle !== "task" : null
4080
+ })
4081
+ }));
4082
+ var MarkdownBlocks = () => /* @__PURE__ */ React2.createElement("div", {
4083
+ role: "none"
4084
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
4085
+ Icon: CaretRight,
4086
+ title: "Block quote",
4087
+ getState: (s) => s.blockQuote,
4088
+ onClick: (s) => ({
4089
+ type: "blockquote",
4090
+ data: s ? !s.blockQuote : null
4091
+ })
4092
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4093
+ Icon: CodeBlock,
4094
+ title: "Code block",
4095
+ disable: (s) => !s.blankLine,
4096
+ getState: (s) => s.blockType === "codeblock",
4097
+ onClick: (s) => ({
4098
+ type: "codeblock",
4099
+ data: s ? s.blockType !== "codeblock" : null
4100
+ })
4101
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4102
+ Icon: Table2,
4103
+ title: "Table",
4104
+ disable: (s) => !s.blankLine,
4105
+ onClick: () => ({
4106
+ type: "table"
4107
+ })
4108
+ }));
4109
+ var MarkdownLinks = () => /* @__PURE__ */ React2.createElement("div", {
4110
+ role: "none"
4111
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
4112
+ Icon: Link,
4113
+ title: "Link",
4114
+ getState: (s) => s.link,
4115
+ onClick: (s) => ({
4116
+ type: "link",
4117
+ data: s ? !s.link : null
4118
+ })
4119
+ }));
4120
+ 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), /* @__PURE__ */ React2.createElement(MarkdownLinks, null));
4121
+ var MarkdownExtended = () => /* @__PURE__ */ React2.createElement(Toolbar.Button, {
4122
+ Icon: ChatText,
4123
+ title: "Create comment",
4124
+ onClick: () => ({
4125
+ type: "comment"
4126
+ })
4127
+ });
4128
+ var Toolbar = {
4129
+ Root: ToolbarRoot,
4130
+ Button: ToolbarButton,
4131
+ Separator: ToolbarSeparator,
4132
+ Markdown: MarkdownStandard,
4133
+ Extended: MarkdownExtended
4134
+ };
4135
+
2883
4136
  // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2884
4137
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2885
4138
  import { Context as Context2 } from "@dxos/context";
2886
- import { invariant as invariant3 } from "@dxos/invariant";
4139
+ import { invariant as invariant2 } from "@dxos/invariant";
2887
4140
  import { log as log3 } from "@dxos/log";
2888
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
4141
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2889
4142
  var DEBOUNCE_INTERVAL = 100;
2890
4143
  var SpaceAwarenessProvider = class {
2891
4144
  constructor(params) {
@@ -2925,7 +4178,7 @@ var SpaceAwarenessProvider = class {
2925
4178
  log3.debug("failed to query awareness", {
2926
4179
  err
2927
4180
  }, {
2928
- F: __dxlog_file5,
4181
+ F: __dxlog_file4,
2929
4182
  L: 89,
2930
4183
  S: this,
2931
4184
  C: (f, a) => f(...a)
@@ -2941,8 +4194,8 @@ var SpaceAwarenessProvider = class {
2941
4194
  return Array.from(this._remoteStates.values());
2942
4195
  }
2943
4196
  update(position) {
2944
- invariant3(this._postTask, void 0, {
2945
- F: __dxlog_file5,
4197
+ invariant2(this._postTask, void 0, {
4198
+ F: __dxlog_file4,
2946
4199
  L: 104,
2947
4200
  S: this,
2948
4201
  A: [
@@ -2958,8 +4211,8 @@ var SpaceAwarenessProvider = class {
2958
4211
  this._postTask.schedule();
2959
4212
  }
2960
4213
  _handleQueryMessage() {
2961
- invariant3(this._postTask, void 0, {
2962
- F: __dxlog_file5,
4214
+ invariant2(this._postTask, void 0, {
4215
+ F: __dxlog_file4,
2963
4216
  L: 115,
2964
4217
  S: this,
2965
4218
  A: [
@@ -2970,8 +4223,8 @@ var SpaceAwarenessProvider = class {
2970
4223
  this._postTask.schedule();
2971
4224
  }
2972
4225
  _handlePostMessage(message) {
2973
- invariant3(message.kind === "post", void 0, {
2974
- F: __dxlog_file5,
4226
+ invariant2(message.kind === "post", void 0, {
4227
+ F: __dxlog_file4,
2975
4228
  L: 120,
2976
4229
  S: this,
2977
4230
  A: [
@@ -2997,36 +4250,52 @@ var useActionHandler = (view) => {
2997
4250
  if (!view) {
2998
4251
  return;
2999
4252
  }
4253
+ let inlineType, listType;
3000
4254
  switch (action.type) {
3001
4255
  case "heading":
3002
4256
  setHeading(parseInt(action.data))(view);
3003
4257
  break;
3004
- case "bold":
3005
- toggleBold(view);
4258
+ case "strong":
4259
+ case "emphasis":
4260
+ case "strikethrough":
4261
+ case "code":
4262
+ inlineType = action.type === "strong" ? Inline.Strong : action.type === "emphasis" ? Inline.Emphasis : action.type === "strikethrough" ? Inline.Strikethrough : Inline.Code;
4263
+ (typeof action.data === "boolean" ? setStyle(inlineType, action.data) : toggleStyle(inlineType))(view);
3006
4264
  break;
3007
- case "italic":
3008
- toggleItalic(view);
4265
+ case "list-ordered":
4266
+ case "list-bullet":
4267
+ case "list-tasks":
4268
+ listType = action.type === "list-ordered" ? List.Ordered : action.type === "list-bullet" ? List.Bullet : List.Task;
4269
+ (action.data === false ? removeList(listType) : action.data === true ? addList(listType) : toggleList(listType))(view);
3009
4270
  break;
3010
- case "strikethrough":
3011
- toggleStrikethrough(view);
4271
+ case "blockquote":
4272
+ (action.data === false ? removeBlockquote : action.data === true ? addBlockquote : toggleBlockquote)(view);
4273
+ break;
4274
+ case "codeblock":
4275
+ (action.data === false ? removeCodeblock : addCodeblock)(view);
3012
4276
  break;
3013
- case "list":
3014
- toggleList(view);
4277
+ case "table":
4278
+ insertTable(view);
4279
+ break;
4280
+ case "link":
4281
+ (action.data === false ? removeLink : addLink)(view);
3015
4282
  break;
3016
4283
  case "comment":
3017
4284
  createComment(view);
3018
4285
  break;
3019
4286
  }
3020
4287
  setTimeout(() => {
3021
- view.focus();
4288
+ if (!view.hasFocus) {
4289
+ view.focus();
4290
+ }
3022
4291
  });
3023
4292
  };
3024
4293
  };
3025
4294
 
3026
- // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
3027
- import { useState as useState2 } from "react";
3028
- var useTextEditor = () => {
3029
- const [view, setView] = useState2(null);
4295
+ // packages/ui/react-ui-editor/src/hooks/useEditorView.ts
4296
+ import { useState as useState3 } from "react";
4297
+ var useEditorView = () => {
4298
+ const [view, setView] = useState3(null);
3030
4299
  return [
3031
4300
  (ref) => {
3032
4301
  setView(ref);
@@ -3035,29 +4304,144 @@ var useTextEditor = () => {
3035
4304
  ];
3036
4305
  };
3037
4306
 
4307
+ // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
4308
+ import { EditorSelection as EditorSelection2, EditorState as EditorState3 } from "@codemirror/state";
4309
+ import { EditorView as EditorView15 } from "@codemirror/view";
4310
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState4 } from "react";
4311
+ import { log as log4 } from "@dxos/log";
4312
+ import { isNotFalsy as isNotFalsy4 } from "@dxos/util";
4313
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
4314
+ var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions } = {}) => {
4315
+ const onUpdate = useRef2();
4316
+ const [view, setView] = useState4();
4317
+ const parentRef = useRef2(null);
4318
+ useEffect3(() => {
4319
+ if (parentRef.current) {
4320
+ const state = EditorState3.create({
4321
+ doc,
4322
+ selection,
4323
+ extensions: [
4324
+ // TODO(burdon): Doesn't catch errors in keymap functions.
4325
+ EditorView15.exceptionSink.of((err) => {
4326
+ log4.catch(err, void 0, {
4327
+ F: __dxlog_file5,
4328
+ L: 57,
4329
+ S: void 0,
4330
+ C: (f, a) => f(...a)
4331
+ });
4332
+ }),
4333
+ extensions,
4334
+ EditorView15.updateListener.of(() => {
4335
+ onUpdate.current?.();
4336
+ })
4337
+ ].filter(isNotFalsy4)
4338
+ });
4339
+ const view2 = new EditorView15({
4340
+ parent: parentRef.current,
4341
+ scrollTo,
4342
+ state,
4343
+ // NOTE: Uncomment to debug/monitor all transactions.
4344
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
4345
+ dispatchTransactions: (trs, view3) => {
4346
+ if (debug) {
4347
+ logChanges(trs);
4348
+ }
4349
+ view3.update(trs);
4350
+ }
4351
+ });
4352
+ setView(view2);
4353
+ }
4354
+ return () => {
4355
+ view?.destroy();
4356
+ };
4357
+ }, [
4358
+ parentRef
4359
+ ]);
4360
+ useEffect3(() => {
4361
+ if (view) {
4362
+ if (!selection && !view.state.selection.main.anchor) {
4363
+ selection = EditorSelection2.single(view.state.doc.line(1).to);
4364
+ }
4365
+ if (selection || scrollTo) {
4366
+ onUpdate.current = () => {
4367
+ onUpdate.current = void 0;
4368
+ view.dispatch({
4369
+ selection,
4370
+ effects: scrollTo && [
4371
+ scrollTo
4372
+ ],
4373
+ scrollIntoView: !scrollTo
4374
+ });
4375
+ };
4376
+ }
4377
+ if (autoFocus) {
4378
+ view.focus();
4379
+ }
4380
+ }
4381
+ }, [
4382
+ view,
4383
+ autoFocus,
4384
+ selection,
4385
+ scrollTo
4386
+ ]);
4387
+ return {
4388
+ parentRef,
4389
+ view
4390
+ };
4391
+ };
4392
+ var createDataExtensions = ({ readonly = false } = {}) => {
4393
+ return [
4394
+ //
4395
+ EditorState3.readOnly.of(readonly),
4396
+ EditorView15.editable.of(!readonly)
4397
+ ];
4398
+ };
4399
+ var createThemeExtensions = ({ theme, themeMode, slots } = {}) => {
4400
+ return [
4401
+ //
4402
+ EditorView15.baseTheme(defaultTheme),
4403
+ theme && EditorView15.theme(theme),
4404
+ EditorView15.darkTheme.of(themeMode === "dark"),
4405
+ EditorView15.editorAttributes.of({
4406
+ class: slots?.editor?.className ?? ""
4407
+ }),
4408
+ EditorView15.contentAttributes.of({
4409
+ class: slots?.content?.className ?? ""
4410
+ })
4411
+ ].filter(isNotFalsy4);
4412
+ };
4413
+
3038
4414
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
3039
4415
  import get4 from "lodash.get";
3040
- import { useEffect as useEffect3, useState as useState3 } from "react";
4416
+ import { useEffect as useEffect4, useState as useState5 } from "react";
3041
4417
  import { generateName } from "@dxos/display-name";
3042
4418
  import { getRawDoc } from "@dxos/echo-schema";
3043
- import { invariant as invariant4 } from "@dxos/invariant";
4419
+ import { invariant as invariant3 } from "@dxos/invariant";
3044
4420
  import { isAutomergeObject } from "@dxos/react-client/echo";
3045
- import { isNotNullOrUndefined } from "@dxos/util";
3046
4421
  var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
3047
4422
  var useTextModel = (props) => {
3048
4423
  const { identity, space, text } = props;
3049
- const [model, setModel] = useState3();
3050
- useEffect3(() => setModel(createModel(props)), [
4424
+ const [model, setModel] = useState5();
4425
+ useEffect4(() => setModel(createModel(props)), [
3051
4426
  identity,
3052
4427
  space,
3053
4428
  text
3054
4429
  ]);
3055
4430
  return model;
3056
4431
  };
4432
+ var useInMemoryTextModel = ({ id, defaultContent }) => {
4433
+ const [content, setContent] = useState5(defaultContent ?? "");
4434
+ return {
4435
+ id,
4436
+ content,
4437
+ setContent,
4438
+ text: () => content
4439
+ };
4440
+ };
3057
4441
  var createModel = ({ space, identity, text }) => {
3058
- invariant4(isAutomergeObject(text), void 0, {
4442
+ invariant3(isAutomergeObject(text), void 0, {
3059
4443
  F: __dxlog_file6,
3060
- L: 35,
4444
+ L: 50,
3061
4445
  S: void 0,
3062
4446
  A: [
3063
4447
  "isAutomergeObject(text)",
@@ -3068,7 +4452,7 @@ var createModel = ({ space, identity, text }) => {
3068
4452
  const doc = getRawDoc(obj, [
3069
4453
  obj.field
3070
4454
  ]);
3071
- const awarenessProvider = space && new SpaceAwarenessProvider({
4455
+ const awarenessProvider2 = space && new SpaceAwarenessProvider({
3072
4456
  space,
3073
4457
  channel: `automerge.awareness.${obj.id}`,
3074
4458
  info: {
@@ -3078,19 +4462,22 @@ var createModel = ({ space, identity, text }) => {
3078
4462
  },
3079
4463
  peerId: identity?.identityKey.toHex() ?? "Anonymous"
3080
4464
  });
4465
+ const extensions = [
4466
+ //
4467
+ modelState.init(() => model),
4468
+ automerge({
4469
+ handle: doc.handle,
4470
+ path: doc.path
4471
+ })
4472
+ ];
4473
+ if (awarenessProvider2) {
4474
+ extensions.push(awareness(awarenessProvider2));
4475
+ }
3081
4476
  const model = {
3082
4477
  id: obj.id,
3083
4478
  content: doc,
3084
4479
  text: () => get4(doc.handle.docSync(), doc.path),
3085
- extension: [
3086
- modelState.init(() => model),
3087
- automerge4({
3088
- handle: doc.handle,
3089
- path: doc.path
3090
- }),
3091
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
3092
- awareness()
3093
- ].filter(isNotNullOrUndefined),
4480
+ extension: extensions,
3094
4481
  peer: identity ? {
3095
4482
  id: identity.identityKey.toHex(),
3096
4483
  name: identity.profile?.displayName
@@ -3099,42 +4486,57 @@ var createModel = ({ space, identity, text }) => {
3099
4486
  return model;
3100
4487
  };
3101
4488
  export {
3102
- AwarenessProvider,
3103
4489
  BaseTextEditor,
3104
4490
  Cursor,
3105
4491
  Doc,
3106
4492
  EditorModes,
4493
+ Inline,
4494
+ List,
3107
4495
  MarkdownEditor,
3108
4496
  RemoteSelectionsDecorator,
3109
4497
  SpaceAwarenessProvider,
3110
4498
  TextEditor,
3111
4499
  TextKind,
4500
+ Toolbar,
3112
4501
  YText,
3113
4502
  YXmlFragment,
4503
+ addBlockquote,
4504
+ addCodeblock,
4505
+ addLink,
4506
+ addList,
4507
+ addStyle,
3114
4508
  autocomplete,
3115
- automerge4 as automerge,
4509
+ automerge,
3116
4510
  awareness,
3117
- basicBundle,
4511
+ awarenessProvider,
3118
4512
  blast,
3119
4513
  callbackWrapper,
3120
4514
  code2 as code,
3121
4515
  codeTheme,
3122
4516
  comments,
4517
+ compareFormatting,
4518
+ createBasicBundle,
3123
4519
  createComment,
4520
+ createDataExtensions,
4521
+ createMarkdownExtensions,
4522
+ createThemeExtensions,
3124
4523
  defaultMarkdownSlots,
3125
4524
  defaultOptions,
3126
4525
  defaultSlots,
3127
4526
  defaultTextSlots,
3128
4527
  defaultTheme,
4528
+ focusComment,
3129
4529
  formatting,
4530
+ getFormatting,
3130
4531
  getToken,
3131
4532
  heading2 as heading,
3132
4533
  hr,
3133
4534
  image,
4535
+ insertTable,
4536
+ keymap7 as keymap,
3134
4537
  link,
3135
4538
  listener,
3136
4539
  logChanges,
3137
- markdownBundle,
3138
4540
  markdownHighlightStyle,
3139
4541
  markdownTags,
3140
4542
  markdownTagsExtensions,
@@ -3142,23 +4544,36 @@ export {
3142
4544
  mention,
3143
4545
  modelState,
3144
4546
  outliner,
4547
+ removeBlockquote,
4548
+ removeCodeblock,
4549
+ removeLink,
4550
+ removeList,
4551
+ removeStyle,
4552
+ setBlockquote,
3145
4553
  setComments,
3146
- setFocus,
3147
4554
  setHeading,
3148
4555
  setSelection,
4556
+ setStyle,
3149
4557
  table,
3150
4558
  tags2 as tags,
3151
4559
  tasklist,
3152
4560
  textTheme,
3153
- toggleBold,
3154
- toggleItalic,
4561
+ toggleBlockquote,
4562
+ toggleCodeblock,
4563
+ toggleEmphasis,
4564
+ toggleInlineCode,
3155
4565
  toggleList,
3156
4566
  toggleStrikethrough,
4567
+ toggleStrong,
3157
4568
  toggleStyle,
3158
4569
  typewriter,
3159
4570
  useActionHandler,
3160
4571
  useComments,
4572
+ useEditorView,
4573
+ useFormattingState,
4574
+ useInMemoryTextModel,
3161
4575
  useTextEditor,
3162
- useTextModel
4576
+ useTextModel,
4577
+ useToolbarContext
3163
4578
  };
3164
4579
  //# sourceMappingURL=index.mjs.map