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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/dist/lib/browser/index.mjs +2065 -1565
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +17 -14
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +19 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -3
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -1
  11. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  14. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  15. package/dist/types/src/components/TextEditor/index.d.ts +0 -2
  16. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  17. package/dist/types/src/{hooks/yjs → components/TextEditor}/yjs.stories.d.ts +1 -1
  18. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
  19. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
  20. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  21. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  22. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  23. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  24. package/dist/types/src/extensions/automerge/defs.d.ts +24 -0
  25. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  26. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  27. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  28. package/dist/types/src/extensions/automerge/semaphore.d.ts +14 -0
  29. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  30. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  31. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  32. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  33. package/dist/types/src/extensions/awareness.d.ts +48 -0
  34. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  35. package/dist/types/src/extensions/basic.d.ts +8 -0
  36. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  37. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  38. package/dist/types/src/extensions/code.d.ts.map +1 -0
  39. package/dist/types/src/{components/TextEditor/extensions → extensions}/comments.d.ts +32 -17
  40. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  41. package/dist/types/src/extensions/cursor.d.ts +25 -0
  42. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  43. package/dist/types/src/extensions/hr.d.ts.map +1 -0
  44. package/dist/types/src/extensions/image.d.ts.map +1 -0
  45. package/dist/types/src/{components/TextEditor/extensions → extensions}/index.d.ts +5 -2
  46. package/dist/types/src/extensions/index.d.ts.map +1 -0
  47. package/dist/types/src/{components/TextEditor/extensions → extensions}/link.d.ts +1 -0
  48. package/dist/types/src/extensions/link.d.ts.map +1 -0
  49. package/dist/types/src/extensions/listener.d.ts +10 -0
  50. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  51. package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/bundle.d.ts +2 -5
  52. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  53. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  54. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  55. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  56. package/dist/types/src/extensions/outliner.d.ts +4 -0
  57. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  58. package/dist/types/src/extensions/table.d.ts.map +1 -0
  59. package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
  60. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  61. package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
  62. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
  63. package/dist/types/src/extensions/yjs/index.d.ts +2 -0
  64. package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
  65. package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
  66. package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
  67. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
  68. package/dist/types/src/hooks/automerge.d.ts +4 -0
  69. package/dist/types/src/hooks/automerge.d.ts.map +1 -0
  70. package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
  71. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  72. package/dist/types/src/hooks/defs.d.ts +28 -0
  73. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  74. package/dist/types/src/hooks/index.d.ts +4 -1
  75. package/dist/types/src/hooks/index.d.ts.map +1 -1
  76. package/dist/types/src/hooks/useTextModel.d.ts +2 -31
  77. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  78. package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +9 -8
  79. package/dist/types/src/hooks/yjs.d.ts.map +1 -0
  80. package/dist/types/src/index.d.ts +5 -0
  81. package/dist/types/src/index.d.ts.map +1 -1
  82. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  83. package/dist/types/src/styles/index.d.ts +1 -0
  84. package/dist/types/src/styles/index.d.ts.map +1 -1
  85. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  86. package/dist/types/src/styles/tokens.d.ts +1 -0
  87. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  88. package/dist/types/src/testing/replicator.d.ts +2 -2
  89. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  90. package/dist/types/src/{components/TextEditor/themes → themes}/default.d.ts +1 -1
  91. package/dist/types/src/themes/default.d.ts.map +1 -0
  92. package/dist/types/src/themes/index.d.ts.map +1 -0
  93. package/dist/types/src/util.d.ts +7 -0
  94. package/dist/types/src/util.d.ts.map +1 -0
  95. package/package.json +25 -23
  96. package/src/components/TextEditor/MarkdownEditor.stories.tsx +77 -81
  97. package/src/components/TextEditor/TextEditor.stories.tsx +13 -34
  98. package/src/components/TextEditor/TextEditor.tsx +115 -94
  99. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +66 -6
  100. package/src/components/TextEditor/codemirror.stories.ts +3 -2
  101. package/src/components/TextEditor/comments.stories.tsx +357 -0
  102. package/src/components/TextEditor/index.ts +0 -3
  103. package/src/{hooks/yjs → components/TextEditor}/yjs.stories.tsx +1 -1
  104. package/src/{components/TextEditor/extensions → extensions}/autocomplete.ts +23 -19
  105. package/src/extensions/automerge/automerge.ts +70 -0
  106. package/src/extensions/automerge/cursor.ts +52 -0
  107. package/src/extensions/automerge/defs.ts +41 -0
  108. package/src/extensions/automerge/index.ts +6 -0
  109. package/src/{hooks → extensions}/automerge/semaphore.ts +37 -28
  110. package/src/{hooks → extensions}/automerge/update-automerge.ts +5 -6
  111. package/src/{hooks → extensions}/automerge/update-codemirror.ts +13 -5
  112. package/src/extensions/awareness.ts +327 -0
  113. package/src/extensions/basic.ts +37 -0
  114. package/src/{components/TextEditor/extensions → extensions}/code.ts +28 -23
  115. package/src/extensions/comments.ts +428 -0
  116. package/src/extensions/cursor.ts +50 -0
  117. package/src/extensions/hr.ts +72 -0
  118. package/src/extensions/image.ts +87 -0
  119. package/src/{components/TextEditor/extensions → extensions}/index.ts +5 -2
  120. package/src/{components/TextEditor/extensions → extensions}/link.ts +116 -62
  121. package/src/extensions/listener.ts +35 -0
  122. package/src/{components/TextEditor/extensions → extensions}/markdown/bundle.ts +18 -24
  123. package/src/{components/TextEditor/extensions → extensions}/markdown/highlight.ts +4 -13
  124. package/src/{components/TextEditor/extensions → extensions}/mention.ts +6 -1
  125. package/src/extensions/outliner.ts +12 -0
  126. package/src/{components/TextEditor/extensions → extensions}/table.ts +10 -4
  127. package/src/extensions/tasklist.ts +111 -0
  128. package/src/{components/TextEditor/extensions → extensions}/typewriter.ts +1 -1
  129. package/src/extensions/yjs/cursor.ts +22 -0
  130. package/src/extensions/yjs/index.ts +5 -0
  131. package/src/{hooks → extensions}/yjs/yjs.test.ts +2 -26
  132. package/src/extensions/yjs/yjs.ts +16 -0
  133. package/src/hooks/automerge.ts +53 -0
  134. package/src/hooks/awareness-provider.ts +120 -0
  135. package/src/hooks/defs.ts +47 -0
  136. package/src/hooks/index.ts +5 -1
  137. package/src/hooks/useTextModel.ts +9 -128
  138. package/src/hooks/{yjs/space-provider.ts → yjs.ts} +50 -19
  139. package/src/index.ts +5 -0
  140. package/src/styles/index.ts +1 -0
  141. package/src/styles/markdown.ts +0 -2
  142. package/src/styles/tokens.ts +3 -0
  143. package/src/testing/replicator.ts +10 -15
  144. package/src/{components/TextEditor/themes → themes}/default.ts +52 -79
  145. package/src/util.ts +41 -0
  146. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
  147. package/dist/types/src/components/TextEditor/extensions/basic.d.ts +0 -9
  148. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
  149. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
  150. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +0 -1
  151. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +0 -1
  152. package/dist/types/src/components/TextEditor/extensions/hr.d.ts.map +0 -1
  153. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
  154. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  155. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
  156. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +0 -9
  157. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
  158. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  159. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
  160. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  161. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
  162. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts +0 -3
  163. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts.map +0 -1
  164. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
  165. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
  166. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
  167. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
  168. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts.map +0 -1
  169. package/dist/types/src/components/TextEditor/extensions/util.d.ts +0 -5
  170. package/dist/types/src/components/TextEditor/extensions/util.d.ts.map +0 -1
  171. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
  172. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
  173. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  174. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  175. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  176. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  177. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  178. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  179. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  180. package/dist/types/src/hooks/automerge/semaphore.d.ts +0 -19
  181. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
  182. package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
  183. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
  184. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
  185. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  186. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  187. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  188. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  189. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  190. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
  191. package/src/components/TextEditor/extensions/basic.ts +0 -32
  192. package/src/components/TextEditor/extensions/comments.ts +0 -388
  193. package/src/components/TextEditor/extensions/hr.ts +0 -74
  194. package/src/components/TextEditor/extensions/image.ts +0 -69
  195. package/src/components/TextEditor/extensions/listener.ts +0 -23
  196. package/src/components/TextEditor/extensions/mermaid.ts +0 -11
  197. package/src/components/TextEditor/extensions/tasklist.ts +0 -121
  198. package/src/components/TextEditor/extensions/tooltip.ts +0 -61
  199. package/src/components/TextEditor/extensions/util.ts +0 -18
  200. package/src/hooks/automerge/handle.ts +0 -14
  201. package/src/hooks/automerge/index.ts +0 -6
  202. package/src/hooks/automerge/plugin.ts +0 -134
  203. package/src/hooks/yjs/index.ts +0 -6
  204. /package/dist/types/src/{components/TextEditor/extensions → extensions}/autocomplete.d.ts +0 -0
  205. /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
  206. /package/dist/types/src/{components/TextEditor/extensions → extensions}/blast.d.ts +0 -0
  207. /package/dist/types/src/{components/TextEditor/extensions → extensions}/code.d.ts +0 -0
  208. /package/dist/types/src/{components/TextEditor/extensions → extensions}/hr.d.ts +0 -0
  209. /package/dist/types/src/{components/TextEditor/extensions → extensions}/image.d.ts +0 -0
  210. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/highlight.d.ts +0 -0
  211. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/index.d.ts +0 -0
  212. /package/dist/types/src/{components/TextEditor/extensions → extensions}/mention.d.ts +0 -0
  213. /package/dist/types/src/{components/TextEditor/extensions → extensions}/table.d.ts +0 -0
  214. /package/dist/types/src/{components/TextEditor/extensions → extensions}/tasklist.d.ts +0 -0
  215. /package/dist/types/src/{components/TextEditor/extensions → extensions}/typewriter.d.ts +0 -0
  216. /package/dist/types/src/{hooks → extensions}/yjs/yjs.test.d.ts +0 -0
  217. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  218. /package/dist/types/src/{components/TextEditor/themes → themes}/index.d.ts +0 -0
  219. /package/src/{components/TextEditor/extensions → extensions}/blast.ts +0 -0
  220. /package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +0 -0
  221. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  222. /package/src/{components/TextEditor/themes → themes}/index.ts +0 -0
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { yCollab } from 'y-codemirror.next';
6
+ import type * as YP from 'y-protocols/awareness';
7
+
8
+ import type { YText } from '@dxos/text-model';
9
+
10
+ import { cursorConverter } from './cursor';
11
+ import { Cursor } from '../cursor';
12
+
13
+ export const yjs = (content: YText, awareness?: YP.Awareness) => [
14
+ Cursor.converter.of(cursorConverter(content)),
15
+ yCollab(content, awareness),
16
+ ];
@@ -0,0 +1,53 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import get from 'lodash.get';
6
+
7
+ import { type AutomergeTextCompat, getRawDoc } from '@dxos/echo-schema';
8
+ import { isNotNullOrUndefined } from '@dxos/util';
9
+
10
+ import { SpaceAwarenessProvider } from './awareness-provider';
11
+ import { type EditorModel, modelState } from './defs';
12
+ import { type UseTextModelProps } from './useTextModel';
13
+ import { automerge, awareness, AwarenessProvider } from '../extensions';
14
+ import { cursorColor } from '../styles';
15
+
16
+ export const createAutomergeModel = ({ space, identity, text }: UseTextModelProps): EditorModel => {
17
+ const obj = text as any as AutomergeTextCompat;
18
+ const doc = getRawDoc(obj, [obj.field]);
19
+
20
+ const awarenessProvider =
21
+ space &&
22
+ new SpaceAwarenessProvider({
23
+ space,
24
+ channel: `automerge.awareness.${obj.id}`,
25
+ info: {
26
+ displayName: identity?.profile?.displayName ?? '',
27
+ color: cursorColor.color,
28
+ lightColor: cursorColor.light,
29
+ },
30
+ peerId: identity?.identityKey.toHex() ?? 'Anonymous',
31
+ });
32
+
33
+ const model: EditorModel = {
34
+ id: obj.id,
35
+ content: doc,
36
+ text: () => get(doc.handle.docSync(), doc.path),
37
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
38
+ extension: [
39
+ modelState.init(() => model),
40
+ automerge({ handle: doc.handle, path: doc.path }),
41
+ awarenessProvider && AwarenessProvider.of(awarenessProvider),
42
+ awareness(),
43
+ ].filter(isNotNullOrUndefined),
44
+ peer: identity
45
+ ? {
46
+ id: identity.identityKey.toHex(),
47
+ name: identity.profile?.displayName,
48
+ }
49
+ : undefined,
50
+ };
51
+
52
+ return model;
53
+ };
@@ -0,0 +1,120 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { DeferredTask, Event, sleep } from '@dxos/async';
6
+ import { Context } from '@dxos/context';
7
+ import { invariant } from '@dxos/invariant';
8
+ import { log } from '@dxos/log';
9
+ import { type Space } from '@dxos/react-client/echo';
10
+ import { type GossipMessage } from '@dxos/react-client/mesh';
11
+
12
+ import { type AwarenessInfo, type AwarenessPosition, type AwarenessProvider, type AwarenessState } from '../extensions';
13
+
14
+ type ProtocolMessage =
15
+ | {
16
+ kind: 'query';
17
+ }
18
+ | {
19
+ kind: 'post';
20
+ state: AwarenessState;
21
+ };
22
+
23
+ const DEBOUNCE_INTERVAL = 100; // ms
24
+
25
+ export type AwarenessProviderParams = {
26
+ space: Space;
27
+ channel: string;
28
+ peerId: string;
29
+ info: AwarenessInfo;
30
+ };
31
+
32
+ export class SpaceAwarenessProvider implements AwarenessProvider {
33
+ public readonly remoteStateChange = new Event<void>();
34
+
35
+ private readonly _remoteStates = new Map<string, AwarenessState>();
36
+
37
+ private readonly _space: Space;
38
+ private readonly _channel: string;
39
+ private readonly _peerId: string;
40
+ private readonly _info: AwarenessInfo;
41
+
42
+ private _localState?: AwarenessState;
43
+ private _postTask?: DeferredTask;
44
+ private _ctx?: Context;
45
+
46
+ constructor(params: AwarenessProviderParams) {
47
+ this._space = params.space;
48
+ this._channel = params.channel;
49
+ this._peerId = params.peerId;
50
+ this._info = params.info;
51
+ }
52
+
53
+ open() {
54
+ this._ctx = new Context();
55
+ this._postTask = new DeferredTask(this._ctx, async () => {
56
+ if (this._localState) {
57
+ await this._space.postMessage(this._channel, {
58
+ kind: 'post',
59
+ state: this._localState,
60
+ } satisfies ProtocolMessage);
61
+
62
+ await sleep(DEBOUNCE_INTERVAL);
63
+ }
64
+ });
65
+
66
+ const unsubscribe = this._space.listen(this._channel, (message: GossipMessage) => {
67
+ switch (message.payload.kind) {
68
+ case 'query': {
69
+ this._handleQueryMessage();
70
+ break;
71
+ }
72
+ case 'post': {
73
+ this._handlePostMessage(message.payload);
74
+ break;
75
+ }
76
+ }
77
+ });
78
+ this._ctx.onDispose(unsubscribe);
79
+
80
+ void this._space
81
+ .postMessage(this._channel, {
82
+ kind: 'query',
83
+ } satisfies ProtocolMessage)
84
+ .catch((err) => {
85
+ log.debug('failed to query awareness', { err });
86
+ });
87
+ }
88
+
89
+ close() {
90
+ void this._ctx?.dispose();
91
+ this._ctx = undefined;
92
+ this._postTask = undefined;
93
+ }
94
+
95
+ update(position: AwarenessPosition | undefined): void {
96
+ invariant(this._postTask);
97
+ this._localState = {
98
+ peerId: this._peerId,
99
+ position,
100
+ info: this._info,
101
+ };
102
+
103
+ this._postTask.schedule();
104
+ }
105
+
106
+ getRemoteStates(): AwarenessState[] {
107
+ return Array.from(this._remoteStates.values());
108
+ }
109
+
110
+ private _handleQueryMessage() {
111
+ invariant(this._postTask);
112
+ this._postTask.schedule();
113
+ }
114
+
115
+ private _handlePostMessage(message: ProtocolMessage) {
116
+ invariant(message.kind === 'post');
117
+ this._remoteStates.set(message.state.peerId, message.state);
118
+ this.remoteStateChange.emit();
119
+ }
120
+ }
@@ -0,0 +1,47 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Extension, StateField } from '@codemirror/state';
6
+ import type * as YP from 'y-protocols/awareness';
7
+
8
+ import { type DocAccessor } from '@dxos/echo-schema';
9
+ import type { YText, YXmlFragment } from '@dxos/text-model';
10
+
11
+ // Runtime data structure.
12
+ export type Range = {
13
+ from: number;
14
+ to: number;
15
+ };
16
+
17
+ // Persistent data structure.
18
+ export type Comment = {
19
+ id: string;
20
+ cursor?: string;
21
+ };
22
+
23
+ export type EditorModel = {
24
+ id: string;
25
+ text: () => string;
26
+ extension?: Extension;
27
+
28
+ // TODO(burdon): Remove.
29
+ content: string | YText | YXmlFragment | DocAccessor;
30
+
31
+ // TODO(burdon): Move into extension.
32
+ awareness?: YP.Awareness;
33
+
34
+ // TODO(burdon): Remove.
35
+ peer?: {
36
+ id: string;
37
+ name?: string;
38
+ };
39
+ };
40
+
41
+ /**
42
+ * State field makes the model available to other extensions.
43
+ */
44
+ export const modelState = StateField.define<EditorModel | undefined>({
45
+ create: () => undefined,
46
+ update: (model) => model,
47
+ });
@@ -2,5 +2,9 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './useTextModel';
5
+ export * from './automerge';
6
+ export * from './awareness-provider';
7
+ export * from './defs';
6
8
  export * from './yjs';
