@y/y 14.0.0-18 → 14.0.0-20

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 (129) hide show
  1. package/README.md +7 -5
  2. package/dist/src/index.d.ts +2 -1
  3. package/dist/src/internals.d.ts +2 -9
  4. package/dist/src/structs/ContentType.d.ts +6 -12
  5. package/dist/src/structs/ContentType.d.ts.map +1 -1
  6. package/dist/src/structs/Item.d.ts +5 -6
  7. package/dist/src/structs/Item.d.ts.map +1 -1
  8. package/dist/src/utils/AttributionManager.d.ts +20 -6
  9. package/dist/src/utils/AttributionManager.d.ts.map +1 -1
  10. package/dist/src/utils/Doc.d.ts +7 -70
  11. package/dist/src/utils/Doc.d.ts.map +1 -1
  12. package/dist/src/utils/ID.d.ts +2 -2
  13. package/dist/src/utils/ID.d.ts.map +1 -1
  14. package/dist/src/utils/IdMap.d.ts +19 -16
  15. package/dist/src/utils/IdMap.d.ts.map +1 -1
  16. package/dist/src/utils/IdSet.d.ts +2 -2
  17. package/dist/src/utils/IdSet.d.ts.map +1 -1
  18. package/dist/src/utils/RelativePosition.d.ts +8 -8
  19. package/dist/src/utils/RelativePosition.d.ts.map +1 -1
  20. package/dist/src/utils/Transaction.d.ts +9 -5
  21. package/dist/src/utils/Transaction.d.ts.map +1 -1
  22. package/dist/src/utils/UndoManager.d.ts +14 -12
  23. package/dist/src/utils/UndoManager.d.ts.map +1 -1
  24. package/dist/src/utils/UpdateEncoder.d.ts +2 -0
  25. package/dist/src/utils/UpdateEncoder.d.ts.map +1 -1
  26. package/dist/src/utils/YEvent.d.ts +21 -42
  27. package/dist/src/utils/YEvent.d.ts.map +1 -1
  28. package/dist/src/utils/isParentOf.d.ts +1 -1
  29. package/dist/src/utils/isParentOf.d.ts.map +1 -1
  30. package/dist/src/utils/logging.d.ts +2 -2
  31. package/dist/src/utils/logging.d.ts.map +1 -1
  32. package/dist/src/utils/meta.d.ts +43 -0
  33. package/dist/src/utils/meta.d.ts.map +1 -0
  34. package/dist/src/utils/ts.d.ts +4 -0
  35. package/dist/src/utils/ts.d.ts.map +1 -0
  36. package/dist/src/utils/updates.d.ts +3 -9
  37. package/dist/src/utils/updates.d.ts.map +1 -1
  38. package/dist/src/ytype.d.ts +498 -0
  39. package/dist/src/ytype.d.ts.map +1 -0
  40. package/dist/tests/IdMap.tests.d.ts.map +1 -1
  41. package/dist/tests/attribution.tests.d.ts +1 -0
  42. package/dist/tests/attribution.tests.d.ts.map +1 -1
  43. package/dist/tests/compatibility.tests.d.ts.map +1 -1
  44. package/dist/tests/doc.tests.d.ts.map +1 -1
  45. package/dist/tests/relativePositions.tests.d.ts +9 -9
  46. package/dist/tests/relativePositions.tests.d.ts.map +1 -1
  47. package/dist/tests/snapshot.tests.d.ts.map +1 -1
  48. package/dist/tests/testHelper.d.ts +28 -27
  49. package/dist/tests/testHelper.d.ts.map +1 -1
  50. package/dist/tests/undo-redo.tests.d.ts.map +1 -1
  51. package/dist/tests/updates.tests.d.ts +1 -1
  52. package/dist/tests/updates.tests.d.ts.map +1 -1
  53. package/dist/tests/y-array.tests.d.ts +0 -2
  54. package/dist/tests/y-array.tests.d.ts.map +1 -1
  55. package/dist/tests/y-map.tests.d.ts +0 -3
  56. package/dist/tests/y-map.tests.d.ts.map +1 -1
  57. package/dist/tests/y-text.tests.d.ts +1 -1
  58. package/dist/tests/y-text.tests.d.ts.map +1 -1
  59. package/dist/tests/y-xml.tests.d.ts +0 -1
  60. package/dist/tests/y-xml.tests.d.ts.map +1 -1
  61. package/package.json +16 -16
  62. package/src/index.js +152 -0
  63. package/src/internals.js +35 -0
  64. package/src/structs/AbstractStruct.js +59 -0
  65. package/src/structs/ContentAny.js +115 -0
  66. package/src/structs/ContentBinary.js +93 -0
  67. package/src/structs/ContentDeleted.js +101 -0
  68. package/src/structs/ContentDoc.js +141 -0
  69. package/src/structs/ContentEmbed.js +98 -0
  70. package/src/structs/ContentFormat.js +105 -0
  71. package/src/structs/ContentJSON.js +119 -0
  72. package/src/structs/ContentString.js +113 -0
  73. package/src/structs/ContentType.js +152 -0
  74. package/src/structs/GC.js +80 -0
  75. package/src/structs/Item.js +841 -0
  76. package/src/structs/Skip.js +75 -0
  77. package/src/utils/AttributionManager.js +653 -0
  78. package/src/utils/Doc.js +266 -0
  79. package/src/utils/EventHandler.js +87 -0
  80. package/src/utils/ID.js +89 -0
  81. package/src/utils/IdMap.js +673 -0
  82. package/src/utils/IdSet.js +825 -0
  83. package/src/utils/RelativePosition.js +352 -0
  84. package/src/utils/Snapshot.js +220 -0
  85. package/src/utils/StructSet.js +137 -0
  86. package/src/utils/StructStore.js +289 -0
  87. package/src/utils/Transaction.js +671 -0
  88. package/src/utils/UndoManager.js +406 -0
  89. package/src/utils/UpdateDecoder.js +281 -0
  90. package/src/utils/UpdateEncoder.js +327 -0
  91. package/src/utils/YEvent.js +189 -0
  92. package/src/utils/delta-helpers.js +54 -0
  93. package/src/utils/encoding.js +623 -0
  94. package/src/utils/isParentOf.js +21 -0
  95. package/src/utils/logging.js +21 -0
  96. package/src/utils/meta.js +97 -0
  97. package/src/utils/ts.js +3 -0
  98. package/src/utils/updates.js +711 -0
  99. package/src/ytype.js +1962 -0
  100. package/dist/Skip-wRT7BKFP.js +0 -11877
  101. package/dist/Skip-wRT7BKFP.js.map +0 -1
  102. package/dist/index-BV-j5wdP.js +0 -163
  103. package/dist/index-BV-j5wdP.js.map +0 -1
  104. package/dist/internals.js +0 -25
  105. package/dist/internals.js.map +0 -1
  106. package/dist/src/types/AbstractType.d.ts +0 -239
  107. package/dist/src/types/AbstractType.d.ts.map +0 -1
  108. package/dist/src/types/YArray.d.ts +0 -128
  109. package/dist/src/types/YArray.d.ts.map +0 -1
  110. package/dist/src/types/YMap.d.ts +0 -112
  111. package/dist/src/types/YMap.d.ts.map +0 -1
  112. package/dist/src/types/YText.d.ts +0 -216
  113. package/dist/src/types/YText.d.ts.map +0 -1
  114. package/dist/src/types/YXmlElement.d.ts +0 -106
  115. package/dist/src/types/YXmlElement.d.ts.map +0 -1
  116. package/dist/src/types/YXmlFragment.d.ts +0 -143
  117. package/dist/src/types/YXmlFragment.d.ts.map +0 -1
  118. package/dist/src/types/YXmlHook.d.ts +0 -32
  119. package/dist/src/types/YXmlHook.d.ts.map +0 -1
  120. package/dist/src/types/YXmlText.d.ts +0 -34
  121. package/dist/src/types/YXmlText.d.ts.map +0 -1
  122. package/dist/src/utils/AbstractConnector.d.ts +0 -20
  123. package/dist/src/utils/AbstractConnector.d.ts.map +0 -1
  124. package/dist/src/utils/types.d.ts +0 -7
  125. package/dist/src/utils/types.d.ts.map +0 -1
  126. package/dist/testHelper.js +0 -617
  127. package/dist/testHelper.js.map +0 -1
  128. package/dist/yjs.js +0 -26
  129. package/dist/yjs.js.map +0 -1
