@dxos/react-ui-editor 0.3.11-main.9bacd9f → 0.3.11-main.9d68491

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