9
+
10
+ export * from './useTextModel';
@@ -2,67 +2,14 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type Extension, StateField } from '@codemirror/state';
6
- import get from 'lodash.get';
7
5
  import { useEffect, useState } from 'react';
8
- import { yCollab } from 'y-codemirror.next';
9
- import type * as awarenessProtocol from 'y-protocols/awareness';
10
- import * as Y from 'yjs';
11
6
 
12
- import { invariant } from '@dxos/invariant';
13
- import {
14
- type DocAccessor,
15
- type Space,
16
- type TextObject,
17
- type AutomergeTextCompat,
18
- getRawDoc,
19
- isActualAutomergeObject,
20
- } from '@dxos/react-client/echo';
7
+ import { isAutomergeObject, type Space, type TextObject } from '@dxos/react-client/echo';
21
8
  import { type Identity } from '@dxos/react-client/halo';
22
- import type { YText, YXmlFragment } from '@dxos/text-model';
23
- import { arrayToString, stringToArray } from '@dxos/util';
24
9
 
25
- import { automergePlugin } from './automerge';
26
- import { SpaceAwarenessProvider } from './yjs';
27
-
28
- // TODO(burdon): Move.
29
- type Awareness = awarenessProtocol.Awareness;
30
-
31
- /**
32
- * State field makes the model available to other extensions.
33
- */
34
- // TODO(burdon): Use facet?
35
- export const modelState = StateField.define<EditorModel | undefined>({
36
- create: () => undefined,
37
- update: (model) => model,
38
- });
39
-
40
- export type Range = {
41
- from: number;
42
- to: number;
43
- };
44
-
45
- export type CommentRange = {
46
- id: string;
47
- // TODO(burdon): Split into begin/end?
48
- cursor: string;
49
- };
50
-
51
- // TODO(wittjosiah): Factor out to common package? @dxos/react-client?
52
- export type EditorModel = {
53
- id: string;
54
- // TODO(burdon): Remove.
55
- content: string | YText | YXmlFragment | DocAccessor;
56
- text: () => string;
57
- getCursorFromRange?: (value: Range) => string;
58
- getRangeFromCursor?: (cursor: string) => Range;
59
- extension?: Extension;
60
- awareness?: Awareness;
61
- peer?: {
62
- id: string;
63
- name?: string;
64
- };
65
- };
10
+ import { createAutomergeModel } from './automerge';
11
+ import { type EditorModel } from './defs';
12
+ import { createYjsModel } from './yjs';
66
13
 
