@getguru/slate-yjs-core 1.0.3
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 +67 -0
- package/README.md +3 -0
- package/dist/applyToSlate/index.d.ts +23 -0
- package/dist/applyToSlate/textEvent.d.ts +4 -0
- package/dist/applyToYjs/index.d.ts +4 -0
- package/dist/applyToYjs/node/index.d.ts +4 -0
- package/dist/applyToYjs/node/insertNode.d.ts +4 -0
- package/dist/applyToYjs/node/mergeNode.d.ts +4 -0
- package/dist/applyToYjs/node/moveNode.d.ts +4 -0
- package/dist/applyToYjs/node/removeNode.d.ts +4 -0
- package/dist/applyToYjs/node/setNode.d.ts +4 -0
- package/dist/applyToYjs/node/splitNode.d.ts +4 -0
- package/dist/applyToYjs/text/index.d.ts +4 -0
- package/dist/applyToYjs/text/insertText.d.ts +4 -0
- package/dist/applyToYjs/text/removeText.d.ts +4 -0
- package/dist/applyToYjs/types.d.ts +9 -0
- package/dist/index.cjs +1360 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.global.js +10365 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +1338 -0
- package/dist/index.js.map +1 -0
- package/dist/model/types.d.ts +38 -0
- package/dist/plugins/index.d.ts +4 -0
- package/dist/plugins/withCursors.d.ts +39 -0
- package/dist/plugins/withYHistory.d.ts +20 -0
- package/dist/plugins/withYjs.d.ts +43 -0
- package/dist/utils/clone.d.ts +5 -0
- package/dist/utils/convert.d.ts +8 -0
- package/dist/utils/delta.d.ts +8 -0
- package/dist/utils/errors.d.ts +2 -0
- package/dist/utils/location.d.ts +12 -0
- package/dist/utils/object.d.ts +10 -0
- package/dist/utils/position.d.ts +20 -0
- package/dist/utils/slate.d.ts +3 -0
- package/dist/utils/yjs.d.ts +5 -0
- package/package.json +47 -0
- package/src/applyToSlate/index.ts +118 -0
- package/src/applyToSlate/textEvent.ts +280 -0
- package/src/applyToYjs/index.ts +28 -0
- package/src/applyToYjs/node/index.ts +17 -0
- package/src/applyToYjs/node/insertNode.ts +23 -0
- package/src/applyToYjs/node/mergeNode.ts +82 -0
- package/src/applyToYjs/node/moveNode.ts +57 -0
- package/src/applyToYjs/node/removeNode.ts +16 -0
- package/src/applyToYjs/node/setNode.ts +42 -0
- package/src/applyToYjs/node/splitNode.ts +98 -0
- package/src/applyToYjs/text/index.ts +9 -0
- package/src/applyToYjs/text/insertText.ts +27 -0
- package/src/applyToYjs/text/removeText.ts +16 -0
- package/src/applyToYjs/types.ts +12 -0
- package/src/index.ts +47 -0
- package/src/model/types.ts +50 -0
- package/src/plugins/index.ts +3 -0
- package/src/plugins/withCursors.ts +269 -0
- package/src/plugins/withYHistory.ts +183 -0
- package/src/plugins/withYjs.ts +284 -0
- package/src/utils/clone.ts +29 -0
- package/src/utils/convert.ts +48 -0
- package/src/utils/delta.ts +97 -0
- package/src/utils/errors.ts +20 -0
- package/src/utils/location.ts +157 -0
- package/src/utils/object.ts +93 -0
- package/src/utils/position.ts +300 -0
- package/src/utils/slate.ts +11 -0
- package/src/utils/yjs.ts +10 -0
- package/test/collaboration/addMark/acrossMarks.tsx +40 -0
- package/test/collaboration/addMark/acrossMarksSame.tsx +36 -0
- package/test/collaboration/addMark/atBeginningOfDocument.tsx +27 -0
- package/test/collaboration/addMark/atEndOfDocument.tsx +30 -0
- package/test/collaboration/addMark/withOtherMarks.tsx +31 -0
- package/test/collaboration/insertNode/atBeginningOfDocument.tsx +28 -0
- package/test/collaboration/insertNode/atEndOfDocument.tsx +28 -0
- package/test/collaboration/insertNode/inTheMiddle.tsx +30 -0
- package/test/collaboration/insertText/atBeginningOfBlock.tsx +26 -0
- package/test/collaboration/insertText/atBeginningOfDocument.tsx +26 -0
- package/test/collaboration/insertText/atEndOfBlock.tsx +26 -0
- package/test/collaboration/insertText/atEndOfDocument.tsx +26 -0
- package/test/collaboration/insertText/inTheMiddle.tsx +32 -0
- package/test/collaboration/insertText/inTheMiddleOfNestedBlock.tsx +30 -0
- package/test/collaboration/insertText/insideMarks.tsx +28 -0
- package/test/collaboration/insertText/withEmptyString.tsx +25 -0
- package/test/collaboration/insertText/withEntities.tsx +28 -0
- package/test/collaboration/insertText/withMarks.tsx +25 -0
- package/test/collaboration/insertText/withUnicode.tsx +28 -0
- package/test/collaboration/mergeNode/afterADeleteBackward.tsx +27 -0
- package/test/collaboration/mergeNode/inSameParent.tsx +34 -0
- package/test/collaboration/mergeNode/onMixedNestedNodes.tsx +29 -0
- package/test/collaboration/mergeNode/onMixedTypeNodes.tsx +27 -0
- package/test/collaboration/mergeNode/withUnicode.tsx +25 -0
- package/test/collaboration/moveNode/downward/whenBlockBecomesNested.tsx +43 -0
- package/test/collaboration/moveNode/downward/whenBlockBecomesNonNested.tsx +41 -0
- package/test/collaboration/moveNode/downward/whenBlockStaysNested.tsx +38 -0
- package/test/collaboration/moveNode/downward/whenBlockStaysNonNested.tsx +30 -0
- package/test/collaboration/moveNode/upward/whenBlockBecomesNested.tsx +43 -0
- package/test/collaboration/moveNode/upward/whenBlockBecomesNonNested.tsx +41 -0
- package/test/collaboration/moveNode/upward/whenBlockStaysNested.tsx +38 -0
- package/test/collaboration/moveNode/upward/whenBlockStaysNonNested.tsx +30 -0
- package/test/collaboration/removeMark/inTheMiddleOfText.tsx +43 -0
- package/test/collaboration/removeMark/withAddMark.tsx +31 -0
- package/test/collaboration/removeMark/withOtherMarks.tsx +47 -0
- package/test/collaboration/removeNode/atBeginningOfDocument.tsx +26 -0
- package/test/collaboration/removeNode/atEndOfDocument.tsx +26 -0
- package/test/collaboration/removeNode/nestedBlock.tsx +28 -0
- package/test/collaboration/removeNode/wrapperBlock.tsx +28 -0
- package/test/collaboration/removeText/atBeginningOfDocument.tsx +34 -0
- package/test/collaboration/removeText/atEndOfDocument.tsx +37 -0
- package/test/collaboration/removeText/withUnicode.tsx +29 -0
- package/test/collaboration/setNode/atBeginningOfDocument.tsx +27 -0
- package/test/collaboration/setNode/atEndOfDocument.tsx +27 -0
- package/test/collaboration/setNode/onDataChange.tsx +35 -0
- package/test/collaboration/setNode/onDataChangeOnInline.tsx +35 -0
- package/test/collaboration/setNode/onResetBlock.tsx +27 -0
- package/test/collaboration/setNode/withAChangeOfType.tsx +26 -0
- package/test/collaboration/splitNode/atBeginningOfDocument.tsx +28 -0
- package/test/collaboration/splitNode/atEndOfBlock.tsx +27 -0
- package/test/collaboration/splitNode/atEndOfDocument.tsx +27 -0
- package/test/collaboration/splitNode/onNonDefaultBlock.tsx +27 -0
- package/test/collaboration/splitNode/withMultipleSubNodes.tsx +32 -0
- package/test/collaboration/splitNode/withUnicode.tsx +29 -0
- package/test/index.test.ts +65 -0
- package/test/slate.d.ts +8 -0
- package/test/withTestingElements.ts +46 -0
- package/tsconfig.json +11 -0
- package/tsup.config.ts +32 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
type InspectableObject = Record<string | number | symbol, unknown>;
|
|
2
|
+
|
|
3
|
+
function isObject(o: unknown): o is InspectableObject {
|
|
4
|
+
return Object.prototype.toString.call(o) === '[object Object]';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isPlainObject(o: unknown): o is InspectableObject {
|
|
8
|
+
if (!isObject(o)) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// If has modified constructor
|
|
13
|
+
const ctor = o.constructor;
|
|
14
|
+
if (ctor === undefined) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// If has modified prototype
|
|
19
|
+
const prot = ctor.prototype;
|
|
20
|
+
if (isObject(prot) === false) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// If constructor does not have an Object-specific method
|
|
25
|
+
if (prot.hasOwnProperty('isPrototypeOf') === false) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Most likely a plain Object
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Slates deep equality function: https://github.com/ianstormtaylor/slate/blob/68aff89e892fe15a16314398ff052ade6068900b/packages/slate/src/utils/deep-equal.ts#L13
|
|
34
|
+
// We have to match slates deepEquals behavior to merge insert deltas in the same way slate does.
|
|
35
|
+
export function deepEquals(
|
|
36
|
+
node: InspectableObject,
|
|
37
|
+
another: InspectableObject
|
|
38
|
+
): boolean {
|
|
39
|
+
// eslint-disable-next-line guard-for-in
|
|
40
|
+
for (const key in node) {
|
|
41
|
+
const a = node[key];
|
|
42
|
+
const b = another[key];
|
|
43
|
+
|
|
44
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
45
|
+
if (!deepEquals(a, b)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
} else if (Array.isArray(a) && Array.isArray(b)) {
|
|
49
|
+
if (a.length !== b.length) return false;
|
|
50
|
+
for (let i = 0; i < a.length; i++) {
|
|
51
|
+
if (a[i] !== b[i]) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} else if (a !== b) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const key in another) {
|
|
61
|
+
if (node[key] === undefined && another[key] !== undefined) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function pick<TObj, TKeys extends keyof TObj>(
|
|
70
|
+
obj: TObj,
|
|
71
|
+
...keys: TKeys[]
|
|
72
|
+
): Pick<TObj, TKeys> {
|
|
73
|
+
return Object.fromEntries(
|
|
74
|
+
Object.entries(obj).filter(([key]) => keys.includes(key as TKeys))
|
|
75
|
+
) as Pick<TObj, TKeys>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function omit<TObj, TKeys extends keyof TObj>(
|
|
79
|
+
obj: TObj,
|
|
80
|
+
...keys: TKeys[]
|
|
81
|
+
): Omit<TObj, TKeys> {
|
|
82
|
+
return Object.fromEntries(
|
|
83
|
+
Object.entries(obj).filter(([key]) => !keys.includes(key as TKeys))
|
|
84
|
+
) as Omit<TObj, TKeys>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function omitNullEntries<TObj>(obj: TObj): {
|
|
88
|
+
[K in keyof TObj]: TObj[K] extends null ? never : K;
|
|
89
|
+
} {
|
|
90
|
+
return Object.fromEntries(
|
|
91
|
+
Object.entries(obj).filter(([, value]) => value !== null)
|
|
92
|
+
) as { [K in keyof TObj]: TObj[K] extends null ? never : K };
|
|
93
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { BasePoint, BaseRange, Node, Text } from 'slate';
|
|
2
|
+
import * as Y from 'yjs';
|
|
3
|
+
import { InsertDelta, RelativeRange, TextRange } from '../model/types';
|
|
4
|
+
import { getInsertDeltaLength, yTextToInsertDelta } from './delta';
|
|
5
|
+
import { isSyncSkewError } from './errors';
|
|
6
|
+
import { getSlatePath, getYTarget, yOffsetToSlateOffsets } from './location';
|
|
7
|
+
import { assertDocumentAttachment } from './yjs';
|
|
8
|
+
|
|
9
|
+
export const STORED_POSITION_PREFIX = '__slateYjsStoredPosition_';
|
|
10
|
+
|
|
11
|
+
export function slatePointToRelativePosition(
|
|
12
|
+
sharedRoot: Y.XmlText,
|
|
13
|
+
slateRoot: Node,
|
|
14
|
+
point: BasePoint
|
|
15
|
+
): Y.RelativePosition {
|
|
16
|
+
const { yTarget, yParent, textRange } = getYTarget(
|
|
17
|
+
sharedRoot,
|
|
18
|
+
slateRoot,
|
|
19
|
+
point.path
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (yTarget) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'Slate point points to a non-text element inside sharedRoot'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const index = textRange.start + point.offset;
|
|
29
|
+
return Y.createRelativePositionFromTypeIndex(
|
|
30
|
+
yParent,
|
|
31
|
+
index,
|
|
32
|
+
index === textRange.end ? -1 : 0
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function absolutePositionToSlatePoint(
|
|
37
|
+
sharedRoot: Y.XmlText,
|
|
38
|
+
slateRoot: Node,
|
|
39
|
+
{ type, index, assoc }: Y.AbsolutePosition
|
|
40
|
+
): BasePoint | null {
|
|
41
|
+
if (!(type instanceof Y.XmlText)) {
|
|
42
|
+
throw new Error('Absolute position points to a non-XMLText');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const parentPath = getSlatePath(sharedRoot, slateRoot, type);
|
|
47
|
+
const parent = Node.get(slateRoot, parentPath);
|
|
48
|
+
|
|
49
|
+
if (Text.isText(parent)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const [pathOffset, textOffset] = yOffsetToSlateOffsets(parent, index, {
|
|
54
|
+
assoc,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const target = parent.children[pathOffset];
|
|
58
|
+
if (!Text.isText(target)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { path: [...parentPath, pathOffset], offset: textOffset };
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (isSyncSkewError(error)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function relativePositionToSlatePoint(
|
|
72
|
+
sharedRoot: Y.XmlText,
|
|
73
|
+
slateRoot: Node,
|
|
74
|
+
pos: Y.RelativePosition
|
|
75
|
+
): BasePoint | null {
|
|
76
|
+
if (!sharedRoot.doc) {
|
|
77
|
+
throw new Error("sharedRoot isn't attach to a yDoc");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const absPos = Y.createAbsolutePositionFromRelativePosition(
|
|
81
|
+
pos,
|
|
82
|
+
sharedRoot.doc
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return absPos && absolutePositionToSlatePoint(sharedRoot, slateRoot, absPos);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function getStoredPosition(
|
|
89
|
+
sharedRoot: Y.XmlText,
|
|
90
|
+
key: string
|
|
91
|
+
): Y.RelativePosition | null {
|
|
92
|
+
const rawPosition = sharedRoot.getAttribute(STORED_POSITION_PREFIX + key);
|
|
93
|
+
if (!rawPosition) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Y.decodeRelativePosition(rawPosition);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getStoredPositions(
|
|
101
|
+
sharedRoot: Y.XmlText
|
|
102
|
+
): Record<string, Y.RelativePosition> {
|
|
103
|
+
return Object.fromEntries(
|
|
104
|
+
Object.entries(sharedRoot.getAttributes())
|
|
105
|
+
.filter(([key]) => key.startsWith(STORED_POSITION_PREFIX))
|
|
106
|
+
.map(([key, position]) => [
|
|
107
|
+
key.slice(STORED_POSITION_PREFIX.length),
|
|
108
|
+
Y.createRelativePositionFromJSON(position),
|
|
109
|
+
])
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getStoredPositionsAbsolute(sharedRoot: Y.XmlText) {
|
|
114
|
+
assertDocumentAttachment(sharedRoot);
|
|
115
|
+
|
|
116
|
+
return Object.fromEntries(
|
|
117
|
+
Object.entries(sharedRoot.getAttributes())
|
|
118
|
+
.filter(([key]) => key.startsWith(STORED_POSITION_PREFIX))
|
|
119
|
+
.map(
|
|
120
|
+
([key, position]) =>
|
|
121
|
+
[
|
|
122
|
+
key.slice(STORED_POSITION_PREFIX.length),
|
|
123
|
+
Y.createAbsolutePositionFromRelativePosition(
|
|
124
|
+
Y.decodeRelativePosition(position),
|
|
125
|
+
sharedRoot.doc
|
|
126
|
+
),
|
|
127
|
+
] as const
|
|
128
|
+
)
|
|
129
|
+
.filter(([, position]) => position)
|
|
130
|
+
) as Record<string, Y.AbsolutePosition>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function removeStoredPosition(sharedRoot: Y.XmlText, key: string) {
|
|
134
|
+
sharedRoot.removeAttribute(STORED_POSITION_PREFIX + key);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function setStoredPosition(
|
|
138
|
+
sharedRoot: Y.XmlText,
|
|
139
|
+
key: string,
|
|
140
|
+
position: Y.RelativePosition
|
|
141
|
+
) {
|
|
142
|
+
sharedRoot.setAttribute(
|
|
143
|
+
STORED_POSITION_PREFIX + key,
|
|
144
|
+
Y.encodeRelativePosition(position)
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getAbsolutePositionsInTextRange(
|
|
149
|
+
absolutePositions: Record<string, Y.AbsolutePosition>,
|
|
150
|
+
yTarget: Y.XmlText,
|
|
151
|
+
textRange?: TextRange
|
|
152
|
+
) {
|
|
153
|
+
return Object.fromEntries(
|
|
154
|
+
Object.entries(absolutePositions).filter(([, position]) => {
|
|
155
|
+
if (position.type !== yTarget) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!textRange) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return position.assoc >= 0
|
|
164
|
+
? position.index >= textRange.start && position.index < textRange.end
|
|
165
|
+
: position.index > textRange.start && position.index >= textRange.end;
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getAbsolutePositionsInYText(
|
|
171
|
+
absolutePositions: Record<string, Y.AbsolutePosition>,
|
|
172
|
+
yText: Y.XmlText,
|
|
173
|
+
parentPath = ''
|
|
174
|
+
): Record<string, Record<string, Y.AbsolutePosition>> {
|
|
175
|
+
const positions = {
|
|
176
|
+
[parentPath]: getAbsolutePositionsInTextRange(absolutePositions, yText),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const insertDelta = yTextToInsertDelta(yText);
|
|
180
|
+
insertDelta.forEach(({ insert }, i) => {
|
|
181
|
+
if (insert instanceof Y.XmlText) {
|
|
182
|
+
Object.assign(
|
|
183
|
+
positions,
|
|
184
|
+
getAbsolutePositionsInYText(
|
|
185
|
+
absolutePositions,
|
|
186
|
+
insert,
|
|
187
|
+
parentPath ? `${parentPath}.${i}` : i.toString()
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return positions;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function getStoredPositionsInDeltaAbsolute(
|
|
197
|
+
sharedRoot: Y.XmlText,
|
|
198
|
+
yText: Y.XmlText,
|
|
199
|
+
delta: InsertDelta,
|
|
200
|
+
deltaOffset = 0
|
|
201
|
+
) {
|
|
202
|
+
const absolutePositions = getStoredPositionsAbsolute(sharedRoot);
|
|
203
|
+
|
|
204
|
+
const positions = {
|
|
205
|
+
'': getAbsolutePositionsInTextRange(absolutePositions, yText, {
|
|
206
|
+
start: deltaOffset,
|
|
207
|
+
end: deltaOffset + getInsertDeltaLength(delta),
|
|
208
|
+
}),
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
delta.forEach(({ insert }, i) => {
|
|
212
|
+
if (insert instanceof Y.XmlText) {
|
|
213
|
+
Object.assign(
|
|
214
|
+
positions,
|
|
215
|
+
getAbsolutePositionsInYText(absolutePositions, insert, i.toString())
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return positions;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function restoreStoredPositionsWithDeltaAbsolute(
|
|
224
|
+
sharedRoot: Y.XmlText,
|
|
225
|
+
yText: Y.XmlText,
|
|
226
|
+
absolutePositions: Record<string, Record<string, Y.AbsolutePosition>>,
|
|
227
|
+
delta: InsertDelta,
|
|
228
|
+
newDeltaOffset = 0,
|
|
229
|
+
previousDeltaOffset = 0,
|
|
230
|
+
path = ''
|
|
231
|
+
) {
|
|
232
|
+
const toRestore = absolutePositions[path];
|
|
233
|
+
|
|
234
|
+
if (toRestore) {
|
|
235
|
+
Object.entries(toRestore).forEach(([key, position]) => {
|
|
236
|
+
setStoredPosition(
|
|
237
|
+
sharedRoot,
|
|
238
|
+
key,
|
|
239
|
+
Y.createRelativePositionFromTypeIndex(
|
|
240
|
+
yText,
|
|
241
|
+
position.index - previousDeltaOffset + newDeltaOffset,
|
|
242
|
+
position.assoc
|
|
243
|
+
)
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
delta.forEach(({ insert }, i) => {
|
|
249
|
+
if (insert instanceof Y.XmlText) {
|
|
250
|
+
restoreStoredPositionsWithDeltaAbsolute(
|
|
251
|
+
sharedRoot,
|
|
252
|
+
insert,
|
|
253
|
+
absolutePositions,
|
|
254
|
+
yTextToInsertDelta(insert),
|
|
255
|
+
0,
|
|
256
|
+
0,
|
|
257
|
+
path ? `${path}.${i}` : i.toString()
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function slateRangeToRelativeRange(
|
|
264
|
+
sharedRoot: Y.XmlText,
|
|
265
|
+
slateRoot: Node,
|
|
266
|
+
range: BaseRange
|
|
267
|
+
): RelativeRange {
|
|
268
|
+
return {
|
|
269
|
+
anchor: slatePointToRelativePosition(sharedRoot, slateRoot, range.anchor),
|
|
270
|
+
focus: slatePointToRelativePosition(sharedRoot, slateRoot, range.focus),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function relativeRangeToSlateRange(
|
|
275
|
+
sharedRoot: Y.XmlText,
|
|
276
|
+
slateRoot: Node,
|
|
277
|
+
range: RelativeRange
|
|
278
|
+
): BaseRange | null {
|
|
279
|
+
const anchor = relativePositionToSlatePoint(
|
|
280
|
+
sharedRoot,
|
|
281
|
+
slateRoot,
|
|
282
|
+
range.anchor
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
if (!anchor) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const focus = relativePositionToSlatePoint(
|
|
290
|
+
sharedRoot,
|
|
291
|
+
slateRoot,
|
|
292
|
+
range.focus
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
if (!focus) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { anchor, focus };
|
|
300
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseText, Descendant, Text } from 'slate';
|
|
2
|
+
import { omit } from './object';
|
|
3
|
+
|
|
4
|
+
export function getProperties<TNode extends Descendant>(
|
|
5
|
+
node: TNode
|
|
6
|
+
): Omit<TNode, TNode extends BaseText ? 'text' : 'children'> {
|
|
7
|
+
return omit(
|
|
8
|
+
node,
|
|
9
|
+
(Text.isText(node) ? 'text' : 'children') as keyof TNode
|
|
10
|
+
) as Omit<TNode, TNode extends BaseText ? 'text' : 'children'>;
|
|
11
|
+
}
|
package/src/utils/yjs.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
export function assertDocumentAttachment<T extends Y.AbstractType<any>>(
|
|
5
|
+
sharedType: T
|
|
6
|
+
): asserts sharedType is T & { doc: NonNullable<T['doc']> } {
|
|
7
|
+
if (!sharedType.doc) {
|
|
8
|
+
throw new Error("shared type isn't attached to a document");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { Editor } from 'slate';
|
|
3
|
+
import { jsx } from '../../../../../support/jsx';
|
|
4
|
+
|
|
5
|
+
export const input = (
|
|
6
|
+
<editor>
|
|
7
|
+
<unstyled>
|
|
8
|
+
<text>
|
|
9
|
+
Hel
|
|
10
|
+
<anchor />l
|
|
11
|
+
</text>
|
|
12
|
+
<text bold>o</text>
|
|
13
|
+
<text>
|
|
14
|
+
{' '}
|
|
15
|
+
w<focus />
|
|
16
|
+
orld!
|
|
17
|
+
</text>
|
|
18
|
+
</unstyled>
|
|
19
|
+
</editor>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const expected = (
|
|
23
|
+
<editor>
|
|
24
|
+
<unstyled>
|
|
25
|
+
<text>Hel</text>
|
|
26
|
+
<anchor />
|
|
27
|
+
<text italic>l</text>
|
|
28
|
+
<text italic bold>
|
|
29
|
+
o
|
|
30
|
+
</text>
|
|
31
|
+
<text italic> w</text>
|
|
32
|
+
<focus />
|
|
33
|
+
<text>orld!</text>
|
|
34
|
+
</unstyled>
|
|
35
|
+
</editor>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export function run(editor: Editor) {
|
|
39
|
+
editor.addMark('italic', true);
|
|
40
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { Editor } from 'slate';
|
|
3
|
+
import { jsx } from '../../../../../support/jsx';
|
|
4
|
+
|
|
5
|
+
export const input = (
|
|
6
|
+
<editor>
|
|
7
|
+
<unstyled>
|
|
8
|
+
<text>
|
|
9
|
+
Hel
|
|
10
|
+
<anchor />l
|
|
11
|
+
</text>
|
|
12
|
+
<text bold>o</text>
|
|
13
|
+
<text>
|
|
14
|
+
{' '}
|
|
15
|
+
w<focus />
|
|
16
|
+
orld!
|
|
17
|
+
</text>
|
|
18
|
+
</unstyled>
|
|
19
|
+
</editor>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const expected = (
|
|
23
|
+
<editor>
|
|
24
|
+
<unstyled>
|
|
25
|
+
<text>Hel</text>
|
|
26
|
+
<anchor />
|
|
27
|
+
<text bold>lo w</text>
|
|
28
|
+
<focus />
|
|
29
|
+
<text>orld!</text>
|
|
30
|
+
</unstyled>
|
|
31
|
+
</editor>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export function run(editor: Editor) {
|
|
35
|
+
editor.addMark('bold', true);
|
|
36
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { Editor } from 'slate';
|
|
3
|
+
import { jsx } from '../../../../../support/jsx';
|
|
4
|
+
|
|
5
|
+
export const input = (
|
|
6
|
+
<editor>
|
|
7
|
+
<unstyled>
|
|
8
|
+
<anchor />
|
|
9
|
+
Hello
|
|
10
|
+
<focus /> world!
|
|
11
|
+
</unstyled>
|
|
12
|
+
</editor>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const expected = (
|
|
16
|
+
<editor>
|
|
17
|
+
<unstyled>
|
|
18
|
+
<anchor />
|
|
19
|
+
<text bold>Hello</text>
|
|
20
|
+
<focus /> world!
|
|
21
|
+
</unstyled>
|
|
22
|
+
</editor>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export function run(editor: Editor) {
|
|
26
|
+
editor.addMark('bold', true);
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { Editor } from 'slate';
|
|
3
|
+
import { jsx } from '../../../../../support/jsx';
|
|
4
|
+
|
|
5
|
+
export const input = (
|
|
6
|
+
<editor>
|
|
7
|
+
<unstyled>
|
|
8
|
+
Hello <anchor />
|
|
9
|
+
world!
|
|
10
|
+
<focus />
|
|
11
|
+
</unstyled>
|
|
12
|
+
</editor>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const expected = (
|
|
16
|
+
<editor>
|
|
17
|
+
<unstyled>
|
|
18
|
+
Hello{' '}
|
|
19
|
+
<text bold>
|
|
20
|
+
<anchor />
|
|
21
|
+
world!
|
|
22
|
+
</text>
|
|
23
|
+
<focus />
|
|
24
|
+
</unstyled>
|
|
25
|
+
</editor>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export function run(editor: Editor) {
|
|
29
|
+
editor.addMark('bold', true);
|
|
30
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { Editor } from 'slate';
|
|
3
|
+
import { jsx } from '../../../../../support/jsx';
|
|
4
|
+
|
|
5
|
+
export const input = (
|
|
6
|
+
<editor>
|
|
7
|
+
<unstyled>
|
|
8
|
+
<anchor />
|
|
9
|
+
<text italic>Hello world</text>
|
|
10
|
+
<focus />
|
|
11
|
+
<text bold>!</text>
|
|
12
|
+
</unstyled>
|
|
13
|
+
</editor>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export const expected = (
|
|
17
|
+
<editor>
|
|
18
|
+
<unstyled>
|
|
19
|
+
<anchor />
|
|
20
|
+
<text italic bold>
|
|
21
|
+
Hello world
|
|
22
|
+
</text>
|
|
23
|
+
<focus />
|
|
24
|
+
<text bold>!</text>
|
|
25
|
+
</unstyled>
|
|
26
|
+
</editor>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export function run(editor: Editor) {
|
|
30
|
+
editor.addMark('bold', true);
|
|
31
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { Editor } from 'slate';
|
|
3
|
+
import { jsx } from '../../../../../support/jsx';
|
|
4
|
+
|
|
5
|
+
export const input = (
|
|
6
|
+
<editor>
|
|
7
|
+
<unstyled>
|
|
8
|
+
<cursor />
|
|
9
|
+
Hello world!
|
|
10
|
+
</unstyled>
|
|
11
|
+
<unstyled>Welcome to slate-yjs!</unstyled>
|
|
12
|
+
</editor>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const expected = (
|
|
16
|
+
<editor>
|
|
17
|
+
<h1>
|
|
18
|
+
Foo bar!
|
|
19
|
+
<cursor />
|
|
20
|
+
</h1>
|
|
21
|
+
<unstyled>Hello world!</unstyled>
|
|
22
|
+
<unstyled>Welcome to slate-yjs!</unstyled>
|
|
23
|
+
</editor>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export function run(editor: Editor) {
|
|
27
|
+
editor.insertNode(<h1>Foo bar!</h1>);
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { Editor } from 'slate';
|
|
3
|
+
import { jsx } from '../../../../../support/jsx';
|
|
4
|
+
|
|
5
|
+
export const input = (
|
|
6
|
+
<editor>
|
|
7
|
+
<unstyled>Hello world!</unstyled>
|
|
8
|
+
<unstyled>
|
|
9
|
+
Welcome to slate-yjs!
|
|
10
|
+
<cursor />
|
|
11
|
+
</unstyled>
|
|
12
|
+
</editor>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const expected = (
|
|
16
|
+
<editor>
|
|
17
|
+
<unstyled>Hello world!</unstyled>
|
|
18
|
+
<unstyled>Welcome to slate-yjs!</unstyled>
|
|
19
|
+
<h1>
|
|
20
|
+
Foo bar!
|
|
21
|
+
<cursor />
|
|
22
|
+
</h1>
|
|
23
|
+
</editor>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export function run(editor: Editor) {
|
|
27
|
+
editor.insertNode(<h1>Foo bar!</h1>);
|
|
28
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { Editor } from 'slate';
|
|
3
|
+
import { jsx } from '../../../../../support/jsx';
|
|
4
|
+
|
|
5
|
+
export const input = (
|
|
6
|
+
<editor>
|
|
7
|
+
<unstyled>Hello world!</unstyled>
|
|
8
|
+
<unstyled>
|
|
9
|
+
Welcome
|
|
10
|
+
<cursor />
|
|
11
|
+
to slate-yjs!
|
|
12
|
+
</unstyled>
|
|
13
|
+
</editor>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export const expected = (
|
|
17
|
+
<editor>
|
|
18
|
+
<unstyled>Hello world!</unstyled>
|
|
19
|
+
<unstyled>Welcome</unstyled>
|
|
20
|
+
<h1>
|
|
21
|
+
Foo bar!
|
|
22
|
+
<cursor />
|
|
23
|
+
</h1>
|
|
24
|
+
<unstyled>to slate-yjs!</unstyled>
|
|
25
|
+
</editor>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export function run(editor: Editor) {
|
|
29
|
+
editor.insertNode(<h1>Foo bar!</h1>);
|
|
30
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { Editor } from 'slate';
|
|
3
|
+
import { jsx } from '../../../../../support/jsx';
|
|
4
|
+
|
|
5
|
+
export const input = (
|
|
6
|
+
<editor>
|
|
7
|
+
<unstyled>Hello world!</unstyled>
|
|
8
|
+
<unstyled>
|
|
9
|
+
<cursor />
|
|
10
|
+
</unstyled>
|
|
11
|
+
</editor>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export const expected = (
|
|
15
|
+
<editor>
|
|
16
|
+
<unstyled>Hello world!</unstyled>
|
|
17
|
+
<unstyled>
|
|
18
|
+
Welcome to slate-yjs!
|
|
19
|
+
<cursor />
|
|
20
|
+
</unstyled>
|
|
21
|
+
</editor>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export function run(editor: Editor) {
|
|
25
|
+
editor.insertText('Welcome to slate-yjs!');
|
|
26
|
+
}
|