edytor 4.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/block/block.svelte.js +6 -21
- package/dist/block/inlineBlock.svelte.js +4 -6
- package/dist/clipboard/moveText.js +6 -8
- package/dist/cloudflare/DocumentRoom.d.ts +10 -3
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/room/access.d.ts +8 -3
- package/dist/cloudflare/room/access.js +44 -20
- package/dist/cloudflare/room/admission.js +28 -5
- package/dist/cloudflare/room/comments.js +28 -1
- package/dist/cloudflare/room/context.d.ts +106 -109
- package/dist/cloudflare/room/context.js +10 -3
- package/dist/cloudflare/room/forged.d.ts +14 -0
- package/dist/cloudflare/room/forged.js +30 -2
- package/dist/cloudflare/room/frames.d.ts +4 -0
- package/dist/cloudflare/room/frames.js +5 -1
- package/dist/cloudflare/room/purge.js +2 -1
- package/dist/cloudflare/room/replicas.d.ts +4 -2
- package/dist/cloudflare/room/replicas.js +8 -3
- package/dist/cloudflare/room/suggest.d.ts +7 -0
- package/dist/cloudflare/room/suggest.js +143 -0
- package/dist/cloudflare/routeDocumentSocket.d.ts +10 -1
- package/dist/cloudflare/routeDocumentSocket.js +5 -3
- package/dist/components/Content.svelte +9 -1
- package/dist/crdt/anchors.js +3 -2
- package/dist/crdt/attribution/attribution.d.ts +3 -0
- package/dist/crdt/attribution/attribution.js +3 -3
- package/dist/crdt/doc/funnel.d.ts +1 -1
- package/dist/crdt/doc/funnel.js +5 -2
- package/dist/crdt/doc/gate.d.ts +1 -1
- package/dist/crdt/doc/history.d.ts +6 -0
- package/dist/crdt/doc/history.js +79 -59
- package/dist/crdt/doc/ops.d.ts +81 -0
- package/dist/crdt/doc/ops.js +1898 -0
- package/dist/crdt/doc/reads.d.ts +2 -2
- package/dist/crdt/doc/reads.js +18 -28
- package/dist/crdt/doc/suggest.d.ts +167 -0
- package/dist/crdt/doc/suggest.js +579 -0
- package/dist/crdt/document.d.ts +10 -1
- package/dist/crdt/document.js +19 -11
- package/dist/crdt/edytor-doc.d.ts +294 -272
- package/dist/crdt/edytor-doc.js +123 -108
- package/dist/crdt/engine-api.d.ts +8 -2
- package/dist/crdt/engine.d.ts +2 -1
- package/dist/crdt/engine.js +3 -1
- package/dist/crdt/flow.d.ts +5 -47
- package/dist/crdt/flow.js +3 -269
- package/dist/crdt/index.d.ts +102 -108
- package/dist/crdt/index.js +3 -0
- package/dist/crdt/operations.d.ts +13 -4
- package/dist/crdt/parts.d.ts +43 -0
- package/dist/crdt/parts.js +44 -0
- package/dist/crdt/placement/display.d.ts +1 -1
- package/dist/crdt/placement/display.js +3 -3
- package/dist/crdt/placement/keys.d.ts +5 -0
- package/dist/crdt/placement/keys.js +5 -0
- package/dist/crdt/placement/resolve.d.ts +1 -1
- package/dist/crdt/placement/resolve.js +2 -1
- package/dist/crdt/protocol.d.ts +1 -1
- package/dist/crdt/protocol.js +1 -1
- package/dist/crdt/protocols/auth.d.ts +11 -1
- package/dist/crdt/protocols/auth.js +17 -1
- package/dist/crdt/protocols/awareness.js +46 -81
- package/dist/crdt/protocols/comments.d.ts +16 -1
- package/dist/crdt/protocols/comments.js +9 -1
- package/dist/crdt/providers/index.d.ts +12 -22
- package/dist/crdt/providers/index.js +3 -1
- 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 +8 -12
- package/dist/crdt/providers/websocket.js +17 -19
- package/dist/crdt/purge.d.ts +7 -1
- package/dist/crdt/purge.js +32 -2
- package/dist/crdt/rangeDelete.d.ts +2 -47
- package/dist/crdt/rangeDelete.js +1 -169
- package/dist/crdt/restore.d.ts +2 -1
- package/dist/crdt/restore.js +9 -0
- package/dist/crdt/schema.d.ts +9 -1
- package/dist/crdt/schema.js +13 -3
- package/dist/crdt/structs.d.ts +2 -0
- package/dist/crdt/structs.js +2 -0
- 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 +19 -19
- package/dist/crdt/text/model.d.ts +10 -0
- package/dist/crdt/text/model.js +10 -4
- package/dist/crdt/text/runs.d.ts +43 -42
- package/dist/crdt/text/runs.js +2 -165
- package/dist/crdt/vendor/yjs/UPSTREAM.md +83 -0
- package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
- package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +6 -3
- package/dist/crdt/vendor/yjs/dts/ynode.d.ts +13 -2
- package/dist/crdt/vendor/yjs/src/index.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/Item.js +44 -5
- package/dist/crdt/vendor/yjs/src/ynode.js +29 -7
- package/dist/edytor.svelte.d.ts +1 -1
- package/dist/edytor.svelte.js +100 -75
- package/dist/events/beforeInputCommands.d.ts +2 -2
- package/dist/events/domTextOffset.d.ts +2 -0
- package/dist/events/domTextOffset.js +8 -0
- package/dist/events/nativeInteractiveControl.d.ts +11 -1
- package/dist/events/nativeInteractiveControl.js +41 -1
- package/dist/events/onBeforeInput.js +3 -3
- package/dist/events/onFocus.js +9 -11
- package/dist/export/forms.d.ts +75 -75
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/labels.d.ts +8 -0
- package/dist/labels.js +6 -1
- package/dist/plugins/comments/CommentsController.svelte.d.ts +5 -1
- package/dist/plugins/comments/CommentsController.svelte.js +14 -4
- package/dist/plugins/find/search.js +2 -2
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
- package/dist/plugins/richtext/richTextOperations.js +26 -32
- package/dist/plugins/suggestions/SuggestionBars.svelte +40 -23
- package/dist/plugins/suggestions/SuggestionBars.svelte.d.ts +3 -1
- package/dist/plugins/suggestions/SuggestionRuns.svelte +77 -0
- package/dist/plugins/suggestions/SuggestionRuns.svelte.d.ts +9 -0
- package/dist/plugins/suggestions/shared.svelte.d.ts +58 -0
- package/dist/plugins/suggestions/shared.svelte.js +421 -0
- package/dist/plugins/suggestions/suggestionsPlugin.d.ts +15 -4
- package/dist/selection/domSelection.d.ts +2 -0
- package/dist/selection/domSelection.js +6 -2
- package/dist/selection/pointer.svelte.js +75 -48
- package/dist/selection/replaceSelection.js +2 -6
- package/dist/selection/selection.svelte.d.ts +3 -3
- package/dist/selection/selection.svelte.js +39 -32
- package/dist/selection/selection.utils.d.ts +14 -0
- package/dist/selection/selection.utils.js +33 -11
- package/dist/session/commands.js +21 -11
- package/dist/session/composition.svelte.d.ts +5 -3
- package/dist/session/composition.svelte.js +21 -17
- package/dist/session/handles.d.ts +4 -17
- package/dist/session/handles.js +14 -44
- package/dist/session/navigation.js +6 -9
- package/dist/session/selection.d.ts +11 -5
- package/dist/session/selection.js +14 -48
- package/dist/session/suggestions.svelte.d.ts +38 -9
- package/dist/session/suggestions.svelte.js +37 -18
- package/dist/session/virtual.d.ts +1 -1
- package/dist/surface/attributes.d.ts +5 -0
- package/dist/surface/attributes.js +2 -2
- package/dist/surface/cells.d.ts +2 -26
- package/dist/surface/cells.js +2 -33
- package/dist/surface/observer.svelte.d.ts +6 -1
- package/dist/surface/observer.svelte.js +143 -111
- package/dist/surface/pin.svelte.d.ts +9 -9
- package/dist/surface/pin.svelte.js +15 -22
- package/dist/surface/projector.svelte.js +10 -16
- package/dist/text/text.svelte.js +14 -19
- package/dist/themes/notion.css +29 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +7 -0
- package/package.json +3 -1
- package/dist/crdt/doc/capability.d.ts +0 -56
- package/dist/crdt/doc/capability.js +0 -211
- 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 -199
- 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 -225
- 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 -24
- package/dist/crdt/text/index/layout.js +0 -271
- package/dist/crdt/text/index/placement.d.ts +0 -47
- package/dist/crdt/text/index/placement.js +0 -741
- 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
package/dist/crdt/doc/moves.js
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Block placement ops: insert (`insertBlocks`, `duplicateBlock`), move
|
|
3
|
-
* (`moveBlocks`), outdent (`unNestBlocks`) and the lift out of the
|
|
4
|
-
* containers a kind does not fit (`landingOf`, `splitOut`, `liftOut`).
|
|
5
|
-
*/
|
|
6
|
-
import { id as newId } from '../../utils.js';
|
|
7
|
-
import { asBlockSpec, sanitizeSpec } from '../../utils/json.js';
|
|
8
|
-
import { collides } from '../placement/model.js';
|
|
9
|
-
import { ref } from './plan.js';
|
|
10
|
-
import { PART } from './steps.js';
|
|
11
|
-
/** The moves ops of one facade, prepared. */
|
|
12
|
-
export const moveOps = (c) => {
|
|
13
|
-
const { doc, blockTypeOf, blockDataOf, childrenIds, positionOf, isVoid, isIsland, isLine, contentItems, live, canPlace, isContainer, fits, kindToCopy, REFUSED, plan, moveTo, move, leaving, settledKind, settle, landing, emptying, emptied } = c;
|
|
14
|
-
/**
|
|
15
|
-
* Insert blocks (specs may carry children/content/data; the batch is
|
|
16
|
-
* all-or-nothing). A spec is a `BlockSpec` or the `JSONBlock` that
|
|
17
|
-
* `toJSON` and the view's `value` speak (`{ type, content: [{ text }] }`):
|
|
18
|
-
* its ids are kept, minted where missing (`asBlockSpec`). Ids must be fresh.
|
|
19
|
-
* Refused when the parent is not live, is `void` or is an island's
|
|
20
|
-
* line (it holds no children), or any id collides.
|
|
21
|
-
* Inserting INSIDE an island is allowed — island interiors are built
|
|
22
|
-
* this way. `ids`: the inserted roots.
|
|
23
|
-
*/
|
|
24
|
-
const insertBlocks = (dest, specs) => {
|
|
25
|
-
const parent = ref(dest.parent);
|
|
26
|
-
const clean = specs.map((spec) => sanitizeSpec(asBlockSpec(spec)));
|
|
27
|
-
if (parent !== null && (isVoid(parent) || isLine(parent)))
|
|
28
|
-
return REFUSED;
|
|
29
|
-
if (clean.length === 0)
|
|
30
|
-
return plan([], []);
|
|
31
|
-
if ((parent !== null && !live(parent)) || collides(doc, clean))
|
|
32
|
-
return REFUSED;
|
|
33
|
-
return plan(clean.map((s) => s.id), [{ op: 'insertBlocks', parent, index: dest.index, specs: clean }]);
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* Grouped move — one step, per-member conflict resolution; refused
|
|
37
|
-
* exactly when `canPlace` refuses the group. `ids`: the moved blocks,
|
|
38
|
-
* in request order.
|
|
39
|
-
*/
|
|
40
|
-
const moveBlocks = (ids, dest) => {
|
|
41
|
-
const moved = ids.map(ref);
|
|
42
|
-
const parent = ref(dest.parent);
|
|
43
|
-
if (moved.length === 0)
|
|
44
|
-
return plan([], []);
|
|
45
|
-
if (!canPlace(moved, parent))
|
|
46
|
-
return REFUSED;
|
|
47
|
-
// A list the move leaves with no item goes (not one it moves or lands in).
|
|
48
|
-
const into = new Set([...moved, ...landing(parent)]);
|
|
49
|
-
return plan(moved, emptied(moved, move(moved, parent, dest.index), into));
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Outdent: move sibling blocks `ids` (document order) right
|
|
53
|
-
* after their parent, and hand the siblings that followed the last of
|
|
54
|
-
* them to it as its last children — every outliner's Shift+Tab. A last
|
|
55
|
-
* block that cannot adopt them (void, island, a container they do not
|
|
56
|
-
* fit) leaves them with the parent. The blocks take the kind they show
|
|
57
|
-
* in their new slot (`settledKind`: a list's item becomes a paragraph,
|
|
58
|
-
* a paragraph outdented into a list its item); refused where one would
|
|
59
|
-
* not fit (a paragraph out of a column into its columns layout).
|
|
60
|
-
* Items leaving a container never take the items after them:
|
|
61
|
-
* the list splits around them, Notion's way — outdented first items go
|
|
62
|
-
* before it, last ones after it; from the middle, the list keeps the
|
|
63
|
-
* items after them and a new list of its kind takes the ones before
|
|
64
|
-
* them, so an item a peer appends meanwhile stays with the items it
|
|
65
|
-
* follows (`splitOut`, the split Turn into's `liftOut` makes). A
|
|
66
|
-
* container left with no child goes. One plan; refused as the
|
|
67
|
-
* move is. `ids`: the moved blocks.
|
|
68
|
-
*/
|
|
69
|
-
const unNestBlocks = (ids) => {
|
|
70
|
-
const moved = ids.map(ref);
|
|
71
|
-
const last = moved.at(-1);
|
|
72
|
-
const pos = last === undefined ? null : positionOf(last);
|
|
73
|
-
const ppos = pos?.parent != null ? positionOf(pos.parent) : null;
|
|
74
|
-
if (!ppos || moved.some((id) => positionOf(id)?.parent !== pos.parent))
|
|
75
|
-
return REFUSED;
|
|
76
|
-
const from = pos.parent;
|
|
77
|
-
if (!canPlace(moved, ppos.parent, (id) => settledKind(from, id, ppos.parent)))
|
|
78
|
-
return REFUSED;
|
|
79
|
-
const after = childrenIds(from).slice(pos.index + 1);
|
|
80
|
-
const retype = settle(from, moved, ppos.parent);
|
|
81
|
-
if (isContainer(from))
|
|
82
|
-
return splitOut(moved, [from], [], false, retype);
|
|
83
|
-
const writes = [...moveTo(moved, ppos.parent, ppos.index + 1, leaving(true)), ...retype];
|
|
84
|
-
const adopts = after.length > 0 &&
|
|
85
|
-
!isVoid(last) &&
|
|
86
|
-
!isIsland(last) &&
|
|
87
|
-
after.every((id) => fits(last, blockTypeOf(id)));
|
|
88
|
-
if (adopts)
|
|
89
|
-
writes.push(...move(after, last, Infinity));
|
|
90
|
-
return plan(moved, writes);
|
|
91
|
-
};
|
|
92
|
-
/** Outdent one block (`unNestBlocks`). */
|
|
93
|
-
const unNestBlock = (id) => unNestBlocks([id]);
|
|
94
|
-
/**
|
|
95
|
-
* Where a block of `kind` at `id`'s place lands: `id`'s parent, or the
|
|
96
|
-
* first one up that `kind` fits (`fits`: a list holds only its items),
|
|
97
|
-
* and the containers it leaves on the way, innermost first (`levels`).
|
|
98
|
-
* Its own kind keeps a block where it is, as a reorder
|
|
99
|
-
* does (`canPlace`); a block inserted after it (`after`) has no place
|
|
100
|
-
* yet.
|
|
101
|
-
*/
|
|
102
|
-
const landingOf = (id, kind, after = false) => {
|
|
103
|
-
const levels = [];
|
|
104
|
-
let parent = positionOf(id)?.parent ?? null;
|
|
105
|
-
const own = !after && blockTypeOf(id) === kind;
|
|
106
|
-
for (; !own && parent !== null && !fits(parent, kind); parent = positionOf(parent).parent)
|
|
107
|
-
levels.push(parent);
|
|
108
|
-
return { parent, levels };
|
|
109
|
-
};
|
|
110
|
-
/**
|
|
111
|
-
* Lift sibling blocks `ids` (document order) out of `levels`, the
|
|
112
|
-
* containers around them, innermost first: each level
|
|
113
|
-
* splits around them as an outdent splits one list — the blocks
|
|
114
|
-
* before them go to a new container of its kind, which the level above
|
|
115
|
-
* holds the same way, and the level keeps the ones after them, so an
|
|
116
|
-
* item a peer appends meanwhile stays with the items it follows; one
|
|
117
|
-
* left with no child goes. `specs` (new blocks) land right after them.
|
|
118
|
-
* With `keep`, `ids` stay before the split and only `specs` go out.
|
|
119
|
-
* `retype` (the caller's kind steps) joins the plan. The one split
|
|
120
|
-
* `unNestBlocks` and `liftOut` share. `ids`: the placed blocks.
|
|
121
|
-
*/
|
|
122
|
-
const splitOut = (ids, levels, specs, keep, retype = []) => {
|
|
123
|
-
const moved = keep ? [] : [...ids];
|
|
124
|
-
const placed = [...moved, ...specs.map((s) => s.id)];
|
|
125
|
-
const insert = (at, index, s) => s.length ? [{ op: 'insertBlocks', parent: at, index, specs: s }] : [];
|
|
126
|
-
const last = ids.at(-1);
|
|
127
|
-
const { parent, index } = positionOf(levels.at(-1) ?? last);
|
|
128
|
-
if (levels.length === 0)
|
|
129
|
-
return plan(placed, [...insert(parent, index + 1, specs), ...retype]);
|
|
130
|
-
// Bottom up: what stays before the split at each level (the level
|
|
131
|
-
// itself, or a new head holding it), and whether anything follows it.
|
|
132
|
-
const heads = [];
|
|
133
|
-
let before = null;
|
|
134
|
-
let follows = false;
|
|
135
|
-
// The block right after `last` (what the split ends at when `keep`).
|
|
136
|
-
let next;
|
|
137
|
-
let child = last;
|
|
138
|
-
for (const level of levels) {
|
|
139
|
-
const kids = childrenIds(level);
|
|
140
|
-
const at = kids.indexOf(child);
|
|
141
|
-
const stays = (keep && child === last) || (before !== null && !before.head);
|
|
142
|
-
const lead = kids.slice(0, at + (stays ? 1 : 0)).filter((k) => !moved.includes(k));
|
|
143
|
-
if (!follows && at + 1 < kids.length)
|
|
144
|
-
[follows, next] = [true, kids[at + 1]];
|
|
145
|
-
if ((lead.length > 0 || before?.head) && follows) {
|
|
146
|
-
const spec = sanitizeSpec({
|
|
147
|
-
id: newId('b'),
|
|
148
|
-
type: kindToCopy(level),
|
|
149
|
-
data: blockDataOf(level) ?? {}
|
|
150
|
-
});
|
|
151
|
-
heads.push({ spec, kids: lead, inner: before?.head });
|
|
152
|
-
before = { id: spec.id, head: spec };
|
|
153
|
-
}
|
|
154
|
-
else
|
|
155
|
-
before = lead.length > 0 ? { id: level } : null;
|
|
156
|
-
child = level;
|
|
157
|
-
}
|
|
158
|
-
// Written in one gap, each right after the block before it, as an
|
|
159
|
-
// after-record out of the level's slot or a before-record into the gap
|
|
160
|
-
// before it (`leaving`): the engine's sort decides their order against
|
|
161
|
-
// a peer's records there (fork patch YP22, `sourceOrder`: the block a
|
|
162
|
-
// record stands at, then its part), so a peer's heads, pieces, lifts
|
|
163
|
-
// and new blocks in the same gap keep the serial order whatever the
|
|
164
|
-
// client ids (`del.merge.container`). The head stands at the first block
|
|
165
|
-
// (with `keep`, at it: `with`), the blocks at themselves, the new
|
|
166
|
-
// blocks after the last (`after`) — or, kept before the block that
|
|
167
|
-
// follows, at that block (`kept`). The records are written last first,
|
|
168
|
-
// so each stands before the one written before it, in the order the
|
|
169
|
-
// sort gives them; the head and the new blocks are created in the
|
|
170
|
-
// innermost level right after the block they stand at (their home,
|
|
171
|
-
// hidden while their records show them) and moved out by them.
|
|
172
|
-
const outside = before !== null && !before.head;
|
|
173
|
-
const side = leaving(outside);
|
|
174
|
-
const gap = index + (outside ? 1 : 0);
|
|
175
|
-
const at = moved[0] ?? last;
|
|
176
|
-
const kids0 = childrenIds(levels[0]);
|
|
177
|
-
const home = kids0.indexOf(last) + 1;
|
|
178
|
-
const specIds = specs.map((sp) => sp.id);
|
|
179
|
-
const [stands, part] = keep && !outside ? [next, PART.kept] : [last, PART.after];
|
|
180
|
-
// One record per run of adjacent blocks, each standing at its first: a
|
|
181
|
-
// block a peer's move takes out of a run leaves the others at their own place.
|
|
182
|
-
const runs = [];
|
|
183
|
-
for (const id of moved) {
|
|
184
|
-
const run = runs.at(-1);
|
|
185
|
-
if (run !== undefined && kids0.indexOf(run.at(-1)) + 1 === kids0.indexOf(id))
|
|
186
|
-
run.push(id);
|
|
187
|
-
else
|
|
188
|
-
runs.push([id]);
|
|
189
|
-
}
|
|
190
|
-
const writes = [
|
|
191
|
-
...insert(levels[0], home, specs),
|
|
192
|
-
...(before?.head ? insert(levels[0], home, [before.head]) : []),
|
|
193
|
-
...moveTo(specIds, parent, gap, side, stands, part),
|
|
194
|
-
...runs.reverse().flatMap((run) => moveTo(run, parent, gap, side, run[0], PART.self)),
|
|
195
|
-
...(before?.head
|
|
196
|
-
? moveTo([before.head.id], parent, gap, side, at, keep ? PART.with : PART.head)
|
|
197
|
-
: [])
|
|
198
|
-
];
|
|
199
|
-
for (const { spec, kids, inner } of heads.reverse()) {
|
|
200
|
-
writes.push(...moveTo(kids, spec.id, 0), ...(inner ? insert(spec.id, kids.length, [inner]) : []));
|
|
201
|
-
}
|
|
202
|
-
writes.push(...retype);
|
|
203
|
-
void next;
|
|
204
|
-
return plan(placed, emptying(levels[0], moved, writes, landing(parent)));
|
|
205
|
-
};
|
|
206
|
-
/**
|
|
207
|
-
* Place `id` where a block of `kind` fits, in one plan:
|
|
208
|
-
* out of every container around it that `kind` does not fit
|
|
209
|
-
* (`landingOf`: a list, and a list holding that list directly, for a
|
|
210
|
-
* heading or a divider), each split around it (`splitOut`). `after`
|
|
211
|
-
* (new blocks) lands right after it. With `keep`, `id` stays and only
|
|
212
|
-
* `after` goes out, the split right after `id` (a divider inserted
|
|
213
|
-
* after an item). Its own kind never moves it. The kinds are the
|
|
214
|
-
* caller's: it composes the retype (`setBlock`). Refused where `id`
|
|
215
|
-
* may not move there (`canPlace`). `ids`: the placed blocks.
|
|
216
|
-
*/
|
|
217
|
-
const liftOut = (id, kind, { keep = false, after = [] } = {}) => {
|
|
218
|
-
id = ref(id);
|
|
219
|
-
kind = ref(kind);
|
|
220
|
-
const specs = after.map(sanitizeSpec);
|
|
221
|
-
const { parent, levels } = landingOf(id, kind, keep);
|
|
222
|
-
if (!positionOf(id) || collides(doc, specs) || !canPlace([id], parent, () => kind))
|
|
223
|
-
return REFUSED;
|
|
224
|
-
return splitOut([id], levels, specs, keep);
|
|
225
|
-
};
|
|
226
|
-
/**
|
|
227
|
-
* Explicit fresh-identity copy of a subtree (paste / drag-clone):
|
|
228
|
-
* serializes `id`, remaps every block and inline atom id through
|
|
229
|
-
* `freshId`, inserts the copy right after `id`. Text atoms get new
|
|
230
|
-
* identity too — duplication is a creation op, not a relocation. An
|
|
231
|
-
* inline id `freshId` does not answer (a block-only callback) is
|
|
232
|
-
* minted; a block id it does not answer refuses the copy. `ids`: the
|
|
233
|
-
* copy's root.
|
|
234
|
-
*/
|
|
235
|
-
const duplicateBlock = (id, freshId = (_, kind) => newId(kind === 'block' ? 'b' : 'i')) => {
|
|
236
|
-
id = ref(id);
|
|
237
|
-
const pos = positionOf(id);
|
|
238
|
-
if (pos === null)
|
|
239
|
-
return REFUSED;
|
|
240
|
-
const spec = (b) => {
|
|
241
|
-
const fresh = freshId(b, 'block');
|
|
242
|
-
if (typeof fresh !== 'string')
|
|
243
|
-
return null;
|
|
244
|
-
const content = contentItems(b).map((item) => {
|
|
245
|
-
if (item.kind !== 'inline')
|
|
246
|
-
return item;
|
|
247
|
-
const atom = freshId(item.id, 'inline');
|
|
248
|
-
return { ...item, id: typeof atom === 'string' ? atom : newId('i') };
|
|
249
|
-
});
|
|
250
|
-
const children = childrenIds(b).map(spec);
|
|
251
|
-
if (children.includes(null))
|
|
252
|
-
return null;
|
|
253
|
-
const data = blockDataOf(b);
|
|
254
|
-
return {
|
|
255
|
-
id: fresh,
|
|
256
|
-
type: kindToCopy(b),
|
|
257
|
-
...(data !== undefined && { data }),
|
|
258
|
-
content,
|
|
259
|
-
children: children
|
|
260
|
-
};
|
|
261
|
-
};
|
|
262
|
-
const copy = spec(id);
|
|
263
|
-
return copy ? insertBlocks({ parent: pos.parent, index: pos.index + 1 }, [copy]) : REFUSED;
|
|
264
|
-
};
|
|
265
|
-
return {
|
|
266
|
-
insertBlocks,
|
|
267
|
-
moveBlocks,
|
|
268
|
-
unNestBlocks,
|
|
269
|
-
unNestBlock,
|
|
270
|
-
landingOf,
|
|
271
|
-
liftOut,
|
|
272
|
-
duplicateBlock
|
|
273
|
-
};
|
|
274
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type BlockId, type SplitTail } from '../placement/model.js';
|
|
2
|
-
import type { Prepared } from './types.js';
|
|
3
|
-
import type { OpsContext } from './steps.js';
|
|
4
|
-
/** The split-merge ops of one facade, prepared. */
|
|
5
|
-
export declare const splitMergeOps: (c: OpsContext) => {
|
|
6
|
-
splitBlock: (id: BlockId, offset: number, newId: BlockId, tail?: SplitTail) => Prepared;
|
|
7
|
-
mergeBlocks: (fromId: BlockId, intoId: BlockId) => Prepared;
|
|
8
|
-
mergeBackward: (id: BlockId) => Prepared;
|
|
9
|
-
mergeForward: (id: BlockId) => Prepared;
|
|
10
|
-
};
|
|
@@ -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;
|