67
14
  // TODO(burdon): Remove space/identity dependency. Define interface for the framework re content and presence.
68
15
  export type UseTextModelProps = {
@@ -71,86 +18,20 @@ export type UseTextModelProps = {
71
18
  text?: TextObject;
72
19
  };
73
20
 
74
- // TODO(burdon): Remove YJS/Automerge deps (from UI component -- create abstraction; incl. all ECHO/Space deps).
75
- // TODO(wittjosiah): Factor out to common package? @dxos/react-client?
76
21
  export const useTextModel = (props: UseTextModelProps): EditorModel | undefined => {
77
22
  const { identity, space, text } = props;
78
- const [model, setModel] = useState<EditorModel | undefined>(() => createModel(props));
23
+ const [model, setModel] = useState<EditorModel | undefined>();
79
24
  useEffect(() => setModel(createModel(props)), [identity, space, text]);
80
25
  return model;
81
26
  };
82
27
 
83
28
  const createModel = (props: UseTextModelProps) => {
84
29
  const { text } = props;
85
- if (isActualAutomergeObject(text)) {
30
+ if (isAutomergeObject(text)) {
86
31
  return createAutomergeModel(props);
87
- } else {
88
- if (!text?.doc) {
89
- return undefined;
90
- }
91
-
32
+ } else if (text?.doc) {
92
33
  return createYjsModel(props);
34
+ } else {
35
+ return undefined;
93
36
  }
94
37
  };
95
-
96
- const createYjsModel = ({ identity, space, text }: UseTextModelProps): EditorModel => {
97
- invariant(text?.doc && text?.content);
98
- const provider = space
99
- ? new SpaceAwarenessProvider({ space, doc: text.doc, channel: `yjs.awareness.${text.id}` })
100
- : undefined;
101
-
102
- const model: EditorModel = {
103
- id: text.doc.guid,
104
- content: text.content,
105
- text: () => text.content!.toString(),
106
- // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
107
- getCursorFromRange: (range: Range) => {
108
- const from = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content as YText, range.from));
109
- const to = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content as YText, range.to, -1));
110
- return [arrayToString(from), arrayToString(to)].join(':');
111
- },
112
- getRangeFromCursor: (cursor: string) => {
113
- const parts = cursor.split(':');
114
- const from = Y.createAbsolutePositionFromRelativePosition(
115
- Y.decodeRelativePosition(stringToArray(parts[0])),
116
- text.doc!,
117
- );
118
- const to = Y.createAbsolutePositionFromRelativePosition(
119
- Y.decodeRelativePosition(stringToArray(parts[1])),
120
- text.doc!,
121
- );
122
- return { from: from!.index, to: to!.index };
123
- },
124
- extension: [yCollab(text.content as YText, provider?.awareness), modelState.init(() => model)],
125
- awareness: provider?.awareness,
126
- peer: identity
127
- ? {
128
- id: identity.identityKey.toHex(),
129
- name: identity.profile?.displayName,
130
- }
131
- : undefined,
132
- };
133
-
134
- return model;
135
- };
136
-
137
- const createAutomergeModel = ({ identity, text }: UseTextModelProps): EditorModel => {
138
- const obj = text as any as AutomergeTextCompat;
139
- const doc = getRawDoc(obj, [obj.field]);
140
-
141
- const model: EditorModel = {
142
- id: obj.id,
143
- content: doc,
144
- text: () => get(doc.handle.docSync(), doc.path),
145
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
146
- extension: [automergePlugin(doc.handle, doc.path), modelState.init(() => model)],
147
- peer: identity
148
- ? {
149
- id: identity.identityKey.toHex(),
150
- name: identity.profile?.displayName,
151
- }
152
- : undefined,
153
- };
154
-
155
- return model;
156
- };
@@ -5,14 +5,41 @@
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
9
  import { type Doc } from 'yjs';
