@dxos/react-ui-editor 0.3.11-main.bd26370 → 0.3.11-main.c244d7e

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