@dxos/react-ui-editor 0.3.11-main.870164f → 0.3.11-main.8be6c8a

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 (142) hide show
  1. package/dist/lib/browser/index.mjs +1609 -1139
  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 +9 -5
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +10 -10
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +2 -2
  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/{hooks/yjs → components/TextEditor}/yjs.stories.d.ts +1 -1
  14. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
  15. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  16. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  17. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  18. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  19. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  20. package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
  21. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  22. package/dist/types/src/extensions/automerge/index.d.ts +4 -0
  23. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  24. package/dist/types/src/{hooks → extensions}/automerge/semaphore.d.ts +4 -4
  25. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  26. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  27. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  28. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  29. package/dist/types/src/extensions/awareness.d.ts +46 -0
  30. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  31. package/dist/types/src/extensions/code.d.ts.map +1 -1
  32. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  33. package/dist/types/src/extensions/hr.d.ts.map +1 -1
  34. package/dist/types/src/extensions/image.d.ts.map +1 -1
  35. package/dist/types/src/extensions/index.d.ts +3 -1
  36. package/dist/types/src/extensions/index.d.ts.map +1 -1
  37. package/dist/types/src/extensions/link.d.ts.map +1 -1
  38. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  39. package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
  40. package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
  41. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
  42. package/dist/types/src/extensions/yjs/index.d.ts +2 -0
  43. package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
  44. package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
  45. package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
  46. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
  47. package/dist/types/src/hooks/automerge.d.ts +3 -0
  48. package/dist/types/src/hooks/automerge.d.ts.map +1 -0
  49. package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
  50. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  51. package/dist/types/src/hooks/index.d.ts +2 -0
  52. package/dist/types/src/hooks/index.d.ts.map +1 -1
  53. package/dist/types/src/hooks/useTextModel.d.ts +8 -7
  54. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  55. package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
  56. package/dist/types/src/hooks/yjs.d.ts.map +1 -0
  57. package/dist/types/src/index.d.ts +3 -0
  58. package/dist/types/src/index.d.ts.map +1 -1
  59. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  60. package/dist/types/src/styles/index.d.ts +1 -0
  61. package/dist/types/src/styles/index.d.ts.map +1 -1
  62. package/dist/types/src/styles/tokens.d.ts +1 -0
  63. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  64. package/dist/types/src/testing/replicator.d.ts +2 -2
  65. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  66. package/dist/types/src/themes/default.d.ts.map +1 -1
  67. package/dist/types/src/util/cursor.d.ts +17 -0
  68. package/dist/types/src/util/cursor.d.ts.map +1 -0
  69. package/dist/types/src/util/index.d.ts +3 -0
  70. package/dist/types/src/util/index.d.ts.map +1 -0
  71. package/dist/types/src/util/util.d.ts.map +1 -0
  72. package/package.json +22 -21
  73. package/src/components/TextEditor/MarkdownEditor.stories.tsx +13 -11
  74. package/src/components/TextEditor/TextEditor.stories.tsx +3 -4
  75. package/src/components/TextEditor/TextEditor.tsx +60 -46
  76. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +66 -6
  77. package/src/components/TextEditor/codemirror.stories.ts +3 -2
  78. package/src/{hooks/yjs → components/TextEditor}/yjs.stories.tsx +1 -1
  79. package/src/extensions/autocomplete.ts +18 -17
  80. package/src/{hooks/automerge/plugin.ts → extensions/automerge/automerge.ts} +20 -58
  81. package/src/extensions/automerge/cursor.ts +51 -0
  82. package/src/extensions/automerge/defs.ts +62 -0
  83. package/src/extensions/automerge/index.ts +7 -0
  84. package/src/{hooks → extensions}/automerge/semaphore.ts +14 -12
  85. package/src/{hooks → extensions}/automerge/update-automerge.ts +5 -6
  86. package/src/{hooks → extensions}/automerge/update-codemirror.ts +13 -5
  87. package/src/extensions/awareness.ts +316 -0
  88. package/src/extensions/code.ts +4 -5
  89. package/src/extensions/comments.ts +30 -15
  90. package/src/extensions/hr.ts +2 -3
  91. package/src/extensions/image.ts +38 -20
  92. package/src/extensions/index.ts +3 -1
  93. package/src/extensions/link.ts +82 -84
  94. package/src/extensions/markdown/highlight.ts +3 -4
  95. package/src/extensions/table.ts +9 -3
  96. package/src/extensions/tasklist.ts +1 -3
  97. package/src/extensions/yjs/cursor.ts +21 -0
  98. package/src/extensions/yjs/index.ts +5 -0
  99. package/src/{hooks → extensions}/yjs/yjs.test.ts +1 -25
  100. package/src/extensions/yjs/yjs.ts +16 -0
  101. package/src/hooks/automerge.ts +52 -0
  102. package/src/hooks/awareness-provider.ts +120 -0
  103. package/src/hooks/index.ts +2 -0
  104. package/src/hooks/useTextModel.ts +20 -94
  105. package/src/hooks/{yjs/space-provider.ts → yjs.ts} +71 -19
  106. package/src/index.ts +3 -0
  107. package/src/styles/index.ts +1 -0
  108. package/src/styles/tokens.ts +3 -0
  109. package/src/testing/replicator.ts +10 -15
  110. package/src/themes/default.ts +39 -33
  111. package/src/util/cursor.ts +29 -0
  112. package/src/util/index.ts +6 -0
  113. package/dist/types/src/extensions/mermaid.d.ts +0 -3
  114. package/dist/types/src/extensions/mermaid.d.ts.map +0 -1
  115. package/dist/types/src/extensions/util.d.ts.map +0 -1
  116. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  117. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  118. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  119. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  120. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  121. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  122. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  123. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
  124. package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
  125. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
  126. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
  127. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  128. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  129. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  130. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  131. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  132. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
  133. package/src/extensions/mermaid.ts +0 -11
  134. package/src/hooks/automerge/handle.ts +0 -14
  135. package/src/hooks/automerge/index.ts +0 -6
  136. package/src/hooks/yjs/index.ts +0 -6
  137. /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
  138. /package/dist/types/src/{hooks → extensions}/yjs/yjs.test.d.ts +0 -0
  139. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  140. /package/dist/types/src/{extensions → util}/util.d.ts +0 -0
  141. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  142. /package/src/{extensions → util}/util.ts +0 -0
