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

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 (262) hide show
  1. package/dist/lib/browser/index.mjs +2561 -1619
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +14 -15
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +20 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -3
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -1
  11. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  13. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  14. package/dist/types/src/components/TextEditor/hooks.stories.d.ts +23 -0
  15. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -0
  16. package/dist/types/src/components/TextEditor/index.d.ts +0 -2
  17. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  18. package/dist/types/src/components/Toolbar/Toolbar.d.ts +25 -0
  19. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
  20. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +23 -0
  21. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
  22. package/dist/types/src/components/Toolbar/index.d.ts +2 -0
  23. package/dist/types/src/components/Toolbar/index.d.ts.map +1 -0
  24. package/dist/types/src/components/index.d.ts +1 -0
  25. package/dist/types/src/components/index.d.ts.map +1 -1
  26. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
  27. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  28. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  29. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
  30. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
  31. package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
  32. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
  33. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  34. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  35. package/dist/types/src/extensions/automerge/defs.d.ts +24 -0
  36. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  37. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  38. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  39. package/dist/types/src/extensions/automerge/semaphore.d.ts +15 -0
  40. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  41. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  42. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  43. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  44. package/dist/types/src/extensions/awareness.d.ts +46 -0
  45. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  46. package/dist/types/src/extensions/basic.d.ts +8 -0
  47. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  48. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  49. package/dist/types/src/extensions/comments.d.ts +67 -0
  50. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  51. package/dist/types/src/extensions/cursor.d.ts +25 -0
  52. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  53. package/dist/types/src/extensions/index.d.ts +13 -0
  54. package/dist/types/src/extensions/index.d.ts.map +1 -0
  55. package/dist/types/src/extensions/listener.d.ts +10 -0
  56. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  57. package/dist/types/src/extensions/markdown/bundle.d.ts +14 -0
  58. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  59. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
  60. package/dist/types/src/extensions/markdown/formatting.d.ts +13 -0
  61. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  62. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  63. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  64. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  65. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  66. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  67. package/dist/types/src/extensions/markdown/index.d.ts +11 -0
  68. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  69. package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/link.d.ts +1 -0
  70. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  71. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  72. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  73. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  74. package/dist/types/src/extensions/outliner.d.ts +4 -0
  75. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  76. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  77. package/dist/types/src/hooks/awareness-provider.d.ts +31 -0
  78. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  79. package/dist/types/src/hooks/defs.d.ts +25 -0
  80. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  81. package/dist/types/src/hooks/index.d.ts +5 -1
  82. package/dist/types/src/hooks/index.d.ts.map +1 -1
  83. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  84. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  85. package/dist/types/src/hooks/useEditorView.d.ts +17 -0
  86. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
  87. package/dist/types/src/hooks/useTextEditor.d.ts +39 -0
  88. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  89. package/dist/types/src/hooks/useTextModel.d.ts +2 -31
  90. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  91. package/dist/types/src/index.d.ts +5 -0
  92. package/dist/types/src/index.d.ts.map +1 -1
  93. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  94. package/dist/types/src/styles/index.d.ts +1 -0
  95. package/dist/types/src/styles/index.d.ts.map +1 -1
  96. package/dist/types/src/styles/markdown.d.ts +2 -2
  97. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  98. package/dist/types/src/styles/tokens.d.ts +1 -0
  99. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  100. package/dist/types/src/{components/TextEditor/themes → themes}/default.d.ts +1 -1
  101. package/dist/types/src/themes/default.d.ts.map +1 -0
  102. package/dist/types/src/themes/index.d.ts.map +1 -0
  103. package/dist/types/src/util.d.ts +7 -0
  104. package/dist/types/src/util.d.ts.map +1 -0
  105. package/package.json +48 -36
  106. package/src/components/TextEditor/MarkdownEditor.stories.tsx +93 -102
  107. package/src/components/TextEditor/TextEditor.stories.tsx +13 -34
  108. package/src/components/TextEditor/TextEditor.tsx +138 -97
  109. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +66 -6
  110. package/src/components/TextEditor/comments.stories.tsx +357 -0
  111. package/src/components/TextEditor/hooks.stories.tsx +106 -0
  112. package/src/components/TextEditor/index.ts +0 -3
  113. package/src/components/Toolbar/Toolbar.stories.tsx +90 -0
  114. package/src/components/Toolbar/Toolbar.tsx +166 -0
  115. package/src/components/Toolbar/index.ts +5 -0
  116. package/src/components/index.ts +1 -0
  117. package/src/{components/TextEditor/extensions → extensions}/autocomplete.ts +23 -19
  118. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  119. package/src/extensions/automerge/automerge.test.ts +13 -0
  120. package/src/extensions/automerge/automerge.ts +103 -0
  121. package/src/extensions/automerge/cursor.ts +52 -0
  122. package/src/extensions/automerge/defs.ts +41 -0
  123. package/src/extensions/automerge/index.ts +6 -0
  124. package/src/extensions/automerge/semaphore.ts +81 -0
  125. package/src/extensions/automerge/update-automerge.ts +49 -0
  126. package/src/{hooks → extensions}/automerge/update-codemirror.ts +17 -9
  127. package/src/extensions/awareness.ts +310 -0
  128. package/src/{components/TextEditor/extensions → extensions}/basic.ts +17 -10
  129. package/src/extensions/comments.ts +476 -0
  130. package/src/extensions/cursor.ts +50 -0
  131. package/src/{components/TextEditor/extensions → extensions}/index.ts +4 -8
  132. package/src/extensions/listener.ts +35 -0
  133. package/src/{components/TextEditor/extensions → extensions}/markdown/bundle.ts +23 -28
  134. package/src/extensions/markdown/code.ts +150 -0
  135. package/src/extensions/markdown/formatting.ts +188 -0
  136. package/src/extensions/markdown/heading.ts +59 -0
  137. package/src/{components/TextEditor/extensions → extensions}/markdown/highlight.ts +12 -27
  138. package/src/extensions/markdown/hr.ts +71 -0
  139. package/src/extensions/markdown/image.ts +87 -0
  140. package/src/extensions/markdown/index.ts +14 -0
  141. package/src/{components/TextEditor/extensions → extensions/markdown}/link.ts +119 -62
  142. package/src/{components/TextEditor/extensions → extensions/markdown}/table.ts +10 -4
  143. package/src/extensions/markdown/tasklist.ts +110 -0
  144. package/src/{components/TextEditor/extensions → extensions}/mention.ts +9 -2
  145. package/src/extensions/outliner.ts +12 -0
  146. package/src/{components/TextEditor/extensions → extensions}/typewriter.ts +1 -1
  147. package/src/hooks/awareness-provider.ts +124 -0
  148. package/src/hooks/defs.ts +42 -0
  149. package/src/hooks/index.ts +6 -1
  150. package/src/hooks/useActionHandler.ts +62 -0
  151. package/src/hooks/useEditorView.ts +29 -0
  152. package/src/hooks/useTextEditor.ts +153 -0
  153. package/src/hooks/useTextModel.ts +33 -115
  154. package/src/index.ts +5 -0
  155. package/src/styles/index.ts +1 -0
  156. package/src/styles/markdown.ts +9 -12
  157. package/src/styles/tokens.ts +3 -0
  158. package/src/{components/TextEditor/themes → themes}/default.ts +54 -81
  159. package/src/util.ts +41 -0
  160. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  161. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  162. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
  163. package/dist/types/src/components/TextEditor/extensions/basic.d.ts +0 -9
  164. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
  165. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
  166. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +0 -1
  167. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +0 -48
  168. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +0 -1
  169. package/dist/types/src/components/TextEditor/extensions/hr.d.ts.map +0 -1
  170. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
  171. package/dist/types/src/components/TextEditor/extensions/index.d.ts +0 -17
  172. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  173. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
  174. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +0 -9
  175. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
  176. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +0 -17
  177. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  178. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
  179. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +0 -3
  180. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  181. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
  182. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts +0 -3
  183. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts.map +0 -1
  184. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
  185. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
  186. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
  187. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
  188. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts.map +0 -1
  189. package/dist/types/src/components/TextEditor/extensions/util.d.ts +0 -5
  190. package/dist/types/src/components/TextEditor/extensions/util.d.ts.map +0 -1
  191. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
  192. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
  193. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  194. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  195. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  196. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  197. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  198. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  199. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  200. package/dist/types/src/hooks/automerge/semaphore.d.ts +0 -19
  201. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
  202. package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
  203. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
  204. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
  205. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  206. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  207. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  208. package/dist/types/src/hooks/yjs/space-provider.d.ts +0 -27
  209. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  210. package/dist/types/src/hooks/yjs/yjs.stories.d.ts +0 -12
  211. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  212. package/dist/types/src/hooks/yjs/yjs.test.d.ts +0 -2
  213. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
  214. package/dist/types/src/testing/index.d.ts +0 -3
  215. package/dist/types/src/testing/index.d.ts.map +0 -1
  216. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  217. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  218. package/dist/types/src/testing/proto/index.d.ts +0 -2
  219. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  220. package/dist/types/src/testing/replicator.d.ts +0 -45
  221. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  222. package/src/components/TextEditor/codemirror.stories.ts +0 -114
  223. package/src/components/TextEditor/extensions/code.ts +0 -99
  224. package/src/components/TextEditor/extensions/comments.ts +0 -388
  225. package/src/components/TextEditor/extensions/hr.ts +0 -74
  226. package/src/components/TextEditor/extensions/image.ts +0 -69
  227. package/src/components/TextEditor/extensions/listener.ts +0 -23
  228. package/src/components/TextEditor/extensions/markdown/index.ts +0 -6
  229. package/src/components/TextEditor/extensions/mermaid.ts +0 -11
  230. package/src/components/TextEditor/extensions/tasklist.ts +0 -121
  231. package/src/components/TextEditor/extensions/tooltip.ts +0 -61
  232. package/src/components/TextEditor/extensions/util.ts +0 -18
  233. package/src/hooks/automerge/handle.ts +0 -14
  234. package/src/hooks/automerge/index.ts +0 -6
  235. package/src/hooks/automerge/plugin.ts +0 -134
  236. package/src/hooks/automerge/semaphore.ts +0 -78
  237. package/src/hooks/automerge/update-automerge.ts +0 -44
  238. package/src/hooks/yjs/index.ts +0 -6
  239. package/src/hooks/yjs/space-provider.ts +0 -126
  240. package/src/hooks/yjs/yjs.stories.tsx +0 -56
  241. package/src/hooks/yjs/yjs.test.ts +0 -59
  242. package/src/testing/index.ts +0 -6
  243. package/src/testing/proto/gen/schema.ts +0 -49
  244. package/src/testing/proto/index.ts +0 -5
  245. package/src/testing/proto/schema.proto +0 -15
  246. package/src/testing/replicator.ts +0 -189
  247. /package/dist/types/src/{components/TextEditor/extensions → extensions}/autocomplete.d.ts +0 -0
  248. /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
  249. /package/dist/types/src/{components/TextEditor/extensions → extensions}/blast.d.ts +0 -0
  250. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/code.d.ts +0 -0
  251. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/highlight.d.ts +0 -0
  252. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/hr.d.ts +0 -0
  253. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/image.d.ts +0 -0
  254. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/table.d.ts +0 -0
  255. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/tasklist.d.ts +0 -0
  256. /package/dist/types/src/{components/TextEditor/extensions → extensions}/mention.d.ts +0 -0
  257. /package/dist/types/src/{components/TextEditor/extensions → extensions}/typewriter.d.ts +0 -0
  258. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  259. /package/dist/types/src/{components/TextEditor/themes → themes}/index.d.ts +0 -0
  260. /package/src/{components/TextEditor/extensions → extensions}/blast.ts +0 -0
  261. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  262. /package/src/{components/TextEditor/themes → themes}/index.ts +0 -0
