@dxos/react-ui-editor 0.3.10 → 0.3.11-main.0ac3ef5

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