@@ -2,36 +2,22 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type Extension, StateField } from '@codemirror/state';
6
- import get from 'lodash.get';
5
+ import { StateField, type Extension } from '@codemirror/state';
7
6
  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';
7
+ import type * as YP from 'y-protocols/awareness';
11
8
 
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';
9
+ import { isAutomergeObject, type DocAccessor, type Space, type TextObject } from '@dxos/react-client/echo';
21
10
  import { type Identity } from '@dxos/react-client/halo';
22
11
  import type { YText, YXmlFragment } from '@dxos/text-model';
23
- import { arrayToString, stringToArray } from '@dxos/util';
24
12
 
25
- import { automergePlugin } from './automerge';
26
- import { SpaceAwarenessProvider } from './yjs';
13
+ import { createAutomergeModel } from './automerge';
14
+ import { createYjsModel } from './yjs';
27
15
 
28
- // TODO(burdon): Move.
29
- type Awareness = awarenessProtocol.Awareness;
16
+ // TODO(burdon): Factor out defs.
30
17
 
31
18
  /**
32
19
  * State field makes the model available to other extensions.
33
20
  */
34
- // TODO(burdon): Use facet?
35
21
  export const modelState = StateField.define<EditorModel | undefined>({
36
22
  create: () => undefined,
37
23
  update: (model) => model,
@@ -44,20 +30,25 @@ export type Range = {
44
30
 
45
31
  export type CommentRange = {
46
32
  id: string;
47
- // TODO(burdon): Split into begin/end?
48
33
  cursor: string;
49
34
  };
50
35
 
51
36
  // TODO(wittjosiah): Factor out to common package? @dxos/react-client?
52
37
  export type EditorModel = {
53
38
  id: string;
39
+ text: () => string;
40
+ extension?: Extension;
41
+
54
42
  // TODO(burdon): Remove.
55
43
  content: string | YText | YXmlFragment | DocAccessor;
56
- text: () => string;
44
+ /**
45
+ * @deprecated Use CursorConverter.
46
+ */
57
47
  getCursorFromRange?: (value: Range) => string;
58
48
  getRangeFromCursor?: (cursor: string) => Range | undefined;
59
- extension?: Extension;
60
- awareness?: Awareness;
49
+ // TODO(burdon): Move into extension.
50
+ awareness?: YP.Awareness;
51
+ // TODO(burdon): Remove.
61
52
  peer?: {
62
53
  id: string;
63
54
  name?: string;
@@ -75,83 +66,18 @@ export type UseTextModelProps = {
75
66
  // TODO(wittjosiah): Factor out to common package? @dxos/react-client?
76
67
  export const useTextModel = (props: UseTextModelProps): EditorModel | undefined => {
77
68
  const { identity, space, text } = props;
78
- const [model, setModel] = useState<EditorModel | undefined>(() => createModel(props));
69
+ const [model, setModel] = useState<EditorModel | undefined>();
79
70
  useEffect(() => setModel(createModel(props)), [identity, space, text]);
80
71
  return model;
81
72
  };
82
73
 
83
74
  const createModel = (props: UseTextModelProps) => {
84
75
  const { text } = props;
85
- if (isActualAutomergeObject(text)) {
76
+ if (isAutomergeObject(text)) {
86
77
  return createAutomergeModel(props);
87
- } else {
88
- if (!text?.doc) {
89
- return undefined;
90
- }
91
-
78
+ } else if (text?.doc) {
92
79
  return createYjsModel(props);
80
+ } else {
81
+ return undefined;
93
82
  }
94
83
  };
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
- invariant(text.doc);
114
- const parts = cursor.split(':');
115
- const from = Y.createAbsolutePositionFromRelativePosition(
116
- Y.decodeRelativePosition(stringToArray(parts[0])),
117
- text.doc,
118
- );
119
- const to = Y.createAbsolutePositionFromRelativePosition(
120
- Y.decodeRelativePosition(stringToArray(parts[1])),
121
- text.doc,
122
- );
123
- return from && to ? { from: from.index, to: to.index } : undefined;
124
- },
125
- extension: [yCollab(text.content as YText, provider?.awareness), modelState.init(() => model)],
126
- awareness: provider?.awareness,
127
- peer: identity
128
- ? {
129
- id: identity.identityKey.toHex(),
130
- name: identity.profile?.displayName,
131
- }
132
- : undefined,
133
- };
134
-
135
- return model;
136
- };
137
-
138
- const createAutomergeModel = ({ identity, text }: UseTextModelProps): EditorModel => {
139
- const obj = text as any as AutomergeTextCompat;
140
- const doc = getRawDoc(obj, [obj.field]);
141
-
142
- const model: EditorModel = {
143
- id: obj.id,
144
- content: doc,
145
- text: () => get(doc.handle.docSync(), doc.path),
146
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
147
- extension: [automergePlugin(doc.handle, doc.path), modelState.init(() => model)],
148
- peer: identity
149
- ? {
150
- id: identity.identityKey.toHex(),
151
- name: identity.profile?.displayName,
152
- }
153
- : undefined,
154
- };
155
-
156
- return model;
157
- };
@@ -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
@@ -5,8 +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';
10
11
  export * from './extensions';
11
12
  export * from './hooks';
13
+ export { getToken } from './styles';
12
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';
@@ -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
 
@@ -49,12 +49,12 @@ export const defaultTheme: ThemeStyles = {
49
49
  '&.cm-focused': {
50
50
  outline: 'none',
51
51
  },
52
- '& .cm-scroller': {
52
+ '.cm-scroller': {
53
53
  overflow: 'visible',
54
54
  fontFamily: get(tokens, 'fontFamily.mono', []).join(','),
55
55
  },
56
56
 
57
- '& .cm-content': {
57
+ '.cm-content': {
58
58
  padding: 0,
59
59
  // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
60
60
  fontSize: '16px',
@@ -77,17 +77,17 @@ export const defaultTheme: ThemeStyles = {
77
77
  '&dark .cm-cursor, &dark .cm-dropCursor': {
78
78
  borderLeft: '2px solid white',
79
79
  },
80
- '& .cm-placeholder': {
80
+ '.cm-placeholder': {
81
81
  fontWeight: 100,
82
82
  },
83
83
 
84
84
  //
85
85
  // line
86
86
  //
87
- '& .cm-line': {
87
+ '.cm-line': {
88
88
  paddingInline: 0,
89
89
  },
90
- '& .cm-activeLine': {
90
+ '.cm-activeLine': {
91
91
  background: 'inherit',
92
92
  },
93
93
 
@@ -131,28 +131,28 @@ export const defaultTheme: ThemeStyles = {
131
131
  mixBlendMode: 'screen',
132
132
  },
133
133
 
134
- '& .cm-ySelectionInfo': {
134
+ '.cm-ySelectionInfo': {
135
135
  padding: '2px 4px',
136
136
  marginBlockStart: '-4px',
137
137
  },
138
- '& .cm-ySelection, & .cm-selectionMatch': {
138
+ '.cm-ySelection, & .cm-selectionMatch': {
139
139
  paddingBlockStart: '.15em',
140
140
  paddingBlockEnd: '.15em',
141
141
  },
142
- '& .cm-ySelectionCaret': {
142
+ '.cm-ySelectionCaret': {
143
143
  display: 'inline-block',
144
144
  insetBlockStart: '.1em',
145
145
  blockSize: '1.4em',
146
146
  verticalAlign: 'top',
147
147
  },
148
- '& .cm-yLineSelection': {
148
+ '.cm-yLineSelection': {
149
149
  margin: 0,
150
150
  },
151
151
 
152
152
  //
153
153
  // link
154
154
  //
155
- '& .cm-link': {
155
+ '.cm-link': {
156
156
  color: get(tokens, 'extend.colors.primary.500'),
157
157
  textDecorationLine: 'underline',
158
158
  textDecorationThickness: '1px',
@@ -164,34 +164,34 @@ export const defaultTheme: ThemeStyles = {
164
164
  //
165
165
  // tooltip
166
166
  //
167
- '& .cm-tooltip': {
167
+ '.cm-tooltip': {
168
168
  border: 'none',
169
169
  },
170
- '& .cm-tooltip-below': {},
170
+ '.cm-tooltip-below': {},
171
171
 
172
172
  //
173
173
  // autocomplete
174
174
  //
175
- '& .cm-tooltip-autocomplete': {
175
+ '.cm-tooltip-autocomplete': {
176
176
  marginTop: '4px',
177
177
  marginLeft: '-3px',
178
178
  },
179
- '& .cm-tooltip-autocomplete ul li': {},
180
- '& .cm-tooltip-autocomplete ul li[aria-selected]': {},
181
- '& .cm-completionIcon': {
179
+ '.cm-tooltip-autocomplete ul li': {},
180
+ '.cm-tooltip-autocomplete ul li[aria-selected]': {},
181
+ '.cm-completionIcon': {
182
182
  display: 'none',
183
183
  },
184
- '& .cm-completionLabel': {
184
+ '.cm-completionLabel': {
185
185
  fontFamily: get(tokens, 'fontFamily.body', []).join(','),
186
186
  },
187
- '& .cm-completionMatchedText': {
187
+ '.cm-completionMatchedText': {
188
188
  textDecoration: 'none',
189
189
  },
190
190
 
191
191
  //
192
192
  // widgets
193
193
  //
194
- '& .cm-widgetBuffer': {
194
+ '.cm-widgetBuffer': {
195
195
  display: 'none',
196
196
  height: 0,
197
197
  },
@@ -199,26 +199,32 @@ export const defaultTheme: ThemeStyles = {
199
199
  //
200
200
  // table
201
201
  //
202
- '& .cm-table *': {
202
+ '.cm-table *': {
203
203
  fontFamily: `${get(tokens, 'fontFamily.mono', []).join(',')} !important`,
204
204
  textDecoration: 'none !important',
205
205
  },
206
- '& .cm-table-head': {
206
+ '.cm-table-head': {
207
207
  padding: '2px 16px 2px 0px',
208
208
  borderBottom: `1px solid ${get(tokens, 'extend.colors.neutral.500')}`,
209
209
  fontWeight: 100,
210
210
  textAlign: 'left',
211
211
  color: get(tokens, 'extend.colors.neutral.500'),
212
212
  },
213
- '& .cm-table-cell': {
213
+ '.cm-table-cell': {
214
214
  padding: '2px 16px 2px 0px',
215
215
  },
216
216
 
217
217
  //
218
218
  // image
219
219
  //
220
- '& .cm-image': {
221
- margin: '0.5rem 0',
220
+ '.cm-image': {
221
+ display: 'block',
222
+ height: '0',
223
+ },
224
+ '.cm-image.cm-loaded-image': {
225
+ height: 'auto',
226
+ borderTop: '0.5rem solid transparent',
227
+ borderBottom: '0.5rem solid transparent',
222
228
  },
223
229
 
224
230
  //
@@ -250,14 +256,14 @@ export const defaultTheme: ThemeStyles = {
250
256
  * </div>
251
257
  * </div
252
258
  */
253
- '& .cm-panels': {
259
+ '.cm-panels': {
254
260
  border: `1px solid ${get(tokens, 'extend.colors.neutral.200')}`,
255
261
  },
256
- '& .cm-panel': {
262
+ '.cm-panel': {
257
263
  background: get(tokens, 'extend.colors.neutral.50'),
258
264
  fontFamily: get(tokens, 'fontFamily.body', []).join(','),
259
265
  },
260
- '& .cm-button': {
266
+ '.cm-button': {
261
267
  margin: '4px',
262
268
  fontFamily: get(tokens, 'fontFamily.body', []).join(','),
263
269
  background: get(tokens, 'extend.colors.neutral.100'),
@@ -271,32 +277,32 @@ export const defaultTheme: ThemeStyles = {
271
277
  backgroundImage: 'none',
272
278
  },
273
279
  },
274
- '& .cm-panel input[type=checkbox]': {
280
+ '.cm-panel input[type=checkbox]': {
275
281
  marginRight: '0.4rem !important',
276
282
  },
277
283
  };
278
284
 
279
285
  export const textTheme: ThemeStyles = {
280
- '& .cm-scroller': {
286
+ '.cm-scroller': {
281
287
  fontFamily: get(tokens, 'fontFamily.body', []).join(','),
282
288
  },
283
- '& .cm-placeholder': {
289
+ '.cm-placeholder': {
284
290
  fontFamily: get(tokens, 'fontFamily.body', []).join(','),
285
291
  },
286
292
  };
287
293
 
288
294
  export const markdownTheme: ThemeStyles = {
289
295
  // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
290
- '& .cm-placeholder': {
296
+ '.cm-placeholder': {
291
297
  fontFamily: get(tokens, 'fontFamily.body', []).join(','),
292
298
  },
293
299
  };
294
300
 
295
301
  export const codeTheme: ThemeStyles = {
296
- '& .cm-scroller': {
302
+ '.cm-scroller': {
297
303
  fontFamily: get(tokens, 'fontFamily.mono', []).join(','),
298
304
  },
299
- '& .cm-placeholder': {
305
+ '.cm-placeholder': {
300
306
  fontFamily: get(tokens, 'fontFamily.mono', []).join(','),
301
307
  },
302
308
  };
@@ -0,0 +1,29 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Facet } from '@codemirror/state';
6
+
7
+ /**
8
+ * Converts indexes into the text document into stable peer-independent cursors.
9
+ *
10
+ * See:
11
+ * - https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
12
+ * - https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
13
+ *
14
+ * @param {assoc} number Negative values will associate the cursor with the previous character
15
+ * while positive - with the next one.
16
+ */
17
+ export interface CursorConverter {
18
+ toCursor(position: number, assoc?: -1 | 1 | undefined): string;
19
+ fromCursor(cursor: string): number;
20
+ }
21
+
22
+ const DEFAULT_CURSOR_CONVERTER: CursorConverter = {
23
+ toCursor: (position) => position.toString(),
24
+ fromCursor: (cursor) => parseInt(cursor),
25
+ };
26
+
27
+ export const CursorConverter = Facet.define<CursorConverter, CursorConverter>({
28
+ combine: (providers) => providers[0] ?? DEFAULT_CURSOR_CONVERTER,
29
+ });
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './cursor';
6
+ export * from './util';
@@ -1,3 +0,0 @@
1
- import { type Extension } from '@codemirror/state';
2
- export declare const mermaid: () => Extension;
3
- //# sourceMappingURL=mermaid.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mermaid.d.ts","sourceRoot":"","sources":["../../../../src/extensions/mermaid.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAInD,eAAO,MAAM,OAAO,QAAO,SAE1B,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../../src/extensions/util.ts"],"names":[],"mappings":"AAMA;;GAEG;AAEH,eAAO,MAAM,eAAe,kCAOR,CAAC"}