@@ -1,9 +1,9 @@
1
1
  //
2
2
  // Copyright 2023 DXOS.org
3
+ // Copyright 2024 Automerge
4
+ // Ref: https://github.com/automerge/automerge-codemirror
3
5
  //
4
6
 
5
- // TODO(burdon): Additional copyright?
6
-
7
7
  import { ChangeSet, type ChangeSpec, type EditorSelection, type EditorState } from '@codemirror/state';
8
8
  import { type EditorView } from '@codemirror/view';
9
9
 
@@ -16,7 +16,7 @@ import {
16
16
  type SpliceTextPatch,
17
17
  } from '@dxos/automerge/automerge';
18
18
 
19
- import { reconcileAnnotationType } from './plugin';
19
+ import { reconcileAnnotation } from './defs';
20
20
 
21
21
  export const updateCodeMirror = (view: EditorView, selection: EditorSelection, target: Prop[], patches: Patch[]) => {
22
22
  for (const patch of patches) {
@@ -26,14 +26,14 @@ export const updateCodeMirror = (view: EditorView, selection: EditorSelection, t
26
26
  selection = selection.map(changeSet, 1);
27
27
  view.dispatch({
28
28
  changes: changeSet,
29
- annotations: reconcileAnnotationType.of({}),
29
+ annotations: reconcileAnnotation.of(false),
30
30
  });
31
31
  }
32
32
  }
33
33
 
34
34
  view.dispatch({
35
35
  selection,
36
- annotations: reconcileAnnotationType.of({}),
36
+ annotations: reconcileAnnotation.of(false),
37
37
  });
38
38
  };
