edytor 3.0.0 → 5.0.0
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 +2 -2
- package/dist/block/block.svelte.js +6 -21
- package/dist/block/inlineBlock.svelte.js +4 -6
- package/dist/clipboard/moveText.js +6 -8
- package/dist/cloudflare/DocumentRoom.d.ts +23 -12
- package/dist/cloudflare/DocumentRoom.js +10 -6
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/room/access.d.ts +8 -3
- package/dist/cloudflare/room/access.js +44 -20
- package/dist/cloudflare/room/admission.js +28 -5
- package/dist/cloudflare/room/comments.js +28 -1
- package/dist/cloudflare/room/context.d.ts +139 -129
- package/dist/cloudflare/room/context.js +11 -4
- package/dist/cloudflare/room/forged.d.ts +14 -0
- package/dist/cloudflare/room/forged.js +30 -2
- package/dist/cloudflare/room/frames.d.ts +4 -0
- package/dist/cloudflare/room/frames.js +5 -1
- package/dist/cloudflare/room/purge.d.ts +6 -3
- package/dist/cloudflare/room/purge.js +45 -19
- package/dist/cloudflare/room/replicas.d.ts +4 -2
- package/dist/cloudflare/room/replicas.js +8 -3
- package/dist/cloudflare/room/suggest.d.ts +7 -0
- package/dist/cloudflare/room/suggest.js +143 -0
- package/dist/cloudflare/room/updates.d.ts +3 -4
- package/dist/cloudflare/room/updates.js +6 -8
- package/dist/cloudflare/routeDocumentSocket.d.ts +10 -1
- package/dist/cloudflare/routeDocumentSocket.js +5 -3
- package/dist/components/Content.svelte +9 -1
- package/dist/crdt/anchors.js +3 -2
- package/dist/crdt/attribution/attribution.d.ts +34 -12
- package/dist/crdt/attribution/attribution.js +59 -21
- package/dist/crdt/attribution/block.d.ts +3 -3
- package/dist/crdt/attribution/block.js +7 -8
- package/dist/crdt/attribution/index.d.ts +1 -1
- package/dist/crdt/attribution/index.js +1 -1
- package/dist/crdt/data.d.ts +62 -31
- package/dist/crdt/data.js +211 -103
- package/dist/crdt/doc/capability.js +9 -13
- package/dist/crdt/doc/delete.d.ts +1 -1
- package/dist/crdt/doc/delete.js +13 -30
- package/dist/crdt/doc/funnel.d.ts +1 -1
- package/dist/crdt/doc/funnel.js +34 -26
- package/dist/crdt/doc/gate.d.ts +3 -3
- package/dist/crdt/doc/history.d.ts +31 -9
- package/dist/crdt/doc/history.js +204 -73
- package/dist/crdt/doc/layout.js +16 -27
- package/dist/crdt/doc/meta.js +17 -23
- package/dist/crdt/doc/moves.d.ts +1 -1
- package/dist/crdt/doc/moves.js +52 -32
- package/dist/crdt/doc/reads.d.ts +2 -2
- package/dist/crdt/doc/reads.js +20 -30
- package/dist/crdt/doc/seed.js +2 -2
- package/dist/crdt/doc/split-merge.js +12 -15
- package/dist/crdt/doc/steps.d.ts +25 -13
- package/dist/crdt/doc/steps.js +55 -125
- package/dist/crdt/doc/suggest.d.ts +167 -0
- package/dist/crdt/doc/suggest.js +579 -0
- package/dist/crdt/doc/types.d.ts +21 -9
- package/dist/crdt/document.d.ts +26 -6
- package/dist/crdt/document.js +40 -15
- package/dist/crdt/edytor-doc.d.ts +410 -390
- package/dist/crdt/edytor-doc.js +143 -95
- package/dist/crdt/engine-api.d.ts +39 -1
- package/dist/crdt/engine.d.ts +2 -2
- package/dist/crdt/engine.js +4 -5
- package/dist/crdt/flow.d.ts +2 -2
- package/dist/crdt/flow.js +29 -37
- package/dist/crdt/index.d.ts +134 -127
- package/dist/crdt/index.js +4 -1
- package/dist/crdt/operations.d.ts +12 -3
- package/dist/crdt/parts.d.ts +43 -0
- package/dist/crdt/parts.js +44 -0
- package/dist/crdt/placement/display.d.ts +6 -31
- package/dist/crdt/placement/display.js +41 -146
- package/dist/crdt/placement/keys.d.ts +63 -0
- package/dist/crdt/placement/keys.js +95 -0
- package/dist/crdt/placement/model.d.ts +119 -118
- package/dist/crdt/placement/model.js +242 -142
- package/dist/crdt/placement/resolve.d.ts +123 -35
- package/dist/crdt/placement/resolve.js +188 -153
- package/dist/crdt/placement/slots.d.ts +134 -0
- package/dist/crdt/placement/slots.js +152 -0
- package/dist/crdt/protocol.d.ts +1 -1
- package/dist/crdt/protocol.js +1 -1
- package/dist/crdt/protocols/auth.d.ts +11 -1
- package/dist/crdt/protocols/auth.js +17 -1
- package/dist/crdt/protocols/comments.d.ts +16 -1
- package/dist/crdt/protocols/comments.js +9 -1
- package/dist/crdt/providers/index.d.ts +6 -4
- package/dist/crdt/providers/index.js +3 -1
- package/dist/crdt/providers/room.d.ts +16 -2
- package/dist/crdt/providers/room.js +64 -17
- package/dist/crdt/providers/websocket.d.ts +6 -4
- package/dist/crdt/providers/websocket.js +14 -7
- package/dist/crdt/purge.d.ts +24 -9
- package/dist/crdt/purge.js +266 -126
- package/dist/crdt/restore.d.ts +4 -3
- package/dist/crdt/restore.js +27 -34
- package/dist/crdt/schema.d.ts +30 -21
- package/dist/crdt/schema.js +39 -23
- package/dist/crdt/storage.d.ts +1 -1
- package/dist/crdt/storage.js +1 -1
- package/dist/crdt/structs.d.ts +17 -15
- package/dist/crdt/structs.js +10 -22
- package/dist/crdt/text/index/cache.js +4 -0
- package/dist/crdt/text/index/claims.js +13 -23
- package/dist/crdt/text/index/fold.d.ts +2 -3
- package/dist/crdt/text/index/fold.js +194 -44
- package/dist/crdt/text/index/layout.d.ts +2 -4
- package/dist/crdt/text/index/layout.js +14 -30
- package/dist/crdt/text/index/placement.d.ts +26 -18
- package/dist/crdt/text/index/placement.js +420 -157
- package/dist/crdt/text/index/records.d.ts +3 -3
- package/dist/crdt/text/index/records.js +24 -31
- package/dist/crdt/text/index/shared.d.ts +11 -3
- package/dist/crdt/text/index/shared.js +29 -17
- package/dist/crdt/text/index/state.d.ts +21 -17
- package/dist/crdt/text/index/state.js +15 -17
- package/dist/crdt/text/index/streams.d.ts +10 -5
- package/dist/crdt/text/index/streams.js +78 -27
- package/dist/crdt/text/items.d.ts +10 -3
- package/dist/crdt/text/items.js +7 -0
- package/dist/crdt/text/model.d.ts +87 -62
- package/dist/crdt/text/model.js +91 -169
- package/dist/crdt/text/ranges.d.ts +1 -1
- package/dist/crdt/text/ranges.js +3 -3
- package/dist/crdt/text/rows.d.ts +13 -7
- package/dist/crdt/text/rows.js +24 -7
- package/dist/crdt/text/runs.d.ts +51 -42
- package/dist/crdt/text/runs.js +24 -7
- package/dist/crdt/text/streams.d.ts +26 -13
- package/dist/crdt/text/streams.js +41 -14
- package/dist/crdt/vendor/yjs/UPSTREAM.md +651 -21
- package/dist/crdt/vendor/yjs/dts/global.d.ts +2 -0
- package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
- package/dist/crdt/vendor/yjs/dts/structs/AbstractStruct.d.ts +7 -0
- package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +197 -10
- package/dist/crdt/vendor/yjs/dts/utils/Doc.d.ts +6 -3
- package/dist/crdt/vendor/yjs/dts/utils/StructStore.d.ts +5 -18
- package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +15 -1
- package/dist/crdt/vendor/yjs/dts/utils/UpdateDecoder.d.ts +14 -6
- package/dist/crdt/vendor/yjs/dts/utils/UpdateEncoder.d.ts +33 -6
- package/dist/crdt/vendor/yjs/dts/utils/ids.d.ts +1 -65
- package/dist/crdt/vendor/yjs/dts/utils/marks.d.ts +6 -1
- package/dist/crdt/vendor/yjs/dts/ynode.d.ts +54 -7
- package/dist/crdt/vendor/yjs/global.d.ts +2 -0
- package/dist/crdt/vendor/yjs/src/index.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/AbstractStruct.js +10 -0
- package/dist/crdt/vendor/yjs/src/structs/Item.js +649 -51
- package/dist/crdt/vendor/yjs/src/utils/BlockSet.js +18 -9
- package/dist/crdt/vendor/yjs/src/utils/Doc.js +5 -3
- package/dist/crdt/vendor/yjs/src/utils/StructStore.js +5 -0
- package/dist/crdt/vendor/yjs/src/utils/Transaction.js +36 -13
- package/dist/crdt/vendor/yjs/src/utils/UpdateDecoder.js +88 -18
- package/dist/crdt/vendor/yjs/src/utils/UpdateEncoder.js +119 -32
- package/dist/crdt/vendor/yjs/src/utils/encoding.js +13 -1
- package/dist/crdt/vendor/yjs/src/utils/ids.js +7 -7
- package/dist/crdt/vendor/yjs/src/utils/marks.js +193 -8
- package/dist/crdt/vendor/yjs/src/utils/redo.js +1 -1
- package/dist/crdt/vendor/yjs/src/utils/transaction-helpers.js +1 -1
- package/dist/crdt/vendor/yjs/src/utils/updates.js +3 -3
- package/dist/crdt/vendor/yjs/src/ynode.js +199 -12
- package/dist/edytor.svelte.d.ts +1 -1
- package/dist/edytor.svelte.js +103 -75
- package/dist/events/beforeInputCommands.d.ts +2 -2
- package/dist/events/beforeInputCommands.js +2 -2
- package/dist/events/nativeInteractiveControl.d.ts +11 -1
- package/dist/events/nativeInteractiveControl.js +41 -1
- package/dist/events/onBeforeInput.js +3 -3
- package/dist/events/onFocus.js +9 -11
- package/dist/events/onPageHide.d.ts +3 -0
- package/dist/events/onPageHide.js +16 -0
- package/dist/export/forms.d.ts +50 -50
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/labels.d.ts +8 -0
- package/dist/labels.js +6 -1
- package/dist/plugins/comments/CommentsController.svelte.d.ts +5 -1
- package/dist/plugins/comments/CommentsController.svelte.js +14 -4
- package/dist/plugins/find/search.js +2 -2
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/suggestions/SuggestionBars.svelte +40 -23
- package/dist/plugins/suggestions/SuggestionBars.svelte.d.ts +3 -1
- package/dist/plugins/suggestions/SuggestionRuns.svelte +77 -0
- package/dist/plugins/suggestions/SuggestionRuns.svelte.d.ts +9 -0
- package/dist/plugins/suggestions/shared.svelte.d.ts +58 -0
- package/dist/plugins/suggestions/shared.svelte.js +421 -0
- package/dist/plugins/suggestions/suggestionsPlugin.d.ts +15 -4
- package/dist/selection/domSelection.d.ts +2 -0
- package/dist/selection/domSelection.js +6 -2
- package/dist/selection/pointer.svelte.js +75 -48
- package/dist/selection/replaceSelection.js +2 -6
- package/dist/selection/selection.svelte.d.ts +3 -3
- package/dist/selection/selection.svelte.js +39 -32
- package/dist/selection/selection.utils.d.ts +14 -0
- package/dist/selection/selection.utils.js +28 -3
- package/dist/session/commands.js +21 -11
- package/dist/session/composition.svelte.d.ts +5 -3
- package/dist/session/composition.svelte.js +21 -17
- package/dist/session/handles.d.ts +4 -17
- package/dist/session/handles.js +11 -37
- package/dist/session/navigation.js +6 -9
- package/dist/session/selection.d.ts +11 -5
- package/dist/session/selection.js +14 -48
- package/dist/session/suggestions.svelte.d.ts +38 -9
- package/dist/session/suggestions.svelte.js +37 -18
- package/dist/session/virtual.d.ts +1 -1
- package/dist/surface/attributes.d.ts +5 -0
- package/dist/surface/attributes.js +2 -2
- package/dist/surface/cells.d.ts +2 -26
- package/dist/surface/cells.js +2 -33
- package/dist/surface/observer.svelte.d.ts +6 -1
- package/dist/surface/observer.svelte.js +130 -83
- package/dist/surface/pin.svelte.d.ts +9 -9
- package/dist/surface/pin.svelte.js +15 -22
- package/dist/surface/projector.svelte.d.ts +15 -3
- package/dist/surface/projector.svelte.js +67 -22
- package/dist/text/text.svelte.js +14 -19
- package/dist/themes/notion.css +29 -0
- package/package.json +4 -2
- package/dist/crdt/placement/rank.d.ts +0 -87
- package/dist/crdt/placement/rank.js +0 -332
- package/dist/crdt/placement/source.d.ts +0 -43
- package/dist/crdt/placement/source.js +0 -66
- package/dist/crdt/text/delete-index.d.ts +0 -129
- package/dist/crdt/text/delete-index.js +0 -365
- package/dist/crdt/text/deletes.d.ts +0 -76
- package/dist/crdt/text/deletes.js +0 -362
- package/dist/crdt/text/index/anchored.d.ts +0 -17
- package/dist/crdt/text/index/anchored.js +0 -197
|
@@ -5,11 +5,15 @@ import { AbstractStruct, addStructToIdSet } from '../structs/AbstractStruct.js'
|
|
|
5
5
|
|
|
6
6
|
import { ID, createID, compareIDs, findRootTypeKey } from '../utils/ID.js'
|
|
7
7
|
import { GC } from '../structs/GC.js'
|
|
8
|
-
import { sortsBefore, pairedRole } from '../utils/marks.js' // YP13
|
|
8
|
+
import { sortsBefore, pairedRole, markClass } from '../utils/marks.js' // YP13, YP21
|
|
9
|
+
|
|
10
|
+
import * as math from 'lib0-v14/math' // YP20
|
|
9
11
|
|
|
10
12
|
import {
|
|
11
13
|
replaceStruct,
|
|
12
14
|
getItemCleanEnd,
|
|
15
|
+
getItemCleanStart, // YP20
|
|
16
|
+
findIndexSS, // YP20
|
|
13
17
|
addChangedTypeToTransaction,
|
|
14
18
|
isKeptReplaced // YP14
|
|
15
19
|
} from '../utils/transaction-helpers.js'
|
|
@@ -107,6 +111,21 @@ export class Item extends AbstractStruct {
|
|
|
107
111
|
* @type {number} byte
|
|
108
112
|
*/
|
|
109
113
|
this.info = this.content.isCountable() ? binary.BIT2 : 0
|
|
114
|
+
/**
|
|
115
|
+
* YP20: how many live hold records (`ContentHold`) name this item. A
|
|
116
|
+
* held item reads as deleted; the count is local, never encoded (each
|
|
117
|
+
* replica counts the records it integrated).
|
|
118
|
+
* @type {number}
|
|
119
|
+
*/
|
|
120
|
+
this.holds = 0
|
|
121
|
+
/**
|
|
122
|
+
* YP22: the live move records (`ContentMove`) naming this item, the
|
|
123
|
+
* strongest first — the greatest `(priority, client, clock)` — or `null`.
|
|
124
|
+
* The first is its mover: the item shows there, hidden at home. Local,
|
|
125
|
+
* never encoded.
|
|
126
|
+
* @type {Array<Item>|null}
|
|
127
|
+
*/
|
|
128
|
+
this.movers = null
|
|
110
129
|
}
|
|
111
130
|
|
|
112
131
|
/**
|
|
@@ -142,10 +161,38 @@ export class Item extends AbstractStruct {
|
|
|
142
161
|
}
|
|
143
162
|
|
|
144
163
|
/**
|
|
145
|
-
* Whether this item
|
|
164
|
+
* Whether this item is hidden at home: removed, held by a live hold record
|
|
165
|
+
* (YP20), or moved by a live move record (YP22: it shows at its mover).
|
|
146
166
|
* @type {Boolean}
|
|
147
167
|
*/
|
|
148
168
|
get deleted () {
|
|
169
|
+
return (this.info & binary.BIT3) > 0 || this.holds > 0 || this.movers !== null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Whether this item shows nowhere: removed or held (YP22; a moved item
|
|
174
|
+
* shows at its mover).
|
|
175
|
+
* @type {Boolean}
|
|
176
|
+
*/
|
|
177
|
+
get gone () {
|
|
178
|
+
return (this.info & binary.BIT3) > 0 || this.holds > 0
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* YP22: the live move record this item shows at (the strongest of
|
|
183
|
+
* `movers`), or `null` at home.
|
|
184
|
+
* @type {Item|null}
|
|
185
|
+
*/
|
|
186
|
+
get mover () {
|
|
187
|
+
return this.movers === null ? null : this.movers[0]
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Whether the engine deleted this item (the delete set's fact; YP20: a held
|
|
192
|
+
* item is hidden, not removed).
|
|
193
|
+
* @type {Boolean}
|
|
194
|
+
*/
|
|
195
|
+
get removed () {
|
|
149
196
|
return (this.info & binary.BIT3) > 0
|
|
150
197
|
}
|
|
151
198
|
|
|
@@ -206,7 +253,7 @@ export class Item extends AbstractStruct {
|
|
|
206
253
|
conflictingItems.add(o)
|
|
207
254
|
if (compareIDs(this.origin, o.origin)) {
|
|
208
255
|
// case 1
|
|
209
|
-
if (sortsBefore(o, this)) { // YP13: paired marks sort by side, then by client id
|
|
256
|
+
if (sortsBefore(o, this, transaction)) { // YP13: paired marks sort by side, then by client id
|
|
210
257
|
left = o
|
|
211
258
|
conflictingItems.clear()
|
|
212
259
|
} else if (compareIDs(this.rightOrigin, o.rightOrigin)) {
|
|
@@ -266,7 +313,7 @@ export class Item extends AbstractStruct {
|
|
|
266
313
|
addChangedTypeToTransaction(transaction, /** @type {YNode} */ (this.parent), this.parentSub)
|
|
267
314
|
const parentItem = /** @type {YNode} */ (this.parent)._item
|
|
268
315
|
// YP14: a node a concurrent value replaced keeps its subtree: what lands in it stays
|
|
269
|
-
if ((parentItem !== null && parentItem.
|
|
316
|
+
if ((parentItem !== null && parentItem.removed && !isKeptReplaced(parentItem)) || (this.parentSub !== null && this.right !== null)) { // YP20: a held parent is alive
|
|
270
317
|
// delete if parent is deleted or if this is not the current attribute value of parent
|
|
271
318
|
this.delete(transaction)
|
|
272
319
|
}
|
|
@@ -298,7 +345,9 @@ export class Item extends AbstractStruct {
|
|
|
298
345
|
compareIDs(this.rightOrigin, right.rightOrigin) &&
|
|
299
346
|
this.id.client === right.id.client &&
|
|
300
347
|
this.id.clock + this.length === right.id.clock &&
|
|
301
|
-
this.
|
|
348
|
+
this.removed === right.removed &&
|
|
349
|
+
this.holds === right.holds && // YP20
|
|
350
|
+
this.movers === null && right.movers === null && // YP22: a moved unit stands alone
|
|
302
351
|
this.redone === null &&
|
|
303
352
|
right.redone === null &&
|
|
304
353
|
this.content.constructor === right.content.constructor &&
|
|
@@ -339,10 +388,16 @@ export class Item extends AbstractStruct {
|
|
|
339
388
|
* @param {Transaction} transaction
|
|
340
389
|
*/
|
|
341
390
|
delete (transaction) {
|
|
342
|
-
if (!this.
|
|
391
|
+
if (!this.removed) {
|
|
343
392
|
const parent = /** @type {YNode} */ (this.parent)
|
|
344
393
|
// adjust the length of parent
|
|
345
|
-
if (this.
|
|
394
|
+
if (this.holds > 0) {
|
|
395
|
+
// YP20: already hidden — the delete set counts it again, the log gives it back
|
|
396
|
+
logHold(transaction, this, this.length)
|
|
397
|
+
} else if (this.movers !== null) {
|
|
398
|
+
// YP22: hidden at home already; its mover shows it no more (`gone`)
|
|
399
|
+
logMove(transaction, this, this.mover)
|
|
400
|
+
} else if (this.countable && this.parentSub === null) {
|
|
346
401
|
parent._length -= this.length
|
|
347
402
|
}
|
|
348
403
|
// WU9 format-aware markers: tombstoning a format item changes the format
|
|
@@ -358,7 +413,7 @@ export class Item extends AbstractStruct {
|
|
|
358
413
|
transaction.deleteSet.add(this.id.client, this.id.clock, this.length)
|
|
359
414
|
addChangedTypeToTransaction(transaction, parent, this.parentSub)
|
|
360
415
|
// YP14: a node a concurrent value replaced keeps its subtree (the document asked)
|
|
361
|
-
if (!isKeptReplaced(this)) this.content.delete(transaction)
|
|
416
|
+
if (!isKeptReplaced(this)) this.content.delete(transaction, this) // YP22: a move record drops itself from its units' movers
|
|
362
417
|
}
|
|
363
418
|
}
|
|
364
419
|
|
|
@@ -367,16 +422,28 @@ export class Item extends AbstractStruct {
|
|
|
367
422
|
* @param {boolean} parentGCd
|
|
368
423
|
*/
|
|
369
424
|
gc (tr, parentGCd) {
|
|
370
|
-
if (!this.
|
|
425
|
+
if (!this.removed) { // YP20
|
|
371
426
|
throw error.unexpectedCase()
|
|
372
427
|
}
|
|
373
428
|
this.content.gc(tr)
|
|
374
429
|
if (parentGCd) {
|
|
375
430
|
replaceStruct(tr, this, new GC(this.id, this.length))
|
|
376
|
-
} else
|
|
377
|
-
// YP13
|
|
378
|
-
// concurrent inserts (`markClass`) on every
|
|
379
|
-
|
|
431
|
+
} else {
|
|
432
|
+
// YP13, YP21, YP22, YP29: an item of another class than content keeps
|
|
433
|
+
// what orders concurrent inserts at its origin (`markClass`) on every
|
|
434
|
+
// replica, collected or not: a paired mark item or a claim item its
|
|
435
|
+
// class, a move record its content (its sort reads its chain). A block
|
|
436
|
+
// slot a split made keeps its split key the same way (`splitKey`),
|
|
437
|
+
// trimmed to it.
|
|
438
|
+
const cls = markClass(this)
|
|
439
|
+
const arr = /** @type {any} */ (this.content).arr
|
|
440
|
+
if (cls === 1) {
|
|
441
|
+
const keys = arr?.map((/** @type {any} */ v) => (Array.isArray(v?.s) ? { s: v.s } : null))
|
|
442
|
+
this.content = keys?.some((/** @type {any} */ k) => k !== null) ? new ContentAny(keys) : new ContentDeleted(this.length)
|
|
443
|
+
} else if (cls < 3 || (cls === 3 && arr?.every((/** @type {any} */ v) => typeof v?.m === 'string'))) {
|
|
444
|
+
// every unit of the item has its class (a mark is one unit; merged claims all are claims)
|
|
445
|
+
this.content = new ContentDeleted(this.length, cls)
|
|
446
|
+
}
|
|
380
447
|
}
|
|
381
448
|
}
|
|
382
449
|
|
|
@@ -399,9 +466,11 @@ export class Item extends AbstractStruct {
|
|
|
399
466
|
this.parentSub,
|
|
400
467
|
this.content.splice(diff)
|
|
401
468
|
)
|
|
402
|
-
if (this.
|
|
469
|
+
if (this.removed) { // YP20
|
|
403
470
|
rightItem.markDeleted()
|
|
404
471
|
}
|
|
472
|
+
rightItem.holds = this.holds // YP20
|
|
473
|
+
rightItem.movers = this.movers?.slice() ?? null // YP22
|
|
405
474
|
if (this.keep) {
|
|
406
475
|
rightItem.keep = true
|
|
407
476
|
}
|
|
@@ -443,10 +512,15 @@ export class Item extends AbstractStruct {
|
|
|
443
512
|
const origin = offset > 0 ? createID(this.id.client, this.id.clock + offset - 1) : this.origin
|
|
444
513
|
const rightOrigin = this.rightOrigin
|
|
445
514
|
const parentSub = this.parentSub
|
|
446
|
-
const
|
|
515
|
+
const parent = /** @type {any} */ (this.parent)
|
|
516
|
+
// YP27: an item written with its parent says in bit 5 whether the parent is
|
|
517
|
+
// a root (its name follows) or an item (its id follows): no parentInfo column
|
|
518
|
+
const named = origin === null && rightOrigin === null && (parent.constructor === String || parent._item === null)
|
|
519
|
+
const info = (this.content.getRef() & binary.BITS4) | // YP27: content refs fit four bits
|
|
447
520
|
(origin === null ? 0 : binary.BIT8) | // origin is defined
|
|
448
521
|
(rightOrigin === null ? 0 : binary.BIT7) | // right origin is defined
|
|
449
|
-
(parentSub === null ? 0 : binary.BIT6) // parentSub is non-null
|
|
522
|
+
(parentSub === null ? 0 : binary.BIT6) | // parentSub is non-null
|
|
523
|
+
(named ? binary.BIT5 : 0) // YP27: the parent is a root, by name
|
|
450
524
|
encoder.writeInfo(info)
|
|
451
525
|
if (origin !== null) {
|
|
452
526
|
encoder.writeLeftID(origin)
|
|
@@ -455,33 +529,21 @@ export class Item extends AbstractStruct {
|
|
|
455
529
|
encoder.writeRightID(rightOrigin)
|
|
456
530
|
}
|
|
457
531
|
if (origin === null && rightOrigin === null) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (parentItem === null) {
|
|
462
|
-
// parent type on y._map
|
|
463
|
-
// find the correct key
|
|
464
|
-
const ykey = findRootTypeKey(parent)
|
|
465
|
-
encoder.writeParentInfo(true) // write parentYKey
|
|
466
|
-
encoder.writeString(ykey)
|
|
467
|
-
} else {
|
|
468
|
-
encoder.writeParentInfo(false) // write parent id
|
|
469
|
-
encoder.writeLeftID(parentItem.id)
|
|
470
|
-
}
|
|
471
|
-
} else if (parent.constructor === String) { // this edge case was added by differential updates
|
|
472
|
-
encoder.writeParentInfo(true) // write parentYKey
|
|
473
|
-
encoder.writeString(parent)
|
|
532
|
+
if (named) {
|
|
533
|
+
// a root's name (a string parent: an edge case of differential updates)
|
|
534
|
+
encoder.writeKey(parent.constructor === String ? /** @type {string} */ (parent) : findRootTypeKey(parent)) // YP24
|
|
474
535
|
} else if (parent.constructor === ID) {
|
|
475
|
-
encoder.writeParentInfo(false) // write parent id
|
|
476
536
|
encoder.writeLeftID(parent)
|
|
537
|
+
} else if (parent._item) {
|
|
538
|
+
encoder.writeLeftID(parent._item.id)
|
|
477
539
|
} else {
|
|
478
540
|
error.unexpectedCase()
|
|
479
541
|
}
|
|
480
542
|
if (parentSub !== null) {
|
|
481
|
-
encoder.
|
|
543
|
+
encoder.writeKey(parentSub) // YP24
|
|
482
544
|
}
|
|
483
545
|
}
|
|
484
|
-
this.content.write(encoder, offset, offsetEnd)
|
|
546
|
+
this.content.write(encoder, offset, offsetEnd, this) // YP25: a format item reads its own id
|
|
485
547
|
}
|
|
486
548
|
|
|
487
549
|
get ref () {
|
|
@@ -558,8 +620,9 @@ export class AbstractContent {
|
|
|
558
620
|
|
|
559
621
|
/**
|
|
560
622
|
* @param {Transaction} _transaction
|
|
623
|
+
* @param {Item} [_item] the item deleted (YP22: a move record reads it)
|
|
561
624
|
*/
|
|
562
|
-
delete (_transaction) {
|
|
625
|
+
delete (_transaction, _item) {
|
|
563
626
|
throw error.methodUnimplemented()
|
|
564
627
|
}
|
|
565
628
|
|
|
@@ -574,15 +637,16 @@ export class AbstractContent {
|
|
|
574
637
|
* @param {UpdateEncoder} _encoder
|
|
575
638
|
* @param {number} _offset
|
|
576
639
|
* @param {number} _offsetEnd
|
|
640
|
+
* @param {Item} [_item] the item written (YP25: a format item reads its id)
|
|
577
641
|
*/
|
|
578
|
-
write (_encoder, _offset, _offsetEnd) {
|
|
642
|
+
write (_encoder, _offset, _offsetEnd, _item) {
|
|
579
643
|
throw error.methodUnimplemented()
|
|
580
644
|
}
|
|
581
645
|
|
|
582
646
|
/**
|
|
583
|
-
* @return {1|2|3|4|5|6|7|8|9}
|
|
647
|
+
* @return {1|2|3|4|5|6|7|8|9|11|12|13}
|
|
584
648
|
*/
|
|
585
|
-
getRef () {
|
|
649
|
+
getRef () { // YP20: 11, a hold record; YP22: 12, a move record; YP29: 13, a collected mark's class
|
|
586
650
|
throw error.methodUnimplemented()
|
|
587
651
|
}
|
|
588
652
|
}
|
|
@@ -766,9 +830,19 @@ export class ContentBinary {
|
|
|
766
830
|
export class ContentDeleted {
|
|
767
831
|
/**
|
|
768
832
|
* @param {number} len
|
|
833
|
+
* @param {number} [cls] YP29: a collected mark's or claim's sort class
|
|
769
834
|
*/
|
|
770
|
-
constructor (len) {
|
|
835
|
+
constructor (len, cls) {
|
|
771
836
|
this.len = len
|
|
837
|
+
/**
|
|
838
|
+
* YP29: what a collected paired mark item or claim item keeps, for each
|
|
839
|
+
* of its units: its sort class (`markClass`: `0` a left-side mark, `2` a
|
|
840
|
+
* right-side one, `3` a claim), which orders concurrent inserts at its
|
|
841
|
+
* origin on every replica (`undefined`: none, any other deleted content).
|
|
842
|
+
*
|
|
843
|
+
* @type {number|undefined}
|
|
844
|
+
*/
|
|
845
|
+
this.cls = cls
|
|
772
846
|
}
|
|
773
847
|
|
|
774
848
|
/**
|
|
@@ -789,14 +863,14 @@ export class ContentDeleted {
|
|
|
789
863
|
* @return {boolean}
|
|
790
864
|
*/
|
|
791
865
|
isCountable () {
|
|
792
|
-
return
|
|
866
|
+
return this.cls === 3 // YP29: a claim is a unit of its text
|
|
793
867
|
}
|
|
794
868
|
|
|
795
869
|
/**
|
|
796
870
|
* @return {ContentDeleted}
|
|
797
871
|
*/
|
|
798
872
|
copy () {
|
|
799
|
-
return new ContentDeleted(this.len)
|
|
873
|
+
return new ContentDeleted(this.len, this.cls)
|
|
800
874
|
}
|
|
801
875
|
|
|
802
876
|
/**
|
|
@@ -804,7 +878,7 @@ export class ContentDeleted {
|
|
|
804
878
|
* @return {ContentDeleted}
|
|
805
879
|
*/
|
|
806
880
|
splice (offset) {
|
|
807
|
-
const right = new ContentDeleted(this.len - offset)
|
|
881
|
+
const right = new ContentDeleted(this.len - offset, this.cls)
|
|
808
882
|
this.len = offset
|
|
809
883
|
return right
|
|
810
884
|
}
|
|
@@ -814,6 +888,7 @@ export class ContentDeleted {
|
|
|
814
888
|
* @return {boolean}
|
|
815
889
|
*/
|
|
816
890
|
mergeWith (right) {
|
|
891
|
+
if (right.cls !== this.cls) return false // YP29
|
|
817
892
|
this.len += right.len
|
|
818
893
|
return true
|
|
819
894
|
}
|
|
@@ -841,20 +916,541 @@ export class ContentDeleted {
|
|
|
841
916
|
* @param {number} offsetEnd
|
|
842
917
|
*/
|
|
843
918
|
write (encoder, offset, offsetEnd) {
|
|
844
|
-
|
|
919
|
+
const len = this.len - offset - offsetEnd
|
|
920
|
+
encoder.writeLen(this.cls === undefined ? len : len * 4 + this.cls) // YP29
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* @return {1|13}
|
|
925
|
+
*/
|
|
926
|
+
getRef () {
|
|
927
|
+
return this.cls === undefined ? 1 : 13 // YP29: 13, a collected mark or claim
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// YP20 begin (edytor fork: hold records — see UPSTREAM.md YP20)
|
|
932
|
+
/**
|
|
933
|
+
* Log that a hold changed `item`'s visibility (`len > 0`: shown, `< 0`:
|
|
934
|
+
* hidden) in `tr.holds`, in order, and its parent as changed.
|
|
935
|
+
*
|
|
936
|
+
* @param {Transaction} tr
|
|
937
|
+
* @param {Item} item
|
|
938
|
+
* @param {number} len
|
|
939
|
+
*/
|
|
940
|
+
const logHold = (tr, item, len) => {
|
|
941
|
+
tr.holds.push(item.id.client, item.id.clock, len)
|
|
942
|
+
addChangedTypeToTransaction(tr, /** @type {YNode} */ (item.parent), item.parentSub)
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Add `delta` (1, or -1: release) of a hold record (`record` null: `holds`)
|
|
947
|
+
* or of the move record `record` (its `movers`, YP22; a record never moves a
|
|
948
|
+
* record) to `item`. An item that is not removed and starts or stops being
|
|
949
|
+
* hidden moves its parent's length and drops its search markers; a hold that
|
|
950
|
+
* does is logged (`tr.holds`), and so is a unit whose mover changed
|
|
951
|
+
* (`tr.moves`).
|
|
952
|
+
*
|
|
953
|
+
* @param {Transaction} tr
|
|
954
|
+
* @param {Item} item
|
|
955
|
+
* @param {number} delta
|
|
956
|
+
* @param {Item|null} record
|
|
957
|
+
*/
|
|
958
|
+
const holdItem = (tr, item, delta, record) => {
|
|
959
|
+
const was = item.deleted
|
|
960
|
+
const before = item.mover
|
|
961
|
+
if (record === null) item.holds += delta
|
|
962
|
+
else if (item.content.constructor === ContentMove) return
|
|
963
|
+
else if (delta > 0) {
|
|
964
|
+
const m = (item.movers ??= [])
|
|
965
|
+
const i = m.findIndex(r => stronger(record, r))
|
|
966
|
+
m.splice(i < 0 ? m.length : i, 0, record)
|
|
967
|
+
}
|
|
968
|
+
else if (item.movers !== null) {
|
|
969
|
+
const i = item.movers.indexOf(record)
|
|
970
|
+
if (i >= 0) item.movers.splice(i, 1)
|
|
971
|
+
if (item.movers.length === 0) item.movers = null
|
|
972
|
+
}
|
|
973
|
+
if (was !== item.deleted) {
|
|
974
|
+
const parent = /** @type {YNode} */ (item.parent)
|
|
975
|
+
const len = was ? item.length : -item.length
|
|
976
|
+
if (item.countable && item.parentSub === null) parent._length += len
|
|
977
|
+
// the indices the search markers counted moved (a held delete moves them itself)
|
|
978
|
+
if (parent._searchMarker) parent._searchMarker.length = 0
|
|
979
|
+
if (record === null) logHold(tr, item, len)
|
|
980
|
+
}
|
|
981
|
+
const after = item.mover
|
|
982
|
+
if (before !== after) {
|
|
983
|
+
logMove(tr, item, after)
|
|
984
|
+
if (before !== null) addChangedTypeToTransaction(tr, /** @type {YNode} */ (before.parent), null)
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* A hold this replica cannot apply yet (its items have not arrived): it waits
|
|
990
|
+
* in `store.pendingHolds` (`client → [clock, len, delta, …]`).
|
|
991
|
+
*
|
|
992
|
+
* @param {import('../utils/StructStore.js').StructStore} store
|
|
993
|
+
* @param {number} client
|
|
994
|
+
* @param {number} from
|
|
995
|
+
* @param {number} to
|
|
996
|
+
* @param {number} delta
|
|
997
|
+
*/
|
|
998
|
+
const pendHold = (store, client, from, to, delta) => {
|
|
999
|
+
const pending = store.pendingHolds ?? (store.pendingHolds = new Map())
|
|
1000
|
+
const list = pending.get(client)
|
|
1001
|
+
if (list === undefined) pending.set(client, [from, to - from, delta])
|
|
1002
|
+
else list.push(from, to - from, delta)
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Hold (`delta` 1) or release (-1) the ids `client:clock … clock+len-1`, or
|
|
1007
|
+
* add or drop the move record `record` on them (YP22): the items here take
|
|
1008
|
+
* it now, split at the edges; what this replica lacks (past its clock, or a
|
|
1009
|
+
* Skip) waits until it integrates (`applyPending`) — a hold's only: a move
|
|
1010
|
+
* record integrates once every unit it names has, and no Skip stands for
|
|
1011
|
+
* one (`getMissing`). A GC struct takes nothing (it is gone everywhere).
|
|
1012
|
+
*
|
|
1013
|
+
* @param {Transaction} tr
|
|
1014
|
+
* @param {number} client
|
|
1015
|
+
* @param {number} clock
|
|
1016
|
+
* @param {number} len
|
|
1017
|
+
* @param {number} delta
|
|
1018
|
+
* @param {Item|null} [record]
|
|
1019
|
+
*/
|
|
1020
|
+
const holdRange = (tr, client, clock, len, delta, record = null) => {
|
|
1021
|
+
const store = tr.doc.store
|
|
1022
|
+
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
|
|
1023
|
+
const end = clock + len
|
|
1024
|
+
const state = store.getClock(client)
|
|
1025
|
+
if (end > state) pendHold(store, client, math.max(clock, state), end, delta)
|
|
1026
|
+
for (let k = clock; k < math.min(end, state);) {
|
|
1027
|
+
let struct = structs[findIndexSS(structs, k)]
|
|
1028
|
+
const to = math.min(struct.id.clock + struct.length, end)
|
|
1029
|
+
if (struct.isItem) {
|
|
1030
|
+
if (struct.id.clock < k) struct = getItemCleanStart(tr, createID(client, k))
|
|
1031
|
+
if (struct.id.clock + struct.length > to) getItemCleanStart(tr, createID(client, to))
|
|
1032
|
+
holdItem(tr, /** @type {Item} */ (struct), delta, record)
|
|
1033
|
+
} else if (!struct.deleted) {
|
|
1034
|
+
pendHold(store, client, k, to, delta) // a Skip
|
|
1035
|
+
}
|
|
1036
|
+
k = to
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* Hold or release (`delta`) the items `ids` names (`client, clock, len`
|
|
1042
|
+
* triples), or add or drop the move record `record` on them.
|
|
1043
|
+
*
|
|
1044
|
+
* @param {Transaction} tr
|
|
1045
|
+
* @param {Array<number>} ids
|
|
1046
|
+
* @param {number} delta
|
|
1047
|
+
* @param {Item|null} [record]
|
|
1048
|
+
*/
|
|
1049
|
+
const holdIds = (tr, ids, delta, record = null) => {
|
|
1050
|
+
for (let i = 0; i < ids.length; i += 3) holdRange(tr, ids[i], ids[i + 1], ids[i + 2], delta, record)
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Write the id ranges `ids[from … to)` (`client, clock, len` triples): their
|
|
1055
|
+
* count, then each as a left id and a length (a hold record's and a move
|
|
1056
|
+
* record's ranges).
|
|
1057
|
+
*
|
|
1058
|
+
* @param {UpdateEncoder} encoder
|
|
1059
|
+
* @param {Array<number>} ids
|
|
1060
|
+
* @param {number} from
|
|
1061
|
+
* @param {number} to
|
|
1062
|
+
*/
|
|
1063
|
+
const writeIds = (encoder, ids, from, to) => {
|
|
1064
|
+
encoder.writeLen((to - from) / 3)
|
|
1065
|
+
for (let i = from; i < to; i += 3) {
|
|
1066
|
+
encoder.writeLeftID(createID(ids[i], ids[i + 1]))
|
|
1067
|
+
encoder.writeLen(ids[i + 2])
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* A hold record: one unit per held id range (`client, clock, len`). While the
|
|
1073
|
+
* record is live, the items it names read as deleted; deleting the record
|
|
1074
|
+
* shows them again (an undo), unless another live record holds them. A record
|
|
1075
|
+
* integrates at once; what it names that has not arrived takes its hold when
|
|
1076
|
+
* it does (`applyPending`).
|
|
1077
|
+
*/
|
|
1078
|
+
export class ContentHold {
|
|
1079
|
+
/**
|
|
1080
|
+
* @param {Array<number>} ids
|
|
1081
|
+
*/
|
|
1082
|
+
constructor (ids) {
|
|
1083
|
+
this.ids = ids
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* @return {number}
|
|
1088
|
+
*/
|
|
1089
|
+
getLength () {
|
|
1090
|
+
return this.ids.length / 3
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* @return {Array<any>}
|
|
1095
|
+
*/
|
|
1096
|
+
getContent () {
|
|
1097
|
+
const out = []
|
|
1098
|
+
for (let i = 0; i < this.ids.length; i += 3) out.push(this.ids.slice(i, i + 3))
|
|
1099
|
+
return out
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* @return {boolean}
|
|
1104
|
+
*/
|
|
1105
|
+
isCountable () {
|
|
1106
|
+
return true
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* @return {ContentHold}
|
|
1111
|
+
*/
|
|
1112
|
+
copy () {
|
|
1113
|
+
return new ContentHold(this.ids)
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* @param {number} offset
|
|
1118
|
+
* @return {ContentHold}
|
|
1119
|
+
*/
|
|
1120
|
+
splice (offset) {
|
|
1121
|
+
const right = new ContentHold(this.ids.slice(offset * 3))
|
|
1122
|
+
this.ids = this.ids.slice(0, offset * 3)
|
|
1123
|
+
return right
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* @param {ContentHold} right
|
|
1128
|
+
* @return {boolean}
|
|
1129
|
+
*/
|
|
1130
|
+
mergeWith (right) {
|
|
1131
|
+
this.ids = this.ids.concat(right.ids)
|
|
1132
|
+
return true
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* @param {Transaction} transaction
|
|
1137
|
+
* @param {Item} item
|
|
1138
|
+
*/
|
|
1139
|
+
integrate (transaction, item) {
|
|
1140
|
+
if (holding(item)) holdIds(transaction, this.ids, 1) // YP31
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* @param {Transaction} transaction
|
|
1145
|
+
* @param {Item} item
|
|
1146
|
+
*/
|
|
1147
|
+
delete (transaction, item) {
|
|
1148
|
+
if (holding(item)) holdIds(transaction, this.ids, -1) // YP31
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* @param {Transaction} _tr
|
|
1153
|
+
*/
|
|
1154
|
+
gc (_tr) {}
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* @param {UpdateEncoder} encoder
|
|
1158
|
+
* @param {number} offset
|
|
1159
|
+
* @param {number} offsetEnd
|
|
1160
|
+
*/
|
|
1161
|
+
write (encoder, offset, offsetEnd) {
|
|
1162
|
+
writeIds(encoder, this.ids, offset * 3, this.ids.length - offsetEnd * 3)
|
|
845
1163
|
}
|
|
846
1164
|
|
|
847
1165
|
/**
|
|
848
|
-
* @return {
|
|
1166
|
+
* @return {11}
|
|
849
1167
|
*/
|
|
850
1168
|
getRef () {
|
|
1169
|
+
return 11
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
// YP20 end
|
|
1173
|
+
|
|
1174
|
+
// YP31 begin (edytor fork: id lists — see UPSTREAM.md YP31)
|
|
1175
|
+
/**
|
|
1176
|
+
* Whether the hold record `item` holds what it names: one in a root's
|
|
1177
|
+
* sequence does (the document's records list, YP20); one in a nested node's
|
|
1178
|
+
* sequence is a list of ids, the same codec, that holds nothing.
|
|
1179
|
+
*
|
|
1180
|
+
* @param {Item} item
|
|
1181
|
+
* @return {boolean}
|
|
1182
|
+
*/
|
|
1183
|
+
const holding = item => /** @type {YNode} */ (item.parent)._item === null
|
|
1184
|
+
// YP31 end
|
|
1185
|
+
|
|
1186
|
+
// YP32 begin (edytor fork: a delete that proposes — see UPSTREAM.md YP32)
|
|
1187
|
+
/**
|
|
1188
|
+
* Hide the units `ids` names until `unhide` (a suggestion's proposed
|
|
1189
|
+
* deletion, which the rest of its plan reads gone): a hold without a
|
|
1190
|
+
* record, kept in `tr.hidden`.
|
|
1191
|
+
*
|
|
1192
|
+
* @param {Transaction} tr
|
|
1193
|
+
* @param {Array<number>} ids
|
|
1194
|
+
*/
|
|
1195
|
+
export const hideIds = (tr, ids) => {
|
|
1196
|
+
holdIds(tr, ids, 1)
|
|
1197
|
+
;/** @type {any} */ (tr).hidden = (/** @type {any} */ (tr).hidden ?? []).concat(ids)
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Show again what `hideIds` hid in `tr`.
|
|
1202
|
+
*
|
|
1203
|
+
* @param {Transaction} tr
|
|
1204
|
+
*/
|
|
1205
|
+
export const unhide = tr => {
|
|
1206
|
+
const hidden = /** @type {any} */ (tr).hidden
|
|
1207
|
+
if (hidden) holdIds(tr, hidden, -1)
|
|
1208
|
+
;/** @type {any} */ (tr).hidden = null
|
|
1209
|
+
}
|
|
1210
|
+
// YP32 end
|
|
1211
|
+
|
|
1212
|
+
// YP22 begin (edytor fork: move records — see UPSTREAM.md YP22)
|
|
1213
|
+
/**
|
|
1214
|
+
* Whether move record `a` beats `b`: the greater `(priority, client, clock)`
|
|
1215
|
+
* (a unit's `movers` are kept in that order, the strongest first).
|
|
1216
|
+
*
|
|
1217
|
+
* @param {Item} a
|
|
1218
|
+
* @param {Item} b
|
|
1219
|
+
* @return {boolean}
|
|
1220
|
+
*/
|
|
1221
|
+
const stronger = (a, b) =>
|
|
1222
|
+
(/** @type {ContentMove} */ (a.content).priority - /** @type {ContentMove} */ (b.content).priority || a.id.client - b.id.client || a.id.clock - b.id.clock) > 0
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Log that `item`'s mover changed (or it left its mover: removed) in
|
|
1226
|
+
* `tr.moves`, in order, and its home and its new mover's sequences as changed.
|
|
1227
|
+
*
|
|
1228
|
+
* @param {Transaction} tr
|
|
1229
|
+
* @param {Item} item
|
|
1230
|
+
* @param {Item|null} mover
|
|
1231
|
+
*/
|
|
1232
|
+
const logMove = (tr, item, mover) => {
|
|
1233
|
+
tr.moves.push(item.id.client, item.id.clock, item.length)
|
|
1234
|
+
addChangedTypeToTransaction(tr, /** @type {YNode} */ (item.parent), item.parentSub)
|
|
1235
|
+
if (mover !== null) addChangedTypeToTransaction(tr, /** @type {YNode} */ (mover.parent), null)
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Apply the holds (YP20) that waited for items of `clients` an update just
|
|
1240
|
+
* integrated (what is still missing waits on).
|
|
1241
|
+
*
|
|
1242
|
+
* @param {Transaction} tr
|
|
1243
|
+
* @param {Array<number>} clients
|
|
1244
|
+
*/
|
|
1245
|
+
export const applyPending = (tr, clients) => {
|
|
1246
|
+
const store = tr.doc.store
|
|
1247
|
+
for (const client of clients) {
|
|
1248
|
+
const holds = store.pendingHolds?.get(client)
|
|
1249
|
+
if (holds !== undefined) {
|
|
1250
|
+
/** @type {Map<number, Array<number>>} */ (store.pendingHolds).delete(client)
|
|
1251
|
+
for (let i = 0; i < holds.length; i += 3) holdRange(tr, client, holds[i], holds[i + 1], holds[i + 2])
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (store.pendingHolds?.size === 0) store.pendingHolds = null
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* A move record: one countable unit at its destination, naming the units
|
|
1259
|
+
* it moves (`client, clock, len` ranges, in the order they show) with a
|
|
1260
|
+
* Lamport `priority`, a `side` (`0`: it stands after what precedes it,
|
|
1261
|
+
* `1`: before what follows it — its sort class among the items of one
|
|
1262
|
+
* origin, `markClass`), and for the sort of two records at one origin
|
|
1263
|
+
* (`utils/marks.js` `sourceOrder`) the chain of units it stands at (`at`:
|
|
1264
|
+
* the home slots from the root sequence down to that unit, else the first
|
|
1265
|
+
* unit it names) and its `part` there. While the record is live, each named unit shows at
|
|
1266
|
+
* the record that wins its movers and is hidden at home; deleting the
|
|
1267
|
+
* record (an undo) hands the unit to the next record, else home. A record
|
|
1268
|
+
* waits for the units it names as an item waits for its origin
|
|
1269
|
+
* (`getMissing`), so two records at one origin sort by their units' home
|
|
1270
|
+
* order on every replica (`sourceOrder`). Immutable once written, so
|
|
1271
|
+
* replicas converge in any order. The record's item is the one integrating
|
|
1272
|
+
* or deleting it (it is what the units' `movers` hold).
|
|
1273
|
+
*/
|
|
1274
|
+
export class ContentMove {
|
|
1275
|
+
/**
|
|
1276
|
+
* @param {number} priority
|
|
1277
|
+
* @param {number} side
|
|
1278
|
+
* @param {Array<number>} ids
|
|
1279
|
+
* @param {number} [part] the writer's order among the records standing at one unit (0–7)
|
|
1280
|
+
* @param {Array<ID>|null} [at] the chain the record stands at, outermost first (`null`: the first unit it names)
|
|
1281
|
+
*/
|
|
1282
|
+
constructor (priority, side, ids, part = 0, at = null) {
|
|
1283
|
+
this.priority = priority
|
|
1284
|
+
this.side = side
|
|
1285
|
+
this.ids = ids
|
|
1286
|
+
this.part = part
|
|
1287
|
+
this.at = at
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* @return {number}
|
|
1292
|
+
*/
|
|
1293
|
+
getLength () {
|
|
851
1294
|
return 1
|
|
852
1295
|
}
|
|
1296
|
+
|
|
1297
|
+
/**
|
|
1298
|
+
* @return {Array<any>}
|
|
1299
|
+
*/
|
|
1300
|
+
getContent () {
|
|
1301
|
+
return [this]
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* @return {boolean}
|
|
1306
|
+
*/
|
|
1307
|
+
isCountable () {
|
|
1308
|
+
return true
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* @return {ContentMove}
|
|
1313
|
+
*/
|
|
1314
|
+
copy () {
|
|
1315
|
+
return new ContentMove(this.priority, this.side, this.ids, this.part, this.at)
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* @param {number} _offset
|
|
1320
|
+
* @return {ContentMove}
|
|
1321
|
+
*/
|
|
1322
|
+
splice (_offset) {
|
|
1323
|
+
throw error.methodUnimplemented()
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* @param {ContentMove} _right
|
|
1328
|
+
* @return {boolean}
|
|
1329
|
+
*/
|
|
1330
|
+
mergeWith (_right) {
|
|
1331
|
+
return false
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* @param {Transaction} transaction
|
|
1336
|
+
* @param {Item} item
|
|
1337
|
+
*/
|
|
1338
|
+
integrate (transaction, item) {
|
|
1339
|
+
const doc = /** @type {any} */ (transaction.doc)
|
|
1340
|
+
// A peer's record raises the clock; this writer's own leave it, so its
|
|
1341
|
+
// next records stay level with these until it sees a peer's.
|
|
1342
|
+
if (!transaction.local && this.priority > doc._clock) doc._clock = this.priority
|
|
1343
|
+
holdIds(transaction, this.ids, 1, item)
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* @param {Transaction} transaction
|
|
1348
|
+
* @param {Item} item
|
|
1349
|
+
*/
|
|
1350
|
+
delete (transaction, item) {
|
|
1351
|
+
holdIds(transaction, this.ids, -1, item)
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
/**
|
|
1355
|
+
* @param {Transaction} _tr
|
|
1356
|
+
*/
|
|
1357
|
+
gc (_tr) {}
|
|
1358
|
+
|
|
1359
|
+
/**
|
|
1360
|
+
* @param {UpdateEncoder} encoder
|
|
1361
|
+
* @param {number} _offset
|
|
1362
|
+
* @param {number} _offsetEnd
|
|
1363
|
+
*/
|
|
1364
|
+
write (encoder, _offset, _offsetEnd) {
|
|
1365
|
+
// One varint: the priority, whether an anchor chain follows, the part, the side.
|
|
1366
|
+
encoder.writeLen(this.priority * 32 + (this.at === null ? 0 : 16) + this.part * 2 + this.side)
|
|
1367
|
+
if (this.at !== null) {
|
|
1368
|
+
encoder.writeLen(this.at.length)
|
|
1369
|
+
for (const id of this.at) encoder.writeLeftID(id)
|
|
1370
|
+
}
|
|
1371
|
+
writeIds(encoder, this.ids, 0, this.ids.length)
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
* @return {12}
|
|
1376
|
+
*/
|
|
1377
|
+
getRef () {
|
|
1378
|
+
return 12
|
|
1379
|
+
}
|
|
853
1380
|
}
|
|
1381
|
+
// YP22 end
|
|
854
1382
|
|
|
855
1383
|
/**
|
|
856
1384
|
* @private
|
|
857
1385
|
*/
|
|
1386
|
+
// YP25 begin (edytor fork: paired marks written natively — see UPSTREAM.md YP25)
|
|
1387
|
+
/**
|
|
1388
|
+
* Whether `x` is a non-negative integer (a clock, a client id, a stamp).
|
|
1389
|
+
*
|
|
1390
|
+
* @param {any} x
|
|
1391
|
+
* @return {boolean}
|
|
1392
|
+
*/
|
|
1393
|
+
const natural = x => Number.isInteger(x) && x >= 0
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* Write a format item's value. A paired mark's (YP13) is written in parts: a
|
|
1397
|
+
* flags length — bit 0 its side; bit 1 a start's operation id is the item's
|
|
1398
|
+
* own, an end's start is the item's own client (its clock follows as a
|
|
1399
|
+
* distance); bit 2 a start's value is `true`, bit 3 `null` — then a start's
|
|
1400
|
+
* Lamport stamp, the operation id where the flags do not imply it, and a
|
|
1401
|
+
* start's value where it is neither `true` nor `null`. Any other value (a
|
|
1402
|
+
* plain format, a value of another shape) is flagged `16` and written whole.
|
|
1403
|
+
*
|
|
1404
|
+
* @param {UpdateEncoder} encoder
|
|
1405
|
+
* @param {ContentFormat} content
|
|
1406
|
+
* @param {Item} [item]
|
|
1407
|
+
*/
|
|
1408
|
+
const writeFormatValue = (encoder, content, item) => {
|
|
1409
|
+
const v = /** @type {any} */ (content.value)
|
|
1410
|
+
const role = pairedRole(content)
|
|
1411
|
+
if (role === 0 && Array.isArray(v) && v.length === 5 && natural(v[1]) && natural(v[2]) && natural(v[3]) && (v[4] === 0 || v[4] === 1)) {
|
|
1412
|
+
const own = item !== undefined && v[2] === item.id.client && v[3] === item.id.clock
|
|
1413
|
+
const tail = v[0] === true ? 4 : v[0] === null ? 8 : 0
|
|
1414
|
+
encoder.writeLen(v[4] | (own ? 2 : 0) | tail)
|
|
1415
|
+
encoder.writeLen(v[1])
|
|
1416
|
+
if (!own) encoder.writeLeftID(createID(v[2], v[3]))
|
|
1417
|
+
if (tail === 0) encoder.writeJSON(v[0])
|
|
1418
|
+
} else if (role === 1 && Array.isArray(v) && v.length === 3 && natural(v[0]) && natural(v[1]) && (v[2] === 0 || v[2] === 1)) {
|
|
1419
|
+
const near = item !== undefined && v[0] === item.id.client && v[1] <= item.id.clock
|
|
1420
|
+
encoder.writeLen(v[2] | (near ? 2 : 0))
|
|
1421
|
+
if (near) encoder.writeLen(/** @type {Item} */ (item).id.clock - v[1])
|
|
1422
|
+
else encoder.writeLeftID(createID(v[0], v[1]))
|
|
1423
|
+
} else {
|
|
1424
|
+
encoder.writeLen(16)
|
|
1425
|
+
encoder.writeJSON(v)
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/**
|
|
1430
|
+
* Read a format item's value (`writeFormatValue`): `id` is the item's.
|
|
1431
|
+
*
|
|
1432
|
+
* @param {UpdateDecoder} decoder
|
|
1433
|
+
* @param {string} key
|
|
1434
|
+
* @param {ID} id
|
|
1435
|
+
* @return {any}
|
|
1436
|
+
*/
|
|
1437
|
+
export const readFormatValue = (decoder, key, id) => {
|
|
1438
|
+
const flags = decoder.readLen()
|
|
1439
|
+
if ((flags & 16) !== 0) return decoder.readJSON()
|
|
1440
|
+
const side = flags & 1
|
|
1441
|
+
const role = pairedRole({ key })
|
|
1442
|
+
if (role === 0) {
|
|
1443
|
+
const l = decoder.readLen()
|
|
1444
|
+
const at = (flags & 2) !== 0 ? id : decoder.readLeftID()
|
|
1445
|
+
const v = (flags & 4) !== 0 ? true : (flags & 8) !== 0 ? null : decoder.readJSON()
|
|
1446
|
+
return [v, l, at.client, at.clock, side]
|
|
1447
|
+
}
|
|
1448
|
+
if (role !== 1) error.unexpectedCase()
|
|
1449
|
+
const at = (flags & 2) !== 0 ? createID(id.client, id.clock - decoder.readLen()) : decoder.readLeftID()
|
|
1450
|
+
return [at.client, at.clock, side]
|
|
1451
|
+
}
|
|
1452
|
+
// YP25 end
|
|
1453
|
+
|
|
858
1454
|
export class ContentFormat {
|
|
859
1455
|
/**
|
|
860
1456
|
* @param {string} key
|
|
@@ -916,11 +1512,12 @@ export class ContentFormat {
|
|
|
916
1512
|
integrate (_transaction, item) {
|
|
917
1513
|
// Format items are non-countable: no marker index shifts (YP4).
|
|
918
1514
|
// YP13: a paired mark's start carries its Lamport timestamp; the document
|
|
919
|
-
// keeps the greatest it saw
|
|
1515
|
+
// keeps the greatest it saw (YP22: one clock with the move records), so the
|
|
1516
|
+
// next mark or move it writes wins over it.
|
|
920
1517
|
if (pairedRole(this) === 0) {
|
|
921
1518
|
const doc = /** @type {any} */ (_transaction.doc)
|
|
922
1519
|
const l = /** @type {Array<any>} */ (this.value)[1]
|
|
923
|
-
if (l > doc.
|
|
1520
|
+
if (l > doc._clock) doc._clock = l
|
|
924
1521
|
}
|
|
925
1522
|
}
|
|
926
1523
|
|
|
@@ -936,10 +1533,11 @@ export class ContentFormat {
|
|
|
936
1533
|
* @param {UpdateEncoder} encoder
|
|
937
1534
|
* @param {number} _offset
|
|
938
1535
|
* @param {number} _offsetEnd
|
|
1536
|
+
* @param {Item} [item] YP25: the item, whose id a mark's operation id usually is
|
|
939
1537
|
*/
|
|
940
|
-
write (encoder, _offset, _offsetEnd) {
|
|
1538
|
+
write (encoder, _offset, _offsetEnd, item) {
|
|
941
1539
|
encoder.writeKey(this.key)
|
|
942
|
-
encoder
|
|
1540
|
+
writeFormatValue(encoder, this, item) // YP25
|
|
943
1541
|
}
|
|
944
1542
|
|
|
945
1543
|
/**
|
|
@@ -1132,7 +1730,7 @@ export class ContentType {
|
|
|
1132
1730
|
delete (transaction) {
|
|
1133
1731
|
let item = this.type._start
|
|
1134
1732
|
while (item !== null) {
|
|
1135
|
-
if (!item.
|
|
1733
|
+
if (!item.removed) { // YP20: a held child goes with its node
|
|
1136
1734
|
item.delete(transaction)
|
|
1137
1735
|
} else if (!transaction.insertSet.hasId(item.id)) {
|
|
1138
1736
|
// This will be gc'd later and we want to merge it if possible
|
|
@@ -1144,7 +1742,7 @@ export class ContentType {
|
|
|
1144
1742
|
item = item.right
|
|
1145
1743
|
}
|
|
1146
1744
|
this.type._map.forEach(item => {
|
|
1147
|
-
if (!item.
|
|
1745
|
+
if (!item.removed) { // YP20
|
|
1148
1746
|
item.delete(transaction)
|
|
1149
1747
|
} else if (!transaction.insertSet.hasId(item.id)) {
|
|
1150
1748
|
// same as above
|