@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.
- package/dist/lib/browser/index.mjs +961 -1406
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +0 -3
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/util.d.ts.map +1 -0
- package/dist/types/src/{components/TextEditor → hooks/automerge}/automerge.stories.d.ts +1 -6
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/handle.d.ts +9 -0
- package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/index.d.ts +3 -0
- package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/plugin.d.ts +26 -0
- package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
- package/dist/types/src/{extensions → hooks}/automerge/semaphore.d.ts +4 -4
- package/dist/types/src/hooks/automerge/semaphore.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/update-automerge.d.ts +6 -0
- package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +0 -2
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +7 -8
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/hooks/yjs/cursors.d.ts.map +1 -0
- package/dist/types/src/hooks/yjs/index.d.ts +3 -0
- package/dist/types/src/hooks/yjs/index.d.ts.map +1 -0
- package/dist/types/src/hooks/{yjs.d.ts → yjs/space-provider.d.ts} +8 -8
- package/dist/types/src/hooks/yjs/space-provider.d.ts.map +1 -0
- package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +1 -1
- package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
- package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +1 -0
- package/dist/types/src/styles/index.d.ts +0 -1
- package/dist/types/src/styles/index.d.ts.map +1 -1
- package/dist/types/src/testing/replicator.d.ts +2 -2
- package/dist/types/src/testing/replicator.d.ts.map +1 -1
- package/package.json +21 -22
- package/src/components/TextEditor/TextEditor.tsx +1 -1
- package/src/components/TextEditor/codemirror.stories.ts +2 -3
- package/src/extensions/comments.ts +11 -27
- package/src/extensions/index.ts +0 -3
- package/src/{components/TextEditor → hooks/automerge}/automerge.stories.tsx +6 -66
- package/src/hooks/automerge/handle.ts +14 -0
- package/src/hooks/automerge/index.ts +6 -0
- package/src/{extensions/automerge/automerge.ts → hooks/automerge/plugin.ts} +58 -20
- package/src/{extensions → hooks}/automerge/semaphore.ts +12 -14
- package/src/{extensions → hooks}/automerge/update-automerge.ts +6 -5
- package/src/{extensions → hooks}/automerge/update-codemirror.ts +5 -13
- package/src/hooks/index.ts +0 -2
- package/src/hooks/useTextModel.ts +93 -19
- package/src/hooks/yjs/index.ts +6 -0
- package/src/hooks/{yjs.ts → yjs/space-provider.ts} +19 -71
- package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +1 -1
- package/src/{extensions → hooks}/yjs/yjs.test.ts +25 -1
- package/src/styles/index.ts +0 -1
- package/src/testing/replicator.ts +15 -10
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts +0 -9
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts +0 -5
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/defs.d.ts +0 -26
- package/dist/types/src/extensions/automerge/defs.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/index.d.ts +0 -3
- package/dist/types/src/extensions/automerge/index.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/semaphore.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts +0 -5
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +0 -1
- package/dist/types/src/extensions/awareness.d.ts +0 -46
- package/dist/types/src/extensions/awareness.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
- package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/index.d.ts +0 -2
- package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
- package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge.d.ts +0 -3
- package/dist/types/src/hooks/automerge.d.ts.map +0 -1
- package/dist/types/src/hooks/awareness-provider.d.ts +0 -28
- package/dist/types/src/hooks/awareness-provider.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs.d.ts.map +0 -1
- package/dist/types/src/styles/cursors.d.ts.map +0 -1
- package/dist/types/src/util/cursor.d.ts +0 -17
- package/dist/types/src/util/cursor.d.ts.map +0 -1
- package/dist/types/src/util/index.d.ts +0 -3
- package/dist/types/src/util/index.d.ts.map +0 -1
- package/dist/types/src/util/util.d.ts.map +0 -1
- package/src/extensions/automerge/cursor.ts +0 -51
- package/src/extensions/automerge/defs.ts +0 -62
- package/src/extensions/automerge/index.ts +0 -6
- package/src/extensions/awareness.ts +0 -316
- package/src/extensions/yjs/cursor.ts +0 -21
- package/src/extensions/yjs/index.ts +0 -5
- package/src/extensions/yjs/yjs.ts +0 -16
- package/src/hooks/automerge.ts +0 -52
- package/src/hooks/awareness-provider.ts +0 -120
- package/src/util/cursor.ts +0 -29
- package/src/util/index.ts +0 -6
- /package/dist/types/src/{util → extensions}/util.d.ts +0 -0
- /package/dist/types/src/{extensions → hooks}/automerge/update-codemirror.d.ts +0 -0
- /package/dist/types/src/{styles → hooks/yjs}/cursors.d.ts +0 -0
- /package/dist/types/src/{extensions → hooks}/yjs/yjs.test.d.ts +0 -0
- /package/src/{util → extensions}/util.ts +0 -0
- /package/src/{styles → hooks/yjs}/cursors.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-editor",
|
|
3
|
-
"version": "0.3.11-main.
|
|
3
|
+
"version": "0.3.11-main.870164f",
|
|
4
4
|
"description": "Document editing experience within a DXOS shell.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -38,20 +38,19 @@
|
|
|
38
38
|
"y-codemirror.next": "^0.3.2",
|
|
39
39
|
"y-protocols": "^1.0.5",
|
|
40
40
|
"yjs": "^13.6.10",
|
|
41
|
-
"@dxos/async": "0.3.11-main.
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/debug": "0.3.11-main.
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/react-
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/react-ui-theme": "0.3.11-main.
|
|
53
|
-
"@dxos/util": "0.3.11-main.
|
|
54
|
-
"@dxos/text-model": "0.3.11-main.6d5715f"
|
|
41
|
+
"@dxos/async": "0.3.11-main.870164f",
|
|
42
|
+
"@dxos/automerge": "0.3.11-main.870164f",
|
|
43
|
+
"@dxos/display-name": "0.3.11-main.870164f",
|
|
44
|
+
"@dxos/invariant": "0.3.11-main.870164f",
|
|
45
|
+
"@dxos/debug": "0.3.11-main.870164f",
|
|
46
|
+
"@dxos/log": "0.3.11-main.870164f",
|
|
47
|
+
"@dxos/echo-schema": "0.3.11-main.870164f",
|
|
48
|
+
"@dxos/protocols": "0.3.11-main.870164f",
|
|
49
|
+
"@dxos/react-async": "0.3.11-main.870164f",
|
|
50
|
+
"@dxos/react-ui": "0.3.11-main.870164f",
|
|
51
|
+
"@dxos/text-model": "0.3.11-main.870164f",
|
|
52
|
+
"@dxos/react-ui-theme": "0.3.11-main.870164f",
|
|
53
|
+
"@dxos/util": "0.3.11-main.870164f"
|
|
55
54
|
},
|
|
56
55
|
"devDependencies": {
|
|
57
56
|
"@automerge/automerge-repo-network-broadcastchannel": "^1.0.19",
|
|
@@ -67,18 +66,18 @@
|
|
|
67
66
|
"deepsignal": "1.4.0-shallow.0",
|
|
68
67
|
"react": "^18.2.0",
|
|
69
68
|
"react-dom": "^18.2.0",
|
|
70
|
-
"@dxos/config": "0.3.11-main.
|
|
71
|
-
"@dxos/
|
|
72
|
-
"@dxos/echo-
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/
|
|
75
|
-
"@dxos/
|
|
69
|
+
"@dxos/config": "0.3.11-main.870164f",
|
|
70
|
+
"@dxos/echo-typegen": "0.3.11-main.870164f",
|
|
71
|
+
"@dxos/echo-signals": "0.3.11-main.870164f",
|
|
72
|
+
"@dxos/react-client": "0.3.11-main.870164f",
|
|
73
|
+
"@dxos/storybook-utils": "0.3.11-main.870164f",
|
|
74
|
+
"@dxos/react-ui": "0.3.11-main.870164f"
|
|
76
75
|
},
|
|
77
76
|
"peerDependencies": {
|
|
78
77
|
"@phosphor-icons/react": "^2.0.5",
|
|
79
78
|
"react": "^18.2.0",
|
|
80
79
|
"react-dom": "^18.2.0",
|
|
81
|
-
"@dxos/react-client": "0.3.11-main.
|
|
80
|
+
"@dxos/react-client": "0.3.11-main.870164f"
|
|
82
81
|
},
|
|
83
82
|
"publishConfig": {
|
|
84
83
|
"access": "public"
|
|
@@ -77,7 +77,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
77
77
|
>();
|
|
78
78
|
useImperativeHandle(forwardedRef, () => ({ root, state, view }), [view, state, root]);
|
|
79
79
|
|
|
80
|
-
// TODO(burdon): Factor out
|
|
80
|
+
// TODO(burdon): Factor out?
|
|
81
81
|
const { awareness, peer } = model;
|
|
82
82
|
useEffect(() => {
|
|
83
83
|
if (awareness && peer) {
|
|
@@ -17,8 +17,7 @@ import { type Identity } from '@dxos/react-client/halo';
|
|
|
17
17
|
import { joinCommonSpace, TestBuilder, textGenerator } from '@dxos/react-client/testing';
|
|
18
18
|
import { YText } from '@dxos/text-model';
|
|
19
19
|
|
|
20
|
-
import {
|
|
21
|
-
import { cursorColor } from '../../styles';
|
|
20
|
+
import { cursorColor, SpaceAwarenessProvider } from '../../hooks';
|
|
22
21
|
import { EditorDocument, types as schema } from '../../testing';
|
|
23
22
|
|
|
24
23
|
export default {
|
|
@@ -36,7 +35,7 @@ const updateEditor = async (id: number, editor: HTMLDivElement, identity: Identi
|
|
|
36
35
|
return;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
const { awareness } = new
|
|
38
|
+
const { awareness } = new SpaceAwarenessProvider({ space, doc, channel: `yjs.awareness.${text.id}` });
|
|
40
39
|
awareness.setLocalStateField('user', {
|
|
41
40
|
name: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
|
|
42
41
|
// TODO(wittjosiah): Pick colours from theme based on identity key.
|
|
@@ -24,9 +24,9 @@ import { invariant } from '@dxos/invariant';
|
|
|
24
24
|
import { log } from '@dxos/log';
|
|
25
25
|
import { nonNullable } from '@dxos/util';
|
|
26
26
|
|
|
27
|
-
import {
|
|
27
|
+
import { callbackWrapper } from './util';
|
|
28
|
+
import { type CommentRange, type EditorModel, modelState, type Range } from '../hooks';
|
|
28
29
|
import { tokens } from '../styles';
|
|
29
|
-
import { callbackWrapper, CursorConverter } from '../util';
|
|
30
30
|
|
|
31
31
|
// TODO(burdon): Handle delete, cut, copy, and paste (separately) text that includes comment range.
|
|
32
32
|
// TODO(burdon): Consider breaking into separate plugin (since not standalone)? Like mermaid?
|
|
@@ -99,8 +99,6 @@ const setCommentState = StateEffect.define<CommentsState>();
|
|
|
99
99
|
const commentsStateField = StateField.define<CommentsState>({
|
|
100
100
|
create: () => ({ ranges: [] }),
|
|
101
101
|
update: (value, tr) => {
|
|
102
|
-
const cursorConverter = tr.state.facet(CursorConverter);
|
|
103
|
-
|
|
104
102
|
for (const effect of tr.effects) {
|
|
105
103
|
// Update selection.
|
|
106
104
|
if (effect.is(setSelection)) {
|
|
@@ -109,10 +107,10 @@ const commentsStateField = StateField.define<CommentsState>({
|
|
|
109
107
|
|
|
110
108
|
// Update range from store.
|
|
111
109
|
if (effect.is(setCommentRange)) {
|
|
112
|
-
const { comments } = effect.value;
|
|
110
|
+
const { model, comments } = effect.value;
|
|
113
111
|
const ranges: ExtendedCommentRange[] = comments
|
|
114
112
|
.map((comment) => {
|
|
115
|
-
const range = getRangeFromCursor(
|
|
113
|
+
const range = model.getRangeFromCursor!(comment.cursor);
|
|
116
114
|
return range && { ...comment, ...range };
|
|
117
115
|
})
|
|
118
116
|
.filter(nonNullable);
|
|
@@ -220,15 +218,14 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
220
218
|
* Create comment thread action.
|
|
221
219
|
*/
|
|
222
220
|
const createCommentThread: Command = (view) => {
|
|
223
|
-
const cursorConverter = view.state.facet(CursorConverter);
|
|
224
|
-
|
|
225
221
|
invariant(options.onCreate);
|
|
226
222
|
const { head, from, to } = view.state.selection.main;
|
|
227
223
|
if (from === to) {
|
|
228
224
|
return false;
|
|
229
225
|
}
|
|
230
226
|
|
|
231
|
-
const
|
|
227
|
+
const model = view.state.field(modelState);
|
|
228
|
+
const relPos = model?.getCursorFromRange?.({ from, to });
|
|
232
229
|
if (relPos) {
|
|
233
230
|
// Create thread via callback.
|
|
234
231
|
const id = callbackWrapper(options.onCreate)(relPos);
|
|
@@ -341,19 +338,19 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
341
338
|
// Monitor cursor movement and text updates.
|
|
342
339
|
// TODO(burdon): Is there a better (finer grained) way to do this?
|
|
343
340
|
//
|
|
344
|
-
EditorView.updateListener.of(({ view, state, changes }) => {
|
|
345
|
-
const cursorConverter = view.state.facet(CursorConverter);
|
|
346
|
-
|
|
341
|
+
EditorView.updateListener.of(({ view, state, startState, changes }) => {
|
|
347
342
|
//
|
|
348
343
|
// Test if need to recompute indexed range if document changes before the end of the range.
|
|
349
344
|
//
|
|
350
345
|
{
|
|
351
346
|
let mod = false;
|
|
347
|
+
const model = state.field(modelState);
|
|
352
348
|
const { active, ranges } = state.field(commentsStateField);
|
|
353
349
|
changes.iterChanges((from, to, from2, to2) => {
|
|
350
|
+
invariant(model);
|
|
354
351
|
ranges.forEach((range) => {
|
|
355
352
|
if (from2 === to2) {
|
|
356
|
-
const newRange = getRangeFromCursor(
|
|
353
|
+
const newRange = model.getRangeFromCursor!(range.cursor);
|
|
357
354
|
if (!newRange || newRange.to - newRange.from === 0) {
|
|
358
355
|
// TODO(burdon): Delete range if empty.
|
|
359
356
|
log.info('deleted comment', { thread: range.id });
|
|
@@ -361,7 +358,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
361
358
|
}
|
|
362
359
|
|
|
363
360
|
if (from <= range.to) {
|
|
364
|
-
const newRange = getRangeFromCursor(
|
|
361
|
+
const newRange = model.getRangeFromCursor!(range.cursor);
|
|
365
362
|
Object.assign(range, newRange);
|
|
366
363
|
mod = true;
|
|
367
364
|
}
|
|
@@ -406,16 +403,3 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
406
403
|
}),
|
|
407
404
|
];
|
|
408
405
|
};
|
|
409
|
-
|
|
410
|
-
const getCursorFromRange = (cursorConverter: CursorConverter, range: Range) => {
|
|
411
|
-
const from = cursorConverter.toCursor(range.from);
|
|
412
|
-
const to = cursorConverter.toCursor(range.to, -1);
|
|
413
|
-
return [from, to].join(':');
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
const getRangeFromCursor = (cursorConverter: CursorConverter, cursor: string) => {
|
|
417
|
-
const parts = cursor.split(':');
|
|
418
|
-
const from = cursorConverter.fromCursor(parts[0]);
|
|
419
|
-
const to = cursorConverter.fromCursor(parts[1]);
|
|
420
|
-
return from && to ? { from, to } : undefined;
|
|
421
|
-
};
|
package/src/extensions/index.ts
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
export * from './autocomplete';
|
|
6
|
-
export * from './automerge';
|
|
7
|
-
export * from './awareness';
|
|
8
6
|
export * from './basic';
|
|
9
7
|
export * from './blast';
|
|
10
8
|
export * from './code';
|
|
@@ -19,4 +17,3 @@ export * from './mermaid';
|
|
|
19
17
|
export * from './table';
|
|
20
18
|
export * from './tasklist';
|
|
21
19
|
export * from './typewriter';
|
|
22
|
-
export * from './yjs';
|
|
@@ -2,29 +2,19 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
5
|
+
import '@preact/signals-react'; // Register react integration
|
|
7
6
|
import { BroadcastChannelNetworkAdapter } from '@automerge/automerge-repo-network-broadcastchannel';
|
|
8
7
|
import { EditorView } from '@codemirror/view';
|
|
9
|
-
import '@preact/signals-react'; // Register react integration
|
|
10
8
|
import { basicSetup } from 'codemirror';
|
|
11
9
|
import get from 'lodash.get';
|
|
12
10
|
import React, { useEffect, useRef, useState } from 'react';
|
|
13
11
|
|
|
12
|
+
// TODO(burdon): Why separate imports?
|
|
14
13
|
import { type Prop } from '@dxos/automerge/automerge';
|
|
15
|
-
import {
|
|
16
|
-
import { Filter, setGlobalAutomergePreference } from '@dxos/echo-schema';
|
|
17
|
-
import { type PublicKey } from '@dxos/keys';
|
|
18
|
-
import { Expando, TextObject, useSpace } from '@dxos/react-client/echo';
|
|
19
|
-
import { useIdentity } from '@dxos/react-client/halo';
|
|
20
|
-
import { ClientRepeater } from '@dxos/react-client/testing';
|
|
21
|
-
import { withTheme } from '@dxos/storybook-utils';
|
|
14
|
+
import { type DocHandle, Repo } from '@dxos/automerge/automerge-repo';
|
|
22
15
|
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { useTextModel } from '../../hooks';
|
|
26
|
-
|
|
27
|
-
// TODO(burdon): Move to components.
|
|
16
|
+
import { type IDocHandle } from './handle';
|
|
17
|
+
import { automergePlugin } from './plugin';
|
|
28
18
|
|
|
29
19
|
type EditorProps = {
|
|
30
20
|
handle: IDocHandle;
|
|
@@ -38,7 +28,7 @@ const Editor = ({ handle, path }: EditorProps) => {
|
|
|
38
28
|
useEffect(() => {
|
|
39
29
|
const view = (editorRoot.current = new EditorView({
|
|
40
30
|
doc: get(handle.docSync()!, path),
|
|
41
|
-
extensions: [basicSetup,
|
|
31
|
+
extensions: [basicSetup, automergePlugin(handle, path)],
|
|
42
32
|
parent: containerRef.current as any,
|
|
43
33
|
}));
|
|
44
34
|
|
|
@@ -99,53 +89,3 @@ export default {
|
|
|
99
89
|
};
|
|
100
90
|
|
|
101
91
|
export const Default = {};
|
|
102
|
-
|
|
103
|
-
const EchoStory = ({ id, spaceKey }: { id: number; spaceKey: PublicKey }) => {
|
|
104
|
-
const identity = useIdentity();
|
|
105
|
-
const space = useSpace(spaceKey);
|
|
106
|
-
// TODO(dmaretskyi): useQuery doesn't work.
|
|
107
|
-
const [obj] = space?.db.query(Filter.from({ type: 'test' })).objects ?? [];
|
|
108
|
-
|
|
109
|
-
const model = useTextModel({
|
|
110
|
-
text: obj?.content,
|
|
111
|
-
identity,
|
|
112
|
-
space,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
if (!model) {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return (
|
|
120
|
-
// <div className={mx(fixedInsetFlexLayout, groupSurface)}>
|
|
121
|
-
<div className='flex justify-center overflow-y-scroll'>
|
|
122
|
-
<div className='flex flex-col w-[800px] py-16'>
|
|
123
|
-
<MarkdownEditor model={model} />
|
|
124
|
-
<div className='flex shrink-0 h-[300px]'></div>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
// </div>
|
|
128
|
-
);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
export const WithEcho = {
|
|
132
|
-
render: () => {
|
|
133
|
-
setGlobalAutomergePreference(true);
|
|
134
|
-
return (
|
|
135
|
-
<ClientRepeater
|
|
136
|
-
count={2}
|
|
137
|
-
createSpace
|
|
138
|
-
onCreateSpace={async (space) => {
|
|
139
|
-
space.db.add(
|
|
140
|
-
new Expando({
|
|
141
|
-
type: 'test',
|
|
142
|
-
content: new TextObject('Hello world!'),
|
|
143
|
-
}),
|
|
144
|
-
);
|
|
145
|
-
}}
|
|
146
|
-
Component={EchoStory}
|
|
147
|
-
/>
|
|
148
|
-
);
|
|
149
|
-
},
|
|
150
|
-
decorators: [withTheme],
|
|
151
|
-
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type * as A from '@dxos/automerge/automerge';
|
|
6
|
+
|
|
7
|
+
export type IDocHandle<T = any> = {
|
|
8
|
+
docSync(): A.Doc<T> | undefined;
|
|
9
|
+
change(callback: A.ChangeFn<T>, options?: A.ChangeOptions<T>): void;
|
|
10
|
+
changeAt(heads: A.Heads, callback: A.ChangeFn<T>, options?: A.ChangeOptions<T>): string[] | undefined;
|
|
11
|
+
|
|
12
|
+
addListener(event: 'change', listener: () => void): void;
|
|
13
|
+
removeListener(event: 'change', listener: () => void): void;
|
|
14
|
+
};
|
|
@@ -1,34 +1,57 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
|
-
// Copyright 2024 Automerge
|
|
4
|
-
// Ref: https://github.com/automerge/automerge-codemirror
|
|
5
3
|
//
|
|
6
4
|
|
|
7
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Annotation,
|
|
7
|
+
type EditorState,
|
|
8
|
+
type Extension,
|
|
9
|
+
Facet,
|
|
10
|
+
StateEffect,
|
|
11
|
+
StateField,
|
|
12
|
+
type Transaction,
|
|
13
|
+
type TransactionSpec,
|
|
14
|
+
} from '@codemirror/state';
|
|
8
15
|
import { ViewPlugin, type EditorView, type PluginValue, type ViewUpdate } from '@codemirror/view';
|
|
9
16
|
|
|
10
|
-
import
|
|
11
|
-
import { type Prop } from '@dxos/automerge/automerge';
|
|
17
|
+
import * as automerge from '@dxos/automerge/automerge';
|
|
18
|
+
import { type Heads, type Prop } from '@dxos/automerge/automerge';
|
|
12
19
|
|
|
13
|
-
import {
|
|
14
|
-
import { effectType, type IDocHandle, isReconcileTx, type Value } from './defs';
|
|
20
|
+
import { type IDocHandle } from './handle';
|
|
15
21
|
import { PatchSemaphore } from './semaphore';
|
|
16
|
-
|
|
22
|
+
|
|
23
|
+
export type Value = {
|
|
24
|
+
lastHeads: Heads;
|
|
25
|
+
path: Prop[];
|
|
26
|
+
unreconciledTransactions: Transaction[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type UpdateHeads = {
|
|
30
|
+
newHeads: Heads;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const effectType = StateEffect.define<UpdateHeads>({});
|
|
34
|
+
|
|
35
|
+
export const updateHeads = (newHeads: Heads): StateEffect<UpdateHeads> => effectType.of({ newHeads });
|
|
36
|
+
|
|
37
|
+
export const getLastHeads = (state: EditorState, field: Field): Heads => state.field(field).lastHeads;
|
|
38
|
+
|
|
39
|
+
export const getPath = (state: EditorState, field: Field): Prop[] => state.field(field).path;
|
|
40
|
+
|
|
41
|
+
export type Field = StateField<Value>;
|
|
17
42
|
|
|
18
43
|
const semaphoreFacet = Facet.define<PatchSemaphore, PatchSemaphore>({
|
|
19
44
|
combine: (values) => values.at(-1)!, // Take last.
|
|
20
45
|
});
|
|
21
46
|
|
|
22
|
-
export type
|
|
23
|
-
|
|
24
|
-
path: Prop[];
|
|
47
|
+
export type AutomergePlugin = {
|
|
48
|
+
extension: Extension;
|
|
25
49
|
};
|
|
26
50
|
|
|
27
|
-
export const
|
|
28
|
-
// TODO(burdon): Rename.
|
|
51
|
+
export const automergePlugin = (handle: IDocHandle, path: Prop[]): AutomergePlugin => {
|
|
29
52
|
const stateField: StateField<Value> = StateField.define({
|
|
30
53
|
create: () => ({
|
|
31
|
-
lastHeads:
|
|
54
|
+
lastHeads: automerge.getHeads(handle.docSync()!),
|
|
32
55
|
unreconciledTransactions: [],
|
|
33
56
|
path: path.slice(),
|
|
34
57
|
}),
|
|
@@ -84,11 +107,26 @@ export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
|
84
107
|
);
|
|
85
108
|
|
|
86
109
|
return {
|
|
87
|
-
extension: [
|
|
88
|
-
CursorConverter.of(cursorConverter(handle, path)),
|
|
89
|
-
semaphoreFacet.of(semaphore),
|
|
90
|
-
stateField,
|
|
91
|
-
viewPlugin,
|
|
92
|
-
],
|
|
110
|
+
extension: [stateField, semaphoreFacet.of(semaphore), viewPlugin],
|
|
93
111
|
};
|
|
94
112
|
};
|
|
113
|
+
|
|
114
|
+
export const reconcileAnnotationType = Annotation.define<unknown>();
|
|
115
|
+
|
|
116
|
+
export const isReconcileTx = (tr: Transaction): boolean => !!tr.annotation(reconcileAnnotationType);
|
|
117
|
+
|
|
118
|
+
export const makeReconcile = (tr: TransactionSpec) => {
|
|
119
|
+
if (tr.annotations != null) {
|
|
120
|
+
if (tr.annotations instanceof Array) {
|
|
121
|
+
tr.annotations = [...tr.annotations, reconcileAnnotationType.of({})];
|
|
122
|
+
} else {
|
|
123
|
+
tr.annotations = [tr.annotations, reconcileAnnotationType.of({})];
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
tr.annotations = [reconcileAnnotationType.of({})];
|
|
127
|
+
}
|
|
128
|
+
// return {
|
|
129
|
+
// ...tr,
|
|
130
|
+
// annotations: reconcileAnnotationType.of({})
|
|
131
|
+
// }
|
|
132
|
+
};
|
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
|
-
// Copyright 2024 Automerge
|
|
4
|
-
// Ref: https://github.com/automerge/automerge-codemirror
|
|
5
3
|
//
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
// TODO(burdon): Additional copyright?
|
|
6
|
+
|
|
8
7
|
import { type EditorView } from '@codemirror/view';
|
|
9
8
|
|
|
10
9
|
import { next as automerge } from '@dxos/automerge/automerge';
|
|
11
10
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
getPath,
|
|
15
|
-
isReconcileTx,
|
|
16
|
-
reconcileAnnotationType,
|
|
17
|
-
updateHeads,
|
|
18
|
-
type IDocHandle,
|
|
19
|
-
type Value,
|
|
20
|
-
} from './defs';
|
|
11
|
+
import { type IDocHandle } from './handle';
|
|
12
|
+
import { type Field, isReconcileTx, getPath, reconcileAnnotationType, updateHeads, getLastHeads } from './plugin';
|
|
21
13
|
import { updateAutomerge } from './update-automerge';
|
|
22
14
|
import { updateCodeMirror } from './update-codemirror';
|
|
23
15
|
|
|
@@ -30,10 +22,15 @@ type ChangeFn = (atHeads: Heads, change: (doc: Doc<unknown>) => void) => Heads |
|
|
|
30
22
|
* TODO(burdon): Comment.
|
|
31
23
|
*/
|
|
32
24
|
export class PatchSemaphore {
|
|
25
|
+
_field!: Field;
|
|
33
26
|
_inReconcile = false;
|
|
34
27
|
_queue: Array<ChangeFn> = [];
|
|
35
28
|
|
|
36
|
-
constructor(
|
|
29
|
+
constructor(field?: Field) {
|
|
30
|
+
if (field !== undefined) {
|
|
31
|
+
this._field = field;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
37
34
|
|
|
38
35
|
reconcile = (handle: IDocHandle, view: EditorView) => {
|
|
39
36
|
if (this._inReconcile) {
|
|
@@ -45,8 +42,9 @@ export class PatchSemaphore {
|
|
|
45
42
|
const oldHeads = getLastHeads(view.state, this._field);
|
|
46
43
|
let selection = view.state.selection;
|
|
47
44
|
|
|
48
|
-
// First undo all the unreconciled transactions.
|
|
49
45
|
const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
|
|
46
|
+
|
|
47
|
+
// First undo all the unreconciled transactions.
|
|
50
48
|
const toInvert = transactions.slice().reverse();
|
|
51
49
|
for (const tx of toInvert) {
|
|
52
50
|
const inverted = tx.changes.invert(tx.startState.doc);
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
|
-
// Copyright 2024 Automerge
|
|
4
|
-
// Ref: https://github.com/automerge/automerge-codemirror
|
|
5
3
|
//
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
// TODO(burdon): Additional copyright?
|
|
6
|
+
|
|
7
|
+
import { type EditorState, type Text, type Transaction } from '@codemirror/state';
|
|
8
8
|
|
|
9
9
|
import { next as am, type Heads } from '@dxos/automerge/automerge';
|
|
10
10
|
|
|
11
|
-
import { type IDocHandle
|
|
11
|
+
import { type IDocHandle } from './handle';
|
|
12
|
+
import { type Field } from './plugin';
|
|
12
13
|
|
|
13
14
|
export const updateAutomerge = (
|
|
14
|
-
field:
|
|
15
|
+
field: Field,
|
|
15
16
|
handle: IDocHandle,
|
|
16
17
|
transactions: Transaction[],
|
|
17
18
|
state: EditorState,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
|
-
// Copyright 2024 Automerge
|
|
4
|
-
// Ref: https://github.com/automerge/automerge-codemirror
|
|
5
3
|
//
|
|
6
4
|
|
|
5
|
+
// TODO(burdon): Additional copyright?
|
|
6
|
+
|
|
7
7
|
import { ChangeSet, type ChangeSpec, type EditorSelection, type EditorState } from '@codemirror/state';
|
|
8
8
|
import { type EditorView } from '@codemirror/view';
|
|
9
9
|
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
type SpliceTextPatch,
|
|
17
17
|
} from '@dxos/automerge/automerge';
|
|
18
18
|
|
|
19
|
-
import { reconcileAnnotationType } from './
|
|
19
|
+
import { reconcileAnnotationType } from './plugin';
|
|
20
20
|
|
|
21
21
|
export const updateCodeMirror = (view: EditorView, selection: EditorSelection, target: Prop[], patches: Patch[]) => {
|
|
22
22
|
for (const patch of patches) {
|
|
@@ -56,7 +56,6 @@ const handleInsert = (target: Prop[], patch: InsertPatch): Array<ChangeSpec> =>
|
|
|
56
56
|
if (index == null) {
|
|
57
57
|
return [];
|
|
58
58
|
}
|
|
59
|
-
|
|
60
59
|
const text = patch.values.map((v) => (v ? v.toString() : '')).join('');
|
|
61
60
|
return [{ from: index, to: index, insert: text }];
|
|
62
61
|
};
|
|
@@ -66,7 +65,6 @@ const handleSplice = (target: Prop[], patch: SpliceTextPatch): Array<ChangeSpec>
|
|
|
66
65
|
if (index == null) {
|
|
67
66
|
return [];
|
|
68
67
|
}
|
|
69
|
-
|
|
70
68
|
return [{ from: index, insert: patch.value }];
|
|
71
69
|
};
|
|
72
70
|
|
|
@@ -75,7 +73,6 @@ const handleDel = (target: Prop[], patch: DelPatch): Array<ChangeSpec> => {
|
|
|
75
73
|
if (index == null) {
|
|
76
74
|
return [];
|
|
77
75
|
}
|
|
78
|
-
|
|
79
76
|
const length = patch.length || 1;
|
|
80
77
|
return [{ from: index, to: index + length }];
|
|
81
78
|
};
|
|
@@ -85,32 +82,27 @@ const handlePut = (target: Prop[], patch: PutPatch, state: EditorState): Array<C
|
|
|
85
82
|
if (index == null) {
|
|
86
83
|
return [];
|
|
87
84
|
}
|
|
88
|
-
|
|
89
85
|
const length = state.doc.length;
|
|
90
86
|
if (typeof patch.value !== 'string') {
|
|
91
87
|
return []; // TODO(dmaretskyi): How to handle non string values?
|
|
92
88
|
}
|
|
93
|
-
|
|
94
89
|
return [{ from: 0, to: length, insert: patch.value as any }];
|
|
95
90
|
};
|
|
96
91
|
|
|
97
|
-
// If the path of the patch is of the form [path, <index>] then we know this is
|
|
98
|
-
// within the sequence given by path
|
|
92
|
+
// If the path of the patch is of the form [path, <index>] then we know this is
|
|
93
|
+
// a path to a character within the sequence given by path
|
|
99
94
|
const charPath = (textPath: Prop[], candidatePath: Prop[]): number | null => {
|
|
100
95
|
if (candidatePath.length !== textPath.length + 1) {
|
|
101
96
|
return null;
|
|
102
97
|
}
|
|
103
|
-
|
|
104
98
|
for (let i = 0; i < textPath.length; i++) {
|
|
105
99
|
if (textPath[i] !== candidatePath[i]) {
|
|
106
100
|
return null;
|
|
107
101
|
}
|
|
108
102
|
}
|
|
109
|
-
|
|
110
103
|
const index = candidatePath[candidatePath.length - 1];
|
|
111
104
|
if (typeof index === 'number') {
|
|
112
105
|
return index;
|
|
113
106
|
}
|
|
114
|
-
|
|
115
107
|
return null;
|
|
116
108
|
};
|