@y/y 14.0.0-rc.2 → 14.0.0-rc.21
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/README.md +36 -12
- package/dist/src/index.d.ts +24 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/structs/AbstractStruct.d.ts +14 -17
- package/dist/src/structs/AbstractStruct.d.ts.map +1 -1
- package/dist/src/structs/GC.d.ts +9 -14
- package/dist/src/structs/GC.d.ts.map +1 -1
- package/dist/src/structs/Item.d.ts +569 -31
- package/dist/src/structs/Item.d.ts.map +1 -1
- package/dist/src/structs/Skip.d.ts +9 -11
- package/dist/src/structs/Skip.d.ts.map +1 -1
- package/dist/src/utils/BlockSet.d.ts +8 -7
- package/dist/src/utils/BlockSet.d.ts.map +1 -1
- package/dist/src/utils/Doc.d.ts +4 -9
- package/dist/src/utils/Doc.d.ts.map +1 -1
- package/dist/src/utils/ID.d.ts +0 -1
- package/dist/src/utils/ID.d.ts.map +1 -1
- package/dist/src/utils/RelativePosition.d.ts +2 -5
- package/dist/src/utils/RelativePosition.d.ts.map +1 -1
- package/dist/src/utils/Renderer.d.ts +144 -0
- package/dist/src/utils/Renderer.d.ts.map +1 -0
- package/dist/src/utils/Snapshot.d.ts +7 -11
- package/dist/src/utils/Snapshot.d.ts.map +1 -1
- package/dist/src/utils/StructStore.d.ts +45 -23
- package/dist/src/utils/StructStore.d.ts.map +1 -1
- package/dist/src/utils/Transaction.d.ts +11 -21
- package/dist/src/utils/Transaction.d.ts.map +1 -1
- package/dist/src/utils/UndoManager.d.ts +13 -14
- package/dist/src/utils/UndoManager.d.ts.map +1 -1
- package/dist/src/utils/UpdateDecoder.d.ts +1 -1
- package/dist/src/utils/UpdateDecoder.d.ts.map +1 -1
- package/dist/src/utils/UpdateEncoder.d.ts +0 -1
- package/dist/src/utils/UpdateEncoder.d.ts.map +1 -1
- package/dist/src/utils/YEvent.d.ts +22 -26
- package/dist/src/utils/YEvent.d.ts.map +1 -1
- package/dist/src/utils/content-helper.d.ts +2 -0
- package/dist/src/utils/content-helper.d.ts.map +1 -0
- package/dist/src/utils/delta-helpers.d.ts +2 -3
- package/dist/src/utils/delta-helpers.d.ts.map +1 -1
- package/dist/src/utils/encoding-helpers.d.ts +6 -0
- package/dist/src/utils/encoding-helpers.d.ts.map +1 -0
- package/dist/src/utils/encoding.d.ts +16 -18
- package/dist/src/utils/encoding.d.ts.map +1 -1
- package/dist/src/utils/ids.d.ts +331 -0
- package/dist/src/utils/ids.d.ts.map +1 -0
- package/dist/src/utils/isParentOf.d.ts +1 -2
- package/dist/src/utils/isParentOf.d.ts.map +1 -1
- package/dist/src/utils/logging.d.ts +0 -1
- package/dist/src/utils/logging.d.ts.map +1 -1
- package/dist/src/utils/meta.d.ts +15 -64
- package/dist/src/utils/meta.d.ts.map +1 -1
- package/dist/src/utils/renderer-helpers.d.ts +99 -0
- package/dist/src/utils/renderer-helpers.d.ts.map +1 -0
- package/dist/src/utils/schemas.d.ts +3 -0
- package/dist/src/utils/schemas.d.ts.map +1 -0
- package/dist/src/utils/transaction-helpers.d.ts +18 -0
- package/dist/src/utils/transaction-helpers.d.ts.map +1 -0
- package/dist/src/utils/updates.d.ts +19 -25
- package/dist/src/utils/updates.d.ts.map +1 -1
- package/dist/src/ytype.d.ts +144 -41
- package/dist/src/ytype.d.ts.map +1 -1
- package/global.d.ts +53 -0
- package/package.json +12 -16
- package/src/index.js +32 -124
- package/src/structs/AbstractStruct.js +21 -16
- package/src/structs/GC.js +15 -20
- package/src/structs/Item.js +992 -318
- package/src/structs/Skip.js +16 -19
- package/src/utils/BlockSet.js +20 -20
- package/src/utils/Doc.js +18 -29
- package/src/utils/ID.js +0 -2
- package/src/utils/RelativePosition.js +15 -25
- package/src/utils/{AttributionManager.js → Renderer.js} +42 -197
- package/src/utils/Snapshot.js +15 -37
- package/src/utils/StructStore.js +89 -227
- package/src/utils/Transaction.js +89 -321
- package/src/utils/UndoManager.js +157 -19
- package/src/utils/UpdateDecoder.js +2 -3
- package/src/utils/UpdateEncoder.js +0 -4
- package/src/utils/YEvent.js +20 -26
- package/src/utils/content-helper.js +0 -0
- package/src/utils/delta-helpers.js +21 -42
- package/src/utils/encoding-helpers.js +110 -0
- package/src/utils/encoding.js +197 -122
- package/src/utils/ids.js +1527 -0
- package/src/utils/isParentOf.js +2 -4
- package/src/utils/logging.js +0 -4
- package/src/utils/meta.js +57 -46
- package/src/utils/renderer-helpers.js +110 -0
- package/src/utils/schemas.js +3 -0
- package/src/utils/transaction-helpers.js +413 -0
- package/src/utils/updates.js +24 -146
- package/src/ytype.js +626 -255
- package/tests/testHelper.js +10 -12
- package/dist/src/internals.d.ts +0 -36
- package/dist/src/internals.d.ts.map +0 -1
- package/dist/src/structs/ContentAny.d.ts +0 -67
- package/dist/src/structs/ContentAny.d.ts.map +0 -1
- package/dist/src/structs/ContentBinary.d.ts +0 -64
- package/dist/src/structs/ContentBinary.d.ts.map +0 -1
- package/dist/src/structs/ContentDeleted.d.ts +0 -64
- package/dist/src/structs/ContentDeleted.d.ts.map +0 -1
- package/dist/src/structs/ContentDoc.d.ts +0 -72
- package/dist/src/structs/ContentDoc.d.ts.map +0 -1
- package/dist/src/structs/ContentEmbed.d.ts +0 -67
- package/dist/src/structs/ContentEmbed.d.ts.map +0 -1
- package/dist/src/structs/ContentFormat.d.ts +0 -69
- package/dist/src/structs/ContentFormat.d.ts.map +0 -1
- package/dist/src/structs/ContentJSON.d.ts +0 -70
- package/dist/src/structs/ContentJSON.d.ts.map +0 -1
- package/dist/src/structs/ContentString.d.ts +0 -70
- package/dist/src/structs/ContentString.d.ts.map +0 -1
- package/dist/src/structs/ContentType.d.ts +0 -77
- package/dist/src/structs/ContentType.d.ts.map +0 -1
- package/dist/src/utils/AttributionManager.d.ts +0 -238
- package/dist/src/utils/AttributionManager.d.ts.map +0 -1
- package/dist/src/utils/IdMap.d.ts +0 -164
- package/dist/src/utils/IdMap.d.ts.map +0 -1
- package/dist/src/utils/IdSet.d.ts +0 -163
- package/dist/src/utils/IdSet.d.ts.map +0 -1
- package/dist/tests/IdMap.tests.d.ts +0 -9
- package/dist/tests/IdMap.tests.d.ts.map +0 -1
- package/dist/tests/IdSet.tests.d.ts +0 -9
- package/dist/tests/IdSet.tests.d.ts.map +0 -1
- package/dist/tests/attribution.tests.d.ts +0 -9
- package/dist/tests/attribution.tests.d.ts.map +0 -1
- package/dist/tests/compatibility.tests.d.ts +0 -5
- package/dist/tests/compatibility.tests.d.ts.map +0 -1
- package/dist/tests/delta.tests.d.ts +0 -7
- package/dist/tests/delta.tests.d.ts.map +0 -1
- package/dist/tests/doc.tests.d.ts +0 -13
- package/dist/tests/doc.tests.d.ts.map +0 -1
- package/dist/tests/encoding.tests.d.ts +0 -5
- package/dist/tests/encoding.tests.d.ts.map +0 -1
- package/dist/tests/index.d.ts +0 -2
- package/dist/tests/index.d.ts.map +0 -1
- package/dist/tests/relativePositions.tests.d.ts +0 -11
- package/dist/tests/relativePositions.tests.d.ts.map +0 -1
- package/dist/tests/snapshot.tests.d.ts +0 -14
- package/dist/tests/snapshot.tests.d.ts.map +0 -1
- package/dist/tests/testHelper.d.ts +0 -171
- package/dist/tests/testHelper.d.ts.map +0 -1
- package/dist/tests/undo-redo.tests.d.ts +0 -27
- package/dist/tests/undo-redo.tests.d.ts.map +0 -1
- package/dist/tests/updates.tests.d.ts +0 -26
- package/dist/tests/updates.tests.d.ts.map +0 -1
- package/dist/tests/y-array.tests.d.ts +0 -43
- package/dist/tests/y-array.tests.d.ts.map +0 -1
- package/dist/tests/y-map.tests.d.ts +0 -42
- package/dist/tests/y-map.tests.d.ts.map +0 -1
- package/dist/tests/y-text.tests.d.ts +0 -49
- package/dist/tests/y-text.tests.d.ts.map +0 -1
- package/dist/tests/y-xml.tests.d.ts +0 -14
- package/dist/tests/y-xml.tests.d.ts.map +0 -1
- package/src/internals.js +0 -35
- package/src/structs/ContentAny.js +0 -115
- package/src/structs/ContentBinary.js +0 -93
- package/src/structs/ContentDeleted.js +0 -101
- package/src/structs/ContentDoc.js +0 -141
- package/src/structs/ContentEmbed.js +0 -98
- package/src/structs/ContentFormat.js +0 -105
- package/src/structs/ContentJSON.js +0 -119
- package/src/structs/ContentString.js +0 -113
- package/src/structs/ContentType.js +0 -152
- package/src/utils/IdMap.js +0 -673
- package/src/utils/IdSet.js +0 -825
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import * as math from 'lib0/math'
|
|
2
|
+
import * as error from 'lib0/error'
|
|
3
|
+
import * as map from 'lib0/map'
|
|
4
|
+
import * as set from 'lib0/set'
|
|
5
|
+
|
|
6
|
+
import { createID } from './ID.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* These modules don't require any imports.
|
|
10
|
+
* These helpers are used by items to integrate themselves
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Perform a binary search on a sorted array
|
|
15
|
+
* @param {Array<Item|GC|Skip>} structs
|
|
16
|
+
* @param {number} clock
|
|
17
|
+
* @return {number}
|
|
18
|
+
*
|
|
19
|
+
* @private
|
|
20
|
+
* @function
|
|
21
|
+
*/
|
|
22
|
+
export const findIndexSS = (structs, clock) => {
|
|
23
|
+
let left = 0
|
|
24
|
+
let right = structs.length - 1
|
|
25
|
+
let mid = structs[right]
|
|
26
|
+
let midclock = mid.id.clock
|
|
27
|
+
if (midclock === clock) {
|
|
28
|
+
return right
|
|
29
|
+
}
|
|
30
|
+
// @todo does it even make sense to pivot the search?
|
|
31
|
+
// If a good split misses, it might actually increase the time to find the correct item.
|
|
32
|
+
// Currently, the only advantage is that search with pivoting might find the item on the first try.
|
|
33
|
+
let midindex = math.floor((clock / (midclock + mid.length - 1)) * right) // pivoting the search
|
|
34
|
+
while (left <= right) {
|
|
35
|
+
mid = structs[midindex]
|
|
36
|
+
midclock = mid.id.clock
|
|
37
|
+
if (midclock <= clock) {
|
|
38
|
+
if (clock < midclock + mid.length) {
|
|
39
|
+
return midindex
|
|
40
|
+
}
|
|
41
|
+
left = midindex + 1
|
|
42
|
+
} else {
|
|
43
|
+
right = midindex - 1
|
|
44
|
+
}
|
|
45
|
+
midindex = math.floor((left + right) / 2)
|
|
46
|
+
}
|
|
47
|
+
// Always check state before looking for a struct in StructStore
|
|
48
|
+
// Therefore the case of not finding a struct is unexpected
|
|
49
|
+
throw error.unexpectedCase()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Transaction?} transaction
|
|
54
|
+
* @param {Array<Item|GC|Skip>} structs
|
|
55
|
+
* @param {number} clock
|
|
56
|
+
*/
|
|
57
|
+
export const findIndexCleanStart = (transaction, structs, clock) => {
|
|
58
|
+
const index = findIndexSS(structs, clock)
|
|
59
|
+
const struct = structs[index]
|
|
60
|
+
if (struct.id.clock < clock) {
|
|
61
|
+
structs.splice(index + 1, 0, splitStruct(transaction, struct, clock - struct.id.clock))
|
|
62
|
+
return index + 1
|
|
63
|
+
}
|
|
64
|
+
return index
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Expects that id is actually in store. This function throws or is an infinite loop otherwise.
|
|
69
|
+
*
|
|
70
|
+
* @param {Transaction} transaction
|
|
71
|
+
* @param {ID} id
|
|
72
|
+
* @return {Item}
|
|
73
|
+
*
|
|
74
|
+
* @private
|
|
75
|
+
* @function
|
|
76
|
+
*/
|
|
77
|
+
export const getItemCleanStart = (transaction, id) => {
|
|
78
|
+
const structs = /** @type {Array<Item>} */ (transaction.doc.store.clients.get(id.client))
|
|
79
|
+
return structs[findIndexCleanStart(transaction, structs, id.clock)]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Expects that id is actually in store. This function throws or is an infinite loop otherwise.
|
|
84
|
+
*
|
|
85
|
+
* @param {Transaction} transaction
|
|
86
|
+
* @param {StructStore} store
|
|
87
|
+
* @param {ID} id
|
|
88
|
+
* @return {Item}
|
|
89
|
+
*
|
|
90
|
+
* @private
|
|
91
|
+
* @function
|
|
92
|
+
*/
|
|
93
|
+
export const getItemCleanEnd = (transaction, store, id) => {
|
|
94
|
+
const structs = store.clients.get(id.client) || []
|
|
95
|
+
const index = findIndexSS(structs, id.clock)
|
|
96
|
+
const struct = structs[index]
|
|
97
|
+
if (id.clock !== struct.id.clock + struct.length - 1 && struct.isItem) {
|
|
98
|
+
structs.splice(index + 1, 0, /** @type {Item} */ (struct).split(transaction, id.clock - struct.id.clock + 1))
|
|
99
|
+
}
|
|
100
|
+
return /** @type {Item} */ (struct)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Replace `item` with `newitem` in store
|
|
105
|
+
* @param {Transaction} tr
|
|
106
|
+
* @param {GC|Item} struct
|
|
107
|
+
* @param {GC|Item} newStruct
|
|
108
|
+
*
|
|
109
|
+
* @private
|
|
110
|
+
* @function
|
|
111
|
+
*/
|
|
112
|
+
export const replaceStruct = (tr, struct, newStruct) => {
|
|
113
|
+
const structs = /** @type {Array<GC|Item>} */ (tr.doc.store.clients.get(struct.id.client))
|
|
114
|
+
structs[findIndexSS(structs, struct.id.clock)] = newStruct
|
|
115
|
+
tr._mergeStructs.push(newStruct)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Iterate over a range of structs
|
|
120
|
+
*
|
|
121
|
+
* @param {Transaction} transaction
|
|
122
|
+
* @param {Array<Item|GC>} structs
|
|
123
|
+
* @param {number} clockStart Inclusive start
|
|
124
|
+
* @param {number} len
|
|
125
|
+
* @param {function(GC|Item):void} f
|
|
126
|
+
*
|
|
127
|
+
* @function
|
|
128
|
+
*/
|
|
129
|
+
export const iterateStructs = (transaction, structs, clockStart, len, f) => {
|
|
130
|
+
if (len === 0) {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
const clockEnd = clockStart + len
|
|
134
|
+
let index = findIndexCleanStart(transaction, structs, clockStart)
|
|
135
|
+
let struct
|
|
136
|
+
do {
|
|
137
|
+
struct = structs[index++]
|
|
138
|
+
if (clockEnd < struct.id.clock + struct.length) {
|
|
139
|
+
findIndexCleanStart(transaction, structs, clockEnd)
|
|
140
|
+
}
|
|
141
|
+
f(struct)
|
|
142
|
+
} while (index < structs.length && structs[index].id.clock < clockEnd)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Iterate over a range of structs without splitting the structs. You will get content that you did
|
|
147
|
+
* not expect.
|
|
148
|
+
*
|
|
149
|
+
* @param {Array<Item|GC>} structs
|
|
150
|
+
* @param {number} clockStart Inclusive start
|
|
151
|
+
* @param {number} len
|
|
152
|
+
* @param {(struct: GC|Item|Skip, offset:number, len:number)=>void} f
|
|
153
|
+
*
|
|
154
|
+
* @function
|
|
155
|
+
*/
|
|
156
|
+
export const iterateStructsWithoutSplits = (structs, clockStart, len, f) => {
|
|
157
|
+
if (len === 0) {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
const clockEnd = clockStart + len
|
|
161
|
+
for (
|
|
162
|
+
let index = findIndexSS(structs, clockStart), struct = structs[index], clock = struct.id.clock;
|
|
163
|
+
clock < clockEnd;
|
|
164
|
+
struct = structs[++index], clock = struct.id.clock
|
|
165
|
+
) {
|
|
166
|
+
const offset = clock < clockStart ? clockStart - clock : 0
|
|
167
|
+
f(struct, offset, math.min(clock + struct.length, clockEnd) - clock)
|
|
168
|
+
if (index + 1 === structs.length) break
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* More generalized version of splitItem. Split leftStruct into two structs
|
|
174
|
+
* @param {Transaction?} transaction
|
|
175
|
+
* @param {Item|GC|Skip} leftStruct
|
|
176
|
+
* @param {number} diff
|
|
177
|
+
* @return {GC|Item|Skip}
|
|
178
|
+
*
|
|
179
|
+
* @function
|
|
180
|
+
* @private
|
|
181
|
+
*/
|
|
182
|
+
export const splitStruct = (transaction, leftStruct, diff) => {
|
|
183
|
+
if (leftStruct.isItem) {
|
|
184
|
+
return /** @type {Item} */ (leftStruct).split(transaction, diff)
|
|
185
|
+
} else {
|
|
186
|
+
const rightItem = leftStruct.splice(diff)
|
|
187
|
+
transaction?._mergeStructs.push(rightItem)
|
|
188
|
+
return rightItem
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @param {Transaction} transaction
|
|
194
|
+
*
|
|
195
|
+
* @private
|
|
196
|
+
* @function
|
|
197
|
+
*/
|
|
198
|
+
export const nextID = transaction => {
|
|
199
|
+
const y = transaction.doc
|
|
200
|
+
return createID(y.clientID, y.store.getClock(y.clientID))
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* If `type.parent` was added in current transaction, `type` technically
|
|
205
|
+
* did not change, it was just added and we should not fire events for `type`.
|
|
206
|
+
*
|
|
207
|
+
* @param {Transaction} transaction
|
|
208
|
+
* @param {YType} type
|
|
209
|
+
* @param {string|null} parentSub
|
|
210
|
+
*/
|
|
211
|
+
export const addChangedTypeToTransaction = (transaction, type, parentSub) => {
|
|
212
|
+
const item = type._item
|
|
213
|
+
if (item === null || !transaction.insertSet.hasId(item.id)) {
|
|
214
|
+
map.setIfUndefined(transaction.changed, type, set.create).add(parentSub)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @param {Array<GC | Item | Skip>} structs
|
|
220
|
+
* @param {number} pos
|
|
221
|
+
* @return {number} # of merged structs
|
|
222
|
+
*/
|
|
223
|
+
export const tryToMergeWithLefts = (structs, pos) => {
|
|
224
|
+
let right = structs[pos]
|
|
225
|
+
let left = structs[pos - 1]
|
|
226
|
+
let i = pos
|
|
227
|
+
for (; i > 0; right = left, left = structs[--i - 1]) {
|
|
228
|
+
if (left.deleted === right.deleted && left.constructor === right.constructor) {
|
|
229
|
+
if (left.mergeWith(/** @type {any} */ (right))) {
|
|
230
|
+
if (right.isItem && /** @type {Item} */ (right).parentSub !== null && /** @type {YType} */ (/** @type {Item} */ (right).parent)._map.get(/** @type {Item} */ (right).parentSub) === right) {
|
|
231
|
+
/** @type {YType} */ (right.parent)._map.set(right.parentSub, /** @type {Item} */ (left))
|
|
232
|
+
}
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
break
|
|
237
|
+
}
|
|
238
|
+
const merged = pos - i
|
|
239
|
+
if (merged) {
|
|
240
|
+
// remove all merged structs from the array
|
|
241
|
+
structs.splice(pos + 1 - merged, merged)
|
|
242
|
+
}
|
|
243
|
+
return merged
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @param {Transaction} tr
|
|
248
|
+
* @param {IdSet} ds
|
|
249
|
+
* @param {function(Item):boolean} gcFilter
|
|
250
|
+
*/
|
|
251
|
+
export const tryGcDeleteSet = (tr, ds, gcFilter) => {
|
|
252
|
+
for (const [client, _deleteItems] of ds.clients.entries()) {
|
|
253
|
+
const deleteItems = _deleteItems.getIds()
|
|
254
|
+
const structs = /** @type {Array<Item>} */ (tr.doc.store.clients.get(client))
|
|
255
|
+
for (let di = deleteItems.length - 1; di >= 0; di--) {
|
|
256
|
+
const deleteItem = deleteItems[di]
|
|
257
|
+
const endDeleteItemClock = deleteItem.clock + deleteItem.len
|
|
258
|
+
for (
|
|
259
|
+
let si = findIndexSS(structs, deleteItem.clock), struct = structs[si];
|
|
260
|
+
si < structs.length && struct.id.clock < endDeleteItemClock;
|
|
261
|
+
struct = structs[++si]
|
|
262
|
+
) {
|
|
263
|
+
const struct = structs[si]
|
|
264
|
+
if (deleteItem.clock + deleteItem.len <= struct.id.clock) {
|
|
265
|
+
break
|
|
266
|
+
}
|
|
267
|
+
if (struct.isItem && struct.deleted && !(struct).keep && gcFilter(/** @type {Item} */ (struct))) {
|
|
268
|
+
/** @type {Item} */ (struct).gc(tr, false)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @param {IdSet} ds
|
|
277
|
+
* @param {StructStore} store
|
|
278
|
+
*/
|
|
279
|
+
export const tryMerge = (ds, store) => {
|
|
280
|
+
// try to merge deleted / gc'd items
|
|
281
|
+
// merge from right to left for better efficiency and so we don't miss any merge targets
|
|
282
|
+
ds.clients.forEach((_deleteItems, client) => {
|
|
283
|
+
const deleteItems = _deleteItems.getIds()
|
|
284
|
+
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
|
|
285
|
+
for (let di = deleteItems.length - 1; di >= 0; di--) {
|
|
286
|
+
const deleteItem = deleteItems[di]
|
|
287
|
+
// start with merging the item next to the last deleted item
|
|
288
|
+
const mostRightIndexToCheck = math.min(structs.length - 1, 1 + findIndexSS(structs, deleteItem.clock + deleteItem.len - 1))
|
|
289
|
+
for (
|
|
290
|
+
let si = mostRightIndexToCheck, struct = structs[si];
|
|
291
|
+
si > 0 && struct.id.clock >= deleteItem.clock;
|
|
292
|
+
struct = structs[si]
|
|
293
|
+
) {
|
|
294
|
+
si -= 1 + tryToMergeWithLefts(structs, si)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @param {Transaction} tr
|
|
302
|
+
* @param {IdSet} idset
|
|
303
|
+
* @param {function(Item):boolean} gcFilter
|
|
304
|
+
*/
|
|
305
|
+
export const tryGc = (tr, idset, gcFilter) => {
|
|
306
|
+
tryGcDeleteSet(tr, idset, gcFilter)
|
|
307
|
+
tryMerge(idset, tr.doc.store)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @param {Transaction} transaction
|
|
312
|
+
* @param {Item | null} item
|
|
313
|
+
*/
|
|
314
|
+
export const cleanupContextlessFormattingGap = (transaction, item) => {
|
|
315
|
+
if (!transaction.doc.cleanupFormatting) return 0
|
|
316
|
+
// iterate until item.right is null or content
|
|
317
|
+
while (item && item.right && (item.right.deleted || !item.right.countable)) {
|
|
318
|
+
item = item.right
|
|
319
|
+
}
|
|
320
|
+
const attrs = new Set()
|
|
321
|
+
// iterate back until a content item is found
|
|
322
|
+
while (item && (item.deleted || !item.countable)) {
|
|
323
|
+
if (!item.deleted && item.content.getRef() === 6) { // is a ContentFormat
|
|
324
|
+
const key = /** @type {ContentFormat} */ (item.content).key
|
|
325
|
+
if (attrs.has(key)) {
|
|
326
|
+
item.delete(transaction)
|
|
327
|
+
transaction.cleanUps.add(item.id.client, item.id.clock, item.length)
|
|
328
|
+
} else {
|
|
329
|
+
attrs.add(key)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
item = item.left
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @param {Map<string,any>} currentFormats
|
|
338
|
+
* @param {ContentFormat} format
|
|
339
|
+
*
|
|
340
|
+
* @private
|
|
341
|
+
* @function
|
|
342
|
+
*/
|
|
343
|
+
export const updateCurrentFormats = (currentFormats, { key, value }) => {
|
|
344
|
+
if (value === null) {
|
|
345
|
+
currentFormats.delete(key)
|
|
346
|
+
} else {
|
|
347
|
+
currentFormats.set(key, value)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Call this function after string content has been deleted in order to
|
|
353
|
+
* clean up formatting Items.
|
|
354
|
+
*
|
|
355
|
+
* @param {Transaction} transaction
|
|
356
|
+
* @param {Item} start
|
|
357
|
+
* @param {Item|null} curr exclusive end, automatically iterates to the next Content Item
|
|
358
|
+
* @param {Map<string,any>} startFormats
|
|
359
|
+
* @param {Map<string,any>} currFormats
|
|
360
|
+
* @return {number} The amount of formatting Items deleted.
|
|
361
|
+
*
|
|
362
|
+
* @function
|
|
363
|
+
*/
|
|
364
|
+
export const cleanupFormattingGap = (transaction, start, curr, startFormats, currFormats) => {
|
|
365
|
+
if (!transaction.doc.cleanupFormatting) return 0
|
|
366
|
+
/**
|
|
367
|
+
* @type {Item|null}
|
|
368
|
+
*/
|
|
369
|
+
let end = start
|
|
370
|
+
/**
|
|
371
|
+
* @type {Map<string,ContentFormat>}
|
|
372
|
+
*/
|
|
373
|
+
const endFormats = map.create()
|
|
374
|
+
while (end && (!end.countable || end.deleted)) {
|
|
375
|
+
if (!end.deleted && end.content.getRef() === 6) {
|
|
376
|
+
const cf = /** @type {ContentFormat} */ (end.content)
|
|
377
|
+
endFormats.set(cf.key, cf)
|
|
378
|
+
}
|
|
379
|
+
end = end.right
|
|
380
|
+
}
|
|
381
|
+
let cleanups = 0
|
|
382
|
+
let reachedCurr = false
|
|
383
|
+
while (start !== end) {
|
|
384
|
+
if (curr === start) {
|
|
385
|
+
reachedCurr = true
|
|
386
|
+
}
|
|
387
|
+
if (!start.deleted) {
|
|
388
|
+
const content = start.content
|
|
389
|
+
if (content.getRef() === 6) { // is ContentFormat
|
|
390
|
+
const { key, value } = /** @type {ContentFormat} */ (content)
|
|
391
|
+
const startFormatValue = startFormats.get(key) ?? null
|
|
392
|
+
if (endFormats.get(key) !== content || startFormatValue === value) {
|
|
393
|
+
// Either this format is overwritten or it is not necessary because the format already existed.
|
|
394
|
+
start.delete(transaction)
|
|
395
|
+
transaction.cleanUps.add(start.id.client, start.id.clock, start.length)
|
|
396
|
+
cleanups++
|
|
397
|
+
if (!reachedCurr && (currFormats.get(key) ?? null) === value && startFormatValue !== value) {
|
|
398
|
+
if (startFormatValue === null) {
|
|
399
|
+
currFormats.delete(key)
|
|
400
|
+
} else {
|
|
401
|
+
currFormats.set(key, startFormatValue)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (!reachedCurr && !start.deleted) {
|
|
406
|
+
updateCurrentFormats(currFormats, /** @type {ContentFormat} */ (content))
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
start = /** @type {Item} */ (start.right)
|
|
411
|
+
}
|
|
412
|
+
return cleanups
|
|
413
|
+
}
|
package/src/utils/updates.js
CHANGED
|
@@ -5,10 +5,16 @@ import * as error from 'lib0/error'
|
|
|
5
5
|
import * as f from 'lib0/function'
|
|
6
6
|
import * as logging from 'lib0/logging'
|
|
7
7
|
import * as map from 'lib0/map'
|
|
8
|
-
import * as math from 'lib0/math'
|
|
9
8
|
import * as string from 'lib0/string'
|
|
10
9
|
|
|
10
|
+
import { readIdSet, writeIdSet, createIdSet, intersectSets } from './ids.js'
|
|
11
|
+
|
|
12
|
+
import { createID } from './ID.js'
|
|
13
|
+
import { IdSetEncoderV1, IdSetEncoderV2, UpdateEncoderV1, UpdateEncoderV2 } from './UpdateEncoder.js'
|
|
14
|
+
import { UpdateDecoderV1, UpdateDecoderV2 } from './UpdateDecoder.js'
|
|
15
|
+
import { GC } from '../structs/GC.js'
|
|
11
16
|
import {
|
|
17
|
+
Item,
|
|
12
18
|
ContentAny,
|
|
13
19
|
ContentBinary,
|
|
14
20
|
ContentDeleted,
|
|
@@ -17,32 +23,12 @@ import {
|
|
|
17
23
|
ContentFormat,
|
|
18
24
|
ContentJSON,
|
|
19
25
|
ContentString,
|
|
20
|
-
ContentType
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Item,
|
|
27
|
-
mergeIdSets,
|
|
28
|
-
readIdSet,
|
|
29
|
-
readItemContent,
|
|
30
|
-
Skip,
|
|
31
|
-
UpdateDecoderV1,
|
|
32
|
-
UpdateDecoderV2,
|
|
33
|
-
UpdateEncoderV1,
|
|
34
|
-
UpdateEncoderV2,
|
|
35
|
-
writeIdSet,
|
|
36
|
-
createIdSet,
|
|
37
|
-
Doc,
|
|
38
|
-
applyUpdate,
|
|
39
|
-
applyUpdateV2,
|
|
40
|
-
readBlockSet,
|
|
41
|
-
writeBlockSet,
|
|
42
|
-
encodeStateAsUpdateV2
|
|
43
|
-
} from '../internals.js'
|
|
44
|
-
|
|
45
|
-
import * as idset from './IdSet.js'
|
|
26
|
+
ContentType
|
|
27
|
+
} from '../structs/Item.js'
|
|
28
|
+
import {
|
|
29
|
+
readItemContent
|
|
30
|
+
} from '../ytype.js'
|
|
31
|
+
import { Skip } from '../structs/Skip.js'
|
|
46
32
|
|
|
47
33
|
/**
|
|
48
34
|
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
@@ -183,12 +169,6 @@ export class LazyStructWriter {
|
|
|
183
169
|
}
|
|
184
170
|
}
|
|
185
171
|
|
|
186
|
-
/**
|
|
187
|
-
* @param {Array<Uint8Array<ArrayBuffer>>} updates
|
|
188
|
-
* @return {Uint8Array<ArrayBuffer>}
|
|
189
|
-
*/
|
|
190
|
-
export const mergeUpdates = updates => mergeUpdatesV2(updates, UpdateDecoderV1, UpdateEncoderV1)
|
|
191
|
-
|
|
192
172
|
/**
|
|
193
173
|
* @param {Uint8Array} update
|
|
194
174
|
* @param {typeof IdSetEncoderV1 | typeof IdSetEncoderV2} YEncoder
|
|
@@ -252,7 +232,7 @@ export const encodeStateVectorFromUpdate = update => encodeStateVectorFromUpdate
|
|
|
252
232
|
/**
|
|
253
233
|
* @param {Uint8Array} update
|
|
254
234
|
* @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} [YDecoder]
|
|
255
|
-
* @return {
|
|
235
|
+
* @return {ContentIds}
|
|
256
236
|
*/
|
|
257
237
|
export const createContentIdsFromUpdateV2 = (update, YDecoder = UpdateDecoderV2) => {
|
|
258
238
|
const updateDecoder = new YDecoder(decoding.createDecoder(update))
|
|
@@ -284,7 +264,7 @@ export const createContentIdsFromUpdateV2 = (update, YDecoder = UpdateDecoderV2)
|
|
|
284
264
|
|
|
285
265
|
/**
|
|
286
266
|
* @param {Uint8Array} update
|
|
287
|
-
* @return {
|
|
267
|
+
* @return {ContentIds}
|
|
288
268
|
*/
|
|
289
269
|
export const createContentIdsFromUpdate = update => createContentIdsFromUpdateV2(update, UpdateDecoderV1)
|
|
290
270
|
|
|
@@ -294,7 +274,7 @@ export const createContentIdsFromUpdate = update => createContentIdsFromUpdateV2
|
|
|
294
274
|
*
|
|
295
275
|
* @param {Item | GC | Skip} left
|
|
296
276
|
* @param {number} diff
|
|
297
|
-
* @return {Item | GC}
|
|
277
|
+
* @return {Item | GC | Skip}
|
|
298
278
|
*/
|
|
299
279
|
export const sliceStruct = (left, diff) => {
|
|
300
280
|
if (left.constructor === GC) {
|
|
@@ -319,88 +299,6 @@ export const sliceStruct = (left, diff) => {
|
|
|
319
299
|
}
|
|
320
300
|
}
|
|
321
301
|
|
|
322
|
-
/**
|
|
323
|
-
*
|
|
324
|
-
* This function works similarly to `readUpdateV2`.
|
|
325
|
-
*
|
|
326
|
-
* @param {Array<Uint8Array<ArrayBuffer>>} updates
|
|
327
|
-
* @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
|
|
328
|
-
* @param {typeof UpdateEncoderV1 | typeof UpdateEncoderV2} [YEncoder]
|
|
329
|
-
* @return {Uint8Array<ArrayBuffer>}
|
|
330
|
-
*/
|
|
331
|
-
export const mergeUpdatesV2 = (updates, YDecoder = UpdateDecoderV2, YEncoder = UpdateEncoderV2) => {
|
|
332
|
-
if (updates.length === 1) {
|
|
333
|
-
return updates[0]
|
|
334
|
-
} else if (updates.length === 0) {
|
|
335
|
-
return encodeStateAsUpdateV2(new Doc(), new Uint8Array([0]), new YEncoder())
|
|
336
|
-
}
|
|
337
|
-
const updateDecoders = updates.map(update => new YDecoder(decoding.createDecoder(update)))
|
|
338
|
-
const blocksets = updateDecoders.map(dec => readBlockSet(dec))
|
|
339
|
-
|
|
340
|
-
const mergedBlockset = blocksets[0]
|
|
341
|
-
for (let i = 1; i < blocksets.length; i++) {
|
|
342
|
-
mergedBlockset.insertInto(blocksets[i])
|
|
343
|
-
}
|
|
344
|
-
const updateEncoder = new YEncoder()
|
|
345
|
-
writeBlockSet(updateEncoder, mergedBlockset)
|
|
346
|
-
const dss = updateDecoders.map(decoder => readIdSet(decoder))
|
|
347
|
-
const ds = mergeIdSets(dss)
|
|
348
|
-
writeIdSet(updateEncoder, ds)
|
|
349
|
-
return updateEncoder.toUint8Array()
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* @deprecated
|
|
354
|
-
* @param {Uint8Array} update
|
|
355
|
-
* @param {Uint8Array} sv
|
|
356
|
-
* @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
|
|
357
|
-
* @param {typeof UpdateEncoderV1 | typeof UpdateEncoderV2} [YEncoder]
|
|
358
|
-
*/
|
|
359
|
-
export const diffUpdateV2 = (update, sv, YDecoder = UpdateDecoderV2, YEncoder = UpdateEncoderV2) => {
|
|
360
|
-
const state = decodeStateVector(sv)
|
|
361
|
-
const encoder = new YEncoder()
|
|
362
|
-
const lazyStructWriter = new LazyStructWriter(encoder)
|
|
363
|
-
const decoder = new YDecoder(decoding.createDecoder(update))
|
|
364
|
-
const reader = new LazyStructReader(decoder, false)
|
|
365
|
-
while (reader.curr) {
|
|
366
|
-
const curr = reader.curr
|
|
367
|
-
const currClient = curr.id.client
|
|
368
|
-
const svClock = state.get(currClient) || 0
|
|
369
|
-
if (reader.curr.constructor === Skip) {
|
|
370
|
-
// the first written struct shouldn't be a skip
|
|
371
|
-
reader.next()
|
|
372
|
-
continue
|
|
373
|
-
}
|
|
374
|
-
if (curr.id.clock + curr.length > svClock) {
|
|
375
|
-
writeStructToLazyStructWriter(lazyStructWriter, curr, math.max(svClock - curr.id.clock, 0), 0)
|
|
376
|
-
reader.next()
|
|
377
|
-
while (reader.curr && reader.curr.id.client === currClient) {
|
|
378
|
-
writeStructToLazyStructWriter(lazyStructWriter, reader.curr, 0, 0)
|
|
379
|
-
reader.next()
|
|
380
|
-
}
|
|
381
|
-
} else {
|
|
382
|
-
// read until something new comes up
|
|
383
|
-
while (reader.curr && reader.curr.id.client === currClient && reader.curr.id.clock + reader.curr.length <= svClock) {
|
|
384
|
-
reader.next()
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
finishLazyStructWriting(lazyStructWriter)
|
|
389
|
-
// write ds
|
|
390
|
-
const ds = readIdSet(decoder)
|
|
391
|
-
writeIdSet(encoder, ds)
|
|
392
|
-
return encoder.toUint8Array()
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* @deprecated
|
|
397
|
-
* @todo remove this in favor of intersectupdate
|
|
398
|
-
*
|
|
399
|
-
* @param {Uint8Array<ArrayBuffer>} update
|
|
400
|
-
* @param {Uint8Array<ArrayBuffer>} sv
|
|
401
|
-
*/
|
|
402
|
-
export const diffUpdate = (update, sv) => diffUpdateV2(update, sv, UpdateDecoderV1, UpdateEncoderV1)
|
|
403
|
-
|
|
404
302
|
/**
|
|
405
303
|
* @param {LazyStructWriter} lazyWriter
|
|
406
304
|
*/
|
|
@@ -414,11 +312,11 @@ const flushLazyStructWriter = lazyWriter => {
|
|
|
414
312
|
|
|
415
313
|
/**
|
|
416
314
|
* @param {LazyStructWriter} lazyWriter
|
|
417
|
-
* @param {Item | GC} struct
|
|
315
|
+
* @param {Item | GC | Skip} struct
|
|
418
316
|
* @param {number} offset
|
|
419
317
|
* @param {number} offsetEnd
|
|
420
318
|
*/
|
|
421
|
-
const writeStructToLazyStructWriter = (lazyWriter, struct, offset, offsetEnd) => {
|
|
319
|
+
export const writeStructToLazyStructWriter = (lazyWriter, struct, offset, offsetEnd) => {
|
|
422
320
|
// flush curr if we start another client
|
|
423
321
|
if (lazyWriter.written > 0 && lazyWriter.currClient !== struct.id.client) {
|
|
424
322
|
flushLazyStructWriter(lazyWriter)
|
|
@@ -440,7 +338,7 @@ const writeStructToLazyStructWriter = (lazyWriter, struct, offset, offsetEnd) =>
|
|
|
440
338
|
*
|
|
441
339
|
* @param {LazyStructWriter} lazyWriter
|
|
442
340
|
*/
|
|
443
|
-
const finishLazyStructWriting = (lazyWriter) => {
|
|
341
|
+
export const finishLazyStructWriting = (lazyWriter) => {
|
|
444
342
|
flushLazyStructWriter(lazyWriter)
|
|
445
343
|
|
|
446
344
|
// this is a fresh encoder because we called flushCurr
|
|
@@ -541,7 +439,7 @@ const createObfuscator = ({ formatting = true, subdocs = true, name = true } = {
|
|
|
541
439
|
const c = /** @type {ContentDoc} */ (content)
|
|
542
440
|
if (subdocs) {
|
|
543
441
|
c.opts = {}
|
|
544
|
-
c.
|
|
442
|
+
c.guid = i + ''
|
|
545
443
|
}
|
|
546
444
|
break
|
|
547
445
|
}
|
|
@@ -625,7 +523,7 @@ export const convertUpdateFormatV2ToV1 = update => convertUpdateFormat(update, f
|
|
|
625
523
|
* overlapping portion is included in the result.
|
|
626
524
|
*
|
|
627
525
|
* @param {Uint8Array} update
|
|
628
|
-
* @param {
|
|
526
|
+
* @param {ContentIds} contentIds - Pattern specifying which content to include
|
|
629
527
|
* @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
|
|
630
528
|
* @param {typeof UpdateEncoderV1 | typeof UpdateEncoderV2} [YEncoder]
|
|
631
529
|
* @return {Uint8Array<ArrayBuffer>}
|
|
@@ -643,7 +541,7 @@ export const intersectUpdateWithContentIdsV2 = (update, contentIds, YDecoder = U
|
|
|
643
541
|
let firstWrite = false
|
|
644
542
|
while (reader.curr != null && reader.curr.id.client === currClientId) {
|
|
645
543
|
const curr = reader.curr
|
|
646
|
-
for (const slice of inserts.slice(currClientId,
|
|
544
|
+
for (const slice of inserts.slice(currClientId, curr.id.clock, curr.length)) {
|
|
647
545
|
if (slice.exists) {
|
|
648
546
|
const skipLen = slice.clock - nextClock
|
|
649
547
|
if (skipLen > 0 && firstWrite) {
|
|
@@ -662,7 +560,7 @@ export const intersectUpdateWithContentIdsV2 = (update, contentIds, YDecoder = U
|
|
|
662
560
|
finishLazyStructWriting(lazyStructWriter)
|
|
663
561
|
// Filter the delete set to only include entries in contentIds.deletes
|
|
664
562
|
const ds = readIdSet(decoder)
|
|
665
|
-
const filteredDs =
|
|
563
|
+
const filteredDs = intersectSets(ds, deletes)
|
|
666
564
|
writeIdSet(encoder, filteredDs)
|
|
667
565
|
return encoder.toUint8Array()
|
|
668
566
|
}
|
|
@@ -671,28 +569,8 @@ export const intersectUpdateWithContentIdsV2 = (update, contentIds, YDecoder = U
|
|
|
671
569
|
* Filter an update (V1 format) to only include content specified by a ContentIds pattern.
|
|
672
570
|
*
|
|
673
571
|
* @param {Uint8Array} update
|
|
674
|
-
* @param {
|
|
572
|
+
* @param {ContentIds} contentIds - Pattern specifying which content to include
|
|
675
573
|
* @return {Uint8Array<ArrayBuffer>}
|
|
676
574
|
*/
|
|
677
575
|
export const intersectUpdateWithContentIds = (update, contentIds) =>
|
|
678
576
|
intersectUpdateWithContentIdsV2(update, contentIds, UpdateDecoderV1, UpdateEncoderV1)
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* @param {Uint8Array} update
|
|
682
|
-
* @param {import('./Doc.js').DocOpts} opts
|
|
683
|
-
*/
|
|
684
|
-
export const createDocFromUpdate = (update, opts = {}) => {
|
|
685
|
-
const ydoc = new Doc(opts)
|
|
686
|
-
applyUpdate(ydoc, update)
|
|
687
|
-
return ydoc
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
/**
|
|
691
|
-
* @param {Uint8Array} update
|
|
692
|
-
* @param {import('./Doc.js').DocOpts} opts
|
|
693
|
-
*/
|
|
694
|
-
export const createDocFromUpdateV2 = (update, opts = {}) => {
|
|
695
|
-
const ydoc = new Doc(opts)
|
|
696
|
-
applyUpdateV2(ydoc, update)
|
|
697
|
-
return ydoc
|
|
698
|
-
}
|