mobx-keystone-yjs 1.3.1 → 1.5.0
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/CHANGELOG.md +9 -1
- package/dist/mobx-keystone-yjs.esm.js +318 -36
- package/dist/mobx-keystone-yjs.esm.mjs +318 -36
- package/dist/mobx-keystone-yjs.umd.js +317 -37
- package/dist/types/binding/YjsTextModel.d.ts +39 -0
- package/dist/types/binding/bindYjsToMobxKeystone.d.ts +23 -2
- package/dist/types/binding/convertJsonToYjsData.d.ts +16 -4
- package/dist/types/binding/convertYjsDataToJson.d.ts +4 -0
- package/dist/types/binding/resolveYjsPath.d.ts +1 -0
- package/dist/types/binding/yjsBindingContext.d.ts +27 -2
- package/dist/types/index.d.ts +2 -1
- package/dist/types/jsonTypes.d.ts +5 -5
- package/dist/types/utils/getOrCreateYjsCollectionAtom.d.ts +1 -0
- package/package.json +12 -8
- package/src/binding/YjsTextModel.ts +248 -0
- package/src/binding/applyMobxKeystonePatchToYjsObject.ts +15 -9
- package/src/binding/bindYjsToMobxKeystone.ts +53 -18
- package/src/binding/convertJsonToYjsData.ts +43 -15
- package/src/binding/convertYjsDataToJson.ts +31 -0
- package/src/binding/convertYjsEventToPatches.ts +15 -4
- package/src/binding/resolveYjsPath.ts +27 -0
- package/src/binding/yjsBindingContext.ts +32 -2
- package/src/index.ts +10 -9
- package/src/jsonTypes.ts +9 -4
- package/src/utils/getOrCreateYjsCollectionAtom.ts +27 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { IAtom } from "mobx";
|
|
2
|
+
import { Frozen } from "mobx-keystone";
|
|
3
|
+
import * as Y from "yjs";
|
|
4
|
+
export declare const yjsTextModelId = "mobx-keystone-yjs/YjsTextModel";
|
|
5
|
+
declare const YjsTextModel_base: import("mobx-keystone")._Model<unknown, {
|
|
6
|
+
deltaList: import("mobx-keystone").OptionalModelProp<Frozen<unknown[]>[]>;
|
|
7
|
+
}, never, never>;
|
|
8
|
+
/**
|
|
9
|
+
* A mobx-keystone model that represents a Yjs.Text object.
|
|
10
|
+
*/
|
|
11
|
+
export declare class YjsTextModel extends YjsTextModel_base {
|
|
12
|
+
/**
|
|
13
|
+
* Helper function to create a YjsTextModel instance with a simple text.
|
|
14
|
+
*/
|
|
15
|
+
static withText(text: string): YjsTextModel;
|
|
16
|
+
/**
|
|
17
|
+
* The Y.js path from the bound object to the YjsTextModel instance.
|
|
18
|
+
*/
|
|
19
|
+
private get _yjsObjectPath();
|
|
20
|
+
/**
|
|
21
|
+
* The Yjs.Text object present at this mobx-keystone node's path.
|
|
22
|
+
*/
|
|
23
|
+
private get _yjsObjectAtPath();
|
|
24
|
+
/**
|
|
25
|
+
* The Yjs.Text object represented by this mobx-keystone node.
|
|
26
|
+
*/
|
|
27
|
+
get yjsText(): Y.Text;
|
|
28
|
+
/**
|
|
29
|
+
* Atom that gets changed when the associated Y.js text changes.
|
|
30
|
+
*/
|
|
31
|
+
yjsTextChangedAtom: IAtom;
|
|
32
|
+
/**
|
|
33
|
+
* The text value of the Yjs.Text object.
|
|
34
|
+
* Shortcut for `yjsText.toString()`, but computed.
|
|
35
|
+
*/
|
|
36
|
+
get text(): string;
|
|
37
|
+
protected onInit(): () => void;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
import { AnyDataModel, AnyModel, AnyStandardType, ModelClass, TypeToData } from "mobx-keystone";
|
|
2
2
|
import * as Y from "yjs";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a bidirectional binding between a Y.js data structure and a mobx-keystone model.
|
|
5
|
+
*/
|
|
3
6
|
export declare function bindYjsToMobxKeystone<TType extends AnyStandardType | ModelClass<AnyModel> | ModelClass<AnyDataModel>>({ yjsDoc, yjsObject, mobxKeystoneType, }: {
|
|
7
|
+
/**
|
|
8
|
+
* The Y.js document.
|
|
9
|
+
*/
|
|
4
10
|
yjsDoc: Y.Doc;
|
|
5
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The bound Y.js data structure.
|
|
13
|
+
*/
|
|
14
|
+
yjsObject: Y.Map<unknown> | Y.Array<unknown> | Y.Text;
|
|
15
|
+
/**
|
|
16
|
+
* The mobx-keystone model type.
|
|
17
|
+
*/
|
|
6
18
|
mobxKeystoneType: TType;
|
|
7
19
|
}): {
|
|
20
|
+
/**
|
|
21
|
+
* The bound mobx-keystone instance.
|
|
22
|
+
*/
|
|
8
23
|
boundObject: TypeToData<TType>;
|
|
9
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Disposes the binding.
|
|
26
|
+
*/
|
|
27
|
+
dispose: () => void;
|
|
28
|
+
/**
|
|
29
|
+
* The Y.js origin symbol used for binding transactions.
|
|
30
|
+
*/
|
|
10
31
|
yjsOrigin: symbol;
|
|
11
32
|
};
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import * as Y from "yjs";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { YjsData } from "./convertYjsDataToJson";
|
|
3
|
+
import { JsonArrayWithUndefined, JsonObjectWithUndefined, JsonValueWithUndefined } from "jsonTypes";
|
|
4
|
+
/**
|
|
5
|
+
* Converts a JSON value to a Y.js data structure.
|
|
6
|
+
* Objects are converted to Y.Maps, arrays to Y.Arrays, primitives are untouched.
|
|
7
|
+
* Frozen values are a special case and they are kept as immutable plain values.
|
|
8
|
+
*/
|
|
9
|
+
export declare function convertJsonToYjsData(v: JsonValueWithUndefined | undefined): YjsData;
|
|
10
|
+
/**
|
|
11
|
+
* Applies a JSON array to a Y.Array, using the convertJsonToYjsData to convert the values.
|
|
12
|
+
*/
|
|
13
|
+
export declare function applyJsonArrayToYArray(dest: Y.Array<unknown>, source: JsonArrayWithUndefined): void;
|
|
14
|
+
/**
|
|
15
|
+
* Applies a JSON object to a Y.Map, using the convertJsonToYjsData to convert the values.
|
|
16
|
+
*/
|
|
17
|
+
export declare function applyJsonObjectToYMap(dest: Y.Map<unknown>, source: JsonObjectWithUndefined): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveYjsPath(yjsObject: unknown, path: readonly (string | number)[]): unknown;
|
|
@@ -1,10 +1,35 @@
|
|
|
1
1
|
import { AnyType } from "mobx-keystone";
|
|
2
2
|
import * as Y from "yjs";
|
|
3
|
+
/**
|
|
4
|
+
* Context with info on how a mobx-keystone model is bound to a Y.js data structure.
|
|
5
|
+
*/
|
|
3
6
|
export interface YjsBindingContext {
|
|
7
|
+
/**
|
|
8
|
+
* The Y.js document.
|
|
9
|
+
*/
|
|
4
10
|
yjsDoc: Y.Doc;
|
|
5
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The bound Y.js data structure.
|
|
13
|
+
*/
|
|
14
|
+
yjsObject: Y.Map<unknown> | Y.Array<unknown> | Y.Text;
|
|
15
|
+
/**
|
|
16
|
+
* The mobx-keystone model type.
|
|
17
|
+
*/
|
|
6
18
|
mobxKeystoneType: AnyType;
|
|
19
|
+
/**
|
|
20
|
+
* The origin symbol used for transactions.
|
|
21
|
+
*/
|
|
7
22
|
yjsOrigin: symbol;
|
|
8
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The bound mobx-keystone instance.
|
|
25
|
+
*/
|
|
26
|
+
boundObject: unknown;
|
|
27
|
+
/**
|
|
28
|
+
* Whether we are currently applying Y.js changes to the mobx-keystone model.
|
|
29
|
+
*/
|
|
30
|
+
isApplyingYjsChangesToMobxKeystone: boolean;
|
|
9
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Context with info on how a mobx-keystone model is bound to a Y.js data structure.
|
|
34
|
+
*/
|
|
10
35
|
export declare const yjsBindingContext: import("mobx-keystone").Context<YjsBindingContext | undefined>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { YjsTextModel, yjsTextModelId } from "./binding/YjsTextModel";
|
|
1
2
|
export { bindYjsToMobxKeystone } from "./binding/bindYjsToMobxKeystone";
|
|
2
|
-
export {
|
|
3
|
+
export { applyJsonArrayToYArray, applyJsonObjectToYMap, convertJsonToYjsData, } from "./binding/convertJsonToYjsData";
|
|
3
4
|
export { yjsBindingContext } from "./binding/yjsBindingContext";
|
|
4
5
|
export type { YjsBindingContext } from "./binding/yjsBindingContext";
|
|
5
6
|
export { MobxKeystoneYjsError } from "./utils/error";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type
|
|
2
|
-
export type
|
|
3
|
-
export type
|
|
4
|
-
[key: string]:
|
|
1
|
+
export type JsonPrimitiveWithUndefined = string | number | boolean | null | undefined;
|
|
2
|
+
export type JsonValueWithUndefined = JsonPrimitiveWithUndefined | JsonObjectWithUndefined | JsonArrayWithUndefined;
|
|
3
|
+
export type JsonObjectWithUndefined = {
|
|
4
|
+
[key: string]: JsonValueWithUndefined;
|
|
5
5
|
};
|
|
6
|
-
export interface
|
|
6
|
+
export interface JsonArrayWithUndefined extends Array<JsonValueWithUndefined> {
|
|
7
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mobx-keystone-yjs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Yjs bindings for mobx-keystone",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mobx",
|
|
@@ -60,23 +60,27 @@
|
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"mobx": "^6.0.0 || ^5.0.0 || ^4.0.0",
|
|
63
|
-
"mobx-keystone": "^1.
|
|
63
|
+
"mobx-keystone": "^1.9.0",
|
|
64
64
|
"yjs": "^13.0.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@
|
|
68
|
-
"@
|
|
67
|
+
"@babel/core": "^7.24.4",
|
|
68
|
+
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
69
|
+
"@babel/plugin-proposal-decorators": "^7.24.1",
|
|
70
|
+
"@babel/preset-env": "^7.24.4",
|
|
71
|
+
"@babel/preset-typescript": "^7.24.1",
|
|
72
|
+
"@types/jest": "^29.5.12",
|
|
73
|
+
"@types/node": "^20.12.6",
|
|
69
74
|
"babel-jest": "^29.7.0",
|
|
70
75
|
"jest": "^29.7.0",
|
|
71
|
-
"mobx": "^6.12.0",
|
|
72
76
|
"mobx-keystone": "workspace:packages/lib",
|
|
73
77
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
74
78
|
"shx": "^0.3.4",
|
|
75
79
|
"spec.ts": "^1.1.3",
|
|
76
|
-
"ts-jest": "^29.1.
|
|
80
|
+
"ts-jest": "^29.1.2",
|
|
77
81
|
"ts-node": "^10.9.2",
|
|
78
|
-
"typescript": "^5.
|
|
79
|
-
"vite": "^5.
|
|
82
|
+
"typescript": "^5.4.4",
|
|
83
|
+
"vite": "^5.2.8"
|
|
80
84
|
},
|
|
81
85
|
"dependencies": {
|
|
82
86
|
"tslib": "^2.6.2"
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { IAtom, computed, createAtom, observe, reaction } from "mobx"
|
|
2
|
+
import {
|
|
3
|
+
Frozen,
|
|
4
|
+
Model,
|
|
5
|
+
frozen,
|
|
6
|
+
getParentToChildPath,
|
|
7
|
+
model,
|
|
8
|
+
onSnapshot,
|
|
9
|
+
tProp,
|
|
10
|
+
types,
|
|
11
|
+
} from "mobx-keystone"
|
|
12
|
+
import * as Y from "yjs"
|
|
13
|
+
import { failure } from "../utils/error"
|
|
14
|
+
import { YjsBindingContext, yjsBindingContext } from "./yjsBindingContext"
|
|
15
|
+
import { resolveYjsPath } from "./resolveYjsPath"
|
|
16
|
+
|
|
17
|
+
// Delta[][], since each single change is a Delta[]
|
|
18
|
+
// we use frozen so that we can reuse each delta change
|
|
19
|
+
const deltaListType = types.array(types.frozen(types.unchecked<unknown[]>()))
|
|
20
|
+
|
|
21
|
+
export const yjsTextModelId = "mobx-keystone-yjs/YjsTextModel"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A mobx-keystone model that represents a Yjs.Text object.
|
|
25
|
+
*/
|
|
26
|
+
@model(yjsTextModelId)
|
|
27
|
+
export class YjsTextModel extends Model({
|
|
28
|
+
deltaList: tProp(deltaListType, () => []),
|
|
29
|
+
}) {
|
|
30
|
+
/**
|
|
31
|
+
* Helper function to create a YjsTextModel instance with a simple text.
|
|
32
|
+
*/
|
|
33
|
+
static withText(text: string): YjsTextModel {
|
|
34
|
+
return new DecoratedYjsTextModel({
|
|
35
|
+
deltaList: [
|
|
36
|
+
frozen([
|
|
37
|
+
{
|
|
38
|
+
insert: text,
|
|
39
|
+
},
|
|
40
|
+
]),
|
|
41
|
+
],
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The Y.js path from the bound object to the YjsTextModel instance.
|
|
47
|
+
*/
|
|
48
|
+
@computed
|
|
49
|
+
private get _yjsObjectPath() {
|
|
50
|
+
const ctx = yjsBindingContext.get(this)
|
|
51
|
+
if (ctx?.boundObject == null) {
|
|
52
|
+
throw failure(
|
|
53
|
+
"the YjsTextModel instance must be part of a bound object before it can be accessed"
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const path = getParentToChildPath(ctx.boundObject, this)
|
|
58
|
+
if (!path) {
|
|
59
|
+
throw failure("a path from the bound object to the YjsTextModel instance is not available")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return path
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The Yjs.Text object present at this mobx-keystone node's path.
|
|
67
|
+
*/
|
|
68
|
+
@computed
|
|
69
|
+
private get _yjsObjectAtPath(): unknown {
|
|
70
|
+
const path = this._yjsObjectPath
|
|
71
|
+
|
|
72
|
+
const ctx = yjsBindingContext.get(this)!
|
|
73
|
+
|
|
74
|
+
return resolveYjsPath(ctx.yjsObject, path)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The Yjs.Text object represented by this mobx-keystone node.
|
|
79
|
+
*/
|
|
80
|
+
@computed
|
|
81
|
+
get yjsText(): Y.Text {
|
|
82
|
+
const yjsObject = this._yjsObjectAtPath
|
|
83
|
+
|
|
84
|
+
if (!(yjsObject instanceof Y.Text)) {
|
|
85
|
+
throw failure(`Y.Text was expected at path ${JSON.stringify(this._yjsObjectPath)}`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return yjsObject
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Atom that gets changed when the associated Y.js text changes.
|
|
93
|
+
*/
|
|
94
|
+
yjsTextChangedAtom = createAtom("yjsTextChangedAtom")
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The text value of the Yjs.Text object.
|
|
98
|
+
* Shortcut for `yjsText.toString()`, but computed.
|
|
99
|
+
*/
|
|
100
|
+
@computed
|
|
101
|
+
get text(): string {
|
|
102
|
+
this.yjsTextChangedAtom.reportObserved()
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
104
|
+
return this.yjsText.toString()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected onInit() {
|
|
108
|
+
const shouldReplicateToYjs = (ctx: YjsBindingContext | undefined): ctx is YjsBindingContext => {
|
|
109
|
+
return !!ctx && !!ctx.boundObject && !ctx.isApplyingYjsChangesToMobxKeystone
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let reapplyDeltasToYjsText = false
|
|
113
|
+
const newDeltas: Frozen<unknown[]>[] = []
|
|
114
|
+
|
|
115
|
+
let disposeObserveDeltaList: (() => void) | undefined
|
|
116
|
+
|
|
117
|
+
const disposeReactionToDeltaListRefChange = reaction(
|
|
118
|
+
() => this.$.deltaList,
|
|
119
|
+
(deltaList) => {
|
|
120
|
+
disposeObserveDeltaList?.()
|
|
121
|
+
disposeObserveDeltaList = undefined
|
|
122
|
+
|
|
123
|
+
disposeObserveDeltaList = observe(deltaList, (change) => {
|
|
124
|
+
if (reapplyDeltasToYjsText) {
|
|
125
|
+
// already gonna replace them all
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
if (!shouldReplicateToYjs(yjsBindingContext.get(this))) {
|
|
129
|
+
// yjs text is already up to date with these changes
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
change.type === "splice" &&
|
|
135
|
+
change.removedCount === 0 &&
|
|
136
|
+
change.addedCount > 0 &&
|
|
137
|
+
change.index === this.deltaList.length
|
|
138
|
+
) {
|
|
139
|
+
// optimization, just adding new ones to the end
|
|
140
|
+
newDeltas.push(...change.added)
|
|
141
|
+
} else {
|
|
142
|
+
// any other change, we need to reapply all deltas
|
|
143
|
+
reapplyDeltasToYjsText = true
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
},
|
|
147
|
+
{ fireImmediately: true }
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
const disposeOnSnapshot = onSnapshot(this, () => {
|
|
151
|
+
try {
|
|
152
|
+
if (reapplyDeltasToYjsText) {
|
|
153
|
+
const ctx = yjsBindingContext.get(this)
|
|
154
|
+
|
|
155
|
+
if (shouldReplicateToYjs(ctx)) {
|
|
156
|
+
const { yjsText } = this
|
|
157
|
+
|
|
158
|
+
ctx.yjsDoc.transact(() => {
|
|
159
|
+
// didn't find a better way than this to reapply all deltas
|
|
160
|
+
// without having to re-create the Y.Text object
|
|
161
|
+
if (yjsText.length > 0) {
|
|
162
|
+
yjsText.delete(0, yjsText.length)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.deltaList.forEach((frozenDeltas) => {
|
|
166
|
+
yjsText.applyDelta(frozenDeltas.data)
|
|
167
|
+
})
|
|
168
|
+
}, ctx.yjsOrigin)
|
|
169
|
+
}
|
|
170
|
+
} else if (newDeltas.length > 0) {
|
|
171
|
+
const ctx = yjsBindingContext.get(this)
|
|
172
|
+
|
|
173
|
+
if (shouldReplicateToYjs(ctx)) {
|
|
174
|
+
const { yjsText } = this
|
|
175
|
+
|
|
176
|
+
ctx.yjsDoc.transact(() => {
|
|
177
|
+
newDeltas.forEach((frozenDeltas) => {
|
|
178
|
+
yjsText.applyDelta(frozenDeltas.data)
|
|
179
|
+
})
|
|
180
|
+
}, ctx.yjsOrigin)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} finally {
|
|
184
|
+
reapplyDeltasToYjsText = false
|
|
185
|
+
newDeltas.length = 0
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const diposeYjsTextChangedAtom = hookYjsTextChangedAtom(
|
|
190
|
+
() => this.yjsText,
|
|
191
|
+
this.yjsTextChangedAtom
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return () => {
|
|
195
|
+
disposeOnSnapshot()
|
|
196
|
+
disposeReactionToDeltaListRefChange()
|
|
197
|
+
disposeObserveDeltaList?.()
|
|
198
|
+
disposeObserveDeltaList = undefined
|
|
199
|
+
|
|
200
|
+
diposeYjsTextChangedAtom()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// we use this trick just to avoid a babel bug that causes classes used inside classes not to be overriden
|
|
206
|
+
// by the decorator
|
|
207
|
+
const DecoratedYjsTextModel = YjsTextModel
|
|
208
|
+
|
|
209
|
+
function hookYjsTextChangedAtom(getYjsText: () => Y.Text, textChangedAtom: IAtom) {
|
|
210
|
+
let disposeObserveYjsText: (() => void) | undefined
|
|
211
|
+
|
|
212
|
+
const observeFn = () => {
|
|
213
|
+
textChangedAtom.reportChanged()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const disposeReactionToYTextChange = reaction(
|
|
217
|
+
() => {
|
|
218
|
+
try {
|
|
219
|
+
return getYjsText()
|
|
220
|
+
} catch {
|
|
221
|
+
return undefined
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
(yjsText) => {
|
|
225
|
+
disposeObserveYjsText?.()
|
|
226
|
+
disposeObserveYjsText = undefined
|
|
227
|
+
|
|
228
|
+
if (yjsText) {
|
|
229
|
+
yjsText.observe(observeFn)
|
|
230
|
+
|
|
231
|
+
disposeObserveYjsText = () => {
|
|
232
|
+
yjsText.unobserve(observeFn)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
textChangedAtom.reportChanged()
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
fireImmediately: true,
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return () => {
|
|
244
|
+
disposeReactionToYTextChange()
|
|
245
|
+
disposeObserveYjsText?.()
|
|
246
|
+
disposeObserveYjsText = undefined
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -2,13 +2,14 @@ import { Patch } from "mobx-keystone"
|
|
|
2
2
|
import * as Y from "yjs"
|
|
3
3
|
import { failure } from "../utils/error"
|
|
4
4
|
import { convertJsonToYjsData } from "./convertJsonToYjsData"
|
|
5
|
+
import { JsonValueWithUndefined } from "jsonTypes"
|
|
5
6
|
|
|
6
7
|
export function applyMobxKeystonePatchToYjsObject(patch: Patch, yjs: unknown): void {
|
|
7
8
|
if (patch.path.length > 1) {
|
|
8
9
|
const [key, ...rest] = patch.path
|
|
9
10
|
|
|
10
11
|
if (yjs instanceof Y.Map) {
|
|
11
|
-
const child = yjs.get(String(key))
|
|
12
|
+
const child = yjs.get(String(key)) as unknown
|
|
12
13
|
if (child === undefined) {
|
|
13
14
|
throw failure(
|
|
14
15
|
`invalid patch path, key "${key}" not found in Yjs map - patch: ${JSON.stringify(patch)}`
|
|
@@ -16,7 +17,7 @@ export function applyMobxKeystonePatchToYjsObject(patch: Patch, yjs: unknown): v
|
|
|
16
17
|
}
|
|
17
18
|
applyMobxKeystonePatchToYjsObject({ ...patch, path: rest }, child)
|
|
18
19
|
} else if (yjs instanceof Y.Array) {
|
|
19
|
-
const child = yjs.get(Number(key))
|
|
20
|
+
const child = yjs.get(Number(key)) as unknown
|
|
20
21
|
if (child === undefined) {
|
|
21
22
|
throw failure(
|
|
22
23
|
`invalid patch path, key "${key}" not found in Yjs array - patch: ${JSON.stringify(
|
|
@@ -25,6 +26,8 @@ export function applyMobxKeystonePatchToYjsObject(patch: Patch, yjs: unknown): v
|
|
|
25
26
|
)
|
|
26
27
|
}
|
|
27
28
|
applyMobxKeystonePatchToYjsObject({ ...patch, path: rest }, child)
|
|
29
|
+
} else if (yjs instanceof Y.Text) {
|
|
30
|
+
// changes to deltaList will be handled by the array observe in the YjsTextModel class
|
|
28
31
|
} else {
|
|
29
32
|
throw failure(
|
|
30
33
|
`invalid patch path, key "${key}" not found in unknown Yjs object - patch: ${JSON.stringify(
|
|
@@ -39,7 +42,7 @@ export function applyMobxKeystonePatchToYjsObject(patch: Patch, yjs: unknown): v
|
|
|
39
42
|
switch (patch.op) {
|
|
40
43
|
case "add":
|
|
41
44
|
case "replace": {
|
|
42
|
-
yjs.set(key, convertJsonToYjsData(patch.value))
|
|
45
|
+
yjs.set(key, convertJsonToYjsData(patch.value as JsonValueWithUndefined))
|
|
43
46
|
break
|
|
44
47
|
}
|
|
45
48
|
case "remove": {
|
|
@@ -56,21 +59,22 @@ export function applyMobxKeystonePatchToYjsObject(patch: Patch, yjs: unknown): v
|
|
|
56
59
|
switch (patch.op) {
|
|
57
60
|
case "replace": {
|
|
58
61
|
if (key === "length") {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
yjs.
|
|
62
|
+
const newLength = patch.value as number
|
|
63
|
+
if (yjs.length > newLength) {
|
|
64
|
+
const toDelete = yjs.length - newLength
|
|
65
|
+
yjs.delete(newLength, toDelete)
|
|
62
66
|
} else if (yjs.length < patch.value) {
|
|
63
67
|
const toInsert = patch.value - yjs.length
|
|
64
68
|
yjs.insert(yjs.length, Array(toInsert).fill(undefined))
|
|
65
69
|
}
|
|
66
70
|
} else {
|
|
67
71
|
yjs.delete(Number(key))
|
|
68
|
-
yjs.insert(Number(key), [convertJsonToYjsData(patch.value)])
|
|
72
|
+
yjs.insert(Number(key), [convertJsonToYjsData(patch.value as JsonValueWithUndefined)])
|
|
69
73
|
}
|
|
70
74
|
break
|
|
71
75
|
}
|
|
72
76
|
case "add": {
|
|
73
|
-
yjs.insert(Number(key), [convertJsonToYjsData(patch.value)])
|
|
77
|
+
yjs.insert(Number(key), [convertJsonToYjsData(patch.value as JsonValueWithUndefined)])
|
|
74
78
|
break
|
|
75
79
|
}
|
|
76
80
|
case "remove": {
|
|
@@ -81,9 +85,11 @@ export function applyMobxKeystonePatchToYjsObject(patch: Patch, yjs: unknown): v
|
|
|
81
85
|
throw failure(`invalid patch operation for array`)
|
|
82
86
|
}
|
|
83
87
|
}
|
|
88
|
+
} else if (yjs instanceof Y.Text) {
|
|
89
|
+
// initialization of a YjsTextModel, do nothing
|
|
84
90
|
} else {
|
|
85
91
|
throw failure(
|
|
86
|
-
`invalid patch path, the Yjs object is of an unkown type, so key "${patch.path[0]}" cannot be found in it`
|
|
92
|
+
`invalid patch path, the Yjs object is of an unkown type, so key "${String(patch.path[0])}" cannot be found in it`
|
|
87
93
|
)
|
|
88
94
|
}
|
|
89
95
|
} else {
|