@dxos/react-ui-editor 0.3.11-main.f89c150 → 0.3.11-main.fc97a54

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 (198) hide show
  1. package/dist/lib/browser/index.mjs +2787 -1305
  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 +16 -14
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +20 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -6
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/TextEditor/hooks.stories.d.ts +23 -0
  12. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -0
  13. package/dist/types/src/components/Toolbar/Toolbar.d.ts +29 -0
  14. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
  15. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +25 -0
  16. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
  17. package/dist/types/src/components/Toolbar/index.d.ts +2 -0
  18. package/dist/types/src/components/Toolbar/index.d.ts.map +1 -0
  19. package/dist/types/src/components/index.d.ts +1 -0
  20. package/dist/types/src/components/index.d.ts.map +1 -1
  21. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  22. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  23. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
  24. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
  25. package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
  26. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
  27. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  28. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  29. package/dist/types/src/extensions/automerge/defs.d.ts +9 -11
  30. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  31. package/dist/types/src/extensions/automerge/index.d.ts +0 -1
  32. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
  33. package/dist/types/src/extensions/automerge/semaphore.d.ts +8 -12
  34. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  35. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
  36. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  37. package/dist/types/src/extensions/awareness.d.ts +2 -2
  38. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  39. package/dist/types/src/extensions/basic.d.ts +4 -5
  40. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  41. package/dist/types/src/extensions/comments.d.ts +41 -20
  42. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  43. package/dist/types/src/{util → extensions}/cursor.d.ts +10 -2
  44. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  45. package/dist/types/src/extensions/index.d.ts +2 -7
  46. package/dist/types/src/extensions/index.d.ts.map +1 -1
  47. package/dist/types/src/extensions/markdown/bundle.d.ts +3 -6
  48. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  49. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
  50. package/dist/types/src/extensions/markdown/formatting.d.ts +58 -0
  51. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  52. package/dist/types/src/extensions/markdown/formatting.test.d.ts +3 -0
  53. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -0
  54. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  55. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  56. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  57. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  58. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  59. package/dist/types/src/extensions/markdown/index.d.ts +8 -0
  60. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  61. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  62. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  63. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  64. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  65. package/dist/types/src/extensions/outliner.d.ts +4 -0
  66. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  67. package/dist/types/src/hooks/awareness-provider.d.ts +7 -4
  68. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -1
  69. package/dist/types/src/hooks/defs.d.ts +25 -0
  70. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  71. package/dist/types/src/hooks/index.d.ts +4 -2
  72. package/dist/types/src/hooks/index.d.ts.map +1 -1
  73. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  74. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  75. package/dist/types/src/hooks/useEditorView.d.ts +17 -0
  76. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
  77. package/dist/types/src/hooks/useTextEditor.d.ts +39 -0
  78. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  79. package/dist/types/src/hooks/useTextModel.d.ts +14 -32
  80. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  81. package/dist/types/src/index.d.ts +1 -0
  82. package/dist/types/src/index.d.ts.map +1 -1
  83. package/dist/types/src/styles/markdown.d.ts +2 -2
  84. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  85. package/dist/types/src/themes/default.d.ts.map +1 -1
  86. package/dist/types/src/util.d.ts +7 -0
  87. package/dist/types/src/util.d.ts.map +1 -0
  88. package/package.json +48 -37
  89. package/src/components/TextEditor/MarkdownEditor.stories.tsx +85 -91
  90. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  91. package/src/components/TextEditor/TextEditor.tsx +135 -95
  92. package/src/components/TextEditor/automerge.stories.tsx +1 -2
  93. package/src/components/TextEditor/hooks.stories.tsx +111 -0
  94. package/src/components/Toolbar/Toolbar.stories.tsx +102 -0
  95. package/src/components/Toolbar/Toolbar.tsx +264 -0
  96. package/src/components/Toolbar/index.ts +5 -0
  97. package/src/components/index.ts +1 -0
  98. package/src/extensions/autocomplete.ts +6 -3
  99. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  100. package/src/extensions/automerge/automerge.test.ts +13 -0
  101. package/src/extensions/automerge/automerge.ts +55 -46
  102. package/src/extensions/automerge/cursor.ts +5 -4
  103. package/src/extensions/automerge/defs.ts +13 -34
  104. package/src/extensions/automerge/index.ts +0 -1
  105. package/src/extensions/automerge/semaphore.ts +33 -32
  106. package/src/extensions/automerge/update-automerge.ts +14 -8
  107. package/src/extensions/automerge/update-codemirror.ts +5 -5
  108. package/src/extensions/awareness.ts +46 -49
  109. package/src/extensions/basic.ts +17 -10
  110. package/src/extensions/blast.ts +4 -1
  111. package/src/extensions/comments.ts +331 -265
  112. package/src/extensions/cursor.ts +50 -0
  113. package/src/extensions/index.ts +2 -7
  114. package/src/extensions/markdown/bundle.ts +23 -28
  115. package/src/extensions/markdown/code.ts +150 -0
  116. package/src/extensions/markdown/formatting.test.ts +482 -0
  117. package/src/extensions/markdown/formatting.ts +1237 -0
  118. package/src/extensions/markdown/heading.ts +59 -0
  119. package/src/extensions/markdown/highlight.ts +9 -23
  120. package/src/extensions/markdown/hr.ts +71 -0
  121. package/src/extensions/{image.ts → markdown/image.ts} +2 -2
  122. package/src/extensions/markdown/index.ts +8 -0
  123. package/src/extensions/{link.ts → markdown/link.ts} +20 -11
  124. package/src/extensions/{table.ts → markdown/table.ts} +2 -2
  125. package/src/extensions/markdown/tasklist.ts +110 -0
  126. package/src/extensions/mention.ts +9 -2
  127. package/src/extensions/outliner.ts +12 -0
  128. package/src/hooks/awareness-provider.ts +24 -20
  129. package/src/hooks/defs.ts +42 -0
  130. package/src/hooks/index.ts +5 -2
  131. package/src/hooks/useActionHandler.ts +93 -0
  132. package/src/hooks/useEditorView.ts +29 -0
  133. package/src/hooks/useTextEditor.ts +153 -0
  134. package/src/hooks/useTextModel.ts +67 -60
  135. package/src/index.ts +1 -0
  136. package/src/styles/markdown.ts +9 -12
  137. package/src/themes/default.ts +26 -56
  138. package/src/util.ts +41 -0
  139. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  140. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  141. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +0 -12
  142. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  143. package/dist/types/src/extensions/code.d.ts.map +0 -1
  144. package/dist/types/src/extensions/hr.d.ts.map +0 -1
  145. package/dist/types/src/extensions/image.d.ts.map +0 -1
  146. package/dist/types/src/extensions/link.d.ts.map +0 -1
  147. package/dist/types/src/extensions/table.d.ts.map +0 -1
  148. package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
  149. package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
  150. package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
  151. package/dist/types/src/extensions/yjs/index.d.ts +0 -2
  152. package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
  153. package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
  154. package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
  155. package/dist/types/src/extensions/yjs/yjs.test.d.ts +0 -2
  156. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
  157. package/dist/types/src/hooks/automerge.d.ts +0 -3
  158. package/dist/types/src/hooks/automerge.d.ts.map +0 -1
  159. package/dist/types/src/hooks/yjs.d.ts +0 -27
  160. package/dist/types/src/hooks/yjs.d.ts.map +0 -1
  161. package/dist/types/src/testing/index.d.ts +0 -3
  162. package/dist/types/src/testing/index.d.ts.map +0 -1
  163. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  164. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  165. package/dist/types/src/testing/proto/index.d.ts +0 -2
  166. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  167. package/dist/types/src/testing/replicator.d.ts +0 -45
  168. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  169. package/dist/types/src/util/cursor.d.ts.map +0 -1
  170. package/dist/types/src/util/index.d.ts +0 -3
  171. package/dist/types/src/util/index.d.ts.map +0 -1
  172. package/dist/types/src/util/util.d.ts +0 -5
  173. package/dist/types/src/util/util.d.ts.map +0 -1
  174. package/src/components/TextEditor/codemirror.stories.ts +0 -115
  175. package/src/components/TextEditor/yjs.stories.tsx +0 -56
  176. package/src/extensions/code.ts +0 -98
  177. package/src/extensions/hr.ts +0 -73
  178. package/src/extensions/tasklist.ts +0 -119
  179. package/src/extensions/yjs/cursor.ts +0 -21
  180. package/src/extensions/yjs/index.ts +0 -5
  181. package/src/extensions/yjs/yjs.test.ts +0 -35
  182. package/src/extensions/yjs/yjs.ts +0 -16
  183. package/src/hooks/automerge.ts +0 -52
  184. package/src/hooks/yjs.ts +0 -178
  185. package/src/testing/index.ts +0 -6
  186. package/src/testing/proto/gen/schema.ts +0 -49
  187. package/src/testing/proto/index.ts +0 -5
  188. package/src/testing/proto/schema.proto +0 -15
  189. package/src/testing/replicator.ts +0 -184
  190. package/src/util/cursor.ts +0 -29
  191. package/src/util/index.ts +0 -6
  192. package/src/util/util.ts +0 -18
  193. /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
  194. /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
  195. /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
  196. /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
  197. /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
  198. /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.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 } 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
54
- import { Facet as Facet2, StateField } from "@codemirror/state";
55
- import { ViewPlugin } from "@codemirror/view";
56
- import { next as A } from "@dxos/automerge/automerge";
57
+ import { invertedEffects } from "@codemirror/commands";
58
+ import { StateField } from "@codemirror/state";
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,26 +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
99
  import { Annotation, StateEffect } from "@codemirror/state";
96
- var effectType = StateEffect.define({});
97
- var updateHeads = (newHeads) => effectType.of({
100
+ var getPath = (state, field) => state.field(field).path;
101
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
102
+ var updateHeadsEffect = StateEffect.define({});
103
+ var updateHeads = (newHeads) => updateHeadsEffect.of({
98
104
  newHeads
99
105
  });
100
- var getLastHeads = (state, field) => state.field(field).lastHeads;
101
- var getPath = (state, field) => state.field(field).path;
102
- var reconcileAnnotationType = Annotation.define();
103
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
106
+ var reconcileAnnotation = Annotation.define();
107
+ var isReconcile = (tr) => {
108
+ return !!tr.annotation(reconcileAnnotation);
109
+ };
104
110
 
105
111
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
106
- import { next as automerge2 } from "@dxos/automerge/automerge";
112
+ import { next as A3 } from "@dxos/automerge/automerge";
107
113
 
108
114
  // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
109
- import { next as am } from "@dxos/automerge/automerge";
115
+ import { next as A2 } from "@dxos/automerge/automerge";
110
116
  var updateAutomerge = (field, handle, transactions, state) => {
111
117
  const { lastHeads, path } = state.field(field);
112
118
  let hasChanges = false;
@@ -119,11 +125,19 @@ var updateAutomerge = (field, handle, transactions, state) => {
119
125
  return void 0;
120
126
  }
121
127
  const newHeads = handle.changeAt(lastHeads, (doc) => {
128
+ const invertedTransactions = [];
122
129
  for (const tr of transactions) {
123
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
124
- 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
+ });
125
136
  });
126
137
  }
138
+ invertedTransactions.reverse().forEach(({ from, del, insert }) => {
139
+ A2.splice(doc, path.slice(), from, del, insert.toString());
140
+ });
127
141
  });
128
142
  return newHeads ?? void 0;
129
143
  };
@@ -138,13 +152,13 @@ var updateCodeMirror = (view, selection, target, patches) => {
138
152
  selection = selection.map(changeSet, 1);
139
153
  view.dispatch({
140
154
  changes: changeSet,
141
- annotations: reconcileAnnotationType.of({})
155
+ annotations: reconcileAnnotation.of(false)
142
156
  });
143
157
  }
144
158
  }
145
159
  view.dispatch({
146
160
  selection,
147
- annotations: reconcileAnnotationType.of({})
161
+ annotations: reconcileAnnotation.of(false)
148
162
  });
149
163
  };
