edytor 3.0.0 → 4.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/cloudflare/DocumentRoom.d.ts +13 -9
- package/dist/cloudflare/DocumentRoom.js +10 -6
- package/dist/cloudflare/room/context.d.ts +27 -26
- package/dist/cloudflare/room/context.js +2 -2
- package/dist/cloudflare/room/purge.d.ts +6 -3
- package/dist/cloudflare/room/purge.js +44 -19
- package/dist/cloudflare/room/updates.d.ts +3 -4
- package/dist/cloudflare/room/updates.js +6 -8
- package/dist/crdt/attribution/attribution.d.ts +31 -12
- package/dist/crdt/attribution/attribution.js +56 -18
- 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/delete.d.ts +1 -1
- package/dist/crdt/doc/delete.js +13 -30
- package/dist/crdt/doc/funnel.js +29 -24
- package/dist/crdt/doc/gate.d.ts +3 -3
- package/dist/crdt/doc/history.d.ts +25 -9
- package/dist/crdt/doc/history.js +150 -40
- package/dist/crdt/doc/layout.js +11 -22
- package/dist/crdt/doc/meta.js +12 -17
- package/dist/crdt/doc/moves.d.ts +1 -1
- package/dist/crdt/doc/moves.js +52 -32
- package/dist/crdt/doc/reads.js +2 -2
- 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 +52 -122
- package/dist/crdt/doc/types.d.ts +21 -9
- package/dist/crdt/document.d.ts +16 -5
- package/dist/crdt/document.js +21 -4
- package/dist/crdt/edytor-doc.d.ts +62 -64
- package/dist/crdt/edytor-doc.js +18 -13
- package/dist/crdt/engine-api.d.ts +33 -1
- package/dist/crdt/engine.d.ts +0 -1
- package/dist/crdt/engine.js +1 -4
- package/dist/crdt/flow.d.ts +1 -1
- package/dist/crdt/flow.js +25 -27
- package/dist/crdt/index.d.ts +26 -25
- package/dist/crdt/index.js +1 -1
- package/dist/crdt/placement/display.d.ts +5 -30
- package/dist/crdt/placement/display.js +39 -144
- package/dist/crdt/placement/keys.d.ts +58 -0
- package/dist/crdt/placement/keys.js +90 -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 +187 -153
- package/dist/crdt/placement/slots.d.ts +134 -0
- package/dist/crdt/placement/slots.js +152 -0
- package/dist/crdt/providers/room.d.ts +16 -2
- package/dist/crdt/providers/room.js +64 -17
- package/dist/crdt/providers/websocket.js +5 -3
- package/dist/crdt/purge.d.ts +17 -8
- package/dist/crdt/purge.js +236 -126
- package/dist/crdt/restore.d.ts +2 -2
- package/dist/crdt/restore.js +18 -34
- package/dist/crdt/schema.d.ts +22 -21
- package/dist/crdt/schema.js +29 -23
- package/dist/crdt/storage.d.ts +1 -1
- package/dist/crdt/storage.js +1 -1
- package/dist/crdt/structs.d.ts +15 -15
- package/dist/crdt/structs.js +8 -22
- package/dist/crdt/text/index/cache.js +4 -0
- package/dist/crdt/text/index/claims.js +12 -0
- 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.js +5 -8
- package/dist/crdt/text/index/placement.d.ts +26 -18
- package/dist/crdt/text/index/placement.js +404 -138
- 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 +12 -3
- 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 +77 -62
- package/dist/crdt/text/model.js +84 -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 +17 -12
- 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 +568 -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 +194 -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 +43 -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 +610 -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 +177 -12
- package/dist/edytor.svelte.js +4 -1
- package/dist/events/beforeInputCommands.js +2 -2
- package/dist/events/onPageHide.d.ts +3 -0
- package/dist/events/onPageHide.js +16 -0
- package/dist/export/forms.d.ts +25 -25
- package/dist/surface/observer.svelte.js +2 -2
- package/dist/surface/projector.svelte.d.ts +15 -3
- package/dist/surface/projector.svelte.js +59 -8
- package/package.json +2 -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
|
@@ -481,11 +481,11 @@ export const createDeleteSetFromStructStore = ss => {
|
|
|
481
481
|
const dsitems = []
|
|
482
482
|
for (let i = 0; i < structs.length; i++) {
|
|
483
483
|
const struct = structs[i]
|
|
484
|
-
if (struct.
|
|
484
|
+
if (struct.removed) { // YP20: a held item is not deleted
|
|
485
485
|
const clock = struct.id.clock
|
|
486
486
|
let len = struct.length
|
|
487
487
|
if (i + 1 < structs.length) {
|
|
488
|
-
for (let next = structs[i + 1]; i + 1 < structs.length && next.
|
|
488
|
+
for (let next = structs[i + 1]; i + 1 < structs.length && next.removed; next = structs[++i + 1]) {
|
|
489
489
|
len += next.length
|
|
490
490
|
}
|
|
491
491
|
}
|
|
@@ -514,7 +514,7 @@ export const writeIdSet = (encoder, idSet) => {
|
|
|
514
514
|
.forEach(([client, _idRanges]) => {
|
|
515
515
|
const idRanges = _idRanges.getIds()
|
|
516
516
|
encoder.resetIdSetCurVal()
|
|
517
|
-
|
|
517
|
+
encoder.writeDsClient(client) // YP26
|
|
518
518
|
const len = idRanges.length
|
|
519
519
|
encoding.writeVarUint(encoder.restEncoder, len)
|
|
520
520
|
for (let i = 0; i < len; i++) {
|
|
@@ -537,7 +537,7 @@ export const readIdSet = decoder => {
|
|
|
537
537
|
const numClients = decoding.readVarUint(decoder.restDecoder)
|
|
538
538
|
for (let i = 0; i < numClients; i++) {
|
|
539
539
|
decoder.resetDsCurVal()
|
|
540
|
-
const client =
|
|
540
|
+
const client = decoder.readDsClient() // YP26
|
|
541
541
|
const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
|
|
542
542
|
if (numberOfDeletes > 0) {
|
|
543
543
|
/**
|
|
@@ -571,7 +571,7 @@ export const readAndApplyDeleteSet = (decoder, transaction, store) => {
|
|
|
571
571
|
const numClients = decoding.readVarUint(decoder.restDecoder)
|
|
572
572
|
for (let i = 0; i < numClients; i++) {
|
|
573
573
|
decoder.resetDsCurVal()
|
|
574
|
-
const client =
|
|
574
|
+
const client = decoder.readDsClient() // YP26
|
|
575
575
|
const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
|
|
576
576
|
const structs = store.clients.get(client) || []
|
|
577
577
|
const state = store.getClock(client)
|
|
@@ -589,7 +589,7 @@ export const readAndApplyDeleteSet = (decoder, transaction, store) => {
|
|
|
589
589
|
*/
|
|
590
590
|
let struct = structs[index]
|
|
591
591
|
// split the first item if necessary
|
|
592
|
-
if (!struct.
|
|
592
|
+
if (!struct.removed && struct.id.clock < clock && struct.isItem) { // YP20: removed, not held
|
|
593
593
|
// increment index, we now want to use the next struct
|
|
594
594
|
structs.splice(++index, 0, /** @type {Item} */ (struct).split(transaction, clock - struct.id.clock))
|
|
595
595
|
}
|
|
@@ -597,7 +597,7 @@ export const readAndApplyDeleteSet = (decoder, transaction, store) => {
|
|
|
597
597
|
// @ts-ignore
|
|
598
598
|
struct = structs[index++]
|
|
599
599
|
if (struct.id.clock < clockEnd) {
|
|
600
|
-
if (!struct.
|
|
600
|
+
if (!struct.removed) { // YP20
|
|
601
601
|
if (struct.isItem) {
|
|
602
602
|
if (clockEnd < struct.id.clock + struct.length) {
|
|
603
603
|
structs.splice(index, 0, /** @type {Item} */ (struct).split(transaction, clockEnd - struct.id.clock))
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('./Transaction.js').Transaction} Transaction
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Paired marks — an edytor fork addition (UPSTREAM.md YP13), not upstream source.
|
|
3
7
|
*
|
|
@@ -117,33 +121,214 @@ export const formatValue = value => value instanceof MarkState ? value.value : v
|
|
|
117
121
|
/**
|
|
118
122
|
* The integration class of an item among the items of one origin: a
|
|
119
123
|
* left-side mark item `0`, content and plain formats `1`, a right-side
|
|
120
|
-
* mark item `2
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
+
* mark item `2`, a claim item `3` (YP21: a sequence unit `{ m }`, after
|
|
125
|
+
* everything else, so content and boundaries a concurrent writer puts at
|
|
126
|
+
* its origin land before it), a move record `4` or `5` by its side (YP22:
|
|
127
|
+
* after every slot of its origin — a split's pieces first — and a record
|
|
128
|
+
* standing after what precedes it before one standing before what follows
|
|
129
|
+
* it: the order every serial run of the gestures gives). Deleted or collected items keep the class
|
|
130
|
+
* their content gave them while it is there; a collected item (its content
|
|
131
|
+
* gone) would read `1`, which is why the fork never collects the content of
|
|
132
|
+
* an item of another class (`Item#gc` keeps it).
|
|
124
133
|
*
|
|
125
134
|
* @param {any} item
|
|
126
135
|
* @return {number}
|
|
127
136
|
*/
|
|
128
137
|
export const markClass = item => {
|
|
129
138
|
const content = item.content
|
|
130
|
-
if (content === undefined
|
|
139
|
+
if (content === undefined) return 1
|
|
140
|
+
if (content.cls !== undefined) return content.cls // YP29: a collected mark or claim (`ContentDeleted`)
|
|
141
|
+
if (typeof content.key !== 'string') {
|
|
142
|
+
// YP22: a move record (its side: 0 after, 1 before)
|
|
143
|
+
if (content.priority !== undefined && content.ids !== undefined) return 4 + content.side
|
|
144
|
+
// YP21: a claim item
|
|
145
|
+
return item.parentSub === null && typeof content.arr?.[0]?.m === 'string' ? 3 : 1
|
|
146
|
+
}
|
|
131
147
|
const role = pairedRole(content)
|
|
132
148
|
if (role < 0) return 1
|
|
133
149
|
const side = role === 0 ? content.value[4] : content.value[2]
|
|
134
150
|
return side === SIDE_LEFT ? 0 : 2
|
|
135
151
|
}
|
|
136
152
|
|
|
153
|
+
/**
|
|
154
|
+
* YP22: the unit id `client:clock` names, as its item and clock, `null` where
|
|
155
|
+
* a GC struct replaced it.
|
|
156
|
+
*
|
|
157
|
+
* @param {import('./StructStore.js').StructStore} store
|
|
158
|
+
* @param {number} client
|
|
159
|
+
* @param {number} clock
|
|
160
|
+
* @return {{ item: any, clock: number } | null}
|
|
161
|
+
*/
|
|
162
|
+
const unitAt = (store, client, clock) => {
|
|
163
|
+
const item = store.getItem({ client, clock })
|
|
164
|
+
return item.isItem ? { item, clock } : null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* YP22: the order of two units (`unitAt`): by clock in one item, else by
|
|
169
|
+
* `sequenceOrder` (`NaN`: not in one sequence); `0` for one unit.
|
|
170
|
+
*
|
|
171
|
+
* @param {{ item: any, clock: number }} p
|
|
172
|
+
* @param {{ item: any, clock: number }} q
|
|
173
|
+
* @return {number}
|
|
174
|
+
*/
|
|
175
|
+
const unitOrder = (p, q) => (p.item === q.item ? p.clock - q.clock : sequenceOrder(p.item, q.item))
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* YP22: a block slot's split key (`{ b, s: [client, clock] }`: the id of the
|
|
179
|
+
* boundary item of the split that made the block, kept when the slot is
|
|
180
|
+
* collected, `Item#gc`) as a unit (`unitAt`: `null` where a GC struct
|
|
181
|
+
* replaced the boundary), `undefined` for any other item (a slot no split
|
|
182
|
+
* made, a boundary, whose own `s` names a block).
|
|
183
|
+
*
|
|
184
|
+
* @param {any} item
|
|
185
|
+
* @param {import('./StructStore.js').StructStore} store
|
|
186
|
+
* @return {{ item: any, clock: number } | null | undefined}
|
|
187
|
+
*/
|
|
188
|
+
const splitKey = (item, store) => {
|
|
189
|
+
const s = item.content.arr?.[0]?.s
|
|
190
|
+
return Array.isArray(s) ? unitAt(store, s[0], s[1]) : undefined
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* YP22: whether `item` is a list item outside a text (a block's or an
|
|
195
|
+
* array's slot: its parent is no `content` node) written by the writer of
|
|
196
|
+
* its origin: it extends that writer's run there, and sorts before every
|
|
197
|
+
* other writer's slot of the same origin (`order.insert.run`: the lines one
|
|
198
|
+
* writer types after its own line stay together around a peer's line
|
|
199
|
+
* inserted there meanwhile). Read from the item's ids and parent, which
|
|
200
|
+
* collection never changes, so every replica integrates alike.
|
|
201
|
+
*
|
|
202
|
+
* @param {any} item
|
|
203
|
+
* @return {boolean}
|
|
204
|
+
*/
|
|
205
|
+
const extendsRun = (/** @type {Item} */ item) =>
|
|
206
|
+
item.parentSub === null && /** @type {YNode} */ (item.parent).name !== 'content' && item.origin !== null && item.origin.client === item.id.client
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* YP22: the order of two items of one sequence (one parent, one key):
|
|
210
|
+
* negative when `x` stands before `y`, positive after, `0` for one item,
|
|
211
|
+
* `NaN` when they are not in one sequence. The walk goes left from both at
|
|
212
|
+
* once, so it costs the distance between them.
|
|
213
|
+
*
|
|
214
|
+
* @param {any} x
|
|
215
|
+
* @param {any} y
|
|
216
|
+
* @return {number}
|
|
217
|
+
*/
|
|
218
|
+
export const sequenceOrder = (x, y) => {
|
|
219
|
+
if (x === y) return 0
|
|
220
|
+
if (x.parent !== y.parent || x.parentSub !== y.parentSub) return NaN
|
|
221
|
+
for (let l = x, r = y; ;) {
|
|
222
|
+
l = l.left
|
|
223
|
+
r = r.left
|
|
224
|
+
if (l === y || r === null) return 1
|
|
225
|
+
if (r === x || l === null) return -1
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* YP22: the chain record `r` stands at, outermost first — the units its
|
|
231
|
+
* writer named (`at`: the home slots from the root sequence down to the unit
|
|
232
|
+
* it stands at), else the first unit it moves — each as its item and clock,
|
|
233
|
+
* `null` where a GC struct replaced it.
|
|
234
|
+
*
|
|
235
|
+
* @param {any} r
|
|
236
|
+
* @param {import('./StructStore.js').StructStore} store
|
|
237
|
+
* @return {Array<{ item: any, clock: number } | null>}
|
|
238
|
+
*/
|
|
239
|
+
const chainOf = (r, store) => {
|
|
240
|
+
const c = r.content
|
|
241
|
+
return c.at !== null
|
|
242
|
+
? c.at.map((/** @type {ID} */ id) => unitAt(store, id.client, id.clock))
|
|
243
|
+
: c.ids.length > 0 ? [unitAt(store, c.ids[0], c.ids[1])] : []
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* YP22: the order of two move records at one origin: by the chains they
|
|
248
|
+
* stand at (`chainOf`), level by level from the root — two units of one
|
|
249
|
+
* sequence by `sequenceOrder` (one item: by clock), a unit before the units
|
|
250
|
+
* inside its block (the shorter chain first); chains that meet no common
|
|
251
|
+
* sequence order nothing — then by their `part` (the writer's order among
|
|
252
|
+
* the records standing at one unit), then the stronger record first (the
|
|
253
|
+
* greater priority, then client: a unit both move shows at the first of the
|
|
254
|
+
* two). Two writers that move blocks out of one list into one gap at once (an
|
|
255
|
+
* outdent, a lift, a divider after an item) keep the list's order there and
|
|
256
|
+
* every serial order's placement of what stands at one item, whatever their
|
|
257
|
+
* client ids (the delete contract's CW-01 and DR-crdt-6 rules). It reads only
|
|
258
|
+
* the records and the units they name, all present when a record integrates
|
|
259
|
+
* (`getMissing`), so every replica sorts alike in any delivery order.
|
|
260
|
+
*
|
|
261
|
+
* @param {any} o
|
|
262
|
+
* @param {any} item
|
|
263
|
+
* @param {Transaction} tr
|
|
264
|
+
* @return {number}
|
|
265
|
+
*/
|
|
266
|
+
const sourceOrder = (o, item, tr) => {
|
|
267
|
+
const store = tr.doc.store
|
|
268
|
+
const x = chainOf(o, store)
|
|
269
|
+
const y = chainOf(item, store)
|
|
270
|
+
let d = 0
|
|
271
|
+
for (let i = 0; ; i++) {
|
|
272
|
+
const p = x[i]
|
|
273
|
+
const q = y[i]
|
|
274
|
+
if (p === undefined || q === undefined) {
|
|
275
|
+
d = (p === undefined ? 0 : 1) - (q === undefined ? 0 : 1)
|
|
276
|
+
break
|
|
277
|
+
}
|
|
278
|
+
if (p === null || q === null) break
|
|
279
|
+
d = unitOrder(p, q)
|
|
280
|
+
if (d === 0) continue
|
|
281
|
+
if (Number.isNaN(d)) d = 0
|
|
282
|
+
break
|
|
283
|
+
}
|
|
284
|
+
if (d === 0) d = o.content.part - item.content.part
|
|
285
|
+
if (d === 0) d = item.content.priority - o.content.priority || item.id.client - o.id.client
|
|
286
|
+
return d
|
|
287
|
+
}
|
|
288
|
+
|
|
137
289
|
/**
|
|
138
290
|
* Whether `o` sorts before `item` among the items of one origin (the
|
|
139
|
-
* integration's conflict rule): by class, then
|
|
291
|
+
* integration's conflict rule): by class, then (YP22) block slots by their
|
|
292
|
+
* split key and the origin writer's own first, records by `sourceOrder`,
|
|
293
|
+
* then by client id as upstream.
|
|
140
294
|
*
|
|
141
295
|
* @param {any} o
|
|
142
296
|
* @param {any} item
|
|
297
|
+
* @param {Transaction} tr
|
|
143
298
|
* @return {boolean}
|
|
144
299
|
*/
|
|
145
|
-
export const sortsBefore = (o, item) => {
|
|
300
|
+
export const sortsBefore = (o, item, tr) => {
|
|
146
301
|
const a = markClass(o)
|
|
147
302
|
const b = markClass(item)
|
|
148
|
-
|
|
303
|
+
if (a !== b) return a < b
|
|
304
|
+
if (a === 1) {
|
|
305
|
+
// YP22: block slots a split made sort by their boundaries' order in the
|
|
306
|
+
// text (two writers' splits of one block keep its text's order), before
|
|
307
|
+
// the slots no split made; then the origin writer's own slots first.
|
|
308
|
+
const store = tr.doc.store
|
|
309
|
+
const x = splitKey(o, store)
|
|
310
|
+
const y = splitKey(item, store)
|
|
311
|
+
if (x !== undefined || y !== undefined) {
|
|
312
|
+
if (x === undefined || y === undefined) return y === undefined
|
|
313
|
+
if (x !== null && y !== null) {
|
|
314
|
+
// One total order over the keyed slots of an origin: the boundaries' text
|
|
315
|
+
// first (by the text node's item id, a root text first), then their place in
|
|
316
|
+
// it (two boundaries one item holds: their clocks).
|
|
317
|
+
const px = /** @type {YNode} */ (x.item.parent)._item
|
|
318
|
+
const py = /** @type {YNode} */ (y.item.parent)._item
|
|
319
|
+
if (x.item.parent !== y.item.parent) {
|
|
320
|
+
if (px === null || py === null) return px === null
|
|
321
|
+
return (px.id.client - py.id.client || px.id.clock - py.id.clock) < 0
|
|
322
|
+
}
|
|
323
|
+
const d = unitOrder(x, y)
|
|
324
|
+
if (d !== 0) return d < 0
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const run = extendsRun(o)
|
|
328
|
+
if (run !== extendsRun(item)) return run
|
|
329
|
+
} else if (a >= 4) {
|
|
330
|
+
const d = sourceOrder(o, item, tr)
|
|
331
|
+
if (d !== 0) return d < 0
|
|
332
|
+
}
|
|
333
|
+
return o.id.client < item.id.client
|
|
149
334
|
}
|
|
@@ -38,7 +38,7 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, deletedByH
|
|
|
38
38
|
*/
|
|
39
39
|
let right
|
|
40
40
|
// make sure that parent is redone
|
|
41
|
-
if (parentItem !== null && parentItem.
|
|
41
|
+
if (parentItem !== null && parentItem.removed) { // YP20: a held parent is alive
|
|
42
42
|
// try to undo parent if it will be undone anyway
|
|
43
43
|
if (parentItem.redone === null && (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems, itemsToDelete, deletedByHistory) === null)) {
|
|
44
44
|
return null
|
|
@@ -245,7 +245,7 @@ export const tryGcDeleteSet = (tr, ds, gcFilter) => {
|
|
|
245
245
|
if (deleteItem.clock + deleteItem.len <= struct.id.clock) {
|
|
246
246
|
break
|
|
247
247
|
}
|
|
248
|
-
if (struct.isItem && struct.
|
|
248
|
+
if (struct.isItem && struct.removed && !(struct).keep && !isKeptReplaced(/** @type {Item} */ (struct)) && gcFilter(/** @type {Item} */ (struct))) { // YP14, YP20: removed
|
|
249
249
|
/** @type {Item} */ (struct).gc(tr, false)
|
|
250
250
|
}
|
|
251
251
|
}
|
|
@@ -42,9 +42,9 @@ function * lazyStructReaderGenerator (decoder) {
|
|
|
42
42
|
null, // right
|
|
43
43
|
(info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null, // right origin
|
|
44
44
|
// @ts-ignore Force writing a string here.
|
|
45
|
-
cantCopyParentInfo ? (
|
|
46
|
-
cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.
|
|
47
|
-
readItemContent(decoder, info) // item content
|
|
45
|
+
cantCopyParentInfo ? ((info & binary.BIT5) === binary.BIT5 ? decoder.readKey() : decoder.readLeftID()) : null, // parent (YP27: bit 5 a root's name, YP24: a key)
|
|
46
|
+
cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readKey() : null, // parentSub (YP24: a key)
|
|
47
|
+
readItemContent(decoder, info, createID(client, clock)) // item content (YP25: with its id)
|
|
48
48
|
)
|
|
49
49
|
yield struct
|
|
50
50
|
clock += struct.length
|
|
@@ -10,6 +10,9 @@ import {
|
|
|
10
10
|
ContentFormat,
|
|
11
11
|
ContentString,
|
|
12
12
|
ContentType,
|
|
13
|
+
ContentHold, // YP20
|
|
14
|
+
ContentMove, // YP22
|
|
15
|
+
readFormatValue, // YP25
|
|
13
16
|
YXmlFragmentRefID,
|
|
14
17
|
YXmlElementRefID,
|
|
15
18
|
YXmlHookRefID
|
|
@@ -248,7 +251,7 @@ const insertMarkItem = (transaction, parent, index, side, content) => {
|
|
|
248
251
|
const markHelper = (transaction, parent, index, length, mark, value, startSide, endSide) => {
|
|
249
252
|
const doc = transaction.doc
|
|
250
253
|
const client = doc.clientID
|
|
251
|
-
const l = /** @type {any} */ (doc).
|
|
254
|
+
const l = /** @type {any} */ (doc)._clock + 1 // YP22: one clock with the move records
|
|
252
255
|
const k = doc.store.getClock(client)
|
|
253
256
|
insertMarkItem(transaction, parent, index, startSide, new ContentFormat(MARK_START + mark, [value, l, client, k, startSide]))
|
|
254
257
|
insertMarkItem(transaction, parent, index + length, endSide, new ContentFormat(MARK_END + mark, [client, k, endSide]))
|
|
@@ -292,28 +295,74 @@ export const insertAtGapEndHelper = (transaction, parent, index, content) => {
|
|
|
292
295
|
}
|
|
293
296
|
// YP7 end
|
|
294
297
|
|
|
298
|
+
// YP20 begin (edytor fork: hold records — see UPSTREAM.md YP20)
|
|
295
299
|
/**
|
|
300
|
+
* The first clock in `(clock, end)` where membership in `set` changes, else `end`.
|
|
301
|
+
*
|
|
302
|
+
* @param {import('./utils/ids.js').IdSet} set
|
|
303
|
+
* @param {number} client
|
|
304
|
+
* @param {number} clock
|
|
305
|
+
* @param {number} end
|
|
306
|
+
* @return {number}
|
|
307
|
+
*/
|
|
308
|
+
const runEnd = (set, client, clock, end) => {
|
|
309
|
+
for (const r of set.clients.get(client)?.getIds() ?? []) {
|
|
310
|
+
if (r.clock > clock) return math.min(r.clock, end)
|
|
311
|
+
if (r.clock + r.len > clock) return math.min(r.clock + r.len, end)
|
|
312
|
+
}
|
|
313
|
+
return end
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Append a hold record of `ids` to `list` (the document's records list).
|
|
318
|
+
*
|
|
319
|
+
* @param {Transaction} transaction
|
|
320
|
+
* @param {YNode} list
|
|
321
|
+
* @param {Array<number>} ids
|
|
322
|
+
*/
|
|
323
|
+
const holdHelper = (transaction, list, ids) => {
|
|
324
|
+
insertInGapHelper(transaction, list, list._length, new ContentHold(ids))
|
|
325
|
+
}
|
|
326
|
+
// YP20 end
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* With `held`, the live units are not deleted: their ids go to `held`
|
|
330
|
+
* (`client, clock, len`), except those `fresh` names (YP20).
|
|
331
|
+
*
|
|
296
332
|
* @param {Transaction} transaction
|
|
297
333
|
* @param {ItemTextListPosition} currPos
|
|
298
334
|
* @param {number} length
|
|
335
|
+
* @param {Array<number>|null} [held]
|
|
336
|
+
* @param {import('./utils/ids.js').IdSet|null} [fresh]
|
|
299
337
|
* @return {ItemTextListPosition}
|
|
300
338
|
*
|
|
301
339
|
* @private
|
|
302
340
|
* @function
|
|
303
341
|
*/
|
|
304
|
-
export const deleteText = (transaction, currPos, length) => {
|
|
342
|
+
export const deleteText = (transaction, currPos, length, held = null, fresh = null) => {
|
|
305
343
|
const startLength = length
|
|
344
|
+
let kept = 0 // YP20
|
|
306
345
|
while (length > 0 && currPos.right !== null) {
|
|
307
346
|
const item = currPos.right
|
|
308
347
|
if (!item.deleted && item.countable) {
|
|
309
|
-
|
|
310
|
-
|
|
348
|
+
const { client, clock } = item.id
|
|
349
|
+
// YP20: a held run ends where the open step's inserts start or end
|
|
350
|
+
const n = fresh === null ? math.min(length, item.length) : runEnd(fresh, client, clock, clock + math.min(length, item.length)) - clock
|
|
351
|
+
if (n < item.length) {
|
|
352
|
+
getItemCleanStart(transaction, createID(client, clock + n))
|
|
311
353
|
}
|
|
312
354
|
length -= item.length
|
|
313
|
-
|
|
355
|
+
if (held === null || (fresh !== null && fresh.has(client, clock))) {
|
|
356
|
+
item.delete(transaction)
|
|
357
|
+
} else {
|
|
358
|
+
held.push(client, clock, item.length)
|
|
359
|
+
kept += item.length
|
|
360
|
+
}
|
|
314
361
|
}
|
|
315
362
|
currPos.forward()
|
|
316
363
|
}
|
|
364
|
+
// YP20: the held units hide when their record integrates
|
|
365
|
+
currPos.index -= kept
|
|
317
366
|
const parent = /** @type {YNode} */ (/** @type {Item} */ (currPos.left || currPos.right).parent)
|
|
318
367
|
if (parent._searchMarker) {
|
|
319
368
|
updateMarkerChanges(parent._searchMarker, currPos.index, -startLength + length)
|
|
@@ -939,11 +988,17 @@ export class YNode {
|
|
|
939
988
|
|
|
940
989
|
/**
|
|
941
990
|
* Deletes elements starting from an index. A deleted node is left as it is.
|
|
991
|
+
* YP20: with `into` (the document's records list), the live units are not
|
|
992
|
+
* removed but held by one record appended to `into` (their writer's delete,
|
|
993
|
+
* which an undo of the record takes back), except those `fresh` names (the
|
|
994
|
+
* open undo step's inserts: removed, so that step's undo changes nothing).
|
|
942
995
|
*
|
|
943
996
|
* @param {number} index Index at which to start deleting elements
|
|
944
997
|
* @param {number} length The number of elements to remove. Defaults to 1.
|
|
998
|
+
* @param {YNode|null} [into]
|
|
999
|
+
* @param {import('./utils/ids.js').IdSet|null} [fresh]
|
|
945
1000
|
*/
|
|
946
|
-
delete (index, length = 1) {
|
|
1001
|
+
delete (index, length = 1, into = null, fresh = null) {
|
|
947
1002
|
if (!(length > 0)) return
|
|
948
1003
|
if (this.doc == null) {
|
|
949
1004
|
const runs = prelimOf(this).runs
|
|
@@ -959,11 +1014,59 @@ export class YNode {
|
|
|
959
1014
|
if (this._item !== null && this._item.deleted) return
|
|
960
1015
|
transact(this.doc, transaction => {
|
|
961
1016
|
const currPos = cursorAt(transaction, this, index)
|
|
962
|
-
|
|
1017
|
+
const held = into === null ? null : [] // YP20
|
|
1018
|
+
deleteText(transaction, currPos, length, held, fresh)
|
|
1019
|
+
if (held !== null && held.length > 0) {
|
|
1020
|
+
// YP20: `deleteText` moved the markers as for a delete; the hold keeps them
|
|
1021
|
+
const ms = this._searchMarker === null ? null : this._searchMarker.slice()
|
|
1022
|
+
holdHelper(transaction, /** @type {YNode} */ (into), held)
|
|
1023
|
+
if (ms !== null && this._searchMarker !== null) this._searchMarker.splice(0, this._searchMarker.length, ...ms)
|
|
1024
|
+
}
|
|
963
1025
|
plantMarker(this, currPos)
|
|
964
1026
|
})
|
|
965
1027
|
}
|
|
966
1028
|
|
|
1029
|
+
/**
|
|
1030
|
+
* YP20: append to this node (the document's records list) one hold record of
|
|
1031
|
+
* the items `ids` names (`client, clock, len` triples): they read as deleted
|
|
1032
|
+
* while the record is live.
|
|
1033
|
+
*
|
|
1034
|
+
* @param {Array<number>} ids
|
|
1035
|
+
*/
|
|
1036
|
+
hold (ids) {
|
|
1037
|
+
if (this.doc == null) throw new Error('hold: node is not integrated')
|
|
1038
|
+
if (ids.length > 0) transact(this.doc, transaction => holdHelper(transaction, this, ids))
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// YP22 begin (edytor fork: move records — see UPSTREAM.md YP22)
|
|
1042
|
+
/**
|
|
1043
|
+
* YP22: move the units `ids` (`client, clock, len` ranges, in the order
|
|
1044
|
+
* they will show) to live index `index` of this sequence: one move record
|
|
1045
|
+
* there (`ContentMove`); `side` is its class among the items of its origin
|
|
1046
|
+
* (`0` after, `1` before), `at` the chain it stands at for the sort of two
|
|
1047
|
+
* records at one origin — the units from the root sequence down to the
|
|
1048
|
+
* unit it stands at, outermost first (`null`: the first unit it names) —
|
|
1049
|
+
* and `part` the writer's order among the records standing at that unit.
|
|
1050
|
+
*
|
|
1051
|
+
* @param {number} index
|
|
1052
|
+
* @param {Array<number>} ids
|
|
1053
|
+
* @param {number} [side]
|
|
1054
|
+
* @param {number} [part]
|
|
1055
|
+
* @param {Array<ID>|null} [at]
|
|
1056
|
+
*/
|
|
1057
|
+
move (index, ids, side = 0, part = 0, at = null) {
|
|
1058
|
+
if (this.doc == null) throw new Error('move: node is not integrated')
|
|
1059
|
+
if (ids.length === 0) return
|
|
1060
|
+
transact(this.doc, transaction => {
|
|
1061
|
+
// Above every record seen from a peer (`_clock`, raised by remote records
|
|
1062
|
+
// only): this writer's records keep one priority until it sees another's,
|
|
1063
|
+
// so its gestures win or lose together against a concurrent peer's.
|
|
1064
|
+
const priority = /** @type {any} */ (transaction.doc)._clock + 1
|
|
1065
|
+
insertInGapHelper(transaction, this, index, new ContentMove(priority, side, ids, part, at))
|
|
1066
|
+
})
|
|
1067
|
+
}
|
|
1068
|
+
// YP22 end
|
|
1069
|
+
|
|
967
1070
|
/**
|
|
968
1071
|
* Executes a provided function on once on every key-value pair.
|
|
969
1072
|
*
|
|
@@ -1081,9 +1184,13 @@ export const readContentString = decoder => new ContentString(decoder.readString
|
|
|
1081
1184
|
|
|
1082
1185
|
/**
|
|
1083
1186
|
* @param {UpdateDecoder} decoder
|
|
1187
|
+
* @param {import('./utils/ID.js').ID} id YP25: the item's id (a paired mark's operation id is usually it)
|
|
1084
1188
|
* @return {ContentFormat}
|
|
1085
1189
|
*/
|
|
1086
|
-
export const readContentFormat =
|
|
1190
|
+
export const readContentFormat = (decoder, id) => {
|
|
1191
|
+
const key = decoder.readKey()
|
|
1192
|
+
return new ContentFormat(key, readFormatValue(decoder, key, id)) // YP25
|
|
1193
|
+
}
|
|
1087
1194
|
|
|
1088
1195
|
/**
|
|
1089
1196
|
* @param {UpdateDecoder} decoder
|
|
@@ -1112,14 +1219,68 @@ export const readContentBinary = decoder => new ContentBinary(decoder.readBuf())
|
|
|
1112
1219
|
*/
|
|
1113
1220
|
export const readContentDeleted = decoder => new ContentDeleted(decoder.readLen())
|
|
1114
1221
|
|
|
1222
|
+
/**
|
|
1223
|
+
* YP20, YP22: a record's id ranges, a count then one `client, clock, len` each.
|
|
1224
|
+
*
|
|
1225
|
+
* @param {UpdateDecoder} decoder
|
|
1226
|
+
* @return {Array<number>}
|
|
1227
|
+
*/
|
|
1228
|
+
const readIds = decoder => {
|
|
1229
|
+
const ids = []
|
|
1230
|
+
for (let n = decoder.readLen(); n > 0; n--) {
|
|
1231
|
+
const id = decoder.readLeftID()
|
|
1232
|
+
ids.push(id.client, id.clock, decoder.readLen())
|
|
1233
|
+
}
|
|
1234
|
+
return ids
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* YP20: a hold record, one `client, clock, len` per unit.
|
|
1239
|
+
*
|
|
1240
|
+
* @param {UpdateDecoder} decoder
|
|
1241
|
+
* @return {ContentHold}
|
|
1242
|
+
*/
|
|
1243
|
+
export const readContentHold = decoder => new ContentHold(readIds(decoder))
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* YP22: a move record: its priority, side, part and whether an anchor chain
|
|
1247
|
+
* follows in one word, the chain (a count, then its units), then one
|
|
1248
|
+
* `client, clock, len` per range.
|
|
1249
|
+
*
|
|
1250
|
+
* @param {UpdateDecoder} decoder
|
|
1251
|
+
* @return {ContentMove}
|
|
1252
|
+
*/
|
|
1253
|
+
export const readContentMove = decoder => {
|
|
1254
|
+
const word = decoder.readLen()
|
|
1255
|
+
/** @type {Array<ID>|null} */
|
|
1256
|
+
let at = null
|
|
1257
|
+
if (word & 16) {
|
|
1258
|
+
at = []
|
|
1259
|
+
for (let n = decoder.readLen(); n > 0; n--) at.push(decoder.readLeftID())
|
|
1260
|
+
}
|
|
1261
|
+
return new ContentMove(word >> 5, word & 1, readIds(decoder), (word >> 1) & 7, at)
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* YP29: a collected mark or claim, its length and class.
|
|
1266
|
+
*
|
|
1267
|
+
* @param {UpdateDecoder} decoder
|
|
1268
|
+
* @return {ContentDeleted}
|
|
1269
|
+
*/
|
|
1270
|
+
export const readContentClass = decoder => {
|
|
1271
|
+
const word = decoder.readLen()
|
|
1272
|
+
return new ContentDeleted(Math.floor(word / 4), word % 4)
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1115
1275
|
const refused = () => { error.unexpectedCase() }
|
|
1116
1276
|
|
|
1117
1277
|
/**
|
|
1118
1278
|
* A lookup map for reading Item content. YP19: JSON (2), embed (5) and
|
|
1119
1279
|
* subdocument (9) contents are refused like GC (0) and Skip (10): edytor
|
|
1120
|
-
* writes none, so an update holding one is malformed.
|
|
1280
|
+
* writes none, so an update holding one is malformed. YP20: 11 is a hold
|
|
1281
|
+
* record. YP22: 12 is a move record.
|
|
1121
1282
|
*
|
|
1122
|
-
* @type {Array<function(UpdateDecoder):AbstractContent>}
|
|
1283
|
+
* @type {Array<function(UpdateDecoder, import('./utils/ID.js').ID):AbstractContent>}
|
|
1123
1284
|
*/
|
|
1124
1285
|
export const contentRefs = [
|
|
1125
1286
|
refused, // 0 - GC is not ItemContent
|
|
@@ -1132,14 +1293,18 @@ export const contentRefs = [
|
|
|
1132
1293
|
readContentType, // 7
|
|
1133
1294
|
readContentAny, // 8
|
|
1134
1295
|
refused, // 9 - subdocument
|
|
1135
|
-
refused // 10 - Skip is not ItemContent
|
|
1296
|
+
refused, // 10 - Skip is not ItemContent
|
|
1297
|
+
readContentHold, // 11 - a hold record (YP20)
|
|
1298
|
+
readContentMove, // 12 - a move record (YP22)
|
|
1299
|
+
readContentClass // 13 - a collected mark or claim, its class (YP29)
|
|
1136
1300
|
]
|
|
1137
1301
|
|
|
1138
1302
|
/**
|
|
1139
1303
|
* @param {UpdateDecoder} decoder
|
|
1140
1304
|
* @param {number} info
|
|
1305
|
+
* @param {import('./utils/ID.js').ID} id YP25: the item's id (a format item reads it)
|
|
1141
1306
|
*/
|
|
1142
|
-
export const readItemContent = (decoder, info) => contentRefs[info & binary.
|
|
1307
|
+
export const readItemContent = (decoder, info, id) => contentRefs[info & binary.BITS4](decoder, id) // YP27: four bits
|
|
1143
1308
|
|
|
1144
1309
|
/**
|
|
1145
1310
|
* @param {UpdateDecoder} decoder
|
package/dist/edytor.svelte.js
CHANGED
|
@@ -8,6 +8,7 @@ import { onInput } from './events/onInput.js';
|
|
|
8
8
|
import { onPaste } from './events/onPaste.js';
|
|
9
9
|
import { onTextDragEnd, onTextDragStart, preventUnsupportedDrop } from './events/onDrop.js';
|
|
10
10
|
import { attachFocus, selectionIsInside, takeKeys } from './events/onFocus.js';
|
|
11
|
+
import { attachPageHide } from './events/onPageHide.js';
|
|
11
12
|
import { insertLineBreak, runIntent } from './events/beforeInputCommands.js';
|
|
12
13
|
import { textPointAt } from './selection/selection.utils.js';
|
|
13
14
|
import { Attempts } from './session/attempt.js';
|
|
@@ -794,7 +795,9 @@ export class Edytor {
|
|
|
794
795
|
keydown(event);
|
|
795
796
|
}),
|
|
796
797
|
// Pointer and focus ownership, and the focus-time caret restore.
|
|
797
|
-
...attachFocus(this, node),
|
|
798
|
+
...attachFocus(this, node),
|
|
799
|
+
// The page leaving for good ends the session's history.
|
|
800
|
+
attachPageHide(this), on(node, 'mousedown', this.selection.pointer.preventTripleClick), on(node, 'click', this.selection.pointer.tripleClick),
|
|
798
801
|
// Settle queued mutation repairs before the native menu opens —
|
|
799
802
|
// spellcheck suggestions are computed against the DOM at this
|
|
800
803
|
// moment (PM flushes its DOM observer on contextmenu for the
|
|
@@ -209,14 +209,14 @@ const splitKeepingChildren = (edytor, [id, offset, born, tail], child) => {
|
|
|
209
209
|
return REFUSED;
|
|
210
210
|
if (!child)
|
|
211
211
|
return facade.compose({ ...split, writes: [step] });
|
|
212
|
-
// The
|
|
212
|
+
// The document's answer on placing the kind as a first child.
|
|
213
213
|
const slot = facade.prepare.insertBlocks({ parent: id, index: 0 }, [
|
|
214
214
|
{ id: born, type: tail?.type ?? facade.defaultChild(id) }
|
|
215
215
|
]);
|
|
216
216
|
const first = 'writes' in slot ? slot.writes[0] : undefined;
|
|
217
217
|
if (first?.op !== 'insertBlocks')
|
|
218
218
|
return REFUSED;
|
|
219
|
-
const placed = { ...step, parent: id,
|
|
219
|
+
const placed = { ...step, parent: id, index: 0 };
|
|
220
220
|
return facade.compose({ ...split, writes: [placed] });
|
|
221
221
|
};
|
|
222
222
|
/**
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page leaves for good (installed by `Edytor.attach`): a `pagehide` the
|
|
3
|
+
* back/forward cache keeps nothing of. The view tells its document the
|
|
4
|
+
* session's history ended (`document.endHistory()`, `hist.release.end`): the
|
|
5
|
+
* history's rule releases what the session deleted. A page the cache keeps
|
|
6
|
+
* may come back with its history, and a lost connection fires nothing here:
|
|
7
|
+
* both keep it.
|
|
8
|
+
*
|
|
9
|
+
* The page's lifecycle is the sync layer's (`onPageLeave`, which the
|
|
10
|
+
* providers leave by too): the view subscribes to its `write` phase, run
|
|
11
|
+
* before the providers' teardown whatever order they subscribed in, so the
|
|
12
|
+
* release goes out on a socket or store that is still open.
|
|
13
|
+
*/
|
|
14
|
+
import { onPageLeave } from '../crdt/providers/room.js';
|
|
15
|
+
/** End the view's session when its page leaves for good; answers the teardown. */
|
|
16
|
+
export const attachPageHide = (edytor) => onPageLeave(() => edytor.document.endHistory(), 'write');
|