edytor 4.0.0 → 5.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 (147) hide show
  1. package/README.md +2 -2
  2. package/dist/block/block.svelte.js +6 -21
  3. package/dist/block/inlineBlock.svelte.js +4 -6
  4. package/dist/clipboard/moveText.js +6 -8
  5. package/dist/cloudflare/DocumentRoom.d.ts +10 -3
  6. package/dist/cloudflare/index.d.ts +1 -1
  7. package/dist/cloudflare/room/access.d.ts +8 -3
  8. package/dist/cloudflare/room/access.js +44 -20
  9. package/dist/cloudflare/room/admission.js +28 -5
  10. package/dist/cloudflare/room/comments.js +28 -1
  11. package/dist/cloudflare/room/context.d.ts +115 -106
  12. package/dist/cloudflare/room/context.js +10 -3
  13. package/dist/cloudflare/room/forged.d.ts +14 -0
  14. package/dist/cloudflare/room/forged.js +30 -2
  15. package/dist/cloudflare/room/frames.d.ts +4 -0
  16. package/dist/cloudflare/room/frames.js +5 -1
  17. package/dist/cloudflare/room/purge.js +2 -1
  18. package/dist/cloudflare/room/replicas.d.ts +4 -2
  19. package/dist/cloudflare/room/replicas.js +8 -3
  20. package/dist/cloudflare/room/suggest.d.ts +7 -0
  21. package/dist/cloudflare/room/suggest.js +143 -0
  22. package/dist/cloudflare/routeDocumentSocket.d.ts +10 -1
  23. package/dist/cloudflare/routeDocumentSocket.js +5 -3
  24. package/dist/components/Content.svelte +9 -1
  25. package/dist/crdt/anchors.js +3 -2
  26. package/dist/crdt/attribution/attribution.d.ts +3 -0
  27. package/dist/crdt/attribution/attribution.js +3 -3
  28. package/dist/crdt/doc/capability.js +9 -13
  29. package/dist/crdt/doc/funnel.d.ts +1 -1
  30. package/dist/crdt/doc/funnel.js +5 -2
  31. package/dist/crdt/doc/gate.d.ts +1 -1
  32. package/dist/crdt/doc/history.d.ts +6 -0
  33. package/dist/crdt/doc/history.js +77 -56
  34. package/dist/crdt/doc/layout.js +5 -5
  35. package/dist/crdt/doc/meta.js +6 -7
  36. package/dist/crdt/doc/reads.d.ts +2 -2
  37. package/dist/crdt/doc/reads.js +18 -28
  38. package/dist/crdt/doc/steps.js +4 -4
  39. package/dist/crdt/doc/suggest.d.ts +167 -0
  40. package/dist/crdt/doc/suggest.js +579 -0
  41. package/dist/crdt/document.d.ts +10 -1
  42. package/dist/crdt/document.js +19 -11
  43. package/dist/crdt/edytor-doc.d.ts +355 -333
  44. package/dist/crdt/edytor-doc.js +125 -82
  45. package/dist/crdt/engine-api.d.ts +8 -2
  46. package/dist/crdt/engine.d.ts +2 -1
  47. package/dist/crdt/engine.js +3 -1
  48. package/dist/crdt/flow.d.ts +1 -1
  49. package/dist/crdt/flow.js +4 -10
  50. package/dist/crdt/index.d.ts +111 -105
  51. package/dist/crdt/index.js +3 -0
  52. package/dist/crdt/operations.d.ts +12 -3
  53. package/dist/crdt/parts.d.ts +43 -0
  54. package/dist/crdt/parts.js +44 -0
  55. package/dist/crdt/placement/display.d.ts +1 -1
  56. package/dist/crdt/placement/display.js +3 -3
  57. package/dist/crdt/placement/keys.d.ts +5 -0
  58. package/dist/crdt/placement/keys.js +5 -0
  59. package/dist/crdt/placement/resolve.js +2 -1
  60. package/dist/crdt/protocol.d.ts +1 -1
  61. package/dist/crdt/protocol.js +1 -1
  62. package/dist/crdt/protocols/auth.d.ts +11 -1
  63. package/dist/crdt/protocols/auth.js +17 -1
  64. package/dist/crdt/protocols/comments.d.ts +16 -1
  65. package/dist/crdt/protocols/comments.js +9 -1
  66. package/dist/crdt/providers/index.d.ts +6 -4
  67. package/dist/crdt/providers/index.js +3 -1
  68. package/dist/crdt/providers/websocket.d.ts +6 -4
  69. package/dist/crdt/providers/websocket.js +9 -4
  70. package/dist/crdt/purge.d.ts +7 -1
  71. package/dist/crdt/purge.js +32 -2
  72. package/dist/crdt/restore.d.ts +2 -1
  73. package/dist/crdt/restore.js +9 -0
  74. package/dist/crdt/schema.d.ts +9 -1
  75. package/dist/crdt/schema.js +13 -3
  76. package/dist/crdt/structs.d.ts +2 -0
  77. package/dist/crdt/structs.js +2 -0
  78. package/dist/crdt/text/index/claims.js +1 -23
  79. package/dist/crdt/text/index/layout.d.ts +2 -4
  80. package/dist/crdt/text/index/layout.js +9 -22
  81. package/dist/crdt/text/index/placement.js +21 -24
  82. package/dist/crdt/text/index/shared.js +17 -14
  83. package/dist/crdt/text/model.d.ts +10 -0
  84. package/dist/crdt/text/model.js +8 -1
  85. package/dist/crdt/text/runs.d.ts +34 -30
  86. package/dist/crdt/vendor/yjs/UPSTREAM.md +83 -0
  87. package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
  88. package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +6 -3
  89. package/dist/crdt/vendor/yjs/dts/ynode.d.ts +13 -2
  90. package/dist/crdt/vendor/yjs/src/index.js +1 -1
  91. package/dist/crdt/vendor/yjs/src/structs/Item.js +44 -5
  92. package/dist/crdt/vendor/yjs/src/ynode.js +29 -7
  93. package/dist/edytor.svelte.d.ts +1 -1
  94. package/dist/edytor.svelte.js +100 -75
  95. package/dist/events/beforeInputCommands.d.ts +2 -2
  96. package/dist/events/nativeInteractiveControl.d.ts +11 -1
  97. package/dist/events/nativeInteractiveControl.js +41 -1
  98. package/dist/events/onBeforeInput.js +3 -3
  99. package/dist/events/onFocus.js +9 -11
  100. package/dist/export/forms.d.ts +25 -25
  101. package/dist/index.d.ts +4 -2
  102. package/dist/index.js +1 -1
  103. package/dist/labels.d.ts +8 -0
  104. package/dist/labels.js +6 -1
  105. package/dist/plugins/comments/CommentsController.svelte.d.ts +5 -1
  106. package/dist/plugins/comments/CommentsController.svelte.js +14 -4
  107. package/dist/plugins/find/search.js +2 -2
  108. package/dist/plugins/index.d.ts +1 -0
  109. package/dist/plugins/index.js +1 -0
  110. package/dist/plugins/suggestions/SuggestionBars.svelte +40 -23
  111. package/dist/plugins/suggestions/SuggestionBars.svelte.d.ts +3 -1
  112. package/dist/plugins/suggestions/SuggestionRuns.svelte +77 -0
  113. package/dist/plugins/suggestions/SuggestionRuns.svelte.d.ts +9 -0
  114. package/dist/plugins/suggestions/shared.svelte.d.ts +58 -0
  115. package/dist/plugins/suggestions/shared.svelte.js +421 -0
  116. package/dist/plugins/suggestions/suggestionsPlugin.d.ts +15 -4
  117. package/dist/selection/domSelection.d.ts +2 -0
  118. package/dist/selection/domSelection.js +6 -2
  119. package/dist/selection/pointer.svelte.js +75 -48
  120. package/dist/selection/replaceSelection.js +2 -6
  121. package/dist/selection/selection.svelte.d.ts +3 -3
  122. package/dist/selection/selection.svelte.js +39 -32
  123. package/dist/selection/selection.utils.d.ts +14 -0
  124. package/dist/selection/selection.utils.js +28 -3
  125. package/dist/session/commands.js +21 -11
  126. package/dist/session/composition.svelte.d.ts +5 -3
  127. package/dist/session/composition.svelte.js +21 -17
  128. package/dist/session/handles.d.ts +4 -17
  129. package/dist/session/handles.js +11 -37
  130. package/dist/session/navigation.js +6 -9
  131. package/dist/session/selection.d.ts +11 -5
  132. package/dist/session/selection.js +14 -48
  133. package/dist/session/suggestions.svelte.d.ts +38 -9
  134. package/dist/session/suggestions.svelte.js +37 -18
  135. package/dist/session/virtual.d.ts +1 -1
  136. package/dist/surface/attributes.d.ts +5 -0
  137. package/dist/surface/attributes.js +2 -2
  138. package/dist/surface/cells.d.ts +2 -26
  139. package/dist/surface/cells.js +2 -33
  140. package/dist/surface/observer.svelte.d.ts +6 -1
  141. package/dist/surface/observer.svelte.js +128 -81
  142. package/dist/surface/pin.svelte.d.ts +9 -9
  143. package/dist/surface/pin.svelte.js +15 -22
  144. package/dist/surface/projector.svelte.js +8 -14
  145. package/dist/text/text.svelte.js +14 -19
  146. package/dist/themes/notion.css +29 -0
  147. package/package.json +3 -1
