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
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Split and merge ops: `splitBlock` (one boundary item, no text copied),
|
|
3
|
-
* the engine merge primitive `mergeBlocks`, and the baseline-shaped
|
|
4
|
-
* `mergeBackward`/`mergeForward` (a first item lifts out of its container,
|
|
5
|
-
* a layout item's first block merges across items).
|
|
6
|
-
*/
|
|
7
|
-
import { isIncarnationId } from '../incarnations.js';
|
|
8
|
-
import { sanitizeWireJson } from '../../utils/json.js';
|
|
9
|
-
import { readData } from '../data.js';
|
|
10
|
-
import { blockNodeOf, isSelfOrDescendant } from '../placement/model.js';
|
|
11
|
-
import { ref } from './plan.js';
|
|
12
|
-
/** The split-merge ops of one facade, prepared. */
|
|
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, moveTo, move, leaving, settledKind, settle, landing, emptying, emptied, merge, clamp } = c;
|
|
15
|
-
/**
|
|
16
|
-
* Split `id` at content `offset` into a new sibling `newId` (one boundary
|
|
17
|
-
* item and the claims that follow it, no text copied; children follow).
|
|
18
|
-
* `tail` decides the sibling's type/data once (default: the source's —
|
|
19
|
-
* the kind it displays; an empty `tail.type` is the default too).
|
|
20
|
-
* Refused on `void` blocks and on blocks that render no content (a
|
|
21
|
-
* list, a code block: nothing to split). `ids`: the new
|
|
22
|
-
* block.
|
|
23
|
-
*/
|
|
24
|
-
const splitBlock = (id, offset, newId, tail) => {
|
|
25
|
-
id = ref(id);
|
|
26
|
-
const born = ref(newId);
|
|
27
|
-
const pos = positionOf(id);
|
|
28
|
-
const rec = view().blocks.get(id);
|
|
29
|
-
if (pos === null || isVoid(id) || !rendersContent(id) || rec === undefined)
|
|
30
|
-
return REFUSED;
|
|
31
|
-
// A table's cell holds its lines as line breaks: it never splits (`table.split`).
|
|
32
|
-
if (isCellKind(blockTypeOf(id)))
|
|
33
|
-
return REFUSED;
|
|
34
|
-
if (isIncarnationId(born) || blockNodeOf(doc, born) !== null)
|
|
35
|
-
return REFUSED;
|
|
36
|
-
const [at] = clamp(id, offset, 0);
|
|
37
|
-
// An empty tail type is the one a view reads mid-retype: copy the kind.
|
|
38
|
-
const type = tail?.type ? ref(tail.type) : kindToCopy(id);
|
|
39
|
-
const t = tail
|
|
40
|
-
? { type, data: tail.data && sanitizeWireJson(tail.data) }
|
|
41
|
-
: { type, data: readData(rec.node) };
|
|
42
|
-
const length = displayLength(id) - at;
|
|
43
|
-
const split = {
|
|
44
|
-
op: 'splitBlock',
|
|
45
|
-
id,
|
|
46
|
-
offset: at,
|
|
47
|
-
length,
|
|
48
|
-
newId: born,
|
|
49
|
-
tail: t,
|
|
50
|
-
parent: pos.parent,
|
|
51
|
-
index: pos.index + 1
|
|
52
|
-
};
|
|
53
|
-
return plan([born], [split, ...move(childrenIds(id), born, 0)]);
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* Engine merge primitive: `from`'s content is claimed by `into`, its
|
|
57
|
-
* children ADOPTED as `into`'s last children — an island's take
|
|
58
|
-
* `into`'s default child, like a baseline merge's — and `from` is
|
|
59
|
-
* hidden via the claim (undo restores it). The children stay under
|
|
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`.
|
|
65
|
-
*/
|
|
66
|
-
const mergeBlocks = (fromId, intoId) => {
|
|
67
|
-
const from = ref(fromId);
|
|
68
|
-
const into = ref(intoId);
|
|
69
|
-
const v = view();
|
|
70
|
-
if (!canMerge(from, into) || !contentTarget(into))
|
|
71
|
-
return REFUSED;
|
|
72
|
-
if (isSelfOrDescendant(v.placements, v.own, into, from))
|
|
73
|
-
return REFUSED;
|
|
74
|
-
const kids = childrenIds(from);
|
|
75
|
-
const retype = settle(from, kids, into);
|
|
76
|
-
// A list its only item leaves goes, as with every sibling op.
|
|
77
|
-
return plan([into], emptied([from], [merge(from, into), ...retype], landing(into)));
|
|
78
|
-
};
|
|
79
|
-
/**
|
|
80
|
-
* Baseline-shaped merge (both `mergeBackward` and `mergeForward`):
|
|
81
|
-
* `from`'s children unnest right after `from`'s vacated sibling slot —
|
|
82
|
-
* NOT adopted into `into` — and take the kind they show there
|
|
83
|
-
* (`settledKind`: an island's children its parent's default child, a
|
|
84
|
-
* paragraph in a list its item); then `from`'s content claims
|
|
85
|
-
* into `into`. Placed after `from`, not at it: a concurrent delete of
|
|
86
|
-
* `into` revives `from` above its former children.
|
|
87
|
-
*/
|
|
88
|
-
const mergeUnnesting = (from, into) => {
|
|
89
|
-
const pos = canMerge(from, into) ? positionOf(from) : null;
|
|
90
|
-
if (pos === null || !contentTarget(into))
|
|
91
|
-
return REFUSED;
|
|
92
|
-
const kids = childrenIds(from);
|
|
93
|
-
const retype = settle(from, kids, pos.parent);
|
|
94
|
-
return plan([into], [...moveTo(kids, pos.parent, pos.index + 1, leaving(true)), ...retype, merge(from, into)]);
|
|
95
|
-
};
|
|
96
|
-
/** Remove `container`, left empty, for the key at `id` (refused if it holds text). */
|
|
97
|
-
const gone = (id, container) => {
|
|
98
|
-
const writes = emptying(container, [], []);
|
|
99
|
-
return writes.length > 0 ? plan([id], writes) : REFUSED;
|
|
100
|
-
};
|
|
101
|
-
/**
|
|
102
|
-
* Baseline `mergeBlockBackward`: merge `id` into the previous block in
|
|
103
|
-
* document order. No previous block → an empty block merges forward,
|
|
104
|
-
* else refused. The first item of a container (`isContainer`) outside
|
|
105
|
-
* any island lifts out of it instead (Notion): it takes the
|
|
106
|
-
* container's slot, as its new parent's default child, with its
|
|
107
|
-
* children (a first cell stays: nothing leaves an island; a block that
|
|
108
|
-
* would land directly in a container it is no item of stays too).
|
|
109
|
-
* The first block of a layout item merges across items
|
|
110
|
-
* instead, in reading order (`layout.merge`, Notion): into the
|
|
111
|
-
* previous item's last line, or, in the first item, the line before
|
|
112
|
-
* the layout; an item it empties goes and the layout dissolves.
|
|
113
|
-
* `ids`: the surviving block.
|
|
114
|
-
*/
|
|
115
|
-
const mergeBackward = (id) => {
|
|
116
|
-
id = ref(id);
|
|
117
|
-
const prev = previous(id);
|
|
118
|
-
if (prev === null && childrenIds(id).length === 0 && displayLength(id) === 0) {
|
|
119
|
-
return mergeForward(id);
|
|
120
|
-
}
|
|
121
|
-
// A container a concurrent edit left empty shows nothing: the key removes it.
|
|
122
|
-
if (prev !== null && isContainer(prev) && childrenIds(prev).length === 0)
|
|
123
|
-
return gone(id, prev);
|
|
124
|
-
if (prev !== null && prev === positionOf(id)?.parent && isLayoutItem(prev)) {
|
|
125
|
-
const layout = positionOf(prev).parent;
|
|
126
|
-
let into = previous(prev);
|
|
127
|
-
if (into === layout)
|
|
128
|
-
into = previous(layout);
|
|
129
|
-
const merged = into === null ? REFUSED : mergeUnnesting(id, into);
|
|
130
|
-
if (!('writes' in merged) || childrenIds(id).length > 0)
|
|
131
|
-
return merged;
|
|
132
|
-
return plan(merged.ids, emptying(prev, [id], merged.writes));
|
|
133
|
-
}
|
|
134
|
-
if (prev !== null &&
|
|
135
|
-
prev === positionOf(id)?.parent &&
|
|
136
|
-
isContainer(prev) &&
|
|
137
|
-
!insideIsland(prev)) {
|
|
138
|
-
// It lands only where it fits: never directly in a container it is no item of.
|
|
139
|
-
const slot = positionOf(prev);
|
|
140
|
-
if (!fits(slot.parent, settledKind(prev, id, slot.parent)))
|
|
141
|
-
return REFUSED;
|
|
142
|
-
const lift = [
|
|
143
|
-
...moveTo([id], slot.parent, slot.index, leaving(false)),
|
|
144
|
-
...settle(prev, [id], slot.parent)
|
|
145
|
-
];
|
|
146
|
-
return plan([id], emptying(prev, [id], lift, landing(slot.parent)));
|
|
147
|
-
}
|
|
148
|
-
return prev ? mergeUnnesting(id, prev) : REFUSED;
|
|
149
|
-
};
|
|
150
|
-
/**
|
|
151
|
-
* Baseline `mergeBlockForward`: pull the next block in document order
|
|
152
|
-
* into `id`. A container passes the merge to its first item (`del.merge.container`:
|
|
153
|
-
* Delete above a list pulls the item's text up and the list keeps the
|
|
154
|
-
* rest, or goes when that was its only item). A container left
|
|
155
|
-
* empty (by concurrent edits) is removed instead, by either key.
|
|
156
|
-
*/
|
|
157
|
-
const mergeForward = (id) => {
|
|
158
|
-
id = ref(id);
|
|
159
|
-
let after = next(id);
|
|
160
|
-
while (after !== null && isContainer(after)) {
|
|
161
|
-
const first = childrenIds(after)[0];
|
|
162
|
-
// A container a concurrent edit left empty shows nothing: the key removes it.
|
|
163
|
-
if (first === undefined)
|
|
164
|
-
return gone(id, after);
|
|
165
|
-
after = first;
|
|
166
|
-
}
|
|
167
|
-
const out = after ? mergeUnnesting(after, id) : REFUSED;
|
|
168
|
-
const parent = after && positionOf(after)?.parent;
|
|
169
|
-
if (!('writes' in out) || !parent || childrenIds(after).length > 0)
|
|
170
|
-
return out;
|
|
171
|
-
return plan(out.ids, emptying(parent, [after], out.writes));
|
|
172
|
-
};
|
|
173
|
-
return {
|
|
174
|
-
splitBlock,
|
|
175
|
-
mergeBlocks,
|
|
176
|
-
mergeBackward,
|
|
177
|
-
mergeForward
|
|
178
|
-
};
|
|
179
|
-
};
|
package/dist/crdt/doc/steps.d.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
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
|
-
*/
|
|
8
|
-
import { TYPE } from '../schema.js';
|
|
9
|
-
import { type BlockId, type ContentItem } from '../placement/model.js';
|
|
10
|
-
import type { Plan, PlanStep } from './types.js';
|
|
11
|
-
import type { DocBase, DocReads } from './reads.js';
|
|
12
|
-
import type { DocCapability } from './capability.js';
|
|
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
|
-
};
|
|
30
|
-
export type StepsContext = DocBase & DocReads & DocCapability;
|
|
31
|
-
/** The step writers of one facade. */
|
|
32
|
-
export declare const planSteps: (c: StepsContext) => {
|
|
33
|
-
REFUSED: import("./types.js").OpResult;
|
|
34
|
-
plan: (ids: readonly BlockId[], writes: PlanStep[]) => Plan;
|
|
35
|
-
moveTo: (ids: BlockId[], parent: BlockId | null, index: number, side?: 0 | 1, at?: BlockId, part?: number) => PlanStep[];
|
|
36
|
-
move: (ids: BlockId[], parent: BlockId | null, index: number) => PlanStep[];
|
|
37
|
-
leaving: (after: boolean) => 0 | 1;
|
|
38
|
-
attr: (id: BlockId, key: typeof TYPE, value: string) => PlanStep[];
|
|
39
|
-
leavingIsland: (island: BlockId, kind: string | undefined, to: string) => string | undefined;
|
|
40
|
-
settledKind: (from: BlockId | null, kid: BlockId, parent: BlockId | null) => string | undefined;
|
|
41
|
-
settle: (from: BlockId | null, kids: readonly BlockId[], parent: BlockId | null) => PlanStep[];
|
|
42
|
-
landing: (parent: BlockId | null) => Set<BlockId | null>;
|
|
43
|
-
emptyingAll: (from: readonly (BlockId | null)[], leaving: readonly BlockId[], writes: readonly PlanStep[], kept?: ReadonlySet<BlockId | null>, removed?: readonly BlockId[]) => PlanStep[];
|
|
44
|
-
dissolving: (gone: ReadonlySet<BlockId>, leaving: readonly BlockId[], writes: readonly PlanStep[]) => PlanStep[];
|
|
45
|
-
emptying: (container: BlockId, leaving: readonly BlockId[], writes: readonly PlanStep[], kept?: ReadonlySet<BlockId | null>) => PlanStep[];
|
|
46
|
-
emptied: (leaving: readonly BlockId[], writes: PlanStep[], kept?: ReadonlySet<BlockId | null>) => PlanStep[];
|
|
47
|
-
deleting: (id: BlockId, removes: BlockId[]) => PlanStep;
|
|
48
|
-
remove: (id: BlockId, kept?: readonly BlockId[]) => PlanStep;
|
|
49
|
-
merge: (from: BlockId, into: BlockId) => PlanStep;
|
|
50
|
-
clamp: (id: BlockId, offset: number, length: number) => [number, number];
|
|
51
|
-
atomOf: (id: BlockId, atom: string) => {
|
|
52
|
-
item: ContentItem;
|
|
53
|
-
at: number;
|
|
54
|
-
end: number;
|
|
55
|
-
} | undefined;
|
|
56
|
-
textIn: (id: BlockId, at: number, end: number) => {
|
|
57
|
-
kind: "text";
|
|
58
|
-
text: string;
|
|
59
|
-
marks?: Record<string, unknown>;
|
|
60
|
-
}[];
|
|
61
|
-
writeContent: (writes: PlanStep[], id: BlockId, at: number, items?: readonly ContentItem[]) => number;
|
|
62
|
-
};
|
|
63
|
-
export type PlanSteps = ReturnType<typeof planSteps>;
|
|
64
|
-
/** What every op family is prepared over: the reads, the capability and the step writers. */
|
|
65
|
-
export type OpsContext = StepsContext & PlanSteps;
|
package/dist/crdt/doc/steps.js
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
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
|
-
*/
|
|
8
|
-
import { TYPE } from '../schema.js';
|
|
9
|
-
import { blockNodeOf } from '../placement/model.js';
|
|
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 };
|
|
22
|
-
/** The step writers of one facade. */
|
|
23
|
-
export const planSteps = (c) => {
|
|
24
|
-
const { doc, runsView, roles, view, blockTypeOf, childrenIds, positionOf, ancestorsOf, isLines, isIsland, itemKindOf, isLayout, displayLength, contentItems, live, isContainer, fitted, emptiable, defaultChild } = c;
|
|
25
|
-
// ── prepared ops ─────────────────────────────────────────────
|
|
26
|
-
// Every op is `prepare` (pure: `refused`, or a plan of named steps plus
|
|
27
|
-
// its effect summary, against the current version) then `apply(plan)`;
|
|
28
|
-
// composites compose their steps into one plan, so a hook sees the
|
|
29
|
-
// whole command before any write and a refusal refuses before one.
|
|
30
|
-
// Each prepare normalizes its inputs once, here at ingress: ids
|
|
31
|
-
// and strings as the wire would deliver them, payloads cloned.
|
|
32
|
-
const plan = (ids, writes) => ({
|
|
33
|
-
ids,
|
|
34
|
-
writes,
|
|
35
|
-
effect: effectOf(writes),
|
|
36
|
-
version: runsView.version()
|
|
37
|
-
});
|
|
38
|
-
/**
|
|
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.
|
|
49
|
-
* Every planned move carries the type steps that keep what the moved
|
|
50
|
-
* blocks show: one displayed out of an island as another kind than its
|
|
51
|
-
* stored one (`displayType`) gets that kind written, so leaving the
|
|
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).
|
|
56
|
-
*/
|
|
57
|
-
const moveTo = (ids, parent, index, side = 0, at, part) => ids.length === 0
|
|
58
|
-
? []
|
|
59
|
-
: [
|
|
60
|
-
{
|
|
61
|
-
op: 'moveBlocks',
|
|
62
|
-
ids,
|
|
63
|
-
parent,
|
|
64
|
-
index,
|
|
65
|
-
...(side === 1 && { side }),
|
|
66
|
-
...(at !== undefined && { at }),
|
|
67
|
-
...(part !== undefined && { part })
|
|
68
|
-
},
|
|
69
|
-
...ids.flatMap((id) => {
|
|
70
|
-
const shown = runsView.displayType(id);
|
|
71
|
-
return shown === undefined ? [] : attr(id, TYPE, shown);
|
|
72
|
-
})
|
|
73
|
-
];
|
|
74
|
-
/** Move `ids` to `index` among `parent`'s children. */
|
|
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);
|
|
78
|
-
/**
|
|
79
|
-
* A type step, planned only when the kind differs (the one same-value
|
|
80
|
-
* guard). A kind counts as the same only when the block is stored and
|
|
81
|
-
* shown as it: a move in the same plan pins the kind a block shows
|
|
82
|
-
* (`moveTo`), so a paragraph shown as its list's item (`itemOf`) that
|
|
83
|
-
* an outdent settles back to a paragraph is written back.
|
|
84
|
-
*/
|
|
85
|
-
const attr = (id, key, value) => {
|
|
86
|
-
const same = blockNodeOf(doc, id).getAttr(key) === value;
|
|
87
|
-
if (same && (runsView.displayType(id) ?? value) === value)
|
|
88
|
-
return [];
|
|
89
|
-
return [{ op: 'setBlockType', id, type: value }];
|
|
90
|
-
};
|
|
91
|
-
/**
|
|
92
|
-
* The kind `kid` shows once it leaves `from` for a slot under `parent`
|
|
93
|
-
* — the island and container rules, one answer:
|
|
94
|
-
* - an island's child takes `parent`'s default child (it leaves the
|
|
95
|
-
* island's kinds) — unless that renders no content while the child
|
|
96
|
-
* does: a line then takes the document's default kind (a code line
|
|
97
|
-
* shed into a columns layout is a paragraph), any other child
|
|
98
|
-
* keeps its kind, as `typeOf` shows one a peer adds meanwhile;
|
|
99
|
-
* - a container's item (its default child) takes `parent`'s default
|
|
100
|
-
* child — an item never shows outside its list — unless it
|
|
101
|
-
* stays inside an outer container of that kind (a nested list),
|
|
102
|
-
* or that kind renders no content (a column in a
|
|
103
|
-
* columns layout: its text would vanish);
|
|
104
|
-
* - then a plain block that does not fit `parent` (`fits`: a paragraph
|
|
105
|
-
* shed into a list) becomes its item, when that item renders content
|
|
106
|
-
* (`fitted`). A block that cannot (a paragraph in a columns
|
|
107
|
-
* layout) keeps its kind: its text never vanishes; any other kind (an
|
|
108
|
-
* image, a code block, a heading) keeps its kind.
|
|
109
|
-
*/
|
|
110
|
-
/**
|
|
111
|
-
* The kind a child of `island` (now `kind`) takes where the default
|
|
112
|
-
* child is `to`: `to` — unless `to` renders no content while the child
|
|
113
|
-
* does (its text would vanish): a line then takes the document's
|
|
114
|
-
* default kind, any other child keeps its kind (as `typeOf`).
|
|
115
|
-
*/
|
|
116
|
-
const leavingIsland = (island, kind, to) => {
|
|
117
|
-
if (roles.of(to).rendersContent || (kind !== undefined && !roles.of(kind).rendersContent))
|
|
118
|
-
return to;
|
|
119
|
-
return isLines(island) ? defaultChild(null) : kind;
|
|
120
|
-
};
|
|
121
|
-
const settledKind = (from, kid, parent) => {
|
|
122
|
-
let kind = blockTypeOf(kid);
|
|
123
|
-
if (from !== null && isIsland(from))
|
|
124
|
-
kind = leavingIsland(from, kind, defaultChild(parent));
|
|
125
|
-
else if (from !== null && isContainer(from) && kind === defaultChild(from)) {
|
|
126
|
-
const within = parent === null ? [] : [parent, ...ancestorsOf(parent)];
|
|
127
|
-
const to = defaultChild(parent);
|
|
128
|
-
// An outer container of its kind keeps it an item (a nested list); a
|
|
129
|
-
// column's default child is the document's: it holds no items of its own.
|
|
130
|
-
const outer = kind !== defaultChild(null) &&
|
|
131
|
-
within.some((a) => isContainer(a) && defaultChild(a) === kind);
|
|
132
|
-
if (!outer && roles.of(to).rendersContent)
|
|
133
|
-
kind = to;
|
|
134
|
-
}
|
|
135
|
-
return fitted(parent, kind);
|
|
136
|
-
};
|
|
137
|
-
/** The kind steps for the `kids` of `from` (`null`: the root) landing under `parent` (`settledKind`). */
|
|
138
|
-
const settle = (from, kids, parent) => kids.flatMap((kid) => {
|
|
139
|
-
const kind = settledKind(from, kid, parent);
|
|
140
|
-
return kind === undefined || kind === blockTypeOf(kid) ? [] : attr(kid, TYPE, kind);
|
|
141
|
-
});
|
|
142
|
-
/** `parent` and its display ancestors: where blocks landing under `parent` keep alive. */
|
|
143
|
-
const landing = (parent) => new Set(parent === null ? [] : [parent, ...ancestorsOf(parent)]);
|
|
144
|
-
/**
|
|
145
|
-
* `writes`, then every container one of `from` (the blocks' former
|
|
146
|
-
* parents) is left with no child but `leaving` removed
|
|
147
|
-
* (`del.range.empty-container`), and so on upward — never one of
|
|
148
|
-
* `kept`, and only an `emptiable` one (a container holding text of its
|
|
149
|
-
* own stays). The containers are found together, deepest first: a
|
|
150
|
-
* list that loses its last item along with a list nested in it goes
|
|
151
|
-
* too. Then every layout the plan leaves with one
|
|
152
|
-
* item or none dissolves (`dissolving`); `removed`: blocks the plan
|
|
153
|
-
* deletes besides (a block delete's members), which only that counts.
|
|
154
|
-
*/
|
|
155
|
-
const emptyingAll = (from, leaving, writes, kept = new Set(), removed = []) => {
|
|
156
|
-
const gone = new Set(leaving);
|
|
157
|
-
const tops = [];
|
|
158
|
-
const deepest = [...new Set(from)]
|
|
159
|
-
.filter((c) => c !== null)
|
|
160
|
-
.sort((a, b) => ancestorsOf(b).length - ancestorsOf(a).length);
|
|
161
|
-
for (const start of deepest) {
|
|
162
|
-
let top = null;
|
|
163
|
-
for (let c = start; c !== null && !gone.has(c) && !kept.has(c) && emptiable(c); c = positionOf(c).parent) {
|
|
164
|
-
if (!childrenIds(c).every((kid) => gone.has(kid)))
|
|
165
|
-
break;
|
|
166
|
-
gone.add((top = c));
|
|
167
|
-
}
|
|
168
|
-
if (top !== null)
|
|
169
|
-
tops.push(top);
|
|
170
|
-
}
|
|
171
|
-
const roots = tops.filter((t) => !ancestorsOf(t).some((a) => tops.includes(a)));
|
|
172
|
-
const steps = [...writes, ...roots.map((t) => remove(t, leaving))];
|
|
173
|
-
for (const id of removed)
|
|
174
|
-
gone.add(id);
|
|
175
|
-
return [...steps, ...dissolving(gone, leaving, steps)];
|
|
176
|
-
};
|
|
177
|
-
/**
|
|
178
|
-
* `layout.dissolving`: the steps that delete each layout a plan leaves
|
|
179
|
-
* with one item or none — `gone` (blocks the plan removes) and
|
|
180
|
-
* `leaving` (blocks it moves) no longer count, an item `writes` move or
|
|
181
|
-
* insert into it does. With one left, the layout and that item are
|
|
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.
|
|
186
|
-
*/
|
|
187
|
-
const dissolving = (gone, leaving, writes) => {
|
|
188
|
-
const away = new Set([...gone, ...leaving]);
|
|
189
|
-
const layouts = new Set();
|
|
190
|
-
for (const id of away) {
|
|
191
|
-
const parent = positionOf(id)?.parent;
|
|
192
|
-
if (parent != null && isLayout(parent))
|
|
193
|
-
layouts.add(parent);
|
|
194
|
-
}
|
|
195
|
-
const { kids } = view();
|
|
196
|
-
const out = [];
|
|
197
|
-
for (const layout of layouts) {
|
|
198
|
-
if ([layout, ...ancestorsOf(layout)].some((a) => gone.has(a)))
|
|
199
|
-
continue;
|
|
200
|
-
const item = itemKindOf(layout);
|
|
201
|
-
const arriving = writes.reduce((n, w) => w.op === 'moveBlocks' && w.parent === layout
|
|
202
|
-
? n + w.ids.filter((id) => blockTypeOf(id) === item).length
|
|
203
|
-
: w.op === 'insertBlocks' && w.parent === layout
|
|
204
|
-
? n + w.specs.filter((spec) => spec.type === item).length
|
|
205
|
-
: n, 0);
|
|
206
|
-
const slots = kids.get(layout) ?? [];
|
|
207
|
-
const items = slots.filter((k) => !away.has(k.id) && blockTypeOf(k.id) === item);
|
|
208
|
-
if (items.length + arriving > 1)
|
|
209
|
-
continue;
|
|
210
|
-
const { parent } = positionOf(layout);
|
|
211
|
-
const last = items[0];
|
|
212
|
-
if (last === undefined) {
|
|
213
|
-
out.push(deleting(layout, [layout]));
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
// Its blocks keep their home slots: the read shows them in the layout's
|
|
217
|
-
// slot (`layout.single`), settled for its parent.
|
|
218
|
-
const moved = (kids.get(last.id) ?? []).filter((k) => !away.has(k.id));
|
|
219
|
-
const ids = moved.map((k) => k.id);
|
|
220
|
-
out.push(...settle(last.id, ids, parent), deleting(layout, [layout, last.id]));
|
|
221
|
-
}
|
|
222
|
-
return out;
|
|
223
|
-
};
|
|
224
|
-
/** `writes`, then `container` removed when they leave it no child but `leaving` (`emptyingAll`). */
|
|
225
|
-
const emptying = (container, leaving, writes, kept) => emptyingAll([container], leaving, writes, kept);
|
|
226
|
-
/**
|
|
227
|
-
* Delete: `removes` leave. Every one is marked with what it
|
|
228
|
-
* displays — an unmarked one would be promoted into the deleted slot
|
|
229
|
-
* (`displaySlotOf`).
|
|
230
|
-
*/
|
|
231
|
-
const deleting = (id, removes) => {
|
|
232
|
-
const marks = [...new Set(removes.flatMap((b) => view().displays(b)))];
|
|
233
|
-
return { op: 'deleteBlock', id, marks, removes };
|
|
234
|
-
};
|
|
235
|
-
/** Delete `id` and its subtree, `kept` children aside. */
|
|
236
|
-
const remove = (id, kept = []) => {
|
|
237
|
-
const removes = [];
|
|
238
|
-
const skip = new Set(kept);
|
|
239
|
-
const walk = (b) => {
|
|
240
|
-
removes.push(b);
|
|
241
|
-
for (const kid of childrenIds(b))
|
|
242
|
-
if (!skip.has(kid))
|
|
243
|
-
walk(kid);
|
|
244
|
-
};
|
|
245
|
-
walk(id);
|
|
246
|
-
return deleting(id, removes);
|
|
247
|
-
};
|
|
248
|
-
const merge = (from, into) => ({
|
|
249
|
-
op: 'mergeBlocks',
|
|
250
|
-
from,
|
|
251
|
-
into,
|
|
252
|
-
at: displayLength(into),
|
|
253
|
-
length: displayLength(from)
|
|
254
|
-
});
|
|
255
|
-
/** `[at, end)` — `[offset, offset + length)` clamped to `id`'s display. */
|
|
256
|
-
const clamp = (id, offset, length) => {
|
|
257
|
-
const total = displayLength(id);
|
|
258
|
-
const at = Math.max(0, Math.min(offset, total));
|
|
259
|
-
return [at, Math.min(total, at + Math.max(0, length))];
|
|
260
|
-
};
|
|
261
|
-
/** `id`'s display items, each with its `[at, end)` display offsets. */
|
|
262
|
-
const spans = (id) => {
|
|
263
|
-
let end = 0;
|
|
264
|
-
return contentItems(id).map((item) => {
|
|
265
|
-
const at = end;
|
|
266
|
-
end += item.kind === 'text' ? item.text.length : 1;
|
|
267
|
-
return { item, at, end };
|
|
268
|
-
});
|
|
269
|
-
};
|
|
270
|
-
/** The live inline atom `atom` in `id`'s display, or undefined. */
|
|
271
|
-
const atomOf = (id, atom) => live(id) ? spans(id).find(({ item }) => item.kind === 'inline' && item.id === atom) : undefined;
|
|
272
|
-
/** Text items overlapping `[at, end)` of `id` (inline atoms carry no marks). */
|
|
273
|
-
const textIn = (id, at, end) => spans(id).flatMap((s) => (s.item.kind === 'text' && s.at < end && s.end > at ? [s.item] : []));
|
|
274
|
-
/** Push the steps writing `items` into `id` from `at` (text runs and atoms in order); the end offset. */
|
|
275
|
-
const writeContent = (writes, id, at, items = []) => {
|
|
276
|
-
for (const item of items) {
|
|
277
|
-
if (item.kind === 'inline')
|
|
278
|
-
writes.push({ op: 'insertInline', id, offset: at++, atom: item });
|
|
279
|
-
else if (item.text !== '') {
|
|
280
|
-
writes.push({ op: 'insertText', id, offset: at, text: item.text, marks: item.marks });
|
|
281
|
-
at += item.text.length;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return at;
|
|
285
|
-
};
|
|
286
|
-
/** `writes`, then every container the `leaving` blocks leave with no child removed. */
|
|
287
|
-
const emptied = (leaving, writes, kept) => emptyingAll(leaving.map((id) => positionOf(id).parent), leaving, writes, kept);
|
|
288
|
-
return {
|
|
289
|
-
REFUSED,
|
|
290
|
-
plan,
|
|
291
|
-
moveTo,
|
|
292
|
-
move,
|
|
293
|
-
leaving,
|
|
294
|
-
attr,
|
|
295
|
-
leavingIsland,
|
|
296
|
-
settledKind,
|
|
297
|
-
settle,
|
|
298
|
-
landing,
|
|
299
|
-
emptyingAll,
|
|
300
|
-
dissolving,
|
|
301
|
-
emptying,
|
|
302
|
-
emptied,
|
|
303
|
-
deleting,
|
|
304
|
-
remove,
|
|
305
|
-
merge,
|
|
306
|
-
clamp,
|
|
307
|
-
atomOf,
|
|
308
|
-
textIn,
|
|
309
|
-
writeContent
|
|
310
|
-
};
|
|
311
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { BlockId } from '../../placement/model.js';
|
|
2
|
-
import type { ContentRun, RunViewDebug } from '../runs.js';
|
|
3
|
-
import type { IndexPlacement } from './placement.js';
|
|
4
|
-
import { type Deps } from './shared.js';
|
|
5
|
-
import type { IndexState } from './state.js';
|
|
6
|
-
/** The run cache of one doc's index. */
|
|
7
|
-
export declare const indexCache: (ix: IndexState & IndexPlacement) => {
|
|
8
|
-
intern: <T>(v: T) => T;
|
|
9
|
-
computeFresh: (b: BlockId) => {
|
|
10
|
-
fresh: ContentRun[];
|
|
11
|
-
deps: Deps;
|
|
12
|
-
};
|
|
13
|
-
computeRuns: (b: BlockId) => void;
|
|
14
|
-
debug: RunViewDebug;
|
|
15
|
-
};
|
|
16
|
-
export type IndexCache = ReturnType<typeof indexCache>;
|