edytor 3.0.0 → 4.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 (143) hide show
  1. package/README.md +2 -2
  2. package/dist/cloudflare/DocumentRoom.d.ts +13 -9
  3. package/dist/cloudflare/DocumentRoom.js +10 -6
  4. package/dist/cloudflare/room/context.d.ts +27 -26
  5. package/dist/cloudflare/room/context.js +2 -2
  6. package/dist/cloudflare/room/purge.d.ts +6 -3
  7. package/dist/cloudflare/room/purge.js +44 -19
  8. package/dist/cloudflare/room/updates.d.ts +3 -4
  9. package/dist/cloudflare/room/updates.js +6 -8
  10. package/dist/crdt/attribution/attribution.d.ts +31 -12
  11. package/dist/crdt/attribution/attribution.js +56 -18
  12. package/dist/crdt/attribution/block.d.ts +3 -3
  13. package/dist/crdt/attribution/block.js +7 -8
  14. package/dist/crdt/attribution/index.d.ts +1 -1
  15. package/dist/crdt/attribution/index.js +1 -1
  16. package/dist/crdt/data.d.ts +62 -31
  17. package/dist/crdt/data.js +211 -103
  18. package/dist/crdt/doc/delete.d.ts +1 -1
  19. package/dist/crdt/doc/delete.js +13 -30
  20. package/dist/crdt/doc/funnel.js +29 -24
  21. package/dist/crdt/doc/gate.d.ts +3 -3
  22. package/dist/crdt/doc/history.d.ts +25 -9
  23. package/dist/crdt/doc/history.js +150 -40
  24. package/dist/crdt/doc/layout.js +11 -22
  25. package/dist/crdt/doc/meta.js +12 -17
  26. package/dist/crdt/doc/moves.d.ts +1 -1
  27. package/dist/crdt/doc/moves.js +52 -32
  28. package/dist/crdt/doc/reads.js +2 -2
  29. package/dist/crdt/doc/seed.js +2 -2
  30. package/dist/crdt/doc/split-merge.js +12 -15
  31. package/dist/crdt/doc/steps.d.ts +25 -13
  32. package/dist/crdt/doc/steps.js +52 -122
  33. package/dist/crdt/doc/types.d.ts +21 -9
  34. package/dist/crdt/document.d.ts +16 -5
  35. package/dist/crdt/document.js +21 -4
  36. package/dist/crdt/edytor-doc.d.ts +62 -64
  37. package/dist/crdt/edytor-doc.js +18 -13
  38. package/dist/crdt/engine-api.d.ts +33 -1
  39. package/dist/crdt/engine.d.ts +0 -1
  40. package/dist/crdt/engine.js +1 -4
  41. package/dist/crdt/flow.d.ts +1 -1
  42. package/dist/crdt/flow.js +25 -27
  43. package/dist/crdt/index.d.ts +26 -25
  44. package/dist/crdt/index.js +1 -1
  45. package/dist/crdt/placement/display.d.ts +5 -30
  46. package/dist/crdt/placement/display.js +39 -144
  47. package/dist/crdt/placement/keys.d.ts +58 -0
  48. package/dist/crdt/placement/keys.js +90 -0
  49. package/dist/crdt/placement/model.d.ts +119 -118
  50. package/dist/crdt/placement/model.js +242 -142
  51. package/dist/crdt/placement/resolve.d.ts +123 -35
  52. package/dist/crdt/placement/resolve.js +187 -153
  53. package/dist/crdt/placement/slots.d.ts +134 -0
  54. package/dist/crdt/placement/slots.js +152 -0
  55. package/dist/crdt/providers/room.d.ts +16 -2
  56. package/dist/crdt/providers/room.js +64 -17
  57. package/dist/crdt/providers/websocket.js +5 -3
  58. package/dist/crdt/purge.d.ts +17 -8
  59. package/dist/crdt/purge.js +236 -126
  60. package/dist/crdt/restore.d.ts +2 -2
  61. package/dist/crdt/restore.js +18 -34
  62. package/dist/crdt/schema.d.ts +22 -21
  63. package/dist/crdt/schema.js +29 -23
  64. package/dist/crdt/storage.d.ts +1 -1
  65. package/dist/crdt/storage.js +1 -1
  66. package/dist/crdt/structs.d.ts +15 -15
  67. package/dist/crdt/structs.js +8 -22
  68. package/dist/crdt/text/index/cache.js +4 -0
  69. package/dist/crdt/text/index/claims.js +12 -0
  70. package/dist/crdt/text/index/fold.d.ts +2 -3
  71. package/dist/crdt/text/index/fold.js +194 -44
  72. package/dist/crdt/text/index/layout.js +5 -8
  73. package/dist/crdt/text/index/placement.d.ts +26 -18
  74. package/dist/crdt/text/index/placement.js +404 -138
  75. package/dist/crdt/text/index/records.d.ts +3 -3
  76. package/dist/crdt/text/index/records.js +24 -31
  77. package/dist/crdt/text/index/shared.d.ts +11 -3
  78. package/dist/crdt/text/index/shared.js +12 -3
  79. package/dist/crdt/text/index/state.d.ts +21 -17
  80. package/dist/crdt/text/index/state.js +15 -17
  81. package/dist/crdt/text/index/streams.d.ts +10 -5
  82. package/dist/crdt/text/index/streams.js +78 -27
  83. package/dist/crdt/text/items.d.ts +10 -3
  84. package/dist/crdt/text/items.js +7 -0
  85. package/dist/crdt/text/model.d.ts +77 -62
  86. package/dist/crdt/text/model.js +84 -169
  87. package/dist/crdt/text/ranges.d.ts +1 -1
  88. package/dist/crdt/text/ranges.js +3 -3
  89. package/dist/crdt/text/rows.d.ts +13 -7
  90. package/dist/crdt/text/rows.js +24 -7
  91. package/dist/crdt/text/runs.d.ts +17 -12
  92. package/dist/crdt/text/runs.js +24 -7
  93. package/dist/crdt/text/streams.d.ts +26 -13
  94. package/dist/crdt/text/streams.js +41 -14
  95. package/dist/crdt/vendor/yjs/UPSTREAM.md +568 -21
  96. package/dist/crdt/vendor/yjs/dts/global.d.ts +2 -0
  97. package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
  98. package/dist/crdt/vendor/yjs/dts/structs/AbstractStruct.d.ts +7 -0
  99. package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +194 -10
  100. package/dist/crdt/vendor/yjs/dts/utils/Doc.d.ts +6 -3
  101. package/dist/crdt/vendor/yjs/dts/utils/StructStore.d.ts +5 -18
  102. package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +15 -1
  103. package/dist/crdt/vendor/yjs/dts/utils/UpdateDecoder.d.ts +14 -6
  104. package/dist/crdt/vendor/yjs/dts/utils/UpdateEncoder.d.ts +33 -6
  105. package/dist/crdt/vendor/yjs/dts/utils/ids.d.ts +1 -65
  106. package/dist/crdt/vendor/yjs/dts/utils/marks.d.ts +6 -1
  107. package/dist/crdt/vendor/yjs/dts/ynode.d.ts +43 -7
  108. package/dist/crdt/vendor/yjs/global.d.ts +2 -0
  109. package/dist/crdt/vendor/yjs/src/index.js +1 -1
  110. package/dist/crdt/vendor/yjs/src/structs/AbstractStruct.js +10 -0
  111. package/dist/crdt/vendor/yjs/src/structs/Item.js +610 -51
  112. package/dist/crdt/vendor/yjs/src/utils/BlockSet.js +18 -9
  113. package/dist/crdt/vendor/yjs/src/utils/Doc.js +5 -3
  114. package/dist/crdt/vendor/yjs/src/utils/StructStore.js +5 -0
  115. package/dist/crdt/vendor/yjs/src/utils/Transaction.js +36 -13
  116. package/dist/crdt/vendor/yjs/src/utils/UpdateDecoder.js +88 -18
  117. package/dist/crdt/vendor/yjs/src/utils/UpdateEncoder.js +119 -32
  118. package/dist/crdt/vendor/yjs/src/utils/encoding.js +13 -1
  119. package/dist/crdt/vendor/yjs/src/utils/ids.js +7 -7
  120. package/dist/crdt/vendor/yjs/src/utils/marks.js +193 -8
  121. package/dist/crdt/vendor/yjs/src/utils/redo.js +1 -1
  122. package/dist/crdt/vendor/yjs/src/utils/transaction-helpers.js +1 -1
  123. package/dist/crdt/vendor/yjs/src/utils/updates.js +3 -3
  124. package/dist/crdt/vendor/yjs/src/ynode.js +177 -12
  125. package/dist/edytor.svelte.js +4 -1
  126. package/dist/events/beforeInputCommands.js +2 -2
  127. package/dist/events/onPageHide.d.ts +3 -0
  128. package/dist/events/onPageHide.js +16 -0
  129. package/dist/export/forms.d.ts +25 -25
  130. package/dist/surface/observer.svelte.js +2 -2
  131. package/dist/surface/projector.svelte.d.ts +15 -3
  132. package/dist/surface/projector.svelte.js +59 -8
  133. package/package.json +2 -2
  134. package/dist/crdt/placement/rank.d.ts +0 -87
  135. package/dist/crdt/placement/rank.js +0 -332
  136. package/dist/crdt/placement/source.d.ts +0 -43
  137. package/dist/crdt/placement/source.js +0 -66
  138. package/dist/crdt/text/delete-index.d.ts +0 -129
  139. package/dist/crdt/text/delete-index.js +0 -365
  140. package/dist/crdt/text/deletes.d.ts +0 -76
  141. package/dist/crdt/text/deletes.js +0 -362
  142. package/dist/crdt/text/index/anchored.d.ts +0 -17
  143. package/dist/crdt/text/index/anchored.js +0 -197
