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
package/dist/crdt/purge.js
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
import { view as modelView } from './placement/model.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { deleteIndex } from './text/delete-index.js';
|
|
1
|
+
import { recordsOf, registryOf, view as modelView } from './placement/model.js';
|
|
2
|
+
import { removeUnit, unitOf } from './placement/slots.js';
|
|
3
|
+
import { deleteClaims, purgeStreams, scanText } from './text/model.js';
|
|
4
|
+
import { BLOCK_ATTR_ROOT, DATA, DATA_LEAF_PREFIX, DEL_PREFIX, HORIZON_ATTR, HORIZON_ROOT, REGISTRY_KEY, RELEASE_ROOT, SUGGEST_ROOT, TEXT_DELETES_ROOT, WITHDRAW_PREFIX } from './schema.js';
|
|
6
5
|
import { baseIdOf, isIncarnationId } from './incarnations.js';
|
|
7
|
-
import {
|
|
6
|
+
import { suggestionsOf } from './doc/suggest.js';
|
|
7
|
+
import { attrItems, dropSearchMarkers, engineOps, firstItem, holdsIds } from './structs.js';
|
|
8
8
|
const mapOf = (node) => attrItems(node);
|
|
9
|
+
/** The clock a suggestion's record opened at (its writer's clocks it owns start there). */
|
|
10
|
+
const clockOf = (rec) => rec.start ?? 0;
|
|
11
|
+
/**
|
|
12
|
+
* The ended histories a document holds (`hist.release.end`): per writer,
|
|
13
|
+
* the clock its latest live release record ended at (what it wrote below
|
|
14
|
+
* it is released).
|
|
15
|
+
*/
|
|
16
|
+
export const releasesOf = (doc) => {
|
|
17
|
+
const out = new Map();
|
|
18
|
+
for (let it = firstItem(doc.get(RELEASE_ROOT)); it; it = it.right) {
|
|
19
|
+
if (it.deleted)
|
|
20
|
+
continue;
|
|
21
|
+
const { client, clock } = it.id;
|
|
22
|
+
out.set(client, Math.max(out.get(client) ?? 0, clock + it.length - 1));
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
};
|
|
9
26
|
/** The horizon a document holds (`null`: never purged). */
|
|
10
27
|
export const readHorizon = (doc) => {
|
|
11
28
|
const h = doc.get(HORIZON_ROOT).getAttr(HORIZON_ATTR);
|
|
@@ -14,73 +31,76 @@ export const readHorizon = (doc) => {
|
|
|
14
31
|
: null;
|
|
15
32
|
};
|
|
16
33
|
export const bindPurge = (Y) => {
|
|
17
|
-
const
|
|
18
|
-
|
|
34
|
+
const ops = engineOps(Y);
|
|
35
|
+
/** The live hold records of `doc`, one per held range. */
|
|
36
|
+
const holdsOf = (doc) => {
|
|
37
|
+
const out = [];
|
|
38
|
+
for (let it = firstItem(doc.get(TEXT_DELETES_ROOT)); it; it = it.right) {
|
|
39
|
+
const ids = it.content.ids;
|
|
40
|
+
if (it.deleted || ids === undefined)
|
|
41
|
+
continue;
|
|
42
|
+
for (let j = 0; j < ids.length; j += 3)
|
|
43
|
+
out.push({
|
|
44
|
+
client: it.id.client,
|
|
45
|
+
clock: it.id.clock + j / 3,
|
|
46
|
+
c: ids[j],
|
|
47
|
+
k: ids[j + 1],
|
|
48
|
+
n: ids[j + 2]
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
};
|
|
19
53
|
/**
|
|
20
|
-
* Whether every live
|
|
21
|
-
*
|
|
22
|
-
*
|
|
54
|
+
* Whether every live record holding an item of `spans` was written where
|
|
55
|
+
* `old` says (a withdrawn block's content was deleted before the
|
|
56
|
+
* horizon). Items no record holds count as old.
|
|
23
57
|
*/
|
|
24
|
-
const heldBefore = (
|
|
25
|
-
|
|
26
|
-
return spans
|
|
27
|
-
.flatMap((sp) => rootSpans(s, sp))
|
|
28
|
-
.every((r) => holders(s, r).every((mark) => old(mark.client, mark.clock)));
|
|
29
|
-
};
|
|
58
|
+
const heldBefore = (holds, spans, old) => holds.every((h) => old(h.client, h.clock) ||
|
|
59
|
+
!spans.some((sp) => sp.c === h.c && sp.k < h.k + h.n && h.k < sp.k + sp.n));
|
|
30
60
|
/**
|
|
31
|
-
* The text's part (`room.purge.what`), inside the purge
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
61
|
+
* The text's part (`room.purge.what`), inside the purge transaction:
|
|
62
|
+
* every hold record `old` names is deleted with what it holds (a real
|
|
63
|
+
* delete: the engine collects it everywhere; a newer record holding the
|
|
64
|
+
* same text has nothing to show again, the older delete is past the
|
|
65
|
+
* horizon). A record whose text the room lacks (lost, coming back with
|
|
66
|
+
* its author) is kept: it holds that text when it returns.
|
|
36
67
|
*/
|
|
37
68
|
const purgeText = (doc, old) => {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
if (tr === null)
|
|
69
|
+
const tr = doc._transaction;
|
|
70
|
+
if (tr == null)
|
|
41
71
|
throw new Error('purge: outside a transaction');
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const last = spans[spans.length - 1];
|
|
52
|
-
if (last !== undefined && last.c === r.client && last.k + last.n === r.clock)
|
|
53
|
-
last.n++;
|
|
54
|
-
else
|
|
55
|
-
spans.push({ c: r.client, k: r.clock, n: 1 });
|
|
56
|
-
}
|
|
57
|
-
dropMarks(s, dropped);
|
|
58
|
-
const marks = dropped.length;
|
|
59
|
-
D.remove(tr, spans);
|
|
60
|
-
const doomed = [];
|
|
61
|
-
attrItems(s.restored).forEach((it, key) => {
|
|
62
|
-
if (it.deleted || !old(it.id.client, it.id.clock))
|
|
63
|
-
return;
|
|
64
|
-
const [record, bytes] = recordsIn(it, null)[0] ?? [];
|
|
65
|
-
if (bytes === undefined)
|
|
72
|
+
const doomed = holdsOf(doc).filter((h) => old(h.client, h.clock) && holdsIds(doc, h.c, h.k, h.n));
|
|
73
|
+
const ids = (spans) => {
|
|
74
|
+
const set = ops.idSet();
|
|
75
|
+
for (const sp of spans)
|
|
76
|
+
set.add(sp.c, sp.k, sp.n);
|
|
77
|
+
return set;
|
|
78
|
+
};
|
|
79
|
+
ops.iterate(tr, ids(doomed), (it) => {
|
|
80
|
+
if (it.removed !== false)
|
|
66
81
|
return;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
holders(s, { c: e.rc, k: e.rk, n: e.n }).length === 0);
|
|
70
|
-
if (dead)
|
|
71
|
-
doomed.push({ key, copies });
|
|
82
|
+
it.delete(tr);
|
|
83
|
+
dropSearchMarkers(it.parent);
|
|
72
84
|
});
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
// The records are read first and deleted after: deleting one releases
|
|
86
|
+
// its holds, which can split items of the store the walk is reading
|
|
87
|
+
// (its writer's own text), and the walk would then visit the structs
|
|
88
|
+
// written before the record (a delete mark beside it) as if they were it.
|
|
89
|
+
const records = [];
|
|
90
|
+
ops.iterate(tr, ids(doomed.map((h) => ({ c: h.client, k: h.clock, n: 1 }))), (it) => {
|
|
91
|
+
records.push(it);
|
|
92
|
+
});
|
|
93
|
+
for (const it of records)
|
|
94
|
+
it.delete(tr);
|
|
95
|
+
return doomed.length;
|
|
79
96
|
};
|
|
80
97
|
/**
|
|
81
98
|
* Purge from `doc` (through `facade`, the facade over it) what was
|
|
82
|
-
* deleted before `horizon`, and
|
|
83
|
-
*
|
|
99
|
+
* deleted before `horizon`, and what the sessions whose history ended
|
|
100
|
+
* deleted (`hist.release.end`), and write the horizon record. With no
|
|
101
|
+
* horizon (`null`: the room's tick found none due), only the ended
|
|
102
|
+
* sessions' deletes go, and the horizon record is left as it is. Runs
|
|
103
|
+
* inside the caller's transaction.
|
|
84
104
|
*/
|
|
85
105
|
const purge = (doc, facade, horizon) => {
|
|
86
106
|
const report = {
|
|
@@ -88,13 +108,19 @@ export const bindPurge = (Y) => {
|
|
|
88
108
|
emptied: 0,
|
|
89
109
|
marks: 0,
|
|
90
110
|
records: 0,
|
|
91
|
-
candidates: 0,
|
|
92
111
|
claims: 0,
|
|
93
|
-
merged: 0
|
|
112
|
+
merged: 0,
|
|
113
|
+
released: 0,
|
|
114
|
+
suggestions: 0
|
|
94
115
|
};
|
|
95
|
-
const vector =
|
|
116
|
+
const vector = horizon === null
|
|
117
|
+
? new Map()
|
|
118
|
+
: Y.decodeStateVector(horizon.sv);
|
|
96
119
|
const old = (client, clock) => clock < (vector.get(client) ?? 0);
|
|
97
|
-
|
|
120
|
+
// A delete of an ended session is past the horizon (`hist.release.end`).
|
|
121
|
+
const releases = releasesOf(doc);
|
|
122
|
+
const freed = (client, clock) => old(client, clock) || clock < (releases.get(client) ?? 0);
|
|
123
|
+
const oldItem = (item) => item !== undefined && !item.deleted && freed(item.id.client, item.id.clock);
|
|
98
124
|
const view = modelView(doc);
|
|
99
125
|
const { blocks, own, placements } = view;
|
|
100
126
|
const marks = (node, prefix) => [...node.attrKeys()]
|
|
@@ -120,6 +146,35 @@ export const bindPurge = (Y) => {
|
|
|
120
146
|
}
|
|
121
147
|
return out;
|
|
122
148
|
};
|
|
149
|
+
/** The paired mark items of `s`'s region whose start and end both lie in it. */
|
|
150
|
+
const regionMarks = (s) => {
|
|
151
|
+
const opened = new Map();
|
|
152
|
+
const out = [];
|
|
153
|
+
let at = 0;
|
|
154
|
+
let inside = s.start === 0;
|
|
155
|
+
for (let it = firstItem(s.text); it !== null; it = it.right) {
|
|
156
|
+
const live = !it.deleted && it.countable !== false;
|
|
157
|
+
if (live && at >= s.end)
|
|
158
|
+
break;
|
|
159
|
+
const { key, value } = it.content;
|
|
160
|
+
if (inside && !it.deleted && typeof key === 'string' && Array.isArray(value)) {
|
|
161
|
+
const role = key.charCodeAt(0);
|
|
162
|
+
if (role === 1)
|
|
163
|
+
opened.set(`${value[2]}:${value[3]}`, it);
|
|
164
|
+
else if (role === 2) {
|
|
165
|
+
const start = opened.get(`${value[0]}:${value[1]}`);
|
|
166
|
+
if (start !== undefined)
|
|
167
|
+
out.push(start, it);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (live) {
|
|
171
|
+
at += it.length;
|
|
172
|
+
if (at === s.start)
|
|
173
|
+
inside = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
};
|
|
123
178
|
const dead = new Set();
|
|
124
179
|
const shells = [];
|
|
125
180
|
for (const [id, rec] of blocks) {
|
|
@@ -136,7 +191,8 @@ export const bindPurge = (Y) => {
|
|
|
136
191
|
shells.push(id);
|
|
137
192
|
}
|
|
138
193
|
// A withdrawn block is hidden since it last held something: what its
|
|
139
|
-
// stream held was deleted, and its children left
|
|
194
|
+
// stream held was deleted, and its children left (the move that took
|
|
195
|
+
// them is old), before the horizon.
|
|
140
196
|
const children = new Map();
|
|
141
197
|
const add = (map, key, id) => {
|
|
142
198
|
const list = map.get(key);
|
|
@@ -145,23 +201,27 @@ export const bindPurge = (Y) => {
|
|
|
145
201
|
else
|
|
146
202
|
list.push(id);
|
|
147
203
|
};
|
|
148
|
-
for (const [id, rec] of blocks)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
204
|
+
for (const [id, rec] of blocks) {
|
|
205
|
+
const p = rec.home?.parent;
|
|
206
|
+
if (typeof p === 'string')
|
|
207
|
+
add(children, p, id);
|
|
208
|
+
}
|
|
209
|
+
/** The block's display edge is old: it shows at its home slot (written with the block), or at an old record. */
|
|
210
|
+
const edgeOld = (id) => {
|
|
211
|
+
const home = blocks.get(id)?.home;
|
|
212
|
+
const r = home ? (unitOf(doc, home)?.item.mover ?? null) : null;
|
|
213
|
+
return r === null ? true : old(r.id.client, r.id.clock);
|
|
214
|
+
};
|
|
215
|
+
const holds = shells.length > 0 ? holdsOf(doc) : [];
|
|
153
216
|
for (let grew = true; grew;) {
|
|
154
217
|
grew = false;
|
|
155
218
|
for (const id of shells) {
|
|
156
219
|
if (dead.has(id))
|
|
157
220
|
continue;
|
|
158
221
|
const s = own.streamOf(id);
|
|
159
|
-
if (s !== undefined && !heldBefore(
|
|
222
|
+
if (s !== undefined && !heldBefore(holds, regionSpans(s), freed))
|
|
160
223
|
continue;
|
|
161
|
-
const left = (children.get(id) ?? []).every((child) =>
|
|
162
|
-
const rec = blocks.get(child);
|
|
163
|
-
return rec.cands[0]?.p === id ? dead.has(child) : winnerOld(rec);
|
|
164
|
-
});
|
|
224
|
+
const left = (children.get(id) ?? []).every((child) => placements.get(child)?.parent === id ? dead.has(child) : edgeOld(child));
|
|
165
225
|
if (!left)
|
|
166
226
|
continue;
|
|
167
227
|
dead.add(id);
|
|
@@ -174,8 +234,11 @@ export const bindPurge = (Y) => {
|
|
|
174
234
|
dead.add(id);
|
|
175
235
|
// ── Which of them go whole ────────────────────────────────────────
|
|
176
236
|
// A text family: the blocks with a stream in one backing text, and the
|
|
177
|
-
// block owning it.
|
|
178
|
-
//
|
|
237
|
+
// block owning it. The block owning a text goes only with every block
|
|
238
|
+
// streaming in it (its text goes with its node). A block streaming in
|
|
239
|
+
// another's text, owning none, goes alone when one boundary names it:
|
|
240
|
+
// that boundary stays and delimits the stream the purge empties, which
|
|
241
|
+
// nobody shows (a removed key's, `text/streams.ts` `removedKeys`).
|
|
179
242
|
const parent = new Map();
|
|
180
243
|
const root = (id) => {
|
|
181
244
|
let r = id;
|
|
@@ -205,32 +268,71 @@ export const bindPurge = (Y) => {
|
|
|
205
268
|
if (family.every((id) => dead.has(id)))
|
|
206
269
|
for (const id of family)
|
|
207
270
|
removable.add(id);
|
|
208
|
-
//
|
|
271
|
+
// The live boundaries naming each block, and the keys with losing incarnations.
|
|
272
|
+
const naming = new Map();
|
|
273
|
+
for (const rec of blocks.values())
|
|
274
|
+
if (rec.content)
|
|
275
|
+
for (const b of scanText(rec.id, rec.content).bounds)
|
|
276
|
+
if (b.m === undefined)
|
|
277
|
+
naming.set(b.s, (naming.get(b.s) ?? 0) + 1);
|
|
278
|
+
const incarnated = new Set();
|
|
279
|
+
for (const id of blocks.keys())
|
|
280
|
+
if (isIncarnationId(id))
|
|
281
|
+
incarnated.add(baseIdOf(id));
|
|
282
|
+
const lone = new Set();
|
|
283
|
+
for (const id of dead) {
|
|
284
|
+
if (removable.has(id) || isIncarnationId(id) || incarnated.has(id))
|
|
285
|
+
continue;
|
|
286
|
+
const s = own.streamOf(id);
|
|
287
|
+
if (s === undefined || s.home === id || blocks.get(id).content !== undefined)
|
|
288
|
+
continue;
|
|
289
|
+
if (naming.get(id) !== 1)
|
|
290
|
+
continue;
|
|
291
|
+
lone.add(id);
|
|
292
|
+
removable.add(id);
|
|
293
|
+
}
|
|
294
|
+
// Nothing that stays may be placed under a block that goes, or keep its
|
|
295
|
+
// home slot in the sequence of one (the slot goes with the node).
|
|
209
296
|
for (let shrank = true; shrank;) {
|
|
210
297
|
shrank = false;
|
|
211
|
-
for (const [id,
|
|
212
|
-
if (removable.has(id)
|
|
298
|
+
for (const [id, rec] of blocks) {
|
|
299
|
+
if (removable.has(id))
|
|
213
300
|
continue;
|
|
214
|
-
for (const
|
|
215
|
-
removable.
|
|
216
|
-
|
|
301
|
+
for (const p of [placements.get(id)?.parent, rec.home?.parent]) {
|
|
302
|
+
if (typeof p !== 'string' || !removable.has(p))
|
|
303
|
+
continue;
|
|
304
|
+
for (const member of lone.has(p) ? [p] : (families.get(root(p)) ?? [])) {
|
|
305
|
+
removable.delete(member);
|
|
306
|
+
lone.delete(member);
|
|
307
|
+
}
|
|
308
|
+
shrank = true;
|
|
309
|
+
}
|
|
217
310
|
}
|
|
218
311
|
}
|
|
219
312
|
// ── The writes ────────────────────────────────────────────────────
|
|
220
313
|
// A deleted block that stays: its stream's text (boundaries kept), its
|
|
221
|
-
// data leaves and its claims
|
|
314
|
+
// data leaves and its claims; a block that goes alone: its stream's
|
|
315
|
+
// text and its claims (the rest goes with its node).
|
|
222
316
|
const emptied = [...dead].filter((id) => !removable.has(id));
|
|
223
|
-
|
|
317
|
+
const purged = [...emptied, ...lone].flatMap((id) => {
|
|
224
318
|
const s = own.streamOf(id);
|
|
225
|
-
return s === undefined
|
|
226
|
-
})
|
|
319
|
+
return s === undefined ? [] : [s];
|
|
320
|
+
});
|
|
321
|
+
// Their marks format nothing any reader shows: each pair whose start and
|
|
322
|
+
// end lie in one of these streams goes (collected, it keeps its class).
|
|
323
|
+
const tr = doc._transaction;
|
|
324
|
+
for (const s of purged)
|
|
325
|
+
for (const item of regionMarks(s))
|
|
326
|
+
item.delete(tr);
|
|
327
|
+
purgeStreams(purged.filter((s) => s.end - s.start > s.inert.length));
|
|
328
|
+
for (const id of lone)
|
|
329
|
+
deleteClaims(doc, blocks.get(id).claims);
|
|
227
330
|
for (const id of emptied) {
|
|
228
331
|
const rec = blocks.get(id);
|
|
229
332
|
for (const key of [...rec.node.attrKeys()])
|
|
230
333
|
if (key === DATA || key.startsWith(DATA_LEAF_PREFIX))
|
|
231
334
|
rec.node.deleteAttr(key);
|
|
232
|
-
|
|
233
|
-
rec.claimsNode.delete(0, rec.claimsNode.length);
|
|
335
|
+
deleteClaims(doc, rec.claims);
|
|
234
336
|
report.emptied++;
|
|
235
337
|
}
|
|
236
338
|
// The blocks that go whole: their entries, attribution records and the claims naming them.
|
|
@@ -238,18 +340,17 @@ export const bindPurge = (Y) => {
|
|
|
238
340
|
const attribution = doc.get(BLOCK_ATTR_ROOT);
|
|
239
341
|
for (const [id, rec] of blocks) {
|
|
240
342
|
// An emptied block's claims are gone already (above).
|
|
241
|
-
if (removable.has(id) || dead.has(id)
|
|
343
|
+
if (removable.has(id) || dead.has(id))
|
|
242
344
|
continue;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
.filter((c) => removable.has(c.m))
|
|
246
|
-
.map((c) => c.seqIndex)
|
|
247
|
-
.filter((at) => at >= 0);
|
|
248
|
-
for (const at of named.sort((a, b) => b - a))
|
|
249
|
-
rec.claimsNode.delete(at, 1);
|
|
345
|
+
const named = rec.claims.filter((c) => c.stamp.c >= 0 && removable.has(c.m));
|
|
346
|
+
deleteClaims(doc, named);
|
|
250
347
|
report.claims += named.length;
|
|
251
348
|
}
|
|
252
349
|
for (const id of removable) {
|
|
350
|
+
// Its home slot goes with it: nothing shows it any more.
|
|
351
|
+
const home = blocks.get(id)?.home;
|
|
352
|
+
if (home)
|
|
353
|
+
removeUnit(doc, home);
|
|
253
354
|
if (isIncarnationId(id)) {
|
|
254
355
|
// A losing incarnation is no registry value: its subtree goes.
|
|
255
356
|
// Its node is a deleted value, which the node API writes nothing to:
|
|
@@ -264,8 +365,8 @@ export const bindPurge = (Y) => {
|
|
|
264
365
|
continue;
|
|
265
366
|
}
|
|
266
367
|
registry.deleteAttr(id);
|
|
267
|
-
if (attribution.getAttr(
|
|
268
|
-
attribution.deleteAttr(
|
|
368
|
+
if (attribution.getAttr(id) !== undefined)
|
|
369
|
+
attribution.deleteAttr(id);
|
|
269
370
|
report.removed++;
|
|
270
371
|
}
|
|
271
372
|
// Blocks merged away before the horizon (`room.purge.merged`): live,
|
|
@@ -273,10 +374,10 @@ export const bindPurge = (Y) => {
|
|
|
273
374
|
// own attribution records go (no history reaches a step below the
|
|
274
375
|
// horizon to split them out again); their registry entries stay.
|
|
275
376
|
const claimsOn = new Map();
|
|
276
|
-
for (const
|
|
377
|
+
for (const rec of blocks.values()) {
|
|
277
378
|
if (rec.deleted)
|
|
278
379
|
continue;
|
|
279
|
-
for (const c of
|
|
380
|
+
for (const c of rec.claims) {
|
|
280
381
|
const list = claimsOn.get(c.m);
|
|
281
382
|
if (list === undefined)
|
|
282
383
|
claimsOn.set(c.m, [c]);
|
|
@@ -284,40 +385,79 @@ export const bindPurge = (Y) => {
|
|
|
284
385
|
list.push(c);
|
|
285
386
|
}
|
|
286
387
|
}
|
|
287
|
-
const claimedOld = (id) => (claimsOn.get(id) ?? []).every((c) => c.
|
|
388
|
+
const claimedOld = (id) => (claimsOn.get(id) ?? []).every((c) => c.stamp.c >= 0 && old(c.stamp.c, c.stamp.k));
|
|
288
389
|
for (const [id, rec] of blocks) {
|
|
289
390
|
if (rec.deleted || removable.has(id) || dead.has(id) || isIncarnationId(id))
|
|
290
391
|
continue;
|
|
291
392
|
const owner = own.ownerOf(id);
|
|
292
393
|
if (owner === id || typeof owner !== 'string' || !claimedOld(id))
|
|
293
394
|
continue;
|
|
294
|
-
if (attribution.getAttr(
|
|
395
|
+
if (attribution.getAttr(id) === undefined)
|
|
295
396
|
continue;
|
|
296
|
-
attribution.deleteAttr(
|
|
397
|
+
attribution.deleteAttr(id);
|
|
297
398
|
report.merged++;
|
|
298
399
|
}
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
400
|
+
// Old move records no block shows at (lost to a newer move, or their blocks
|
|
401
|
+
// removed): no undo below the horizon can make them movers again.
|
|
402
|
+
const sequences = [registryOf(doc)];
|
|
403
|
+
for (const [id, rec] of blocks)
|
|
404
|
+
if (!removable.has(id))
|
|
405
|
+
sequences.push(rec.node);
|
|
406
|
+
// A record a block shows at stays, though it is no unit's mover (a cycle
|
|
407
|
+
// sent the block back to it): it is read before any record goes.
|
|
408
|
+
const shownNow = modelView(doc);
|
|
409
|
+
const listed = sequences.map((node) => [node, recordsOf(doc, node, shownNow)]);
|
|
410
|
+
doc.transact((tr) => {
|
|
411
|
+
for (const [node, records] of listed)
|
|
412
|
+
for (const r of records) {
|
|
413
|
+
if (r.wins || !old(r.item.id.client, r.item.id.clock))
|
|
414
|
+
continue;
|
|
415
|
+
ops.remove(tr, r.item);
|
|
416
|
+
dropSearchMarkers(node);
|
|
417
|
+
report.records++;
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
// Text hold records past the horizon, with what they hold.
|
|
421
|
+
report.marks = purgeText(doc, freed);
|
|
422
|
+
// Release records the horizon covers: what they released is past it.
|
|
423
|
+
{
|
|
424
|
+
const tr = doc._transaction;
|
|
425
|
+
for (let it = firstItem(doc.get(RELEASE_ROOT)); it; it = it.right) {
|
|
426
|
+
if (it.deleted || !old(it.id.client, it.id.clock + it.length - 1))
|
|
427
|
+
continue;
|
|
428
|
+
it.delete(tr);
|
|
429
|
+
report.released++;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Suggestions decided before the horizon (`sugg.purge`): the decision
|
|
433
|
+
// trail goes with them. A writer's oldest record first, so no record's
|
|
434
|
+
// clocks pass to another.
|
|
435
|
+
const suggest = facade.part && suggestionsOf(facade);
|
|
436
|
+
const records = suggest?.records() ?? [];
|
|
437
|
+
if (records.length > 0) {
|
|
438
|
+
const pending = new Set(suggest.changes().map((c) => c.suggestion));
|
|
439
|
+
const root = doc.get(SUGGEST_ROOT);
|
|
440
|
+
const seen = new Set();
|
|
441
|
+
for (const rec of [...records].sort((a, b) => clockOf(a) - clockOf(b))) {
|
|
442
|
+
if (seen.has(rec.writer))
|
|
443
|
+
continue;
|
|
444
|
+
seen.add(rec.writer);
|
|
445
|
+
const node = root.getAttr(rec.id);
|
|
446
|
+
const items = [node._item];
|
|
447
|
+
for (let it = firstItem(node); it; it = it.right)
|
|
448
|
+
if (!it.deleted)
|
|
449
|
+
items.push(it);
|
|
450
|
+
if (pending.has(rec.id) ||
|
|
451
|
+
!items.every((it) => old(it.id.client, it.id.clock + it.length - 1)))
|
|
452
|
+
continue;
|
|
453
|
+
root.deleteAttr(rec.id);
|
|
454
|
+
seen.delete(rec.writer);
|
|
455
|
+
report.suggestions++;
|
|
456
|
+
}
|
|
314
457
|
}
|
|
315
|
-
// Text delete marks and restoration records past the horizon.
|
|
316
|
-
const text = purgeText(doc, old);
|
|
317
|
-
report.marks = text.marks;
|
|
318
|
-
report.records = text.records;
|
|
319
458
|
// The horizon record, last.
|
|
320
|
-
|
|
459
|
+
if (horizon !== null)
|
|
460
|
+
doc.get(HORIZON_ROOT).setAttr(HORIZON_ATTR, { at: horizon.at, sv: horizon.sv });
|
|
321
461
|
return report;
|
|
322
462
|
};
|
|
323
463
|
return { purge };
|
package/dist/crdt/restore.d.ts
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* 2. its type and data, per leaf, where they differ;
|
|
15
15
|
* 3. its place: under its snapshot parent, in the snapshot's order (the
|
|
16
16
|
* longest run of that parent's children already in order stays; each
|
|
17
|
-
* other block
|
|
18
|
-
*
|
|
17
|
+
* other block is moved after the one before it); a snapshot block the
|
|
18
|
+
* registry no longer holds is created there;
|
|
19
19
|
* 4. its content, where it differs: its own claims removed, then its
|
|
20
20
|
* stream's text replaced through the facade's per-stream delete and
|
|
21
21
|
* insert, the common prefix and suffix kept;
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import type { EngineDoc } from './engine-api.js';
|
|
27
27
|
import { type BlockId, type ContentItem, type InlineSpec, type PlacementModel } from './placement/model.js';
|
|
28
28
|
import type { DataPatch } from './data.js';
|
|
29
|
+
import { type SuggestFacade } from './doc/suggest.js';
|
|
29
30
|
import type { JSONDoc } from '../utils/json.js';
|
|
30
31
|
/** One display unit: a character (a code point) with its marks, or an inline atom, keyed for comparison. */
|
|
31
32
|
export type Unit = {
|
|
@@ -60,7 +61,7 @@ export type RestoreReport = {
|
|
|
60
61
|
deleted: number;
|
|
61
62
|
};
|
|
62
63
|
/** What a restore reads and writes through: the facade over the document (structurally). */
|
|
63
|
-
export type RestoreFacade<Plan> = {
|
|
64
|
+
export type RestoreFacade<Plan> = Partial<SuggestFacade> & {
|
|
64
65
|
model: PlacementModel;
|
|
65
66
|
childSlots(parent: BlockId | null): readonly {
|
|
66
67
|
id: BlockId;
|