@dxos/react-ui-editor 0.3.11-main.668f0ff → 0.3.11-main.674a7f9

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