@dxos/react-ui-editor 0.3.11-main.42059c4 → 0.3.11-main.47875f3
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 +72 -57
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +2 -2
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/comments.stories.d.ts +1 -1
- package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/defs.d.ts +4 -4
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/semaphore.d.ts +9 -4
- package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +4 -9
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/package.json +23 -23
- package/src/components/TextEditor/TextEditor.tsx +10 -10
- package/src/components/TextEditor/comments.stories.tsx +48 -74
- package/src/extensions/automerge/automerge.ts +11 -11
- package/src/extensions/automerge/defs.ts +9 -13
- package/src/extensions/automerge/semaphore.ts +12 -10
- package/src/extensions/comments.ts +49 -61
- package/src/hooks/index.ts +1 -2
|
@@ -6,12 +6,11 @@ import '@dxosTheme';
|
|
|
6
6
|
|
|
7
7
|
import { type EditorView, keymap } from '@codemirror/view';
|
|
8
8
|
import { faker } from '@faker-js/faker';
|
|
9
|
-
import { Check
|
|
9
|
+
import { Check } from '@phosphor-icons/react';
|
|
10
10
|
import React, { type FC, useEffect, useMemo, useRef, useState } from 'react';
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { TextObject } from '@dxos/echo-schema';
|
|
13
13
|
import { PublicKey } from '@dxos/keys';
|
|
14
|
-
import { log } from '@dxos/log';
|
|
15
14
|
import { Button, DensityProvider } from '@dxos/react-ui';
|
|
16
15
|
import { fixedInsetFlexLayout, mx } from '@dxos/react-ui-theme';
|
|
17
16
|
import { withTheme } from '@dxos/storybook-utils';
|
|
@@ -31,10 +30,9 @@ const Editor: FC<{
|
|
|
31
30
|
commentSelected?: string;
|
|
32
31
|
commentRanges: CommentRange[];
|
|
33
32
|
onCreateComment: CommentsOptions['onCreate'];
|
|
34
|
-
|
|
35
|
-
onUpdateComment: CommentsOptions['onUpdate'];
|
|
33
|
+
onMoveComment: CommentsOptions['onMove'];
|
|
36
34
|
onSelectComment: CommentsOptions['onSelect'];
|
|
37
|
-
}> = ({ item, commentSelected, commentRanges, onCreateComment,
|
|
35
|
+
}> = ({ item, commentSelected, commentRanges, onCreateComment, onMoveComment, onSelectComment }) => {
|
|
38
36
|
const model = useTextModel({ text: item.text });
|
|
39
37
|
const editorRef = useRef<EditorView>(null);
|
|
40
38
|
const [selected, setSelected] = useState<string>();
|
|
@@ -58,8 +56,7 @@ const Editor: FC<{
|
|
|
58
56
|
return [
|
|
59
57
|
comments({
|
|
60
58
|
onCreate: onCreateComment,
|
|
61
|
-
|
|
62
|
-
onUpdate: onUpdateComment,
|
|
59
|
+
onMove: onMoveComment,
|
|
63
60
|
onSelect: onSelectComment,
|
|
64
61
|
}),
|
|
65
62
|
];
|
|
@@ -87,7 +84,6 @@ type CommentThread = {
|
|
|
87
84
|
yPos?: number;
|
|
88
85
|
selection?: Range;
|
|
89
86
|
messages: TextObject[];
|
|
90
|
-
deleted?: boolean;
|
|
91
87
|
};
|
|
92
88
|
|
|
93
89
|
const Thread: FC<{
|
|
@@ -96,17 +92,23 @@ const Thread: FC<{
|
|
|
96
92
|
onSelect: () => void;
|
|
97
93
|
onResolve: () => void;
|
|
98
94
|
}> = ({ thread, selected, onSelect, onResolve }) => {
|
|
99
|
-
const [focus, setFocus] = useState(false);
|
|
100
95
|
const [item, setItem] = useState({ text: new TextObject() });
|
|
101
96
|
const model = useTextModel({ text: item.text });
|
|
102
97
|
const editorRef = useRef<EditorView>(null);
|
|
103
98
|
|
|
99
|
+
// TODO(burdon): Hack to focus (view is recreated when text object changes).
|
|
100
|
+
const focus = () => {
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
editorRef.current?.focus();
|
|
103
|
+
}, 100);
|
|
104
|
+
};
|
|
105
|
+
|
|
104
106
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
105
107
|
useEffect(() => {
|
|
106
108
|
if (selected) {
|
|
107
109
|
containerRef.current?.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
108
110
|
if (thread.messages.length === 0) {
|
|
109
|
-
|
|
111
|
+
focus();
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
}, [selected]);
|
|
@@ -116,7 +118,7 @@ const Thread: FC<{
|
|
|
116
118
|
if (text?.length) {
|
|
117
119
|
thread.messages.push(item.text);
|
|
118
120
|
setItem({ text: new TextObject() });
|
|
119
|
-
|
|
121
|
+
focus();
|
|
120
122
|
}
|
|
121
123
|
};
|
|
122
124
|
|
|
@@ -125,49 +127,41 @@ const Thread: FC<{
|
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
return (
|
|
128
|
-
<div
|
|
129
|
-
className=
|
|
130
|
-
'flex flex-col m-1 rounded shadow divide-y bg-white',
|
|
131
|
-
selected && 'ring',
|
|
132
|
-
thread.deleted && 'opacity-50',
|
|
133
|
-
)}
|
|
134
|
-
>
|
|
135
|
-
<div className='flex p-2 gap-2 items-center text-xs font-mono text-neutral-500 font-thin'>
|
|
130
|
+
<div className={mx('flex flex-col m-1 rounded shadow divide-y bg-white', selected && 'ring')}>
|
|
131
|
+
<div className='flex p-2 text-xs font-mono gap-2 text-neutral-500 font-thin'>
|
|
136
132
|
<span>id:{thread.id.slice(0, 4)}</span>
|
|
137
133
|
<span>from:{thread.selection?.from}</span>
|
|
138
134
|
<span>to:{thread.selection?.to}</span>
|
|
139
135
|
<span>y:{thread.yPos}</span>
|
|
140
|
-
<span className='grow' />
|
|
141
|
-
{thread.deleted && <Trash />}
|
|
142
136
|
</div>
|
|
143
137
|
|
|
144
138
|
{thread.messages.map((message, i) => (
|
|
145
139
|
// TODO(burdon): Fix default editor padding so content doesn't jump on creating message.
|
|
146
140
|
<div key={i} className='p-2' onClick={() => onSelect()}>
|
|
147
|
-
{
|
|
141
|
+
{message.text}
|
|
148
142
|
</div>
|
|
149
143
|
))}
|
|
150
144
|
|
|
151
|
-
<div ref={containerRef} onClick={() => onSelect()} className='flex'>
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
145
|
+
<div ref={containerRef} onClick={() => onSelect()} className='flex py-1'>
|
|
146
|
+
<div className='grow'>
|
|
147
|
+
<TextEditor
|
|
148
|
+
ref={editorRef}
|
|
149
|
+
model={model}
|
|
150
|
+
placeholder={'Enter comment...'}
|
|
151
|
+
extensions={[
|
|
152
|
+
keymap.of([
|
|
153
|
+
{
|
|
154
|
+
key: 'Enter',
|
|
155
|
+
run: () => {
|
|
156
|
+
handleCreateMessage();
|
|
157
|
+
return true;
|
|
158
|
+
},
|
|
165
159
|
},
|
|
166
|
-
|
|
167
|
-
]
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
<Button variant='ghost' classNames='px-1' title='Resolve' onClick={onResolve}>
|
|
160
|
+
]),
|
|
161
|
+
]}
|
|
162
|
+
/>
|
|
163
|
+
</div>
|
|
164
|
+
<Button variant='ghost' classNames='px-1 mr-1' title='Resolve' onClick={onResolve}>
|
|
171
165
|
<Check />
|
|
172
166
|
</Button>
|
|
173
167
|
</div>
|
|
@@ -191,7 +185,7 @@ const Sidebar: FC<{
|
|
|
191
185
|
|
|
192
186
|
return (
|
|
193
187
|
<DensityProvider density='fine'>
|
|
194
|
-
<div className='flex flex-col grow overflow-y-scroll py-4 gap-4'>
|
|
188
|
+
<div className='flex flex-col grow overflow-y-scroll py-4 gap-4 pr-4'>
|
|
195
189
|
{sortedThreads.map((thread) => (
|
|
196
190
|
<Thread
|
|
197
191
|
key={thread.id}
|
|
@@ -226,7 +220,6 @@ const Story = ({ text, autoCreate }: StoryProps) => {
|
|
|
226
220
|
|
|
227
221
|
const handleCreateComment: CommentsOptions['onCreate'] = (cursor, location) => {
|
|
228
222
|
const id = PublicKey.random().toHex();
|
|
229
|
-
log.info('create', { id: id.slice(0, 4), cursor });
|
|
230
223
|
setThreads((threads) => [
|
|
231
224
|
...threads,
|
|
232
225
|
{
|
|
@@ -234,7 +227,7 @@ const Story = ({ text, autoCreate }: StoryProps) => {
|
|
|
234
227
|
range: { id, cursor },
|
|
235
228
|
yPos: location ? Math.floor(location.top) : undefined,
|
|
236
229
|
messages: autoCreate
|
|
237
|
-
? faker.helpers.multiple(() => new TextObject(faker.lorem.sentence()), { count: { min:
|
|
230
|
+
? faker.helpers.multiple(() => new TextObject(faker.lorem.sentence()), { count: { min: 2, max: 5 } })
|
|
238
231
|
: [],
|
|
239
232
|
},
|
|
240
233
|
]);
|
|
@@ -242,50 +235,32 @@ const Story = ({ text, autoCreate }: StoryProps) => {
|
|
|
242
235
|
return id;
|
|
243
236
|
};
|
|
244
237
|
|
|
245
|
-
|
|
246
|
-
|
|
238
|
+
// TODO(burdon): Scroll sidebar on select.
|
|
239
|
+
const handleSelectComment: CommentsOptions['onSelect'] = ({ active, closest, ranges }) => {
|
|
247
240
|
setThreads((threads) =>
|
|
248
241
|
threads.map((thread) => {
|
|
249
|
-
|
|
250
|
-
|
|
242
|
+
const range = ranges.find((range) => range.id === thread.range.id);
|
|
243
|
+
if (range) {
|
|
244
|
+
thread.yPos = range.location ? Math.floor(range.location.top) : undefined;
|
|
245
|
+
thread.selection = { from: range.from, to: range.to };
|
|
251
246
|
}
|
|
252
247
|
|
|
253
248
|
return thread;
|
|
254
249
|
}),
|
|
255
250
|
);
|
|
256
251
|
|
|
257
|
-
setSelected(
|
|
252
|
+
setSelected(active ?? closest);
|
|
258
253
|
};
|
|
259
254
|
|
|
260
|
-
const
|
|
261
|
-
log.info('update', { id: id.slice(0, 4), cursor });
|
|
255
|
+
const handleMoveComment: CommentsOptions['onMove'] = (id, cursor) => {
|
|
262
256
|
setThreads((threads) =>
|
|
263
257
|
threads.map((thread) => {
|
|
264
258
|
if (thread.id === id) {
|
|
265
259
|
thread.range.cursor = cursor;
|
|
266
|
-
delete thread.deleted;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return thread;
|
|
270
|
-
}),
|
|
271
|
-
);
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
const handleSelectComment: CommentsOptions['onSelect'] = ({ active, closest, ranges }) => {
|
|
275
|
-
log.info('select', { active: active?.slice(0, 4), closest: closest?.slice(0, 4) });
|
|
276
|
-
setThreads((threads) =>
|
|
277
|
-
threads.map((thread) => {
|
|
278
|
-
const range = ranges.find((range) => range.id === thread.range.id);
|
|
279
|
-
if (range) {
|
|
280
|
-
thread.yPos = range.location ? Math.floor(range.location.top) : undefined;
|
|
281
|
-
thread.selection = { from: range.from, to: range.to };
|
|
282
260
|
}
|
|
283
|
-
|
|
284
261
|
return thread;
|
|
285
262
|
}),
|
|
286
263
|
);
|
|
287
|
-
|
|
288
|
-
setSelected(active ?? closest);
|
|
289
264
|
};
|
|
290
265
|
|
|
291
266
|
const handleSelectThread = (id: string) => {
|
|
@@ -306,8 +281,7 @@ const Story = ({ text, autoCreate }: StoryProps) => {
|
|
|
306
281
|
commentSelected={selected}
|
|
307
282
|
commentRanges={commentRanges}
|
|
308
283
|
onCreateComment={handleCreateComment}
|
|
309
|
-
|
|
310
|
-
onUpdateComment={handleUpdateComment}
|
|
284
|
+
onMoveComment={handleMoveComment}
|
|
311
285
|
onSelectComment={handleSelectComment}
|
|
312
286
|
/>
|
|
313
287
|
</div>
|
|
@@ -347,7 +321,7 @@ export const Default = {
|
|
|
347
321
|
},
|
|
348
322
|
};
|
|
349
323
|
|
|
350
|
-
export const
|
|
324
|
+
export const Demo = {
|
|
351
325
|
args: {
|
|
352
326
|
text: document,
|
|
353
327
|
autoCreate: true,
|
|
@@ -4,37 +4,40 @@
|
|
|
4
4
|
// Ref: https://github.com/automerge/automerge-codemirror
|
|
5
5
|
//
|
|
6
6
|
|
|
7
|
-
import { StateField, type Extension, type Transaction } from '@codemirror/state';
|
|
7
|
+
import { Facet, StateField, type Extension, type Transaction } from '@codemirror/state';
|
|
8
8
|
import { ViewPlugin, type EditorView, type PluginValue, type ViewUpdate } from '@codemirror/view';
|
|
9
9
|
|
|
10
10
|
import { next as A } from '@dxos/automerge/automerge';
|
|
11
11
|
import { type Prop } from '@dxos/automerge/automerge';
|
|
12
12
|
|
|
13
13
|
import { cursorConverter } from './cursor';
|
|
14
|
-
import { effectType, type IDocHandle, isReconcileTx,
|
|
14
|
+
import { effectType, type IDocHandle, isReconcileTx, type Value } from './defs';
|
|
15
15
|
import { PatchSemaphore } from './semaphore';
|
|
16
16
|
import { Cursor } from '../cursor';
|
|
17
17
|
|
|
18
|
+
const semaphoreFacet = Facet.define<PatchSemaphore, PatchSemaphore>({
|
|
19
|
+
combine: (values) => values.at(-1)!, // Take last.
|
|
20
|
+
});
|
|
21
|
+
|
|
18
22
|
export type AutomergeOptions = {
|
|
19
23
|
handle: IDocHandle;
|
|
20
24
|
path: Prop[];
|
|
21
25
|
};
|
|
22
26
|
|
|
23
27
|
export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
28
|
+
// TODO(burdon): Rename.
|
|
24
29
|
const stateField: StateField<Value> = StateField.define({
|
|
25
30
|
create: () => ({
|
|
26
31
|
lastHeads: A.getHeads(handle.docSync()!),
|
|
27
32
|
unreconciledTransactions: [],
|
|
28
33
|
path: path.slice(),
|
|
29
34
|
}),
|
|
30
|
-
|
|
31
35
|
update: (value: Value, tr: Transaction) => {
|
|
32
36
|
const result: Value = {
|
|
33
37
|
lastHeads: value.lastHeads,
|
|
34
38
|
unreconciledTransactions: value.unreconciledTransactions.slice(),
|
|
35
39
|
path: path.slice(),
|
|
36
40
|
};
|
|
37
|
-
|
|
38
41
|
let clearUnreconciled = false;
|
|
39
42
|
for (const effect of tr.effects) {
|
|
40
43
|
if (effect.is(effectType)) {
|
|
@@ -42,7 +45,6 @@ export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
|
42
45
|
clearUnreconciled = true;
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
|
-
|
|
46
48
|
if (clearUnreconciled) {
|
|
47
49
|
result.unreconciledTransactions = [];
|
|
48
50
|
} else {
|
|
@@ -50,15 +52,14 @@ export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
|
50
52
|
result.unreconciledTransactions.push(tr);
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
|
-
|
|
54
55
|
return result;
|
|
55
56
|
},
|
|
56
57
|
});
|
|
57
58
|
|
|
58
|
-
const semaphore = new PatchSemaphore(
|
|
59
|
+
const semaphore = new PatchSemaphore(stateField);
|
|
59
60
|
|
|
60
61
|
const viewPlugin = ViewPlugin.fromClass(
|
|
61
|
-
class
|
|
62
|
+
class AutomergeCodemirrorViewPlugin implements PluginValue {
|
|
62
63
|
constructor(private readonly _view: EditorView) {
|
|
63
64
|
handle.addListener('change', this._handleChange);
|
|
64
65
|
}
|
|
@@ -66,9 +67,8 @@ export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
|
66
67
|
update(update: ViewUpdate) {
|
|
67
68
|
if (update.transactions.length > 0 && update.transactions.some((tr) => !isReconcileTx(tr))) {
|
|
68
69
|
// TODO(burdon): This is a hack to ensure that the view is updated after the transaction is applied.
|
|
69
|
-
// Cannot trigger update while update is in progress.
|
|
70
70
|
queueMicrotask(() => {
|
|
71
|
-
this._view.state.facet(semaphoreFacet).reconcile(this._view);
|
|
71
|
+
this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -78,7 +78,7 @@ export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
private _handleChange = () => {
|
|
81
|
-
this._view.state.facet(semaphoreFacet).reconcile(this._view);
|
|
81
|
+
this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
|
|
82
82
|
};
|
|
83
83
|
},
|
|
84
84
|
);
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
|
-
// Copyright 2024 Automerge
|
|
4
3
|
// Ref: https://github.com/automerge/automerge-codemirror
|
|
5
4
|
//
|
|
6
5
|
|
|
7
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
Annotation,
|
|
8
|
+
StateEffect,
|
|
9
|
+
type StateField,
|
|
10
|
+
type EditorState,
|
|
11
|
+
type Transaction,
|
|
12
|
+
type TransactionSpec,
|
|
13
|
+
} from '@codemirror/state';
|
|
8
14
|
|
|
9
15
|
import { type ChangeFn, type ChangeOptions, type Doc, type Heads, type Prop } from '@dxos/automerge/automerge';
|
|
10
16
|
|
|
11
|
-
import { type PatchSemaphore } from './semaphore';
|
|
12
|
-
|
|
13
17
|
export type Value = {
|
|
14
18
|
lastHeads: Heads;
|
|
15
19
|
path: Prop[];
|
|
16
20
|
unreconciledTransactions: Transaction[];
|
|
17
21
|
};
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
type UpdateHeads = {
|
|
20
24
|
newHeads: Heads;
|
|
21
25
|
};
|
|
22
26
|
|
|
@@ -32,12 +36,6 @@ export const reconcileAnnotationType = Annotation.define<unknown>();
|
|
|
32
36
|
|
|
33
37
|
export const isReconcileTx = (tr: Transaction): boolean => !!tr.annotation(reconcileAnnotationType);
|
|
34
38
|
|
|
35
|
-
// TODO(burdon): Rename sync/create generic def (like cursor) to decouple automerge deps.
|
|
36
|
-
export const semaphoreFacet = Facet.define<PatchSemaphore, PatchSemaphore>({
|
|
37
|
-
combine: (values) => values.at(-1)!, // Take last.
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
/*
|
|
41
39
|
export const makeReconcile = (tr: TransactionSpec) => {
|
|
42
40
|
if (tr.annotations != null) {
|
|
43
41
|
if (tr.annotations instanceof Array) {
|
|
@@ -48,13 +46,11 @@ export const makeReconcile = (tr: TransactionSpec) => {
|
|
|
48
46
|
} else {
|
|
49
47
|
tr.annotations = [reconcileAnnotationType.of({})];
|
|
50
48
|
}
|
|
51
|
-
|
|
52
49
|
// return {
|
|
53
50
|
// ...tr,
|
|
54
51
|
// annotations: reconcileAnnotationType.of({})
|
|
55
52
|
// }
|
|
56
53
|
};
|
|
57
|
-
*/
|
|
58
54
|
|
|
59
55
|
export type IDocHandle<T = any> = {
|
|
60
56
|
docSync(): Doc<T> | undefined;
|
|
@@ -21,23 +21,27 @@ import {
|
|
|
21
21
|
import { updateAutomerge } from './update-automerge';
|
|
22
22
|
import { updateCodeMirror } from './update-codemirror';
|
|
23
23
|
|
|
24
|
+
type Doc<T> = automerge.Doc<T>;
|
|
25
|
+
type Heads = automerge.Heads;
|
|
26
|
+
|
|
27
|
+
type ChangeFn = (atHeads: Heads, change: (doc: Doc<unknown>) => void) => Heads | undefined;
|
|
28
|
+
|
|
24
29
|
/**
|
|
25
|
-
*
|
|
30
|
+
* TODO(burdon): Comment.
|
|
26
31
|
*/
|
|
27
32
|
export class PatchSemaphore {
|
|
28
33
|
_inReconcile = false;
|
|
34
|
+
_queue: Array<ChangeFn> = [];
|
|
29
35
|
|
|
30
|
-
constructor(private readonly
|
|
36
|
+
constructor(private readonly _field: StateField<Value>) {}
|
|
31
37
|
|
|
32
|
-
reconcile = (view: EditorView) => {
|
|
38
|
+
reconcile = (handle: IDocHandle, view: EditorView) => {
|
|
33
39
|
if (this._inReconcile) {
|
|
34
40
|
return;
|
|
35
41
|
}
|
|
36
42
|
this._inReconcile = true;
|
|
37
43
|
|
|
38
44
|
const path = getPath(view.state, this._field);
|
|
39
|
-
|
|
40
|
-
// Get the heads before the unreconciled transactions are applied.
|
|
41
45
|
const oldHeads = getLastHeads(view.state, this._field);
|
|
42
46
|
let selection = view.state.selection;
|
|
43
47
|
|
|
@@ -54,18 +58,16 @@ export class PatchSemaphore {
|
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
// Apply the unreconciled transactions to the document.
|
|
57
|
-
let newHeads = updateAutomerge(this._field,
|
|
61
|
+
let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
|
|
58
62
|
|
|
59
63
|
// NOTE: null and undefined each come from automerge and repo respectively.
|
|
60
64
|
if (newHeads === null || newHeads === undefined) {
|
|
61
65
|
// TODO: @alexjg this is the call that's resetting the editor state on click
|
|
62
|
-
newHeads = automerge.getHeads(
|
|
66
|
+
newHeads = automerge.getHeads(handle.docSync()!);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
// Now get the diff between the updated state of the document and the heads and apply that to the codemirror doc.
|
|
66
|
-
const diff = automerge.equals(oldHeads, newHeads)
|
|
67
|
-
? []
|
|
68
|
-
: automerge.diff(this._handle.docSync()!, oldHeads, newHeads);
|
|
70
|
+
const diff = automerge.equals(oldHeads, newHeads) ? [] : automerge.diff(handle.docSync()!, oldHeads, newHeads);
|
|
69
71
|
updateCodeMirror(view, selection, path, diff);
|
|
70
72
|
|
|
71
73
|
view.dispatch({
|