@dxos/react-ui-editor 0.3.11-main.690ca41 → 0.3.11-main.69484a6

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 (194) hide show
  1. package/dist/lib/browser/index.mjs +1748 -1307
  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/comments.stories.d.ts +26 -0
  12. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  13. package/dist/types/src/components/TextEditor/hooks.stories.d.ts +23 -0
  14. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -0
  15. package/dist/types/src/components/Toolbar/Toolbar.d.ts +25 -0
  16. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
  17. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +23 -0
  18. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
  19. package/dist/types/src/components/Toolbar/index.d.ts +2 -0
  20. package/dist/types/src/components/Toolbar/index.d.ts.map +1 -0
  21. package/dist/types/src/components/index.d.ts +1 -0
  22. package/dist/types/src/components/index.d.ts.map +1 -1
  23. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  24. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  25. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
  26. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
  27. package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
  28. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
  29. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  30. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  31. package/dist/types/src/extensions/automerge/defs.d.ts +9 -11
  32. package/dist/types/src/extensions/automerge/defs.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 +37 -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 +13 -0
  51. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  52. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  53. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  54. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  55. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  56. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  57. package/dist/types/src/extensions/markdown/index.d.ts +8 -0
  58. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  59. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  60. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  61. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  62. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  63. package/dist/types/src/extensions/outliner.d.ts +4 -0
  64. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  65. package/dist/types/src/hooks/awareness-provider.d.ts +7 -4
  66. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -1
  67. package/dist/types/src/hooks/defs.d.ts +25 -0
  68. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  69. package/dist/types/src/hooks/index.d.ts +4 -2
  70. package/dist/types/src/hooks/index.d.ts.map +1 -1
  71. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  72. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  73. package/dist/types/src/hooks/useEditorView.d.ts +17 -0
  74. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
  75. package/dist/types/src/hooks/useTextEditor.d.ts +39 -0
  76. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  77. package/dist/types/src/hooks/useTextModel.d.ts +2 -32
  78. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  79. package/dist/types/src/index.d.ts +2 -0
  80. package/dist/types/src/index.d.ts.map +1 -1
  81. package/dist/types/src/styles/markdown.d.ts +2 -2
  82. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  83. package/dist/types/src/themes/default.d.ts.map +1 -1
  84. package/dist/types/src/util.d.ts +7 -0
  85. package/dist/types/src/util.d.ts.map +1 -0
  86. package/package.json +48 -37
  87. package/src/components/TextEditor/MarkdownEditor.stories.tsx +84 -90
  88. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  89. package/src/components/TextEditor/TextEditor.tsx +136 -95
  90. package/src/components/TextEditor/automerge.stories.tsx +1 -2
  91. package/src/components/TextEditor/comments.stories.tsx +357 -0
  92. package/src/components/TextEditor/hooks.stories.tsx +106 -0
  93. package/src/components/Toolbar/Toolbar.stories.tsx +90 -0
  94. package/src/components/Toolbar/Toolbar.tsx +166 -0
  95. package/src/components/Toolbar/index.ts +5 -0
  96. package/src/components/index.ts +1 -0
  97. package/src/extensions/autocomplete.ts +23 -19
  98. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  99. package/src/extensions/automerge/automerge.test.ts +13 -0
  100. package/src/extensions/automerge/automerge.ts +55 -46
  101. package/src/extensions/automerge/cursor.ts +5 -4
  102. package/src/extensions/automerge/defs.ts +13 -34
  103. package/src/extensions/automerge/semaphore.ts +33 -32
  104. package/src/extensions/automerge/update-automerge.ts +14 -8
  105. package/src/extensions/automerge/update-codemirror.ts +5 -5
  106. package/src/extensions/awareness.ts +43 -49
  107. package/src/extensions/basic.ts +17 -10
  108. package/src/extensions/comments.ts +320 -264
  109. package/src/extensions/cursor.ts +50 -0
  110. package/src/extensions/index.ts +2 -7
  111. package/src/extensions/markdown/bundle.ts +23 -28
  112. package/src/extensions/markdown/code.ts +150 -0
  113. package/src/extensions/markdown/formatting.ts +188 -0
  114. package/src/extensions/markdown/heading.ts +59 -0
  115. package/src/extensions/markdown/highlight.ts +9 -23
  116. package/src/extensions/markdown/hr.ts +71 -0
  117. package/src/extensions/{image.ts → markdown/image.ts} +2 -2
  118. package/src/extensions/markdown/index.ts +8 -0
  119. package/src/extensions/{link.ts → markdown/link.ts} +12 -9
  120. package/src/extensions/{table.ts → markdown/table.ts} +6 -3
  121. package/src/extensions/markdown/tasklist.ts +110 -0
  122. package/src/extensions/mention.ts +9 -2
  123. package/src/extensions/outliner.ts +12 -0
  124. package/src/hooks/awareness-provider.ts +24 -20
  125. package/src/hooks/defs.ts +42 -0
  126. package/src/hooks/index.ts +5 -2
  127. package/src/hooks/useActionHandler.ts +62 -0
  128. package/src/hooks/useEditorView.ts +29 -0
  129. package/src/hooks/useTextEditor.ts +153 -0
  130. package/src/hooks/useTextModel.ts +50 -59
  131. package/src/index.ts +2 -0
  132. package/src/styles/markdown.ts +9 -12
  133. package/src/themes/default.ts +19 -52
  134. package/src/util.ts +41 -0
  135. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  136. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  137. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +0 -12
  138. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  139. package/dist/types/src/extensions/code.d.ts.map +0 -1
  140. package/dist/types/src/extensions/hr.d.ts.map +0 -1
  141. package/dist/types/src/extensions/image.d.ts.map +0 -1
  142. package/dist/types/src/extensions/link.d.ts.map +0 -1
  143. package/dist/types/src/extensions/table.d.ts.map +0 -1
  144. package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
  145. package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
  146. package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
  147. package/dist/types/src/extensions/yjs/index.d.ts +0 -2
  148. package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
  149. package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
  150. package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
  151. package/dist/types/src/extensions/yjs/yjs.test.d.ts +0 -2
  152. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
  153. package/dist/types/src/hooks/automerge.d.ts +0 -3
  154. package/dist/types/src/hooks/automerge.d.ts.map +0 -1
  155. package/dist/types/src/hooks/yjs.d.ts +0 -27
  156. package/dist/types/src/hooks/yjs.d.ts.map +0 -1
  157. package/dist/types/src/testing/index.d.ts +0 -3
  158. package/dist/types/src/testing/index.d.ts.map +0 -1
  159. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  160. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  161. package/dist/types/src/testing/proto/index.d.ts +0 -2
  162. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  163. package/dist/types/src/testing/replicator.d.ts +0 -45
  164. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  165. package/dist/types/src/util/cursor.d.ts.map +0 -1
  166. package/dist/types/src/util/index.d.ts +0 -3
  167. package/dist/types/src/util/index.d.ts.map +0 -1
  168. package/dist/types/src/util/util.d.ts +0 -5
  169. package/dist/types/src/util/util.d.ts.map +0 -1
  170. package/src/components/TextEditor/codemirror.stories.ts +0 -115
  171. package/src/components/TextEditor/yjs.stories.tsx +0 -56
  172. package/src/extensions/code.ts +0 -98
  173. package/src/extensions/hr.ts +0 -73
  174. package/src/extensions/tasklist.ts +0 -119
  175. package/src/extensions/yjs/cursor.ts +0 -21
  176. package/src/extensions/yjs/index.ts +0 -5
  177. package/src/extensions/yjs/yjs.test.ts +0 -35
  178. package/src/extensions/yjs/yjs.ts +0 -16
  179. package/src/hooks/automerge.ts +0 -52
  180. package/src/hooks/yjs.ts +0 -178
  181. package/src/testing/index.ts +0 -6
  182. package/src/testing/proto/gen/schema.ts +0 -49
  183. package/src/testing/proto/index.ts +0 -5
  184. package/src/testing/proto/schema.proto +0 -15
  185. package/src/testing/replicator.ts +0 -184
  186. package/src/util/cursor.ts +0 -29
  187. package/src/util/index.ts +0 -6
  188. package/src/util/util.ts +0 -18
  189. /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
  190. /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
  191. /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
  192. /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
  193. /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
  194. /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.ts} +0 -0
@@ -7,56 +7,60 @@ import { tags as tags2 } from "@lezer/highlight";
7
7
 
8
8
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
9
9
  import { EditorState as EditorState2 } from "@codemirror/state";
10
- import { EditorView as EditorView12 } from "@codemirror/view";
11
- import { useFocusableGroup } from "@fluentui/react-tabster";
10
+ import { EditorView as EditorView13 } from "@codemirror/view";
12
11
  import { vim } from "@replit/codemirror-vim";
13
12
  import defaultsDeep2 from "lodash.defaultsdeep";
14
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
15
- import { generateName } from "@dxos/display-name";
13
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState } from "react";
14
+ import { log as log2 } from "@dxos/log";
16
15
  import { useThemeContext } from "@dxos/react-ui";
17
- import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
16
+ import { attentionSurface, mx as mx3 } from "@dxos/react-ui-theme";
17
+ import { isNotFalsy as isNotFalsy3 } from "@dxos/util";
18
18
 
19
19
  // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
20
20
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
21
+ import { markdownLanguage } from "@codemirror/lang-markdown";
21
22
  import { keymap } from "@codemirror/view";
