@dxos/react-ui-editor 0.3.11-main.e716c43 → 0.3.11-main.ea1c9ce

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