@dxos/react-ui-editor 0.3.11-main.6d5715f → 0.3.11-main.870164f

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 (108) hide show
  1. package/dist/lib/browser/index.mjs +961 -1406
  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/codemirror.stories.d.ts.map +1 -1
  5. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  6. package/dist/types/src/extensions/index.d.ts +0 -3
  7. package/dist/types/src/extensions/index.d.ts.map +1 -1
  8. package/dist/types/src/extensions/util.d.ts.map +1 -0
  9. package/dist/types/src/{components/TextEditor → hooks/automerge}/automerge.stories.d.ts +1 -6
  10. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
  11. package/dist/types/src/hooks/automerge/handle.d.ts +9 -0
  12. package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
  13. package/dist/types/src/hooks/automerge/index.d.ts +3 -0
  14. package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
  15. package/dist/types/src/hooks/automerge/plugin.d.ts +26 -0
  16. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
  17. package/dist/types/src/{extensions → hooks}/automerge/semaphore.d.ts +4 -4
  18. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +1 -0
  19. package/dist/types/src/hooks/automerge/update-automerge.d.ts +6 -0
  20. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +1 -0
  21. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +1 -0
  22. package/dist/types/src/hooks/index.d.ts +0 -2
  23. package/dist/types/src/hooks/index.d.ts.map +1 -1
  24. package/dist/types/src/hooks/useTextModel.d.ts +7 -8
  25. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  26. package/dist/types/src/hooks/yjs/cursors.d.ts.map +1 -0
  27. package/dist/types/src/hooks/yjs/index.d.ts +3 -0
  28. package/dist/types/src/hooks/yjs/index.d.ts.map +1 -0
  29. package/dist/types/src/hooks/{yjs.d.ts → yjs/space-provider.d.ts} +8 -8
  30. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +1 -0
  31. package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +1 -1
  32. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
  33. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +1 -0
  34. package/dist/types/src/styles/index.d.ts +0 -1
  35. package/dist/types/src/styles/index.d.ts.map +1 -1
  36. package/dist/types/src/testing/replicator.d.ts +2 -2
  37. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  38. package/package.json +21 -22
  39. package/src/components/TextEditor/TextEditor.tsx +1 -1
  40. package/src/components/TextEditor/codemirror.stories.ts +2 -3
  41. package/src/extensions/comments.ts +11 -27
  42. package/src/extensions/index.ts +0 -3
  43. package/src/{components/TextEditor → hooks/automerge}/automerge.stories.tsx +6 -66
  44. package/src/hooks/automerge/handle.ts +14 -0
  45. package/src/hooks/automerge/index.ts +6 -0
  46. package/src/{extensions/automerge/automerge.ts → hooks/automerge/plugin.ts} +58 -20
  47. package/src/{extensions → hooks}/automerge/semaphore.ts +12 -14
  48. package/src/{extensions → hooks}/automerge/update-automerge.ts +6 -5
  49. package/src/{extensions → hooks}/automerge/update-codemirror.ts +5 -13
  50. package/src/hooks/index.ts +0 -2
  51. package/src/hooks/useTextModel.ts +93 -19
  52. package/src/hooks/yjs/index.ts +6 -0
  53. package/src/hooks/{yjs.ts → yjs/space-provider.ts} +19 -71
  54. package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +1 -1
  55. package/src/{extensions → hooks}/yjs/yjs.test.ts +25 -1
  56. package/src/styles/index.ts +0 -1
  57. package/src/testing/replicator.ts +15 -10
  58. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +0 -1
  59. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  60. package/dist/types/src/extensions/automerge/automerge.d.ts +0 -9
  61. package/dist/types/src/extensions/automerge/automerge.d.ts.map +0 -1
  62. package/dist/types/src/extensions/automerge/cursor.d.ts +0 -5
  63. package/dist/types/src/extensions/automerge/cursor.d.ts.map +0 -1
  64. package/dist/types/src/extensions/automerge/defs.d.ts +0 -26
  65. package/dist/types/src/extensions/automerge/defs.d.ts.map +0 -1
  66. package/dist/types/src/extensions/automerge/index.d.ts +0 -3
  67. package/dist/types/src/extensions/automerge/index.d.ts.map +0 -1
  68. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +0 -1
  69. package/dist/types/src/extensions/automerge/update-automerge.d.ts +0 -5
  70. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +0 -1
  71. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +0 -1
  72. package/dist/types/src/extensions/awareness.d.ts +0 -46
  73. package/dist/types/src/extensions/awareness.d.ts.map +0 -1
  74. package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
  75. package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
  76. package/dist/types/src/extensions/yjs/index.d.ts +0 -2
  77. package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
  78. package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
  79. package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
  80. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
  81. package/dist/types/src/hooks/automerge.d.ts +0 -3
  82. package/dist/types/src/hooks/automerge.d.ts.map +0 -1
  83. package/dist/types/src/hooks/awareness-provider.d.ts +0 -28
  84. package/dist/types/src/hooks/awareness-provider.d.ts.map +0 -1
  85. package/dist/types/src/hooks/yjs.d.ts.map +0 -1
  86. package/dist/types/src/styles/cursors.d.ts.map +0 -1
  87. package/dist/types/src/util/cursor.d.ts +0 -17
  88. package/dist/types/src/util/cursor.d.ts.map +0 -1
  89. package/dist/types/src/util/index.d.ts +0 -3
  90. package/dist/types/src/util/index.d.ts.map +0 -1
  91. package/dist/types/src/util/util.d.ts.map +0 -1
  92. package/src/extensions/automerge/cursor.ts +0 -51
  93. package/src/extensions/automerge/defs.ts +0 -62
  94. package/src/extensions/automerge/index.ts +0 -6
  95. package/src/extensions/awareness.ts +0 -316
  96. package/src/extensions/yjs/cursor.ts +0 -21
  97. package/src/extensions/yjs/index.ts +0 -5
  98. package/src/extensions/yjs/yjs.ts +0 -16
  99. package/src/hooks/automerge.ts +0 -52
  100. package/src/hooks/awareness-provider.ts +0 -120
  101. package/src/util/cursor.ts +0 -29
  102. package/src/util/index.ts +0 -6
  103. /package/dist/types/src/{util → extensions}/util.d.ts +0 -0
  104. /package/dist/types/src/{extensions → hooks}/automerge/update-codemirror.d.ts +0 -0
  105. /package/dist/types/src/{styles → hooks/yjs}/cursors.d.ts +0 -0
  106. /package/dist/types/src/{extensions → hooks}/yjs/yjs.test.d.ts +0 -0
  107. /package/src/{util → extensions}/util.ts +0 -0
  108. /package/src/{styles → hooks/yjs}/cursors.ts +0 -0