39
39
 
@@ -56,7 +56,8 @@ const handleInsert = (target: Prop[], patch: InsertPatch): Array<ChangeSpec> =>
56
56
  if (index == null) {
57
57
  return [];
58
58
  }
59
- const text = patch.values.map((v) => (v ? v.toString() : '')).join('');
59
+
60
+ const text = patch.values.map((value) => (value ? value.toString() : '')).join('');
60
61
  return [{ from: index, to: index, insert: text }];
61
62
  };
62
63
 
@@ -65,6 +66,7 @@ const handleSplice = (target: Prop[], patch: SpliceTextPatch): Array<ChangeSpec>
65
66
  if (index == null) {
66
67
  return [];
67
68
  }
69
+
68
70
  return [{ from: index, insert: patch.value }];
69
71
  };
70
72
 
@@ -73,6 +75,7 @@ const handleDel = (target: Prop[], patch: DelPatch): Array<ChangeSpec> => {
73
75
  if (index == null) {
74
76
  return [];
75
77
  }
78
+
76
79
  const length = patch.length || 1;
77
80
  return [{ from: index, to: index + length }];
78
81
  };
@@ -82,27 +85,32 @@ const handlePut = (target: Prop[], patch: PutPatch, state: EditorState): Array<C
82
85
  if (index == null) {
83
86
  return [];
84
87
  }