150
164
  var handlePatch = (patch, target, state) => {
@@ -165,7 +179,7 @@ var handleInsert = (target, patch) => {
165
179
  if (index == null) {
166
180
  return [];
167
181
  }
168
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
182
+ const text = patch.values.map((value) => value ? value.toString() : "").join("");
169
183
  return [
170
184
  {
171
185
  from: index,
@@ -237,89 +251,100 @@ var charPath = (textPath, candidatePath) => {
237
251
 
238
252
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
239
253
  var PatchSemaphore = class {
240
- constructor(_field) {
241
- this._field = _field;
254
+ // prettier-ignore
255
+ constructor(_handle, _state) {
256
+ this._handle = _handle;
257
+ this._state = _state;
242
258
  this._inReconcile = false;
243
- this._queue = [];
244
- this.reconcile = (handle, view) => {
245
- if (this._inReconcile) {
246
- return;
247
- }
259
+ }
260
+ reconcile(view) {
261
+ if (!this._inReconcile) {
248
262
  this._inReconcile = true;
249
- const path = getPath(view.state, this._field);
250
- const oldHeads = getLastHeads(view.state, this._field);
251
- let selection = view.state.selection;
252
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
253
- const toInvert = transactions.slice().reverse();
254
- for (const tx of toInvert) {
255
- const inverted = tx.changes.invert(tx.startState.doc);
256
- selection = selection.map(inverted);
257
- view.dispatch({
258
- changes: inverted,
259
- annotations: reconcileAnnotationType.of(true)
260
- });
261
- }
262
- let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
263
- if (newHeads === null || newHeads === void 0) {
264
- newHeads = automerge2.getHeads(handle.docSync());
265
- }
266
- const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(handle.docSync(), oldHeads, newHeads);
267
- 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);
268
276
  view.dispatch({
269
- effects: updateHeads(newHeads),
270
- annotations: reconcileAnnotationType.of({})
277
+ changes: inverted,
278
+ annotations: reconcileAnnotation.of(true)
271
279
  });
272
- this._inReconcile = false;
273
- };
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
+ });
274
291
  }
275
292
  };
276
293
 
277
- // packages/ui/react-ui-editor/src/util/cursor.ts
294
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
278
295
  import { Facet } from "@codemirror/state";
279
- var DEFAULT_CURSOR_CONVERTER = {
296
+ var defaultCursorConverter = {
280
297
  toCursor: (position) => position.toString(),
281
298
  fromCursor: (cursor) => parseInt(cursor)
282
299
  };
283
- var CursorConverter = Facet.define({
284
- combine: (providers) => providers[0] ?? DEFAULT_CURSOR_CONVERTER
285
- });
286
-
287
- // packages/ui/react-ui-editor/src/util/util.ts
288
- import { log } from "@dxos/log";
289
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util/util.ts";
290
- var callbackWrapper = (fn) => (...args) => {
291
- try {
292
- return fn(...args);
293
- } catch (error) {
294
- log.catch(error, void 0, {
295
- F: __dxlog_file,
296
- L: 16,
297
- S: void 0,
298
- C: (f, a) => f(...a)
300
+ var Cursor = class _Cursor {
301
+ static {
302
+ this.converter = Facet.define({
303
+ combine: (providers) => providers[0] ?? defaultCursorConverter
299
304
  });
300
305
  }
306
+ static {
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);
311
+ return [
312
+ from,
313
+ to
314
+ ].join(":");
315
+ };
316
+ }
317
+ static {
318
+ this.getRangeFromCursor = (state, cursor) => {
319
+ const cursorConverter2 = state.facet(_Cursor.converter);
320
+ const parts = cursor.split(":");
321
+ const from = cursorConverter2.fromCursor(parts[0]);
322
+ const to = cursorConverter2.fromCursor(parts[1]);
323
+ return from !== void 0 && to !== void 0 ? {
324
+ from,
325
+ to
326
+ } : void 0;
327
+ };
328
+ }
301
329
  };
302
330
 
303
331
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
304
- var semaphoreFacet = Facet2.define({
305
- combine: (values) => values.at(-1)
306
- });
307
- var automerge3 = ({ handle, path }) => {
308
- const stateField = StateField.define({
332
+ var automerge = ({ handle, path }) => {
333
+ const syncState = StateField.define({
309
334
  create: () => ({
310
- lastHeads: A.getHeads(handle.docSync()),
311
- unreconciledTransactions: [],
312
- path: path.slice()
335
+ path: path.slice(),
336
+ lastHeads: A4.getHeads(handle.docSync()),
337
+ unreconciledTransactions: []
313
338
  }),
314
339
  update: (value, tr) => {
315
340
  const result = {
341
+ path: path.slice(),
316
342
  lastHeads: value.lastHeads,
317
- unreconciledTransactions: value.unreconciledTransactions.slice(),
318
- path: path.slice()
343
+ unreconciledTransactions: value.unreconciledTransactions.slice()
319
344
  };
320
345
  let clearUnreconciled = false;
321
346
  for (const effect of tr.effects) {
322
- if (effect.is(effectType)) {
347
+ if (effect.is(updateHeadsEffect)) {
323
348
  result.lastHeads = effect.value.newHeads;
324
349
  clearUnreconciled = true;
325
350
  }
@@ -327,46 +352,53 @@ var automerge3 = ({ handle, path }) => {
327
352
  if (clearUnreconciled) {
328
353
  result.unreconciledTransactions = [];
329
354
  } else {
330
- if (!isReconcileTx(tr)) {
355
+ if (!isReconcile(tr)) {
331
356
  result.unreconciledTransactions.push(tr);
332
357
  }
333
358
  }
334
359
  return result;
335
360
  }
336
361
  });
337
- const semaphore = new PatchSemaphore(stateField);
338
- const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
339
- constructor(_view) {
340
- this._view = _view;
341
- this._handleChange = () => {
342
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
343
- };
344
- handle.addListener("change", this._handleChange);
345
- }
346
- update(update2) {
347
- if (update2.transactions.length > 0 && update2.transactions.some((tr) => !isReconcileTx(tr))) {
348
- queueMicrotask(() => {
349
- this._view.state.facet(semaphoreFacet).reconcile(handle, 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) => {
350
392
  });
351
393
  }
352
- }
353
- destroy() {
354
- handle.addListener("change", this._handleChange);
355
- }
356
- });
357
- return {
358
- extension: [
359
- CursorConverter.of(cursorConverter(handle, path)),
360
- semaphoreFacet.of(semaphore),
361
- stateField,
362
- viewPlugin
363
- ]
364
- };
394
+ return effects;
395
+ })
396
+ ];
365
397
  };
366
398
 
367
399
  // packages/ui/react-ui-editor/src/extensions/awareness.ts
368
- import { Annotation as Annotation2, Facet as Facet3, RangeSet } from "@codemirror/state";
369
- 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";
370
402
  import { Event } from "@dxos/async";
371
403
  import { Context } from "@dxos/context";
372
404
  var dummyProvider = {
@@ -375,17 +407,17 @@ var dummyProvider = {
375
407
  },
376
408
  close: () => {
377
409
  },
410
+ getRemoteStates: () => [],
378
411
  update: () => {
379
- },
380
- getRemoteStates: () => []
412
+ }
381
413
  };
382
- var AwarenessProvider = Facet3.define({
414
+ var awarenessProvider = Facet2.define({
383
415
  combine: (providers) => providers[0] ?? dummyProvider
384
416
  });
385
417
  var RemoteSelectionChangedAnnotation = Annotation2.define();
386
418
  var awareness = (provider = dummyProvider) => {
387
419
  return [
388
- AwarenessProvider.of(provider),
420
+ awarenessProvider.of(provider),
389
421
  ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
390
422
  decorations: (value) => value.decorations
391
423
  }),
@@ -398,8 +430,8 @@ var RemoteSelectionsDecorator = class {
398
430
  this._ctx = new Context();
399
431
  this._lastAnchor = void 0;
400
432
  this._lastHead = void 0;
401
- this._cursorConverter = view.state.facet(CursorConverter);
402
- this._provider = view.state.facet(AwarenessProvider);
433
+ this._cursorConverter = view.state.facet(Cursor.converter);
434
+ this._provider = view.state.facet(awarenessProvider);
403
435
  this._provider.open();
404
436
  this._provider.remoteStateChange.on(this._ctx, () => {
405
437
  view.dispatch({
@@ -453,7 +485,7 @@ var RemoteSelectionsDecorator = class {
453
485
  attributes: {
454
486
  style: `background-color: ${lightColor}`
455
487
  },
456
- class: "cm-ySelection"
488
+ class: "cm-collab-selection"
457
489
  })
458
490
  });
459
491
  } else {
@@ -464,7 +496,7 @@ var RemoteSelectionsDecorator = class {
464
496
  attributes: {
465
497
  style: `background-color: ${color}`
466
498
  },
467
- class: "cm-ySelection"
499
+ class: "cm-collab-selection"
468
500
  })
469
501
  });
470
502
  decorations.push({
@@ -474,7 +506,7 @@ var RemoteSelectionsDecorator = class {
474
506
  attributes: {
475
507
  style: `background-color: ${color}`
476
508
  },
477
- class: "cm-ySelection"
509
+ class: "cm-collab-selection"
478
510
  })
479
511
  });
480
512
  for (let i = startLine.number + 1; i < endLine.number; i++) {
@@ -485,7 +517,7 @@ var RemoteSelectionsDecorator = class {
485
517
  value: Decoration.line({
486
518
  attributes: {
487
519
  style: `background-color: ${color}`,
488
- class: "cm-yLineSelection"
520
+ class: "cm-collab-selectionLine"
489
521
  }
490
522
  })
491
523
  });
@@ -505,23 +537,21 @@ var RemoteSelectionsDecorator = class {
505
537
  }
506
538
  };
507
539
  var RemoteCaretWidget = class extends WidgetType {
508
- constructor(name, color) {
540
+ constructor(_name, _color) {
509
541
  super();
510
- this.name = name;
511
- this.color = color;
512
- this.name = name;
513
- this.color = color;
542
+ this._name = _name;
543
+ this._color = _color;
514
544
  }
515
545
  toDOM() {
516
546
  const span = document.createElement("span");
517
- span.className = "cm-ySelectionCaret";
518
- span.style.backgroundColor = this.color;
519
- span.style.borderColor = this.color;
547
+ span.className = "cm-collab-selectionCaret";
548
+ span.style.backgroundColor = this._color;
549
+ span.style.borderColor = this._color;
520
550
  const dot = document.createElement("div");
521
- dot.className = "cm-ySelectionCaretDot";
551
+ dot.className = "cm-collab-selectionCaretDot";
522
552
  const info = document.createElement("div");
523
- info.className = "cm-ySelectionInfo";
524
- info.innerText = this.name;
553
+ info.className = "cm-collab-selectionInfo";
554
+ info.innerText = this._name;
525
555
  span.appendChild(document.createTextNode("\u2060"));
526
556
  span.appendChild(dot);
527
557
  span.appendChild(document.createTextNode("\u2060"));
@@ -529,15 +559,12 @@ var RemoteCaretWidget = class extends WidgetType {
529
559
  span.appendChild(document.createTextNode("\u2060"));
530
560
  return span;
531
561
  }
532
- eq(widget) {
533
- return widget.color === this.color;
534
- }
535
- compare(widget) {
536
- return widget.color === this.color;
537
- }
538
562
  updateDOM() {
539
563
  return false;
540
564
  }
565
+ eq(widget) {
566
+ return widget._color === this._color;
567
+ }
541
568
  get estimatedHeight() {
542
569
  return -1;
543
570
  }
@@ -545,58 +572,60 @@ var RemoteCaretWidget = class extends WidgetType {
545
572
  return true;
546
573
  }
547
574
  };
548
- var styles = EditorView.baseTheme({
549
- ".cm-ySelection": {},
550
- ".cm-yLineSelection": {
575
+ var styles = EditorView2.baseTheme({
576
+ ".cm-collab-selection": {},
577
+ ".cm-collab-selectionLine": {
551
578
  padding: 0,
552
579
  margin: "0px 2px 0px 4px"
553
580
  },
554
- ".cm-ySelectionCaret": {
581
+ ".cm-collab-selectionCaret": {
555
582
  position: "relative",
556
583
  borderLeft: "1px solid black",
557
584
  borderRight: "1px solid black",
558
585
  marginLeft: "-1px",
559
586
  marginRight: "-1px",
560
587
  boxSizing: "border-box",
561
- display: "inline"
588
+ display: "inline",
589
+ cursor: "pointer"
562
590
  },
563
- ".cm-ySelectionCaretDot": {
591
+ ".cm-collab-selectionCaretDot": {
564
592
  borderRadius: "50%",
565
593
  position: "absolute",
566
- width: ".4em",
567
- height: ".4em",
568
- top: "-.2em",
569
- left: "-.2em",
594
+ width: ".5em",
595
+ height: ".5em",
596
+ top: "-.25em",
597
+ left: "-.25em",
570
598
  backgroundColor: "inherit",
571
599
  transition: "transform .3s ease-in-out",
572
600
  boxSizing: "border-box"
573
601
  },
574
- ".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot": {
575
- transformOrigin: "bottom center",
576
- transform: "scale(0)"
602
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
603
+ transform: "scale(0)",
604
+ transformOrigin: "center"
577
605
  },
578
- ".cm-ySelectionInfo": {
606
+ ".cm-collab-selectionInfo": {
579
607
  position: "absolute",
580
- top: "-1.05em",
581
- left: "-1px",
608
+ transform: "translate(-50%, 0)",
609
+ top: "-20px",
610
+ left: 0,
582
611
  fontSize: ".75em",
583
- fontFamily: "serif",
612
+ fontFamily: "sans-serif",
584
613
  fontStyle: "normal",
585
614
  fontWeight: "normal",
586
615
  lineHeight: "normal",
587
616
  userSelect: "none",
588
617
  color: "white",
589
- paddingLeft: "2px",
590
- paddingRight: "2px",
618
+ padding: "2px",
591
619
  zIndex: 101,
592
620
  transition: "opacity .3s ease-in-out",
593
621
  backgroundColor: "inherit",
594
- // these should be separate
622
+ borderRadius: "2px",
623
+ // These should be separate.
595
624
  opacity: 0,
596
625
  transitionDelay: "0s",
597
626
  whiteSpace: "nowrap"
598
627
  },
599
- ".cm-ySelectionCaret:hover > .cm-ySelectionInfo": {
628
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
600
629
  opacity: 1,
601
630
  transitionDelay: "0s"
602
631
  }
@@ -604,26 +633,29 @@ var styles = EditorView.baseTheme({
604
633
 
605
634
  // packages/ui/react-ui-editor/src/extensions/basic.ts
606
635
  import { closeBrackets } from "@codemirror/autocomplete";
636
+ import { history } from "@codemirror/commands";
607
637
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
608
638
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
609
- import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
610
- var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
611
- EditorView2.lineWrapping,
612
- // 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,
613
643
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
614
644
  bracketMatching(),
615
645
  closeBrackets(),
616
646
  drawSelection(),
647
+ highlightActiveLine(),
648
+ history(),
617
649
  _placeholder && placeholder(_placeholder),
618
- // TODO(burdon): Is this required?
650
+ // https://github.com/codemirror/theme-one-dark
619
651
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
620
- ].filter(Boolean);
652
+ ].filter(isNotFalsy);
621
653
 
622
654
  // packages/ui/react-ui-editor/src/extensions/blast.ts
623
- import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
655
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
624
656
  import defaultsDeep from "lodash.defaultsdeep";
625
657
  import { invariant } from "@dxos/invariant";
626
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
658
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
627
659
  var defaultOptions = {
628
660
  effect: 2,
629
661
  maxParticles: 200,
@@ -656,10 +688,10 @@ var blast = (options = defaultOptions) => {
656
688
  const getPoint = (view, pos) => {
657
689
  const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
658
690
  const element = document.elementFromPoint(x, y);
659
- const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
691
+ const { offsetLeft: left = 0, offsetTop: top2 = 0 } = view.scrollDOM.parentElement ?? {};
660
692
  const point = {
661
693
  x: x - left,
662
- y: y - top
694
+ y: y - top2
663
695
  };
664
696
  return {
665
697
  element,
@@ -668,7 +700,7 @@ var blast = (options = defaultOptions) => {
668
700
  };
669
701
  return [
670
702
  // Cursor moved.
671
- EditorView3.updateListener.of((update2) => {
703
+ EditorView4.updateListener.of((update2) => {
672
704
  if (blaster?.node !== update2.view.scrollDOM) {
673
705
  if (blaster) {
674
706
  blaster.destroy();
@@ -750,8 +782,8 @@ var Blaster = class {
750
782
  }
751
783
  initialize() {
752
784
  invariant(!this._canvas && !this._ctx, void 0, {
753
- F: __dxlog_file2,
754
- L: 138,
785
+ F: __dxlog_file,
786
+ L: 141,
755
787
  S: this,
756
788
  A: [
757
789
  "!this._canvas && !this._ctx",
@@ -787,8 +819,8 @@ var Blaster = class {
787
819
  }
788
820
  start() {
789
821
  invariant(this._canvas && this._ctx, void 0, {
790
- F: __dxlog_file2,
791
- L: 177,
822
+ F: __dxlog_file,
823
+ L: 180,
792
824
  S: this,
793
825
  A: [
794
826
  "this._canvas && this._ctx",
@@ -929,9 +961,14 @@ var random = (min, max) => {
929
961
  return min + ~~(Math.random() * (max - min + 1));
930
962
  };
931
963
 
932
- // packages/ui/react-ui-editor/src/extensions/code.ts
933
- import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
934
- 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";
935
972
 
936
973
  // packages/ui/react-ui-editor/src/styles/cursors.ts
937
974
  import * as random2 from "lib0/random";
@@ -974,34 +1011,15 @@ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
974
1011
  // packages/ui/react-ui-editor/src/styles/markdown.ts
975
1012
  import { mx } from "@dxos/react-ui-theme";
976
1013
  var headings = {
977
- 1: [
978
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
979
- "-ml-[10px]"
980
- ],
981
- 2: [
982
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
983
- "-ml-[8px]"
984
- ],
985
- 3: [
986
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
987
- "-ml-[7px]"
988
- ],
989
- 4: [
990
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
991
- "-ml-[6px]"
992
- ],
993
- 5: [
994
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
995
- "-ml-[5px]"
996
- ],
997
- 6: [
998
- "mbs-4 mbe-2 font-medium text-inherit no-underline",
999
- "-ml-[4px]"
1000
- ]
1001
- };
1002
- var heading = (level, readonly) => {
1003
- const [style, offset] = headings[level];
1004
- 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];
1005
1023
  };
1006
1024
  var light = "text-neutral-200 dark:text-neutral-800";
1007
1025
  var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
@@ -1019,154 +1037,123 @@ import { tailwindConfig } from "@dxos/react-ui-theme";
1019
1037
  var tokens = tailwindConfig({}).theme;
1020
1038
  var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue);
1021
1039
 
1022
- // packages/ui/react-ui-editor/src/extensions/code.ts
1023
- var CODE_REGEX = /```[\s\S]*?```/gs;
1024
- var styles2 = EditorView4.baseTheme({
1025
- "& .cm-codeblock": {
1026
- fontFamily: getToken("fontFamily.mono", []).join(",")
1027
- },
1028
- '&light [aria-readonly="true"] .cm-codeblock': {
1029
- background: getToken("extend.colors.neutral.50")
1030
- },
1031
- '&dark [aria-readonly="true"] .cm-codeblock': {
1032
- background: getToken("extend.colors.neutral.850")
1033
- },
1034
- '& [aria-readonly="true"] .cm-codeblock': {
1035
- display: "block",
1036
- paddingInline: "4px !important"
1037
- },
1038
- '& [aria-readonly="true"] .cm-codeblock-first': {
1039
- paddingTop: "4px !important",
1040
- borderTopLeftRadius: "4px",
1041
- borderTopRightRadius: "4px"
1042
- },
1043
- '& [aria-readonly="true"] .cm-codeblock-last': {
1044
- paddingBottom: "4px !important",
1045
- borderBottomLeftRadius: "4px",
1046
- borderBottomRightRadius: "4px"
1040
+ // packages/ui/react-ui-editor/src/util.ts
1041
+ import { log } from "@dxos/log";
1042
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1043
+ var callbackWrapper = (fn) => (...args) => {
1044
+ try {
1045
+ return fn(...args);
1046
+ } catch (err) {
1047
+ log.catch(err, void 0, {
1048
+ F: __dxlog_file2,
1049
+ L: 18,
1050
+ S: void 0,
1051
+ C: (f, a) => f(...a)
1052
+ });
1047
1053
  }
1048
- });
1049
- var getLineRange = (lines, from, to) => {
1050
- const start = lines.findIndex((line) => line.from >= from);
1051
- const end = lines.findIndex((line) => line.to >= to);
1052
- return [
1053
- start,
1054
- end
1055
- ];
1056
1054
  };
1057
- var code2 = () => {
1058
- const getDecorations = (view) => {
1059
- const decorations = [];
1060
- if (view.state.readOnly) {
1061
- const text = view.state.doc.sliceString(0);
1062
- const matches = text.matchAll(CODE_REGEX);
1063
- const blocks = view.viewportLineBlocks;
1064
- for (const match of matches) {
1065
- const range = getLineRange(blocks, match.index, match.index + match[0].length);
1066
- for (let i = range[0]; i <= range[1]; i++) {
1067
- const block = blocks[i];
1068
- decorations.push(Decoration2.line({
1069
- class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1070
- }).range(block.from));
1071
- }
1072
- }
1055
+ var logChanges = (trs) => {
1056
+ const changes = trs.flatMap((tr) => {
1057
+ if (tr.changes.empty) {
1058
+ return void 0;
1073
1059
  }
1074
- return Decoration2.set(decorations);
1075
- };
1076
- return [
1077
- ViewPlugin3.fromClass(class {
1078
- constructor(view) {
1079
- this.decorations = getDecorations(view);
1080
- }
1081
- update(update2) {
1082
- if (update2.docChanged || update2.viewportChanged) {
1083
- this.decorations = getDecorations(update2.view);
1084
- }
1085
- }
1086
- }, {
1087
- decorations: (value) => value.decorations
1088
- }),
1089
- styles2
1090
- ];
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
+ }
1091
1073
  };
1092
1074
 
1093
1075
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1094
- import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1095
- import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1096
- import sortBy from "lodash.sortby";
1097
- import { debounce } from "@dxos/async";
1098
- import { invariant as invariant2 } from "@dxos/invariant";
1099
- import { log as log2 } from "@dxos/log";
1100
- import { nonNullable } from "@dxos/util";
1101
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1102
- var styles3 = EditorView5.baseTheme({
1103
- "& .cm-bookmark": {
1104
- cursor: "pointer",
1105
- margin: "4px",
1106
- padding: "4px",
1107
- backgroundColor: "yellow"
1076
+ var styles2 = EditorView5.baseTheme({
1077
+ "&light .cm-comment, &light .cm-comment-current": {
1078
+ mixBlendMode: "darken"
1108
1079
  },
1109
- "& .cm-bookmark-selected": {
1110
- backgroundColor: "orange"
1080
+ "&dark .cm-comment, &dark .cm-comment-current": {
1081
+ mixBlendMode: "plus-lighter"
1111
1082
  },
1112
- "& .cm-comment": {
1083
+ "&light .cm-comment": {
1113
1084
  backgroundColor: getToken("extend.colors.yellow.50")
1114
1085
  },
1115
- "& .cm-comment-active": {
1086
+ "&light .cm-comment-current": {
1116
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")
1117
1096
  }
1118
1097
  });
1119
- var marks = {
1120
- highlight: Decoration3.mark({
1121
- class: "cm-comment"
1122
- }),
1123
- highlightActive: Decoration3.mark({
1124
- class: "cm-comment-active"
1125
- })
1126
- };
1127
- var setFocus = (view, thread) => {
1128
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1129
- 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) {
1130
1107
  return;
1131
1108
  }
1132
- view.dispatch({
1133
- effects: setSelection.of({
1134
- active: thread
1135
- }),
1136
- selection: {
1137
- anchor: range.from
1138
- },
1139
- scrollIntoView: true
1140
- });
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
+ }
1141
1126
  };
1142
- var setCommentRange = StateEffect2.define();
1127
+ var setComments = StateEffect2.define();
1143
1128
  var setSelection = StateEffect2.define();
1144
1129
  var setCommentState = StateEffect2.define();
1145
- var commentsStateField = StateField2.define({
1130
+ var commentsState = StateField2.define({
1146
1131
  create: () => ({
1147
- ranges: []
1132
+ comments: [],
1133
+ selection: {}
1148
1134
  }),
1149
1135
  update: (value, tr) => {
1150
- const cursorConverter3 = tr.state.facet(CursorConverter);
1151
1136
  for (const effect of tr.effects) {
1152
1137
  if (effect.is(setSelection)) {
1153
1138
  return {
1154
1139
  ...value,
1155
- ...effect.value
1140
+ selection: effect.value
1156
1141
  };
1157
1142
  }
1158
- if (effect.is(setCommentRange)) {
1159
- const { comments: comments2 } = effect.value;
1160
- const ranges = comments2.map((comment) => {
1161
- const range = 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);
1162
1149
  return range && {
1163
- ...comment,
1164
- ...range
1150
+ comment,
1151
+ range
1165
1152
  };
1166
1153
  }).filter(nonNullable);
1167
1154
  return {
1168
1155
  ...value,
1169
- ranges
1156
+ comments: comments2
1170
1157
  };
1171
1158
  }
1172
1159
  if (effect.is(setCommentState)) {
@@ -1176,143 +1163,178 @@ var commentsStateField = StateField2.define({
1176
1163
  return value;
1177
1164
  }
1178
1165
  });
1179
- var highlightDecorations = EditorView5.decorations.compute([
1180
- commentsStateField
1166
+ var commentsDecorations = EditorView5.decorations.compute([
1167
+ commentsState
1181
1168
  ], (state) => {
1182
- const { active, ranges } = state.field(commentsStateField);
1183
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1184
- 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);
1185
1174
  return void 0;
1186
1175
  }
1187
- const range = {
1188
- from: selection.from,
1189
- to: selection.to
1190
- };
1191
- if (selection.id === active) {
1192
- return marks.highlightActive.range(range.from, range.to);
1176
+ if (comment.comment.id === current) {
1177
+ return commentCurrentMark.range(range.from, range.to);
1193
1178
  } else {
1194
- return marks.highlight.range(range.from, range.to);
1179
+ return commentMark.range(range.from, range.to);
1195
1180
  }
1196
- }).filter(nonNullable) ?? [];
1197
- return Decoration3.set(decorations);
1181
+ }).filter(nonNullable);
1182
+ return Decoration2.set(decorations);
1198
1183
  });
1199
- var BookmarkWidget = class extends WidgetType2 {
1200
- constructor(_anchor, _id, _handleClick) {
1201
- super();
1202
- this._anchor = _anchor;
1203
- this._id = _id;
1204
- this._handleClick = _handleClick;
1205
- invariant2(this._id, void 0, {
1206
- F: __dxlog_file3,
1207
- L: 168,
1208
- S: this,
1209
- A: [
1210
- "this._id",
1211
- ""
1212
- ]
1213
- });
1214
- }
1215
- eq(other) {
1216
- return this._anchor === other._anchor && this._id === other._id;
1217
- }
1218
- toDOM() {
1219
- const span = document.createElement("span");
1220
- span.className = "cm-bookmark";
1221
- span.textContent = "\u203B";
1222
- if (this._handleClick) {
1223
- span.onclick = () => this._handleClick();
1224
- }
1225
- return span;
1226
- }
1227
- };
1228
- var comments = (options = {}) => {
1229
- const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1230
- const createCommentThread = (view) => {
1231
- const cursorConverter3 = view.state.facet(CursorConverter);
1232
- invariant2(options.onCreate, void 0, {
1233
- F: __dxlog_file3,
1234
- L: 224,
1235
- S: void 0,
1236
- A: [
1237
- "options.onCreate",
1238
- ""
1239
- ]
1240
- });
1241
- const { head, from, to } = view.state.selection.main;
1242
- if (from === to) {
1243
- return false;
1184
+ var trackPastedComments = (onUpdate) => {
1185
+ let tracked = null;
1186
+ const handleTrack = (event, view) => {
1187
+ const comments2 = view.state.field(commentsState);
1188
+ const { main } = view.state.selection;
1189
+ const selectedRanges = comments2.comments.filter(({ range }) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1190
+ if (!selectedRanges.length) {
1191
+ tracked = null;
1192
+ } else {
1193
+ tracked = {
1194
+ text: view.state.doc.slice(main.from, main.to),
1195
+ comments: selectedRanges.map(({ comment, range }) => ({
1196
+ id: comment.id,
1197
+ from: range.from - main.from,
1198
+ to: range.to - main.from
1199
+ }))
1200
+ };
1244
1201
  }
1245
- const relPos = getCursorFromRange(cursorConverter3, {
1246
- from,
1247
- to
1248
- });
1249
- if (relPos) {
1250
- const id = callbackWrapper(options.onCreate)(relPos);
1251
- if (id) {
1252
- view.dispatch({
1253
- effects: setSelection.of({
1254
- active: id
1255
- }),
1256
- selection: {
1257
- anchor: from
1202
+ };
1203
+ return [
1204
+ EditorView5.domEventHandlers({
1205
+ cut: handleTrack,
1206
+ copy: handleTrack
1207
+ }),
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
+ }));
1258
1220
  }
1259
- });
1260
- if (options.footnote) {
1261
- const tag = `[^${id}]`;
1262
- view.dispatch({
1263
- changes: {
1264
- from: head,
1265
- insert: tag
1266
- },
1267
- selection: {
1268
- anchor: head + tag.length
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
+ }
1239
+ if (tracked) {
1240
+ const paste = update2.transactions.find((tr) => tr.isUserEvent("input.paste"));
1241
+ if (paste) {
1242
+ let found = -1;
1243
+ paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1244
+ if (text.eq(tracked.text)) {
1245
+ for (let i = update2.transactions.indexOf(paste) + 1; i < update2.transactions.length; i++) {
1246
+ fromB = update2.transactions[i].changes.mapPos(fromB);
1247
+ }
1248
+ found = fromB;
1269
1249
  }
1270
1250
  });
1251
+ if (found > -1) {
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
+ });
1258
+ }
1259
+ }
1260
+ tracked = null;
1271
1261
  }
1272
- return true;
1273
1262
  }
1274
- }
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
+ }
1271
+ })
1272
+ ];
1273
+ };
1274
+ var mapTrackedComment = (comment, changes) => ({
1275
+ id: comment.id,
1276
+ from: changes.mapPos(comment.from, 1),
1277
+ to: changes.mapPos(comment.to, 1)
1278
+ });
1279
+ var restoreCommentEffect = StateEffect2.define({
1280
+ map: mapTrackedComment
1281
+ });
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) {
1275
1289
  return false;
1276
- };
1277
- const bookmarksViewPlugin = ViewPlugin4.fromClass(class BookmarkViewPlugin {
1278
- static {
1279
- // Match markdown footnotes (option).
1280
- // https://www.markdownguide.org/extended-syntax/#footnotes
1281
- this.bookmarkMatcher = new MatchDecorator({
1282
- regexp: /\[\^(\w+)\]/g,
1283
- decoration: (match, view, pos) => {
1284
- const id = match[1];
1285
- return Decoration3.replace({
1286
- id,
1287
- widget: new BookmarkWidget(pos, id)
1288
- });
1290
+ }
1291
+ if (to === view.state.doc.length) {
1292
+ view.dispatch({
1293
+ changes: {
1294
+ from: to,
1295
+ insert: "\n"
1296
+ }
1297
+ });
1298
+ }
1299
+ const cursor = Cursor.getCursorFromRange(view.state, {
1300
+ from,
1301
+ to
1302
+ });
1303
+ if (cursor) {
1304
+ const id = options.onCreate?.({
1305
+ cursor,
1306
+ from,
1307
+ location: view.coordsAtPos(from)
1308
+ });
1309
+ if (id) {
1310
+ view.dispatch({
1311
+ effects: setSelection.of({
1312
+ current: id
1313
+ }),
1314
+ selection: {
1315
+ anchor: to
1289
1316
  }
1290
1317
  });
1318
+ return true;
1291
1319
  }
1292
- constructor(view) {
1293
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
1294
- }
1295
- update(update2) {
1296
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update2, this.bookmarks);
1297
- }
1298
- }, {
1299
- decorations: (instance) => instance.bookmarks,
1300
- provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1301
- return view.plugin(plugin)?.bookmarks || Decoration3.none;
1302
- })
1303
- });
1320
+ }
1321
+ return false;
1322
+ };
1323
+ var comments = (options = {}) => {
1324
+ const { key: shortcut = "meta-'" } = options;
1325
+ const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1304
1326
  return [
1305
- bookmarksViewPlugin,
1306
- commentsStateField,
1307
- highlightDecorations,
1308
- styles3,
1327
+ optionsFacet.of(options),
1328
+ commentsState,
1329
+ commentsDecorations,
1330
+ styles2,
1309
1331
  //
1310
1332
  // Keymap.
1311
1333
  //
1312
1334
  options.onCreate ? keymap3.of([
1313
1335
  {
1314
- key: options?.key ?? "meta-'",
1315
- run: callbackWrapper(createCommentThread)
1336
+ key: shortcut,
1337
+ run: callbackWrapper(createComment)
1316
1338
  }
1317
1339
  ]) : [],
1318
1340
  //
@@ -1328,7 +1350,7 @@ var comments = (options = {}) => {
1328
1350
  above: true,
1329
1351
  create: () => {
1330
1352
  const el = document.createElement("div");
1331
- options.onHover?.(el);
1353
+ options.onHover(el, shortcut);
1332
1354
  return {
1333
1355
  dom: el,
1334
1356
  offset: {
@@ -1341,366 +1363,100 @@ var comments = (options = {}) => {
1341
1363
  }
1342
1364
  return null;
1343
1365
  }, {
1344
- hideOnChange: true,
1366
+ // TODO(burdon): Hide on change triggered immediately?
1367
+ // hideOnChange: true,
1345
1368
  hoverTime: 1e3
1346
1369
  }) : [],
1347
1370
  //
1348
- // Monitor cursor movement and text updates.
1349
- // TODO(burdon): Is there a better (finer grained) way to do this?
1371
+ // Track deleted ranges and update ranges for decorations.
1350
1372
  //
1351
1373
  EditorView5.updateListener.of(({ view, state, changes }) => {
1352
- const cursorConverter3 = view.state.facet(CursorConverter);
1353
- {
1354
- let mod = false;
1355
- const { active, ranges } = state.field(commentsStateField);
1356
- changes.iterChanges((from, to, from2, to2) => {
1357
- ranges.forEach((range) => {
1358
- if (from2 === to2) {
1359
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1360
- if (!newRange || newRange.to - newRange.from === 0) {
1361
- log2.info("deleted comment", {
1362
- thread: range.id
1363
- }, {
1364
- F: __dxlog_file3,
1365
- L: 358,
1366
- S: void 0,
1367
- C: (f, a) => f(...a)
1368
- });
1369
- }
1370
- }
1371
- if (from <= range.to) {
1372
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1373
- Object.assign(range, newRange);
1374
- mod = true;
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);
1375
1382
  }
1376
- });
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
+ })
1377
1397
  });
1378
- if (mod) {
1379
- view.dispatch({
1380
- effects: setCommentState.of({
1381
- active,
1382
- ranges
1383
- })
1384
- });
1385
- }
1386
1398
  }
1387
- {
1388
- const { head } = state.selection.main;
1389
- let min = Infinity;
1390
- const { active, closest, ranges } = state.field(commentsStateField);
1391
- const selected = {
1392
- active: void 0,
1393
- closest: void 0
1394
- };
1395
- ranges.forEach((comment) => {
1396
- const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1397
- if (head >= comment.from && head <= comment.to) {
1398
- selected.active = comment.id;
1399
- }
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));
1400
1415
  if (d < min) {
1401
- selected.closest = comment.id;
1416
+ selection.closest = comment.id;
1402
1417
  min = d;
1403
1418
  }
1404
- });
1405
- if (selected.active !== active || selected.closest !== closest) {
1406
- view.dispatch({
1407
- effects: setSelection.of(selected)
1408
- });
1409
- handleSelect({
1410
- ...selected,
1411
- ranges: ranges.map((range) => ({
1412
- ...range,
1413
- location: view.coordsAtPos(range.from)
1414
- }))
1415
- });
1416
1419
  }
1417
- }
1418
- })
1419
- ];
1420
- };
1421
- var getCursorFromRange = (cursorConverter3, range) => {
1422
- const from = cursorConverter3.toCursor(range.from);
1423
- const to = cursorConverter3.toCursor(range.to, -1);
1424
- return [
1425
- from,
1426
- to
1427
- ].join(":");
1428
- };
1429
- var getRangeFromCursor = (cursorConverter3, cursor) => {
1430
- const parts = cursor.split(":");
1431
- const from = cursorConverter3.fromCursor(parts[0]);
1432
- const to = cursorConverter3.fromCursor(parts[1]);
1433
- return from !== void 0 && to !== void 0 ? {
1434
- from,
1435
- to
1436
- } : void 0;
1437
- };
1438
-
1439
- // packages/ui/react-ui-editor/src/extensions/hr.ts
1440
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin5, WidgetType as WidgetType3 } from "@codemirror/view";
1441
- var styles4 = EditorView6.baseTheme({
1442
- "& .cm-hr": {
1443
- // TODO(burdon): ???
1444
- // Note that block-level decorations should not have vertical margins,
1445
- borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1446
- }
1447
- });
1448
- var HorizontalRuleWidget = class extends WidgetType3 {
1449
- constructor(_pos) {
1450
- super();
1451
- this._pos = _pos;
1452
- }
1453
- eq(other) {
1454
- return this._pos === other._pos;
1455
- }
1456
- toDOM(view) {
1457
- const el = document.createElement("div");
1458
- el.className = "cm-hr";
1459
- return el;
1460
- }
1461
- };
1462
- var placeholderMatcher = new MatchDecorator2({
1463
- regexp: /^---$/g,
1464
- decoration: (match, view, pos) => Decoration4.replace({
1465
- widget: new HorizontalRuleWidget(pos)
1466
- })
1467
- });
1468
- var hr = () => [
1469
- styles4,
1470
- ViewPlugin5.fromClass(class {
1471
- constructor(view) {
1472
- this.rules = placeholderMatcher.createDeco(view);
1473
- }
1474
- update(update2) {
1475
- this.rules = placeholderMatcher.updateDeco(update2, this.rules);
1476
- }
1477
- }, {
1478
- decorations: (instance) => instance.rules,
1479
- // TODO(burdon): Is this really atomic (cursor can move into ---).
1480
- provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1481
- return view.plugin(plugin)?.rules || Decoration4.none;
1482
- })
1483
- })
1484
- ];
1485
-
1486
- // packages/ui/react-ui-editor/src/extensions/image.ts
1487
- import { syntaxTree } from "@codemirror/language";
1488
- import { StateField as StateField3 } from "@codemirror/state";
1489
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1490
- var image = (options = {}) => {
1491
- return StateField3.define({
1492
- create: (state) => {
1493
- return Decoration5.set(buildDecorations(0, state.doc.length, state));
1494
- },
1495
- update: (value, tr) => {
1496
- if (!tr.docChanged && !tr.selection) {
1497
- return value;
1498
- }
1499
- const cursor = tr.state.selection.main.head;
1500
- const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
1501
- let from = Math.min(cursor, oldCursor);
1502
- let to = Math.max(cursor, oldCursor);
1503
- tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
1504
- from = Math.min(from, fromB);
1505
- to = Math.max(to, toB);
1506
- });
1507
- from = tr.state.doc.lineAt(from).from;
1508
- to = tr.state.doc.lineAt(to).to;
1509
- return value.map(tr.changes).update({
1510
- filterFrom: from,
1511
- filterTo: to,
1512
- filter: () => false,
1513
- add: buildDecorations(from, to, tr.state)
1514
1420
  });
1515
- },
1516
- provide: (field) => EditorView7.decorations.from(field)
1517
- });
1518
- };
1519
- var buildDecorations = (from, to, state) => {
1520
- const decorations = [];
1521
- const cursor = state.selection.main.head;
1522
- syntaxTree(state).iterate({
1523
- enter: (node) => {
1524
- if (node.name === "Image") {
1525
- const urlNode = node.node.getChild("URL");
1526
- if (urlNode) {
1527
- const hide = state.readOnly || cursor < node.from || cursor > node.to;
1528
- const url = state.sliceDoc(urlNode.from, urlNode.to);
1529
- decorations.push(Decoration5.replace({
1530
- block: true,
1531
- widget: new ImageWidget(url)
1532
- }).range(hide ? node.from : node.to, node.to));
1533
- }
1534
- }
1535
- },
1536
- from,
1537
- to
1538
- });
1539
- return decorations;
1540
- };
1541
- var ImageWidget = class extends WidgetType4 {
1542
- constructor(_url) {
1543
- super();
1544
- this._url = _url;
1545
- }
1546
- eq(other) {
1547
- return this._url === other._url;
1548
- }
1549
- toDOM(view) {
1550
- const img = document.createElement("img");
1551
- img.setAttribute("src", this._url);
1552
- img.setAttribute("class", "cm-image");
1553
- img.onload = () => img.classList.add("cm-loaded-image");
1554
- return img;
1555
- }
1556
- };
1557
-
1558
- // packages/ui/react-ui-editor/src/extensions/link.ts
1559
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1560
- import { RangeSetBuilder } from "@codemirror/state";
1561
- import { Decoration as Decoration6, WidgetType as WidgetType5, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
1562
- import { tooltipContent } from "@dxos/react-ui-theme";
1563
- var link = (options = {}) => {
1564
- const extensions = [
1565
- ViewPlugin6.fromClass(class {
1566
- constructor(view) {
1567
- this.decorations = buildDecorations2(view, options);
1568
- }
1569
- update(update2) {
1570
- if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1571
- this.decorations = buildDecorations2(update2.view, options);
1572
- }
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
+ });
1573
1433
  }
1574
- }, {
1575
- decorations: (v) => v.decorations
1576
- })
1434
+ }),
1435
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
1577
1436
  ];
1578
- if (options.onHover) {
1579
- extensions.push(
1580
- // https://codemirror.net/examples/tooltip
1581
- // https://codemirror.net/docs/ref/#view.hoverTooltip
1582
- // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1583
- hoverTooltip2((view, pos, side) => {
1584
- const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1585
- let link2 = null;
1586
- for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1587
- link2 = node.name === "Link" ? node : null;
1588
- }
1589
- const url = link2 && link2.getChild("URL");
1590
- if (!url || !link2) {
1591
- return null;
1592
- }
1593
- const urlText = view.state.sliceDoc(url.from, url.to);
1594
- return {
1595
- pos: link2.from,
1596
- end: link2.to,
1597
- above: true,
1598
- create: () => {
1599
- const el = document.createElement("div");
1600
- el.className = tooltipContent({}, "pli-2 plb-1");
1601
- options.onHover(el, urlText);
1602
- return {
1603
- dom: el,
1604
- offset: {
1605
- x: 0,
1606
- y: 4
1607
- }
1608
- };
1609
- }
1610
- };
1611
- })
1612
- );
1613
- }
1614
- return extensions;
1615
- };
1616
- var LinkButton = class extends WidgetType5 {
1617
- constructor(_url, _onAttach) {
1618
- super();
1619
- this._url = _url;
1620
- this._onAttach = _onAttach;
1621
- }
1622
- eq(other) {
1623
- return this._url === other._url;
1624
- }
1625
- toDOM(view) {
1626
- const el = document.createElement("span");
1627
- this._onAttach(el, this._url);
1628
- return el;
1629
- }
1630
1437
  };
1631
- var LinkText = class extends WidgetType5 {
1632
- constructor(_text, _url) {
1633
- super();
1634
- this._text = _text;
1635
- this._url = _url;
1636
- }
1637
- eq(other) {
1638
- return this._url === other._url;
1639
- }
1640
- toDOM(view) {
1641
- const link2 = document.createElement("a");
1642
- link2.setAttribute("class", "cm-link");
1643
- link2.textContent = this._text;
1644
- if (this._url) {
1645
- link2.setAttribute("rel", "noreferrer");
1646
- link2.setAttribute("target", "_blank");
1647
- link2.href = this._url;
1648
- } else {
1649
- link2.onclick = () => {
1650
- view.dispatch({
1651
- selection: {
1652
- anchor: view.posAtDOM(link2)
1653
- }
1654
- });
1655
- };
1438
+ var useComments = (view, comments2 = []) => {
1439
+ useEffect(() => {
1440
+ if (view) {
1441
+ view.dispatch({
1442
+ effects: setComments.of(comments2)
1443
+ });
1656
1444
  }
1657
- return link2;
1658
- }
1659
- };
1660
- var buildDecorations2 = (view, options) => {
1661
- const builder = new RangeSetBuilder();
1662
- const { state } = view;
1663
- const cursor = state.selection.main.head;
1664
- for (const { from, to } of view.visibleRanges) {
1665
- syntaxTree2(state).iterate({
1666
- enter: (node) => {
1667
- if (node.name === "Link") {
1668
- const marks2 = node.node.getChildren("LinkMark");
1669
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1670
- const urlNode = node.node.getChild("URL");
1671
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1672
- if (!url) {
1673
- return false;
1674
- }
1675
- if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
1676
- builder.add(node.from, node.to, Decoration6.replace({
1677
- widget: new LinkText(text, options.link ? url : void 0)
1678
- }));
1679
- }
1680
- if (options.onRender) {
1681
- builder.add(node.to, node.to, Decoration6.replace({
1682
- widget: new LinkButton(url, options.onRender)
1683
- }));
1684
- }
1685
- return false;
1686
- }
1687
- },
1688
- from,
1689
- to
1690
- });
1691
- }
1692
- return builder.finish();
1445
+ }, [
1446
+ view,
1447
+ comments2
1448
+ ]);
1693
1449
  };
1694
1450
 
1695
1451
  // packages/ui/react-ui-editor/src/extensions/listener.ts
1696
- import { EditorView as EditorView8 } from "@codemirror/view";
1452
+ import { EditorView as EditorView6 } from "@codemirror/view";
1697
1453
  var listener = ({ onFocus, onChange }) => {
1698
1454
  const extensions = [];
1699
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1455
+ onFocus && extensions.push(EditorView6.focusChangeEffect.of((_, focused) => {
1700
1456
  onFocus(focused);
1701
1457
  return null;
1702
1458
  }));
1703
- onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1459
+ onChange && extensions.push(EditorView6.updateListener.of((update2) => {
1704
1460
  onChange(update2.state.doc.toString());
1705
1461
  }));
1706
1462
  return extensions;
@@ -1708,14 +1464,15 @@ var listener = ({ onFocus, onChange }) => {
1708
1464
 
1709
1465
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1710
1466
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1711
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1467
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1712
1468
  import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1713
1469
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1714
1470
  import { languages } from "@codemirror/language-data";
1715
1471
  import { searchKeymap } from "@codemirror/search";
1716
1472
  import { EditorState } from "@codemirror/state";
1717
1473
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1718
- 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";
1719
1476
 
1720
1477
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1721
1478
  import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
@@ -1728,7 +1485,6 @@ var markdownTags = {
1728
1485
  CodeText: Tag.define(),
1729
1486
  EmphasisMark: Tag.define(),
1730
1487
  HeaderMark: Tag.define(),
1731
- // HorizontalRule: Tag.define(),
1732
1488
  InlineCode: Tag.define(),
1733
1489
  LinkLabel: Tag.define(),
1734
1490
  LinkReference: Tag.define(),
@@ -1813,7 +1569,7 @@ var markdownHighlightStyle = (readonly) => {
1813
1569
  markdownTags.QuoteMark,
1814
1570
  markdownTags.EmphasisMark
1815
1571
  ],
1816
- class: readonly ? "hidden" : mark
1572
+ class: mark
1817
1573
  },
1818
1574
  // E.g., code block language (after ```).
1819
1575
  {
@@ -1821,7 +1577,7 @@ var markdownHighlightStyle = (readonly) => {
1821
1577
  tags.function(tags.variableName),
1822
1578
  tags.labelName
1823
1579
  ],
1824
- class: readonly ? "hidden" : codeMark
1580
+ class: codeMark
1825
1581
  },
1826
1582
  {
1827
1583
  tag: [
@@ -1832,27 +1588,27 @@ var markdownHighlightStyle = (readonly) => {
1832
1588
  // Headings.
1833
1589
  {
1834
1590
  tag: tags.heading1,
1835
- class: heading(1, readonly)
1591
+ class: heading(1)
1836
1592
  },
1837
1593
  {
1838
1594
  tag: tags.heading2,
1839
- class: heading(2, readonly)
1595
+ class: heading(2)
1840
1596
  },
1841
1597
  {
1842
1598
  tag: tags.heading3,
1843
- class: heading(3, readonly)
1599
+ class: heading(3)
1844
1600
  },
1845
1601
  {
1846
1602
  tag: tags.heading4,
1847
- class: heading(4, readonly)
1603
+ class: heading(4)
1848
1604
  },
1849
1605
  {
1850
1606
  tag: tags.heading5,
1851
- class: heading(5, readonly)
1607
+ class: heading(5)
1852
1608
  },
1853
1609
  {
1854
1610
  tag: tags.heading6,
1855
- class: heading(6, readonly)
1611
+ class: heading(6)
1856
1612
  },
1857
1613
  // Emphasis.
1858
1614
  {
@@ -1878,7 +1634,7 @@ var markdownHighlightStyle = (readonly) => {
1878
1634
  // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1879
1635
  // when a language is specified. In this case, the syntax highlighting extensions will colorize
1880
1636
  // the code, but all other CSS properties will be inherited.
1881
- // 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.
1882
1638
  {
1883
1639
  tag: [
1884
1640
  markdownTags.CodeText,
@@ -1892,12 +1648,6 @@ var markdownHighlightStyle = (readonly) => {
1892
1648
  ],
1893
1649
  class: blockquote
1894
1650
  },
1895
- // TODO(burdon): Replace with extension.
1896
- // {
1897
- // tag: [markdownTags.HorizontalRule],
1898
- // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1899
- // },
1900
- // TODO(burdon): Only if being edited.
1901
1651
  {
1902
1652
  tag: [
1903
1653
  markdownTags.TableCell
@@ -1913,19 +1663,19 @@ var markdownHighlightStyle = (readonly) => {
1913
1663
  };
1914
1664
 
1915
1665
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1916
- var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1666
+ var createMarkdownExtensions = ({ themeMode, placeholder: _placeholder, lineWrapping = true } = {}) => {
1917
1667
  return [
1668
+ lineWrapping && EditorView7.lineWrapping,
1918
1669
  EditorState.allowMultipleSelections.of(true),
1919
1670
  EditorState.tabSize.of(2),
1920
- EditorView9.lineWrapping,
1921
- customKeymap(),
1671
+ // https://github.com/codemirror/basic-setup
1922
1672
  bracketMatching2(),
1923
1673
  closeBrackets2(),
1924
1674
  crosshairCursor(),
1925
1675
  dropCursor(),
1926
1676
  drawSelection2(),
1927
- highlightActiveLine(),
1928
- history(),
1677
+ highlightActiveLine2(),
1678
+ history2(),
1929
1679
  indentOnInput(),
1930
1680
  _placeholder && placeholder2(_placeholder),
1931
1681
  // Main extension.
@@ -1949,58 +1699,1552 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1949
1699
  // https://github.com/codemirror/theme-one-dark
1950
1700
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1951
1701
  // Custom styles.
1952
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1953
- ].filter(Boolean);
1954
- };
1955
- var customKeymap = () => keymap4.of([
1956
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1957
- indentWithTab,
1958
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1959
- ...standardKeymap,
1960
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1961
- ...historyKeymap,
1962
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1963
- ...closeBracketsKeymap,
1964
- // https://codemirror.net/docs/ref/#search.searchKeymap
1965
- ...searchKeymap
1966
- ]);
1967
-
1968
- // packages/ui/react-ui-editor/src/extensions/mention.ts
1969
- import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1970
- var mention = ({ onSearch }) => {
1971
- return autocompletion2({
1972
- closeOnBlur: false,
1973
- override: [
1974
- (context) => {
1975
- const match = context.matchBefore(/@(\w+)?/);
1976
- if (!match || match.from === match.to && !context.explicit) {
1977
- return null;
1978
- }
1979
- console.log(match);
1980
- return {
1981
- from: match.from,
1982
- options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1983
- label: `@${value}`
1984
- }))
1985
- };
1986
- }
1987
- ]
1988
- });
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);
1989
1716
  };
1990
1717
 
1991
- // packages/ui/react-ui-editor/src/extensions/table.ts
1992
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1993
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1994
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType6 } from "@codemirror/view";
1995
- var table = (options = {}) => {
1996
- return StateField4.define({
1997
- create: (state) => update(state, options),
1998
- update: (_, tr) => update(tr.state, options),
1999
- provide: (field) => EditorView10.decorations.from(field)
2000
- });
2001
- };
2002
- var update = (state, options) => {
2003
- const builder = new RangeSetBuilder2();
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
+ ];
1771
+ };
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;
1790
+ const cursor = state.selection.main.head;
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();
2004
3248
  const cursor = state.selection.main.head;
2005
3249
  const tables = [];
2006
3250
  const getTable = () => tables[tables.length - 1];
@@ -2008,7 +3252,7 @@ var update = (state, options) => {
2008
3252
  const table2 = getTable();
2009
3253
  return table2.rows?.[table2.rows.length - 1];
2010
3254
  };
2011
- syntaxTree3(state).iterate({
3255
+ syntaxTree7(state).iterate({
2012
3256
  enter: (node) => {
2013
3257
  switch (node.name) {
2014
3258
  case "Table": {
@@ -2040,10 +3284,10 @@ var update = (state, options) => {
2040
3284
  });
2041
3285
  tables.forEach((table2) => {
2042
3286
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
2043
- hide && builder.add(table2.from, table2.to, Decoration7.replace({
3287
+ hide && builder.add(table2.from, table2.to, Decoration9.replace({
2044
3288
  widget: new TableWidget(table2)
2045
3289
  }));
2046
- builder.add(table2.from, table2.to, Decoration7.mark({
3290
+ builder.add(table2.from, table2.to, Decoration9.mark({
2047
3291
  class: "cm-table"
2048
3292
  }));
2049
3293
  });
@@ -2086,92 +3330,139 @@ var TableWidget = class extends WidgetType6 {
2086
3330
  }
2087
3331
  };
2088
3332
 
2089
- // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2090
- import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType7, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin7 } from "@codemirror/view";
2091
- var styles5 = EditorView11.baseTheme({
2092
- "& .cm-task-item": {
2093
- paddingLeft: "4px",
2094
- paddingRight: "8px"
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"
2095
3344
  }
2096
3345
  });
2097
3346
  var CheckboxWidget = class extends WidgetType7 {
2098
- constructor(_checked, _indent, _onCheck) {
3347
+ constructor(_checked) {
2099
3348
  super();
2100
3349
  this._checked = _checked;
2101
- this._indent = _indent;
2102
- this._onCheck = _onCheck;
2103
3350
  }
2104
3351
  eq(other) {
2105
- return this._checked === other._checked && this._indent === other._indent;
3352
+ return this._checked === other._checked;
2106
3353
  }
2107
3354
  toDOM(view) {
2108
- const wrap = document.createElement("span");
2109
- wrap.className = "cm-task-item";
2110
- wrap.setAttribute("aria-hidden", "true");
2111
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
2112
- const input = wrap.appendChild(document.createElement("input"));
3355
+ const input = document.createElement("input");
3356
+ input.className = "cm-task-checkbox";
2113
3357
  input.type = "checkbox";
2114
3358
  input.checked = this._checked;
2115
- if (!this._onCheck) {
3359
+ if (view.state.readOnly) {
2116
3360
  input.setAttribute("disabled", "true");
2117
- }
2118
- if (this._onCheck) {
2119
- input.onchange = (event) => {
2120
- this._onCheck?.(event.target.checked);
2121
- return true;
3361
+ } else {
3362
+ input.onmousedown = (event) => {
3363
+ const pos = view.posAtDOM(input);
3364
+ const text = view.state.sliceDoc(pos, pos + 3);
3365
+ if (text === (this._checked ? "[x]" : "[ ]")) {
3366
+ view.dispatch({
3367
+ changes: {
3368
+ from: pos + 1,
3369
+ to: pos + 2,
3370
+ insert: this._checked ? " " : "x"
3371
+ }
3372
+ });
3373
+ event.preventDefault();
3374
+ }
2122
3375
  };
2123
3376
  }
2124
- return wrap;
3377
+ return input;
2125
3378
  }
2126
3379
  ignoreEvent() {
2127
3380
  return false;
2128
3381
  }
2129
3382
  };
3383
+ var checkedDecoration = Decoration10.replace({
3384
+ widget: new CheckboxWidget(true)
3385
+ });
3386
+ var uncheckedDecoration = Decoration10.replace({
3387
+ widget: new CheckboxWidget(false)
3388
+ });
3389
+ var buildDecorations6 = (view) => {
3390
+ const builder = new RangeSetBuilder7();
3391
+ const { state } = view;
3392
+ const cursor = state.selection.main.head;
3393
+ for (const { from, to } of view.visibleRanges) {
3394
+ syntaxTree8(state).iterate({
3395
+ enter: (node) => {
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
+ }
3404
+ }
3405
+ },
3406
+ from,
3407
+ to
3408
+ });
3409
+ }
3410
+ return builder.finish();
3411
+ };
2130
3412
  var tasklist = (options = {}) => {
2131
- const taskMatcher = new MatchDecorator3({
2132
- regexp: /^(\s*)- \[([ xX])\]\s/g,
2133
- decoration: (match, view, pos) => {
2134
- const indent = Math.floor(match[1].length / 2);
2135
- const checked = match[2] === "x" || match[2] === "X";
2136
- return Decoration8.replace({
2137
- widget: new CheckboxWidget(checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2138
- const idx = pos + match[0].indexOf("[") + 1;
2139
- view.dispatch({
2140
- changes: {
2141
- from: idx,
2142
- to: idx + 1,
2143
- insert: checked2 ? "x" : " "
2144
- },
2145
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
2146
- selection: {
2147
- anchor: idx + 3
2148
- }
2149
- });
2150
- })
2151
- });
2152
- }
2153
- });
2154
- const tasks = ViewPlugin7.fromClass(class {
2155
- constructor(view) {
2156
- this.tasks = taskMatcher.createDeco(view);
2157
- }
2158
- update(update2) {
2159
- this.tasks = taskMatcher.updateDeco(update2, this.tasks);
2160
- }
2161
- }, {
2162
- decorations: (value) => value.tasks,
2163
- provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2164
- return view.plugin(plugin)?.tasks || Decoration8.none;
2165
- })
2166
- });
2167
3413
  return [
2168
- tasks,
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
+ }),
2169
3426
  styles5
2170
3427
  ];
2171
3428
  };
2172
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
+ };
3463
+
2173
3464
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
2174
- import { keymap as keymap5 } from "@codemirror/view";
3465
+ import { keymap as keymap6 } from "@codemirror/view";
2175
3466
  var defaultItems = [
2176
3467
  "hello world!",
2177
3468
  "this is a test.",
@@ -2181,7 +3472,7 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2181
3472
  let t;
2182
3473
  let idx = 0;
2183
3474
  return [
2184
- keymap5.of([
3475
+ keymap6.of([
2185
3476
  {
2186
3477
  // Reset.
2187
3478
  key: "alt-meta-'",
@@ -2227,27 +3518,6 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2227
3518
  ];
2228
3519
  };
2229
3520
 
2230
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2231
- import { yCollab } from "y-codemirror.next";
2232
-
2233
- // packages/ui/react-ui-editor/src/extensions/yjs/cursor.ts
2234
- import * as Y from "yjs";
2235
- import { arrayToString, stringToArray } from "@dxos/util";
2236
- var cursorConverter2 = (text) => ({
2237
- toCursor: (index, assoc) => {
2238
- return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
2239
- },
2240
- fromCursor: (cursor) => {
2241
- return Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc)?.index ?? 0;
2242
- }
2243
- });
2244
-
2245
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2246
- var yjs = (content, awareness2) => [
2247
- CursorConverter.of(cursorConverter2(content)),
2248
- yCollab(content, awareness2)
2249
- ];
2250
-
2251
3521
  // packages/ui/react-ui-editor/src/themes/default.ts
2252
3522
  import get3 from "lodash.get";
2253
3523
  var defaultTheme = {
@@ -2279,8 +3549,9 @@ var defaultTheme = {
2279
3549
  outline: "none"
2280
3550
  },
2281
3551
  ".cm-scroller": {
2282
- overflow: "visible",
2283
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
3552
+ // overflow: 'visible',
3553
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(","),
3554
+ lineHeight: 1.4
2284
3555
  },
2285
3556
  ".cm-content": {
2286
3557
  padding: 0,
@@ -2288,11 +3559,11 @@ var defaultTheme = {
2288
3559
  fontSize: "16px"
2289
3560
  },
2290
3561
  "&light .cm-content": {
2291
- color: get3(tokens, "extend.colors.neutral.900", "black"),
3562
+ color: get3(tokens, "extend.semanticColors.base.fg.light", "black"),
2292
3563
  caretColor: "black"
2293
3564
  },
2294
3565
  "&dark .cm-content": {
2295
- color: get3(tokens, "extend.colors.neutral.100", "white"),
3566
+ color: get3(tokens, "extend.semanticColors.base.fg.dark", "white"),
2296
3567
  caretColor: "white"
2297
3568
  },
2298
3569
  //
@@ -2304,8 +3575,11 @@ var defaultTheme = {
2304
3575
  "&dark .cm-cursor, &dark .cm-dropCursor": {
2305
3576
  borderLeft: "2px solid white"
2306
3577
  },
2307
- ".cm-placeholder": {
2308
- 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)")
2309
3583
  },
2310
3584
  //
2311
3585
  // line
@@ -2314,7 +3588,7 @@ var defaultTheme = {
2314
3588
  paddingInline: 0
2315
3589
  },
2316
3590
  ".cm-activeLine": {
2317
- background: "inherit"
3591
+ background: "transparent"
2318
3592
  },
2319
3593
  //
2320
3594
  // Selection
@@ -2341,33 +3615,6 @@ var defaultTheme = {
2341
3615
  backgroundColor: get3(tokens, "extend.colors.yellow.700")
2342
3616
  },
2343
3617
  //
2344
- // collaboration
2345
- // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2346
- //
2347
- "&light .cm-ySelection, &light .cm-yLineSelection": {
2348
- mixBlendMode: "multiply"
2349
- },
2350
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2351
- mixBlendMode: "screen"
2352
- },
2353
- ".cm-ySelectionInfo": {
2354
- padding: "2px 4px",
2355
- marginBlockStart: "-4px"
2356
- },
2357
- ".cm-ySelection, & .cm-selectionMatch": {
2358
- paddingBlockStart: ".15em",
2359
- paddingBlockEnd: ".15em"
2360
- },
2361
- ".cm-ySelectionCaret": {
2362
- display: "inline-block",
2363
- insetBlockStart: ".1em",
2364
- blockSize: "1.4em",
2365
- verticalAlign: "top"
2366
- },
2367
- ".cm-yLineSelection": {
2368
- margin: 0
2369
- },
2370
- //
2371
3618
  // link
2372
3619
  //
2373
3620
  ".cm-link": {
@@ -2404,13 +3651,6 @@ var defaultTheme = {
2404
3651
  textDecoration: "none"
2405
3652
  },
2406
3653
  //
2407
- // widgets
2408
- //
2409
- ".cm-widgetBuffer": {
2410
- display: "none",
2411
- height: 0
2412
- },
2413
- //
2414
3654
  // table
2415
3655
  //
2416
3656
  ".cm-table *": {
@@ -2441,27 +3681,17 @@ var defaultTheme = {
2441
3681
  },
2442
3682
  //
2443
3683
  // font size
2444
- // TODO(thure): This appears to be the best or only way to set selection caret heights, but it's far more verbose than it needs to be.
3684
+ // TODO(thure): This appears to be the best or only way to set selection caret heights,
3685
+ // but it's far more verbose than it needs to be.
2445
3686
  //
2446
- ...Object.keys(get3(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2447
- const height = get3(tokens, [
2448
- "extend",
2449
- "fontSize",
2450
- fontSize,
2451
- 1,
2452
- "lineHeight"
2453
- ]);
2454
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2455
- height
2456
- };
2457
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2458
- height
2459
- };
2460
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2461
- height
2462
- };
2463
- return acc;
2464
- }, {}),
3687
+ // ...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
3688
+ // const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
3689
+ //
3690
+ // acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
3691
+ // acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
3692
+ // acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
3693
+ // return acc;
3694
+ // }, {}),
2465
3695
  /**
2466
3696
  * Panels
2467
3697
  * TODO(burdon): Needs styling attention (esp. dark mode).
@@ -2512,7 +3742,9 @@ var textTheme = {
2512
3742
  }
2513
3743
  };
2514
3744
  var markdownTheme = {
2515
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
3745
+ ".cm-scroller": {
3746
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
3747
+ },
2516
3748
  ".cm-placeholder": {
2517
3749
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2518
3750
  }
@@ -2527,95 +3759,95 @@ var codeTheme = {
2527
3759
  };
2528
3760
 
2529
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";
2530
3763
  var EditorModes = [
2531
3764
  "default",
2532
3765
  "vim"
2533
3766
  ];
2534
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
3767
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2535
3768
  const { themeMode } = useThemeContext();
2536
- const tabsterDOMAttribute = useFocusableGroup({
2537
- tabBehavior: "limited"
2538
- });
2539
- const [root, setRoot] = useState(null);
2540
- const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
2541
- useImperativeHandle(forwardedRef, () => ({
2542
- root,
2543
- state,
3769
+ const rootRef = useRef(null);
3770
+ const [view, setView] = useState2(null);
3771
+ useImperativeHandle(forwardedRef, () => view, [
2544
3772
  view
2545
- }), [
2546
- view,
2547
- state,
2548
- root
2549
- ]);
2550
- const { awareness: awareness2, peer } = model;
2551
- useEffect(() => {
2552
- if (awareness2 && peer) {
2553
- awareness2.setLocalStateField("user", {
2554
- name: peer.name ?? generateName(peer.id),
2555
- color: getColorForValue({
2556
- value: peer.id,
2557
- type: "color"
2558
- }),
2559
- colorLight: getColorForValue({
2560
- value: peer.id,
2561
- themeMode,
2562
- type: "highlight"
2563
- })
2564
- });
2565
- }
2566
- }, [
2567
- awareness2,
2568
- peer,
2569
- themeMode
2570
3773
  ]);
2571
- useEffect(() => {
2572
- if (view && comments2?.length) {
2573
- view.dispatch({
2574
- effects: setCommentRange.of({
2575
- model,
2576
- comments: comments2
2577
- })
2578
- });
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
+ }
3784
+ view?.focus();
2579
3785
  }
2580
3786
  }, [
2581
3787
  view,
2582
- comments2
3788
+ autoFocus
2583
3789
  ]);
2584
- useEffect(() => {
2585
- if (!root) {
3790
+ useEffect2(() => {
3791
+ if (!model || !rootRef.current) {
2586
3792
  return;
2587
3793
  }
2588
- const state2 = EditorState2.create({
3794
+ const state = EditorState2.create({
2589
3795
  doc: model.text(),
3796
+ selection,
2590
3797
  extensions: [
2591
- readonly && EditorState2.readOnly.of(readonly),
2592
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2593
- 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
+ }),
2594
3807
  // Theme.
2595
- // TODO(burdon): Make theme configurable.
2596
- EditorView12.baseTheme(defaultTheme),
2597
- EditorView12.theme(slots?.editor?.theme ?? {}),
2598
- 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),
2599
3821
  // Storage and replication.
3822
+ // NOTE: This must come before user extensions.
2600
3823
  model.extension,
3824
+ // TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
3825
+ editorMode === "vim" && vim(),
2601
3826
  // Custom.
2602
3827
  ...extensions
2603
- ].filter(Boolean)
3828
+ ].filter(isNotFalsy3)
2604
3829
  });
2605
- view?.destroy();
2606
- setEditor({
2607
- state: state2,
2608
- view: new EditorView12({
2609
- state: state2,
2610
- parent: root
2611
- })
3830
+ const newView = new EditorView14({
3831
+ state,
3832
+ parent: rootRef.current,
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
+ }
2612
3842
  });
3843
+ view?.destroy();
3844
+ setView(newView);
2613
3845
  return () => {
2614
- view?.destroy();
2615
- setEditor(void 0);
3846
+ newView?.destroy();
3847
+ setView(null);
2616
3848
  };
2617
3849
  }, [
2618
- root,
3850
+ rootRef,
2619
3851
  model,
2620
3852
  readonly,
2621
3853
  editorMode,
@@ -2625,11 +3857,13 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2625
3857
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2626
3858
  switch (key) {
2627
3859
  case "Enter": {
2628
- view?.contentDOM.focus();
3860
+ view?.focus();
2629
3861
  break;
2630
3862
  }
2631
3863
  case "Escape": {
2632
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
3864
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
3865
+ view?.focus();
3866
+ }
2633
3867
  break;
2634
3868
  }
2635
3869
  }
@@ -2639,83 +3873,277 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2639
3873
  ]);
2640
3874
  return /* @__PURE__ */ React.createElement("div", {
2641
3875
  key: model.id,
2642
- ref: setRoot,
3876
+ role: "none",
2643
3877
  tabIndex: 0,
2644
- ...slots?.root,
2645
- ...editorMode !== "vim" && tabsterDOMAttribute,
2646
- onKeyUp: handleKeyUp
3878
+ ...slots.root,
3879
+ // {...(editorMode !== 'vim' && tabsterDOMAttribute)}
3880
+ onKeyUp: handleKeyUp,
3881
+ ref: rootRef
2647
3882
  });
2648
3883
  });
2649
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
3884
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, lineWrapping, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
2650
3885
  const { themeMode } = useThemeContext();
2651
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
3886
+ const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2652
3887
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2653
3888
  ref: forwardedRef,
2654
3889
  readonly,
2655
3890
  extensions: [
2656
- basicBundle({
2657
- readonly,
3891
+ createBasicBundle({
2658
3892
  themeMode,
2659
- placeholder: slots?.editor?.placeholder
3893
+ placeholder: placeholder3,
3894
+ lineWrapping
2660
3895
  }),
2661
3896
  ...extensions
2662
3897
  ],
2663
- slots,
3898
+ theme,
3899
+ slots: updatedSlots,
2664
3900
  ...props
2665
3901
  });
2666
3902
  });
2667
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
3903
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
2668
3904
  const { themeMode } = useThemeContext();
2669
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
3905
+ const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2670
3906
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2671
3907
  ref: forwardedRef,
2672
3908
  readonly,
2673
3909
  extensions: [
2674
- markdownBundle({
2675
- readonly,
3910
+ createMarkdownExtensions({
2676
3911
  themeMode,
2677
- placeholder: slots?.editor?.placeholder
3912
+ placeholder: placeholder3
2678
3913
  }),
2679
3914
  ...extensions
2680
3915
  ],
2681
- slots,
3916
+ theme,
3917
+ slots: updatedSlots,
2682
3918
  ...props
2683
3919
  });
2684
3920
  });
2685
3921
  var defaultSlots = {
2686
3922
  root: {
2687
- className: mx3("p-2", inputSurface)
3923
+ className: mx3("grow", focusRing)
3924
+ },
3925
+ editor: {
3926
+ className: "bs-full"
2688
3927
  }
2689
3928
  };
2690
3929
  var defaultTextSlots = {
2691
- ...defaultSlots,
2692
- editor: {
2693
- theme: textTheme
2694
- }
3930
+ ...defaultSlots
2695
3931
  };
2696
3932
  var defaultMarkdownSlots = {
2697
- ...defaultSlots,
2698
- editor: {
2699
- theme: markdownTheme
2700
- }
3933
+ ...defaultSlots
2701
3934
  };
2702
3935
 
2703
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2704
- import get4 from "lodash.get";
2705
- import { getRawDoc } from "@dxos/echo-schema";
2706
- 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
+ };
2707
4135
 
2708
4136
  // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2709
4137
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2710
4138
  import { Context as Context2 } from "@dxos/context";
2711
- import { invariant as invariant3 } from "@dxos/invariant";
4139
+ import { invariant as invariant2 } from "@dxos/invariant";
2712
4140
  import { log as log3 } from "@dxos/log";
2713
4141
  var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2714
4142
  var DEBOUNCE_INTERVAL = 100;
2715
4143
  var SpaceAwarenessProvider = class {
2716
4144
  constructor(params) {
2717
- this.remoteStateChange = new Event2();
2718
4145
  this._remoteStates = /* @__PURE__ */ new Map();
4146
+ this.remoteStateChange = new Event2();
2719
4147
  this._space = params.space;
2720
4148
  this._channel = params.channel;
2721
4149
  this._peerId = params.peerId;
@@ -2732,7 +4160,7 @@ var SpaceAwarenessProvider = class {
2732
4160
  await sleep(DEBOUNCE_INTERVAL);
2733
4161
  }
2734
4162
  });
2735
- const unsubscribe = this._space.listen(this._channel, (message) => {
4163
+ this._ctx.onDispose(this._space.listen(this._channel, (message) => {
2736
4164
  switch (message.payload.kind) {
2737
4165
  case "query": {
2738
4166
  this._handleQueryMessage();
@@ -2743,8 +4171,7 @@ var SpaceAwarenessProvider = class {
2743
4171
  break;
2744
4172
  }
2745
4173
  }
2746
- });
2747
- this._ctx.onDispose(unsubscribe);
4174
+ }));
2748
4175
  void this._space.postMessage(this._channel, {
2749
4176
  kind: "query"
2750
4177
  }).catch((err) => {
@@ -2752,7 +4179,7 @@ var SpaceAwarenessProvider = class {
2752
4179
  err
2753
4180
  }, {
2754
4181
  F: __dxlog_file4,
2755
- L: 85,
4182
+ L: 89,
2756
4183
  S: this,
2757
4184
  C: (f, a) => f(...a)
2758
4185
  });
@@ -2763,10 +4190,13 @@ var SpaceAwarenessProvider = class {
2763
4190
  this._ctx = void 0;
2764
4191
  this._postTask = void 0;
2765
4192
  }
4193
+ getRemoteStates() {
4194
+ return Array.from(this._remoteStates.values());
4195
+ }
2766
4196
  update(position) {
2767
- invariant3(this._postTask, void 0, {
4197
+ invariant2(this._postTask, void 0, {
2768
4198
  F: __dxlog_file4,
2769
- L: 96,
4199
+ L: 104,
2770
4200
  S: this,
2771
4201
  A: [
2772
4202
  "this._postTask",
@@ -2780,13 +4210,10 @@ var SpaceAwarenessProvider = class {
2780
4210
  };
2781
4211
  this._postTask.schedule();
2782
4212
  }
2783
- getRemoteStates() {
2784
- return Array.from(this._remoteStates.values());
2785
- }
2786
4213
  _handleQueryMessage() {
2787
- invariant3(this._postTask, void 0, {
4214
+ invariant2(this._postTask, void 0, {
2788
4215
  F: __dxlog_file4,
2789
- L: 111,
4216
+ L: 115,
2790
4217
  S: this,
2791
4218
  A: [
2792
4219
  "this._postTask",
@@ -2796,9 +4223,9 @@ var SpaceAwarenessProvider = class {
2796
4223
  this._postTask.schedule();
2797
4224
  }
2798
4225
  _handlePostMessage(message) {
2799
- invariant3(message.kind === "post", void 0, {
4226
+ invariant2(message.kind === "post", void 0, {
2800
4227
  F: __dxlog_file4,
2801
- L: 116,
4228
+ L: 120,
2802
4229
  S: this,
2803
4230
  A: [
2804
4231
  "message.kind === 'post'",
@@ -2810,229 +4237,247 @@ var SpaceAwarenessProvider = class {
2810
4237
  }
2811
4238
  };
2812
4239
 
2813
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
4240
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
2814
4241
  import { StateField as StateField5 } from "@codemirror/state";
2815
- import { useEffect as useEffect2, useState as useState2 } from "react";
2816
- import { isAutomergeObject } from "@dxos/react-client/echo";
4242
+ var modelState = StateField5.define({
4243
+ create: () => void 0,
4244
+ update: (model) => model
4245
+ });
2817
4246
 
2818
- // packages/ui/react-ui-editor/src/hooks/yjs.ts
2819
- import * as decoding from "lib0/decoding";
2820
- import * as encoding from "lib0/encoding";
2821
- import { Observable } from "lib0/observable";
2822
- import * as YP from "y-protocols/awareness";
2823
- import * as Y2 from "yjs";
2824
- import { invariant as invariant4 } from "@dxos/invariant";
2825
- import { log as log4 } from "@dxos/log";
2826
- import { arrayToString as arrayToString2, stringToArray as stringToArray2 } from "@dxos/util";
2827
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2828
- var createYjsModel = ({ identity, space, text }) => {
2829
- invariant4(text?.doc && text?.content, void 0, {
2830
- F: __dxlog_file5,
2831
- L: 23,
2832
- S: void 0,
2833
- A: [
2834
- "text?.doc && text?.content",
2835
- ""
2836
- ]
2837
- });
2838
- const provider = space ? new YAwarenessProvider({
2839
- space,
2840
- doc: text.doc,
2841
- channel: `yjs.awareness.${text.id}`
2842
- }) : void 0;
2843
- const model = {
2844
- id: text.doc.guid,
2845
- content: text.content,
2846
- text: () => text.content.toString(),
2847
- // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
2848
- // TODO(dmaretskyi): Refactor as cursor-converter.
2849
- getCursorFromRange: (range) => {
2850
- const from = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.from));
2851
- const to = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.to, -1));
2852
- return [
2853
- arrayToString2(from),
2854
- arrayToString2(to)
2855
- ].join(":");
2856
- },
2857
- getRangeFromCursor: (cursor) => {
2858
- invariant4(text.doc, void 0, {
2859
- F: __dxlog_file5,
2860
- L: 40,
2861
- S: void 0,
2862
- A: [
2863
- "text.doc",
2864
- ""
2865
- ]
2866
- });
2867
- const parts = cursor.split(":");
2868
- const from = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[0])), text.doc);
2869
- const to = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[1])), text.doc);
2870
- return from && to ? {
2871
- from: from.index,
2872
- to: to.index
2873
- } : void 0;
2874
- },
2875
- extension: [
2876
- modelState.init(() => model),
2877
- yjs(text.content, provider?.awareness)
2878
- ],
2879
- awareness: provider?.awareness,
2880
- peer: identity ? {
2881
- id: identity.identityKey.toHex(),
2882
- name: identity.profile?.displayName
2883
- } : 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
+ });
2884
4292
  };
2885
- return model;
2886
4293
  };
2887
- var messageAwareness = 1;
2888
- var messageQueryAwareness = 3;
2889
- var YAwarenessProvider = class extends Observable {
2890
- constructor({ space, doc, channel, awareness: awareness2 }) {
2891
- super();
2892
- this._space = space;
2893
- this._awareness = awareness2 ?? new YP.Awareness(doc);
2894
- this._channel = channel;
2895
- this._clientId = doc.clientID;
2896
- this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
2897
- this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
2898
- if (typeof window !== "undefined") {
2899
- window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
2900
- } else if (typeof process !== "undefined") {
2901
- process.on("exit", this._handleBeforeUnload.bind(this));
2902
- }
2903
- const encoderAwarenessQuery = encoding.createEncoder();
2904
- encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
2905
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
2906
- const encoderAwarenessState = encoding.createEncoder();
2907
- encoding.writeVarUint(encoderAwarenessState, messageAwareness);
2908
- encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [
2909
- this._clientId
2910
- ]));
2911
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
2912
- }
2913
- get awareness() {
2914
- return this._awareness;
2915
- }
2916
- _handleAwarenessUpdate({ added, updated, removed }, origin) {
2917
- log4("awareness update", {
2918
- added,
2919
- updated,
2920
- removed,
2921
- origin
2922
- }, {
2923
- F: __dxlog_file5,
2924
- L: 122,
2925
- S: this,
2926
- C: (f, a) => f(...a)
2927
- });
2928
- const changedClients = added.concat(updated).concat(removed);
2929
- const encoderAwareness = encoding.createEncoder();
2930
- encoding.writeVarUint(encoderAwareness, messageAwareness);
2931
- encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
2932
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2933
- }
2934
- _handleSpaceMessage({ payload }) {
2935
- log4("space message", payload, {
2936
- F: __dxlog_file5,
2937
- L: 131,
2938
- S: this,
2939
- C: (f, a) => f(...a)
2940
- });
2941
- const data = new Uint8Array(Array.from(Object.values(payload)));
2942
- const encoder = this._readMessage(data);
2943
- if (encoder) {
2944
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
2945
- }
2946
- }
2947
- _readMessage(message) {
2948
- const decoder = decoding.createDecoder(message);
2949
- const encoder = encoding.createEncoder();
2950
- const messageType = decoding.readVarUint(decoder);
2951
- let sendReply = false;
2952
- switch (messageType) {
2953
- case messageAwareness: {
2954
- YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2955
- 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);
2956
4364
  }
2957
- case messageQueryAwareness: {
2958
- encoding.writeVarUint(encoder, messageAwareness);
2959
- encoding.writeVarUint8Array(encoder, YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2960
- sendReply = true;
2961
- 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
+ };
2962
4376
  }
2963
- default: {
2964
- console.error("Invalid message:", messageType);
2965
- return encoder;
4377
+ if (autoFocus) {
4378
+ view.focus();
2966
4379
  }
2967
4380
  }
2968
- if (!sendReply) {
2969
- return null;
2970
- }
2971
- return encoder;
2972
- }
2973
- _handleBeforeUnload() {
2974
- YP.removeAwarenessStates(this._awareness, [
2975
- this._clientId
2976
- ], "window unload");
2977
- }
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);
2978
4412
  };
2979
4413
 
2980
4414
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2981
- var modelState = StateField5.define({
2982
- create: () => void 0,
2983
- update: (model) => model
2984
- });
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";
2985
4422
  var useTextModel = (props) => {
2986
4423
  const { identity, space, text } = props;
2987
- const [model, setModel] = useState2(() => createModel(props));
2988
- useEffect2(() => setModel(createModel(props)), [
4424
+ const [model, setModel] = useState5();
4425
+ useEffect4(() => setModel(createModel(props)), [
2989
4426
  identity,
2990
4427
  space,
2991
4428
  text
2992
4429
  ]);
2993
4430
  return model;
2994
4431
  };
2995
- var createModel = (props) => {
2996
- const { text } = props;
2997
- if (isAutomergeObject(text)) {
2998
- return createAutomergeModel(props);
2999
- } else if (text?.doc) {
3000
- return createYjsModel(props);
3001
- } else {
3002
- return void 0;
3003
- }
4432
+ var useInMemoryTextModel = ({ id, defaultContent }) => {
4433
+ const [content, setContent] = useState5(defaultContent ?? "");
4434
+ return {
4435
+ id,
4436
+ content,
4437
+ setContent,
4438
+ text: () => content
4439
+ };
3004
4440
  };
3005
-
3006
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
3007
- 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
+ });
3008
4451
  const obj = text;
3009
4452
  const doc = getRawDoc(obj, [
3010
4453
  obj.field
3011
4454
  ]);
3012
- const awarenessProvider = space && new SpaceAwarenessProvider({
4455
+ const awarenessProvider2 = space && new SpaceAwarenessProvider({
3013
4456
  space,
3014
4457
  channel: `automerge.awareness.${obj.id}`,
3015
4458
  info: {
3016
- displayName: identity?.profile?.displayName ?? "",
4459
+ displayName: identity ? identity.profile?.displayName ?? generateName(identity.identityKey.toHex()) : void 0,
3017
4460
  color: cursorColor.color,
3018
4461
  lightColor: cursorColor.light
3019
4462
  },
3020
4463
  peerId: identity?.identityKey.toHex() ?? "Anonymous"
3021
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
+ }
3022
4476
  const model = {
3023
4477
  id: obj.id,
3024
4478
  content: doc,
3025
4479
  text: () => get4(doc.handle.docSync(), doc.path),
3026
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
3027
- extension: [
3028
- modelState.init(() => model),
3029
- automerge3({
3030
- handle: doc.handle,
3031
- path: doc.path
3032
- }),
3033
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
3034
- awareness()
3035
- ].filter(isNotNullOrUndefined),
4480
+ extension: extensions,
3036
4481
  peer: identity ? {
3037
4482
  id: identity.identityKey.toHex(),
3038
4483
  name: identity.profile?.displayName
@@ -3041,57 +4486,94 @@ var createAutomergeModel = ({ space, identity, text }) => {
3041
4486
  return model;
3042
4487
  };
3043
4488
  export {
3044
- AwarenessProvider,
3045
4489
  BaseTextEditor,
3046
- CursorConverter,
4490
+ Cursor,
3047
4491
  Doc,
3048
4492
  EditorModes,
4493
+ Inline,
4494
+ List,
3049
4495
  MarkdownEditor,
3050
4496
  RemoteSelectionsDecorator,
3051
4497
  SpaceAwarenessProvider,
3052
4498
  TextEditor,
3053
4499
  TextKind,
3054
- YAwarenessProvider,
4500
+ Toolbar,
3055
4501
  YText,
3056
4502
  YXmlFragment,
4503
+ addBlockquote,
4504
+ addCodeblock,
4505
+ addLink,
4506
+ addList,
4507
+ addStyle,
3057
4508
  autocomplete,
3058
- automerge3 as automerge,
4509
+ automerge,
3059
4510
  awareness,
3060
- basicBundle,
4511
+ awarenessProvider,
3061
4512
  blast,
3062
4513
  callbackWrapper,
3063
4514
  code2 as code,
3064
4515
  codeTheme,
3065
4516
  comments,
3066
- createAutomergeModel,
3067
- createYjsModel,
3068
- cursorConverter,
4517
+ compareFormatting,
4518
+ createBasicBundle,
4519
+ createComment,
4520
+ createDataExtensions,
4521
+ createMarkdownExtensions,
4522
+ createThemeExtensions,
3069
4523
  defaultMarkdownSlots,
3070
4524
  defaultOptions,
3071
4525
  defaultSlots,
3072
4526
  defaultTextSlots,
3073
4527
  defaultTheme,
4528
+ focusComment,
4529
+ formatting,
4530
+ getFormatting,
3074
4531
  getToken,
4532
+ heading2 as heading,
3075
4533
  hr,
3076
4534
  image,
4535
+ insertTable,
4536
+ keymap7 as keymap,
3077
4537
  link,
3078
4538
  listener,
3079
- markdownBundle,
4539
+ logChanges,
3080
4540
  markdownHighlightStyle,
3081
4541
  markdownTags,
3082
4542
  markdownTagsExtensions,
3083
4543
  markdownTheme,
3084
4544
  mention,
3085
4545
  modelState,
3086
- setCommentRange,
3087
- setFocus,
4546
+ outliner,
4547
+ removeBlockquote,
4548
+ removeCodeblock,
4549
+ removeLink,
4550
+ removeList,
4551
+ removeStyle,
4552
+ setBlockquote,
4553
+ setComments,
4554
+ setHeading,
3088
4555
  setSelection,
4556
+ setStyle,
3089
4557
  table,
3090
4558
  tags2 as tags,
3091
4559
  tasklist,
3092
4560
  textTheme,
4561
+ toggleBlockquote,
4562
+ toggleCodeblock,
4563
+ toggleEmphasis,
4564
+ toggleInlineCode,
4565
+ toggleList,
4566
+ toggleStrikethrough,
4567
+ toggleStrong,
4568
+ toggleStyle,
3093
4569
  typewriter,
4570
+ useActionHandler,
4571
+ useComments,
4572
+ useEditorView,
4573
+ useFormattingState,
4574
+ useInMemoryTextModel,
4575
+ useTextEditor,
3094
4576
  useTextModel,
3095
- yjs
4577
+ useToolbarContext
3096
4578
  };
3097
4579
  //# sourceMappingURL=index.mjs.map