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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/dist/lib/browser/index.mjs +2814 -1096
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +63 -0
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -0
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +9 -4
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +15 -4
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/automerge.stories.d.ts +20 -0
  11. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/TextEditor/index.d.ts +1 -1
  14. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  15. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -0
  16. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -1
  17. package/dist/types/src/extensions/autocomplete.d.ts +10 -0
  18. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
  19. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  20. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  21. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  22. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  23. package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
  24. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  25. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  26. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  27. package/dist/types/src/{automerge/automerge-plugin/PatchSemaphore.d.ts → extensions/automerge/semaphore.d.ts} +8 -5
  28. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  29. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  30. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  31. package/dist/types/src/extensions/automerge/update-codemirror.d.ts +5 -0
  32. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  33. package/dist/types/src/extensions/awareness.d.ts +46 -0
  34. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  35. package/dist/types/src/{components/TextEditor/extensions/basic/bundle.d.ts → extensions/basic.d.ts} +3 -2
  36. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  37. package/dist/types/src/extensions/blast.d.ts +25 -0
  38. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  39. package/dist/types/src/extensions/code.d.ts +2 -0
  40. package/dist/types/src/extensions/code.d.ts.map +1 -0
  41. package/dist/types/src/extensions/comments.d.ts +50 -0
  42. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  43. package/dist/types/src/extensions/hr.d.ts +2 -0
  44. package/dist/types/src/extensions/hr.d.ts.map +1 -0
  45. package/dist/types/src/extensions/image.d.ts +4 -0
  46. package/dist/types/src/extensions/image.d.ts.map +1 -0
  47. package/dist/types/src/extensions/index.d.ts +19 -0
  48. package/dist/types/src/extensions/index.d.ts.map +1 -0
  49. package/dist/types/src/extensions/link.d.ts +12 -0
  50. package/dist/types/src/extensions/link.d.ts.map +1 -0
  51. package/dist/types/src/extensions/listener.d.ts +10 -0
  52. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  53. package/dist/types/src/extensions/markdown/bundle.d.ts +17 -0
  54. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  55. package/dist/types/src/extensions/markdown/highlight.d.ts +36 -0
  56. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  57. package/dist/types/src/extensions/markdown/index.d.ts +3 -0
  58. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  59. package/dist/types/src/extensions/mention.d.ts +6 -0
  60. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  61. package/dist/types/src/extensions/mermaid.d.ts +3 -0
  62. package/dist/types/src/extensions/mermaid.d.ts.map +1 -0
  63. package/dist/types/src/extensions/table.d.ts +8 -0
  64. package/dist/types/src/extensions/table.d.ts.map +1 -0
  65. package/dist/types/src/extensions/tasklist.d.ts +3 -0
  66. package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
  67. package/dist/types/src/extensions/typewriter.d.ts +10 -0
  68. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  69. package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
  70. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
  71. package/dist/types/src/extensions/yjs/index.d.ts +2 -0
  72. package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
  73. package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
  74. package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
  75. package/dist/types/src/extensions/yjs/yjs.test.d.ts +2 -0
  76. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
  77. package/dist/types/src/hooks/automerge.d.ts +3 -0
  78. package/dist/types/src/hooks/automerge.d.ts.map +1 -0
  79. package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
  80. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  81. package/dist/types/src/hooks/index.d.ts +2 -1
  82. package/dist/types/src/hooks/index.d.ts.map +1 -1
  83. package/dist/types/src/hooks/useTextModel.d.ts +23 -8
  84. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  85. package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
  86. package/dist/types/src/hooks/yjs.d.ts.map +1 -0
  87. package/dist/types/src/index.d.ts +3 -0
  88. package/dist/types/src/index.d.ts.map +1 -1
  89. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  90. package/dist/types/src/styles/index.d.ts +1 -0
  91. package/dist/types/src/styles/index.d.ts.map +1 -1
  92. package/dist/types/src/styles/markdown.d.ts +14 -11
  93. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  94. package/dist/types/src/testing/replicator.d.ts +2 -2
  95. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  96. package/dist/types/src/themes/default.d.ts +17 -0
  97. package/dist/types/src/themes/default.d.ts.map +1 -0
  98. package/dist/types/src/themes/index.d.ts +2 -0
  99. package/dist/types/src/themes/index.d.ts.map +1 -0
  100. package/dist/types/src/util/cursor.d.ts +17 -0
  101. package/dist/types/src/util/cursor.d.ts.map +1 -0
  102. package/dist/types/src/util/index.d.ts +3 -0
  103. package/dist/types/src/util/index.d.ts.map +1 -0
  104. package/dist/types/src/util/util.d.ts +5 -0
  105. package/dist/types/src/util/util.d.ts.map +1 -0
  106. package/package.json +29 -33
  107. package/src/components/TextEditor/MarkdownEditor.stories.tsx +426 -0
  108. package/src/components/TextEditor/TextEditor.stories.tsx +70 -43
  109. package/src/components/TextEditor/TextEditor.tsx +98 -52
  110. package/src/{automerge → components/TextEditor}/automerge.stories.tsx +69 -7
  111. package/src/components/TextEditor/codemirror.stories.ts +3 -2
  112. package/src/components/TextEditor/index.ts +1 -1
  113. package/src/components/TextEditor/yjs.stories.tsx +1 -0
  114. package/src/extensions/autocomplete.ts +59 -0
  115. package/src/extensions/automerge/automerge.ts +94 -0
  116. package/src/extensions/automerge/cursor.ts +51 -0
  117. package/src/extensions/automerge/defs.ts +62 -0
  118. package/src/extensions/automerge/index.ts +6 -0
  119. package/src/{automerge/automerge-plugin/PatchSemaphore.ts → extensions/automerge/semaphore.ts} +24 -18
  120. package/src/{automerge/automerge-plugin/codeMirrorToAm.ts → extensions/automerge/update-automerge.ts} +8 -6
  121. package/src/{automerge/automerge-plugin/amToCodemirror.ts → extensions/automerge/update-codemirror.ts} +15 -4
  122. package/src/extensions/awareness.ts +316 -0
  123. package/src/{components/TextEditor/extensions/basic/bundle.ts → extensions/basic.ts} +8 -4
  124. package/src/extensions/blast.ts +373 -0
  125. package/src/extensions/code.ts +99 -0
  126. package/src/extensions/comments.ts +421 -0
  127. package/src/extensions/hr.ts +74 -0
  128. package/src/extensions/image.ts +69 -0
  129. package/src/extensions/index.ts +22 -0
  130. package/src/extensions/link.ts +192 -0
  131. package/src/extensions/listener.ts +35 -0
  132. package/src/extensions/markdown/bundle.ts +106 -0
  133. package/src/extensions/markdown/highlight.ts +204 -0
  134. package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +1 -1
  135. package/src/extensions/mention.ts +31 -0
  136. package/src/extensions/mermaid.ts +11 -0
  137. package/src/extensions/table.ts +132 -0
  138. package/src/extensions/tasklist.ts +121 -0
  139. package/src/extensions/typewriter.ts +68 -0
  140. package/src/extensions/yjs/cursor.ts +21 -0
  141. package/src/extensions/yjs/index.ts +5 -0
  142. package/src/extensions/yjs/yjs.test.ts +35 -0
  143. package/src/extensions/yjs/yjs.ts +16 -0
  144. package/src/hooks/automerge.ts +52 -0
  145. package/src/hooks/awareness-provider.ts +120 -0
  146. package/src/hooks/index.ts +2 -1
  147. package/src/hooks/useTextModel.ts +53 -67
  148. package/src/hooks/{yjs/space-provider.ts → yjs.ts} +71 -19
  149. package/src/index.ts +3 -0
  150. package/src/styles/index.ts +1 -0
  151. package/src/styles/markdown.ts +33 -24
  152. package/src/testing/replicator.ts +16 -21
  153. package/src/themes/default.ts +302 -0
  154. package/src/themes/index.ts +5 -0
  155. package/src/util/cursor.ts +29 -0
  156. package/src/util/index.ts +6 -0
  157. package/src/util/util.ts +18 -0
  158. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
  159. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts +0 -6
  160. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
  161. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts +0 -7
  162. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
  163. package/dist/types/src/automerge/automerge-plugin/handle.d.ts +0 -9
  164. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
  165. package/dist/types/src/automerge/automerge-plugin/index.d.ts +0 -3
  166. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
  167. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts +0 -26
  168. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
  169. package/dist/types/src/automerge/automerge.stories.d.ts +0 -9
  170. package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
  171. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
  172. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
  173. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
  174. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
  175. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
  176. package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
  177. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
  178. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
  179. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
  180. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
  181. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
  182. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts +0 -10
  183. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
  184. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
  185. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
  186. package/dist/types/src/components/TextEditor/extensions/index.d.ts +0 -5
  187. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  188. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +0 -8
  189. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  190. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +0 -3
  191. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  192. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
  193. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
  194. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
  195. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
  196. package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
  197. package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
  198. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  199. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  200. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  201. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  202. package/src/automerge/automerge-plugin/handle.ts +0 -14
  203. package/src/automerge/automerge-plugin/index.ts +0 -6
  204. package/src/automerge/automerge-plugin/plugin.ts +0 -133
  205. package/src/components/TextEditor/extensions/basic/index.ts +0 -6
  206. package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
  207. package/src/components/TextEditor/extensions/change.ts +0 -22
  208. package/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts +0 -99
  209. package/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.tsx +0 -61
  210. package/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx +0 -119
  211. package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
  212. package/src/components/TextEditor/extensions/index.ts +0 -8
  213. package/src/components/TextEditor/extensions/markdown/bundle.ts +0 -99
  214. package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
  215. package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
  216. package/src/hooks/useCollaboration.ts +0 -45
  217. package/src/hooks/yjs/index.ts +0 -6
  218. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  219. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
