@y/y 14.0.0-16 → 14.0.0-17

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 (68) hide show
  1. package/dist/{index-DyTeTfmj.js → index-BV-j5wdP.js} +2 -2
  2. package/dist/{index-R7GxO-36.js.map → index-BV-j5wdP.js.map} +1 -1
  3. package/dist/{internals.mjs → internals.js} +1 -1
  4. package/dist/internals.js.map +1 -0
  5. package/dist/{testHelper.mjs → testHelper.js} +2 -2
  6. package/dist/testHelper.js.map +1 -0
  7. package/dist/{yjs.mjs → yjs.js} +2 -2
  8. package/dist/yjs.js.map +1 -0
  9. package/package.json +8 -17
  10. package/dist/Skip-j0kX7pdq.js +0 -12173
  11. package/dist/Skip-j0kX7pdq.js.map +0 -1
  12. package/dist/index-DyTeTfmj.js.map +0 -1
  13. package/dist/index-R7GxO-36.js +0 -165
  14. package/dist/internals.cjs +0 -286
  15. package/dist/internals.cjs.map +0 -1
  16. package/dist/internals.mjs.map +0 -1
  17. package/dist/testHelper.cjs +0 -780
  18. package/dist/testHelper.cjs.map +0 -1
  19. package/dist/testHelper.mjs.map +0 -1
  20. package/dist/yjs.cjs +0 -151
  21. package/dist/yjs.cjs.map +0 -1
  22. package/dist/yjs.mjs.map +0 -1
  23. package/src/index.js +0 -153
  24. package/src/internals.js +0 -44
  25. package/src/structs/AbstractStruct.js +0 -59
  26. package/src/structs/ContentAny.js +0 -115
  27. package/src/structs/ContentBinary.js +0 -93
  28. package/src/structs/ContentDeleted.js +0 -101
  29. package/src/structs/ContentDoc.js +0 -141
  30. package/src/structs/ContentEmbed.js +0 -98
  31. package/src/structs/ContentFormat.js +0 -105
  32. package/src/structs/ContentJSON.js +0 -119
  33. package/src/structs/ContentString.js +0 -113
  34. package/src/structs/ContentType.js +0 -176
  35. package/src/structs/GC.js +0 -80
  36. package/src/structs/Item.js +0 -845
  37. package/src/structs/Skip.js +0 -75
  38. package/src/types/AbstractType.js +0 -1434
  39. package/src/types/YArray.js +0 -270
  40. package/src/types/YMap.js +0 -244
  41. package/src/types/YText.js +0 -934
  42. package/src/types/YXmlElement.js +0 -227
  43. package/src/types/YXmlFragment.js +0 -266
  44. package/src/types/YXmlHook.js +0 -68
  45. package/src/types/YXmlText.js +0 -66
  46. package/src/utils/AbstractConnector.js +0 -25
  47. package/src/utils/AttributionManager.js +0 -619
  48. package/src/utils/Doc.js +0 -372
  49. package/src/utils/EventHandler.js +0 -87
  50. package/src/utils/ID.js +0 -89
  51. package/src/utils/IdMap.js +0 -629
  52. package/src/utils/IdSet.js +0 -823
  53. package/src/utils/RelativePosition.js +0 -352
  54. package/src/utils/Snapshot.js +0 -220
  55. package/src/utils/StructSet.js +0 -137
  56. package/src/utils/StructStore.js +0 -289
  57. package/src/utils/Transaction.js +0 -489
  58. package/src/utils/UndoManager.js +0 -391
  59. package/src/utils/UpdateDecoder.js +0 -281
  60. package/src/utils/UpdateEncoder.js +0 -320
  61. package/src/utils/YEvent.js +0 -216
  62. package/src/utils/delta-helpers.js +0 -54
  63. package/src/utils/encoding.js +0 -623
  64. package/src/utils/isParentOf.js +0 -21
  65. package/src/utils/logging.js +0 -21
  66. package/src/utils/types.js +0 -28
  67. package/src/utils/updates.js +0 -715
  68. package/tests/testHelper.js +0 -600
