@y/y 14.0.0-16
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/LICENSE +23 -0
- package/README.md +1406 -0
- package/dist/Skip-j0kX7pdq.js +12173 -0
- package/dist/Skip-j0kX7pdq.js.map +1 -0
- package/dist/Skip-wRT7BKFP.js +11877 -0
- package/dist/Skip-wRT7BKFP.js.map +1 -0
- package/dist/index-DyTeTfmj.js +163 -0
- package/dist/index-DyTeTfmj.js.map +1 -0
- package/dist/index-R7GxO-36.js +165 -0
- package/dist/index-R7GxO-36.js.map +1 -0
- package/dist/internals.cjs +286 -0
- package/dist/internals.cjs.map +1 -0
- package/dist/internals.mjs +25 -0
- package/dist/internals.mjs.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/internals.d.ts +43 -0
- package/dist/src/internals.d.ts.map +1 -0
- package/dist/src/structs/AbstractStruct.d.ts +42 -0
- package/dist/src/structs/AbstractStruct.d.ts.map +1 -0
- package/dist/src/structs/ContentAny.d.ts +67 -0
- package/dist/src/structs/ContentAny.d.ts.map +1 -0
- package/dist/src/structs/ContentBinary.d.ts +64 -0
- package/dist/src/structs/ContentBinary.d.ts.map +1 -0
- package/dist/src/structs/ContentDeleted.d.ts +64 -0
- package/dist/src/structs/ContentDeleted.d.ts.map +1 -0
- package/dist/src/structs/ContentDoc.d.ts +72 -0
- package/dist/src/structs/ContentDoc.d.ts.map +1 -0
- package/dist/src/structs/ContentEmbed.d.ts +67 -0
- package/dist/src/structs/ContentEmbed.d.ts.map +1 -0
- package/dist/src/structs/ContentFormat.d.ts +69 -0
- package/dist/src/structs/ContentFormat.d.ts.map +1 -0
- package/dist/src/structs/ContentJSON.d.ts +70 -0
- package/dist/src/structs/ContentJSON.d.ts.map +1 -0
- package/dist/src/structs/ContentString.d.ts +70 -0
- package/dist/src/structs/ContentString.d.ts.map +1 -0
- package/dist/src/structs/ContentType.d.ts +83 -0
- package/dist/src/structs/ContentType.d.ts.map +1 -0
- package/dist/src/structs/GC.d.ts +31 -0
- package/dist/src/structs/GC.d.ts.map +1 -0
- package/dist/src/structs/Item.d.ts +212 -0
- package/dist/src/structs/Item.d.ts.map +1 -0
- package/dist/src/structs/Skip.d.ts +33 -0
- package/dist/src/structs/Skip.d.ts.map +1 -0
- package/dist/src/types/AbstractType.d.ts +239 -0
- package/dist/src/types/AbstractType.d.ts.map +1 -0
- package/dist/src/types/YArray.d.ts +128 -0
- package/dist/src/types/YArray.d.ts.map +1 -0
- package/dist/src/types/YMap.d.ts +112 -0
- package/dist/src/types/YMap.d.ts.map +1 -0
- package/dist/src/types/YText.d.ts +216 -0
- package/dist/src/types/YText.d.ts.map +1 -0
- package/dist/src/types/YXmlElement.d.ts +106 -0
- package/dist/src/types/YXmlElement.d.ts.map +1 -0
- package/dist/src/types/YXmlFragment.d.ts +143 -0
- package/dist/src/types/YXmlFragment.d.ts.map +1 -0
- package/dist/src/types/YXmlHook.d.ts +32 -0
- package/dist/src/types/YXmlHook.d.ts.map +1 -0
- package/dist/src/types/YXmlText.d.ts +34 -0
- package/dist/src/types/YXmlText.d.ts.map +1 -0
- package/dist/src/utils/AbstractConnector.d.ts +20 -0
- package/dist/src/utils/AbstractConnector.d.ts.map +1 -0
- package/dist/src/utils/AttributionManager.d.ts +224 -0
- package/dist/src/utils/AttributionManager.d.ts.map +1 -0
- package/dist/src/utils/Doc.d.ts +267 -0
- package/dist/src/utils/Doc.d.ts.map +1 -0
- package/dist/src/utils/EventHandler.d.ts +19 -0
- package/dist/src/utils/EventHandler.d.ts.map +1 -0
- package/dist/src/utils/ID.d.ts +26 -0
- package/dist/src/utils/ID.d.ts.map +1 -0
- package/dist/src/utils/IdMap.d.ts +161 -0
- package/dist/src/utils/IdMap.d.ts.map +1 -0
- package/dist/src/utils/IdSet.d.ts +163 -0
- package/dist/src/utils/IdSet.d.ts.map +1 -0
- package/dist/src/utils/RelativePosition.d.ts +91 -0
- package/dist/src/utils/RelativePosition.d.ts.map +1 -0
- package/dist/src/utils/Snapshot.d.ts +40 -0
- package/dist/src/utils/Snapshot.d.ts.map +1 -0
- package/dist/src/utils/StructSet.d.ts +27 -0
- package/dist/src/utils/StructSet.d.ts.map +1 -0
- package/dist/src/utils/StructStore.d.ts +41 -0
- package/dist/src/utils/StructStore.d.ts.map +1 -0
- package/dist/src/utils/Transaction.d.ts +136 -0
- package/dist/src/utils/Transaction.d.ts.map +1 -0
- package/dist/src/utils/UndoManager.d.ts +188 -0
- package/dist/src/utils/UndoManager.d.ts.map +1 -0
- package/dist/src/utils/UpdateDecoder.d.ts +167 -0
- package/dist/src/utils/UpdateDecoder.d.ts.map +1 -0
- package/dist/src/utils/UpdateEncoder.d.ts +164 -0
- package/dist/src/utils/UpdateEncoder.d.ts.map +1 -0
- package/dist/src/utils/YEvent.d.ts +120 -0
- package/dist/src/utils/YEvent.d.ts.map +1 -0
- package/dist/src/utils/delta-helpers.d.ts +6 -0
- package/dist/src/utils/delta-helpers.d.ts.map +1 -0
- package/dist/src/utils/encoding.d.ts +30 -0
- package/dist/src/utils/encoding.d.ts.map +1 -0
- package/dist/src/utils/isParentOf.d.ts +3 -0
- package/dist/src/utils/isParentOf.d.ts.map +1 -0
- package/dist/src/utils/logging.d.ts +3 -0
- package/dist/src/utils/logging.d.ts.map +1 -0
- package/dist/src/utils/types.d.ts +7 -0
- package/dist/src/utils/types.d.ts.map +1 -0
- package/dist/src/utils/updates.d.ts +89 -0
- package/dist/src/utils/updates.d.ts.map +1 -0
- package/dist/testHelper.cjs +780 -0
- package/dist/testHelper.cjs.map +1 -0
- package/dist/testHelper.mjs +617 -0
- package/dist/testHelper.mjs.map +1 -0
- package/dist/tests/IdMap.tests.d.ts +9 -0
- package/dist/tests/IdMap.tests.d.ts.map +1 -0
- package/dist/tests/IdSet.tests.d.ts +9 -0
- package/dist/tests/IdSet.tests.d.ts.map +1 -0
- package/dist/tests/attribution.tests.d.ts +8 -0
- package/dist/tests/attribution.tests.d.ts.map +1 -0
- package/dist/tests/compatibility.tests.d.ts +5 -0
- package/dist/tests/compatibility.tests.d.ts.map +1 -0
- package/dist/tests/delta.tests.d.ts +7 -0
- package/dist/tests/delta.tests.d.ts.map +1 -0
- package/dist/tests/doc.tests.d.ts +13 -0
- package/dist/tests/doc.tests.d.ts.map +1 -0
- package/dist/tests/encoding.tests.d.ts +5 -0
- package/dist/tests/encoding.tests.d.ts.map +1 -0
- package/dist/tests/index.d.ts +2 -0
- package/dist/tests/index.d.ts.map +1 -0
- package/dist/tests/relativePositions.tests.d.ts +11 -0
- package/dist/tests/relativePositions.tests.d.ts.map +1 -0
- package/dist/tests/snapshot.tests.d.ts +13 -0
- package/dist/tests/snapshot.tests.d.ts.map +1 -0
- package/dist/tests/testHelper.d.ts +167 -0
- package/dist/tests/testHelper.d.ts.map +1 -0
- package/dist/tests/undo-redo.tests.d.ts +27 -0
- package/dist/tests/undo-redo.tests.d.ts.map +1 -0
- package/dist/tests/updates.tests.d.ts +24 -0
- package/dist/tests/updates.tests.d.ts.map +1 -0
- package/dist/tests/y-array.tests.d.ts +45 -0
- package/dist/tests/y-array.tests.d.ts.map +1 -0
- package/dist/tests/y-map.tests.d.ts +45 -0
- package/dist/tests/y-map.tests.d.ts.map +1 -0
- package/dist/tests/y-text.tests.d.ts +49 -0
- package/dist/tests/y-text.tests.d.ts.map +1 -0
- package/dist/tests/y-xml.tests.d.ts +15 -0
- package/dist/tests/y-xml.tests.d.ts.map +1 -0
- package/dist/yjs.cjs +151 -0
- package/dist/yjs.cjs.map +1 -0
- package/dist/yjs.mjs +26 -0
- package/dist/yjs.mjs.map +1 -0
- package/package.json +101 -0
- package/src/index.js +153 -0
- package/src/internals.js +44 -0
- package/src/structs/AbstractStruct.js +59 -0
- package/src/structs/ContentAny.js +115 -0
- package/src/structs/ContentBinary.js +93 -0
- package/src/structs/ContentDeleted.js +101 -0
- package/src/structs/ContentDoc.js +141 -0
- package/src/structs/ContentEmbed.js +98 -0
- package/src/structs/ContentFormat.js +105 -0
- package/src/structs/ContentJSON.js +119 -0
- package/src/structs/ContentString.js +113 -0
- package/src/structs/ContentType.js +176 -0
- package/src/structs/GC.js +80 -0
- package/src/structs/Item.js +845 -0
- package/src/structs/Skip.js +75 -0
- package/src/types/AbstractType.js +1434 -0
- package/src/types/YArray.js +270 -0
- package/src/types/YMap.js +244 -0
- package/src/types/YText.js +934 -0
- package/src/types/YXmlElement.js +227 -0
- package/src/types/YXmlFragment.js +266 -0
- package/src/types/YXmlHook.js +68 -0
- package/src/types/YXmlText.js +66 -0
- package/src/utils/AbstractConnector.js +25 -0
- package/src/utils/AttributionManager.js +619 -0
- package/src/utils/Doc.js +372 -0
- package/src/utils/EventHandler.js +87 -0
- package/src/utils/ID.js +89 -0
- package/src/utils/IdMap.js +629 -0
- package/src/utils/IdSet.js +823 -0
- package/src/utils/RelativePosition.js +352 -0
- package/src/utils/Snapshot.js +220 -0
- package/src/utils/StructSet.js +137 -0
- package/src/utils/StructStore.js +289 -0
- package/src/utils/Transaction.js +489 -0
- package/src/utils/UndoManager.js +391 -0
- package/src/utils/UpdateDecoder.js +281 -0
- package/src/utils/UpdateEncoder.js +320 -0
- package/src/utils/YEvent.js +216 -0
- package/src/utils/delta-helpers.js +54 -0
- package/src/utils/encoding.js +623 -0
- package/src/utils/isParentOf.js +21 -0
- package/src/utils/logging.js +21 -0
- package/src/utils/types.js +28 -0
- package/src/utils/updates.js +715 -0
- package/tests/testHelper.js +600 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import {
|
|
2
|
+
writeID,
|
|
3
|
+
readID,
|
|
4
|
+
compareIDs,
|
|
5
|
+
getState,
|
|
6
|
+
findRootTypeKey,
|
|
7
|
+
Item,
|
|
8
|
+
createID,
|
|
9
|
+
ContentType,
|
|
10
|
+
followRedone,
|
|
11
|
+
getItem,
|
|
12
|
+
StructStore, ID, Doc, AbstractType, noAttributionsManager, // eslint-disable-line
|
|
13
|
+
} from '../internals.js'
|
|
14
|
+
|
|
15
|
+
import * as encoding from 'lib0/encoding'
|
|
16
|
+
import * as decoding from 'lib0/decoding'
|
|
17
|
+
import * as error from 'lib0/error'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A relative position is based on the Yjs model and is not affected by document changes.
|
|
21
|
+
* E.g. If you place a relative position before a certain character, it will always point to this character.
|
|
22
|
+
* If you place a relative position at the end of a type, it will always point to the end of the type.
|
|
23
|
+
*
|
|
24
|
+
* A numeric position is often unsuited for user selections, because it does not change when content is inserted
|
|
25
|
+
* before or after.
|
|
26
|
+
*
|
|
27
|
+
* ```Insert(0, 'x')('a|bc') = 'xa|bc'``` Where | is the relative position.
|
|
28
|
+
*
|
|
29
|
+
* One of the properties must be defined.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // Current cursor position is at position 10
|
|
33
|
+
* const relativePosition = createRelativePositionFromIndex(yText, 10)
|
|
34
|
+
* // modify yText
|
|
35
|
+
* yText.insert(0, 'abc')
|
|
36
|
+
* yText.delete(3, 10)
|
|
37
|
+
* // Compute the cursor position
|
|
38
|
+
* const absolutePosition = createAbsolutePositionFromRelativePosition(y, relativePosition)
|
|
39
|
+
* absolutePosition.type === yText // => true
|
|
40
|
+
* console.log('cursor location is ' + absolutePosition.index) // => cursor location is 3
|
|
41
|
+
*
|
|
42
|
+
*/
|
|
43
|
+
export class RelativePosition {
|
|
44
|
+
/**
|
|
45
|
+
* @param {ID|null} type
|
|
46
|
+
* @param {string|null} tname
|
|
47
|
+
* @param {ID|null} item
|
|
48
|
+
* @param {number} assoc
|
|
49
|
+
*/
|
|
50
|
+
constructor (type, tname, item, assoc = 0) {
|
|
51
|
+
/**
|
|
52
|
+
* @type {ID|null}
|
|
53
|
+
*/
|
|
54
|
+
this.type = type
|
|
55
|
+
/**
|
|
56
|
+
* @type {string|null}
|
|
57
|
+
*/
|
|
58
|
+
this.tname = tname
|
|
59
|
+
/**
|
|
60
|
+
* @type {ID | null}
|
|
61
|
+
*/
|
|
62
|
+
this.item = item
|
|
63
|
+
/**
|
|
64
|
+
* A relative position is associated to a specific character. By default
|
|
65
|
+
* assoc >= 0, the relative position is associated to the character
|
|
66
|
+
* after the meant position.
|
|
67
|
+
* I.e. position 1 in 'ab' is associated to character 'b'.
|
|
68
|
+
*
|
|
69
|
+
* If assoc < 0, then the relative position is associated to the character
|
|
70
|
+
* before the meant position.
|
|
71
|
+
*
|
|
72
|
+
* @type {number}
|
|
73
|
+
*/
|
|
74
|
+
this.assoc = assoc
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {RelativePosition} rpos
|
|
80
|
+
* @return {any}
|
|
81
|
+
*/
|
|
82
|
+
export const relativePositionToJSON = rpos => {
|
|
83
|
+
const json = {}
|
|
84
|
+
if (rpos.type) {
|
|
85
|
+
json.type = rpos.type
|
|
86
|
+
}
|
|
87
|
+
if (rpos.tname) {
|
|
88
|
+
json.tname = rpos.tname
|
|
89
|
+
}
|
|
90
|
+
if (rpos.item) {
|
|
91
|
+
json.item = rpos.item
|
|
92
|
+
}
|
|
93
|
+
if (rpos.assoc != null) {
|
|
94
|
+
json.assoc = rpos.assoc
|
|
95
|
+
}
|
|
96
|
+
return json
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {any} json
|
|
101
|
+
* @return {RelativePosition}
|
|
102
|
+
*
|
|
103
|
+
* @function
|
|
104
|
+
*/
|
|
105
|
+
export const createRelativePositionFromJSON = json => new RelativePosition(json.type == null ? null : createID(json.type.client, json.type.clock), json.tname ?? null, json.item == null ? null : createID(json.item.client, json.item.clock), json.assoc == null ? 0 : json.assoc)
|
|
106
|
+
|
|
107
|
+
export class AbsolutePosition {
|
|
108
|
+
/**
|
|
109
|
+
* @param {AbstractType<any>} type
|
|
110
|
+
* @param {number} index
|
|
111
|
+
* @param {number} [assoc]
|
|
112
|
+
*/
|
|
113
|
+
constructor (type, index, assoc = 0) {
|
|
114
|
+
/**
|
|
115
|
+
* @type {AbstractType<any>}
|
|
116
|
+
*/
|
|
117
|
+
this.type = type
|
|
118
|
+
/**
|
|
119
|
+
* @type {number}
|
|
120
|
+
*/
|
|
121
|
+
this.index = index
|
|
122
|
+
this.assoc = assoc
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {AbstractType<any>} type
|
|
128
|
+
* @param {number} index
|
|
129
|
+
* @param {number} [assoc]
|
|
130
|
+
*
|
|
131
|
+
* @function
|
|
132
|
+
*/
|
|
133
|
+
export const createAbsolutePosition = (type, index, assoc = 0) => new AbsolutePosition(type, index, assoc)
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {AbstractType<any>} type
|
|
137
|
+
* @param {ID|null} item
|
|
138
|
+
* @param {number} [assoc]
|
|
139
|
+
*
|
|
140
|
+
* @function
|
|
141
|
+
*/
|
|
142
|
+
export const createRelativePosition = (type, item, assoc) => {
|
|
143
|
+
let typeid = null
|
|
144
|
+
let tname = null
|
|
145
|
+
if (type._item === null) {
|
|
146
|
+
tname = findRootTypeKey(type)
|
|
147
|
+
} else {
|
|
148
|
+
typeid = createID(type._item.id.client, type._item.id.clock)
|
|
149
|
+
}
|
|
150
|
+
return new RelativePosition(typeid, tname, item, assoc)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Create a relativePosition based on a absolute position.
|
|
155
|
+
*
|
|
156
|
+
* @param {AbstractType} type The base type (e.g. YText or YArray).
|
|
157
|
+
* @param {number} index The absolute position.
|
|
158
|
+
* @param {number} [assoc]
|
|
159
|
+
* @param {import('../utils/AttributionManager.js').AbstractAttributionManager} attributionManager
|
|
160
|
+
* @return {RelativePosition}
|
|
161
|
+
*
|
|
162
|
+
* @function
|
|
163
|
+
*/
|
|
164
|
+
export const createRelativePositionFromTypeIndex = (type, index, assoc = 0, attributionManager = noAttributionsManager) => {
|
|
165
|
+
let t = type._start
|
|
166
|
+
if (assoc < 0) {
|
|
167
|
+
// associated to the left character or the beginning of a type, increment index if possible.
|
|
168
|
+
if (index === 0) {
|
|
169
|
+
return createRelativePosition(type, null, assoc)
|
|
170
|
+
}
|
|
171
|
+
index--
|
|
172
|
+
}
|
|
173
|
+
while (t !== null) {
|
|
174
|
+
const len = attributionManager.contentLength(t)
|
|
175
|
+
if (len > index) {
|
|
176
|
+
// case 1: found position somewhere in the linked list
|
|
177
|
+
return createRelativePosition(type, createID(t.id.client, t.id.clock + index), assoc)
|
|
178
|
+
}
|
|
179
|
+
index -= len
|
|
180
|
+
if (t.right === null && assoc < 0) {
|
|
181
|
+
// left-associated position, return last available id
|
|
182
|
+
return createRelativePosition(type, t.lastId, assoc)
|
|
183
|
+
}
|
|
184
|
+
t = t.right
|
|
185
|
+
}
|
|
186
|
+
return createRelativePosition(type, null, assoc)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @param {encoding.Encoder} encoder
|
|
191
|
+
* @param {RelativePosition} rpos
|
|
192
|
+
*
|
|
193
|
+
* @function
|
|
194
|
+
*/
|
|
195
|
+
export const writeRelativePosition = (encoder, rpos) => {
|
|
196
|
+
const { type, tname, item, assoc } = rpos
|
|
197
|
+
if (item !== null) {
|
|
198
|
+
encoding.writeVarUint(encoder, 0)
|
|
199
|
+
writeID(encoder, item)
|
|
200
|
+
} else if (tname !== null) {
|
|
201
|
+
// case 2: found position at the end of the list and type is stored in y.share
|
|
202
|
+
encoding.writeUint8(encoder, 1)
|
|
203
|
+
encoding.writeVarString(encoder, tname)
|
|
204
|
+
} else if (type !== null) {
|
|
205
|
+
// case 3: found position at the end of the list and type is attached to an item
|
|
206
|
+
encoding.writeUint8(encoder, 2)
|
|
207
|
+
writeID(encoder, type)
|
|
208
|
+
} else {
|
|
209
|
+
throw error.unexpectedCase()
|
|
210
|
+
}
|
|
211
|
+
encoding.writeVarInt(encoder, assoc)
|
|
212
|
+
return encoder
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @param {RelativePosition} rpos
|
|
217
|
+
* @return {Uint8Array}
|
|
218
|
+
*/
|
|
219
|
+
export const encodeRelativePosition = rpos => {
|
|
220
|
+
const encoder = encoding.createEncoder()
|
|
221
|
+
writeRelativePosition(encoder, rpos)
|
|
222
|
+
return encoding.toUint8Array(encoder)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @param {decoding.Decoder} decoder
|
|
227
|
+
* @return {RelativePosition}
|
|
228
|
+
*
|
|
229
|
+
* @function
|
|
230
|
+
*/
|
|
231
|
+
export const readRelativePosition = decoder => {
|
|
232
|
+
let type = null
|
|
233
|
+
let tname = null
|
|
234
|
+
let itemID = null
|
|
235
|
+
switch (decoding.readVarUint(decoder)) {
|
|
236
|
+
case 0:
|
|
237
|
+
// case 1: found position somewhere in the linked list
|
|
238
|
+
itemID = readID(decoder)
|
|
239
|
+
break
|
|
240
|
+
case 1:
|
|
241
|
+
// case 2: found position at the end of the list and type is stored in y.share
|
|
242
|
+
tname = decoding.readVarString(decoder)
|
|
243
|
+
break
|
|
244
|
+
case 2: {
|
|
245
|
+
// case 3: found position at the end of the list and type is attached to an item
|
|
246
|
+
type = readID(decoder)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const assoc = decoding.hasContent(decoder) ? decoding.readVarInt(decoder) : 0
|
|
250
|
+
return new RelativePosition(type, tname, itemID, assoc)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @param {Uint8Array} uint8Array
|
|
255
|
+
* @return {RelativePosition}
|
|
256
|
+
*/
|
|
257
|
+
export const decodeRelativePosition = uint8Array => readRelativePosition(decoding.createDecoder(uint8Array))
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @param {StructStore} store
|
|
261
|
+
* @param {ID} id
|
|
262
|
+
*/
|
|
263
|
+
const getItemWithOffset = (store, id) => {
|
|
264
|
+
const item = getItem(store, id)
|
|
265
|
+
const diff = id.clock - item.id.clock
|
|
266
|
+
return {
|
|
267
|
+
item, diff
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Transform a relative position to an absolute position.
|
|
273
|
+
*
|
|
274
|
+
* If you want to share the relative position with other users, you should set
|
|
275
|
+
* `followUndoneDeletions` to false to get consistent results across all clients.
|
|
276
|
+
*
|
|
277
|
+
* When calculating the absolute position, we try to follow the "undone deletions". This yields
|
|
278
|
+
* better results for the user who performed undo. However, only the user who performed the undo
|
|
279
|
+
* will get the better results, the other users don't know which operations recreated a deleted
|
|
280
|
+
* range of content. There is more information in this ticket: https://github.com/yjs/yjs/issues/638
|
|
281
|
+
*
|
|
282
|
+
* @param {RelativePosition} rpos
|
|
283
|
+
* @param {Doc} doc
|
|
284
|
+
* @param {boolean} followUndoneDeletions - whether to follow undone deletions - see https://github.com/yjs/yjs/issues/638
|
|
285
|
+
* @param {import('../utils/AttributionManager.js').AbstractAttributionManager} attributionManager
|
|
286
|
+
* @return {AbsolutePosition|null}
|
|
287
|
+
*
|
|
288
|
+
* @function
|
|
289
|
+
*/
|
|
290
|
+
export const createAbsolutePositionFromRelativePosition = (rpos, doc, followUndoneDeletions = true, attributionManager = noAttributionsManager) => {
|
|
291
|
+
const store = doc.store
|
|
292
|
+
const rightID = rpos.item
|
|
293
|
+
const typeID = rpos.type
|
|
294
|
+
const tname = rpos.tname
|
|
295
|
+
const assoc = rpos.assoc
|
|
296
|
+
let type = null
|
|
297
|
+
let index = 0
|
|
298
|
+
if (rightID !== null) {
|
|
299
|
+
if (getState(store, rightID.client) <= rightID.clock) {
|
|
300
|
+
return null
|
|
301
|
+
}
|
|
302
|
+
const res = followUndoneDeletions ? followRedone(store, rightID) : getItemWithOffset(store, rightID)
|
|
303
|
+
const right = res.item
|
|
304
|
+
if (!(right instanceof Item)) {
|
|
305
|
+
return null
|
|
306
|
+
}
|
|
307
|
+
type = /** @type {AbstractType<any>} */ (right.parent)
|
|
308
|
+
if (type._item === null || !type._item.deleted) {
|
|
309
|
+
index = attributionManager.contentLength(right) === 0 ? 0 : (res.diff + (assoc >= 0 ? 0 : 1)) // adjust position based on left association if necessary
|
|
310
|
+
let n = right.left
|
|
311
|
+
while (n !== null) {
|
|
312
|
+
index += attributionManager.contentLength(n)
|
|
313
|
+
n = n.left
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
if (tname !== null) {
|
|
318
|
+
type = doc.get(tname)
|
|
319
|
+
} else if (typeID !== null) {
|
|
320
|
+
if (getState(store, typeID.client) <= typeID.clock) {
|
|
321
|
+
// type does not exist yet
|
|
322
|
+
return null
|
|
323
|
+
}
|
|
324
|
+
const { item } = followUndoneDeletions ? followRedone(store, typeID) : { item: getItem(store, typeID) }
|
|
325
|
+
if (item instanceof Item && item.content instanceof ContentType) {
|
|
326
|
+
type = item.content.type
|
|
327
|
+
} else {
|
|
328
|
+
// struct is garbage collected
|
|
329
|
+
return null
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
throw error.unexpectedCase()
|
|
333
|
+
}
|
|
334
|
+
if (assoc >= 0) {
|
|
335
|
+
index = type._length
|
|
336
|
+
} else {
|
|
337
|
+
index = 0
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return createAbsolutePosition(type, index, rpos.assoc)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* @param {RelativePosition|null} a
|
|
345
|
+
* @param {RelativePosition|null} b
|
|
346
|
+
* @return {boolean}
|
|
347
|
+
*
|
|
348
|
+
* @function
|
|
349
|
+
*/
|
|
350
|
+
export const compareRelativePositions = (a, b) => a === b || (
|
|
351
|
+
a !== null && b !== null && a.tname === b.tname && compareIDs(a.item, b.item) && compareIDs(a.type, b.type) && a.assoc === b.assoc
|
|
352
|
+
)
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDeleteSetFromStructStore,
|
|
3
|
+
getStateVector,
|
|
4
|
+
getItemCleanStart,
|
|
5
|
+
iterateStructsByIdSet,
|
|
6
|
+
writeIdSet,
|
|
7
|
+
writeStateVector,
|
|
8
|
+
readIdSet,
|
|
9
|
+
readStateVector,
|
|
10
|
+
createIdSet,
|
|
11
|
+
createID,
|
|
12
|
+
getState,
|
|
13
|
+
findIndexSS,
|
|
14
|
+
UpdateEncoderV2,
|
|
15
|
+
applyUpdateV2,
|
|
16
|
+
LazyStructReader,
|
|
17
|
+
equalIdSets,
|
|
18
|
+
UpdateDecoderV1, UpdateDecoderV2, IdSetEncoderV1, IdSetEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, IdSet, Item, // eslint-disable-line
|
|
19
|
+
mergeIdSets
|
|
20
|
+
} from '../internals.js'
|
|
21
|
+
|
|
22
|
+
import * as map from 'lib0/map'
|
|
23
|
+
import * as set from 'lib0/set'
|
|
24
|
+
import * as decoding from 'lib0/decoding'
|
|
25
|
+
import * as encoding from 'lib0/encoding'
|
|
26
|
+
|
|
27
|
+
export class Snapshot {
|
|
28
|
+
/**
|
|
29
|
+
* @param {IdSet} ds
|
|
30
|
+
* @param {Map<number,number>} sv state map
|
|
31
|
+
*/
|
|
32
|
+
constructor (ds, sv) {
|
|
33
|
+
/**
|
|
34
|
+
* @type {IdSet}
|
|
35
|
+
*/
|
|
36
|
+
this.ds = ds
|
|
37
|
+
/**
|
|
38
|
+
* State Map
|
|
39
|
+
* @type {Map<number,number>}
|
|
40
|
+
*/
|
|
41
|
+
this.sv = sv
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {Snapshot} snap1
|
|
47
|
+
* @param {Snapshot} snap2
|
|
48
|
+
* @return {boolean}
|
|
49
|
+
*/
|
|
50
|
+
export const equalSnapshots = (snap1, snap2) => {
|
|
51
|
+
const sv1 = snap1.sv
|
|
52
|
+
const sv2 = snap2.sv
|
|
53
|
+
if (sv1.size !== sv2.size) {
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
for (const [key, value] of sv1.entries()) {
|
|
57
|
+
if (sv2.get(key) !== value) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return equalIdSets(snap1.ds, snap2.ds)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {Snapshot} snapshot
|
|
66
|
+
* @param {IdSetEncoderV1 | IdSetEncoderV2} [encoder]
|
|
67
|
+
* @return {Uint8Array}
|
|
68
|
+
*/
|
|
69
|
+
export const encodeSnapshotV2 = (snapshot, encoder = new IdSetEncoderV2()) => {
|
|
70
|
+
writeIdSet(encoder, snapshot.ds)
|
|
71
|
+
writeStateVector(encoder, snapshot.sv)
|
|
72
|
+
return encoder.toUint8Array()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {Snapshot} snapshot
|
|
77
|
+
* @return {Uint8Array}
|
|
78
|
+
*/
|
|
79
|
+
export const encodeSnapshot = snapshot => encodeSnapshotV2(snapshot, new IdSetEncoderV1())
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {Uint8Array} buf
|
|
83
|
+
* @param {DSDecoderV1 | DSDecoderV2} [decoder]
|
|
84
|
+
* @return {Snapshot}
|
|
85
|
+
*/
|
|
86
|
+
export const decodeSnapshotV2 = (buf, decoder = new DSDecoderV2(decoding.createDecoder(buf))) => {
|
|
87
|
+
return new Snapshot(readIdSet(decoder), readStateVector(decoder))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {Uint8Array} buf
|
|
92
|
+
* @return {Snapshot}
|
|
93
|
+
*/
|
|
94
|
+
export const decodeSnapshot = buf => decodeSnapshotV2(buf, new DSDecoderV1(decoding.createDecoder(buf)))
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {IdSet} ds
|
|
98
|
+
* @param {Map<number,number>} sm
|
|
99
|
+
* @return {Snapshot}
|
|
100
|
+
*/
|
|
101
|
+
export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
|
|
102
|
+
|
|
103
|
+
export const emptySnapshot = createSnapshot(createIdSet(), new Map())
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {Doc} doc
|
|
107
|
+
* @return {Snapshot}
|
|
108
|
+
*/
|
|
109
|
+
export const snapshot = doc => createSnapshot(createDeleteSetFromStructStore(doc.store), getStateVector(doc.store))
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {Item} item
|
|
113
|
+
* @param {Snapshot|undefined} snapshot
|
|
114
|
+
*
|
|
115
|
+
* @protected
|
|
116
|
+
* @function
|
|
117
|
+
*/
|
|
118
|
+
export const isVisible = (item, snapshot) => snapshot === undefined
|
|
119
|
+
? !item.deleted
|
|
120
|
+
: snapshot.sv.has(item.id.client) && (snapshot.sv.get(item.id.client) || 0) > item.id.clock && !snapshot.ds.hasId(item.id)
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {Transaction} transaction
|
|
124
|
+
* @param {Snapshot} snapshot
|
|
125
|
+
*/
|
|
126
|
+
export const splitSnapshotAffectedStructs = (transaction, snapshot) => {
|
|
127
|
+
const meta = map.setIfUndefined(transaction.meta, splitSnapshotAffectedStructs, set.create)
|
|
128
|
+
const store = transaction.doc.store
|
|
129
|
+
// check if we already split for this snapshot
|
|
130
|
+
if (!meta.has(snapshot)) {
|
|
131
|
+
snapshot.sv.forEach((clock, client) => {
|
|
132
|
+
if (clock < getState(store, client)) {
|
|
133
|
+
getItemCleanStart(transaction, createID(client, clock))
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
iterateStructsByIdSet(transaction, snapshot.ds, _item => {})
|
|
137
|
+
meta.add(snapshot)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @example
|
|
143
|
+
* const ydoc = new Y.Doc({ gc: false })
|
|
144
|
+
* ydoc.getText().insert(0, 'world!')
|
|
145
|
+
* const snapshot = Y.snapshot(ydoc)
|
|
146
|
+
* ydoc.getText().insert(0, 'hello ')
|
|
147
|
+
* const restored = Y.createDocFromSnapshot(ydoc, snapshot)
|
|
148
|
+
* assert(restored.getText().toString() === 'world!')
|
|
149
|
+
*
|
|
150
|
+
* @param {Doc} originDoc
|
|
151
|
+
* @param {Snapshot} snapshot
|
|
152
|
+
* @param {Doc} [newDoc] Optionally, you may define the Yjs document that receives the data from originDoc
|
|
153
|
+
* @return {Doc}
|
|
154
|
+
*/
|
|
155
|
+
export const createDocFromSnapshot = (originDoc, snapshot, newDoc = new Doc()) => {
|
|
156
|
+
if (originDoc.gc) {
|
|
157
|
+
// we should not try to restore a GC-ed document, because some of the restored items might have their content deleted
|
|
158
|
+
throw new Error('Garbage-collection must be disabled in `originDoc`!')
|
|
159
|
+
}
|
|
160
|
+
const { sv, ds } = snapshot
|
|
161
|
+
|
|
162
|
+
const encoder = new UpdateEncoderV2()
|
|
163
|
+
originDoc.transact(transaction => {
|
|
164
|
+
let size = 0
|
|
165
|
+
sv.forEach(clock => {
|
|
166
|
+
if (clock > 0) {
|
|
167
|
+
size++
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
encoding.writeVarUint(encoder.restEncoder, size)
|
|
171
|
+
// splitting the structs before writing them to the encoder
|
|
172
|
+
for (const [client, clock] of sv) {
|
|
173
|
+
if (clock === 0) {
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
if (clock < getState(originDoc.store, client)) {
|
|
177
|
+
getItemCleanStart(transaction, createID(client, clock))
|
|
178
|
+
}
|
|
179
|
+
const structs = originDoc.store.clients.get(client) || []
|
|
180
|
+
const lastStructIndex = findIndexSS(structs, clock - 1)
|
|
181
|
+
// write # encoded structs
|
|
182
|
+
encoding.writeVarUint(encoder.restEncoder, lastStructIndex + 1)
|
|
183
|
+
encoder.writeClient(client)
|
|
184
|
+
// first clock written is 0
|
|
185
|
+
encoding.writeVarUint(encoder.restEncoder, 0)
|
|
186
|
+
for (let i = 0; i <= lastStructIndex; i++) {
|
|
187
|
+
structs[i].write(encoder, 0, 0)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
writeIdSet(encoder, ds)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
applyUpdateV2(newDoc, encoder.toUint8Array(), 'snapshot')
|
|
194
|
+
return newDoc
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @param {Snapshot} snapshot
|
|
199
|
+
* @param {Uint8Array} update
|
|
200
|
+
* @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} [YDecoder]
|
|
201
|
+
*/
|
|
202
|
+
export const snapshotContainsUpdateV2 = (snapshot, update, YDecoder = UpdateDecoderV2) => {
|
|
203
|
+
const structs = []
|
|
204
|
+
const updateDecoder = new YDecoder(decoding.createDecoder(update))
|
|
205
|
+
const lazyDecoder = new LazyStructReader(updateDecoder, false)
|
|
206
|
+
for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) {
|
|
207
|
+
structs.push(curr)
|
|
208
|
+
if ((snapshot.sv.get(curr.id.client) || 0) < curr.id.clock + curr.length) {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const mergedDS = mergeIdSets([snapshot.ds, readIdSet(updateDecoder)])
|
|
213
|
+
return equalIdSets(snapshot.ds, mergedDS)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @param {Snapshot} snapshot
|
|
218
|
+
* @param {Uint8Array} update
|
|
219
|
+
*/
|
|
220
|
+
export const snapshotContainsUpdate = (snapshot, update) => snapshotContainsUpdateV2(snapshot, update, UpdateDecoderV1)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createID,
|
|
3
|
+
readItemContent,
|
|
4
|
+
findIndexCleanStart,
|
|
5
|
+
Skip,
|
|
6
|
+
UpdateDecoderV1, UpdateDecoderV2, IdSet, Doc, GC, Item, ID, // eslint-disable-line
|
|
7
|
+
} from '../internals.js'
|
|
8
|
+
|
|
9
|
+
import * as decoding from 'lib0/decoding'
|
|
10
|
+
import * as binary from 'lib0/binary'
|
|
11
|
+
import * as map from 'lib0/map'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder The decoder object to read data from.
|
|
15
|
+
* @param {Doc} doc
|
|
16
|
+
* @return {StructSet}
|
|
17
|
+
*
|
|
18
|
+
* @private
|
|
19
|
+
* @function
|
|
20
|
+
*/
|
|
21
|
+
export const readStructSet = (decoder, doc) => {
|
|
22
|
+
const clientRefs = new StructSet()
|
|
23
|
+
const numOfStateUpdates = decoding.readVarUint(decoder.restDecoder)
|
|
24
|
+
for (let i = 0; i < numOfStateUpdates; i++) {
|
|
25
|
+
const numberOfStructs = decoding.readVarUint(decoder.restDecoder)
|
|
26
|
+
/**
|
|
27
|
+
* @type {Array<GC|Item>}
|
|
28
|
+
*/
|
|
29
|
+
const refs = new Array(numberOfStructs)
|
|
30
|
+
const client = decoder.readClient()
|
|
31
|
+
let clock = decoding.readVarUint(decoder.restDecoder)
|
|
32
|
+
clientRefs.clients.set(client, new StructRange(refs))
|
|
33
|
+
for (let i = 0; i < numberOfStructs; i++) {
|
|
34
|
+
const info = decoder.readInfo()
|
|
35
|
+
switch (binary.BITS5 & info) {
|
|
36
|
+
case 0: { // GC
|
|
37
|
+
const len = decoder.readLen()
|
|
38
|
+
refs[i] = new GC(createID(client, clock), len)
|
|
39
|
+
clock += len
|
|
40
|
+
break
|
|
41
|
+
}
|
|
42
|
+
case 10: { // Skip Struct (nothing to apply)
|
|
43
|
+
// @todo we could reduce the amount of checks by adding Skip struct to clientRefs so we know that something is missing.
|
|
44
|
+
const len = decoding.readVarUint(decoder.restDecoder)
|
|
45
|
+
refs[i] = new Skip(createID(client, clock), len)
|
|
46
|
+
clock += len
|
|
47
|
+
break
|
|
48
|
+
}
|
|
49
|
+
default: { // Item with content
|
|
50
|
+
/**
|
|
51
|
+
* The optimized implementation doesn't use any variables because inlining variables is faster.
|
|
52
|
+
* Below a non-optimized version is shown that implements the basic algorithm with
|
|
53
|
+
* a few comments
|
|
54
|
+
*/
|
|
55
|
+
const cantCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
|
|
56
|
+
// If parent = null and neither left nor right are defined, then we know that `parent` is child of `y`
|
|
57
|
+
// and we read the next string as parentYKey.
|
|
58
|
+
// It indicates how we store/retrieve parent from `y.share`
|
|
59
|
+
// @type {string|null}
|
|
60
|
+
const struct = new Item(
|
|
61
|
+
createID(client, clock),
|
|
62
|
+
null, // left
|
|
63
|
+
(info & binary.BIT8) === binary.BIT8 ? decoder.readLeftID() : null, // origin
|
|
64
|
+
null, // right
|
|
65
|
+
(info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null, // right origin
|
|
66
|
+
cantCopyParentInfo ? (decoder.readParentInfo() ? doc.get(decoder.readString()) : decoder.readLeftID()) : null, // parent
|
|
67
|
+
cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub
|
|
68
|
+
readItemContent(decoder, info) // item content
|
|
69
|
+
)
|
|
70
|
+
refs[i] = struct
|
|
71
|
+
clock += struct.length
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return clientRefs
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Remove item-ranges from the StructSet.
|
|
81
|
+
*
|
|
82
|
+
* @param {StructSet} ss
|
|
83
|
+
* @param {IdSet} exclude
|
|
84
|
+
*/
|
|
85
|
+
export const removeRangesFromStructSet = (ss, exclude) => {
|
|
86
|
+
// @todo walk through ss instead to reduce iterations
|
|
87
|
+
exclude.clients.forEach((range, client) => {
|
|
88
|
+
const structs = /** @type {StructRange} */ (ss.clients.get(client))?.refs
|
|
89
|
+
if (structs != null) {
|
|
90
|
+
const firstStruct = structs[0]
|
|
91
|
+
const lastStruct = structs[structs.length - 1]
|
|
92
|
+
const idranges = range.getIds()
|
|
93
|
+
for (let i = 0; i < idranges.length; i++) {
|
|
94
|
+
const range = idranges[i]
|
|
95
|
+
let startIndex = 0
|
|
96
|
+
if (range.clock >= lastStruct.id.clock + lastStruct.length) continue
|
|
97
|
+
if (range.clock > firstStruct.id.clock) {
|
|
98
|
+
startIndex = findIndexCleanStart(null, structs, range.clock)
|
|
99
|
+
}
|
|
100
|
+
let endIndex = structs.length // must be set here, after structs is modified
|
|
101
|
+
if (range.clock + range.len <= firstStruct.id.clock) continue
|
|
102
|
+
if (range.clock + range.len < lastStruct.id.clock + lastStruct.length) {
|
|
103
|
+
endIndex = findIndexCleanStart(null, structs, range.clock + range.len)
|
|
104
|
+
}
|
|
105
|
+
if (startIndex < endIndex) {
|
|
106
|
+
structs[startIndex] = new Skip(new ID(client, range.clock), range.len)
|
|
107
|
+
const d = endIndex - startIndex
|
|
108
|
+
if (d > 1) {
|
|
109
|
+
structs.splice(startIndex + 1, d - 1)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class StructRange {
|
|
118
|
+
/**
|
|
119
|
+
* @param {Array<Item|GC>} refs
|
|
120
|
+
*/
|
|
121
|
+
constructor (refs) {
|
|
122
|
+
this.i = 0
|
|
123
|
+
/**
|
|
124
|
+
* @type {Array<Item | GC>}
|
|
125
|
+
*/
|
|
126
|
+
this.refs = refs
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export class StructSet {
|
|
131
|
+
constructor () {
|
|
132
|
+
/**
|
|
133
|
+
* @type {Map<number, StructRange>}
|
|
134
|
+
*/
|
|
135
|
+
this.clients = map.create()
|
|
136
|
+
}
|
|
137
|
+
}
|