@@ -2,96 +2,82 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { useMemo } from 'react';
6
- import type * as awarenessProtocol from 'y-protocols/awareness';
5
+ import { StateField, type Extension } from '@codemirror/state';
6
+ import { useEffect, useState } from 'react';
7
+ import type * as YP from 'y-protocols/awareness';
7
8
 
8
- import {
9
- type DocAccessor,
10
- type Space,
11
- type TextObject,
12
- type AutomergeTextCompat,
13
- getRawDoc,
14
- isActualAutomergeObject,
15
- } from '@dxos/react-client/echo';
9
+ import { isAutomergeObject, type DocAccessor, type Space, type TextObject } from '@dxos/react-client/echo';
16
10
  import { type Identity } from '@dxos/react-client/halo';
17
11
  import type { YText, YXmlFragment } from '@dxos/text-model';
18
12
 
19
- import { SpaceAwarenessProvider } from './yjs';
13
+ import { createAutomergeModel } from './automerge';
14
+ import { createYjsModel } from './yjs';
20
15
 
21
- // TODO(burdon): Move.
22
- type Awareness = awarenessProtocol.Awareness;
23
- type Provider = { awareness: Awareness };
16
+ // TODO(burdon): Factor out defs.
17
+
18
+ /**
19
+ * State field makes the model available to other extensions.
20
+ */
21
+ export const modelState = StateField.define<EditorModel | undefined>({
22
+ create: () => undefined,
23
+ update: (model) => model,
24
+ });
25
+
26
+ export type Range = {
27
+ from: number;
28
+ to: number;
29
+ };
30
+
31
+ export type CommentRange = {
32
+ id: string;
33
+ cursor: string;
34
+ };
24
35
 
