@y/y 14.0.0-19 → 14.0.0-21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/dist/src/index.d.ts +2 -1
- package/dist/src/internals.d.ts +2 -9
- package/dist/src/structs/ContentType.d.ts +6 -12
- package/dist/src/structs/ContentType.d.ts.map +1 -1
- package/dist/src/structs/Item.d.ts +5 -6
- package/dist/src/structs/Item.d.ts.map +1 -1
- package/dist/src/utils/AttributionManager.d.ts +16 -14
- package/dist/src/utils/AttributionManager.d.ts.map +1 -1
- package/dist/src/utils/Doc.d.ts +7 -70
- package/dist/src/utils/Doc.d.ts.map +1 -1
- package/dist/src/utils/ID.d.ts +2 -2
- package/dist/src/utils/ID.d.ts.map +1 -1
- package/dist/src/utils/IdMap.d.ts +22 -19
- package/dist/src/utils/IdMap.d.ts.map +1 -1
- package/dist/src/utils/IdSet.d.ts +6 -6
- package/dist/src/utils/IdSet.d.ts.map +1 -1
- package/dist/src/utils/RelativePosition.d.ts +8 -8
- package/dist/src/utils/RelativePosition.d.ts.map +1 -1
- package/dist/src/utils/Snapshot.d.ts +3 -3
- package/dist/src/utils/Snapshot.d.ts.map +1 -1
- package/dist/src/utils/Transaction.d.ts +9 -5
- package/dist/src/utils/Transaction.d.ts.map +1 -1
- package/dist/src/utils/UndoManager.d.ts +14 -12
- package/dist/src/utils/UndoManager.d.ts.map +1 -1
- package/dist/src/utils/UpdateDecoder.d.ts +8 -4
- package/dist/src/utils/UpdateDecoder.d.ts.map +1 -1
- package/dist/src/utils/UpdateEncoder.d.ts +2 -0
- package/dist/src/utils/UpdateEncoder.d.ts.map +1 -1
- package/dist/src/utils/YEvent.d.ts +21 -42
- package/dist/src/utils/YEvent.d.ts.map +1 -1
- package/dist/src/utils/encoding.d.ts +3 -3
- package/dist/src/utils/encoding.d.ts.map +1 -1
- package/dist/src/utils/isParentOf.d.ts +1 -1
- package/dist/src/utils/isParentOf.d.ts.map +1 -1
- package/dist/src/utils/logging.d.ts +2 -2
- package/dist/src/utils/logging.d.ts.map +1 -1
- package/dist/src/utils/meta.d.ts +71 -0
- package/dist/src/utils/meta.d.ts.map +1 -0
- package/dist/src/utils/ts.d.ts +4 -0
- package/dist/src/utils/ts.d.ts.map +1 -0
- package/dist/src/utils/updates.d.ts +11 -11
- package/dist/src/utils/updates.d.ts.map +1 -1
- package/dist/src/ytype.d.ts +498 -0
- package/dist/src/ytype.d.ts.map +1 -0
- package/dist/tests/IdMap.tests.d.ts.map +1 -1
- package/dist/tests/attribution.tests.d.ts +1 -0
- package/dist/tests/attribution.tests.d.ts.map +1 -1
- package/dist/tests/compatibility.tests.d.ts.map +1 -1
- package/dist/tests/doc.tests.d.ts.map +1 -1
- package/dist/tests/relativePositions.tests.d.ts +9 -9
- package/dist/tests/relativePositions.tests.d.ts.map +1 -1
- package/dist/tests/snapshot.tests.d.ts.map +1 -1
- package/dist/tests/testHelper.d.ts +28 -27
- package/dist/tests/testHelper.d.ts.map +1 -1
- package/dist/tests/undo-redo.tests.d.ts.map +1 -1
- package/dist/tests/updates.tests.d.ts +2 -1
- package/dist/tests/updates.tests.d.ts.map +1 -1
- package/dist/tests/y-array.tests.d.ts +0 -2
- package/dist/tests/y-array.tests.d.ts.map +1 -1
- package/dist/tests/y-map.tests.d.ts +0 -3
- package/dist/tests/y-map.tests.d.ts.map +1 -1
- package/dist/tests/y-text.tests.d.ts +1 -1
- package/dist/tests/y-text.tests.d.ts.map +1 -1
- package/dist/tests/y-xml.tests.d.ts +0 -1
- package/dist/tests/y-xml.tests.d.ts.map +1 -1
- package/package.json +16 -16
- package/src/index.js +156 -0
- package/src/internals.js +35 -0
- package/src/structs/AbstractStruct.js +59 -0
- package/src/structs/ContentAny.js +115 -0
- package/src/structs/ContentBinary.js +93 -0
- package/src/structs/ContentDeleted.js +101 -0
- package/src/structs/ContentDoc.js +141 -0
- package/src/structs/ContentEmbed.js +98 -0
- package/src/structs/ContentFormat.js +105 -0
- package/src/structs/ContentJSON.js +119 -0
- package/src/structs/ContentString.js +113 -0
- package/src/structs/ContentType.js +152 -0
- package/src/structs/GC.js +80 -0
- package/src/structs/Item.js +841 -0
- package/src/structs/Skip.js +75 -0
- package/src/utils/AttributionManager.js +653 -0
- package/src/utils/Doc.js +266 -0
- package/src/utils/EventHandler.js +87 -0
- package/src/utils/ID.js +89 -0
- package/src/utils/IdMap.js +673 -0
- package/src/utils/IdSet.js +825 -0
- package/src/utils/RelativePosition.js +352 -0
- package/src/utils/Snapshot.js +220 -0
- package/src/utils/StructSet.js +137 -0
- package/src/utils/StructStore.js +289 -0
- package/src/utils/Transaction.js +671 -0
- package/src/utils/UndoManager.js +406 -0
- package/src/utils/UpdateDecoder.js +285 -0
- package/src/utils/UpdateEncoder.js +327 -0
- package/src/utils/YEvent.js +189 -0
- package/src/utils/delta-helpers.js +54 -0
- package/src/utils/encoding.js +623 -0
- package/src/utils/isParentOf.js +21 -0
- package/src/utils/logging.js +21 -0
- package/src/utils/meta.js +190 -0
- package/src/utils/ts.js +3 -0
- package/src/utils/updates.js +802 -0
- package/src/ytype.js +1962 -0
- package/dist/Skip-CE05BUF8.js +0 -11875
- package/dist/Skip-CE05BUF8.js.map +0 -1
- package/dist/index-C21sDQ5u.js +0 -163
- package/dist/index-C21sDQ5u.js.map +0 -1
- package/dist/internals.js +0 -25
- package/dist/internals.js.map +0 -1
- package/dist/src/types/AbstractType.d.ts +0 -239
- package/dist/src/types/AbstractType.d.ts.map +0 -1
- package/dist/src/types/YArray.d.ts +0 -128
- package/dist/src/types/YArray.d.ts.map +0 -1
- package/dist/src/types/YMap.d.ts +0 -112
- package/dist/src/types/YMap.d.ts.map +0 -1
- package/dist/src/types/YText.d.ts +0 -216
- package/dist/src/types/YText.d.ts.map +0 -1
- package/dist/src/types/YXmlElement.d.ts +0 -106
- package/dist/src/types/YXmlElement.d.ts.map +0 -1
- package/dist/src/types/YXmlFragment.d.ts +0 -143
- package/dist/src/types/YXmlFragment.d.ts.map +0 -1
- package/dist/src/types/YXmlHook.d.ts +0 -32
- package/dist/src/types/YXmlHook.d.ts.map +0 -1
- package/dist/src/types/YXmlText.d.ts +0 -34
- package/dist/src/types/YXmlText.d.ts.map +0 -1
- package/dist/src/utils/AbstractConnector.d.ts +0 -20
- package/dist/src/utils/AbstractConnector.d.ts.map +0 -1
- package/dist/src/utils/types.d.ts +0 -7
- package/dist/src/utils/types.d.ts.map +0 -1
- package/dist/testHelper.js +0 -617
- package/dist/testHelper.js.map +0 -1
- package/dist/yjs.js +0 -26
- 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
|
+
}
|