@@ -1,619 +0,0 @@
1
- import {
2
- getItem,
3
- diffIdSet,
4
- createInsertSetFromStructStore,
5
- createDeleteSetFromStructStore,
6
- createIdMapFromIdSet,
7
- ContentDeleted,
8
- insertIntoIdMap,
9
- insertIntoIdSet,
10
- diffIdMap,
11
- createIdMap,
12
- createAttributionItem,
13
- mergeIdMaps,
14
- createID,
15
- mergeIdSets,
16
- applyUpdate,
17
- writeIdSet,
18
- UpdateEncoderV1,
19
- transact,
20
- createMaybeAttrRange,
21
- createIdSet,
22
- writeStructsFromIdSet,
23
- UndoManager,
24
- StackItem,
25
- getItemCleanStart,
26
- intersectSets,
27
- ContentFormat,
28
- StructStore, Transaction, ID, IdSet, Item, Snapshot, Doc, AbstractContent, IdMap // eslint-disable-line
29
- } from '../internals.js'
30
-
31
- import * as error from 'lib0/error'
32
- import { ObservableV2 } from 'lib0/observable'
33
- import * as encoding from 'lib0/encoding'
34
- import * as s from 'lib0/schema'
35
-
36
- export const attributionJsonSchema = s.$object({
37
- insert: s.$array(s.$string).optional,
38
- insertedAt: s.$number.optional,
39
- delete: s.$array(s.$string).optional,
40
- deletedAt: s.$number.optional,
41
- format: s.$record(s.$string, s.$array(s.$string)).optional,
42
- formatAt: s.$number.optional
43
- })
44
-
45
- /**
46
- * @todo rename this to `insertBy`, `insertAt`, ..
47
- *
48
- * @typedef {s.Unwrap<typeof attributionJsonSchema>} Attribution
49
- */
50
-
51
- /**
52
- * @todo SHOULD NOT RETURN AN OBJECT!
53
- * @param {Array<import('./IdMap.js').AttributionItem<any>>?} attrs
54
- * @param {boolean} deleted - whether the attributed item is deleted
55
- * @return {Attribution?}
56
- */
57
- export const createAttributionFromAttributionItems = (attrs, deleted) => {
58
- if (attrs == null) {
59
- return null
60
- }
61
- /**
62
- * @type {Attribution}
63
- */
64
- const attribution = {}
65
- if (deleted) {
66
- attribution.delete = s.$array(s.$string).cast([])
67
- } else {
68
- attribution.insert = []
69
- }
70
- attrs.forEach(attr => {
71
- switch (attr.name) {
72
- // eslint-disable-next-line no-fallthrough
73
- case 'insert':
74
- case 'delete': {
75
- const as = /** @type {import('lib0/delta').Attribution} */ (attribution)
76
- const ls = as[attr.name] = as[attr.name] ?? []
77
- ls.push(attr.val)
78
- break
79
- }
80
- default: {
81
- if (attr.name[0] !== '_') {
82
- /** @type {any} */ (attribution)[attr.name] = attr.val
83
- }
84
- }
85
- }
86
- })
87
- return attribution
88
- }
89
-
90
- /**
91
- * @template T
92
- */
93
- export class AttributedContent {
94
- /**
95
- * @param {AbstractContent} content
96
- * @param {number} clock
97
- * @param {boolean} deleted
98
- * @param {Array<import('./IdMap.js').AttributionItem<T>> | null} attrs
99
- * @param {0|1|2} renderBehavior
100
- */
101
- constructor (content, clock, deleted, attrs, renderBehavior) {
102
- this.content = content
103
- this.clock = clock
104
- this.deleted = deleted
105
- this.attrs = attrs
106
- this.render = renderBehavior === 0 ? false : (renderBehavior === 1 ? (!deleted || attrs != null) : true)
107
- }
108
- }
109
-
110
- /**
111
- * Abstract class for associating Attributions to content / changes
112
- *
113
- * Should fire an event when the attributions changed _after_ the original change happens. This
114
- * Event will be used to update the attribution on the current content.
115
- *
116
- * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>}
117
- */
118
- export class AbstractAttributionManager extends ObservableV2 {
119
- /**
120
- * @param {Array<AttributedContent<any>>} _contents - where to write the result
121
- * @param {number} _client
122
- * @param {number} _clock
123
- * @param {boolean} _deleted
124
- * @param {AbstractContent} _content
125
- * @param {0|1|2} _shouldRender - 0: if undeleted or attributed, render as a retain operation. 1: render only if undeleted or attributed. 2: render as insert operation (if unattributed and deleted, render as delete).
126
- */
127
- readContent (_contents, _client, _clock, _deleted, _content, _shouldRender) {
128
- error.methodUnimplemented()
129
- }
130
-
131
- /**
132
- * Calculate the length of the attributed content. This is used by iterators that walk through the
133
- * content.
134
- *
135
- * If the content is not countable, it should return 0.
136
- *
137
- * @param {Item} _item
138
- * @return {number}
139
- */
140
- contentLength (_item) {
141
- error.methodUnimplemented()
142
- }
143
- }
144
-
145
- /**
146
- * @implements AbstractAttributionManager
147
- *
148
- * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>}
149
- */
150
- export class TwosetAttributionManager extends ObservableV2 {
151
- /**
152
- * @param {IdMap<any>} inserts
153
- * @param {IdMap<any>} deletes
154
- */
155
- constructor (inserts, deletes) {
156
- super()
157
- this.inserts = inserts
158
- this.deletes = deletes
159
- }
160
-
161
- /**
162
- * @param {Array<AttributedContent<any>>} contents - where to write the result
163
- * @param {number} client
164
- * @param {number} clock
165
- * @param {boolean} deleted
166
- * @param {AbstractContent} content
167
- * @param {0|1|2} shouldRender - whether this should render or just result in a `retain` operation
168
- */
169
- readContent (contents, client, clock, deleted, content, shouldRender) {
170
- const slice = (deleted ? this.deletes : this.inserts).slice(client, clock, content.getLength())
171
- content = slice.length === 1 ? content : content.copy()
172
- slice.forEach(s => {
173
- const c = content
174
- if (s.len < c.getLength()) {
175
- content = c.splice(s.len)
176
- }
177
- if (!deleted || s.attrs != null || shouldRender) {
178
- contents.push(new AttributedContent(c, s.clock, deleted, s.attrs, shouldRender))
179
- }
180
- })
181
- }
182
-
183
- /**
184
- * @param {Item} item
185
- * @return {number}
186
- */
187
- contentLength (item) {
188
- if (!item.content.isCountable()) {
189
- return 0
190
- } else if (!item.deleted) {
191
- return item.length
192
- } else {
193
- return this.deletes.sliceId(item.id, item.length).reduce((len, s) => s.attrs != null ? len + s.len : len, 0)
194
- }
195
- }
196
- }
197
-
198
- /**
199
- * Abstract class for associating Attributions to content / changes
200
- *
201
- * @implements AbstractAttributionManager
202
- *
203
- * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>}
204
- */
205
- export class NoAttributionsManager extends ObservableV2 {
206
- /**
207
- * @param {Array<AttributedContent<any>>} contents - where to write the result
208
- * @param {number} _client
209
- * @param {number} clock
210
- * @param {boolean} deleted
211
- * @param {AbstractContent} content
212
- * @param {0|1|2} shouldRender - whether this should render or just result in a `retain` operation
213
- */
214
- readContent (contents, _client, clock, deleted, content, shouldRender) {
215
- if (!deleted || shouldRender) {
216
- contents.push(new AttributedContent(content, clock, deleted, null, shouldRender))
217
- }
218
- }
219
-
220
- /**
221
- * @param {Item} item
222
- * @return {number}
223
- */
224
- contentLength (item) {
225
- return (item.deleted || !item.content.isCountable()) ? 0 : item.length
226
- }
227
- }
228
-
229
- export const noAttributionsManager = new NoAttributionsManager()
230
-
231
- /**
232
- * @param {StructStore} store
233
- * @param {number} client
234
- * @param {number} clock
235
- * @param {number} len
236
- */
237
- const getItemContent = (store, client, clock, len) => {
238
- // Retrieved item is never more fragmented than the newer item.
239
- const prevItem = getItem(store, createID(client, clock))
240
- const diffStart = clock - prevItem.id.clock
241
- let content = prevItem.length > 1 ? prevItem.content.copy() : prevItem.content
242
- // trim itemContent to the correct size.
243
- if (diffStart > 0) {
244
- content = content.splice(diffStart)
245
- }
246
- if (len < content.getLength()) {
247
- content.splice(len)
248
- }
249
- return content
250
- }
251
-
252
- /**
253
- * @param {Transaction?} tr - only specify this if you want to fill the content of deleted content
254
- * @param {DiffAttributionManager} am
255
- * @param {ID} start
256
- * @param {ID} end
257
- * @param {boolean} collectAll - collect as many items as possible. Accept adding redundant changes.
258
- */
259
- const collectSuggestedChanges = (tr, am, start, end, collectAll) => {
260
- const inserts = createIdSet()
261
- const deletes = createIdSet()
262
- const store = am._nextDoc.store
263
- /**
264
- * make sure to collect suggestions until all formats are closed
265
- * @type {Set<string>}
266
- */
267
- const openedCollectedFormats = new Set()
268
- /**
269
- * @type {Item?}
270
- */
271
- let item = getItem(store, start)
272
- const endItem = start === end ? item : (end == null ? null : getItem(store, end))
273
-
274
- // walk to the left and find first un-attributed change that is rendered
275
- while (item.left != null) {
276
- item = item.left
277
- if (item.content instanceof ContentFormat && item.content.value == null) {
278
- item = item.right
279
- break
280
- }
281
- if (!item.deleted) {
282
- const slice = am.inserts.slice(item.id.client, item.id.clock, item.length)
283
- if (slice.some(s => s.attrs === null)) {
284
- for (let i = slice.length - 1; i >= 0; i--) {
285
- const s = slice[i]
286
- if (s.attrs == null) break
287
- inserts.add(item.id.client, s.clock, s.len)
288
- }
289
- item = item.right
290
- break
291
- }
292
- }
293
- }
294
- let foundEndItem = false
295
- // eslint-disable-next-line
296
- itemLoop: while (item != null) {
297
- const itemClient = item.id.client
298
- const slice = (item.deleted ? am.deletes : am.inserts).slice(itemClient, item.id.clock, item.length)
299
- foundEndItem ||= item === endItem
300
- if (item.deleted) {
301
- // item probably gc'd content. Need to split item and fill with content again
302
- for (let i = slice.length - 1; i >= 0; i--) {
303
- const s = slice[i]
304
- if (s.attrs != null || collectAll) {
305
- deletes.add(itemClient, s.clock, s.len)
306
- if (collectAll) {
307
- // in case item has been added and deleted this might be necessary. the forked document
308
- // will automatically filter this if it doesn't have it already.
309
- inserts.add(itemClient, s.clock, s.len)
310
- }
311
- }
312
- if (tr != null) {
313
- const splicedItem = getItemCleanStart(tr, createID(itemClient, s.clock))
314
- if (s.attrs != null) {
315
- splicedItem.content = getItemContent(am._prevDocStore, itemClient, s.clock, s.len)
316
- }
317
- }
318
- }
319
- } else {
320
- if (item.content instanceof ContentFormat) {
321
- const { key, value } = item.content
322
- if (value == null) {
323
- openedCollectedFormats.delete(key)
324
- } else {
325
- openedCollectedFormats.add(key)
326
- }
327
- }
328
- for (let i = 0; i < slice.length; i++) {
329
- const s = slice[i]
330
- if (s.attrs != null) {
331
- inserts.add(itemClient, s.clock, s.len)
332
- } else if (foundEndItem && openedCollectedFormats.size === 0) {
333
- // eslint-disable-next-line
334
- break itemLoop
335
- }
336
- }
337
- }
338
- item = item.right
339
- }
340
- return { inserts, deletes }
341
- }
342
-
343
- /**
344
- * @implements AbstractAttributionManager
345
- *
346
- * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>}
347
- */
348
- export class DiffAttributionManager extends ObservableV2 {
349
- /**
350
- * @param {Doc} prevDoc
351
- * @param {Doc} nextDoc
352
- */
353
- constructor (prevDoc, nextDoc) {
354
- super()
355
- const _nextDocInserts = createInsertSetFromStructStore(nextDoc.store, false) // unmaintained
356
- const _prevDocInserts = createInsertSetFromStructStore(prevDoc.store, false) // unmaintained
357
- const nextDocDeletes = createDeleteSetFromStructStore(nextDoc.store) // maintained
358
- const prevDocDeletes = createDeleteSetFromStructStore(prevDoc.store) // maintained
359
- this.inserts = createIdMapFromIdSet(diffIdSet(_nextDocInserts, _prevDocInserts), [])
360
- this.deletes = createIdMapFromIdSet(diffIdSet(nextDocDeletes, prevDocDeletes), [])
361
- this._prevDoc = prevDoc
362
- this._prevDocStore = prevDoc.store
363
- this._nextDoc = nextDoc
364
- // update before observer calls fired
365
- this._nextBOH = nextDoc.on('beforeObserverCalls', tr => {
366
- // update inserts
367
- const diffInserts = diffIdSet(tr.insertSet, _prevDocInserts)
368
- insertIntoIdMap(this.inserts, createIdMapFromIdSet(diffInserts, []))
369
- // update deletes
370
- const diffDeletes = diffIdSet(diffIdSet(tr.deleteSet, prevDocDeletes), this.inserts)
371
- insertIntoIdMap(this.deletes, createIdMapFromIdSet(diffDeletes, []))
372
- // @todo fire update ranges on `diffInserts` and `diffDeletes`
373
- })
374
- this._prevBOH = prevDoc.on('beforeObserverCalls', tr => {
375
- insertIntoIdSet(_prevDocInserts, tr.insertSet)
376
- insertIntoIdSet(prevDocDeletes, tr.deleteSet)
377
- // insertIntoIdMap(this.inserts, createIdMapFromIdSet(intersectSets(tr.insertSet, this.inserts), [createAttributionItem('acceptInsert', 'unknown')]))
378
- if (tr.insertSet.clients.size < 2) {
379
- tr.insertSet.forEach((attrRange, client) => {
380
- this.inserts.delete(client, attrRange.clock, attrRange.len)
381
- })
382
- } else {
383
- this.inserts = diffIdMap(this.inserts, tr.insertSet)
384
- }
385
- // insertIntoIdMap(this.deletes, createIdMapFromIdSet(intersectSets(tr.deleteSet, this.deletes), [createAttributionItem('acceptDelete', 'unknown')]))
386
- if (tr.deleteSet.clients.size < 2) {
387
- tr.deleteSet.forEach((attrRange, client) => {
388
- this.deletes.delete(client, attrRange.clock, attrRange.len)
389
- })
390
- } else {
391
- this.deletes = diffIdMap(this.deletes, tr.deleteSet)
392
- }
393
- // fire event of "changed" attributions. exclude items that were added & deleted in the same
394
- // transaction
395
- this.emit('change', [diffIdSet(mergeIdSets([tr.insertSet, tr.deleteSet]), intersectSets(tr.insertSet, tr.deleteSet)), tr.origin, tr.local])
396
- })
397
- // changes from prevDoc should always flow into suggestionDoc
398
- // changes from suggestionDoc only flow into ydoc if suggestion-mode is disabled
399
- this._prevUpdateListener = prevDoc.on('update', (update, origin) => {
400
- origin !== this && applyUpdate(nextDoc, update)
401
- })
402
- this._ndUpdateListener = nextDoc.on('update', (update, origin, _doc, tr) => {
403
- // only if event is local and suggestion mode is enabled
404
- if (!this.suggestionMode && tr.local && (this.suggestionOrigins == null || this.suggestionOrigins.some(o => o === origin))) {
405
- applyUpdate(prevDoc, update, this)
406
- }
407
- })
408
- this._afterTrListener = nextDoc.on('afterTransaction', (tr) => {
409
- // apply deletes on attributed deletes (content that is already deleted, but is rendered by
410
- // the attribution manager)
411
- if (!this.suggestionMode && tr.local && (this.suggestionOrigins == null || this.suggestionOrigins.some(o => o === tr.origin))) {
412
- const attributedDeletes = tr.meta.get('attributedDeletes')
413
- if (attributedDeletes != null) {
414
- transact(prevDoc, () => {
415
- // apply attributed deletes if there are any
416
- const ds = new UpdateEncoderV1()
417
- encoding.writeVarUint(ds.restEncoder, 0) // encode 0 structs
418
- writeIdSet(ds, attributedDeletes)
419
- applyUpdate(prevDoc, ds.toUint8Array())
420
- }, this)
421
- }
422
- }
423
- })
424
- this.suggestionMode = true
425
- /**
426
- * Optionally limit origins that may sync changes to the main doc if suggestion-mode is
427
- * disabled.
428
- *
429
- * @type {Array<any>?}
430
- */
431
- this.suggestionOrigins = null
432
- this._destroyHandler = nextDoc.on('destroy', this.destroy.bind(this))
433
- prevDoc.on('destroy', this._destroyHandler)
434
- }
435
-
436
- destroy () {
437
- super.destroy()
438
- this._nextDoc.off('destroy', this._destroyHandler)
439
- this._prevDoc.off('destroy', this._destroyHandler)
440
- this._nextDoc.off('beforeObserverCalls', this._nextBOH)
441
- this._prevDoc.off('beforeObserverCalls', this._prevBOH)
442
- this._prevDoc.off('update', this._prevUpdateListener)
443
- this._nextDoc.off('update', this._ndUpdateListener)
444
- this._nextDoc.off('afterTransaction', this._afterTrListener)
445
- }
446
-
447
- /**
448
- * @param {ID} start
449
- * @param {ID} end
450
- */
451
- acceptChanges (start, end = start) {
452
- const { inserts, deletes } = collectSuggestedChanges(null, this, start, end, true)
453
- const encoder = new UpdateEncoderV1()
454
- writeStructsFromIdSet(encoder, this._nextDoc.store, inserts)
455
- writeIdSet(encoder, deletes)
456
- applyUpdate(this._prevDoc, encoder.toUint8Array())
457
- }
458
-
459
- /**
460
- * @param {ID} start
461
- * @param {ID} end
462
- */
463
- rejectChanges (start, end = start) {
464
- this._nextDoc.transact(tr => {
465
- const { inserts, deletes } = collectSuggestedChanges(tr, this, start, end, false)
466
- const encoder = new UpdateEncoderV1()
467
- writeStructsFromIdSet(encoder, this._nextDoc.store, inserts)
468
- writeIdSet(encoder, deletes)
469
- const um = new UndoManager(this._nextDoc)
470
- um.undoStack.push(new StackItem(deletes, inserts))
471
- um.undo()
472
- um.destroy()
473
- })
474
- this.acceptChanges(start, end)
475
- }
476
-
477
- /**
478
- * @param {Array<AttributedContent<any>>} contents - where to write the result
479
- * @param {number} client
480
- * @param {number} clock
481
- * @param {boolean} deleted
482
- * @param {AbstractContent} _content
483
- * @param {0|1|2} shouldRender - whether this should render or just result in a `retain` operation
484
- */
485
- readContent (contents, client, clock, deleted, _content, shouldRender) {
486
- const slice = (deleted ? this.deletes : this.inserts).slice(client, clock, _content.getLength())
487
- /**
488
- * @type {AbstractContent?}
489
- */
490
- let content = slice.length === 1 ? _content : _content.copy()
491
- for (let i = 0; i < slice.length; i++) {
492
- const s = slice[i]
493
- if (content == null || content instanceof ContentDeleted) {
494
- if ((!shouldRender && s.attrs == null) || this.inserts.has(client, s.clock)) {
495
- continue
496
- }
497
- // Retrieved item is never more fragmented than the newer item.
498
- const prevItem = getItem(this._prevDocStore, createID(client, s.clock))
499
- const diffStart = s.clock - prevItem.id.clock
500
- content = prevItem.length > 1 ? prevItem.content.copy() : prevItem.content
501
- // trim itemContent to the correct size.
502
- if (diffStart > 0) {
503
- content = content.splice(diffStart)
504
- }
505
- }
506
- const c = /** @type {AbstractContent} */ (content)
507
- const clen = c.getLength()
508
- if (clen < s.len) {
509
- slice.splice(i + 1, 0, createMaybeAttrRange(s.clock + clen, s.len - clen, s.attrs))
510
- s.len = clen
511
- }
512
- content = s.len < clen ? c.splice(s.len) : null
513
- if (shouldRender || !deleted || s.attrs != null) {
514
- contents.push(new AttributedContent(c, s.clock, deleted, s.attrs, shouldRender))
515
- }
516
- }
517
- }
518
-
519
- /**
520
- * @param {Item} item
521
- * @return {number}
522
- */
523
- contentLength (item) {
524
- if (!item.deleted) {
525
- return item.content.isCountable() ? item.length : 0
526
- }
527
- /**
528
- * @type {Array<AttributedContent<any>>}
529
- */
530
- const cs = []
531
- this.readContent(cs, item.id.client, item.id.clock, true, item.content, 0)
532
- return cs.reduce((cnt, c) => cnt + ((c.attrs != null && c.content.isCountable()) ? c.content.getLength() : 0), 0)
533
- }
534
- }
535
-
536
- /**
537
- * Attribute changes from ydoc1 to ydoc2.
538
- *
539
- * @param {Doc} prevDoc
540
- * @param {Doc} nextDoc
541
- */
542
- export const createAttributionManagerFromDiff = (prevDoc, nextDoc) => new DiffAttributionManager(prevDoc, nextDoc)
543
-
544
- /**
545
- * Intended for projects that used the v13 snapshot feature. With this AttributionManager you can
546
- * read content similar to the previous snapshot api. Requires that `ydoc.gc` is turned off.
547
- *
548
- * @implements AbstractAttributionManager
549
- *
550
- * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>}
551
- */
552
- export class SnapshotAttributionManager extends ObservableV2 {
553
- /**
554
- * @param {Snapshot} prevSnapshot
555
- * @param {Snapshot} nextSnapshot
556
- */
557
- constructor (prevSnapshot, nextSnapshot) {
558
- super()
559
- this.prevSnapshot = prevSnapshot
560
- this.nextSnapshot = nextSnapshot
561
- const inserts = createIdMap()
562
- const deletes = createIdMapFromIdSet(diffIdSet(nextSnapshot.ds, prevSnapshot.ds), [createAttributionItem('change', '')])
563
- nextSnapshot.sv.forEach((clock, client) => {
564
- const prevClock = prevSnapshot.sv.get(client) || 0
565
- inserts.add(client, 0, prevClock, []) // content is included in prevSnapshot is rendered without attributes
566
- inserts.add(client, prevClock, clock - prevClock, [createAttributionItem('change', '')]) // content is rendered as "inserted"
567
- })
568
- this.attrs = mergeIdMaps([diffIdMap(inserts, prevSnapshot.ds), deletes])
569
- }
570
-
571
- /**
572
- * @param {Array<AttributedContent<any>>} contents - where to write the result
573
- * @param {number} client
574
- * @param {number} clock
575
- * @param {boolean} _deleted
576
- * @param {AbstractContent} content
577
- * @param {0|1|2} shouldRender - whether this should render or just result in a `retain` operation
578
- */
579
- readContent (contents, client, clock, _deleted, content, shouldRender) {
580
- if ((this.nextSnapshot.sv.get(client) ?? 0) <= clock) return // future item that should not be displayed
581
- const slice = this.attrs.slice(client, clock, content.getLength())
582
- content = slice.length === 1 ? content : content.copy()
583
- slice.forEach(s => {
584
- const deleted = this.nextSnapshot.ds.has(client, s.clock)
585
- const nonExistend = (this.nextSnapshot.sv.get(client) ?? 0) <= s.clock
586
- const c = content
587
- if (s.len < c.getLength()) {
588
- content = c.splice(s.len)
589
- }
590
- if (nonExistend) return
591
- if (shouldRender || !deleted || (s.attrs != null && s.attrs.length > 0)) {
592
- let attrsWithoutChange = s.attrs?.filter(attr => attr.name !== 'change') ?? null
593
- if (s.attrs?.length === 0) {
594
- attrsWithoutChange = null
595
- }
596
- contents.push(new AttributedContent(c, s.clock, deleted, attrsWithoutChange, shouldRender))
597
- }
598
- })
599
- }
600
-
601
- /**
602
- * @param {Item} item
603
- * @return {number}
604
- */
605
- contentLength (item) {
606
- return item.content.isCountable()
607
- ? (item.deleted
608
- ? this.attrs.sliceId(item.id, item.length).reduce((len, s) => s.attrs != null ? len + s.len : len, 0)
609
- : item.length
610
- )
611
- : 0
612
- }
613
- }
614
-
615
- /**
616
- * @param {Snapshot} prevSnapshot
617
- * @param {Snapshot} nextSnapshot
618
- */
619
- export const createAttributionManagerFromSnapshots = (prevSnapshot, nextSnapshot = prevSnapshot) => new SnapshotAttributionManager(prevSnapshot, nextSnapshot)