@dxos/react-ui-editor 0.3.10 → 0.3.11-main.00a28cb

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 (222) hide show
  1. package/dist/lib/browser/index.mjs +1724 -1105
  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 +24 -13
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +15 -19
  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 -3
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -2
  11. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  14. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  15. package/dist/types/src/components/TextEditor/index.d.ts +0 -2
  16. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  17. package/dist/types/src/{hooks/yjs → components/TextEditor}/yjs.stories.d.ts +1 -1
  18. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
  19. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
  20. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  21. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  22. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  23. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  24. package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
  25. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  26. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  27. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  28. package/dist/types/src/extensions/automerge/semaphore.d.ts +14 -0
  29. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  30. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  31. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  32. package/dist/types/src/extensions/automerge/update-codemirror.d.ts +5 -0
  33. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  34. package/dist/types/src/extensions/awareness.d.ts +46 -0
  35. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  36. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  37. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  38. package/dist/types/src/extensions/code.d.ts.map +1 -0
  39. package/dist/types/src/extensions/comments.d.ts +59 -0
  40. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  41. package/dist/types/src/extensions/cursor.d.ts +25 -0
  42. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  43. package/dist/types/src/extensions/hr.d.ts +2 -0
  44. package/dist/types/src/extensions/hr.d.ts.map +1 -0
  45. package/dist/types/src/extensions/image.d.ts.map +1 -0
  46. package/dist/types/src/{components/TextEditor/extensions → extensions}/index.d.ts +7 -3
  47. package/dist/types/src/extensions/index.d.ts.map +1 -0
  48. package/dist/types/src/{components/TextEditor/extensions → extensions}/link.d.ts +1 -0
  49. package/dist/types/src/extensions/link.d.ts.map +1 -0
  50. package/dist/types/src/extensions/listener.d.ts +10 -0
  51. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  52. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  53. package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/highlight.d.ts +0 -1
  54. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  55. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  56. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  57. package/dist/types/src/extensions/outliner.d.ts +4 -0
  58. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  59. package/dist/types/src/extensions/table.d.ts.map +1 -0
  60. package/dist/types/src/extensions/tasklist.d.ts +7 -0
  61. package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
  62. package/dist/types/src/{components/TextEditor/extensions/demo.d.ts → extensions/typewriter.d.ts} +2 -3
  63. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  64. package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
  65. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
  66. package/dist/types/src/extensions/yjs/index.d.ts +2 -0
  67. package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
  68. package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
  69. package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
  70. package/dist/types/src/extensions/yjs/yjs.test.d.ts +2 -0
  71. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
  72. package/dist/types/src/hooks/automerge.d.ts +3 -0
  73. package/dist/types/src/hooks/automerge.d.ts.map +1 -0
  74. package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
  75. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  76. package/dist/types/src/hooks/index.d.ts +3 -1
  77. package/dist/types/src/hooks/index.d.ts.map +1 -1
  78. package/dist/types/src/hooks/useTextModel.d.ts +15 -11
  79. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  80. package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
  81. package/dist/types/src/hooks/yjs.d.ts.map +1 -0
  82. package/dist/types/src/index.d.ts +6 -0
  83. package/dist/types/src/index.d.ts.map +1 -1
  84. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  85. package/dist/types/src/styles/index.d.ts +1 -0
  86. package/dist/types/src/styles/index.d.ts.map +1 -1
  87. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  88. package/dist/types/src/styles/tokens.d.ts +1 -0
  89. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  90. package/dist/types/src/testing/replicator.d.ts +2 -2
  91. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  92. package/dist/types/src/{components/TextEditor/themes → themes}/default.d.ts +5 -13
  93. package/dist/types/src/themes/default.d.ts.map +1 -0
  94. package/dist/types/src/themes/index.d.ts.map +1 -0
  95. package/dist/types/src/util.d.ts +5 -0
  96. package/dist/types/src/util.d.ts.map +1 -0
  97. package/package.json +28 -22
  98. package/src/components/TextEditor/MarkdownEditor.stories.tsx +150 -87
  99. package/src/components/TextEditor/TextEditor.stories.tsx +14 -35
  100. package/src/components/TextEditor/TextEditor.tsx +87 -79
  101. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +67 -8
  102. package/src/components/TextEditor/codemirror.stories.ts +3 -2
  103. package/src/components/TextEditor/comments.stories.tsx +355 -0
  104. package/src/components/TextEditor/index.ts +0 -3
  105. package/src/{hooks/yjs → components/TextEditor}/yjs.stories.tsx +1 -1
  106. package/src/{components/TextEditor/extensions → extensions}/autocomplete.ts +18 -17
  107. package/src/extensions/automerge/automerge.ts +92 -0
  108. package/src/extensions/automerge/cursor.ts +52 -0
  109. package/src/extensions/automerge/defs.ts +66 -0
  110. package/src/extensions/automerge/index.ts +6 -0
  111. package/src/{hooks/automerge/PatchSemaphore.ts → extensions/automerge/semaphore.ts} +26 -25
  112. package/src/{hooks/automerge/translation/codemirror-to-automerge.ts → extensions/automerge/update-automerge.ts} +6 -7
  113. package/src/{hooks/automerge/translation/automerge-to-codemirror.ts → extensions/automerge/update-codemirror.ts} +14 -11
  114. package/src/extensions/awareness.ts +310 -0
  115. package/src/extensions/code.ts +104 -0
  116. package/src/extensions/comments.ts +408 -0
  117. package/src/extensions/cursor.ts +46 -0
  118. package/src/extensions/hr.ts +72 -0
  119. package/src/extensions/image.ts +87 -0
  120. package/src/{components/TextEditor/extensions → extensions}/index.ts +7 -3
  121. package/src/{components/TextEditor/extensions → extensions}/link.ts +116 -62
  122. package/src/extensions/listener.ts +35 -0
  123. package/src/{components/TextEditor/extensions → extensions}/markdown/highlight.ts +4 -16
  124. package/src/extensions/outliner.ts +12 -0
  125. package/src/{components/TextEditor/extensions → extensions}/table.ts +23 -4
  126. package/src/extensions/tasklist.ts +96 -0
  127. package/src/{components/TextEditor/extensions/demo.ts → extensions/typewriter.ts} +2 -3
  128. package/src/extensions/yjs/cursor.ts +22 -0
  129. package/src/extensions/yjs/index.ts +5 -0
  130. package/src/extensions/yjs/yjs.test.ts +35 -0
  131. package/src/extensions/yjs/yjs.ts +16 -0
  132. package/src/hooks/automerge.ts +52 -0
  133. package/src/hooks/awareness-provider.ts +120 -0
  134. package/src/hooks/index.ts +4 -1
  135. package/src/hooks/useTextModel.ts +40 -79
  136. package/src/hooks/{yjs/space-provider.ts → yjs.ts} +49 -19
  137. package/src/index.ts +6 -0
  138. package/src/styles/index.ts +1 -0
  139. package/src/styles/markdown.ts +0 -2
  140. package/src/styles/tokens.ts +3 -0
  141. package/src/testing/replicator.ts +10 -16
  142. package/src/themes/default.ts +275 -0
  143. package/src/util.ts +18 -0
  144. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
  145. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
  146. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
  147. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +0 -1
  148. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +0 -18
  149. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +0 -1
  150. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +0 -1
  151. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts +0 -10
  152. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +0 -1
  153. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts +0 -24
  154. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts.map +0 -1
  155. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
  156. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  157. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
  158. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +0 -9
  159. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
  160. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  161. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
  162. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  163. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
  164. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
  165. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +0 -3
  166. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
  167. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
  168. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
  169. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
  170. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
  171. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts +0 -19
  172. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +0 -1
  173. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  174. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  175. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  176. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  177. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  178. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  179. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  180. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts +0 -5
  181. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts.map +0 -1
  182. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts +0 -6
  183. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts.map +0 -1
  184. package/dist/types/src/hooks/automerge/translation/index.d.ts +0 -3
  185. package/dist/types/src/hooks/automerge/translation/index.d.ts.map +0 -1
  186. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  187. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  188. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  189. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  190. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  191. package/src/components/TextEditor/extensions/code.ts +0 -99
  192. package/src/components/TextEditor/extensions/comments.ts +0 -173
  193. package/src/components/TextEditor/extensions/experimental.tsx +0 -114
  194. package/src/components/TextEditor/extensions/highlight.ts +0 -128
  195. package/src/components/TextEditor/extensions/image.ts +0 -67
  196. package/src/components/TextEditor/extensions/listener.ts +0 -23
  197. package/src/components/TextEditor/extensions/tasklist.ts +0 -121
  198. package/src/components/TextEditor/extensions/tooltip.tsx +0 -61
  199. package/src/components/TextEditor/themes/default.ts +0 -279
  200. package/src/hooks/automerge/handle.ts +0 -14
  201. package/src/hooks/automerge/index.ts +0 -6
  202. package/src/hooks/automerge/plugin.ts +0 -134
  203. package/src/hooks/automerge/translation/index.ts +0 -6
  204. package/src/hooks/yjs/index.ts +0 -6
  205. /package/dist/types/src/{components/TextEditor/extensions → extensions}/autocomplete.d.ts +0 -0
  206. /package/dist/types/src/{components/TextEditor/extensions → extensions}/basic.d.ts +0 -0
  207. /package/dist/types/src/{components/TextEditor/extensions → extensions}/blast.d.ts +0 -0
  208. /package/dist/types/src/{components/TextEditor/extensions → extensions}/code.d.ts +0 -0
  209. /package/dist/types/src/{components/TextEditor/extensions → extensions}/image.d.ts +0 -0
  210. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/bundle.d.ts +0 -0
  211. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/index.d.ts +0 -0
  212. /package/dist/types/src/{components/TextEditor/extensions → extensions}/mention.d.ts +0 -0
  213. /package/dist/types/src/{components/TextEditor/extensions → extensions}/table.d.ts +0 -0
  214. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  215. /package/dist/types/src/{components/TextEditor/themes → themes}/index.d.ts +0 -0
  216. /package/src/{components/TextEditor/extensions → extensions}/basic.ts +0 -0
  217. /package/src/{components/TextEditor/extensions → extensions}/blast.ts +0 -0
  218. /package/src/{components/TextEditor/extensions → extensions}/markdown/bundle.ts +0 -0
  219. /package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +0 -0
  220. /package/src/{components/TextEditor/extensions → extensions}/mention.ts +0 -0
  221. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  222. /package/src/{components/TextEditor/themes → themes}/index.ts +0 -0
@@ -5,46 +5,614 @@ import { Doc, YText, YXmlFragment } from "@dxos/text-model";
5
5
  // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
6
6
  import { tags as tags2 } from "@lezer/highlight";
7
7
 
8
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/autocomplete.ts
8
+ // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
9
+ import { EditorState as EditorState2 } from "@codemirror/state";
10
+ import { EditorView as EditorView12 } from "@codemirror/view";
11
+ import { useFocusableGroup } from "@fluentui/react-tabster";
12
+ import { vim } from "@replit/codemirror-vim";
13
+ import defaultsDeep2 from "lodash.defaultsdeep";
14
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState, useRef } from "react";
15
+ import { generateName } from "@dxos/display-name";
16
+ import { useThemeContext } from "@dxos/react-ui";
17
+ import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
18
+
19
+ // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
9
20
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
21
+ import { markdownLanguage } from "@codemirror/lang-markdown";
10
22
  import { keymap } from "@codemirror/view";
