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
|
@@ -1,27 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { nonceOf, randOf } from '../rand.js';
|
|
1
|
+
import { BLOCK_NODE, CONTENT, CONTENT_NODE, DEL_PREFIX, hasDeleteMark, ID, INLINE_NODE, isNodeLike, NONCE, REGISTRY_KEY, TYPE, WITHDRAW_PREFIX } from '../schema.js';
|
|
2
|
+
import { nonceOf } from '../rand.js';
|
|
4
3
|
import { indexOf } from '../text/runs.js';
|
|
5
4
|
import { Y } from '../engine.js';
|
|
6
5
|
import { writeRunMarks } from '../text/marks.js';
|
|
7
|
-
import { blockTextOf, DEAD, newNode, ownText as ownTextOf, splitAt } from '../text/model.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { blockTextOf, DEAD, deleteClaims, newNode, ownText as ownTextOf, splitAt } from '../text/model.js';
|
|
7
|
+
import { cloneJsonSafe } from '../../utils/json.js';
|
|
8
|
+
import { dataWrites, patchWrites, writeData } from '../data.js';
|
|
10
9
|
import { incarnationNode, isIncarnationId } from '../incarnations.js';
|
|
11
|
-
import { engineOps } from '../structs.js';
|
|
10
|
+
import { engineOps, firstItem, storeOf } from '../structs.js';
|
|
11
|
+
import { anchorOf, isBlockSlot, isRecord, nodeOf, placeAfter, sameUnit, shownBy, stampOf, triples, unitOf, unitsOf } from './slots.js';
|
|
12
|
+
import { ownerOfNode, slotOwner } from './resolve.js';
|
|
12
13
|
export { REGISTRY_KEY };
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
|
|
16
|
-
import { candidatesOf, resolvePlacements } from './resolve.js';
|
|
17
|
-
import { bySlot, childrenIndex, displayParentOf, displaySlotOf, isLiveIn } from './display.js';
|
|
18
|
-
/**
|
|
19
|
-
* Bind the placement model to a concrete engine surface.
|
|
20
|
-
*
|
|
21
|
-
* `Y` must be the vendored v14 module (`import * as Y from
|
|
22
|
-
* 'lib/crdt/vendor/yjs'`). Consumers inject it so this file stays free of
|
|
23
|
-
* runtime vendor imports (see `engine-api.ts`).
|
|
24
|
-
*/
|
|
14
|
+
export { promotedRank } from './keys.js';
|
|
15
|
+
export { bySlot, childrenIndex, displayParentOf, displaySlotOf, documentOrder, isLiveIn } from './display.js';
|
|
16
|
+
import { bySlot, childrenIndex, displaySlotOf, displayParentOf, isLiveIn } from './display.js';
|
|
25
17
|
const { isKeptReplaced } = engineOps(Y);
|
|
26
18
|
// ── registry / record access ────────────────────────────────────────
|
|
27
19
|
export const registryOf = (doc) => doc.get(REGISTRY_KEY);
|
|
@@ -47,14 +39,24 @@ export const isLive = (doc, id) => isLiveIn(view(doc), id);
|
|
|
47
39
|
/**
|
|
48
40
|
* The history's creation rule (`doc/history.ts`, `hist.undo.withdraw`): an
|
|
49
41
|
* undo never deletes a block the undone step created. Its node and
|
|
50
|
-
* structure (attrs,
|
|
51
|
-
* stay and the undoer's withdraw mark is written on it; what the step
|
|
52
|
-
* inside the content
|
|
53
|
-
*
|
|
42
|
+
* structure (attrs, its home slot in its parent's sequence, the content
|
|
43
|
+
* node) stay and the undoer's withdraw mark is written on it; what the step
|
|
44
|
+
* wrote inside the content node (the undoer's own text, atoms, boundaries,
|
|
45
|
+
* claims) is deleted as usual, and so are the move records and data slots
|
|
46
|
+
* the step wrote in the created node's sequence. The index shows a withdrawn
|
|
54
47
|
* block while it holds another writer's content.
|
|
55
48
|
*/
|
|
56
49
|
export const withdrawOnUndo = (doc) => (item, stackItem) => {
|
|
57
50
|
const registry = registryOf(doc);
|
|
51
|
+
const created = (id) => {
|
|
52
|
+
const node = registry.getAttr(id);
|
|
53
|
+
const entry = isNodeLike(node) ? node._item : null;
|
|
54
|
+
return !!entry?.id && stackItem.inserts.has(entry.id.client, entry.id.clock);
|
|
55
|
+
};
|
|
56
|
+
// A home slot of a block the step created stays with the block (withdrawn, not gone).
|
|
57
|
+
const seq = item;
|
|
58
|
+
if (seq?.parentSub === null && seq.content?.arr !== undefined)
|
|
59
|
+
return seq.content.arr.every((v) => isBlockSlot(v) && created(v.b));
|
|
58
60
|
let below = null;
|
|
59
61
|
let cur = item;
|
|
60
62
|
while (cur?.parent !== registry) {
|
|
@@ -63,33 +65,18 @@ export const withdrawOnUndo = (doc) => (item, stackItem) => {
|
|
|
63
65
|
if (cur === null)
|
|
64
66
|
return false;
|
|
65
67
|
}
|
|
66
|
-
if (!cur.id || !stackItem.inserts.has(cur.id.client, cur.id.clock))
|
|
68
|
+
if (!cur.id || cur.parentSub === null || !stackItem.inserts.has(cur.id.client, cur.id.clock))
|
|
67
69
|
return false;
|
|
68
70
|
if (below === null) {
|
|
69
71
|
const node = cur.content.type;
|
|
70
72
|
node.setAttr(WITHDRAW_PREFIX + doc.clientID, true);
|
|
71
73
|
return true;
|
|
72
74
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
* map, stamped `seq = localMax+1, client = doc.clientID`, then compact:
|
|
79
|
-
* candidates below the top-2 are tombstoned (the runner-up survives as
|
|
80
|
-
* the acyclic fallback; deeper history is re-created by undo anyway).
|
|
81
|
-
* Must run inside a transaction.
|
|
82
|
-
*/
|
|
83
|
-
export const writePlacement = (doc, node, p, r) => {
|
|
84
|
-
const at = node.getAttr(AT);
|
|
85
|
-
if (!isNodeLike(at))
|
|
86
|
-
throw new Error(`writePlacement: block missing at-map`);
|
|
87
|
-
const cands = candidatesOf(node);
|
|
88
|
-
const seq = (cands[0]?.seq ?? 0) + 1;
|
|
89
|
-
// Tombstone everything below the runner-up (top-2 kept).
|
|
90
|
-
for (const c of cands.slice(2))
|
|
91
|
-
at.deleteAttr(c.key);
|
|
92
|
-
at.setAttr(`${seq}.${doc.clientID}`, { p, r });
|
|
75
|
+
// The created node's own attrs stay (its content node among them); the records and data
|
|
76
|
+
// slots of its sequence go, and so does what the step wrote inside the content node.
|
|
77
|
+
if (below === item)
|
|
78
|
+
return below.parentSub !== null;
|
|
79
|
+
return below.parentSub !== CONTENT;
|
|
93
80
|
};
|
|
94
81
|
// ── derived state ───────────────────────────────────────────────────
|
|
95
82
|
/** The doc's index as a `ModelView` — folded up to the open transaction's last write. */
|
|
@@ -100,7 +87,7 @@ export const buildInline = (atom) => {
|
|
|
100
87
|
const node = newNode(INLINE_NODE);
|
|
101
88
|
node.setAttr(ID, atom.id);
|
|
102
89
|
node.setAttr(TYPE, atom.type);
|
|
103
|
-
|
|
90
|
+
writeData(node, dataWrites(atom.data));
|
|
104
91
|
return node;
|
|
105
92
|
};
|
|
106
93
|
// ── structural predicates ───────────────────────────────────────────
|
|
@@ -110,7 +97,7 @@ export const buildInline = (atom) => {
|
|
|
110
97
|
* (`displayParentOf`), not the raw placement chain. A merge claim can
|
|
111
98
|
* place a block inside another's display subtree without appearing in
|
|
112
99
|
* its raw ancestry, so the merge refusal must see through the claim
|
|
113
|
-
* redirect — this is exactly the relation
|
|
100
|
+
* redirect — this is exactly the relation the index's placements keep
|
|
114
101
|
* acyclic: a merge that would close a cycle is refused without mutation.
|
|
115
102
|
*/
|
|
116
103
|
export const isSelfOrDescendant = (placements, own, id, maybeAncestor) => {
|
|
@@ -133,62 +120,152 @@ export const isSelfOrDescendant = (placements, own, id, maybeAncestor) => {
|
|
|
133
120
|
// ── write primitives ────────────────────────────────────────────────
|
|
134
121
|
// The document's prepared plans decide every write; these only
|
|
135
122
|
// perform one planned step and never refuse. Must run inside a transaction.
|
|
123
|
+
/** The sequence node of `parent` (`null`: the root's). */
|
|
124
|
+
const sequenceOf = (doc, parent) => parent === null ? registryOf(doc) : blockNodeOf(doc, parent);
|
|
136
125
|
/**
|
|
137
|
-
* `
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* Degenerate seams (`left >= right`) are legal replicated states, not
|
|
141
|
-
* caller errors: equal-rank neighbours occur after cycle-fallback
|
|
142
|
-
* rehoming (two blocks may carry the same rank string minted in
|
|
143
|
-
* different sibling lists), and `rankBetween` THROWS on them — so
|
|
144
|
-
* callers must guard. No string sorts strictly between equal strings,
|
|
145
|
-
* so each new member takes `left` verbatim: it JOINS the tie and the
|
|
146
|
-
* `(rank, id)` display sort orders the whole group deterministically.
|
|
147
|
-
* On a valid seam the emitted chain is `rankBetween(left, right)` then
|
|
148
|
-
* `rankBetween(prev, right)` — with `run`, `rankAfter` (an insert: after a
|
|
149
|
-
* block this client ranked, in its run there).
|
|
126
|
+
* The block slots item `it` shows as `v` reads it: a record's, the units
|
|
127
|
+
* whose block's accepted edge is it, in the order it names them; a slot
|
|
128
|
+
* item's, those shown at home (moved away or not: a cycle may send one back).
|
|
150
129
|
*/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
130
|
+
const shownIn = (doc, v, it) => {
|
|
131
|
+
const registry = registryOf(doc);
|
|
132
|
+
const via = isRecord(it) ? it : null;
|
|
133
|
+
return unitsOf(doc, it, (_, value, unit) => {
|
|
134
|
+
if (!isBlockSlot(value))
|
|
135
|
+
return false;
|
|
136
|
+
const b = slotOwner(registry, isKeptReplaced, value.b, unit.c);
|
|
137
|
+
const home = v.blocks.get(b)?.home;
|
|
138
|
+
return !!home && sameUnit(home, unit) && v.accepted(b)?.via === via;
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
/** The entry block `id` displays as in `v` (its home slot, or its unit at the record it shows at), or `null`. */
|
|
142
|
+
const anchorIn = (doc, v, id) => {
|
|
143
|
+
const home = v.blocks.get(id)?.home;
|
|
144
|
+
if (home === undefined || home === null)
|
|
145
|
+
return null;
|
|
146
|
+
const edge = v.accepted(id);
|
|
147
|
+
// At the root's tail (a cycle): the engine's place, the nearest one there is.
|
|
148
|
+
if (edge === null)
|
|
149
|
+
return anchorOf(doc, home);
|
|
150
|
+
if (edge.via === null) {
|
|
151
|
+
const u = unitOf(doc, home);
|
|
152
|
+
return u === null ? null : { value: u.value, unit: home, via: null, at: 0 };
|
|
156
153
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
154
|
+
return shownIn(doc, v, edge.via).find((e) => sameUnit(e.unit, home)) ?? null;
|
|
155
|
+
};
|
|
156
|
+
/** The entry right before `e` in its sequence as `v` reads it (`e`'s record's previous unit, else the nearest shown to the left). */
|
|
157
|
+
const beforeIn = (doc, v, e) => {
|
|
158
|
+
const start = e.via ?? unitOf(doc, e.unit).item;
|
|
159
|
+
const own = shownIn(doc, v, start);
|
|
160
|
+
// A record's unit before `e`'s place; a slot item's last unit before `e`.
|
|
161
|
+
const before = e.via === null
|
|
162
|
+
? own.findLast((x) => x.unit.k < e.unit.k)
|
|
163
|
+
: e.at > 0
|
|
164
|
+
? (own[e.at - 1] ?? null)
|
|
165
|
+
: undefined;
|
|
166
|
+
if (before !== undefined)
|
|
167
|
+
return before;
|
|
168
|
+
for (let p = start.left; p !== null; p = p.left) {
|
|
169
|
+
const list = shownIn(doc, v, p);
|
|
170
|
+
if (list.length > 0)
|
|
171
|
+
return list[list.length - 1];
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Write `slots` (new blocks' home slots) and/or a move of `moved` (their
|
|
177
|
+
* home units, as `client, clock, len` triples) under `parent`, right after
|
|
178
|
+
* the displayed child `left` (`null`: first) and before `right`: into the
|
|
179
|
+
* sequence that holds `left`'s displaying unit (a promoted or claimed
|
|
180
|
+
* neighbour's own parent), after it — or before `right`'s — else into
|
|
181
|
+
* `parent`'s own sequence. `side` is the record's class among the items of
|
|
182
|
+
* its origin (`placement/slots.ts`: `0` after, `1` before).
|
|
183
|
+
*/
|
|
184
|
+
export const placeBlocks = (doc, v, parent, left, right, slots, moved = null, side = 0, part = 0, at = null) => {
|
|
185
|
+
// Where `v` shows its blocks: a unit a cycle sent back elsewhere is neither an anchor nor carried.
|
|
186
|
+
const shown = (r) => shownIn(doc, v, r);
|
|
187
|
+
// The record stands at `at` (else its first unit), named with the home slots above it.
|
|
188
|
+
const chain = (first) => unitChain(doc, v, at ?? first);
|
|
189
|
+
const l = left === null ? null : anchorIn(doc, v, left);
|
|
190
|
+
if (l !== null)
|
|
191
|
+
return void placeAfter(doc, nodeOf(doc, l), l, null, slots, moved, side, part, chain, shown);
|
|
192
|
+
const r = right === null ? null : anchorIn(doc, v, right);
|
|
193
|
+
if (r !== null)
|
|
194
|
+
return void placeAfter(doc, nodeOf(doc, r), beforeIn(doc, v, r), r, slots, moved, side, part, chain, shown);
|
|
195
|
+
placeAfter(doc, sequenceOf(doc, parent), null, null, slots, moved, side, part, chain, shown);
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* The chain a move record names for the engine's sort (fork patch YP22):
|
|
199
|
+
* the home slots from the root sequence down to unit `u`, outermost first —
|
|
200
|
+
* the slot of the block owning `u`'s sequence, that block's parent's, …
|
|
201
|
+
* Home slots never move, so every writer names one chain for one unit.
|
|
202
|
+
*/
|
|
203
|
+
const unitChain = (doc, v, u) => {
|
|
204
|
+
const out = [u];
|
|
205
|
+
const item = unitOf(doc, u)?.item;
|
|
206
|
+
let owner = item === undefined
|
|
207
|
+
? null
|
|
208
|
+
: ownerOfNode(registryOf(doc), isKeptReplaced, item.parent);
|
|
209
|
+
for (let n = 0; owner != null && n < 4096; n++) {
|
|
210
|
+
const home = v.blocks.get(owner)?.home;
|
|
211
|
+
if (!home)
|
|
212
|
+
break;
|
|
213
|
+
out.unshift({ c: home.c, k: home.k });
|
|
214
|
+
owner = home.parent;
|
|
165
215
|
}
|
|
166
216
|
return out;
|
|
167
217
|
};
|
|
168
218
|
/**
|
|
169
|
-
*
|
|
170
|
-
* `
|
|
171
|
-
*
|
|
172
|
-
* first placement candidate `{p, r}` stamped `1.clientID`. Pre-integration
|
|
173
|
-
* writes materialize when the node integrates (the registry write comes last).
|
|
219
|
+
* The displayed children of `parent` in `v` around display index `index`
|
|
220
|
+
* (the `exclude`d left out): the one before it and the one at it, the
|
|
221
|
+
* neighbours {@link placeBlocks} writes between.
|
|
174
222
|
*/
|
|
175
|
-
const
|
|
223
|
+
const around = (v, parent, index, exclude = []) => {
|
|
224
|
+
const all = v.kids.get(parent) ?? [];
|
|
225
|
+
const list = exclude.length === 0 ? all : all.filter((k) => !exclude.includes(k.id));
|
|
226
|
+
const i = Math.max(0, Math.min(index, list.length));
|
|
227
|
+
return [list[i - 1]?.id ?? null, list[i]?.id ?? null];
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Move `ids` (in order) to display index `index` among `parent`'s children
|
|
231
|
+
* (themselves left out): one record naming their home slots, after the
|
|
232
|
+
* child before that index. `at` is the block the record stands at for the
|
|
233
|
+
* engine's sort of two records at one origin (its home slot; `null`: the
|
|
234
|
+
* first moved) and `part` its order among the records standing there.
|
|
235
|
+
*/
|
|
236
|
+
export const moveBlocksTo = (doc, v, ids, parent, index, side = 0, part = 0, at = null) => {
|
|
237
|
+
const units = ids.flatMap((id) => {
|
|
238
|
+
const home = v.blocks.get(id)?.home;
|
|
239
|
+
return home ? [home] : [];
|
|
240
|
+
});
|
|
241
|
+
if (units.length === 0)
|
|
242
|
+
return;
|
|
243
|
+
const anchor = at === null ? null : (v.blocks.get(at)?.home ?? null);
|
|
244
|
+
placeBlocks(doc, v, parent, ...around(v, parent, index, ids), null, triples(units), side, part, anchor);
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* One registry entry: the block node with its own backing text holding
|
|
248
|
+
* `items` (none for a split-born block: its stream lives in the text it was
|
|
249
|
+
* split from) and, in its sequence, its children's home slots in order (each
|
|
250
|
+
* child's node created the same way). Pre-integration writes materialize
|
|
251
|
+
* when the node integrates (the registry write comes last). The block's own
|
|
252
|
+
* home slot is the caller's ({@link placeBlocks}).
|
|
253
|
+
*/
|
|
254
|
+
const createBlock = (doc, sp, items, n = nonceOf(doc)) => {
|
|
255
|
+
// The block's id is its registry key: the node writes none of its own.
|
|
176
256
|
const node = newNode(BLOCK_NODE);
|
|
177
|
-
node.setAttr(ID, id);
|
|
178
257
|
node.setAttr(NONCE, n);
|
|
179
|
-
node.setAttr(TYPE, type);
|
|
180
|
-
|
|
258
|
+
node.setAttr(TYPE, sp.type);
|
|
259
|
+
writeData(node, dataWrites(sp.data));
|
|
181
260
|
const content = items === null ? null : textOf(items);
|
|
182
261
|
if (content !== null)
|
|
183
262
|
node.setAttr(CONTENT, content);
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
at.setAttr(`1.${doc.clientID}`, place);
|
|
191
|
-
registryOf(doc).setAttr(id, node);
|
|
263
|
+
const children = sp.children ?? [];
|
|
264
|
+
if (children.length > 0)
|
|
265
|
+
node.insert(node.length, children.map((c) => ({ b: c.id })));
|
|
266
|
+
for (const child of children)
|
|
267
|
+
createBlock(doc, child, child.content ?? []);
|
|
268
|
+
registryOf(doc).setAttr(sp.id, node);
|
|
192
269
|
if (content !== null)
|
|
193
270
|
markText(doc, content, items);
|
|
194
271
|
};
|
|
@@ -211,36 +288,55 @@ const textOf = (items) => {
|
|
|
211
288
|
const markText = (doc, content, items) => writeRunMarks(doc, content, 0, items.map((item) => item.kind === 'text'
|
|
212
289
|
? { length: item.text.length, marks: item.marks }
|
|
213
290
|
: { length: 1 }));
|
|
291
|
+
/**
|
|
292
|
+
* Materialize `specs` (whole subtrees, in order) under `parent` at display
|
|
293
|
+
* index `index`: each spec's node with its children, then their home slots
|
|
294
|
+
* in one run after the child before that index. `split`: the boundary of
|
|
295
|
+
* the split these blocks come with (a paste's lines at a split point), their
|
|
296
|
+
* slots' sort key among the slots of one origin ({@link BlockSlot}).
|
|
297
|
+
*/
|
|
298
|
+
export const insertSpecsAt = (doc, v, parent, index, specs, split = null) => {
|
|
299
|
+
if (specs.length === 0)
|
|
300
|
+
return;
|
|
301
|
+
const [left, right] = around(v, parent, index);
|
|
302
|
+
for (const sp of specs)
|
|
303
|
+
createBlock(doc, sp, sp.content ?? []);
|
|
304
|
+
placeBlocks(doc, v, parent, left, right, specs.map((sp) => (split === null ? { b: sp.id } : { b: sp.id, s: [split.c, split.k] })));
|
|
305
|
+
};
|
|
214
306
|
/**
|
|
215
307
|
* Restore definition (migration only; a first import
|
|
216
308
|
* restores into an empty doc): make `specs` the whole visible document
|
|
217
309
|
* under their own ids, in ONE transaction. An existing id keeps its
|
|
218
310
|
* registry entry: every delete mark is cleared and its type, data,
|
|
219
|
-
*
|
|
220
|
-
* the spec's content
|
|
311
|
+
* place and content are rewritten in place — its claims deleted, a
|
|
312
|
+
* fresh own text holding the spec's content; a block whose stream still
|
|
221
313
|
* starts at a live boundary gets a new nonce, so that boundary goes inert
|
|
222
314
|
* and the own text is its stream. An absent id is created. Every other
|
|
223
|
-
* block gets this writer's delete mark.
|
|
224
|
-
*
|
|
315
|
+
* block gets this writer's delete mark. The places are written in the
|
|
316
|
+
* tree's order and the rewrites are last-writer-wins attrs, so two replicas
|
|
225
317
|
* restoring the same specs converge on one copy.
|
|
226
318
|
*/
|
|
227
319
|
export const restoreBlocks = (doc, specs) => doc.transact(() => {
|
|
228
|
-
const { own } = view(doc);
|
|
320
|
+
const { blocks, own } = view(doc);
|
|
229
321
|
const keep = new Set();
|
|
230
322
|
const restore = (list, parent) => {
|
|
231
|
-
let
|
|
323
|
+
let left = null;
|
|
232
324
|
for (const sp of list) {
|
|
233
325
|
keep.add(sp.id);
|
|
234
326
|
let node = blockNodeOf(doc, sp.id);
|
|
327
|
+
const v = view(doc);
|
|
235
328
|
if (node === null) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
node
|
|
239
|
-
node.setAttr(AT, newNode(AT_NODE));
|
|
240
|
-
registryOf(doc).setAttr(sp.id, node);
|
|
329
|
+
createBlock(doc, { ...sp, children: [] }, []);
|
|
330
|
+
placeBlocks(doc, v, parent, left, null, [{ b: sp.id }]);
|
|
331
|
+
node = blockNodeOf(doc, sp.id);
|
|
241
332
|
}
|
|
242
|
-
else
|
|
243
|
-
|
|
333
|
+
else {
|
|
334
|
+
deleteClaims(doc, blocks.get(sp.id)?.claims ?? []);
|
|
335
|
+
if (own.streamOf(sp.id)?.home !== sp.id && own.streamOf(sp.id) !== undefined)
|
|
336
|
+
node.setAttr(NONCE, nonceOf(doc));
|
|
337
|
+
const home = v.blocks.get(sp.id)?.home;
|
|
338
|
+
if (home)
|
|
339
|
+
placeBlocks(doc, v, parent, left, null, null, triples([home]));
|
|
244
340
|
}
|
|
245
341
|
for (const key of [...node.attrKeys()]) {
|
|
246
342
|
if (key.startsWith(DEL_PREFIX) || key.startsWith(WITHDRAW_PREFIX))
|
|
@@ -248,13 +344,11 @@ export const restoreBlocks = (doc, specs) => doc.transact(() => {
|
|
|
248
344
|
}
|
|
249
345
|
if (node.getAttr(TYPE) !== sp.type)
|
|
250
346
|
node.setAttr(TYPE, sp.type);
|
|
251
|
-
|
|
347
|
+
writeData(node, patchWrites(node, [{ path: [], value: sp.data ?? {} }]) ?? []);
|
|
252
348
|
const text = textOf(sp.content ?? []);
|
|
253
349
|
node.setAttr(CONTENT, text);
|
|
254
350
|
markText(doc, text, sp.content ?? []);
|
|
255
|
-
|
|
256
|
-
rank = rankBetween(rank, undefined, 0, () => 0);
|
|
257
|
-
writePlacement(doc, node, parent, rank);
|
|
351
|
+
left = sp.id;
|
|
258
352
|
restore(sp.children ?? [], sp.id);
|
|
259
353
|
}
|
|
260
354
|
};
|
|
@@ -265,20 +359,6 @@ export const restoreBlocks = (doc, specs) => doc.transact(() => {
|
|
|
265
359
|
}
|
|
266
360
|
});
|
|
267
361
|
});
|
|
268
|
-
/**
|
|
269
|
-
* Materialize one spec subtree: the block owns its backing text (its
|
|
270
|
-
* stream from index 0); children recurse under a fresh sequential rank
|
|
271
|
-
* chain (a new block has no siblings to interleave with).
|
|
272
|
-
*/
|
|
273
|
-
export const materializeSpec = (doc, sp, parent, rank) => {
|
|
274
|
-
createBlock(doc, sp.id, sp.type, sp.data, { p: parent, r: rank }, [], sp.content ?? []);
|
|
275
|
-
let left;
|
|
276
|
-
for (const child of sp.children ?? []) {
|
|
277
|
-
const r = rankBetween(left, undefined, doc.clientID, randOf(doc));
|
|
278
|
-
materializeSpec(doc, child, sp.id, r);
|
|
279
|
-
left = r;
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
362
|
/**
|
|
283
363
|
* Does any id of `specs` (whole subtrees) collide — with another spec id,
|
|
284
364
|
* or with any registry entry, live or deleted? The registry is keyed by
|
|
@@ -313,30 +393,35 @@ export const insertBlocks = (doc, dest, specs) => {
|
|
|
313
393
|
if (collides(doc, specs))
|
|
314
394
|
return false;
|
|
315
395
|
return doc.transact(() => {
|
|
316
|
-
|
|
317
|
-
const at = Math.max(0, Math.min(dest.index, sibs.length));
|
|
318
|
-
const ranks = ranksAt(sibs, at, specs.length, doc.clientID, randOf(doc));
|
|
319
|
-
specs.forEach((sp, i) => materializeSpec(doc, sp, dest.parent, ranks[i]));
|
|
396
|
+
insertSpecsAt(doc, view(doc), dest.parent, dest.index, specs);
|
|
320
397
|
return true;
|
|
321
398
|
});
|
|
322
399
|
};
|
|
323
400
|
/** {@link insertBlocks} of one spec. */
|
|
324
401
|
export const insertBlock = (doc, dest, spec) => insertBlocks(doc, dest, [spec]);
|
|
325
402
|
/**
|
|
326
|
-
* Split `id` at content `offset` into the new block `newId` placed
|
|
327
|
-
* `
|
|
328
|
-
* (fork patch YP7) and the merge claims that follow it
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
* the new block
|
|
332
|
-
* separate step. `id` must have a
|
|
403
|
+
* Split `id` at content `offset` into the new block `newId` placed under
|
|
404
|
+
* `parent` at display `index`: one boundary item for `newId` at the end of
|
|
405
|
+
* the gap at `offset` (fork patch YP7) and the merge claims that follow it
|
|
406
|
+
* written again in the new block's stream — no text is copied, so an
|
|
407
|
+
* offline edit to the tail keeps landing on the same items and displays in
|
|
408
|
+
* the new block after convergence. `tail` is the new block's type/data,
|
|
409
|
+
* decided once by the plan. Children are a separate step. `id` must have a
|
|
410
|
+
* stream or a claim ({@link ownText}).
|
|
411
|
+
* Returns the split's boundary unit (the key of the slots a paste adds at it).
|
|
333
412
|
*/
|
|
334
|
-
export const writeSplit = (doc, id, offset, newId, tail,
|
|
335
|
-
const
|
|
413
|
+
export const writeSplit = (doc, id, offset, newId, tail, parent, index) => {
|
|
414
|
+
const v = view(doc);
|
|
336
415
|
const n = nonceOf(doc);
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
|
|
416
|
+
// The boundary is the split's first write: its id is this writer's next.
|
|
417
|
+
const bound = { c: doc.clientID, k: storeOf(doc).getClock(doc.clientID) };
|
|
418
|
+
splitAt(doc, v.blocks, v.own, id, offset, newId, n);
|
|
419
|
+
const data = tail.data === undefined ? undefined : cloneJsonSafe(tail.data);
|
|
420
|
+
createBlock(doc, { id: newId, type: tail.type, data }, null, n);
|
|
421
|
+
// The tail's slot carries its boundary (`BlockSlot.s`): two peers' splits of
|
|
422
|
+
// one block sort by their boundaries' order in the text, in the gap after it.
|
|
423
|
+
placeBlocks(doc, v, parent, ...around(v, parent, index), [{ b: newId, s: [bound.c, bound.k] }]);
|
|
424
|
+
return bound;
|
|
340
425
|
};
|
|
341
426
|
/**
|
|
342
427
|
* Give the streamless block `id` its own text (`T.ownText`: a derived
|
|
@@ -346,7 +431,7 @@ export const ownText = (doc, id) => ownTextOf(doc, view(doc).blocks.get(id));
|
|
|
346
431
|
// ── queries ─────────────────────────────────────────────────────────
|
|
347
432
|
/**
|
|
348
433
|
* Canonical projection: pure derivation from replicated state — the
|
|
349
|
-
* visible tree of live blocks
|
|
434
|
+
* visible tree of live blocks in their sequences' order, children of a
|
|
350
435
|
* merged-away parent under its owner, those of a deleted parent in its
|
|
351
436
|
* slot ({@link displaySlotOf}). Content is each block's display.
|
|
352
437
|
*/
|
|
@@ -402,6 +487,22 @@ export const crdtId = (doc, id) => {
|
|
|
402
487
|
};
|
|
403
488
|
/** Engine handle for a logical id, or null when absent or delete-marked (debug surface). */
|
|
404
489
|
export const resolveBlock = (doc, id) => liveNodeOf(doc, id);
|
|
490
|
+
/**
|
|
491
|
+
* The live move records of `node`'s sequence (the room's purge, tests):
|
|
492
|
+
* each with its stamp and whether some unit still shows at it — the
|
|
493
|
+
* engine's mover of a unit, or (with `v`) the record a block shows at in
|
|
494
|
+
* `v` (a cycle sent it back to an older one).
|
|
495
|
+
*/
|
|
496
|
+
export const recordsOf = (doc, node, v) => {
|
|
497
|
+
const out = [];
|
|
498
|
+
for (let it = firstItem(node); it !== null; it = it.right) {
|
|
499
|
+
if (it.deleted || !isRecord(it))
|
|
500
|
+
continue;
|
|
501
|
+
const wins = (v !== undefined && shownIn(doc, v, it).length > 0) || shownBy(doc, it).length > 0;
|
|
502
|
+
out.push({ item: it, stamp: stampOf(it), wins });
|
|
503
|
+
}
|
|
504
|
+
return out;
|
|
505
|
+
};
|
|
405
506
|
/**
|
|
406
507
|
* The placement model as one object (the public binding's `model`): the
|
|
407
508
|
* primitives above. Edytor's own modules import them directly.
|
|
@@ -414,22 +515,21 @@ export const bindModel = (_Y) => ({
|
|
|
414
515
|
blockNodeOf,
|
|
415
516
|
liveNodeOf,
|
|
416
517
|
isLive,
|
|
417
|
-
candidatesOf,
|
|
418
518
|
/** The doc's index as a `ModelView`. */
|
|
419
519
|
view,
|
|
420
|
-
resolvePlacements,
|
|
421
520
|
childrenIndex,
|
|
422
521
|
positionInView,
|
|
423
522
|
// write primitives (the document's prepared plans apply these)
|
|
424
523
|
buildInline,
|
|
425
524
|
isSelfOrDescendant,
|
|
426
|
-
|
|
427
|
-
|
|
525
|
+
placeBlocks,
|
|
526
|
+
moveBlocksTo,
|
|
527
|
+
insertSpecsAt,
|
|
428
528
|
withdrawOnUndo,
|
|
429
|
-
materializeSpec,
|
|
430
529
|
collides,
|
|
431
530
|
writeSplit,
|
|
432
531
|
ownText,
|
|
532
|
+
recordsOf,
|
|
433
533
|
// the seed writer's bulk insert
|
|
434
534
|
insertBlocks,
|
|
435
535
|
insertBlock,
|