package/src/ytype.js ADDED
@@ -0,0 +1,1962 @@
1
+ import {
2
+ cleanupFormattingGap,
3
+ createIdSet,
4
+ removeEventHandlerListener,
5
+ callEventHandlerListeners,
6
+ addEventHandlerListener,
7
+ createEventHandler,
8
+ getState,
9
+ isVisible,
10
+ ContentType,
11
+ createID,
12
+ ContentAny,
13
+ ContentFormat,
14
+ ContentBinary,
15
+ ContentJSON,
16
+ ContentDeleted,
17
+ ContentString,
18
+ ContentEmbed,
19
+ getItemCleanStart,
20
+ noAttributionsManager,
21
+ transact,
22
+ ContentDoc, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, createAttributionFromAttributionItems, AbstractAttributionManager // eslint-disable-line
23
+ } from './internals.js'
24
+
25
+ import * as contentType from './structs/ContentType.js'
26
+
27
+ import * as traits from 'lib0/traits'
28
+ import * as delta from 'lib0/delta'
29
+ import * as array from 'lib0/array'
30
+ import * as map from 'lib0/map'
31
+ import * as iterator from 'lib0/iterator'
32
+ import * as error from 'lib0/error'
33
+ import * as math from 'lib0/math'
34
+ import * as log from 'lib0/logging'
35
+ import * as object from 'lib0/object'
36
+ import * as s from 'lib0/schema'
37
+
38
+ /**
39
+ * @typedef {Object<string,any>|Array<any>|number|null|string|Uint8Array|BigInt|YType<any>} YValue
40
+ */
41
+
42
+ /**
43
+ * https://docs.yjs.dev/getting-started/working-with-shared-types#caveats
44
+ */
45
+ export const warnPrematureAccess = () => { log.warn('Invalid access: Add Yjs type to a document before reading data.') }
46
+
47
+ const maxSearchMarker = 80
48
+
49
+ /**
50
+ * A unique timestamp that identifies each marker.
51
+ *
52
+ * Time is relative,.. this is more like an ever-increasing clock.
53
+ *
54
+ * @type {number}
55
+ */
56
+ let globalSearchMarkerTimestamp = 0
57
+
58
+ export class ItemTextListPosition {
59
+ /**
60
+ * @param {Item|null} left
61
+ * @param {Item|null} right
62
+ * @param {number} index
63
+ * @param {Map<string,any>} currentAttributes
64
+ * @param {AbstractAttributionManager} am
65
+ */
66
+ constructor (left, right, index, currentAttributes, am) {
67
+ this.left = left
68
+ this.right = right
69
+ this.index = index
70
+ this.currentAttributes = currentAttributes
71
+ this.am = am
72
+ }
73
+
74
+ /**
75
+ * Only call this if you know that this.right is defined
76
+ */
77
+ forward () {
78
+ if (this.right === null) {
79
+ error.unexpectedCase()
80
+ }
81
+ switch (this.right.content.constructor) {
82
+ case ContentFormat:
83
+ if (!this.right.deleted) {
84
+ updateCurrentAttributes(this.currentAttributes, /** @type {ContentFormat} */ (this.right.content))
85
+ }
86
+ break
87
+ default:
88
+ this.index += this.am.contentLength(this.right)
89
+ break
90
+ }
91
+ this.left = this.right
92
+ this.right = this.right.right
93
+ }
94
+
95
+ /**
96
+ * @param {Transaction} transaction
97
+ * @param {YType} parent
98
+ * @param {number} length
99
+ * @param {Object<string,any>} attributes
100
+ *
101
+ * @function
102
+ */
103
+ formatText (transaction, parent, length, attributes) {
104
+ minimizeAttributeChanges(this, attributes)
105
+ const negatedAttributes = insertAttributes(transaction, parent, this, attributes)
106
+ // iterate until first non-format or null is found
107
+ // delete all formats with attributes[format.key] != null
108
+ // also check the attributes after the first non-format as we do not want to insert redundant negated attributes there
109
+ // eslint-disable-next-line no-labels
110
+ iterationLoop: while (
111
+ this.right !== null &&
112
+ (length > 0 ||
113
+ (
114
+ negatedAttributes.size > 0 &&
115
+ ((this.right.deleted && this.am.contentLength(this.right) === 0) || this.right.content.constructor === ContentFormat)
116
+ )
117
+ )
118
+ ) {
119
+ switch (this.right.content.constructor) {
120
+ case ContentFormat: {
121
+ if (!this.right.deleted) {
122
+ const { key, value } = /** @type {ContentFormat} */ (this.right.content)
123
+ const attr = attributes[key]
124
+ if (attr !== undefined) {
125
+ if (equalAttrs(attr, value)) {
126
+ negatedAttributes.delete(key)
127
+ } else {
128
+ if (length === 0) {
129
+ // no need to further extend negatedAttributes
130
+ // eslint-disable-next-line no-labels
131
+ break iterationLoop
132
+ }
133
+ negatedAttributes.set(key, value)
134
+ }
135
+ this.right.delete(transaction)
136
+ } else {
137
+ this.currentAttributes.set(key, value)
138
+ }
139
+ }
140
+ break
141
+ }
142
+ default: {
143
+ const item = this.right
144
+ const rightLen = this.am.contentLength(item)
145
+ if (length < rightLen) {
146
+ /**
147
+ * @type {Array<import('./internals.js').AttributedContent<any>>}
148
+ */
149
+ const contents = []
150
+ this.am.readContent(contents, item.id.client, item.id.clock, item.deleted, item.content, 0)
151
+ let i = 0
152
+ for (; i < contents.length && length > 0; i++) {
153
+ const c = contents[i]
154
+ if ((!c.deleted || c.attrs != null) && c.content.isCountable()) {
155
+ length -= c.content.getLength()
156
+ }
157
+ }
158
+ if (length < 0 || (length === 0 && i !== contents.length)) {
159
+ const c = contents[--i]
160
+ getItemCleanStart(transaction, createID(item.id.client, c.clock + c.content.getLength() + length))
161
+ }
162
+ } else {
163
+ length -= rightLen
164
+ }
165
+ break
166
+ }
167
+ }
168
+ this.forward()
169
+ }
170
+ if (length > 0) {
171
+ throw new Error('Exceeded content range')
172
+ }
173
+ insertNegatedAttributes(transaction, parent, this, negatedAttributes)
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Negate applied formats
179
+ *
180
+ * @param {Transaction} transaction
181
+ * @param {YType} parent
182
+ * @param {ItemTextListPosition} currPos
183
+ * @param {Map<string,any>} negatedAttributes
184
+ *
185
+ * @private
186
+ * @function
187
+ */
188
+ const insertNegatedAttributes = (transaction, parent, currPos, negatedAttributes) => {
189
+ // check if we really need to remove attributes
190
+ while (
191
+ currPos.right !== null && (
192
+ (currPos.right.deleted && (currPos.am === noAttributionsManager || currPos.am.contentLength(currPos.right) === 0)) || (
193
+ currPos.right.content.constructor === ContentFormat &&
194
+ equalAttrs(negatedAttributes.get(/** @type {ContentFormat} */ (currPos.right.content).key), /** @type {ContentFormat} */ (currPos.right.content).value)
195
+ )
196
+ )
197
+ ) {
198
+ if (!currPos.right.deleted) {
199
+ negatedAttributes.delete(/** @type {ContentFormat} */ (currPos.right.content).key)
200
+ }
201
+ currPos.forward()
202
+ }
203
+ const doc = transaction.doc
204
+ const ownClientId = doc.clientID
205
+ negatedAttributes.forEach((val, key) => {
206
+ const left = currPos.left
207
+ const right = currPos.right
208
+ const nextFormat = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
209
+ nextFormat.integrate(transaction, 0)
210
+ currPos.right = nextFormat
211
+ currPos.forward()
212
+ })
213
+ }
214
+
215
+ /**
216
+ * @param {Map<string,any>} currentAttributes
217
+ * @param {ContentFormat} format
218
+ *
219
+ * @private
220
+ * @function
221
+ */
222
+ const updateCurrentAttributes = (currentAttributes, format) => {
223
+ const { key, value } = format
224
+ if (value === null) {
225
+ currentAttributes.delete(key)
226
+ } else {
227
+ currentAttributes.set(key, value)
228
+ }
229
+ }
230
+
231
+ /**
232
+ * @param {ItemTextListPosition} currPos
233
+ * @param {Object<string,any>} attributes
234
+ *
235
+ * @private
236
+ * @function
237
+ */
238
+ const minimizeAttributeChanges = (currPos, attributes) => {
239
+ // go right while attributes[right.key] === right.value (or right is deleted)
240
+ while (true) {
241
+ if (currPos.right === null) {
242
+ break
243
+ } else if (currPos.right.deleted ? (currPos.am.contentLength(currPos.right) === 0) : (!currPos.right.deleted && currPos.right.content.constructor === ContentFormat && equalAttrs(attributes[(/** @type {ContentFormat} */ (currPos.right.content)).key] ?? null, /** @type {ContentFormat} */ (currPos.right.content).value))) {
244
+ //
245
+ } else {
246
+ break
247
+ }
248
+ currPos.forward()
249
+ }
250
+ }
251
+
252
+ /**
253
+ * @param {Transaction} transaction
254
+ * @param {YType} parent
255
+ * @param {ItemTextListPosition} currPos
256
+ * @param {Object<string,any>} attributes
257
+ * @return {Map<string,any>}
258
+ *
259
+ * @private
260
+ * @function
261
+ **/
262
+ const insertAttributes = (transaction, parent, currPos, attributes) => {
263
+ const doc = transaction.doc
264
+ const ownClientId = doc.clientID
265
+ const negatedAttributes = new Map()
266
+ // insert format-start items
267
+ for (const key in attributes) {
268
+ const val = attributes[key]
269
+ const currentVal = currPos.currentAttributes.get(key) ?? null
270
+ if (!equalAttrs(currentVal, val)) {
271
+ // save negated attribute (set null if currentVal undefined)
272
+ negatedAttributes.set(key, currentVal)
273
+ const { left, right } = currPos
274
+ currPos.right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
275
+ currPos.right.integrate(transaction, 0)
276
+ currPos.forward()
277
+ }
278
+ }
279
+ return negatedAttributes
280
+ }
281
+
282
+ /**
283
+ * @param {Transaction} transaction
284
+ * @param {YType} parent
285
+ * @param {ItemTextListPosition} currPos
286
+ * @param {import('./structs/Item.js').AbstractContent} content
287
+ * @param {Object<string,any>} attributes
288
+ *
289
+ * @private
290
+ * @function
291
+ **/
292
+ export const insertContent = (transaction, parent, currPos, content, attributes) => {
293
+ currPos.currentAttributes.forEach((_val, key) => {
294
+ if (attributes[key] === undefined) {
295
+ attributes[key] = null
296
+ }
297
+ })
298
+ const doc = transaction.doc
299
+ const ownClientId = doc.clientID
300
+ minimizeAttributeChanges(currPos, attributes)
301
+ const negatedAttributes = insertAttributes(transaction, parent, currPos, attributes)
302
+ let { left, right, index } = currPos
303
+ if (parent._searchMarker) {
304
+ updateMarkerChanges(parent._searchMarker, currPos.index, content.getLength())
305
+ }
306
+ right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, content)
307
+ right.integrate(transaction, 0)
308
+ currPos.right = right
309
+ currPos.index = index
310
+ currPos.forward()
311
+ insertNegatedAttributes(transaction, parent, currPos, negatedAttributes)
312
+ }
313
+
314
+ /**
315
+ * @param {Transaction} transaction
316
+ * @param {YType} parent
317
+ * @param {ItemTextListPosition} currPos
318
+ * @param {Array<any>|string} insert
319
+ * @param {Object<string,any>} attributes
320
+ */
321
+ export const insertContentHelper = (transaction, parent, currPos, insert, attributes) => {
322
+ if (s.$string.check(insert)) {
323
+ insertContent(transaction, parent, currPos, new ContentString(insert), attributes)
324
+ } else {
325
+ insert = insert.map(ins => delta.$deltaAny.check(ins) ? YType.from(ins) : ins)
326
+ for (let i = 0; i < insert.length;) {
327
+ const first = insert[i]
328
+ if (first instanceof YType) {
329
+ insertContent(transaction, parent, currPos, new ContentType(first), attributes)
330
+ i++
331
+ } else if (first instanceof Doc) {
332
+ insertContent(transaction, parent, currPos, new ContentDoc(first), attributes)
333
+ i++
334
+ } else {
335
+ // insert "any" content
336
+ // compute slice len
337
+ let j = i + 1
338
+ for (; j < insert.length && !(insert[j] instanceof YType || insert[j] instanceof Doc); j++) { /* nop */ }
339
+ insertContent(transaction, parent, currPos, new ContentAny((i === 0 && j === insert.length) ? insert : insert.slice(i, j)), attributes)
340
+ i = j
341
+ }
342
+ }
343
+ }
344
+ }
345
+
346
+ /**
347
+ * @param {Transaction} transaction
348
+ * @param {ItemTextListPosition} currPos
349
+ * @param {number} length
350
+ * @return {ItemTextListPosition}
351
+ *
352
+ * @private
353
+ * @function
354
+ */
355
+ export const deleteText = (transaction, currPos, length) => {
356
+ const startLength = length
357
+ const startAttrs = map.copy(currPos.currentAttributes)
358
+ const start = currPos.right
359
+ while (length > 0 && currPos.right !== null) {
360
+ const item = currPos.right
361
+ if (!item.deleted && item.countable) {
362
+ if (length < item.length) {
363
+ getItemCleanStart(transaction, createID(item.id.client, item.id.clock + length))
364
+ }
365
+ length -= item.length
366
+ item.delete(transaction)
367
+ } else if (currPos.am !== noAttributionsManager) {
368
+ /**
369
+ * @type {Array<import('./internals.js').AttributedContent<any>>}
370
+ */
371
+ const contents = []
372
+ currPos.am.readContent(contents, item.id.client, item.id.clock, true, item.content, 0)
373
+ for (let i = 0; i < contents.length; i++) {
374
+ const c = contents[i]
375
+ if (c.content.isCountable() && c.attrs != null) {
376
+ // deleting already deleted content. store that information in a meta property, but do
377
+ // nothing
378
+ const contentLen = math.min(c.content.getLength(), length)
379
+ map.setIfUndefined(transaction.meta, 'attributedDeletes', createIdSet).add(item.id.client, c.clock, contentLen)
380
+ length -= contentLen
381
+ }
382
+ }
383
+ const lastContent = contents.length > 0 ? contents[contents.length - 1] : null
384
+ const nextItemClock = item.id.clock + item.length
385
+ const nextContentClock = lastContent != null ? lastContent.clock + lastContent.content.getLength() : nextItemClock
386
+ if (nextContentClock < nextItemClock) {
387
+ getItemCleanStart(transaction, createID(item.id.client, nextContentClock))
388
+ }
389
+ }
390
+ currPos.forward()
391
+ }
392
+ if (start) {
393
+ cleanupFormattingGap(transaction, start, currPos.right, startAttrs, currPos.currentAttributes)
394
+ }
395
+ const parent = /** @type {YType<any>} */ (/** @type {Item} */ (currPos.left || currPos.right).parent)
396
+ if (parent._searchMarker) {
397
+ updateMarkerChanges(parent._searchMarker, currPos.index, -startLength + length)
398
+ }
399
+ return currPos
400
+ }
401
+
402
+ export class ArraySearchMarker {
403
+ /**
404
+ * @param {Item} p
405
+ * @param {number} index
406
+ */
407
+ constructor (p, index) {
408
+ p.marker = true
409
+ this.p = p
410
+ this.index = index
411
+ this.timestamp = globalSearchMarkerTimestamp++
412
+ }
413
+ }
414
+
415
+ /**
416
+ * @param {ArraySearchMarker} marker
417
+ */
418
+ const refreshMarkerTimestamp = marker => { marker.timestamp = globalSearchMarkerTimestamp++ }
419
+
420
+ /**
421
+ * This is rather complex so this function is the only thing that should overwrite a marker
422
+ *
423
+ * @param {ArraySearchMarker} marker
424
+ * @param {Item} p
425
+ * @param {number} index
426
+ */
427
+ const overwriteMarker = (marker, p, index) => {
428
+ marker.p.marker = false
429
+ marker.p = p
430
+ p.marker = true
431
+ marker.index = index
432
+ marker.timestamp = globalSearchMarkerTimestamp++
433
+ }
434
+
435
+ /**
436
+ * @param {Array<ArraySearchMarker>} searchMarker
437
+ * @param {Item} p
438
+ * @param {number} index
439
+ */
440
+ const markPosition = (searchMarker, p, index) => {
441
+ if (searchMarker.length >= maxSearchMarker) {
442
+ // override oldest marker (we don't want to create more objects)
443
+ const marker = searchMarker.reduce((a, b) => a.timestamp < b.timestamp ? a : b)
444
+ overwriteMarker(marker, p, index)
445
+ return marker
446
+ } else {
447
+ // create new marker
448
+ const pm = new ArraySearchMarker(p, index)
449
+ searchMarker.push(pm)
450
+ return pm
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Search marker help us to find positions in the associative array faster.
456
+ *
457
+ * They speed up the process of finding a position without much bookkeeping.
458
+ *
459
+ * A maximum of `maxSearchMarker` objects are created.
460
+ *
461
+ * This function always returns a refreshed marker (updated timestamp)
462
+ *
463
+ * @param {YType} yarray
464
+ * @param {number} index
465
+ */
466
+ export const findMarker = (yarray, index) => {
467
+ if (yarray._start === null || index === 0 || yarray._searchMarker === null) {
468
+ return null
469
+ }
470
+ const marker = yarray._searchMarker.length === 0 ? null : yarray._searchMarker.reduce((a, b) => math.abs(index - a.index) < math.abs(index - b.index) ? a : b)
471
+ let p = yarray._start
472
+ let pindex = 0
473
+ if (marker !== null) {
474
+ p = marker.p
475
+ pindex = marker.index
476
+ refreshMarkerTimestamp(marker) // we used it, we might need to use it again
477
+ }
478
+ // iterate to right if possible
479
+ while (p.right !== null && pindex < index) {
480
+ if (!p.deleted && p.countable) {
481
+ if (index < pindex + p.length) {
482
+ break
483
+ }
484
+ pindex += p.length
485
+ }
486
+ p = p.right
487
+ }
488
+ // iterate to left if necessary (might be that pindex > index)
489
+ while (p.left !== null && pindex > index) {
490
+ p = p.left
491
+ if (!p.deleted && p.countable) {
492
+ pindex -= p.length
493
+ }
494
+ }
495
+ // we want to make sure that p can't be merged with left, because that would screw up everything
496
+ // in that cas just return what we have (it is most likely the best marker anyway)
497
+ // iterate to left until p can't be merged with left
498
+ while (p.left !== null && p.left.id.client === p.id.client && p.left.id.clock + p.left.length === p.id.clock) {
499
+ p = p.left
500
+ if (!p.deleted && p.countable) {
501
+ pindex -= p.length
502
+ }
503
+ }
504
+ if (marker !== null && math.abs(marker.index - pindex) < /** @type {any} */ (p.parent).length / maxSearchMarker) {
505
+ // adjust existing marker
506
+ overwriteMarker(marker, p, pindex)
507
+ return marker
508
+ } else {
509
+ // create new marker
510
+ return markPosition(yarray._searchMarker, p, pindex)
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Update markers when a change happened.
516
+ *
517
+ * This should be called before doing a deletion!
518
+ *
519
+ * @param {Array<ArraySearchMarker>} searchMarker
520
+ * @param {number} index
521
+ * @param {number} len If insertion, len is positive. If deletion, len is negative.
522
+ */
523
+ export const updateMarkerChanges = (searchMarker, index, len) => {
524
+ for (let i = searchMarker.length - 1; i >= 0; i--) {
525
+ const m = searchMarker[i]
526
+ if (len > 0) {
527
+ /**
528
+ * @type {Item|null}
529
+ */
530
+ let p = m.p
531
+ p.marker = false
532
+ // Ideally we just want to do a simple position comparison, but this will only work if
533
+ // search markers don't point to deleted items for formats.
534
+ // Iterate marker to prev undeleted countable position so we know what to do when updating a position
535
+ while (p && (p.deleted || !p.countable)) {
536
+ p = p.left
537
+ if (p && !p.deleted && p.countable) {
538
+ // adjust position. the loop should break now
539
+ m.index -= p.length
540
+ }
541
+ }
542
+ if (p === null || p.marker === true) {
543
+ // remove search marker if updated position is null or if position is already marked
544
+ searchMarker.splice(i, 1)
545
+ continue
546
+ }
547
+ m.p = p
548
+ p.marker = true
549
+ }
550
+ if (index < m.index || (len > 0 && index === m.index)) { // a simple index <= m.index check would actually suffice
551
+ m.index = math.max(index, m.index + len)
552
+ }
553
+ }
554
+ }
555
+
556
+ /**
557
+ * Accumulate all (list) children of a type and return them as an Array.
558
+ *
559
+ * @param {YType} t
560
+ * @return {Array<Item>}
561
+ */
562
+ export const getTypeChildren = t => {
563
+ t.doc ?? warnPrematureAccess()
564
+ let s = t._start
565
+ const arr = []
566
+ while (s) {
567
+ arr.push(s)
568
+ s = s.right
569
+ }
570
+ return arr
571
+ }
572
+
573
+ /**
574
+ * Call event listeners with an event. This will also add an event to all
575
+ * parents (for `.observeDeep` handlers).
576
+ *
577
+ * @param {YType} type
578
+ * @param {Transaction} transaction
579
+ * @param {YEvent<any>} event
580
+ */
581
+ export const callTypeObservers = (type, transaction, event) => {
582
+ const changedType = type
583
+ const changedParentTypes = transaction.changedParentTypes
584
+ while (true) {
585
+ // @ts-ignore
586
+ map.setIfUndefined(changedParentTypes, type, () => []).push(event)
587
+ if (type._item === null) {
588
+ break
589
+ }
590
+ type = /** @type {YType} */ (type._item.parent)
591
+ }
592
+ callEventHandlerListeners(/** @type {any} */ (changedType._eH), event, transaction)
593
+ }
594
+
595
+ /**
596
+ * Abstract Yjs Type class
597
+ * @template {delta.DeltaConf} [DConf=any]
598
+ */
599
+ export class YType {
600
+ /**
601
+ * @param {delta.DeltaConfGetName<DConf>?} name
602
+ */
603
+ constructor (name = null) {
604
+ /**
605
+ * @type {delta.DeltaConfGetName<DConf>}
606
+ */
607
+ this.name = /** @type {delta.DeltaConfGetName<DConf>} */ (name)
608
+ /**
609
+ * @type {Item|null}
610
+ */
611
+ this._item = null
612
+ /**
613
+ * @type {Map<string,Item>}
614
+ */
615
+ this._map = new Map()
616
+ /**
617
+ * @type {Item|null}
618
+ */
619
+ this._start = null
620
+ /**
621
+ * @type {Doc|null}
622
+ */
623
+ this.doc = null
624
+ this._length = 0
625
+ /**
626
+ * Event handlers
627
+ * @type {EventHandler<YEvent<DeltaToYType<DConf>>,Transaction>}
628
+ */
629
+ this._eH = createEventHandler()
630
+ /**
631
+ * Deep event handlers
632
+ * @type {EventHandler<YEvent<DConf>,Transaction>}
633
+ */
634
+ this._dEH = createEventHandler()
635
+ /**
636
+ * @type {null | Array<ArraySearchMarker>}
637
+ */
638
+ this._searchMarker = null
639
+ /**
640
+ * @type {delta.DeltaBuilder<DConf>}
641
+ * @private
642
+ */
643
+ this._content = /** @type {delta.DeltaBuilderAny} */ (delta.create())
644
+ this._legacyTypeRef = this.name == null ? contentType.YXmlFragmentRefID : contentType.YXmlElementRefID
645
+ /**
646
+ * @type {Array<ArraySearchMarker>|null}
647
+ */
648
+ this._searchMarker = []
649
+ /**
650
+ * Whether this YText contains formatting attributes.
651
+ * This flag is updated when a formatting item is integrated (see ContentFormat.integrate)
652
+ */
653
+ this._hasFormatting = false
654
+ }
655
+
656
+ /**
657
+ * @template {delta.DeltaConf} DC
658
+ * @param {delta.Delta<DC>} d
659
+ * @return {YType<DC>}
660
+ */
661
+ static from (d) {
662
+ const yt = new YType(d.name)
663
+ yt.applyDelta(d)
664
+ return yt
665
+ }
666
+
667
+ get length () {
668
+ this.doc ?? warnPrematureAccess()
669
+ return this._length
670
+ }
671
+
672
+ /**
673
+ * Returns a fresh delta that can be used to change this YType.
674
+ * @type {delta.DeltaBuilder<DeltaToYType<DConf>>}
675
+ */
676
+ get change () {
677
+ return /** @type {any} */ (delta.create())
678
+ }
679
+
680
+ /**
681
+ * @return {YType<any>?}
682
+ */
683
+ get parent () {
684
+ return /** @type {YType<any>?} */ (this._item ? this._item.parent : null)
685
+ }
686
+
687
+ /**
688
+ * Integrate this type into the Yjs instance.
689
+ *
690
+ * * Save this struct in the os
691
+ * * This type is sent to other client
692
+ * * Observer functions are fired
693
+ *
694
+ * @param {Doc} y The Yjs instance
695
+ * @param {Item|null} item
696
+ */
697
+ _integrate (y, item) {
698
+ this.doc = y
699
+ this._item = item
700
+ if (this._prelim) {
701
+ this.applyDelta(this._prelim)
702
+ this._prelim = null
703
+ }
704
+ }
705
+
706
+ /**
707
+ * @return {YType<DConf>}
708
+ */
709
+ _copy () {
710
+ return new YType(this.name)
711
+ }
712
+
713
+ /**
714
+ * Creates YEvent and calls all type observers.
715
+ * Must be implemented by each type.
716
+ *
717
+ * @param {Transaction} transaction
718
+ * @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
719
+ */
720
+ _callObserver (transaction, parentSubs) {
721
+ const event = new YEvent(/** @type {any} */ (this), transaction, parentSubs)
722
+ callTypeObservers(/** @type {any} */ (this), transaction, event)
723
+ if (!transaction.local && this._searchMarker) {
724
+ this._searchMarker.length = 0
725
+ }
726
+ // If a remote change happened, we try to cleanup potential formatting duplicates.
727
+ if (!transaction.local && this._hasFormatting) {
728
+ transaction._needFormattingCleanup = true
729
+ }
730
+ }
731
+
732
+ /**
733
+ * Observe all events that are created on this type.
734
+ *
735
+ * @template {(target: YEvent<DeltaToYType<DConf>>, tr: Transaction) => void} F
736
+ * @param {F} f Observer function
737
+ * @return {F}
738
+ */
739
+ observe (f) {
740
+ addEventHandlerListener(this._eH, f)
741
+ return f
742
+ }
743
+
744
+ /**
745
+ * Observe all events that are created by this type and its children.
746
+ *
747
+ * @template {function(YEvent<DConf>,Transaction):void} F
748
+ * @param {F} f Observer function
749
+ * @return {F}
750
+ */
751
+ observeDeep (f) {
752
+ addEventHandlerListener(this._dEH, f)
753
+ return f
754
+ }
755
+
756
+ /**
757
+ * Unregister an observer function.
758
+ *
759
+ * @param {(type:YEvent<DeltaToYType<DConf>>,tr:Transaction)=>void} f Observer function
760
+ */
761
+ unobserve (f) {
762
+ removeEventHandlerListener(this._eH, f)
763
+ }
764
+
765
+ /**
766
+ * Unregister an observer function.
767
+ *
768
+ * @param {function(YEvent<DConf>,Transaction):void} f Observer function
769
+ */
770
+ unobserveDeep (f) {
771
+ removeEventHandlerListener(this._dEH, f)
772
+ }
773
+
774
+ /**
775
+ * Render the difference to another ydoc (which can be empty) and highlight the differences with
776
+ * attributions.
777
+ *
778
+ * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
779
+ * attribution `{ isDeleted: true, .. }`.
780
+ *
781
+ * @template {boolean} [Deep=false]
782
+ *
783
+ * @param {AbstractAttributionManager} am
784
+ * @param {Object} [opts]
785
+ * @param {import('./utils/IdSet.js').IdSet?} [opts.itemsToRender]
786
+ * @param {boolean} [opts.retainInserts] - if true, retain rendered inserts with attributions
787
+ * @param {boolean} [opts.retainDeletes] - if true, retain rendered+attributed deletes only
788
+ * @param {import('./utils/IdSet.js').IdSet?} [opts.deletedItems] - used for computing prevItem in attributes
789
+ * @param {Map<YType,Set<string|null>>|null} [opts.modified] - set of types that should be rendered as modified children
790
+ * @param {Deep} [opts.deep] - render child types as delta
791
+ * @return {Deep extends true ? delta.Delta<DConf> : delta.Delta<DeltaConfDeltaToYType<DConf>>} The Delta representation of this type.
792
+ *
793
+ * @public
794
+ */
795
+ toDelta (am = noAttributionsManager, opts = {}) {
796
+ const { itemsToRender = null, retainInserts = false, retainDeletes = false, deletedItems = null, modified = null, deep = false } = opts
797
+ const renderAttrs = modified?.get(this) || null
798
+ const renderChildren = !!(modified == null || modified.get(this)?.has(null))
799
+ /**
800
+ * @type {delta.DeltaBuilderAny}
801
+ */
802
+ const d = /** @type {any} */ (delta.create(this.name))
803
+ const optsAll = modified == null ? opts : object.assign({}, opts, { modified: null })
804
+ typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am, deep, modified, deletedItems, itemsToRender, opts, optsAll)
805
+ if (renderChildren) {
806
+ /**
807
+ * @type {delta.FormattingAttributes}
808
+ */
809
+ let currentAttributes = {} // saves all current attributes for insert
810
+ let usingCurrentAttributes = false
811
+ /**
812
+ * @type {delta.FormattingAttributes}
813
+ */
814
+ let changedAttributes = {} // saves changed attributes for retain
815
+ let usingChangedAttributes = false
816
+ /**
817
+ * Logic for formatting attribute attribution
818
+ * Everything that comes after an formatting attribute is formatted by the user that created it.
819
+ * Two exceptions:
820
+ * - the user resets formatting to the previously known formatting that is not attributed
821
+ * - the user deletes a formatting attribute and hence restores the previously known formatting
822
+ * that is not attributed.
823
+ * @type {delta.FormattingAttributes}
824
+ */
825
+ const previousUnattributedAttributes = {} // contains previously known unattributed formatting
826
+ /**
827
+ * @type {delta.FormattingAttributes}
828
+ */
829
+ const previousAttributes = {} // The value before changes
830
+ /**
831
+ * @type {Array<import('./internals.js').AttributedContent<any>>}
832
+ */
833
+ const cs = []
834
+ for (let item = this._start; item !== null; cs.length = 0) {
835
+ if (itemsToRender != null) {
836
+ for (; item !== null && cs.length < 50; item = item.right) {
837
+ const rslice = itemsToRender.slice(item.id.client, item.id.clock, item.length)
838
+ let itemContent = rslice.length > 1 ? item.content.copy() : item.content
839
+ for (let ir = 0; ir < rslice.length; ir++) {
840
+ const idrange = rslice[ir]
841
+ const content = itemContent
842
+ if (ir !== rslice.length - 1) {
843
+ itemContent = itemContent.splice(idrange.len)
844
+ }
845
+ am.readContent(cs, item.id.client, idrange.clock, item.deleted, content, idrange.exists ? 2 : 0)
846
+ }
847
+ }
848
+ } else {
849
+ for (; item !== null && cs.length < 50; item = item.right) {
850
+ am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
851
+ }
852
+ }
853
+ for (let i = 0; i < cs.length; i++) {
854
+ const c = cs[i]
855
+ // render (attributed) content even if it was deleted
856
+ const renderContent = c.render && (!c.deleted || c.attrs != null)
857
+ // content that was just deleted. It is not rendered as an insertion, because it doesn't
858
+ // have any attributes.
859
+ const renderDelete = c.render && c.deleted
860
+ // existing content that should be retained, only adding changed attributes
861
+ const retainContent = !c.render && (!c.deleted || c.attrs != null)
862
+ const attribution = (renderContent || c.content.constructor === ContentFormat) ? createAttributionFromAttributionItems(c.attrs, c.deleted) : null
863
+ switch (c.content.constructor) {
864
+ case ContentDeleted: {
865
+ if (renderDelete) d.delete(c.content.getLength())
866
+ break
867
+ }
868
+ case ContentString:
869
+ if (renderContent) {
870
+ d.usedAttributes = currentAttributes
871
+ usingCurrentAttributes = true
872
+ if (c.deleted ? retainDeletes : retainInserts) {
873
+ d.retain(/** @type {ContentString} */ (c.content).str.length, null, attribution ?? {})
874
+ } else {
875
+ d.insert(/** @type {ContentString} */ (c.content).str, null, attribution)
876
+ }
877
+ } else if (renderDelete) {
878
+ d.delete(c.content.getLength())
879
+ } else if (retainContent) {
880
+ d.usedAttributes = changedAttributes
881
+ usingChangedAttributes = true
882
+ d.retain(c.content.getLength())
883
+ }
884
+ break
885
+ case ContentEmbed:
886
+ case ContentAny:
887
+ case ContentJSON:
888
+ case ContentType:
889
+ case ContentBinary:
890
+ if (renderContent) {
891
+ d.usedAttributes = currentAttributes
892
+ usingCurrentAttributes = true
893
+ if (c.deleted ? retainDeletes : retainInserts) {
894
+ d.retain(c.content.getLength(), null, attribution ?? {})
895
+ } else if (deep && c.content.constructor === ContentType) {
896
+ d.insert([/** @type {any} */(c.content).type.toDelta(am, optsAll)], null, attribution)
897
+ } else {
898
+ d.insert(c.content.getContent(), null, attribution)
899
+ }
900
+ } else if (renderDelete) {
901
+ d.delete(1)
902
+ } else if (retainContent) {
903
+ if (c.content.constructor === ContentType && modified?.has(/** @type {ContentType} */ (c.content).type)) {
904
+ // @todo use current transaction instead
905
+ d.modify(/** @type {any} */ (c.content).type.toDelta(am, opts))
906
+ } else {
907
+ d.usedAttributes = changedAttributes
908
+ usingChangedAttributes = true
909
+ d.retain(1)
910
+ }
911
+ }
912
+ break
913
+ case ContentFormat: {
914
+ const { key, value } = /** @type {ContentFormat} */ (c.content)
915
+ const currAttrVal = currentAttributes[key] ?? null
916
+ if (attribution != null && (c.deleted || !object.hasProperty(previousUnattributedAttributes, key))) {
917
+ previousUnattributedAttributes[key] = c.deleted ? value : currAttrVal
918
+ }
919
+ // @todo write a function "updateCurrentAttributes" and "updateChangedAttributes"
920
+ // # Update Attributes
921
+ if (renderContent || renderDelete) {
922
+ // create fresh references
923
+ if (usingCurrentAttributes) {
924
+ currentAttributes = object.assign({}, currentAttributes)
925
+ usingCurrentAttributes = false
926
+ }
927
+ if (usingChangedAttributes) {
928
+ usingChangedAttributes = false
929
+ changedAttributes = object.assign({}, changedAttributes)
930
+ }
931
+ }
932
+ if (renderContent || renderDelete) {
933
+ if (c.deleted) {
934
+ // content was deleted, but is possibly attributed
935
+ if (!equalAttrs(value, currAttrVal)) { // do nothing if nothing changed
936
+ if (equalAttrs(currAttrVal, previousAttributes[key] ?? null) && changedAttributes[key] !== undefined) {
937
+ delete changedAttributes[key]
938
+ } else {
939
+ changedAttributes[key] = currAttrVal
940
+ }
941
+ // current attributes doesn't change
942
+ previousAttributes[key] = value
943
+ }
944
+ } else { // !c.deleted
945
+ // content was inserted, and is possibly attributed
946
+ if (equalAttrs(value, currAttrVal)) {
947
+ // item.delete(transaction)
948
+ } else if (equalAttrs(value, previousAttributes[key] ?? null)) {
949
+ delete changedAttributes[key]
950
+ } else {
951
+ changedAttributes[key] = value
952
+ }
953
+ if (value == null) {
954
+ delete currentAttributes[key]
955
+ } else {
956
+ currentAttributes[key] = value
957
+ }
958
+ }
959
+ } else if (retainContent && !c.deleted) {
960
+ // fresh reference to currentAttributes only
961
+ if (usingCurrentAttributes) {
962
+ currentAttributes = object.assign({}, currentAttributes)
963
+ usingCurrentAttributes = false
964
+ }
965
+ if (usingChangedAttributes && changedAttributes[key] !== undefined) {
966
+ usingChangedAttributes = false
967
+ changedAttributes = object.assign({}, changedAttributes)
968
+ }
969
+ if (value == null) {
970
+ delete currentAttributes[key]
971
+ } else {
972
+ currentAttributes[key] = value
973
+ }
974
+ delete changedAttributes[key]
975
+ previousAttributes[key] = value
976
+ }
977
+ // # Update Attributions
978
+ if (attribution != null || object.hasProperty(previousUnattributedAttributes, key)) {
979
+ /**
980
+ * @type {import('./utils/AttributionManager.js').Attribution}
981
+ */
982
+ const formattingAttribution = object.assign({}, d.usedAttribution)
983
+ const changedAttributedAttributes = /** @type {{ [key: string]: Array<any> }} */ (formattingAttribution.format = object.assign({}, formattingAttribution.format ?? {}))
984
+ if (attribution == null || equalAttrs(previousUnattributedAttributes[key], currentAttributes[key] ?? null)) {
985
+ // an unattributed formatting attribute was found or an attributed formatting
986
+ // attribute was found that resets to the previous status
987
+ delete changedAttributedAttributes[key]
988
+ delete previousUnattributedAttributes[key]
989
+ } else {
990
+ const by = changedAttributedAttributes[key] = (changedAttributedAttributes[key]?.slice() ?? [])
991
+ by.push(...((c.deleted ? attribution.delete : attribution.insert) ?? []))
992
+ const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
993
+ if (attributedAt) formattingAttribution.formatAt = attributedAt
994
+ }
995
+ if (object.isEmpty(changedAttributedAttributes)) {
996
+ d.useAttribution(null)
997
+ } else if (attribution != null) {
998
+ const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
999
+ if (attributedAt != null) formattingAttribution.formatAt = attributedAt
1000
+ d.useAttribution(formattingAttribution)
1001
+ }
1002
+ }
1003
+ break
1004
+ }
1005
+ }
1006
+ }
1007
+ }
1008
+ }
1009
+ return /** @type {any} */ (d.done(false))
1010
+ }
1011
+
1012
+ /**
1013
+ * Render the difference to another ydoc (which can be empty) and highlight the differences with
1014
+ * attributions.
1015
+ *
1016
+ * @param {AbstractAttributionManager} am
1017
+ * @return {delta.Delta<DConf>}
1018
+ */
1019
+ toDeltaDeep (am = noAttributionsManager) {
1020
+ return /** @type {any} */ (this.toDelta(am, { deep: true }))
1021
+ }
1022
+
1023
+ /**
1024
+ * Apply a {@link Delta} on this shared type.
1025
+ *
1026
+ * @param {delta.DeltaAny} d The changes to apply on this element.
1027
+ * @param {AbstractAttributionManager} am
1028
+ *
1029
+ * @public
1030
+ */
1031
+ applyDelta (d, am = noAttributionsManager) {
1032
+ if (this.doc == null) {
1033
+ (this._prelim || (this._prelim = /** @type {any} */ (delta.create()))).apply(d)
1034
+ } else {
1035
+ // @todo this was moved here from ytext. Make this more generic
1036
+ transact(this.doc, transaction => {
1037
+ const currPos = new ItemTextListPosition(null, this._start, 0, new Map(), am)
1038
+ for (const op of d.children) {
1039
+ if (delta.$textOp.check(op)) {
1040
+ insertContent(transaction, /** @type {any} */ (this), currPos, new ContentString(op.insert), op.format || {})
1041
+ } else if (delta.$insertOp.check(op)) {
1042
+ insertContentHelper(transaction, this, currPos, op.insert, op.format || {})
1043
+ } else if (delta.$retainOp.check(op)) {
1044
+ currPos.formatText(transaction, /** @type {any} */ (this), op.retain, op.format || {})
1045
+ } else if (delta.$deleteOp.check(op)) {
1046
+ deleteText(transaction, currPos, op.delete)
1047
+ } else if (delta.$modifyOp.check(op)) {
1048
+ if (currPos.right) {
1049
+ /** @type {ContentType} */ (currPos.right.content).type.applyDelta(op.value)
1050
+ } else {
1051
+ error.unexpectedCase()
1052
+ }
1053
+ currPos.formatText(transaction, /** @type {any} */ (this), 1, op.format || {})
1054
+ } else {
1055
+ error.unexpectedCase()
1056
+ }
1057
+ }
1058
+ for (const op of d.attrs) {
1059
+ if (delta.$setAttrOp.check(op)) {
1060
+ typeMapSet(transaction, /** @type {any} */ (this), /** @type {any} */ (op.key), op.value)
1061
+ } else if (delta.$deleteAttrOp.check(op)) {
1062
+ typeMapDelete(transaction, /** @type {any} */ (this), /** @type {any} */ (op.key))
1063
+ } else {
1064
+ const sub = typeMapGet(/** @type {any} */ (this), /** @type {any} */ (op.key))
1065
+ if (!(sub instanceof YType)) {
1066
+ error.unexpectedCase()
1067
+ }
1068
+ sub.applyDelta(op.value)
1069
+ }
1070
+ }
1071
+ })
1072
+ }
1073
+ return this
1074
+ }
1075
+
1076
+ /**
1077
+ * Makes a copy of this data type that can be included somewhere else.
1078
+ *
1079
+ * Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
1080
+ *
1081
+ * @return {YType<DConf>}
1082
+ */
1083
+ clone () {
1084
+ const cpy = this._copy()
1085
+ cpy.applyDelta(this.toDeltaDeep())
1086
+ return cpy
1087
+ }
1088
+
1089
+ /**
1090
+ * Removes all elements from this YMap.
1091
+ */
1092
+ clearAttrs () {
1093
+ const d = delta.create()
1094
+ this.forEachAttr((_, key) => {
1095
+ d.deleteAttr(/** @type {any} */ (key))
1096
+ })
1097
+ this.applyDelta(d)
1098
+ }
1099
+
1100
+ /**
1101
+ * Removes an attribute from this YXmlElement.
1102
+ *
1103
+ * @param {string} attributeName The attribute name that is to be removed.
1104
+ *
1105
+ * @public
1106
+ */
1107
+ deleteAttr (attributeName) {
1108
+ this.applyDelta(delta.create().deleteAttr(attributeName).done())
1109
+ }
1110
+
1111
+ /**
1112
+ * Sets or updates an attribute.
1113
+ *
1114
+ * @template {Exclude<keyof delta.DeltaConfGetAttrs<DConf>,symbol>} KEY
1115
+ * @template {delta.DeltaConfGetAttrs<DConf>[KEY]} VAL
1116
+ *
1117
+ * @param {KEY} attributeName The attribute name that is to be set.
1118
+ * @param {VAL} attributeValue The attribute value that is to be set.
1119
+ * @return {VAL}
1120
+ *
1121
+ * @public
1122
+ */
1123
+ setAttr (attributeName, attributeValue) {
1124
+ this.applyDelta(delta.create().setAttr(attributeName, attributeValue).done())
1125
+ return attributeValue
1126
+ }
1127
+
1128
+ /**
1129
+ * Returns an attribute value that belongs to the attribute name.
1130
+ *
1131
+ * @template {Exclude<keyof delta.DeltaConfGetAttrs<DConf>,symbol|number>} KEY
1132
+ * @param {KEY} attributeName The attribute name that identifies the queried value.
1133
+ * @return {delta.DeltaConfGetAttrs<DConf>[KEY]|undefined} The queried attribute value.
1134
+ * @public
1135
+ */
1136
+ getAttr (attributeName) {
1137
+ return /** @type {any} */ (typeMapGet(this, attributeName))
1138
+ }
1139
+
1140
+ /**
1141
+ * Returns whether an attribute exists
1142
+ *
1143
+ * @param {string} attributeName The attribute name to check for existence.
1144
+ * @return {boolean} whether the attribute exists.
1145
+ *
1146
+ * @public
1147
+ */
1148
+ hasAttr (attributeName) {
1149
+ return /** @type {any} */ (typeMapHas(this, attributeName))
1150
+ }
1151
+
1152
+ /**
1153
+ * Returns all attribute name/value pairs in a JSON Object.
1154
+ *
1155
+ * @param {Snapshot} [snapshot]
1156
+ * @return {{ [Key in Extract<keyof delta.DeltaConfGetAttrs<DConf>,string>]?: delta.DeltaConfGetAttrs<DConf>[Key]}} A JSON Object that describes the attributes.
1157
+ *
1158
+ * @public
1159
+ */
1160
+ getAttrs (snapshot) {
1161
+ return /** @type {any} */ (snapshot ? typeMapGetAllSnapshot(this, snapshot) : typeMapGetAll(this))
1162
+ }
1163
+
1164
+ /**
1165
+ * Inserts new content at an index.
1166
+ *
1167
+ * Important: This function expects an array of content. Not just a content
1168
+ * object. The reason for this "weirdness" is that inserting several elements
1169
+ * is very efficient when it is done as a single operation.
1170
+ *
1171
+ * @example
1172
+ * // Insert character 'a' at position 0
1173
+ * yarray.insert(0, ['a'])
1174
+ * // Insert numbers 1, 2 at position 1
1175
+ * yarray.insert(1, [1, 2])
1176
+ *
1177
+ * @param {number} index The index to insert content at.
1178
+ * @param {Array<delta.DeltaConfGetChildren<DConf>>|delta.DeltaConfGetText<DConf>} content Array of content to append.
1179
+ * @param {delta.FormattingAttributes} [format]
1180
+ */
1181
+ insert (index, content, format) {
1182
+ this.applyDelta(delta.create().retain(index).insert(/** @type {any} */ (content), format))
1183
+ }
1184
+
1185
+ /**
1186
+ * Inserts new content at an index.
1187
+ *
1188
+ * Important: This function expects an array of content. Not just a content
1189
+ * object. The reason for this "weirdness" is that inserting several elements
1190
+ * is very efficient when it is done as a single operation.
1191
+ *
1192
+ * @example
1193
+ * // Insert character 'a' at position 0
1194
+ * yarray.insert(0, ['a'])
1195
+ * // Insert numbers 1, 2 at position 1
1196
+ * yarray.insert(1, [1, 2])
1197
+ *
1198
+ * @param {number} index The index to insert content at.
1199
+ * @param {number} length The index to insert content at.
1200
+ * @param {delta.FormattingAttributes} formats
1201
+ *
1202
+ */
1203
+ format (index, length, formats) {
1204
+ this.applyDelta(delta.create().retain(index).retain(length, formats))
1205
+ }
1206
+
1207
+ /**
1208
+ * Appends content to this YArray.
1209
+ *
1210
+ * @param {Array<delta.DeltaConfGetChildren<DConf>>|delta.DeltaConfGetText<DConf>} content Array of content to append.
1211
+ *
1212
+ * @todo Use the following implementation in all types.
1213
+ */
1214
+ push (content) {
1215
+ this.insert(this.length, content)
1216
+ }
1217
+
1218
+ /**
1219
+ * Prepends content to this YArray.
1220
+ *
1221
+ * @param {delta.DeltaConfGetText<DConf>} content Array of content to prepend.
1222
+ */
1223
+ unshift (content) {
1224
+ this.insert(0, content)
1225
+ }
1226
+
1227
+ /**
1228
+ * Deletes elements starting from an index.
1229
+ *
1230
+ * @param {number} index Index at which to start deleting elements
1231
+ * @param {number} length The number of elements to remove. Defaults to 1.
1232
+ */
1233
+ delete (index, length = 1) {
1234
+ this.applyDelta(delta.create().retain(index).delete(length))
1235
+ }
1236
+
1237
+ /**
1238
+ * Returns the i-th element from a YArray.
1239
+ *
1240
+ * @param {number} index The index of the element to return from the YArray
1241
+ * @return {delta.DeltaConfGetChildren<DConf>}
1242
+ */
1243
+ get (index) {
1244
+ return typeListGet(this, index)
1245
+ }
1246
+
1247
+ /**
1248
+ * Returns a portion of this YXmlFragment into a JavaScript Array selected
1249
+ * from start to end (end not included).
1250
+ *
1251
+ * @param {number} [start]
1252
+ * @param {number} [end]
1253
+ * @return {Array<delta.DeltaConfGetChildren<DConf>>}
1254
+ */
1255
+ slice (start = 0, end = this.length) {
1256
+ return typeListSlice(this, start, end)
1257
+ }
1258
+
1259
+ /**
1260
+ * @todo refactor this, this should use getContent only!
1261
+ *
1262
+ * Transforms this YArray to a JavaScript Array.
1263
+ *
1264
+ * @return {Array<delta.DeltaConfGetChildren<DConf> | delta.DeltaConfGetText<DConf>>}
1265
+ */
1266
+ toArray () {
1267
+ const dcontent = this.toDelta()
1268
+ /**
1269
+ * @type {Array<any>}
1270
+ */
1271
+ const children = []
1272
+ for (const child of dcontent.children) {
1273
+ if (delta.$insertOp.check(child)) {
1274
+ children.push(...child.insert)
1275
+ } else if (delta.$textOp.check(child)) {
1276
+ children.push(child.insert)
1277
+ }
1278
+ }
1279
+ return children
1280
+ }
1281
+
1282
+ /**
1283
+ * Transforms this Shared Type to a JSON object.
1284
+ * @return {{ name?: string, attrs?: { [K:string|number]: any }, children?: Array<any> }}
1285
+ */
1286
+ toJSON () {
1287
+ /**
1288
+ * @type {{[K:string]:any}}
1289
+ */
1290
+ const attrs = this.getAttrs()
1291
+ for (const k in attrs) {
1292
+ const attr = attrs[k]
1293
+ attrs[k] = attr instanceof YType ? attr.toJSON() : attr
1294
+ }
1295
+ const children = this.toArray().map(child => child instanceof YType ? /** @type {any} */ (child.toJSON()) : child)
1296
+ /**
1297
+ * @type {any}
1298
+ */
1299
+ const res = {}
1300
+ if (this.name != null) {
1301
+ res.name = this.name
1302
+ }
1303
+ if (this.length > 0) {
1304
+ res.children = children
1305
+ }
1306
+ if (this.attrSize > 0) {
1307
+ res.attrs = attrs
1308
+ }
1309
+ return res
1310
+ }
1311
+
1312
+ /**
1313
+ * @param {object} opts
1314
+ * @param {boolean} [opts.forceTag] enforce creating a surrouning <name /> tag, even if it is null.
1315
+ */
1316
+ toString ({ forceTag = false } = {}) {
1317
+ /**
1318
+ * @type {Array<[string|number,string]>}
1319
+ */
1320
+ const attrs = []
1321
+ this.forEachAttr((attr, key) => {
1322
+ attrs.push([(key), /** @type {any} */ (attr) instanceof YType ? attr.toString({ forceTag: true }) : JSON.stringify(attr)])
1323
+ })
1324
+ const attrsString = (attrs.length > 0 ? ' ' : '') + attrs.sort((a, b) => a[0].toString() < b[0].toString() ? -1 : 1).map(attr => attr[0] + '=' + attr[1]).join(' ')
1325
+ /**
1326
+ * @type {string}
1327
+ */
1328
+ const children = this.toArray().map(c => s.$string.check(c) ? c : (c instanceof YType ? c.toString({ forceTag: true }) : JSON.stringify(c))).join('')
1329
+ if (this.name == null && !forceTag && attrs.length === 0) {
1330
+ return children
1331
+ }
1332
+ if (this.length === 0) {
1333
+ return `<${this.name ?? ''}${attrsString} />`
1334
+ }
1335
+ return `<${this.name ?? ''}${attrsString}>${children}</${this.name ?? ''}>`
1336
+ }
1337
+
1338
+ /**
1339
+ * Returns an Array with the result of calling a provided function on every
1340
+ * child-element.
1341
+ *
1342
+ * @template M
1343
+ * @param {(child:delta.DeltaConfGetChildren<DConf>|delta.DeltaConfGetText<DConf>,index:number)=>M} f Function that produces an element of the new Array
1344
+ * @return {Array<M>} A new array with each element being the result of the
1345
+ * callback function
1346
+ */
1347
+ map (f) {
1348
+ return this.toArray().map(f)
1349
+ }
1350
+
1351
+ /**
1352
+ * Executes a provided function once on every element of this YArray.
1353
+ *
1354
+ * @param {(child:delta.DeltaConfGetChildren<DConf>|delta.DeltaConfGetText<DConf>,index:number)=>any} f Function that produces an element of the new Array
1355
+ */
1356
+ forEach (f) {
1357
+ return this.toArray().forEach(f)
1358
+ }
1359
+
1360
+ /**
1361
+ * Executes a provided function on once on every key-value pair.
1362
+ *
1363
+ * @param {(val:delta.DeltaConfGetAttrs<DConf>[any],key:Exclude<keyof delta.DeltaConfGetAttrs<DConf>,symbol>,ytype:this)=>any} f
1364
+ */
1365
+ forEachAttr (f) {
1366
+ this._map.forEach((item, key) => {
1367
+ if (!item.deleted) {
1368
+ f(item.content.getContent()[item.length - 1], /** @type {any} */ (key), this)
1369
+ }
1370
+ })
1371
+ }
1372
+
1373
+ /**
1374
+ * Returns the keys for each element in the YMap Type.
1375
+ *
1376
+ * @return {IterableIterator<import('lib0/ts').KeyOf<delta.DeltaConfGetAttrs<DConf>>>}
1377
+ */
1378
+ attrKeys () {
1379
+ return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => v[0])
1380
+ }
1381
+
1382
+ /**
1383
+ * Returns the values for each element in the YMap Type.
1384
+ *
1385
+ * @return {IterableIterator<delta.DeltaConfGetAttrs<DConf>[any]>}
1386
+ */
1387
+ attrValues () {
1388
+ return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => v[1].content.getContent()[v[1].length - 1])
1389
+ }
1390
+
1391
+ /**
1392
+ * Returns an Iterator of [key, value] pairs
1393
+ *
1394
+ * @return {IterableIterator<{ [K in keyof delta.DeltaConfGetAttrs<DConf>]: [K,delta.DeltaConfGetAttrs<DConf>[K]] }[any]>}
1395
+ */
1396
+ attrEntries () {
1397
+ return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => /** @type {any} */ ([v[0], v[1].content.getContent()[v[1].length - 1]]))
1398
+ }
1399
+
1400
+ /**
1401
+ * Returns the number of stored attributes (count of key/value pairs)
1402
+ *
1403
+ * @return {number}
1404
+ */
1405
+ get attrSize () {
1406
+ return [...createMapIterator(this)].length
1407
+ }
1408
+
1409
+ /**
1410
+ * @param {this} other
1411
+ */
1412
+ [traits.EqualityTraitSymbol] (other) {
1413
+ return this.toDelta().equals(other.toDelta())
1414
+ }
1415
+
1416
+ /**
1417
+ * @todo this doesn't need to live in a method.
1418
+ *
1419
+ * Transform the properties of this type to binary and write it to an
1420
+ * BinaryEncoder.
1421
+ *
1422
+ * This is called when this Item is sent to a remote peer.
1423
+ *
1424
+ * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to.
1425
+ */
1426
+ _write (encoder) {
1427
+ encoder.writeTypeRef(this._legacyTypeRef)
1428
+ switch (this._legacyTypeRef) {
1429
+ case contentType.YXmlElementRefID:
1430
+ case contentType.YXmlHookRefID: {
1431
+ encoder.writeKey(this.name)
1432
+ break
1433
+ }
1434
+ }
1435
+ }
1436
+ }
1437
+
1438
+ /**
1439
+ * @param {import('./utils/UpdateDecoder.js').UpdateDecoderV1 | import('./utils/UpdateDecoder.js').UpdateDecoderV2} decoder
1440
+ * @return {YType}
1441
+ *
1442
+ * @private
1443
+ * @function
1444
+ */
1445
+ export const readYType = decoder => {
1446
+ const typeRef = decoder.readTypeRef()
1447
+ const ytype = new YType(typeRef === contentType.YXmlElementRefID || typeRef === contentType.YXmlHookRefID ? decoder.readKey() : null)
1448
+ ytype._legacyTypeRef = typeRef
1449
+ return ytype
1450
+ }
1451
+
1452
+ /**
1453
+ * @param {any} a
1454
+ * @param {any} b
1455
+ * @return {boolean}
1456
+ */
1457
+ export const equalAttrs = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && object.equalFlat(a, b))
1458
+
1459
+ /**
1460
+ * @template {delta.DeltaConf} DConf
1461
+ * @typedef {delta.DeltaConfOverwrite<DConf, {
1462
+ * attrs: { [K in keyof delta.DeltaConfGetAttrs<DConf>]: DeltaToYType<delta.DeltaConfGetAttrs<DConf>[K]> },
1463
+ * children: DeltaToYType<delta.DeltaConfGetChildren<DConf>>
1464
+ * }>
1465
+ * } DeltaConfDeltaToYType
1466
+ */
1467
+
1468
+ /**
1469
+ * @template {any} Data
1470
+ * @typedef {Exclude<Data,delta.DeltaAny> | (Extract<Data,delta.DeltaAny> extends delta.Delta<infer DConf> ? (unknown extends DConf ? YType<DConf> : never) : never)} DeltaToYType
1471
+ */
1472
+
1473
+ /**
1474
+ * @param {YType<any>} type
1475
+ * @param {number} start
1476
+ * @param {number} end
1477
+ * @return {Array<any>}
1478
+ *
1479
+ * @private
1480
+ * @function
1481
+ */
1482
+ export const typeListSlice = (type, start, end) => {
1483
+ type.doc ?? warnPrematureAccess()
1484
+ if (start < 0) {
1485
+ start = type._length + start
1486
+ }
1487
+ if (end < 0) {
1488
+ end = type._length + end
1489
+ }
1490
+ let len = end - start
1491
+ const cs = []
1492
+ let n = type._start
1493
+ while (n !== null && len > 0) {
1494
+ if (n.countable && !n.deleted) {
1495
+ const c = n.content.getContent()
1496
+ if (c.length <= start) {
1497
+ start -= c.length
1498
+ } else {
1499
+ for (let i = start; i < c.length && len > 0; i++) {
1500
+ cs.push(c[i])
1501
+ len--
1502
+ }
1503
+ start = 0
1504
+ }
1505
+ }
1506
+ n = n.right
1507
+ }
1508
+ return cs
1509
+ }
1510
+
1511
+ /**
1512
+ * @todo remove / inline this
1513
+ *
1514
+ * @param {YType} type
1515
+ * @param {number} index
1516
+ * @return {any}
1517
+ *
1518
+ * @private
1519
+ * @function
1520
+ */
1521
+ export const typeListGet = (type, index) => {
1522
+ type.doc ?? warnPrematureAccess()
1523
+ const marker = findMarker(type, index)
1524
+ let n = type._start
1525
+ if (marker !== null) {
1526
+ n = marker.p
1527
+ index -= marker.index
1528
+ }
1529
+ for (; n !== null; n = n.right) {
1530
+ if (!n.deleted && n.countable) {
1531
+ if (index < n.length) {
1532
+ return n.content.getContent()[index]
1533
+ }
1534
+ index -= n.length
1535
+ }
1536
+ }
1537
+ }
1538
+
1539
+ /**
1540
+ * @todo this is a duplicate. use the unified insert function and remove this.
1541
+ *
1542
+ * @param {Transaction} transaction
1543
+ * @param {YType} parent
1544
+ * @param {Item?} referenceItem
1545
+ * @param {Array<YValue>} content
1546
+ *
1547
+ * @private
1548
+ * @function
1549
+ */
1550
+ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem, content) => {
1551
+ let left = referenceItem
1552
+ const doc = transaction.doc
1553
+ const ownClientId = doc.clientID
1554
+ const store = doc.store
1555
+ const right = referenceItem === null ? parent._start : referenceItem.right
1556
+ /**
1557
+ * @type {Array<Object|Array<any>|number|null>}
1558
+ */
1559
+ let jsonContent = []
1560
+ const packJsonContent = () => {
1561
+ if (jsonContent.length > 0) {
1562
+ left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentAny(jsonContent))
1563
+ left.integrate(transaction, 0)
1564
+ jsonContent = []
1565
+ }
1566
+ }
1567
+ content.forEach(c => {
1568
+ if (c === null) {
1569
+ jsonContent.push(c)
1570
+ } else {
1571
+ switch (c.constructor) {
1572
+ case Number:
1573
+ case Object:
1574
+ case undefined:
1575
+ case Boolean:
1576
+ case Array:
1577
+ case String:
1578
+ case BigInt:
1579
+ case Date:
1580
+ jsonContent.push(c)
1581
+ break
1582
+ default:
1583
+ packJsonContent()
1584
+ switch (c.constructor) {
1585
+ case Uint8Array:
1586
+ case ArrayBuffer:
1587
+ left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
1588
+ left.integrate(transaction, 0)
1589
+ break
1590
+ case Doc:
1591
+ left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
1592
+ left.integrate(transaction, 0)
1593
+ break
1594
+ default:
1595
+ if (c instanceof YType) {
1596
+ left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(/** @type {any} */ (c)))
1597
+ left.integrate(transaction, 0)
1598
+ } else {
1599
+ throw new Error('Unexpected content type in insert operation')
1600
+ }
1601
+ }
1602
+ }
1603
+ }
1604
+ })
1605
+ packJsonContent()
1606
+ }
1607
+
1608
+ const lengthExceeded = () => error.create('Length exceeded!')
1609
+
1610
+ /**
1611
+ * @param {Transaction} transaction
1612
+ * @param {YType} parent
1613
+ * @param {number} index
1614
+ * @param {Array<Object<string,any>|Array<any>|number|null|string|Uint8Array>} content
1615
+ *
1616
+ * @private
1617
+ * @function
1618
+ */
1619
+ export const typeListInsertGenerics = (transaction, parent, index, content) => {
1620
+ if (index > parent._length) {
1621
+ throw lengthExceeded()
1622
+ }
1623
+ if (index === 0) {
1624
+ if (parent._searchMarker) {
1625
+ updateMarkerChanges(parent._searchMarker, index, content.length)
1626
+ }
1627
+ return typeListInsertGenericsAfter(transaction, parent, null, content)
1628
+ }
1629
+ const startIndex = index
1630
+ const marker = findMarker(parent, index)
1631
+ let n = parent._start
1632
+ if (marker !== null) {
1633
+ n = marker.p
1634
+ index -= marker.index
1635
+ // we need to iterate one to the left so that the algorithm works
1636
+ if (index === 0) {
1637
+ // @todo refactor this as it actually doesn't consider formats
1638
+ n = n.prev // important! get the left undeleted item so that we can actually decrease index
1639
+ index += (n && n.countable && !n.deleted) ? n.length : 0
1640
+ }
1641
+ }
1642
+ for (; n !== null; n = n.right) {
1643
+ if (!n.deleted && n.countable) {
1644
+ if (index <= n.length) {
1645
+ if (index < n.length) {
1646
+ // insert in-between
1647
+ getItemCleanStart(transaction, createID(n.id.client, n.id.clock + index))
1648
+ }
1649
+ break
1650
+ }
1651
+ index -= n.length
1652
+ }
1653
+ }
1654
+ if (parent._searchMarker) {
1655
+ updateMarkerChanges(parent._searchMarker, startIndex, content.length)
1656
+ }
1657
+ return typeListInsertGenericsAfter(transaction, parent, n, content)
1658
+ }
1659
+
1660
+ /**
1661
+ * Pushing content is special as we generally want to push after the last item. So we don't have to update
1662
+ * the search marker.
1663
+ *
1664
+ * @param {Transaction} transaction
1665
+ * @param {YType} parent
1666
+ * @param {Array<Object<string,any>|Array<any>|number|null|string|Uint8Array>} content
1667
+ *
1668
+ * @private
1669
+ * @function
1670
+ */
1671
+ export const typeListPushGenerics = (transaction, parent, content) => {
1672
+ // Use the marker with the highest index and iterate to the right.
1673
+ const marker = (parent._searchMarker || []).reduce((maxMarker, currMarker) => currMarker.index > maxMarker.index ? currMarker : maxMarker, { index: 0, p: parent._start })
1674
+ let n = marker.p
1675
+ if (n) {
1676
+ while (n.right) {
1677
+ n = n.right
1678
+ }
1679
+ }
1680
+ return typeListInsertGenericsAfter(transaction, parent, n, content)
1681
+ }
1682
+
1683
+ /**
1684
+ * @param {Transaction} transaction
1685
+ * @param {YType} parent
1686
+ * @param {number} index
1687
+ * @param {number} length
1688
+ *
1689
+ * @private
1690
+ * @function
1691
+ */
1692
+ export const typeListDelete = (transaction, parent, index, length) => {
1693
+ if (length === 0) { return }
1694
+ const startIndex = index
1695
+ const startLength = length
1696
+ const marker = findMarker(parent, index)
1697
+ let n = parent._start
1698
+ if (marker !== null) {
1699
+ n = marker.p
1700
+ index -= marker.index
1701
+ }
1702
+ // compute the first item to be deleted
1703
+ for (; n !== null && index > 0; n = n.right) {
1704
+ if (!n.deleted && n.countable) {
1705
+ if (index < n.length) {
1706
+ getItemCleanStart(transaction, createID(n.id.client, n.id.clock + index))
1707
+ }
1708
+ index -= n.length
1709
+ }
1710
+ }
1711
+ // delete all items until done
1712
+ while (length > 0 && n !== null) {
1713
+ if (!n.deleted) {
1714
+ if (length < n.length) {
1715
+ getItemCleanStart(transaction, createID(n.id.client, n.id.clock + length))
1716
+ }
1717
+ n.delete(transaction)
1718
+ length -= n.length
1719
+ }
1720
+ n = n.right
1721
+ }
1722
+ if (length > 0) {
1723
+ throw lengthExceeded()
1724
+ }
1725
+ if (parent._searchMarker) {
1726
+ updateMarkerChanges(parent._searchMarker, startIndex, -startLength + length /* in case we remove the above exception */)
1727
+ }
1728
+ }
1729
+
1730
+ /**
1731
+ * @todo inline this code
1732
+ *
1733
+ * @param {Transaction} transaction
1734
+ * @param {YType} parent
1735
+ * @param {string} key
1736
+ *
1737
+ * @private
1738
+ * @function
1739
+ */
1740
+ export const typeMapDelete = (transaction, parent, key) => {
1741
+ const c = parent._map.get(key)
1742
+ if (c !== undefined) {
1743
+ c.delete(transaction)
1744
+ }
1745
+ }
1746
+
1747
+ /**
1748
+ * @param {Transaction} transaction
1749
+ * @param {YType} parent
1750
+ * @param {string} key
1751
+ * @param {YValue} value
1752
+ *
1753
+ * @private
1754
+ * @function
1755
+ */
1756
+ export const typeMapSet = (transaction, parent, key, value) => {
1757
+ const left = parent._map.get(key) || null
1758
+ const doc = transaction.doc
1759
+ const ownClientId = doc.clientID
1760
+ let content
1761
+ if (value == null) {
1762
+ content = new ContentAny([value])
1763
+ } else {
1764
+ switch (value.constructor) {
1765
+ case Number:
1766
+ case Object:
1767
+ case Boolean:
1768
+ case Array:
1769
+ case String:
1770
+ case Date:
1771
+ case BigInt:
1772
+ content = new ContentAny([value])
1773
+ break
1774
+ case Uint8Array:
1775
+ content = new ContentBinary(/** @type {Uint8Array} */ (value))
1776
+ break
1777
+ case Doc:
1778
+ content = new ContentDoc(/** @type {Doc} */ (value))
1779
+ break
1780
+ default:
1781
+ if (value instanceof YType) {
1782
+ content = new ContentType(/** @type {any} */ (value))
1783
+ } else {
1784
+ throw new Error('Unexpected content type')
1785
+ }
1786
+ }
1787
+ }
1788
+ new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, null, null, parent, key, content).integrate(transaction, 0)
1789
+ }
1790
+
1791
+ /**
1792
+ * @param {YType<any>} parent
1793
+ * @param {string} key
1794
+ * @return {Object<string,any>|number|null|Array<any>|string|Uint8Array|YType<any>|undefined}
1795
+ *
1796
+ * @private
1797
+ * @function
1798
+ */
1799
+ export const typeMapGet = (parent, key) => {
1800
+ parent.doc ?? warnPrematureAccess()
1801
+ const val = parent._map.get(key)
1802
+ return val !== undefined && !val.deleted ? val.content.getContent()[val.length - 1] : undefined
1803
+ }
1804
+
1805
+ /**
1806
+ * @param {YType<any>} parent
1807
+ * @return {Object<string,Object<string,any>|number|null|Array<any>|string|Uint8Array|YType<any>|undefined>}
1808
+ *
1809
+ * @private
1810
+ * @function
1811
+ */
1812
+ export const typeMapGetAll = (parent) => {
1813
+ /**
1814
+ * @type {Object<string,any>}
1815
+ */
1816
+ const res = {}
1817
+ parent.doc ?? warnPrematureAccess()
1818
+ parent._map.forEach((value, key) => {
1819
+ if (!value.deleted) {
1820
+ res[key] = value.content.getContent()[value.length - 1]
1821
+ }
1822
+ })
1823
+ return res
1824
+ }
1825
+
1826
+ /**
1827
+ * @todo move this to getContent/getDelta
1828
+ *
1829
+ * Render the difference to another ydoc (which can be empty) and highlight the differences with
1830
+ * attributions.
1831
+ *
1832
+ * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
1833
+ * attribution `{ isDeleted: true, .. }`.
1834
+ *
1835
+ * @template {delta.DeltaBuilderAny} TypeDelta
1836
+ * @param {TypeDelta} d
1837
+ * @param {YType} parent
1838
+ * @param {Set<string|null>?} attrsToRender
1839
+ * @param {import('./internals.js').AbstractAttributionManager} am
1840
+ * @param {boolean} deep
1841
+ * @param {Set<YType>|Map<YType,any>|null} [modified] - set of types that should be rendered as modified children
1842
+ * @param {import('./utils/IdSet.js').IdSet?} [deletedItems]
1843
+ * @param {import('./utils/IdSet.js').IdSet?} [itemsToRender]
1844
+ * @param {any} [opts]
1845
+ * @param {any} [optsAll]
1846
+ *
1847
+ * @private
1848
+ * @function
1849
+ */
1850
+ export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, deletedItems, itemsToRender, opts, optsAll) => {
1851
+ // @todo support modified ops!
1852
+ /**
1853
+ * @param {Item} item
1854
+ * @param {string} key
1855
+ */
1856
+ const renderAttrs = (item, key) => {
1857
+ /**
1858
+ * @type {Array<import('./internals.js').AttributedContent<any>>}
1859
+ */
1860
+ const cs = []
1861
+ am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
1862
+ const { deleted, attrs, content, render } = cs[cs.length - 1]
1863
+ if (!render) return
1864
+ const attribution = createAttributionFromAttributionItems(attrs, deleted)
1865
+ let c = array.last(content.getContent())
1866
+ if (deleted) {
1867
+ if (itemsToRender == null || itemsToRender.hasId(item.lastId)) {
1868
+ d.deleteAttr(key, attribution, c)
1869
+ }
1870
+ } else if (deep && c instanceof YType && modified?.has(c)) {
1871
+ d.modifyAttr(key, c.toDelta(am, opts))
1872
+ } else {
1873
+ // find prev content
1874
+ let prevContentItem = item
1875
+ // this algorithm is problematic. should check all previous content using am.readcontent
1876
+ for (; prevContentItem.left !== null && deletedItems?.hasId(prevContentItem.left.lastId); prevContentItem = prevContentItem.left) {
1877
+ // nop
1878
+ }
1879
+ const prevValue = (prevContentItem !== item && itemsToRender?.hasId(prevContentItem.lastId)) ? array.last(prevContentItem.content.getContent()) : undefined
1880
+ if (deep && c instanceof YType) {
1881
+ c = /** @type {any} */(c).toDelta(am, optsAll)
1882
+ }
1883
+ d.setAttr(key, c, attribution, prevValue)
1884
+ }
1885
+ }
1886
+ if (attrsToRender == null) {
1887
+ parent._map.forEach(renderAttrs)
1888
+ } else {
1889
+ attrsToRender.forEach(key => key != null && renderAttrs(/** @type {Item} */ (parent._map.get(key)), key))
1890
+ }
1891
+ }
1892
+
1893
+ /**
1894
+ * @param {YType<any>} parent
1895
+ * @param {string} key
1896
+ * @return {boolean}
1897
+ *
1898
+ * @private
1899
+ * @function
1900
+ */
1901
+ export const typeMapHas = (parent, key) => {
1902
+ parent.doc ?? warnPrematureAccess()
1903
+ const val = parent._map.get(key)
1904
+ return val !== undefined && !val.deleted
1905
+ }
1906
+
1907
+ /**
1908
+ * @param {YType<any>} parent
1909
+ * @param {string} key
1910
+ * @param {Snapshot} snapshot
1911
+ * @return {Object<string,any>|number|null|Array<any>|string|Uint8Array|YType<any>|undefined}
1912
+ *
1913
+ * @private
1914
+ * @function
1915
+ */
1916
+ export const typeMapGetSnapshot = (parent, key, snapshot) => {
1917
+ let v = parent._map.get(key) || null
1918
+ while (v !== null && (!snapshot.sv.has(v.id.client) || v.id.clock >= (snapshot.sv.get(v.id.client) || 0))) {
1919
+ v = v.left
1920
+ }
1921
+ return v !== null && isVisible(v, snapshot) ? v.content.getContent()[v.length - 1] : undefined
1922
+ }
1923
+
1924
+ /**
1925
+ * @param {YType<any>} parent
1926
+ * @param {Snapshot} snapshot
1927
+ * @return {Object<string,Object<string,any>|number|null|Array<any>|string|Uint8Array|YType<any>|undefined>}
1928
+ *
1929
+ * @private
1930
+ * @function
1931
+ */
1932
+ export const typeMapGetAllSnapshot = (parent, snapshot) => {
1933
+ /**
1934
+ * @type {Object<string,any>}
1935
+ */
1936
+ const res = {}
1937
+ parent._map.forEach((value, key) => {
1938
+ /**
1939
+ * @type {Item|null}
1940
+ */
1941
+ let v = value
1942
+ while (v !== null && (!snapshot.sv.has(v.id.client) || v.id.clock >= (snapshot.sv.get(v.id.client) || 0))) {
1943
+ v = v.left
1944
+ }
1945
+ if (v !== null && isVisible(v, snapshot)) {
1946
+ res[key] = v.content.getContent()[v.length - 1]
1947
+ }
1948
+ })
1949
+ return res
1950
+ }
1951
+
1952
+ /**
1953
+ * @param {YType<any> & { _map: Map<string, Item> }} type
1954
+ * @return {IterableIterator<Array<any>>}
1955
+ *
1956
+ * @private
1957
+ * @function
1958
+ */
1959
+ export const createMapIterator = type => {
1960
+ type.doc ?? warnPrematureAccess()
1961
+ return iterator.iteratorFilter(type._map.entries(), /** @param {any} entry */ entry => !entry[1].deleted)
1962
+ }