11
23
  var autocomplete = ({ onSearch }) => {
12
24
  return [
13
25
  // https://codemirror.net/docs/ref/#view.keymap
14
26
  // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
27
+ // TODO(burdon): Set custom keymap.
15
28
  keymap.of(completionKeymap),
16
29
  // https://codemirror.net/examples/autocompletion
17
30
  // https://codemirror.net/docs/ref/#autocomplete.autocompletion
18
31
  autocompletion({
19
- // TODO(burdon): Set custom keymap.
20
- // defaultKeymap: false,
32
+ defaultKeymap: false,
21
33
  // Don't start unless key press.
22
- activateOnTyping: false,
23
- // TODO(burdon): Option to create new page?
24
- // TODO(burdon): Optional decoration via addToOptions
25
- override: [
26
- (context) => {
27
- const match = context.matchBefore(/\w*/);
28
- if (!match || match.from === match.to && !context.explicit) {
29
- return null;
30
- }
31
- return {
32
- from: match.from,
33
- options: onSearch(match.text.toLowerCase())
34
- };
34
+ activateOnTyping: false
35
+ }),
36
+ // TODO(burdon): Option to create new page?
37
+ // TODO(burdon): Optional decoration via addToOptions
38
+ markdownLanguage.data.of({
39
+ autocomplete: (context) => {
40
+ const match = context.matchBefore(/\w*/);
41
+ if (!match || match.from === match.to && !context.explicit) {
42
+ return null;
35
43
  }
36
- ]
44
+ return {
45
+ from: match.from,
46
+ options: onSearch(match.text.toLowerCase())
47
+ };
48
+ }
37
49
  })
38
50
  ];
39
51
  };
40
52
 
41
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic.ts
53
+ // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
54
+ import { StateField } from "@codemirror/state";
55
+ import { ViewPlugin } from "@codemirror/view";
56
+ import { next as A } from "@dxos/automerge/automerge";
57
+
58
+ // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
59
+ import get from "lodash.get";
60
+ import { next as automerge } from "@dxos/automerge/automerge";
61
+ var cursorConverter = (handle, path) => ({
62
+ // TODO(burdon): Handle assoc to associate with a previous character.
63
+ toCursor: (pos) => {
64
+ const doc = handle.docSync();
65
+ if (!doc) {
66
+ return "";
67
+ }
68
+ const value = get(doc, path);
69
+ if (typeof value === "string" && value.length <= pos) {
70
+ return "end";
71
+ }
72
+ return automerge.getCursor(doc, path.slice(), pos);
73
+ },
74
+ fromCursor: (cursor) => {
75
+ if (cursor === "") {
76
+ return 0;
77
+ }
78
+ const doc = handle.docSync();
79
+ if (!doc) {
80
+ return 0;
81
+ }
82
+ if (cursor === "end") {
83
+ const value = get(doc, path);
84
+ if (typeof value === "string") {
85
+ return value.length;
86
+ } else {
87
+ return 0;
88
+ }
89
+ }
90
+ return automerge.getCursorPosition(doc, path.slice(), cursor);
91
+ }
92
+ });
93
+
94
+ // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
95
+ import { Annotation, StateEffect, Facet } from "@codemirror/state";
96
+ var effectType = StateEffect.define({});
97
+ var updateHeads = (newHeads) => effectType.of({
98
+ newHeads
99
+ });
100
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
101
+ var getPath = (state, field) => state.field(field).path;
102
+ var reconcileAnnotationType = Annotation.define();
103
+ var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
104
+ var semaphoreFacet = Facet.define({
105
+ combine: (values) => values.at(-1)
106
+ });
107
+
108
+ // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
109
+ import { next as automerge2 } from "@dxos/automerge/automerge";
110
+
111
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
112
+ import { next as am } from "@dxos/automerge/automerge";
113
+ var updateAutomerge = (field, handle, transactions, state) => {
114
+ const { lastHeads, path } = state.field(field);
115
+ let hasChanges = false;
116
+ for (const tr of transactions) {
117
+ tr.changes.iterChanges(() => {
118
+ hasChanges = true;
119
+ });
120
+ }
121
+ if (!hasChanges) {
122
+ return void 0;
123
+ }
124
+ const newHeads = handle.changeAt(lastHeads, (doc) => {
125
+ for (const tr of transactions) {
126
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
127
+ am.splice(doc, path, fromA, toA - fromA, inserted.toString());
128
+ });
129
+ }
130
+ });
131
+ return newHeads ?? void 0;
132
+ };
133
+
134
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-codemirror.ts
135
+ import { ChangeSet } from "@codemirror/state";
136
+ var updateCodeMirror = (view, selection, target, patches) => {
137
+ for (const patch of patches) {
138
+ const changeSpec = handlePatch(patch, target, view.state);
139
+ if (changeSpec != null) {
140
+ const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
141
+ selection = selection.map(changeSet, 1);
142
+ view.dispatch({
143
+ changes: changeSet,
144
+ annotations: reconcileAnnotationType.of({})
145
+ });
146
+ }
147
+ }
148
+ view.dispatch({
149
+ selection,
150
+ annotations: reconcileAnnotationType.of({})
151
+ });
152
+ };
153
+ var handlePatch = (patch, target, state) => {
154
+ if (patch.action === "insert") {
155
+ return handleInsert(target, patch);
156
+ } else if (patch.action === "splice") {
157
+ return handleSplice(target, patch);
158
+ } else if (patch.action === "del") {
159
+ return handleDel(target, patch);
160
+ } else if (patch.action === "put") {
161
+ return handlePut(target, patch, state);
162
+ } else {
163
+ return null;
164
+ }
165
+ };
166
+ var handleInsert = (target, patch) => {
167
+ const index = charPath(target, patch.path);
168
+ if (index == null) {
169
+ return [];
170
+ }
171
+ const text = patch.values.map((v) => v ? v.toString() : "").join("");
172
+ return [
173
+ {
174
+ from: index,
175
+ to: index,
176
+ insert: text
177
+ }
178
+ ];
179
+ };
180
+ var handleSplice = (target, patch) => {
181
+ const index = charPath(target, patch.path);
182
+ if (index == null) {
183
+ return [];
184
+ }
185
+ return [
186
+ {
187
+ from: index,
188
+ insert: patch.value
189
+ }
190
+ ];
191
+ };
192
+ var handleDel = (target, patch) => {
193
+ const index = charPath(target, patch.path);
194
+ if (index == null) {
195
+ return [];
196
+ }
197
+ const length = patch.length || 1;
198
+ return [
199
+ {
200
+ from: index,
201
+ to: index + length
202
+ }
203
+ ];
204
+ };
205
+ var handlePut = (target, patch, state) => {
206
+ const index = charPath(target, [
207
+ ...patch.path,
208
+ 0
209
+ ]);
210
+ if (index == null) {
211
+ return [];
212
+ }
213
+ const length = state.doc.length;
214
+ if (typeof patch.value !== "string") {
215
+ return [];
216
+ }
217
+ return [
218
+ {
219
+ from: 0,
220
+ to: length,
221
+ insert: patch.value
222
+ }
223
+ ];
224
+ };
225
+ var charPath = (textPath, candidatePath) => {
226
+ if (candidatePath.length !== textPath.length + 1) {
227
+ return null;
228
+ }
229
+ for (let i = 0; i < textPath.length; i++) {
230
+ if (textPath[i] !== candidatePath[i]) {
231
+ return null;
232
+ }
233
+ }
234
+ const index = candidatePath[candidatePath.length - 1];
235
+ if (typeof index === "number") {
236
+ return index;
237
+ }
238
+ return null;
239
+ };
240
+
241
+ // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
242
+ var PatchSemaphore = class {
243
+ constructor(_handle, _field) {
244
+ this._handle = _handle;
245
+ this._field = _field;
246
+ this._inReconcile = false;
247
+ this.reconcile = (view) => {
248
+ if (this._inReconcile) {
249
+ return;
250
+ }
251
+ this._inReconcile = true;
252
+ const path = getPath(view.state, this._field);
253
+ const oldHeads = getLastHeads(view.state, this._field);
254
+ let selection = view.state.selection;
255
+ const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
256
+ const toInvert = transactions.slice().reverse();
257
+ for (const tx of toInvert) {
258
+ const inverted = tx.changes.invert(tx.startState.doc);
259
+ selection = selection.map(inverted);
260
+ view.dispatch({
261
+ changes: inverted,
262
+ annotations: reconcileAnnotationType.of(true)
263
+ });
264
+ }
265
+ let newHeads = updateAutomerge(this._field, this._handle, transactions, view.state);
266
+ if (newHeads === null || newHeads === void 0) {
267
+ newHeads = automerge2.getHeads(this._handle.docSync());
268
+ }
269
+ const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(this._handle.docSync(), oldHeads, newHeads);
270
+ updateCodeMirror(view, selection, path, diff);
271
+ view.dispatch({
272
+ effects: updateHeads(newHeads),
273
+ annotations: reconcileAnnotationType.of({})
274
+ });
275
+ this._inReconcile = false;
276
+ };
277
+ }
278
+ };
279
+
280
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
281
+ import { Facet as Facet2 } from "@codemirror/state";
282
+ var defaultCursorConverter = {
283
+ toCursor: (position) => position.toString(),
284
+ fromCursor: (cursor) => parseInt(cursor)
285
+ };
286
+ var Cursor = class {
287
+ static {
288
+ this.converter = Facet2.define({
289
+ combine: (providers) => providers[0] ?? defaultCursorConverter
290
+ });
291
+ }
292
+ static {
293
+ this.getCursorFromRange = (cursorConverter3, range) => {
294
+ const from = cursorConverter3.toCursor(range.from);
295
+ const to = cursorConverter3.toCursor(range.to, -1);
296
+ return [
297
+ from,
298
+ to
299
+ ].join(":");
300
+ };
301
+ }
302
+ static {
303
+ this.getRangeFromCursor = (cursorConverter3, cursor) => {
304
+ const parts = cursor.split(":");
305
+ const from = cursorConverter3.fromCursor(parts[0]);
306
+ const to = cursorConverter3.fromCursor(parts[1]);
307
+ return from !== void 0 && to !== void 0 ? {
308
+ from,
309
+ to
310
+ } : void 0;
311
+ };
312
+ }
313
+ };
314
+
315
+ // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
316
+ var automerge3 = ({ handle, path }) => {
317
+ const stateField = StateField.define({
318
+ create: () => ({
319
+ lastHeads: A.getHeads(handle.docSync()),
320
+ unreconciledTransactions: [],
321
+ path: path.slice()
322
+ }),
323
+ update: (value, tr) => {
324
+ const result = {
325
+ lastHeads: value.lastHeads,
326
+ unreconciledTransactions: value.unreconciledTransactions.slice(),
327
+ path: path.slice()
328
+ };
329
+ let clearUnreconciled = false;
330
+ for (const effect of tr.effects) {
331
+ if (effect.is(effectType)) {
332
+ result.lastHeads = effect.value.newHeads;
333
+ clearUnreconciled = true;
334
+ }
335
+ }
336
+ if (clearUnreconciled) {
337
+ result.unreconciledTransactions = [];
338
+ } else {
339
+ if (!isReconcileTx(tr)) {
340
+ result.unreconciledTransactions.push(tr);
341
+ }
342
+ }
343
+ return result;
344
+ }
345
+ });
346
+ const semaphore = new PatchSemaphore(handle, stateField);
347
+ const viewPlugin = ViewPlugin.fromClass(class AutomergeViewPlugin {
348
+ constructor(_view) {
349
+ this._view = _view;
350
+ this._handleChange = () => {
351
+ this._view.state.facet(semaphoreFacet).reconcile(this._view);
352
+ };
353
+ handle.addListener("change", this._handleChange);
354
+ }
355
+ update(update2) {
356
+ if (update2.transactions.length > 0 && update2.transactions.some((tr) => !isReconcileTx(tr))) {
357
+ queueMicrotask(() => {
358
+ this._view.state.facet(semaphoreFacet).reconcile(this._view);
359
+ });
360
+ }
361
+ }
362
+ destroy() {
363
+ handle.addListener("change", this._handleChange);
364
+ }
365
+ });
366
+ return {
367
+ extension: [
368
+ Cursor.converter.of(cursorConverter(handle, path)),
369
+ semaphoreFacet.of(semaphore),
370
+ stateField,
371
+ viewPlugin
372
+ ]
373
+ };
374
+ };
375
+
376
+ // packages/ui/react-ui-editor/src/extensions/awareness.ts
377
+ import { Annotation as Annotation2, Facet as Facet3, RangeSet } from "@codemirror/state";
378
+ import { Decoration, EditorView, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
379
+ import { Event } from "@dxos/async";
380
+ import { Context } from "@dxos/context";
381
+ var dummyProvider = {
382
+ remoteStateChange: new Event(),
383
+ open: () => {
384
+ },
385
+ close: () => {
386
+ },
387
+ update: () => {
388
+ },
389
+ getRemoteStates: () => []
390
+ };
391
+ var AwarenessProvider = Facet3.define({
392
+ combine: (providers) => providers[0] ?? dummyProvider
393
+ });
394
+ var RemoteSelectionChangedAnnotation = Annotation2.define();
395
+ var awareness = (provider = dummyProvider) => {
396
+ return [
397
+ AwarenessProvider.of(provider),
398
+ ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
399
+ decorations: (value) => value.decorations
400
+ }),
401
+ styles
402
+ ];
403
+ };
404
+ var RemoteSelectionsDecorator = class {
405
+ constructor(view) {
406
+ this.decorations = RangeSet.of([]);
407
+ this._ctx = new Context();
408
+ this._lastAnchor = void 0;
409
+ this._lastHead = void 0;
410
+ this._cursorConverter = view.state.facet(Cursor.converter);
411
+ this._provider = view.state.facet(AwarenessProvider);
412
+ this._provider.open();
413
+ this._provider.remoteStateChange.on(this._ctx, () => {
414
+ view.dispatch({
415
+ annotations: [
416
+ RemoteSelectionChangedAnnotation.of([])
417
+ ]
418
+ });
419
+ });
420
+ }
421
+ destroy() {
422
+ void this._ctx.dispose();
423
+ this._provider.close();
424
+ }
425
+ update(update2) {
426
+ this._updateLocalSelection(update2);
427
+ this._updateRemoteSelections(update2);
428
+ }
429
+ _updateLocalSelection(update2) {
430
+ const hasFocus = update2.view.hasFocus && update2.view.dom.ownerDocument.hasFocus();
431
+ const { anchor = void 0, head = void 0 } = hasFocus ? update2.state.selection.main : {};
432
+ if (this._lastAnchor === anchor && this._lastHead === head) {
433
+ return;
434
+ }
435
+ this._lastAnchor = anchor;
436
+ this._lastHead = head;
437
+ this._provider.update(anchor !== void 0 && head !== void 0 ? {
438
+ anchor: this._cursorConverter.toCursor(anchor),
439
+ head: this._cursorConverter.toCursor(head)
440
+ } : void 0);
441
+ }
442
+ _updateRemoteSelections(update2) {
443
+ const decorations = [];
444
+ const awarenessStates = this._provider.getRemoteStates();
445
+ for (const state of awarenessStates) {
446
+ const anchor = state.position?.anchor ? this._cursorConverter.fromCursor(state.position.anchor) : null;
447
+ const head = state.position?.head ? this._cursorConverter.fromCursor(state.position.head) : null;
448
+ if (anchor == null || head == null) {
449
+ continue;
450
+ }
451
+ const start = Math.min(Math.min(anchor, head), update2.view.state.doc.length);
452
+ const end = Math.min(Math.max(anchor, head), update2.view.state.doc.length);
453
+ const startLine = update2.view.state.doc.lineAt(start);
454
+ const endLine = update2.view.state.doc.lineAt(end);
455
+ const color = state.info.color ?? "#30bced";
456
+ const lightColor = state.info.lightColor ?? color + "33";
457
+ if (startLine.number === endLine.number) {
458
+ decorations.push({
459
+ from: start,
460
+ to: end,
461
+ value: Decoration.mark({
462
+ attributes: {
463
+ style: `background-color: ${lightColor}`
464
+ },
465
+ class: "cm-collab-selection"
466
+ })
467
+ });
468
+ } else {
469
+ decorations.push({
470
+ from: start,
471
+ to: startLine.from + startLine.length,
472
+ value: Decoration.mark({
473
+ attributes: {
474
+ style: `background-color: ${color}`
475
+ },
476
+ class: "cm-collab-selection"
477
+ })
478
+ });
479
+ decorations.push({
480
+ from: endLine.from,
481
+ to: end,
482
+ value: Decoration.mark({
483
+ attributes: {
484
+ style: `background-color: ${color}`
485
+ },
486
+ class: "cm-collab-selection"
487
+ })
488
+ });
489
+ for (let i = startLine.number + 1; i < endLine.number; i++) {
490
+ const linePos = update2.view.state.doc.line(i).from;
491
+ decorations.push({
492
+ from: linePos,
493
+ to: linePos,
494
+ value: Decoration.line({
495
+ attributes: {
496
+ style: `background-color: ${color}`,
497
+ class: "cm-collab-selectionLine"
498
+ }
499
+ })
500
+ });
501
+ }
502
+ }
503
+ decorations.push({
504
+ from: head,
505
+ to: head,
506
+ value: Decoration.widget({
507
+ side: head - anchor > 0 ? -1 : 1,
508
+ block: false,
509
+ widget: new RemoteCaretWidget(state.info.displayName ?? "Anonymous", color)
510
+ })
511
+ });
512
+ }
513
+ this.decorations = Decoration.set(decorations, true);
514
+ }
515
+ };
516
+ var RemoteCaretWidget = class extends WidgetType {
517
+ constructor(_name, _color) {
518
+ super();
519
+ this._name = _name;
520
+ this._color = _color;
521
+ }
522
+ toDOM() {
523
+ const span = document.createElement("span");
524
+ span.className = "cm-collab-selectionCaret";
525
+ span.style.backgroundColor = this._color;
526
+ span.style.borderColor = this._color;
527
+ const dot = document.createElement("div");
528
+ dot.className = "cm-collab-selectionCaretDot";
529
+ const info = document.createElement("div");
530
+ info.className = "cm-collab-selectionInfo";
531
+ info.innerText = this._name;
532
+ span.appendChild(document.createTextNode("\u2060"));
533
+ span.appendChild(dot);
534
+ span.appendChild(document.createTextNode("\u2060"));
535
+ span.appendChild(info);
536
+ span.appendChild(document.createTextNode("\u2060"));
537
+ return span;
538
+ }
539
+ updateDOM() {
540
+ return false;
541
+ }
542
+ eq(widget) {
543
+ return widget._color === this._color;
544
+ }
545
+ get estimatedHeight() {
546
+ return -1;
547
+ }
548
+ ignoreEvent() {
549
+ return true;
550
+ }
551
+ };
552
+ var styles = EditorView.baseTheme({
553
+ ".cm-collab-selection": {},
554
+ ".cm-collab-selectionLine": {
555
+ padding: 0,
556
+ margin: "0px 2px 0px 4px"
557
+ },
558
+ ".cm-collab-selectionCaret": {
559
+ position: "relative",
560
+ borderLeft: "1px solid black",
561
+ borderRight: "1px solid black",
562
+ marginLeft: "-1px",
563
+ marginRight: "-1px",
564
+ boxSizing: "border-box",
565
+ display: "inline"
566
+ },
567
+ ".cm-collab-selectionCaretDot": {
568
+ borderRadius: "50%",
569
+ position: "absolute",
570
+ width: ".4em",
571
+ height: ".4em",
572
+ top: "-.2em",
573
+ left: "-.2em",
574
+ backgroundColor: "inherit",
575
+ transition: "transform .3s ease-in-out",
576
+ boxSizing: "border-box"
577
+ },
578
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
579
+ transformOrigin: "bottom center",
580
+ transform: "scale(0)"
581
+ },
582
+ ".cm-collab-selectionInfo": {
583
+ position: "absolute",
584
+ top: "-1.05em",
585
+ left: "-1px",
586
+ fontSize: ".75em",
587
+ fontFamily: "serif",
588
+ fontStyle: "normal",
589
+ fontWeight: "normal",
590
+ lineHeight: "normal",
591
+ userSelect: "none",
592
+ color: "white",
593
+ paddingLeft: "2px",
594
+ paddingRight: "2px",
595
+ zIndex: 101,
596
+ transition: "opacity .3s ease-in-out",
597
+ backgroundColor: "inherit",
598
+ // These should be separate.
599
+ opacity: 0,
600
+ transitionDelay: "0s",
601
+ whiteSpace: "nowrap"
602
+ },
603
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
604
+ opacity: 1,
605
+ transitionDelay: "0s"
606
+ }
607
+ });
608
+
609
+ // packages/ui/react-ui-editor/src/extensions/basic.ts
42
610
  import { closeBrackets } from "@codemirror/autocomplete";
43
611
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
44
612
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
45
- import { drawSelection, EditorView, placeholder } from "@codemirror/view";
613
+ import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
46
614
  var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
47
- EditorView.lineWrapping,
615
+ EditorView2.lineWrapping,
48
616
  // TODO(burdon): Compare with minimalSetup.
49
617
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
50
618
  bracketMatching(),
@@ -55,11 +623,11 @@ var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
55
623
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
56
624
  ].filter(Boolean);
57
625
 
58
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts
59
- import { EditorView as EditorView2, keymap as keymap2 } from "@codemirror/view";
626
+ // packages/ui/react-ui-editor/src/extensions/blast.ts
627
+ import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
60
628
  import defaultsDeep from "lodash.defaultsdeep";
61
629
  import { invariant } from "@dxos/invariant";