10
10
 
11
+ import { invariant } from '@dxos/invariant';
11
12
  import { log } from '@dxos/log';
12
13
  import { type Space } from '@dxos/react-client/echo';
13
14
  import { type GossipMessage } from '@dxos/react-client/mesh';
14
-
15
- type Awareness = awarenessProtocol.Awareness;
15
+ import type { YText } from '@dxos/text-model';
16
+
17
+ import { type EditorModel, modelState } from './defs';
18
+ import { type UseTextModelProps } from './useTextModel';
19
+ import { yjs } from '../extensions';
20
+
21
+ export const createYjsModel = ({ identity, space, text }: UseTextModelProps): EditorModel => {
22
+ invariant(text?.doc && text?.content);
23
+ const provider = space
24
+ ? new YAwarenessProvider({ space, doc: text.doc, channel: `yjs.awareness.${text.id}` })
25
+ : undefined;
26
+
27
+ const model: EditorModel = {
28
+ id: text.doc.guid,
29
+ content: text.content,
30
+ text: () => text.content!.toString(),
31
+ extension: [modelState.init(() => model), yjs(text.content as YText, provider?.awareness)],
32
+ awareness: provider?.awareness,
33
+ peer: identity
34
+ ? {
35
+ id: identity.identityKey.toHex(),
36
+ name: identity.profile?.displayName,
37
+ }
38
+ : undefined,
39
+ };
40
+
41
+ return model;
42
+ };
16
43
 
