@y/y 14.0.0-16 → 14.0.0-18
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/dist/{index-DyTeTfmj.js → index-BV-j5wdP.js} +2 -2
- package/dist/{index-R7GxO-36.js.map → index-BV-j5wdP.js.map} +1 -1
- package/dist/{internals.mjs → internals.js} +1 -1
- package/dist/internals.js.map +1 -0
- package/dist/{testHelper.mjs → testHelper.js} +2 -2
- package/dist/testHelper.js.map +1 -0
- package/dist/{yjs.mjs → yjs.js} +2 -2
- package/dist/yjs.js.map +1 -0
- package/package.json +9 -18
- package/dist/Skip-j0kX7pdq.js +0 -12173
- package/dist/Skip-j0kX7pdq.js.map +0 -1
- package/dist/index-DyTeTfmj.js.map +0 -1
- package/dist/index-R7GxO-36.js +0 -165
- package/dist/internals.cjs +0 -286
- package/dist/internals.cjs.map +0 -1
- package/dist/internals.mjs.map +0 -1
- package/dist/testHelper.cjs +0 -780
- package/dist/testHelper.cjs.map +0 -1
- package/dist/testHelper.mjs.map +0 -1
- package/dist/yjs.cjs +0 -151
- package/dist/yjs.cjs.map +0 -1
- package/dist/yjs.mjs.map +0 -1
- package/src/index.js +0 -153
- package/src/internals.js +0 -44
- package/src/structs/AbstractStruct.js +0 -59
- 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 -176
- package/src/structs/GC.js +0 -80
- package/src/structs/Item.js +0 -845
- package/src/structs/Skip.js +0 -75
- package/src/types/AbstractType.js +0 -1434
- package/src/types/YArray.js +0 -270
- package/src/types/YMap.js +0 -244
- package/src/types/YText.js +0 -934
- package/src/types/YXmlElement.js +0 -227
- package/src/types/YXmlFragment.js +0 -266
- package/src/types/YXmlHook.js +0 -68
- package/src/types/YXmlText.js +0 -66
- package/src/utils/AbstractConnector.js +0 -25
- package/src/utils/AttributionManager.js +0 -619
- package/src/utils/Doc.js +0 -372
- package/src/utils/EventHandler.js +0 -87
- package/src/utils/ID.js +0 -89
- package/src/utils/IdMap.js +0 -629
- package/src/utils/IdSet.js +0 -823
- package/src/utils/RelativePosition.js +0 -352
- package/src/utils/Snapshot.js +0 -220
- package/src/utils/StructSet.js +0 -137
- package/src/utils/StructStore.js +0 -289
- package/src/utils/Transaction.js +0 -489
- package/src/utils/UndoManager.js +0 -391
- package/src/utils/UpdateDecoder.js +0 -281
- package/src/utils/UpdateEncoder.js +0 -320
- package/src/utils/YEvent.js +0 -216
- package/src/utils/delta-helpers.js +0 -54
- package/src/utils/encoding.js +0 -623
- package/src/utils/isParentOf.js +0 -21
- package/src/utils/logging.js +0 -21
- package/src/utils/types.js +0 -28
- package/src/utils/updates.js +0 -715
- 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)
|