62
- var __dxlog_file = "/home/circleci/project/packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts";
630
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
63
631
  var defaultOptions = {
64
632
  effect: 2,
65
633
  maxParticles: 200,
@@ -104,12 +672,12 @@ var blast = (options = defaultOptions) => {
104
672
  };
105
673
  return [
106
674
  // Cursor moved.
107
- EditorView2.updateListener.of((update4) => {
108
- if (blaster?.node !== update4.view.scrollDOM) {
675
+ EditorView3.updateListener.of((update2) => {
676
+ if (blaster?.node !== update2.view.scrollDOM) {
109
677
  if (blaster) {
110
678
  blaster.destroy();
111
679
  }
112
- blaster = new Blaster(update4.view.scrollDOM, defaultsDeep({
680
+ blaster = new Blaster(update2.view.scrollDOM, defaultsDeep({
113
681
  particleGravity: 0.2,
114
682
  particleShrinkRate: 0.995,
115
683
  color: () => [
@@ -123,10 +691,10 @@ var blast = (options = defaultOptions) => {
123
691
  } else {
124
692
  blaster.resize();
125
693
  }
126
- const current = update4.state.selection.main.head;
694
+ const current = update2.state.selection.main.head;
127
695
  if (current !== last) {
128
696
  last = current;
129
- const { element, point } = getPoint(update4.view, current - 1);
697
+ const { element, point } = getPoint(update2.view, current - 1);
130
698
  if (element && point) {
131
699
  blaster.spawn({
132
700
  element,
@@ -365,11 +933,48 @@ var random = (min, max) => {
365
933
  return min + ~~(Math.random() * (max - min + 1));
366
934
  };
367
935
 
368
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/code.ts
369
- import { ViewPlugin, EditorView as EditorView3, Decoration } from "@codemirror/view";
370
- import get from "lodash.get";
936
+ // packages/ui/react-ui-editor/src/extensions/code.ts
937
+ import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
371
938
  import { mx as mx2 } from "@dxos/react-ui-theme";
372
939
 
940
+ // packages/ui/react-ui-editor/src/styles/cursors.ts
941
+ import * as random2 from "lib0/random";
942
+ var cursorColors = [
943
+ {
944
+ color: "#30bced",
945
+ light: "#30bced33"
946
+ },
947
+ {
948
+ color: "#6eeb83",
949
+ light: "#6eeb8333"
950
+ },
951
+ {
952
+ color: "#ffbc42",
953
+ light: "#ffbc4233"
954
+ },
955
+ {
956
+ color: "#ecd444",
957
+ light: "#ecd44433"
958
+ },
959
+ {
960
+ color: "#ee6352",
961
+ light: "#ee635233"
962
+ },
963
+ {
964
+ color: "#9ac2c9",
965
+ light: "#9ac2c933"
966
+ },
967
+ {
968
+ color: "#8acb88",
969
+ light: "#8acb8833"
970
+ },
971
+ {
972
+ color: "#1be7ff",
973
+ light: "#1be7ff33"
974
+ }
975
+ ];
976
+ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
977
+
373
978
  // packages/ui/react-ui-editor/src/styles/markdown.ts
374
979
  import { mx } from "@dxos/react-ui-theme";
375
980
  var headings = {
@@ -411,31 +1016,35 @@ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
411
1016
  var codeMark = "font-mono text-primary-500";
412
1017
  var inlineUrl = mx(code, "px-1");
413
1018
  var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
414
- var horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800";
415
1019
 
416
1020
  // packages/ui/react-ui-editor/src/styles/tokens.ts
1021
+ import get2 from "lodash.get";
417
1022
  import { tailwindConfig } from "@dxos/react-ui-theme";
418
1023
  var tokens = tailwindConfig({}).theme;
1024
+ var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue);
419
1025
 
420
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/code.ts
1026
+ // packages/ui/react-ui-editor/src/extensions/code.ts
421
1027
  var CODE_REGEX = /```[\s\S]*?```/gs;
422
- var styles = EditorView3.baseTheme({
423
- "& .cm-code-block": {
424
- display: "block",
425
- paddingInline: "4px !important"
1028
+ var styles2 = EditorView4.baseTheme({
1029
+ "& .cm-codeblock": {
1030
+ fontFamily: getToken("fontFamily.mono", []).join(",")
1031
+ },
1032
+ '&light [aria-readonly="true"] .cm-codeblock': {
1033
+ background: getToken("extend.colors.neutral.50")
426
1034
  },
427
- "&light .cm-code-block": {
428
- background: get(tokens, "extend.colors.neutral.50")
1035
+ '&dark [aria-readonly="true"] .cm-codeblock': {
1036
+ background: getToken("extend.colors.neutral.850")
429
1037
  },
430
- "&dark .cm-code-block": {
431
- background: get(tokens, "extend.colors.neutral.850")
1038
+ '& [aria-readonly="true"] .cm-codeblock': {
1039
+ display: "block",
1040
+ paddingInline: "4px !important"
432
1041
  },
433
- "& .cm-code-block-first": {
1042
+ '& [aria-readonly="true"] .cm-codeblock-first': {
434
1043
  paddingTop: "4px !important",
435
1044
  borderTopLeftRadius: "4px",
436
1045
  borderTopRightRadius: "4px"
437
1046
  },
438
- "& .cm-code-block-last": {
1047
+ '& [aria-readonly="true"] .cm-codeblock-last': {
439
1048
  paddingBottom: "4px !important",
440
1049
  borderBottomLeftRadius: "4px",
441
1050
  borderBottomRightRadius: "4px"
@@ -445,361 +1054,451 @@ var getLineRange = (lines, from, to) => {
445
1054
  const start = lines.findIndex((line) => line.from >= from);
446
1055
  const end = lines.findIndex((line) => line.to >= to);
447
1056
  return [
448
- start,
449
- end
450
- ];
451
- };
452
- var code2 = () => {
453
- const getDecorations = (view) => {
454
- const decorations = [];
455
- if (view.state.readOnly) {
456
- const text = view.state.doc.sliceString(0);
457
- const matches = text.matchAll(CODE_REGEX);
458
- const blocks = view.viewportLineBlocks;
459
- for (const match of matches) {
460
- const range = getLineRange(blocks, match.index, match.index + match[0].length);
461
- for (let i = range[0]; i <= range[1]; i++) {
462
- const block = blocks[i];
463
- decorations.push(Decoration.line({
464
- class: mx2("cm-code-block", i === range[0] && "cm-code-block-first", i === range[1] && "cm-code-block-last")
465
- }).range(block.from));
466
- }
467
- }
468
- }
469
- return Decoration.set(decorations);
470
- };
471
- return [
472
- ViewPlugin.fromClass(class {
473
- constructor(view) {
474
- this.decorations = getDecorations(view);
475
- }
476
- update(update4) {
477
- if (update4.docChanged || update4.viewportChanged) {
478
- this.decorations = getDecorations(update4.view);
479
- }
480
- }
481
- }, {
482
- decorations: (value) => value.decorations
483
- }),
484
- styles
485
- ];
486
- };
487
-
488
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/demo.ts
489
- import { keymap as keymap3 } from "@codemirror/view";
490
- var defaultItems = [
491
- "hello world!",
492
- "this is a test.",
493
- "this is [DXOS](https://dxos.org)"
494
- ];
495
- var demo = ({ delay = 75, items = defaultItems } = {}) => {
496
- let t;
497
- let idx = 0;
498
- return [
499
- keymap3.of([
500
- {
501
- // Reset.
502
- key: "alt-meta-'",
503
- run: (view) => {
504
- clearTimeout(t);
505
- idx = 0;
506
- return true;
507
- }
508
- },
509
- {
510
- // Next prompt.
511
- // TODO(burdon): Press 1-9 to select prompt?
512
- key: "shift-meta-'",
513
- run: (view) => {
514
- clearTimeout(t);
515
- const text = items[idx++];
516
- if (idx === items?.length) {
517
- idx = 0;
518
- }
519
- let i = 0;
520
- const insert = (d = 0) => {
521
- t = setTimeout(() => {
522
- const pos = view.state.selection.main.head;
523
- view.dispatch({
524
- changes: {
525
- from: pos,
526
- insert: text[i++]
527
- },
528
- selection: {
529
- anchor: pos + 1
530
- }
531
- });
532
- if (i < text.length) {
533
- insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
534
- }
535
- }, d);
536
- };
537
- insert();
538
- return true;
539
- }
540
- }
541
- ])
542
- ];
543
- };
544
-
545
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts
546
- import { keymap as keymap4, Decoration as Decoration2, EditorView as EditorView4, MatchDecorator, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
547
- import { debounce } from "@dxos/async";
548
- import { invariant as invariant2 } from "@dxos/invariant";
549
- var __dxlog_file2 = "/home/circleci/project/packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts";
550
- var styles2 = EditorView4.baseTheme({
551
- "& .cm-bookmark": {
552
- cursor: "pointer",
553
- margin: "4px",
554
- padding: "4px",
555
- backgroundColor: "yellow"
556
- },
557
- "& .cm-bookmark-selected": {
558
- backgroundColor: "orange"
559
- }
560
- });
561
- var BookmarkWidget = class extends WidgetType {
562
- constructor(_pos, _id, _handleClick) {
563
- super();
564
- this._pos = _pos;
565
- this._id = _id;
566
- this._handleClick = _handleClick;
567
- invariant2(this._id, void 0, {
568
- F: __dxlog_file2,
569
- L: 37,
570
- S: this,
571
- A: [
572
- "this._id",
573
- ""
574
- ]
575
- });
576
- }
577
- eq(other) {
578
- return this._id === other._id;
579
- }
580
- toDOM() {
581
- const span = document.createElement("span");
582
- span.className = "cm-bookmark";
583
- span.textContent = "\xA7";
584
- span.onclick = () => this._handleClick();
585
- return span;
586
- }
587
- };
588
- var comments = (options = {}) => {
589
- let active;
590
- const bookmarkMatcher = new MatchDecorator({
591
- regexp: /\[\^(\w+)\]/g,
592
- decoration: (match, view, pos) => {
593
- const id = match[1];
594
- return Decoration2.replace({
595
- id,
596
- widget: new BookmarkWidget(pos, id, () => handleUpdate({
597
- active: id
598
- }))
599
- });
600
- }
601
- });
602
- const bookmarks = ViewPlugin2.fromClass(class {
603
- constructor(view) {
604
- this.bookmarks = bookmarkMatcher.createDeco(view);
605
- }
606
- update(update4) {
607
- this.bookmarks = bookmarkMatcher.updateDeco(update4, this.bookmarks);
608
- }
609
- }, {
610
- decorations: (instance) => instance.bookmarks,
611
- provide: (plugin) => EditorView4.atomicRanges.of((view) => {
612
- return view.plugin(plugin)?.bookmarks || Decoration2.none;
613
- })
614
- });
615
- const handleUpdate = debounce((data) => {
616
- options.onUpdate?.(data);
617
- }, 200);
618
- return [
619
- keymap4.of([
620
- {
621
- key: options?.key ?? "shift-meta-c",
622
- run: (view) => {
623
- const { from, to, head } = view.state.selection.main;
624
- const id = options.onCreate?.(from, to);
625
- if (id) {
626
- const tag = `[^${id}]`;
627
- view.dispatch({
628
- changes: {
629
- from: head,
630
- insert: tag
631
- },
632
- selection: {
633
- anchor: head + tag.length
634
- }
635
- });
636
- return true;
637
- }
638
- return false;
639
- }
1057
+ start,
1058
+ end
1059
+ ];
1060
+ };
1061
+ var code2 = () => {
1062
+ const buildDecorations4 = (view) => {
1063
+ const decorations = [];
1064
+ const text = view.state.doc.sliceString(0);
1065
+ const matches = text.matchAll(CODE_REGEX);
1066
+ const blocks = view.viewportLineBlocks;
1067
+ for (const match of matches) {
1068
+ const range = getLineRange(blocks, match.index, match.index + match[0].length);
1069
+ for (let i = range[0]; i <= range[1]; i++) {
1070
+ const block = blocks[i];
1071
+ decorations.push(Decoration2.line({
1072
+ class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1073
+ }).range(block.from));
640
1074
  }
641
- ]),
642
- bookmarks,
643
- // Monitor cursor movement.
644
- EditorView4.updateListener.of((update4) => {
645
- active = void 0;
646
- if (!options.onUpdate) {
647
- return;
1075
+ }
1076
+ return Decoration2.set(decorations);
1077
+ };
1078
+ return [
1079
+ ViewPlugin3.fromClass(class {
1080
+ constructor(view) {
1081
+ this.hasFocus = false;
1082
+ this.decorations = buildDecorations4(view);
648
1083
  }
649
- const { view, state } = update4;
650
- const pos = state.selection.main.head;
651
- const decorations = [];
652
- const rangeSet = view.plugin(bookmarks)?.bookmarks;
653
- let closest = Infinity;
654
- const { from, to } = (
655
- /* view.visibleRanges?.[0] ?? */
656
- {
657
- from: 0,
658
- to: state.doc.length
659
- }
660
- );
661
- rangeSet?.between(from, to, (from2, to2, value) => {
662
- decorations.push({
663
- from: from2,
664
- to: to2,
665
- value
666
- });
667
- const d = Math.min(Math.abs(pos - from2), Math.abs(pos - to2));
668
- if (d < closest) {
669
- active = value.spec.id;
670
- closest = d;
1084
+ update(update2) {
1085
+ if (
1086
+ // TODO(burdon): Generalize for other extensions.
1087
+ this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1088
+ ) {
1089
+ this.hasFocus = update2.view.hasFocus;
1090
+ this.decorations = buildDecorations4(update2.view);
671
1091
  }
672
- });
673
- if (decorations.length) {
674
- handleUpdate({
675
- active,
676
- items: decorations.map(({ from: from2, value: { spec: { id } } }) => ({
677
- id,
678
- pos: from2,
679
- location: view.coordsAtPos(from2)
680
- }))
681
- });
682
1092
  }
1093
+ }, {
1094
+ decorations: (value) => value.decorations
683
1095
  }),
684
1096
  styles2
685
1097
  ];
686
1098
  };
687
1099
 
688
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/highlight.ts
689
- import { StateEffect, StateField } from "@codemirror/state";
690
- import { Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
1100
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
1101
+ import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1102
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
691
1103
  import sortBy from "lodash.sortby";
692
- import { useEffect } from "react";
1104
+ import { debounce } from "@dxos/async";
1105
+ import { invariant as invariant2 } from "@dxos/invariant";
1106
+ import { nonNullable } from "@dxos/util";
1107
+
1108
+ // packages/ui/react-ui-editor/src/util.ts
1109
+ import { log } from "@dxos/log";
1110
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1111
+ var callbackWrapper = (fn) => (...args) => {
1112
+ try {
1113
+ return fn(...args);
1114
+ } catch (error) {
1115
+ log.catch(error, void 0, {
1116
+ F: __dxlog_file2,
1117
+ L: 16,
1118
+ S: void 0,
1119
+ C: (f, a) => f(...a)
1120
+ });
1121
+ }
1122
+ };
1123
+
1124
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
1125
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
693
1126
  var styles3 = EditorView5.baseTheme({
694
- "& .cm-highlight": {
695
- backgroundColor: "yellow"
696
- },
697
- "& .cm-highlight::before": {
698
- content: "]"
1127
+ "& .cm-comment": {
1128
+ backgroundColor: getToken("extend.colors.yellow.50")
699
1129
  },
700
- "& .cm-highlight-active": {
701
- backgroundColor: "lime"
1130
+ "& .cm-comment-active": {
1131
+ backgroundColor: getToken("extend.colors.yellow.100")
702
1132
  }
703
1133
  });
704
1134
  var marks = {
705
1135
  highlight: Decoration3.mark({
706
- class: "cm-highlight"
1136
+ class: "cm-comment"
707
1137
  }),
708
- HighlightActive: Decoration3.mark({
709
- class: "cm-highlight-active"
1138
+ highlightActive: Decoration3.mark({
1139
+ class: "cm-comment-active"
710
1140
  })
711
1141
  };
712
- var setHighlights = StateEffect.define();
713
- var useHighlights = (view, ranges = []) => {
714
- useEffect(() => {
715
- view?.dispatch({
716
- effects: setHighlights.of(ranges)
717
- });
718
- }, [
719
- view,
720
- ranges
721
- ]);
1142
+ var setFocus = (view, thread) => {
1143
+ const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1144
+ if (!range) {
1145
+ return;
1146
+ }
1147
+ view.dispatch({
1148
+ effects: setSelection.of({
1149
+ active: thread
1150
+ }),
1151
+ selection: {
1152
+ anchor: range.from
1153
+ },
1154
+ scrollIntoView: true
1155
+ });
722
1156
  };
723
- var highlightStateField = StateField.define({
724
- create: () => [],
725
- update: (threads, tr) => {
1157
+ var setCommentRange = StateEffect2.define();
1158
+ var setSelection = StateEffect2.define();
1159
+ var setCommentState = StateEffect2.define();
1160
+ var commentsStateField = StateField2.define({
1161
+ create: () => ({
1162
+ ranges: []
1163
+ }),
1164
+ update: (value, tr) => {
1165
+ const cursorConverter3 = tr.state.facet(Cursor.converter);
726
1166
  for (const effect of tr.effects) {
727
- if (effect.is(setHighlights)) {
1167
+ if (effect.is(setSelection)) {
1168
+ return {
1169
+ ...value,
1170
+ ...effect.value
1171
+ };
1172
+ }
1173
+ if (effect.is(setCommentRange)) {
1174
+ const { comments: comments2 } = effect.value;
1175
+ const ranges = comments2.map((comment) => {
1176
+ const range = Cursor.getRangeFromCursor(cursorConverter3, comment.cursor);
1177
+ return range && {
1178
+ ...comment,
1179
+ ...range
1180
+ };
1181
+ }).filter(nonNullable);
1182
+ return {
1183
+ ...value,
1184
+ ranges
1185
+ };
1186
+ }
1187
+ if (effect.is(setCommentState)) {
728
1188
  return effect.value;
729
1189
  }
730
1190
  }
731
- return threads;
1191
+ return value;
732
1192
  }
733
1193
  });
734
1194
  var highlightDecorations = EditorView5.decorations.compute([
735
- highlightStateField
1195
+ commentsStateField
736
1196
  ], (state) => {
737
- const selectionRanges = state.field(highlightStateField);
738
- const decorations = sortBy(selectionRanges ?? [], (selection) => selection.from)?.flatMap((selection) => {
1197
+ const { active, ranges } = state.field(commentsStateField);
1198
+ const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1199
+ if (selection.from === selection.to) {
1200
+ return void 0;
1201
+ }
739
1202
  const range = {
740
1203
  from: selection.from,
741
- to: selection.to + 1
1204
+ to: selection.to
742
1205
  };
743
- if (selection.to > selection.from) {
744
- if (selection.active) {
745
- return marks.HighlightActive.range(range.from, range.to);
746
- } else {
747
- return marks.highlight.range(range.from, range.to);
748
- }
1206
+ if (selection.id === active) {
1207
+ return marks.highlightActive.range(range.from, range.to);
749
1208
  } else {
750
- return [];
1209
+ return marks.highlight.range(range.from, range.to);
751
1210
  }
752
- }) ?? [];
1211
+ }).filter(nonNullable) ?? [];
753
1212
  return Decoration3.set(decorations);
754
1213
  });
755
- var listener = (options) => {
756
- return EditorView5.updateListener.of((update4) => {
757
- const { view, state } = update4;
758
- const { head } = state.selection.main;
759
- const ranges = state.field(highlightStateField);
760
- let mod = false;
761
- const newRanges = ranges.map((range) => {
762
- const active = head >= range.from && head <= range.to;
763
- if (active !== range.active) {
764
- mod = true;
765
- return {
766
- ...range,
767
- active
768
- };
769
- } else {
770
- return range;
1214
+ var trackPastedComments = (onUpdate) => {
1215
+ let tracked = null;
1216
+ const handleTrack = (event, view) => {
1217
+ const comments2 = view.state.field(commentsStateField);
1218
+ const { main } = view.state.selection;
1219
+ const selectedRanges = comments2.ranges.filter((range) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1220
+ if (!selectedRanges.length) {
1221
+ tracked = null;
1222
+ } else {
1223
+ tracked = {
1224
+ text: view.state.doc.slice(main.from, main.to),
1225
+ comments: selectedRanges.map((range) => ({
1226
+ id: range.id,
1227
+ from: range.from - main.from,
1228
+ to: range.to - main.from
1229
+ }))
1230
+ };
1231
+ }
1232
+ };
1233
+ return [
1234
+ EditorView5.domEventHandlers({
1235
+ cut: handleTrack,
1236
+ copy: handleTrack
1237
+ }),
1238
+ // Handle paste.
1239
+ EditorView5.updateListener.of(({ view, state, transactions }) => {
1240
+ if (tracked) {
1241
+ const paste = transactions.find((tr) => tr.isUserEvent("input.paste"));
1242
+ if (paste) {
1243
+ let found = -1;
1244
+ paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1245
+ if (text.eq(tracked.text)) {
1246
+ for (let i = transactions.indexOf(paste) + 1; i < transactions.length; i++) {
1247
+ fromB = transactions[i].changes.mapPos(fromB);
1248
+ }
1249
+ found = fromB;
1250
+ }
1251
+ });
1252
+ if (found > -1) {
1253
+ const comments2 = tracked.comments;
1254
+ view.state.facet(semaphoreFacet).reconcile(view);
1255
+ const active = state.field(commentsStateField).ranges;
1256
+ for (const moved of comments2) {
1257
+ if (active.some((range) => range.id === moved.id && range.from === range.to)) {
1258
+ const range = {
1259
+ from: found + moved.from,
1260
+ to: found + moved.to
1261
+ };
1262
+ const cursor = Cursor.getCursorFromRange(state.facet(Cursor.converter), range);
1263
+ onUpdate(moved.id, cursor);
1264
+ }
1265
+ }
1266
+ }
1267
+ tracked = null;
1268
+ }
771
1269
  }
1270
+ })
1271
+ ];
1272
+ };
1273
+ var comments = (options = {}) => {
1274
+ const { key: shortcut = "meta-'" } = options;
1275
+ const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1276
+ const createCommentThread = (view) => {
1277
+ const cursorConverter3 = view.state.facet(Cursor.converter);
1278
+ invariant2(options.onCreate, void 0, {
1279
+ F: __dxlog_file3,
1280
+ L: 264,
1281
+ S: void 0,
1282
+ A: [
1283
+ "options.onCreate",
1284
+ ""
1285
+ ]
772
1286
  });
773
- if (mod) {
774
- view.dispatch({
775
- effects: setHighlights.of(newRanges)
776
- });
777
- options?.onChange?.(newRanges.find((range) => range.active)?.id);
1287
+ const { from, to } = view.state.selection.main;
1288
+ if (from === to) {
1289
+ return false;
778
1290
  }
779
- });
780
- };
781
- var highlight = (options = {}) => {
1291
+ const cursor = Cursor.getCursorFromRange(cursorConverter3, {
1292
+ from,
1293
+ to
1294
+ });
1295
+ if (cursor) {
1296
+ const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
1297
+ if (id) {
1298
+ view.dispatch({
1299
+ effects: setSelection.of({
1300
+ active: id
1301
+ }),
1302
+ selection: {
1303
+ anchor: from
1304
+ }
1305
+ });
1306
+ return true;
1307
+ }
1308
+ }
1309
+ return false;
1310
+ };
782
1311
  return [
783
- highlightStateField,
1312
+ commentsStateField,
784
1313
  highlightDecorations,
785
- listener(options),
786
- styles3
1314
+ styles3,
1315
+ //
1316
+ // Keymap.
1317
+ //
1318
+ options.onCreate ? keymap3.of([
1319
+ {
1320
+ key: shortcut,
1321
+ run: callbackWrapper(createCommentThread)
1322
+ }
1323
+ ]) : [],
1324
+ //
1325
+ // Hover tooltip (for key shortcut hints, etc.)
1326
+ // TODO(burdon): Factor out to generic hints extension for current selection/line.
1327
+ //
1328
+ options.onHover ? hoverTooltip((view, pos) => {
1329
+ const selection = view.state.selection.main;
1330
+ if (selection && pos >= selection.from && pos <= selection.to) {
1331
+ return {
1332
+ pos: selection.from,
1333
+ end: selection.to,
1334
+ above: true,
1335
+ create: () => {
1336
+ const el = document.createElement("div");
1337
+ options.onHover(el, shortcut);
1338
+ return {
1339
+ dom: el,
1340
+ offset: {
1341
+ x: 0,
1342
+ y: 8
1343
+ }
1344
+ };
1345
+ }
1346
+ };
1347
+ }
1348
+ return null;
1349
+ }, {
1350
+ // TODO(burdon): Hide on change triggered immediately?
1351
+ // hideOnChange: true,
1352
+ hoverTime: 1e3
1353
+ }) : [],
1354
+ //
1355
+ // Monitor cursor movement and text updates.
1356
+ // TODO(burdon): Is there a better (finer grained) way to do this?
1357
+ //
1358
+ EditorView5.updateListener.of(({ view, state, changes }) => {
1359
+ const cursorConverter3 = view.state.facet(Cursor.converter);
1360
+ {
1361
+ let mod = false;
1362
+ const { active, ranges } = state.field(commentsStateField);
1363
+ changes.iterChanges((from, to, from2, to2) => {
1364
+ ranges.forEach((range) => {
1365
+ if (from2 === to2) {
1366
+ const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1367
+ if (!newRange || newRange.to - newRange.from === 0) {
1368
+ options.onDelete?.(range.id);
1369
+ }
1370
+ }
1371
+ if (from <= range.to) {
1372
+ const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1373
+ Object.assign(range, newRange);
1374
+ mod = true;
1375
+ }
1376
+ });
1377
+ });
1378
+ if (mod) {
1379
+ view.dispatch({
1380
+ effects: setCommentState.of({
1381
+ active,
1382
+ ranges
1383
+ })
1384
+ });
1385
+ }
1386
+ }
1387
+ {
1388
+ const { head } = state.selection.main;
1389
+ let min = Infinity;
1390
+ const { active, closest, ranges } = state.field(commentsStateField);
1391
+ const selected = {
1392
+ active: void 0,
1393
+ closest: void 0
1394
+ };
1395
+ ranges.forEach((comment) => {
1396
+ const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1397
+ if (head >= comment.from && head <= comment.to) {
1398
+ selected.active = comment.id;
1399
+ }
1400
+ if (d < min) {
1401
+ selected.closest = comment.id;
1402
+ min = d;
1403
+ }
1404
+ });
1405
+ if (selected.active !== active || selected.closest !== closest) {
1406
+ view.dispatch({
1407
+ effects: setSelection.of(selected)
1408
+ });
1409
+ handleSelect({
1410
+ ...selected,
1411
+ ranges: ranges.map((range) => ({
1412
+ ...range,
1413
+ location: view.coordsAtPos(range.from)
1414
+ }))
1415
+ });
1416
+ }
1417
+ }
1418
+ }),
1419
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
787
1420
  ];
788
1421
  };
789
1422
 
790
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/image.ts
1423
+ // packages/ui/react-ui-editor/src/extensions/hr.ts
1424
+ import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1425
+ var styles4 = EditorView6.baseTheme({
1426
+ "& .cm-hr": {
1427
+ // TODO(burdon): ???
1428
+ // Note that block-level decorations should not have vertical margins,
1429
+ borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1430
+ }
1431
+ });
1432
+ var HorizontalRuleWidget = class extends WidgetType2 {
1433
+ constructor(_pos) {
1434
+ super();
1435
+ this._pos = _pos;
1436
+ }
1437
+ eq(other) {
1438
+ return this._pos === other._pos;
1439
+ }
1440
+ toDOM(view) {
1441
+ const el = document.createElement("div");
1442
+ el.className = "cm-hr";
1443
+ return el;
1444
+ }
1445
+ };
1446
+ var placeholderMatcher = new MatchDecorator({
1447
+ // regexp: /(?<=\n\n)---/gs,
1448
+ regexp: /^---$/gs,
1449
+ decoration: (match, view, pos) => Decoration4.replace({
1450
+ widget: new HorizontalRuleWidget(pos)
1451
+ })
1452
+ });
1453
+ var hr = () => [
1454
+ styles4,
1455
+ ViewPlugin4.fromClass(class {
1456
+ constructor(view) {
1457
+ this.rules = placeholderMatcher.createDeco(view);
1458
+ }
1459
+ update(update2) {
1460
+ this.rules = placeholderMatcher.updateDeco(update2, this.rules);
1461
+ }
1462
+ }, {
1463
+ decorations: (instance) => instance.rules
1464
+ })
1465
+ ];
1466
+
1467
+ // packages/ui/react-ui-editor/src/extensions/image.ts
791
1468
  import { syntaxTree } from "@codemirror/language";
792
- import { RangeSetBuilder, StateField as StateField2 } from "@codemirror/state";
793
- import { Decoration as Decoration4, EditorView as EditorView6, WidgetType as WidgetType2 } from "@codemirror/view";
1469
+ import { StateField as StateField3 } from "@codemirror/state";
1470
+ import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
794
1471
  var image = (options = {}) => {
795
- return StateField2.define({
796
- create: (state) => update(state, options),
797
- update: (_, tr) => update(tr.state, options),
798
- provide: (field) => EditorView6.decorations.from(field)
1472
+ return StateField3.define({
1473
+ create: (state) => {
1474
+ return Decoration5.set(buildDecorations(0, state.doc.length, state));
1475
+ },
1476
+ update: (value, tr) => {
1477
+ if (!tr.docChanged && !tr.selection) {
1478
+ return value;
1479
+ }
1480
+ const cursor = tr.state.selection.main.head;
1481
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
1482
+ let from = Math.min(cursor, oldCursor);
1483
+ let to = Math.max(cursor, oldCursor);
1484
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
1485
+ from = Math.min(from, fromB);
1486
+ to = Math.max(to, toB);
1487
+ });
1488
+ from = tr.state.doc.lineAt(from).from;
1489
+ to = tr.state.doc.lineAt(to).to;
1490
+ return value.map(tr.changes).update({
1491
+ filterFrom: from,
1492
+ filterTo: to,
1493
+ filter: () => false,
1494
+ add: buildDecorations(from, to, tr.state)
1495
+ });
1496
+ },
1497
+ provide: (field) => EditorView7.decorations.from(field)
799
1498
  });
800
1499
  };
801
- var update = (state, options) => {
802
- const builder = new RangeSetBuilder();
1500
+ var buildDecorations = (from, to, state) => {
1501
+ const decorations = [];
803
1502
  const cursor = state.selection.main.head;
804
1503
  syntaxTree(state).iterate({
805
1504
  enter: (node) => {
@@ -808,51 +1507,101 @@ var update = (state, options) => {
808
1507
  if (urlNode) {
809
1508
  const hide = state.readOnly || cursor < node.from || cursor > node.to;
810
1509
  const url = state.sliceDoc(urlNode.from, urlNode.to);
811
- builder.add(hide ? node.from : node.to, node.to, Decoration4.replace({
1510
+ decorations.push(Decoration5.replace({
1511
+ block: true,
812
1512
  widget: new ImageWidget(url)
813
- }));
1513
+ }).range(hide ? node.from : node.to, node.to));
814
1514
  }
815
1515
  }
816
- }
1516
+ },
1517
+ from,
1518
+ to
817
1519
  });
818
- return builder.finish();
1520
+ return decorations;
819
1521
  };
820
- var ImageWidget = class extends WidgetType2 {
1522
+ var ImageWidget = class extends WidgetType3 {
821
1523
  constructor(_url) {
822
1524
  super();
823
1525
  this._url = _url;
824
1526
  }
825
1527
  eq(other) {
826
- return this._url === other?._url;
1528
+ return this._url === other._url;
827
1529
  }
828
1530
  toDOM(view) {
829
1531
  const img = document.createElement("img");
830
1532
  img.setAttribute("src", this._url);
831
1533
  img.setAttribute("class", "cm-image");
1534
+ img.onload = () => img.classList.add("cm-loaded-image");
832
1535
  return img;
833
1536
  }
834
1537
  };
835
1538
 
836
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
1539
+ // packages/ui/react-ui-editor/src/extensions/link.ts
837
1540
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
838
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField3 } from "@codemirror/state";
839
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
1541
+ import { RangeSetBuilder } from "@codemirror/state";
1542
+ import { hoverTooltip as hoverTooltip2, Decoration as Decoration6, ViewPlugin as ViewPlugin5, WidgetType as WidgetType4 } from "@codemirror/view";
1543
+ import { tooltipContent } from "@dxos/react-ui-theme";
840
1544
  var link = (options = {}) => {
841
- return StateField3.define({
842
- create: (state) => update2(state, options),
843
- update: (_, tr) => update2(tr.state, options),
844
- provide: (field) => EditorView7.decorations.from(field)
845
- });
1545
+ const extensions = [
1546
+ ViewPlugin5.fromClass(class {
1547
+ constructor(view) {
1548
+ this.decorations = buildDecorations2(view, options);
1549
+ }
1550
+ update(update2) {
1551
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
1552
+ this.decorations = buildDecorations2(update2.view, options);
1553
+ }
1554
+ }
1555
+ }, {
1556
+ decorations: (v) => v.decorations
1557
+ })
1558
+ ];
1559
+ if (options.onHover) {
1560
+ extensions.push(
1561
+ // https://codemirror.net/examples/tooltip
1562
+ // https://codemirror.net/docs/ref/#view.hoverTooltip
1563
+ // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1564
+ hoverTooltip2((view, pos, side) => {
1565
+ const syntax = syntaxTree2(view.state).resolveInner(pos, side);
1566
+ let link2 = null;
1567
+ for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
1568
+ link2 = node.name === "Link" ? node : null;
1569
+ }
1570
+ const url = link2 && link2.getChild("URL");
1571
+ if (!url || !link2) {
1572
+ return null;
1573
+ }
1574
+ const urlText = view.state.sliceDoc(url.from, url.to);
1575
+ return {
1576
+ pos: link2.from,
1577
+ end: link2.to,
1578
+ above: true,
1579
+ create: () => {
1580
+ const el = document.createElement("div");
1581
+ el.className = tooltipContent({}, "pli-2 plb-1");
1582
+ options.onHover(el, urlText);
1583
+ return {
1584
+ dom: el,
1585
+ offset: {
1586
+ x: 0,
1587
+ y: 4
1588
+ }
1589
+ };
1590
+ }
1591
+ };
1592
+ })
1593
+ );
1594
+ }
1595
+ return extensions;
846
1596
  };
847
- var LinkButton = class extends WidgetType3 {
848
- constructor(_pos, _url, _onAttach) {
1597
+ var LinkButton = class extends WidgetType4 {
1598
+ constructor(_url, _onAttach) {
849
1599
  super();
850
- this._pos = _pos;
851
1600
  this._url = _url;
852
1601
  this._onAttach = _onAttach;
853
1602
  }
854
1603
  eq(other) {
855
- return this._pos === other._pos && this._url === other._url;
1604
+ return this._url === other._url;
856
1605
  }
857
1606
  toDOM(view) {
858
1607
  const el = document.createElement("span");
@@ -860,15 +1609,14 @@ var LinkButton = class extends WidgetType3 {
860
1609
  return el;
861
1610
  }
862
1611
  };
863
- var LinkText = class extends WidgetType3 {
864
- constructor(_pos, _text, _url) {
1612
+ var LinkText = class extends WidgetType4 {
1613
+ constructor(_text, _url) {
865
1614
  super();
866
- this._pos = _pos;
867
1615
  this._text = _text;
868
1616
  this._url = _url;
869
1617
  }
870
1618
  eq(other) {
871
- return this._pos === other._pos && this._url === other._url;
1619
+ return this._url === other._url;
872
1620
  }
873
1621
  toDOM(view) {
874
1622
  const link2 = document.createElement("a");
@@ -882,7 +1630,7 @@ var LinkText = class extends WidgetType3 {
882
1630
  link2.onclick = () => {
883
1631
  view.dispatch({
884
1632
  selection: {
885
- anchor: this._pos
1633
+ anchor: view.posAtDOM(link2)
886
1634
  }
887
1635
  });
888
1636
  };
@@ -890,75 +1638,77 @@ var LinkText = class extends WidgetType3 {
890
1638
  return link2;
891
1639
  }
892
1640
  };
893
- var update2 = (state, options) => {
894
- const builder = new RangeSetBuilder2();
1641
+ var buildDecorations2 = (view, options) => {
1642
+ const builder = new RangeSetBuilder();
1643
+ const { state } = view;
895
1644
  const cursor = state.selection.main.head;
896
- syntaxTree2(state).iterate({
897
- enter: (node) => {
898
- if (node.name === "Link") {
899
- const marks2 = node.node.getChildren("LinkMark");
900
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
901
- const urlNode = node.node.getChild("URL");
902
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
903
- if (!url) {
1645
+ for (const { from, to } of view.visibleRanges) {
1646
+ syntaxTree2(state).iterate({
1647
+ enter: (node) => {
1648
+ if (node.name === "Link") {
1649
+ const marks2 = node.node.getChildren("LinkMark");
1650
+ const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1651
+ const urlNode = node.node.getChild("URL");
1652
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1653
+ if (!url) {
1654
+ return false;
1655
+ }
1656
+ if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
1657
+ builder.add(node.from, node.to, Decoration6.replace({
1658
+ widget: new LinkText(text, options.link ? url : void 0)
1659
+ }));
1660
+ }
1661
+ if (options.onRender) {
1662
+ builder.add(node.to, node.to, Decoration6.replace({
1663
+ widget: new LinkButton(url, options.onRender)
1664
+ }));
1665
+ }
904
1666
  return false;
905
1667
  }
906
- if (state.readOnly || cursor < node.from || cursor > node.to) {
907
- builder.add(node.from, node.to, Decoration5.replace({
908
- widget: new LinkText(node.from, text, options.link ? url : void 0)
909
- }));
910
- }
911
- if (options.onRender) {
912
- builder.add(node.to, node.to, Decoration5.replace({
913
- widget: new LinkButton(node.from, url, options.onRender)
914
- }));
915
- }
916
- return false;
917
- }
918
- }
919
- });
1668
+ },
1669
+ from,
1670
+ to
1671
+ });
1672
+ }
920
1673
  return builder.finish();
921
1674
  };
922
1675
 
923
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
924
- import { StateField as StateField4 } from "@codemirror/state";
925
- var listener2 = ({ onChange }) => {
926
- return StateField4.define({
927
- create: () => null,
928
- update: (_value, transaction) => {
929
- if (transaction.docChanged && onChange) {
930
- onChange(transaction.newDoc.toString());
931
- }
932
- return null;
933
- }
934
- });
1676
+ // packages/ui/react-ui-editor/src/extensions/listener.ts
1677
+ import { EditorView as EditorView8 } from "@codemirror/view";
1678
+ var listener = ({ onFocus, onChange }) => {
1679
+ const extensions = [];
1680
+ onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1681
+ onFocus(focused);
1682
+ return null;
1683
+ }));
1684
+ onChange && extensions.push(EditorView8.updateListener.of((update2) => {
1685
+ onChange(update2.state.doc.toString());
1686
+ }));
1687
+ return extensions;
935
1688
  };
936
1689
 
937
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
1690
+ // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
938
1691
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
939
1692
  import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
940
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
1693
+ import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
941
1694
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
942
1695
  import { languages } from "@codemirror/language-data";
943
1696
  import { searchKeymap } from "@codemirror/search";
944
1697
  import { EditorState } from "@codemirror/state";
945
1698
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
946
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView8, highlightActiveLine, keymap as keymap5, placeholder as placeholder2 } from "@codemirror/view";
1699
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
947
1700
 
948
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
949
- import { markdownLanguage } from "@codemirror/lang-markdown";
1701
+ // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1702
+ import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
950
1703
  import { HighlightStyle } from "@codemirror/language";
951
1704
  import { tags, styleTags, Tag } from "@lezer/highlight";
952
1705
  import { Table } from "@lezer/markdown";
953
- import get2 from "lodash.get";
954
- import { mx as mx3 } from "@dxos/react-ui-theme";
955
1706
  var markdownTags = {
956
1707
  Blockquote: Tag.define(),
957
1708
  CodeMark: Tag.define(),
958
1709
  CodeText: Tag.define(),
959
1710
  EmphasisMark: Tag.define(),
960
1711
  HeaderMark: Tag.define(),
961
- HorizontalRule: Tag.define(),
962
1712
  InlineCode: Tag.define(),
963
1713
  LinkLabel: Tag.define(),
964
1714
  LinkReference: Tag.define(),
@@ -1122,14 +1872,6 @@ var markdownHighlightStyle = (readonly) => {
1122
1872
  ],
1123
1873
  class: blockquote
1124
1874
  },
1125
- // TODO(burdon): Replace with extension.
1126
- {
1127
- tag: [
1128
- markdownTags.HorizontalRule
1129
- ],
1130
- class: mx3(horizontalRule, readonly && "-indent-[100rem]")
1131
- },
1132
- // TODO(burdon): Only if being edited.
1133
1875
  {
1134
1876
  tag: [
1135
1877
  markdownTags.TableCell
@@ -1137,19 +1879,19 @@ var markdownHighlightStyle = (readonly) => {
1137
1879
  class: "font-mono"
1138
1880
  }
1139
1881
  ], {
1140
- scope: markdownLanguage,
1882
+ scope: markdownLanguage2,
1141
1883
  all: {
1142
- fontFamily: get2(tokens, "fontFamily.body", []).join(",")
1884
+ fontFamily: getToken("fontFamily.body", []).join(",")
1143
1885
  }
1144
1886
  });
1145
1887
  };
1146
1888
 
1147
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
1889
+ // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1148
1890
  var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1149
1891
  return [
1150
1892
  EditorState.allowMultipleSelections.of(true),
1151
1893
  EditorState.tabSize.of(2),
1152
- EditorView8.lineWrapping,
1894
+ EditorView9.lineWrapping,
1153
1895
  customKeymap(),
1154
1896
  bracketMatching2(),
1155
1897
  closeBrackets2(),
@@ -1169,7 +1911,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1169
1911
  // NOTE: This extends the parser; it doesn't affect rendering.
1170
1912
  // https://github.github.com/gfm
1171
1913
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1172
- base: markdownLanguage2,
1914
+ base: markdownLanguage3,
1173
1915
  // Languages for syntax highlighting fenced code blocks.
1174
1916
  codeLanguages: languages,
1175
1917
  // Parser extensions.
@@ -1184,7 +1926,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1184
1926
  syntaxHighlighting2(markdownHighlightStyle(readonly))
1185
1927
  ].filter(Boolean);
1186
1928
  };
1187
- var customKeymap = () => keymap5.of([
1929
+ var customKeymap = () => keymap4.of([
1188
1930
  // https://codemirror.net/docs/ref/#commands.indentWithTab
1189
1931
  indentWithTab,
1190
1932
  // https://codemirror.net/docs/ref/#commands.standardKeymap
@@ -1197,7 +1939,7 @@ var customKeymap = () => keymap5.of([
1197
1939
  ...searchKeymap
1198
1940
  ]);
1199
1941
 
1200
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/mention.ts
1942
+ // packages/ui/react-ui-editor/src/extensions/mention.ts
1201
1943
  import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1202
1944
  var mention = ({ onSearch }) => {
1203
1945
  return autocompletion2({
@@ -1220,19 +1962,24 @@ var mention = ({ onSearch }) => {
1220
1962
  });
1221
1963
  };
1222
1964
 
1223
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/table.ts
1965
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
1966
+ var outliner = (options = {}) => {
1967
+ return [];
1968
+ };
1969
+
1970
+ // packages/ui/react-ui-editor/src/extensions/table.ts
1224
1971
  import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1225
- import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField5 } from "@codemirror/state";
1226
- import { Decoration as Decoration6, EditorView as EditorView9, WidgetType as WidgetType4 } from "@codemirror/view";
1972
+ import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1973
+ import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
1227
1974
  var table = (options = {}) => {
1228
- return StateField5.define({
1229
- create: (state) => update3(state, options),
1230
- update: (_, tr) => update3(tr.state, options),
1231
- provide: (field) => EditorView9.decorations.from(field)
1975
+ return StateField4.define({
1976
+ create: (state) => update(state, options),
1977
+ update: (_, tr) => update(tr.state, options),
1978
+ provide: (field) => EditorView10.decorations.from(field)
1232
1979
  });
1233
1980
  };
1234
- var update3 = (state, options) => {
1235
- const builder = new RangeSetBuilder3();
1981
+ var update = (state, options) => {
1982
+ const builder = new RangeSetBuilder2();
1236
1983
  const cursor = state.selection.main.head;
1237
1984
  const tables = [];
1238
1985
  const getTable = () => tables[tables.length - 1];
@@ -1272,22 +2019,22 @@ var update3 = (state, options) => {
1272
2019
  });
1273
2020
  tables.forEach((table2) => {
1274
2021
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1275
- hide && builder.add(table2.from, table2.to, Decoration6.replace({
2022
+ hide && builder.add(table2.from, table2.to, Decoration7.replace({
1276
2023
  widget: new TableWidget(table2)
1277
2024
  }));
1278
- builder.add(table2.from, table2.to, Decoration6.mark({
2025
+ builder.add(table2.from, table2.to, Decoration7.mark({
1279
2026
  class: "cm-table"
1280
2027
  }));
1281
2028
  });
1282
2029
  return builder.finish();
1283
2030
  };
1284
- var TableWidget = class extends WidgetType4 {
2031
+ var TableWidget = class extends WidgetType5 {
1285
2032
  constructor(_table) {
1286
2033
  super();
1287
2034
  this._table = _table;
1288
2035
  }
1289
2036
  eq(other) {
1290
- return this._table.from === other?._table?.from;
2037
+ return this._table.header?.join() === other._table.header?.join() && this._table.rows?.join() === other._table.rows?.join();
1291
2038
  }
1292
2039
  toDOM(view) {
1293
2040
  const table2 = document.createElement("table");
@@ -1313,146 +2060,194 @@ var TableWidget = class extends WidgetType4 {
1313
2060
  }
1314
2061
  return table2;
1315
2062
  }
2063
+ ignoreEvent(e) {
2064
+ return !/^mouse/.test(e.type);
2065
+ }
1316
2066
  };
1317
2067
 
1318
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
1319
- import { EditorView as EditorView10, Decoration as Decoration7, WidgetType as WidgetType5, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin3 } from "@codemirror/view";
1320
- var styles4 = EditorView10.baseTheme({
1321
- "& .cm-task-item": {
1322
- paddingLeft: "4px",
1323
- paddingRight: "8px"
1324
- }
1325
- });
1326
- var CheckboxWidget = class extends WidgetType5 {
1327
- constructor(_pos, _checked, _indent, _onCheck) {
2068
+ // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2069
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2070
+ import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
2071
+ import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType6, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2072
+ var CheckboxWidget = class extends WidgetType6 {
2073
+ constructor(_checked) {
1328
2074
  super();
1329
- this._pos = _pos;
1330
2075
  this._checked = _checked;
1331
- this._indent = _indent;
1332
- this._onCheck = _onCheck;
1333
2076
  }
1334
2077
  eq(other) {
1335
- return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
2078
+ return this._checked === other._checked;
1336
2079
  }
1337
2080
  toDOM(view) {
1338
- const wrap = document.createElement("span");
1339
- wrap.className = "cm-task-item";
1340
- wrap.setAttribute("aria-hidden", "true");
1341
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
1342
- const input = wrap.appendChild(document.createElement("input"));
2081
+ const input = document.createElement("input");
1343
2082
  input.type = "checkbox";
1344
2083
  input.checked = this._checked;
1345
- if (!this._onCheck) {
2084
+ if (view.state.readOnly) {
1346
2085
  input.setAttribute("disabled", "true");
1347
- }
1348
- if (this._onCheck) {
1349
- input.onchange = (event) => {
1350
- this._onCheck?.(event.target.checked);
1351
- return true;
2086
+ } else {
2087
+ input.onmousedown = (event) => {
2088
+ const pos = view.posAtDOM(input);
2089
+ const text = view.state.sliceDoc(pos, pos + 3);
2090
+ if (text === (this._checked ? "[x]" : "[ ]")) {
2091
+ view.dispatch({
2092
+ changes: {
2093
+ from: pos + 1,
2094
+ to: pos + 2,
2095
+ insert: this._checked ? " " : "x"
2096
+ }
2097
+ });
2098
+ event.preventDefault();
2099
+ }
1352
2100
  };
1353
2101
  }
1354
- return wrap;
2102
+ return input;
1355
2103
  }
1356
2104
  ignoreEvent() {
1357
2105
  return false;
1358
2106
  }
1359
2107
  };
2108
+ var checkedDeco = Decoration8.replace({
2109
+ widget: new CheckboxWidget(true)
2110
+ });
2111
+ var uncheckedDeco = Decoration8.replace({
2112
+ widget: new CheckboxWidget(false)
2113
+ });
1360
2114
  var tasklist = (options = {}) => {
1361
- const taskMatcher = new MatchDecorator2({
1362
- regexp: /^(\s*)- \[([ xX])\]\s/g,
1363
- decoration: (match, view, pos) => {
1364
- const indent = Math.floor(match[1].length / 2);
1365
- const checked = match[2] === "x" || match[2] === "X";
1366
- return Decoration7.replace({
1367
- widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
1368
- const idx = pos + match[0].indexOf("[") + 1;
1369
- view.dispatch({
1370
- changes: {
1371
- from: idx,
1372
- to: idx + 1,
1373
- insert: checked2 ? "x" : " "
1374
- },
1375
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
1376
- selection: {
1377
- anchor: idx + 3
1378
- }
1379
- });
1380
- })
1381
- });
1382
- }
1383
- });
1384
- const tasks = ViewPlugin3.fromClass(class {
2115
+ return ViewPlugin6.fromClass(class {
1385
2116
  constructor(view) {
1386
- this.tasks = taskMatcher.createDeco(view);
2117
+ this.decorations = buildDecorations3(view);
1387
2118
  }
1388
- update(update4) {
1389
- this.tasks = taskMatcher.updateDeco(update4, this.tasks);
2119
+ update(update2) {
2120
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2121
+ this.decorations = buildDecorations3(update2.view);
2122
+ }
1390
2123
  }
1391
2124
  }, {
1392
- decorations: (value) => value.tasks,
1393
- provide: (plugin) => EditorView10.atomicRanges.of((view) => {
1394
- return view.plugin(plugin)?.tasks || Decoration7.none;
2125
+ decorations: (v) => v.decorations,
2126
+ // TODO(burdon): Is this still required?
2127
+ provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2128
+ return view.plugin(plugin)?.decorations || Decoration8.none;
1395
2129
  })
1396
2130
  });
2131
+ };
2132
+ var buildDecorations3 = (view) => {
2133
+ const builder = new RangeSetBuilder3();
2134
+ const { state } = view;
2135
+ const cursor = state.selection.main.head;
2136
+ for (const { from, to } of view.visibleRanges) {
2137
+ syntaxTree4(state).iterate({
2138
+ enter: (node) => {
2139
+ if (node.name === "TaskMarker" && (cursor < node.from || cursor > node.to)) {
2140
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2141
+ builder.add(node.from, node.to, checked ? checkedDeco : uncheckedDeco);
2142
+ }
2143
+ },
2144
+ from,
2145
+ to
2146
+ });
2147
+ }
2148
+ return builder.finish();
2149
+ };
2150
+
2151
+ // packages/ui/react-ui-editor/src/extensions/typewriter.ts
2152
+ import { keymap as keymap5 } from "@codemirror/view";
2153
+ var defaultItems = [
2154
+ "hello world!",
2155
+ "this is a test.",
2156
+ "this is [DXOS](https://dxos.org)"
2157
+ ];
2158
+ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2159
+ let t;
2160
+ let idx = 0;
1397
2161
  return [
1398
- tasks,
1399
- styles4
2162
+ keymap5.of([
2163
+ {
2164
+ // Reset.
2165
+ key: "alt-meta-'",
2166
+ run: (view) => {
2167
+ clearTimeout(t);
2168
+ idx = 0;
2169
+ return true;
2170
+ }
2171
+ },
2172
+ {
2173
+ // Next prompt.
2174
+ // TODO(burdon): Press 1-9 to select prompt?
2175
+ key: "shift-meta-'",
2176
+ run: (view) => {
2177
+ clearTimeout(t);
2178
+ const text = items[idx++];
2179
+ if (idx === items?.length) {
2180
+ idx = 0;
2181
+ }
2182
+ let i = 0;
2183
+ const insert = (d = 0) => {
2184
+ t = setTimeout(() => {
2185
+ const pos = view.state.selection.main.head;
2186
+ view.dispatch({
2187
+ changes: {
2188
+ from: pos,
2189
+ insert: text[i++]
2190
+ },
2191
+ selection: {
2192
+ anchor: pos + 1
2193
+ }
2194
+ });
2195
+ if (i < text.length) {
2196
+ insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
2197
+ }
2198
+ }, d);
2199
+ };
2200
+ insert();
2201
+ return true;
2202
+ }
2203
+ }
2204
+ ])
1400
2205
  ];
1401
2206
  };
1402
2207
 
1403
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.tsx
1404
- import { hoverTooltip } from "@codemirror/view";
1405
- import { tooltipContent } from "@dxos/react-ui-theme";
1406
- var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1407
- var tooltip = ({ onHover, regexp = markdownLinkRegexp }) => hoverTooltip((view, pos) => {
1408
- const { from, text } = view.state.doc.lineAt(pos);
1409
- const p = pos - from;
1410
- let idx = 0;
1411
- let match;
1412
- do {
1413
- match = text.substring(idx).match(regexp);
1414
- if (!match) {
1415
- match = void 0;
1416
- break;
1417
- }
1418
- idx = text.indexOf(match[0]);
1419
- if (p >= idx && p < idx + match[0].length) {
1420
- break;
1421
- }
1422
- idx += match[0].length;
1423
- } while (true);
1424
- if (!match) {
1425
- return null;
2208
+ // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2209
+ import { yCollab } from "y-codemirror.next";
2210
+
2211
+ // packages/ui/react-ui-editor/src/extensions/yjs/cursor.ts
2212
+ import * as Y from "yjs";
2213
+ import { arrayToString, stringToArray } from "@dxos/util";
2214
+ var cursorConverter2 = (text) => ({
2215
+ toCursor: (index, assoc) => {
2216
+ return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
2217
+ },
2218
+ fromCursor: (cursor) => {
2219
+ return Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc)?.index ?? 0;
1426
2220
  }
1427
- const [, , url] = match ?? [];
1428
- return {
1429
- pos: idx + from,
1430
- end: idx + from + match[0].length,
1431
- above: true,
1432
- create: () => {
1433
- const el = document.createElement("div");
1434
- el.className = tooltipContent({}, "pli-2 plb-1");
1435
- onHover(el, url);
1436
- return {
1437
- dom: el,
1438
- offset: {
1439
- x: 0,
1440
- y: 12
1441
- }
1442
- };
1443
- }
1444
- };
1445
2221
  });
1446
2222
 
1447
- // packages/ui/react-ui-editor/src/components/TextEditor/themes/default.ts
2223
+ // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2224
+ var yjs = (content, awareness2) => [
2225
+ Cursor.converter.of(cursorConverter2(content)),
2226
+ yCollab(content, awareness2)
2227
+ ];
2228
+
2229
+ // packages/ui/react-ui-editor/src/themes/default.ts
1448
2230
  import get3 from "lodash.get";
1449
2231
  var defaultTheme = {
1450
2232
  //
1451
2233
  // Main layout:
1452
- // <div class=".cm-editor .cm-focused">
1453
- // <div class=".cm-scroller">
1454
- // <div class=".cm-content" role="textbox" contenteditable="true">
1455
- // <div class=".cm-line" />
2234
+ // https://codemirror.net/examples/styling
2235
+ //
2236
+ // <div class="cm-editor [cm-focused] [generated classes]">
2237
+ // <div class="cm-scroller">
2238
+ // <div class="cm-gutters">
2239
+ // <div class="cm-gutter [...]">
2240
+ // <div class="cm-gutterElement">...</div>
2241
+ // </div>
2242
+ // </div>
2243
+ // <div class="cm-content" role="textbox" contenteditable="true">
2244
+ // <div class="cm-line"></div>
2245
+ // </div>
2246
+ // <div class="cm-selectionLayer">
2247
+ // <div class="cm-selectionBackground"></div>
2248
+ // </div>
2249
+ // <div class="cm-cursorLayer">
2250
+ // <div class="cm-cursor"></div>
1456
2251
  // </div>
1457
2252
  // </div>
1458
2253
  // </div>
@@ -1461,13 +2256,13 @@ var defaultTheme = {
1461
2256
  "&.cm-focused": {
1462
2257
  outline: "none"
1463
2258
  },
1464
- "& .cm-scroller": {
2259
+ ".cm-scroller": {
1465
2260
  overflow: "visible",
1466
2261
  fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
1467
2262
  },
1468
- "& .cm-content": {
2263
+ ".cm-content": {
1469
2264
  padding: 0,
1470
- // Base font size (otherwise defined by HTML tag, which might be different for storybook).
2265
+ // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
1471
2266
  fontSize: "16px"
1472
2267
  },
1473
2268
  "&light .cm-content": {
@@ -1487,65 +2282,46 @@ var defaultTheme = {
1487
2282
  "&dark .cm-cursor, &dark .cm-dropCursor": {
1488
2283
  borderLeft: "2px solid white"
1489
2284
  },
1490
- "& .cm-placeholder": {
2285
+ ".cm-placeholder": {
1491
2286
  fontWeight: 100
1492
2287
  },
1493
2288
  //
1494
2289
  // line
1495
2290
  //
1496
- "& .cm-line": {
2291
+ ".cm-line": {
1497
2292
  paddingInline: 0
1498
2293
  },
1499
- "& .cm-line *": {},
1500
- "& .cm-activeLine": {
1501
- backgroundColor: "transparent"
2294
+ ".cm-activeLine": {
2295
+ background: "inherit"
1502
2296
  },
1503
2297
  //
1504
- // selection
2298
+ // Selection
1505
2299
  //
1506
- "&light .cm-selectionBackground, &light.cm-focused .cm-selectionBackground": {
1507
- background: get3(tokens, "extend.colors.primary.150", "#00ffff")
2300
+ "&light .cm-selectionBackground": {
2301
+ background: get3(tokens, "extend.colors.primary.100")
1508
2302
  },
1509
- "&dark .cm-selectionBackground, &dark.cm-focused .cm-selectionBackground": {
1510
- background: get3(tokens, "extend.colors.primary.850", "#00ffff")
2303
+ "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2304
+ background: get3(tokens, "extend.colors.primary.200")
1511
2305
  },
1512
- "&light .cm-selectionMatch": {
1513
- background: get3(tokens, "extend.colors.primary.250", "#00ffff") + "44"
2306
+ "&dark .cm-selectionBackground": {
2307
+ background: get3(tokens, "extend.colors.primary.700")
1514
2308
  },
1515
- "&dark .cm-selectionMatch": {
1516
- background: get3(tokens, "extend.colors.primary.600", "#00ffff") + "44"
2309
+ "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2310
+ background: get3(tokens, "extend.colors.primary.600")
1517
2311
  },
1518
2312
  //
1519
- // collaboration
1520
- // TODO(burdon): Review classnames (YJS dependent?)
2313
+ // Search
1521
2314
  //
1522
- "&light .cm-ySelection, &light .cm-yLineSelection": {
1523
- mixBlendMode: "multiply"
1524
- },
1525
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
1526
- mixBlendMode: "screen"
1527
- },
1528
- "& .cm-ySelectionInfo": {
1529
- padding: "2px 4px",
1530
- marginBlockStart: "-4px"
1531
- },
1532
- "& .cm-ySelection, & .cm-selectionMatch": {
1533
- paddingBlockStart: ".15em",
1534
- paddingBlockEnd: ".15em"
1535
- },
1536
- "& .cm-ySelectionCaret": {
1537
- display: "inline-block",
1538
- insetBlockStart: ".1em",
1539
- blockSize: "1.4em",
1540
- verticalAlign: "top"
2315
+ "&light .cm-searchMatch": {
2316
+ backgroundColor: get3(tokens, "extend.colors.yellow.100")
1541
2317
  },
1542
- "& .cm-yLineSelection": {
1543
- margin: 0
2318
+ "&dark .cm-searchMatch": {
2319
+ backgroundColor: get3(tokens, "extend.colors.yellow.700")
1544
2320
  },
1545
2321
  //
1546
2322
  // link
1547
2323
  //
1548
- "& .cm-link": {
2324
+ ".cm-link": {
1549
2325
  color: get3(tokens, "extend.colors.primary.500"),
1550
2326
  textDecorationLine: "underline",
1551
2327
  textDecorationThickness: "1px",
@@ -1556,83 +2332,73 @@ var defaultTheme = {
1556
2332
  //
1557
2333
  // tooltip
1558
2334
  //
1559
- "& .cm-tooltip": {
2335
+ ".cm-tooltip": {
1560
2336
  border: "none"
1561
2337
  },
1562
- "& .cm-tooltip-below": {},
2338
+ ".cm-tooltip-below": {},
1563
2339
  //
1564
2340
  // autocomplete
1565
2341
  //
1566
- "& .cm-tooltip-autocomplete": {
2342
+ ".cm-tooltip-autocomplete": {
1567
2343
  marginTop: "4px",
1568
2344
  marginLeft: "-3px"
1569
2345
  },
1570
- "& .cm-tooltip-autocomplete ul li": {},
1571
- "& .cm-tooltip-autocomplete ul li[aria-selected]": {},
1572
- "& .cm-completionIcon": {
2346
+ ".cm-tooltip-autocomplete ul li": {},
2347
+ ".cm-tooltip-autocomplete ul li[aria-selected]": {},
2348
+ ".cm-completionIcon": {
1573
2349
  display: "none"
1574
2350
  },
1575
- "& .cm-completionLabel": {
2351
+ ".cm-completionLabel": {
1576
2352
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1577
2353
  },
1578
- "& .cm-completionMatchedText": {
2354
+ ".cm-completionMatchedText": {
1579
2355
  textDecoration: "none"
1580
2356
  },
1581
2357
  //
1582
- // widgets
1583
- //
1584
- "& .cm-widgetBuffer": {
1585
- display: "none",
1586
- height: 0
1587
- },
1588
- //
1589
2358
  // table
1590
2359
  //
1591
- "& .cm-table *": {
2360
+ ".cm-table *": {
1592
2361
  fontFamily: `${get3(tokens, "fontFamily.mono", []).join(",")} !important`,
1593
2362
  textDecoration: "none !important"
1594
2363
  },
1595
- "& .cm-table-head": {
2364
+ ".cm-table-head": {
1596
2365
  padding: "2px 16px 2px 0px",
1597
2366
  borderBottom: `1px solid ${get3(tokens, "extend.colors.neutral.500")}`,
1598
2367
  fontWeight: 100,
1599
2368
  textAlign: "left",
1600
2369
  color: get3(tokens, "extend.colors.neutral.500")
1601
2370
  },
1602
- "& .cm-table-cell": {
2371
+ ".cm-table-cell": {
1603
2372
  padding: "2px 16px 2px 0px"
1604
2373
  },
1605
2374
  //
1606
2375
  // image
1607
2376
  //
1608
- "& .cm-image": {
1609
- margin: "0.5rem 0"
2377
+ ".cm-image": {
2378
+ display: "block",
2379
+ height: "0"
2380
+ },
2381
+ ".cm-image.cm-loaded-image": {
2382
+ height: "auto",
2383
+ borderTop: "0.5rem solid transparent",
2384
+ borderBottom: "0.5rem solid transparent"
1610
2385
  },
1611
2386
  //
1612
2387
  // font size
1613
- // 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.
2388
+ // TODO(thure): This appears to be the best or only way to set selection caret heights,
2389
+ // but it's far more verbose than it needs to be.
1614
2390
  //
1615
- ...Object.keys(get3(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
1616
- const height = get3(tokens, [
1617
- "extend",
1618
- "fontSize",
1619
- fontSize,
1620
- 1,
1621
- "lineHeight"
1622
- ]);
1623
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
1624
- height
1625
- };
1626
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
1627
- height
1628
- };
1629
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
1630
- height
1631
- };
1632
- return acc;
1633
- }, {}),
2391
+ // ...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
2392
+ // const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
2393
+ //
2394
+ // acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
2395
+ // acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
2396
+ // acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
2397
+ // return acc;
2398
+ // }, {}),
1634
2399
  /**
1635
2400
  * Panels
2401
+ * TODO(burdon): Needs styling attention (esp. dark mode).
1636
2402
  * https://github.com/codemirror/search/blob/main/src/search.ts#L745
1637
2403
  *
1638
2404
  * Find/replace panel.
@@ -1646,85 +2412,83 @@ var defaultTheme = {
1646
2412
  * </div>
1647
2413
  * </div
1648
2414
  */
1649
- "& .cm-panels": {
1650
- border: `1px solid ${get3(tokens, "extend.colors.neutral.200")}`,
1651
- borderBottom: "none"
2415
+ ".cm-panels": {
2416
+ border: `1px solid ${get3(tokens, "extend.colors.neutral.200")}`
1652
2417
  },
1653
- "& .cm-panel": {
2418
+ ".cm-panel": {
1654
2419
  background: get3(tokens, "extend.colors.neutral.50"),
1655
2420
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1656
2421
  },
1657
- "& .cm-button": {
2422
+ ".cm-button": {
1658
2423
  margin: "4px",
1659
2424
  fontFamily: get3(tokens, "fontFamily.body", []).join(","),
1660
2425
  background: get3(tokens, "extend.colors.neutral.100"),
1661
- border: "none"
1662
- },
1663
- "& .cm-searchMatch": {
1664
- backgroundColor: get3(tokens, "extend.colors.yellow.100")
2426
+ backgroundImage: "none",
2427
+ border: "none",
2428
+ "&:hover": {
2429
+ background: get3(tokens, "extend.colors.neutral.200")
2430
+ },
2431
+ "&:active": {
2432
+ background: get3(tokens, "extend.colors.neutral.300"),
2433
+ backgroundImage: "none"
2434
+ }
1665
2435
  },
1666
- "& .cm-searchMatch.cm-searchMatch-selected": {
1667
- backgroundColor: get3(tokens, "extend.colors.primary.100")
2436
+ ".cm-panel input[type=checkbox]": {
2437
+ marginRight: "0.4rem !important"
1668
2438
  }
1669
2439
  };
1670
2440
  var textTheme = {
1671
- "& .cm-scroller": {
2441
+ ".cm-scroller": {
1672
2442
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1673
2443
  },
1674
- "& .cm-placeholder": {
2444
+ ".cm-placeholder": {
1675
2445
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1676
2446
  }
1677
2447
  };
1678
2448
  var markdownTheme = {
1679
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
1680
- "& .cm-placeholder": {
2449
+ ".cm-scroller": {
2450
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2451
+ },
2452
+ ".cm-placeholder": {
1681
2453
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1682
2454
  }
1683
2455
  };
1684
2456
  var codeTheme = {
1685
- "& .cm-scroller": {
2457
+ ".cm-scroller": {
1686
2458
  fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
1687
2459
  },
1688
- "& .cm-placeholder": {
2460
+ ".cm-placeholder": {
1689
2461
  fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
1690
2462
  }
1691
2463
  };
1692
2464
 
1693
2465
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
1694
- import { EditorState as EditorState2 } from "@codemirror/state";
1695
- import { EditorView as EditorView11 } from "@codemirror/view";
1696
- import { useFocusableGroup } from "@fluentui/react-tabster";
1697
- import { vim } from "@replit/codemirror-vim";
1698
- import defaultsDeep2 from "lodash.defaultsdeep";
1699
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState } from "react";
1700
- import { generateName } from "@dxos/display-name";
1701
- import { useThemeContext } from "@dxos/react-ui";
1702
- import { getColorForValue, inputSurface, mx as mx4 } from "@dxos/react-ui-theme";
1703
2466
  var EditorModes = [
1704
2467
  "default",
1705
2468
  "vim"
1706
2469
  ];
1707
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
1708
- const { themeMode } = useThemeContext();
2470
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, autofocus, readonly, selection, comments: comments2, extensions = [], editorMode, theme, slots = defaultSlots }, forwardedRef) => {
1709
2471
  const tabsterDOMAttribute = useFocusableGroup({
1710
2472
  tabBehavior: "limited"
1711
2473
  });
1712
- const [root, setRoot] = useState(null);
1713
- const [state, setState] = useState();
1714
- const [view, setView] = useState();
1715
- useImperativeHandle(forwardedRef, () => ({
1716
- root,
1717
- state,
2474
+ const { themeMode } = useThemeContext();
2475
+ const rootRef = useRef(null);
2476
+ const [view, setView] = useState(null);
2477
+ useImperativeHandle(forwardedRef, () => view, [
2478
+ view
2479
+ ]);
2480
+ useEffect(() => {
2481
+ if (autofocus) {
2482
+ view?.focus();
2483
+ }
2484
+ }, [
2485
+ autofocus,
1718
2486
  view
1719
- }), [
1720
- view,
1721
- state,
1722
- root
1723
2487
  ]);
1724
- const { awareness, peer } = model;
1725
- useEffect2(() => {
1726
- if (awareness && peer) {
1727
- awareness.setLocalStateField("user", {
2488
+ const { awareness: awareness2, peer } = model;
2489
+ useEffect(() => {
2490
+ if (awareness2 && peer) {
2491
+ awareness2.setLocalStateField("user", {
1728
2492
  name: peer.name ?? generateName(peer.id),
1729
2493
  color: getColorForValue({
1730
2494
  value: peer.id,
@@ -1738,26 +2502,40 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode,
1738
2502
  });
1739
2503
  }
1740
2504
  }, [
1741
- awareness,
2505
+ awareness2,
1742
2506
  peer,
1743
2507
  themeMode
1744
2508
  ]);
1745
- useEffect2(() => {
1746
- if (!root) {
2509
+ useEffect(() => {
2510
+ if (view && comments2 !== void 0) {
2511
+ view.dispatch({
2512
+ effects: setCommentRange.of({
2513
+ model,
2514
+ comments: comments2
2515
+ })
2516
+ });
2517
+ }
2518
+ }, [
2519
+ view,
2520
+ comments2
2521
+ ]);
2522
+ useEffect(() => {
2523
+ if (!model || !rootRef.current) {
1747
2524
  return;
1748
2525
  }
1749
- const state2 = EditorState2.create({
2526
+ const state = EditorState2.create({
1750
2527
  doc: model.text(),
2528
+ // TODO(burdon): Composer should store and set selection when switching documents.
2529
+ selection,
1751
2530
  extensions: [
1752
2531
  readonly && EditorState2.readOnly.of(readonly),
1753
- // TODO(burdon): Factor out VIM mode?
2532
+ // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
1754
2533
  editorMode === "vim" && vim(),
1755
2534
  // Theme.
1756
- // TODO(burdon): Make optional.
1757
- EditorView11.baseTheme(defaultTheme),
1758
- EditorView11.theme(slots?.editor?.theme ?? {}),
1759
- // TODO(burdon): themeMode doesn't change in storybooks.
1760
- EditorView11.darkTheme.of(themeMode === "dark"),
2535
+ // TODO(burdon): Make theme configurable.
2536
+ EditorView12.baseTheme(defaultTheme),
2537
+ EditorView12.theme(theme ?? {}),
2538
+ EditorView12.darkTheme.of(themeMode === "dark"),
1761
2539
  // Storage and replication.
1762
2540
  model.extension,
1763
2541
  // Custom.
@@ -1765,32 +2543,27 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode,
1765
2543
  ].filter(Boolean)
1766
2544
  });
1767
2545
  view?.destroy();
1768
- setView(new EditorView11({
1769
- state: state2,
1770
- parent: root
1771
- }));
1772
- setState(state2);
2546
+ const newView = new EditorView12({
2547
+ parent: rootRef.current,
2548
+ state
2549
+ });
2550
+ setView(newView);
1773
2551
  return () => {
1774
- view?.destroy();
1775
- setView(void 0);
1776
- setState(void 0);
2552
+ newView?.destroy();
2553
+ setView(null);
1777
2554
  };
1778
2555
  }, [
1779
- root,
2556
+ rootRef,
1780
2557
  model,
1781
- themeMode,
1782
2558
  readonly,
1783
- editorMode
2559
+ editorMode,
2560
+ themeMode
1784
2561
  ]);
1785
2562
  const handleKeyUp = useCallback((event) => {
1786
2563
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
1787
2564
  switch (key) {
1788
- case "Enter": {
1789
- view?.contentDOM.focus();
1790
- break;
1791
- }
1792
2565
  case "Escape": {
1793
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2566
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
1794
2567
  break;
1795
2568
  }
1796
2569
  }
@@ -1800,16 +2573,17 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode,
1800
2573
  ]);
1801
2574
  return /* @__PURE__ */ React.createElement("div", {
1802
2575
  key: model.id,
1803
- ref: setRoot,
2576
+ role: "none",
1804
2577
  tabIndex: 0,
1805
- ...slots?.root,
2578
+ onKeyUp: handleKeyUp,
2579
+ ...slots.root,
1806
2580
  ...editorMode !== "vim" && tabsterDOMAttribute,
1807
- onKeyUp: handleKeyUp
2581
+ ref: rootRef
1808
2582
  });
1809
2583
  });
1810
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2584
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
1811
2585
  const { themeMode } = useThemeContext();
1812
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2586
+ const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
1813
2587
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1814
2588
  ref: forwardedRef,
1815
2589
  readonly,
@@ -1817,359 +2591,207 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots:
1817
2591
  basicBundle({
1818
2592
  readonly,
1819
2593
  themeMode,
1820
- placeholder: slots?.editor?.placeholder
1821
- }),
1822
- ...extensions
1823
- ],
1824
- slots,
1825
- ...props
1826
- });
1827
- });
1828
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
1829
- const { themeMode } = useThemeContext();
1830
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
1831
- return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1832
- ref: forwardedRef,
1833
- readonly,
1834
- extensions: [
1835
- markdownBundle({
1836
- readonly,
1837
- themeMode,
1838
- placeholder: slots?.editor?.placeholder
2594
+ placeholder: placeholder3
1839
2595
  }),
1840
2596
  ...extensions
1841
2597
  ],
1842
- slots,
2598
+ theme,
2599
+ slots: updatedSlots,
1843
2600
  ...props
1844
2601
  });
1845
2602
  });
1846
- var defaultSlots = {
1847
- root: {
1848
- className: mx4("p-2", inputSurface)
1849
- }
1850
- };
1851
- var defaultTextSlots = {
1852
- ...defaultSlots,
1853
- editor: {
1854
- theme: textTheme
1855
- }
1856
- };
1857
- var defaultMarkdownSlots = {
1858
- ...defaultSlots,
1859
- editor: {
1860
- theme: markdownTheme
1861
- }
1862
- };
1863
-
1864
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1865
- import get4 from "lodash.get";
1866
- import { useEffect as useEffect3, useState as useState2 } from "react";
1867
- import { yCollab } from "y-codemirror.next";
1868
- import { invariant as invariant3 } from "@dxos/invariant";
1869
- import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
1870
-
1871
- // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
1872
- import { Annotation, Facet, StateEffect as StateEffect2, StateField as StateField6 } from "@codemirror/state";
1873
- import { ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1874
- import * as automerge2 from "@dxos/automerge/automerge";
1875
-
1876
- // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
1877
- import { next as automerge } from "@dxos/automerge/automerge";
1878
-
1879
- // packages/ui/react-ui-editor/src/hooks/automerge/translation/automerge-to-codemirror.ts
1880
- import { ChangeSet } from "@codemirror/state";
1881
- var automergeToCodemirror = (view, selection, target, patches) => {
1882
- for (const patch of patches) {
1883
- const changeSpec = handlePatch(patch, target, view.state);
1884
- if (changeSpec != null) {
1885
- const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
1886
- selection = selection.map(changeSet, 1);
1887
- view.dispatch({
1888
- changes: changeSet,
1889
- annotations: reconcileAnnotationType.of({})
1890
- });
1891
- }
1892
- }
1893
- view.dispatch({
1894
- selection,
1895
- annotations: reconcileAnnotationType.of({})
1896
- });
1897
- };
1898
- var handlePatch = (patch, target, state) => {
1899
- if (patch.action === "insert") {
1900
- return handleInsert(target, patch);
1901
- } else if (patch.action === "splice") {
1902
- return handleSplice(target, patch);
1903
- } else if (patch.action === "del") {
1904
- return handleDel(target, patch);
1905
- } else if (patch.action === "put") {
1906
- return handlePut(target, patch, state);
1907
- } else {
1908
- return null;
1909
- }
1910
- };
1911
- var handleInsert = (target, patch) => {
1912
- const index = charPath(target, patch.path);
1913
- if (index == null) {
1914
- return [];
1915
- }
1916
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
1917
- return [
1918
- {
1919
- from: index,
1920
- to: index,
1921
- insert: text
1922
- }
1923
- ];
1924
- };
1925
- var handleSplice = (target, patch) => {
1926
- const index = charPath(target, patch.path);
1927
- if (index == null) {
1928
- return [];
1929
- }
1930
- return [
1931
- {
1932
- from: index,
1933
- insert: patch.value
1934
- }
1935
- ];
1936
- };
1937
- var handleDel = (target, patch) => {
1938
- const index = charPath(target, patch.path);
1939
- if (index == null) {
1940
- return [];
1941
- }
1942
- const length = patch.length || 1;
1943
- return [
1944
- {
1945
- from: index,
1946
- to: index + length
1947
- }
1948
- ];
1949
- };
1950
- var handlePut = (target, patch, state) => {
1951
- const index = charPath(target, [
1952
- ...patch.path,
1953
- 0
1954
- ]);
1955
- if (index == null) {
1956
- return [];
1957
- }
1958
- const length = state.doc.length;
1959
- if (typeof patch.value !== "string") {
1960
- return [];
1961
- }
1962
- return [
1963
- {
1964
- from: 0,
1965
- to: length,
1966
- insert: patch.value
1967
- }
1968
- ];
1969
- };
1970
- var charPath = (textPath, candidatePath) => {
1971
- if (candidatePath.length !== textPath.length + 1) {
1972
- return null;
1973
- }
1974
- for (let i = 0; i < textPath.length; i++) {
1975
- if (textPath[i] !== candidatePath[i]) {
1976
- return null;
1977
- }
1978
- }
1979
- const index = candidatePath[candidatePath.length - 1];
1980
- if (typeof index === "number") {
1981
- return index;
2603
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
2604
+ const { themeMode } = useThemeContext();
2605
+ const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2606
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2607
+ ref: forwardedRef,
2608
+ readonly,
2609
+ extensions: [
2610
+ markdownBundle({
2611
+ readonly,
2612
+ themeMode,
2613
+ placeholder: placeholder3
2614
+ }),
2615
+ ...extensions
2616
+ ],
2617
+ theme,
2618
+ slots: updatedSlots,
2619
+ ...props
2620
+ });
2621
+ });
2622
+ var defaultSlots = {
2623
+ root: {
2624
+ className: mx3("w-full p-2 overflow-y-auto", inputSurface)
1982
2625
  }
1983
- return null;
1984
2626
  };
1985
-
1986
- // packages/ui/react-ui-editor/src/hooks/automerge/translation/codemirror-to-automerge.ts
1987
- import { next as am } from "@dxos/automerge/automerge";
1988
- var codemirrorToAutomerge = (field, handle, transactions, state) => {
1989
- const { lastHeads, path } = state.field(field);
1990
- let hasChanges = false;
1991
- for (const tr of transactions) {
1992
- tr.changes.iterChanges(() => {
1993
- hasChanges = true;
1994
- });
1995
- }
1996
- if (!hasChanges) {
1997
- return void 0;
1998
- }
1999
- const newHeads = handle.changeAt(lastHeads, (doc) => {
2000
- for (const tr of transactions) {
2001
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
2002
- am.splice(doc, path, fromA, toA - fromA, inserted.toString());
2003
- });
2004
- }
2005
- });
2006
- return newHeads ?? void 0;
2627
+ var defaultTextSlots = {
2628
+ ...defaultSlots
2629
+ };
2630
+ var defaultMarkdownSlots = {
2631
+ ...defaultSlots
2007
2632
  };
2008
2633
 
2009
- // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
2010
- var PatchSemaphore = class {
2011
- constructor(field) {
2012
- this._inReconcile = false;
2013
- this._queue = [];
2014
- this.reconcile = (handle, view) => {
2015
- if (this._inReconcile) {
2016
- return;
2017
- }
2018
- this._inReconcile = true;
2019
- const path = getPath(view.state, this._field);
2020
- const oldHeads = getLastHeads(view.state, this._field);
2021
- let selection = view.state.selection;
2022
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
2023
- const toInvert = transactions.slice().reverse();
2024
- for (const tx of toInvert) {
2025
- const inverted = tx.changes.invert(tx.startState.doc);
2026
- selection = selection.map(inverted);
2027
- view.dispatch({
2028
- changes: inverted,
2029
- annotations: reconcileAnnotationType.of(true)
2634
+ // packages/ui/react-ui-editor/src/hooks/automerge.ts
2635
+ import get4 from "lodash.get";
2636
+ import { getRawDoc } from "@dxos/echo-schema";
2637
+ import { isNotNullOrUndefined } from "@dxos/util";
2638
+
2639
+ // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2640
+ import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2641
+ import { Context as Context2 } from "@dxos/context";
2642
+ import { invariant as invariant3 } from "@dxos/invariant";
2643
+ import { log as log2 } from "@dxos/log";
2644
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2645
+ var DEBOUNCE_INTERVAL = 100;
2646
+ var SpaceAwarenessProvider = class {
2647
+ constructor(params) {
2648
+ this.remoteStateChange = new Event2();
2649
+ this._remoteStates = /* @__PURE__ */ new Map();
2650
+ this._space = params.space;
2651
+ this._channel = params.channel;
2652
+ this._peerId = params.peerId;
2653
+ this._info = params.info;
2654
+ }
2655
+ open() {
2656
+ this._ctx = new Context2();
2657
+ this._postTask = new DeferredTask(this._ctx, async () => {
2658
+ if (this._localState) {
2659
+ await this._space.postMessage(this._channel, {
2660
+ kind: "post",
2661
+ state: this._localState
2030
2662
  });
2663
+ await sleep(DEBOUNCE_INTERVAL);
2031
2664
  }
2032
- let newHeads = codemirrorToAutomerge(this._field, handle, transactions, view.state);
2033
- if (newHeads === null || newHeads === void 0) {
2034
- newHeads = automerge.getHeads(handle.docSync());
2665
+ });
2666
+ const unsubscribe = this._space.listen(this._channel, (message) => {
2667
+ switch (message.payload.kind) {
2668
+ case "query": {
2669
+ this._handleQueryMessage();
2670
+ break;
2671
+ }
2672
+ case "post": {
2673
+ this._handlePostMessage(message.payload);
2674
+ break;
2675
+ }
2035
2676
  }
2036
- const diff = automerge.equals(oldHeads, newHeads) ? [] : automerge.diff(handle.docSync(), oldHeads, newHeads);
2037
- automergeToCodemirror(view, selection, path, diff);
2038
- view.dispatch({
2039
- effects: updateHeads(newHeads),
2040
- annotations: reconcileAnnotationType.of({})
2677
+ });
2678
+ this._ctx.onDispose(unsubscribe);
2679
+ void this._space.postMessage(this._channel, {
2680
+ kind: "query"
2681
+ }).catch((err) => {
2682
+ log2.debug("failed to query awareness", {
2683
+ err
2684
+ }, {
2685
+ F: __dxlog_file4,
2686
+ L: 85,
2687
+ S: this,
2688
+ C: (f, a) => f(...a)
2041
2689
  });
2042
- this._inReconcile = false;
2690
+ });
2691
+ }
2692
+ close() {
2693
+ void this._ctx?.dispose();
2694
+ this._ctx = void 0;
2695
+ this._postTask = void 0;
2696
+ }
2697
+ update(position) {
2698
+ invariant3(this._postTask, void 0, {
2699
+ F: __dxlog_file4,
2700
+ L: 96,
2701
+ S: this,
2702
+ A: [
2703
+ "this._postTask",
2704
+ ""
2705
+ ]
2706
+ });
2707
+ this._localState = {
2708
+ peerId: this._peerId,
2709
+ position,
2710
+ info: this._info
2043
2711
  };
2044
- if (field !== void 0) {
2045
- this._field = field;
2046
- }
2712
+ this._postTask.schedule();
2713
+ }
2714
+ getRemoteStates() {
2715
+ return Array.from(this._remoteStates.values());
2716
+ }
2717
+ _handleQueryMessage() {
2718
+ invariant3(this._postTask, void 0, {
2719
+ F: __dxlog_file4,
2720
+ L: 111,
2721
+ S: this,
2722
+ A: [
2723
+ "this._postTask",
2724
+ ""
2725
+ ]
2726
+ });
2727
+ this._postTask.schedule();
2728
+ }
2729
+ _handlePostMessage(message) {
2730
+ invariant3(message.kind === "post", void 0, {
2731
+ F: __dxlog_file4,
2732
+ L: 116,
2733
+ S: this,
2734
+ A: [
2735
+ "message.kind === 'post'",
2736
+ ""
2737
+ ]
2738
+ });
2739
+ this._remoteStates.set(message.state.peerId, message.state);
2740
+ this.remoteStateChange.emit();
2047
2741
  }
2048
2742
  };
2049
2743
 
2050
- // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
2051
- var effectType = StateEffect2.define({});
2052
- var updateHeads = (newHeads) => effectType.of({
2053
- newHeads
2054
- });
2055
- var getLastHeads = (state, field) => state.field(field).lastHeads;
2056
- var getPath = (state, field) => state.field(field).path;
2057
- var semaphoreFacet = Facet.define({
2058
- combine: (values) => values.at(-1)
2059
- });
2060
- var automergePlugin = (handle, path) => {
2061
- const stateField = StateField6.define({
2062
- create: () => ({
2063
- lastHeads: automerge2.getHeads(handle.docSync()),
2064
- unreconciledTransactions: [],
2065
- path: path.slice()
2066
- }),
2067
- update: (value, tr) => {
2068
- const result = {
2069
- lastHeads: value.lastHeads,
2070
- unreconciledTransactions: value.unreconciledTransactions.slice(),
2071
- path: path.slice()
2072
- };
2073
- let clearUnreconciled = false;
2074
- for (const effect of tr.effects) {
2075
- if (effect.is(effectType)) {
2076
- result.lastHeads = effect.value.newHeads;
2077
- clearUnreconciled = true;
2078
- }
2079
- }
2080
- if (clearUnreconciled) {
2081
- result.unreconciledTransactions = [];
2082
- } else {
2083
- if (!isReconcileTx(tr)) {
2084
- result.unreconciledTransactions.push(tr);
2085
- }
2086
- }
2087
- return result;
2088
- }
2089
- });
2090
- const semaphore = new PatchSemaphore(stateField);
2091
- const viewPlugin = ViewPlugin4.fromClass(class AutomergeCodemirrorViewPlugin {
2092
- constructor(view) {
2093
- this._handleChange = () => {
2094
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
2095
- };
2096
- this._view = view;
2097
- handle.addListener("change", this._handleChange);
2098
- }
2099
- update(update4) {
2100
- if (update4.transactions.length > 0 && update4.transactions.some((t) => !isReconcileTx(t))) {
2101
- queueMicrotask(() => {
2102
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
2103
- });
2104
- }
2105
- }
2106
- destroy() {
2107
- handle.addListener("change", this._handleChange);
2108
- }
2109
- });
2110
- return {
2111
- extension: [
2112
- stateField,
2113
- semaphoreFacet.of(semaphore),
2114
- viewPlugin
2115
- ]
2116
- };
2117
- };
2118
- var reconcileAnnotationType = Annotation.define();
2119
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
2120
-
2121
- // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
2122
- import * as random2 from "lib0/random";
2123
- var cursorColors = [
2124
- {
2125
- color: "#30bced",
2126
- light: "#30bced33"
2127
- },
2128
- {
2129
- color: "#6eeb83",
2130
- light: "#6eeb8333"
2131
- },
2132
- {
2133
- color: "#ffbc42",
2134
- light: "#ffbc4233"
2135
- },
2136
- {
2137
- color: "#ecd444",
2138
- light: "#ecd44433"
2139
- },
2140
- {
2141
- color: "#ee6352",
2142
- light: "#ee635233"
2143
- },
2144
- {
2145
- color: "#9ac2c9",
2146
- light: "#9ac2c933"
2147
- },
2148
- {
2149
- color: "#8acb88",
2150
- light: "#8acb8833"
2151
- },
2152
- {
2153
- color: "#1be7ff",
2154
- light: "#1be7ff33"
2155
- }
2156
- ];
2157
- var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
2744
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2745
+ import { StateField as StateField5 } from "@codemirror/state";
2746
+ import { useEffect as useEffect2, useState as useState2 } from "react";
2747
+ import { isAutomergeObject } from "@dxos/react-client/echo";
2158
2748
 
2159
- // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
2749
+ // packages/ui/react-ui-editor/src/hooks/yjs.ts
2160
2750
  import * as decoding from "lib0/decoding";
2161
2751
  import * as encoding from "lib0/encoding";
2162
2752
  import { Observable } from "lib0/observable";
2163
- import * as awarenessProtocol from "y-protocols/awareness";
2164
- import { log } from "@dxos/log";
2165
- var __dxlog_file3 = "/home/circleci/project/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
2753
+ import * as YP from "y-protocols/awareness";
2754
+ import { invariant as invariant4 } from "@dxos/invariant";
2755
+ import { log as log3 } from "@dxos/log";
2756
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2757
+ var createYjsModel = ({ identity, space, text }) => {
2758
+ invariant4(text?.doc && text?.content, void 0, {
2759
+ F: __dxlog_file5,
2760
+ L: 21,
2761
+ S: void 0,
2762
+ A: [
2763
+ "text?.doc && text?.content",
2764
+ ""
2765
+ ]
2766
+ });
2767
+ const provider = space ? new YAwarenessProvider({
2768
+ space,
2769
+ doc: text.doc,
2770
+ channel: `yjs.awareness.${text.id}`
2771
+ }) : void 0;
2772
+ const model = {
2773
+ id: text.doc.guid,
2774
+ content: text.content,
2775
+ text: () => text.content.toString(),
2776
+ extension: [
2777
+ modelState.init(() => model),
2778
+ yjs(text.content, provider?.awareness)
2779
+ ],
2780
+ awareness: provider?.awareness,
2781
+ peer: identity ? {
2782
+ id: identity.identityKey.toHex(),
2783
+ name: identity.profile?.displayName
2784
+ } : void 0
2785
+ };
2786
+ return model;
2787
+ };
2166
2788
  var messageAwareness = 1;
2167
2789
  var messageQueryAwareness = 3;
2168
- var SpaceAwarenessProvider = class extends Observable {
2169
- constructor({ space, doc, channel, awareness }) {
2790
+ var YAwarenessProvider = class extends Observable {
2791
+ constructor({ space, doc, channel, awareness: awareness2 }) {
2170
2792
  super();
2171
2793
  this._space = space;
2172
- this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
2794
+ this._awareness = awareness2 ?? new YP.Awareness(doc);
2173
2795
  this._channel = channel;
2174
2796
  this._clientId = doc.clientID;
2175
2797
  this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
@@ -2184,7 +2806,7 @@ var SpaceAwarenessProvider = class extends Observable {
2184
2806
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
2185
2807
  const encoderAwarenessState = encoding.createEncoder();
2186
2808
  encoding.writeVarUint(encoderAwarenessState, messageAwareness);
2187
- encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
2809
+ encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [
2188
2810
  this._clientId
2189
2811
  ]));
2190
2812
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
@@ -2193,27 +2815,27 @@ var SpaceAwarenessProvider = class extends Observable {
2193
2815
  return this._awareness;
2194
2816
  }
2195
2817
  _handleAwarenessUpdate({ added, updated, removed }, origin) {
2196
- log("awareness update", {
2818
+ log3("awareness update", {
2197
2819
  added,
2198
2820
  updated,
2199
2821
  removed,
2200
2822
  origin
2201
2823
  }, {
2202
- F: __dxlog_file3,
2203
- L: 67,
2824
+ F: __dxlog_file5,
2825
+ L: 100,
2204
2826
  S: this,
2205
2827
  C: (f, a) => f(...a)
2206
2828
  });
2207
2829
  const changedClients = added.concat(updated).concat(removed);
2208
2830
  const encoderAwareness = encoding.createEncoder();
2209
2831
  encoding.writeVarUint(encoderAwareness, messageAwareness);
2210
- encoding.writeVarUint8Array(encoderAwareness, awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients));
2832
+ encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
2211
2833
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2212
2834
  }
2213
2835
  _handleSpaceMessage({ payload }) {
2214
- log("space message", payload, {
2215
- F: __dxlog_file3,
2216
- L: 79,
2836
+ log3("space message", payload, {
2837
+ F: __dxlog_file5,
2838
+ L: 109,
2217
2839
  S: this,
2218
2840
  C: (f, a) => f(...a)
2219
2841
  });
@@ -2230,12 +2852,12 @@ var SpaceAwarenessProvider = class extends Observable {
2230
2852
  let sendReply = false;
2231
2853
  switch (messageType) {
2232
2854
  case messageAwareness: {
2233
- awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2855
+ YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2234
2856
  break;
2235
2857
  }
2236
2858
  case messageQueryAwareness: {
2237
2859
  encoding.writeVarUint(encoder, messageAwareness);
2238
- encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2860
+ encoding.writeVarUint8Array(encoder, YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2239
2861
  sendReply = true;
2240
2862
  break;
2241
2863
  }
@@ -2250,130 +2872,127 @@ var SpaceAwarenessProvider = class extends Observable {
2250
2872
  return encoder;
2251
2873
  }
2252
2874
  _handleBeforeUnload() {
2253
- awarenessProtocol.removeAwarenessStates(this._awareness, [
2875
+ YP.removeAwarenessStates(this._awareness, [
2254
2876
  this._clientId
2255
2877
  ], "window unload");
2256
2878
  }
2257
2879
  };
2258
2880
 
2259
2881
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2260
- var __dxlog_file4 = "/home/circleci/project/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
2261
- var useTextModel = ({ identity, space, text }) => {
2262
- const [model, setModel] = useState2(() => createModel({
2263
- identity,
2264
- space,
2265
- text
2266
- }));
2267
- useEffect3(() => {
2268
- setModel(createModel({
2269
- identity,
2270
- space,
2271
- text
2272
- }));
2273
- }, [
2882
+ var modelState = StateField5.define({
2883
+ create: () => void 0,
2884
+ update: (model) => model
2885
+ });
2886
+ var useTextModel = (props) => {
2887
+ const { identity, space, text } = props;
2888
+ const [model, setModel] = useState2();
2889
+ useEffect2(() => setModel(createModel(props)), [
2274
2890
  identity,
2275
2891
  space,
2276
2892
  text
2277
2893
  ]);
2278
2894
  return model;
2279
2895
  };
2280
- var createModel = (options) => {
2281
- const { text } = options;
2282
- if (isActualAutomergeObject(text)) {
2283
- return createAutomergeModel(options);
2896
+ var createModel = (props) => {
2897
+ const { text } = props;
2898
+ if (isAutomergeObject(text)) {
2899
+ return createAutomergeModel(props);
2900
+ } else if (text?.doc) {
2901
+ return createYjsModel(props);
2284
2902
  } else {
2285
- if (!text?.doc) {
2286
- return void 0;
2287
- }
2288
- return createYjsModel(options);
2903
+ return void 0;
2289
2904
  }
2290
2905
  };
2291
- var createYjsModel = ({ identity, space, text }) => {
2292
- invariant3(text?.doc && text?.content, void 0, {
2293
- F: __dxlog_file4,
2294
- L: 81,
2295
- S: void 0,
2296
- A: [
2297
- "text?.doc && text?.content",
2298
- ""
2299
- ]
2300
- });
2301
- const provider = space ? new SpaceAwarenessProvider({
2302
- space,
2303
- doc: text.doc,
2304
- channel: `yjs.awareness.${text.id}`
2305
- }) : void 0;
2306
- return {
2307
- id: text.doc.guid,
2308
- content: text.content,
2309
- text: () => text.content.toString(),
2310
- ranges: [],
2311
- extension: yCollab(text.content, provider?.awareness),
2312
- awareness: provider?.awareness,
2313
- peer: identity ? {
2314
- id: identity.identityKey.toHex(),
2315
- name: identity.profile?.displayName
2316
- } : void 0
2317
- };
2318
- };
2319
- var createAutomergeModel = ({ identity, text }) => {
2906
+
2907
+ // packages/ui/react-ui-editor/src/hooks/automerge.ts
2908
+ var createAutomergeModel = ({ space, identity, text }) => {
2320
2909
  const obj = text;
2321
2910
  const doc = getRawDoc(obj, [
2322
2911
  obj.field
2323
2912
  ]);
2324
- return {
2913
+ const awarenessProvider = space && new SpaceAwarenessProvider({
2914
+ space,
2915
+ channel: `automerge.awareness.${obj.id}`,
2916
+ info: {
2917
+ displayName: identity?.profile?.displayName ?? "",
2918
+ color: cursorColor.color,
2919
+ lightColor: cursorColor.light
2920
+ },
2921
+ peerId: identity?.identityKey.toHex() ?? "Anonymous"
2922
+ });
2923
+ const model = {
2325
2924
  id: obj.id,
2326
2925
  content: doc,
2327
2926
  text: () => get4(doc.handle.docSync(), doc.path),
2328
- ranges: [],
2329
- extension: automergePlugin(doc.handle, doc.path),
2927
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
2928
+ extension: [
2929
+ modelState.init(() => model),
2930
+ automerge3({
2931
+ handle: doc.handle,
2932
+ path: doc.path
2933
+ }),
2934
+ awarenessProvider && AwarenessProvider.of(awarenessProvider),
2935
+ awareness()
2936
+ ].filter(isNotNullOrUndefined),
2330
2937
  peer: identity ? {
2331
2938
  id: identity.identityKey.toHex(),
2332
2939
  name: identity.profile?.displayName
2333
2940
  } : void 0
2334
2941
  };
2942
+ return model;
2335
2943
  };
2336
2944
  export {
2945
+ AwarenessProvider,
2337
2946
  BaseTextEditor,
2947
+ Cursor,
2338
2948
  Doc,
2339
2949
  EditorModes,
2340
2950
  MarkdownEditor,
2951
+ RemoteSelectionsDecorator,
2341
2952
  SpaceAwarenessProvider,
2342
2953
  TextEditor,
2343
2954
  TextKind,
2955
+ YAwarenessProvider,
2344
2956
  YText,
2345
2957
  YXmlFragment,
2346
2958
  autocomplete,
2959
+ automerge3 as automerge,
2960
+ awareness,
2347
2961
  basicBundle,
2348
2962
  blast,
2963
+ callbackWrapper,
2349
2964
  code2 as code,
2350
2965
  codeTheme,
2351
2966
  comments,
2352
- cursorColor,
2967
+ createAutomergeModel,
2968
+ createYjsModel,
2353
2969
  defaultMarkdownSlots,
2354
2970
  defaultOptions,
2355
2971
  defaultSlots,
2356
2972
  defaultTextSlots,
2357
2973
  defaultTheme,
2358
- demo,
2359
- highlight,
2360
- highlightDecorations,
2361
- highlightStateField,
2974
+ getToken,
2975
+ hr,
2362
2976
  image,
2363
2977
  link,
2364
- listener2 as listener,
2978
+ listener,
2365
2979
  markdownBundle,
2366
2980
  markdownHighlightStyle,
2367
2981
  markdownTags,
2368
2982
  markdownTagsExtensions,
2369
2983
  markdownTheme,
2370
2984
  mention,
2985
+ modelState,
2986
+ outliner,
2987
+ setCommentRange,
2988
+ setFocus,
2989
+ setSelection,
2371
2990
  table,
2372
2991
  tags2 as tags,
2373
2992
  tasklist,
2374
2993
  textTheme,
2375
- tooltip,
2376
- useHighlights,
2377
- useTextModel
2994
+ typewriter,
2995
+ useTextModel,
2996
+ yjs
2378
2997
  };
2379
2998
  //# sourceMappingURL=index.mjs.map