17
44
  // Based on https://github.com/yjs/y-webrtc/blob/88baab2/src/y-webrtc.js.
18
45
 
@@ -22,16 +49,26 @@ const messageQueryAwareness = 3;
22
49
  /**
23
50
  * Yjs awareness provider on top of a DXOS space.
24
51
  */
25
- export class SpaceAwarenessProvider extends Observable<any> {
52
+ export class YAwarenessProvider extends Observable<any> {
26
53
  private readonly _space: Space;
27
- private readonly _awareness: Awareness;
54
+ private readonly _awareness: YP.Awareness;
28
55
  private readonly _clientId: number;
29
56
  private readonly _channel: string;
30
57
 
31
- constructor({ space, doc, channel, awareness }: { space: Space; doc: Doc; channel: string; awareness?: Awareness }) {
58
+ constructor({
59
+ space,
60
+ doc,
61
+ channel,
62
+ awareness,
63
+ }: {
64
+ space: Space;
65
+ doc: Doc;
66
+ channel: string;
67
+ awareness?: YP.Awareness;
68
+ }) {
32
69
  super();
33
70
  this._space = space;
34
- this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
71
+ this._awareness = awareness ?? new YP.Awareness(doc);
35
72
  this._channel = channel;
36
73
  this._clientId = doc.clientID;
37
74
 
@@ -52,14 +89,11 @@ export class SpaceAwarenessProvider extends Observable<any> {
52
89
  // Post local awareness state.
53
90
  const encoderAwarenessState = encoding.createEncoder();
54
91
  encoding.writeVarUint(encoderAwarenessState, messageAwareness);
55
- encoding.writeVarUint8Array(
56
- encoderAwarenessState,
57
- awarenessProtocol.encodeAwarenessUpdate(this.awareness, [this._clientId]),
58
- );
92
+ encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [this._clientId]));
59
93
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
60
94
  }
61
95
 
62
- get awareness(): Awareness {
96
+ get awareness(): YP.Awareness {
63
97
  return this._awareness;
64
98
  }
65
99
 
@@ -68,10 +102,7 @@ export class SpaceAwarenessProvider extends Observable<any> {
68
102
  const changedClients = added.concat(updated).concat(removed);
69
103
  const encoderAwareness = encoding.createEncoder();
70
104
  encoding.writeVarUint(encoderAwareness, messageAwareness);
71
- encoding.writeVarUint8Array(
72
- encoderAwareness,
73
- awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients),
74
- );
105
+ encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
75
106
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
76
107
  }
77
108
 
@@ -92,7 +123,7 @@ export class SpaceAwarenessProvider extends Observable<any> {
92
123
  let sendReply = false;
93
124
  switch (messageType) {
94
125
  case messageAwareness: {
95
- awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
126
+ YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
96
127
  break;
97
128
  }
98
129
 
@@ -100,7 +131,7 @@ export class SpaceAwarenessProvider extends Observable<any> {
100
131
  encoding.writeVarUint(encoder, messageAwareness);
101
132
  encoding.writeVarUint8Array(
102
133
  encoder,
103
- awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())),
134
+ YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())),
104
135
  );
105
136
  sendReply = true;
106
137
  break;
@@ -121,6 +152,6 @@ export class SpaceAwarenessProvider extends Observable<any> {
121
152
  }
122
153
 
123
154
  private _handleBeforeUnload() {
124
- awarenessProtocol.removeAwarenessStates(this._awareness, [this._clientId], 'window unload');
155
+ YP.removeAwarenessStates(this._awareness, [this._clientId], 'window unload');
125
156
  }
126
157
  }
