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
package/dist/crdt/doc/meta.js
CHANGED
|
@@ -7,11 +7,11 @@ import { randOf } from '../rand.js';
|
|
|
7
7
|
import { TYPE } from '../schema.js';
|
|
8
8
|
import { asBlockSpec, asContentItem, sanitizeSpec, sanitizeWireJson } from '../../utils/json.js';
|
|
9
9
|
import { patchWrites, leafKey, isObject } from '../data.js';
|
|
10
|
-
import { collides
|
|
10
|
+
import { collides } from '../placement/model.js';
|
|
11
11
|
import { ref, refused, sanitizeItem } from './plan.js';
|
|
12
12
|
/** The meta ops of one facade, prepared. */
|
|
13
13
|
export const metaOps = (c) => {
|
|
14
|
-
const { doc, roles, roleOf, dataNode, view, blockTypeOf, blockDataOf, childrenIds, positionOf, is, isVoid, displayLength, live, contentTarget, REFUSED, plan,
|
|
14
|
+
const { doc, roles, roleOf, dataNode, view, blockTypeOf, blockDataOf, childrenIds, positionOf, is, isVoid, displayLength, live, contentTarget, REFUSED, plan, attr, leavingIsland, settle, remove, atomOf, writeContent } = c;
|
|
15
15
|
/**
|
|
16
16
|
* The data step of `patches` (sanitized) on a block, one of its atoms or
|
|
17
17
|
* the document (`crdt/data.ts`): none when it changes nothing, `null`
|
|
@@ -23,12 +23,12 @@ export const metaOps = (c) => {
|
|
|
23
23
|
// A block kind's atomic paths are written as one leaf each (`data.atomic`).
|
|
24
24
|
const type = inlineId === undefined && id !== undefined ? blockTypeOf(id) : undefined;
|
|
25
25
|
const atomic = (type === undefined ? undefined : roleOf(type)?.atomic) ?? [];
|
|
26
|
-
const
|
|
26
|
+
const writes = node
|
|
27
27
|
? patchWrites(node, patches, doc.clientID, randOf(doc), atomic.map((p) => leafKey(typeof p === 'string' ? [p] : p)))
|
|
28
28
|
: [];
|
|
29
|
-
if (
|
|
29
|
+
if (writes === null)
|
|
30
30
|
return null; // a patch fits no value there
|
|
31
|
-
if (
|
|
31
|
+
if (writes.length === 0)
|
|
32
32
|
return [];
|
|
33
33
|
const offset = inlineId === undefined ? undefined : atomOf(id, inlineId)?.at;
|
|
34
34
|
return [
|
|
@@ -37,7 +37,7 @@ export const metaOps = (c) => {
|
|
|
37
37
|
...(id !== undefined && { id }),
|
|
38
38
|
...(inlineId !== undefined && { inlineId, offset }),
|
|
39
39
|
ops: patches,
|
|
40
|
-
|
|
40
|
+
writes
|
|
41
41
|
}
|
|
42
42
|
];
|
|
43
43
|
};
|
|
@@ -65,10 +65,8 @@ export const metaOps = (c) => {
|
|
|
65
65
|
/**
|
|
66
66
|
* The target role decides: nothing renders a void's children,
|
|
67
67
|
* so a block retyped to a void kind hands them to the slot right after
|
|
68
|
-
* it, as the kind they show there (`settledKind`)
|
|
69
|
-
*
|
|
70
|
-
* shedding gives it (`promotedRank`), so a child a peer adds
|
|
71
|
-
* meanwhile keeps its place in the void's order among them. An island
|
|
68
|
+
* it, as the kind they show there (`settledKind`), in their order (where
|
|
69
|
+
* the read-time shedding shows a child a peer adds meanwhile). An island
|
|
72
70
|
* declared `lines` retyped to an ordinary kind keeps its lines, each
|
|
73
71
|
* retyped to the new kind's default child (the document's where that
|
|
74
72
|
* renders no content): no line kind outside its island. Any other
|
|
@@ -95,12 +93,11 @@ export const metaOps = (c) => {
|
|
|
95
93
|
}
|
|
96
94
|
if (!roles.childless(type) || moved.length === 0 || pos === null)
|
|
97
95
|
return steps;
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
// The children keep their home slots: the read shows them in the void's
|
|
97
|
+
// slot (`conc.void-children`), settled for its parent.
|
|
100
98
|
return [
|
|
101
99
|
...steps,
|
|
102
|
-
...
|
|
103
|
-
...settle(id, ids, pos.parent)
|
|
100
|
+
...settle(id, moved.map((k) => k.id), pos.parent)
|
|
104
101
|
];
|
|
105
102
|
};
|
|
106
103
|
/** Set the block type (attr write — the block keeps its identity; see `retypeSteps`). */
|
|
@@ -187,10 +184,8 @@ export const metaOps = (c) => {
|
|
|
187
184
|
if (children !== undefined) {
|
|
188
185
|
for (const kid of childrenIds(id))
|
|
189
186
|
writes.push(remove(kid));
|
|
190
|
-
// Every current child is deleted, so the new ones rank from an empty list.
|
|
191
|
-
const ranks = ranksAt([], 0, children.length, doc.clientID, randOf(doc));
|
|
192
187
|
if (children.length > 0)
|
|
193
|
-
writes.push({ op: 'insertBlocks', parent: id, index: 0, specs: children
|
|
188
|
+
writes.push({ op: 'insertBlocks', parent: id, index: 0, specs: children });
|
|
194
189
|
}
|
|
195
190
|
return plan([id], writes);
|
|
196
191
|
};
|
package/dist/crdt/doc/moves.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type JSONBlock } from '../../utils/json.js';
|
|
2
2
|
import { type BlockId, type BlockSpec, type Destination } from '../placement/model.js';
|
|
3
3
|
import type { Prepared } from './types.js';
|
|
4
|
-
import type
|
|
4
|
+
import { type OpsContext } from './steps.js';
|
|
5
5
|
/** The moves ops of one facade, prepared. */
|
|
6
6
|
export declare const moveOps: (c: OpsContext) => {
|
|
7
7
|
insertBlocks: (dest: Destination, specs: readonly (BlockSpec | JSONBlock)[]) => Prepared;
|
package/dist/crdt/doc/moves.js
CHANGED
|
@@ -7,9 +7,10 @@ import { id as newId } from '../../utils.js';
|
|
|
7
7
|
import { asBlockSpec, sanitizeSpec } from '../../utils/json.js';
|
|
8
8
|
import { collides } from '../placement/model.js';
|
|
9
9
|
import { ref } from './plan.js';
|
|
10
|
+
import { PART } from './steps.js';
|
|
10
11
|
/** The moves ops of one facade, prepared. */
|
|
11
12
|
export const moveOps = (c) => {
|
|
12
|
-
const { doc, blockTypeOf, blockDataOf, childrenIds, positionOf, isVoid, isIsland, isLine, contentItems, live, canPlace, isContainer, fits, kindToCopy, REFUSED,
|
|
13
|
+
const { doc, blockTypeOf, blockDataOf, childrenIds, positionOf, isVoid, isIsland, isLine, contentItems, live, canPlace, isContainer, fits, kindToCopy, REFUSED, plan, moveTo, move, leaving, settledKind, settle, landing, emptying, emptied } = c;
|
|
13
14
|
/**
|
|
14
15
|
* Insert blocks (specs may carry children/content/data; the batch is
|
|
15
16
|
* all-or-nothing). A spec is a `BlockSpec` or the `JSONBlock` that
|
|
@@ -29,8 +30,7 @@ export const moveOps = (c) => {
|
|
|
29
30
|
return plan([], []);
|
|
30
31
|
if ((parent !== null && !live(parent)) || collides(doc, clean))
|
|
31
32
|
return REFUSED;
|
|
32
|
-
|
|
33
|
-
return plan(clean.map((s) => s.id), [{ op: 'insertBlocks', parent, index: dest.index, specs: clean, ranks }]);
|
|
33
|
+
return plan(clean.map((s) => s.id), [{ op: 'insertBlocks', parent, index: dest.index, specs: clean }]);
|
|
34
34
|
};
|
|
35
35
|
/**
|
|
36
36
|
* Grouped move — one step, per-member conflict resolution; refused
|
|
@@ -80,8 +80,7 @@ export const moveOps = (c) => {
|
|
|
80
80
|
const retype = settle(from, moved, ppos.parent);
|
|
81
81
|
if (isContainer(from))
|
|
82
82
|
return splitOut(moved, [from], [], false, retype);
|
|
83
|
-
const
|
|
84
|
-
const writes = [...moveTo(moved, ppos.parent, ppos.index + 1, out), ...retype];
|
|
83
|
+
const writes = [...moveTo(moved, ppos.parent, ppos.index + 1, leaving(true)), ...retype];
|
|
85
84
|
const adopts = after.length > 0 &&
|
|
86
85
|
!isVoid(last) &&
|
|
87
86
|
!isIsland(last) &&
|
|
@@ -123,13 +122,11 @@ export const moveOps = (c) => {
|
|
|
123
122
|
const splitOut = (ids, levels, specs, keep, retype = []) => {
|
|
124
123
|
const moved = keep ? [] : [...ids];
|
|
125
124
|
const placed = [...moved, ...specs.map((s) => s.id)];
|
|
126
|
-
const insert = (at, index, s
|
|
125
|
+
const insert = (at, index, s) => s.length ? [{ op: 'insertBlocks', parent: at, index, specs: s }] : [];
|
|
127
126
|
const last = ids.at(-1);
|
|
128
127
|
const { parent, index } = positionOf(levels.at(-1) ?? last);
|
|
129
|
-
if (levels.length === 0)
|
|
130
|
-
|
|
131
|
-
return plan(placed, [...insert(parent, index + 1, specs, ranks), ...retype]);
|
|
132
|
-
}
|
|
128
|
+
if (levels.length === 0)
|
|
129
|
+
return plan(placed, [...insert(parent, index + 1, specs), ...retype]);
|
|
133
130
|
// Bottom up: what stays before the split at each level (the level
|
|
134
131
|
// itself, or a new head holding it), and whether anything follows it.
|
|
135
132
|
const heads = [];
|
|
@@ -141,10 +138,8 @@ export const moveOps = (c) => {
|
|
|
141
138
|
for (const level of levels) {
|
|
142
139
|
const kids = childrenIds(level);
|
|
143
140
|
const at = kids.indexOf(child);
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
...(before && !before.head ? [before.id] : [])
|
|
147
|
-
];
|
|
141
|
+
const stays = (keep && child === last) || (before !== null && !before.head);
|
|
142
|
+
const lead = kids.slice(0, at + (stays ? 1 : 0)).filter((k) => !moved.includes(k));
|
|
148
143
|
if (!follows && at + 1 < kids.length)
|
|
149
144
|
[follows, next] = [true, kids[at + 1]];
|
|
150
145
|
if ((lead.length > 0 || before?.head) && follows) {
|
|
@@ -160,27 +155,52 @@ export const moveOps = (c) => {
|
|
|
160
155
|
before = lead.length > 0 ? { id: level } : null;
|
|
161
156
|
child = level;
|
|
162
157
|
}
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
158
|
+
// Written in one gap, each right after the block before it, as an
|
|
159
|
+
// after-record out of the level's slot or a before-record into the gap
|
|
160
|
+
// before it (`leaving`): the engine's sort decides their order against
|
|
161
|
+
// a peer's records there (fork patch YP22, `sourceOrder`: the block a
|
|
162
|
+
// record stands at, then its part), so a peer's heads, pieces, lifts
|
|
163
|
+
// and new blocks in the same gap keep the serial order whatever the
|
|
164
|
+
// client ids (`del.merge.container`). The head stands at the first block
|
|
165
|
+
// (with `keep`, at it: `with`), the blocks at themselves, the new
|
|
166
|
+
// blocks after the last (`after`) — or, kept before the block that
|
|
167
|
+
// follows, at that block (`kept`). The records are written last first,
|
|
168
|
+
// so each stands before the one written before it, in the order the
|
|
169
|
+
// sort gives them; the head and the new blocks are created in the
|
|
170
|
+
// innermost level right after the block they stand at (their home,
|
|
171
|
+
// hidden while their records show them) and moved out by them.
|
|
168
172
|
const outside = before !== null && !before.head;
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
173
|
+
const side = leaving(outside);
|
|
174
|
+
const gap = index + (outside ? 1 : 0);
|
|
175
|
+
const at = moved[0] ?? last;
|
|
176
|
+
const kids0 = childrenIds(levels[0]);
|
|
177
|
+
const home = kids0.indexOf(last) + 1;
|
|
178
|
+
const specIds = specs.map((sp) => sp.id);
|
|
179
|
+
const [stands, part] = keep && !outside ? [next, PART.kept] : [last, PART.after];
|
|
180
|
+
// One record per run of adjacent blocks, each standing at its first: a
|
|
181
|
+
// block a peer's move takes out of a run leaves the others at their own place.
|
|
182
|
+
const runs = [];
|
|
183
|
+
for (const id of moved) {
|
|
184
|
+
const run = runs.at(-1);
|
|
185
|
+
if (run !== undefined && kids0.indexOf(run.at(-1)) + 1 === kids0.indexOf(id))
|
|
186
|
+
run.push(id);
|
|
187
|
+
else
|
|
188
|
+
runs.push([id]);
|
|
189
|
+
}
|
|
190
|
+
const writes = [
|
|
191
|
+
...insert(levels[0], home, specs),
|
|
192
|
+
...(before?.head ? insert(levels[0], home, [before.head]) : []),
|
|
193
|
+
...moveTo(specIds, parent, gap, side, stands, part),
|
|
194
|
+
...runs.reverse().flatMap((run) => moveTo(run, parent, gap, side, run[0], PART.self)),
|
|
195
|
+
...(before?.head
|
|
196
|
+
? moveTo([before.head.id], parent, gap, side, at, keep ? PART.with : PART.head)
|
|
197
|
+
: [])
|
|
198
|
+
];
|
|
178
199
|
for (const { spec, kids, inner } of heads.reverse()) {
|
|
179
|
-
|
|
180
|
-
writes.push(...moveTo(kids, spec.id, 0, inside.slice(0, kids.length)), ...(inner ? insert(spec.id, kids.length, [inner], inside.slice(-1)) : []));
|
|
200
|
+
writes.push(...moveTo(kids, spec.id, 0), ...(inner ? insert(spec.id, kids.length, [inner]) : []));
|
|
181
201
|
}
|
|
182
|
-
|
|
183
|
-
|
|
202
|
+
writes.push(...retype);
|
|
203
|
+
void next;
|
|
184
204
|
return plan(placed, emptying(levels[0], moved, writes, landing(parent)));
|
|
185
205
|
};
|
|
186
206
|
/**
|
package/dist/crdt/doc/reads.js
CHANGED
|
@@ -301,8 +301,8 @@ export const docReads = (c) => {
|
|
|
301
301
|
const hasBlock = (id) => blockNodeOf(doc, id) !== null;
|
|
302
302
|
/** The one liveness answer (`isLive`): `id` renders in `project()`. O(depth). */
|
|
303
303
|
const live = (id) => isLive(doc, id);
|
|
304
|
-
/** A live block that can hold content
|
|
305
|
-
const contentTarget = (id) => live(id) && view().blocks.
|
|
304
|
+
/** A live block that can hold content (a streamless block gets its own text on first write). */
|
|
305
|
+
const contentTarget = (id) => live(id) && view().blocks.has(id);
|
|
306
306
|
return {
|
|
307
307
|
dataNode,
|
|
308
308
|
blockJSON,
|
package/dist/crdt/doc/seed.js
CHANGED
|
@@ -3,7 +3,7 @@ import { hash32, setDocRand } from '../rand.js';
|
|
|
3
3
|
import { keepingReplaced } from '../incarnations.js';
|
|
4
4
|
import { asEngineDoc, asYDoc } from '../structs.js';
|
|
5
5
|
import { DOC_DATA_ROOT, SCHEMA } from '../schema.js';
|
|
6
|
-
import {
|
|
6
|
+
import { dataWrites, isObject, writeData } from '../data.js';
|
|
7
7
|
import { jsonBlockToSpec, sanitizeSpec, sanitizeWireJson, sanitizeWireString } from '../../utils/json.js';
|
|
8
8
|
import { isInitialized, META_KEY, registryEmpty, SCHEMA_NAME, SCHEMA_VERSION } from './gate.js';
|
|
9
9
|
/** Origin of the seed update's apply — non-local, like any integrated update. */
|
|
@@ -100,7 +100,7 @@ export const bindSeed = (Y, BA) => {
|
|
|
100
100
|
};
|
|
101
101
|
scratch.transact(() => {
|
|
102
102
|
specs.forEach(records);
|
|
103
|
-
|
|
103
|
+
writeData(scratch.get(DOC_DATA_ROOT), dataWrites(own));
|
|
104
104
|
});
|
|
105
105
|
return Y.encodeStateAsUpdate(raw);
|
|
106
106
|
};
|
|
@@ -11,7 +11,7 @@ import { blockNodeOf, isSelfOrDescendant } from '../placement/model.js';
|
|
|
11
11
|
import { ref } from './plan.js';
|
|
12
12
|
/** The split-merge ops of one facade, prepared. */
|
|
13
13
|
export const splitMergeOps = (c) => {
|
|
14
|
-
const { doc, view, childrenIds, positionOf, isVoid, isCellKind, blockTypeOf, insideIsland, isLayoutItem, next, previous, displayLength, contentTarget, isContainer, fits, canMerge, rendersContent, kindToCopy, REFUSED, plan,
|
|
14
|
+
const { doc, view, childrenIds, positionOf, isVoid, isCellKind, blockTypeOf, insideIsland, isLayoutItem, next, previous, displayLength, contentTarget, isContainer, fits, canMerge, rendersContent, kindToCopy, REFUSED, plan, moveTo, move, leaving, settledKind, settle, landing, emptying, emptied, merge, clamp } = c;
|
|
15
15
|
/**
|
|
16
16
|
* Split `id` at content `offset` into a new sibling `newId` (one boundary
|
|
17
17
|
* item and the claims that follow it, no text copied; children follow).
|
|
@@ -26,7 +26,7 @@ export const splitMergeOps = (c) => {
|
|
|
26
26
|
const born = ref(newId);
|
|
27
27
|
const pos = positionOf(id);
|
|
28
28
|
const rec = view().blocks.get(id);
|
|
29
|
-
if (pos === null || isVoid(id) || !rendersContent(id) ||
|
|
29
|
+
if (pos === null || isVoid(id) || !rendersContent(id) || rec === undefined)
|
|
30
30
|
return REFUSED;
|
|
31
31
|
// A table's cell holds its lines as line breaks: it never splits (`table.split`).
|
|
32
32
|
if (isCellKind(blockTypeOf(id)))
|
|
@@ -39,7 +39,6 @@ export const splitMergeOps = (c) => {
|
|
|
39
39
|
const t = tail
|
|
40
40
|
? { type, data: tail.data && sanitizeWireJson(tail.data) }
|
|
41
41
|
: { type, data: readData(rec.node) };
|
|
42
|
-
const [rank] = pieceRanks(id, at, 1);
|
|
43
42
|
const length = displayLength(id) - at;
|
|
44
43
|
const split = {
|
|
45
44
|
op: 'splitBlock',
|
|
@@ -49,7 +48,7 @@ export const splitMergeOps = (c) => {
|
|
|
49
48
|
newId: born,
|
|
50
49
|
tail: t,
|
|
51
50
|
parent: pos.parent,
|
|
52
|
-
|
|
51
|
+
index: pos.index + 1
|
|
53
52
|
};
|
|
54
53
|
return plan([born], [split, ...move(childrenIds(id), born, 0)]);
|
|
55
54
|
};
|
|
@@ -58,11 +57,11 @@ export const splitMergeOps = (c) => {
|
|
|
58
57
|
* children ADOPTED as `into`'s last children — an island's take
|
|
59
58
|
* `into`'s default child, like a baseline merge's — and `from` is
|
|
60
59
|
* hidden via the claim (undo restores it). The children stay under
|
|
61
|
-
* `from
|
|
62
|
-
*
|
|
63
|
-
* claim and `from` comes back with its children,
|
|
64
|
-
* Role rules are `canMerge`'s; a merge that would
|
|
65
|
-
* cycle is refused. `ids`: `into`.
|
|
60
|
+
* `from` and display under `into` through the claim, after `into`'s
|
|
61
|
+
* own children (the display's claim order): a concurrent delete of
|
|
62
|
+
* `into` voids the claim and `from` comes back with its children,
|
|
63
|
+
* never below them. Role rules are `canMerge`'s; a merge that would
|
|
64
|
+
* close a display cycle is refused. `ids`: `into`.
|
|
66
65
|
*/
|
|
67
66
|
const mergeBlocks = (fromId, intoId) => {
|
|
68
67
|
const from = ref(fromId);
|
|
@@ -74,9 +73,8 @@ export const splitMergeOps = (c) => {
|
|
|
74
73
|
return REFUSED;
|
|
75
74
|
const kids = childrenIds(from);
|
|
76
75
|
const retype = settle(from, kids, into);
|
|
77
|
-
const adopt = moveTo(kids, from, kids.length, ranksFor(into, Infinity, kids.length, kids));
|
|
78
76
|
// A list its only item leaves goes, as with every sibling op.
|
|
79
|
-
return plan([into], emptied([from], [merge(from, into), ...
|
|
77
|
+
return plan([into], emptied([from], [merge(from, into), ...retype], landing(into)));
|
|
80
78
|
};
|
|
81
79
|
/**
|
|
82
80
|
* Baseline-shaped merge (both `mergeBackward` and `mergeForward`):
|
|
@@ -84,7 +82,7 @@ export const splitMergeOps = (c) => {
|
|
|
84
82
|
* NOT adopted into `into` — and take the kind they show there
|
|
85
83
|
* (`settledKind`: an island's children its parent's default child, a
|
|
86
84
|
* paragraph in a list its item); then `from`'s content claims
|
|
87
|
-
* into `into`.
|
|
85
|
+
* into `into`. Placed after `from`, not at it: a concurrent delete of
|
|
88
86
|
* `into` revives `from` above its former children.
|
|
89
87
|
*/
|
|
90
88
|
const mergeUnnesting = (from, into) => {
|
|
@@ -93,8 +91,7 @@ export const splitMergeOps = (c) => {
|
|
|
93
91
|
return REFUSED;
|
|
94
92
|
const kids = childrenIds(from);
|
|
95
93
|
const retype = settle(from, kids, pos.parent);
|
|
96
|
-
|
|
97
|
-
return plan([into], [...moveTo(kids, pos.parent, pos.index + 1, out), ...retype, merge(from, into)]);
|
|
94
|
+
return plan([into], [...moveTo(kids, pos.parent, pos.index + 1, leaving(true)), ...retype, merge(from, into)]);
|
|
98
95
|
};
|
|
99
96
|
/** Remove `container`, left empty, for the key at `id` (refused if it holds text). */
|
|
100
97
|
const gone = (id, container) => {
|
|
@@ -143,7 +140,7 @@ export const splitMergeOps = (c) => {
|
|
|
143
140
|
if (!fits(slot.parent, settledKind(prev, id, slot.parent)))
|
|
144
141
|
return REFUSED;
|
|
145
142
|
const lift = [
|
|
146
|
-
...moveTo([id], slot.parent, slot.index, leaving(
|
|
143
|
+
...moveTo([id], slot.parent, slot.index, leaving(false)),
|
|
147
144
|
...settle(prev, [id], slot.parent)
|
|
148
145
|
];
|
|
149
146
|
return plan([id], emptying(prev, [id], lift, landing(slot.parent)));
|
package/dist/crdt/doc/steps.d.ts
CHANGED
|
@@ -1,28 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The step writers every prepared op composes: the plan itself, moves (a
|
|
3
|
+
* move's place and side), type steps and the kind a block shows where it
|
|
4
|
+
* lands (`settle`), the containers and layouts a write leaves empty
|
|
5
|
+
* (`emptying`, `dissolving`), delete and merge steps, and display spans of a
|
|
6
|
+
* block's content.
|
|
7
|
+
*/
|
|
1
8
|
import { TYPE } from '../schema.js';
|
|
2
9
|
import { type BlockId, type ContentItem } from '../placement/model.js';
|
|
3
10
|
import type { Plan, PlanStep } from './types.js';
|
|
4
11
|
import type { DocBase, DocReads } from './reads.js';
|
|
5
12
|
import type { DocCapability } from './capability.js';
|
|
6
13
|
/** What the step writers read: the reads and the structural capability. */
|
|
14
|
+
/**
|
|
15
|
+
* A record's order among the records standing at one block, for the
|
|
16
|
+
* engine's sort of two records at one origin (fork patch YP22): what a split
|
|
17
|
+
* of a list around a block writes there — a new head list holding the items
|
|
18
|
+
* before it (`head`), new blocks placed after the items before it, which
|
|
19
|
+
* stay (`kept`), a head holding those items and the block (`with`), the
|
|
20
|
+
* block itself leaving (`self`), new blocks after it (`after`) — in the
|
|
21
|
+
* order every serial run of the gestures gives (`del.merge.container`).
|
|
22
|
+
*/
|
|
23
|
+
export declare const PART: {
|
|
24
|
+
readonly head: 0;
|
|
25
|
+
readonly kept: 1;
|
|
26
|
+
readonly with: 2;
|
|
27
|
+
readonly self: 3;
|
|
28
|
+
readonly after: 4;
|
|
29
|
+
};
|
|
7
30
|
export type StepsContext = DocBase & DocReads & DocCapability;
|
|
8
31
|
/** The step writers of one facade. */
|
|
9
32
|
export declare const planSteps: (c: StepsContext) => {
|
|
10
33
|
REFUSED: import("./types.js").OpResult;
|
|
11
|
-
HEAD: number;
|
|
12
|
-
KEPT: number;
|
|
13
|
-
WITH: number;
|
|
14
|
-
SELF: number;
|
|
15
|
-
AFTER: number;
|
|
16
34
|
plan: (ids: readonly BlockId[], writes: PlanStep[]) => Plan;
|
|
17
|
-
|
|
18
|
-
moveTo: (ids: BlockId[], parent: BlockId | null, index: number, ranks: string[]) => PlanStep[];
|
|
35
|
+
moveTo: (ids: BlockId[], parent: BlockId | null, index: number, side?: 0 | 1, at?: BlockId, part?: number) => PlanStep[];
|
|
19
36
|
move: (ids: BlockId[], parent: BlockId | null, index: number) => PlanStep[];
|
|
20
|
-
|
|
21
|
-
at: BlockId;
|
|
22
|
-
part: readonly number[];
|
|
23
|
-
}[]) => string[];
|
|
24
|
-
pieceRanks: (id: BlockId, at: number, count: number) => string[];
|
|
25
|
-
leaving: (outer: BlockId, after: boolean, ids: readonly BlockId[]) => string[];
|
|
37
|
+
leaving: (after: boolean) => 0 | 1;
|
|
26
38
|
attr: (id: BlockId, key: typeof TYPE, value: string) => PlanStep[];
|
|
27
39
|
leavingIsland: (island: BlockId, kind: string | undefined, to: string) => string | undefined;
|
|
28
40
|
settledKind: (from: BlockId | null, kid: BlockId, parent: BlockId | null) => string | undefined;
|
package/dist/crdt/doc/steps.js
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The step writers every prepared op composes: the plan itself,
|
|
3
|
-
* move's
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* The step writers every prepared op composes: the plan itself, moves (a
|
|
3
|
+
* move's place and side), type steps and the kind a block shows where it
|
|
4
|
+
* lands (`settle`), the containers and layouts a write leaves empty
|
|
5
|
+
* (`emptying`, `dissolving`), delete and merge steps, and display spans of a
|
|
6
|
+
* block's content.
|
|
7
7
|
*/
|
|
8
|
-
import { randOf } from '../rand.js';
|
|
9
8
|
import { TYPE } from '../schema.js';
|
|
10
|
-
import { blockNodeOf
|
|
9
|
+
import { blockNodeOf } from '../placement/model.js';
|
|
11
10
|
import { REFUSED, effectOf } from './plan.js';
|
|
11
|
+
/** What the step writers read: the reads and the structural capability. */
|
|
12
|
+
/**
|
|
13
|
+
* A record's order among the records standing at one block, for the
|
|
14
|
+
* engine's sort of two records at one origin (fork patch YP22): what a split
|
|
15
|
+
* of a list around a block writes there — a new head list holding the items
|
|
16
|
+
* before it (`head`), new blocks placed after the items before it, which
|
|
17
|
+
* stay (`kept`), a head holding those items and the block (`with`), the
|
|
18
|
+
* block itself leaving (`self`), new blocks after it (`after`) — in the
|
|
19
|
+
* order every serial run of the gestures gives (`del.merge.container`).
|
|
20
|
+
*/
|
|
21
|
+
export const PART = { head: 0, kept: 1, with: 2, self: 3, after: 4 };
|
|
12
22
|
/** The step writers of one facade. */
|
|
13
23
|
export const planSteps = (c) => {
|
|
14
|
-
const { doc, runsView, roles, rendersContentOf, view, blockTypeOf, childrenIds, positionOf, ancestorsOf, is, isIsland, itemKindOf, isLayout, displayLength, contentItems, live, isContainer, fitted, emptiable,
|
|
24
|
+
const { doc, runsView, roles, rendersContentOf, view, blockTypeOf, childrenIds, positionOf, ancestorsOf, is, isIsland, itemKindOf, isLayout, displayLength, contentItems, live, isContainer, fitted, emptiable, defaultChild } = c;
|
|
15
25
|
// ── prepared ops ─────────────────────────────────────────────
|
|
16
26
|
// Every op is `prepare` (pure: `refused`, or a plan of named steps plus
|
|
17
27
|
// its effect summary, against the current version) then `apply(plan)`;
|
|
@@ -19,124 +29,52 @@ export const planSteps = (c) => {
|
|
|
19
29
|
// whole command before any write and a refusal refuses before one.
|
|
20
30
|
// Each prepare normalizes its inputs once, here at ingress: ids
|
|
21
31
|
// and strings as the wire would deliver them, payloads cloned.
|
|
22
|
-
/** `exitRanks` parts, in their order at one block. */
|
|
23
|
-
const [HEAD, KEPT, WITH, SELF, AFTER] = [0, 1, 2, 3, 5];
|
|
24
32
|
const plan = (ids, writes) => ({
|
|
25
33
|
ids,
|
|
26
34
|
writes,
|
|
27
35
|
effect: effectOf(writes),
|
|
28
36
|
version: runsView.version()
|
|
29
37
|
});
|
|
30
|
-
/** This client's next clock: what its source ranks are tied by (`sourceRank`). */
|
|
31
|
-
const clock = () => doc.store?.getClock(doc.clientID) ?? 0;
|
|
32
|
-
/**
|
|
33
|
-
* `count` ranks at `index` among `parent`'s children, the moving
|
|
34
|
-
* `exclude` left out. `run`: new blocks, which extend this client's run
|
|
35
|
-
* after a block it ranked (`order.insert.run`); a move never does
|
|
36
|
-
* (the rank-growth guard).
|
|
37
|
-
*/
|
|
38
|
-
const ranksFor = (parent, index, count, exclude = [], run = false) => {
|
|
39
|
-
const v = view();
|
|
40
|
-
const all = v.kids.get(parent) ?? [];
|
|
41
|
-
// The moving blocks' own slots left out: index → the list's index past them.
|
|
42
|
-
const gone = exclude
|
|
43
|
-
.map((id) => positionInView(v, id))
|
|
44
|
-
.filter((p) => p !== null && p.parent === parent)
|
|
45
|
-
.map((p) => p.index)
|
|
46
|
-
.sort((a, b) => a - b);
|
|
47
|
-
const raw = (i) => {
|
|
48
|
-
for (const g of gone)
|
|
49
|
-
if (g <= i)
|
|
50
|
-
i++;
|
|
51
|
-
return i;
|
|
52
|
-
};
|
|
53
|
-
const at = Math.max(0, Math.min(index, all.length - gone.length));
|
|
54
|
-
const pair = [at > 0 ? all[raw(at - 1)] : undefined, all[raw(at)]];
|
|
55
|
-
return ranksAt(pair, 1, count, doc.clientID, randOf(doc), run);
|
|
56
|
-
};
|
|
57
38
|
/**
|
|
58
|
-
* Move `ids` to `
|
|
39
|
+
* Move `ids` to display index `index` among `parent`'s children (the
|
|
40
|
+
* slot hooks see). `side`: the move record's class among the items of
|
|
41
|
+
* its origin (`placement/slots.ts`) — `0`, the blocks stand after what
|
|
42
|
+
* precedes them (a move; blocks leaving a block for the gap after it:
|
|
43
|
+
* its children, a list's last items); `1`, before what follows them
|
|
44
|
+
* (blocks leaving a block for the gap before it: a list's first item
|
|
45
|
+
* lifted out). In one gap every serial order of the gestures puts a
|
|
46
|
+
* split's pieces first, then the after-records, then the before-records
|
|
47
|
+
* (`del.merge.container`), which the engine's sort gives whatever the
|
|
48
|
+
* client ids.
|
|
59
49
|
* Every planned move carries the type steps that keep what the moved
|
|
60
50
|
* blocks show: one displayed out of an island as another kind than its
|
|
61
51
|
* stored one (`displayType`) gets that kind written, so leaving the
|
|
62
52
|
* island's slot never brings the island's child kind back.
|
|
53
|
+
* `at`, `part`: the block the record stands at and its order among the
|
|
54
|
+
* records standing there, for the engine's sort of two records at one
|
|
55
|
+
* origin (`PART`; by default the first moved block, as itself).
|
|
63
56
|
*/
|
|
64
|
-
const moveTo = (ids, parent, index,
|
|
57
|
+
const moveTo = (ids, parent, index, side = 0, at, part) => ids.length === 0
|
|
65
58
|
? []
|
|
66
59
|
: [
|
|
67
|
-
{
|
|
60
|
+
{
|
|
61
|
+
op: 'moveBlocks',
|
|
62
|
+
ids,
|
|
63
|
+
parent,
|
|
64
|
+
index,
|
|
65
|
+
...(side === 1 && { side }),
|
|
66
|
+
...(at !== undefined && { at }),
|
|
67
|
+
...(part !== undefined && { part })
|
|
68
|
+
},
|
|
68
69
|
...ids.flatMap((id) => {
|
|
69
70
|
const shown = runsView.displayType(id);
|
|
70
71
|
return shown === undefined ? [] : attr(id, TYPE, shown);
|
|
71
72
|
})
|
|
72
73
|
];
|
|
73
74
|
/** Move `ids` to `index` among `parent`'s children. */
|
|
74
|
-
const move = (ids, parent, index) => moveTo(ids, parent, index
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
* right after it), in the order they come from (`sourceRank`):
|
|
78
|
-
* two peers that split or lift out of the same list at once keep the
|
|
79
|
-
* text in its order, whatever their client ids. Each part names the
|
|
80
|
-
* block in `outer` it stands at (`at`; one that shows no text of its
|
|
81
|
-
* own, a list, stands at its first item) and its `part` there:
|
|
82
|
-
* - `HEAD`: a new list holding the items before `at`;
|
|
83
|
-
* - `KEPT`, index: a block placed after the items before `at`, which
|
|
84
|
-
* stay (a divider inserted after an item: a peer's head that takes
|
|
85
|
-
* them sorts before it);
|
|
86
|
-
* - `WITH`: a new list holding the items before `at` and `at` (`keep`);
|
|
87
|
-
* - `SELF`: `at` itself leaving (`SELF + 1` when it stands at its first item);
|
|
88
|
-
* - `AFTER`, index: a block placed after it.
|
|
89
|
-
* A degenerate gap ranks them as any insert.
|
|
90
|
-
*/
|
|
91
|
-
const exitRanks = (outer, after, parts) => {
|
|
92
|
-
const { kids } = view();
|
|
93
|
-
const { parent, index } = positionOf(outer);
|
|
94
|
-
const sibs = kids.get(parent) ?? [];
|
|
95
|
-
const gap = after ? index + 1 : index;
|
|
96
|
-
const ranks = [];
|
|
97
|
-
for (let { at, part } of parts) {
|
|
98
|
-
const own = at;
|
|
99
|
-
while (!rendersContent(at) && childrenIds(at).length > 0)
|
|
100
|
-
at = childrenIds(at)[0];
|
|
101
|
-
if (part[0] === SELF && at !== own)
|
|
102
|
-
part = [SELF + 1];
|
|
103
|
-
const path = [];
|
|
104
|
-
for (let c = at; c !== outer; c = positionOf(c).parent) {
|
|
105
|
-
const pos = positionOf(c);
|
|
106
|
-
path.unshift(kids.get(pos.parent)[pos.index].rank);
|
|
107
|
-
}
|
|
108
|
-
const side = after ? SOURCE_SIDE.after : SOURCE_SIDE.before;
|
|
109
|
-
const rank = sourceRank(sibs[gap - 1]?.rank, sibs[gap]?.rank, side, path, part, doc.clientID, clock());
|
|
110
|
-
if (rank === null)
|
|
111
|
-
return ranksFor(parent, gap, parts.length);
|
|
112
|
-
ranks.push(rank);
|
|
113
|
-
}
|
|
114
|
-
return ranks;
|
|
115
|
-
};
|
|
116
|
-
/**
|
|
117
|
-
* Ranks for the `count` blocks a split of `id` at `at` puts right after
|
|
118
|
-
* it (Enter, a paste of several lines), by where it splits
|
|
119
|
-
* (SW12-crdt-1, SW12-crdt-4): two peers splitting one block at once
|
|
120
|
-
* keep its pieces in text order, whatever their client ids. Counted
|
|
121
|
-
* from the end — the text after the split point, most first — so a
|
|
122
|
-
* peer's own edit before its split point (typing, then Enter; a paste
|
|
123
|
-
* over a selection) does not move it; an unseen edit after
|
|
124
|
-
* it does (the residual, in the delete contract).
|
|
125
|
-
*/
|
|
126
|
-
const pieceRanks = (id, at, count) => {
|
|
127
|
-
const { parent, index } = positionOf(id);
|
|
128
|
-
const sibs = view().kids.get(parent) ?? [];
|
|
129
|
-
const ranks = [];
|
|
130
|
-
for (let i = 0; i < count; i++) {
|
|
131
|
-
const rank = sourceRank(sibs[index]?.rank, sibs[index + 1]?.rank, SOURCE_SIDE.pieces, [], [at - displayLength(id), i], doc.clientID, clock());
|
|
132
|
-
if (rank === null)
|
|
133
|
-
return ranksFor(parent, index + 1, count);
|
|
134
|
-
ranks.push(rank);
|
|
135
|
-
}
|
|
136
|
-
return ranks;
|
|
137
|
-
};
|
|
138
|
-
/** `exitRanks` for `ids` themselves leaving `outer`. */
|
|
139
|
-
const leaving = (outer, after, ids) => exitRanks(outer, after, ids.map((at) => ({ at, part: [SELF] })));
|
|
75
|
+
const move = (ids, parent, index) => moveTo(ids, parent, index);
|
|
76
|
+
/** The side of blocks leaving a block for the gap right after it (`after`) or right before it. */
|
|
77
|
+
const leaving = (after) => (after ? 0 : 1);
|
|
140
78
|
/**
|
|
141
79
|
* A type step, planned only when the kind differs (the one same-value
|
|
142
80
|
* guard). A kind counts as the same only when the block is stored and
|
|
@@ -241,10 +179,10 @@ export const planSteps = (c) => {
|
|
|
241
179
|
* with one item or none — `gone` (blocks the plan removes) and
|
|
242
180
|
* `leaving` (blocks it moves) no longer count, an item `writes` move or
|
|
243
181
|
* insert into it does. With one left, the layout and that item are
|
|
244
|
-
* deleted and the item's children moved
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
182
|
+
* deleted and the item's children moved right after the layout, where
|
|
183
|
+
* `layout.single` shows them, as the kind they show there (`settle`);
|
|
184
|
+
* with none, the layout is deleted. One plan with the write that caused
|
|
185
|
+
* it.
|
|
248
186
|
*/
|
|
249
187
|
const dissolving = (gone, leaving, writes) => {
|
|
250
188
|
const away = new Set([...gone, ...leaving]);
|
|
@@ -269,17 +207,17 @@ export const planSteps = (c) => {
|
|
|
269
207
|
const items = slots.filter((k) => !away.has(k.id) && blockTypeOf(k.id) === item);
|
|
270
208
|
if (items.length + arriving > 1)
|
|
271
209
|
continue;
|
|
272
|
-
const { parent
|
|
273
|
-
const rank = kids.get(parent)[index].rank;
|
|
210
|
+
const { parent } = positionOf(layout);
|
|
274
211
|
const last = items[0];
|
|
275
212
|
if (last === undefined) {
|
|
276
213
|
out.push(deleting(layout, [layout]));
|
|
277
214
|
continue;
|
|
278
215
|
}
|
|
216
|
+
// Its blocks keep their home slots: the read shows them in the layout's
|
|
217
|
+
// slot (`layout.single`), settled for its parent.
|
|
279
218
|
const moved = (kids.get(last.id) ?? []).filter((k) => !away.has(k.id));
|
|
280
219
|
const ids = moved.map((k) => k.id);
|
|
281
|
-
|
|
282
|
-
out.push(...moveTo(ids, parent, index + 1, ranks), ...settle(last.id, ids, parent), deleting(layout, [layout, last.id]));
|
|
220
|
+
out.push(...settle(last.id, ids, parent), deleting(layout, [layout, last.id]));
|
|
283
221
|
}
|
|
284
222
|
return out;
|
|
285
223
|
};
|
|
@@ -349,17 +287,9 @@ export const planSteps = (c) => {
|
|
|
349
287
|
const emptied = (leaving, writes, kept) => emptyingAll(leaving.map((id) => positionOf(id).parent), leaving, writes, kept);
|
|
350
288
|
return {
|
|
351
289
|
REFUSED,
|
|
352
|
-
HEAD,
|
|
353
|
-
KEPT,
|
|
354
|
-
WITH,
|
|
355
|
-
SELF,
|
|
356
|
-
AFTER,
|
|
357
290
|
plan,
|
|
358
|
-
ranksFor,
|
|
359
291
|
moveTo,
|
|
360
292
|
move,
|
|
361
|
-
exitRanks,
|
|
362
|
-
pieceRanks,
|
|
363
293
|
leaving,
|
|
364
294
|
attr,
|
|
365
295
|
leavingIsland,
|