25
36
  // TODO(wittjosiah): Factor out to common package? @dxos/react-client?
26
37
  export type EditorModel = {
27
38
  id: string;
39
+ text: () => string;
40
+ extension?: Extension;
41
+
42
+ // TODO(burdon): Remove.
28
43
  content: string | YText | YXmlFragment | DocAccessor;
29
- provider?: Provider;
44
+ /**
45
+ * @deprecated Use CursorConverter.
46
+ */
47
+ getCursorFromRange?: (value: Range) => string;
48
+ getRangeFromCursor?: (cursor: string) => Range | undefined;
49
+ // TODO(burdon): Move into extension.
50
+ awareness?: YP.Awareness;
51
+ // TODO(burdon): Remove.
30
52
  peer?: {
31
53
  id: string;
32
54
  name?: string;
33
55
  };
34
56
  };
35
57
 
36
- export type UseTextModelOptions = {
58
+ // TODO(burdon): Remove space/identity dependency. Define interface for the framework re content and presence.
59
+ export type UseTextModelProps = {
37
60
  identity?: Identity | null;
38
61
  space?: Space;
39
62
  text?: TextObject;
40
63
  };
41
64
 
42
- // TODO(burdon): Don't support returning undefined (and remove checks from calling code).
43
- // TODO(burdon): Decouple space (make Editor less dependent on entire stack)?
65
+ // TODO(burdon): Remove YJS/Automerge deps (from UI component -- create abstraction; incl. all ECHO/Space deps).
44
66
  // TODO(wittjosiah): Factor out to common package? @dxos/react-client?
45
- export const useTextModel = ({ identity, space, text }: UseTextModelOptions): EditorModel | undefined => {
46
- const provider = useMemo(() => {
47
- if (isActualAutomergeObject(text)) {
48
- return undefined;
49
- }
50
-
51
- if (!space || !text?.doc) {
52
- return undefined;
53
- }
54
-
55
- return new SpaceAwarenessProvider({ space, doc: text.doc, channel: `yjs.awareness.${text.id}` });
56
- }, [identity, space, text, text?.doc]);
57
-
58
- const content = useMemo(() => {
59
- if (isActualAutomergeObject(text)) {
60
- const obj = text as any as AutomergeTextCompat;
61
- return getRawDoc(obj, [obj.field]);
62
- } else {
63
- return text?.content;
64
- }
65
- }, [text]);
66
-
67
- if (isActualAutomergeObject(text)) {
68
- const obj = text as any as AutomergeTextCompat;
69
- return {
70
- id: obj.id,
71
- content: content!,
72
- provider: undefined,
73
- peer: identity
74
- ? {
75
- id: identity.identityKey.toHex(),
76
- name: identity.profile?.displayName,
77
- }
78
- : undefined,
79
- };
80
- }
67
+ export const useTextModel = (props: UseTextModelProps): EditorModel | undefined => {
68
+ const { identity, space, text } = props;
69
+ const [model, setModel] = useState<EditorModel | undefined>(() => createModel(props));
70
+ useEffect(() => setModel(createModel(props)), [identity, space, text]);
71
+ return model;
72
+ };
81
73
 
82
- if (!text?.doc || !text?.content) {
74
+ const createModel = (props: UseTextModelProps) => {
75
+ const { text } = props;
76
+ if (isAutomergeObject(text)) {
77
+ return createAutomergeModel(props);
78
+ } else if (text?.doc) {
79
+ return createYjsModel(props);
80
+ } else {
83
81
  return undefined;
84
82
  }
85
-
86
- return {
87
- id: text.doc.guid,
88
- content: text.content,
89
- provider,
90
- peer: identity
91
- ? {
92
- id: identity.identityKey.toHex(),
93
- name: identity.profile?.displayName,
94
- }
95
- : undefined,
96
- };
97
83
  };
@@ -5,14 +5,62 @@
5
5
  import * as decoding from 'lib0/decoding';
6
6
  import * as encoding from 'lib0/encoding';
7
7
  import { Observable } from 'lib0/observable';
8
- import * as awarenessProtocol from 'y-protocols/awareness';
8
+ import * as YP from 'y-protocols/awareness';
9
+ import * as Y from 'yjs';
9
10
  import { type Doc } from 'yjs';
10
11
 
12
+ import { invariant } from '@dxos/invariant';
11
13
  import { log } from '@dxos/log';
12
14
  import { type Space } from '@dxos/react-client/echo';
13
15
  import { type GossipMessage } from '@dxos/react-client/mesh';
14
-
15
- type Awareness = awarenessProtocol.Awareness;
16
+ import type { YText } from '@dxos/text-model';
17
+ import { arrayToString, stringToArray } from '@dxos/util';
18
+
19
+ import { type EditorModel, modelState, type Range, type UseTextModelProps } from './useTextModel';
20
+ import { yjs } from '../extensions';
21
+
22
+ export const createYjsModel = ({ identity, space, text }: UseTextModelProps): EditorModel => {
23
+ invariant(text?.doc && text?.content);
24
+ const provider = space
25
+ ? new YAwarenessProvider({ space, doc: text.doc, channel: `yjs.awareness.${text.id}` })
26
+ : undefined;
27
+
28
+ const model: EditorModel = {
29
+ id: text.doc.guid,
30
+ content: text.content,
31
+ text: () => text.content!.toString(),
32
+ // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
33
+ // TODO(dmaretskyi): Refactor as cursor-converter.
34
+ getCursorFromRange: (range: Range) => {
35
+ const from = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content as YText, range.from));
36
+ const to = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content as YText, range.to, -1));
37
+ return [arrayToString(from), arrayToString(to)].join(':');
38
+ },
39
+ getRangeFromCursor: (cursor: string) => {
40
+ invariant(text.doc);
41
+ const parts = cursor.split(':');
42
+ const from = Y.createAbsolutePositionFromRelativePosition(
43
+ Y.decodeRelativePosition(stringToArray(parts[0])),
44
+ text.doc,
45
+ );
46
+ const to = Y.createAbsolutePositionFromRelativePosition(
47
+ Y.decodeRelativePosition(stringToArray(parts[1])),
48
+ text.doc,
49
+ );
50
+ return from && to ? { from: from.index, to: to.index } : undefined;
51
+ },
52
+ extension: [modelState.init(() => model), yjs(text.content as YText, provider?.awareness)],
53
+ awareness: provider?.awareness,
54
+ peer: identity
55
+ ? {
56
+ id: identity.identityKey.toHex(),
57
+ name: identity.profile?.displayName,
58
+ }
59
+ : undefined,
60
+ };
61
+
62
+ return model;
63
+ };
16
64
 