@@ -1,28 +0,0 @@
1
- import { Event } from '@dxos/async';
2
- import { type Space } from '@dxos/react-client/echo';
3
- import { type AwarenessInfo, type AwarenessPosition, type AwarenessProvider, type AwarenessState } from '../extensions';
4
- export type AwarenessProviderParams = {
5
- space: Space;
6
- channel: string;
7
- peerId: string;
8
- info: AwarenessInfo;
9
- };
10
- export declare class SpaceAwarenessProvider implements AwarenessProvider {
11
- readonly remoteStateChange: Event<void>;
12
- private readonly _remoteStates;
13
- private readonly _space;
14
- private readonly _channel;
15
- private readonly _peerId;
16
- private readonly _info;
17
- private _localState?;
18
- private _postTask?;
19
- private _ctx?;
20
- constructor(params: AwarenessProviderParams);
21
- open(): void;
22
- close(): void;
23
- update(position: AwarenessPosition | undefined): void;
24
- getRemoteStates(): AwarenessState[];
25
- private _handleQueryMessage;
26
- private _handlePostMessage;
27
- }
28
- //# sourceMappingURL=awareness-provider.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"awareness-provider.d.ts","sourceRoot":"","sources":["../../../../src/hooks/awareness-provider.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,KAAK,EAAS,MAAM,aAAa,CAAC;AAIzD,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAGrD,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAaxH,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,aAAa,CAAC;CACrB,CAAC;AAEF,qBAAa,sBAAuB,YAAW,iBAAiB;IAC9D,SAAgB,iBAAiB,cAAqB;IAEtD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqC;IAEnE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IAEtC,OAAO,CAAC,WAAW,CAAC,CAAiB;IACrC,OAAO,CAAC,SAAS,CAAC,CAAe;IACjC,OAAO,CAAC,IAAI,CAAC,CAAU;gBAEX,MAAM,EAAE,uBAAuB;IAO3C,IAAI;IAoCJ,KAAK;IAML,MAAM,CAAC,QAAQ,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI;IAWrD,eAAe,IAAI,cAAc,EAAE;IAInC,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,kBAAkB;CAK3B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"yjs.d.ts","sourceRoot":"","sources":["../../../../src/hooks/yjs.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAE5C,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAI/B,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAKrD,OAAO,EAAE,KAAK,WAAW,EAA0B,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGlG,eAAO,MAAM,cAAc,8BAA+B,iBAAiB,KAAG,WAyC7E,CAAC;AAOF;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,UAAU,CAAC,GAAG,CAAC;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,EACV,KAAK,EACL,GAAG,EACH,OAAO,EACP,SAAS,GACV,EAAE;QACD,KAAK,EAAE,KAAK,CAAC;QACb,GAAG,EAAE,GAAG,CAAC;QACT,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC;KAC1B;IA4BD,IAAI,SAAS,IAAI,EAAE,CAAC,SAAS,CAE5B;IAED,OAAO,CAAC,sBAAsB;IAS9B,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,YAAY;IAoCpB,OAAO,CAAC,mBAAmB;CAG5B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"cursors.d.ts","sourceRoot":"","sources":["../../../../src/styles/cursors.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,WAAW;;;CAAsD,CAAC"}
@@ -1,17 +0,0 @@
1
- import { Facet } from '@codemirror/state';
2
- /**
3
- * Converts indexes into the text document into stable peer-independent cursors.
4
- *
5
- * See:
6
- * - https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
7
- * - https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
8
- *
9
- * @param {assoc} number Negative values will associate the cursor with the previous character
10
- * while positive - with the next one.
11
- */
12
- export interface CursorConverter {
13
- toCursor(position: number, assoc?: -1 | 1 | undefined): string;
14
- fromCursor(cursor: string): number;
15
- }
16
- export declare const CursorConverter: Facet<CursorConverter, CursorConverter>;
17
- //# sourceMappingURL=cursor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../../../src/util/cursor.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC;IAC/D,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACpC;AAOD,eAAO,MAAM,eAAe,yCAE1B,CAAC"}
@@ -1,3 +0,0 @@
1
- export * from './cursor';
2
- export * from './util';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/util/index.ts"],"names":[],"mappings":"AAIA,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../../src/util/util.ts"],"names":[],"mappings":"AAMA;;GAEG;AAEH,eAAO,MAAM,eAAe,kCAOR,CAAC"}
@@ -1,51 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import get from 'lodash.get';
6
-
7
- import type { Prop } from '@dxos/automerge/automerge';
8
- import { next as automerge } from '@dxos/automerge/automerge';
9
-
10
- import { type IDocHandle } from './defs';
11
- import { type CursorConverter } from '../../util';
12
-
13
- export const cursorConverter = (handle: IDocHandle, path: Prop[]): CursorConverter => ({
14
- // TODO(burdon): Handle assoc to associate with a previous character.
15
- toCursor: (pos) => {
16
- const doc = handle.docSync();
17
- if (!doc) {
18
- return '';
19
- }
20
-
21
- const value = get(doc, path);
22
- if (typeof value === 'string' && value.length <= pos) {
23
- return 'end';
24
- }
25
-
26
- // NOTE: Slice is needed because getCursor mutates the array.
27
- return automerge.getCursor(doc, path.slice(), pos);
28
- },
29
- fromCursor: (cursor) => {
30
- if (cursor === '') {
31
- return 0;
32
- }
33
-
34
- const doc = handle.docSync();
35
- if (!doc) {
36
- return 0;
37
- }
38
-
39
- if (cursor === 'end') {
40
- const value = get(doc, path);
41
- if (typeof value === 'string') {
42
- return value.length;
43
- } else {
44
- return 0;
45
- }
46
- }
47
-
48
- // NOTE: Slice is needed because getCursor mutates the array.
49
- return automerge.getCursorPosition(doc, path.slice(), cursor);
50
- },
51
- });
@@ -1,62 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- // Ref: https://github.com/automerge/automerge-codemirror
4
- //
5
-
6
- import {
7
- Annotation,
8
- StateEffect,
9
- type StateField,
10
- type EditorState,
11
- type Transaction,
12
- type TransactionSpec,
13
- } from '@codemirror/state';
14
-
15
- import { type ChangeFn, type ChangeOptions, type Doc, type Heads, type Prop } from '@dxos/automerge/automerge';
16
-
17
- export type Value = {
18
- lastHeads: Heads;
19
- path: Prop[];
20
- unreconciledTransactions: Transaction[];
21
- };
22
-
23
- type UpdateHeads = {
24
- newHeads: Heads;
25
- };
26
-
27
- export const effectType = StateEffect.define<UpdateHeads>({});
28
-
29
- export const updateHeads = (newHeads: Heads): StateEffect<UpdateHeads> => effectType.of({ newHeads });
30
-
31
- export const getLastHeads = (state: EditorState, field: StateField<Value>): Heads => state.field(field).lastHeads;
32
-
33
- export const getPath = (state: EditorState, field: StateField<Value>): Prop[] => state.field(field).path;
34
-
35
- export const reconcileAnnotationType = Annotation.define<unknown>();
36
-
37
- export const isReconcileTx = (tr: Transaction): boolean => !!tr.annotation(reconcileAnnotationType);
38
-
39
- export const makeReconcile = (tr: TransactionSpec) => {
40
- if (tr.annotations != null) {
41
- if (tr.annotations instanceof Array) {
42
- tr.annotations = [...tr.annotations, reconcileAnnotationType.of({})];
43
- } else {
44
- tr.annotations = [tr.annotations, reconcileAnnotationType.of({})];
45
- }
46
- } else {
47
- tr.annotations = [reconcileAnnotationType.of({})];
48
- }
49
- // return {
50
- // ...tr,
51
- // annotations: reconcileAnnotationType.of({})
52
- // }
53
- };
54
-
55
- export type IDocHandle<T = any> = {
56
- docSync(): Doc<T> | undefined;
57
- change(callback: ChangeFn<T>, options?: ChangeOptions<T>): void;
58
- changeAt(heads: Heads, callback: ChangeFn<T>, options?: ChangeOptions<T>): string[] | undefined;
59
-
60
- addListener(event: 'change', listener: () => void): void;
61
- removeListener(event: 'change', listener: () => void): void;
62
- };
@@ -1,6 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- export { automerge } from './automerge';
6
- export { type IDocHandle } from './defs';
@@ -1,316 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { Annotation, Facet, type Extension, RangeSet, type Range } from '@codemirror/state';
6
- import {
7
- Decoration,
8
- type DecorationSet,
9
- EditorView,
10
- type PluginValue,
11
- ViewPlugin,
12
- type ViewUpdate,
13
- WidgetType,
14
- } from '@codemirror/view';
15
-
16
- import { Event } from '@dxos/async';
17
- import { Context } from '@dxos/context';
18
-
19
- import { CursorConverter } from '../util';
20
-
21
- export interface AwarenessProvider {
22
- remoteStateChange: Event<void>;
23
-
24
- open(): void;
25
- close(): void;
26
-
27
- update(position: AwarenessPosition | undefined): void;
28
- getRemoteStates(): AwarenessState[];
29
- }
30
-
31
- const dummyProvider: AwarenessProvider = {
32
- remoteStateChange: new Event(),
33
-
34
- open: () => {},
35
- close: () => {},
36
-
37
- update: () => {},
38
- getRemoteStates: () => [],
39
- };
40
-
41
- export const AwarenessProvider = Facet.define<AwarenessProvider, AwarenessProvider>({
42
- combine: (providers) => providers[0] ?? dummyProvider,
43
- });
44
-
45
- // TODO(dmaretskyi): Specify the users that actually changed. Currently, we recalculate positions for every user.
46
- const RemoteSelectionChangedAnnotation = Annotation.define();
47
-
48
- export type AwarenessPosition = {
49
- anchor?: string;
50
- head?: string;
51
- };
52
-
53
- export type AwarenessInfo = {
54
- displayName?: string;
55
- // TODO(burdon): Rename light/dark.
56
- color?: string;
57
- lightColor?: string;
58
- };
59
-
60
- export type AwarenessState = {
61
- position?: AwarenessPosition;
62
- peerId: string;
63
- info: AwarenessInfo;
64
- };
65
-
66
- /**
67
- * Extension provides presence information about other peers.
68
- */
69
- // TODO(burdon): Why provide default?
70
- export const awareness = (provider = dummyProvider): Extension => {
71
- return [
72
- AwarenessProvider.of(provider),
73
- ViewPlugin.fromClass(RemoteSelectionsDecorator, {
74
- decorations: (value) => value.decorations,
75
- }),
76
- styles,
77
- ];
78
- };
79
-
80
- /**
81
- * Generates selection decorations from remote peers.
82
- */
83
- export class RemoteSelectionsDecorator implements PluginValue {
84
- public decorations: DecorationSet = RangeSet.of([]);
85
-
86
- private readonly _ctx = new Context();
87
-
88
- private _cursorConverter: CursorConverter;
89
- private _provider: AwarenessProvider;
90
- private _lastAnchor?: number = undefined;
91
- private _lastHead?: number = undefined;
92
-
93
- constructor(view: EditorView) {
94
- this._cursorConverter = view.state.facet(CursorConverter);
95
- this._provider = view.state.facet(AwarenessProvider);
96
- this._provider.open();
97
- this._provider.remoteStateChange.on(this._ctx, () => {
98
- view.dispatch({ annotations: [RemoteSelectionChangedAnnotation.of([])] });
99
- });
100
- }
101
-
102
- destroy() {
103
- void this._ctx.dispose();
104
- this._provider.close();
105
- }
106
-
107
- update(update: ViewUpdate) {
108
- this._updateLocalSelection(update);
109
- this._updateRemoteSelections(update);
110
- }
111
-
112
- private _updateLocalSelection(update: ViewUpdate) {
113
- const hasFocus = update.view.hasFocus && update.view.dom.ownerDocument.hasFocus();
114
- const { anchor = undefined, head = undefined } = hasFocus ? update.state.selection.main : {};
115
- if (this._lastAnchor === anchor && this._lastHead === head) {
116
- return;
117
- }
118
-
119
- this._lastAnchor = anchor;
120
- this._lastHead = head;
121
-
122
- this._provider.update(
123
- anchor !== undefined && head !== undefined
124
- ? {
125
- anchor: this._cursorConverter.toCursor(anchor),
126
- head: this._cursorConverter.toCursor(head),
127
- }
128
- : undefined,
129
- );
130
- }
131
-
132
- private _updateRemoteSelections(update: ViewUpdate) {
133
- const decorations: Range<Decoration>[] = [];
134
- const awarenessStates = this._provider.getRemoteStates();
135
- for (const state of awarenessStates) {
136
- const anchor = state.position?.anchor ? this._cursorConverter.fromCursor(state.position.anchor) : null;
137
- const head = state.position?.head ? this._cursorConverter.fromCursor(state.position.head) : null;
138
- if (anchor == null || head == null) {
139
- continue;
140
- }
141
-
142
- const start = Math.min(Math.min(anchor, head), update.view.state.doc.length);
143
- const end = Math.min(Math.max(anchor, head), update.view.state.doc.length);
144
-
145
- const startLine = update.view.state.doc.lineAt(start);
146
- const endLine = update.view.state.doc.lineAt(end);
147
-
148
- // TODO(burdon): Factor out styles.
149
- const color = state.info.color ?? '#30bced';
150
- const lightColor = state.info.lightColor ?? color + '33';
151
-
152
- if (startLine.number === endLine.number) {
153
- // Selected content in a single line.
154
- decorations.push({
155
- from: start,
156
- to: end,
157
- value: Decoration.mark({
158
- attributes: { style: `background-color: ${lightColor}` },
159
- class: 'cm-ySelection',
160
- }),
161
- });
162
- } else {
163
- // Selected content in multiple lines; first, render text-selection in the first line.
164
- decorations.push({
165
- from: start,
166
- to: startLine.from + startLine.length,
167
- value: Decoration.mark({
168
- attributes: { style: `background-color: ${color}` },
169
- class: 'cm-ySelection',
170
- }),
171
- });
172
-
173
- // Render text-selection in the last line.
174
- decorations.push({
175
- from: endLine.from,
176
- to: end,
177
- value: Decoration.mark({
178
- attributes: { style: `background-color: ${color}` },
179
- class: 'cm-ySelection',
180
- }),
181
- });
182
-
183
- for (let i = startLine.number + 1; i < endLine.number; i++) {
184
- const linePos = update.view.state.doc.line(i).from;
185
- decorations.push({
186
- from: linePos,
187
- to: linePos,
188
- value: Decoration.line({
189
- attributes: { style: `background-color: ${color}`, class: 'cm-yLineSelection' },
190
- }),
191
- });
192
- }
193
- }
194
-
195
- decorations.push({
196
- from: head,
197
- to: head,
198
- value: Decoration.widget({
199
- side: head - anchor > 0 ? -1 : 1, // The local cursor should be rendered outside the remote selection.
200
- block: false,
201
- widget: new RemoteCaretWidget(state.info.displayName ?? 'Anonymous', color),
202
- }),
203
- });
204
- }
205
-
206
- this.decorations = Decoration.set(decorations, true);
207
- }
208
- }
209
-
210
- class RemoteCaretWidget extends WidgetType {
211
- constructor(public name: string, public color: string) {
212
- super();
213
- this.name = name;
214
- this.color = color;
215
- }
216
-
217
- toDOM(): HTMLElement {
218
- const span = document.createElement('span');
219
- span.className = 'cm-ySelectionCaret';
220
- span.style.backgroundColor = this.color;
221
- span.style.borderColor = this.color;
222
-
223
- const dot = document.createElement('div');
224
- dot.className = 'cm-ySelectionCaretDot';
225
-
226
- const info = document.createElement('div');
227
- info.className = 'cm-ySelectionInfo';
228
- info.innerText = this.name;
229
-
230
- span.appendChild(document.createTextNode('\u2060'));
231
- span.appendChild(dot);
232
- span.appendChild(document.createTextNode('\u2060'));
233
- span.appendChild(info);
234
- span.appendChild(document.createTextNode('\u2060'));
235
-
236
- return span;
237
- }
238
-
239
- override eq(widget: this) {
240
- return widget.color === this.color;
241
- }
242
-
243
- compare(widget: this) {
244
- return widget.color === this.color;
245
- }
246
-
247
- override updateDOM() {
248
- return false;
249
- }
250
-
251
- override get estimatedHeight() {
252
- return -1;
253
- }
254
-
255
- override ignoreEvent() {
256
- return true;
257
- }
258
- }
259
-
260
- // TODO(burdon): Rename prefix (y for yjs?)
261
- const styles = EditorView.baseTheme({
262
- '.cm-ySelection': {},
263
- '.cm-yLineSelection': {
264
- padding: 0,
265
- margin: '0px 2px 0px 4px',
266
- },
267
- '.cm-ySelectionCaret': {
268
- position: 'relative',
269
- borderLeft: '1px solid black',
270
- borderRight: '1px solid black',
271
- marginLeft: '-1px',
272
- marginRight: '-1px',
273
- boxSizing: 'border-box',
274
- display: 'inline',
275
- },
276
- '.cm-ySelectionCaretDot': {
277
- borderRadius: '50%',
278
- position: 'absolute',
279
- width: '.4em',
280
- height: '.4em',
281
- top: '-.2em',
282
- left: '-.2em',
283
- backgroundColor: 'inherit',
284
- transition: 'transform .3s ease-in-out',
285
- boxSizing: 'border-box',
286
- },
287
- '.cm-ySelectionCaret:hover > .cm-ySelectionCaretDot': {
288
- transformOrigin: 'bottom center',
289
- transform: 'scale(0)',
290
- },
291
- '.cm-ySelectionInfo': {
292
- position: 'absolute',
293
- top: '-1.05em',
294
- left: '-1px',
295
- fontSize: '.75em',
296
- fontFamily: 'serif',
297
- fontStyle: 'normal',
298
- fontWeight: 'normal',
299
- lineHeight: 'normal',
300
- userSelect: 'none',
301
- color: 'white',
302
- paddingLeft: '2px',
303
- paddingRight: '2px',
304
- zIndex: 101,
305
- transition: 'opacity .3s ease-in-out',
306
- backgroundColor: 'inherit',
307
- // these should be separate
308
- opacity: 0,
309
- transitionDelay: '0s',
310
- whiteSpace: 'nowrap',
311
- },
312
- '.cm-ySelectionCaret:hover > .cm-ySelectionInfo': {
313
- opacity: 1,
314
- transitionDelay: '0s',
315
- },
316
- });
@@ -1,21 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import * as Y from 'yjs';
6
-
7
- import { arrayToString, stringToArray } from '@dxos/util';
8
-
9
- import { type CursorConverter } from '../../util';
10
-
11
- export const cursorConverter = (text: Y.Text): CursorConverter => ({
12
- toCursor: (index, assoc) => {
13
- return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
14
- },
15
- fromCursor: (cursor) => {
16
- return (
17
- Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc!)?.index ??
18
- 0
19
- );
20
- },
21
- });
@@ -1,5 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- export * from './yjs';
@@ -1,16 +0,0 @@
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 { CursorConverter } from '../../util';
12
-
13
- export const yjs = (content: YText, awareness?: YP.Awareness) => [
14
- CursorConverter.of(cursorConverter(content)),
15
- yCollab(content, awareness),
16
- ];
@@ -1,52 +0,0 @@
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, type UseTextModelProps } from './useTextModel';
12
- import { automerge, awareness, AwarenessProvider } from '../extensions';
13
- import { cursorColor } from '../styles';
14
-
15
- export const createAutomergeModel = ({ space, identity, text }: UseTextModelProps): EditorModel => {
16
- const obj = text as any as AutomergeTextCompat;
17
- const doc = getRawDoc(obj, [obj.field]);
18
-
19
- const awarenessProvider =
20
- space &&
21
- new SpaceAwarenessProvider({
22
- space,
23
- channel: `automerge.awareness.${obj.id}`,
24
- info: {
25
- displayName: identity?.profile?.displayName ?? '',
26
- color: cursorColor.color,
27
- lightColor: cursorColor.light,
28
- },
29
- peerId: identity?.identityKey.toHex() ?? 'Anonymous',
30
- });
31
-
32
- const model: EditorModel = {
33
- id: obj.id,
34
- content: doc,
35
- text: () => get(doc.handle.docSync(), doc.path),
36
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
37
- extension: [
38
- modelState.init(() => model),
39
- automerge({ handle: doc.handle, path: doc.path }),
40
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
41
- awareness(),
42
- ].filter(isNotNullOrUndefined),
43
- peer: identity
44
- ? {
45
- id: identity.identityKey.toHex(),
46
- name: identity.profile?.displayName,
47
- }
48
- : undefined,
49
- };
50
-
51
- return model;
52
- };