@dxos/react-ui-editor 0.3.10-next.ef70620 → 0.3.11-main.008b7c6

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