edytor 5.0.0 → 6.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/room/context.d.ts +62 -74
- package/dist/crdt/doc/history.js +2 -3
- package/dist/crdt/doc/ops.d.ts +81 -0
- package/dist/crdt/doc/ops.js +1898 -0
- package/dist/crdt/edytor-doc.d.ts +156 -156
- package/dist/crdt/edytor-doc.js +14 -42
- package/dist/crdt/flow.d.ts +4 -46
- package/dist/crdt/flow.js +2 -262
- package/dist/crdt/index.d.ts +62 -74
- package/dist/crdt/operations.d.ts +1 -1
- package/dist/crdt/placement/resolve.d.ts +1 -1
- package/dist/crdt/protocols/awareness.js +46 -81
- package/dist/crdt/providers/index.d.ts +10 -22
- package/dist/crdt/providers/indexeddb.d.ts +6 -10
- package/dist/crdt/providers/indexeddb.js +10 -16
- package/dist/crdt/providers/room.d.ts +11 -0
- package/dist/crdt/providers/room.js +26 -12
- package/dist/crdt/providers/websocket.d.ts +6 -12
- package/dist/crdt/providers/websocket.js +8 -15
- package/dist/crdt/rangeDelete.d.ts +2 -47
- package/dist/crdt/rangeDelete.js +1 -169
- package/dist/crdt/text/index/build.d.ts +125 -0
- package/dist/crdt/text/index/build.js +2895 -0
- package/dist/crdt/text/index/shared.d.ts +2 -6
- package/dist/crdt/text/index/shared.js +2 -5
- package/dist/crdt/text/model.js +2 -3
- package/dist/crdt/text/runs.d.ts +9 -12
- package/dist/crdt/text/runs.js +2 -165
- package/dist/events/domTextOffset.d.ts +2 -0
- package/dist/events/domTextOffset.js +8 -0
- package/dist/export/forms.d.ts +50 -50
- package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
- package/dist/plugins/richtext/richTextOperations.js +26 -32
- package/dist/selection/selection.utils.js +5 -8
- package/dist/session/handles.js +3 -7
- package/dist/surface/observer.svelte.js +15 -30
- package/dist/surface/projector.svelte.js +2 -2
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +7 -0
- package/package.json +1 -1
- package/dist/crdt/doc/capability.d.ts +0 -56
- package/dist/crdt/doc/capability.js +0 -207
- package/dist/crdt/doc/content.d.ts +0 -16
- package/dist/crdt/doc/content.js +0 -77
- package/dist/crdt/doc/delete.d.ts +0 -15
- package/dist/crdt/doc/delete.js +0 -84
- package/dist/crdt/doc/layout.d.ts +0 -9
- package/dist/crdt/doc/layout.js +0 -178
- package/dist/crdt/doc/meta.d.ts +0 -19
- package/dist/crdt/doc/meta.js +0 -198
- package/dist/crdt/doc/moves.d.ts +0 -21
- package/dist/crdt/doc/moves.js +0 -274
- package/dist/crdt/doc/split-merge.d.ts +0 -10
- package/dist/crdt/doc/split-merge.js +0 -179
- package/dist/crdt/doc/steps.d.ts +0 -65
- package/dist/crdt/doc/steps.js +0 -311
- package/dist/crdt/text/index/cache.d.ts +0 -16
- package/dist/crdt/text/index/cache.js +0 -175
- package/dist/crdt/text/index/claims.d.ts +0 -18
- package/dist/crdt/text/index/claims.js +0 -203
- package/dist/crdt/text/index/fold.d.ts +0 -21
- package/dist/crdt/text/index/fold.js +0 -656
- package/dist/crdt/text/index/layout.d.ts +0 -22
- package/dist/crdt/text/index/layout.js +0 -258
- package/dist/crdt/text/index/placement.d.ts +0 -47
- package/dist/crdt/text/index/placement.js +0 -738
- package/dist/crdt/text/index/records.d.ts +0 -13
- package/dist/crdt/text/index/records.js +0 -139
- package/dist/crdt/text/index/report.d.ts +0 -18
- package/dist/crdt/text/index/report.js +0 -337
- package/dist/crdt/text/index/state.d.ts +0 -197
- package/dist/crdt/text/index/state.js +0 -116
- package/dist/crdt/text/index/streams.d.ts +0 -25
- package/dist/crdt/text/index/streams.js +0 -248
|
@@ -56,7 +56,7 @@ import { messagePermissionDenied, messageAccess, messageReadOnly, READ_ONLY_DENI
|
|
|
56
56
|
import { bindSync } from '../protocols/sync.js';
|
|
57
57
|
import { readCommentsMessage, writeCommentsSubscribe } from '../protocols/comments.js';
|
|
58
58
|
import { frame } from '../protocols/envelope.js';
|
|
59
|
-
import { assertRoomId, beginDestroy, bindRoomProtocol, tabs, chunkFrame, CLOSE, createChunkReader, emitFailed, initLifecycle, isRefusal, LOCAL_PRESENCE_LOSS, markSynced, messageAuth, messageChunk, messageComments, messageSaved, messageSync, MAX_FRAME_BYTES, SyncRefusedError } from './room.js';
|
|
59
|
+
import { assertRoomId, beginDestroy, bindRoomProtocol, tabs, chunkFrame, CLOSE, createChunkReader, emitFailed, fault, initLifecycle, isRefusal, unknownType, LOCAL_PRESENCE_LOSS, markSynced, messageAuth, messageChunk, messageComments, messageSaved, messageSync, MAX_FRAME_BYTES, SyncRefusedError } from './room.js';
|
|
60
60
|
import { asEngineDoc } from '../structs.js';
|
|
61
61
|
import { ATTRIBUTION_ROOT } from '../schema.js';
|
|
62
62
|
import { ATTRIBUTION_ORIGIN, writerOf } from '../attribution/attribution.js';
|
|
@@ -135,10 +135,7 @@ export const bindWebsocketProvider = (Y) => {
|
|
|
135
135
|
authType !== messageAccess) {
|
|
136
136
|
// An auth type we do not speak inside a valid v14 envelope is
|
|
137
137
|
// protocol skew — reported, never dropped silently.
|
|
138
|
-
provider
|
|
139
|
-
new Error(`Unknown auth message type ${authType}`),
|
|
140
|
-
provider
|
|
141
|
-
]);
|
|
138
|
+
unknownType(provider, 'auth', authType);
|
|
142
139
|
}
|
|
143
140
|
}
|
|
144
141
|
},
|
|
@@ -459,7 +456,7 @@ export const bindWebsocketProvider = (Y) => {
|
|
|
459
456
|
room.readMessage(provider, new Uint8Array(event.data), true, (buf) => sendOn(provider, websocket, buf));
|
|
460
457
|
}
|
|
461
458
|
catch (error) {
|
|
462
|
-
provider
|
|
459
|
+
fault(provider, error);
|
|
463
460
|
}
|
|
464
461
|
};
|
|
465
462
|
websocket.onerror = (event) => {
|
|
@@ -526,8 +523,8 @@ export const bindWebsocketProvider = (Y) => {
|
|
|
526
523
|
/** Closes the current dial if it has not opened in time. */
|
|
527
524
|
_connectTimer;
|
|
528
525
|
_checkInterval;
|
|
529
|
-
|
|
530
|
-
|
|
526
|
+
/** @internal Removes the document and presence listeners (`room.listen`). */
|
|
527
|
+
_unlisten;
|
|
531
528
|
constructor(serverUrl, roomname, doc, { connect = true, awareness = new Awareness(doc), params = {}, WebSocketPolyfill = WebSocket, resyncInterval = 0, maxBackoffTime = 2500, connectTimeout = defaultConnectTimeout, disableBc = false, maxFrameBytes = MAX_FRAME_BYTES } = {}) {
|
|
532
529
|
assertRoomId(roomname);
|
|
533
530
|
super();
|
|
@@ -548,14 +545,13 @@ export const bindWebsocketProvider = (Y) => {
|
|
|
548
545
|
if (resyncInterval > 0) {
|
|
549
546
|
this._resync = setInterval(() => send(this, room.step1(this)), resyncInterval);
|
|
550
547
|
}
|
|
551
|
-
this._bcSubscriber = room.bcSubscriber(this);
|
|
552
548
|
providers.add(this);
|
|
553
549
|
// Local doc updates go to the room; another tab's go to the server
|
|
554
550
|
// only (`broadcastUpdate` quarantines a read-only document). A
|
|
555
551
|
// socket the room made read-only gets this document's own edits
|
|
556
552
|
// alone (each is denied): what a tab or a store delivered, and the
|
|
557
553
|
// actor records, would draw denials nobody made.
|
|
558
|
-
|
|
554
|
+
const onUpdate = (update, origin, _doc, transaction) => {
|
|
559
555
|
if (origin === this)
|
|
560
556
|
return;
|
|
561
557
|
const heard = origin === this._fromTab;
|
|
@@ -573,9 +569,7 @@ export const bindWebsocketProvider = (Y) => {
|
|
|
573
569
|
};
|
|
574
570
|
this._writes = new OwnWrites(doc, (unsaved) => this.emit('saved', [{ saved: unsaved === 0, unsaved }, this]));
|
|
575
571
|
this._writes.trackDocument();
|
|
576
|
-
this.
|
|
577
|
-
this._awarenessUpdateHandler = room.awarenessUpdateHandler(this);
|
|
578
|
-
awareness.on('update', this._awarenessUpdateHandler);
|
|
572
|
+
this._unlisten = room.listen(this, onUpdate);
|
|
579
573
|
this._checkInterval = setInterval(() => {
|
|
580
574
|
if (!this.wsconnected)
|
|
581
575
|
return;
|
|
@@ -651,8 +645,7 @@ export const bindWebsocketProvider = (Y) => {
|
|
|
651
645
|
clearInterval(this._checkInterval);
|
|
652
646
|
providers.delete(this);
|
|
653
647
|
this.disconnect();
|
|
654
|
-
this.
|
|
655
|
-
this.doc.off('update', this._updateHandler);
|
|
648
|
+
this._unlisten();
|
|
656
649
|
super.destroy();
|
|
657
650
|
}
|
|
658
651
|
disconnect() {
|
|
@@ -1,48 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* `del.range.*` rows of `docs/editor-delete-contract.md`:
|
|
5
|
-
*
|
|
6
|
-
* - the head keeps `[0, start)`, the tail keeps `[end, len)`, and every
|
|
7
|
-
* block strictly between them in document order dies;
|
|
8
|
-
* - the head dies iff its prefix is empty (never for a replacement,
|
|
9
|
-
* `del.range.replace`, nor when nothing else would be left to hold the
|
|
10
|
-
* caret, `del.range.whole-doc`: the head is kept, emptied, with its id,
|
|
11
|
-
* type and data, so peers deleting the same range keep the same block)
|
|
12
|
-
* and the tail then keeps its id; the tail dies iff
|
|
13
|
-
* its suffix is empty; otherwise the tail's suffix merges into the head
|
|
14
|
-
* when `canMerge(tail, head)` allows it (`del.range.island-seal`) — an end
|
|
15
|
-
* at the tail's start included: the seam between them is deleted;
|
|
16
|
-
* - what follows the range end survives: the dying tail's children and the
|
|
17
|
-
* later siblings inside every container the range dies through take the
|
|
18
|
-
* topmost such container's slot, unless that would cross an island seal —
|
|
19
|
-
* then those containers stay (`del.range.outside-survives`);
|
|
20
|
-
* - a container (a list: the document's `isContainer`) never dies because
|
|
21
|
-
* the range starts before it: it keeps what follows the range end, like
|
|
22
|
-
* the keys (`del.merge.container`); it dies only when it loses every
|
|
23
|
-
* child and holds no text of its own — the document's `emptiable`, the
|
|
24
|
-
* keys' rule (`del.range.empty-container`). An island that renders
|
|
25
|
-
* no content dies when it loses every child too, except a `lines` island
|
|
26
|
-
* (a code block) the range lies in: its head is then kept, emptied
|
|
27
|
-
* (`del.range.island-kept`);
|
|
28
|
-
* - what a range rescues takes the kind it shows in its new slot (the
|
|
29
|
-
* document's `settle`: a paragraph rescued into a list is its item);
|
|
30
|
-
* - blocks the view hides (a closed toggle's body) are not in the range:
|
|
31
|
-
* they go only with a block that goes, so a surviving head keeps its
|
|
32
|
-
* hidden children, unless it is kept only to hold the caret; a dying
|
|
33
|
-
* closed tail's body takes its place, shown (`del.range.hidden-body`);
|
|
34
|
-
* - a layout the range leaves with one column dissolves, in the same plan
|
|
35
|
-
* (`layout.dissolving`);
|
|
36
|
-
* - a range with an end in a table's cell keeps every cell and row: each
|
|
37
|
-
* cell it crosses loses the text it covers, nothing merges across a
|
|
38
|
-
* cell's edge, the blocks outside the table between the ends go, a
|
|
39
|
-
* table wholly between them goes whole, and the caret lands at the start
|
|
40
|
-
* (`table.range`);
|
|
41
|
-
* - the plan's `at` is where the caret lands (`del.range.caret`).
|
|
2
|
+
* The types range deletion (`deleteRange`, `replaceRange`: the `del.range.*`
|
|
3
|
+
* rows, prepared in `doc/ops.ts`) reads.
|
|
42
4
|
*/
|
|
43
5
|
import type { BlockId } from './placement/model.js';
|
|
44
|
-
import type { Prepared } from './edytor-doc.js';
|
|
45
|
-
import type { OpsContext } from './doc/steps.js';
|
|
46
6
|
/** A position in one document version: a block and a display offset. */
|
|
47
7
|
export type DocPosition = {
|
|
48
8
|
block: BlockId;
|
|
@@ -56,8 +16,3 @@ export type DocPosition = {
|
|
|
56
16
|
export type RangeView = {
|
|
57
17
|
hidden?: (id: BlockId, removed?: ReadonlySet<BlockId>) => boolean;
|
|
58
18
|
};
|
|
59
|
-
/** `deleteRange` and `replaceRange` (the head kept), prepared. */
|
|
60
|
-
export declare const rangeDeleteOps: (c: OpsContext) => {
|
|
61
|
-
deleteRange: (from: DocPosition, to: DocPosition, view?: RangeView) => Prepared;
|
|
62
|
-
replaceRange: (from: DocPosition, to: DocPosition, view?: RangeView) => Prepared;
|
|
63
|
-
};
|
package/dist/crdt/rangeDelete.js
CHANGED
|
@@ -1,169 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/** `deleteRange` and `replaceRange` (the head kept), prepared. */
|
|
3
|
-
export const rangeDeleteOps = (c) => {
|
|
4
|
-
// `headSpent`: a whole-doc retry keeps the head, but what it hid goes as if it went.
|
|
5
|
-
const prepare = (keepHead, headSpent = false) => (from, to, view = {}) => {
|
|
6
|
-
// A position names shown text: a live block that renders its own content.
|
|
7
|
-
const holds = (id) => c.contentTarget(id) && c.rendersContent(id);
|
|
8
|
-
const pos = (p) => {
|
|
9
|
-
const block = ref(p.block);
|
|
10
|
-
const len = holds(block) ? c.displayLength(block) : -1;
|
|
11
|
-
return { block, offset: Math.max(0, Math.min(p.offset, len)) };
|
|
12
|
-
};
|
|
13
|
-
let [s, e] = [pos(from), pos(to)];
|
|
14
|
-
if (!holds(s.block) || !holds(e.block))
|
|
15
|
-
return c.REFUSED;
|
|
16
|
-
const { ids, at } = c.view().order;
|
|
17
|
-
if (at.get(s.block) > at.get(e.block) || (s.block === e.block && s.offset > e.offset))
|
|
18
|
-
[s, e] = [e, s];
|
|
19
|
-
const S = s.block;
|
|
20
|
-
const E = e.block;
|
|
21
|
-
if (S === E) {
|
|
22
|
-
const length = e.offset - s.offset;
|
|
23
|
-
const writes = length > 0 ? [{ op: 'deleteText', id: S, offset: s.offset, length }] : [];
|
|
24
|
-
return { ...c.plan([S], writes), at: s };
|
|
25
|
-
}
|
|
26
|
-
const parent = (id) => c.ancestorsOf(id)[0] ?? null;
|
|
27
|
-
const chain = c.ancestorsOf(E);
|
|
28
|
-
if (c.isTableCell(S) || c.isTableCell(E))
|
|
29
|
-
return inTables(s, e, view);
|
|
30
|
-
// What the view hides once `removed` go (nothing: as it is now).
|
|
31
|
-
const hiddenOnce = (removed) => {
|
|
32
|
-
const set = new Set(removed);
|
|
33
|
-
return (id) => !!view.hidden?.(id, set);
|
|
34
|
-
};
|
|
35
|
-
const headDies = !keepHead && s.offset === 0;
|
|
36
|
-
// What the view hides is not in the range: it goes only with a block that goes,
|
|
37
|
-
// a dying head's body included.
|
|
38
|
-
const hidden = hiddenOnce(headDies || headSpent ? [S] : []);
|
|
39
|
-
const between = ids.slice(at.get(S) + 1, at.get(E)).filter((id) => !hidden(id));
|
|
40
|
-
const lenE = c.displayLength(E);
|
|
41
|
-
const tailDies = e.offset === lenE;
|
|
42
|
-
const merges = !headDies && !tailDies && c.canMerge(E, S);
|
|
43
|
-
const tailGone = tailDies || merges;
|
|
44
|
-
// E's ancestors the range starts before die unless the rescue would cross an island.
|
|
45
|
-
// A container (a list) is not one: it dies only when the range empties it, and
|
|
46
|
-
// keeps its later items like the keys do (`del.merge.container`).
|
|
47
|
-
const partial = chain.filter((a) => (a === (headDies ? S : null) || between.includes(a)) && !c.isContainer(a));
|
|
48
|
-
const top = partial.at(-1);
|
|
49
|
-
const sealed = top !== undefined && chain.slice(0, chain.indexOf(top) + 1).some(c.isIsland);
|
|
50
|
-
const spared = new Set(sealed ? chain : []);
|
|
51
|
-
const doomed = new Set(between.filter((id) => !spared.has(id) && !chain.includes(id)));
|
|
52
|
-
if (!sealed)
|
|
53
|
-
partial.forEach((a) => doomed.add(a));
|
|
54
|
-
if (headDies && !spared.has(S))
|
|
55
|
-
doomed.add(S);
|
|
56
|
-
if (tailDies)
|
|
57
|
-
doomed.add(E);
|
|
58
|
-
// Rescue what follows the range end right after the topmost dying container's slot
|
|
59
|
-
// (after, not at it: a concurrent delete of S revives a merged E above them).
|
|
60
|
-
const home = sealed || top === undefined ? (tailGone ? E : undefined) : top;
|
|
61
|
-
const rescued = home === undefined ? [] : tailGone ? c.childrenIds(E) : [E];
|
|
62
|
-
for (let cur = E; home !== undefined && cur !== home; cur = parent(cur)) {
|
|
63
|
-
const sibs = c.childrenIds(parent(cur));
|
|
64
|
-
rescued.push(...sibs.slice(sibs.indexOf(cur) + 1));
|
|
65
|
-
}
|
|
66
|
-
const dest = home === undefined || rescued.length === 0 ? null : c.positionOf(home);
|
|
67
|
-
// A container (or an island that renders nothing) losing every child dies too.
|
|
68
|
-
const gone = (id) => doomed.has(id) || (merges && id === E);
|
|
69
|
-
const dies = (id) => c.emptiable(id) || (c.isIsland(id) && !c.rendersContent(id));
|
|
70
|
-
const emptied = (id) => {
|
|
71
|
-
if (id === null || gone(id) || !dies(id) || id === dest?.parent)
|
|
72
|
-
return;
|
|
73
|
-
if (!c.childrenIds(id).every(gone))
|
|
74
|
-
return;
|
|
75
|
-
doomed.add(id);
|
|
76
|
-
emptied(parent(id));
|
|
77
|
-
};
|
|
78
|
-
[...doomed, ...(merges ? [E] : [])].forEach((id) => emptied(parent(id)));
|
|
79
|
-
// A range inside one `lines` island never removes it: the head stays, emptied
|
|
80
|
-
// (a code block's Mod+A, `del.range.island-kept`).
|
|
81
|
-
const island = c.ancestorsOf(S).find(c.isIsland);
|
|
82
|
-
const linesIsland = island !== undefined && c.isLines(island);
|
|
83
|
-
if (!keepHead && linesIsland && doomed.has(island) && chain.includes(island))
|
|
84
|
-
return prepare(true, headDies)(from, to, view);
|
|
85
|
-
const lenS = c.displayLength(S);
|
|
86
|
-
const writes = [];
|
|
87
|
-
if (!doomed.has(S) && lenS > s.offset)
|
|
88
|
-
writes.push({ op: 'deleteText', id: S, offset: s.offset, length: lenS - s.offset });
|
|
89
|
-
if (!gone(E) && e.offset > 0)
|
|
90
|
-
writes.push({ op: 'deleteText', id: E, offset: 0, length: e.offset });
|
|
91
|
-
if (dest !== null) {
|
|
92
|
-
writes.push(...c.move(rescued, dest.parent, dest.index + 1));
|
|
93
|
-
// Each shows the kind of its new slot (an island's line, a list's item leave theirs).
|
|
94
|
-
for (const id of rescued)
|
|
95
|
-
writes.push(...c.settle(parent(id), [id], dest.parent));
|
|
96
|
-
}
|
|
97
|
-
if (merges) {
|
|
98
|
-
// The whole tail merges, then its cut prefix goes: E's own text is never written.
|
|
99
|
-
writes.push({ op: 'mergeBlocks', from: E, into: S, at: s.offset, length: lenE });
|
|
100
|
-
if (e.offset > 0)
|
|
101
|
-
writes.push({ op: 'deleteText', id: S, offset: s.offset, length: e.offset });
|
|
102
|
-
}
|
|
103
|
-
const kept = [...rescued, ...(merges ? [E] : [])];
|
|
104
|
-
for (const id of doomed)
|
|
105
|
-
if (!doomed.has(parent(id)))
|
|
106
|
-
writes.push(c.remove(id, kept));
|
|
107
|
-
// A layout the range leaves with one column dissolves (`layout.dissolving`).
|
|
108
|
-
writes.push(...c.dissolving(new Set([...doomed, ...(merges ? [E] : [])]), rescued, writes));
|
|
109
|
-
const caret = (() => {
|
|
110
|
-
if (!doomed.has(S))
|
|
111
|
-
return s;
|
|
112
|
-
if (!gone(E))
|
|
113
|
-
return { block: E, offset: 0 };
|
|
114
|
-
const survives = (id) => id === null || rescued.includes(id) || (!gone(id) && survives(parent(id)));
|
|
115
|
-
// Shown once the plan runs: a dying closed tail's rescued body is.
|
|
116
|
-
const hiddenAfter = hiddenOnce([...doomed, ...(merges ? [E] : [])]);
|
|
117
|
-
const shown = (id) => survives(id) && holds(id) && !hiddenAfter(id);
|
|
118
|
-
// Before the tail: blocks a sealed rescue spared between S and E count too.
|
|
119
|
-
const prev = ids.slice(0, at.get(E)).findLast(shown);
|
|
120
|
-
if (prev !== undefined)
|
|
121
|
-
return { block: prev, offset: c.displayLength(prev) };
|
|
122
|
-
const next = ids.slice(at.get(E) + 1).find(shown);
|
|
123
|
-
return next === undefined ? null : { block: next, offset: 0 };
|
|
124
|
-
})();
|
|
125
|
-
// Nothing else left to hold the caret: the head stays, emptied (whole-doc).
|
|
126
|
-
if (caret === null)
|
|
127
|
-
return prepare(true, headDies)(from, to, view);
|
|
128
|
-
return { ...c.plan([caret.block], writes), at: caret };
|
|
129
|
-
};
|
|
130
|
-
/**
|
|
131
|
-
* `table.range`: a range with an end in a cell. Every cell it crosses
|
|
132
|
-
* loses the text it covers and stays, with its row; the ends keep what
|
|
133
|
-
* lies outside the range, merging nothing; any other block between the
|
|
134
|
-
* ends goes (a table wholly between them whole), but the ancestors of
|
|
135
|
-
* the end, which stay and lose their text (the range covers it: they
|
|
136
|
-
* come after the start in reading order); the caret lands at the start.
|
|
137
|
-
*/
|
|
138
|
-
const inTables = (s, e, view) => {
|
|
139
|
-
const { ids, at } = c.view().order;
|
|
140
|
-
const [S, E] = [s.block, e.block];
|
|
141
|
-
const kept = new Set(c.ancestorsOf(E));
|
|
142
|
-
const between = ids.slice(at.get(S) + 1, at.get(E)).filter((id) => !view.hidden?.(id));
|
|
143
|
-
const writes = [];
|
|
144
|
-
const lenS = c.displayLength(S);
|
|
145
|
-
if (lenS > s.offset)
|
|
146
|
-
writes.push({ op: 'deleteText', id: S, offset: s.offset, length: lenS - s.offset });
|
|
147
|
-
const [tableS, tableE] = [c.tableOf(S), c.tableOf(E)];
|
|
148
|
-
const doomed = new Set();
|
|
149
|
-
for (const id of between) {
|
|
150
|
-
const table = c.tableOf(id);
|
|
151
|
-
if (kept.has(id) || (table !== null && (table === tableS || table === tableE))) {
|
|
152
|
-
const len = c.isTableCell(id) || kept.has(id) ? c.displayLength(id) : 0;
|
|
153
|
-
if (len > 0)
|
|
154
|
-
writes.push({ op: 'deleteText', id, offset: 0, length: len });
|
|
155
|
-
}
|
|
156
|
-
else
|
|
157
|
-
doomed.add(id);
|
|
158
|
-
}
|
|
159
|
-
if (e.offset > 0)
|
|
160
|
-
writes.push({ op: 'deleteText', id: E, offset: 0, length: e.offset });
|
|
161
|
-
const parentOf = (id) => c.ancestorsOf(id)[0] ?? null;
|
|
162
|
-
const roots = [...doomed].filter((id) => !doomed.has(parentOf(id)));
|
|
163
|
-
for (const id of roots)
|
|
164
|
-
writes.push(c.remove(id, []));
|
|
165
|
-
writes.push(...c.dissolving(new Set(roots), [], writes));
|
|
166
|
-
return { ...c.plan([S], writes), at: s };
|
|
167
|
-
};
|
|
168
|
-
return { deleteRange: prepare(false), replaceRange: prepare(true) };
|
|
169
|
-
};
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-document index (`indexOf`) as ONE closure: its state, then its
|
|
3
|
+
* parts — the claim graph, the stream table and each block's claims, the
|
|
4
|
+
* layout and table rules, placements and the children index (with THE
|
|
5
|
+
* shown-kind rule, `typeOf`), block records, the run cache, the fold and the
|
|
6
|
+
* change report — each reading the state and the parts before it as closure
|
|
7
|
+
* variables, then the view (`RunView`) over them. `../runs.ts` declares the
|
|
8
|
+
* index's types and contracts. The self-checks (`indexChecks`) read the
|
|
9
|
+
* maintained facts through {@link IndexFacts} and rebuild their own.
|
|
10
|
+
*/
|
|
11
|
+
import type { EngineDoc, EngineNode, EngineTransaction } from '../../engine-api.js';
|
|
12
|
+
import { type BlockId, type BlockRec, type ChildSlot, type DisplayOwnership, type ResolvedPlacement } from '../../placement/model.js';
|
|
13
|
+
import { type IdSetLike } from '../../structs.js';
|
|
14
|
+
import { type LiveRow, type RowItem } from '../rows.js';
|
|
15
|
+
import type { ContentRun, RunView } from '../runs.js';
|
|
16
|
+
import { type Cached } from './shared.js';
|
|
17
|
+
import { type Claim, type Owner, type Stream } from '../model.js';
|
|
18
|
+
import { type Edge, type SlotRef } from '../../placement/resolve.js';
|
|
19
|
+
export type TextEdit = {
|
|
20
|
+
item: RowItem;
|
|
21
|
+
len: number;
|
|
22
|
+
del: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type TextEdits = {
|
|
25
|
+
edits: TextEdit[];
|
|
26
|
+
scan: boolean;
|
|
27
|
+
opaque: boolean;
|
|
28
|
+
};
|
|
29
|
+
/** One fold's effects: invalidated blocks, rescanned texts, rebuilt facets. */
|
|
30
|
+
export type FoldCtx = {
|
|
31
|
+
invalidated: Set<BlockId>;
|
|
32
|
+
/** Per home block: its text's edits, whether it rescans, whether every reader re-reads. */
|
|
33
|
+
texts: Map<BlockId, {
|
|
34
|
+
edits: TextEdit[];
|
|
35
|
+
scan: boolean;
|
|
36
|
+
opaque: boolean;
|
|
37
|
+
seen: Set<TextEdits>;
|
|
38
|
+
}>;
|
|
39
|
+
/** The blocks whose entry or nonce changed: their delimiters are re-decided. */
|
|
40
|
+
named: Set<BlockId>;
|
|
41
|
+
table: boolean;
|
|
42
|
+
/** The winning parents, before and after, of the blocks the fold touched. */
|
|
43
|
+
parents: Set<BlockId>;
|
|
44
|
+
};
|
|
45
|
+
/** A slot in a children index: its display parent, rank and the island it displays out of. */
|
|
46
|
+
export type Slot = {
|
|
47
|
+
parent: BlockId | null;
|
|
48
|
+
rank: string;
|
|
49
|
+
reset?: BlockId;
|
|
50
|
+
};
|
|
51
|
+
/** Each row's segments at its last placement: segment 0's block, then each cut's key and block. */
|
|
52
|
+
export type Placed = {
|
|
53
|
+
head: BlockId | null;
|
|
54
|
+
keys: string[];
|
|
55
|
+
blocks: (BlockId | null)[];
|
|
56
|
+
};
|
|
57
|
+
/** The tree the change report last published. */
|
|
58
|
+
export type Published = {
|
|
59
|
+
nodes: Map<BlockId, {
|
|
60
|
+
parent: BlockId | null;
|
|
61
|
+
index: number;
|
|
62
|
+
type: string;
|
|
63
|
+
data: unknown;
|
|
64
|
+
runs: readonly ContentRun[];
|
|
65
|
+
key?: string;
|
|
66
|
+
}>;
|
|
67
|
+
order: Map<BlockId | null, readonly BlockId[]>;
|
|
68
|
+
/** The children index version it was taken at. */
|
|
69
|
+
kids: number;
|
|
70
|
+
};
|
|
71
|
+
/** A transaction as the fold reads it. */
|
|
72
|
+
export type Tx = {
|
|
73
|
+
insertSet?: IdSetLike;
|
|
74
|
+
deleteSet?: IdSetLike;
|
|
75
|
+
changed?: EngineTransaction['changed'];
|
|
76
|
+
/** The visibility changes hold records made, in order (fork patch YP20). */
|
|
77
|
+
holds?: number[];
|
|
78
|
+
/** The units whose mover changed, in order (fork patch YP22). */
|
|
79
|
+
moves?: number[];
|
|
80
|
+
};
|
|
81
|
+
/** The index's self-checks (`indexChecks`), run after each fold and each report. */
|
|
82
|
+
export type SelfChecks = {
|
|
83
|
+
check: () => void;
|
|
84
|
+
checkPublished: (pub: Published) => void;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* What the self-checks read of one index: the facts it maintains
|
|
88
|
+
* incrementally (compared with their rebuild from the replicated state) and
|
|
89
|
+
* the reads they compare through.
|
|
90
|
+
*/
|
|
91
|
+
export type IndexFacts = {
|
|
92
|
+
doc: EngineDoc;
|
|
93
|
+
registry: EngineNode;
|
|
94
|
+
blocks: ReadonlyMap<BlockId, BlockRec>;
|
|
95
|
+
displaysMap: ReadonlyMap<BlockId, ReadonlySet<BlockId>>;
|
|
96
|
+
rows: ReadonlyMap<BlockId, LiveRow>;
|
|
97
|
+
streamIx: ReadonlyMap<BlockId, unknown>;
|
|
98
|
+
delim: ReadonlyMap<BlockId, string>;
|
|
99
|
+
placementsMap: ReadonlyMap<BlockId, ResolvedPlacement>;
|
|
100
|
+
slotsByBlock: ReadonlyMap<BlockId, SlotRef[]>;
|
|
101
|
+
edges: ReadonlyMap<BlockId, Edge | null>;
|
|
102
|
+
rejected: ReadonlySet<BlockId>;
|
|
103
|
+
kids0: Map<BlockId | null, ChildSlot[]>;
|
|
104
|
+
kidsMap: Map<BlockId | null, ChildSlot[]>;
|
|
105
|
+
slots: ReadonlyMap<BlockId, Slot>;
|
|
106
|
+
dissolved: ReadonlySet<BlockId>;
|
|
107
|
+
following: ReadonlySet<BlockId>;
|
|
108
|
+
cache: ReadonlyMap<BlockId, Cached>;
|
|
109
|
+
dirty: ReadonlySet<BlockId>;
|
|
110
|
+
ownShim: DisplayOwnership;
|
|
111
|
+
own0: DisplayOwnership;
|
|
112
|
+
ownerOf: (b: BlockId) => Owner;
|
|
113
|
+
streamOf: (b: BlockId) => Stream | undefined;
|
|
114
|
+
sameClaims: (x: readonly Claim[], y: readonly Claim[]) => boolean;
|
|
115
|
+
dissolve: (kids: Map<BlockId | null, ChildSlot[]>) => Set<BlockId>;
|
|
116
|
+
noteFollowing: (id: BlockId) => void;
|
|
117
|
+
ensurePlacements: () => void;
|
|
118
|
+
computeFresh: (b: BlockId) => {
|
|
119
|
+
fresh: readonly ContentRun[];
|
|
120
|
+
};
|
|
121
|
+
unfolded: () => boolean;
|
|
122
|
+
reachable: (prev?: Published['nodes']) => Published;
|
|
123
|
+
};
|
|
124
|
+
/** The doc's index — built on first use, torn down when the doc is destroyed. */
|
|
125
|
+
export declare const indexOf: (doc: EngineDoc) => RunView;
|