@y/y 14.0.0-rc.2 → 14.0.0-rc.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 +36 -12
- package/dist/src/index.d.ts +24 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/structs/AbstractStruct.d.ts +14 -17
- package/dist/src/structs/AbstractStruct.d.ts.map +1 -1
- package/dist/src/structs/GC.d.ts +9 -14
- package/dist/src/structs/GC.d.ts.map +1 -1
- package/dist/src/structs/Item.d.ts +569 -31
- package/dist/src/structs/Item.d.ts.map +1 -1
- package/dist/src/structs/Skip.d.ts +9 -11
- package/dist/src/structs/Skip.d.ts.map +1 -1
- package/dist/src/utils/BlockSet.d.ts +8 -7
- package/dist/src/utils/BlockSet.d.ts.map +1 -1
- package/dist/src/utils/Doc.d.ts +4 -9
- package/dist/src/utils/Doc.d.ts.map +1 -1
- package/dist/src/utils/ID.d.ts +0 -1
- package/dist/src/utils/ID.d.ts.map +1 -1
- package/dist/src/utils/RelativePosition.d.ts +2 -5
- package/dist/src/utils/RelativePosition.d.ts.map +1 -1
- package/dist/src/utils/Renderer.d.ts +144 -0
- package/dist/src/utils/Renderer.d.ts.map +1 -0
- package/dist/src/utils/Snapshot.d.ts +7 -11
- package/dist/src/utils/Snapshot.d.ts.map +1 -1
- package/dist/src/utils/StructStore.d.ts +45 -23
- package/dist/src/utils/StructStore.d.ts.map +1 -1
- package/dist/src/utils/Transaction.d.ts +11 -21
- package/dist/src/utils/Transaction.d.ts.map +1 -1
- package/dist/src/utils/UndoManager.d.ts +13 -14
- package/dist/src/utils/UndoManager.d.ts.map +1 -1
- package/dist/src/utils/UpdateDecoder.d.ts +1 -1
- package/dist/src/utils/UpdateDecoder.d.ts.map +1 -1
- package/dist/src/utils/UpdateEncoder.d.ts +0 -1
- package/dist/src/utils/UpdateEncoder.d.ts.map +1 -1
- package/dist/src/utils/YEvent.d.ts +22 -26
- package/dist/src/utils/YEvent.d.ts.map +1 -1
- package/dist/src/utils/content-helper.d.ts +2 -0
- package/dist/src/utils/content-helper.d.ts.map +1 -0
- package/dist/src/utils/delta-helpers.d.ts +2 -3
- package/dist/src/utils/delta-helpers.d.ts.map +1 -1
- package/dist/src/utils/encoding-helpers.d.ts +6 -0
- package/dist/src/utils/encoding-helpers.d.ts.map +1 -0
- package/dist/src/utils/encoding.d.ts +16 -18
- package/dist/src/utils/encoding.d.ts.map +1 -1
- package/dist/src/utils/ids.d.ts +331 -0
- package/dist/src/utils/ids.d.ts.map +1 -0
- package/dist/src/utils/isParentOf.d.ts +1 -2
- package/dist/src/utils/isParentOf.d.ts.map +1 -1
- package/dist/src/utils/logging.d.ts +0 -1
- package/dist/src/utils/logging.d.ts.map +1 -1
- package/dist/src/utils/meta.d.ts +15 -64
- package/dist/src/utils/meta.d.ts.map +1 -1
- package/dist/src/utils/renderer-helpers.d.ts +99 -0
- package/dist/src/utils/renderer-helpers.d.ts.map +1 -0
- package/dist/src/utils/schemas.d.ts +3 -0
- package/dist/src/utils/schemas.d.ts.map +1 -0
- package/dist/src/utils/transaction-helpers.d.ts +18 -0
- package/dist/src/utils/transaction-helpers.d.ts.map +1 -0
- package/dist/src/utils/updates.d.ts +19 -25
- package/dist/src/utils/updates.d.ts.map +1 -1
- package/dist/src/ytype.d.ts +144 -41
- package/dist/src/ytype.d.ts.map +1 -1
- package/global.d.ts +53 -0
- package/package.json +12 -16
- package/src/index.js +32 -124
- package/src/structs/AbstractStruct.js +21 -16
- package/src/structs/GC.js +15 -20
- package/src/structs/Item.js +992 -318
- package/src/structs/Skip.js +16 -19
- package/src/utils/BlockSet.js +20 -20
- package/src/utils/Doc.js +18 -29
- package/src/utils/ID.js +0 -2
- package/src/utils/RelativePosition.js +15 -25
- package/src/utils/{AttributionManager.js → Renderer.js} +42 -197
- package/src/utils/Snapshot.js +15 -37
- package/src/utils/StructStore.js +89 -227
- package/src/utils/Transaction.js +89 -321
- package/src/utils/UndoManager.js +157 -19
- package/src/utils/UpdateDecoder.js +2 -3
- package/src/utils/UpdateEncoder.js +0 -4
- package/src/utils/YEvent.js +20 -26
- package/src/utils/content-helper.js +0 -0
- package/src/utils/delta-helpers.js +21 -42
- package/src/utils/encoding-helpers.js +110 -0
- package/src/utils/encoding.js +197 -122
- package/src/utils/ids.js +1527 -0
- package/src/utils/isParentOf.js +2 -4
- package/src/utils/logging.js +0 -4
- package/src/utils/meta.js +57 -46
- package/src/utils/renderer-helpers.js +110 -0
- package/src/utils/schemas.js +3 -0
- package/src/utils/transaction-helpers.js +413 -0
- package/src/utils/updates.js +24 -146
- package/src/ytype.js +626 -255
- package/tests/testHelper.js +10 -12
- package/dist/src/internals.d.ts +0 -36
- package/dist/src/internals.d.ts.map +0 -1
- package/dist/src/structs/ContentAny.d.ts +0 -67
- package/dist/src/structs/ContentAny.d.ts.map +0 -1
- package/dist/src/structs/ContentBinary.d.ts +0 -64
- package/dist/src/structs/ContentBinary.d.ts.map +0 -1
- package/dist/src/structs/ContentDeleted.d.ts +0 -64
- package/dist/src/structs/ContentDeleted.d.ts.map +0 -1
- package/dist/src/structs/ContentDoc.d.ts +0 -72
- package/dist/src/structs/ContentDoc.d.ts.map +0 -1
- package/dist/src/structs/ContentEmbed.d.ts +0 -67
- package/dist/src/structs/ContentEmbed.d.ts.map +0 -1
- package/dist/src/structs/ContentFormat.d.ts +0 -69
- package/dist/src/structs/ContentFormat.d.ts.map +0 -1
- package/dist/src/structs/ContentJSON.d.ts +0 -70
- package/dist/src/structs/ContentJSON.d.ts.map +0 -1
- package/dist/src/structs/ContentString.d.ts +0 -70
- package/dist/src/structs/ContentString.d.ts.map +0 -1
- package/dist/src/structs/ContentType.d.ts +0 -77
- package/dist/src/structs/ContentType.d.ts.map +0 -1
- package/dist/src/utils/AttributionManager.d.ts +0 -238
- package/dist/src/utils/AttributionManager.d.ts.map +0 -1
- package/dist/src/utils/IdMap.d.ts +0 -164
- package/dist/src/utils/IdMap.d.ts.map +0 -1
- package/dist/src/utils/IdSet.d.ts +0 -163
- package/dist/src/utils/IdSet.d.ts.map +0 -1
- package/dist/tests/IdMap.tests.d.ts +0 -9
- package/dist/tests/IdMap.tests.d.ts.map +0 -1
- package/dist/tests/IdSet.tests.d.ts +0 -9
- package/dist/tests/IdSet.tests.d.ts.map +0 -1
- package/dist/tests/attribution.tests.d.ts +0 -9
- package/dist/tests/attribution.tests.d.ts.map +0 -1
- package/dist/tests/compatibility.tests.d.ts +0 -5
- package/dist/tests/compatibility.tests.d.ts.map +0 -1
- package/dist/tests/delta.tests.d.ts +0 -7
- package/dist/tests/delta.tests.d.ts.map +0 -1
- package/dist/tests/doc.tests.d.ts +0 -13
- package/dist/tests/doc.tests.d.ts.map +0 -1
- package/dist/tests/encoding.tests.d.ts +0 -5
- package/dist/tests/encoding.tests.d.ts.map +0 -1
- package/dist/tests/index.d.ts +0 -2
- package/dist/tests/index.d.ts.map +0 -1
- package/dist/tests/relativePositions.tests.d.ts +0 -11
- package/dist/tests/relativePositions.tests.d.ts.map +0 -1
- package/dist/tests/snapshot.tests.d.ts +0 -14
- package/dist/tests/snapshot.tests.d.ts.map +0 -1
- package/dist/tests/testHelper.d.ts +0 -171
- package/dist/tests/testHelper.d.ts.map +0 -1
- package/dist/tests/undo-redo.tests.d.ts +0 -27
- package/dist/tests/undo-redo.tests.d.ts.map +0 -1
- package/dist/tests/updates.tests.d.ts +0 -26
- package/dist/tests/updates.tests.d.ts.map +0 -1
- package/dist/tests/y-array.tests.d.ts +0 -43
- package/dist/tests/y-array.tests.d.ts.map +0 -1
- package/dist/tests/y-map.tests.d.ts +0 -42
- package/dist/tests/y-map.tests.d.ts.map +0 -1
- package/dist/tests/y-text.tests.d.ts +0 -49
- package/dist/tests/y-text.tests.d.ts.map +0 -1
- package/dist/tests/y-xml.tests.d.ts +0 -14
- package/dist/tests/y-xml.tests.d.ts.map +0 -1
- package/src/internals.js +0 -35
- package/src/structs/ContentAny.js +0 -115
- package/src/structs/ContentBinary.js +0 -93
- package/src/structs/ContentDeleted.js +0 -101
- package/src/structs/ContentDoc.js +0 -141
- package/src/structs/ContentEmbed.js +0 -98
- package/src/structs/ContentFormat.js +0 -105
- package/src/structs/ContentJSON.js +0 -119
- package/src/structs/ContentString.js +0 -113
- package/src/structs/ContentType.js +0 -152
- package/src/utils/IdMap.js +0 -673
- package/src/utils/IdSet.js +0 -825
package/src/ytype.js
CHANGED
|
@@ -1,39 +1,40 @@
|
|
|
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'
|
|
1
|
+
import * as binary from 'lib0/binary'
|
|
29
2
|
import * as array from 'lib0/array'
|
|
30
|
-
import * as
|
|
31
|
-
import * as iterator from 'lib0/iterator'
|
|
3
|
+
import * as delta from 'lib0/delta'
|
|
32
4
|
import * as error from 'lib0/error'
|
|
33
|
-
import * as
|
|
5
|
+
import * as iterator from 'lib0/iterator'
|
|
34
6
|
import * as log from 'lib0/logging'
|
|
7
|
+
import * as map from 'lib0/map'
|
|
8
|
+
import * as set from 'lib0/set'
|
|
9
|
+
import * as math from 'lib0/math'
|
|
35
10
|
import * as object from 'lib0/object'
|
|
36
11
|
import * as s from 'lib0/schema'
|
|
12
|
+
import * as traits from 'lib0/traits'
|
|
13
|
+
import { ObservableV2 } from 'lib0/observable'
|
|
14
|
+
import {
|
|
15
|
+
Item,
|
|
16
|
+
ContentAny,
|
|
17
|
+
ContentBinary,
|
|
18
|
+
ContentDeleted,
|
|
19
|
+
ContentEmbed,
|
|
20
|
+
ContentFormat,
|
|
21
|
+
ContentJSON,
|
|
22
|
+
ContentString,
|
|
23
|
+
ContentType,
|
|
24
|
+
YXmlFragmentRefID,
|
|
25
|
+
YXmlElementRefID,
|
|
26
|
+
YXmlHookRefID,
|
|
27
|
+
ContentDoc,
|
|
28
|
+
createContentDocFromDoc
|
|
29
|
+
} from './structs/Item.js'
|
|
30
|
+
import { baseRenderer } from './utils/renderer-helpers.js'
|
|
31
|
+
import { removeEventHandlerListener, callEventHandlerListeners, addEventHandlerListener, createEventHandler } from './utils/EventHandler.js'
|
|
32
|
+
import { createID } from './utils/ID.js'
|
|
33
|
+
import { createIdSet, iterateStructsByIdSetWithoutSplits } from './utils/ids.js'
|
|
34
|
+
import { getItemCleanStart, cleanupFormattingGap } from './utils/transaction-helpers.js'
|
|
35
|
+
import { transact } from './utils/Transaction.js'
|
|
36
|
+
import { YEvent } from './utils/YEvent.js'
|
|
37
|
+
import { $ydoc } from './utils/schemas.js'
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* @typedef {Object<string,any>|Array<any>|number|null|string|Uint8Array|BigInt|YType<any>} YValue
|
|
@@ -46,6 +47,45 @@ export const warnPrematureAccess = () => { log.warn('Invalid access: Add Yjs typ
|
|
|
46
47
|
|
|
47
48
|
const maxSearchMarker = 80
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* @todo SHOULD NOT RETURN AN OBJECT!
|
|
52
|
+
* @param {Array<ContentAttribute<any>>?} attrs
|
|
53
|
+
* @param {boolean} deleted - whether the attributed item is deleted
|
|
54
|
+
* @return {Attribution|undefined} `undefined` when there is no attribution — under lib0's tri-state
|
|
55
|
+
* that means "skip / inherit the builder's attribution context" (NOT `null`, which would clear it).
|
|
56
|
+
*/
|
|
57
|
+
export const createAttributionFromAttributionItems = (attrs, deleted) => {
|
|
58
|
+
if (attrs == null) {
|
|
59
|
+
return undefined
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* @type {Attribution}
|
|
63
|
+
*/
|
|
64
|
+
const attribution = {}
|
|
65
|
+
if (deleted) {
|
|
66
|
+
attribution.delete = []
|
|
67
|
+
} else {
|
|
68
|
+
attribution.insert = []
|
|
69
|
+
}
|
|
70
|
+
attrs.forEach(attr => {
|
|
71
|
+
switch (attr.name) {
|
|
72
|
+
// eslint-disable-next-line no-fallthrough
|
|
73
|
+
case 'insert':
|
|
74
|
+
case 'delete': {
|
|
75
|
+
// needs to be non-ambiguous: don't add existing attr if it doesn't match the actual status
|
|
76
|
+
attribution[attr.name]?.push(attr.val)
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
default: {
|
|
80
|
+
if (attr.name[0] !== '_') {
|
|
81
|
+
/** @type {any} */ (attribution)[attr.name] = attr.val
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
return attribution
|
|
87
|
+
}
|
|
88
|
+
|
|
49
89
|
/**
|
|
50
90
|
* A unique timestamp that identifies each marker.
|
|
51
91
|
*
|
|
@@ -60,15 +100,15 @@ export class ItemTextListPosition {
|
|
|
60
100
|
* @param {Item|null} left
|
|
61
101
|
* @param {Item|null} right
|
|
62
102
|
* @param {number} index
|
|
63
|
-
* @param {Map<string,any>}
|
|
64
|
-
* @param {
|
|
103
|
+
* @param {Map<string,any>} currentFormats
|
|
104
|
+
* @param {AbstractRenderer} renderer
|
|
65
105
|
*/
|
|
66
|
-
constructor (left, right, index,
|
|
106
|
+
constructor (left, right, index, currentFormats, renderer) {
|
|
67
107
|
this.left = left
|
|
68
108
|
this.right = right
|
|
69
109
|
this.index = index
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
110
|
+
this.currentFormats = currentFormats
|
|
111
|
+
this.renderer = renderer
|
|
72
112
|
}
|
|
73
113
|
|
|
74
114
|
/**
|
|
@@ -81,11 +121,11 @@ export class ItemTextListPosition {
|
|
|
81
121
|
switch (this.right.content.constructor) {
|
|
82
122
|
case ContentFormat:
|
|
83
123
|
if (!this.right.deleted) {
|
|
84
|
-
|
|
124
|
+
updateCurrentFormats(this.currentFormats, /** @type {ContentFormat} */ (this.right.content))
|
|
85
125
|
}
|
|
86
126
|
break
|
|
87
127
|
default:
|
|
88
|
-
this.index += this.
|
|
128
|
+
this.index += this.renderer.contentLength(this.right)
|
|
89
129
|
break
|
|
90
130
|
}
|
|
91
131
|
this.left = this.right
|
|
@@ -96,23 +136,23 @@ export class ItemTextListPosition {
|
|
|
96
136
|
* @param {Transaction} transaction
|
|
97
137
|
* @param {YType} parent
|
|
98
138
|
* @param {number} length
|
|
99
|
-
* @param {Object<string,any>}
|
|
139
|
+
* @param {Object<string,any>} formats
|
|
100
140
|
*
|
|
101
141
|
* @function
|
|
102
142
|
*/
|
|
103
|
-
formatText (transaction, parent, length,
|
|
104
|
-
|
|
105
|
-
const
|
|
143
|
+
formatText (transaction, parent, length, formats) {
|
|
144
|
+
minimizeFormatChanges(this, formats)
|
|
145
|
+
const negatedFormats = insertFormats(transaction, parent, this, formats)
|
|
106
146
|
// iterate until first non-format or null is found
|
|
107
|
-
// delete all formats with
|
|
108
|
-
// also check the
|
|
147
|
+
// delete all formats with formats[format.key] != null
|
|
148
|
+
// also check the formats after the first non-format as we do not want to insert redundant negated formats there
|
|
109
149
|
// eslint-disable-next-line no-labels
|
|
110
150
|
iterationLoop: while (
|
|
111
151
|
this.right !== null &&
|
|
112
152
|
(length > 0 ||
|
|
113
153
|
(
|
|
114
|
-
|
|
115
|
-
((this.right.deleted && this.
|
|
154
|
+
negatedFormats.size > 0 &&
|
|
155
|
+
((this.right.deleted && this.renderer.contentLength(this.right) === 0) || this.right.content.constructor === ContentFormat)
|
|
116
156
|
)
|
|
117
157
|
)
|
|
118
158
|
) {
|
|
@@ -120,34 +160,34 @@ export class ItemTextListPosition {
|
|
|
120
160
|
case ContentFormat: {
|
|
121
161
|
if (!this.right.deleted) {
|
|
122
162
|
const { key, value } = /** @type {ContentFormat} */ (this.right.content)
|
|
123
|
-
const attr =
|
|
163
|
+
const attr = formats[key]
|
|
124
164
|
if (attr !== undefined) {
|
|
125
|
-
if (
|
|
126
|
-
|
|
165
|
+
if (equalFormats(attr, value)) {
|
|
166
|
+
negatedFormats.delete(key)
|
|
127
167
|
} else {
|
|
128
168
|
if (length === 0) {
|
|
129
|
-
// no need to further extend
|
|
169
|
+
// no need to further extend negatedFormats
|
|
130
170
|
// eslint-disable-next-line no-labels
|
|
131
171
|
break iterationLoop
|
|
132
172
|
}
|
|
133
|
-
|
|
173
|
+
negatedFormats.set(key, value)
|
|
134
174
|
}
|
|
135
175
|
this.right.delete(transaction)
|
|
136
176
|
} else {
|
|
137
|
-
this.
|
|
177
|
+
this.currentFormats.set(key, value)
|
|
138
178
|
}
|
|
139
179
|
}
|
|
140
180
|
break
|
|
141
181
|
}
|
|
142
182
|
default: {
|
|
143
183
|
const item = this.right
|
|
144
|
-
const rightLen = this.
|
|
184
|
+
const rightLen = this.renderer.contentLength(item)
|
|
145
185
|
if (length < rightLen) {
|
|
146
186
|
/**
|
|
147
|
-
* @type {Array<
|
|
187
|
+
* @type {Array<AttributedContent<any>>}
|
|
148
188
|
*/
|
|
149
189
|
const contents = []
|
|
150
|
-
this.
|
|
190
|
+
this.renderer.readContent(contents, item.id.client, item.id.clock, item.deleted, item.content, 0)
|
|
151
191
|
let i = 0
|
|
152
192
|
for (; i < contents.length && length > 0; i++) {
|
|
153
193
|
const c = contents[i]
|
|
@@ -170,7 +210,7 @@ export class ItemTextListPosition {
|
|
|
170
210
|
if (length > 0) {
|
|
171
211
|
throw new Error('Exceeded content range')
|
|
172
212
|
}
|
|
173
|
-
|
|
213
|
+
insertNegatedFormats(transaction, parent, this, negatedFormats)
|
|
174
214
|
}
|
|
175
215
|
}
|
|
176
216
|
|
|
@@ -180,32 +220,32 @@ export class ItemTextListPosition {
|
|
|
180
220
|
* @param {Transaction} transaction
|
|
181
221
|
* @param {YType} parent
|
|
182
222
|
* @param {ItemTextListPosition} currPos
|
|
183
|
-
* @param {Map<string,any>}
|
|
223
|
+
* @param {Map<string,any>} negatedFormats
|
|
184
224
|
*
|
|
185
225
|
* @private
|
|
186
226
|
* @function
|
|
187
227
|
*/
|
|
188
|
-
const
|
|
189
|
-
// check if we really need to remove
|
|
228
|
+
const insertNegatedFormats = (transaction, parent, currPos, negatedFormats) => {
|
|
229
|
+
// check if we really need to remove formats
|
|
190
230
|
while (
|
|
191
231
|
currPos.right !== null && (
|
|
192
|
-
(currPos.right.deleted && (currPos.
|
|
232
|
+
(currPos.right.deleted && (currPos.renderer === baseRenderer || currPos.renderer.contentLength(currPos.right) === 0)) || (
|
|
193
233
|
currPos.right.content.constructor === ContentFormat &&
|
|
194
|
-
|
|
234
|
+
equalFormats(negatedFormats.get(/** @type {ContentFormat} */ (currPos.right.content).key), /** @type {ContentFormat} */ (currPos.right.content).value)
|
|
195
235
|
)
|
|
196
236
|
)
|
|
197
237
|
) {
|
|
198
238
|
if (!currPos.right.deleted) {
|
|
199
|
-
|
|
239
|
+
negatedFormats.delete(/** @type {ContentFormat} */ (currPos.right.content).key)
|
|
200
240
|
}
|
|
201
241
|
currPos.forward()
|
|
202
242
|
}
|
|
203
243
|
const doc = transaction.doc
|
|
204
244
|
const ownClientId = doc.clientID
|
|
205
|
-
|
|
245
|
+
negatedFormats.forEach((val, key) => {
|
|
206
246
|
const left = currPos.left
|
|
207
247
|
const right = currPos.right
|
|
208
|
-
const nextFormat = new Item(createID(ownClientId,
|
|
248
|
+
const nextFormat = new Item(createID(ownClientId, doc.store.getClock(ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
|
|
209
249
|
nextFormat.integrate(transaction, 0)
|
|
210
250
|
currPos.right = nextFormat
|
|
211
251
|
currPos.forward()
|
|
@@ -213,34 +253,34 @@ const insertNegatedAttributes = (transaction, parent, currPos, negatedAttributes
|
|
|
213
253
|
}
|
|
214
254
|
|
|
215
255
|
/**
|
|
216
|
-
* @param {Map<string,any>}
|
|
256
|
+
* @param {Map<string,any>} currentFormats
|
|
217
257
|
* @param {ContentFormat} format
|
|
218
258
|
*
|
|
219
259
|
* @private
|
|
220
260
|
* @function
|
|
221
261
|
*/
|
|
222
|
-
const
|
|
262
|
+
const updateCurrentFormats = (currentFormats, format) => {
|
|
223
263
|
const { key, value } = format
|
|
224
264
|
if (value === null) {
|
|
225
|
-
|
|
265
|
+
currentFormats.delete(key)
|
|
226
266
|
} else {
|
|
227
|
-
|
|
267
|
+
currentFormats.set(key, value)
|
|
228
268
|
}
|
|
229
269
|
}
|
|
230
270
|
|
|
231
271
|
/**
|
|
232
272
|
* @param {ItemTextListPosition} currPos
|
|
233
|
-
* @param {Object<string,any>}
|
|
273
|
+
* @param {Object<string,any>} formats
|
|
234
274
|
*
|
|
235
275
|
* @private
|
|
236
276
|
* @function
|
|
237
277
|
*/
|
|
238
|
-
const
|
|
239
|
-
// go right while
|
|
278
|
+
const minimizeFormatChanges = (currPos, formats) => {
|
|
279
|
+
// go right while formats[right.key] === right.value (or right is deleted)
|
|
240
280
|
while (true) {
|
|
241
281
|
if (currPos.right === null) {
|
|
242
282
|
break
|
|
243
|
-
} else if (currPos.right.deleted ? (currPos.
|
|
283
|
+
} else if (currPos.right.deleted ? (currPos.renderer.contentLength(currPos.right) === 0) : (!currPos.right.deleted && currPos.right.content.constructor === ContentFormat && equalFormats(formats[(/** @type {ContentFormat} */ (currPos.right.content)).key] ?? null, /** @type {ContentFormat} */ (currPos.right.content).value))) {
|
|
244
284
|
//
|
|
245
285
|
} else {
|
|
246
286
|
break
|
|
@@ -253,30 +293,30 @@ const minimizeAttributeChanges = (currPos, attributes) => {
|
|
|
253
293
|
* @param {Transaction} transaction
|
|
254
294
|
* @param {YType} parent
|
|
255
295
|
* @param {ItemTextListPosition} currPos
|
|
256
|
-
* @param {Object<string,any>}
|
|
296
|
+
* @param {Object<string,any>} formats
|
|
257
297
|
* @return {Map<string,any>}
|
|
258
298
|
*
|
|
259
299
|
* @private
|
|
260
300
|
* @function
|
|
261
301
|
**/
|
|
262
|
-
const
|
|
302
|
+
const insertFormats = (transaction, parent, currPos, formats) => {
|
|
263
303
|
const doc = transaction.doc
|
|
264
304
|
const ownClientId = doc.clientID
|
|
265
|
-
const
|
|
305
|
+
const negatedFormats = new Map()
|
|
266
306
|
// insert format-start items
|
|
267
|
-
for (const key in
|
|
268
|
-
const val =
|
|
269
|
-
const currentVal = currPos.
|
|
270
|
-
if (!
|
|
271
|
-
// save negated
|
|
272
|
-
|
|
307
|
+
for (const key in formats) {
|
|
308
|
+
const val = formats[key]
|
|
309
|
+
const currentVal = currPos.currentFormats.get(key) ?? null
|
|
310
|
+
if (!equalFormats(currentVal, val)) {
|
|
311
|
+
// save negated format (set null if currentVal undefined)
|
|
312
|
+
negatedFormats.set(key, currentVal)
|
|
273
313
|
const { left, right } = currPos
|
|
274
|
-
currPos.right = new Item(createID(ownClientId,
|
|
314
|
+
currPos.right = new Item(createID(ownClientId, doc.store.getClock(ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
|
|
275
315
|
currPos.right.integrate(transaction, 0)
|
|
276
316
|
currPos.forward()
|
|
277
317
|
}
|
|
278
318
|
}
|
|
279
|
-
return
|
|
319
|
+
return negatedFormats
|
|
280
320
|
}
|
|
281
321
|
|
|
282
322
|
/**
|
|
@@ -284,31 +324,31 @@ const insertAttributes = (transaction, parent, currPos, attributes) => {
|
|
|
284
324
|
* @param {YType} parent
|
|
285
325
|
* @param {ItemTextListPosition} currPos
|
|
286
326
|
* @param {import('./structs/Item.js').AbstractContent} content
|
|
287
|
-
* @param {Object<string,any>}
|
|
327
|
+
* @param {Object<string,any>} formats
|
|
288
328
|
*
|
|
289
329
|
* @private
|
|
290
330
|
* @function
|
|
291
331
|
**/
|
|
292
|
-
export const insertContent = (transaction, parent, currPos, content,
|
|
293
|
-
currPos.
|
|
294
|
-
if (
|
|
295
|
-
|
|
332
|
+
export const insertContent = (transaction, parent, currPos, content, formats) => {
|
|
333
|
+
currPos.currentFormats.forEach((_val, key) => {
|
|
334
|
+
if (formats[key] === undefined) {
|
|
335
|
+
formats[key] = null
|
|
296
336
|
}
|
|
297
337
|
})
|
|
298
338
|
const doc = transaction.doc
|
|
299
339
|
const ownClientId = doc.clientID
|
|
300
|
-
|
|
301
|
-
const
|
|
340
|
+
minimizeFormatChanges(currPos, formats)
|
|
341
|
+
const negatedFormats = insertFormats(transaction, parent, currPos, formats)
|
|
302
342
|
let { left, right, index } = currPos
|
|
303
343
|
if (parent._searchMarker) {
|
|
304
344
|
updateMarkerChanges(parent._searchMarker, currPos.index, content.getLength())
|
|
305
345
|
}
|
|
306
|
-
right = new Item(createID(ownClientId,
|
|
346
|
+
right = new Item(createID(ownClientId, doc.store.getClock(ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, content)
|
|
307
347
|
right.integrate(transaction, 0)
|
|
308
348
|
currPos.right = right
|
|
309
349
|
currPos.index = index
|
|
310
350
|
currPos.forward()
|
|
311
|
-
|
|
351
|
+
insertNegatedFormats(transaction, parent, currPos, negatedFormats)
|
|
312
352
|
}
|
|
313
353
|
|
|
314
354
|
/**
|
|
@@ -316,27 +356,27 @@ export const insertContent = (transaction, parent, currPos, content, attributes)
|
|
|
316
356
|
* @param {YType} parent
|
|
317
357
|
* @param {ItemTextListPosition} currPos
|
|
318
358
|
* @param {Array<any>|string} insert
|
|
319
|
-
* @param {Object<string,any>}
|
|
359
|
+
* @param {Object<string,any>} formats
|
|
320
360
|
*/
|
|
321
|
-
export const insertContentHelper = (transaction, parent, currPos, insert,
|
|
361
|
+
export const insertContentHelper = (transaction, parent, currPos, insert, formats) => {
|
|
322
362
|
if (s.$string.check(insert)) {
|
|
323
|
-
insertContent(transaction, parent, currPos, new ContentString(insert),
|
|
363
|
+
insertContent(transaction, parent, currPos, new ContentString(insert), formats)
|
|
324
364
|
} else {
|
|
325
365
|
insert = insert.map(ins => delta.$deltaAny.check(ins) ? YType.from(ins) : ins)
|
|
326
366
|
for (let i = 0; i < insert.length;) {
|
|
327
367
|
const first = insert[i]
|
|
328
368
|
if (first instanceof YType) {
|
|
329
|
-
insertContent(transaction, parent, currPos, new ContentType(first),
|
|
369
|
+
insertContent(transaction, parent, currPos, new ContentType(first), formats)
|
|
330
370
|
i++
|
|
331
|
-
} else if (first
|
|
332
|
-
insertContent(transaction, parent, currPos,
|
|
371
|
+
} else if ($ydoc.check(first)) {
|
|
372
|
+
insertContent(transaction, parent, currPos, createContentDocFromDoc(first), formats)
|
|
333
373
|
i++
|
|
334
374
|
} else {
|
|
335
375
|
// insert "any" content
|
|
336
376
|
// compute slice len
|
|
337
377
|
let j = i + 1
|
|
338
|
-
for (; j < insert.length && !(insert[j] instanceof YType || insert[j]
|
|
339
|
-
insertContent(transaction, parent, currPos, new ContentAny((i === 0 && j === insert.length) ? insert : insert.slice(i, j)),
|
|
378
|
+
for (; j < insert.length && !(insert[j] instanceof YType || $ydoc.check(insert[j])); j++) { /* nop */ }
|
|
379
|
+
insertContent(transaction, parent, currPos, new ContentAny((i === 0 && j === insert.length) ? insert : insert.slice(i, j)), formats)
|
|
340
380
|
i = j
|
|
341
381
|
}
|
|
342
382
|
}
|
|
@@ -354,7 +394,7 @@ export const insertContentHelper = (transaction, parent, currPos, insert, attrib
|
|
|
354
394
|
*/
|
|
355
395
|
export const deleteText = (transaction, currPos, length) => {
|
|
356
396
|
const startLength = length
|
|
357
|
-
const
|
|
397
|
+
const startFormats = map.copy(currPos.currentFormats)
|
|
358
398
|
const start = currPos.right
|
|
359
399
|
while (length > 0 && currPos.right !== null) {
|
|
360
400
|
const item = currPos.right
|
|
@@ -364,12 +404,12 @@ export const deleteText = (transaction, currPos, length) => {
|
|
|
364
404
|
}
|
|
365
405
|
length -= item.length
|
|
366
406
|
item.delete(transaction)
|
|
367
|
-
} else if (currPos.
|
|
407
|
+
} else if (currPos.renderer !== baseRenderer) {
|
|
368
408
|
/**
|
|
369
|
-
* @type {Array<
|
|
409
|
+
* @type {Array<AttributedContent<any>>}
|
|
370
410
|
*/
|
|
371
411
|
const contents = []
|
|
372
|
-
currPos.
|
|
412
|
+
currPos.renderer.readContent(contents, item.id.client, item.id.clock, true, item.content, 0)
|
|
373
413
|
for (let i = 0; i < contents.length; i++) {
|
|
374
414
|
const c = contents[i]
|
|
375
415
|
if (c.content.isCountable() && c.attrs != null) {
|
|
@@ -390,7 +430,7 @@ export const deleteText = (transaction, currPos, length) => {
|
|
|
390
430
|
currPos.forward()
|
|
391
431
|
}
|
|
392
432
|
if (start) {
|
|
393
|
-
cleanupFormattingGap(transaction, start, currPos.right,
|
|
433
|
+
cleanupFormattingGap(transaction, start, currPos.right, startFormats, currPos.currentFormats)
|
|
394
434
|
}
|
|
395
435
|
const parent = /** @type {YType<any>} */ (/** @type {Item} */ (currPos.left || currPos.right).parent)
|
|
396
436
|
if (parent._searchMarker) {
|
|
@@ -593,14 +633,25 @@ export const callTypeObservers = (type, transaction, event) => {
|
|
|
593
633
|
}
|
|
594
634
|
|
|
595
635
|
/**
|
|
596
|
-
* Abstract Yjs Type class
|
|
636
|
+
* Abstract Yjs Type class.
|
|
637
|
+
*
|
|
638
|
+
* A `YType` is a {@link https://github.com/dmonad/lib0 lib0} `RDT` ("replicated data type", see
|
|
639
|
+
* `lib0/delta/rdt.js`): it emits a `'delta'` event whenever its state changes, accepts foreign
|
|
640
|
+
* changes via {@link YType#applyDelta}, exposes its delta {@link YType#$delta schema}, and can be
|
|
641
|
+
* torn down via {@link YType#destroy}. This lets a `YType` be `bind()`-ed to any other RDT (another
|
|
642
|
+
* `YType`, an in-memory delta, a DOM subtree, …). The legacy {@link YType#observe `observe`} /
|
|
643
|
+
* {@link YType#observeDeep `observeDeep`} `YEvent` API continues to work alongside the `'delta'`
|
|
644
|
+
* channel.
|
|
645
|
+
*
|
|
597
646
|
* @template {delta.DeltaConf} [DConf=any]
|
|
647
|
+
* @extends {ObservableV2<{ delta: (delta: delta.Delta<DConf>) => void, destroy: (type: YType<DConf>) => void }>}
|
|
598
648
|
*/
|
|
599
|
-
export class YType {
|
|
649
|
+
export class YType extends ObservableV2 {
|
|
600
650
|
/**
|
|
601
651
|
* @param {delta.DeltaConfGetName<DConf>?} name
|
|
602
652
|
*/
|
|
603
653
|
constructor (name = null) {
|
|
654
|
+
super()
|
|
604
655
|
/**
|
|
605
656
|
* @type {delta.DeltaConfGetName<DConf>}
|
|
606
657
|
*/
|
|
@@ -637,20 +688,121 @@ export class YType {
|
|
|
637
688
|
*/
|
|
638
689
|
this._searchMarker = null
|
|
639
690
|
/**
|
|
640
|
-
* @
|
|
641
|
-
*
|
|
691
|
+
* Maintained deep-delta cache backing {@link YType#delta}. `null` until `delta` is first
|
|
692
|
+
* accessed; thereafter kept current on every event of this type (incrementally, by applying the
|
|
693
|
+
* deep change) and re-diffed by {@link YType#useRenderer}. Cleared by {@link YType#clearCache}.
|
|
694
|
+
* @type {delta.DeltaBuilderAny | null}
|
|
642
695
|
*/
|
|
643
|
-
this.
|
|
644
|
-
this._legacyTypeRef = this.name == null ?
|
|
696
|
+
this._delta = null
|
|
697
|
+
this._legacyTypeRef = this.name == null ? YXmlFragmentRefID : YXmlElementRefID
|
|
645
698
|
/**
|
|
646
699
|
* @type {Array<ArraySearchMarker>|null}
|
|
647
700
|
*/
|
|
648
701
|
this._searchMarker = []
|
|
649
702
|
/**
|
|
650
|
-
* Whether this YText contains
|
|
703
|
+
* Whether this YText contains formats.
|
|
651
704
|
* This flag is updated when a formatting item is integrated (see ContentFormat.integrate)
|
|
652
705
|
*/
|
|
653
706
|
this._hasFormatting = false
|
|
707
|
+
/**
|
|
708
|
+
* The active default renderer. Used by `toDelta`, `applyDelta`, and the events whenever no
|
|
709
|
+
* explicit renderer is passed. Change it via {@link YType#useRenderer}.
|
|
710
|
+
* @type {AbstractRenderer}
|
|
711
|
+
*/
|
|
712
|
+
this._renderer = baseRenderer
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Schema of the deltas this type produces — part of the lib0 `RDT` interface.
|
|
717
|
+
*
|
|
718
|
+
* @type {s.Schema<delta.Delta<DConf>>}
|
|
719
|
+
*/
|
|
720
|
+
get $delta () {
|
|
721
|
+
return /** @type {any} */ (delta.$deltaAny)
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* The deep delta of this type (the full nested content tree, children rendered as their own
|
|
726
|
+
* deltas).
|
|
727
|
+
*
|
|
728
|
+
* The returned value is the type's **live** maintained cache: it is materialized on first access
|
|
729
|
+
* and then kept current on every event fired on this type (and re-diffed by
|
|
730
|
+
* {@link YType#useRenderer}), so a reference held across edits keeps updating in place. Clone it
|
|
731
|
+
* (e.g. `type.delta.clone()`) if you need a stable snapshot, and call {@link YType#clearCache} to
|
|
732
|
+
* drop the cache.
|
|
733
|
+
*
|
|
734
|
+
* @type {delta.Delta<DConf>}
|
|
735
|
+
*/
|
|
736
|
+
get delta () {
|
|
737
|
+
if (this._delta === null) {
|
|
738
|
+
this._delta = this._renderDelta()
|
|
739
|
+
}
|
|
740
|
+
return /** @type {any} */ (this._delta)
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Render the full deep current state into a fresh `isFinal` builder (so subsequent `.apply`s of
|
|
745
|
+
* deep changes update content in place). Uses this type's active renderer.
|
|
746
|
+
*
|
|
747
|
+
* @return {delta.DeltaBuilderAny}
|
|
748
|
+
*/
|
|
749
|
+
_renderDelta () {
|
|
750
|
+
const state = /** @type {delta.DeltaBuilderAny} */ (delta.create(this.name))
|
|
751
|
+
state.isFinal = true
|
|
752
|
+
state.apply(this.toDelta({ deep: true }))
|
|
753
|
+
return state
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Discard the cached deep delta backing {@link YType#delta}.
|
|
758
|
+
*
|
|
759
|
+
* After `delta` is first accessed, the cache is updated on every event fired on this type (and
|
|
760
|
+
* re-diffed by {@link YType#useRenderer}). Call this to drop it — e.g. to reclaim memory, or to
|
|
761
|
+
* force an exact recomputation after editing while a non-base renderer is active (the incremental
|
|
762
|
+
* updates can drift from a fresh deep render in that case).
|
|
763
|
+
*/
|
|
764
|
+
clearCache () {
|
|
765
|
+
this._delta = null
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Change the default renderer used by this type. After calling `useRenderer(renderer)`, the
|
|
770
|
+
* `toDelta`, `applyDelta`, and event methods all use `renderer` whenever no explicit renderer is
|
|
771
|
+
* passed (an explicit `{ renderer }` argument still overrides it per call).
|
|
772
|
+
*
|
|
773
|
+
* If the deep-delta cache ({@link YType#delta}) is being maintained, or a `'delta'` listener is
|
|
774
|
+
* attached, the content is re-rendered with the new renderer and the difference is emitted on the
|
|
775
|
+
* `'delta'` channel only (a renderer switch is not a CRDT change, so no `YEvent` is produced).
|
|
776
|
+
*
|
|
777
|
+
* @param {AbstractRenderer} renderer
|
|
778
|
+
* @return {this}
|
|
779
|
+
*/
|
|
780
|
+
useRenderer (renderer) {
|
|
781
|
+
const prev = this._renderer
|
|
782
|
+
const hasDeltaListeners = (this._observers.get('delta')?.size ?? 0) > 0
|
|
783
|
+
if (renderer !== prev && (this._delta !== null || hasDeltaListeners)) {
|
|
784
|
+
const oldState = this._delta ?? this._renderDelta()
|
|
785
|
+
this._renderer = renderer
|
|
786
|
+
const newState = this._renderDelta()
|
|
787
|
+
if (this._delta !== null) this._delta = newState
|
|
788
|
+
if (hasDeltaListeners) {
|
|
789
|
+
const d = /** @type {any} */ (delta.diff(/** @type {any} */ (oldState), /** @type {any} */ (newState)))
|
|
790
|
+
if (!d.isEmpty()) this.emit('delta', [d])
|
|
791
|
+
}
|
|
792
|
+
} else {
|
|
793
|
+
this._renderer = renderer
|
|
794
|
+
}
|
|
795
|
+
return this
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Tear down this type as an `RDT`: emit the `'destroy'` event and unregister all `'delta'` /
|
|
800
|
+
* `'destroy'` listeners. The CRDT content and the `observe`/`observeDeep` handlers are left
|
|
801
|
+
* untouched — this only releases the RDT/binding observers.
|
|
802
|
+
*/
|
|
803
|
+
destroy () {
|
|
804
|
+
this.emit('destroy', [this])
|
|
805
|
+
super.destroy()
|
|
654
806
|
}
|
|
655
807
|
|
|
656
808
|
/**
|
|
@@ -722,6 +874,9 @@ export class YType {
|
|
|
722
874
|
_callObserver (transaction, parentSubs) {
|
|
723
875
|
const event = new YEvent(/** @type {any} */ (this), transaction, parentSubs)
|
|
724
876
|
callTypeObservers(/** @type {any} */ (this), transaction, event)
|
|
877
|
+
// Note: the RDT `'delta'` channel (and the deep-delta cache) is driven in the transaction
|
|
878
|
+
// cleanup's `changedParentTypes` loop (see Transaction.js) so it bubbles to ancestors like
|
|
879
|
+
// `observeDeep`, not here where only the directly-changed type is visible.
|
|
725
880
|
if (!transaction.local && this._searchMarker) {
|
|
726
881
|
this._searchMarker.length = 0
|
|
727
882
|
}
|
|
@@ -782,55 +937,57 @@ export class YType {
|
|
|
782
937
|
*
|
|
783
938
|
* @template {boolean} [Deep=false]
|
|
784
939
|
*
|
|
785
|
-
* @param {AbstractAttributionManager} am
|
|
786
940
|
* @param {Object} [opts]
|
|
787
|
-
* @param {
|
|
941
|
+
* @param {AbstractRenderer} [opts.renderer] - renders the content (with attributions); defaults to this type's active renderer (see {@link YType#useRenderer}), i.e. `baseRenderer` unless changed
|
|
942
|
+
* @param {IdSet?} [opts.itemsToRender]
|
|
788
943
|
* @param {boolean} [opts.retainInserts] - if true, retain rendered inserts with attributions
|
|
789
944
|
* @param {boolean} [opts.retainDeletes] - if true, retain rendered+attributed deletes only
|
|
790
|
-
* @param {
|
|
945
|
+
* @param {IdSet?} [opts.deletedItems] - used for computing prevItem in attributes
|
|
791
946
|
* @param {Map<YType,Set<string|null>>|null} [opts.modified] - set of types that should be rendered as modified children
|
|
792
947
|
* @param {Deep} [opts.deep] - render child types as delta
|
|
793
948
|
* @return {Deep extends true ? delta.Delta<DConf> : delta.Delta<DeltaConfDeltaToYType<DConf>>} The Delta representation of this type.
|
|
794
949
|
*
|
|
795
950
|
* @public
|
|
796
951
|
*/
|
|
797
|
-
toDelta (
|
|
798
|
-
const { itemsToRender = null, retainInserts = false, retainDeletes = false, deletedItems = null,
|
|
952
|
+
toDelta (opts = {}) {
|
|
953
|
+
const { renderer = this._renderer, itemsToRender = null, retainInserts = false, retainDeletes = false, deletedItems = null, deep = false } = opts
|
|
954
|
+
const { modified = (deep && itemsToRender) ? computeModifiedFromItems(/** @type {Doc} */ (this.doc).store, itemsToRender) : null } = opts
|
|
799
955
|
const renderAttrs = modified?.get(this) || null
|
|
800
|
-
const renderChildren =
|
|
956
|
+
const renderChildren = modified == null || !modified.has(this) || /** @type {Set<string|null>} */ (modified.get(this)).has(null)
|
|
801
957
|
/**
|
|
802
958
|
* @type {delta.DeltaBuilderAny}
|
|
803
959
|
*/
|
|
804
960
|
const d = /** @type {any} */ (delta.create(this.name))
|
|
805
|
-
const optsAll =
|
|
806
|
-
|
|
961
|
+
const optsAll = object.assign({}, opts, { renderer, modified })
|
|
962
|
+
// opts has been re-computed - do not use opts after this point!
|
|
963
|
+
typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, renderer, deep, modified, deletedItems, itemsToRender, optsAll, optsAll)
|
|
807
964
|
if (renderChildren) {
|
|
808
965
|
/**
|
|
809
|
-
* @type {delta.
|
|
966
|
+
* @type {delta.Formats}
|
|
810
967
|
*/
|
|
811
|
-
let
|
|
812
|
-
let
|
|
968
|
+
let currentFormats = {} // saves all current formats for insert
|
|
969
|
+
let usingCurrentFormats = false
|
|
813
970
|
/**
|
|
814
|
-
* @type {delta.
|
|
971
|
+
* @type {delta.Formats}
|
|
815
972
|
*/
|
|
816
|
-
let
|
|
817
|
-
let
|
|
973
|
+
let changedFormats = {} // saves changed formats for retain
|
|
974
|
+
let usingChangedFormats = false
|
|
818
975
|
/**
|
|
819
|
-
* Logic for
|
|
820
|
-
* Everything that comes after
|
|
976
|
+
* Logic for format attribution
|
|
977
|
+
* Everything that comes after a format is formatted by the user that created it.
|
|
821
978
|
* Two exceptions:
|
|
822
979
|
* - the user resets formatting to the previously known formatting that is not attributed
|
|
823
|
-
* - the user deletes a
|
|
980
|
+
* - the user deletes a format and hence restores the previously known formatting
|
|
824
981
|
* that is not attributed.
|
|
825
|
-
* @type {delta.
|
|
982
|
+
* @type {delta.Formats}
|
|
826
983
|
*/
|
|
827
|
-
const
|
|
984
|
+
const previousUnattributedFormats = {} // contains previously known unattributed formatting
|
|
828
985
|
/**
|
|
829
|
-
* @type {delta.
|
|
986
|
+
* @type {delta.Formats}
|
|
830
987
|
*/
|
|
831
|
-
const
|
|
988
|
+
const previousFormats = {} // The value before changes
|
|
832
989
|
/**
|
|
833
|
-
* @type {Array<
|
|
990
|
+
* @type {Array<AttributedContent<any>>}
|
|
834
991
|
*/
|
|
835
992
|
const cs = []
|
|
836
993
|
for (let item = this._start; item !== null; cs.length = 0) {
|
|
@@ -844,12 +1001,12 @@ export class YType {
|
|
|
844
1001
|
if (ir !== rslice.length - 1) {
|
|
845
1002
|
itemContent = itemContent.splice(idrange.len)
|
|
846
1003
|
}
|
|
847
|
-
|
|
1004
|
+
renderer.readContent(cs, item.id.client, idrange.clock, item.deleted, content, idrange.exists ? 2 : 0)
|
|
848
1005
|
}
|
|
849
1006
|
}
|
|
850
1007
|
} else {
|
|
851
1008
|
for (; item !== null && cs.length < 50; item = item.right) {
|
|
852
|
-
|
|
1009
|
+
renderer.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
|
|
853
1010
|
}
|
|
854
1011
|
}
|
|
855
1012
|
for (let i = 0; i < cs.length; i++) {
|
|
@@ -857,11 +1014,11 @@ export class YType {
|
|
|
857
1014
|
// render (attributed) content even if it was deleted
|
|
858
1015
|
const renderContent = c.render && (!c.deleted || c.attrs != null)
|
|
859
1016
|
// content that was just deleted. It is not rendered as an insertion, because it doesn't
|
|
860
|
-
// have any
|
|
1017
|
+
// have any formats.
|
|
861
1018
|
const renderDelete = c.render && c.deleted
|
|
862
|
-
// existing content that should be retained, only adding changed
|
|
1019
|
+
// existing content that should be retained, only adding changed formats
|
|
863
1020
|
const retainContent = !c.render && (!c.deleted || c.attrs != null)
|
|
864
|
-
const attribution = (renderContent || c.content.constructor === ContentFormat) ? createAttributionFromAttributionItems(c.attrs, c.deleted) :
|
|
1021
|
+
const attribution = (renderContent || c.content.constructor === ContentFormat) ? createAttributionFromAttributionItems(c.attrs, c.deleted) : undefined
|
|
865
1022
|
switch (c.content.constructor) {
|
|
866
1023
|
case ContentDeleted: {
|
|
867
1024
|
if (renderDelete) d.delete(c.content.getLength())
|
|
@@ -869,18 +1026,26 @@ export class YType {
|
|
|
869
1026
|
}
|
|
870
1027
|
case ContentString:
|
|
871
1028
|
if (renderContent) {
|
|
872
|
-
d.usedAttributes = currentAttributes
|
|
873
|
-
usingCurrentAttributes = true
|
|
874
1029
|
if (c.deleted ? retainDeletes : retainInserts) {
|
|
875
|
-
|
|
1030
|
+
// a retain expresses the format *diff* against existing (cached) content, so use
|
|
1031
|
+
// `changedFormats`: a format removed this change (e.g. its marker was deleted)
|
|
1032
|
+
// is present there as a `null` clear, whereas `currentFormats` (absolute) can
|
|
1033
|
+
// only re-assert present formats and would silently keep a stale one.
|
|
1034
|
+
d.usedFormats = changedFormats
|
|
1035
|
+
usingChangedFormats = true
|
|
1036
|
+
// change render: a retained item with no attribution means its attribution was
|
|
1037
|
+
// removed → emit `null` (clear) rather than `{}` (skip). Present attribution merges.
|
|
1038
|
+
d.retain(/** @type {ContentString} */ (c.content).str.length, undefined, attribution ?? null)
|
|
876
1039
|
} else {
|
|
877
|
-
d.
|
|
1040
|
+
d.usedFormats = currentFormats
|
|
1041
|
+
usingCurrentFormats = true
|
|
1042
|
+
d.insert(/** @type {ContentString} */ (c.content).str, undefined, attribution)
|
|
878
1043
|
}
|
|
879
1044
|
} else if (renderDelete) {
|
|
880
1045
|
d.delete(c.content.getLength())
|
|
881
1046
|
} else if (retainContent) {
|
|
882
|
-
d.
|
|
883
|
-
|
|
1047
|
+
d.usedFormats = changedFormats
|
|
1048
|
+
usingChangedFormats = true
|
|
884
1049
|
d.retain(c.content.getLength())
|
|
885
1050
|
}
|
|
886
1051
|
break
|
|
@@ -890,114 +1055,153 @@ export class YType {
|
|
|
890
1055
|
case ContentType:
|
|
891
1056
|
case ContentBinary:
|
|
892
1057
|
if (renderContent) {
|
|
893
|
-
d.usedAttributes = currentAttributes
|
|
894
|
-
usingCurrentAttributes = true
|
|
895
1058
|
if (c.deleted ? retainDeletes : retainInserts) {
|
|
896
|
-
|
|
1059
|
+
// a retain expresses the format *diff* → use `changedFormats` (see ContentString)
|
|
1060
|
+
d.usedFormats = changedFormats
|
|
1061
|
+
usingChangedFormats = true
|
|
1062
|
+
if (c.deleted && c.content.constructor === ContentType) {
|
|
1063
|
+
// @todo use current transaction instead
|
|
1064
|
+
d.modify(/** @type {any} */ (c.content).type.toDelta(optsAll), undefined, attribution ?? null)
|
|
1065
|
+
} else {
|
|
1066
|
+
d.retain(c.content.getLength(), undefined, attribution ?? null)
|
|
1067
|
+
}
|
|
897
1068
|
} else if (deep && c.content.constructor === ContentType) {
|
|
898
|
-
d.
|
|
1069
|
+
d.usedFormats = currentFormats
|
|
1070
|
+
usingCurrentFormats = true
|
|
1071
|
+
d.insert([/** @type {any} */(c.content).type.toDelta(optsAll)], undefined, attribution)
|
|
899
1072
|
} else {
|
|
900
|
-
d.
|
|
1073
|
+
d.usedFormats = currentFormats
|
|
1074
|
+
usingCurrentFormats = true
|
|
1075
|
+
d.insert(c.content.getContent(), undefined, attribution)
|
|
901
1076
|
}
|
|
902
1077
|
} else if (renderDelete) {
|
|
903
1078
|
d.delete(1)
|
|
904
1079
|
} else if (retainContent) {
|
|
905
1080
|
if (c.content.constructor === ContentType && modified?.has(/** @type {ContentType} */ (c.content).type)) {
|
|
906
1081
|
// @todo use current transaction instead
|
|
907
|
-
d.modify(/** @type {any} */ (c.content).type.toDelta(
|
|
1082
|
+
d.modify(/** @type {any} */ (c.content).type.toDelta(optsAll))
|
|
908
1083
|
} else {
|
|
909
|
-
d.
|
|
910
|
-
|
|
1084
|
+
d.usedFormats = changedFormats
|
|
1085
|
+
usingChangedFormats = true
|
|
911
1086
|
d.retain(1)
|
|
912
1087
|
}
|
|
913
1088
|
}
|
|
914
1089
|
break
|
|
915
1090
|
case ContentFormat: {
|
|
916
1091
|
const { key, value } = /** @type {ContentFormat} */ (c.content)
|
|
917
|
-
const
|
|
918
|
-
if (attribution != null && (c.deleted || !object.hasProperty(
|
|
919
|
-
|
|
1092
|
+
const currFormatVal = currentFormats[key] ?? null
|
|
1093
|
+
if (attribution != null && (c.deleted || !object.hasProperty(previousUnattributedFormats, key))) {
|
|
1094
|
+
previousUnattributedFormats[key] = c.deleted ? value : currFormatVal
|
|
920
1095
|
}
|
|
921
|
-
// @todo write a function "
|
|
922
|
-
// # Update
|
|
1096
|
+
// @todo write a function "updateCurrentFormats" and "updateChangedFormats"
|
|
1097
|
+
// # Update Formats
|
|
923
1098
|
if (renderContent || renderDelete) {
|
|
924
1099
|
// create fresh references
|
|
925
|
-
if (
|
|
926
|
-
|
|
927
|
-
|
|
1100
|
+
if (usingCurrentFormats) {
|
|
1101
|
+
currentFormats = object.assign({}, currentFormats)
|
|
1102
|
+
usingCurrentFormats = false
|
|
928
1103
|
}
|
|
929
|
-
if (
|
|
930
|
-
|
|
931
|
-
|
|
1104
|
+
if (usingChangedFormats) {
|
|
1105
|
+
usingChangedFormats = false
|
|
1106
|
+
changedFormats = object.assign({}, changedFormats)
|
|
932
1107
|
}
|
|
933
1108
|
}
|
|
934
1109
|
if (renderContent || renderDelete) {
|
|
935
1110
|
if (c.deleted) {
|
|
936
1111
|
// content was deleted, but is possibly attributed
|
|
937
|
-
if (!
|
|
938
|
-
if (
|
|
939
|
-
delete
|
|
1112
|
+
if (!equalFormats(value, currFormatVal)) { // do nothing if nothing changed
|
|
1113
|
+
if (equalFormats(currFormatVal, previousFormats[key] ?? null) && changedFormats[key] !== undefined) {
|
|
1114
|
+
delete changedFormats[key]
|
|
940
1115
|
} else {
|
|
941
|
-
|
|
1116
|
+
changedFormats[key] = currFormatVal
|
|
942
1117
|
}
|
|
943
|
-
// current
|
|
944
|
-
|
|
1118
|
+
// current formats doesn't change
|
|
1119
|
+
previousFormats[key] = value
|
|
945
1120
|
}
|
|
946
1121
|
} else { // !c.deleted
|
|
947
1122
|
// content was inserted, and is possibly attributed
|
|
948
|
-
if (
|
|
1123
|
+
if (equalFormats(value, currFormatVal)) {
|
|
949
1124
|
// item.delete(transaction)
|
|
950
|
-
} else if (
|
|
951
|
-
delete
|
|
1125
|
+
} else if (equalFormats(value, previousFormats[key] ?? null)) {
|
|
1126
|
+
delete changedFormats[key]
|
|
952
1127
|
} else {
|
|
953
|
-
|
|
1128
|
+
changedFormats[key] = value
|
|
954
1129
|
}
|
|
955
1130
|
if (value == null) {
|
|
956
|
-
delete
|
|
1131
|
+
delete currentFormats[key]
|
|
957
1132
|
} else {
|
|
958
|
-
|
|
1133
|
+
currentFormats[key] = value
|
|
959
1134
|
}
|
|
960
1135
|
}
|
|
961
1136
|
} else if (retainContent && !c.deleted) {
|
|
962
|
-
// fresh reference to
|
|
963
|
-
if (
|
|
964
|
-
|
|
965
|
-
|
|
1137
|
+
// fresh reference to currentFormats only
|
|
1138
|
+
if (usingCurrentFormats) {
|
|
1139
|
+
currentFormats = object.assign({}, currentFormats)
|
|
1140
|
+
usingCurrentFormats = false
|
|
966
1141
|
}
|
|
967
|
-
if (
|
|
968
|
-
|
|
969
|
-
|
|
1142
|
+
if (usingChangedFormats && changedFormats[key] !== undefined) {
|
|
1143
|
+
usingChangedFormats = false
|
|
1144
|
+
changedFormats = object.assign({}, changedFormats)
|
|
970
1145
|
}
|
|
971
1146
|
if (value == null) {
|
|
972
|
-
delete
|
|
1147
|
+
delete currentFormats[key]
|
|
973
1148
|
} else {
|
|
974
|
-
|
|
1149
|
+
currentFormats[key] = value
|
|
975
1150
|
}
|
|
976
|
-
delete
|
|
977
|
-
|
|
1151
|
+
delete changedFormats[key]
|
|
1152
|
+
previousFormats[key] = value
|
|
978
1153
|
}
|
|
979
1154
|
// # Update Attributions
|
|
980
|
-
|
|
1155
|
+
// A format marker deleted in a change render under an *attributing* renderer nets to no
|
|
1156
|
+
// attribution (its insert+delete suggestion cancels → `attribution == null`), yet it ends
|
|
1157
|
+
// the attributed range it opened: the following retained content must drop the stale
|
|
1158
|
+
// `{ format: { [key]: [] } }` the marker's insertion wrote to the cache. Emit an explicit
|
|
1159
|
+
// `null` leaf for the key (a context-wide `useAttribution(null)` cannot carry a per-key
|
|
1160
|
+
// clear). Conditions: only an attributing render (`renderer !== baseRenderer`; the base
|
|
1161
|
+
// renderer has no attributions to clear), and only when the deletion actually *removes*
|
|
1162
|
+
// the format — i.e. it reverts to no value (`currFormatVal == null`). If it reverts to a
|
|
1163
|
+
// still-present surrounding value (e.g. deleting a `bold:null` marker re-exposes an
|
|
1164
|
+
// enclosing attributed `bold:true`, as when re-bolding), the attribution is preserved.
|
|
1165
|
+
const isDeletedFormatClear = attribution == null && renderer !== baseRenderer && renderDelete && c.deleted && itemsToRender != null && currFormatVal == null && !equalFormats(value, currFormatVal)
|
|
1166
|
+
if (attribution != null || isDeletedFormatClear || object.hasProperty(previousUnattributedFormats, key)) {
|
|
981
1167
|
/**
|
|
982
|
-
* @type {
|
|
1168
|
+
* @type {Attribution}
|
|
983
1169
|
*/
|
|
984
1170
|
const formattingAttribution = object.assign({}, d.usedAttribution)
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
delete
|
|
990
|
-
|
|
1171
|
+
const changedAttributedFormats = /** @type {{ [key: string]: Array<any>|null }} */ (formattingAttribution.format = object.assign({}, formattingAttribution.format ?? {}))
|
|
1172
|
+
const sameAsPreviousAttributions = equalFormats(previousUnattributedFormats[key], currentFormats[key] ?? null)
|
|
1173
|
+
if (isDeletedFormatClear) {
|
|
1174
|
+
changedAttributedFormats[key] = null
|
|
1175
|
+
delete previousUnattributedFormats[key]
|
|
1176
|
+
} else if (attribution == null && !sameAsPreviousAttributions) {
|
|
1177
|
+
// skip
|
|
1178
|
+
} else if (attribution == null || sameAsPreviousAttributions) {
|
|
1179
|
+
// an unattributed format was found or an attributed format
|
|
1180
|
+
// was found that resets to the previous status. When this format item is
|
|
1181
|
+
// itself rendered this transaction (`renderContent || renderDelete`) in a change/diff
|
|
1182
|
+
// render (`itemsToRender != null`), it is the END of an attributed format range:
|
|
1183
|
+
// emit an explicit clear (a `null` leaf) so the retained content drops any stale
|
|
1184
|
+
// `{ format: { [key]: [] } }` from the maintained `delta` cache — a bare context-skip
|
|
1185
|
+
// (`delete`) would leave it in place. For a merely-retained (unchanged) boundary, or a
|
|
1186
|
+
// full insert render (removal is already modeled as absence in `currentFormats`),
|
|
1187
|
+
// just drop the key: a change render must not emit ops for unchanged ranges, and
|
|
1188
|
+
// inserts must stay free of a spurious `{ format: { [key]: null } }`.
|
|
1189
|
+
if (attribution != null && itemsToRender != null && (renderContent || renderDelete)) {
|
|
1190
|
+
changedAttributedFormats[key] = null
|
|
1191
|
+
} else {
|
|
1192
|
+
delete changedAttributedFormats[key]
|
|
1193
|
+
}
|
|
1194
|
+
delete previousUnattributedFormats[key]
|
|
991
1195
|
} else {
|
|
992
|
-
const by =
|
|
1196
|
+
const by = changedAttributedFormats[key] = (changedAttributedFormats[key]?.slice() ?? [])
|
|
993
1197
|
by.push(...((c.deleted ? attribution.delete : attribution.insert) ?? []))
|
|
994
|
-
const attributedAt = (c.deleted ? attribution.
|
|
1198
|
+
const attributedAt = (c.deleted ? attribution.deleteAt : attribution.insertAt)
|
|
995
1199
|
if (attributedAt) formattingAttribution.formatAt = attributedAt
|
|
996
1200
|
}
|
|
997
|
-
if (object.isEmpty(
|
|
1201
|
+
if (object.isEmpty(changedAttributedFormats)) {
|
|
998
1202
|
d.useAttribution(null)
|
|
999
|
-
} else if (attribution != null) {
|
|
1000
|
-
const attributedAt = (c.deleted ? attribution
|
|
1203
|
+
} else if (attribution != null || isDeletedFormatClear) {
|
|
1204
|
+
const attributedAt = (c.deleted ? attribution?.deleteAt : attribution?.insertAt)
|
|
1001
1205
|
if (attributedAt != null) formattingAttribution.formatAt = attributedAt
|
|
1002
1206
|
d.useAttribution(formattingAttribution)
|
|
1003
1207
|
}
|
|
@@ -1015,28 +1219,33 @@ export class YType {
|
|
|
1015
1219
|
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
|
1016
1220
|
* attributions.
|
|
1017
1221
|
*
|
|
1018
|
-
* @param {
|
|
1222
|
+
* @param {Object} [opts]
|
|
1223
|
+
* @param {AbstractRenderer} [opts.renderer] - renders the content (with attributions); defaults to this type's active renderer (see {@link YType#useRenderer}), i.e. `baseRenderer` unless changed
|
|
1019
1224
|
* @return {delta.Delta<DConf>}
|
|
1020
1225
|
*/
|
|
1021
|
-
toDeltaDeep (
|
|
1022
|
-
return /** @type {any} */ (this.toDelta(
|
|
1226
|
+
toDeltaDeep (opts = {}) {
|
|
1227
|
+
return /** @type {any} */ (this.toDelta({ ...opts, deep: true }))
|
|
1023
1228
|
}
|
|
1024
1229
|
|
|
1025
1230
|
/**
|
|
1026
1231
|
* Apply a {@link Delta} on this shared type.
|
|
1027
1232
|
*
|
|
1028
1233
|
* @param {delta.DeltaAny} d The changes to apply on this element.
|
|
1029
|
-
* @param {
|
|
1234
|
+
* @param {Object} [opts]
|
|
1235
|
+
* @param {AbstractRenderer} [opts.renderer] - renders the content (with attributions); defaults to this type's active renderer (see {@link YType#useRenderer}), i.e. `baseRenderer` unless changed
|
|
1236
|
+
* @return {null} The lib0 `RDT` "fix" of this apply — always `null`: a `YType` accepts every valid
|
|
1237
|
+
* delta as-is and never needs to self-correct.
|
|
1030
1238
|
*
|
|
1031
1239
|
* @public
|
|
1032
1240
|
*/
|
|
1033
|
-
applyDelta (d,
|
|
1241
|
+
applyDelta (d, { renderer = this._renderer } = {}) {
|
|
1242
|
+
if (d.isEmpty()) return null
|
|
1034
1243
|
if (this.doc == null) {
|
|
1035
1244
|
(this._prelim || (this._prelim = /** @type {any} */ (delta.create()))).apply(d)
|
|
1036
|
-
} else {
|
|
1245
|
+
} else if (this._item?.deleted !== true) {
|
|
1037
1246
|
// @todo this was moved here from ytext. Make this more generic
|
|
1038
1247
|
transact(this.doc, transaction => {
|
|
1039
|
-
const currPos = new ItemTextListPosition(null, this._start, 0, new Map(),
|
|
1248
|
+
const currPos = new ItemTextListPosition(null, this._start, 0, new Map(), renderer)
|
|
1040
1249
|
for (const op of d.children) {
|
|
1041
1250
|
if (delta.$textOp.check(op)) {
|
|
1042
1251
|
insertContent(transaction, /** @type {any} */ (this), currPos, new ContentString(op.insert), op.format || {})
|
|
@@ -1047,11 +1256,10 @@ export class YType {
|
|
|
1047
1256
|
} else if (delta.$deleteOp.check(op)) {
|
|
1048
1257
|
deleteText(transaction, currPos, op.delete)
|
|
1049
1258
|
} else if (delta.$modifyOp.check(op)) {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
}
|
|
1259
|
+
let item = currPos.right
|
|
1260
|
+
while (item != null && (item.deleted || !item.countable)) { item = item.next }
|
|
1261
|
+
if (item == null || item.content.constructor !== ContentType) { error.unexpectedCase() }
|
|
1262
|
+
/** @type {ContentType} */ (item.content).type.applyDelta(op.value, { renderer })
|
|
1055
1263
|
currPos.formatText(transaction, /** @type {any} */ (this), 1, op.format || {})
|
|
1056
1264
|
} else {
|
|
1057
1265
|
error.unexpectedCase()
|
|
@@ -1067,12 +1275,12 @@ export class YType {
|
|
|
1067
1275
|
if (!(sub instanceof YType)) {
|
|
1068
1276
|
error.unexpectedCase()
|
|
1069
1277
|
}
|
|
1070
|
-
sub.applyDelta(op.value)
|
|
1278
|
+
sub.applyDelta(op.value, { renderer })
|
|
1071
1279
|
}
|
|
1072
1280
|
}
|
|
1073
1281
|
})
|
|
1074
1282
|
}
|
|
1075
|
-
return
|
|
1283
|
+
return null
|
|
1076
1284
|
}
|
|
1077
1285
|
|
|
1078
1286
|
/**
|
|
@@ -1178,7 +1386,7 @@ export class YType {
|
|
|
1178
1386
|
*
|
|
1179
1387
|
* @param {number} index The index to insert content at.
|
|
1180
1388
|
* @param {Array<delta.DeltaConfGetChildren<DConf>>|delta.DeltaConfGetText<DConf>} content Array of content to append.
|
|
1181
|
-
* @param {delta.
|
|
1389
|
+
* @param {delta.Formats} [format]
|
|
1182
1390
|
*/
|
|
1183
1391
|
insert (index, content, format) {
|
|
1184
1392
|
this.applyDelta(delta.create().retain(index).insert(/** @type {any} */ (content), format).done())
|
|
@@ -1199,7 +1407,7 @@ export class YType {
|
|
|
1199
1407
|
*
|
|
1200
1408
|
* @param {number} index The index to insert content at.
|
|
1201
1409
|
* @param {number} length The index to insert content at.
|
|
1202
|
-
* @param {delta.
|
|
1410
|
+
* @param {delta.Formats} formats
|
|
1203
1411
|
*
|
|
1204
1412
|
*/
|
|
1205
1413
|
format (index, length, formats) {
|
|
@@ -1273,7 +1481,9 @@ export class YType {
|
|
|
1273
1481
|
const children = []
|
|
1274
1482
|
for (const child of dcontent.children) {
|
|
1275
1483
|
if (delta.$insertOp.check(child)) {
|
|
1276
|
-
|
|
1484
|
+
for (let i = 0; i < child.insert.length; i++) {
|
|
1485
|
+
children.push(child.insert[i])
|
|
1486
|
+
}
|
|
1277
1487
|
} else if (delta.$textOp.check(child)) {
|
|
1278
1488
|
children.push(child.insert)
|
|
1279
1489
|
}
|
|
@@ -1428,8 +1638,8 @@ export class YType {
|
|
|
1428
1638
|
_write (encoder) {
|
|
1429
1639
|
encoder.writeTypeRef(this._legacyTypeRef)
|
|
1430
1640
|
switch (this._legacyTypeRef) {
|
|
1431
|
-
case
|
|
1432
|
-
case
|
|
1641
|
+
case YXmlElementRefID:
|
|
1642
|
+
case YXmlHookRefID: {
|
|
1433
1643
|
encoder.writeKey(this.name)
|
|
1434
1644
|
break
|
|
1435
1645
|
}
|
|
@@ -1438,17 +1648,32 @@ export class YType {
|
|
|
1438
1648
|
}
|
|
1439
1649
|
|
|
1440
1650
|
/**
|
|
1441
|
-
* @
|
|
1442
|
-
* @
|
|
1443
|
-
*
|
|
1444
|
-
* @private
|
|
1445
|
-
* @function
|
|
1651
|
+
* @template {import('lib0/delta').ReadableDeltaConf} DConf
|
|
1652
|
+
* @param {DConf} _dconf
|
|
1653
|
+
* @return {s.Schema<YType<import('lib0/delta').ReadDeltaConf<DConf>>>}
|
|
1446
1654
|
*/
|
|
1447
|
-
export const
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1655
|
+
export const $ytype = _dconf => s.$instanceOf(YType)
|
|
1656
|
+
export const $ytypeAny = s.$instanceOf(YType)
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* @param {StructStore} store
|
|
1660
|
+
* @param {IdSet} items
|
|
1661
|
+
*/
|
|
1662
|
+
export const computeModifiedFromItems = (store, items) => {
|
|
1663
|
+
/**
|
|
1664
|
+
* @type {Map<YType,Set<null|string>>}
|
|
1665
|
+
*/
|
|
1666
|
+
const modified = new Map()
|
|
1667
|
+
iterateStructsByIdSetWithoutSplits(store, items, /** @param {Item | GC | Skip | null} item */ item => {
|
|
1668
|
+
while (item instanceof Item) {
|
|
1669
|
+
const parent = /** @type {YType} */ (item.parent)
|
|
1670
|
+
const conf = map.setIfUndefined(modified, parent, set.create)
|
|
1671
|
+
if (conf.has(item.parentSub)) break // has already been marked as modified
|
|
1672
|
+
conf.add(item.parentSub)
|
|
1673
|
+
item = parent._item
|
|
1674
|
+
}
|
|
1675
|
+
})
|
|
1676
|
+
return modified
|
|
1452
1677
|
}
|
|
1453
1678
|
|
|
1454
1679
|
/**
|
|
@@ -1456,7 +1681,7 @@ export const readYType = decoder => {
|
|
|
1456
1681
|
* @param {any} b
|
|
1457
1682
|
* @return {boolean}
|
|
1458
1683
|
*/
|
|
1459
|
-
export const
|
|
1684
|
+
export const equalFormats = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && object.equalFlat(a, b))
|
|
1460
1685
|
|
|
1461
1686
|
/**
|
|
1462
1687
|
* @template {delta.DeltaConf} DConf
|
|
@@ -1561,7 +1786,7 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
|
|
|
1561
1786
|
let jsonContent = []
|
|
1562
1787
|
const packJsonContent = () => {
|
|
1563
1788
|
if (jsonContent.length > 0) {
|
|
1564
|
-
left = new Item(createID(ownClientId,
|
|
1789
|
+
left = new Item(createID(ownClientId, store.getClock(ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentAny(jsonContent))
|
|
1565
1790
|
left.integrate(transaction, 0)
|
|
1566
1791
|
jsonContent = []
|
|
1567
1792
|
}
|
|
@@ -1586,16 +1811,15 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
|
|
|
1586
1811
|
switch (c.constructor) {
|
|
1587
1812
|
case Uint8Array:
|
|
1588
1813
|
case ArrayBuffer:
|
|
1589
|
-
left = new Item(createID(ownClientId,
|
|
1590
|
-
left.integrate(transaction, 0)
|
|
1591
|
-
break
|
|
1592
|
-
case Doc:
|
|
1593
|
-
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
|
|
1814
|
+
left = new Item(createID(ownClientId, store.getClock(ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
|
|
1594
1815
|
left.integrate(transaction, 0)
|
|
1595
1816
|
break
|
|
1596
1817
|
default:
|
|
1597
|
-
if (c
|
|
1598
|
-
left = new Item(createID(ownClientId,
|
|
1818
|
+
if ($ydoc.check(c)) {
|
|
1819
|
+
left = new Item(createID(ownClientId, store.getClock(ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, createContentDocFromDoc(/** @type {Doc} */ (c)))
|
|
1820
|
+
left.integrate(transaction, 0)
|
|
1821
|
+
} else if (c instanceof YType) {
|
|
1822
|
+
left = new Item(createID(ownClientId, store.getClock(ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(/** @type {any} */ (c)))
|
|
1599
1823
|
left.integrate(transaction, 0)
|
|
1600
1824
|
} else {
|
|
1601
1825
|
throw new Error('Unexpected content type in insert operation')
|
|
@@ -1776,18 +2000,17 @@ export const typeMapSet = (transaction, parent, key, value) => {
|
|
|
1776
2000
|
case Uint8Array:
|
|
1777
2001
|
content = new ContentBinary(/** @type {Uint8Array} */ (value))
|
|
1778
2002
|
break
|
|
1779
|
-
case Doc:
|
|
1780
|
-
content = new ContentDoc(/** @type {Doc} */ (value))
|
|
1781
|
-
break
|
|
1782
2003
|
default:
|
|
1783
|
-
if (value
|
|
2004
|
+
if ($ydoc.check(value)) {
|
|
2005
|
+
content = createContentDocFromDoc(/** @type {Doc} */ (value))
|
|
2006
|
+
} else if (value instanceof YType) {
|
|
1784
2007
|
content = new ContentType(/** @type {any} */ (value))
|
|
1785
2008
|
} else {
|
|
1786
2009
|
throw new Error('Unexpected content type')
|
|
1787
2010
|
}
|
|
1788
2011
|
}
|
|
1789
2012
|
}
|
|
1790
|
-
new Item(createID(ownClientId,
|
|
2013
|
+
new Item(createID(ownClientId, doc.store.getClock(ownClientId)), left, left && left.lastId, null, null, parent, key, content).integrate(transaction, 0)
|
|
1791
2014
|
}
|
|
1792
2015
|
|
|
1793
2016
|
/**
|
|
@@ -1838,18 +2061,18 @@ export const typeMapGetAll = (parent) => {
|
|
|
1838
2061
|
* @param {TypeDelta} d
|
|
1839
2062
|
* @param {YType} parent
|
|
1840
2063
|
* @param {Set<string|null>?} attrsToRender
|
|
1841
|
-
* @param {
|
|
2064
|
+
* @param {AbstractRenderer} renderer
|
|
1842
2065
|
* @param {boolean} deep
|
|
1843
2066
|
* @param {Set<YType>|Map<YType,any>|null} [modified] - set of types that should be rendered as modified children
|
|
1844
|
-
* @param {
|
|
1845
|
-
* @param {
|
|
2067
|
+
* @param {IdSet?} [deletedItems]
|
|
2068
|
+
* @param {IdSet?} [itemsToRender]
|
|
1846
2069
|
* @param {any} [opts]
|
|
1847
2070
|
* @param {any} [optsAll]
|
|
1848
2071
|
*
|
|
1849
2072
|
* @private
|
|
1850
2073
|
* @function
|
|
1851
2074
|
*/
|
|
1852
|
-
export const typeMapGetDelta = (d, parent, attrsToRender,
|
|
2075
|
+
export const typeMapGetDelta = (d, parent, attrsToRender, renderer, deep, modified, deletedItems, itemsToRender, opts, optsAll) => {
|
|
1853
2076
|
// @todo support modified ops!
|
|
1854
2077
|
/**
|
|
1855
2078
|
* @param {Item} item
|
|
@@ -1857,30 +2080,43 @@ export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, de
|
|
|
1857
2080
|
*/
|
|
1858
2081
|
const renderAttrs = (item, key) => {
|
|
1859
2082
|
/**
|
|
1860
|
-
* @type {Array<
|
|
2083
|
+
* @type {Array<AttributedContent>}
|
|
1861
2084
|
*/
|
|
1862
2085
|
const cs = []
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2086
|
+
renderer.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
|
|
2087
|
+
if (cs.length === 0) return // the renderer surfaces nothing for this attribute (e.g. a diff renderer hiding an unchanged delete)
|
|
2088
|
+
const { deleted, attrs, content } = cs[cs.length - 1]
|
|
1866
2089
|
const attribution = createAttributionFromAttributionItems(attrs, deleted)
|
|
1867
2090
|
let c = array.last(content.getContent())
|
|
1868
2091
|
if (deleted) {
|
|
1869
|
-
if (
|
|
2092
|
+
if (attribution != null) {
|
|
2093
|
+
// Item surfaced under attribution (suggestion view / diff renderer, either in snapshot mode
|
|
2094
|
+
// or in an event-driven render). The attribute is still observable in the rendered state, so
|
|
2095
|
+
// emit a positive `SetAttrOp` carrying the attribution metadata - matching how content
|
|
2096
|
+
// children are rendered for the same case (positive `InsertOp` with attribution, never
|
|
2097
|
+
// `DeleteOp`).
|
|
2098
|
+
if (itemsToRender == null || itemsToRender.hasId(item.lastId)) {
|
|
2099
|
+
d.setAttr(key, c, attribution)
|
|
2100
|
+
}
|
|
2101
|
+
} else if (itemsToRender != null && itemsToRender.hasId(item.lastId)) {
|
|
2102
|
+
// Hard-deleted attribute within a change render: emit the `deleteAttr` op so consumers (the
|
|
2103
|
+
// `YEvent` delta, RDT bindings, the maintained `delta` cache) can apply the removal. In
|
|
2104
|
+
// full-state mode (`itemsToRender == null`) the attribute is simply omitted (above renders
|
|
2105
|
+
// run with `render === false` for such items, so nothing was emitted before either).
|
|
1870
2106
|
d.deleteAttr(key, attribution, c)
|
|
1871
2107
|
}
|
|
1872
2108
|
} else if (deep && c instanceof YType && modified?.has(c)) {
|
|
1873
|
-
d.modifyAttr(key, c.toDelta(
|
|
2109
|
+
d.modifyAttr(key, c.toDelta(opts))
|
|
1874
2110
|
} else {
|
|
1875
2111
|
// find prev content
|
|
1876
2112
|
let prevContentItem = item
|
|
1877
|
-
// this algorithm is problematic. should check all previous content using
|
|
2113
|
+
// this algorithm is problematic. should check all previous content using renderer.readcontent
|
|
1878
2114
|
for (; prevContentItem.left !== null && deletedItems?.hasId(prevContentItem.left.lastId); prevContentItem = prevContentItem.left) {
|
|
1879
2115
|
// nop
|
|
1880
2116
|
}
|
|
1881
2117
|
const prevValue = (prevContentItem !== item && itemsToRender?.hasId(prevContentItem.lastId)) ? array.last(prevContentItem.content.getContent()) : undefined
|
|
1882
2118
|
if (deep && c instanceof YType) {
|
|
1883
|
-
c = /** @type {any} */(c).toDelta(
|
|
2119
|
+
c = /** @type {any} */(c).toDelta(optsAll)
|
|
1884
2120
|
}
|
|
1885
2121
|
d.setAttr(key, c, attribution, prevValue)
|
|
1886
2122
|
}
|
|
@@ -1906,6 +2142,17 @@ export const typeMapHas = (parent, key) => {
|
|
|
1906
2142
|
return val !== undefined && !val.deleted
|
|
1907
2143
|
}
|
|
1908
2144
|
|
|
2145
|
+
/**
|
|
2146
|
+
* @param {Item} item
|
|
2147
|
+
* @param {Snapshot|undefined} snapshot
|
|
2148
|
+
*
|
|
2149
|
+
* @protected
|
|
2150
|
+
* @function
|
|
2151
|
+
*/
|
|
2152
|
+
export const isVisible = (item, snapshot) => snapshot === undefined
|
|
2153
|
+
? !item.deleted
|
|
2154
|
+
: snapshot.sv.has(item.id.client) && (snapshot.sv.get(item.id.client) || 0) > item.id.clock && !snapshot.ds.hasId(item.id)
|
|
2155
|
+
|
|
1909
2156
|
/**
|
|
1910
2157
|
* @param {YType<any>} parent
|
|
1911
2158
|
* @param {string} key
|
|
@@ -1962,3 +2209,127 @@ export const createMapIterator = type => {
|
|
|
1962
2209
|
type.doc ?? warnPrematureAccess()
|
|
1963
2210
|
return iterator.iteratorFilter(type._map.entries(), /** @param {any} entry */ entry => !entry[1].deleted)
|
|
1964
2211
|
}
|
|
2212
|
+
|
|
2213
|
+
/**
|
|
2214
|
+
* @private
|
|
2215
|
+
*
|
|
2216
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
2217
|
+
* @return {ContentType}
|
|
2218
|
+
*/
|
|
2219
|
+
export const readContentType = decoder => new ContentType(readYType(decoder))
|
|
2220
|
+
|
|
2221
|
+
/**
|
|
2222
|
+
* @private
|
|
2223
|
+
*
|
|
2224
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
2225
|
+
* @return {ContentString}
|
|
2226
|
+
*/
|
|
2227
|
+
export const readContentString = decoder => new ContentString(decoder.readString())
|
|
2228
|
+
|
|
2229
|
+
/**
|
|
2230
|
+
* @private
|
|
2231
|
+
*
|
|
2232
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
2233
|
+
* @return {ContentJSON}
|
|
2234
|
+
*/
|
|
2235
|
+
export const readContentJSON = decoder => {
|
|
2236
|
+
const len = decoder.readLen()
|
|
2237
|
+
const cs = []
|
|
2238
|
+
for (let i = 0; i < len; i++) {
|
|
2239
|
+
const c = decoder.readString()
|
|
2240
|
+
if (c === 'undefined') {
|
|
2241
|
+
cs.push(undefined)
|
|
2242
|
+
} else {
|
|
2243
|
+
cs.push(JSON.parse(c))
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
return new ContentJSON(cs)
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
/**
|
|
2250
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
2251
|
+
* @return {ContentFormat}
|
|
2252
|
+
*/
|
|
2253
|
+
export const readContentFormat = decoder => new ContentFormat(decoder.readKey(), decoder.readJSON())
|
|
2254
|
+
|
|
2255
|
+
/**
|
|
2256
|
+
* @private
|
|
2257
|
+
*
|
|
2258
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
2259
|
+
* @return {ContentEmbed}
|
|
2260
|
+
*/
|
|
2261
|
+
export const readContentEmbed = decoder => new ContentEmbed(decoder.readJSON())
|
|
2262
|
+
|
|
2263
|
+
/**
|
|
2264
|
+
* @private
|
|
2265
|
+
*
|
|
2266
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
2267
|
+
* @return {ContentDoc}
|
|
2268
|
+
*/
|
|
2269
|
+
export const readContentDoc = decoder => new ContentDoc(decoder.readString(), decoder.readAny())
|
|
2270
|
+
|
|
2271
|
+
/**
|
|
2272
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
2273
|
+
* @return {ContentAny}
|
|
2274
|
+
*/
|
|
2275
|
+
export const readContentAny = decoder => {
|
|
2276
|
+
const len = decoder.readLen()
|
|
2277
|
+
const cs = []
|
|
2278
|
+
for (let i = 0; i < len; i++) {
|
|
2279
|
+
cs.push(decoder.readAny())
|
|
2280
|
+
}
|
|
2281
|
+
return new ContentAny(cs)
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
/**
|
|
2285
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2 } decoder
|
|
2286
|
+
* @return {ContentBinary}
|
|
2287
|
+
*/
|
|
2288
|
+
export const readContentBinary = decoder => new ContentBinary(decoder.readBuf())
|
|
2289
|
+
|
|
2290
|
+
/**
|
|
2291
|
+
* @private
|
|
2292
|
+
*
|
|
2293
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2 } decoder
|
|
2294
|
+
* @return {ContentDeleted}
|
|
2295
|
+
*/
|
|
2296
|
+
export const readContentDeleted = decoder => new ContentDeleted(decoder.readLen())
|
|
2297
|
+
|
|
2298
|
+
/**
|
|
2299
|
+
* A lookup map for reading Item content.
|
|
2300
|
+
*
|
|
2301
|
+
* @type {Array<function(UpdateDecoderV1 | UpdateDecoderV2):AbstractContent>}
|
|
2302
|
+
*/
|
|
2303
|
+
export const contentRefs = [
|
|
2304
|
+
() => { error.unexpectedCase() }, // GC is not ItemContent
|
|
2305
|
+
readContentDeleted, // 1
|
|
2306
|
+
readContentJSON, // 2
|
|
2307
|
+
readContentBinary, // 3
|
|
2308
|
+
readContentString, // 4
|
|
2309
|
+
readContentEmbed, // 5
|
|
2310
|
+
readContentFormat, // 6
|
|
2311
|
+
readContentType, // 7
|
|
2312
|
+
readContentAny, // 8
|
|
2313
|
+
readContentDoc, // 9
|
|
2314
|
+
() => { error.unexpectedCase() } // 10 - Skip is not ItemContent
|
|
2315
|
+
]
|
|
2316
|
+
|
|
2317
|
+
/**
|
|
2318
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
2319
|
+
* @param {number} info
|
|
2320
|
+
*/
|
|
2321
|
+
export const readItemContent = (decoder, info) => contentRefs[info & binary.BITS5](decoder)
|
|
2322
|
+
|
|
2323
|
+
/**
|
|
2324
|
+
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
2325
|
+
* @return {YType}
|
|
2326
|
+
*
|
|
2327
|
+
* @private
|
|
2328
|
+
* @function
|
|
2329
|
+
*/
|
|
2330
|
+
export const readYType = decoder => {
|
|
2331
|
+
const typeRef = decoder.readTypeRef()
|
|
2332
|
+
const ytype = new YType(typeRef === YXmlElementRefID || typeRef === YXmlHookRefID ? decoder.readKey() : null)
|
|
2333
|
+
ytype._legacyTypeRef = typeRef
|
|
2334
|
+
return ytype
|
|
2335
|
+
}
|