17
65
  // Based on https://github.com/yjs/y-webrtc/blob/88baab2/src/y-webrtc.js.
18
66
 
@@ -22,16 +70,26 @@ const messageQueryAwareness = 3;
22
70
  /**
23
71
  * Yjs awareness provider on top of a DXOS space.
24
72
  */
25
- export class SpaceAwarenessProvider extends Observable<any> {
73
+ export class YAwarenessProvider extends Observable<any> {
26
74
  private readonly _space: Space;
27
- private readonly _awareness: Awareness;
75
+ private readonly _awareness: YP.Awareness;
28
76
  private readonly _clientId: number;
29
77
  private readonly _channel: string;
30
78
 
31
- constructor({ space, doc, channel, awareness }: { space: Space; doc: Doc; channel: string; awareness?: Awareness }) {
79
+ constructor({
80
+ space,
81
+ doc,
82
+ channel,
83
+ awareness,
84
+ }: {
85
+ space: Space;
86
+ doc: Doc;
87
+ channel: string;
88
+ awareness?: YP.Awareness;
89
+ }) {
32
90
  super();
33
91
  this._space = space;
34
- this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
92
+ this._awareness = awareness ?? new YP.Awareness(doc);
35
93
  this._channel = channel;
36
94
  this._clientId = doc.clientID;
37
95
 
@@ -52,14 +110,11 @@ export class SpaceAwarenessProvider extends Observable<any> {
52
110
  // Post local awareness state.
53
111
  const encoderAwarenessState = encoding.createEncoder();
54
112
  encoding.writeVarUint(encoderAwarenessState, messageAwareness);
55
- encoding.writeVarUint8Array(
56
- encoderAwarenessState,
57
- awarenessProtocol.encodeAwarenessUpdate(this.awareness, [this._clientId]),
58
- );
113
+ encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [this._clientId]));
59
114
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
60
115
  }
61
116
 
62
- get awareness(): Awareness {
117
+ get awareness(): YP.Awareness {
63
118
  return this._awareness;
64
119
  }
65
120
 
@@ -68,10 +123,7 @@ export class SpaceAwarenessProvider extends Observable<any> {
68
123
  const changedClients = added.concat(updated).concat(removed);
69
124
  const encoderAwareness = encoding.createEncoder();
70
125
  encoding.writeVarUint(encoderAwareness, messageAwareness);
71
- encoding.writeVarUint8Array(
72
- encoderAwareness,
73
- awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients),
74
- );
126
+ encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
75
127
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
76
128
  }
77
129
 
@@ -92,7 +144,7 @@ export class SpaceAwarenessProvider extends Observable<any> {
92
144
  let sendReply = false;
93
145
  switch (messageType) {
94
146
  case messageAwareness: {
95
- awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
147
+ YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
96
148
  break;
97
149
  }
98
150
 
@@ -100,7 +152,7 @@ export class SpaceAwarenessProvider extends Observable<any> {
100
152
  encoding.writeVarUint(encoder, messageAwareness);
101
153
  encoding.writeVarUint8Array(
102
154
  encoder,
103
- awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())),
155
+ YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())),
104
156
  );
105
157
  sendReply = true;
106
158
  break;
@@ -121,6 +173,6 @@ export class SpaceAwarenessProvider extends Observable<any> {
121
173
  }
122
174
 
123
175
  private _handleBeforeUnload() {
124
- awarenessProtocol.removeAwarenessStates(this._awareness, [this._clientId], 'window unload');
176
+ YP.removeAwarenessStates(this._awareness, [this._clientId], 'window unload');
125
177
  }
126
178
  }
