@dxos/react-ui-editor 0.3.11-main.42059c4 → 0.3.11-main.474ad53

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 (185) hide show
  1. package/dist/lib/browser/index.mjs +2666 -1082
  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 +13 -15
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +13 -5
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +1 -1
  9. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/{yjs.stories.d.ts → hooks.stories.d.ts} +13 -2
  11. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/Toolbar/Toolbar.d.ts +29 -0
  13. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
  14. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +25 -0
  15. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
  16. package/dist/types/src/components/Toolbar/index.d.ts +2 -0
  17. package/dist/types/src/components/Toolbar/index.d.ts.map +1 -0
  18. package/dist/types/src/components/index.d.ts +1 -0
  19. package/dist/types/src/components/index.d.ts.map +1 -1
  20. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  21. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  22. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
  23. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
  24. package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
  25. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
  26. package/dist/types/src/extensions/automerge/defs.d.ts +8 -10
  27. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  28. package/dist/types/src/extensions/automerge/semaphore.d.ts +7 -6
  29. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  30. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
  31. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  32. package/dist/types/src/extensions/awareness.d.ts +2 -2
  33. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  34. package/dist/types/src/extensions/basic.d.ts +4 -5
  35. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  36. package/dist/types/src/extensions/comments.d.ts +28 -16
  37. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  38. package/dist/types/src/extensions/cursor.d.ts +3 -3
  39. package/dist/types/src/extensions/cursor.d.ts.map +1 -1
  40. package/dist/types/src/extensions/index.d.ts +1 -7
  41. package/dist/types/src/extensions/index.d.ts.map +1 -1
  42. package/dist/types/src/extensions/markdown/bundle.d.ts +3 -6
  43. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  44. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
  45. package/dist/types/src/extensions/markdown/formatting.d.ts +58 -0
  46. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  47. package/dist/types/src/extensions/markdown/formatting.test.d.ts +3 -0
  48. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -0
  49. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  50. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  51. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  52. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  53. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  54. package/dist/types/src/extensions/markdown/index.d.ts +8 -0
  55. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  56. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  57. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  58. package/dist/types/src/extensions/markdown/tasklist.d.ts +3 -0
  59. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  60. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  61. package/dist/types/src/extensions/outliner.d.ts +4 -0
  62. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  63. package/dist/types/src/hooks/awareness-provider.d.ts +7 -4
  64. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -1
  65. package/dist/types/src/hooks/defs.d.ts +25 -0
  66. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  67. package/dist/types/src/hooks/index.d.ts +4 -2
  68. package/dist/types/src/hooks/index.d.ts.map +1 -1
  69. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  70. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  71. package/dist/types/src/hooks/useEditorView.d.ts +17 -0
  72. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
  73. package/dist/types/src/hooks/useTextEditor.d.ts +39 -0
  74. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  75. package/dist/types/src/hooks/useTextModel.d.ts +14 -27
  76. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  77. package/dist/types/src/index.d.ts +1 -1
  78. package/dist/types/src/index.d.ts.map +1 -1
  79. package/dist/types/src/styles/markdown.d.ts +2 -2
  80. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  81. package/dist/types/src/themes/default.d.ts.map +1 -1
  82. package/dist/types/src/util.d.ts +2 -0
  83. package/dist/types/src/util.d.ts.map +1 -1
  84. package/package.json +47 -37
  85. package/src/components/TextEditor/MarkdownEditor.stories.tsx +75 -84
  86. package/src/components/TextEditor/TextEditor.tsx +107 -71
  87. package/src/components/TextEditor/automerge.stories.tsx +1 -2
  88. package/src/components/TextEditor/hooks.stories.tsx +111 -0
  89. package/src/components/Toolbar/Toolbar.stories.tsx +102 -0
  90. package/src/components/Toolbar/Toolbar.tsx +264 -0
  91. package/src/components/Toolbar/index.ts +5 -0
  92. package/src/components/index.ts +1 -0
  93. package/src/extensions/autocomplete.ts +6 -3
  94. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  95. package/src/extensions/automerge/automerge.test.ts +13 -0
  96. package/src/extensions/automerge/automerge.ts +50 -41
  97. package/src/extensions/automerge/cursor.ts +3 -3
  98. package/src/extensions/automerge/defs.ts +11 -36
  99. package/src/extensions/automerge/semaphore.ts +31 -28
  100. package/src/extensions/automerge/update-automerge.ts +14 -8
  101. package/src/extensions/automerge/update-codemirror.ts +5 -5
  102. package/src/extensions/awareness.ts +22 -18
  103. package/src/extensions/basic.ts +17 -10
  104. package/src/extensions/blast.ts +4 -1
  105. package/src/extensions/comments.ts +265 -181
  106. package/src/extensions/cursor.ts +7 -3
  107. package/src/extensions/index.ts +1 -7
  108. package/src/extensions/markdown/bundle.ts +23 -28
  109. package/src/extensions/markdown/code.ts +150 -0
  110. package/src/extensions/markdown/formatting.test.ts +482 -0
  111. package/src/extensions/markdown/formatting.ts +1237 -0
  112. package/src/extensions/markdown/heading.ts +59 -0
  113. package/src/extensions/markdown/highlight.ts +9 -15
  114. package/src/extensions/markdown/hr.ts +71 -0
  115. package/src/extensions/markdown/index.ts +8 -0
  116. package/src/extensions/{link.ts → markdown/link.ts} +16 -7
  117. package/src/extensions/{tasklist.ts → markdown/tasklist.ts} +48 -33
  118. package/src/extensions/mention.ts +9 -2
  119. package/src/extensions/outliner.ts +12 -0
  120. package/src/hooks/awareness-provider.ts +24 -20
  121. package/src/hooks/defs.ts +42 -0
  122. package/src/hooks/index.ts +4 -2
  123. package/src/hooks/useActionHandler.ts +93 -0
  124. package/src/hooks/useEditorView.ts +29 -0
  125. package/src/hooks/useTextEditor.ts +153 -0
  126. package/src/hooks/useTextModel.ts +66 -56
  127. package/src/index.ts +1 -1
  128. package/src/styles/markdown.ts +9 -10
  129. package/src/themes/default.ts +11 -8
  130. package/src/util.ts +26 -3
  131. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  132. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  133. package/dist/types/src/components/TextEditor/comments.stories.d.ts +0 -26
  134. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +0 -1
  135. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  136. package/dist/types/src/extensions/code.d.ts.map +0 -1
  137. package/dist/types/src/extensions/hr.d.ts.map +0 -1
  138. package/dist/types/src/extensions/image.d.ts.map +0 -1
  139. package/dist/types/src/extensions/link.d.ts.map +0 -1
  140. package/dist/types/src/extensions/table.d.ts.map +0 -1
  141. package/dist/types/src/extensions/tasklist.d.ts +0 -7
  142. package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
  143. package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
  144. package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
  145. package/dist/types/src/extensions/yjs/index.d.ts +0 -2
  146. package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
  147. package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
  148. package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
  149. package/dist/types/src/extensions/yjs/yjs.test.d.ts +0 -2
  150. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
  151. package/dist/types/src/hooks/automerge.d.ts +0 -3
  152. package/dist/types/src/hooks/automerge.d.ts.map +0 -1
  153. package/dist/types/src/hooks/yjs.d.ts +0 -27
  154. package/dist/types/src/hooks/yjs.d.ts.map +0 -1
  155. package/dist/types/src/testing/index.d.ts +0 -3
  156. package/dist/types/src/testing/index.d.ts.map +0 -1
  157. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  158. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  159. package/dist/types/src/testing/proto/index.d.ts +0 -2
  160. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  161. package/dist/types/src/testing/replicator.d.ts +0 -45
  162. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  163. package/src/components/TextEditor/codemirror.stories.ts +0 -115
  164. package/src/components/TextEditor/comments.stories.tsx +0 -355
  165. package/src/components/TextEditor/yjs.stories.tsx +0 -56
  166. package/src/extensions/code.ts +0 -106
  167. package/src/extensions/hr.ts +0 -73
  168. package/src/extensions/yjs/cursor.ts +0 -22
  169. package/src/extensions/yjs/index.ts +0 -5
  170. package/src/extensions/yjs/yjs.test.ts +0 -35
  171. package/src/extensions/yjs/yjs.ts +0 -16
  172. package/src/hooks/automerge.ts +0 -52
  173. package/src/hooks/yjs.ts +0 -156
  174. package/src/testing/index.ts +0 -6
  175. package/src/testing/proto/gen/schema.ts +0 -49
  176. package/src/testing/proto/index.ts +0 -5
  177. package/src/testing/proto/schema.proto +0 -15
  178. package/src/testing/replicator.ts +0 -184
  179. /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
  180. /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
  181. /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
  182. /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
  183. /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
  184. /package/src/extensions/{image.ts → markdown/image.ts} +0 -0
  185. /package/src/extensions/{table.ts → markdown/table.ts} +0 -0
@@ -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 EditorView12 } 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, useImperativeHandle, useState, useRef } from "react";
15
- import { generateName } from "@dxos/display-name";
14
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState as useState2 } from "react";
15
+ import { log as log2 } from "@dxos/log";
16
16
  import { useThemeContext } from "@dxos/react-ui";
17
- import { getColorForValue, inputSurface, 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";
@@ -29,9 +30,11 @@ var autocomplete = ({ onSearch }) => {
29
30
  // https://codemirror.net/examples/autocompletion
30
31
  // https://codemirror.net/docs/ref/#autocomplete.autocompletion
31
32
  autocompletion({
32
- defaultKeymap: false,
33
- // Don't start unless key press.
34
- activateOnTyping: false
33
+ activateOnTyping: true,
34
+ // closeOnBlur: false,
35
+ // defaultKeymap: false,
36
+ // TODO(burdon): Styles/fragments.
37
+ tooltipClass: () => "shadow rounded"
35
38
  }),
36
39
  // TODO(burdon): Option to create new page?
37
40
  // TODO(burdon): Optional decoration via addToOptions
@@ -51,13 +54,14 @@ var autocomplete = ({ onSearch }) => {
51
54
  };
52
55
 
53
56
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
57
+ import { invertedEffects } from "@codemirror/commands";
54
58
  import { StateField } from "@codemirror/state";
55
- import { ViewPlugin } from "@codemirror/view";
56
- import { next as A } from "@dxos/automerge/automerge";
59
+ import { EditorView, ViewPlugin } from "@codemirror/view";
60
+ import { next as A4 } from "@dxos/automerge/automerge";
57
61
 
58
62
  // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
59
63
  import get from "lodash.get";
60
- import { next as automerge } from "@dxos/automerge/automerge";
64
+ import { next as A } from "@dxos/automerge/automerge";
61
65
  var cursorConverter = (handle, path) => ({
62
66
  // TODO(burdon): Handle assoc to associate with a previous character.
63
67
  toCursor: (pos) => {
@@ -69,7 +73,7 @@ var cursorConverter = (handle, path) => ({
69
73
  if (typeof value === "string" && value.length <= pos) {
70
74
  return "end";
71
75
  }
72
- return automerge.getCursor(doc, path.slice(), pos);
76
+ return A.getCursor(doc, path.slice(), pos);
73
77
  },
74
78
  fromCursor: (cursor) => {
75
79
  if (cursor === "") {
@@ -87,29 +91,28 @@ var cursorConverter = (handle, path) => ({
87
91
  return 0;
88
92
  }
89
93
  }
90
- return automerge.getCursorPosition(doc, path.slice(), cursor);
94
+ return A.getCursorPosition(doc, path.slice(), cursor);
91
95
  }
92
96
  });
93
97
 
94
98
  // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
95
- import { Annotation, StateEffect, Facet } from "@codemirror/state";
96
- var effectType = StateEffect.define({});
97
- var updateHeads = (newHeads) => effectType.of({
98
- newHeads
99
- });
100
- var getLastHeads = (state, field) => state.field(field).lastHeads;
99
+ import { Annotation, StateEffect } from "@codemirror/state";
101
100
  var getPath = (state, field) => state.field(field).path;
102
- var reconcileAnnotationType = Annotation.define();
103
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
104
- var semaphoreFacet = Facet.define({
105
- combine: (values) => values.at(-1)
101
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
102
+ var updateHeadsEffect = StateEffect.define({});
103
+ var updateHeads = (newHeads) => updateHeadsEffect.of({
104
+ newHeads
106
105
  });
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 automerge2 } 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 am } 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,11 +125,19 @@ var updateAutomerge = (field, handle, transactions, state) => {
122
125
  return void 0;
123
126
  }
124
127
  const newHeads = handle.changeAt(lastHeads, (doc) => {
128
+ const invertedTransactions = [];
125
129
  for (const tr of transactions) {
126
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
127
- am.splice(doc, path, fromA, toA - fromA, inserted.toString());
130
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
131
+ invertedTransactions.push({
132
+ from: fromA,
133
+ del: toA - fromA,
134
+ insert
135
+ });
128
136
  });
129
137
  }
138
+ invertedTransactions.reverse().forEach(({ from, del, insert }) => {
139
+ A2.splice(doc, path.slice(), from, del, insert.toString());
140
+ });
130
141
  });
131
142
  return newHeads ?? void 0;
132
143
  };
@@ -141,13 +152,13 @@ var updateCodeMirror = (view, selection, target, patches) => {
141
152
  selection = selection.map(changeSet, 1);
142
153
  view.dispatch({
143
154
  changes: changeSet,
144
- annotations: reconcileAnnotationType.of({})
155
+ annotations: reconcileAnnotation.of(false)
145
156
  });
146
157
  }
147
158
  }
148
159
  view.dispatch({
149
160
  selection,
150
- annotations: reconcileAnnotationType.of({})
161
+ annotations: reconcileAnnotation.of(false)
151
162
  });
152
163
  };
