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.
Files changed (75) hide show
  1. package/README.md +2 -2
  2. package/dist/cloudflare/room/context.d.ts +62 -74
  3. package/dist/crdt/doc/history.js +2 -3
  4. package/dist/crdt/doc/ops.d.ts +81 -0
  5. package/dist/crdt/doc/ops.js +1898 -0
  6. package/dist/crdt/edytor-doc.d.ts +156 -156
  7. package/dist/crdt/edytor-doc.js +14 -42
  8. package/dist/crdt/flow.d.ts +4 -46
  9. package/dist/crdt/flow.js +2 -262
  10. package/dist/crdt/index.d.ts +62 -74
  11. package/dist/crdt/operations.d.ts +1 -1
  12. package/dist/crdt/placement/resolve.d.ts +1 -1
  13. package/dist/crdt/protocols/awareness.js +46 -81
  14. package/dist/crdt/providers/index.d.ts +10 -22
  15. package/dist/crdt/providers/indexeddb.d.ts +6 -10
  16. package/dist/crdt/providers/indexeddb.js +10 -16
  17. package/dist/crdt/providers/room.d.ts +11 -0
  18. package/dist/crdt/providers/room.js +26 -12
  19. package/dist/crdt/providers/websocket.d.ts +6 -12
  20. package/dist/crdt/providers/websocket.js +8 -15
  21. package/dist/crdt/rangeDelete.d.ts +2 -47
  22. package/dist/crdt/rangeDelete.js +1 -169
  23. package/dist/crdt/text/index/build.d.ts +125 -0
  24. package/dist/crdt/text/index/build.js +2895 -0
  25. package/dist/crdt/text/index/shared.d.ts +2 -6
  26. package/dist/crdt/text/index/shared.js +2 -5
  27. package/dist/crdt/text/model.js +2 -3
  28. package/dist/crdt/text/runs.d.ts +9 -12
  29. package/dist/crdt/text/runs.js +2 -165
  30. package/dist/events/domTextOffset.d.ts +2 -0
  31. package/dist/events/domTextOffset.js +8 -0
  32. package/dist/export/forms.d.ts +50 -50
  33. package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
  34. package/dist/plugins/richtext/richTextOperations.js +26 -32
  35. package/dist/selection/selection.utils.js +5 -8
  36. package/dist/session/handles.js +3 -7
  37. package/dist/surface/observer.svelte.js +15 -30
  38. package/dist/surface/projector.svelte.js +2 -2
  39. package/dist/utils.d.ts +5 -0
  40. package/dist/utils.js +7 -0
  41. package/package.json +1 -1
  42. package/dist/crdt/doc/capability.d.ts +0 -56
  43. package/dist/crdt/doc/capability.js +0 -207
  44. package/dist/crdt/doc/content.d.ts +0 -16
  45. package/dist/crdt/doc/content.js +0 -77
  46. package/dist/crdt/doc/delete.d.ts +0 -15
  47. package/dist/crdt/doc/delete.js +0 -84
  48. package/dist/crdt/doc/layout.d.ts +0 -9
  49. package/dist/crdt/doc/layout.js +0 -178
  50. package/dist/crdt/doc/meta.d.ts +0 -19
  51. package/dist/crdt/doc/meta.js +0 -198
  52. package/dist/crdt/doc/moves.d.ts +0 -21
  53. package/dist/crdt/doc/moves.js +0 -274
  54. package/dist/crdt/doc/split-merge.d.ts +0 -10
  55. package/dist/crdt/doc/split-merge.js +0 -179
  56. package/dist/crdt/doc/steps.d.ts +0 -65
  57. package/dist/crdt/doc/steps.js +0 -311
  58. package/dist/crdt/text/index/cache.d.ts +0 -16
  59. package/dist/crdt/text/index/cache.js +0 -175
  60. package/dist/crdt/text/index/claims.d.ts +0 -18
  61. package/dist/crdt/text/index/claims.js +0 -203
  62. package/dist/crdt/text/index/fold.d.ts +0 -21
  63. package/dist/crdt/text/index/fold.js +0 -656
  64. package/dist/crdt/text/index/layout.d.ts +0 -22
  65. package/dist/crdt/text/index/layout.js +0 -258
  66. package/dist/crdt/text/index/placement.d.ts +0 -47
  67. package/dist/crdt/text/index/placement.js +0 -738
  68. package/dist/crdt/text/index/records.d.ts +0 -13
  69. package/dist/crdt/text/index/records.js +0 -139
  70. package/dist/crdt/text/index/report.d.ts +0 -18
  71. package/dist/crdt/text/index/report.js +0 -337
  72. package/dist/crdt/text/index/state.d.ts +0 -197
  73. package/dist/crdt/text/index/state.js +0 -116
  74. package/dist/crdt/text/index/streams.d.ts +0 -25
  75. 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
- };
@@ -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;
@@ -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>;