@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.
Files changed (166) hide show
  1. package/README.md +36 -12
  2. package/dist/src/index.d.ts +24 -1
  3. package/dist/src/index.d.ts.map +1 -1
  4. package/dist/src/structs/AbstractStruct.d.ts +14 -17
  5. package/dist/src/structs/AbstractStruct.d.ts.map +1 -1
  6. package/dist/src/structs/GC.d.ts +9 -14
  7. package/dist/src/structs/GC.d.ts.map +1 -1
  8. package/dist/src/structs/Item.d.ts +569 -31
  9. package/dist/src/structs/Item.d.ts.map +1 -1
  10. package/dist/src/structs/Skip.d.ts +9 -11
  11. package/dist/src/structs/Skip.d.ts.map +1 -1
  12. package/dist/src/utils/BlockSet.d.ts +8 -7
  13. package/dist/src/utils/BlockSet.d.ts.map +1 -1
  14. package/dist/src/utils/Doc.d.ts +4 -9
  15. package/dist/src/utils/Doc.d.ts.map +1 -1
  16. package/dist/src/utils/ID.d.ts +0 -1
  17. package/dist/src/utils/ID.d.ts.map +1 -1
  18. package/dist/src/utils/RelativePosition.d.ts +2 -5
  19. package/dist/src/utils/RelativePosition.d.ts.map +1 -1
  20. package/dist/src/utils/Renderer.d.ts +144 -0
  21. package/dist/src/utils/Renderer.d.ts.map +1 -0
  22. package/dist/src/utils/Snapshot.d.ts +7 -11
  23. package/dist/src/utils/Snapshot.d.ts.map +1 -1
  24. package/dist/src/utils/StructStore.d.ts +45 -23
  25. package/dist/src/utils/StructStore.d.ts.map +1 -1
  26. package/dist/src/utils/Transaction.d.ts +11 -21
  27. package/dist/src/utils/Transaction.d.ts.map +1 -1
  28. package/dist/src/utils/UndoManager.d.ts +13 -14
  29. package/dist/src/utils/UndoManager.d.ts.map +1 -1
  30. package/dist/src/utils/UpdateDecoder.d.ts +1 -1
  31. package/dist/src/utils/UpdateDecoder.d.ts.map +1 -1
  32. package/dist/src/utils/UpdateEncoder.d.ts +0 -1
  33. package/dist/src/utils/UpdateEncoder.d.ts.map +1 -1
  34. package/dist/src/utils/YEvent.d.ts +22 -26
  35. package/dist/src/utils/YEvent.d.ts.map +1 -1
  36. package/dist/src/utils/content-helper.d.ts +2 -0
  37. package/dist/src/utils/content-helper.d.ts.map +1 -0
  38. package/dist/src/utils/delta-helpers.d.ts +2 -3
  39. package/dist/src/utils/delta-helpers.d.ts.map +1 -1
  40. package/dist/src/utils/encoding-helpers.d.ts +6 -0
  41. package/dist/src/utils/encoding-helpers.d.ts.map +1 -0
  42. package/dist/src/utils/encoding.d.ts +16 -18
  43. package/dist/src/utils/encoding.d.ts.map +1 -1
  44. package/dist/src/utils/ids.d.ts +331 -0
  45. package/dist/src/utils/ids.d.ts.map +1 -0
  46. package/dist/src/utils/isParentOf.d.ts +1 -2
  47. package/dist/src/utils/isParentOf.d.ts.map +1 -1
  48. package/dist/src/utils/logging.d.ts +0 -1
  49. package/dist/src/utils/logging.d.ts.map +1 -1
  50. package/dist/src/utils/meta.d.ts +15 -64
  51. package/dist/src/utils/meta.d.ts.map +1 -1
  52. package/dist/src/utils/renderer-helpers.d.ts +99 -0
  53. package/dist/src/utils/renderer-helpers.d.ts.map +1 -0
  54. package/dist/src/utils/schemas.d.ts +3 -0
  55. package/dist/src/utils/schemas.d.ts.map +1 -0
  56. package/dist/src/utils/transaction-helpers.d.ts +18 -0
  57. package/dist/src/utils/transaction-helpers.d.ts.map +1 -0
  58. package/dist/src/utils/updates.d.ts +19 -25
  59. package/dist/src/utils/updates.d.ts.map +1 -1
  60. package/dist/src/ytype.d.ts +144 -41
  61. package/dist/src/ytype.d.ts.map +1 -1
  62. package/global.d.ts +53 -0
  63. package/package.json +12 -16
  64. package/src/index.js +32 -124
  65. package/src/structs/AbstractStruct.js +21 -16
  66. package/src/structs/GC.js +15 -20
  67. package/src/structs/Item.js +992 -318
  68. package/src/structs/Skip.js +16 -19
  69. package/src/utils/BlockSet.js +20 -20
  70. package/src/utils/Doc.js +18 -29
  71. package/src/utils/ID.js +0 -2
  72. package/src/utils/RelativePosition.js +15 -25
  73. package/src/utils/{AttributionManager.js → Renderer.js} +42 -197
  74. package/src/utils/Snapshot.js +15 -37
  75. package/src/utils/StructStore.js +89 -227
  76. package/src/utils/Transaction.js +89 -321
  77. package/src/utils/UndoManager.js +157 -19
  78. package/src/utils/UpdateDecoder.js +2 -3
  79. package/src/utils/UpdateEncoder.js +0 -4
  80. package/src/utils/YEvent.js +20 -26
  81. package/src/utils/content-helper.js +0 -0
  82. package/src/utils/delta-helpers.js +21 -42
  83. package/src/utils/encoding-helpers.js +110 -0
  84. package/src/utils/encoding.js +197 -122
  85. package/src/utils/ids.js +1527 -0
  86. package/src/utils/isParentOf.js +2 -4
  87. package/src/utils/logging.js +0 -4
  88. package/src/utils/meta.js +57 -46
  89. package/src/utils/renderer-helpers.js +110 -0
  90. package/src/utils/schemas.js +3 -0
  91. package/src/utils/transaction-helpers.js +413 -0
  92. package/src/utils/updates.js +24 -146
  93. package/src/ytype.js +626 -255
  94. package/tests/testHelper.js +10 -12
  95. package/dist/src/internals.d.ts +0 -36
  96. package/dist/src/internals.d.ts.map +0 -1
  97. package/dist/src/structs/ContentAny.d.ts +0 -67
  98. package/dist/src/structs/ContentAny.d.ts.map +0 -1
  99. package/dist/src/structs/ContentBinary.d.ts +0 -64
  100. package/dist/src/structs/ContentBinary.d.ts.map +0 -1
  101. package/dist/src/structs/ContentDeleted.d.ts +0 -64
  102. package/dist/src/structs/ContentDeleted.d.ts.map +0 -1
  103. package/dist/src/structs/ContentDoc.d.ts +0 -72
  104. package/dist/src/structs/ContentDoc.d.ts.map +0 -1
  105. package/dist/src/structs/ContentEmbed.d.ts +0 -67
  106. package/dist/src/structs/ContentEmbed.d.ts.map +0 -1
  107. package/dist/src/structs/ContentFormat.d.ts +0 -69
  108. package/dist/src/structs/ContentFormat.d.ts.map +0 -1
  109. package/dist/src/structs/ContentJSON.d.ts +0 -70
  110. package/dist/src/structs/ContentJSON.d.ts.map +0 -1
  111. package/dist/src/structs/ContentString.d.ts +0 -70
  112. package/dist/src/structs/ContentString.d.ts.map +0 -1
  113. package/dist/src/structs/ContentType.d.ts +0 -77
  114. package/dist/src/structs/ContentType.d.ts.map +0 -1
  115. package/dist/src/utils/AttributionManager.d.ts +0 -238
  116. package/dist/src/utils/AttributionManager.d.ts.map +0 -1
  117. package/dist/src/utils/IdMap.d.ts +0 -164
  118. package/dist/src/utils/IdMap.d.ts.map +0 -1
  119. package/dist/src/utils/IdSet.d.ts +0 -163
  120. package/dist/src/utils/IdSet.d.ts.map +0 -1
  121. package/dist/tests/IdMap.tests.d.ts +0 -9
  122. package/dist/tests/IdMap.tests.d.ts.map +0 -1
  123. package/dist/tests/IdSet.tests.d.ts +0 -9
  124. package/dist/tests/IdSet.tests.d.ts.map +0 -1
  125. package/dist/tests/attribution.tests.d.ts +0 -9
  126. package/dist/tests/attribution.tests.d.ts.map +0 -1
  127. package/dist/tests/compatibility.tests.d.ts +0 -5
  128. package/dist/tests/compatibility.tests.d.ts.map +0 -1
  129. package/dist/tests/delta.tests.d.ts +0 -7
  130. package/dist/tests/delta.tests.d.ts.map +0 -1
  131. package/dist/tests/doc.tests.d.ts +0 -13
  132. package/dist/tests/doc.tests.d.ts.map +0 -1
  133. package/dist/tests/encoding.tests.d.ts +0 -5
  134. package/dist/tests/encoding.tests.d.ts.map +0 -1
  135. package/dist/tests/index.d.ts +0 -2
  136. package/dist/tests/index.d.ts.map +0 -1
  137. package/dist/tests/relativePositions.tests.d.ts +0 -11
  138. package/dist/tests/relativePositions.tests.d.ts.map +0 -1
  139. package/dist/tests/snapshot.tests.d.ts +0 -14
  140. package/dist/tests/snapshot.tests.d.ts.map +0 -1
  141. package/dist/tests/testHelper.d.ts +0 -171
  142. package/dist/tests/testHelper.d.ts.map +0 -1
  143. package/dist/tests/undo-redo.tests.d.ts +0 -27
  144. package/dist/tests/undo-redo.tests.d.ts.map +0 -1
  145. package/dist/tests/updates.tests.d.ts +0 -26
  146. package/dist/tests/updates.tests.d.ts.map +0 -1
  147. package/dist/tests/y-array.tests.d.ts +0 -43
  148. package/dist/tests/y-array.tests.d.ts.map +0 -1
  149. package/dist/tests/y-map.tests.d.ts +0 -42
  150. package/dist/tests/y-map.tests.d.ts.map +0 -1
  151. package/dist/tests/y-text.tests.d.ts +0 -49
  152. package/dist/tests/y-text.tests.d.ts.map +0 -1
  153. package/dist/tests/y-xml.tests.d.ts +0 -14
  154. package/dist/tests/y-xml.tests.d.ts.map +0 -1
  155. package/src/internals.js +0 -35
  156. package/src/structs/ContentAny.js +0 -115
  157. package/src/structs/ContentBinary.js +0 -93
  158. package/src/structs/ContentDeleted.js +0 -101
  159. package/src/structs/ContentDoc.js +0 -141
  160. package/src/structs/ContentEmbed.js +0 -98
  161. package/src/structs/ContentFormat.js +0 -105
  162. package/src/structs/ContentJSON.js +0 -119
  163. package/src/structs/ContentString.js +0 -113
  164. package/src/structs/ContentType.js +0 -152
  165. package/src/utils/IdMap.js +0 -673
  166. 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
+ }
@@ -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
- createID,
22
- decodeStateVector,
23
- IdSetEncoderV1,
24
- IdSetEncoderV2,
25
- GC,
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 {import('./meta.js').ContentIds}
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 {import('./meta.js').ContentIds}
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.doc.guid = i + ''
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 {import('./meta.js').ContentIds} contentIds - Pattern specifying which content to include
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, nextClock, curr.length)) {
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 = idset.intersectSets(ds, deletes)
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 {import('./meta.js').ContentIds} contentIds - Pattern specifying which content to include
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
- }