package/src/index.ts CHANGED
@@ -4,6 +4,9 @@
4
4
 
5
5
  export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
6
6
  export { Doc, YText, YXmlFragment } from '@dxos/text-model';
7
+ export { type Extension } from '@codemirror/state';
7
8
 
8
9
  export * from './components';
10
+ export * from './extensions';
9
11
  export * from './hooks';
12
+ export * from './themes';
@@ -2,5 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ export * from './cursors';
5
6
  export * from './markdown';
6
7
  export * from './tokens';
@@ -6,41 +6,50 @@ import { mx } from '@dxos/react-ui-theme';
6
6
 
7
7
  export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
8
8
 
9
- export const heading: Record<HeadingLevel, string> = {
10
- 1: 'mbs-4 mbe-2 text-4xl font-semibold text-inherit no-underline',
11
- 2: 'mbs-4 mbe-2 text-3xl font-bold text-inherit no-underline',
12
- 3: 'mbs-4 mbe-2 text-2xl font-bold text-inherit no-underline',
13
- 4: 'mbs-4 mbe-2 text-xl font-extrabold text-inherit no-underline',
14
- 5: 'mbs-4 mbe-2 text-lg font-extrabold text-inherit no-underline',
15
- 6: 'mbs-4 mbe-2 font-black text-inherit no-underline',
9
+ // TODO(burdon): Better way to align vertically than negative margin? Font-specific?
10
+ // https://tailwindcss.com/docs/font-weight
11
+ export const headings: Record<HeadingLevel, string[]> = {
12
+ 1: ['mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl', '-ml-[10px]'],
13
+ 2: ['mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl', '-ml-[8px]'],
14
+ 3: ['mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl', '-ml-[7px]'],
15
+ 4: ['mbs-4 mbe-2 font-medium text-inherit no-underline text-xl', '-ml-[6px]'],
16
+ 5: ['mbs-4 mbe-2 font-medium text-inherit no-underline text-lg', '-ml-[5px]'],
17
+ 6: ['mbs-4 mbe-2 font-medium text-inherit no-underline', '-ml-[4px]'],
16
18
  };
17
19
 
18
- export const blockquote = 'mlb-2 border-is-4 border-neutral-500/50 pis-5';
19
-
20
- // TODO(thure): Tailwind was not seeing `[&>li:before]:content-["•"]` as a utility class, but it would work if instead of `"•"` it was `"X"`… why?
21
- export const unorderedList =
22
- 'mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[attr(marker)] [&>li:before]:mlb-1 [&>li:before]:mie-2';
23
- export const orderedList =
24
- 'mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[counters(section,_".")_"._"] [counter-reset:section] [&>li:before]:mlb-1';
20
+ export const heading = (level: HeadingLevel, readonly?: boolean) => {
21
+ const [style, offset] = headings[level];
22
+ return mx(style, readonly && offset);
23
+ };
25
24
 
26
- export const listItem = 'contents before:[counter-increment:section]';
25
+ export const text = 'text-neutral-800 dark:text-neutral-200';
26
+ export const light = 'text-neutral-200 dark:text-neutral-800';
27
27
 
28
- export const codeBlock = 'mlb-2 font-mono bg-neutral-500/10 p-3 rounded';
29
-
30
- export const horizontalRule = 'mlb-4 border-neutral-500/50';
28
+ export const mark = mx('!font-normal !no-underline !text-inherit opacity-40', light);
31
29
 
32
30
  export const paragraph = 'mlb-1';
33
31
 
34
32
  export const bold = 'font-bold';
33
+ export const italic = 'italic';
34
+ export const strikethrough = 'line-through';
35
35
 
36
- export const code = 'font-mono bg-neutral-500/10 rounded pli-0.5 plb-0.5 -mlb-0.5';
36
+ export const code = 'font-mono !no-underline text-neutral-700 dark:text-neutral-300';
37
+ export const codeMark = 'font-mono text-primary-500';
38
+ export const codeBlock = 'mlb-2 font-mono bg-neutral-500/10 p-3 rounded';
37
39
 
38
- export const placeholder = 'font-mono';
40
+ export const inlineUrl = mx(code, 'px-1');
39
41
 
40
- export const codeWithoutMarks = mx(code, 'pli-1.5 mli-0.5');
42
+ // TODO(burdon): Replace with widget.
43
+ export const blockquote = mx('pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30', light);
41
44
 
42
- export const italic = 'italic';
45
+ // TODO(burdon): Replace with widget.
46
+ export const horizontalRule =
47
+ 'flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800';
43
48
 
44
- export const strikethrough = 'line-through';
49
+ // TODO(thure): Tailwind was not seeing `[&>li:before]:content-["•"]` as a utility class, but it would work if instead of `"•"` it was `"X"`… why?
50
+ export const unorderedList =
51
+ 'mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[attr(marker)] [&>li:before]:mlb-1 [&>li:before]:mie-2';
52
+ export const orderedList =
53
+ 'mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[counters(section,_".")_"._"] [counter-reset:section] [&>li:before]:mlb-1';
45
54
 
46
- export const mark = '!font-normal !no-underline !text-inherit opacity-40';
55
+ export const listItem = 'contents before:[counter-increment:section]';
@@ -7,7 +7,7 @@ import * as decoding from 'lib0/decoding';
7
7
  import * as encoding from 'lib0/encoding';
8
8
  import { Observable } from 'lib0/observable';
9
9
  import { useMemo } from 'react';
10
- import * as awarenessProtocol from 'y-protocols/awareness';
10
+ import * as YP from 'y-protocols/awareness';
11
11
  import * as syncProtocol from 'y-protocols/sync';
12
12
  import { Doc } from 'yjs';
13
13
 
@@ -15,9 +15,10 @@ import { Event } from '@dxos/async';
15
15
  import { log } from '@dxos/log';
16
16
  import { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
17
17
 
18
- import { cursorColor, type EditorModel } from '../hooks';
18
+ import { type EditorModel } from '../hooks';
19
+ import { cursorColor } from '../styles';
19
20
 
20
- type Awareness = awarenessProtocol.Awareness;
21
+ type Awareness = YP.Awareness;
21
22
 
22
23
  const messageSync = 0;
23
24
  const messageAwareness = 1;
@@ -35,7 +36,7 @@ export class AwarenessProvider extends Observable<any> {
35
36
  super();
36
37
  this._remoteUpdate = update;
37
38
  this._doc = doc;
38
- this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
39
+ this._awareness = awareness ?? new YP.Awareness(doc);
39
40
 
40
41
  this._doc.on('update', this._handleDocUpdate.bind(this));
41
42
  this._awareness.on('update', this._handleAwarenessUpdate.bind(this));
@@ -55,10 +56,7 @@ export class AwarenessProvider extends Observable<any> {
55
56
  // Post local awareness state.
56
57
  const encoderAwarenessState = encoding.createEncoder();
57
58
  encoding.writeVarUint(encoderAwarenessState, messageAwareness);
58
- encoding.writeVarUint8Array(
59
- encoderAwarenessState,
60
- awarenessProtocol.encodeAwarenessUpdate(this.awareness, [this._doc.clientID]),
61
- );
59
+ encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [this._doc.clientID]));
62
60
  void this._remoteUpdate.emit(encoding.toUint8Array(encoderAwarenessState));
63
61
  }
64
62
 
@@ -79,10 +77,7 @@ export class AwarenessProvider extends Observable<any> {
79
77
  const changedClients = added.concat(updated).concat(removed);
80
78
  const encoderAwareness = encoding.createEncoder();
81
79
  encoding.writeVarUint(encoderAwareness, messageAwareness);
82
- encoding.writeVarUint8Array(
83
- encoderAwareness,
84
- awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients),
85
- );
80
+ encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
86
81
  this._remoteUpdate.emit(encoding.toUint8Array(encoderAwareness));
87
82
  }