153
164
  var handlePatch = (patch, target, state) => {
@@ -168,7 +179,7 @@ var handleInsert = (target, patch) => {
168
179
  if (index == null) {
169
180
  return [];
170
181
  }
171
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
182
+ const text = patch.values.map((value) => value ? value.toString() : "").join("");
172
183
  return [
173
184
  {
174
185
  from: index,
@@ -240,59 +251,63 @@ var charPath = (textPath, candidatePath) => {
240
251
 
241
252
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
242
253
  var PatchSemaphore = class {
243
- constructor(_handle, _field) {
254
+ // prettier-ignore
255
+ constructor(_handle, _state) {
244
256
  this._handle = _handle;
245
- this._field = _field;
257
+ this._state = _state;
246
258
  this._inReconcile = false;
247
- this.reconcile = (view) => {
248
- if (this._inReconcile) {
249
- return;
250
- }
259
+ }
260
+ reconcile(view) {
261
+ if (!this._inReconcile) {
251
262
  this._inReconcile = true;
252
- const path = getPath(view.state, this._field);
253
- const oldHeads = getLastHeads(view.state, this._field);
254
- let selection = view.state.selection;
255
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
256
- const toInvert = transactions.slice().reverse();
257
- for (const tx of toInvert) {
258
- const inverted = tx.changes.invert(tx.startState.doc);
259
- selection = selection.map(inverted);
260
- view.dispatch({
261
- changes: inverted,
262
- annotations: reconcileAnnotationType.of(true)
263
- });
264
- }
265
- let newHeads = updateAutomerge(this._field, this._handle, transactions, view.state);
266
- if (newHeads === null || newHeads === void 0) {
267
- newHeads = automerge2.getHeads(this._handle.docSync());
268
- }
269
- const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(this._handle.docSync(), oldHeads, newHeads);
270
- updateCodeMirror(view, selection, path, diff);
263
+ this.doReconcile(view);
264
+ this._inReconcile = false;
265
+ }
266
+ }
267
+ doReconcile(view) {
268
+ const path = getPath(view.state, this._state);
269
+ const oldHeads = getLastHeads(view.state, this._state);
270
+ let selection = view.state.selection;
271
+ const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
272
+ const toInvert = transactions.slice().reverse();
273
+ for (const tr of toInvert) {
274
+ const inverted = tr.changes.invert(tr.startState.doc);
275
+ selection = selection.map(inverted);
271
276
  view.dispatch({
272
- effects: updateHeads(newHeads),
273
- annotations: reconcileAnnotationType.of({})
277
+ changes: inverted,
278
+ annotations: reconcileAnnotation.of(true)
274
279
  });
275
- this._inReconcile = false;
276
- };
280
+ }
281
+ let newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
282
+ if (newHeads === null || newHeads === void 0) {
283
+ newHeads = A3.getHeads(this._handle.docSync());
284
+ }
285
+ const diff = A3.equals(oldHeads, newHeads) ? [] : A3.diff(this._handle.docSync(), oldHeads, newHeads);
286
+ updateCodeMirror(view, selection, path, diff);
287
+ view.dispatch({
288
+ effects: updateHeads(newHeads),
289
+ annotations: reconcileAnnotation.of(false)
290
+ });
277
291
  }
278
292
  };
279
293
 
280
294
  // packages/ui/react-ui-editor/src/extensions/cursor.ts
281
- import { Facet as Facet2 } from "@codemirror/state";
295
+ import { Facet } from "@codemirror/state";
282
296
  var defaultCursorConverter = {
283
297
  toCursor: (position) => position.toString(),
284
298
  fromCursor: (cursor) => parseInt(cursor)
285
299
  };
286
- var Cursor = class {
300
+ var Cursor = class _Cursor {
287
301
  static {
288
- this.converter = Facet2.define({
302
+ this.converter = Facet.define({
289
303
  combine: (providers) => providers[0] ?? defaultCursorConverter
290
304
  });
291
305
  }
292
306
  static {
293
- this.getCursorFromRange = (cursorConverter3, range) => {
294
- const from = cursorConverter3.toCursor(range.from);
295
- const to = cursorConverter3.toCursor(range.to, -1);
307
+ this.getCursorFromRange = (state, range) => {
308
+ const cursorConverter2 = state.facet(_Cursor.converter);
309
+ const from = cursorConverter2.toCursor(range.from);
310
+ const to = cursorConverter2.toCursor(range.to, -1);
296
311
  return [
297
312
  from,
298
313
  to
@@ -300,10 +315,11 @@ var Cursor = class {
300
315
  };
301
316
  }
302
317
  static {
303
- this.getRangeFromCursor = (cursorConverter3, cursor) => {
318
+ this.getRangeFromCursor = (state, cursor) => {
319
+ const cursorConverter2 = state.facet(_Cursor.converter);
304
320
  const parts = cursor.split(":");
305
- const from = cursorConverter3.fromCursor(parts[0]);
306
- const to = cursorConverter3.fromCursor(parts[1]);
321
+ const from = cursorConverter2.fromCursor(parts[0]);
322
+ const to = cursorConverter2.fromCursor(parts[1]);
307
323
  return from !== void 0 && to !== void 0 ? {
308
324
  from,
309
325
  to
@@ -313,22 +329,22 @@ var Cursor = class {
313
329
  };
314
330
 
315
331
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
316
- var automerge3 = ({ handle, path }) => {
317
- const stateField = StateField.define({
332
+ var automerge = ({ handle, path }) => {
333
+ const syncState = StateField.define({
318
334
  create: () => ({
319
- lastHeads: A.getHeads(handle.docSync()),
320
- unreconciledTransactions: [],
321
- path: path.slice()
335
+ path: path.slice(),
336
+ lastHeads: A4.getHeads(handle.docSync()),
337
+ unreconciledTransactions: []
322
338
  }),
323
339
  update: (value, tr) => {
324
340
  const result = {
341
+ path: path.slice(),
325
342
  lastHeads: value.lastHeads,
326
- unreconciledTransactions: value.unreconciledTransactions.slice(),
327
- path: path.slice()
343
+ unreconciledTransactions: value.unreconciledTransactions.slice()
328
344
  };
329
345
  let clearUnreconciled = false;
330
346
  for (const effect of tr.effects) {
331
- if (effect.is(effectType)) {
347
+ if (effect.is(updateHeadsEffect)) {
332
348
  result.lastHeads = effect.value.newHeads;
333
349
  clearUnreconciled = true;
334
350
  }
@@ -336,46 +352,53 @@ var automerge3 = ({ handle, path }) => {
336
352
  if (clearUnreconciled) {
337
353
  result.unreconciledTransactions = [];
338
354
  } else {
339
- if (!isReconcileTx(tr)) {
355
+ if (!isReconcile(tr)) {
340
356
  result.unreconciledTransactions.push(tr);
341
357
  }
342
358
  }
343
359
  return result;
344
360
  }
345
361
  });
346
- const semaphore = new PatchSemaphore(handle, stateField);
347
- const viewPlugin = ViewPlugin.fromClass(class AutomergeViewPlugin {
348
- constructor(_view) {
349
- this._view = _view;
350
- this._handleChange = () => {
351
- this._view.state.facet(semaphoreFacet).reconcile(this._view);
352
- };
353
- handle.addListener("change", this._handleChange);
354
- }
355
- update(update2) {
356
- if (update2.transactions.length > 0 && update2.transactions.some((tr) => !isReconcileTx(tr))) {
357
- queueMicrotask(() => {
358
- this._view.state.facet(semaphoreFacet).reconcile(this._view);
362
+ const semaphore = new PatchSemaphore(handle, syncState);
363
+ return [
364
+ Cursor.converter.of(cursorConverter(handle, path)),
365
+ syncState,
366
+ // Reconcile external updates.
367
+ ViewPlugin.fromClass(class {
368
+ constructor(_view) {
369
+ this._view = _view;
370
+ handle.addListener("change", this._handleChange.bind(this));
371
+ }
372
+ destroy() {
373
+ handle.removeListener("change", this._handleChange.bind(this));
374
+ }
375
+ _handleChange() {
376
+ semaphore.reconcile(this._view);
377
+ }
378
+ }),
379
+ // Reconcile local updates.
380
+ EditorView.updateListener.of(({ view, changes }) => {
381
+ if (!changes.empty) {
382
+ semaphore.reconcile(view);
383
+ }
384
+ }),
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) => {
359
392
  });
360
393
  }
361
- }
362
- destroy() {
363
- handle.addListener("change", this._handleChange);
364
- }
365
- });
366
- return {
367
- extension: [
368
- Cursor.converter.of(cursorConverter(handle, path)),
369
- semaphoreFacet.of(semaphore),
370
- stateField,
371
- viewPlugin
372
- ]
373
- };
394
+ return effects;
395
+ })
396
+ ];
374
397
  };
375
398
 
376
399
  // packages/ui/react-ui-editor/src/extensions/awareness.ts
377
- import { Annotation as Annotation2, Facet as Facet3, RangeSet } from "@codemirror/state";
378
- import { Decoration, EditorView, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
400
+ import { Annotation as Annotation2, Facet as Facet2, RangeSet } from "@codemirror/state";
401
+ import { Decoration, EditorView as EditorView2, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
379
402
  import { Event } from "@dxos/async";
380
403
  import { Context } from "@dxos/context";
381
404
  var dummyProvider = {
@@ -384,17 +407,17 @@ var dummyProvider = {
384
407
  },
385
408
  close: () => {
386
409
  },
410
+ getRemoteStates: () => [],
387
411
  update: () => {
388
- },
389
- getRemoteStates: () => []
412
+ }
390
413
  };
391
- var AwarenessProvider = Facet3.define({
414
+ var awarenessProvider = Facet2.define({
392
415
  combine: (providers) => providers[0] ?? dummyProvider
393
416
  });
394
417
  var RemoteSelectionChangedAnnotation = Annotation2.define();
395
418
  var awareness = (provider = dummyProvider) => {
396
419
  return [
397
- AwarenessProvider.of(provider),
420
+ awarenessProvider.of(provider),
398
421
  ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
399
422
  decorations: (value) => value.decorations
400
423
  }),
@@ -408,7 +431,7 @@ var RemoteSelectionsDecorator = class {
408
431
  this._lastAnchor = void 0;
409
432
  this._lastHead = void 0;
410
433
  this._cursorConverter = view.state.facet(Cursor.converter);
411
- this._provider = view.state.facet(AwarenessProvider);
434
+ this._provider = view.state.facet(awarenessProvider);
412
435
  this._provider.open();
413
436
  this._provider.remoteStateChange.on(this._ctx, () => {
414
437
  view.dispatch({
@@ -549,7 +572,7 @@ var RemoteCaretWidget = class extends WidgetType {
549
572
  return true;
550
573
  }
551
574
  };
552
- var styles = EditorView.baseTheme({
575
+ var styles = EditorView2.baseTheme({
553
576
  ".cm-collab-selection": {},
554
577
  ".cm-collab-selectionLine": {
555
578
  padding: 0,
@@ -562,40 +585,42 @@ var styles = EditorView.baseTheme({
562
585
  marginLeft: "-1px",
563
586
  marginRight: "-1px",
564
587
  boxSizing: "border-box",
565
- display: "inline"
588
+ display: "inline",
589
+ cursor: "pointer"
566
590
  },
567
591
  ".cm-collab-selectionCaretDot": {
568
592
  borderRadius: "50%",
569
593
  position: "absolute",
570
- width: ".4em",
571
- height: ".4em",
572
- top: "-.2em",
573
- left: "-.2em",
594
+ width: ".5em",
595
+ height: ".5em",
596
+ top: "-.25em",
597
+ left: "-.25em",
574
598
  backgroundColor: "inherit",
575
599
  transition: "transform .3s ease-in-out",
576
600
  boxSizing: "border-box"
577
601
  },
578
602
  ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
579
- transformOrigin: "bottom center",
580
- transform: "scale(0)"
603
+ transform: "scale(0)",
604
+ transformOrigin: "center"
581
605
  },
582
606
  ".cm-collab-selectionInfo": {
583
607
  position: "absolute",
584
- top: "-1.05em",
585
- left: "-1px",
608
+ transform: "translate(-50%, 0)",
609
+ top: "-20px",
610
+ left: 0,
586
611
  fontSize: ".75em",
587
- fontFamily: "serif",
612
+ fontFamily: "sans-serif",
588
613
  fontStyle: "normal",
589
614
  fontWeight: "normal",
590
615
  lineHeight: "normal",
591
616
  userSelect: "none",
592
617
  color: "white",
593
- paddingLeft: "2px",
594
- paddingRight: "2px",
618
+ padding: "2px",
595
619
  zIndex: 101,
596
620
  transition: "opacity .3s ease-in-out",
597
621
  backgroundColor: "inherit",
598
- // these should be separate
622
+ borderRadius: "2px",
623
+ // These should be separate.
599
624
  opacity: 0,
600
625
  transitionDelay: "0s",
601
626
  whiteSpace: "nowrap"
@@ -608,23 +633,26 @@ var styles = EditorView.baseTheme({
608
633
 
609
634
  // packages/ui/react-ui-editor/src/extensions/basic.ts
610
635
  import { closeBrackets } from "@codemirror/autocomplete";
636
+ import { history } from "@codemirror/commands";
611
637
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
612
638
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
613
- import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
614
- var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
615
- EditorView2.lineWrapping,
616
- // TODO(burdon): Compare with minimalSetup.
639
+ import { drawSelection, EditorView as EditorView3, highlightActiveLine, placeholder } from "@codemirror/view";
640
+ import { isNotFalsy } from "@dxos/util";
641
+ var createBasicBundle = ({ themeMode, placeholder: _placeholder, lineWrapping = true } = {}) => [
642
+ lineWrapping && EditorView3.lineWrapping,
617
643
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
618
644
  bracketMatching(),
619
645
  closeBrackets(),
620
646
  drawSelection(),
647
+ highlightActiveLine(),
648
+ history(),
621
649
  _placeholder && placeholder(_placeholder),
622
- // TODO(burdon): Is this required?
650
+ // https://github.com/codemirror/theme-one-dark
623
651
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
624
- ].filter(Boolean);
652
+ ].filter(isNotFalsy);
625
653
 
626
654
  // packages/ui/react-ui-editor/src/extensions/blast.ts
627
- import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
655
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
628
656
  import defaultsDeep from "lodash.defaultsdeep";
629
657
  import { invariant } from "@dxos/invariant";
630
658
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
@@ -660,10 +688,10 @@ var blast = (options = defaultOptions) => {
660
688
  const getPoint = (view, pos) => {
661
689
  const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
662
690
  const element = document.elementFromPoint(x, y);
663
- const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
691
+ const { offsetLeft: left = 0, offsetTop: top2 = 0 } = view.scrollDOM.parentElement ?? {};
664
692
  const point = {
665
693
  x: x - left,
666
- y: y - top
694
+ y: y - top2
667
695
  };
668
696
  return {
669
697
  element,
@@ -672,7 +700,7 @@ var blast = (options = defaultOptions) => {
672
700
  };
673
701
  return [
674
702
  // Cursor moved.
675
- EditorView3.updateListener.of((update2) => {
703
+ EditorView4.updateListener.of((update2) => {
676
704
  if (blaster?.node !== update2.view.scrollDOM) {
677
705
  if (blaster) {
678
706
  blaster.destroy();
@@ -755,7 +783,7 @@ var Blaster = class {
755
783
  initialize() {
756
784
  invariant(!this._canvas && !this._ctx, void 0, {
757
785
  F: __dxlog_file,
758
- L: 138,
786
+ L: 141,
759
787
  S: this,
760
788
  A: [
761
789
  "!this._canvas && !this._ctx",
@@ -792,7 +820,7 @@ var Blaster = class {
792
820
  start() {
793
821
  invariant(this._canvas && this._ctx, void 0, {
794
822
  F: __dxlog_file,
795
- L: 177,
823
+ L: 180,
796
824
  S: this,
797
825
  A: [
798
826
  "this._canvas && this._ctx",
@@ -933,9 +961,14 @@ var random = (min, max) => {
933
961
  return min + ~~(Math.random() * (max - min + 1));
934
962
  };
935
963
 
936
- // packages/ui/react-ui-editor/src/extensions/code.ts
937
- import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
938
- import { mx as mx2 } from "@dxos/react-ui-theme";
964
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
965
+ import { invertedEffects as invertedEffects2 } from "@codemirror/commands";
966
+ import { Facet as Facet3, StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
967
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration2, EditorView as EditorView5 } from "@codemirror/view";
968
+ import sortBy from "lodash.sortby";
969
+ import { useEffect } from "react";
970
+ import { debounce } from "@dxos/async";
971
+ import { nonNullable } from "@dxos/util";
939
972
 
940
973
  // packages/ui/react-ui-editor/src/styles/cursors.ts
941
974
  import * as random2 from "lib0/random";
@@ -978,34 +1011,15 @@ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
978
1011
  // packages/ui/react-ui-editor/src/styles/markdown.ts
979
1012
  import { mx } from "@dxos/react-ui-theme";
980
1013
  var headings = {
981
- 1: [
982
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
983
- "-ml-[10px]"
984
- ],
985
- 2: [
986
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
987
- "-ml-[8px]"
988
- ],
989
- 3: [
990
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
991
- "-ml-[7px]"
992
- ],
993
- 4: [
994
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
995
- "-ml-[6px]"
996
- ],
997
- 5: [
998
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
999
- "-ml-[5px]"
1000
- ],
1001
- 6: [
1002
- "mbs-4 mbe-2 font-medium text-inherit no-underline",
1003
- "-ml-[4px]"
1004
- ]
1005
- };
1006
- var heading = (level, readonly) => {
1007
- const [style, offset] = headings[level];
1008
- return mx(style, readonly && offset);
1014
+ 1: "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
1015
+ 2: "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
1016
+ 3: "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
1017
+ 4: "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
1018
+ 5: "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
1019
+ 6: "mbs-4 mbe-2 font-medium text-inherit no-underline"
1020
+ };
1021
+ var heading = (level) => {
1022
+ return headings[level];
1009
1023
  };
1010
1024
  var light = "text-neutral-200 dark:text-neutral-800";
1011
1025
  var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
@@ -1023,167 +1037,123 @@ import { tailwindConfig } from "@dxos/react-ui-theme";
1023
1037
  var tokens = tailwindConfig({}).theme;
1024
1038
  var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue);
1025
1039
 
1026
- // packages/ui/react-ui-editor/src/extensions/code.ts
1027
- var CODE_REGEX = /```[\s\S]*?```/gs;
1028
- var styles2 = EditorView4.baseTheme({
1029
- "& .cm-codeblock": {
1030
- fontFamily: getToken("fontFamily.mono", []).join(",")
1031
- },
1032
- '&light [aria-readonly="true"] .cm-codeblock': {
1033
- background: getToken("extend.colors.neutral.50")
1034
- },
1035
- '&dark [aria-readonly="true"] .cm-codeblock': {
1036
- background: getToken("extend.colors.neutral.850")
1037
- },
1038
- '& [aria-readonly="true"] .cm-codeblock': {
1039
- display: "block",
1040
- paddingInline: "4px !important"
1041
- },
1042
- '& [aria-readonly="true"] .cm-codeblock-first': {
1043
- paddingTop: "4px !important",
1044
- borderTopLeftRadius: "4px",
1045
- borderTopRightRadius: "4px"
1046
- },
1047
- '& [aria-readonly="true"] .cm-codeblock-last': {
1048
- paddingBottom: "4px !important",
1049
- borderBottomLeftRadius: "4px",
1050
- borderBottomRightRadius: "4px"
1051
- }
1052
- });
1053
- var getLineRange = (lines, from, to) => {
1054
- const start = lines.findIndex((line) => line.from >= from);
1055
- const end = lines.findIndex((line) => line.to >= to);
1056
- return [
1057
- start,
1058
- end
1059
- ];
1060
- };
1061
- var code2 = () => {
1062
- const getDecorations = (view) => {
1063
- const decorations = [];
1064
- if (!view.hasFocus || view.state.readOnly) {
1065
- const text = view.state.doc.sliceString(0);
1066
- const matches = text.matchAll(CODE_REGEX);
1067
- const blocks = view.viewportLineBlocks;
1068
- for (const match of matches) {
1069
- const range = getLineRange(blocks, match.index, match.index + match[0].length);
1070
- for (let i = range[0]; i <= range[1]; i++) {
1071
- const block = blocks[i];
1072
- decorations.push(Decoration2.line({
1073
- class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1074
- }).range(block.from));
1075
- }
1076
- }
1077
- }
1078
- return Decoration2.set(decorations);
1079
- };
1080
- return [
1081
- ViewPlugin3.fromClass(class {
1082
- constructor(view) {
1083
- this.hasFocus = false;
1084
- this.decorations = getDecorations(view);
1085
- }
1086
- update(update2) {
1087
- if (
1088
- // TODO(burdon): Generalize for other extensions.
1089
- this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1090
- ) {
1091
- this.hasFocus = update2.view.hasFocus;
1092
- this.decorations = getDecorations(update2.view);
1093
- }
1094
- }
1095
- }, {
1096
- decorations: (value) => value.decorations
1097
- }),
1098
- styles2
1099
- ];
1100
- };
1101
-
1102
- // packages/ui/react-ui-editor/src/extensions/comments.ts
1103
- import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1104
- import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
1105
- import sortBy from "lodash.sortby";
1106
- import { debounce } from "@dxos/async";
1107
- import { invariant as invariant2 } from "@dxos/invariant";
1108
- import { nonNullable } from "@dxos/util";
1109
-
1110
1040
  // packages/ui/react-ui-editor/src/util.ts
1111
1041
  import { log } from "@dxos/log";
1112
1042
  var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1113
1043
  var callbackWrapper = (fn) => (...args) => {
1114
1044
  try {
1115
1045
  return fn(...args);
1116
- } catch (error) {
1117
- log.catch(error, void 0, {
1046
+ } catch (err) {
1047
+ log.catch(err, void 0, {
1118
1048
  F: __dxlog_file2,
1119
- L: 16,
1049
+ L: 18,
1120
1050
  S: void 0,
1121
1051
  C: (f, a) => f(...a)
1122
1052
  });
1123
1053
  }
1124
1054
  };
1055
+ var logChanges = (trs) => {
1056
+ const changes = trs.flatMap((tr) => {
1057
+ if (tr.changes.empty) {
1058
+ return void 0;
1059
+ }
1060
+ const changes2 = [];
1061
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => changes2.push(JSON.stringify({
1062
+ fromA,
1063
+ toA,
1064
+ fromB,
1065
+ toB,
1066
+ inserted: inserted.toString()
1067
+ })));
1068
+ return changes2;
1069
+ }).filter(Boolean);
1070
+ if (changes.length) {
1071
+ console.log("changes", changes);
1072
+ }
1073
+ };
1125
1074
 
1126
1075
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1127
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1128
- var styles3 = EditorView5.baseTheme({
1129
- "& .cm-comment": {
1076
+ var styles2 = EditorView5.baseTheme({
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": {
1130
1084
  backgroundColor: getToken("extend.colors.yellow.50")
1131
1085
  },
1132
- "& .cm-comment-active": {
1086
+ "&light .cm-comment-current": {
1133
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")
1134
1096
  }
1135
1097
  });
1136
- var marks = {
1137
- highlight: Decoration3.mark({
1138
- class: "cm-comment"
1139
- }),
1140
- highlightActive: Decoration3.mark({
1141
- class: "cm-comment-active"
1142
- })
1143
- };
1144
- var setFocus = (view, thread) => {
1145
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1146
- if (!range) {
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) => {
1105
+ const comment = view.state.field(commentsState).comments.find((range2) => range2.comment.id === id);
1106
+ if (!comment?.comment.cursor) {
1147
1107
  return;
1148
1108
  }
1149
- view.dispatch({
1150
- effects: setSelection.of({
1151
- active: thread
1152
- }),
1153
- selection: {
1154
- anchor: range.from
1155
- },
1156
- scrollIntoView: true
1157
- });
1109
+ const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);
1110
+ if (range) {
1111
+ view.dispatch({
1112
+ selection: {
1113
+ anchor: range.from
1114
+ },
1115
+ effects: [
1116
+ //
1117
+ EditorView5.scrollIntoView(range.from, center ? {
1118
+ y: "center"
1119
+ } : void 0),
1120
+ setSelection.of({
1121
+ current: id
1122
+ })
1123
+ ]
1124
+ });
1125
+ }
1158
1126
  };
1159
- var setCommentRange = StateEffect2.define();
1127
+ var setComments = StateEffect2.define();
1160
1128
  var setSelection = StateEffect2.define();
1161
1129
  var setCommentState = StateEffect2.define();
1162
- var commentsStateField = StateField2.define({
1130
+ var commentsState = StateField2.define({
1163
1131
  create: () => ({
1164
- ranges: []
1132
+ comments: [],
1133
+ selection: {}
1165
1134
  }),
1166
1135
  update: (value, tr) => {
1167
- const cursorConverter3 = tr.state.facet(Cursor.converter);
1168
1136
  for (const effect of tr.effects) {
1169
1137
  if (effect.is(setSelection)) {
1170
1138
  return {
1171
1139
  ...value,
1172
- ...effect.value
1140
+ selection: effect.value
1173
1141
  };
1174
1142
  }
1175
- if (effect.is(setCommentRange) && effect.value.comments.length > 0) {
1176
- const { comments: comments2 } = effect.value;
1177
- const ranges = comments2.map((comment) => {
1178
- const range = Cursor.getRangeFromCursor(cursorConverter3, comment.cursor);
1143
+ if (effect.is(setComments)) {
1144
+ const comments2 = effect.value.map((comment) => {
1145
+ if (!comment.cursor) {
1146
+ return void 0;
1147
+ }
1148
+ const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);
1179
1149
  return range && {
1180
- ...comment,
1181
- ...range
1150
+ comment,
1151
+ range
1182
1152
  };
1183
1153
  }).filter(nonNullable);
1184
1154
  return {
1185
1155
  ...value,
1186
- ranges
1156
+ comments: comments2
1187
1157
  };
1188
1158
  }
1189
1159
  if (effect.is(setCommentState)) {
@@ -1193,39 +1163,37 @@ var commentsStateField = StateField2.define({
1193
1163
  return value;
1194
1164
  }
1195
1165
  });
1196
- var highlightDecorations = EditorView5.decorations.compute([
1197
- commentsStateField
1166
+ var commentsDecorations = EditorView5.decorations.compute([
1167
+ commentsState
1198
1168
  ], (state) => {
1199
- const { active, ranges } = state.field(commentsStateField);
1200
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1201
- if (selection.from === selection.to) {
1169
+ const { selection: { current }, comments: comments2 } = state.field(commentsState);
1170
+ const decorations = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
1171
+ const range = comment.range;
1172
+ if (!range || range.from === range.to) {
1173
+ console.warn("Invalid range:", range);
1202
1174
  return void 0;
1203
1175
  }
1204
- const range = {
1205
- from: selection.from,
1206
- to: selection.to
1207
- };
1208
- if (selection.id === active) {
1209
- return marks.highlightActive.range(range.from, range.to);
1176
+ if (comment.comment.id === current) {
1177
+ return commentCurrentMark.range(range.from, range.to);
1210
1178
  } else {
1211
- return marks.highlight.range(range.from, range.to);
1179
+ return commentMark.range(range.from, range.to);
1212
1180
  }
1213
- }).filter(nonNullable) ?? [];
1214
- return Decoration3.set(decorations);
1181
+ }).filter(nonNullable);
1182
+ return Decoration2.set(decorations);
1215
1183
  });
1216
1184
  var trackPastedComments = (onUpdate) => {
1217
1185
  let tracked = null;
1218
1186
  const handleTrack = (event, view) => {
1219
- const comments2 = view.state.field(commentsStateField);
1187
+ const comments2 = view.state.field(commentsState);
1220
1188
  const { main } = view.state.selection;
1221
- const selectedRanges = comments2.ranges.filter((range) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1189
+ const selectedRanges = comments2.comments.filter(({ range }) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1222
1190
  if (!selectedRanges.length) {
1223
1191
  tracked = null;
1224
1192
  } else {
1225
1193
  tracked = {
1226
1194
  text: view.state.doc.slice(main.from, main.to),
1227
- comments: selectedRanges.map((range) => ({
1228
- id: range.id,
1195
+ comments: selectedRanges.map(({ comment, range }) => ({
1196
+ id: comment.id,
1229
1197
  from: range.from - main.from,
1230
1198
  to: range.to - main.from
1231
1199
  }))
@@ -1237,90 +1205,136 @@ var trackPastedComments = (onUpdate) => {
1237
1205
  cut: handleTrack,
1238
1206
  copy: handleTrack
1239
1207
  }),
1240
- // Handle paste.
1241
- 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
+ }
1242
1239
  if (tracked) {
1243
- const paste = transactions.find((tr) => tr.isUserEvent("input.paste"));
1240
+ const paste = update2.transactions.find((tr) => tr.isUserEvent("input.paste"));
1244
1241
  if (paste) {
1245
1242
  let found = -1;
1246
1243
  paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1247
1244
  if (text.eq(tracked.text)) {
1248
- for (let i = transactions.indexOf(paste) + 1; i < transactions.length; i++) {
1249
- 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);
1250
1247
  }
1251
1248
  found = fromB;
1252
1249
  }
1253
1250
  });
1254
1251
  if (found > -1) {
1255
- const comments2 = tracked.comments;
1256
- view.state.facet(semaphoreFacet).reconcile(view);
1257
- const active = state.field(commentsStateField).ranges;
1258
- for (const moved of comments2) {
1259
- if (active.some((range) => range.id === moved.id && range.from === range.to)) {
1260
- const range = {
1261
- from: found + moved.from,
1262
- to: found + moved.to
1263
- };
1264
- const cursor = Cursor.getCursorFromRange(state.facet(Cursor.converter), range);
1265
- onUpdate(moved.id, cursor);
1266
- }
1252
+ for (const moved of tracked.comments) {
1253
+ restore.push({
1254
+ id: moved.id,
1255
+ from: found + moved.from,
1256
+ to: found + moved.to
1257
+ });
1267
1258
  }
1268
1259
  }
1269
1260
  tracked = null;
1270
1261
  }
1271
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
+ }
1272
1271
  })
1273
1272
  ];
1274
1273
  };
1275
- var comments = (options = {}) => {
1276
- const { key: shortcut = "meta-'" } = options;
1277
- const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1278
- const createCommentThread = (view) => {
1279
- const cursorConverter3 = view.state.facet(Cursor.converter);
1280
- invariant2(options.onCreate, void 0, {
1281
- F: __dxlog_file3,
1282
- L: 262,
1283
- S: void 0,
1284
- A: [
1285
- "options.onCreate",
1286
- ""
1287
- ]
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
+ });
1282
+ var optionsFacet = Facet3.define({
1283
+ combine: (providers) => providers[0]
1284
+ });
1285
+ var createComment = (view) => {
1286
+ const options = view.state.facet(optionsFacet);
1287
+ const { from, to } = view.state.selection.main;
1288
+ if (from === to) {
1289
+ return false;
1290
+ }
1291
+ if (to === view.state.doc.length) {
1292
+ view.dispatch({
1293
+ changes: {
1294
+ from: to,
1295
+ insert: "\n"
1296
+ }
1288
1297
  });
1289
- const { from, to } = view.state.selection.main;
1290
- if (from === to) {
1291
- return false;
1292
- }
1293
- const cursor = Cursor.getCursorFromRange(cursorConverter3, {
1298
+ }
1299
+ const cursor = Cursor.getCursorFromRange(view.state, {
1300
+ from,
1301
+ to
1302
+ });
1303
+ if (cursor) {
1304
+ const id = options.onCreate?.({
1305
+ cursor,
1294
1306
  from,
1295
- to
1307
+ location: view.coordsAtPos(from)
1296
1308
  });
1297
- if (cursor) {
1298
- const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
1299
- if (id) {
1300
- view.dispatch({
1301
- effects: setSelection.of({
1302
- active: id
1303
- }),
1304
- selection: {
1305
- anchor: from
1306
- }
1307
- });
1308
- return true;
1309
- }
1309
+ if (id) {
1310
+ view.dispatch({
1311
+ effects: setSelection.of({
1312
+ current: id
1313
+ }),
1314
+ selection: {
1315
+ anchor: to
1316
+ }
1317
+ });
1318
+ return true;
1310
1319
  }
1311
- return false;
1312
- };
1320
+ }
1321
+ return false;
1322
+ };
1323
+ var comments = (options = {}) => {
1324
+ const { key: shortcut = "meta-'" } = options;
1325
+ const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1313
1326
  return [
1314
- commentsStateField,
1315
- highlightDecorations,
1316
- styles3,
1327
+ optionsFacet.of(options),
1328
+ commentsState,
1329
+ commentsDecorations,
1330
+ styles2,
1317
1331
  //
1318
1332
  // Keymap.
1319
1333
  //
1320
1334
  options.onCreate ? keymap3.of([
1321
1335
  {
1322
1336
  key: shortcut,
1323
- run: callbackWrapper(createCommentThread)
1337
+ run: callbackWrapper(createComment)
1324
1338
  }
1325
1339
  ]) : [],
1326
1340
  //
@@ -1336,7 +1350,7 @@ var comments = (options = {}) => {
1336
1350
  above: true,
1337
1351
  create: () => {
1338
1352
  const el = document.createElement("div");
1339
- options.onHover?.(el, shortcut);
1353
+ options.onHover(el, shortcut);
1340
1354
  return {
1341
1355
  dom: el,
1342
1356
  offset: {
@@ -1349,358 +1363,116 @@ var comments = (options = {}) => {
1349
1363
  }
1350
1364
  return null;
1351
1365
  }, {
1352
- hideOnChange: true,
1366
+ // TODO(burdon): Hide on change triggered immediately?
1367
+ // hideOnChange: true,
1353
1368
  hoverTime: 1e3
1354
1369
  }) : [],
1355
1370
  //
1356
- // Monitor cursor movement and text updates.
1357
- // TODO(burdon): Is there a better (finer grained) way to do this?
1371
+ // Track deleted ranges and update ranges for decorations.
1358
1372
  //
1359
1373
  EditorView5.updateListener.of(({ view, state, changes }) => {
1360
- const cursorConverter3 = view.state.facet(Cursor.converter);
1361
- {
1362
- let mod = false;
1363
- const { active, ranges } = state.field(commentsStateField);
1364
- changes.iterChanges((from, to, from2, to2) => {
1365
- ranges.forEach((range) => {
1366
- if (from2 === to2) {
1367
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1368
- if (!newRange || newRange.to - newRange.from === 0) {
1369
- options.onDelete?.(range.id);
1370
- }
1374
+ let mod = false;
1375
+ const { comments: comments2, ...value } = state.field(commentsState);
1376
+ changes.iterChanges((from, to, from2, to2) => {
1377
+ comments2.forEach(({ comment, range }) => {
1378
+ if (from2 === to2) {
1379
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1380
+ if (!newRange || newRange.to - newRange.from === 0) {
1381
+ options.onDelete?.(comment.id);
1371
1382
  }
1372
- if (from <= range.to) {
1373
- const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1374
- Object.assign(range, newRange);
1375
- mod = true;
1376
- }
1377
- });
1383
+ }
1384
+ if (from <= range.to) {
1385
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1386
+ Object.assign(range, newRange);
1387
+ mod = true;
1388
+ }
1389
+ });
1390
+ });
1391
+ if (mod) {
1392
+ view.dispatch({
1393
+ effects: setCommentState.of({
1394
+ comments: comments2,
1395
+ ...value
1396
+ })
1378
1397
  });
1379
- if (mod) {
1380
- view.dispatch({
1381
- effects: setCommentState.of({
1382
- active,
1383
- ranges
1384
- })
1385
- });
1386
- }
1387
1398
  }
1388
- {
1389
- const { head } = state.selection.main;
1390
- let min = Infinity;
1391
- const { active, closest, ranges } = state.field(commentsStateField);
1392
- const selected = {
1393
- active: void 0,
1394
- closest: void 0
1395
- };
1396
- ranges.forEach((comment) => {
1397
- const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1398
- if (head >= comment.from && head <= comment.to) {
1399
- selected.active = comment.id;
1400
- }
1399
+ }),
1400
+ //
1401
+ // Track selection/proximity.
1402
+ //
1403
+ EditorView5.updateListener.of(({ view, state }) => {
1404
+ let min = Infinity;
1405
+ const { selection: { current, closest }, comments: comments2 } = state.field(commentsState);
1406
+ const { head } = state.selection.main;
1407
+ const selection = {};
1408
+ comments2.forEach(({ comment, range }) => {
1409
+ if (head >= range.from && head <= range.to) {
1410
+ selection.current = comment.id;
1411
+ selection.closest = void 0;
1412
+ }
1413
+ if (!selection.current) {
1414
+ const d = Math.min(Math.abs(head - range.from), Math.abs(head - range.to));
1401
1415
  if (d < min) {
1402
- selected.closest = comment.id;
1416
+ selection.closest = comment.id;
1403
1417
  min = d;
1404
1418
  }
1405
- });
1406
- if (selected.active !== active || selected.closest !== closest) {
1407
- view.dispatch({
1408
- effects: setSelection.of(selected)
1409
- });
1410
- handleSelect({
1411
- ...selected,
1412
- ranges: ranges.map((range) => ({
1413
- ...range,
1414
- location: view.coordsAtPos(range.from)
1415
- }))
1416
- });
1417
1419
  }
1420
+ });
1421
+ if (selection.current !== current || selection.closest !== closest) {
1422
+ view.dispatch({
1423
+ effects: setSelection.of(selection)
1424
+ });
1425
+ handleSelect({
1426
+ selection,
1427
+ comments: comments2.map(({ comment, range }) => ({
1428
+ comment,
1429
+ range,
1430
+ location: view.coordsAtPos(range.from)
1431
+ }))
1432
+ });
1418
1433
  }
1419
1434
  }),
1420
1435
  options.onUpdate ? trackPastedComments(options.onUpdate) : []
1421
1436
  ];
1422
1437
  };
1438
+ var useComments = (view, comments2 = []) => {
1439
+ useEffect(() => {
1440
+ if (view) {
1441
+ view.dispatch({
1442
+ effects: setComments.of(comments2)
1443
+ });
1444
+ }
1445
+ }, [
1446
+ view,
1447
+ comments2
1448
+ ]);
1449
+ };
1423
1450
 
1424
- // packages/ui/react-ui-editor/src/extensions/hr.ts
1425
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1426
- var styles4 = EditorView6.baseTheme({
1427
- "& .cm-hr": {
1428
- // TODO(burdon): ???
1429
- // Note that block-level decorations should not have vertical margins,
1430
- borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1431
- }
1432
- });
1433
- var HorizontalRuleWidget = class extends WidgetType2 {
1434
- constructor(_pos) {
1435
- super();
1436
- this._pos = _pos;
1437
- }
1438
- eq(other) {
1439
- return this._pos === other._pos;
1440
- }
1441
- toDOM(view) {
1442
- const el = document.createElement("div");
1443
- el.className = "cm-hr";
1444
- return el;
1445
- }
1446
- };
1447
- var placeholderMatcher = new MatchDecorator({
1448
- regexp: /^---$/g,
1449
- decoration: (match, view, pos) => Decoration4.replace({
1450
- widget: new HorizontalRuleWidget(pos)
1451
- })
1452
- });
1453
- var hr = () => [
1454
- styles4,
1455
- ViewPlugin4.fromClass(class {
1456
- constructor(view) {
1457
- this.rules = placeholderMatcher.createDeco(view);
1458
- }
1459
- update(update2) {
1460
- this.rules = placeholderMatcher.updateDeco(update2, this.rules);
1461
- }
1462
- }, {
1463
- decorations: (instance) => instance.rules,
1464
- // TODO(burdon): Is this really atomic (cursor can move into ---).
1465
- provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1466
- return view.plugin(plugin)?.rules || Decoration4.none;
1467
- })
1468
- })
1469
- ];
1470
-
1471
- // packages/ui/react-ui-editor/src/extensions/image.ts
1472
- import { syntaxTree } from "@codemirror/language";
1473
- import { StateField as StateField3 } from "@codemirror/state";
1474
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
1475
- var image = (options = {}) => {
1476
- return StateField3.define({
1477
- create: (state) => {
1478
- return Decoration5.set(buildDecorations(0, state.doc.length, state));
1479
- },
1480
- update: (value, tr) => {
1481
- if (!tr.docChanged && !tr.selection) {
1482
- return value;
1483
- }
1484
- const cursor = tr.state.selection.main.head;
1485
- const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
1486
- let from = Math.min(cursor, oldCursor);
1487
- let to = Math.max(cursor, oldCursor);
1488
- tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
1489
- from = Math.min(from, fromB);
1490
- to = Math.max(to, toB);
1491
- });
1492
- from = tr.state.doc.lineAt(from).from;
1493
- to = tr.state.doc.lineAt(to).to;
1494
- return value.map(tr.changes).update({
1495
- filterFrom: from,
1496
- filterTo: to,
1497
- filter: () => false,
1498
- add: buildDecorations(from, to, tr.state)
1499
- });
1500
- },
1501
- provide: (field) => EditorView7.decorations.from(field)
1502
- });
1503
- };
1504
- var buildDecorations = (from, to, state) => {
1505
- const decorations = [];
1506
- const cursor = state.selection.main.head;
1507
- syntaxTree(state).iterate({
1508
- enter: (node) => {
1509
- if (node.name === "Image") {
1510
- const urlNode = node.node.getChild("URL");
1511
- if (urlNode) {
1512
- const hide = state.readOnly || cursor < node.from || cursor > node.to;
1513
- const url = state.sliceDoc(urlNode.from, urlNode.to);
1514
- decorations.push(Decoration5.replace({
1515
- block: true,
1516
- widget: new ImageWidget(url)
1517
- }).range(hide ? node.from : node.to, node.to));
1518
- }
1519
- }
1520
- },
1521
- from,
1522
- to
1523
- });
1524
- return decorations;
1525
- };
1526
- var ImageWidget = class extends WidgetType3 {
1527
- constructor(_url) {
1528
- super();
1529
- this._url = _url;
1530
- }
1531
- eq(other) {
1532
- return this._url === other._url;
1533
- }
1534
- toDOM(view) {
1535
- const img = document.createElement("img");
1536
- img.setAttribute("src", this._url);
1537
- img.setAttribute("class", "cm-image");
1538
- img.onload = () => img.classList.add("cm-loaded-image");
1539
- return img;
1540
- }
1541
- };
1542
-
1543
- // packages/ui/react-ui-editor/src/extensions/link.ts
1544
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1545
- import { RangeSetBuilder } from "@codemirror/state";
1546
- import { Decoration as Decoration6, WidgetType as WidgetType4, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1547
- import { tooltipContent } from "@dxos/react-ui-theme";
1548
- var link = (options = {}) => {
1549
- const extensions = [
1550
- ViewPlugin5.fromClass(class {
1551
- constructor(view) {
1552
- this.decorations = buildDecorations2(view, options);
1553
- }
1554
- update(update2) {
1555
- if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1556
- this.decorations = buildDecorations2(update2.view, options);
1557
- }
1558
- }
1559
- }, {
1560
- decorations: (v) => v.decorations
1561
- })
1562
- ];
1563
- if (options.onHover) {
1564
- extensions.push(
1565
- // https://codemirror.net/examples/tooltip
1566
- // https://codemirror.net/docs/ref/#view.hoverTooltip
1567
- // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1568
- hoverTooltip2((view, pos, side) => {
1569
- const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1570
- let link2 = null;
1571
- for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1572
- link2 = node.name === "Link" ? node : null;
1573
- }
1574
- const url = link2 && link2.getChild("URL");
1575
- if (!url || !link2) {
1576
- return null;
1577
- }
1578
- const urlText = view.state.sliceDoc(url.from, url.to);
1579
- return {
1580
- pos: link2.from,
1581
- end: link2.to,
1582
- above: true,
1583
- create: () => {
1584
- const el = document.createElement("div");
1585
- el.className = tooltipContent({}, "pli-2 plb-1");
1586
- options.onHover(el, urlText);
1587
- return {
1588
- dom: el,
1589
- offset: {
1590
- x: 0,
1591
- y: 4
1592
- }
1593
- };
1594
- }
1595
- };
1596
- })
1597
- );
1598
- }
1599
- return extensions;
1600
- };
1601
- var LinkButton = class extends WidgetType4 {
1602
- constructor(_url, _onAttach) {
1603
- super();
1604
- this._url = _url;
1605
- this._onAttach = _onAttach;
1606
- }
1607
- eq(other) {
1608
- return this._url === other._url;
1609
- }
1610
- toDOM(view) {
1611
- const el = document.createElement("span");
1612
- this._onAttach(el, this._url);
1613
- return el;
1614
- }
1615
- };
1616
- var LinkText = class extends WidgetType4 {
1617
- constructor(_text, _url) {
1618
- super();
1619
- this._text = _text;
1620
- this._url = _url;
1621
- }
1622
- eq(other) {
1623
- return this._url === other._url;
1624
- }
1625
- toDOM(view) {
1626
- const link2 = document.createElement("a");
1627
- link2.setAttribute("class", "cm-link");
1628
- link2.textContent = this._text;
1629
- if (this._url) {
1630
- link2.setAttribute("rel", "noreferrer");
1631
- link2.setAttribute("target", "_blank");
1632
- link2.href = this._url;
1633
- } else {
1634
- link2.onclick = () => {
1635
- view.dispatch({
1636
- selection: {
1637
- anchor: view.posAtDOM(link2)
1638
- }
1639
- });
1640
- };
1641
- }
1642
- return link2;
1643
- }
1644
- };
1645
- var buildDecorations2 = (view, options) => {
1646
- const builder = new RangeSetBuilder();
1647
- const { state } = view;
1648
- const cursor = state.selection.main.head;
1649
- for (const { from, to } of view.visibleRanges) {
1650
- syntaxTree2(state).iterate({
1651
- enter: (node) => {
1652
- if (node.name === "Link") {
1653
- const marks2 = node.node.getChildren("LinkMark");
1654
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1655
- const urlNode = node.node.getChild("URL");
1656
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1657
- if (!url) {
1658
- return false;
1659
- }
1660
- if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
1661
- builder.add(node.from, node.to, Decoration6.replace({
1662
- widget: new LinkText(text, options.link ? url : void 0)
1663
- }));
1664
- }
1665
- if (options.onRender) {
1666
- builder.add(node.to, node.to, Decoration6.replace({
1667
- widget: new LinkButton(url, options.onRender)
1668
- }));
1669
- }
1670
- return false;
1671
- }
1672
- },
1673
- from,
1674
- to
1675
- });
1676
- }
1677
- return builder.finish();
1678
- };
1679
-
1680
- // packages/ui/react-ui-editor/src/extensions/listener.ts
1681
- import { EditorView as EditorView8 } from "@codemirror/view";
1682
- var listener = ({ onFocus, onChange }) => {
1683
- const extensions = [];
1684
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1685
- onFocus(focused);
1686
- return null;
1687
- }));
1688
- onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1689
- onChange(update2.state.doc.toString());
1690
- }));
1691
- return extensions;
1451
+ // packages/ui/react-ui-editor/src/extensions/listener.ts
1452
+ import { EditorView as EditorView6 } from "@codemirror/view";
1453
+ var listener = ({ onFocus, onChange }) => {
1454
+ const extensions = [];
1455
+ onFocus && extensions.push(EditorView6.focusChangeEffect.of((_, focused) => {
1456
+ onFocus(focused);
1457
+ return null;
1458
+ }));
1459
+ onChange && extensions.push(EditorView6.updateListener.of((update2) => {
1460
+ onChange(update2.state.doc.toString());
1461
+ }));
1462
+ return extensions;
1692
1463
  };
1693
1464
 
1694
1465
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1695
1466
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1696
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1467
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1697
1468
  import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1698
1469
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1699
1470
  import { languages } from "@codemirror/language-data";
1700
1471
  import { searchKeymap } from "@codemirror/search";
1701
1472
  import { EditorState } from "@codemirror/state";
1702
1473
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1703
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
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";
1704
1476
 
1705
1477
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1706
1478
  import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
@@ -1797,7 +1569,7 @@ var markdownHighlightStyle = (readonly) => {
1797
1569
  markdownTags.QuoteMark,
1798
1570
  markdownTags.EmphasisMark
1799
1571
  ],
1800
- class: readonly ? "hidden" : mark
1572
+ class: mark
1801
1573
  },
1802
1574
  // E.g., code block language (after ```).
1803
1575
  {
@@ -1805,7 +1577,7 @@ var markdownHighlightStyle = (readonly) => {
1805
1577
  tags.function(tags.variableName),
1806
1578
  tags.labelName
1807
1579
  ],
1808
- class: readonly ? "hidden" : codeMark
1580
+ class: codeMark
1809
1581
  },
1810
1582
  {
1811
1583
  tag: [
@@ -1816,27 +1588,27 @@ var markdownHighlightStyle = (readonly) => {
1816
1588
  // Headings.
1817
1589
  {
1818
1590
  tag: tags.heading1,
1819
- class: heading(1, readonly)
1591
+ class: heading(1)
1820
1592
  },
1821
1593
  {
1822
1594
  tag: tags.heading2,
1823
- class: heading(2, readonly)
1595
+ class: heading(2)
1824
1596
  },
1825
1597
  {
1826
1598
  tag: tags.heading3,
1827
- class: heading(3, readonly)
1599
+ class: heading(3)
1828
1600
  },
1829
1601
  {
1830
1602
  tag: tags.heading4,
1831
- class: heading(4, readonly)
1603
+ class: heading(4)
1832
1604
  },
1833
1605
  {
1834
1606
  tag: tags.heading5,
1835
- class: heading(5, readonly)
1607
+ class: heading(5)
1836
1608
  },
1837
1609
  {
1838
1610
  tag: tags.heading6,
1839
- class: heading(6, readonly)
1611
+ class: heading(6)
1840
1612
  },
1841
1613
  // Emphasis.
1842
1614
  {
@@ -1862,7 +1634,7 @@ var markdownHighlightStyle = (readonly) => {
1862
1634
  // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1863
1635
  // when a language is specified. In this case, the syntax highlighting extensions will colorize
1864
1636
  // the code, but all other CSS properties will be inherited.
1865
- // IMPORTANT: Therefore, the fenced code block will use the base editor font.
1637
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font unless changed by an extension.
1866
1638
  {
1867
1639
  tag: [
1868
1640
  markdownTags.CodeText,
@@ -1891,19 +1663,19 @@ var markdownHighlightStyle = (readonly) => {
1891
1663
  };
1892
1664
 
1893
1665
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1894
- var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1666
+ var createMarkdownExtensions = ({ themeMode, placeholder: _placeholder, lineWrapping = true } = {}) => {
1895
1667
  return [
1668
+ lineWrapping && EditorView7.lineWrapping,
1896
1669
  EditorState.allowMultipleSelections.of(true),
1897
1670
  EditorState.tabSize.of(2),
1898
- EditorView9.lineWrapping,
1899
- customKeymap(),
1671
+ // https://github.com/codemirror/basic-setup
1900
1672
  bracketMatching2(),
1901
1673
  closeBrackets2(),
1902
1674
  crosshairCursor(),
1903
1675
  dropCursor(),
1904
1676
  drawSelection2(),
1905
- highlightActiveLine(),
1906
- history(),
1677
+ highlightActiveLine2(),
1678
+ history2(),
1907
1679
  indentOnInput(),
1908
1680
  _placeholder && placeholder2(_placeholder),
1909
1681
  // Main extension.
@@ -1927,73 +1699,1567 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1927
1699
  // https://github.com/codemirror/theme-one-dark
1928
1700
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1929
1701
  // Custom styles.
1930
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1931
- ].filter(Boolean);
1932
- };
1933
- var customKeymap = () => keymap4.of([
1934
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1935
- indentWithTab,
1936
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1937
- ...standardKeymap,
1938
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1939
- ...historyKeymap,
1940
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1941
- ...closeBracketsKeymap,
1942
- // https://codemirror.net/docs/ref/#search.searchKeymap
1943
- ...searchKeymap
1944
- ]);
1945
-
1946
- // packages/ui/react-ui-editor/src/extensions/mention.ts
1947
- import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1948
- var mention = ({ onSearch }) => {
1949
- return autocompletion2({
1950
- closeOnBlur: false,
1951
- override: [
1952
- (context) => {
1953
- const match = context.matchBefore(/@(\w+)?/);
1954
- if (!match || match.from === match.to && !context.explicit) {
1955
- return null;
1956
- }
1957
- console.log(match);
1958
- return {
1959
- from: match.from,
1960
- options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1961
- label: `@${value}`
1962
- }))
1963
- };
1964
- }
1965
- ]
1966
- });
1702
+ syntaxHighlighting2(markdownHighlightStyle()),
1703
+ keymap4.of([
1704
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1705
+ indentWithTab,
1706
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1707
+ ...closeBracketsKeymap,
1708
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
1709
+ ...historyKeymap,
1710
+ // https://codemirror.net/docs/ref/#search.searchKeymap
1711
+ ...searchKeymap,
1712
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
1713
+ ...standardKeymap
1714
+ ])
1715
+ ].filter(isNotFalsy2);
1967
1716
  };
1968
1717
 
1969
- // packages/ui/react-ui-editor/src/extensions/table.ts
1970
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1971
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1972
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
1973
- var table = (options = {}) => {
1974
- return StateField4.define({
1975
- create: (state) => update(state, options),
1976
- update: (_, tr) => update(tr.state, options),
1977
- provide: (field) => EditorView10.decorations.from(field)
1978
- });
1718
+ // packages/ui/react-ui-editor/src/extensions/markdown/code.ts
1719
+ import { syntaxTree } from "@codemirror/language";
1720
+ import { RangeSetBuilder } from "@codemirror/state";
1721
+ import { ViewPlugin as ViewPlugin3, EditorView as EditorView8, Decoration as Decoration3, WidgetType as WidgetType2 } from "@codemirror/view";
1722
+ import { mx as mx2 } from "@dxos/react-ui-theme";
1723
+ var styles3 = EditorView8.baseTheme({
1724
+ "& .cm-code": {
1725
+ fontFamily: getToken("fontFamily.mono", []).join(",")
1726
+ },
1727
+ "& .cm-codeblock": {
1728
+ width: "100%",
1729
+ paddingInline: "1rem !important",
1730
+ position: "relative",
1731
+ "&::after": {
1732
+ content: '""',
1733
+ position: "absolute",
1734
+ inset: 0
1735
+ }
1736
+ },
1737
+ "&light .cm-codeblock": {
1738
+ "&::after": {
1739
+ background: getToken("extend.semanticColors.input.light"),
1740
+ mixBlendMode: "darken"
1741
+ }
1742
+ },
1743
+ "&dark .cm-codeblock": {
1744
+ "&::after": {
1745
+ background: getToken("extend.semanticColors.input.dark"),
1746
+ mixBlendMode: "lighten"
1747
+ }
1748
+ },
1749
+ "& .cm-codeblock-first": {
1750
+ display: "inline-block",
1751
+ "&::after": {
1752
+ borderTopLeftRadius: ".5rem",
1753
+ borderTopRightRadius: ".5rem"
1754
+ }
1755
+ },
1756
+ "& .cm-codeblock-last": {
1757
+ display: "inline-block",
1758
+ "&::after": {
1759
+ borderBottomLeftRadius: ".5rem",
1760
+ borderBottomRightRadius: ".5rem"
1761
+ }
1762
+ }
1763
+ });
1764
+ var getLineRange = (lines, from, to) => {
1765
+ const start = lines.findIndex((line) => line.from >= from);
1766
+ const end = lines.findIndex((line) => line.to >= to);
1767
+ return [
1768
+ start,
1769
+ end
1770
+ ];
1979
1771
  };
1980
- var update = (state, options) => {
1981
- const builder = new RangeSetBuilder2();
1772
+ var LineWidget = class extends WidgetType2 {
1773
+ constructor(_className) {
1774
+ super();
1775
+ this._className = _className;
1776
+ }
1777
+ toDOM() {
1778
+ const el = document.createElement("span");
1779
+ el.innerHTML = "&nbsp;";
1780
+ el.className = mx2("cm-code cm-codeblock", this._className);
1781
+ return el;
1782
+ }
1783
+ };
1784
+ var top = new LineWidget("cm-codeblock-first");
1785
+ var bottom = new LineWidget("cm-codeblock-last");
1786
+ var buildDecorations = (view) => {
1787
+ const builder = new RangeSetBuilder();
1788
+ const { state } = view;
1789
+ const blocks = view.viewportLineBlocks;
1982
1790
  const cursor = state.selection.main.head;
1983
- const tables = [];
1984
- const getTable = () => tables[tables.length - 1];
1985
- const getRow = () => {
1986
- const table2 = getTable();
1987
- return table2.rows?.[table2.rows.length - 1];
1988
- };
1989
- syntaxTree3(state).iterate({
1990
- enter: (node) => {
1991
- switch (node.name) {
1992
- case "Table": {
1993
- tables.push({
1994
- from: node.from,
1995
- to: node.to
1996
- });
1791
+ for (const { from, to } of view.visibleRanges) {
1792
+ syntaxTree(state).iterate({
1793
+ enter: (node) => {
1794
+ if (node.name === "FencedCode") {
1795
+ const edit = !view.state.readOnly && cursor >= node.from && cursor <= node.to;
1796
+ const range = getLineRange(blocks, node.from, node.to);
1797
+ for (let i = range[0]; i <= range[1]; i++) {
1798
+ const block = blocks[i];
1799
+ if (!edit && (i === range[0] || i === range[1])) {
1800
+ builder.add(block.from, block.to, Decoration3.replace({
1801
+ widget: i === range[0] ? top : bottom
1802
+ }));
1803
+ } else {
1804
+ builder.add(block.from, block.from, Decoration3.line({
1805
+ class: mx2("cm-code cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1806
+ }));
1807
+ }
1808
+ }
1809
+ }
1810
+ },
1811
+ from,
1812
+ to
1813
+ });
1814
+ }
1815
+ return builder.finish();
1816
+ };
1817
+ var code2 = () => {
1818
+ return [
1819
+ ViewPlugin3.fromClass(class {
1820
+ constructor(view) {
1821
+ this.decorations = buildDecorations(view);
1822
+ }
1823
+ update(update2) {
1824
+ this.decorations = buildDecorations(update2.view);
1825
+ }
1826
+ }, {
1827
+ decorations: (value) => value.decorations
1828
+ }),
1829
+ styles3
1830
+ ];
1831
+ };
1832
+
1833
+ // packages/ui/react-ui-editor/src/extensions/markdown/formatting.ts
1834
+ import { snippet } from "@codemirror/autocomplete";
1835
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
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;
1855
+ const changes = [];
1856
+ let prevBlock = -1;
1857
+ for (const range of ranges) {
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;
2367
+ changes.push({
2368
+ from: line.from + Math.min(open, column),
2369
+ insert: " ".repeat(mark2.length)
2370
+ });
2371
+ }
2372
+ }
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
+ }
2439
+ });
2440
+ }
2441
+ if (!changes.length) {
2442
+ return false;
2443
+ }
2444
+ dispatch(state.update({
2445
+ changes,
2446
+ userEvent: "format.list.remove",
2447
+ scrollIntoView: true
2448
+ }));
2449
+ return true;
2450
+ };
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++;
2471
+ }
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;
2510
+ }
2511
+ }
2512
+ });
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
+ }));
2539
+ return true;
2540
+ };
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);
2659
+ };
2660
+ var formatting = (options = {}) => {
2661
+ return [
2662
+ keymap5.of([
2663
+ {
2664
+ key: "meta-b",
2665
+ run: toggleStrong
2666
+ }
2667
+ ]),
2668
+ styling()
2669
+ ];
2670
+ };
2671
+ var styling = () => {
2672
+ const buildDecorations7 = (view) => {
2673
+ const builder = new RangeSetBuilder2();
2674
+ const { state } = view;
2675
+ const cursor = state.selection.main.head;
2676
+ const replace = (node, marks) => {
2677
+ if (cursor <= node.from || cursor >= node.to) {
2678
+ for (const mark2 of marks) {
2679
+ builder.add(mark2.from, mark2.to, Decoration4.replace({}));
2680
+ }
2681
+ }
2682
+ };
2683
+ for (const { from, to } of view.visibleRanges) {
2684
+ syntaxTree2(state).iterate({
2685
+ enter: (node) => {
2686
+ switch (node.name) {
2687
+ case "Emphasis":
2688
+ case "StrongEmphasis": {
2689
+ const marks = node.node.getChildren("EmphasisMark");
2690
+ replace(node, marks);
2691
+ break;
2692
+ }
2693
+ case "Strikethrough": {
2694
+ const marks = node.node.getChildren("StrikethroughMark");
2695
+ replace(node, marks);
2696
+ break;
2697
+ }
2698
+ }
2699
+ },
2700
+ from,
2701
+ to
2702
+ });
2703
+ }
2704
+ return builder.finish();
2705
+ };
2706
+ return [
2707
+ ViewPlugin4.fromClass(class {
2708
+ constructor(view) {
2709
+ this.decorations = buildDecorations7(view);
2710
+ }
2711
+ update(update2) {
2712
+ this.decorations = buildDecorations7(update2.view);
2713
+ }
2714
+ }, {
2715
+ decorations: (value) => value.decorations
2716
+ })
2717
+ ];
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
+ };
2922
+
2923
+ // packages/ui/react-ui-editor/src/extensions/markdown/heading.ts
2924
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
2925
+ import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
2926
+ import { Decoration as Decoration5, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
2927
+ var buildDecorations2 = (view) => {
2928
+ const builder = new RangeSetBuilder3();
2929
+ const { state } = view;
2930
+ const cursor = state.selection.main.head;
2931
+ for (const { from, to } of view.visibleRanges) {
2932
+ syntaxTree3(state).iterate({
2933
+ enter: (node) => {
2934
+ switch (node.name) {
2935
+ case "ATXHeading1":
2936
+ case "ATXHeading2":
2937
+ case "ATXHeading3":
2938
+ case "ATXHeading4":
2939
+ case "ATXHeading5":
2940
+ case "ATXHeading6": {
2941
+ const mark2 = node.node.getChild("HeaderMark");
2942
+ if (mark2) {
2943
+ if (!view.hasFocus || view.state.readOnly || cursor < node.from || cursor > node.to) {
2944
+ builder.add(mark2.from, mark2.to + 1, Decoration5.replace({}));
2945
+ }
2946
+ }
2947
+ }
2948
+ }
2949
+ },
2950
+ from,
2951
+ to
2952
+ });
2953
+ }
2954
+ return builder.finish();
2955
+ };
2956
+ var heading2 = () => {
2957
+ return [
2958
+ ViewPlugin5.fromClass(class {
2959
+ constructor(view) {
2960
+ this.decorations = buildDecorations2(view);
2961
+ }
2962
+ update(update2) {
2963
+ this.decorations = buildDecorations2(update2.view);
2964
+ }
2965
+ }, {
2966
+ decorations: (value) => value.decorations
2967
+ })
2968
+ ];
2969
+ };
2970
+
2971
+ // packages/ui/react-ui-editor/src/extensions/markdown/hr.ts
2972
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2973
+ import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
2974
+ import { Decoration as Decoration6, EditorView as EditorView10, ViewPlugin as ViewPlugin6, WidgetType as WidgetType3 } from "@codemirror/view";
2975
+ var styles4 = EditorView10.baseTheme({
2976
+ "& .cm-hr": {
2977
+ // Note that block-level decorations should not have vertical margins,
2978
+ borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
2979
+ }
2980
+ });
2981
+ var HorizontalRuleWidget = class extends WidgetType3 {
2982
+ toDOM() {
2983
+ const el = document.createElement("div");
2984
+ el.className = "cm-hr";
2985
+ return el;
2986
+ }
2987
+ };
2988
+ var decoration = Decoration6.replace({
2989
+ widget: new HorizontalRuleWidget()
2990
+ });
2991
+ var buildDecorations3 = (view) => {
2992
+ const builder = new RangeSetBuilder4();
2993
+ const { state } = view;
2994
+ const cursor = state.selection.main.head;
2995
+ for (const { from, to } of view.visibleRanges) {
2996
+ syntaxTree4(state).iterate({
2997
+ enter: (node) => {
2998
+ if (node.name === "HorizontalRule") {
2999
+ if (cursor <= node.from || cursor >= node.to) {
3000
+ builder.add(node.from, node.to, decoration);
3001
+ }
3002
+ }
3003
+ },
3004
+ from,
3005
+ to
3006
+ });
3007
+ }
3008
+ return builder.finish();
3009
+ };
3010
+ var hr = () => {
3011
+ return [
3012
+ ViewPlugin6.fromClass(class {
3013
+ constructor(view) {
3014
+ this.decorations = buildDecorations3(view);
3015
+ }
3016
+ update(update2) {
3017
+ this.decorations = buildDecorations3(update2.view);
3018
+ }
3019
+ }, {
3020
+ decorations: (value) => value.decorations
3021
+ }),
3022
+ styles4
3023
+ ];
3024
+ };
3025
+
3026
+ // packages/ui/react-ui-editor/src/extensions/markdown/image.ts
3027
+ import { syntaxTree as syntaxTree5 } from "@codemirror/language";
3028
+ import { StateField as StateField3 } from "@codemirror/state";
3029
+ import { Decoration as Decoration7, EditorView as EditorView11, WidgetType as WidgetType4 } from "@codemirror/view";
3030
+ var image = (options = {}) => {
3031
+ return StateField3.define({
3032
+ create: (state) => {
3033
+ return Decoration7.set(buildDecorations4(0, state.doc.length, state));
3034
+ },
3035
+ update: (value, tr) => {
3036
+ if (!tr.docChanged && !tr.selection) {
3037
+ return value;
3038
+ }
3039
+ const cursor = tr.state.selection.main.head;
3040
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
3041
+ let from = Math.min(cursor, oldCursor);
3042
+ let to = Math.max(cursor, oldCursor);
3043
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
3044
+ from = Math.min(from, fromB);
3045
+ to = Math.max(to, toB);
3046
+ });
3047
+ from = tr.state.doc.lineAt(from).from;
3048
+ to = tr.state.doc.lineAt(to).to;
3049
+ return value.map(tr.changes).update({
3050
+ filterFrom: from,
3051
+ filterTo: to,
3052
+ filter: () => false,
3053
+ add: buildDecorations4(from, to, tr.state)
3054
+ });
3055
+ },
3056
+ provide: (field) => EditorView11.decorations.from(field)
3057
+ });
3058
+ };
3059
+ var buildDecorations4 = (from, to, state) => {
3060
+ const decorations = [];
3061
+ const cursor = state.selection.main.head;
3062
+ syntaxTree5(state).iterate({
3063
+ enter: (node) => {
3064
+ if (node.name === "Image") {
3065
+ const urlNode = node.node.getChild("URL");
3066
+ if (urlNode) {
3067
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
3068
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
3069
+ decorations.push(Decoration7.replace({
3070
+ block: true,
3071
+ widget: new ImageWidget(url)
3072
+ }).range(hide ? node.from : node.to, node.to));
3073
+ }
3074
+ }
3075
+ },
3076
+ from,
3077
+ to
3078
+ });
3079
+ return decorations;
3080
+ };
3081
+ var ImageWidget = class extends WidgetType4 {
3082
+ constructor(_url) {
3083
+ super();
3084
+ this._url = _url;
3085
+ }
3086
+ eq(other) {
3087
+ return this._url === other._url;
3088
+ }
3089
+ toDOM(view) {
3090
+ const img = document.createElement("img");
3091
+ img.setAttribute("src", this._url);
3092
+ img.setAttribute("class", "cm-image");
3093
+ img.onload = () => img.classList.add("cm-loaded-image");
3094
+ return img;
3095
+ }
3096
+ };
3097
+
3098
+ // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
3099
+ import { syntaxTree as syntaxTree6 } from "@codemirror/language";
3100
+ import { RangeSetBuilder as RangeSetBuilder5 } from "@codemirror/state";
3101
+ import { hoverTooltip as hoverTooltip2, Decoration as Decoration8, ViewPlugin as ViewPlugin7, WidgetType as WidgetType5 } from "@codemirror/view";
3102
+ import { tooltipContent } from "@dxos/react-ui-theme";
3103
+ var link = (options = {}) => {
3104
+ const extensions = [
3105
+ ViewPlugin7.fromClass(class {
3106
+ constructor(view) {
3107
+ this.decorations = buildDecorations5(view, options);
3108
+ }
3109
+ update(update2) {
3110
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
3111
+ this.decorations = buildDecorations5(update2.view, options);
3112
+ }
3113
+ }
3114
+ }, {
3115
+ decorations: (value) => value.decorations
3116
+ })
3117
+ ];
3118
+ if (options.onHover) {
3119
+ extensions.push(
3120
+ // https://codemirror.net/examples/tooltip
3121
+ // https://codemirror.net/docs/ref/#view.hoverTooltip
3122
+ // https://github.com/codemirror/view/blob/main/src/tooltip.ts
3123
+ hoverTooltip2((view, pos, side) => {
3124
+ const syntax = syntaxTree6(view.state).resolveInner(pos, side);
3125
+ let link2 = null;
3126
+ for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
3127
+ link2 = node.name === "Link" ? node : null;
3128
+ }
3129
+ const url = link2 && link2.getChild("URL");
3130
+ if (!url || !link2) {
3131
+ return null;
3132
+ }
3133
+ const urlText = view.state.sliceDoc(url.from, url.to);
3134
+ return {
3135
+ pos: link2.from,
3136
+ end: link2.to,
3137
+ above: true,
3138
+ create: () => {
3139
+ const el = document.createElement("div");
3140
+ el.className = tooltipContent({}, "pli-2 plb-1");
3141
+ options.onHover(el, urlText);
3142
+ return {
3143
+ dom: el,
3144
+ offset: {
3145
+ x: 0,
3146
+ y: 4
3147
+ }
3148
+ };
3149
+ }
3150
+ };
3151
+ })
3152
+ );
3153
+ }
3154
+ return extensions;
3155
+ };
3156
+ var LinkButton = class extends WidgetType5 {
3157
+ constructor(_url, _onAttach) {
3158
+ super();
3159
+ this._url = _url;
3160
+ this._onAttach = _onAttach;
3161
+ }
3162
+ eq(other) {
3163
+ return this._url === other._url;
3164
+ }
3165
+ toDOM(view) {
3166
+ const el = document.createElement("span");
3167
+ this._onAttach(el, this._url);
3168
+ return el;
3169
+ }
3170
+ };
3171
+ var LinkText = class extends WidgetType5 {
3172
+ constructor(_text, _url) {
3173
+ super();
3174
+ this._text = _text;
3175
+ this._url = _url;
3176
+ }
3177
+ eq(other) {
3178
+ return this._url === other._url;
3179
+ }
3180
+ toDOM(view) {
3181
+ const link2 = document.createElement("a");
3182
+ link2.setAttribute("class", "cm-link");
3183
+ link2.textContent = this._text;
3184
+ if (this._url) {
3185
+ link2.setAttribute("rel", "noreferrer");
3186
+ link2.setAttribute("target", "_blank");
3187
+ link2.href = this._url;
3188
+ } else {
3189
+ link2.onclick = () => {
3190
+ view.dispatch({
3191
+ selection: {
3192
+ anchor: view.posAtDOM(link2)
3193
+ }
3194
+ });
3195
+ };
3196
+ }
3197
+ return link2;
3198
+ }
3199
+ };
3200
+ var buildDecorations5 = (view, options) => {
3201
+ const builder = new RangeSetBuilder5();
3202
+ const { state } = view;
3203
+ const cursor = state.selection.main.head;
3204
+ for (const { from, to } of view.visibleRanges) {
3205
+ syntaxTree6(state).iterate({
3206
+ enter: (node) => {
3207
+ if (node.name === "Link") {
3208
+ const marks = node.node.getChildren("LinkMark");
3209
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
3210
+ const urlNode = node.node.getChild("URL");
3211
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
3212
+ if (!url) {
3213
+ return false;
3214
+ }
3215
+ if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
3216
+ builder.add(node.from, node.to, Decoration8.replace({
3217
+ widget: new LinkText(text, options.link ? url : void 0)
3218
+ }));
3219
+ }
3220
+ if (options.onRender) {
3221
+ builder.add(node.to, node.to, Decoration8.replace({
3222
+ widget: new LinkButton(url, options.onRender)
3223
+ }));
3224
+ }
3225
+ return false;
3226
+ }
3227
+ },
3228
+ from,
3229
+ to
3230
+ });
3231
+ }
3232
+ return builder.finish();
3233
+ };
3234
+
3235
+ // packages/ui/react-ui-editor/src/extensions/markdown/table.ts
3236
+ import { syntaxTree as syntaxTree7 } from "@codemirror/language";
3237
+ import { RangeSetBuilder as RangeSetBuilder6, StateField as StateField4 } from "@codemirror/state";
3238
+ import { Decoration as Decoration9, EditorView as EditorView12, WidgetType as WidgetType6 } from "@codemirror/view";
3239
+ var table = (options = {}) => {
3240
+ return StateField4.define({
3241
+ create: (state) => update(state, options),
3242
+ update: (_, tr) => update(tr.state, options),
3243
+ provide: (field) => EditorView12.decorations.from(field)
3244
+ });
3245
+ };
3246
+ var update = (state, options) => {
3247
+ const builder = new RangeSetBuilder6();
3248
+ const cursor = state.selection.main.head;
3249
+ const tables = [];
3250
+ const getTable = () => tables[tables.length - 1];
3251
+ const getRow = () => {
3252
+ const table2 = getTable();
3253
+ return table2.rows?.[table2.rows.length - 1];
3254
+ };
3255
+ syntaxTree7(state).iterate({
3256
+ enter: (node) => {
3257
+ switch (node.name) {
3258
+ case "Table": {
3259
+ tables.push({
3260
+ from: node.from,
3261
+ to: node.to
3262
+ });
1997
3263
  break;
1998
3264
  }
1999
3265
  case "TableHeader": {
@@ -2018,16 +3284,16 @@ var update = (state, options) => {
2018
3284
  });
2019
3285
  tables.forEach((table2) => {
2020
3286
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
2021
- hide && builder.add(table2.from, table2.to, Decoration7.replace({
3287
+ hide && builder.add(table2.from, table2.to, Decoration9.replace({
2022
3288
  widget: new TableWidget(table2)
2023
3289
  }));
2024
- builder.add(table2.from, table2.to, Decoration7.mark({
3290
+ builder.add(table2.from, table2.to, Decoration9.mark({
2025
3291
  class: "cm-table"
2026
3292
  }));
2027
3293
  });
2028
3294
  return builder.finish();
2029
3295
  };
2030
- var TableWidget = class extends WidgetType5 {
3296
+ var TableWidget = class extends WidgetType6 {
2031
3297
  constructor(_table) {
2032
3298
  super();
2033
3299
  this._table = _table;
@@ -2064,11 +3330,20 @@ var TableWidget = class extends WidgetType5 {
2064
3330
  }
2065
3331
  };
2066
3332
 
2067
- // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2068
- import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2069
- import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
2070
- import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType6, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2071
- var CheckboxWidget = class extends WidgetType6 {
3333
+ // packages/ui/react-ui-editor/src/extensions/markdown/tasklist.ts
3334
+ import { syntaxTree as syntaxTree8 } from "@codemirror/language";
3335
+ import { RangeSetBuilder as RangeSetBuilder7 } from "@codemirror/state";
3336
+ import { EditorView as EditorView13, Decoration as Decoration10, WidgetType as WidgetType7, ViewPlugin as ViewPlugin8 } from "@codemirror/view";
3337
+ var styles5 = EditorView13.baseTheme({
3338
+ "& .cm-task": {
3339
+ color: getToken("extend.colors.blue.500")
3340
+ },
3341
+ "& .cm-task-checkbox": {
3342
+ marginLeft: "4px",
3343
+ marginRight: "4px"
3344
+ }
3345
+ });
3346
+ var CheckboxWidget = class extends WidgetType7 {
2072
3347
  constructor(_checked) {
2073
3348
  super();
2074
3349
  this._checked = _checked;
@@ -2078,6 +3353,7 @@ var CheckboxWidget = class extends WidgetType6 {
2078
3353
  }
2079
3354
  toDOM(view) {
2080
3355
  const input = document.createElement("input");
3356
+ input.className = "cm-task-checkbox";
2081
3357
  input.type = "checkbox";
2082
3358
  input.checked = this._checked;
2083
3359
  if (view.state.readOnly) {
@@ -2104,39 +3380,27 @@ var CheckboxWidget = class extends WidgetType6 {
2104
3380
  return false;
2105
3381
  }
2106
3382
  };
2107
- var checkedDeco = Decoration8.replace({
3383
+ var checkedDecoration = Decoration10.replace({
2108
3384
  widget: new CheckboxWidget(true)
2109
3385
  });
2110
- var uncheckedDeco = Decoration8.replace({
3386
+ var uncheckedDecoration = Decoration10.replace({
2111
3387
  widget: new CheckboxWidget(false)
2112
3388
  });
2113
- var tasklist = (options = {}) => {
2114
- return ViewPlugin6.fromClass(class {
2115
- constructor(view) {
2116
- this.decorations = buildDecorations3(view);
2117
- }
2118
- update(update2) {
2119
- if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2120
- this.decorations = buildDecorations3(update2.view);
2121
- }
2122
- }
2123
- }, {
2124
- decorations: (v) => v.decorations,
2125
- provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2126
- return view.plugin(plugin)?.decorations || Decoration8.none;
2127
- })
2128
- });
2129
- };
2130
- var buildDecorations3 = (view) => {
2131
- const builder = new RangeSetBuilder3();
3389
+ var buildDecorations6 = (view) => {
3390
+ const builder = new RangeSetBuilder7();
2132
3391
  const { state } = view;
2133
3392
  const cursor = state.selection.main.head;
2134
3393
  for (const { from, to } of view.visibleRanges) {
2135
- syntaxTree4(state).iterate({
3394
+ syntaxTree8(state).iterate({
2136
3395
  enter: (node) => {
2137
- if (node.name === "TaskMarker" && (cursor < node.from || cursor > node.to)) {
2138
- const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2139
- builder.add(node.from, node.to, checked ? checkedDeco : uncheckedDeco);
3396
+ if (node.name === "TaskMarker") {
3397
+ if (cursor < node.from || cursor > node.to) {
3398
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
3399
+ builder.add(node.from - 2, node.from - 1, Decoration10.mark({
3400
+ class: "cm-task"
3401
+ }));
3402
+ builder.add(node.from, node.to, checked ? checkedDecoration : uncheckedDecoration);
3403
+ }
2140
3404
  }
2141
3405
  },
2142
3406
  from,
@@ -2145,9 +3409,60 @@ var buildDecorations3 = (view) => {
2145
3409
  }
2146
3410
  return builder.finish();
2147
3411
  };
3412
+ var tasklist = (options = {}) => {
3413
+ return [
3414
+ ViewPlugin8.fromClass(class {
3415
+ constructor(view) {
3416
+ this.decorations = buildDecorations6(view);
3417
+ }
3418
+ update(update2) {
3419
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
3420
+ this.decorations = buildDecorations6(update2.view);
3421
+ }
3422
+ }
3423
+ }, {
3424
+ decorations: (value) => value.decorations
3425
+ }),
3426
+ styles5
3427
+ ];
3428
+ };
3429
+
3430
+ // packages/ui/react-ui-editor/src/extensions/mention.ts
3431
+ import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
3432
+ var mention = ({ onSearch }) => {
3433
+ return autocompletion2({
3434
+ // TODO(burdon): Not working.
3435
+ activateOnTyping: true,
3436
+ // activateOnTypingDelay: 100,
3437
+ // selectOnOpen: true,
3438
+ closeOnBlur: false,
3439
+ // defaultKeymap: false,
3440
+ icons: false,
3441
+ override: [
3442
+ (context) => {
3443
+ console.log(context);
3444
+ const match = context.matchBefore(/@(\w+)?/);
3445
+ if (!match || match.from === match.to && !context.explicit) {
3446
+ return null;
3447
+ }
3448
+ return {
3449
+ from: match.from,
3450
+ options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
3451
+ label: `@${value}`
3452
+ }))
3453
+ };
3454
+ }
3455
+ ]
3456
+ });
3457
+ };
3458
+
3459
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
3460
+ var outliner = (options = {}) => {
3461
+ return [];
3462
+ };
2148
3463
 
2149
3464
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
2150
- import { keymap as keymap5 } from "@codemirror/view";
3465
+ import { keymap as keymap6 } from "@codemirror/view";
2151
3466
  var defaultItems = [
2152
3467
  "hello world!",
2153
3468
  "this is a test.",
@@ -2157,7 +3472,7 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2157
3472
  let t;
2158
3473
  let idx = 0;
2159
3474
  return [
2160
- keymap5.of([
3475
+ keymap6.of([
2161
3476
  {
2162
3477
  // Reset.
2163
3478
  key: "alt-meta-'",
@@ -2203,27 +3518,6 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2203
3518
  ];
2204
3519
  };
2205
3520
 
2206
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2207
- import { yCollab } from "y-codemirror.next";
2208
-
2209
- // packages/ui/react-ui-editor/src/extensions/yjs/cursor.ts
2210
- import * as Y from "yjs";
2211
- import { arrayToString, stringToArray } from "@dxos/util";
2212
- var cursorConverter2 = (text) => ({
2213
- toCursor: (index, assoc) => {
2214
- return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
2215
- },
2216
- fromCursor: (cursor) => {
2217
- return Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc)?.index ?? 0;
2218
- }
2219
- });
2220
-
2221
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2222
- var yjs = (content, awareness2) => [
2223
- Cursor.converter.of(cursorConverter2(content)),
2224
- yCollab(content, awareness2)
2225
- ];
2226
-
2227
3521
  // packages/ui/react-ui-editor/src/themes/default.ts
2228
3522
  import get3 from "lodash.get";
2229
3523
  var defaultTheme = {
@@ -2255,8 +3549,9 @@ var defaultTheme = {
2255
3549
  outline: "none"
2256
3550
  },
2257
3551
  ".cm-scroller": {
2258
- overflow: "visible",
2259
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
3552
+ // overflow: 'visible',
3553
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(","),
3554
+ lineHeight: 1.4
2260
3555
  },
2261
3556
  ".cm-content": {
2262
3557
  padding: 0,
@@ -2264,11 +3559,11 @@ var defaultTheme = {
2264
3559
  fontSize: "16px"
2265
3560
  },
2266
3561
  "&light .cm-content": {
2267
- color: get3(tokens, "extend.colors.neutral.900", "black"),
3562
+ color: get3(tokens, "extend.semanticColors.base.fg.light", "black"),
2268
3563
  caretColor: "black"
2269
3564
  },
2270
3565
  "&dark .cm-content": {
2271
- color: get3(tokens, "extend.colors.neutral.100", "white"),
3566
+ color: get3(tokens, "extend.semanticColors.base.fg.dark", "white"),
2272
3567
  caretColor: "white"
2273
3568
  },
2274
3569
  //
@@ -2280,8 +3575,11 @@ var defaultTheme = {
2280
3575
  "&dark .cm-cursor, &dark .cm-dropCursor": {
2281
3576
  borderLeft: "2px solid white"
2282
3577
  },
2283
- ".cm-placeholder": {
2284
- 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)")
2285
3583
  },
2286
3584
  //
2287
3585
  // line
@@ -2290,7 +3588,7 @@ var defaultTheme = {
2290
3588
  paddingInline: 0
2291
3589
  },
2292
3590
  ".cm-activeLine": {
2293
- background: "inherit"
3591
+ background: "transparent"
2294
3592
  },
2295
3593
  //
2296
3594
  // Selection
@@ -2461,90 +3759,88 @@ var codeTheme = {
2461
3759
  };
2462
3760
 
2463
3761
  // 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";
2464
3763
  var EditorModes = [
2465
3764
  "default",
2466
3765
  "vim"
2467
3766
  ];
2468
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, autofocus, readonly, selection, comments: comments2, extensions = [], editorMode, theme, slots = defaultSlots }, forwardedRef) => {
2469
- const tabsterDOMAttribute = useFocusableGroup({
2470
- tabBehavior: "limited"
2471
- });
3767
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2472
3768
  const { themeMode } = useThemeContext();
2473
3769
  const rootRef = useRef(null);
2474
- const [view, setView] = useState(null);
3770
+ const [view, setView] = useState2(null);
2475
3771
  useImperativeHandle(forwardedRef, () => view, [
2476
3772
  view
2477
3773
  ]);
2478
- useEffect(() => {
2479
- if (autofocus) {
3774
+ useEffect2(() => {
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
+ }
2480
3784
  view?.focus();
2481
3785
  }
2482
- }, [
2483
- autofocus,
2484
- view
2485
- ]);
2486
- const { awareness: awareness2, peer } = model;
2487
- useEffect(() => {
2488
- if (awareness2 && peer) {
2489
- awareness2.setLocalStateField("user", {
2490
- name: peer.name ?? generateName(peer.id),
2491
- color: getColorForValue({
2492
- value: peer.id,
2493
- type: "color"
2494
- }),
2495
- colorLight: getColorForValue({
2496
- value: peer.id,
2497
- themeMode,
2498
- type: "highlight"
2499
- })
2500
- });
2501
- }
2502
- }, [
2503
- awareness2,
2504
- peer,
2505
- themeMode
2506
- ]);
2507
- useEffect(() => {
2508
- if (view && comments2 !== void 0) {
2509
- view.dispatch({
2510
- effects: setCommentRange.of({
2511
- model,
2512
- comments: comments2
2513
- })
2514
- });
2515
- }
2516
3786
  }, [
2517
3787
  view,
2518
- comments2
3788
+ autoFocus
2519
3789
  ]);
2520
- useEffect(() => {
3790
+ useEffect2(() => {
2521
3791
  if (!model || !rootRef.current) {
2522
3792
  return;
2523
3793
  }
2524
3794
  const state = EditorState2.create({
2525
3795
  doc: model.text(),
2526
- // TODO(burdon): Composer should store and set selection when switching documents.
2527
3796
  selection,
2528
3797
  extensions: [
2529
- readonly && EditorState2.readOnly.of(readonly),
2530
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2531
- editorMode === "vim" && vim(),
3798
+ // TODO(burdon): Doesn't catch errors in keymap functions.
3799
+ EditorView14.exceptionSink.of((err) => {
3800
+ log2.catch(err, void 0, {
3801
+ F: __dxlog_file3,
3802
+ L: 134,
3803
+ S: void 0,
3804
+ C: (f, a) => f(...a)
3805
+ });
3806
+ }),
2532
3807
  // Theme.
2533
- // TODO(burdon): Make theme configurable.
2534
- EditorView12.baseTheme(defaultTheme),
2535
- EditorView12.theme(theme ?? {}),
2536
- EditorView12.darkTheme.of(themeMode === "dark"),
3808
+ // TODO(burdon): Make configurable.
3809
+ EditorView14.baseTheme(defaultTheme),
3810
+ EditorView14.theme(theme ?? {}),
3811
+ EditorView14.darkTheme.of(themeMode === "dark"),
3812
+ EditorView14.editorAttributes.of({
3813
+ class: slots.editor?.className ?? ""
3814
+ }),
3815
+ EditorView14.contentAttributes.of({
3816
+ class: slots.content?.className ?? ""
3817
+ }),
3818
+ // State.
3819
+ EditorView14.editable.of(!readonly),
3820
+ EditorState2.readOnly.of(!!readonly),
2537
3821
  // Storage and replication.
3822
+ // NOTE: This must come before user extensions.
2538
3823
  model.extension,
3824
+ // TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
3825
+ editorMode === "vim" && vim(),
2539
3826
  // Custom.
2540
3827
  ...extensions
2541
- ].filter(Boolean)
3828
+ ].filter(isNotFalsy3)
2542
3829
  });
2543
- view?.destroy();
2544
- const newView = new EditorView12({
3830
+ const newView = new EditorView14({
3831
+ state,
2545
3832
  parent: rootRef.current,
2546
- state
3833
+ scrollTo,
3834
+ // NOTE: Uncomment to debug/monitor all transactions.
3835
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
3836
+ dispatchTransactions: (trs, view2) => {
3837
+ if (debug) {
3838
+ logChanges(trs);
3839
+ }
3840
+ view2.update(trs);
3841
+ }
2547
3842
  });
3843
+ view?.destroy();
2548
3844
  setView(newView);
2549
3845
  return () => {
2550
3846
  newView?.destroy();
@@ -2560,8 +3856,14 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, autofocus, readonly, s
2560
3856
  const handleKeyUp = useCallback((event) => {
2561
3857
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2562
3858
  switch (key) {
3859
+ case "Enter": {
3860
+ view?.focus();
3861
+ break;
3862
+ }
2563
3863
  case "Escape": {
2564
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
3864
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
3865
+ view?.focus();
3866
+ }
2565
3867
  break;
2566
3868
  }
2567
3869
  }
@@ -2573,23 +3875,23 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, autofocus, readonly, s
2573
3875
  key: model.id,
2574
3876
  role: "none",
2575
3877
  tabIndex: 0,
2576
- onKeyUp: handleKeyUp,
2577
3878
  ...slots.root,
2578
- ...editorMode !== "vim" && tabsterDOMAttribute,
3879
+ // {...(editorMode !== 'vim' && tabsterDOMAttribute)}
3880
+ onKeyUp: handleKeyUp,
2579
3881
  ref: rootRef
2580
3882
  });
2581
3883
  });
2582
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
3884
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, lineWrapping, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
2583
3885
  const { themeMode } = useThemeContext();
2584
3886
  const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2585
3887
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2586
3888
  ref: forwardedRef,
2587
3889
  readonly,
2588
3890
  extensions: [
2589
- basicBundle({
2590
- readonly,
3891
+ createBasicBundle({
2591
3892
  themeMode,
2592
- placeholder: placeholder3
3893
+ placeholder: placeholder3,
3894
+ lineWrapping
2593
3895
  }),
2594
3896
  ...extensions
2595
3897
  ],
@@ -2598,15 +3900,14 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholde
2598
3900
  ...props
2599
3901
  });
2600
3902
  });
2601
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
3903
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
2602
3904
  const { themeMode } = useThemeContext();
2603
3905
  const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2604
3906
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2605
3907
  ref: forwardedRef,
2606
3908
  readonly,
2607
3909
  extensions: [
2608
- markdownBundle({
2609
- readonly,
3910
+ createMarkdownExtensions({
2610
3911
  themeMode,
2611
3912
  placeholder: placeholder3
2612
3913
  }),
@@ -2619,7 +3920,10 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeh
2619
3920
  });
2620
3921
  var defaultSlots = {
2621
3922
  root: {
2622
- className: mx3("w-full p-2 overflow-y-auto", inputSurface)
3923
+ className: mx3("grow", focusRing)
3924
+ },
3925
+ editor: {
3926
+ className: "bs-full"
2623
3927
  }
2624
3928
  };
2625
3929
  var defaultTextSlots = {
@@ -2629,22 +3933,217 @@ var defaultMarkdownSlots = {
2629
3933
  ...defaultSlots
2630
3934
  };
2631
3935
 
2632
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2633
- import get4 from "lodash.get";
2634
- import { getRawDoc } from "@dxos/echo-schema";
2635
- import { isNotNullOrUndefined } from "@dxos/util";
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
+ };
2636
4135
 
2637
4136
  // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2638
4137
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2639
4138
  import { Context as Context2 } from "@dxos/context";
2640
- import { invariant as invariant3 } from "@dxos/invariant";
2641
- import { log as log2 } from "@dxos/log";
4139
+ import { invariant as invariant2 } from "@dxos/invariant";
4140
+ import { log as log3 } from "@dxos/log";
2642
4141
  var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2643
4142
  var DEBOUNCE_INTERVAL = 100;
2644
4143
  var SpaceAwarenessProvider = class {
2645
4144
  constructor(params) {
2646
- this.remoteStateChange = new Event2();
2647
4145
  this._remoteStates = /* @__PURE__ */ new Map();
4146
+ this.remoteStateChange = new Event2();
2648
4147
  this._space = params.space;
2649
4148
  this._channel = params.channel;
2650
4149
  this._peerId = params.peerId;
@@ -2661,7 +4160,7 @@ var SpaceAwarenessProvider = class {
2661
4160
  await sleep(DEBOUNCE_INTERVAL);
2662
4161
  }
2663
4162
  });
2664
- const unsubscribe = this._space.listen(this._channel, (message) => {
4163
+ this._ctx.onDispose(this._space.listen(this._channel, (message) => {
2665
4164
  switch (message.payload.kind) {
2666
4165
  case "query": {
2667
4166
  this._handleQueryMessage();
@@ -2672,16 +4171,15 @@ var SpaceAwarenessProvider = class {
2672
4171
  break;
2673
4172
  }
2674
4173
  }
2675
- });
2676
- this._ctx.onDispose(unsubscribe);
4174
+ }));
2677
4175
  void this._space.postMessage(this._channel, {
2678
4176
  kind: "query"
2679
4177
  }).catch((err) => {
2680
- log2.debug("failed to query awareness", {
4178
+ log3.debug("failed to query awareness", {
2681
4179
  err
2682
4180
  }, {
2683
4181
  F: __dxlog_file4,
2684
- L: 85,
4182
+ L: 89,
2685
4183
  S: this,
2686
4184
  C: (f, a) => f(...a)
2687
4185
  });
@@ -2692,10 +4190,13 @@ var SpaceAwarenessProvider = class {
2692
4190
  this._ctx = void 0;
2693
4191
  this._postTask = void 0;
2694
4192
  }
4193
+ getRemoteStates() {
4194
+ return Array.from(this._remoteStates.values());
4195
+ }
2695
4196
  update(position) {
2696
- invariant3(this._postTask, void 0, {
4197
+ invariant2(this._postTask, void 0, {
2697
4198
  F: __dxlog_file4,
2698
- L: 96,
4199
+ L: 104,
2699
4200
  S: this,
2700
4201
  A: [
2701
4202
  "this._postTask",
@@ -2709,13 +4210,10 @@ var SpaceAwarenessProvider = class {
2709
4210
  };
2710
4211
  this._postTask.schedule();
2711
4212
  }
2712
- getRemoteStates() {
2713
- return Array.from(this._remoteStates.values());
2714
- }
2715
4213
  _handleQueryMessage() {
2716
- invariant3(this._postTask, void 0, {
4214
+ invariant2(this._postTask, void 0, {
2717
4215
  F: __dxlog_file4,
2718
- L: 111,
4216
+ L: 115,
2719
4217
  S: this,
2720
4218
  A: [
2721
4219
  "this._postTask",
@@ -2725,9 +4223,9 @@ var SpaceAwarenessProvider = class {
2725
4223
  this._postTask.schedule();
2726
4224
  }
2727
4225
  _handlePostMessage(message) {
2728
- invariant3(message.kind === "post", void 0, {
4226
+ invariant2(message.kind === "post", void 0, {
2729
4227
  F: __dxlog_file4,
2730
- L: 116,
4228
+ L: 120,
2731
4229
  S: this,
2732
4230
  A: [
2733
4231
  "message.kind === 'post'",
@@ -2739,199 +4237,247 @@ var SpaceAwarenessProvider = class {
2739
4237
  }
2740
4238
  };
2741
4239
 
2742
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
4240
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
2743
4241
  import { StateField as StateField5 } from "@codemirror/state";
2744
- import { useEffect as useEffect2, useState as useState2 } from "react";
2745
- import { isAutomergeObject } from "@dxos/react-client/echo";
4242
+ var modelState = StateField5.define({
4243
+ create: () => void 0,
4244
+ update: (model) => model
4245
+ });
2746
4246
 
2747
- // packages/ui/react-ui-editor/src/hooks/yjs.ts
2748
- import * as decoding from "lib0/decoding";
2749
- import * as encoding from "lib0/encoding";
2750
- import { Observable } from "lib0/observable";
2751
- import * as YP from "y-protocols/awareness";
2752
- import { invariant as invariant4 } from "@dxos/invariant";
2753
- import { log as log3 } from "@dxos/log";
2754
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2755
- var createYjsModel = ({ identity, space, text }) => {
2756
- invariant4(text?.doc && text?.content, void 0, {
2757
- F: __dxlog_file5,
2758
- L: 21,
2759
- S: void 0,
2760
- A: [
2761
- "text?.doc && text?.content",
2762
- ""
2763
- ]
2764
- });
2765
- const provider = space ? new YAwarenessProvider({
2766
- space,
2767
- doc: text.doc,
2768
- channel: `yjs.awareness.${text.id}`
2769
- }) : void 0;
2770
- const model = {
2771
- id: text.doc.guid,
2772
- content: text.content,
2773
- text: () => text.content.toString(),
2774
- extension: [
2775
- modelState.init(() => model),
2776
- yjs(text.content, provider?.awareness)
2777
- ],
2778
- awareness: provider?.awareness,
2779
- peer: identity ? {
2780
- id: identity.identityKey.toHex(),
2781
- name: identity.profile?.displayName
2782
- } : void 0
4247
+ // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
4248
+ var useActionHandler = (view) => {
4249
+ return (action) => {
4250
+ if (!view) {
4251
+ return;
4252
+ }
4253
+ let inlineType, listType;
4254
+ switch (action.type) {
4255
+ case "heading":
4256
+ setHeading(parseInt(action.data))(view);
4257
+ break;
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);
4264
+ break;
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);
4270
+ break;
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);
4276
+ break;
4277
+ case "table":
4278
+ insertTable(view);
4279
+ break;
4280
+ case "link":
4281
+ (action.data === false ? removeLink : addLink)(view);
4282
+ break;
4283
+ case "comment":
4284
+ createComment(view);
4285
+ break;
4286
+ }
4287
+ setTimeout(() => {
4288
+ if (!view.hasFocus) {
4289
+ view.focus();
4290
+ }
4291
+ });
2783
4292
  };
2784
- return model;
2785
4293
  };
2786
- var messageAwareness = 1;
2787
- var messageQueryAwareness = 3;
2788
- var YAwarenessProvider = class extends Observable {
2789
- constructor({ space, doc, channel, awareness: awareness2 }) {
2790
- super();
2791
- this._space = space;
2792
- this._awareness = awareness2 ?? new YP.Awareness(doc);
2793
- this._channel = channel;
2794
- this._clientId = doc.clientID;
2795
- this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
2796
- this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
2797
- if (typeof window !== "undefined") {
2798
- window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
2799
- } else if (typeof process !== "undefined") {
2800
- process.on("exit", this._handleBeforeUnload.bind(this));
2801
- }
2802
- const encoderAwarenessQuery = encoding.createEncoder();
2803
- encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
2804
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
2805
- const encoderAwarenessState = encoding.createEncoder();
2806
- encoding.writeVarUint(encoderAwarenessState, messageAwareness);
2807
- encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [
2808
- this._clientId
2809
- ]));
2810
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
2811
- }
2812
- get awareness() {
2813
- return this._awareness;
2814
- }
2815
- _handleAwarenessUpdate({ added, updated, removed }, origin) {
2816
- log3("awareness update", {
2817
- added,
2818
- updated,
2819
- removed,
2820
- origin
2821
- }, {
2822
- F: __dxlog_file5,
2823
- L: 100,
2824
- S: this,
2825
- C: (f, a) => f(...a)
2826
- });
2827
- const changedClients = added.concat(updated).concat(removed);
2828
- const encoderAwareness = encoding.createEncoder();
2829
- encoding.writeVarUint(encoderAwareness, messageAwareness);
2830
- encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
2831
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2832
- }
2833
- _handleSpaceMessage({ payload }) {
2834
- log3("space message", payload, {
2835
- F: __dxlog_file5,
2836
- L: 109,
2837
- S: this,
2838
- C: (f, a) => f(...a)
2839
- });
2840
- const data = new Uint8Array(Array.from(Object.values(payload)));
2841
- const encoder = this._readMessage(data);
2842
- if (encoder) {
2843
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
2844
- }
2845
- }
2846
- _readMessage(message) {
2847
- const decoder = decoding.createDecoder(message);
2848
- const encoder = encoding.createEncoder();
2849
- const messageType = decoding.readVarUint(decoder);
2850
- let sendReply = false;
2851
- switch (messageType) {
2852
- case messageAwareness: {
2853
- YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2854
- break;
4294
+
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);
4299
+ return [
4300
+ (ref) => {
4301
+ setView(ref);
4302
+ },
4303
+ view
4304
+ ];
4305
+ };
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);
2855
4364
  }
2856
- case messageQueryAwareness: {
2857
- encoding.writeVarUint(encoder, messageAwareness);
2858
- encoding.writeVarUint8Array(encoder, YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2859
- sendReply = true;
2860
- break;
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
+ };
2861
4376
  }
2862
- default: {
2863
- console.error("Invalid message:", messageType);
2864
- return encoder;
4377
+ if (autoFocus) {
4378
+ view.focus();
2865
4379
  }
2866
4380
  }
2867
- if (!sendReply) {
2868
- return null;
2869
- }
2870
- return encoder;
2871
- }
2872
- _handleBeforeUnload() {
2873
- YP.removeAwarenessStates(this._awareness, [
2874
- this._clientId
2875
- ], "window unload");
2876
- }
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);
2877
4412
  };
2878
4413
 
2879
4414
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2880
- var modelState = StateField5.define({
2881
- create: () => void 0,
2882
- update: (model) => model
2883
- });
4415
+ import get4 from "lodash.get";
4416
+ import { useEffect as useEffect4, useState as useState5 } from "react";
4417
+ import { generateName } from "@dxos/display-name";
4418
+ import { getRawDoc } from "@dxos/echo-schema";
4419
+ import { invariant as invariant3 } from "@dxos/invariant";
4420
+ import { isAutomergeObject } from "@dxos/react-client/echo";
4421
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
2884
4422
  var useTextModel = (props) => {
2885
4423
  const { identity, space, text } = props;
2886
- const [model, setModel] = useState2();
2887
- useEffect2(() => setModel(createModel(props)), [
4424
+ const [model, setModel] = useState5();
4425
+ useEffect4(() => setModel(createModel(props)), [
2888
4426
  identity,
2889
4427
  space,
2890
4428
  text
2891
4429
  ]);
2892
4430
  return model;
2893
4431
  };
2894
- var createModel = (props) => {
2895
- const { text } = props;
2896
- if (isAutomergeObject(text)) {
2897
- return createAutomergeModel(props);
2898
- } else if (text?.doc) {
2899
- return createYjsModel(props);
2900
- } else {
2901
- return void 0;
2902
- }
4432
+ var useInMemoryTextModel = ({ id, defaultContent }) => {
4433
+ const [content, setContent] = useState5(defaultContent ?? "");
4434
+ return {
4435
+ id,
4436
+ content,
4437
+ setContent,
4438
+ text: () => content
4439
+ };
2903
4440
  };
2904
-
2905
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2906
- var createAutomergeModel = ({ space, identity, text }) => {
4441
+ var createModel = ({ space, identity, text }) => {
4442
+ invariant3(isAutomergeObject(text), void 0, {
4443
+ F: __dxlog_file6,
4444
+ L: 50,
4445
+ S: void 0,
4446
+ A: [
4447
+ "isAutomergeObject(text)",
4448
+ ""
4449
+ ]
4450
+ });
2907
4451
  const obj = text;
2908
4452
  const doc = getRawDoc(obj, [
2909
4453
  obj.field
2910
4454
  ]);
2911
- const awarenessProvider = space && new SpaceAwarenessProvider({
4455
+ const awarenessProvider2 = space && new SpaceAwarenessProvider({
2912
4456
  space,
2913
4457
  channel: `automerge.awareness.${obj.id}`,
2914
4458
  info: {
2915
- displayName: identity?.profile?.displayName ?? "",
4459
+ displayName: identity ? identity.profile?.displayName ?? generateName(identity.identityKey.toHex()) : void 0,
2916
4460
  color: cursorColor.color,
2917
4461
  lightColor: cursorColor.light
2918
4462
  },
2919
4463
  peerId: identity?.identityKey.toHex() ?? "Anonymous"
2920
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
+ }
2921
4476
  const model = {
2922
4477
  id: obj.id,
2923
4478
  content: doc,
2924
4479
  text: () => get4(doc.handle.docSync(), doc.path),
2925
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2926
- extension: [
2927
- modelState.init(() => model),
2928
- automerge3({
2929
- handle: doc.handle,
2930
- path: doc.path
2931
- }),
2932
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
2933
- awareness()
2934
- ].filter(isNotNullOrUndefined),
4480
+ extension: extensions,
2935
4481
  peer: identity ? {
2936
4482
  id: identity.identityKey.toHex(),
2937
4483
  name: identity.profile?.displayName
@@ -2940,56 +4486,94 @@ var createAutomergeModel = ({ space, identity, text }) => {
2940
4486
  return model;
2941
4487
  };
2942
4488
  export {
2943
- AwarenessProvider,
2944
4489
  BaseTextEditor,
2945
4490
  Cursor,
2946
4491
  Doc,
2947
4492
  EditorModes,
4493
+ Inline,
4494
+ List,
2948
4495
  MarkdownEditor,
2949
4496
  RemoteSelectionsDecorator,
2950
4497
  SpaceAwarenessProvider,
2951
4498
  TextEditor,
2952
4499
  TextKind,
2953
- YAwarenessProvider,
4500
+ Toolbar,
2954
4501
  YText,
2955
4502
  YXmlFragment,
4503
+ addBlockquote,
4504
+ addCodeblock,
4505
+ addLink,
4506
+ addList,
4507
+ addStyle,
2956
4508
  autocomplete,
2957
- automerge3 as automerge,
4509
+ automerge,
2958
4510
  awareness,
2959
- basicBundle,
4511
+ awarenessProvider,
2960
4512
  blast,
2961
4513
  callbackWrapper,
2962
4514
  code2 as code,
2963
4515
  codeTheme,
2964
4516
  comments,
2965
- createAutomergeModel,
2966
- createYjsModel,
4517
+ compareFormatting,
4518
+ createBasicBundle,
4519
+ createComment,
4520
+ createDataExtensions,
4521
+ createMarkdownExtensions,
4522
+ createThemeExtensions,
2967
4523
  defaultMarkdownSlots,
2968
4524
  defaultOptions,
2969
4525
  defaultSlots,
2970
4526
  defaultTextSlots,
2971
4527
  defaultTheme,
4528
+ focusComment,
4529
+ formatting,
4530
+ getFormatting,
2972
4531
  getToken,
4532
+ heading2 as heading,
2973
4533
  hr,
2974
4534
  image,
4535
+ insertTable,
4536
+ keymap7 as keymap,
2975
4537
  link,
2976
4538
  listener,
2977
- markdownBundle,
4539
+ logChanges,
2978
4540
  markdownHighlightStyle,
2979
4541
  markdownTags,
2980
4542
  markdownTagsExtensions,
2981
4543
  markdownTheme,
2982
4544
  mention,
2983
4545
  modelState,
2984
- setCommentRange,
2985
- setFocus,
4546
+ outliner,
4547
+ removeBlockquote,
4548
+ removeCodeblock,
4549
+ removeLink,
4550
+ removeList,
4551
+ removeStyle,
4552
+ setBlockquote,
4553
+ setComments,
4554
+ setHeading,
2986
4555
  setSelection,
4556
+ setStyle,
2987
4557
  table,
2988
4558
  tags2 as tags,
2989
4559
  tasklist,
2990
4560
  textTheme,
4561
+ toggleBlockquote,
4562
+ toggleCodeblock,
4563
+ toggleEmphasis,
4564
+ toggleInlineCode,
4565
+ toggleList,
4566
+ toggleStrikethrough,
4567
+ toggleStrong,
4568
+ toggleStyle,
2991
4569
  typewriter,
4570
+ useActionHandler,
4571
+ useComments,
4572
+ useEditorView,
4573
+ useFormattingState,
4574
+ useInMemoryTextModel,
4575
+ useTextEditor,
2992
4576
  useTextModel,
2993
- yjs
4577
+ useToolbarContext
2994
4578
  };
2995
4579
  //# sourceMappingURL=index.mjs.map