22
23
  var autocomplete = ({ onSearch }) => {
23
24
  return [
24
25
  // https://codemirror.net/docs/ref/#view.keymap
25
26
  // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
27
+ // TODO(burdon): Set custom keymap.
26
28
  keymap.of(completionKeymap),
27
29
  // https://codemirror.net/examples/autocompletion
28
30
  // https://codemirror.net/docs/ref/#autocomplete.autocompletion
29
31
  autocompletion({
30
- // TODO(burdon): Set custom keymap.
32
+ activateOnTyping: true,
33
+ // closeOnBlur: false,
31
34
  // defaultKeymap: false,
32
- // Don't start unless key press.
33
- activateOnTyping: false,
34
- // TODO(burdon): Option to create new page?
35
- // TODO(burdon): Optional decoration via addToOptions
36
- override: [
37
- (context) => {
38
- const match = context.matchBefore(/\w*/);
39
- if (!match || match.from === match.to && !context.explicit) {
40
- return null;
41
- }
42
- return {
43
- from: match.from,
44
- options: onSearch(match.text.toLowerCase())
45
- };
35
+ // TODO(burdon): Styles/fragments.
36
+ tooltipClass: () => "shadow rounded"
37
+ }),
38
+ // TODO(burdon): Option to create new page?
39
+ // TODO(burdon): Optional decoration via addToOptions
40
+ markdownLanguage.data.of({
41
+ autocomplete: (context) => {
42
+ const match = context.matchBefore(/\w*/);
43
+ if (!match || match.from === match.to && !context.explicit) {
44
+ return null;
46
45
  }
47
- ]
46
+ return {
47
+ from: match.from,
48
+ options: onSearch(match.text.toLowerCase())
49
+ };
50
+ }
48
51
  })
49
52
  ];
50
53
  };
51
54
 
52
55
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
53
- import { Facet as Facet2, StateField } from "@codemirror/state";
54
- import { ViewPlugin } from "@codemirror/view";
55
- import { next as A } from "@dxos/automerge/automerge";
56
+ import { invertedEffects } from "@codemirror/commands";
57
+ import { StateField } from "@codemirror/state";
58
+ import { EditorView, ViewPlugin } from "@codemirror/view";
59
+ import { next as A4 } from "@dxos/automerge/automerge";
56
60
 
57
61
  // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
58
62
  import get from "lodash.get";
59
- import { next as automerge } from "@dxos/automerge/automerge";
63
+ import { next as A } from "@dxos/automerge/automerge";
60
64
  var cursorConverter = (handle, path) => ({
61
65
  // TODO(burdon): Handle assoc to associate with a previous character.
62
66
  toCursor: (pos) => {
@@ -68,7 +72,7 @@ var cursorConverter = (handle, path) => ({
68
72
  if (typeof value === "string" && value.length <= pos) {
69
73
  return "end";
70
74
  }
71
- return automerge.getCursor(doc, path.slice(), pos);
75
+ return A.getCursor(doc, path.slice(), pos);
72
76
  },
73
77
  fromCursor: (cursor) => {
74
78
  if (cursor === "") {
@@ -86,26 +90,28 @@ var cursorConverter = (handle, path) => ({
86
90
  return 0;
87
91
  }
88
92
  }
89
- return automerge.getCursorPosition(doc, path.slice(), cursor);
93
+ return A.getCursorPosition(doc, path.slice(), cursor);
90
94
  }
91
95
  });
92
96
 
93
97
  // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
94
98
  import { Annotation, StateEffect } from "@codemirror/state";
95
- var effectType = StateEffect.define({});
96
- var updateHeads = (newHeads) => effectType.of({
99
+ var getPath = (state, field) => state.field(field).path;
100
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
101
+ var updateHeadsEffect = StateEffect.define({});
102
+ var updateHeads = (newHeads) => updateHeadsEffect.of({
97
103
  newHeads
98
104
  });
99
- var getLastHeads = (state, field) => state.field(field).lastHeads;
100
- var getPath = (state, field) => state.field(field).path;
101
- var reconcileAnnotationType = Annotation.define();
102
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
105
+ var reconcileAnnotation = Annotation.define();
106
+ var isReconcile = (tr) => {
107
+ return !!tr.annotation(reconcileAnnotation);
108
+ };
103
109
 
104
110
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
105
- import { next as automerge2 } from "@dxos/automerge/automerge";
111
+ import { next as A3 } from "@dxos/automerge/automerge";
106
112
 
107
113
  // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
108
- import { next as am } from "@dxos/automerge/automerge";
114
+ import { next as A2 } from "@dxos/automerge/automerge";
109
115
  var updateAutomerge = (field, handle, transactions, state) => {
110
116
  const { lastHeads, path } = state.field(field);
111
117
  let hasChanges = false;
@@ -118,11 +124,19 @@ var updateAutomerge = (field, handle, transactions, state) => {
118
124
  return void 0;
119
125
  }
120
126
  const newHeads = handle.changeAt(lastHeads, (doc) => {
127
+ const invertedTransactions = [];
121
128
  for (const tr of transactions) {
122
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
123
- am.splice(doc, path, fromA, toA - fromA, inserted.toString());
129
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
130
+ invertedTransactions.push({
131
+ from: fromA,
132
+ del: toA - fromA,
133
+ insert
134
+ });
124
135
  });
125
136
  }
137
+ invertedTransactions.reverse().forEach(({ from, del, insert }) => {
138
+ A2.splice(doc, path.slice(), from, del, insert.toString());
139
+ });
126
140
  });
127
141
  return newHeads ?? void 0;
128
142
  };
@@ -137,13 +151,13 @@ var updateCodeMirror = (view, selection, target, patches) => {
137
151
  selection = selection.map(changeSet, 1);
138
152
  view.dispatch({
139
153
  changes: changeSet,
140
- annotations: reconcileAnnotationType.of({})
154
+ annotations: reconcileAnnotation.of(false)
141
155
  });
142
156
  }
143
157
  }
144
158
  view.dispatch({
145
159
  selection,
146
- annotations: reconcileAnnotationType.of({})
160
+ annotations: reconcileAnnotation.of(false)
147
161
  });
148
162
  };
149
163
  var handlePatch = (patch, target, state) => {
@@ -164,7 +178,7 @@ var handleInsert = (target, patch) => {
164
178
  if (index == null) {
165
179
  return [];
166
180
  }
167
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
181
+ const text = patch.values.map((value) => value ? value.toString() : "").join("");
168
182
  return [
169
183
  {
170
184
  from: index,
@@ -236,89 +250,100 @@ var charPath = (textPath, candidatePath) => {
236
250
 
237
251
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
238
252
  var PatchSemaphore = class {
239
- constructor(_field) {
240
- this._field = _field;
253
+ // prettier-ignore
254
+ constructor(_handle, _state) {
255
+ this._handle = _handle;
256
+ this._state = _state;
241
257
  this._inReconcile = false;
242
- this._queue = [];
243
- this.reconcile = (handle, view) => {
244
- if (this._inReconcile) {
245
- return;
246
- }
258
+ }
259
+ reconcile(view) {
260
+ if (!this._inReconcile) {
247
261
  this._inReconcile = true;
248
- const path = getPath(view.state, this._field);
249
- const oldHeads = getLastHeads(view.state, this._field);
250
- let selection = view.state.selection;
251
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
252
- const toInvert = transactions.slice().reverse();
253
- for (const tx of toInvert) {
254
- const inverted = tx.changes.invert(tx.startState.doc);
255
- selection = selection.map(inverted);
256
- view.dispatch({
257
- changes: inverted,
258
- annotations: reconcileAnnotationType.of(true)
259
- });
260
- }
261
- let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
262
- if (newHeads === null || newHeads === void 0) {
263
- newHeads = automerge2.getHeads(handle.docSync());
264
- }
265
- const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(handle.docSync(), oldHeads, newHeads);
266
- updateCodeMirror(view, selection, path, diff);
262
+ this.doReconcile(view);
263
+ this._inReconcile = false;
264
+ }
265
+ }
266
+ doReconcile(view) {
267
+ const path = getPath(view.state, this._state);
268
+ const oldHeads = getLastHeads(view.state, this._state);
269
+ let selection = view.state.selection;
270
+ const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
271
+ const toInvert = transactions.slice().reverse();
272
+ for (const tr of toInvert) {
273
+ const inverted = tr.changes.invert(tr.startState.doc);
274
+ selection = selection.map(inverted);
267
275
  view.dispatch({
268
- effects: updateHeads(newHeads),
269
- annotations: reconcileAnnotationType.of({})
276
+ changes: inverted,
277
+ annotations: reconcileAnnotation.of(true)
270
278
  });
271
- this._inReconcile = false;
272
- };
279
+ }
280
+ let newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
281
+ if (newHeads === null || newHeads === void 0) {
282
+ newHeads = A3.getHeads(this._handle.docSync());
283
+ }
284
+ const diff = A3.equals(oldHeads, newHeads) ? [] : A3.diff(this._handle.docSync(), oldHeads, newHeads);
285
+ updateCodeMirror(view, selection, path, diff);
286
+ view.dispatch({
287
+ effects: updateHeads(newHeads),
288
+ annotations: reconcileAnnotation.of(false)
289
+ });
273
290
  }
274
291
  };
275
292
 
276
- // packages/ui/react-ui-editor/src/util/cursor.ts
293
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
277
294
  import { Facet } from "@codemirror/state";
278
- var DEFAULT_CURSOR_CONVERTER = {
295
+ var defaultCursorConverter = {
279
296
  toCursor: (position) => position.toString(),
280
297
  fromCursor: (cursor) => parseInt(cursor)
281
298
  };
282
- var CursorConverter = Facet.define({
283
- combine: (providers) => providers[0] ?? DEFAULT_CURSOR_CONVERTER
284
- });
285
-
286
- // packages/ui/react-ui-editor/src/util/util.ts
287
- import { log } from "@dxos/log";
288
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util/util.ts";
289
- var callbackWrapper = (fn) => (...args) => {
290
- try {
291
- return fn(...args);
292
- } catch (error) {
293
- log.catch(error, void 0, {
294
- F: __dxlog_file,
295
- L: 16,
296
- S: void 0,
297
- C: (f, a) => f(...a)
299
+ var Cursor = class _Cursor {
300
+ static {
301
+ this.converter = Facet.define({
302
+ combine: (providers) => providers[0] ?? defaultCursorConverter
298
303
  });
299
304
  }
305
+ static {
306
+ this.getCursorFromRange = (state, range) => {
307
+ const cursorConverter2 = state.facet(_Cursor.converter);
308
+ const from = cursorConverter2.toCursor(range.from);
309
+ const to = cursorConverter2.toCursor(range.to, -1);
310
+ return [
311
+ from,
312
+ to
313
+ ].join(":");
314
+ };
315
+ }
316
+ static {
317
+ this.getRangeFromCursor = (state, cursor) => {
318
+ const cursorConverter2 = state.facet(_Cursor.converter);
319
+ const parts = cursor.split(":");
320
+ const from = cursorConverter2.fromCursor(parts[0]);
321
+ const to = cursorConverter2.fromCursor(parts[1]);
322
+ return from !== void 0 && to !== void 0 ? {
323
+ from,
324
+ to
325
+ } : void 0;
326
+ };
327
+ }
300
328
  };
301
329
 
302
330
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
303
- var semaphoreFacet = Facet2.define({
304
- combine: (values) => values.at(-1)
305
- });
306
- var automerge3 = ({ handle, path }) => {
307
- const stateField = StateField.define({
331
+ var automerge = ({ handle, path }) => {
332
+ const syncState = StateField.define({
308
333
  create: () => ({
309
- lastHeads: A.getHeads(handle.docSync()),
310
- unreconciledTransactions: [],
311
- path: path.slice()
334
+ path: path.slice(),
335
+ lastHeads: A4.getHeads(handle.docSync()),
336
+ unreconciledTransactions: []
312
337
  }),
313
338
  update: (value, tr) => {
314
339
  const result = {
340
+ path: path.slice(),
315
341
  lastHeads: value.lastHeads,
316
- unreconciledTransactions: value.unreconciledTransactions.slice(),
317
- path: path.slice()
342
+ unreconciledTransactions: value.unreconciledTransactions.slice()
318
343
  };
319
344
  let clearUnreconciled = false;
320
345
  for (const effect of tr.effects) {
321
- if (effect.is(effectType)) {
346
+ if (effect.is(updateHeadsEffect)) {
322
347
  result.lastHeads = effect.value.newHeads;
323
348
  clearUnreconciled = true;
324
349
  }
@@ -326,46 +351,53 @@ var automerge3 = ({ handle, path }) => {
326
351
  if (clearUnreconciled) {
327
352
  result.unreconciledTransactions = [];
328
353
  } else {
329
- if (!isReconcileTx(tr)) {
354
+ if (!isReconcile(tr)) {
330
355
  result.unreconciledTransactions.push(tr);
331
356
  }
332
357
  }
333
358
  return result;
334
359
  }
335
360
  });
336
- const semaphore = new PatchSemaphore(stateField);
337
- const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
338
- constructor(_view) {
339
- this._view = _view;
340
- this._handleChange = () => {
341
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
342
- };
343
- handle.addListener("change", this._handleChange);
344
- }
345
- update(update2) {
346
- if (update2.transactions.length > 0 && update2.transactions.some((tr) => !isReconcileTx(tr))) {
347
- queueMicrotask(() => {
348
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
361
+ const semaphore = new PatchSemaphore(handle, syncState);
362
+ return [
363
+ Cursor.converter.of(cursorConverter(handle, path)),
364
+ syncState,
365
+ // Reconcile external updates.
366
+ ViewPlugin.fromClass(class {
367
+ constructor(_view) {
368
+ this._view = _view;
369
+ handle.addListener("change", this._handleChange.bind(this));
370
+ }
371
+ destroy() {
372
+ handle.removeListener("change", this._handleChange.bind(this));
373
+ }
374
+ _handleChange() {
375
+ semaphore.reconcile(this._view);
376
+ }
377
+ }),
378
+ // Reconcile local updates.
379
+ EditorView.updateListener.of(({ view, changes }) => {
380
+ if (!changes.empty) {
381
+ semaphore.reconcile(view);
382
+ }
383
+ }),
384
+ // TODO(burdon): Record undo transactions.
385
+ // See https://github.com/yjs/y-codemirror.next/tree/main
386
+ // https://codemirror.net/examples/inverted-effect
387
+ invertedEffects.of((tr) => {
388
+ const effects = [];
389
+ if (!tr.changes.empty) {
390
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
349
391
  });
350
392
  }
351
- }
352
- destroy() {
353
- handle.addListener("change", this._handleChange);
354
- }
355
- });
356
- return {
357
- extension: [
358
- CursorConverter.of(cursorConverter(handle, path)),
359
- semaphoreFacet.of(semaphore),
360
- stateField,
361
- viewPlugin
362
- ]
363
- };
393
+ return effects;
394
+ })
395
+ ];
364
396
  };
365
397
 
366
398
  // packages/ui/react-ui-editor/src/extensions/awareness.ts
367
- import { Annotation as Annotation2, Facet as Facet3, RangeSet } from "@codemirror/state";
368
- import { Decoration, EditorView, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
399
+ import { Annotation as Annotation2, Facet as Facet2, RangeSet } from "@codemirror/state";
400
+ import { Decoration, EditorView as EditorView2, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
369
401
  import { Event } from "@dxos/async";
370
402
  import { Context } from "@dxos/context";
371
403
  var dummyProvider = {
@@ -374,17 +406,17 @@ var dummyProvider = {
374
406
  },
375
407
  close: () => {
376
408
  },
409
+ getRemoteStates: () => [],
377
410
  update: () => {
378
- },
379
- getRemoteStates: () => []
411
+ }
380
412
  };
381
- var AwarenessProvider = Facet3.define({
413
+ var awarenessProvider = Facet2.define({
382
414
  combine: (providers) => providers[0] ?? dummyProvider
383
415
  });
384
416
  var RemoteSelectionChangedAnnotation = Annotation2.define();
385
417
  var awareness = (provider = dummyProvider) => {
386
418
  return [
387
- AwarenessProvider.of(provider),
419
+ awarenessProvider.of(provider),
388
420
  ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
389
421
  decorations: (value) => value.decorations
390
422
  }),
@@ -397,8 +429,8 @@ var RemoteSelectionsDecorator = class {
397
429
  this._ctx = new Context();
398
430
  this._lastAnchor = void 0;
399
431
  this._lastHead = void 0;
400
- this._cursorConverter = view.state.facet(CursorConverter);
401
- this._provider = view.state.facet(AwarenessProvider);
432
+ this._cursorConverter = view.state.facet(Cursor.converter);
433
+ this._provider = view.state.facet(awarenessProvider);
402
434
  this._provider.open();
403
435
  this._provider.remoteStateChange.on(this._ctx, () => {
404
436
  view.dispatch({
@@ -452,7 +484,7 @@ var RemoteSelectionsDecorator = class {
452
484
  attributes: {
453
485
  style: `background-color: ${lightColor}`
454
486
  },
455
- class: "cm-ySelection"
487
+ class: "cm-collab-selection"
456
488
  })
457
489
  });
458
490
  } else {
@@ -463,7 +495,7 @@ var RemoteSelectionsDecorator = class {
463
495
  attributes: {
464
496
  style: `background-color: ${color}`
465
497
  },
466
- class: "cm-ySelection"
498
+ class: "cm-collab-selection"
467
499
  })
468
500
  });
469
501
  decorations.push({
@@ -473,7 +505,7 @@ var RemoteSelectionsDecorator = class {
473
505
  attributes: {
474
506
  style: `background-color: ${color}`
475
507
  },
476
- class: "cm-ySelection"
508
+ class: "cm-collab-selection"
477
509
  })
478
510
  });
479
511
  for (let i = startLine.number + 1; i < endLine.number; i++) {
@@ -484,7 +516,7 @@ var RemoteSelectionsDecorator = class {
484
516
  value: Decoration.line({
485
517
  attributes: {
486
518
  style: `background-color: ${color}`,
487
- class: "cm-yLineSelection"
519
+ class: "cm-collab-selectionLine"
488
520
  }
489
521
  })
490
522
  });
@@ -504,23 +536,21 @@ var RemoteSelectionsDecorator = class {
504
536
  }
505
537
  };
506
538
  var RemoteCaretWidget = class extends WidgetType {
507
- constructor(name, color) {
539
+ constructor(_name, _color) {
508
540
  super();
509
- this.name = name;
510
- this.color = color;
511
- this.name = name;
512
- this.color = color;
541
+ this._name = _name;
542
+ this._color = _color;
513
543
  }
514
544
  toDOM() {
515
545
  const span = document.createElement("span");
516
- span.className = "cm-ySelectionCaret";
517
- span.style.backgroundColor = this.color;
518
- span.style.borderColor = this.color;
546
+ span.className = "cm-collab-selectionCaret";
547
+ span.style.backgroundColor = this._color;
548
+ span.style.borderColor = this._color;
519
549
  const dot = document.createElement("div");
520
- dot.className = "cm-ySelectionCaretDot";
550
+ dot.className = "cm-collab-selectionCaretDot";
521
551
  const info = document.createElement("div");
522
- info.className = "cm-ySelectionInfo";
523
- info.innerText = this.name;
552
+ info.className = "cm-collab-selectionInfo";
553
+ info.innerText = this._name;
524
554
  span.appendChild(document.createTextNode("\u2060"));
525
555
  span.appendChild(dot);
526
556
  span.appendChild(document.createTextNode("\u2060"));
@@ -528,15 +558,12 @@ var RemoteCaretWidget = class extends WidgetType {
528
558
  span.appendChild(document.createTextNode("\u2060"));
529
559
  return span;
530
560
  }
531
- eq(widget) {
532
- return widget.color === this.color;
533
- }
534
- compare(widget) {
535
- return widget.color === this.color;
536
- }
537
561
  updateDOM() {
538
562
  return false;
539
563
  }
564
+ eq(widget) {
565
+ return widget._color === this._color;
566
+ }
540
567
  get estimatedHeight() {
541
568
  return -1;
542
569
  }
@@ -544,58 +571,60 @@ var RemoteCaretWidget = class extends WidgetType {
544
571
  return true;
545
572
  }
546
573
  };
547
- var styles = EditorView.baseTheme({
548
- ".cm-ySelection": {},
549
- ".cm-yLineSelection": {
574
+ var styles = EditorView2.baseTheme({
575
+ ".cm-collab-selection": {},
576
+ ".cm-collab-selectionLine": {
550
577
  padding: 0,
551
578
  margin: "0px 2px 0px 4px"
552
579
  },
553
- ".cm-ySelectionCaret": {
580
+ ".cm-collab-selectionCaret": {
554
581
  position: "relative",
555
582
  borderLeft: "1px solid black",
556
583
  borderRight: "1px solid black",
557
584
  marginLeft: "-1px",
558
585
  marginRight: "-1px",
559
586
  boxSizing: "border-box",
560
- display: "inline"
587
+ display: "inline",
588
+ cursor: "pointer"
561
589
  },
562
- ".cm-ySelectionCaretDot": {
590
+ ".cm-collab-selectionCaretDot": {
563
591
  borderRadius: "50%",
564
592
  position: "absolute",
565
- width: ".4em",
566
- height: ".4em",
567
- top: "-.2em",
568
- left: "-.2em",
593
+ width: ".5em",
594
+ height: ".5em",
595
+ top: "-.25em",
596
+ left: "-.25em",
569
597
  backgroundColor: "inherit",
570
598
  transition: "transform .3s ease-in-out",
571
599
  boxSizing: "border-box"
572
600
  },
573
- ".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot": {
574
- transformOrigin: "bottom center",
575
- transform: "scale(0)"
601
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
602
+ transform: "scale(0)",
603
+ transformOrigin: "center"
576
604
  },
577
- ".cm-ySelectionInfo": {
605
+ ".cm-collab-selectionInfo": {
578
606
  position: "absolute",
579
- top: "-1.05em",
580
- left: "-1px",
607
+ transform: "translate(-50%, 0)",
608
+ top: "-20px",
609
+ left: 0,
581
610
  fontSize: ".75em",
582
- fontFamily: "serif",
611
+ fontFamily: "sans-serif",
583
612
  fontStyle: "normal",
584
613
  fontWeight: "normal",
585
614
  lineHeight: "normal",
586
615
  userSelect: "none",
587
616
  color: "white",
588
- paddingLeft: "2px",
589
- paddingRight: "2px",
617
+ padding: "2px",
590
618
  zIndex: 101,
591
619
  transition: "opacity .3s ease-in-out",
592
620
  backgroundColor: "inherit",
593
- // these should be separate
621
+ borderRadius: "2px",
622
+ // These should be separate.
594
623
  opacity: 0,
595
624
  transitionDelay: "0s",
596
625
  whiteSpace: "nowrap"
597
626
  },
598
- ".cm-ySelectionCaret:hover > .cm-ySelectionInfo": {
627
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
599
628
  opacity: 1,
600
629
  transitionDelay: "0s"
601
630
  }
@@ -603,26 +632,29 @@ var styles = EditorView.baseTheme({
603
632
 
604
633
  // packages/ui/react-ui-editor/src/extensions/basic.ts
605
634
  import { closeBrackets } from "@codemirror/autocomplete";
635
+ import { history } from "@codemirror/commands";
606
636
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
607
637
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
608
- import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
609
- var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
610
- EditorView2.lineWrapping,
611
- // TODO(burdon): Compare with minimalSetup.
638
+ import { drawSelection, EditorView as EditorView3, highlightActiveLine, placeholder } from "@codemirror/view";
639
+ import { isNotFalsy } from "@dxos/util";
640
+ var createBasicBundle = ({ themeMode, placeholder: _placeholder, lineWrapping = true } = {}) => [
641
+ lineWrapping && EditorView3.lineWrapping,
612
642
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
613
643
  bracketMatching(),
614
644
  closeBrackets(),
615
645
  drawSelection(),
646
+ highlightActiveLine(),
647
+ history(),
616
648
  _placeholder && placeholder(_placeholder),
617
- // TODO(burdon): Is this required?
649
+ // https://github.com/codemirror/theme-one-dark
618
650
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
619
- ].filter(Boolean);
651
+ ].filter(isNotFalsy);
620
652
 
621
653
  // packages/ui/react-ui-editor/src/extensions/blast.ts
622
- import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
654
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
623
655
  import defaultsDeep from "lodash.defaultsdeep";
624
656
  import { invariant } from "@dxos/invariant";
625
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
657
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
626
658
  var defaultOptions = {
627
659
  effect: 2,
628
660
  maxParticles: 200,
@@ -655,10 +687,10 @@ var blast = (options = defaultOptions) => {
655
687
  const getPoint = (view, pos) => {
656
688
  const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
657
689
  const element = document.elementFromPoint(x, y);
658
- const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
690
+ const { offsetLeft: left = 0, offsetTop: top2 = 0 } = view.scrollDOM.parentElement ?? {};
659
691
  const point = {
660
692
  x: x - left,
661
- y: y - top
693
+ y: y - top2
662
694
  };
663
695
  return {
664
696
  element,
@@ -667,7 +699,7 @@ var blast = (options = defaultOptions) => {
667
699
  };
668
700
  return [
669
701
  // Cursor moved.
670
- EditorView3.updateListener.of((update2) => {
702
+ EditorView4.updateListener.of((update2) => {
671
703
  if (blaster?.node !== update2.view.scrollDOM) {
672
704
  if (blaster) {
673
705
  blaster.destroy();
@@ -749,7 +781,7 @@ var Blaster = class {
749
781
  }
750
782
  initialize() {
751
783
  invariant(!this._canvas && !this._ctx, void 0, {
752
- F: __dxlog_file2,
784
+ F: __dxlog_file,
753
785
  L: 138,
754
786
  S: this,
755
787
  A: [
@@ -786,7 +818,7 @@ var Blaster = class {
786
818
  }
787
819
  start() {
788
820
  invariant(this._canvas && this._ctx, void 0, {
789
- F: __dxlog_file2,
821
+ F: __dxlog_file,
790
822
  L: 177,
791
823
  S: this,
792
824
  A: [
@@ -928,9 +960,14 @@ var random = (min, max) => {
928
960
  return min + ~~(Math.random() * (max - min + 1));
929
961
  };
930
962
 
931
- // packages/ui/react-ui-editor/src/extensions/code.ts
932
- import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
933
- import { mx as mx2 } from "@dxos/react-ui-theme";
963
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
964
+ import { invertedEffects as invertedEffects2 } from "@codemirror/commands";
965
+ import { Facet as Facet3, StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
966
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration2, EditorView as EditorView5 } from "@codemirror/view";
967
+ import sortBy from "lodash.sortby";
968
+ import { useEffect } from "react";
969
+ import { debounce } from "@dxos/async";
970
+ import { nonNullable } from "@dxos/util";
934
971
 
935
972
  // packages/ui/react-ui-editor/src/styles/cursors.ts
936
973
  import * as random2 from "lib0/random";
@@ -973,34 +1010,15 @@ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
973
1010
  // packages/ui/react-ui-editor/src/styles/markdown.ts
974
1011
  import { mx } from "@dxos/react-ui-theme";
975
1012
  var headings = {
976
- 1: [
977
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
978
- "-ml-[10px]"
979
- ],
980
- 2: [
981
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
982
- "-ml-[8px]"
983
- ],
984
- 3: [
985
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
986
- "-ml-[7px]"
987
- ],
988
- 4: [
989
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
990
- "-ml-[6px]"
991
- ],
992
- 5: [
993
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
994
- "-ml-[5px]"
995
- ],
996
- 6: [
997
- "mbs-4 mbe-2 font-medium text-inherit no-underline",
998
- "-ml-[4px]"
999
- ]
1000
- };
1001
- var heading = (level, readonly) => {
1002
- const [style, offset] = headings[level];
1003
- return mx(style, readonly && offset);
1013
+ 1: "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
1014
+ 2: "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
1015
+ 3: "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
1016
+ 4: "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
1017
+ 5: "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
1018
+ 6: "mbs-4 mbe-2 font-medium text-inherit no-underline"
1019
+ };
1020
+ var heading = (level) => {
1021
+ return headings[level];
1004
1022
  };
1005
1023
  var light = "text-neutral-200 dark:text-neutral-800";
1006
1024
  var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
@@ -1018,154 +1036,109 @@ import { tailwindConfig } from "@dxos/react-ui-theme";
1018
1036
  var tokens = tailwindConfig({}).theme;
1019
1037
  var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue);
1020
1038
 
1021
- // packages/ui/react-ui-editor/src/extensions/code.ts
1022
- var CODE_REGEX = /```[\s\S]*?```/gs;
1023
- var styles2 = EditorView4.baseTheme({
1024
- "& .cm-codeblock": {
1025
- fontFamily: getToken("fontFamily.mono", []).join(",")
1026
- },
1027
- '&light [aria-readonly="true"] .cm-codeblock': {
1028
- background: getToken("extend.colors.neutral.50")
1029
- },
1030
- '&dark [aria-readonly="true"] .cm-codeblock': {
1031
- background: getToken("extend.colors.neutral.850")
1032
- },
1033
- '& [aria-readonly="true"] .cm-codeblock': {
1034
- display: "block",
1035
- paddingInline: "4px !important"
1036
- },
1037
- '& [aria-readonly="true"] .cm-codeblock-first': {
1038
- paddingTop: "4px !important",
1039
- borderTopLeftRadius: "4px",
1040
- borderTopRightRadius: "4px"
1041
- },
1042
- '& [aria-readonly="true"] .cm-codeblock-last': {
1043
- paddingBottom: "4px !important",
1044
- borderBottomLeftRadius: "4px",
1045
- borderBottomRightRadius: "4px"
1039
+ // packages/ui/react-ui-editor/src/util.ts
1040
+ import { log } from "@dxos/log";
1041
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1042
+ var callbackWrapper = (fn) => (...args) => {
1043
+ try {
1044
+ return fn(...args);
1045
+ } catch (err) {
1046
+ log.catch(err, void 0, {
1047
+ F: __dxlog_file2,
1048
+ L: 18,
1049
+ S: void 0,
1050
+ C: (f, a) => f(...a)
1051
+ });
1046
1052
  }
1047
- });
1048
- var getLineRange = (lines, from, to) => {
1049
- const start = lines.findIndex((line) => line.from >= from);
1050
- const end = lines.findIndex((line) => line.to >= to);
1051
- return [
1052
- start,
1053
- end
1054
- ];
1055
1053
  };
1056
- var code2 = () => {
1057
- const getDecorations = (view) => {
1058
- const decorations = [];
1059
- if (view.state.readOnly) {
1060
- const text = view.state.doc.sliceString(0);
1061
- const matches = text.matchAll(CODE_REGEX);
1062
- const blocks = view.viewportLineBlocks;
1063
- for (const match of matches) {
1064
- const range = getLineRange(blocks, match.index, match.index + match[0].length);
1065
- for (let i = range[0]; i <= range[1]; i++) {
1066
- const block = blocks[i];
1067
- decorations.push(Decoration2.line({
1068
- class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1069
- }).range(block.from));
1070
- }
1071
- }
1054
+ var logChanges = (trs) => {
1055
+ const changes = trs.flatMap((tr) => {
1056
+ if (tr.changes.empty) {
1057
+ return void 0;
1072
1058
  }
1073
- return Decoration2.set(decorations);
1074
- };
1075
- return [
1076
- ViewPlugin3.fromClass(class {
1077
- constructor(view) {
1078
- this.decorations = getDecorations(view);
1079
- }
1080
- update(update2) {
1081
- if (update2.docChanged || update2.viewportChanged) {
1082
- this.decorations = getDecorations(update2.view);
1083
- }
1084
- }
1085
- }, {
1086
- decorations: (value) => value.decorations
1087
- }),
1088
- styles2
1089
- ];
1059
+ const changes2 = [];
1060
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => changes2.push(JSON.stringify({
1061
+ fromA,
1062
+ toA,
1063
+ fromB,
1064
+ toB,
1065
+ inserted: inserted.toString()
1066
+ })));
1067
+ return changes2;
1068
+ }).filter(Boolean);
1069
+ if (changes.length) {
1070
+ console.log("changes", changes);
1071
+ }
1090
1072
  };
1091
1073
 
1092
1074
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1093
- import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1094
- import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1095
- import sortBy from "lodash.sortby";
1096
- import { debounce } from "@dxos/async";
1097
- import { invariant as invariant2 } from "@dxos/invariant";
1098
- import { log as log2 } from "@dxos/log";
1099
- import { nonNullable } from "@dxos/util";
1100
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1101
- var styles3 = EditorView5.baseTheme({
1102
- "& .cm-bookmark": {
1103
- cursor: "pointer",
1104
- margin: "4px",
1105
- padding: "4px",
1106
- backgroundColor: "yellow"
1107
- },
1108
- "& .cm-bookmark-selected": {
1109
- backgroundColor: "orange"
1110
- },
1075
+ var styles2 = EditorView5.baseTheme({
1111
1076
  "& .cm-comment": {
1112
1077
  backgroundColor: getToken("extend.colors.yellow.50")
1113
1078
  },
1114
- "& .cm-comment-active": {
1079
+ "& .cm-comment-current": {
1115
1080
  backgroundColor: getToken("extend.colors.yellow.100")
1116
1081
  }
1117
1082
  });
1118
- var marks = {
1119
- highlight: Decoration3.mark({
1120
- class: "cm-comment"
1121
- }),
1122
- highlightActive: Decoration3.mark({
1123
- class: "cm-comment-active"
1124
- })
1125
- };
1126
- var setFocus = (view, thread) => {
1127
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1128
- if (!range) {
1083
+ var commentMark = Decoration2.mark({
1084
+ class: "cm-comment"
1085
+ });
1086
+ var commentCurrentMark = Decoration2.mark({
1087
+ class: "cm-comment-current"
1088
+ });
1089
+ var focusComment = (view, id, center = true) => {
1090
+ const comment = view.state.field(commentsState).comments.find((range2) => range2.comment.id === id);
1091
+ if (!comment?.comment.cursor) {
1129
1092
  return;
1130
1093
  }
1131
- view.dispatch({
1132
- effects: setSelection.of({
1133
- active: thread
1134
- }),
1135
- selection: {
1136
- anchor: range.from
1137
- },
1138
- scrollIntoView: true
1139
- });
1094
+ const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);
1095
+ if (range) {
1096
+ view.dispatch({
1097
+ selection: {
1098
+ anchor: range.from
1099
+ },
1100
+ effects: [
1101
+ //
1102
+ EditorView5.scrollIntoView(range.from, center ? {
1103
+ y: "center"
1104
+ } : void 0),
1105
+ setSelection.of({
1106
+ current: id
1107
+ })
1108
+ ]
1109
+ });
1110
+ }
1140
1111
  };
1141
- var setCommentRange = StateEffect2.define();
1112
+ var setComments = StateEffect2.define();
1142
1113
  var setSelection = StateEffect2.define();
1143
1114
  var setCommentState = StateEffect2.define();
1144
- var commentsStateField = StateField2.define({
1115
+ var commentsState = StateField2.define({
1145
1116
  create: () => ({
1146
- ranges: []
1117
+ comments: [],
1118
+ selection: {}
1147
1119
  }),
1148
1120
  update: (value, tr) => {
1149
- const cursorConverter3 = tr.state.facet(CursorConverter);
1150
1121
  for (const effect of tr.effects) {
1151
1122
  if (effect.is(setSelection)) {
1152
1123
  return {
1153
1124
  ...value,
1154
- ...effect.value
1125
+ selection: effect.value
1155
1126
  };
1156
1127
  }
1157
- if (effect.is(setCommentRange)) {
1158
- const { comments: comments2 } = effect.value;
1159
- const ranges = comments2.map((comment) => {
1160
- const range = getRangeFromCursor(cursorConverter3, comment.cursor);
1128
+ if (effect.is(setComments)) {
1129
+ const comments2 = effect.value.map((comment) => {
1130
+ if (!comment.cursor) {
1131
+ return void 0;
1132
+ }
1133
+ const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);
1161
1134
  return range && {
1162
- ...comment,
1163
- ...range
1135
+ comment,
1136
+ range
1164
1137
  };
1165
1138
  }).filter(nonNullable);
1166
1139
  return {
1167
1140
  ...value,
1168
- ranges
1141
+ comments: comments2
1169
1142
  };
1170
1143
  }
1171
1144
  if (effect.is(setCommentState)) {
@@ -1175,143 +1148,174 @@ var commentsStateField = StateField2.define({
1175
1148
  return value;
1176
1149
  }
1177
1150
  });
1178
- var highlightDecorations = EditorView5.decorations.compute([
1179
- commentsStateField
1151
+ var commentsDecorations = EditorView5.decorations.compute([
1152
+ commentsState
1180
1153
  ], (state) => {
1181
- const { active, ranges } = state.field(commentsStateField);
1182
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1183
- if (selection.from === selection.to) {
1154
+ const { selection: { current }, comments: comments2 } = state.field(commentsState);
1155
+ const decorations = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
1156
+ const range = comment.range;
1157
+ if (!range || range.from === range.to) {
1158
+ console.warn("Invalid range:", range);
1184
1159
  return void 0;
1185
1160
  }
1186
- const range = {
1187
- from: selection.from,
1188
- to: selection.to
1189
- };
1190
- if (selection.id === active) {
1191
- return marks.highlightActive.range(range.from, range.to);
1161
+ if (comment.comment.id === current) {
1162
+ return commentCurrentMark.range(range.from, range.to);
1192
1163
  } else {
1193
- return marks.highlight.range(range.from, range.to);
1164
+ return commentMark.range(range.from, range.to);
1194
1165
  }
1195
- }).filter(nonNullable) ?? [];
1196
- return Decoration3.set(decorations);
1166
+ }).filter(nonNullable);
1167
+ return Decoration2.set(decorations);
1197
1168
  });
1198
- var BookmarkWidget = class extends WidgetType2 {
1199
- constructor(_anchor, _id, _handleClick) {
1200
- super();
1201
- this._anchor = _anchor;
1202
- this._id = _id;
1203
- this._handleClick = _handleClick;
1204
- invariant2(this._id, void 0, {
1205
- F: __dxlog_file3,
1206
- L: 168,
1207
- S: this,
1208
- A: [
1209
- "this._id",
1210
- ""
1211
- ]
1212
- });
1213
- }
1214
- eq(other) {
1215
- return this._anchor === other._anchor && this._id === other._id;
1216
- }
1217
- toDOM() {
1218
- const span = document.createElement("span");
1219
- span.className = "cm-bookmark";
1220
- span.textContent = "\u203B";
1221
- if (this._handleClick) {
1222
- span.onclick = () => this._handleClick();
1223
- }
1224
- return span;
1225
- }
1226
- };
1227
- var comments = (options = {}) => {
1228
- const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1229
- const createCommentThread = (view) => {
1230
- const cursorConverter3 = view.state.facet(CursorConverter);
1231
- invariant2(options.onCreate, void 0, {
1232
- F: __dxlog_file3,
1233
- L: 224,
1234
- S: void 0,
1235
- A: [
1236
- "options.onCreate",
1237
- ""
1238
- ]
1239
- });
1240
- const { head, from, to } = view.state.selection.main;
1241
- if (from === to) {
1242
- return false;
1169
+ var trackPastedComments = (onUpdate) => {
1170
+ let tracked = null;
1171
+ const handleTrack = (event, view) => {
1172
+ const comments2 = view.state.field(commentsState);
1173
+ const { main } = view.state.selection;
1174
+ const selectedRanges = comments2.comments.filter(({ range }) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1175
+ if (!selectedRanges.length) {
1176
+ tracked = null;
1177
+ } else {
1178
+ tracked = {
1179
+ text: view.state.doc.slice(main.from, main.to),
1180
+ comments: selectedRanges.map(({ comment, range }) => ({
1181
+ id: comment.id,
1182
+ from: range.from - main.from,
1183
+ to: range.to - main.from
1184
+ }))
1185
+ };
1243
1186
  }
1244
- const relPos = getCursorFromRange(cursorConverter3, {
1245
- from,
1246
- to
1247
- });
1248
- if (relPos) {
1249
- const id = callbackWrapper(options.onCreate)(relPos);
1250
- if (id) {
1251
- view.dispatch({
1252
- effects: setSelection.of({
1253
- active: id
1254
- }),
1255
- selection: {
1256
- anchor: from
1187
+ };
1188
+ return [
1189
+ EditorView5.domEventHandlers({
1190
+ cut: handleTrack,
1191
+ copy: handleTrack
1192
+ }),
1193
+ // Track deleted comments.
1194
+ invertedEffects2.of((tr) => {
1195
+ const { comments: comments2 } = tr.startState.field(commentsState);
1196
+ const effects = [];
1197
+ tr.changes.iterChangedRanges((fromA, toA) => {
1198
+ for (const { comment: { id }, range: { from, to } } of comments2) {
1199
+ if (from < to && from >= fromA && to <= toA) {
1200
+ effects.push(restoreCommentEffect.of({
1201
+ id,
1202
+ from,
1203
+ to
1204
+ }));
1257
1205
  }
1258
- });
1259
- if (options.footnote) {
1260
- const tag = `[^${id}]`;
1261
- view.dispatch({
1262
- changes: {
1263
- from: head,
1264
- insert: tag
1265
- },
1266
- selection: {
1267
- anchor: head + tag.length
1206
+ }
1207
+ });
1208
+ return effects;
1209
+ }),
1210
+ // Handle paste or the undo of comment deletion.
1211
+ EditorView5.updateListener.of((update2) => {
1212
+ const restore = [];
1213
+ for (let i = 0; i < update2.transactions.length; i++) {
1214
+ const tr = update2.transactions[i];
1215
+ for (let j = 0; j < restore.length; j++) {
1216
+ restore[j] = mapTrackedComment(restore[j], tr.changes);
1217
+ }
1218
+ for (const effect of tr.effects) {
1219
+ if (effect.is(restoreCommentEffect)) {
1220
+ restore.push(effect.value);
1221
+ }
1222
+ }
1223
+ }
1224
+ if (tracked) {
1225
+ const paste = update2.transactions.find((tr) => tr.isUserEvent("input.paste"));
1226
+ if (paste) {
1227
+ let found = -1;
1228
+ paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1229
+ if (text.eq(tracked.text)) {
1230
+ for (let i = update2.transactions.indexOf(paste) + 1; i < update2.transactions.length; i++) {
1231
+ fromB = update2.transactions[i].changes.mapPos(fromB);
1232
+ }
1233
+ found = fromB;
1268
1234
  }
1269
1235
  });
1236
+ if (found > -1) {
1237
+ for (const moved of tracked.comments) {
1238
+ restore.push({
1239
+ id: moved.id,
1240
+ from: found + moved.from,
1241
+ to: found + moved.to
1242
+ });
1243
+ }
1244
+ }
1245
+ tracked = null;
1270
1246
  }
1271
- return true;
1272
1247
  }
1273
- }
1248
+ for (const comment of restore) {
1249
+ const { comments: comments2 } = update2.startState.field(commentsState);
1250
+ const exists = comments2.some((c) => c.comment.id === comment.id && c.range.from < c.range.to);
1251
+ if (!exists) {
1252
+ const cursor = Cursor.getCursorFromRange(update2.state, comment);
1253
+ onUpdate(comment.id, cursor);
1254
+ }
1255
+ }
1256
+ })
1257
+ ];
1258
+ };
1259
+ var mapTrackedComment = (comment, changes) => ({
1260
+ id: comment.id,
1261
+ from: changes.mapPos(comment.from, 1),
1262
+ to: changes.mapPos(comment.to, 1)
1263
+ });
1264
+ var restoreCommentEffect = StateEffect2.define({
1265
+ map: mapTrackedComment
1266
+ });
1267
+ var optionsFacet = Facet3.define({
1268
+ combine: (providers) => providers[0]
1269
+ });
1270
+ var createComment = (view) => {
1271
+ const options = view.state.facet(optionsFacet);
1272
+ const { from, to } = view.state.selection.main;
1273
+ if (from === to) {
1274
1274
  return false;
1275
- };
1276
- const bookmarksViewPlugin = ViewPlugin4.fromClass(class BookmarkViewPlugin {
1277
- static {
1278
- // Match markdown footnotes (option).
1279
- // https://www.markdownguide.org/extended-syntax/#footnotes
1280
- this.bookmarkMatcher = new MatchDecorator({
1281
- regexp: /\[\^(\w+)\]/g,
1282
- decoration: (match, view, pos) => {
1283
- const id = match[1];
1284
- return Decoration3.replace({
1285
- id,
1286
- widget: new BookmarkWidget(pos, id)
1287
- });
1275
+ }
1276
+ if (to === view.state.doc.length) {
1277
+ view.dispatch({
1278
+ changes: {
1279
+ from: to,
1280
+ insert: "\n"
1281
+ }
1282
+ });
1283
+ }
1284
+ const cursor = Cursor.getCursorFromRange(view.state, {
1285
+ from,
1286
+ to
1287
+ });
1288
+ if (cursor) {
1289
+ const id = options.onCreate?.(cursor, view.coordsAtPos(from));
1290
+ if (id) {
1291
+ view.dispatch({
1292
+ effects: setSelection.of({
1293
+ current: id
1294
+ }),
1295
+ selection: {
1296
+ anchor: to
1288
1297
  }
1289
1298
  });
1299
+ return true;
1290
1300
  }
1291
- constructor(view) {
1292
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
1293
- }
1294
- update(update2) {
1295
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update2, this.bookmarks);
1296
- }
1297
- }, {
1298
- decorations: (instance) => instance.bookmarks,
1299
- provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1300
- return view.plugin(plugin)?.bookmarks || Decoration3.none;
1301
- })
1302
- });
1301
+ }
1302
+ return false;
1303
+ };
1304
+ var comments = (options = {}) => {
1305
+ const { key: shortcut = "meta-'" } = options;
1306
+ const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1303
1307
  return [
1304
- bookmarksViewPlugin,
1305
- commentsStateField,
1306
- highlightDecorations,
1307
- styles3,
1308
+ optionsFacet.of(options),
1309
+ commentsState,
1310
+ commentsDecorations,
1311
+ styles2,
1308
1312
  //
1309
1313
  // Keymap.
1310
1314
  //
1311
1315
  options.onCreate ? keymap3.of([
1312
1316
  {
1313
- key: options?.key ?? "meta-'",
1314
- run: callbackWrapper(createCommentThread)
1317
+ key: shortcut,
1318
+ run: callbackWrapper(createComment)
1315
1319
  }
1316
1320
  ]) : [],
1317
1321
  //
@@ -1327,7 +1331,7 @@ var comments = (options = {}) => {
1327
1331
  above: true,
1328
1332
  create: () => {
1329
1333
  const el = document.createElement("div");
1330
- options.onHover?.(el);
1334
+ options.onHover(el, shortcut);
1331
1335
  return {
1332
1336
  dom: el,
1333
1337
  offset: {
@@ -1340,366 +1344,100 @@ var comments = (options = {}) => {
1340
1344
  }
1341
1345
  return null;
1342
1346
  }, {
1343
- hideOnChange: true,
1347
+ // TODO(burdon): Hide on change triggered immediately?
1348
+ // hideOnChange: true,
1344
1349
  hoverTime: 1e3
1345
1350
  }) : [],
1346
1351
  //
1347
- // Monitor cursor movement and text updates.
1348
- // TODO(burdon): Is there a better (finer grained) way to do this?
1352
+ // Track deleted ranges and update ranges for decorations.
1349
1353
  //
1350
1354
  EditorView5.updateListener.of(({ view, state, changes }) => {
1351
- const cursorConverter3 = view.state.facet(CursorConverter);
1352
- {
1353
- let mod = false;
1354
- const { active, ranges } = state.field(commentsStateField);
1355
- changes.iterChanges((from, to, from2, to2) => {
1356
- ranges.forEach((range) => {
1357
- if (from2 === to2) {
1358
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1359
- if (!newRange || newRange.to - newRange.from === 0) {
1360
- log2.info("deleted comment", {
1361
- thread: range.id
1362
- }, {
1363
- F: __dxlog_file3,
1364
- L: 358,
1365
- S: void 0,
1366
- C: (f, a) => f(...a)
1367
- });
1368
- }
1355
+ let mod = false;
1356
+ const { comments: comments2, ...value } = state.field(commentsState);
1357
+ changes.iterChanges((from, to, from2, to2) => {
1358
+ comments2.forEach(({ comment, range }) => {
1359
+ if (from2 === to2) {
1360
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1361
+ if (!newRange || newRange.to - newRange.from === 0) {
1362
+ options.onDelete?.(comment.id);
1369
1363
  }
1370
- if (from <= range.to) {
1371
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1372
- Object.assign(range, newRange);
1373
- mod = true;
1374
- }
1375
- });
1364
+ }
1365
+ if (from <= range.to) {
1366
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1367
+ Object.assign(range, newRange);
1368
+ mod = true;
1369
+ }
1370
+ });
1371
+ });
1372
+ if (mod) {
1373
+ view.dispatch({
1374
+ effects: setCommentState.of({
1375
+ comments: comments2,
1376
+ ...value
1377
+ })
1376
1378
  });
1377
- if (mod) {
1378
- view.dispatch({
1379
- effects: setCommentState.of({
1380
- active,
1381
- ranges
1382
- })
1383
- });
1384
- }
1385
1379
  }
1386
- {
1387
- const { head } = state.selection.main;
1388
- let min = Infinity;
1389
- const { active, closest, ranges } = state.field(commentsStateField);
1390
- const selected = {
1391
- active: void 0,
1392
- closest: void 0
1393
- };
1394
- ranges.forEach((comment) => {
1395
- const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1396
- if (head >= comment.from && head <= comment.to) {
1397
- selected.active = comment.id;
1398
- }
1380
+ }),
1381
+ //
1382
+ // Track selection/proximity.
1383
+ //
1384
+ EditorView5.updateListener.of(({ view, state }) => {
1385
+ let min = Infinity;
1386
+ const { selection: { current, closest }, comments: comments2 } = state.field(commentsState);
1387
+ const { head } = state.selection.main;
1388
+ const selection = {};
1389
+ comments2.forEach(({ comment, range }) => {
1390
+ if (head >= range.from && head <= range.to) {
1391
+ selection.current = comment.id;
1392
+ selection.closest = void 0;
1393
+ }
1394
+ if (!selection.current) {
1395
+ const d = Math.min(Math.abs(head - range.from), Math.abs(head - range.to));
1399
1396
  if (d < min) {
1400
- selected.closest = comment.id;
1397
+ selection.closest = comment.id;
1401
1398
  min = d;
1402
1399
  }
1403
- });
1404
- if (selected.active !== active || selected.closest !== closest) {
1405
- view.dispatch({
1406
- effects: setSelection.of(selected)
1407
- });
1408
- handleSelect({
1409
- ...selected,
1410
- ranges: ranges.map((range) => ({
1411
- ...range,
1412
- location: view.coordsAtPos(range.from)
1413
- }))
1414
- });
1415
1400
  }
1416
- }
1417
- })
1418
- ];
1419
- };
1420
- var getCursorFromRange = (cursorConverter3, range) => {
1421
- const from = cursorConverter3.toCursor(range.from);
1422
- const to = cursorConverter3.toCursor(range.to, -1);
1423
- return [
1424
- from,
1425
- to
1426
- ].join(":");
1427
- };
1428
- var getRangeFromCursor = (cursorConverter3, cursor) => {
1429
- const parts = cursor.split(":");
1430
- const from = cursorConverter3.fromCursor(parts[0]);
1431
- const to = cursorConverter3.fromCursor(parts[1]);
1432
- return from !== void 0 && to !== void 0 ? {
1433
- from,
1434
- to
1435
- } : void 0;
1436
- };
1437
-
1438
- // packages/ui/react-ui-editor/src/extensions/hr.ts
1439
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin5, WidgetType as WidgetType3 } from "@codemirror/view";
1440
- var styles4 = EditorView6.baseTheme({
1441
- "& .cm-hr": {
1442
- // TODO(burdon): ???
1443
- // Note that block-level decorations should not have vertical margins,
1444
- borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1445
- }
1446
- });
1447
- var HorizontalRuleWidget = class extends WidgetType3 {
1448
- constructor(_pos) {
1449
- super();
1450
- this._pos = _pos;
1451
- }
1452
- eq(other) {
1453
- return this._pos === other._pos;
1454
- }
1455
- toDOM(view) {
1456
- const el = document.createElement("div");
1457
- el.className = "cm-hr";
1458
- return el;
1459
- }
1460
- };
1461
- var placeholderMatcher = new MatchDecorator2({
1462
- regexp: /^---$/g,
1463
- decoration: (match, view, pos) => Decoration4.replace({
1464
- widget: new HorizontalRuleWidget(pos)
1465
- })
1466
- });
1467
- var hr = () => [
1468
- styles4,
1469
- ViewPlugin5.fromClass(class {
1470
- constructor(view) {
1471
- this.rules = placeholderMatcher.createDeco(view);
1472
- }
1473
- update(update2) {
1474
- this.rules = placeholderMatcher.updateDeco(update2, this.rules);
1475
- }
1476
- }, {
1477
- decorations: (instance) => instance.rules,
1478
- // TODO(burdon): Is this really atomic (cursor can move into ---).
1479
- provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1480
- return view.plugin(plugin)?.rules || Decoration4.none;
1481
- })
1482
- })
1483
- ];
1484
-
1485
- // packages/ui/react-ui-editor/src/extensions/image.ts
1486
- import { syntaxTree } from "@codemirror/language";
1487
- import { StateField as StateField3 } from "@codemirror/state";
1488
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1489
- var image = (options = {}) => {
1490
- return StateField3.define({
1491
- create: (state) => {
1492
- return Decoration5.set(buildDecorations(0, state.doc.length, state));
1493
- },
1494
- update: (value, tr) => {
1495
- if (!tr.docChanged && !tr.selection) {
1496
- return value;
1497
- }
1498
- const cursor = tr.state.selection.main.head;
1499
- const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
1500
- let from = Math.min(cursor, oldCursor);
1501
- let to = Math.max(cursor, oldCursor);
1502
- tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
1503
- from = Math.min(from, fromB);
1504
- to = Math.max(to, toB);
1505
- });
1506
- from = tr.state.doc.lineAt(from).from;
1507
- to = tr.state.doc.lineAt(to).to;
1508
- return value.map(tr.changes).update({
1509
- filterFrom: from,
1510
- filterTo: to,
1511
- filter: () => false,
1512
- add: buildDecorations(from, to, tr.state)
1513
1401
  });
1514
- },
1515
- provide: (field) => EditorView7.decorations.from(field)
1516
- });
1517
- };
1518
- var buildDecorations = (from, to, state) => {
1519
- const decorations = [];
1520
- const cursor = state.selection.main.head;
1521
- syntaxTree(state).iterate({
1522
- enter: (node) => {
1523
- if (node.name === "Image") {
1524
- const urlNode = node.node.getChild("URL");
1525
- if (urlNode) {
1526
- const hide = state.readOnly || cursor < node.from || cursor > node.to;
1527
- const url = state.sliceDoc(urlNode.from, urlNode.to);
1528
- decorations.push(Decoration5.replace({
1529
- block: true,
1530
- widget: new ImageWidget(url)
1531
- }).range(hide ? node.from : node.to, node.to));
1532
- }
1533
- }
1534
- },
1535
- from,
1536
- to
1537
- });
1538
- return decorations;
1539
- };
1540
- var ImageWidget = class extends WidgetType4 {
1541
- constructor(_url) {
1542
- super();
1543
- this._url = _url;
1544
- }
1545
- eq(other) {
1546
- return this._url === other._url;
1547
- }
1548
- toDOM(view) {
1549
- const img = document.createElement("img");
1550
- img.setAttribute("src", this._url);
1551
- img.setAttribute("class", "cm-image");
1552
- img.onload = () => img.classList.add("cm-loaded-image");
1553
- return img;
1554
- }
1555
- };
1556
-
1557
- // packages/ui/react-ui-editor/src/extensions/link.ts
1558
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1559
- import { RangeSetBuilder } from "@codemirror/state";
1560
- import { Decoration as Decoration6, WidgetType as WidgetType5, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
1561
- import { tooltipContent } from "@dxos/react-ui-theme";
1562
- var link = (options = {}) => {
1563
- const extensions = [
1564
- ViewPlugin6.fromClass(class {
1565
- constructor(view) {
1566
- this.decorations = buildDecorations2(view, options);
1567
- }
1568
- update(update2) {
1569
- if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1570
- this.decorations = buildDecorations2(update2.view, options);
1571
- }
1402
+ if (selection.current !== current || selection.closest !== closest) {
1403
+ view.dispatch({
1404
+ effects: setSelection.of(selection)
1405
+ });
1406
+ handleSelect({
1407
+ selection,
1408
+ comments: comments2.map(({ comment, range }) => ({
1409
+ comment,
1410
+ range,
1411
+ location: view.coordsAtPos(range.from)
1412
+ }))
1413
+ });
1572
1414
  }
1573
- }, {
1574
- decorations: (v) => v.decorations
1575
- })
1415
+ }),
1416
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
1576
1417
  ];
1577
- if (options.onHover) {
1578
- extensions.push(
1579
- // https://codemirror.net/examples/tooltip
1580
- // https://codemirror.net/docs/ref/#view.hoverTooltip
1581
- // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1582
- hoverTooltip2((view, pos, side) => {
1583
- const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1584
- let link2 = null;
1585
- for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1586
- link2 = node.name === "Link" ? node : null;
1587
- }
1588
- const url = link2 && link2.getChild("URL");
1589
- if (!url || !link2) {
1590
- return null;
1591
- }
1592
- const urlText = view.state.sliceDoc(url.from, url.to);
1593
- return {
1594
- pos: link2.from,
1595
- end: link2.to,
1596
- above: true,
1597
- create: () => {
1598
- const el = document.createElement("div");
1599
- el.className = tooltipContent({}, "pli-2 plb-1");
1600
- options.onHover(el, urlText);
1601
- return {
1602
- dom: el,
1603
- offset: {
1604
- x: 0,
1605
- y: 4
1606
- }
1607
- };
1608
- }
1609
- };
1610
- })
1611
- );
1612
- }
1613
- return extensions;
1614
- };
1615
- var LinkButton = class extends WidgetType5 {
1616
- constructor(_url, _onAttach) {
1617
- super();
1618
- this._url = _url;
1619
- this._onAttach = _onAttach;
1620
- }
1621
- eq(other) {
1622
- return this._url === other._url;
1623
- }
1624
- toDOM(view) {
1625
- const el = document.createElement("span");
1626
- this._onAttach(el, this._url);
1627
- return el;
1628
- }
1629
1418
  };
1630
- var LinkText = class extends WidgetType5 {
1631
- constructor(_text, _url) {
1632
- super();
1633
- this._text = _text;
1634
- this._url = _url;
1635
- }
1636
- eq(other) {
1637
- return this._url === other._url;
1638
- }
1639
- toDOM(view) {
1640
- const link2 = document.createElement("a");
1641
- link2.setAttribute("class", "cm-link");
1642
- link2.textContent = this._text;
1643
- if (this._url) {
1644
- link2.setAttribute("rel", "noreferrer");
1645
- link2.setAttribute("target", "_blank");
1646
- link2.href = this._url;
1647
- } else {
1648
- link2.onclick = () => {
1649
- view.dispatch({
1650
- selection: {
1651
- anchor: view.posAtDOM(link2)
1652
- }
1653
- });
1654
- };
1419
+ var useComments = (view, comments2 = []) => {
1420
+ useEffect(() => {
1421
+ if (view) {
1422
+ view.dispatch({
1423
+ effects: setComments.of(comments2)
1424
+ });
1655
1425
  }
1656
- return link2;
1657
- }
1658
- };
1659
- var buildDecorations2 = (view, options) => {
1660
- const builder = new RangeSetBuilder();
1661
- const { state } = view;
1662
- const cursor = state.selection.main.head;
1663
- for (const { from, to } of view.visibleRanges) {
1664
- syntaxTree2(state).iterate({
1665
- enter: (node) => {
1666
- if (node.name === "Link") {
1667
- const marks2 = node.node.getChildren("LinkMark");
1668
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1669
- const urlNode = node.node.getChild("URL");
1670
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1671
- if (!url) {
1672
- return false;
1673
- }
1674
- if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
1675
- builder.add(node.from, node.to, Decoration6.replace({
1676
- widget: new LinkText(text, options.link ? url : void 0)
1677
- }));
1678
- }
1679
- if (options.onRender) {
1680
- builder.add(node.to, node.to, Decoration6.replace({
1681
- widget: new LinkButton(url, options.onRender)
1682
- }));
1683
- }
1684
- return false;
1685
- }
1686
- },
1687
- from,
1688
- to
1689
- });
1690
- }
1691
- return builder.finish();
1426
+ }, [
1427
+ view,
1428
+ comments2
1429
+ ]);
1692
1430
  };
1693
1431
 
1694
1432
  // packages/ui/react-ui-editor/src/extensions/listener.ts
1695
- import { EditorView as EditorView8 } from "@codemirror/view";
1433
+ import { EditorView as EditorView6 } from "@codemirror/view";
1696
1434
  var listener = ({ onFocus, onChange }) => {
1697
1435
  const extensions = [];
1698
- onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1436
+ onFocus && extensions.push(EditorView6.focusChangeEffect.of((_, focused) => {
1699
1437
  onFocus(focused);
1700
1438
  return null;
1701
1439
  }));
1702
- onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1440
+ onChange && extensions.push(EditorView6.updateListener.of((update2) => {
1703
1441
  onChange(update2.state.doc.toString());
1704
1442
  }));
1705
1443
  return extensions;
@@ -1707,17 +1445,18 @@ var listener = ({ onFocus, onChange }) => {
1707
1445
 
1708
1446
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1709
1447
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1710
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1711
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
1448
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1449
+ import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1712
1450
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1713
1451
  import { languages } from "@codemirror/language-data";
1714
1452
  import { searchKeymap } from "@codemirror/search";
1715
1453
  import { EditorState } from "@codemirror/state";
1716
1454
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1717
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1455
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView7, highlightActiveLine as highlightActiveLine2, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1456
+ import { isNotFalsy as isNotFalsy2 } from "@dxos/util";
1718
1457
 
1719
1458
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1720
- import { markdownLanguage } from "@codemirror/lang-markdown";
1459
+ import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
1721
1460
  import { HighlightStyle } from "@codemirror/language";
1722
1461
  import { tags, styleTags, Tag } from "@lezer/highlight";
1723
1462
  import { Table } from "@lezer/markdown";
@@ -1727,7 +1466,6 @@ var markdownTags = {
1727
1466
  CodeText: Tag.define(),
1728
1467
  EmphasisMark: Tag.define(),
1729
1468
  HeaderMark: Tag.define(),
1730
- // HorizontalRule: Tag.define(),
1731
1469
  InlineCode: Tag.define(),
1732
1470
  LinkLabel: Tag.define(),
1733
1471
  LinkReference: Tag.define(),
@@ -1812,7 +1550,7 @@ var markdownHighlightStyle = (readonly) => {
1812
1550
  markdownTags.QuoteMark,
1813
1551
  markdownTags.EmphasisMark
1814
1552
  ],
1815
- class: readonly ? "hidden" : mark
1553
+ class: mark
1816
1554
  },
1817
1555
  // E.g., code block language (after ```).
1818
1556
  {
@@ -1820,7 +1558,7 @@ var markdownHighlightStyle = (readonly) => {
1820
1558
  tags.function(tags.variableName),
1821
1559
  tags.labelName
1822
1560
  ],
1823
- class: readonly ? "hidden" : codeMark
1561
+ class: codeMark
1824
1562
  },
1825
1563
  {
1826
1564
  tag: [
@@ -1831,27 +1569,27 @@ var markdownHighlightStyle = (readonly) => {
1831
1569
  // Headings.
1832
1570
  {
1833
1571
  tag: tags.heading1,
1834
- class: heading(1, readonly)
1572
+ class: heading(1)
1835
1573
  },
1836
1574
  {
1837
1575
  tag: tags.heading2,
1838
- class: heading(2, readonly)
1576
+ class: heading(2)
1839
1577
  },
1840
1578
  {
1841
1579
  tag: tags.heading3,
1842
- class: heading(3, readonly)
1580
+ class: heading(3)
1843
1581
  },
1844
1582
  {
1845
1583
  tag: tags.heading4,
1846
- class: heading(4, readonly)
1584
+ class: heading(4)
1847
1585
  },
1848
1586
  {
1849
1587
  tag: tags.heading5,
1850
- class: heading(5, readonly)
1588
+ class: heading(5)
1851
1589
  },
1852
1590
  {
1853
1591
  tag: tags.heading6,
1854
- class: heading(6, readonly)
1592
+ class: heading(6)
1855
1593
  },
1856
1594
  // Emphasis.
1857
1595
  {
@@ -1877,7 +1615,7 @@ var markdownHighlightStyle = (readonly) => {
1877
1615
  // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1878
1616
  // when a language is specified. In this case, the syntax highlighting extensions will colorize
1879
1617
  // the code, but all other CSS properties will be inherited.
1880
- // IMPORTANT: Therefore, the fenced code block will use the base editor font.
1618
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font unless changed by an extension.
1881
1619
  {
1882
1620
  tag: [
1883
1621
  markdownTags.CodeText,
@@ -1891,12 +1629,6 @@ var markdownHighlightStyle = (readonly) => {
1891
1629
  ],
1892
1630
  class: blockquote
1893
1631
  },
1894
- // TODO(burdon): Replace with extension.
1895
- // {
1896
- // tag: [markdownTags.HorizontalRule],
1897
- // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1898
- // },
1899
- // TODO(burdon): Only if being edited.
1900
1632
  {
1901
1633
  tag: [
1902
1634
  markdownTags.TableCell
@@ -1904,7 +1636,7 @@ var markdownHighlightStyle = (readonly) => {
1904
1636
  class: "font-mono"
1905
1637
  }
1906
1638
  ], {
1907
- scope: markdownLanguage,
1639
+ scope: markdownLanguage2,
1908
1640
  all: {
1909
1641
  fontFamily: getToken("fontFamily.body", []).join(",")
1910
1642
  }
@@ -1912,19 +1644,19 @@ var markdownHighlightStyle = (readonly) => {
1912
1644
  };
1913
1645
 
1914
1646
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1915
- var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1647
+ var createMarkdownExtensions = ({ themeMode, placeholder: _placeholder, lineWrapping = true } = {}) => {
1916
1648
  return [
1649
+ lineWrapping && EditorView7.lineWrapping,
1917
1650
  EditorState.allowMultipleSelections.of(true),
1918
1651
  EditorState.tabSize.of(2),
1919
- EditorView9.lineWrapping,
1920
- customKeymap(),
1652
+ // https://github.com/codemirror/basic-setup
1921
1653
  bracketMatching2(),
1922
1654
  closeBrackets2(),
1923
1655
  crosshairCursor(),
1924
1656
  dropCursor(),
1925
1657
  drawSelection2(),
1926
- highlightActiveLine(),
1927
- history(),
1658
+ highlightActiveLine2(),
1659
+ history2(),
1928
1660
  indentOnInput(),
1929
1661
  _placeholder && placeholder2(_placeholder),
1930
1662
  // Main extension.
@@ -1936,7 +1668,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1936
1668
  // NOTE: This extends the parser; it doesn't affect rendering.
1937
1669
  // https://github.github.com/gfm
1938
1670
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1939
- base: markdownLanguage2,
1671
+ base: markdownLanguage3,
1940
1672
  // Languages for syntax highlighting fenced code blocks.
1941
1673
  codeLanguages: languages,
1942
1674
  // Parser extensions.
@@ -1948,58 +1680,602 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1948
1680
  // https://github.com/codemirror/theme-one-dark
1949
1681
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1950
1682
  // Custom styles.
1951
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1952
- ].filter(Boolean);
1953
- };
1954
- var customKeymap = () => keymap4.of([
1955
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1956
- indentWithTab,
1957
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1958
- ...standardKeymap,
1959
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1960
- ...historyKeymap,
1961
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1962
- ...closeBracketsKeymap,
1963
- // https://codemirror.net/docs/ref/#search.searchKeymap
1964
- ...searchKeymap
1965
- ]);
1966
-
1967
- // packages/ui/react-ui-editor/src/extensions/mention.ts
1968
- import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1969
- var mention = ({ onSearch }) => {
1970
- return autocompletion2({
1971
- closeOnBlur: false,
1972
- override: [
1973
- (context) => {
1974
- const match = context.matchBefore(/@(\w+)?/);
1975
- if (!match || match.from === match.to && !context.explicit) {
1976
- return null;
1977
- }
1978
- console.log(match);
1979
- return {
1980
- from: match.from,
1981
- options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1982
- label: `@${value}`
1983
- }))
1984
- };
1985
- }
1986
- ]
1987
- });
1683
+ syntaxHighlighting2(markdownHighlightStyle()),
1684
+ keymap4.of([
1685
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1686
+ indentWithTab,
1687
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1688
+ ...closeBracketsKeymap,
1689
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
1690
+ ...historyKeymap,
1691
+ // https://codemirror.net/docs/ref/#search.searchKeymap
1692
+ ...searchKeymap,
1693
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
1694
+ ...standardKeymap
1695
+ ])
1696
+ ].filter(isNotFalsy2);
1988
1697
  };
1989
1698
 
1990
- // packages/ui/react-ui-editor/src/extensions/table.ts
1699
+ // packages/ui/react-ui-editor/src/extensions/markdown/code.ts
1700
+ import { syntaxTree } from "@codemirror/language";
1701
+ import { RangeSetBuilder } from "@codemirror/state";
1702
+ import { ViewPlugin as ViewPlugin3, EditorView as EditorView8, Decoration as Decoration3, WidgetType as WidgetType2 } from "@codemirror/view";
1703
+ import { mx as mx2 } from "@dxos/react-ui-theme";
1704
+ var styles3 = EditorView8.baseTheme({
1705
+ "& .cm-code": {
1706
+ fontFamily: getToken("fontFamily.mono", []).join(",")
1707
+ },
1708
+ "& .cm-codeblock": {
1709
+ width: "100%",
1710
+ paddingInline: "1rem !important",
1711
+ position: "relative",
1712
+ "&::after": {
1713
+ content: '""',
1714
+ position: "absolute",
1715
+ inset: 0
1716
+ }
1717
+ },
1718
+ "&light .cm-codeblock": {
1719
+ "&::after": {
1720
+ background: getToken("extend.semanticColors.input.light"),
1721
+ mixBlendMode: "darken"
1722
+ }
1723
+ },
1724
+ "&dark .cm-codeblock": {
1725
+ "&::after": {
1726
+ background: getToken("extend.semanticColors.input.dark"),
1727
+ mixBlendMode: "lighten"
1728
+ }
1729
+ },
1730
+ "& .cm-codeblock-first": {
1731
+ display: "inline-block",
1732
+ "&::after": {
1733
+ borderTopLeftRadius: ".5rem",
1734
+ borderTopRightRadius: ".5rem"
1735
+ }
1736
+ },
1737
+ "& .cm-codeblock-last": {
1738
+ display: "inline-block",
1739
+ "&::after": {
1740
+ borderBottomLeftRadius: ".5rem",
1741
+ borderBottomRightRadius: ".5rem"
1742
+ }
1743
+ }
1744
+ });
1745
+ var getLineRange = (lines, from, to) => {
1746
+ const start = lines.findIndex((line) => line.from >= from);
1747
+ const end = lines.findIndex((line) => line.to >= to);
1748
+ return [
1749
+ start,
1750
+ end
1751
+ ];
1752
+ };
1753
+ var LineWidget = class extends WidgetType2 {
1754
+ constructor(_className) {
1755
+ super();
1756
+ this._className = _className;
1757
+ }
1758
+ toDOM() {
1759
+ const el = document.createElement("span");
1760
+ el.innerHTML = "&nbsp;";
1761
+ el.className = mx2("cm-code cm-codeblock", this._className);
1762
+ return el;
1763
+ }
1764
+ };
1765
+ var top = new LineWidget("cm-codeblock-first");
1766
+ var bottom = new LineWidget("cm-codeblock-last");
1767
+ var buildDecorations = (view) => {
1768
+ const builder = new RangeSetBuilder();
1769
+ const { state } = view;
1770
+ const blocks = view.viewportLineBlocks;
1771
+ const cursor = state.selection.main.head;
1772
+ for (const { from, to } of view.visibleRanges) {
1773
+ syntaxTree(state).iterate({
1774
+ enter: (node) => {
1775
+ if (node.name === "FencedCode") {
1776
+ const edit = !view.state.readOnly && cursor >= node.from && cursor <= node.to;
1777
+ const range = getLineRange(blocks, node.from, node.to);
1778
+ for (let i = range[0]; i <= range[1]; i++) {
1779
+ const block = blocks[i];
1780
+ if (!edit && (i === range[0] || i === range[1])) {
1781
+ builder.add(block.from, block.to, Decoration3.replace({
1782
+ widget: i === range[0] ? top : bottom
1783
+ }));
1784
+ } else {
1785
+ builder.add(block.from, block.from, Decoration3.line({
1786
+ class: mx2("cm-code cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1787
+ }));
1788
+ }
1789
+ }
1790
+ }
1791
+ },
1792
+ from,
1793
+ to
1794
+ });
1795
+ }
1796
+ return builder.finish();
1797
+ };
1798
+ var code2 = () => {
1799
+ return [
1800
+ ViewPlugin3.fromClass(class {
1801
+ constructor(view) {
1802
+ this.decorations = buildDecorations(view);
1803
+ }
1804
+ update(update2) {
1805
+ this.decorations = buildDecorations(update2.view);
1806
+ }
1807
+ }, {
1808
+ decorations: (value) => value.decorations
1809
+ }),
1810
+ styles3
1811
+ ];
1812
+ };
1813
+
1814
+ // packages/ui/react-ui-editor/src/extensions/markdown/formatting.ts
1815
+ import { snippet } from "@codemirror/autocomplete";
1816
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1817
+ import { RangeSetBuilder as RangeSetBuilder2 } from "@codemirror/state";
1818
+ import { Decoration as Decoration4, keymap as keymap5, ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1819
+ var setHeading = (level) => (view) => {
1820
+ const { selection: { ranges }, doc } = view.state;
1821
+ const changes = [];
1822
+ for (const range of ranges) {
1823
+ const { number } = doc.lineAt(range.anchor);
1824
+ const { from, to } = doc.line(number);
1825
+ const line = doc.sliceString(from, to);
1826
+ const [_, marks, spaces] = line.match(/(#+)(\s+)/) ?? [];
1827
+ const current = marks?.length ?? 0;
1828
+ if (level !== current) {
1829
+ changes.push({
1830
+ from,
1831
+ to: from + current + (spaces?.length ?? 0),
1832
+ insert: "#".repeat(level) + (level > 0 ? " " : "")
1833
+ });
1834
+ }
1835
+ }
1836
+ if (changes.length) {
1837
+ view.dispatch({
1838
+ changes
1839
+ });
1840
+ }
1841
+ return true;
1842
+ };
1843
+ var toggleStyle = (mark2) => (view) => {
1844
+ const { ranges } = view.state.selection;
1845
+ for (const range of ranges) {
1846
+ if (range.from === range.to) {
1847
+ return false;
1848
+ }
1849
+ view.dispatch({
1850
+ changes: [
1851
+ {
1852
+ from: range.from,
1853
+ insert: mark2
1854
+ },
1855
+ {
1856
+ from: range.to,
1857
+ insert: mark2
1858
+ }
1859
+ ]
1860
+ });
1861
+ }
1862
+ return true;
1863
+ };
1864
+ var snippets = {
1865
+ codeblock: snippet([
1866
+ "```#{lang}",
1867
+ " #{}",
1868
+ "```"
1869
+ ].join("\n")),
1870
+ table: snippet([
1871
+ "| #{col1} | #{col2} |",
1872
+ "| ---- | ---- |",
1873
+ "| #{val1} | #{val2} |",
1874
+ "| #{val3} | #{val4} |"
1875
+ ].join("\n"))
1876
+ };
1877
+ var insertCodeblock = (view) => {
1878
+ const { selection: { main }, doc } = view.state;
1879
+ const { number } = doc.lineAt(main.anchor);
1880
+ const { from } = doc.line(number);
1881
+ snippets.codeblock(view, null, from, from);
1882
+ };
1883
+ var insertTable = (view) => {
1884
+ const { selection: { main }, doc } = view.state;
1885
+ const { number } = doc.lineAt(main.anchor);
1886
+ const { from } = doc.line(number);
1887
+ snippets.table(view, null, from, from);
1888
+ };
1889
+ var toggleBold = toggleStyle("**");
1890
+ var toggleItalic = toggleStyle("_");
1891
+ var toggleStrikethrough = toggleStyle("~~");
1892
+ var toggleList = (view) => {
1893
+ };
1894
+ var formatting = (options = {}) => {
1895
+ return [
1896
+ keymap5.of([
1897
+ {
1898
+ key: "meta-b",
1899
+ run: toggleBold
1900
+ }
1901
+ ]),
1902
+ styling()
1903
+ ];
1904
+ };
1905
+ var styling = () => {
1906
+ const buildDecorations7 = (view) => {
1907
+ const builder = new RangeSetBuilder2();
1908
+ const { state } = view;
1909
+ const cursor = state.selection.main.head;
1910
+ const replace = (node, marks) => {
1911
+ if (cursor <= node.from || cursor >= node.to) {
1912
+ for (const mark2 of marks) {
1913
+ builder.add(mark2.from, mark2.to, Decoration4.replace({}));
1914
+ }
1915
+ }
1916
+ };
1917
+ for (const { from, to } of view.visibleRanges) {
1918
+ syntaxTree2(state).iterate({
1919
+ enter: (node) => {
1920
+ switch (node.name) {
1921
+ case "Emphasis":
1922
+ case "StrongEmphasis": {
1923
+ const marks = node.node.getChildren("EmphasisMark");
1924
+ replace(node, marks);
1925
+ break;
1926
+ }
1927
+ case "Strikethrough": {
1928
+ const marks = node.node.getChildren("StrikethroughMark");
1929
+ replace(node, marks);
1930
+ break;
1931
+ }
1932
+ }
1933
+ },
1934
+ from,
1935
+ to
1936
+ });
1937
+ }
1938
+ return builder.finish();
1939
+ };
1940
+ return [
1941
+ ViewPlugin4.fromClass(class {
1942
+ constructor(view) {
1943
+ this.decorations = buildDecorations7(view);
1944
+ }
1945
+ update(update2) {
1946
+ this.decorations = buildDecorations7(update2.view);
1947
+ }
1948
+ }, {
1949
+ decorations: (value) => value.decorations
1950
+ })
1951
+ ];
1952
+ };
1953
+
1954
+ // packages/ui/react-ui-editor/src/extensions/markdown/heading.ts
1991
1955
  import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1992
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1993
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType6 } from "@codemirror/view";
1956
+ import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
1957
+ import { Decoration as Decoration5, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1958
+ var buildDecorations2 = (view) => {
1959
+ const builder = new RangeSetBuilder3();
1960
+ const { state } = view;
1961
+ const cursor = state.selection.main.head;
1962
+ for (const { from, to } of view.visibleRanges) {
1963
+ syntaxTree3(state).iterate({
1964
+ enter: (node) => {
1965
+ switch (node.name) {
1966
+ case "ATXHeading1":
1967
+ case "ATXHeading2":
1968
+ case "ATXHeading3":
1969
+ case "ATXHeading4":
1970
+ case "ATXHeading5":
1971
+ case "ATXHeading6": {
1972
+ const mark2 = node.node.getChild("HeaderMark");
1973
+ if (mark2) {
1974
+ if (!view.hasFocus || view.state.readOnly || cursor < node.from || cursor > node.to) {
1975
+ builder.add(mark2.from, mark2.to + 1, Decoration5.replace({}));
1976
+ }
1977
+ }
1978
+ }
1979
+ }
1980
+ },
1981
+ from,
1982
+ to
1983
+ });
1984
+ }
1985
+ return builder.finish();
1986
+ };
1987
+ var heading2 = () => {
1988
+ return [
1989
+ ViewPlugin5.fromClass(class {
1990
+ constructor(view) {
1991
+ this.decorations = buildDecorations2(view);
1992
+ }
1993
+ update(update2) {
1994
+ this.decorations = buildDecorations2(update2.view);
1995
+ }
1996
+ }, {
1997
+ decorations: (value) => value.decorations
1998
+ })
1999
+ ];
2000
+ };
2001
+
2002
+ // packages/ui/react-ui-editor/src/extensions/markdown/hr.ts
2003
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2004
+ import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
2005
+ import { Decoration as Decoration6, EditorView as EditorView9, ViewPlugin as ViewPlugin6, WidgetType as WidgetType3 } from "@codemirror/view";
2006
+ var styles4 = EditorView9.baseTheme({
2007
+ "& .cm-hr": {
2008
+ // Note that block-level decorations should not have vertical margins,
2009
+ borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
2010
+ }
2011
+ });
2012
+ var HorizontalRuleWidget = class extends WidgetType3 {
2013
+ toDOM() {
2014
+ const el = document.createElement("div");
2015
+ el.className = "cm-hr";
2016
+ return el;
2017
+ }
2018
+ };
2019
+ var decoration = Decoration6.replace({
2020
+ widget: new HorizontalRuleWidget()
2021
+ });
2022
+ var buildDecorations3 = (view) => {
2023
+ const builder = new RangeSetBuilder4();
2024
+ const { state } = view;
2025
+ const cursor = state.selection.main.head;
2026
+ for (const { from, to } of view.visibleRanges) {
2027
+ syntaxTree4(state).iterate({
2028
+ enter: (node) => {
2029
+ if (node.name === "HorizontalRule") {
2030
+ if (cursor <= node.from || cursor >= node.to) {
2031
+ builder.add(node.from, node.to, decoration);
2032
+ }
2033
+ }
2034
+ },
2035
+ from,
2036
+ to
2037
+ });
2038
+ }
2039
+ return builder.finish();
2040
+ };
2041
+ var hr = () => {
2042
+ return [
2043
+ ViewPlugin6.fromClass(class {
2044
+ constructor(view) {
2045
+ this.decorations = buildDecorations3(view);
2046
+ }
2047
+ update(update2) {
2048
+ this.decorations = buildDecorations3(update2.view);
2049
+ }
2050
+ }, {
2051
+ decorations: (value) => value.decorations
2052
+ }),
2053
+ styles4
2054
+ ];
2055
+ };
2056
+
2057
+ // packages/ui/react-ui-editor/src/extensions/markdown/image.ts
2058
+ import { syntaxTree as syntaxTree5 } from "@codemirror/language";
2059
+ import { StateField as StateField3 } from "@codemirror/state";
2060
+ import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType4 } from "@codemirror/view";
2061
+ var image = (options = {}) => {
2062
+ return StateField3.define({
2063
+ create: (state) => {
2064
+ return Decoration7.set(buildDecorations4(0, state.doc.length, state));
2065
+ },
2066
+ update: (value, tr) => {
2067
+ if (!tr.docChanged && !tr.selection) {
2068
+ return value;
2069
+ }
2070
+ const cursor = tr.state.selection.main.head;
2071
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
2072
+ let from = Math.min(cursor, oldCursor);
2073
+ let to = Math.max(cursor, oldCursor);
2074
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
2075
+ from = Math.min(from, fromB);
2076
+ to = Math.max(to, toB);
2077
+ });
2078
+ from = tr.state.doc.lineAt(from).from;
2079
+ to = tr.state.doc.lineAt(to).to;
2080
+ return value.map(tr.changes).update({
2081
+ filterFrom: from,
2082
+ filterTo: to,
2083
+ filter: () => false,
2084
+ add: buildDecorations4(from, to, tr.state)
2085
+ });
2086
+ },
2087
+ provide: (field) => EditorView10.decorations.from(field)
2088
+ });
2089
+ };
2090
+ var buildDecorations4 = (from, to, state) => {
2091
+ const decorations = [];
2092
+ const cursor = state.selection.main.head;
2093
+ syntaxTree5(state).iterate({
2094
+ enter: (node) => {
2095
+ if (node.name === "Image") {
2096
+ const urlNode = node.node.getChild("URL");
2097
+ if (urlNode) {
2098
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
2099
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
2100
+ decorations.push(Decoration7.replace({
2101
+ block: true,
2102
+ widget: new ImageWidget(url)
2103
+ }).range(hide ? node.from : node.to, node.to));
2104
+ }
2105
+ }
2106
+ },
2107
+ from,
2108
+ to
2109
+ });
2110
+ return decorations;
2111
+ };
2112
+ var ImageWidget = class extends WidgetType4 {
2113
+ constructor(_url) {
2114
+ super();
2115
+ this._url = _url;
2116
+ }
2117
+ eq(other) {
2118
+ return this._url === other._url;
2119
+ }
2120
+ toDOM(view) {
2121
+ const img = document.createElement("img");
2122
+ img.setAttribute("src", this._url);
2123
+ img.setAttribute("class", "cm-image");
2124
+ img.onload = () => img.classList.add("cm-loaded-image");
2125
+ return img;
2126
+ }
2127
+ };
2128
+
2129
+ // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
2130
+ import { syntaxTree as syntaxTree6 } from "@codemirror/language";
2131
+ import { RangeSetBuilder as RangeSetBuilder5 } from "@codemirror/state";
2132
+ import { hoverTooltip as hoverTooltip2, Decoration as Decoration8, ViewPlugin as ViewPlugin7, WidgetType as WidgetType5 } from "@codemirror/view";
2133
+ import { tooltipContent } from "@dxos/react-ui-theme";
2134
+ var link = (options = {}) => {
2135
+ const extensions = [
2136
+ ViewPlugin7.fromClass(class {
2137
+ constructor(view) {
2138
+ this.decorations = buildDecorations5(view, options);
2139
+ }
2140
+ update(update2) {
2141
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2142
+ this.decorations = buildDecorations5(update2.view, options);
2143
+ }
2144
+ }
2145
+ }, {
2146
+ decorations: (value) => value.decorations
2147
+ })
2148
+ ];
2149
+ if (options.onHover) {
2150
+ extensions.push(
2151
+ // https://codemirror.net/examples/tooltip
2152
+ // https://codemirror.net/docs/ref/#view.hoverTooltip
2153
+ // https://github.com/codemirror/view/blob/main/src/tooltip.ts
2154
+ hoverTooltip2((view, pos, side) => {
2155
+ const syntax = syntaxTree6(view.state).resolveInner(pos, side);
2156
+ let link2 = null;
2157
+ for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
2158
+ link2 = node.name === "Link" ? node : null;
2159
+ }
2160
+ const url = link2 && link2.getChild("URL");
2161
+ if (!url || !link2) {
2162
+ return null;
2163
+ }
2164
+ const urlText = view.state.sliceDoc(url.from, url.to);
2165
+ return {
2166
+ pos: link2.from,
2167
+ end: link2.to,
2168
+ above: true,
2169
+ create: () => {
2170
+ const el = document.createElement("div");
2171
+ el.className = tooltipContent({}, "pli-2 plb-1");
2172
+ options.onHover(el, urlText);
2173
+ return {
2174
+ dom: el,
2175
+ offset: {
2176
+ x: 0,
2177
+ y: 4
2178
+ }
2179
+ };
2180
+ }
2181
+ };
2182
+ })
2183
+ );
2184
+ }
2185
+ return extensions;
2186
+ };
2187
+ var LinkButton = class extends WidgetType5 {
2188
+ constructor(_url, _onAttach) {
2189
+ super();
2190
+ this._url = _url;
2191
+ this._onAttach = _onAttach;
2192
+ }
2193
+ eq(other) {
2194
+ return this._url === other._url;
2195
+ }
2196
+ toDOM(view) {
2197
+ const el = document.createElement("span");
2198
+ this._onAttach(el, this._url);
2199
+ return el;
2200
+ }
2201
+ };
2202
+ var LinkText = class extends WidgetType5 {
2203
+ constructor(_text, _url) {
2204
+ super();
2205
+ this._text = _text;
2206
+ this._url = _url;
2207
+ }
2208
+ eq(other) {
2209
+ return this._url === other._url;
2210
+ }
2211
+ toDOM(view) {
2212
+ const link2 = document.createElement("a");
2213
+ link2.setAttribute("class", "cm-link");
2214
+ link2.textContent = this._text;
2215
+ if (this._url) {
2216
+ link2.setAttribute("rel", "noreferrer");
2217
+ link2.setAttribute("target", "_blank");
2218
+ link2.href = this._url;
2219
+ } else {
2220
+ link2.onclick = () => {
2221
+ view.dispatch({
2222
+ selection: {
2223
+ anchor: view.posAtDOM(link2)
2224
+ }
2225
+ });
2226
+ };
2227
+ }
2228
+ return link2;
2229
+ }
2230
+ };
2231
+ var buildDecorations5 = (view, options) => {
2232
+ const builder = new RangeSetBuilder5();
2233
+ const { state } = view;
2234
+ const cursor = state.selection.main.head;
2235
+ for (const { from, to } of view.visibleRanges) {
2236
+ syntaxTree6(state).iterate({
2237
+ enter: (node) => {
2238
+ if (node.name === "Link") {
2239
+ const marks = node.node.getChildren("LinkMark");
2240
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
2241
+ const urlNode = node.node.getChild("URL");
2242
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
2243
+ if (!url) {
2244
+ return false;
2245
+ }
2246
+ if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
2247
+ builder.add(node.from, node.to, Decoration8.replace({
2248
+ widget: new LinkText(text, options.link ? url : void 0)
2249
+ }));
2250
+ }
2251
+ if (options.onRender) {
2252
+ builder.add(node.to, node.to, Decoration8.replace({
2253
+ widget: new LinkButton(url, options.onRender)
2254
+ }));
2255
+ }
2256
+ return false;
2257
+ }
2258
+ },
2259
+ from,
2260
+ to
2261
+ });
2262
+ }
2263
+ return builder.finish();
2264
+ };
2265
+
2266
+ // packages/ui/react-ui-editor/src/extensions/markdown/table.ts
2267
+ import { syntaxTree as syntaxTree7 } from "@codemirror/language";
2268
+ import { RangeSetBuilder as RangeSetBuilder6, StateField as StateField4 } from "@codemirror/state";
2269
+ import { Decoration as Decoration9, EditorView as EditorView11, WidgetType as WidgetType6 } from "@codemirror/view";
1994
2270
  var table = (options = {}) => {
1995
2271
  return StateField4.define({
1996
2272
  create: (state) => update(state, options),
1997
2273
  update: (_, tr) => update(tr.state, options),
1998
- provide: (field) => EditorView10.decorations.from(field)
2274
+ provide: (field) => EditorView11.decorations.from(field)
1999
2275
  });
2000
2276
  };
2001
2277
  var update = (state, options) => {
2002
- const builder = new RangeSetBuilder2();
2278
+ const builder = new RangeSetBuilder6();
2003
2279
  const cursor = state.selection.main.head;
2004
2280
  const tables = [];
2005
2281
  const getTable = () => tables[tables.length - 1];
@@ -2007,7 +2283,7 @@ var update = (state, options) => {
2007
2283
  const table2 = getTable();
2008
2284
  return table2.rows?.[table2.rows.length - 1];
2009
2285
  };
2010
- syntaxTree3(state).iterate({
2286
+ syntaxTree7(state).iterate({
2011
2287
  enter: (node) => {
2012
2288
  switch (node.name) {
2013
2289
  case "Table": {
@@ -2039,11 +2315,10 @@ var update = (state, options) => {
2039
2315
  });
2040
2316
  tables.forEach((table2) => {
2041
2317
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
2042
- hide && builder.add(table2.from, table2.to, Decoration7.replace({
2043
- block: true,
2318
+ hide && builder.add(table2.from, table2.to, Decoration9.replace({
2044
2319
  widget: new TableWidget(table2)
2045
2320
  }));
2046
- builder.add(table2.from, table2.to, Decoration7.mark({
2321
+ builder.add(table2.from, table2.to, Decoration9.mark({
2047
2322
  class: "cm-table"
2048
2323
  }));
2049
2324
  });
@@ -2081,94 +2356,144 @@ var TableWidget = class extends WidgetType6 {
2081
2356
  }
2082
2357
  return table2;
2083
2358
  }
2359
+ ignoreEvent(e) {
2360
+ return !/^mouse/.test(e.type);
2361
+ }
2084
2362
  };
2085
2363
 
2086
- // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2087
- import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType7, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin7 } from "@codemirror/view";
2088
- var styles5 = EditorView11.baseTheme({
2089
- "& .cm-task-item": {
2090
- paddingLeft: "4px",
2091
- paddingRight: "8px"
2364
+ // packages/ui/react-ui-editor/src/extensions/markdown/tasklist.ts
2365
+ import { syntaxTree as syntaxTree8 } from "@codemirror/language";
2366
+ import { RangeSetBuilder as RangeSetBuilder7 } from "@codemirror/state";
2367
+ import { EditorView as EditorView12, Decoration as Decoration10, WidgetType as WidgetType7, ViewPlugin as ViewPlugin8 } from "@codemirror/view";
2368
+ var styles5 = EditorView12.baseTheme({
2369
+ "& .cm-task": {
2370
+ color: getToken("extend.colors.blue.500")
2371
+ },
2372
+ "& .cm-task-checkbox": {
2373
+ marginLeft: "4px",
2374
+ marginRight: "4px"
2092
2375
  }
2093
2376
  });
2094
2377
  var CheckboxWidget = class extends WidgetType7 {
2095
- constructor(_checked, _indent, _onCheck) {
2378
+ constructor(_checked) {
2096
2379
  super();
2097
2380
  this._checked = _checked;
2098
- this._indent = _indent;
2099
- this._onCheck = _onCheck;
2100
2381
  }
2101
2382
  eq(other) {
2102
- return this._checked === other._checked && this._indent === other._indent;
2383
+ return this._checked === other._checked;
2103
2384
  }
2104
2385
  toDOM(view) {
2105
- const wrap = document.createElement("span");
2106
- wrap.className = "cm-task-item";
2107
- wrap.setAttribute("aria-hidden", "true");
2108
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
2109
- const input = wrap.appendChild(document.createElement("input"));
2386
+ const input = document.createElement("input");
2387
+ input.className = "cm-task-checkbox";
2110
2388
  input.type = "checkbox";
2111
2389
  input.checked = this._checked;
2112
- if (!this._onCheck) {
2390
+ if (view.state.readOnly) {
2113
2391
  input.setAttribute("disabled", "true");
2114
- }
2115
- if (this._onCheck) {
2116
- input.onchange = (event) => {
2117
- this._onCheck?.(event.target.checked);
2118
- return true;
2392
+ } else {
2393
+ input.onmousedown = (event) => {
2394
+ const pos = view.posAtDOM(input);
2395
+ const text = view.state.sliceDoc(pos, pos + 3);
2396
+ if (text === (this._checked ? "[x]" : "[ ]")) {
2397
+ view.dispatch({
2398
+ changes: {
2399
+ from: pos + 1,
2400
+ to: pos + 2,
2401
+ insert: this._checked ? " " : "x"
2402
+ }
2403
+ });
2404
+ event.preventDefault();
2405
+ }
2119
2406
  };
2120
2407
  }
2121
- return wrap;
2408
+ return input;
2122
2409
  }
2123
2410
  ignoreEvent() {
2124
2411
  return false;
2125
2412
  }
2126
2413
  };
2414
+ var checkedDecoration = Decoration10.replace({
2415
+ widget: new CheckboxWidget(true)
2416
+ });
2417
+ var uncheckedDecoration = Decoration10.replace({
2418
+ widget: new CheckboxWidget(false)
2419
+ });
2420
+ var buildDecorations6 = (view) => {
2421
+ const builder = new RangeSetBuilder7();
2422
+ const { state } = view;
2423
+ const cursor = state.selection.main.head;
2424
+ for (const { from, to } of view.visibleRanges) {
2425
+ syntaxTree8(state).iterate({
2426
+ enter: (node) => {
2427
+ if (node.name === "TaskMarker") {
2428
+ if (cursor < node.from || cursor > node.to) {
2429
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2430
+ builder.add(node.from - 2, node.from - 1, Decoration10.mark({
2431
+ class: "cm-task"
2432
+ }));
2433
+ builder.add(node.from, node.to, checked ? checkedDecoration : uncheckedDecoration);
2434
+ }
2435
+ }
2436
+ },
2437
+ from,
2438
+ to
2439
+ });
2440
+ }
2441
+ return builder.finish();
2442
+ };
2127
2443
  var tasklist = (options = {}) => {
2128
- const taskMatcher = new MatchDecorator3({
2129
- regexp: /^(\s*)- \[([ xX])\]\s/g,
2130
- decoration: (match, view, pos) => {
2131
- const indent = Math.floor(match[1].length / 2);
2132
- const checked = match[2] === "x" || match[2] === "X";
2133
- return Decoration8.replace({
2134
- widget: new CheckboxWidget(checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2135
- const idx = pos + match[0].indexOf("[") + 1;
2136
- view.dispatch({
2137
- changes: {
2138
- from: idx,
2139
- to: idx + 1,
2140
- insert: checked2 ? "x" : " "
2141
- },
2142
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
2143
- selection: {
2144
- anchor: idx + 3
2145
- }
2146
- });
2147
- })
2148
- });
2149
- }
2150
- });
2151
- const tasks = ViewPlugin7.fromClass(class {
2152
- constructor(view) {
2153
- this.tasks = taskMatcher.createDeco(view);
2154
- }
2155
- update(update2) {
2156
- this.tasks = taskMatcher.updateDeco(update2, this.tasks);
2157
- }
2158
- }, {
2159
- decorations: (value) => value.tasks,
2160
- provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2161
- return view.plugin(plugin)?.tasks || Decoration8.none;
2162
- })
2163
- });
2164
2444
  return [
2165
- tasks,
2445
+ ViewPlugin8.fromClass(class {
2446
+ constructor(view) {
2447
+ this.decorations = buildDecorations6(view);
2448
+ }
2449
+ update(update2) {
2450
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2451
+ this.decorations = buildDecorations6(update2.view);
2452
+ }
2453
+ }
2454
+ }, {
2455
+ decorations: (value) => value.decorations
2456
+ }),
2166
2457
  styles5
2167
2458
  ];
2168
2459
  };
2169
2460
 
2461
+ // packages/ui/react-ui-editor/src/extensions/mention.ts
2462
+ import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
2463
+ var mention = ({ onSearch }) => {
2464
+ return autocompletion2({
2465
+ // TODO(burdon): Not working.
2466
+ activateOnTyping: true,
2467
+ // activateOnTypingDelay: 100,
2468
+ // selectOnOpen: true,
2469
+ closeOnBlur: false,
2470
+ // defaultKeymap: false,
2471
+ icons: false,
2472
+ override: [
2473
+ (context) => {
2474
+ console.log(context);
2475
+ const match = context.matchBefore(/@(\w+)?/);
2476
+ if (!match || match.from === match.to && !context.explicit) {
2477
+ return null;
2478
+ }
2479
+ return {
2480
+ from: match.from,
2481
+ options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
2482
+ label: `@${value}`
2483
+ }))
2484
+ };
2485
+ }
2486
+ ]
2487
+ });
2488
+ };
2489
+
2490
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
2491
+ var outliner = (options = {}) => {
2492
+ return [];
2493
+ };
2494
+
2170
2495
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
2171
- import { keymap as keymap5 } from "@codemirror/view";
2496
+ import { keymap as keymap6 } from "@codemirror/view";
2172
2497
  var defaultItems = [
2173
2498
  "hello world!",
2174
2499
  "this is a test.",
@@ -2178,7 +2503,7 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2178
2503
  let t;
2179
2504
  let idx = 0;
2180
2505
  return [
2181
- keymap5.of([
2506
+ keymap6.of([
2182
2507
  {
2183
2508
  // Reset.
2184
2509
  key: "alt-meta-'",
@@ -2224,27 +2549,6 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2224
2549
  ];
2225
2550
  };
2226
2551
 
2227
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2228
- import { yCollab } from "y-codemirror.next";
2229
-
2230
- // packages/ui/react-ui-editor/src/extensions/yjs/cursor.ts
2231
- import * as Y from "yjs";
2232
- import { arrayToString, stringToArray } from "@dxos/util";
2233
- var cursorConverter2 = (text) => ({
2234
- toCursor: (index, assoc) => {
2235
- return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
2236
- },
2237
- fromCursor: (cursor) => {
2238
- return Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc)?.index ?? 0;
2239
- }
2240
- });
2241
-
2242
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2243
- var yjs = (content, awareness2) => [
2244
- CursorConverter.of(cursorConverter2(content)),
2245
- yCollab(content, awareness2)
2246
- ];
2247
-
2248
2552
  // packages/ui/react-ui-editor/src/themes/default.ts
2249
2553
  import get3 from "lodash.get";
2250
2554
  var defaultTheme = {
@@ -2276,8 +2580,9 @@ var defaultTheme = {
2276
2580
  outline: "none"
2277
2581
  },
2278
2582
  ".cm-scroller": {
2279
- overflow: "visible",
2280
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2583
+ // overflow: 'visible',
2584
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(","),
2585
+ lineHeight: 1.4
2281
2586
  },
2282
2587
  ".cm-content": {
2283
2588
  padding: 0,
@@ -2311,7 +2616,7 @@ var defaultTheme = {
2311
2616
  paddingInline: 0
2312
2617
  },
2313
2618
  ".cm-activeLine": {
2314
- background: "inherit"
2619
+ background: "transparent"
2315
2620
  },
2316
2621
  //
2317
2622
  // Selection
@@ -2338,33 +2643,6 @@ var defaultTheme = {
2338
2643
  backgroundColor: get3(tokens, "extend.colors.yellow.700")
2339
2644
  },
2340
2645
  //
2341
- // collaboration
2342
- // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2343
- //
2344
- "&light .cm-ySelection, &light .cm-yLineSelection": {
2345
- mixBlendMode: "multiply"
2346
- },
2347
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2348
- mixBlendMode: "screen"
2349
- },
2350
- ".cm-ySelectionInfo": {
2351
- padding: "2px 4px",
2352
- marginBlockStart: "-4px"
2353
- },
2354
- ".cm-ySelection, & .cm-selectionMatch": {
2355
- paddingBlockStart: ".15em",
2356
- paddingBlockEnd: ".15em"
2357
- },
2358
- ".cm-ySelectionCaret": {
2359
- display: "inline-block",
2360
- insetBlockStart: ".1em",
2361
- blockSize: "1.4em",
2362
- verticalAlign: "top"
2363
- },
2364
- ".cm-yLineSelection": {
2365
- margin: 0
2366
- },
2367
- //
2368
2646
  // link
2369
2647
  //
2370
2648
  ".cm-link": {
@@ -2401,13 +2679,6 @@ var defaultTheme = {
2401
2679
  textDecoration: "none"
2402
2680
  },
2403
2681
  //
2404
- // widgets
2405
- //
2406
- ".cm-widgetBuffer": {
2407
- display: "none",
2408
- height: 0
2409
- },
2410
- //
2411
2682
  // table
2412
2683
  //
2413
2684
  ".cm-table *": {
@@ -2438,27 +2709,17 @@ var defaultTheme = {
2438
2709
  },
2439
2710
  //
2440
2711
  // font size
2441
- // 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.
2712
+ // TODO(thure): This appears to be the best or only way to set selection caret heights,
2713
+ // but it's far more verbose than it needs to be.
2442
2714
  //
2443
- ...Object.keys(get3(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2444
- const height = get3(tokens, [
2445
- "extend",
2446
- "fontSize",
2447
- fontSize,
2448
- 1,
2449
- "lineHeight"
2450
- ]);
2451
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2452
- height
2453
- };
2454
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2455
- height
2456
- };
2457
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2458
- height
2459
- };
2460
- return acc;
2461
- }, {}),
2715
+ // ...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
2716
+ // const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
2717
+ //
2718
+ // acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
2719
+ // acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
2720
+ // acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
2721
+ // return acc;
2722
+ // }, {}),
2462
2723
  /**
2463
2724
  * Panels
2464
2725
  * TODO(burdon): Needs styling attention (esp. dark mode).
@@ -2509,7 +2770,9 @@ var textTheme = {
2509
2770
  }
2510
2771
  };
2511
2772
  var markdownTheme = {
2512
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2773
+ ".cm-scroller": {
2774
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2775
+ },
2513
2776
  ".cm-placeholder": {
2514
2777
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2515
2778
  }
@@ -2524,95 +2787,95 @@ var codeTheme = {
2524
2787
  };
2525
2788
 
2526
2789
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2790
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
2527
2791
  var EditorModes = [
2528
2792
  "default",
2529
2793
  "vim"
2530
2794
  ];
2531
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
2795
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2532
2796
  const { themeMode } = useThemeContext();
2533
- const tabsterDOMAttribute = useFocusableGroup({
2534
- tabBehavior: "limited"
2535
- });
2536
- const [root, setRoot] = useState(null);
2537
- const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
2538
- useImperativeHandle(forwardedRef, () => ({
2539
- root,
2540
- state,
2797
+ const rootRef = useRef(null);
2798
+ const [view, setView] = useState(null);
2799
+ useImperativeHandle(forwardedRef, () => view, [
2541
2800
  view
2542
- }), [
2543
- view,
2544
- state,
2545
- root
2546
2801
  ]);
2547
- const { awareness: awareness2, peer } = model;
2548
- useEffect(() => {
2549
- if (awareness2 && peer) {
2550
- awareness2.setLocalStateField("user", {
2551
- name: peer.name ?? generateName(peer.id),
2552
- color: getColorForValue({
2553
- value: peer.id,
2554
- type: "color"
2555
- }),
2556
- colorLight: getColorForValue({
2557
- value: peer.id,
2558
- themeMode,
2559
- type: "highlight"
2560
- })
2561
- });
2562
- }
2563
- }, [
2564
- awareness2,
2565
- peer,
2566
- themeMode
2567
- ]);
2568
- useEffect(() => {
2569
- if (view && comments2?.length) {
2570
- view.dispatch({
2571
- effects: setCommentRange.of({
2572
- model,
2573
- comments: comments2
2574
- })
2575
- });
2802
+ useEffect2(() => {
2803
+ if (autoFocus && !view?.hasFocus) {
2804
+ if (view?.state.selection.main?.from === 0) {
2805
+ const { to } = view.state.doc.lineAt(0);
2806
+ view?.dispatch({
2807
+ selection: {
2808
+ anchor: to
2809
+ }
2810
+ });
2811
+ }
2812
+ view?.focus();
2576
2813
  }
2577
2814
  }, [
2578
2815
  view,
2579
- comments2
2816
+ autoFocus
2580
2817
  ]);
2581
- useEffect(() => {
2582
- if (!root) {
2818
+ useEffect2(() => {
2819
+ if (!model || !rootRef.current) {
2583
2820
  return;
2584
2821
  }
2585
- const state2 = EditorState2.create({
2822
+ const state = EditorState2.create({
2586
2823
  doc: model.text(),
2824
+ selection,
2587
2825
  extensions: [
2588
- readonly && EditorState2.readOnly.of(readonly),
2589
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2590
- editorMode === "vim" && vim(),
2826
+ // TODO(burdon): Doesn't catch errors in keymap functions.
2827
+ EditorView13.exceptionSink.of((err) => {
2828
+ log2.catch(err, void 0, {
2829
+ F: __dxlog_file3,
2830
+ L: 134,
2831
+ S: void 0,
2832
+ C: (f, a) => f(...a)
2833
+ });
2834
+ }),
2591
2835
  // Theme.
2592
- // TODO(burdon): Make theme configurable.
2593
- EditorView12.baseTheme(defaultTheme),
2594
- EditorView12.theme(slots?.editor?.theme ?? {}),
2595
- EditorView12.darkTheme.of(themeMode === "dark"),
2836
+ // TODO(burdon): Make configurable.
2837
+ EditorView13.baseTheme(defaultTheme),
2838
+ EditorView13.theme(theme ?? {}),
2839
+ EditorView13.darkTheme.of(themeMode === "dark"),
2840
+ EditorView13.editorAttributes.of({
2841
+ class: slots.editor?.className ?? ""
2842
+ }),
2843
+ EditorView13.contentAttributes.of({
2844
+ class: slots.content?.className ?? ""
2845
+ }),
2846
+ // State.
2847
+ EditorView13.editable.of(!readonly),
2848
+ EditorState2.readOnly.of(!!readonly),
2596
2849
  // Storage and replication.
2850
+ // NOTE: This must come before user extensions.
2597
2851
  model.extension,
2852
+ // TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
2853
+ editorMode === "vim" && vim(),
2598
2854
  // Custom.
2599
2855
  ...extensions
2600
- ].filter(Boolean)
2856
+ ].filter(isNotFalsy3)
2601
2857
  });
2602
- view?.destroy();
2603
- setEditor({
2604
- state: state2,
2605
- view: new EditorView12({
2606
- state: state2,
2607
- parent: root
2608
- })
2858
+ const newView = new EditorView13({
2859
+ state,
2860
+ parent: rootRef.current,
2861
+ scrollTo,
2862
+ // NOTE: Uncomment to debug/monitor all transactions.
2863
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
2864
+ dispatchTransactions: (trs, view2) => {
2865
+ if (debug) {
2866
+ logChanges(trs);
2867
+ }
2868
+ view2.update(trs);
2869
+ }
2609
2870
  });
2871
+ view?.destroy();
2872
+ setView(newView);
2610
2873
  return () => {
2611
- view?.destroy();
2612
- setEditor(void 0);
2874
+ newView?.destroy();
2875
+ setView(null);
2613
2876
  };
2614
2877
  }, [
2615
- root,
2878
+ rootRef,
2616
2879
  model,
2617
2880
  readonly,
2618
2881
  editorMode,
@@ -2622,11 +2885,13 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2622
2885
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2623
2886
  switch (key) {
2624
2887
  case "Enter": {
2625
- view?.contentDOM.focus();
2888
+ view?.focus();
2626
2889
  break;
2627
2890
  }
2628
2891
  case "Escape": {
2629
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2892
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
2893
+ view?.focus();
2894
+ }
2630
2895
  break;
2631
2896
  }
2632
2897
  }
@@ -2636,83 +2901,239 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2636
2901
  ]);
2637
2902
  return /* @__PURE__ */ React.createElement("div", {
2638
2903
  key: model.id,
2639
- ref: setRoot,
2904
+ role: "none",
2640
2905
  tabIndex: 0,
2641
- ...slots?.root,
2642
- ...editorMode !== "vim" && tabsterDOMAttribute,
2643
- onKeyUp: handleKeyUp
2906
+ ...slots.root,
2907
+ // {...(editorMode !== 'vim' && tabsterDOMAttribute)}
2908
+ onKeyUp: handleKeyUp,
2909
+ ref: rootRef
2644
2910
  });
2645
2911
  });
2646
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2912
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, lineWrapping, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
2647
2913
  const { themeMode } = useThemeContext();
2648
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2914
+ const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2649
2915
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2650
2916
  ref: forwardedRef,
2651
2917
  readonly,
2652
2918
  extensions: [
2653
- basicBundle({
2654
- readonly,
2919
+ createBasicBundle({
2655
2920
  themeMode,
2656
- placeholder: slots?.editor?.placeholder
2921
+ placeholder: placeholder3,
2922
+ lineWrapping
2657
2923
  }),
2658
2924
  ...extensions
2659
2925
  ],
2660
- slots,
2926
+ theme,
2927
+ slots: updatedSlots,
2661
2928
  ...props
2662
2929
  });
2663
2930
  });
2664
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2931
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
2665
2932
  const { themeMode } = useThemeContext();
2666
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
2933
+ const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2667
2934
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2668
2935
  ref: forwardedRef,
2669
2936
  readonly,
2670
2937
  extensions: [
2671
- markdownBundle({
2672
- readonly,
2938
+ createMarkdownExtensions({
2673
2939
  themeMode,
2674
- placeholder: slots?.editor?.placeholder
2940
+ placeholder: placeholder3
2675
2941
  }),
2676
2942
  ...extensions
2677
2943
  ],
2678
- slots,
2944
+ theme,
2945
+ slots: updatedSlots,
2679
2946
  ...props
2680
2947
  });
2681
2948
  });
2682
2949
  var defaultSlots = {
2683
2950
  root: {
2684
- className: mx3("p-2", inputSurface)
2951
+ // TODO(burdon): Add focusRing by default/as property?
2952
+ className: mx3("flex flex-col grow overflow-y-auto", attentionSurface)
2953
+ },
2954
+ editor: {
2955
+ className: "h-full p-2"
2685
2956
  }
2686
2957
  };
2687
2958
  var defaultTextSlots = {
2688
- ...defaultSlots,
2689
- editor: {
2690
- theme: textTheme
2691
- }
2959
+ ...defaultSlots
2692
2960
  };
2693
2961
  var defaultMarkdownSlots = {
2694
- ...defaultSlots,
2695
- editor: {
2696
- theme: markdownTheme
2697
- }
2962
+ ...defaultSlots
2698
2963
  };
2699
2964
 
2700
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2701
- import get4 from "lodash.get";
2702
- import { getRawDoc } from "@dxos/echo-schema";
2703
- import { isNotNullOrUndefined } from "@dxos/util";
2965
+ // packages/ui/react-ui-editor/src/components/Toolbar/Toolbar.tsx
2966
+ import { At, CaretRight, ChatText, Code, Image, Link, ListBullets, ListChecks, ListNumbers, TextStrikethrough, Table as Table2, TextB, TextItalic } from "@phosphor-icons/react";
2967
+ import { createContext } from "@radix-ui/react-context";
2968
+ import React2 from "react";
2969
+ import { Button, DensityProvider, Select } from "@dxos/react-ui";
2970
+ import { getSize } from "@dxos/react-ui-theme";
2971
+ var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
2972
+ var ToolbarRoot = ({ children, onAction }) => {
2973
+ return /* @__PURE__ */ React2.createElement(ToolbarContextProvider, {
2974
+ onAction
2975
+ }, /* @__PURE__ */ React2.createElement(DensityProvider, {
2976
+ density: "fine"
2977
+ }, /* @__PURE__ */ React2.createElement("div", {
2978
+ role: "toolbar",
2979
+ className: "flex w-full shrink-0 p-2 gap-4 items-center whitespace-nowrap overflow-hidden"
2980
+ }, children)));
2981
+ };
2982
+ var ToolbarButton = ({ Icon, onClick, title }) => {
2983
+ const { onAction } = useToolbarContext("ToolbarButton");
2984
+ const handleClick = () => {
2985
+ const action = onClick();
2986
+ if (action) {
2987
+ onAction?.(action);
2988
+ }
2989
+ };
2990
+ return /* @__PURE__ */ React2.createElement(Button, {
2991
+ variant: "ghost",
2992
+ classNames: "p-2",
2993
+ onClick: handleClick,
2994
+ title
2995
+ }, /* @__PURE__ */ React2.createElement(Icon, {
2996
+ className: getSize(5)
2997
+ }));
2998
+ };
2999
+ var ToolbarSeparator = () => /* @__PURE__ */ React2.createElement("div", {
3000
+ className: "grow"
3001
+ });
3002
+ var MarkdownHeading = () => {
3003
+ const { onAction } = useToolbarContext("MarkdownFormatting");
3004
+ return /* @__PURE__ */ React2.createElement(Select.Root, {
3005
+ defaultValue: "0",
3006
+ onValueChange: (value) => onAction?.({
3007
+ type: "heading",
3008
+ data: parseInt(value)
3009
+ })
3010
+ }, /* @__PURE__ */ React2.createElement(Select.TriggerButton, {
3011
+ classNames: "w-[8rem]"
3012
+ }), /* @__PURE__ */ React2.createElement(Select.Portal, null, /* @__PURE__ */ React2.createElement(Select.Content, null, /* @__PURE__ */ React2.createElement(Select.Viewport, null, /* @__PURE__ */ React2.createElement(Select.Option, {
3013
+ value: "0"
3014
+ }, "Paragraph"), [
3015
+ 1,
3016
+ 2,
3017
+ 3,
3018
+ 4,
3019
+ 5,
3020
+ 6
3021
+ ].map((level) => /* @__PURE__ */ React2.createElement(Select.Option, {
3022
+ key: level,
3023
+ value: String(level)
3024
+ }, "Heading ", level))))));
3025
+ };
3026
+ var MarkdownStyles = () => /* @__PURE__ */ React2.createElement("div", {
3027
+ role: "none"
3028
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3029
+ Icon: TextB,
3030
+ title: "Bold",
3031
+ onClick: () => ({
3032
+ type: "bold"
3033
+ })
3034
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3035
+ Icon: TextItalic,
3036
+ title: "Italic",
3037
+ onClick: () => ({
3038
+ type: "italic"
3039
+ })
3040
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3041
+ Icon: TextStrikethrough,
3042
+ title: "Strike-through",
3043
+ onClick: () => ({
3044
+ type: "strikethrough"
3045
+ })
3046
+ }));
3047
+ var MarkdownLists = () => /* @__PURE__ */ React2.createElement("div", {
3048
+ role: "none"
3049
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3050
+ Icon: ListBullets,
3051
+ title: "Bullet list",
3052
+ onClick: () => ({
3053
+ type: "list"
3054
+ })
3055
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3056
+ Icon: ListNumbers,
3057
+ title: "Numbered list",
3058
+ onClick: () => ({
3059
+ type: "list-ordered"
3060
+ })
3061
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3062
+ Icon: ListChecks,
3063
+ title: "Task list",
3064
+ onClick: () => ({
3065
+ type: "list-tasks"
3066
+ })
3067
+ }));
3068
+ var MarkdownBlocks = () => /* @__PURE__ */ React2.createElement("div", {
3069
+ role: "none"
3070
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3071
+ Icon: CaretRight,
3072
+ title: "Block quite",
3073
+ onClick: () => ({
3074
+ type: "blockquote"
3075
+ })
3076
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3077
+ Icon: Code,
3078
+ title: "Code block",
3079
+ onClick: () => ({
3080
+ type: "codeblock"
3081
+ })
3082
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3083
+ Icon: Table2,
3084
+ title: "Table",
3085
+ onClick: () => ({
3086
+ type: "table"
3087
+ })
3088
+ }));
3089
+ var MarkdownLinks = () => /* @__PURE__ */ React2.createElement("div", {
3090
+ role: "none"
3091
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3092
+ Icon: Link,
3093
+ title: "Link",
3094
+ onClick: () => ({
3095
+ type: "link"
3096
+ })
3097
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3098
+ Icon: At,
3099
+ title: "Mention",
3100
+ onClick: () => ({
3101
+ type: "mention"
3102
+ })
3103
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3104
+ Icon: Image,
3105
+ title: "Image",
3106
+ onClick: () => ({
3107
+ type: "image"
3108
+ })
3109
+ }));
3110
+ 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));
3111
+ var MarkdownExtended = () => /* @__PURE__ */ React2.createElement(Toolbar.Button, {
3112
+ Icon: ChatText,
3113
+ title: "Create comment",
3114
+ onClick: () => ({
3115
+ type: "comment"
3116
+ })
3117
+ });
3118
+ var Toolbar = {
3119
+ Root: ToolbarRoot,
3120
+ Button: ToolbarButton,
3121
+ Separator: ToolbarSeparator,
3122
+ Markdown: MarkdownStandard,
3123
+ Extended: MarkdownExtended
3124
+ };
2704
3125
 
2705
3126
  // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2706
3127
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2707
3128
  import { Context as Context2 } from "@dxos/context";
2708
- import { invariant as invariant3 } from "@dxos/invariant";
3129
+ import { invariant as invariant2 } from "@dxos/invariant";
2709
3130
  import { log as log3 } from "@dxos/log";
2710
3131
  var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2711
3132
  var DEBOUNCE_INTERVAL = 100;
2712
3133
  var SpaceAwarenessProvider = class {
2713
3134
  constructor(params) {
2714
- this.remoteStateChange = new Event2();
2715
3135
  this._remoteStates = /* @__PURE__ */ new Map();
3136
+ this.remoteStateChange = new Event2();
2716
3137
  this._space = params.space;
2717
3138
  this._channel = params.channel;
2718
3139
  this._peerId = params.peerId;
@@ -2729,7 +3150,7 @@ var SpaceAwarenessProvider = class {
2729
3150
  await sleep(DEBOUNCE_INTERVAL);
2730
3151
  }
2731
3152
  });
2732
- const unsubscribe = this._space.listen(this._channel, (message) => {
3153
+ this._ctx.onDispose(this._space.listen(this._channel, (message) => {
2733
3154
  switch (message.payload.kind) {
2734
3155
  case "query": {
2735
3156
  this._handleQueryMessage();
@@ -2740,8 +3161,7 @@ var SpaceAwarenessProvider = class {
2740
3161
  break;
2741
3162
  }
2742
3163
  }
2743
- });
2744
- this._ctx.onDispose(unsubscribe);
3164
+ }));
2745
3165
  void this._space.postMessage(this._channel, {
2746
3166
  kind: "query"
2747
3167
  }).catch((err) => {
@@ -2749,7 +3169,7 @@ var SpaceAwarenessProvider = class {
2749
3169
  err
2750
3170
  }, {
2751
3171
  F: __dxlog_file4,
2752
- L: 85,
3172
+ L: 89,
2753
3173
  S: this,
2754
3174
  C: (f, a) => f(...a)
2755
3175
  });
@@ -2760,10 +3180,13 @@ var SpaceAwarenessProvider = class {
2760
3180
  this._ctx = void 0;
2761
3181
  this._postTask = void 0;
2762
3182
  }
3183
+ getRemoteStates() {
3184
+ return Array.from(this._remoteStates.values());
3185
+ }
2763
3186
  update(position) {
2764
- invariant3(this._postTask, void 0, {
3187
+ invariant2(this._postTask, void 0, {
2765
3188
  F: __dxlog_file4,
2766
- L: 96,
3189
+ L: 104,
2767
3190
  S: this,
2768
3191
  A: [
2769
3192
  "this._postTask",
@@ -2777,13 +3200,10 @@ var SpaceAwarenessProvider = class {
2777
3200
  };
2778
3201
  this._postTask.schedule();
2779
3202
  }
2780
- getRemoteStates() {
2781
- return Array.from(this._remoteStates.values());
2782
- }
2783
3203
  _handleQueryMessage() {
2784
- invariant3(this._postTask, void 0, {
3204
+ invariant2(this._postTask, void 0, {
2785
3205
  F: __dxlog_file4,
2786
- L: 111,
3206
+ L: 115,
2787
3207
  S: this,
2788
3208
  A: [
2789
3209
  "this._postTask",
@@ -2793,9 +3213,9 @@ var SpaceAwarenessProvider = class {
2793
3213
  this._postTask.schedule();
2794
3214
  }
2795
3215
  _handlePostMessage(message) {
2796
- invariant3(message.kind === "post", void 0, {
3216
+ invariant2(message.kind === "post", void 0, {
2797
3217
  F: __dxlog_file4,
2798
- L: 116,
3218
+ L: 120,
2799
3219
  S: this,
2800
3220
  A: [
2801
3221
  "message.kind === 'post'",
@@ -2807,229 +3227,231 @@ var SpaceAwarenessProvider = class {
2807
3227
  }
2808
3228
  };
2809
3229
 
2810
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
3230
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
2811
3231
  import { StateField as StateField5 } from "@codemirror/state";
2812
- import { useEffect as useEffect2, useState as useState2 } from "react";
2813
- import { isAutomergeObject } from "@dxos/react-client/echo";
3232
+ var modelState = StateField5.define({
3233
+ create: () => void 0,
3234
+ update: (model) => model
3235
+ });
2814
3236
 
2815
- // packages/ui/react-ui-editor/src/hooks/yjs.ts
2816
- import * as decoding from "lib0/decoding";
2817
- import * as encoding from "lib0/encoding";
2818
- import { Observable } from "lib0/observable";
2819
- import * as YP from "y-protocols/awareness";
2820
- import * as Y2 from "yjs";
2821
- import { invariant as invariant4 } from "@dxos/invariant";
2822
- import { log as log4 } from "@dxos/log";
2823
- import { arrayToString as arrayToString2, stringToArray as stringToArray2 } from "@dxos/util";
2824
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2825
- var createYjsModel = ({ identity, space, text }) => {
2826
- invariant4(text?.doc && text?.content, void 0, {
2827
- F: __dxlog_file5,
2828
- L: 23,
2829
- S: void 0,
2830
- A: [
2831
- "text?.doc && text?.content",
2832
- ""
2833
- ]
2834
- });
2835
- const provider = space ? new YAwarenessProvider({
2836
- space,
2837
- doc: text.doc,
2838
- channel: `yjs.awareness.${text.id}`
2839
- }) : void 0;
2840
- const model = {
2841
- id: text.doc.guid,
2842
- content: text.content,
2843
- text: () => text.content.toString(),
2844
- // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
2845
- // TODO(dmaretskyi): Refactor as cursor-converter.
2846
- getCursorFromRange: (range) => {
2847
- const from = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.from));
2848
- const to = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.to, -1));
2849
- return [
2850
- arrayToString2(from),
2851
- arrayToString2(to)
2852
- ].join(":");
2853
- },
2854
- getRangeFromCursor: (cursor) => {
2855
- invariant4(text.doc, void 0, {
2856
- F: __dxlog_file5,
2857
- L: 40,
2858
- S: void 0,
2859
- A: [
2860
- "text.doc",
2861
- ""
2862
- ]
3237
+ // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
3238
+ var useActionHandler = (view) => {
3239
+ return (action) => {
3240
+ if (!view) {
3241
+ return;
3242
+ }
3243
+ switch (action.type) {
3244
+ case "heading":
3245
+ setHeading(parseInt(action.data))(view);
3246
+ break;
3247
+ case "bold":
3248
+ toggleBold(view);
3249
+ break;
3250
+ case "italic":
3251
+ toggleItalic(view);
3252
+ break;
3253
+ case "strikethrough":
3254
+ toggleStrikethrough(view);
3255
+ break;
3256
+ case "list":
3257
+ toggleList(view);
3258
+ break;
3259
+ case "codeblock":
3260
+ insertCodeblock(view);
3261
+ break;
3262
+ case "table":
3263
+ insertTable(view);
3264
+ break;
3265
+ case "comment":
3266
+ createComment(view);
3267
+ break;
3268
+ }
3269
+ setTimeout(() => {
3270
+ view.focus();
3271
+ view.dispatch({
3272
+ selection: view.state.selection
2863
3273
  });
2864
- const parts = cursor.split(":");
2865
- const from = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[0])), text.doc);
2866
- const to = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[1])), text.doc);
2867
- return from && to ? {
2868
- from: from.index,
2869
- to: to.index
2870
- } : void 0;
2871
- },
2872
- extension: [
2873
- modelState.init(() => model),
2874
- yjs(text.content, provider?.awareness)
2875
- ],
2876
- awareness: provider?.awareness,
2877
- peer: identity ? {
2878
- id: identity.identityKey.toHex(),
2879
- name: identity.profile?.displayName
2880
- } : void 0
3274
+ });
2881
3275
  };
2882
- return model;
2883
3276
  };
2884
- var messageAwareness = 1;
2885
- var messageQueryAwareness = 3;
2886
- var YAwarenessProvider = class extends Observable {
2887
- constructor({ space, doc, channel, awareness: awareness2 }) {
2888
- super();
2889
- this._space = space;
2890
- this._awareness = awareness2 ?? new YP.Awareness(doc);
2891
- this._channel = channel;
2892
- this._clientId = doc.clientID;
2893
- this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
2894
- this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
2895
- if (typeof window !== "undefined") {
2896
- window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
2897
- } else if (typeof process !== "undefined") {
2898
- process.on("exit", this._handleBeforeUnload.bind(this));
2899
- }
2900
- const encoderAwarenessQuery = encoding.createEncoder();
2901
- encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
2902
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
2903
- const encoderAwarenessState = encoding.createEncoder();
2904
- encoding.writeVarUint(encoderAwarenessState, messageAwareness);
2905
- encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [
2906
- this._clientId
2907
- ]));
2908
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
2909
- }
2910
- get awareness() {
2911
- return this._awareness;
2912
- }
2913
- _handleAwarenessUpdate({ added, updated, removed }, origin) {
2914
- log4("awareness update", {
2915
- added,
2916
- updated,
2917
- removed,
2918
- origin
2919
- }, {
2920
- F: __dxlog_file5,
2921
- L: 122,
2922
- S: this,
2923
- C: (f, a) => f(...a)
2924
- });
2925
- const changedClients = added.concat(updated).concat(removed);
2926
- const encoderAwareness = encoding.createEncoder();
2927
- encoding.writeVarUint(encoderAwareness, messageAwareness);
2928
- encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
2929
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2930
- }
2931
- _handleSpaceMessage({ payload }) {
2932
- log4("space message", payload, {
2933
- F: __dxlog_file5,
2934
- L: 131,
2935
- S: this,
2936
- C: (f, a) => f(...a)
2937
- });
2938
- const data = new Uint8Array(Array.from(Object.values(payload)));
2939
- const encoder = this._readMessage(data);
2940
- if (encoder) {
2941
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
3277
+
3278
+ // packages/ui/react-ui-editor/src/hooks/useEditorView.ts
3279
+ import { useState as useState2 } from "react";
3280
+ var useEditorView = () => {
3281
+ const [view, setView] = useState2(null);
3282
+ return [
3283
+ (ref) => {
3284
+ setView(ref);
3285
+ },
3286
+ view
3287
+ ];
3288
+ };
3289
+
3290
+ // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
3291
+ import { EditorSelection, EditorState as EditorState3 } from "@codemirror/state";
3292
+ import { EditorView as EditorView14 } from "@codemirror/view";
3293
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
3294
+ import { log as log4 } from "@dxos/log";
3295
+ import { isNotFalsy as isNotFalsy4 } from "@dxos/util";
3296
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
3297
+ var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions } = {}) => {
3298
+ const onUpdate = useRef2();
3299
+ const [view, setView] = useState3();
3300
+ const parentRef = useRef2(null);
3301
+ useEffect3(() => {
3302
+ if (parentRef.current) {
3303
+ const state = EditorState3.create({
3304
+ doc,
3305
+ selection,
3306
+ extensions: [
3307
+ // TODO(burdon): Doesn't catch errors in keymap functions.
3308
+ EditorView14.exceptionSink.of((err) => {
3309
+ log4.catch(err, void 0, {
3310
+ F: __dxlog_file5,
3311
+ L: 57,
3312
+ S: void 0,
3313
+ C: (f, a) => f(...a)
3314
+ });
3315
+ }),
3316
+ extensions,
3317
+ EditorView14.updateListener.of(() => {
3318
+ onUpdate.current?.();
3319
+ })
3320
+ ].filter(isNotFalsy4)
3321
+ });
3322
+ const view2 = new EditorView14({
3323
+ parent: parentRef.current,
3324
+ scrollTo,
3325
+ state,
3326
+ // NOTE: Uncomment to debug/monitor all transactions.
3327
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
3328
+ dispatchTransactions: (trs, view3) => {
3329
+ if (debug) {
3330
+ logChanges(trs);
3331
+ }
3332
+ view3.update(trs);
3333
+ }
3334
+ });
3335
+ setView(view2);
2942
3336
  }
2943
- }
2944
- _readMessage(message) {
2945
- const decoder = decoding.createDecoder(message);
2946
- const encoder = encoding.createEncoder();
2947
- const messageType = decoding.readVarUint(decoder);
2948
- let sendReply = false;
2949
- switch (messageType) {
2950
- case messageAwareness: {
2951
- YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2952
- break;
3337
+ return () => {
3338
+ view?.destroy();
3339
+ };
3340
+ }, [
3341
+ parentRef
3342
+ ]);
3343
+ useEffect3(() => {
3344
+ if (view) {
3345
+ if (!selection && !view.state.selection.main.anchor) {
3346
+ selection = EditorSelection.single(view.state.doc.line(1).to);
2953
3347
  }
2954
- case messageQueryAwareness: {
2955
- encoding.writeVarUint(encoder, messageAwareness);
2956
- encoding.writeVarUint8Array(encoder, YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2957
- sendReply = true;
2958
- break;
3348
+ if (selection || scrollTo) {
3349
+ onUpdate.current = () => {
3350
+ onUpdate.current = void 0;
3351
+ view.dispatch({
3352
+ selection,
3353
+ effects: scrollTo && [
3354
+ scrollTo
3355
+ ],
3356
+ scrollIntoView: !scrollTo
3357
+ });
3358
+ };
2959
3359
  }
2960
- default: {
2961
- console.error("Invalid message:", messageType);
2962
- return encoder;
3360
+ if (autoFocus) {
3361
+ view.focus();
2963
3362
  }
2964
3363
  }
2965
- if (!sendReply) {
2966
- return null;
2967
- }
2968
- return encoder;
2969
- }
2970
- _handleBeforeUnload() {
2971
- YP.removeAwarenessStates(this._awareness, [
2972
- this._clientId
2973
- ], "window unload");
2974
- }
3364
+ }, [
3365
+ view,
3366
+ autoFocus,
3367
+ selection,
3368
+ scrollTo
3369
+ ]);
3370
+ return {
3371
+ parentRef,
3372
+ view
3373
+ };
3374
+ };
3375
+ var createDataExtensions = ({ readonly = false } = {}) => {
3376
+ return [
3377
+ //
3378
+ EditorState3.readOnly.of(readonly),
3379
+ EditorView14.editable.of(!readonly)
3380
+ ];
3381
+ };
3382
+ var createThemeExtensions = ({ theme, themeMode, slots } = {}) => {
3383
+ return [
3384
+ //
3385
+ EditorView14.baseTheme(defaultTheme),
3386
+ theme && EditorView14.theme(theme),
3387
+ EditorView14.darkTheme.of(themeMode === "dark"),
3388
+ EditorView14.editorAttributes.of({
3389
+ class: slots?.editor?.className ?? ""
3390
+ }),
3391
+ EditorView14.contentAttributes.of({
3392
+ class: slots?.content?.className ?? ""
3393
+ })
3394
+ ].filter(isNotFalsy4);
2975
3395
  };
2976
3396
 
2977
3397
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2978
- var modelState = StateField5.define({
2979
- create: () => void 0,
2980
- update: (model) => model
2981
- });
3398
+ import get4 from "lodash.get";
3399
+ import { useEffect as useEffect4, useState as useState4 } from "react";
3400
+ import { generateName } from "@dxos/display-name";
3401
+ import { getRawDoc } from "@dxos/echo-schema";
3402
+ import { invariant as invariant3 } from "@dxos/invariant";
3403
+ import { isAutomergeObject } from "@dxos/react-client/echo";
3404
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
2982
3405
  var useTextModel = (props) => {
2983
3406
  const { identity, space, text } = props;
2984
- const [model, setModel] = useState2(() => createModel(props));
2985
- useEffect2(() => setModel(createModel(props)), [
3407
+ const [model, setModel] = useState4();
3408
+ useEffect4(() => setModel(createModel(props)), [
2986
3409
  identity,
2987
3410
  space,
2988
3411
  text
2989
3412
  ]);
2990
3413
  return model;
2991
3414
  };
2992
- var createModel = (props) => {
2993
- const { text } = props;
2994
- if (isAutomergeObject(text)) {
2995
- return createAutomergeModel(props);
2996
- } else if (text?.doc) {
2997
- return createYjsModel(props);
2998
- } else {
2999
- return void 0;
3000
- }
3001
- };
3002
-
3003
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
3004
- var createAutomergeModel = ({ space, identity, text }) => {
3415
+ var createModel = ({ space, identity, text }) => {
3416
+ invariant3(isAutomergeObject(text), void 0, {
3417
+ F: __dxlog_file6,
3418
+ L: 34,
3419
+ S: void 0,
3420
+ A: [
3421
+ "isAutomergeObject(text)",
3422
+ ""
3423
+ ]
3424
+ });
3005
3425
  const obj = text;
3006
3426
  const doc = getRawDoc(obj, [
3007
3427
  obj.field
3008
3428
  ]);
3009
- const awarenessProvider = space && new SpaceAwarenessProvider({
3429
+ const awarenessProvider2 = space && new SpaceAwarenessProvider({
3010
3430
  space,
3011
3431
  channel: `automerge.awareness.${obj.id}`,
3012
3432
  info: {
3013
- displayName: identity?.profile?.displayName ?? "",
3433
+ displayName: identity ? identity.profile?.displayName ?? generateName(identity.identityKey.toHex()) : void 0,
3014
3434
  color: cursorColor.color,
3015
3435
  lightColor: cursorColor.light
3016
3436
  },
3017
3437
  peerId: identity?.identityKey.toHex() ?? "Anonymous"
3018
3438
  });
3439
+ const extensions = [
3440
+ //
3441
+ modelState.init(() => model),
3442
+ automerge({
3443
+ handle: doc.handle,
3444
+ path: doc.path
3445
+ })
3446
+ ];
3447
+ if (awarenessProvider2) {
3448
+ extensions.push(awareness(awarenessProvider2));
3449
+ }
3019
3450
  const model = {
3020
3451
  id: obj.id,
3021
3452
  content: doc,
3022
3453
  text: () => get4(doc.handle.docSync(), doc.path),
3023
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
3024
- extension: [
3025
- modelState.init(() => model),
3026
- automerge3({
3027
- handle: doc.handle,
3028
- path: doc.path
3029
- }),
3030
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
3031
- awareness()
3032
- ].filter(isNotNullOrUndefined),
3454
+ extension: extensions,
3033
3455
  peer: identity ? {
3034
3456
  id: identity.identityKey.toHex(),
3035
3457
  name: identity.profile?.displayName
@@ -3038,8 +3460,8 @@ var createAutomergeModel = ({ space, identity, text }) => {
3038
3460
  return model;
3039
3461
  };
3040
3462
  export {
3041
- AwarenessProvider,
3042
3463
  BaseTextEditor,
3464
+ Cursor,
3043
3465
  Doc,
3044
3466
  EditorModes,
3045
3467
  MarkdownEditor,
@@ -3047,45 +3469,64 @@ export {
3047
3469
  SpaceAwarenessProvider,
3048
3470
  TextEditor,
3049
3471
  TextKind,
3050
- YAwarenessProvider,
3472
+ Toolbar,
3051
3473
  YText,
3052
3474
  YXmlFragment,
3053
3475
  autocomplete,
3054
- automerge3 as automerge,
3476
+ automerge,
3055
3477
  awareness,
3056
- basicBundle,
3478
+ awarenessProvider,
3057
3479
  blast,
3480
+ callbackWrapper,
3058
3481
  code2 as code,
3059
3482
  codeTheme,
3060
3483
  comments,
3061
- createAutomergeModel,
3062
- createYjsModel,
3484
+ createBasicBundle,
3485
+ createComment,
3486
+ createDataExtensions,
3487
+ createMarkdownExtensions,
3488
+ createThemeExtensions,
3063
3489
  defaultMarkdownSlots,
3064
3490
  defaultOptions,
3065
3491
  defaultSlots,
3066
3492
  defaultTextSlots,
3067
3493
  defaultTheme,
3494
+ focusComment,
3495
+ formatting,
3068
3496
  getToken,
3497
+ heading2 as heading,
3069
3498
  hr,
3070
3499
  image,
3500
+ insertCodeblock,
3501
+ insertTable,
3071
3502
  link,
3072
3503
  listener,
3073
- markdownBundle,
3504
+ logChanges,
3074
3505
  markdownHighlightStyle,
3075
3506
  markdownTags,
3076
3507
  markdownTagsExtensions,
3077
3508
  markdownTheme,
3078
3509
  mention,
3079
3510
  modelState,
3080
- setCommentRange,
3081
- setFocus,
3511
+ outliner,
3512
+ setComments,
3513
+ setHeading,
3082
3514
  setSelection,
3083
3515
  table,
3084
3516
  tags2 as tags,
3085
3517
  tasklist,
3086
3518
  textTheme,
3519
+ toggleBold,
3520
+ toggleItalic,
3521
+ toggleList,
3522
+ toggleStrikethrough,
3523
+ toggleStyle,
3087
3524
  typewriter,
3525
+ useActionHandler,
3526
+ useComments,
3527
+ useEditorView,
3528
+ useTextEditor,
3088
3529
  useTextModel,
3089
- yjs
3530
+ useToolbarContext
3090
3531
  };
3091
3532
  //# sourceMappingURL=index.mjs.map