88
83
 
@@ -109,7 +104,7 @@ export class AwarenessProvider extends Observable<any> {
109
104
  }
110
105
 
111
106
  case messageAwareness: {
112
- awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
107
+ YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
113
108
  break;
114
109
  }
115
110
 
@@ -117,7 +112,7 @@ export class AwarenessProvider extends Observable<any> {
117
112
  encoding.writeVarUint(encoder, messageAwareness);
118
113
  encoding.writeVarUint8Array(
119
114
  encoder,
120
- awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())),
115
+ YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())),
121
116
  );
122
117
  sendReply = true;
123
118
  break;
@@ -138,7 +133,7 @@ export class AwarenessProvider extends Observable<any> {
138
133
  }
139
134
 
140
135
  private _handleBeforeUnload() {
141
- awarenessProtocol.removeAwarenessStates(this._awareness, [this._doc.clientID], 'window unload');
136
+ YP.removeAwarenessStates(this._awareness, [this._doc.clientID], 'window unload');
142
137
  }
143
138
  }
144
139
 
@@ -163,16 +158,17 @@ export class Replicator {
163
158
  colorLight: cursorColor.light,
164
159
  });
165
160
 
166
- // TODO(burdon): Create concrete class that implements SyncModel?
161
+ const field = 'content';
162
+ const content = this._kind === TextKind.PLAIN ? doc.getText(field) : doc.getXmlFragment(field);
167
163
  const model: EditorModel = {
168
164
  id: doc.guid,
169
- content: this._kind === TextKind.PLAIN ? doc.getText('content') : doc.getXmlFragment('content'),
170
- provider,
165
+ text: () => content.toString(),
166
+ content,
167
+ awareness: provider.awareness,
171
168
  peer: { id },
172
169
  };
173
170
 
174
171
  this._peers.push(model);
175
-
176
172
  return model;
177
173
  };
178
174
  }
@@ -184,6 +180,5 @@ export type UseYjsModelOptions = {
184
180
  };
185
181
 
186
182
  export const useYjsModel = ({ replicator, id, doc }: UseYjsModelOptions): EditorModel => {
187
- const peer = useMemo(() => replicator.createPeer(id, doc), [doc]);
188
- return peer;
183
+ return useMemo(() => replicator.createPeer(id, doc), [doc]);
189
184
  };