@@ -277,6 +277,16 @@ export declare const insertIntoText: (doc: EngineDoc, blocks: unknown, own: Owne
277
277
  * that step's undo still changes nothing.
278
278
  */
279
279
  export declare const deleteRange: (doc: EngineDoc, blocks: unknown, own: Ownership, b: BlockId, offset: number, length: number) => void;
280
+ /**
281
+ * A suggestion's plan being written on a doc (`doc/suggest.ts`): its text
282
+ * deletes propose (fork patch YP32) into its record, `sort` telling each
283
+ * unit's lot (`1` its author's own: held; `2` proposed; `3` already
284
+ * proposed: stepped over).
285
+ */
286
+ export declare const proposing: WeakMap<EngineDoc, {
287
+ into: EngineNode;
288
+ sort: (client: number, clock: number) => number;
289
+ }>;
280
290
  export declare const formatRangeIn: (doc: EngineDoc, blocks: unknown, own: Ownership, b: BlockId, offset: number, length: number, formats: Record<string, unknown>) => void;
281
291
  /**
282
292
  * Delete the content of streams `streams` (the room's purge of a
@@ -198,8 +198,15 @@ export const deleteRange = (doc, blocks, own, b, offset, length) => {
198
198
  const records = doc.get(TEXT_DELETES_ROOT);
199
199
  const fresh = openInserts(doc);
200
200
  for (const r of rangesOf(flatten(b, blocks, own), offset, offset + length))
201
- r.text.delete(r.a, r.b - r.a, records, fresh);
201
+ r.text.delete(r.a, r.b - r.a, records, fresh, proposing.get(doc));
202
202
  };
203
+ /**
204
+ * A suggestion's plan being written on a doc (`doc/suggest.ts`): its text
205
+ * deletes propose (fork patch YP32) into its record, `sort` telling each
206
+ * unit's lot (`1` its author's own: held; `2` proposed; `3` already
207
+ * proposed: stepped over).
208
+ */
209
+ export const proposing = new WeakMap();
203
210
  export const formatRangeIn = (doc, blocks, own, b, offset, length, formats) => {
204
211
  for (const r of rangesOf(flatten(b, blocks, own), offset, offset + length))
205
212
  writeMarks(doc, r.text, r.a, r.b - r.a, formats);
@@ -220,38 +220,42 @@ export type RunView = {
220
220
  debug: RunViewDebug;
221
221
  };
222
222
  /**
223
- * The role table — one answer per kind, which the display and the
224
- * document's guards both ask. `childless`: kinds that display no children
225
- * (void roles) — a child of such a block displays in its slot.
226
- * `island`: a block promoted out of an island kind displays as
227
- * `defaultChild` of its display parent's kind (`null`: the root). `line`:
228
- * an island declared `lines` holds only lines — each direct child displays
229
- * as its line kind and holds no children. `container`: a
230
- * kind that renders no content and is neither void nor an island (a list)
231
- * — a block promoted out of one follows like one promoted out of an island. `rendersContent`: no block that renders content ever shows
232
- * as a kind that does not (its text would vanish). `layout`: a
233
- * layout kind displays only its items, and only two or more (`layout.*`).
234
- * `table`: a table kind displays only its rows, a row only its cells, in
235
- * the table's column order (`table.*`).
223
+ * A kind's answers, read once from its role (the one shape the display and
224
+ * every guard read). `childless`: displays no children (a void role) — a
225
+ * child of such a block displays in its slot. `island`: a block promoted
226
+ * out of one displays as `child` of its display parent's kind.
227
+ * `container`: renders no content and is neither void nor an island (a
228
+ * list) — a block promoted out of one follows like one promoted out of an
229
+ * island. `rendersContent`: no block that renders content ever shows as a
230
+ * kind that does not (its text would vanish). `child`: its default child
231
+ * (the document's default kind for the root, `null`). `line`: an island
232
+ * declared `lines` holds only lines of this kind, which hold no children.
233
+ * `layout`: a layout displays only items of this kind, and only two or
234
+ * more (`layout.*`). `table`: a table displays only rows of this kind, a
235
+ * row only its cells, in the table's column order (`table.*`).
236
236
  */
237
+ export type KindRole = {
238
+ childless: boolean;
239
+ island: boolean;
240
+ container: boolean;
241
+ rendersContent: boolean;
242
+ child: string;
243
+ line?: string;
244
+ layout?: string;
245
+ table?: string;
246
+ };
247
+ /** The kinds the roles declare: line kinds, layout kinds and their item kinds, table kinds and their row kinds (→ cell kind). */
248
+ export type DeclaredKinds = {
249
+ lines: ReadonlySet<string>;
250
+ layouts: readonly string[];
251
+ items: ReadonlySet<string>;
252
+ rows: ReadonlyMap<string, string>;
253
+ cells: ReadonlySet<string>;
254
+ };
255
+ /** The role table: each kind's {@link KindRole} and the declared kinds, both read once until the roles change. */
237
256
  export type DisplayRoles = {
238
- childless: (type: string) => boolean;
239
- island: (type: string) => boolean;
240
- container: (type: string) => boolean;
241
- rendersContent: (type: string) => boolean;
242
- defaultChild: (parentType: string | null) => string;
243
- /** The line kind of an island kind declared `lines` (its `defaultChild`), if any. */
244
- line: (islandType: string) => string | undefined;
245
- /** Every line kind the roles declare, present in the document or not. */
246
- lineKinds: () => Iterable<string>;
247
- /** The item kind of a layout kind (its `defaultChild`), if `type` is one. */
248
- layout: (type: string) => string | undefined;
249
- /** Every layout kind the roles declare, present in the document or not. */
250
- layoutKinds: () => Iterable<string>;
251
- /** The row kind of a table kind (its `defaultChild`), if `type` is one (`table.*`). */
252
- table: (type: string) => string | undefined;
253
- /** Every table kind the roles declare, present in the document or not. */
254
- tableKinds: () => Iterable<string>;
257
+ of: (type: string | null) => KindRole;
258
+ declared: () => DeclaredKinds;
255
259
  };
256
260
  /** The doc's index — built on first use, torn down when the doc is destroyed. */
257
261
  export declare const indexOf: (doc: EngineDoc) => RunView;
@@ -1515,6 +1515,89 @@ Oracle: `src/tests/crdt/attribution/first-write-binding.test.ts` (the first
1515
1515
  local edit's one update carries the binding; a remote update writes none),
1516
1516
  the upstream suite.
1517
1517
 
1518
+ ### YP31 — a hold record inside a nested node holds nothing (`src/structs/Item.js`)
1519
+
1520
+ Reason: edytor's shared suggestions keep, inside each suggestion's record
1521
+ node, the id ranges it proposes to delete and the ranges its reviewers
1522
+ settled: lists of `client, clock, len` ranges that must hide nothing until a
1523
+ reviewer accepts. YP20's hold record is exactly that codec (one unit per
1524
+ range, a left id and a length, consecutive records of one writer merging
1525
+ as typed runs do), but it holds whatever it names wherever it integrates.
1526
+ The study's prototype added a sibling content type that held nothing
1527
+ (content ref 14, `ContentPropose`, a `YNode#propose` writer, about 0.3 KB);
1528
+ the rule here keeps one record type and one reader. Design:
1529
+ `docs/archive/shared-suggestions/design.md`. Edytor's schema generation 11
1530
+ is the cutover (no conversion: a generation-10 replica would hold what a
1531
+ proposal names).
1532
+
1533
+ Patch (new code delimited by `// YP31 begin` / `// YP31 end`, edits marked `// YP31`):
1534
+
1535
+ - **`holding(item)`**: a hold record holds what it names iff its item is an
1536
+ entry of a root's sequence (`item.parent._item === null`: the document's
1537
+ records list, `textdel`). `ContentHold#integrate` and `ContentHold#delete`
1538
+ hold and release only then; inside a nested node's sequence the record is
1539
+ a list of ids: it moves no length, waits in no `pendingHolds`, logs
1540
+ nothing in `transaction.holds`, and its delete (an undo, a reviewer's
1541
+ withdrawal, its node's delete) releases nothing. `YNode#hold(ids)` on a
1542
+ nested node appends one such list entry.
1543
+
1544
+ Plain deletes and the root records list (every upstream test, every YP20
1545
+ row) are untouched: no other code path writes a hold record into a nested
1546
+ node.
1547
+
1548
+ Edytor side: a suggestion's record node (root `suggest`) keeps its writer's
1549
+ proposed deletions and its reviewers' settled ranges as such entries
1550
+ (`crdt/doc/suggest.ts`); accepting a deletion writes a real hold record in
1551
+ the root list.
1552
+
1553
+ Oracle: `src/tests/crdt/yp31-id-lists.test.ts` (written, deleted, merged,
1554
+ split, synced, reloaded, received before the items it names, deleted with
1555
+ its node: nothing hidden or released; the root list still holds), the YP20
1556
+ rows and the shared-suggestion rows, the upstream suite unchanged
1557
+ (`encoding.tests.js` still pins 14 content refs).
1558
+
1559
+ ### YP32 — a delete that proposes (`src/ynode.js`, `src/structs/Item.js`, `src/index.js`)
1560
+
1561
+ Reason: in suggesting mode a text delete writes nothing to the text: the
1562
+ units that are not the suggestion's author's own pending ones are named by
1563
+ an id list in the suggestion's record (YP31), and they stay shown. The
1564
+ plan that deleted them was prepared with them gone, so edytor carried a
1565
+ layer that re-read each later step's offsets over the units the plan had
1566
+ proposed, batched the steps around every proposal and mapped the caret
1567
+ back (`crdt/doc/suggest.ts` `apply`, about 1.4 KB minified). The fork's
1568
+ delete now proposes, and hides what it proposed until the plan ends: the
1569
+ plan's later steps read the offsets they were prepared with, and an
1570
+ insertion at a proposed deletion lands after it (at the end of the gap,
1571
+ as any insertion lands after deleted items). Design:
1572
+ `docs/archive/suggestions-in-engine/design.md`.
1573
+
1574
+ Patch (new code delimited by `// YP32 begin` / `// YP32 end`, edits marked `// YP32`):
1575
+
1576
+ - **`YNode#delete(index, length, into, fresh, propose)`**: with `propose`
1577
+ (`{ into, sort }`), each live unit the delete reaches is sorted by
1578
+ `sort(client, clock)`: `1` held by the record appended to `into` (the
1579
+ author's own pending text, YP20's path), `2` proposed (one id list
1580
+ appended to `propose.into`, the suggestion's record, which holds nothing
1581
+ there, YP31), `3` stepped over (already proposed). `fresh` units are
1582
+ removed as before. Proposed and stepped-over units are hidden
1583
+ (`hideIds`: a hold without a record, kept in `transaction.hidden`) until
1584
+ `unhide(transaction)`, which edytor calls when the plan is written.
1585
+ `deleteText` splits a run where the sort changes.
1586
+ - **`hideIds`, `unhide`** (`Item.js`, `unhide` exported): YP20's
1587
+ `holdIds` with no record, and its release.
1588
+
1589
+ Plain deletes (every upstream test, every YP20 row) are untouched: without
1590
+ `propose` the sort is not read and every changed path reduces to YP20's.
1591
+
1592
+ Edytor side: `crdt/text/model.ts` `deleteRange` passes the plan's
1593
+ `proposing` context; `crdt/doc/suggest.ts` writes a suggesting plan as one
1594
+ frame, its caret anchored before `unhide` and resolved after.
1595
+
1596
+ Oracle: the shared-suggestion rows (`src/tests/crdt/suggest/`, the random
1597
+ schedules with the index's self-checks on), `suggesting-input.test.tsx`
1598
+ (typing over a selection, a paste, Backspace and Delete over struck text),
1599
+ the YP20 rows, the upstream suite unchanged.
1600
+
1518
1601
  ## Generated declarations (`dts/`)
1519
1602
 
1520
1603
  `svelte-package` copies JS verbatim but emits no `.d.ts` for JS inputs, so
@@ -17,4 +17,4 @@ export { idAtIndex, indexOfId } from "./utils/positions.js";
17
17
  export { findIndexSS, getItemCleanStart, getItemCleanEnd, isKeptReplaced } from "./utils/transaction-helpers.js";
18
18
  export { Transaction, transact } from "./utils/Transaction.js";
19
19
  export { redoItem, keepItem } from "./utils/redo.js";
20
- export { Item, followRedone, ContentBinary, ContentDeleted, ContentFormat, ContentAny, ContentString, ContentType, ContentMove } from "./structs/Item.js";
20
+ export { Item, followRedone, ContentBinary, ContentDeleted, ContentFormat, ContentAny, ContentString, ContentType, ContentMove, unhide } from "./structs/Item.js";
@@ -427,13 +427,14 @@ export class ContentHold {
427
427
  mergeWith(right: ContentHold): boolean;
428
428
  /**
429
429
  * @param {Transaction} transaction
430
- * @param {Item} _item
430
+ * @param {Item} item
431
431
  */
432
- integrate(transaction: Transaction, _item: Item): void;
432
+ integrate(transaction: Transaction, item: Item): void;
433
433
  /**
434
434
  * @param {Transaction} transaction
435
+ * @param {Item} item
435
436
  */
436
- delete(transaction: Transaction): void;
437
+ delete(transaction: Transaction, item: Item): void;
437
438
  /**
438
439
  * @param {Transaction} _tr
439
440
  */
@@ -449,6 +450,8 @@ export class ContentHold {
449
450
  */
450
451
  getRef(): 11;
451
452
  }
453
+ export function hideIds(tr: Transaction, ids: Array<number>): void;
454
+ export function unhide(tr: Transaction): void;
452
455
  export function applyPending(tr: Transaction, clients: Array<number>): void;
453
456
  /**
454
457
  * A move record: one countable unit at its destination, naming the units
@@ -25,7 +25,7 @@ export class ItemTextListPosition {
25
25
  advance(transaction: Transaction, length: number): void;
26
26
  }
27
27
  export function insertAtGapEndHelper(transaction: Transaction, parent: YNode, index: number, content: import("./structs/Item.js").AbstractContent): void;
28
- export function deleteText(transaction: Transaction, currPos: ItemTextListPosition, length: number, held?: Array<number> | null, fresh?: import("./utils/ids.js").IdSet | null): ItemTextListPosition;
28
+ export function deleteText(transaction: Transaction, currPos: ItemTextListPosition, length: number, held?: Array<number> | null, fresh?: import("./utils/ids.js").IdSet | null, sort?: ((client: number, clock: number) => number) | null, proposed?: Array<number> | null, hidden?: Array<number> | null): ItemTextListPosition;
29
29
  export class ArraySearchMarker {
30
30
  /**
31
31
  * @param {Item} p
@@ -193,12 +193,23 @@ export class YNode {
193
193
  * which an undo of the record takes back), except those `fresh` names (the
194
194
  * open undo step's inserts: removed, so that step's undo changes nothing).
195
195
  *
196
+ * YP32: with `propose` (a suggestion's delete) each unit is sorted: held
197
+ * by the record in `into` (`1`: its author's own), proposed (`2`: an id
198
+ * list appended to `propose.into`, a suggestion's record, holding nothing)
199
+ * or stepped over (`3`: already proposed); proposed and stepped-over units
200
+ * read gone until `unhide`, so the rest of the plan reads the offsets it
201
+ * was prepared with.
202
+ *
196
203
  * @param {number} index Index at which to start deleting elements
197
204
  * @param {number} length The number of elements to remove. Defaults to 1.
198
205
  * @param {YNode|null} [into]
199
206
  * @param {import('./utils/ids.js').IdSet|null} [fresh]
207
+ * @param {{ into: YNode, sort: (client: number, clock: number) => number }|null} [propose]
200
208
  */
201
- delete(index: number, length?: number, into?: YNode | null, fresh?: import("./utils/ids.js").IdSet | null): void;
209
+ delete(index: number, length?: number, into?: YNode | null, fresh?: import("./utils/ids.js").IdSet | null, propose?: {
210
+ into: YNode;
211
+ sort: (client: number, clock: number) => number;
212
+ } | null): void;
202
213
  /**
203
214
  * YP20: append to this node (the document's records list) one hold record of
204
215
  * the items `ids` names (`client, clock, len` triples): they read as deleted
@@ -19,7 +19,7 @@ export { RangeCursor } from './utils/RangeCursor.js'
19
19
  export { YNode as Node } from './ynode.js'
20
20
  export { AbstractStruct } from './structs/AbstractStruct.js'
21
21
  export { GC } from './structs/GC.js'
22
- export { Item, followRedone, ContentBinary, ContentDeleted, ContentFormat, ContentAny, ContentString, ContentType, ContentMove } from './structs/Item.js' // YP22: ContentMove
22
+ export { Item, followRedone, ContentBinary, ContentDeleted, ContentFormat, ContentAny, ContentString, ContentType, ContentMove, unhide } from './structs/Item.js' // YP22: ContentMove; YP32: unhide
23
23
  export { Skip } from './structs/Skip.js'
24
24
 
25
25
  /**
@@ -1134,17 +1134,18 @@ export class ContentHold {
1134
1134
 
1135
1135
  /**
1136
1136
  * @param {Transaction} transaction
1137
- * @param {Item} _item
1137
+ * @param {Item} item
1138
1138
  */
1139
- integrate (transaction, _item) {
1140
- holdIds(transaction, this.ids, 1)
1139
+ integrate (transaction, item) {
1140
+ if (holding(item)) holdIds(transaction, this.ids, 1) // YP31
1141
1141
  }
1142
1142
 
1143
1143
  /**
1144
1144
  * @param {Transaction} transaction
1145
+ * @param {Item} item
1145
1146
  */
1146
- delete (transaction) {
1147
- holdIds(transaction, this.ids, -1)
1147
+ delete (transaction, item) {
1148
+ if (holding(item)) holdIds(transaction, this.ids, -1) // YP31
1148
1149
  }
1149
1150
 
1150
1151
  /**
@@ -1170,6 +1171,44 @@ export class ContentHold {
1170
1171
  }
1171
1172
  // YP20 end
1172
1173
 
1174
+ // YP31 begin (edytor fork: id lists — see UPSTREAM.md YP31)
1175
+ /**
1176
+ * Whether the hold record `item` holds what it names: one in a root's
1177
+ * sequence does (the document's records list, YP20); one in a nested node's
1178
+ * sequence is a list of ids, the same codec, that holds nothing.
1179
+ *
1180
+ * @param {Item} item
1181
+ * @return {boolean}
1182
+ */
1183
+ const holding = item => /** @type {YNode} */ (item.parent)._item === null
1184
+ // YP31 end
1185
+
1186
+ // YP32 begin (edytor fork: a delete that proposes — see UPSTREAM.md YP32)
1187
+ /**
1188
+ * Hide the units `ids` names until `unhide` (a suggestion's proposed
1189
+ * deletion, which the rest of its plan reads gone): a hold without a
1190
+ * record, kept in `tr.hidden`.
1191
+ *
1192
+ * @param {Transaction} tr
1193
+ * @param {Array<number>} ids
1194
+ */
1195
+ export const hideIds = (tr, ids) => {
1196
+ holdIds(tr, ids, 1)
1197
+ ;/** @type {any} */ (tr).hidden = (/** @type {any} */ (tr).hidden ?? []).concat(ids)
1198
+ }
1199
+
1200
+ /**
1201
+ * Show again what `hideIds` hid in `tr`.
1202
+ *
1203
+ * @param {Transaction} tr
1204
+ */
1205
+ export const unhide = tr => {
1206
+ const hidden = /** @type {any} */ (tr).hidden
1207
+ if (hidden) holdIds(tr, hidden, -1)
1208
+ ;/** @type {any} */ (tr).hidden = null
1209
+ }
1210
+ // YP32 end
1211
+
1173
1212
  // YP22 begin (edytor fork: move records — see UPSTREAM.md YP22)
1174
1213
  /**
1175
1214
  * Whether move record `a` beats `b`: the greater `(priority, client, clock)`
@@ -12,6 +12,7 @@ import {
12
12
  ContentType,
13
13
  ContentHold, // YP20
14
14
  ContentMove, // YP22
15
+ hideIds, // YP32
15
16
  readFormatValue, // YP25
16
17
  YXmlFragmentRefID,
17
18
  YXmlElementRefID,
@@ -327,19 +328,24 @@ const holdHelper = (transaction, list, ids) => {
327
328
 
328
329
  /**
329
330
  * With `held`, the live units are not deleted: their ids go to `held`
330
- * (`client, clock, len`), except those `fresh` names (YP20).
331
+ * (`client, clock, len`), except those `fresh` names (YP20). YP32: with
332
+ * `sort` (a suggestion's delete), `sort`'s `1` goes to `held`, `2` to
333
+ * `proposed` and `hidden`, `3` to `hidden`.
331
334
  *
332
335
  * @param {Transaction} transaction
333
336
  * @param {ItemTextListPosition} currPos
334
337
  * @param {number} length
335
338
  * @param {Array<number>|null} [held]
336
339
  * @param {import('./utils/ids.js').IdSet|null} [fresh]
340
+ * @param {((client: number, clock: number) => number)|null} [sort] YP32: a suggestion's delete sorts each unit
341
+ * @param {Array<number>|null} [proposed] YP32: the units it proposes
342
+ * @param {Array<number>|null} [hidden] YP32: the units it hides until `unhide`
337
343
  * @return {ItemTextListPosition}
338
344
  *
339
345
  * @private
340
346
  * @function
341
347
  */
342
- export const deleteText = (transaction, currPos, length, held = null, fresh = null) => {
348
+ export const deleteText = (transaction, currPos, length, held = null, fresh = null, sort = null, proposed = null, hidden = null) => {
343
349
  const startLength = length
344
350
  let kept = 0 // YP20
345
351
  while (length > 0 && currPos.right !== null) {
@@ -347,15 +353,19 @@ export const deleteText = (transaction, currPos, length, held = null, fresh = nu
347
353
  if (!item.deleted && item.countable) {
348
354
  const { client, clock } = item.id
349
355
  // YP20: a held run ends where the open step's inserts start or end
350
- const n = fresh === null ? math.min(length, item.length) : runEnd(fresh, client, clock, clock + math.min(length, item.length)) - clock
356
+ let n = fresh === null ? math.min(length, item.length) : runEnd(fresh, client, clock, clock + math.min(length, item.length)) - clock
357
+ // YP32: a suggestion's delete sorts each unit — held (`1`), proposed (`2`) or stepped over (`3`)
358
+ const kind = held === null || (fresh !== null && fresh.has(client, clock)) ? 0 : sort === null ? 1 : sort(client, clock)
359
+ if (kind !== 0 && sort !== null) for (let k = 1; k < n; k++) if (sort(client, clock + k) !== kind) n = k
351
360
  if (n < item.length) {
352
361
  getItemCleanStart(transaction, createID(client, clock + n))
353
362
  }
354
363
  length -= item.length
355
- if (held === null || (fresh !== null && fresh.has(client, clock))) {
364
+ if (kind === 0) {
356
365
  item.delete(transaction)
357
366
  } else {
358
- held.push(client, clock, item.length)
367
+ if (kind === 2) /** @type {Array<number>} */ (proposed).push(client, clock, item.length)
368
+ ;/** @type {Array<number>} */ (kind === 1 ? held : hidden).push(client, clock, item.length)
359
369
  kept += item.length
360
370
  }
361
371
  }
@@ -993,12 +1003,20 @@ export class YNode {
993
1003
  * which an undo of the record takes back), except those `fresh` names (the
994
1004
  * open undo step's inserts: removed, so that step's undo changes nothing).
995
1005
  *
1006
+ * YP32: with `propose` (a suggestion's delete) each unit is sorted: held
1007
+ * by the record in `into` (`1`: its author's own), proposed (`2`: an id
1008
+ * list appended to `propose.into`, a suggestion's record, holding nothing)
1009
+ * or stepped over (`3`: already proposed); proposed and stepped-over units
1010
+ * read gone until `unhide`, so the rest of the plan reads the offsets it
1011
+ * was prepared with.
1012
+ *
996
1013
  * @param {number} index Index at which to start deleting elements
997
1014
  * @param {number} length The number of elements to remove. Defaults to 1.
998
1015
  * @param {YNode|null} [into]
999
1016
  * @param {import('./utils/ids.js').IdSet|null} [fresh]
1017
+ * @param {{ into: YNode, sort: (client: number, clock: number) => number }|null} [propose]
1000
1018
  */
1001
- delete (index, length = 1, into = null, fresh = null) {
1019
+ delete (index, length = 1, into = null, fresh = null, propose = null) {
1002
1020
  if (!(length > 0)) return
1003
1021
  if (this.doc == null) {
1004
1022
  const runs = prelimOf(this).runs
@@ -1015,13 +1033,17 @@ export class YNode {
1015
1033
  transact(this.doc, transaction => {
1016
1034
  const currPos = cursorAt(transaction, this, index)
1017
1035
  const held = into === null ? null : [] // YP20
1018
- deleteText(transaction, currPos, length, held, fresh)
1036
+ const [proposed, hidden] = [[], []] // YP32
1037
+ deleteText(transaction, currPos, length, held, fresh, propose && propose.sort, proposed, hidden)
1019
1038
  if (held !== null && held.length > 0) {
1020
1039
  // YP20: `deleteText` moved the markers as for a delete; the hold keeps them
1021
1040
  const ms = this._searchMarker === null ? null : this._searchMarker.slice()
1022
1041
  holdHelper(transaction, /** @type {YNode} */ (into), held)
1023
1042
  if (ms !== null && this._searchMarker !== null) this._searchMarker.splice(0, this._searchMarker.length, ...ms)
1024
1043
  }
1044
+ // YP32: the proposal, then what it hides until `unhide`
1045
+ if (proposed.length > 0) holdHelper(transaction, /** @type {any} */ (propose).into, proposed)
1046
+ if (hidden.length > 0) hideIds(transaction, hidden)
1025
1047
  plantMarker(this, currPos)
1026
1048
  })
1027
1049
  }
@@ -11,7 +11,7 @@ import { Handles } from './session/handles.js';
11
11
  import { type Awareness, type DocChange, type DocumentActor, type EdytorDocument, type OrderPolicy, type YDoc } from './crdt/index.js';
12
12
  import { PresenceWriter, type PresenceOptions } from './collaboration/awarenessSelection.js';
13
13
  import { type BlockMoveRequest } from './session/moves.js';
14
- import { type DocumentWrites } from './crdt/operations.js';
14
+ import type { DocumentWrites } from './crdt/operations.js';
15
15
  import type { Plugin, BlockDefinition, MarkDefinition, InlineBlockDefinition, EditorCommand, Placeholder } from './plugins.js';
16
16
  import { type HotKey, type HotKeyCombination } from './session/keymap.js';
17
17
  import { Dispatcher } from './session/commands.js';