88
+
85
89
  const length = state.doc.length;
86
90
  if (typeof patch.value !== 'string') {
87
- return []; // TODO(dmaretskyi): How to handle non string values?
91
+ return []; // TODO(dmaretskyi): How to handle non-string values?
88
92
  }
93
+
89
94
  return [{ from: 0, to: length, insert: patch.value as any }];
90
95
  };
91
96
 
92
- // If the path of the patch is of the form [path, <index>] then we know this is
93
- // a path to a character within the sequence given by path
97
+ // If the path of the patch is of the form [path, <index>] then we know this is a path to a character
98
+ // within the sequence given by path.
94
99
  const charPath = (textPath: Prop[], candidatePath: Prop[]): number | null => {
95
100
  if (candidatePath.length !== textPath.length + 1) {
96
101
  return null;
97
102
  }
103
+
98
104
  for (let i = 0; i < textPath.length; i++) {
99
105
  if (textPath[i] !== candidatePath[i]) {
100
106
  return null;
101
107
  }
102
108
  }
109
+
103
110
  const index = candidatePath[candidatePath.length - 1];
104
111
  if (typeof index === 'number') {
105
112
  return index;
106
113
  }
114
+
107
115
  return null;
108
116
  };
@@ -0,0 +1,310 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Annotation, Facet, type Extension, RangeSet, type Range } from '@codemirror/state';
6
+ import {
7
+ Decoration,
8
+ type DecorationSet,
9
+ EditorView,
10
+ type PluginValue,
11
+ ViewPlugin,
12
+ type ViewUpdate,
13
+ WidgetType,
14
+ } from '@codemirror/view';
15
+
16
+ import { Event } from '@dxos/async';
17
+ import { Context } from '@dxos/context';
18
+
19
+ import { Cursor, type CursorConverter } from './cursor';
20
+
21
+ export interface AwarenessProvider {
22
+ remoteStateChange: Event<void>;
23
+
24
+ open(): void;
25
+ close(): void;
26
+
27
+ getRemoteStates(): AwarenessState[];
28
+ update(position: AwarenessPosition | undefined): void;
29
+ }
30
+
31
+ const dummyProvider: AwarenessProvider = {
32
+ remoteStateChange: new Event(),
33
+
34
+ open: () => {},
35
+ close: () => {},
36
+
37
+ getRemoteStates: () => [],
38
+ update: () => {},
39
+ };
40
+
41
+ export const awarenessProvider = Facet.define<AwarenessProvider, AwarenessProvider>({
42
+ combine: (providers) => providers[0] ?? dummyProvider,
43
+ });
44
+
45
+ // TODO(dmaretskyi): Specify the users that actually changed. Currently, we recalculate positions for every user.
46
+ const RemoteSelectionChangedAnnotation = Annotation.define();
47
+
48
+ export type AwarenessPosition = {
49
+ anchor?: string;
50
+ head?: string;
51
+ };
52
+
53
+ export type AwarenessInfo = {
54
+ displayName?: string;
55
+ // TODO(burdon): Rename light/dark.
56
+ color?: string;
57
+ lightColor?: string;
58
+ };
59
+
60
+ export type AwarenessState = {
61
+ position?: AwarenessPosition;
62
+ peerId: string;
63
+ info: AwarenessInfo;
64
+ };
65
+
66
+ /**
67
+ * Extension provides presence information about other peers.
68
+ */
69
+ export const awareness = (provider = dummyProvider): Extension => {
70
+ return [
71
+ awarenessProvider.of(provider),
72
+ ViewPlugin.fromClass(RemoteSelectionsDecorator, {
73
+ decorations: (value) => value.decorations,
74
+ }),
75
+ styles,
76
+ ];
77
+ };
78
+
79
+ /**
80
+ * Generates selection decorations from remote peers.
81
+ */
82
+ export class RemoteSelectionsDecorator implements PluginValue {
83
+ public decorations: DecorationSet = RangeSet.of([]);
84
+
85
+ private readonly _ctx = new Context();
86
+
87
+ private _cursorConverter: CursorConverter;
88
+ private _provider: AwarenessProvider;
89
+ private _lastAnchor?: number = undefined;
90
+ private _lastHead?: number = undefined;
91
+
92
+ constructor(view: EditorView) {
93
+ this._cursorConverter = view.state.facet(Cursor.converter);
94
+ this._provider = view.state.facet(awarenessProvider);
95
+ this._provider.open();
96
+ this._provider.remoteStateChange.on(this._ctx, () => {
97
+ view.dispatch({ annotations: [RemoteSelectionChangedAnnotation.of([])] });
98
+ });
99
+ }
100
+
101
+ destroy() {
102
+ void this._ctx.dispose();
103
+ this._provider.close();
104
+ }
105
+
106
+ update(update: ViewUpdate) {
107
+ this._updateLocalSelection(update);
108
+ this._updateRemoteSelections(update);
109
+ }
110
+
111
+ private _updateLocalSelection(update: ViewUpdate) {
112
+ const hasFocus = update.view.hasFocus && update.view.dom.ownerDocument.hasFocus();
113
+ const { anchor = undefined, head = undefined } = hasFocus ? update.state.selection.main : {};
114
+ if (this._lastAnchor === anchor && this._lastHead === head) {
115
+ return;
116
+ }
117
+
118
+ this._lastAnchor = anchor;
119
+ this._lastHead = head;
120
+
121
+ this._provider.update(
122
+ anchor !== undefined && head !== undefined
123
+ ? {
124
+ anchor: this._cursorConverter.toCursor(anchor),
125
+ head: this._cursorConverter.toCursor(head),
126
+ }
127
+ : undefined,
128
+ );
129
+ }
130
+
131
+ private _updateRemoteSelections(update: ViewUpdate) {
132
+ const decorations: Range<Decoration>[] = [];
133
+ const awarenessStates = this._provider.getRemoteStates();
134
+ for (const state of awarenessStates) {
135
+ const anchor = state.position?.anchor ? this._cursorConverter.fromCursor(state.position.anchor) : null;
136
+ const head = state.position?.head ? this._cursorConverter.fromCursor(state.position.head) : null;
137
+ if (anchor == null || head == null) {
138
+ continue;
139
+ }
140
+
141
+ const start = Math.min(Math.min(anchor, head), update.view.state.doc.length);
142
+ const end = Math.min(Math.max(anchor, head), update.view.state.doc.length);
143
+
144
+ const startLine = update.view.state.doc.lineAt(start);
145
+ const endLine = update.view.state.doc.lineAt(end);
146
+
147
+ // TODO(burdon): Factor out styles.
148
+ const color = state.info.color ?? '#30bced';
149
+ const lightColor = state.info.lightColor ?? color + '33';
150
+
151
+ if (startLine.number === endLine.number) {
152
+ // Selected content in a single line.
153
+ decorations.push({
154
+ from: start,
155
+ to: end,
156
+ value: Decoration.mark({
157
+ attributes: { style: `background-color: ${lightColor}` },
158
+ class: 'cm-collab-selection',
159
+ }),
160
+ });
161
+ } else {
162
+ // Selected content in multiple lines; first, render text-selection in the first line.
163
+ decorations.push({
164
+ from: start,
165
+ to: startLine.from + startLine.length,
166
+ value: Decoration.mark({
167
+ attributes: { style: `background-color: ${color}` },
168
+ class: 'cm-collab-selection',
169
+ }),
170
+ });
171
+
172
+ // Render text-selection in the last line.
173
+ decorations.push({
174
+ from: endLine.from,
175
+ to: end,
176
+ value: Decoration.mark({
177
+ attributes: { style: `background-color: ${color}` },
178
+ class: 'cm-collab-selection',
179
+ }),
180
+ });
181
+
182
+ for (let i = startLine.number + 1; i < endLine.number; i++) {
183
+ const linePos = update.view.state.doc.line(i).from;
184
+ decorations.push({
185
+ from: linePos,
186
+ to: linePos,
187
+ value: Decoration.line({
188
+ attributes: { style: `background-color: ${color}`, class: 'cm-collab-selectionLine' },
189
+ }),
190
+ });
191
+ }
192
+ }
193
+
194
+ decorations.push({
195
+ from: head,
196
+ to: head,
197
+ value: Decoration.widget({
198
+ side: head - anchor > 0 ? -1 : 1, // The local cursor should be rendered outside the remote selection.
199
+ block: false,
200
+ widget: new RemoteCaretWidget(state.info.displayName ?? 'Anonymous', color),
201
+ }),
202
+ });
203
+ }
204
+
205
+ this.decorations = Decoration.set(decorations, true);
206
+ }
207
+ }
208
+
209
+ class RemoteCaretWidget extends WidgetType {
210
+ constructor(private readonly _name: string, private readonly _color: string) {
211
+ super();
212
+ }
213
+
214
+ override toDOM(): HTMLElement {
215
+ const span = document.createElement('span');
216
+ span.className = 'cm-collab-selectionCaret';
217
+ span.style.backgroundColor = this._color;
218
+ span.style.borderColor = this._color;
219
+
220
+ const dot = document.createElement('div');
221
+ dot.className = 'cm-collab-selectionCaretDot';
222
+
223
+ const info = document.createElement('div');
224
+ info.className = 'cm-collab-selectionInfo';
225
+ info.innerText = this._name;
226
+
227
+ span.appendChild(document.createTextNode('\u2060'));
228
+ span.appendChild(dot);
229
+ span.appendChild(document.createTextNode('\u2060'));
230
+ span.appendChild(info);
231
+ span.appendChild(document.createTextNode('\u2060'));
232
+
233
+ return span;
234
+ }
235
+
236
+ override updateDOM() {
237
+ return false;
238
+ }
239
+
240
+ override eq(widget: this) {
241
+ return widget._color === this._color;
242
+ }
243
+
244
+ override get estimatedHeight() {
245
+ return -1;
246
+ }
247
+
248
+ override ignoreEvent() {
249
+ return true;
250
+ }
251
+ }
252
+
253
+ const styles = EditorView.baseTheme({
254
+ '.cm-collab-selection': {},
255
+ '.cm-collab-selectionLine': {
256
+ padding: 0,
257
+ margin: '0px 2px 0px 4px',
258
+ },
259
+ '.cm-collab-selectionCaret': {
260
+ position: 'relative',
261
+ borderLeft: '1px solid black',
262
+ borderRight: '1px solid black',
263
+ marginLeft: '-1px',
264
+ marginRight: '-1px',
265
+ boxSizing: 'border-box',
266
+ display: 'inline',
267
+ cursor: 'pointer',
268
+ },
269
+ '.cm-collab-selectionCaretDot': {
270
+ borderRadius: '50%',
271
+ position: 'absolute',
272
+ width: '.5em',
273
+ height: '.5em',
274
+ top: '-.25em',
275
+ left: '-.25em',
276
+ backgroundColor: 'inherit',
277
+ transition: 'transform .3s ease-in-out',
278
+ boxSizing: 'border-box',
279
+ },
280
+ '.cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot': {
281
+ transform: 'scale(0)',
282
+ transformOrigin: 'center',
283
+ },
284
+ '.cm-collab-selectionInfo': {
285
+ position: 'absolute',
286
+ transform: 'translate(-50%, 0)',
287
+ top: '-20px',
288
+ left: 0,
289
+ fontSize: '.75em',
290
+ fontFamily: 'sans-serif',
291
+ fontStyle: 'normal',
292
+ fontWeight: 'normal',
293
+ lineHeight: 'normal',
294
+ userSelect: 'none',
295
+ color: 'white',
296
+ padding: '2px',
297
+ zIndex: 101,
298
+ transition: 'opacity .3s ease-in-out',
299
+ backgroundColor: 'inherit',
300
+ borderRadius: '2px',
301
+ // These should be separate.
302
+ opacity: 0,
303
+ transitionDelay: '0s',
304
+ whiteSpace: 'nowrap',
305
+ },
306
+ '.cm-collab-selectionCaret:hover > .cm-collab-selectionInfo': {
307
+ opacity: 1,
308
+ transitionDelay: '0s',
309
+ },
310
+ });
@@ -3,30 +3,37 @@
3
3
  //
