@y/y 14.0.0-16
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/LICENSE +23 -0
- package/README.md +1406 -0
- package/dist/Skip-j0kX7pdq.js +12173 -0
- package/dist/Skip-j0kX7pdq.js.map +1 -0
- package/dist/Skip-wRT7BKFP.js +11877 -0
- package/dist/Skip-wRT7BKFP.js.map +1 -0
- package/dist/index-DyTeTfmj.js +163 -0
- package/dist/index-DyTeTfmj.js.map +1 -0
- package/dist/index-R7GxO-36.js +165 -0
- package/dist/index-R7GxO-36.js.map +1 -0
- package/dist/internals.cjs +286 -0
- package/dist/internals.cjs.map +1 -0
- package/dist/internals.mjs +25 -0
- package/dist/internals.mjs.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/internals.d.ts +43 -0
- package/dist/src/internals.d.ts.map +1 -0
- package/dist/src/structs/AbstractStruct.d.ts +42 -0
- package/dist/src/structs/AbstractStruct.d.ts.map +1 -0
- package/dist/src/structs/ContentAny.d.ts +67 -0
- package/dist/src/structs/ContentAny.d.ts.map +1 -0
- package/dist/src/structs/ContentBinary.d.ts +64 -0
- package/dist/src/structs/ContentBinary.d.ts.map +1 -0
- package/dist/src/structs/ContentDeleted.d.ts +64 -0
- package/dist/src/structs/ContentDeleted.d.ts.map +1 -0
- package/dist/src/structs/ContentDoc.d.ts +72 -0
- package/dist/src/structs/ContentDoc.d.ts.map +1 -0
- package/dist/src/structs/ContentEmbed.d.ts +67 -0
- package/dist/src/structs/ContentEmbed.d.ts.map +1 -0
- package/dist/src/structs/ContentFormat.d.ts +69 -0
- package/dist/src/structs/ContentFormat.d.ts.map +1 -0
- package/dist/src/structs/ContentJSON.d.ts +70 -0
- package/dist/src/structs/ContentJSON.d.ts.map +1 -0
- package/dist/src/structs/ContentString.d.ts +70 -0
- package/dist/src/structs/ContentString.d.ts.map +1 -0
- package/dist/src/structs/ContentType.d.ts +83 -0
- package/dist/src/structs/ContentType.d.ts.map +1 -0
- package/dist/src/structs/GC.d.ts +31 -0
- package/dist/src/structs/GC.d.ts.map +1 -0
- package/dist/src/structs/Item.d.ts +212 -0
- package/dist/src/structs/Item.d.ts.map +1 -0
- package/dist/src/structs/Skip.d.ts +33 -0
- package/dist/src/structs/Skip.d.ts.map +1 -0
- package/dist/src/types/AbstractType.d.ts +239 -0
- package/dist/src/types/AbstractType.d.ts.map +1 -0
- package/dist/src/types/YArray.d.ts +128 -0
- package/dist/src/types/YArray.d.ts.map +1 -0
- package/dist/src/types/YMap.d.ts +112 -0
- package/dist/src/types/YMap.d.ts.map +1 -0
- package/dist/src/types/YText.d.ts +216 -0
- package/dist/src/types/YText.d.ts.map +1 -0
- package/dist/src/types/YXmlElement.d.ts +106 -0
- package/dist/src/types/YXmlElement.d.ts.map +1 -0
- package/dist/src/types/YXmlFragment.d.ts +143 -0
- package/dist/src/types/YXmlFragment.d.ts.map +1 -0
- package/dist/src/types/YXmlHook.d.ts +32 -0
- package/dist/src/types/YXmlHook.d.ts.map +1 -0
- package/dist/src/types/YXmlText.d.ts +34 -0
- package/dist/src/types/YXmlText.d.ts.map +1 -0
- package/dist/src/utils/AbstractConnector.d.ts +20 -0
- package/dist/src/utils/AbstractConnector.d.ts.map +1 -0
- package/dist/src/utils/AttributionManager.d.ts +224 -0
- package/dist/src/utils/AttributionManager.d.ts.map +1 -0
- package/dist/src/utils/Doc.d.ts +267 -0
- package/dist/src/utils/Doc.d.ts.map +1 -0
- package/dist/src/utils/EventHandler.d.ts +19 -0
- package/dist/src/utils/EventHandler.d.ts.map +1 -0
- package/dist/src/utils/ID.d.ts +26 -0
- package/dist/src/utils/ID.d.ts.map +1 -0
- package/dist/src/utils/IdMap.d.ts +161 -0
- package/dist/src/utils/IdMap.d.ts.map +1 -0
- package/dist/src/utils/IdSet.d.ts +163 -0
- package/dist/src/utils/IdSet.d.ts.map +1 -0
- package/dist/src/utils/RelativePosition.d.ts +91 -0
- package/dist/src/utils/RelativePosition.d.ts.map +1 -0
- package/dist/src/utils/Snapshot.d.ts +40 -0
- package/dist/src/utils/Snapshot.d.ts.map +1 -0
- package/dist/src/utils/StructSet.d.ts +27 -0
- package/dist/src/utils/StructSet.d.ts.map +1 -0
- package/dist/src/utils/StructStore.d.ts +41 -0
- package/dist/src/utils/StructStore.d.ts.map +1 -0
- package/dist/src/utils/Transaction.d.ts +136 -0
- package/dist/src/utils/Transaction.d.ts.map +1 -0
- package/dist/src/utils/UndoManager.d.ts +188 -0
- package/dist/src/utils/UndoManager.d.ts.map +1 -0
- package/dist/src/utils/UpdateDecoder.d.ts +167 -0
- package/dist/src/utils/UpdateDecoder.d.ts.map +1 -0
- package/dist/src/utils/UpdateEncoder.d.ts +164 -0
- package/dist/src/utils/UpdateEncoder.d.ts.map +1 -0
- package/dist/src/utils/YEvent.d.ts +120 -0
- package/dist/src/utils/YEvent.d.ts.map +1 -0
- package/dist/src/utils/delta-helpers.d.ts +6 -0
- package/dist/src/utils/delta-helpers.d.ts.map +1 -0
- package/dist/src/utils/encoding.d.ts +30 -0
- package/dist/src/utils/encoding.d.ts.map +1 -0
- package/dist/src/utils/isParentOf.d.ts +3 -0
- package/dist/src/utils/isParentOf.d.ts.map +1 -0
- package/dist/src/utils/logging.d.ts +3 -0
- package/dist/src/utils/logging.d.ts.map +1 -0
- package/dist/src/utils/types.d.ts +7 -0
- package/dist/src/utils/types.d.ts.map +1 -0
- package/dist/src/utils/updates.d.ts +89 -0
- package/dist/src/utils/updates.d.ts.map +1 -0
- package/dist/testHelper.cjs +780 -0
- package/dist/testHelper.cjs.map +1 -0
- package/dist/testHelper.mjs +617 -0
- package/dist/testHelper.mjs.map +1 -0
- package/dist/tests/IdMap.tests.d.ts +9 -0
- package/dist/tests/IdMap.tests.d.ts.map +1 -0
- package/dist/tests/IdSet.tests.d.ts +9 -0
- package/dist/tests/IdSet.tests.d.ts.map +1 -0
- package/dist/tests/attribution.tests.d.ts +8 -0
- package/dist/tests/attribution.tests.d.ts.map +1 -0
- package/dist/tests/compatibility.tests.d.ts +5 -0
- package/dist/tests/compatibility.tests.d.ts.map +1 -0
- package/dist/tests/delta.tests.d.ts +7 -0
- package/dist/tests/delta.tests.d.ts.map +1 -0
- package/dist/tests/doc.tests.d.ts +13 -0
- package/dist/tests/doc.tests.d.ts.map +1 -0
- package/dist/tests/encoding.tests.d.ts +5 -0
- package/dist/tests/encoding.tests.d.ts.map +1 -0
- package/dist/tests/index.d.ts +2 -0
- package/dist/tests/index.d.ts.map +1 -0
- package/dist/tests/relativePositions.tests.d.ts +11 -0
- package/dist/tests/relativePositions.tests.d.ts.map +1 -0
- package/dist/tests/snapshot.tests.d.ts +13 -0
- package/dist/tests/snapshot.tests.d.ts.map +1 -0
- package/dist/tests/testHelper.d.ts +167 -0
- package/dist/tests/testHelper.d.ts.map +1 -0
- package/dist/tests/undo-redo.tests.d.ts +27 -0
- package/dist/tests/undo-redo.tests.d.ts.map +1 -0
- package/dist/tests/updates.tests.d.ts +24 -0
- package/dist/tests/updates.tests.d.ts.map +1 -0
- package/dist/tests/y-array.tests.d.ts +45 -0
- package/dist/tests/y-array.tests.d.ts.map +1 -0
- package/dist/tests/y-map.tests.d.ts +45 -0
- package/dist/tests/y-map.tests.d.ts.map +1 -0
- package/dist/tests/y-text.tests.d.ts +49 -0
- package/dist/tests/y-text.tests.d.ts.map +1 -0
- package/dist/tests/y-xml.tests.d.ts +15 -0
- package/dist/tests/y-xml.tests.d.ts.map +1 -0
- package/dist/yjs.cjs +151 -0
- package/dist/yjs.cjs.map +1 -0
- package/dist/yjs.mjs +26 -0
- package/dist/yjs.mjs.map +1 -0
- package/package.json +101 -0
- package/src/index.js +153 -0
- package/src/internals.js +44 -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 +176 -0
- package/src/structs/GC.js +80 -0
- package/src/structs/Item.js +845 -0
- package/src/structs/Skip.js +75 -0
- package/src/types/AbstractType.js +1434 -0
- package/src/types/YArray.js +270 -0
- package/src/types/YMap.js +244 -0
- package/src/types/YText.js +934 -0
- package/src/types/YXmlElement.js +227 -0
- package/src/types/YXmlFragment.js +266 -0
- package/src/types/YXmlHook.js +68 -0
- package/src/types/YXmlText.js +66 -0
- package/src/utils/AbstractConnector.js +25 -0
- package/src/utils/AttributionManager.js +619 -0
- package/src/utils/Doc.js +372 -0
- package/src/utils/EventHandler.js +87 -0
- package/src/utils/ID.js +89 -0
- package/src/utils/IdMap.js +629 -0
- package/src/utils/IdSet.js +823 -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 +489 -0
- package/src/utils/UndoManager.js +391 -0
- package/src/utils/UpdateDecoder.js +281 -0
- package/src/utils/UpdateEncoder.js +320 -0
- package/src/utils/YEvent.js +216 -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/types.js +28 -0
- package/src/utils/updates.js +715 -0
- package/tests/testHelper.js +600 -0
|
@@ -0,0 +1,1434 @@
|
|
|
1
|
+
import {
|
|
2
|
+
removeEventHandlerListener,
|
|
3
|
+
callEventHandlerListeners,
|
|
4
|
+
addEventHandlerListener,
|
|
5
|
+
createEventHandler,
|
|
6
|
+
getState,
|
|
7
|
+
isVisible,
|
|
8
|
+
ContentType,
|
|
9
|
+
createID,
|
|
10
|
+
ContentAny,
|
|
11
|
+
ContentFormat,
|
|
12
|
+
ContentBinary,
|
|
13
|
+
ContentJSON,
|
|
14
|
+
ContentDeleted,
|
|
15
|
+
ContentString,
|
|
16
|
+
ContentEmbed,
|
|
17
|
+
getItemCleanStart,
|
|
18
|
+
noAttributionsManager,
|
|
19
|
+
transact,
|
|
20
|
+
ItemTextListPosition,
|
|
21
|
+
insertText,
|
|
22
|
+
deleteText,
|
|
23
|
+
ContentDoc, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, createAttributionFromAttributionItems, AbstractAttributionManager, YXmlElement, // eslint-disable-line
|
|
24
|
+
} from '../internals.js'
|
|
25
|
+
|
|
26
|
+
import * as delta from 'lib0/delta'
|
|
27
|
+
import * as array from 'lib0/array'
|
|
28
|
+
import * as map from 'lib0/map'
|
|
29
|
+
import * as iterator from 'lib0/iterator'
|
|
30
|
+
import * as error from 'lib0/error'
|
|
31
|
+
import * as math from 'lib0/math'
|
|
32
|
+
import * as log from 'lib0/logging'
|
|
33
|
+
import * as object from 'lib0/object'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {import('../utils/types.js').YType} YType_
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {import('../utils/types.js').YValue} _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 ArraySearchMarker {
|
|
59
|
+
/**
|
|
60
|
+
* @param {Item} p
|
|
61
|
+
* @param {number} index
|
|
62
|
+
*/
|
|
63
|
+
constructor (p, index) {
|
|
64
|
+
p.marker = true
|
|
65
|
+
this.p = p
|
|
66
|
+
this.index = index
|
|
67
|
+
this.timestamp = globalSearchMarkerTimestamp++
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {ArraySearchMarker} marker
|
|
73
|
+
*/
|
|
74
|
+
const refreshMarkerTimestamp = marker => { marker.timestamp = globalSearchMarkerTimestamp++ }
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* This is rather complex so this function is the only thing that should overwrite a marker
|
|
78
|
+
*
|
|
79
|
+
* @param {ArraySearchMarker} marker
|
|
80
|
+
* @param {Item} p
|
|
81
|
+
* @param {number} index
|
|
82
|
+
*/
|
|
83
|
+
const overwriteMarker = (marker, p, index) => {
|
|
84
|
+
marker.p.marker = false
|
|
85
|
+
marker.p = p
|
|
86
|
+
p.marker = true
|
|
87
|
+
marker.index = index
|
|
88
|
+
marker.timestamp = globalSearchMarkerTimestamp++
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {Array<ArraySearchMarker>} searchMarker
|
|
93
|
+
* @param {Item} p
|
|
94
|
+
* @param {number} index
|
|
95
|
+
*/
|
|
96
|
+
const markPosition = (searchMarker, p, index) => {
|
|
97
|
+
if (searchMarker.length >= maxSearchMarker) {
|
|
98
|
+
// override oldest marker (we don't want to create more objects)
|
|
99
|
+
const marker = searchMarker.reduce((a, b) => a.timestamp < b.timestamp ? a : b)
|
|
100
|
+
overwriteMarker(marker, p, index)
|
|
101
|
+
return marker
|
|
102
|
+
} else {
|
|
103
|
+
// create new marker
|
|
104
|
+
const pm = new ArraySearchMarker(p, index)
|
|
105
|
+
searchMarker.push(pm)
|
|
106
|
+
return pm
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Search marker help us to find positions in the associative array faster.
|
|
112
|
+
*
|
|
113
|
+
* They speed up the process of finding a position without much bookkeeping.
|
|
114
|
+
*
|
|
115
|
+
* A maximum of `maxSearchMarker` objects are created.
|
|
116
|
+
*
|
|
117
|
+
* This function always returns a refreshed marker (updated timestamp)
|
|
118
|
+
*
|
|
119
|
+
* @param {import('../utils/types.js').YType} yarray
|
|
120
|
+
* @param {number} index
|
|
121
|
+
*/
|
|
122
|
+
export const findMarker = (yarray, index) => {
|
|
123
|
+
if (yarray._start === null || index === 0 || yarray._searchMarker === null) {
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
const marker = yarray._searchMarker.length === 0 ? null : yarray._searchMarker.reduce((a, b) => math.abs(index - a.index) < math.abs(index - b.index) ? a : b)
|
|
127
|
+
let p = yarray._start
|
|
128
|
+
let pindex = 0
|
|
129
|
+
if (marker !== null) {
|
|
130
|
+
p = marker.p
|
|
131
|
+
pindex = marker.index
|
|
132
|
+
refreshMarkerTimestamp(marker) // we used it, we might need to use it again
|
|
133
|
+
}
|
|
134
|
+
// iterate to right if possible
|
|
135
|
+
while (p.right !== null && pindex < index) {
|
|
136
|
+
if (!p.deleted && p.countable) {
|
|
137
|
+
if (index < pindex + p.length) {
|
|
138
|
+
break
|
|
139
|
+
}
|
|
140
|
+
pindex += p.length
|
|
141
|
+
}
|
|
142
|
+
p = p.right
|
|
143
|
+
}
|
|
144
|
+
// iterate to left if necessary (might be that pindex > index)
|
|
145
|
+
while (p.left !== null && pindex > index) {
|
|
146
|
+
p = p.left
|
|
147
|
+
if (!p.deleted && p.countable) {
|
|
148
|
+
pindex -= p.length
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// we want to make sure that p can't be merged with left, because that would screw up everything
|
|
152
|
+
// in that cas just return what we have (it is most likely the best marker anyway)
|
|
153
|
+
// iterate to left until p can't be merged with left
|
|
154
|
+
while (p.left !== null && p.left.id.client === p.id.client && p.left.id.clock + p.left.length === p.id.clock) {
|
|
155
|
+
p = p.left
|
|
156
|
+
if (!p.deleted && p.countable) {
|
|
157
|
+
pindex -= p.length
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// @todo remove!
|
|
162
|
+
// assure position
|
|
163
|
+
// {
|
|
164
|
+
// let start = yarray._start
|
|
165
|
+
// let pos = 0
|
|
166
|
+
// while (start !== p) {
|
|
167
|
+
// if (!start.deleted && start.countable) {
|
|
168
|
+
// pos += start.length
|
|
169
|
+
// }
|
|
170
|
+
// start = /** @type {Item} */ (start.right)
|
|
171
|
+
// }
|
|
172
|
+
// if (pos !== pindex) {
|
|
173
|
+
// debugger
|
|
174
|
+
// throw new Error('Gotcha position fail!')
|
|
175
|
+
// }
|
|
176
|
+
// }
|
|
177
|
+
// if (marker) {
|
|
178
|
+
// if (window.lengths == null) {
|
|
179
|
+
// window.lengths = []
|
|
180
|
+
// window.getLengths = () => window.lengths.sort((a, b) => a - b)
|
|
181
|
+
// }
|
|
182
|
+
// window.lengths.push(marker.index - pindex)
|
|
183
|
+
// console.log('distance', marker.index - pindex, 'len', p && p.parent.length)
|
|
184
|
+
// }
|
|
185
|
+
if (marker !== null && math.abs(marker.index - pindex) < /** @type {any} */ (p.parent).length / maxSearchMarker) {
|
|
186
|
+
// adjust existing marker
|
|
187
|
+
overwriteMarker(marker, p, pindex)
|
|
188
|
+
return marker
|
|
189
|
+
} else {
|
|
190
|
+
// create new marker
|
|
191
|
+
return markPosition(yarray._searchMarker, p, pindex)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Update markers when a change happened.
|
|
197
|
+
*
|
|
198
|
+
* This should be called before doing a deletion!
|
|
199
|
+
*
|
|
200
|
+
* @param {Array<ArraySearchMarker>} searchMarker
|
|
201
|
+
* @param {number} index
|
|
202
|
+
* @param {number} len If insertion, len is positive. If deletion, len is negative.
|
|
203
|
+
*/
|
|
204
|
+
export const updateMarkerChanges = (searchMarker, index, len) => {
|
|
205
|
+
for (let i = searchMarker.length - 1; i >= 0; i--) {
|
|
206
|
+
const m = searchMarker[i]
|
|
207
|
+
if (len > 0) {
|
|
208
|
+
/**
|
|
209
|
+
* @type {Item|null}
|
|
210
|
+
*/
|
|
211
|
+
let p = m.p
|
|
212
|
+
p.marker = false
|
|
213
|
+
// Ideally we just want to do a simple position comparison, but this will only work if
|
|
214
|
+
// search markers don't point to deleted items for formats.
|
|
215
|
+
// Iterate marker to prev undeleted countable position so we know what to do when updating a position
|
|
216
|
+
while (p && (p.deleted || !p.countable)) {
|
|
217
|
+
p = p.left
|
|
218
|
+
if (p && !p.deleted && p.countable) {
|
|
219
|
+
// adjust position. the loop should break now
|
|
220
|
+
m.index -= p.length
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (p === null || p.marker === true) {
|
|
224
|
+
// remove search marker if updated position is null or if position is already marked
|
|
225
|
+
searchMarker.splice(i, 1)
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
m.p = p
|
|
229
|
+
p.marker = true
|
|
230
|
+
}
|
|
231
|
+
if (index < m.index || (len > 0 && index === m.index)) { // a simple index <= m.index check would actually suffice
|
|
232
|
+
m.index = math.max(index, m.index + len)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Accumulate all (list) children of a type and return them as an Array.
|
|
239
|
+
*
|
|
240
|
+
* @param {import('../utils/types.js').YType} t
|
|
241
|
+
* @return {Array<Item>}
|
|
242
|
+
*/
|
|
243
|
+
export const getTypeChildren = t => {
|
|
244
|
+
t.doc ?? warnPrematureAccess()
|
|
245
|
+
let s = t._start
|
|
246
|
+
const arr = []
|
|
247
|
+
while (s) {
|
|
248
|
+
arr.push(s)
|
|
249
|
+
s = s.right
|
|
250
|
+
}
|
|
251
|
+
return arr
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Call event listeners with an event. This will also add an event to all
|
|
256
|
+
* parents (for `.observeDeep` handlers).
|
|
257
|
+
*
|
|
258
|
+
* @param {import('../utils/types.js').YType} type
|
|
259
|
+
* @param {Transaction} transaction
|
|
260
|
+
* @param {YEvent<any>} event
|
|
261
|
+
*/
|
|
262
|
+
export const callTypeObservers = (type, transaction, event) => {
|
|
263
|
+
const changedType = type
|
|
264
|
+
const changedParentTypes = transaction.changedParentTypes
|
|
265
|
+
while (true) {
|
|
266
|
+
// @ts-ignore
|
|
267
|
+
map.setIfUndefined(changedParentTypes, type, () => []).push(event)
|
|
268
|
+
if (type._item === null) {
|
|
269
|
+
break
|
|
270
|
+
}
|
|
271
|
+
type = /** @type {import('../utils/types.js').YType} */ (type._item.parent)
|
|
272
|
+
}
|
|
273
|
+
callEventHandlerListeners(/** @type {any} */ (changedType._eH), event, transaction)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Abstract Yjs Type class
|
|
278
|
+
* @template {delta.Delta<any,any,any,any,any>} [EventDelta=any]
|
|
279
|
+
* @template {AbstractType<any,any>} [Self=any]
|
|
280
|
+
*/
|
|
281
|
+
export class AbstractType {
|
|
282
|
+
constructor () {
|
|
283
|
+
/**
|
|
284
|
+
* @type {Item|null}
|
|
285
|
+
*/
|
|
286
|
+
this._item = null
|
|
287
|
+
/**
|
|
288
|
+
* @type {Map<string,Item>}
|
|
289
|
+
*/
|
|
290
|
+
this._map = new Map()
|
|
291
|
+
/**
|
|
292
|
+
* @type {Item|null}
|
|
293
|
+
*/
|
|
294
|
+
this._start = null
|
|
295
|
+
/**
|
|
296
|
+
* @type {Doc|null}
|
|
297
|
+
*/
|
|
298
|
+
this.doc = null
|
|
299
|
+
this._length = 0
|
|
300
|
+
/**
|
|
301
|
+
* Event handlers
|
|
302
|
+
* @type {EventHandler<YEvent<Self>,Transaction>}
|
|
303
|
+
*/
|
|
304
|
+
this._eH = createEventHandler()
|
|
305
|
+
/**
|
|
306
|
+
* Deep event handlers
|
|
307
|
+
* @type {EventHandler<Array<YEvent<any>>,Transaction>}
|
|
308
|
+
*/
|
|
309
|
+
this._dEH = createEventHandler()
|
|
310
|
+
/**
|
|
311
|
+
* @type {null | Array<ArraySearchMarker>}
|
|
312
|
+
*/
|
|
313
|
+
this._searchMarker = null
|
|
314
|
+
/**
|
|
315
|
+
* @type {EventDelta?}
|
|
316
|
+
*/
|
|
317
|
+
this._prelim = null
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Returns a fresh delta that can be used to change this YType.
|
|
322
|
+
* @type {EventDelta}
|
|
323
|
+
*/
|
|
324
|
+
get change () {
|
|
325
|
+
return /** @type {any} */ (delta.create())
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* @return {import('../utils/types.js').YType|null}
|
|
330
|
+
*/
|
|
331
|
+
get parent () {
|
|
332
|
+
return /** @type {import('../utils/types.js').YType} */ (this._item ? this._item.parent : null)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Integrate this type into the Yjs instance.
|
|
337
|
+
*
|
|
338
|
+
* * Save this struct in the os
|
|
339
|
+
* * This type is sent to other client
|
|
340
|
+
* * Observer functions are fired
|
|
341
|
+
*
|
|
342
|
+
* @param {Doc} y The Yjs instance
|
|
343
|
+
* @param {Item|null} item
|
|
344
|
+
*/
|
|
345
|
+
_integrate (y, item) {
|
|
346
|
+
this.doc = y
|
|
347
|
+
this._item = item
|
|
348
|
+
if (this._prelim) {
|
|
349
|
+
this.applyDelta(this._prelim)
|
|
350
|
+
this._prelim = null
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* @return {Self}
|
|
356
|
+
*/
|
|
357
|
+
_copy () {
|
|
358
|
+
// @ts-ignore
|
|
359
|
+
return new this.constructor()
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Makes a copy of this data type that can be included somewhere else.
|
|
364
|
+
*
|
|
365
|
+
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
|
|
366
|
+
*
|
|
367
|
+
* @return {Self}
|
|
368
|
+
*/
|
|
369
|
+
clone () {
|
|
370
|
+
// @todo remove this method from othern types by doing `_copy().apply(this.getContent())`
|
|
371
|
+
throw error.methodUnimplemented()
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* @param {UpdateEncoderV1 | UpdateEncoderV2} _encoder
|
|
376
|
+
*/
|
|
377
|
+
_write (_encoder) { }
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* The first non-deleted item
|
|
381
|
+
*/
|
|
382
|
+
get _first () {
|
|
383
|
+
let n = this._start
|
|
384
|
+
while (n !== null && n.deleted) {
|
|
385
|
+
n = n.right
|
|
386
|
+
}
|
|
387
|
+
return n
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Creates YEvent and calls all type observers.
|
|
392
|
+
* Must be implemented by each type.
|
|
393
|
+
*
|
|
394
|
+
* @param {Transaction} transaction
|
|
395
|
+
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
|
396
|
+
*/
|
|
397
|
+
_callObserver (transaction, parentSubs) {
|
|
398
|
+
const event = new YEvent(/** @type {any} */ (this), transaction, parentSubs)
|
|
399
|
+
callTypeObservers(/** @type {any} */ (this), transaction, event)
|
|
400
|
+
if (!transaction.local && this._searchMarker) {
|
|
401
|
+
this._searchMarker.length = 0
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Observe all events that are created on this type.
|
|
407
|
+
*
|
|
408
|
+
* @template {(target: YEvent<Self>, tr: Transaction) => void} F
|
|
409
|
+
* @param {F} f Observer function
|
|
410
|
+
* @return {F}
|
|
411
|
+
*/
|
|
412
|
+
observe (f) {
|
|
413
|
+
addEventHandlerListener(this._eH, f)
|
|
414
|
+
return f
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Observe all events that are created by this type and its children.
|
|
419
|
+
*
|
|
420
|
+
* @template {function(Array<YEvent<any>>,Transaction):void} F
|
|
421
|
+
* @param {F} f Observer function
|
|
422
|
+
* @return {F}
|
|
423
|
+
*/
|
|
424
|
+
observeDeep (f) {
|
|
425
|
+
addEventHandlerListener(this._dEH, f)
|
|
426
|
+
return f
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Unregister an observer function.
|
|
431
|
+
*
|
|
432
|
+
* @param {(type:YEvent<Self>,tr:Transaction)=>void} f Observer function
|
|
433
|
+
*/
|
|
434
|
+
unobserve (f) {
|
|
435
|
+
removeEventHandlerListener(this._eH, f)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Unregister an observer function.
|
|
440
|
+
*
|
|
441
|
+
* @param {function(Array<YEvent<any>>,Transaction):void} f Observer function
|
|
442
|
+
*/
|
|
443
|
+
unobserveDeep (f) {
|
|
444
|
+
removeEventHandlerListener(this._dEH, f)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* @abstract
|
|
449
|
+
* @return {any}
|
|
450
|
+
*/
|
|
451
|
+
toJSON () {}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
|
455
|
+
* attributions.
|
|
456
|
+
*
|
|
457
|
+
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
|
|
458
|
+
* attribution `{ isDeleted: true, .. }`.
|
|
459
|
+
*
|
|
460
|
+
* @template {boolean} [Deep=false]
|
|
461
|
+
*
|
|
462
|
+
* @param {AbstractAttributionManager} am
|
|
463
|
+
* @param {Object} [opts]
|
|
464
|
+
* @param {import('../utils/IdSet.js').IdSet?} [opts.itemsToRender]
|
|
465
|
+
* @param {boolean} [opts.retainInserts] - if true, retain rendered inserts with attributions
|
|
466
|
+
* @param {boolean} [opts.retainDeletes] - if true, retain rendered+attributed deletes only
|
|
467
|
+
* @param {import('../utils/IdSet.js').IdSet?} [opts.deletedItems] - used for computing prevItem in attributes
|
|
468
|
+
* @param {Map<import('../utils/types.js').YType,Set<string|null>>|null} [opts.modified] - set of types that should be rendered as modified children
|
|
469
|
+
* @param {Deep} [opts.deep] - render child types as delta
|
|
470
|
+
* @return {Deep extends true ? ToDeepEventDelta<EventDelta> : EventDelta} The Delta representation of this type.
|
|
471
|
+
*
|
|
472
|
+
* @public
|
|
473
|
+
*/
|
|
474
|
+
getContent (am = noAttributionsManager, opts = {}) {
|
|
475
|
+
const { itemsToRender = null, retainInserts = false, retainDeletes = false, deletedItems = null, modified = null, deep = false } = opts
|
|
476
|
+
const renderAttrs = modified?.get(this) || null
|
|
477
|
+
const renderChildren = (modified == null || opts.modified.get(this)?.has(null))
|
|
478
|
+
/**
|
|
479
|
+
* @type {EventDelta extends delta.Delta<infer N,infer Attrs,infer Children,infer Text,any> ? delta.DeltaBuilder<N,Attrs,Children,Text,any> : never}
|
|
480
|
+
*/
|
|
481
|
+
const d = /** @type {any} */ (delta.create(/** @type {any} */ (this).nodeName || null))
|
|
482
|
+
const optsAll = modified == null ? opts : object.assign({}, opts, { modified: null })
|
|
483
|
+
typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am, deep, modified, deletedItems, itemsToRender, opts, optsAll)
|
|
484
|
+
if (renderChildren) {
|
|
485
|
+
/**
|
|
486
|
+
* @type {delta.FormattingAttributes}
|
|
487
|
+
*/
|
|
488
|
+
let currentAttributes = {} // saves all current attributes for insert
|
|
489
|
+
let usingCurrentAttributes = false
|
|
490
|
+
/**
|
|
491
|
+
* @type {delta.FormattingAttributes}
|
|
492
|
+
*/
|
|
493
|
+
let changedAttributes = {} // saves changed attributes for retain
|
|
494
|
+
let usingChangedAttributes = false
|
|
495
|
+
/**
|
|
496
|
+
* Logic for formatting attribute attribution
|
|
497
|
+
* Everything that comes after an formatting attribute is formatted by the user that created it.
|
|
498
|
+
* Two exceptions:
|
|
499
|
+
* - the user resets formatting to the previously known formatting that is not attributed
|
|
500
|
+
* - the user deletes a formatting attribute and hence restores the previously known formatting
|
|
501
|
+
* that is not attributed.
|
|
502
|
+
* @type {delta.FormattingAttributes}
|
|
503
|
+
*/
|
|
504
|
+
const previousUnattributedAttributes = {} // contains previously known unattributed formatting
|
|
505
|
+
/**
|
|
506
|
+
* @type {delta.FormattingAttributes}
|
|
507
|
+
*/
|
|
508
|
+
const previousAttributes = {} // The value before changes
|
|
509
|
+
/**
|
|
510
|
+
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
|
511
|
+
*/
|
|
512
|
+
const cs = []
|
|
513
|
+
for (let item = this._start; item !== null; cs.length = 0) {
|
|
514
|
+
if (itemsToRender != null) {
|
|
515
|
+
for (; item !== null && cs.length < 50; item = item.right) {
|
|
516
|
+
const rslice = itemsToRender.slice(item.id.client, item.id.clock, item.length)
|
|
517
|
+
let itemContent = rslice.length > 1 ? item.content.copy() : item.content
|
|
518
|
+
for (let ir = 0; ir < rslice.length; ir++) {
|
|
519
|
+
const idrange = rslice[ir]
|
|
520
|
+
const content = itemContent
|
|
521
|
+
if (ir !== rslice.length - 1) {
|
|
522
|
+
itemContent = itemContent.splice(idrange.len)
|
|
523
|
+
}
|
|
524
|
+
am.readContent(cs, item.id.client, idrange.clock, item.deleted, content, idrange.exists ? 2 : 0)
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
} else {
|
|
528
|
+
for (; item !== null && cs.length < 50; item = item.right) {
|
|
529
|
+
am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
for (let i = 0; i < cs.length; i++) {
|
|
533
|
+
const c = cs[i]
|
|
534
|
+
// render (attributed) content even if it was deleted
|
|
535
|
+
const renderContent = c.render && (!c.deleted || c.attrs != null)
|
|
536
|
+
// content that was just deleted. It is not rendered as an insertion, because it doesn't
|
|
537
|
+
// have any attributes.
|
|
538
|
+
const renderDelete = c.render && c.deleted
|
|
539
|
+
// existing content that should be retained, only adding changed attributes
|
|
540
|
+
const retainContent = !c.render && (!c.deleted || c.attrs != null)
|
|
541
|
+
const attribution = (renderContent || c.content.constructor === ContentFormat) ? createAttributionFromAttributionItems(c.attrs, c.deleted) : null
|
|
542
|
+
switch (c.content.constructor) {
|
|
543
|
+
case ContentDeleted: {
|
|
544
|
+
if (renderDelete) d.delete(c.content.getLength())
|
|
545
|
+
break
|
|
546
|
+
}
|
|
547
|
+
case ContentString:
|
|
548
|
+
if (renderContent) {
|
|
549
|
+
d.usedAttributes = currentAttributes
|
|
550
|
+
usingCurrentAttributes = true
|
|
551
|
+
if (c.deleted ? retainDeletes : retainInserts) {
|
|
552
|
+
d.retain(/** @type {ContentString} */ (c.content).str.length, null, attribution ?? {})
|
|
553
|
+
} else {
|
|
554
|
+
d.insert(/** @type {ContentString} */ (c.content).str, null, attribution)
|
|
555
|
+
}
|
|
556
|
+
} else if (renderDelete) {
|
|
557
|
+
d.delete(c.content.getLength())
|
|
558
|
+
} else if (retainContent) {
|
|
559
|
+
d.usedAttributes = changedAttributes
|
|
560
|
+
usingChangedAttributes = true
|
|
561
|
+
d.retain(c.content.getLength())
|
|
562
|
+
}
|
|
563
|
+
break
|
|
564
|
+
case ContentEmbed:
|
|
565
|
+
case ContentAny:
|
|
566
|
+
case ContentJSON:
|
|
567
|
+
case ContentType:
|
|
568
|
+
case ContentBinary:
|
|
569
|
+
if (renderContent) {
|
|
570
|
+
d.usedAttributes = currentAttributes
|
|
571
|
+
usingCurrentAttributes = true
|
|
572
|
+
if (c.deleted ? retainDeletes : retainInserts) {
|
|
573
|
+
d.retain(c.content.getLength(), null, attribution ?? {})
|
|
574
|
+
} else if (deep && c.content.constructor === ContentType) {
|
|
575
|
+
d.insert([/** @type {any} */(c.content).type.getContent(am, optsAll)], null, attribution)
|
|
576
|
+
} else {
|
|
577
|
+
d.insert(c.content.getContent(), null, attribution)
|
|
578
|
+
}
|
|
579
|
+
} else if (renderDelete) {
|
|
580
|
+
d.delete(1)
|
|
581
|
+
} else if (retainContent) {
|
|
582
|
+
if (c.content.constructor === ContentType && modified?.has(/** @type {ContentType} */ (c.content).type)) {
|
|
583
|
+
// @todo use current transaction instead
|
|
584
|
+
d.modify(/** @type {any} */ (c.content).type.getContent(am, opts))
|
|
585
|
+
} else {
|
|
586
|
+
d.usedAttributes = changedAttributes
|
|
587
|
+
usingChangedAttributes = true
|
|
588
|
+
d.retain(1)
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
break
|
|
592
|
+
case ContentFormat: {
|
|
593
|
+
const { key, value } = /** @type {ContentFormat} */ (c.content)
|
|
594
|
+
const currAttrVal = currentAttributes[key] ?? null
|
|
595
|
+
if (attribution != null && (c.deleted || !object.hasProperty(previousUnattributedAttributes, key))) {
|
|
596
|
+
previousUnattributedAttributes[key] = c.deleted ? value : currAttrVal
|
|
597
|
+
}
|
|
598
|
+
// @todo write a function "updateCurrentAttributes" and "updateChangedAttributes"
|
|
599
|
+
// # Update Attributes
|
|
600
|
+
if (renderContent || renderDelete) {
|
|
601
|
+
// create fresh references
|
|
602
|
+
if (usingCurrentAttributes) {
|
|
603
|
+
currentAttributes = object.assign({}, currentAttributes)
|
|
604
|
+
usingCurrentAttributes = false
|
|
605
|
+
}
|
|
606
|
+
if (usingChangedAttributes) {
|
|
607
|
+
usingChangedAttributes = false
|
|
608
|
+
changedAttributes = object.assign({}, changedAttributes)
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (renderContent || renderDelete) {
|
|
612
|
+
if (c.deleted) {
|
|
613
|
+
// content was deleted, but is possibly attributed
|
|
614
|
+
if (!equalAttrs(value, currAttrVal)) { // do nothing if nothing changed
|
|
615
|
+
if (equalAttrs(currAttrVal, previousAttributes[key] ?? null) && changedAttributes[key] !== undefined) {
|
|
616
|
+
delete changedAttributes[key]
|
|
617
|
+
} else {
|
|
618
|
+
changedAttributes[key] = currAttrVal
|
|
619
|
+
}
|
|
620
|
+
// current attributes doesn't change
|
|
621
|
+
previousAttributes[key] = value
|
|
622
|
+
}
|
|
623
|
+
} else { // !c.deleted
|
|
624
|
+
// content was inserted, and is possibly attributed
|
|
625
|
+
if (equalAttrs(value, currAttrVal)) {
|
|
626
|
+
// item.delete(transaction)
|
|
627
|
+
} else if (equalAttrs(value, previousAttributes[key] ?? null)) {
|
|
628
|
+
delete changedAttributes[key]
|
|
629
|
+
} else {
|
|
630
|
+
changedAttributes[key] = value
|
|
631
|
+
}
|
|
632
|
+
if (value == null) {
|
|
633
|
+
delete currentAttributes[key]
|
|
634
|
+
} else {
|
|
635
|
+
currentAttributes[key] = value
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
} else if (retainContent && !c.deleted) {
|
|
639
|
+
// fresh reference to currentAttributes only
|
|
640
|
+
if (usingCurrentAttributes) {
|
|
641
|
+
currentAttributes = object.assign({}, currentAttributes)
|
|
642
|
+
usingCurrentAttributes = false
|
|
643
|
+
}
|
|
644
|
+
if (usingChangedAttributes && changedAttributes[key] !== undefined) {
|
|
645
|
+
usingChangedAttributes = false
|
|
646
|
+
changedAttributes = object.assign({}, changedAttributes)
|
|
647
|
+
}
|
|
648
|
+
if (value == null) {
|
|
649
|
+
delete currentAttributes[key]
|
|
650
|
+
} else {
|
|
651
|
+
currentAttributes[key] = value
|
|
652
|
+
}
|
|
653
|
+
delete changedAttributes[key]
|
|
654
|
+
previousAttributes[key] = value
|
|
655
|
+
}
|
|
656
|
+
// # Update Attributions
|
|
657
|
+
if (attribution != null || object.hasProperty(previousUnattributedAttributes, key)) {
|
|
658
|
+
/**
|
|
659
|
+
* @type {import('../utils/AttributionManager.js').Attribution}
|
|
660
|
+
*/
|
|
661
|
+
const formattingAttribution = object.assign({}, d.usedAttribution)
|
|
662
|
+
const changedAttributedAttributes = /** @type {{ [key: string]: Array<any> }} */ (formattingAttribution.format = object.assign({}, formattingAttribution.format ?? {}))
|
|
663
|
+
if (attribution == null || equalAttrs(previousUnattributedAttributes[key], currentAttributes[key] ?? null)) {
|
|
664
|
+
// an unattributed formatting attribute was found or an attributed formatting
|
|
665
|
+
// attribute was found that resets to the previous status
|
|
666
|
+
delete changedAttributedAttributes[key]
|
|
667
|
+
delete previousUnattributedAttributes[key]
|
|
668
|
+
} else {
|
|
669
|
+
const by = changedAttributedAttributes[key] = (changedAttributedAttributes[key]?.slice() ?? [])
|
|
670
|
+
by.push(...((c.deleted ? attribution.delete : attribution.insert) ?? []))
|
|
671
|
+
const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
|
|
672
|
+
if (attributedAt) formattingAttribution.formatAt = attributedAt
|
|
673
|
+
}
|
|
674
|
+
if (object.isEmpty(changedAttributedAttributes)) {
|
|
675
|
+
d.useAttribution(null)
|
|
676
|
+
} else if (attribution != null) {
|
|
677
|
+
const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
|
|
678
|
+
if (attributedAt != null) formattingAttribution.formatAt = attributedAt
|
|
679
|
+
d.useAttribution(formattingAttribution)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
break
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return /** @type {any} */ (d.done(false))
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
|
693
|
+
* attributions.
|
|
694
|
+
*
|
|
695
|
+
* @param {AbstractAttributionManager} am
|
|
696
|
+
* @return {ToDeepEventDelta<EventDelta>}
|
|
697
|
+
*/
|
|
698
|
+
getContentDeep (am = noAttributionsManager) {
|
|
699
|
+
return /** @type {any} */ (this.getContent(am, { deep: true }))
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Apply a {@link Delta} on this shared type.
|
|
704
|
+
*
|
|
705
|
+
* @param {delta.Delta<any,any,any,any,any>} d The changes to apply on this element.
|
|
706
|
+
* @param {AbstractAttributionManager} am
|
|
707
|
+
*
|
|
708
|
+
* @public
|
|
709
|
+
*/
|
|
710
|
+
applyDelta (d, am = noAttributionsManager) {
|
|
711
|
+
if (this.doc == null) {
|
|
712
|
+
(this._prelim || (this._prelim = /** @type {any} */ (delta.create()))).apply(d)
|
|
713
|
+
} else {
|
|
714
|
+
// @todo this was moved here from ytext. Make this more generic
|
|
715
|
+
transact(this.doc, transaction => {
|
|
716
|
+
const currPos = new ItemTextListPosition(null, this._start, 0, new Map(), am)
|
|
717
|
+
for (const op of d.children) {
|
|
718
|
+
if (delta.$textOp.check(op)) {
|
|
719
|
+
insertText(transaction, /** @type {any} */ (this), currPos, op.insert, op.format || {})
|
|
720
|
+
} else if (delta.$insertOp.check(op)) {
|
|
721
|
+
for (let i = 0; i < op.insert.length; i++) {
|
|
722
|
+
let ins = op.insert[i]
|
|
723
|
+
if (delta.$deltaAny.check(ins)) {
|
|
724
|
+
if (ins.name != null) {
|
|
725
|
+
const t = new YXmlElement(ins.name)
|
|
726
|
+
t.applyDelta(ins)
|
|
727
|
+
ins = t
|
|
728
|
+
} else {
|
|
729
|
+
error.unexpectedCase()
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
insertText(transaction, /** @type {any} */ (this), currPos, ins, op.format || {})
|
|
733
|
+
}
|
|
734
|
+
} else if (delta.$retainOp.check(op)) {
|
|
735
|
+
currPos.formatText(transaction, /** @type {any} */ (this), op.retain, op.format || {})
|
|
736
|
+
} else if (delta.$deleteOp.check(op)) {
|
|
737
|
+
deleteText(transaction, currPos, op.delete)
|
|
738
|
+
} else if (delta.$modifyOp.check(op)) {
|
|
739
|
+
/** @type {ContentType} */ (currPos.right?.content).type.applyDelta(op.value)
|
|
740
|
+
currPos.formatText(transaction, /** @type {any} */ (this), 1, op.format || {})
|
|
741
|
+
} else {
|
|
742
|
+
error.unexpectedCase()
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
for (const op of d.attrs) {
|
|
746
|
+
if (delta.$insertOp.check(op)) {
|
|
747
|
+
typeMapSet(transaction, /** @type {any} */ (this), op.key, op.value)
|
|
748
|
+
} else if (delta.$deleteOp.check(op)) {
|
|
749
|
+
typeMapDelete(transaction, /** @type {any} */ (this), op.key)
|
|
750
|
+
} else {
|
|
751
|
+
const sub = typeMapGet(/** @type {any} */ (this), op.key)
|
|
752
|
+
if (!(sub instanceof AbstractType)) {
|
|
753
|
+
error.unexpectedCase()
|
|
754
|
+
}
|
|
755
|
+
sub.applyDelta(op.value)
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
})
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* @param {any} a
|
|
765
|
+
* @param {any} b
|
|
766
|
+
* @return {boolean}
|
|
767
|
+
*/
|
|
768
|
+
export const equalAttrs = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && object.equalFlat(a, b))
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* @template {delta.Delta<any,any,any,any,any>} D
|
|
772
|
+
* @typedef {D extends delta.Delta<infer N,infer Attrs,infer Cs,infer Text,any>
|
|
773
|
+
* ? delta.Delta<
|
|
774
|
+
* N,
|
|
775
|
+
* { [K in keyof Attrs]: TypeToDelta<Attrs[K]> },
|
|
776
|
+
* TypeToDelta<Cs>,
|
|
777
|
+
* Text
|
|
778
|
+
* >
|
|
779
|
+
* : D
|
|
780
|
+
* } ToDeepEventDelta
|
|
781
|
+
*/
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* @template {any} T
|
|
785
|
+
* @typedef {(Extract<T,AbstractType<any>> extends AbstractType<infer D> ? (unknown extends D ? never : ToDeepEventDelta<D>) : never) | Exclude<T,AbstractType<any>>} TypeToDelta
|
|
786
|
+
*/
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* @param {AbstractType<any>} type
|
|
790
|
+
* @param {number} start
|
|
791
|
+
* @param {number} end
|
|
792
|
+
* @return {Array<any>}
|
|
793
|
+
*
|
|
794
|
+
* @private
|
|
795
|
+
* @function
|
|
796
|
+
*/
|
|
797
|
+
export const typeListSlice = (type, start, end) => {
|
|
798
|
+
type.doc ?? warnPrematureAccess()
|
|
799
|
+
if (start < 0) {
|
|
800
|
+
start = type._length + start
|
|
801
|
+
}
|
|
802
|
+
if (end < 0) {
|
|
803
|
+
end = type._length + end
|
|
804
|
+
}
|
|
805
|
+
let len = end - start
|
|
806
|
+
const cs = []
|
|
807
|
+
let n = type._start
|
|
808
|
+
while (n !== null && len > 0) {
|
|
809
|
+
if (n.countable && !n.deleted) {
|
|
810
|
+
const c = n.content.getContent()
|
|
811
|
+
if (c.length <= start) {
|
|
812
|
+
start -= c.length
|
|
813
|
+
} else {
|
|
814
|
+
for (let i = start; i < c.length && len > 0; i++) {
|
|
815
|
+
cs.push(c[i])
|
|
816
|
+
len--
|
|
817
|
+
}
|
|
818
|
+
start = 0
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
n = n.right
|
|
822
|
+
}
|
|
823
|
+
return cs
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* @param {import('../utils/types.js').YType} type
|
|
828
|
+
* @return {Array<any>}
|
|
829
|
+
*
|
|
830
|
+
* @private
|
|
831
|
+
* @function
|
|
832
|
+
*/
|
|
833
|
+
export const typeListToArray = type => {
|
|
834
|
+
type.doc ?? warnPrematureAccess()
|
|
835
|
+
const cs = []
|
|
836
|
+
let n = type._start
|
|
837
|
+
while (n !== null) {
|
|
838
|
+
if (n.countable && !n.deleted) {
|
|
839
|
+
const c = n.content.getContent()
|
|
840
|
+
for (let i = 0; i < c.length; i++) {
|
|
841
|
+
cs.push(c[i])
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
n = n.right
|
|
845
|
+
}
|
|
846
|
+
return cs
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* @param {AbstractType<any>} type
|
|
851
|
+
* @param {Snapshot} snapshot
|
|
852
|
+
* @return {Array<any>}
|
|
853
|
+
*
|
|
854
|
+
* @private
|
|
855
|
+
* @function
|
|
856
|
+
*/
|
|
857
|
+
export const typeListToArraySnapshot = (type, snapshot) => {
|
|
858
|
+
const cs = []
|
|
859
|
+
let n = type._start
|
|
860
|
+
while (n !== null) {
|
|
861
|
+
if (n.countable && isVisible(n, snapshot)) {
|
|
862
|
+
const c = n.content.getContent()
|
|
863
|
+
for (let i = 0; i < c.length; i++) {
|
|
864
|
+
cs.push(c[i])
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
n = n.right
|
|
868
|
+
}
|
|
869
|
+
return cs
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Executes a provided function on once on every element of this YArray.
|
|
874
|
+
*
|
|
875
|
+
* @param {AbstractType<any>} type
|
|
876
|
+
* @param {function(any,number,any):void} f A function to execute on every element of this YArray.
|
|
877
|
+
*
|
|
878
|
+
* @private
|
|
879
|
+
* @function
|
|
880
|
+
*/
|
|
881
|
+
export const typeListForEach = (type, f) => {
|
|
882
|
+
let index = 0
|
|
883
|
+
let n = type._start
|
|
884
|
+
type.doc ?? warnPrematureAccess()
|
|
885
|
+
while (n !== null) {
|
|
886
|
+
if (n.countable && !n.deleted) {
|
|
887
|
+
const c = n.content.getContent()
|
|
888
|
+
for (let i = 0; i < c.length; i++) {
|
|
889
|
+
f(c[i], index++, type)
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
n = n.right
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* @template C,R
|
|
898
|
+
* @param {AbstractType<any>} type
|
|
899
|
+
* @param {function(C,number,AbstractType<any>):R} f
|
|
900
|
+
* @return {Array<R>}
|
|
901
|
+
*
|
|
902
|
+
* @private
|
|
903
|
+
* @function
|
|
904
|
+
*/
|
|
905
|
+
export const typeListMap = (type, f) => {
|
|
906
|
+
/**
|
|
907
|
+
* @type {Array<any>}
|
|
908
|
+
*/
|
|
909
|
+
const result = []
|
|
910
|
+
typeListForEach(type, (c, i) => {
|
|
911
|
+
result.push(f(c, i, type))
|
|
912
|
+
})
|
|
913
|
+
return result
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* @param {AbstractType} type
|
|
918
|
+
* @return {IterableIterator<any>}
|
|
919
|
+
*
|
|
920
|
+
* @private
|
|
921
|
+
* @function
|
|
922
|
+
*/
|
|
923
|
+
export const typeListCreateIterator = type => {
|
|
924
|
+
let n = type._start
|
|
925
|
+
/**
|
|
926
|
+
* @type {Array<any>|null}
|
|
927
|
+
*/
|
|
928
|
+
let currentContent = null
|
|
929
|
+
let currentContentIndex = 0
|
|
930
|
+
return {
|
|
931
|
+
[Symbol.iterator] () {
|
|
932
|
+
return this
|
|
933
|
+
},
|
|
934
|
+
next: () => {
|
|
935
|
+
// find some content
|
|
936
|
+
if (currentContent === null) {
|
|
937
|
+
while (n !== null && n.deleted) {
|
|
938
|
+
n = n.right
|
|
939
|
+
}
|
|
940
|
+
// check if we reached the end, no need to check currentContent, because it does not exist
|
|
941
|
+
if (n === null) {
|
|
942
|
+
return {
|
|
943
|
+
done: true,
|
|
944
|
+
value: undefined
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
// we found n, so we can set currentContent
|
|
948
|
+
currentContent = n.content.getContent()
|
|
949
|
+
currentContentIndex = 0
|
|
950
|
+
n = n.right // we used the content of n, now iterate to next
|
|
951
|
+
}
|
|
952
|
+
const value = currentContent[currentContentIndex++]
|
|
953
|
+
// check if we need to empty currentContent
|
|
954
|
+
if (currentContent.length <= currentContentIndex) {
|
|
955
|
+
currentContent = null
|
|
956
|
+
}
|
|
957
|
+
return {
|
|
958
|
+
done: false,
|
|
959
|
+
value
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Executes a provided function on once on every element of this YArray.
|
|
967
|
+
* Operates on a snapshotted state of the document.
|
|
968
|
+
*
|
|
969
|
+
* @param {AbstractType} type
|
|
970
|
+
* @param {function(any,number,AbstractType):void} f A function to execute on every element of this YArray.
|
|
971
|
+
* @param {Snapshot} snapshot
|
|
972
|
+
*
|
|
973
|
+
* @private
|
|
974
|
+
* @function
|
|
975
|
+
*/
|
|
976
|
+
export const typeListForEachSnapshot = (type, f, snapshot) => {
|
|
977
|
+
let index = 0
|
|
978
|
+
let n = type._start
|
|
979
|
+
while (n !== null) {
|
|
980
|
+
if (n.countable && isVisible(n, snapshot)) {
|
|
981
|
+
const c = n.content.getContent()
|
|
982
|
+
for (let i = 0; i < c.length; i++) {
|
|
983
|
+
f(c[i], index++, type)
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
n = n.right
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* @param {import('../utils/types.js').YType} type
|
|
992
|
+
* @param {number} index
|
|
993
|
+
* @return {any}
|
|
994
|
+
*
|
|
995
|
+
* @private
|
|
996
|
+
* @function
|
|
997
|
+
*/
|
|
998
|
+
export const typeListGet = (type, index) => {
|
|
999
|
+
type.doc ?? warnPrematureAccess()
|
|
1000
|
+
const marker = findMarker(type, index)
|
|
1001
|
+
let n = type._start
|
|
1002
|
+
if (marker !== null) {
|
|
1003
|
+
n = marker.p
|
|
1004
|
+
index -= marker.index
|
|
1005
|
+
}
|
|
1006
|
+
for (; n !== null; n = n.right) {
|
|
1007
|
+
if (!n.deleted && n.countable) {
|
|
1008
|
+
if (index < n.length) {
|
|
1009
|
+
return n.content.getContent()[index]
|
|
1010
|
+
}
|
|
1011
|
+
index -= n.length
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* @param {Transaction} transaction
|
|
1018
|
+
* @param {YType_} parent
|
|
1019
|
+
* @param {Item?} referenceItem
|
|
1020
|
+
* @param {Array<_YValue>} content
|
|
1021
|
+
*
|
|
1022
|
+
* @private
|
|
1023
|
+
* @function
|
|
1024
|
+
*/
|
|
1025
|
+
export const typeListInsertGenericsAfter = (transaction, parent, referenceItem, content) => {
|
|
1026
|
+
let left = referenceItem
|
|
1027
|
+
const doc = transaction.doc
|
|
1028
|
+
const ownClientId = doc.clientID
|
|
1029
|
+
const store = doc.store
|
|
1030
|
+
const right = referenceItem === null ? parent._start : referenceItem.right
|
|
1031
|
+
/**
|
|
1032
|
+
* @type {Array<Object|Array<any>|number|null>}
|
|
1033
|
+
*/
|
|
1034
|
+
let jsonContent = []
|
|
1035
|
+
const packJsonContent = () => {
|
|
1036
|
+
if (jsonContent.length > 0) {
|
|
1037
|
+
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentAny(jsonContent))
|
|
1038
|
+
left.integrate(transaction, 0)
|
|
1039
|
+
jsonContent = []
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
content.forEach(c => {
|
|
1043
|
+
if (c === null) {
|
|
1044
|
+
jsonContent.push(c)
|
|
1045
|
+
} else {
|
|
1046
|
+
switch (c.constructor) {
|
|
1047
|
+
case Number:
|
|
1048
|
+
case Object:
|
|
1049
|
+
case undefined:
|
|
1050
|
+
case Boolean:
|
|
1051
|
+
case Array:
|
|
1052
|
+
case String:
|
|
1053
|
+
case BigInt:
|
|
1054
|
+
case Date:
|
|
1055
|
+
jsonContent.push(c)
|
|
1056
|
+
break
|
|
1057
|
+
default:
|
|
1058
|
+
packJsonContent()
|
|
1059
|
+
switch (c.constructor) {
|
|
1060
|
+
case Uint8Array:
|
|
1061
|
+
case ArrayBuffer:
|
|
1062
|
+
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))))
|
|
1063
|
+
left.integrate(transaction, 0)
|
|
1064
|
+
break
|
|
1065
|
+
case Doc:
|
|
1066
|
+
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
|
|
1067
|
+
left.integrate(transaction, 0)
|
|
1068
|
+
break
|
|
1069
|
+
default:
|
|
1070
|
+
if (c instanceof AbstractType) {
|
|
1071
|
+
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(/** @type {any} */ (c)))
|
|
1072
|
+
left.integrate(transaction, 0)
|
|
1073
|
+
} else {
|
|
1074
|
+
throw new Error('Unexpected content type in insert operation')
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
})
|
|
1080
|
+
packJsonContent()
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const lengthExceeded = () => error.create('Length exceeded!')
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* @param {Transaction} transaction
|
|
1087
|
+
* @param {YType_} parent
|
|
1088
|
+
* @param {number} index
|
|
1089
|
+
* @param {Array<Object<string,any>|Array<any>|number|null|string|Uint8Array>} content
|
|
1090
|
+
*
|
|
1091
|
+
* @private
|
|
1092
|
+
* @function
|
|
1093
|
+
*/
|
|
1094
|
+
export const typeListInsertGenerics = (transaction, parent, index, content) => {
|
|
1095
|
+
if (index > parent._length) {
|
|
1096
|
+
throw lengthExceeded()
|
|
1097
|
+
}
|
|
1098
|
+
if (index === 0) {
|
|
1099
|
+
if (parent._searchMarker) {
|
|
1100
|
+
updateMarkerChanges(parent._searchMarker, index, content.length)
|
|
1101
|
+
}
|
|
1102
|
+
return typeListInsertGenericsAfter(transaction, parent, null, content)
|
|
1103
|
+
}
|
|
1104
|
+
const startIndex = index
|
|
1105
|
+
const marker = findMarker(parent, index)
|
|
1106
|
+
let n = parent._start
|
|
1107
|
+
if (marker !== null) {
|
|
1108
|
+
n = marker.p
|
|
1109
|
+
index -= marker.index
|
|
1110
|
+
// we need to iterate one to the left so that the algorithm works
|
|
1111
|
+
if (index === 0) {
|
|
1112
|
+
// @todo refactor this as it actually doesn't consider formats
|
|
1113
|
+
n = n.prev // important! get the left undeleted item so that we can actually decrease index
|
|
1114
|
+
index += (n && n.countable && !n.deleted) ? n.length : 0
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
for (; n !== null; n = n.right) {
|
|
1118
|
+
if (!n.deleted && n.countable) {
|
|
1119
|
+
if (index <= n.length) {
|
|
1120
|
+
if (index < n.length) {
|
|
1121
|
+
// insert in-between
|
|
1122
|
+
getItemCleanStart(transaction, createID(n.id.client, n.id.clock + index))
|
|
1123
|
+
}
|
|
1124
|
+
break
|
|
1125
|
+
}
|
|
1126
|
+
index -= n.length
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if (parent._searchMarker) {
|
|
1130
|
+
updateMarkerChanges(parent._searchMarker, startIndex, content.length)
|
|
1131
|
+
}
|
|
1132
|
+
return typeListInsertGenericsAfter(transaction, parent, n, content)
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Pushing content is special as we generally want to push after the last item. So we don't have to update
|
|
1137
|
+
* the search marker.
|
|
1138
|
+
*
|
|
1139
|
+
* @param {Transaction} transaction
|
|
1140
|
+
* @param {YType_} parent
|
|
1141
|
+
* @param {Array<Object<string,any>|Array<any>|number|null|string|Uint8Array>} content
|
|
1142
|
+
*
|
|
1143
|
+
* @private
|
|
1144
|
+
* @function
|
|
1145
|
+
*/
|
|
1146
|
+
export const typeListPushGenerics = (transaction, parent, content) => {
|
|
1147
|
+
// Use the marker with the highest index and iterate to the right.
|
|
1148
|
+
const marker = (parent._searchMarker || []).reduce((maxMarker, currMarker) => currMarker.index > maxMarker.index ? currMarker : maxMarker, { index: 0, p: parent._start })
|
|
1149
|
+
let n = marker.p
|
|
1150
|
+
if (n) {
|
|
1151
|
+
while (n.right) {
|
|
1152
|
+
n = n.right
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return typeListInsertGenericsAfter(transaction, parent, n, content)
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* @param {Transaction} transaction
|
|
1160
|
+
* @param {import('../utils/types.js').YType} parent
|
|
1161
|
+
* @param {number} index
|
|
1162
|
+
* @param {number} length
|
|
1163
|
+
*
|
|
1164
|
+
* @private
|
|
1165
|
+
* @function
|
|
1166
|
+
*/
|
|
1167
|
+
export const typeListDelete = (transaction, parent, index, length) => {
|
|
1168
|
+
if (length === 0) { return }
|
|
1169
|
+
const startIndex = index
|
|
1170
|
+
const startLength = length
|
|
1171
|
+
const marker = findMarker(parent, index)
|
|
1172
|
+
let n = parent._start
|
|
1173
|
+
if (marker !== null) {
|
|
1174
|
+
n = marker.p
|
|
1175
|
+
index -= marker.index
|
|
1176
|
+
}
|
|
1177
|
+
// compute the first item to be deleted
|
|
1178
|
+
for (; n !== null && index > 0; n = n.right) {
|
|
1179
|
+
if (!n.deleted && n.countable) {
|
|
1180
|
+
if (index < n.length) {
|
|
1181
|
+
getItemCleanStart(transaction, createID(n.id.client, n.id.clock + index))
|
|
1182
|
+
}
|
|
1183
|
+
index -= n.length
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
// delete all items until done
|
|
1187
|
+
while (length > 0 && n !== null) {
|
|
1188
|
+
if (!n.deleted) {
|
|
1189
|
+
if (length < n.length) {
|
|
1190
|
+
getItemCleanStart(transaction, createID(n.id.client, n.id.clock + length))
|
|
1191
|
+
}
|
|
1192
|
+
n.delete(transaction)
|
|
1193
|
+
length -= n.length
|
|
1194
|
+
}
|
|
1195
|
+
n = n.right
|
|
1196
|
+
}
|
|
1197
|
+
if (length > 0) {
|
|
1198
|
+
throw lengthExceeded()
|
|
1199
|
+
}
|
|
1200
|
+
if (parent._searchMarker) {
|
|
1201
|
+
updateMarkerChanges(parent._searchMarker, startIndex, -startLength + length /* in case we remove the above exception */)
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* @param {Transaction} transaction
|
|
1207
|
+
* @param {YType_} parent
|
|
1208
|
+
* @param {string} key
|
|
1209
|
+
*
|
|
1210
|
+
* @private
|
|
1211
|
+
* @function
|
|
1212
|
+
*/
|
|
1213
|
+
export const typeMapDelete = (transaction, parent, key) => {
|
|
1214
|
+
const c = parent._map.get(key)
|
|
1215
|
+
if (c !== undefined) {
|
|
1216
|
+
c.delete(transaction)
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* @param {Transaction} transaction
|
|
1222
|
+
* @param {AbstractType} parent
|
|
1223
|
+
* @param {string} key
|
|
1224
|
+
* @param {_YValue} value
|
|
1225
|
+
*
|
|
1226
|
+
* @private
|
|
1227
|
+
* @function
|
|
1228
|
+
*/
|
|
1229
|
+
export const typeMapSet = (transaction, parent, key, value) => {
|
|
1230
|
+
const left = parent._map.get(key) || null
|
|
1231
|
+
const doc = transaction.doc
|
|
1232
|
+
const ownClientId = doc.clientID
|
|
1233
|
+
let content
|
|
1234
|
+
if (value == null) {
|
|
1235
|
+
content = new ContentAny([value])
|
|
1236
|
+
} else {
|
|
1237
|
+
switch (value.constructor) {
|
|
1238
|
+
case Number:
|
|
1239
|
+
case Object:
|
|
1240
|
+
case Boolean:
|
|
1241
|
+
case Array:
|
|
1242
|
+
case String:
|
|
1243
|
+
case Date:
|
|
1244
|
+
case BigInt:
|
|
1245
|
+
content = new ContentAny([value])
|
|
1246
|
+
break
|
|
1247
|
+
case Uint8Array:
|
|
1248
|
+
content = new ContentBinary(/** @type {Uint8Array} */ (value))
|
|
1249
|
+
break
|
|
1250
|
+
case Doc:
|
|
1251
|
+
content = new ContentDoc(/** @type {Doc} */ (value))
|
|
1252
|
+
break
|
|
1253
|
+
default:
|
|
1254
|
+
if (value instanceof AbstractType) {
|
|
1255
|
+
content = new ContentType(/** @type {any} */ (value))
|
|
1256
|
+
} else {
|
|
1257
|
+
throw new Error('Unexpected content type')
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, null, null, parent, key, content).integrate(transaction, 0)
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* @param {AbstractType<any,any>} parent
|
|
1266
|
+
* @param {string} key
|
|
1267
|
+
* @return {Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
|
|
1268
|
+
*
|
|
1269
|
+
* @private
|
|
1270
|
+
* @function
|
|
1271
|
+
*/
|
|
1272
|
+
export const typeMapGet = (parent, key) => {
|
|
1273
|
+
parent.doc ?? warnPrematureAccess()
|
|
1274
|
+
const val = parent._map.get(key)
|
|
1275
|
+
return val !== undefined && !val.deleted ? val.content.getContent()[val.length - 1] : undefined
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* @param {AbstractType<any>} parent
|
|
1280
|
+
* @return {Object<string,Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined>}
|
|
1281
|
+
*
|
|
1282
|
+
* @private
|
|
1283
|
+
* @function
|
|
1284
|
+
*/
|
|
1285
|
+
export const typeMapGetAll = (parent) => {
|
|
1286
|
+
/**
|
|
1287
|
+
* @type {Object<string,any>}
|
|
1288
|
+
*/
|
|
1289
|
+
const res = {}
|
|
1290
|
+
parent.doc ?? warnPrematureAccess()
|
|
1291
|
+
parent._map.forEach((value, key) => {
|
|
1292
|
+
if (!value.deleted) {
|
|
1293
|
+
res[key] = value.content.getContent()[value.length - 1]
|
|
1294
|
+
}
|
|
1295
|
+
})
|
|
1296
|
+
return res
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* @todo move this to getContent/getDelta
|
|
1301
|
+
*
|
|
1302
|
+
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
|
1303
|
+
* attributions.
|
|
1304
|
+
*
|
|
1305
|
+
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
|
|
1306
|
+
* attribution `{ isDeleted: true, .. }`.
|
|
1307
|
+
*
|
|
1308
|
+
* @template {delta.DeltaBuilder<any,any,any,any>} TypeDelta
|
|
1309
|
+
* @param {TypeDelta} d
|
|
1310
|
+
* @param {YType_} parent
|
|
1311
|
+
* @param {Set<string|null>?} attrsToRender
|
|
1312
|
+
* @param {import('../internals.js').AbstractAttributionManager} am
|
|
1313
|
+
* @param {boolean} deep
|
|
1314
|
+
* @param {Set<import('../utils/types.js').YType>|Map<import('../utils/types.js').YType,any>|null} [modified] - set of types that should be rendered as modified children
|
|
1315
|
+
* @param {import('../utils/IdSet.js').IdSet?} [deletedItems]
|
|
1316
|
+
* @param {import('../utils/IdSet.js').IdSet?} [itemsToRender]
|
|
1317
|
+
* @param {any} [opts]
|
|
1318
|
+
* @param {any} [optsAll]
|
|
1319
|
+
*
|
|
1320
|
+
* @private
|
|
1321
|
+
* @function
|
|
1322
|
+
*/
|
|
1323
|
+
export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, deletedItems, itemsToRender, opts, optsAll) => {
|
|
1324
|
+
// @todo support modified ops!
|
|
1325
|
+
/**
|
|
1326
|
+
* @param {Item} item
|
|
1327
|
+
* @param {string} key
|
|
1328
|
+
*/
|
|
1329
|
+
const renderAttrs = (item, key) => {
|
|
1330
|
+
/**
|
|
1331
|
+
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
|
1332
|
+
*/
|
|
1333
|
+
const cs = []
|
|
1334
|
+
am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
|
|
1335
|
+
const { deleted, attrs, content } = cs[cs.length - 1]
|
|
1336
|
+
const attribution = createAttributionFromAttributionItems(attrs, deleted)
|
|
1337
|
+
let c = array.last(content.getContent())
|
|
1338
|
+
if (deleted) {
|
|
1339
|
+
if (itemsToRender == null || itemsToRender.hasId(item.lastId)) {
|
|
1340
|
+
d.unset(key, attribution, c)
|
|
1341
|
+
}
|
|
1342
|
+
} else if (deep && c instanceof AbstractType && modified?.has(c)) {
|
|
1343
|
+
d.update(key, c.getContent(am, opts))
|
|
1344
|
+
} else {
|
|
1345
|
+
// find prev content
|
|
1346
|
+
let prevContentItem = item
|
|
1347
|
+
// this algorithm is problematic. should check all previous content using am.readcontent
|
|
1348
|
+
for (; prevContentItem.left !== null && deletedItems?.hasId(prevContentItem.left.lastId); prevContentItem = prevContentItem.left) {
|
|
1349
|
+
// nop
|
|
1350
|
+
}
|
|
1351
|
+
const prevValue = (prevContentItem !== item && itemsToRender?.hasId(prevContentItem.lastId)) ? array.last(prevContentItem.content.getContent()) : undefined
|
|
1352
|
+
if (deep && c instanceof AbstractType) {
|
|
1353
|
+
c = /** @type {any} */(c).getContent(am, optsAll)
|
|
1354
|
+
}
|
|
1355
|
+
d.set(key, c, attribution, prevValue)
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (attrsToRender == null) {
|
|
1359
|
+
parent._map.forEach(renderAttrs)
|
|
1360
|
+
} else {
|
|
1361
|
+
attrsToRender.forEach(key => key != null && renderAttrs(/** @type {Item} */ (parent._map.get(key)), key))
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* @param {AbstractType<any>} parent
|
|
1367
|
+
* @param {string} key
|
|
1368
|
+
* @return {boolean}
|
|
1369
|
+
*
|
|
1370
|
+
* @private
|
|
1371
|
+
* @function
|
|
1372
|
+
*/
|
|
1373
|
+
export const typeMapHas = (parent, key) => {
|
|
1374
|
+
parent.doc ?? warnPrematureAccess()
|
|
1375
|
+
const val = parent._map.get(key)
|
|
1376
|
+
return val !== undefined && !val.deleted
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* @param {AbstractType<any>} parent
|
|
1381
|
+
* @param {string} key
|
|
1382
|
+
* @param {Snapshot} snapshot
|
|
1383
|
+
* @return {Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
|
|
1384
|
+
*
|
|
1385
|
+
* @private
|
|
1386
|
+
* @function
|
|
1387
|
+
*/
|
|
1388
|
+
export const typeMapGetSnapshot = (parent, key, snapshot) => {
|
|
1389
|
+
let v = parent._map.get(key) || null
|
|
1390
|
+
while (v !== null && (!snapshot.sv.has(v.id.client) || v.id.clock >= (snapshot.sv.get(v.id.client) || 0))) {
|
|
1391
|
+
v = v.left
|
|
1392
|
+
}
|
|
1393
|
+
return v !== null && isVisible(v, snapshot) ? v.content.getContent()[v.length - 1] : undefined
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
/**
|
|
1397
|
+
* @param {AbstractType<any>} parent
|
|
1398
|
+
* @param {Snapshot} snapshot
|
|
1399
|
+
* @return {Object<string,Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined>}
|
|
1400
|
+
*
|
|
1401
|
+
* @private
|
|
1402
|
+
* @function
|
|
1403
|
+
*/
|
|
1404
|
+
export const typeMapGetAllSnapshot = (parent, snapshot) => {
|
|
1405
|
+
/**
|
|
1406
|
+
* @type {Object<string,any>}
|
|
1407
|
+
*/
|
|
1408
|
+
const res = {}
|
|
1409
|
+
parent._map.forEach((value, key) => {
|
|
1410
|
+
/**
|
|
1411
|
+
* @type {Item|null}
|
|
1412
|
+
*/
|
|
1413
|
+
let v = value
|
|
1414
|
+
while (v !== null && (!snapshot.sv.has(v.id.client) || v.id.clock >= (snapshot.sv.get(v.id.client) || 0))) {
|
|
1415
|
+
v = v.left
|
|
1416
|
+
}
|
|
1417
|
+
if (v !== null && isVisible(v, snapshot)) {
|
|
1418
|
+
res[key] = v.content.getContent()[v.length - 1]
|
|
1419
|
+
}
|
|
1420
|
+
})
|
|
1421
|
+
return res
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* @param {AbstractType<any> & { _map: Map<string, Item> }} type
|
|
1426
|
+
* @return {IterableIterator<Array<any>>}
|
|
1427
|
+
*
|
|
1428
|
+
* @private
|
|
1429
|
+
* @function
|
|
1430
|
+
*/
|
|
1431
|
+
export const createMapIterator = type => {
|
|
1432
|
+
type.doc ?? warnPrematureAccess()
|
|
1433
|
+
return iterator.iteratorFilter(type._map.entries(), /** @param {any} entry */ entry => !entry[1].deleted)
|
|
1434
|
+
}
|