package/src/index.ts CHANGED
@@ -5,6 +5,11 @@
5
5
  export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
6
6
  export { Doc, YText, YXmlFragment } from '@dxos/text-model';
7
7
  export { type Extension } from '@codemirror/state';
8
+ export { type EditorView } from '@codemirror/view';
8
9
 
9
10
  export * from './components';
11
+ export * from './extensions';
10
12
  export * from './hooks';
13
+ export { getToken } from './styles';
14
+ export * from './themes';
15
+ export * from './util';
@@ -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';
@@ -39,10 +39,8 @@ export const codeBlock = 'mlb-2 font-mono bg-neutral-500/10 p-3 rounded';
39
39
 
40
40
  export const inlineUrl = mx(code, 'px-1');
41
41
 
42
- // TODO(burdon): Replace with widget.
43
42
  export const blockquote = mx('pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30', light);
44
43
 
45
- // TODO(burdon): Replace with widget.
46
44
  export const horizontalRule =
47
45
  'flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800';
48
46
 
@@ -2,6 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import get from 'lodash.get';
5
6
  import type { StyleSpec } from 'style-mod';
6
7
 
7
8
  import { tailwindConfig, type TailwindConfig } from '@dxos/react-ui-theme';
@@ -12,3 +13,5 @@ export type ThemeStyles = {
12
13
 
13
14
  // TODO(thure): Why export the whole theme? Can this be done differently?
14
15
  export const tokens: TailwindConfig['theme'] = tailwindConfig({}).theme;
16
+
17
+ export const getToken = (path: string, defaultValue: any = undefined) => get(tokens, path, defaultValue);
@@ -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