4
4
 
5
5
  import { closeBrackets } from '@codemirror/autocomplete';
6
+ import { history } from '@codemirror/commands';
6
7
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
7
8
  import { type Extension } from '@codemirror/state';
8
9
  import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
9
- import { drawSelection, EditorView, placeholder } from '@codemirror/view';
10
+ import { drawSelection, EditorView, highlightActiveLine, placeholder } from '@codemirror/view';
10
11
 
11
- import type { ThemeMode } from '@dxos/react-ui';
12
+ import { type ThemeMode } from '@dxos/react-ui';
13
+ import { isNotFalsy } from '@dxos/util';
14
+
15
+ import { type TextEditorProps } from '../components';
12
16
 
13
17
  export type BasicBundleOptions = {
14
- readonly?: boolean;
15
18
  themeMode?: ThemeMode;
16
- placeholder?: string;
17
- };
19
+ } & Pick<TextEditorProps, 'placeholder' | 'lineWrapping'>;
18
20
 
19
- export const basicBundle = ({ readonly, themeMode, placeholder: _placeholder }: BasicBundleOptions): Extension[] =>
21
+ export const createBasicBundle = ({
22
+ themeMode,
23
+ placeholder: _placeholder,
24
+ lineWrapping = true,
25
+ }: BasicBundleOptions = {}): Extension[] =>
20
26
  [
21
- EditorView.lineWrapping,
27
+ lineWrapping && EditorView.lineWrapping,
22
28
 
23
- // TODO(burdon): Compare with minimalSetup.
24
29
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
25
30
  bracketMatching(),
26
31
  closeBrackets(),
27
32
  drawSelection(),
33
+ highlightActiveLine(),
34
+ history(),
28
35
  _placeholder && placeholder(_placeholder),
29
36
 
30
- // TODO(burdon): Is this required?
37
+ // https://github.com/codemirror/theme-one-dark
31
38
  themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
32
- ].filter(Boolean) as Extension[];
39
+ ].filter(isNotFalsy);