@@ -1,362 +0,0 @@
1
- import { CONTENT_NODE, HORIZON_ROOT, INLINE_NODE, RESTORED_ROOT, TEXT_DELETES_ROOT } from '../schema.js';
2
- import { attrItems, clientsOf, dropSearchMarkers, engineOps, firstItem, keepFromCollection, nextClock, onTransaction, stepsOf, structAt, walkIdSet } from '../structs.js';
3
- import { clip, decode, deleteIndex, encode, FOLD_SPANS, keyOf, mergeSpans, overlaps } from './delete-index.js';
4
- const REACT = Symbol('edytor.text-deletes');
5
- const states = new WeakMap();
6
- export const bindDeletes = (Y) => {
7
- const { idSet, each, rootSpans, members, liveRoots, shown, indexOf, leaves, holders, minus, parts, addCopies, indexRecord, indexMark, recordsIn, sync } = deleteIndex(Y);
8
- const ops = engineOps(Y);
9
- const attach = (doc) => {
10
- let s = states.get(doc);
11
- if (s !== undefined)
12
- return s;
13
- s = {
14
- doc: doc,
15
- marks: doc.get(TEXT_DELETES_ROOT),
16
- restored: doc.get(RESTORED_ROOT),
17
- horizon: doc.get(HORIZON_ROOT),
18
- held: new Map(),
19
- holds: new Map(),
20
- copies: new Map(),
21
- byRoot: new Map(),
22
- byOrigin: new Map(),
23
- copyKeys: new Set(),
24
- policies: new Set(),
25
- lastMark: null
26
- };
27
- states.set(doc, s);
28
- const st = s;
29
- const records = (node, fn) => {
30
- for (let it = firstItem(node); it; it = it.right)
31
- if (!it.deleted)
32
- for (const [r, bytes] of recordsIn(it, null))
33
- fn(r, bytes);
34
- };
35
- attrItems(st.restored).forEach((it) => {
36
- // A record the purge deleted names nothing any more.
37
- if (!it.deleted)
38
- for (const [r, bytes] of recordsIn(it, null))
39
- indexRecord(st, r, bytes);
40
- });
41
- records(st.marks, (r, bytes) => indexMark(st, r, bytes));
42
- // A deleted copy may have to be copied again by any replica (holds,
43
- // duplicates, pending characters): garbage collection keeps its content.
44
- const copy = (it) => it != null &&
45
- (st.copies.get(it.id.client) ?? []).some((e) => overlaps(e, { k: it.id.clock, n: it.length }));
46
- // (an inline atom copy keeps its attributes too)
47
- keepFromCollection(doc, (it) => !copy(it) && !copy(it.parent?._item));
48
- onTransaction(doc, 'afterTransaction', (tr) => react(st, tr));
49
- return st;
50
- };
51
- // ── the writes ─────────────────────────────────────────────────────
52
- /** Delete the live ids `live` (split at their edges); `into` a step, kept for its undo. */
53
- const remove = (tr, live, into, keep = into !== undefined) => {
54
- if (live.length === 0)
55
- return;
56
- each(tr, live, (it) => {
57
- if (it.deleted)
58
- return;
59
- it.delete(tr);
60
- dropSearchMarkers(it.parent);
61
- if (keep)
62
- it.keep = true;
63
- });
64
- if (into !== undefined)
65
- ops.insertInto(into.deletes, idSet(live));
66
- };
67
- /** Restore root span `r` by copying its leaves; writes the restoration record. */
68
- const restore = (s, p, tr, r) => {
69
- const rows = [];
70
- const copies = [];
71
- for (const leaf of leaves(s, r)) {
72
- const items = [];
73
- each(tr, [leaf], (it) => void (it.deleted && items.push(it)));
74
- for (const it of items) {
75
- // A leaf has no copy: a local `redone` link left by an earlier
76
- // restoration points at a copy this lineage no longer shows.
77
- it.redone = null;
78
- const copy = ops.redo(tr, it, new Set([it]));
79
- if (copy === null)
80
- continue;
81
- dropSearchMarkers(copy.parent);
82
- // An inline atom comes back with its attributes: they were deleted with
83
- // it and are re-created into the copy (their parent's `redone`).
84
- const attrs = it.content.type?._map;
85
- if (attrs !== undefined) {
86
- const kids = [...attrs.values()].filter((c) => c.deleted);
87
- for (const kid of kids)
88
- ops.redo(tr, kid, new Set(kids));
89
- }
90
- const e = {
91
- c: copy.id.client,
92
- k: copy.id.clock,
93
- n: it.length,
94
- rc: r.c,
95
- rk: leaf.rk + it.id.clock - leaf.k,
96
- oc: it.id.client,
97
- ok: it.id.clock
98
- };
99
- rows.push([e.k, e.n, e.rc, e.rk, e.oc, e.ok]);
100
- copies.push(e);
101
- }
102
- }
103
- if (rows.length === 0)
104
- return;
105
- // One key per record (its own id): a map entry depends on nothing, so
106
- // the record integrates with the copies it names, before collection.
107
- const next = nextClock(s.doc, s.doc.clientID);
108
- s.restored.setAttr(`${s.doc.clientID.toString(36)}.${next.toString(36)}`, encode(rows));
109
- addCopies(s, copies);
110
- };
111
- /**
112
- * Keep root spans `roots` visible for `p`: restore what nothing shows and
113
- * no mark (but those `skip` names) holds; return the rest, which `p`
114
- * watches — unless this replica's own copy shows it. `done` collects what
115
- * this pass restored (once per character).
116
- */
117
- const settle = (s, p, tr, roots, skip, done = []) => {
118
- const watch = [];
119
- for (const r of roots)
120
- for (const part of minus(r, done).flatMap((x) => parts(s, x))) {
121
- const live = shown(s, [part]);
122
- if (live.length > 0) {
123
- if (live.some((l) => l.c !== s.doc.clientID))
124
- watch.push(part);
125
- }
126
- else if (holders(s, part, skip).length > 0)
127
- watch.push(part);
128
- else {
129
- restore(s, p, tr, part);
130
- done.push(part);
131
- }
132
- }
133
- return watch;
134
- };
135
- /** The undo step of this replica that wrote `mark`. */
136
- const stepOf = (s, mark) => {
137
- for (const p of s.policies)
138
- for (const step of stepsOf(p.um().undoStack))
139
- if (step.inserts.has(mark.client, mark.clock))
140
- return step;
141
- return undefined;
142
- };
143
- /** This replica's live marks hide what the copies `copies` show, in their steps. */
144
- const hold = (s, tr, copies) => {
145
- for (const e of copies) {
146
- const r = { c: e.rc, k: e.rk, n: e.n };
147
- for (const mark of holders(s, r, (m) => m.client !== s.doc.clientID)) {
148
- const roots = (s.holds.get(keyOf(mark)) ?? []).flatMap((h) => {
149
- const lo = Math.max(h.k, r.k);
150
- const hi = Math.min(h.k + h.n, r.k + r.n);
151
- return h.c === r.c && hi > lo ? [{ c: r.c, k: lo, n: hi - lo }] : [];
152
- });
153
- remove(tr, shown(s, roots), stepOf(s, mark));
154
- }
155
- }
156
- };
157
- /**
158
- * Delete this replica's copies of characters another copy shows further
159
- * left (and watch them). The leftmost copy wins: a copy sits right before
160
- * the member it restored, so keeping the leftmost keeps restored text in
161
- * order whichever restorations overlapped.
162
- */
163
- const dedupe = (s, tr, copies) => {
164
- const doomed = [];
165
- for (const e of copies) {
166
- const ms = members(s, { c: e.rc, k: e.rk, n: e.n });
167
- const live = ms.flatMap((m) => liveRoots(s, m).map((p) => clip(m, p.k, p.k + p.n)));
168
- for (const o of live) {
169
- if (o.c !== s.doc.clientID)
170
- continue;
171
- for (let rk = o.rk; rk < o.rk + o.n; rk++) {
172
- const k = o.k + rk - o.rk;
173
- const at = indexOf(s, o.c, k);
174
- const left = live.some((m) => m !== o && m.rk <= rk && rk < m.rk + m.n && indexOf(s, m.c, m.k + rk - m.rk) < at);
175
- if (left)
176
- doomed.push({ c: o.c, k, n: 1 });
177
- }
178
- }
179
- }
180
- remove(tr, doomed, undefined, true);
181
- const [p] = s.policies;
182
- for (const d of doomed)
183
- p.watch.push(...rootSpans(s, d));
184
- };
185
- /** Whether `tr` deleted a member of a character some history watches. */
186
- const watchedDied = (s, tr) => [...s.policies].some((p) => p.watch.some((w) => members(s, w).some((m) => tr.deleteSet.intersects(m.c, m.k, m.n))));
187
- /** After every commit: index its records; after a remote one, keep this replica's facts. */
188
- const react = (s, tr) => {
189
- const fresh = sync(s, tr);
190
- // A purge deletes marks past the horizon: nothing comes back for
191
- // it, and a pending character only they held stays deleted.
192
- if (tr.changed.has(s.horizon)) {
193
- for (const p of s.policies)
194
- p.watch = p.watch.filter((w) => holders(s, w).length > 0 || shown(s, [w]).length > 0);
195
- return;
196
- }
197
- if (tr.local || s.policies.size === 0)
198
- return;
199
- if (fresh.copies.length === 0 && !fresh.released && !watchedDied(s, tr))
200
- return;
201
- s.doc.transact((t) => {
202
- hold(s, t, fresh.copies);
203
- dedupe(s, t, fresh.copies);
204
- for (const p of s.policies)
205
- p.watch = settle(s, p, t, p.watch);
206
- }, REACT);
207
- };
208
- /**
209
- * The spans of this writer's last record merged with `spans`, when a
210
- * delete may fold into it — it is the records list's last element, live,
211
- * this replica's, written in this transaction or in the step still
212
- * capturing (`open`: the history steps whose capture group is open), or
213
- * anywhere when no history records steps here; and the merged record
214
- * stays small ({@link FOLD_SPANS}). `null`: write a record of its own.
215
- */
216
- const foldInto = (s, tr, open, spans) => {
217
- const at = s.lastMark;
218
- if (at === null || at.c !== s.doc.clientID)
219
- return null;
220
- const last = structAt(Y, clientsOf(s.doc).get(at.c) ?? [], at.k);
221
- if (last === null ||
222
- last.deleted ||
223
- last.parent !== s.marks ||
224
- last.right !== null ||
225
- last.id.clock + last.length - 1 !== at.k)
226
- return null;
227
- const same = tr?.insertSet.has(at.c, at.k) === true ||
228
- open.some((ids) => ids.has(at.c, at.k)) ||
229
- s.policies.size === 0;
230
- if (!same)
231
- return null;
232
- const [, bytes] = recordsIn(last, null).at(-1) ?? [];
233
- if (bytes === undefined)
234
- return null;
235
- const merged = mergeSpans([...decode(bytes, 3).map(([c, k, n]) => ({ c, k, n })), ...spans]);
236
- return merged.length <= FOLD_SPANS ? merged : null;
237
- };
238
- /**
239
- * Delete this replica's last record (the list's tail, {@link foldInto})
240
- * and append `bytes` right after it, by item: no index walk over the
241
- * list's tombstones. The list's search markers are dropped (their
242
- * positions no longer hold).
243
- */
244
- const replaceLast = (s, tr, bytes) => {
245
- const at = s.lastMark;
246
- const tail = ops.cleanStart(tr, at.c, at.k);
247
- tail.delete(tr);
248
- dropSearchMarkers(s.marks);
249
- const client = s.doc.clientID;
250
- ops.appendAfter(tr, tail, s.marks, client, nextClock(s.doc, client), [bytes]);
251
- };
252
- /** The text units `ids` names, as spans. */
253
- const unitsIn = (s, ids) => {
254
- const out = [];
255
- walkIdSet(Y, s.doc, ids, (st, lo, hi) => {
256
- if (hi > lo && isUnit(st))
257
- out.push({ c: st.id.client, k: lo, n: hi - lo });
258
- });
259
- return out;
260
- };
261
- /** A text unit a mark can hold: a character or inline atom of a backing text. */
262
- const isUnit = (it) => it.parentSub === null &&
263
- it.parent?.name === CONTENT_NODE &&
264
- (typeof it.content?.str === 'string' || it.content?.type?.name === INLINE_NODE);
265
- return {
266
- /** The history's extra scope: the marks (an undo removes its own). */
267
- scope: (doc) => attach(doc).marks,
268
- /**
269
- * Mark `spans` (the characters a text delete just deleted, inside its
270
- * transaction) as this writer's, and hide their other visible copies.
271
- */
272
- markDeleted: (doc, deleted) => {
273
- const s = attach(doc);
274
- // Text inserted and deleted within one open capture group never comes
275
- // back by that step's undo (the engine skips such a step): no mark.
276
- const fresh = [...s.policies].flatMap((p) => {
277
- const um = p.um();
278
- const top = stepsOf(um.undoStack).at(-1);
279
- const open = top !== undefined &&
280
- !um.undoing &&
281
- !um.redoing &&
282
- um.lastChange > 0 &&
283
- Date.now() - um.lastChange < um.captureTimeout;
284
- return open ? [top.inserts] : [];
285
- });
286
- const tr = s.doc._transaction;
287
- const spans = deleted.flatMap((sp) => {
288
- const out = [];
289
- for (let k = sp.k; k < sp.k + sp.n; k++) {
290
- if (fresh.some((ids) => ids.has(sp.c, k)))
291
- continue;
292
- const last = out[out.length - 1];
293
- if (last !== undefined && last.k + last.n === k)
294
- last.n++;
295
- else
296
- out.push({ c: sp.c, k, n: 1 });
297
- }
298
- return out;
299
- });
300
- if (spans.length === 0)
301
- return;
302
- // A delete in the step that wrote this writer's last record
303
- // (a backspace run) folds into it, its spans merged — one record per
304
- // step, not per keystroke. The record is replaced (deleted and
305
- // written again), never edited, so an undo of the step still takes
306
- // back exactly what the step deleted.
307
- const folded = tr === null ? null : foldInto(s, tr, fresh, spans);
308
- const k = nextClock(s.doc, s.doc.clientID);
309
- const bytes = encode((folded ?? spans).map((sp) => [sp.c, sp.k, sp.n]));
310
- if (folded === null)
311
- s.marks.insert(s.marks.length, [bytes]);
312
- else
313
- replaceLast(s, tr, bytes);
314
- s.lastMark = { c: s.doc.clientID, k };
315
- if (tr !== null)
316
- remove(tr, shown(s, spans.flatMap((sp) => rootSpans(s, sp))));
317
- },
318
- /** A doc's index of marks and copies, and the delete the purge writes (`purge.ts`). */
319
- state: attach,
320
- remove,
321
- /** The restore and apply rules of a history on `doc` (`doc/history.ts`). */
322
- history: (doc, um) => {
323
- const s = attach(doc);
324
- const p = { um, watch: [], tr: null, units: [] };
325
- s.policies.add(p);
326
- const current = () => {
327
- const tr = s.doc._transaction;
328
- if (p.tr !== tr)
329
- Object.assign(p, { tr, units: [] });
330
- return p;
331
- };
332
- return {
333
- /** Text units are restored by `onApply`, character by character. */
334
- restoreFilter: (item) => {
335
- const it = item;
336
- if (!isUnit(it))
337
- return true;
338
- current().units.push({ c: it.id.client, k: it.id.clock, n: it.length });
339
- return false;
340
- },
341
- onApply: (transaction, stackItem) => {
342
- const tr = transaction;
343
- const q = current();
344
- const step = stackItem;
345
- const fresh = sync(s, tr);
346
- const roots = q.units.flatMap((u) => rootSpans(s, u));
347
- const ours = (m) => step.inserts.has(m.client, m.clock);
348
- q.watch.push(...settle(s, q, tr, roots, ours));
349
- // A redo writes its marks again: they hide what shows their characters.
350
- const held = fresh.marks.flatMap((m) => s.holds.get(keyOf(m)) ?? []);
351
- remove(tr, shown(s, held));
352
- // Text the step deleted that no mark of it holds (an undo of typing) is
353
- // this writer's delete too.
354
- const bare = unitsIn(s, tr.deleteSet).filter((u) => rootSpans(s, u).some((r) => !held.some((h) => h.c === r.c && h.k <= r.k && r.k + r.n <= h.k + h.n)));
355
- if (bare.length > 0)
356
- s.marks.insert(s.marks.length, [encode(bare.map((u) => [u.c, u.k, u.n]))]);
357
- q.units = [];
358
- }
359
- };
360
- }
361
- };
362
- };
@@ -1,17 +0,0 @@
1
- /**
2
- * Anchored merge claims (`merge.claim.anchor`): the effective claimer of
3
- * each claim a split its writer did not see moves to the piece that ends
4
- * its region, re-decided after each fold's texts.
5
- */
6
- import type { BlockId } from '../../placement/model.js';
7
- import { type Claim } from '../model.js';
8
- import type { IndexClaims } from './claims.js';
9
- import type { FoldCtx, IndexState } from './state.js';
10
- /** The anchored merge claims of one doc's index, over its state and claim graph. */
11
- export declare const indexAnchored: (ix: IndexState & IndexClaims) => {
12
- sameClaims: (x: readonly Claim[], y: readonly Claim[]) => boolean;
13
- effectiveClaims: (b: BlockId, list: readonly Claim[]) => Claim[];
14
- targetOf: (holder: BlockId, c: Claim) => [BlockId];
15
- applyRetargets: (ctx: FoldCtx | null) => void;
16
- };
17
- export type IndexAnchored = ReturnType<typeof indexAnchored>;
@@ -1,197 +0,0 @@
1
- import { storeOf } from '../../structs.js';
2
- import { cmpStamp, isBoundary } from '../model.js';
3
- import { gapOfItem, headOf, segmentOfCut, segmentOfGap } from '../rows.js';
4
- import { addTo, dropFrom } from './shared.js';
5
- /** The anchored merge claims of one doc's index, over its state and claim graph. */
6
- export const indexAnchored = (ix) => {
7
- const { doc, blocks, rows, streamIx, homeOfText, attachOf, foreignOf, anchoredIn, retargets, unresolved, reclaim, noteClaims, invalidateBlock } = ix;
8
- // ── anchored merge claims (`merge.claim.anchor`) ──
9
- // A claim written since 0.1.0-next.26 carries the end of its holder's
10
- // stream as its writer saw it (`a`: the last unit, `r`: the item after
11
- // it). A split the writer did not see moves it to the piece that ends
12
- // that region: its EFFECTIVE claimer is the block of the segment just
13
- // before `r` (the text's last segment when `r` is null), if that is
14
- // the holder's segment or one after it in the holder's row. Records
15
- // carry their list (`listClaims`) and their effective claims
16
- // (`claims`: their own that stay, then those other holders' anchors
17
- // move to them, by stamp); every reader of claims reads the effective
18
- // ones. Targets change only with a row's cuts: re-decided after each
19
- // fold's texts for the holders whose record changed and those anchored
20
- // in a re-placed row.
21
- const anchorHomes = new Map();
22
- const sameClaims = (x, y) => x.length === y.length &&
23
- x.every((c, i) => c.m === y[i].m &&
24
- c.holder === y[i].holder &&
25
- c.seqIndex === y[i].seqIndex &&
26
- cmpStamp(c.stamp, y[i].stamp) === 0);
27
- /** `b`'s effective claims: its own that stay with it, then the others' moved to it, by stamp. */
28
- function effectiveClaims(b, list) {
29
- const targets = attachOf.get(b);
30
- const own = targets === undefined ? list : list.filter((_, i) => (targets[i] ?? b) === b);
31
- const holders = foreignOf.get(b);
32
- if (holders === undefined)
33
- return own;
34
- const moved = [];
35
- for (const h of holders) {
36
- const hr = blocks.get(h);
37
- const ts = attachOf.get(h);
38
- if (hr === undefined || ts === undefined)
39
- continue;
40
- (hr.listClaims ?? hr.claims).forEach((c, i) => {
41
- if (ts[i] === b)
42
- moved.push({ ...c, holder: h });
43
- });
44
- }
45
- moved.sort((x, y) => cmpStamp(x.stamp, y.stamp));
46
- return [...own, ...moved];
47
- }
48
- const store = storeOf(doc);
49
- /** The struct holding unit `ref`, and the unit's offset in it (`null`: none held). */
50
- const unitAt = (ref) => {
51
- if (store.getClock(ref.c) <= ref.k)
52
- return null;
53
- const s = store.getItem({ client: ref.c, clock: ref.k });
54
- if (s?.parent === undefined || s.parent === null)
55
- return null;
56
- return [s, ref.k - s.id.clock];
57
- };
58
- /** The gap of row `row` unit `off` of `it` lies in (`-1`: stale, the walk met an unknown boundary). */
59
- const gapOfUnit = (row, it, off) => {
60
- const arr = it.content?.arr;
61
- if (!it.deleted && it.countable !== false && arr !== undefined) {
62
- const key = (k) => row.keyIndex.get(`${it.id.client}:${it.id.clock + k}`);
63
- for (let k = off; k >= 0; k--)
64
- if (isBoundary(arr[k])) {
65
- const j = key(k);
66
- if (j === undefined)
67
- return -1;
68
- // The unit itself, a boundary: the units before it.
69
- return k === off ? j : j + 1;
70
- }
71
- for (let k = off + 1; k < it.length; k++)
72
- if (isBoundary(arr[k])) {
73
- const j = key(k);
74
- return j === undefined ? -1 : j;
75
- }
76
- }
77
- return gapOfItem(row, it);
78
- };
79
- /** The effective claimer of `holder`'s claim `c`. */
80
- const targetOf = (holder, c) => {
81
- if (c.a === undefined)
82
- return [holder];
83
- const at = streamIx.get(holder);
84
- const row = at && rows.get(at.home);
85
- if (row === undefined)
86
- return [holder];
87
- const a = unitAt(c.a);
88
- if (a === null || a[0].parent !== row.text)
89
- return [holder];
90
- const k0 = segmentOfCut(row, at.cut);
91
- let gap = row.bounds.length;
92
- if (c.r != null) {
93
- const r = unitAt(c.r);
94
- if (r === null || r[0].parent !== row.text)
95
- return [holder];
96
- gap = gapOfUnit(row, r[0], r[1]);
97
- }
98
- if (k0 < 0 || gap < 0)
99
- return [holder];
100
- const k = segmentOfGap(row, gap);
101
- const target = k < k0 ? null : headOf(row, k);
102
- return [target ?? holder];
103
- };
104
- /**
105
- * The rows whose placement may move `holder`'s anchored claim `c`: its
106
- * stream's and its anchor's text's. `null` while it is unresolved — the
107
- * holder has no stream, or an anchor names a unit not integrated yet
108
- * (a payload is no dependency: the claim can arrive first) — and
109
- * re-decided at every fold.
110
- */
111
- const watchOf = (holder, c) => {
112
- if (c.a !== undefined && unitAt(c.a) === null)
113
- return null;
114
- if (c.r != null && unitAt(c.r) === null)
115
- return null;
116
- const homes = [];
117
- const own = streamIx.get(holder)?.home;
118
- if (own !== undefined)
119
- homes.push(own);
120
- const a = c.a === undefined ? null : unitAt(c.a);
121
- const text = a === null ? undefined : homeOfText.get(a[0].parent);
122
- if (text !== undefined && text !== own)
123
- homes.push(text);
124
- return homes.length === 0 ? null : homes;
125
- };
126
- /**
127
- * The retarget pass: re-decide the targets of the holders queued, move
128
- * their claims between effective claimers, and re-read the claims of
129
- * every block that gained or lost one (its claim facts and readers).
130
- */
131
- const applyRetargets = (ctx) => {
132
- for (const h of retargets) {
133
- const rec = blocks.get(h);
134
- const before = attachOf.get(h);
135
- for (const home of anchorHomes.get(h) ?? [])
136
- dropFrom(anchoredIn, home, h);
137
- anchorHomes.delete(h);
138
- let after;
139
- unresolved.delete(h);
140
- for (const [i, c] of (rec?.listClaims ?? []).entries()) {
141
- if (c.a === undefined)
142
- continue;
143
- const [t] = targetOf(h, c);
144
- const watch = watchOf(h, c);
145
- if (watch === null)
146
- unresolved.add(h);
147
- else
148
- for (const home of watch) {
149
- addTo(anchoredIn, home, h);
150
- addTo(anchorHomes, h, home);
151
- }
152
- if (t !== h)
153
- (after ??= new Array((rec.listClaims ?? []).length).fill(h))[i] = t;
154
- }
155
- const was = new Set(before ?? []);
156
- const now = new Set(after ?? []);
157
- for (const t of was)
158
- if (t !== h && !now.has(t))
159
- dropFrom(foreignOf, t, h);
160
- for (const t of now)
161
- if (t !== h)
162
- addTo(foreignOf, t, h);
163
- if (after === undefined)
164
- attachOf.delete(h);
165
- else
166
- attachOf.set(h, after);
167
- if (before !== undefined || after !== undefined) {
168
- reclaim.add(h);
169
- for (const t of was)
170
- reclaim.add(t);
171
- for (const t of now)
172
- reclaim.add(t);
173
- }
174
- }
175
- retargets.clear();
176
- for (const b of reclaim) {
177
- const rec = blocks.get(b);
178
- if (rec === undefined)
179
- continue;
180
- const before = rec.claims;
181
- const after = effectiveClaims(b, rec.listClaims ?? before);
182
- if (sameClaims(before, after))
183
- continue;
184
- rec.claims = after;
185
- noteClaims(b, before, after);
186
- if (ctx !== null)
187
- invalidateBlock(b, ctx, before);
188
- }
189
- reclaim.clear();
190
- };
191
- return {
192
- sameClaims,
193
- effectiveClaims,
194
- targetOf,
195
- applyRetargets
196
- };
197
- };