edytor 3.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 (231) 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 +23 -12
  6. package/dist/cloudflare/DocumentRoom.js +10 -6
  7. package/dist/cloudflare/index.d.ts +1 -1
  8. package/dist/cloudflare/room/access.d.ts +8 -3
  9. package/dist/cloudflare/room/access.js +44 -20
  10. package/dist/cloudflare/room/admission.js +28 -5
  11. package/dist/cloudflare/room/comments.js +28 -1
  12. package/dist/cloudflare/room/context.d.ts +139 -129
  13. package/dist/cloudflare/room/context.js +11 -4
  14. package/dist/cloudflare/room/forged.d.ts +14 -0
  15. package/dist/cloudflare/room/forged.js +30 -2
  16. package/dist/cloudflare/room/frames.d.ts +4 -0
  17. package/dist/cloudflare/room/frames.js +5 -1
  18. package/dist/cloudflare/room/purge.d.ts +6 -3
  19. package/dist/cloudflare/room/purge.js +45 -19
  20. package/dist/cloudflare/room/replicas.d.ts +4 -2
  21. package/dist/cloudflare/room/replicas.js +8 -3
  22. package/dist/cloudflare/room/suggest.d.ts +7 -0
  23. package/dist/cloudflare/room/suggest.js +143 -0
  24. package/dist/cloudflare/room/updates.d.ts +3 -4
  25. package/dist/cloudflare/room/updates.js +6 -8
  26. package/dist/cloudflare/routeDocumentSocket.d.ts +10 -1
  27. package/dist/cloudflare/routeDocumentSocket.js +5 -3
  28. package/dist/components/Content.svelte +9 -1
  29. package/dist/crdt/anchors.js +3 -2
  30. package/dist/crdt/attribution/attribution.d.ts +34 -12
  31. package/dist/crdt/attribution/attribution.js +59 -21
  32. package/dist/crdt/attribution/block.d.ts +3 -3
  33. package/dist/crdt/attribution/block.js +7 -8
  34. package/dist/crdt/attribution/index.d.ts +1 -1
  35. package/dist/crdt/attribution/index.js +1 -1
  36. package/dist/crdt/data.d.ts +62 -31
  37. package/dist/crdt/data.js +211 -103
  38. package/dist/crdt/doc/capability.js +9 -13
  39. package/dist/crdt/doc/delete.d.ts +1 -1
  40. package/dist/crdt/doc/delete.js +13 -30
  41. package/dist/crdt/doc/funnel.d.ts +1 -1
  42. package/dist/crdt/doc/funnel.js +34 -26
  43. package/dist/crdt/doc/gate.d.ts +3 -3
  44. package/dist/crdt/doc/history.d.ts +31 -9
  45. package/dist/crdt/doc/history.js +204 -73
  46. package/dist/crdt/doc/layout.js +16 -27
  47. package/dist/crdt/doc/meta.js +17 -23
  48. package/dist/crdt/doc/moves.d.ts +1 -1
  49. package/dist/crdt/doc/moves.js +52 -32
  50. package/dist/crdt/doc/reads.d.ts +2 -2
  51. package/dist/crdt/doc/reads.js +20 -30
  52. package/dist/crdt/doc/seed.js +2 -2
  53. package/dist/crdt/doc/split-merge.js +12 -15
  54. package/dist/crdt/doc/steps.d.ts +25 -13
  55. package/dist/crdt/doc/steps.js +55 -125
  56. package/dist/crdt/doc/suggest.d.ts +167 -0
  57. package/dist/crdt/doc/suggest.js +579 -0
  58. package/dist/crdt/doc/types.d.ts +21 -9
  59. package/dist/crdt/document.d.ts +26 -6
  60. package/dist/crdt/document.js +40 -15
  61. package/dist/crdt/edytor-doc.d.ts +410 -390
  62. package/dist/crdt/edytor-doc.js +143 -95
  63. package/dist/crdt/engine-api.d.ts +39 -1
  64. package/dist/crdt/engine.d.ts +2 -2
  65. package/dist/crdt/engine.js +4 -5
  66. package/dist/crdt/flow.d.ts +2 -2
  67. package/dist/crdt/flow.js +29 -37
  68. package/dist/crdt/index.d.ts +134 -127
  69. package/dist/crdt/index.js +4 -1
  70. package/dist/crdt/operations.d.ts +12 -3
  71. package/dist/crdt/parts.d.ts +43 -0
  72. package/dist/crdt/parts.js +44 -0
  73. package/dist/crdt/placement/display.d.ts +6 -31
  74. package/dist/crdt/placement/display.js +41 -146
  75. package/dist/crdt/placement/keys.d.ts +63 -0
  76. package/dist/crdt/placement/keys.js +95 -0
  77. package/dist/crdt/placement/model.d.ts +119 -118
  78. package/dist/crdt/placement/model.js +242 -142
  79. package/dist/crdt/placement/resolve.d.ts +123 -35
  80. package/dist/crdt/placement/resolve.js +188 -153
  81. package/dist/crdt/placement/slots.d.ts +134 -0
  82. package/dist/crdt/placement/slots.js +152 -0
  83. package/dist/crdt/protocol.d.ts +1 -1
  84. package/dist/crdt/protocol.js +1 -1
  85. package/dist/crdt/protocols/auth.d.ts +11 -1
  86. package/dist/crdt/protocols/auth.js +17 -1
  87. package/dist/crdt/protocols/comments.d.ts +16 -1
  88. package/dist/crdt/protocols/comments.js +9 -1
  89. package/dist/crdt/providers/index.d.ts +6 -4
  90. package/dist/crdt/providers/index.js +3 -1
  91. package/dist/crdt/providers/room.d.ts +16 -2
  92. package/dist/crdt/providers/room.js +64 -17
  93. package/dist/crdt/providers/websocket.d.ts +6 -4
  94. package/dist/crdt/providers/websocket.js +14 -7
  95. package/dist/crdt/purge.d.ts +24 -9
  96. package/dist/crdt/purge.js +266 -126
  97. package/dist/crdt/restore.d.ts +4 -3
  98. package/dist/crdt/restore.js +27 -34
  99. package/dist/crdt/schema.d.ts +30 -21
  100. package/dist/crdt/schema.js +39 -23
  101. package/dist/crdt/storage.d.ts +1 -1
  102. package/dist/crdt/storage.js +1 -1
  103. package/dist/crdt/structs.d.ts +17 -15
  104. package/dist/crdt/structs.js +10 -22
  105. package/dist/crdt/text/index/cache.js +4 -0
  106. package/dist/crdt/text/index/claims.js +13 -23
  107. package/dist/crdt/text/index/fold.d.ts +2 -3
  108. package/dist/crdt/text/index/fold.js +194 -44
  109. package/dist/crdt/text/index/layout.d.ts +2 -4
  110. package/dist/crdt/text/index/layout.js +14 -30
  111. package/dist/crdt/text/index/placement.d.ts +26 -18
  112. package/dist/crdt/text/index/placement.js +420 -157
  113. package/dist/crdt/text/index/records.d.ts +3 -3
  114. package/dist/crdt/text/index/records.js +24 -31
  115. package/dist/crdt/text/index/shared.d.ts +11 -3
  116. package/dist/crdt/text/index/shared.js +29 -17
  117. package/dist/crdt/text/index/state.d.ts +21 -17
  118. package/dist/crdt/text/index/state.js +15 -17
  119. package/dist/crdt/text/index/streams.d.ts +10 -5
  120. package/dist/crdt/text/index/streams.js +78 -27
  121. package/dist/crdt/text/items.d.ts +10 -3
  122. package/dist/crdt/text/items.js +7 -0
  123. package/dist/crdt/text/model.d.ts +87 -62
  124. package/dist/crdt/text/model.js +91 -169
  125. package/dist/crdt/text/ranges.d.ts +1 -1
  126. package/dist/crdt/text/ranges.js +3 -3
  127. package/dist/crdt/text/rows.d.ts +13 -7
  128. package/dist/crdt/text/rows.js +24 -7
  129. package/dist/crdt/text/runs.d.ts +51 -42
  130. package/dist/crdt/text/runs.js +24 -7
  131. package/dist/crdt/text/streams.d.ts +26 -13
  132. package/dist/crdt/text/streams.js +41 -14
  133. package/dist/crdt/vendor/yjs/UPSTREAM.md +651 -21
  134. package/dist/crdt/vendor/yjs/dts/global.d.ts +2 -0
  135. package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
  136. package/dist/crdt/vendor/yjs/dts/structs/AbstractStruct.d.ts +7 -0
  137. package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +197 -10
  138. package/dist/crdt/vendor/yjs/dts/utils/Doc.d.ts +6 -3
  139. package/dist/crdt/vendor/yjs/dts/utils/StructStore.d.ts +5 -18
  140. package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +15 -1
  141. package/dist/crdt/vendor/yjs/dts/utils/UpdateDecoder.d.ts +14 -6
  142. package/dist/crdt/vendor/yjs/dts/utils/UpdateEncoder.d.ts +33 -6
  143. package/dist/crdt/vendor/yjs/dts/utils/ids.d.ts +1 -65
  144. package/dist/crdt/vendor/yjs/dts/utils/marks.d.ts +6 -1
  145. package/dist/crdt/vendor/yjs/dts/ynode.d.ts +54 -7
  146. package/dist/crdt/vendor/yjs/global.d.ts +2 -0
  147. package/dist/crdt/vendor/yjs/src/index.js +1 -1
  148. package/dist/crdt/vendor/yjs/src/structs/AbstractStruct.js +10 -0
  149. package/dist/crdt/vendor/yjs/src/structs/Item.js +649 -51
  150. package/dist/crdt/vendor/yjs/src/utils/BlockSet.js +18 -9
  151. package/dist/crdt/vendor/yjs/src/utils/Doc.js +5 -3
  152. package/dist/crdt/vendor/yjs/src/utils/StructStore.js +5 -0
  153. package/dist/crdt/vendor/yjs/src/utils/Transaction.js +36 -13
  154. package/dist/crdt/vendor/yjs/src/utils/UpdateDecoder.js +88 -18
  155. package/dist/crdt/vendor/yjs/src/utils/UpdateEncoder.js +119 -32
  156. package/dist/crdt/vendor/yjs/src/utils/encoding.js +13 -1
  157. package/dist/crdt/vendor/yjs/src/utils/ids.js +7 -7
  158. package/dist/crdt/vendor/yjs/src/utils/marks.js +193 -8
  159. package/dist/crdt/vendor/yjs/src/utils/redo.js +1 -1
  160. package/dist/crdt/vendor/yjs/src/utils/transaction-helpers.js +1 -1
  161. package/dist/crdt/vendor/yjs/src/utils/updates.js +3 -3
  162. package/dist/crdt/vendor/yjs/src/ynode.js +199 -12
  163. package/dist/edytor.svelte.d.ts +1 -1
  164. package/dist/edytor.svelte.js +103 -75
  165. package/dist/events/beforeInputCommands.d.ts +2 -2
  166. package/dist/events/beforeInputCommands.js +2 -2
  167. package/dist/events/nativeInteractiveControl.d.ts +11 -1
  168. package/dist/events/nativeInteractiveControl.js +41 -1
  169. package/dist/events/onBeforeInput.js +3 -3
  170. package/dist/events/onFocus.js +9 -11
  171. package/dist/events/onPageHide.d.ts +3 -0
  172. package/dist/events/onPageHide.js +16 -0
  173. package/dist/export/forms.d.ts +50 -50
  174. package/dist/index.d.ts +4 -2
  175. package/dist/index.js +1 -1
  176. package/dist/labels.d.ts +8 -0
  177. package/dist/labels.js +6 -1
  178. package/dist/plugins/comments/CommentsController.svelte.d.ts +5 -1
  179. package/dist/plugins/comments/CommentsController.svelte.js +14 -4
  180. package/dist/plugins/find/search.js +2 -2
  181. package/dist/plugins/index.d.ts +1 -0
  182. package/dist/plugins/index.js +1 -0
  183. package/dist/plugins/suggestions/SuggestionBars.svelte +40 -23
  184. package/dist/plugins/suggestions/SuggestionBars.svelte.d.ts +3 -1
  185. package/dist/plugins/suggestions/SuggestionRuns.svelte +77 -0
  186. package/dist/plugins/suggestions/SuggestionRuns.svelte.d.ts +9 -0
  187. package/dist/plugins/suggestions/shared.svelte.d.ts +58 -0
  188. package/dist/plugins/suggestions/shared.svelte.js +421 -0
  189. package/dist/plugins/suggestions/suggestionsPlugin.d.ts +15 -4
  190. package/dist/selection/domSelection.d.ts +2 -0
  191. package/dist/selection/domSelection.js +6 -2
  192. package/dist/selection/pointer.svelte.js +75 -48
  193. package/dist/selection/replaceSelection.js +2 -6
  194. package/dist/selection/selection.svelte.d.ts +3 -3
  195. package/dist/selection/selection.svelte.js +39 -32
  196. package/dist/selection/selection.utils.d.ts +14 -0
  197. package/dist/selection/selection.utils.js +28 -3
  198. package/dist/session/commands.js +21 -11
  199. package/dist/session/composition.svelte.d.ts +5 -3
  200. package/dist/session/composition.svelte.js +21 -17
  201. package/dist/session/handles.d.ts +4 -17
  202. package/dist/session/handles.js +11 -37
  203. package/dist/session/navigation.js +6 -9
  204. package/dist/session/selection.d.ts +11 -5
  205. package/dist/session/selection.js +14 -48
  206. package/dist/session/suggestions.svelte.d.ts +38 -9
  207. package/dist/session/suggestions.svelte.js +37 -18
  208. package/dist/session/virtual.d.ts +1 -1
  209. package/dist/surface/attributes.d.ts +5 -0
  210. package/dist/surface/attributes.js +2 -2
  211. package/dist/surface/cells.d.ts +2 -26
  212. package/dist/surface/cells.js +2 -33
  213. package/dist/surface/observer.svelte.d.ts +6 -1
  214. package/dist/surface/observer.svelte.js +130 -83
  215. package/dist/surface/pin.svelte.d.ts +9 -9
  216. package/dist/surface/pin.svelte.js +15 -22
  217. package/dist/surface/projector.svelte.d.ts +15 -3
  218. package/dist/surface/projector.svelte.js +67 -22
  219. package/dist/text/text.svelte.js +14 -19
  220. package/dist/themes/notion.css +29 -0
  221. package/package.json +4 -2
  222. package/dist/crdt/placement/rank.d.ts +0 -87
  223. package/dist/crdt/placement/rank.js +0 -332
  224. package/dist/crdt/placement/source.d.ts +0 -43
  225. package/dist/crdt/placement/source.js +0 -66
  226. package/dist/crdt/text/delete-index.d.ts +0 -129
  227. package/dist/crdt/text/delete-index.js +0 -365
  228. package/dist/crdt/text/deletes.d.ts +0 -76
  229. package/dist/crdt/text/deletes.js +0 -362
  230. package/dist/crdt/text/index/anchored.d.ts +0 -17
  231. package/dist/crdt/text/index/anchored.js +0 -197
@@ -1,27 +1,19 @@
1
- import { rankAfter, rankBetween } from './rank.js';
2
- import { AT, AT_NODE, BLOCK_NODE, CONTENT, CONTENT_NODE, DEL_PREFIX, hasDeleteMark, ID, CLAIMS, CLAIMS_NODE, INLINE_NODE, isNodeLike, NONCE, REGISTRY_KEY, TYPE, WITHDRAW_PREFIX } from '../schema.js';
3
- import { nonceOf, randOf } from '../rand.js';
1
+ import { BLOCK_NODE, CONTENT, CONTENT_NODE, DEL_PREFIX, hasDeleteMark, ID, INLINE_NODE, isNodeLike, NONCE, REGISTRY_KEY, TYPE, WITHDRAW_PREFIX } from '../schema.js';
2
+ import { nonceOf } from '../rand.js';
4
3
  import { indexOf } from '../text/runs.js';
5
4
  import { Y } from '../engine.js';
6
5
  import { writeRunMarks } from '../text/marks.js';
7
- import { blockTextOf, DEAD, newNode, ownText as ownTextOf, splitAt } from '../text/model.js';
8
- import { cloneJson } from '../../utils/json.js';
9
- import { dataLeaves, patchWrites, writeLeaves } from '../data.js';
6
+ import { blockTextOf, DEAD, deleteClaims, newNode, ownText as ownTextOf, splitAt } from '../text/model.js';
7
+ import { cloneJsonSafe } from '../../utils/json.js';
8
+ import { dataWrites, patchWrites, writeData } from '../data.js';
10
9
  import { incarnationNode, isIncarnationId } from '../incarnations.js';
11
- import { engineOps } from '../structs.js';
10
+ import { engineOps, firstItem, storeOf } from '../structs.js';
11
+ import { anchorOf, isBlockSlot, isRecord, nodeOf, placeAfter, sameUnit, shownBy, stampOf, triples, unitOf, unitsOf } from './slots.js';
12
+ import { ownerOfNode, slotOwner } from './resolve.js';
12
13
  export { REGISTRY_KEY };
13
- export { candidatesOf, resolvePlacements } from './resolve.js';
14
- export { promotedRank, sourceRank, SOURCE_SIDE } from './source.js';
15
- export { bySlot, childrenIndex, displayParentOf, displaySlotOf, documentOrder, isLiveIn, textRanker } from './display.js';
16
- import { candidatesOf, resolvePlacements } from './resolve.js';
17
- import { bySlot, childrenIndex, displayParentOf, displaySlotOf, isLiveIn } from './display.js';
18
- /**
19
- * Bind the placement model to a concrete engine surface.
20
- *
21
- * `Y` must be the vendored v14 module (`import * as Y from
22
- * 'lib/crdt/vendor/yjs'`). Consumers inject it so this file stays free of
23
- * runtime vendor imports (see `engine-api.ts`).
24
- */
14
+ export { promotedRank } from './keys.js';
15
+ export { bySlot, childrenIndex, displayParentOf, displaySlotOf, documentOrder, isLiveIn } from './display.js';
16
+ import { bySlot, childrenIndex, displaySlotOf, displayParentOf, isLiveIn } from './display.js';
25
17
  const { isKeptReplaced } = engineOps(Y);
26
18
  // ── registry / record access ────────────────────────────────────────
27
19
  export const registryOf = (doc) => doc.get(REGISTRY_KEY);
@@ -47,14 +39,24 @@ export const isLive = (doc, id) => isLiveIn(view(doc), id);
47
39
  /**
48
40
  * The history's creation rule (`doc/history.ts`, `hist.undo.withdraw`): an
49
41
  * undo never deletes a block the undone step created. Its node and
50
- * structure (attrs, placement candidates, the content and claims nodes)
51
- * stay and the undoer's withdraw mark is written on it; what the step wrote
52
- * inside the content and claims nodes (the undoer's own text, atoms,
53
- * boundaries, claims) is deleted as usual. The index shows a withdrawn
42
+ * structure (attrs, its home slot in its parent's sequence, the content
43
+ * node) stay and the undoer's withdraw mark is written on it; what the step
44
+ * wrote inside the content node (the undoer's own text, atoms, boundaries,
45
+ * claims) is deleted as usual, and so are the move records and data slots
46
+ * the step wrote in the created node's sequence. The index shows a withdrawn
54
47
  * block while it holds another writer's content.
55
48
  */
56
49
  export const withdrawOnUndo = (doc) => (item, stackItem) => {
57
50
  const registry = registryOf(doc);
51
+ const created = (id) => {
52
+ const node = registry.getAttr(id);
53
+ const entry = isNodeLike(node) ? node._item : null;
54
+ return !!entry?.id && stackItem.inserts.has(entry.id.client, entry.id.clock);
55
+ };
56
+ // A home slot of a block the step created stays with the block (withdrawn, not gone).
57
+ const seq = item;
58
+ if (seq?.parentSub === null && seq.content?.arr !== undefined)
59
+ return seq.content.arr.every((v) => isBlockSlot(v) && created(v.b));
58
60
  let below = null;
59
61
  let cur = item;
60
62
  while (cur?.parent !== registry) {
@@ -63,33 +65,18 @@ export const withdrawOnUndo = (doc) => (item, stackItem) => {
63
65
  if (cur === null)
64
66
  return false;
65
67
  }
66
- if (!cur.id || !stackItem.inserts.has(cur.id.client, cur.id.clock))
68
+ if (!cur.id || cur.parentSub === null || !stackItem.inserts.has(cur.id.client, cur.id.clock))
67
69
  return false;
68
70
  if (below === null) {
69
71
  const node = cur.content.type;
70
72
  node.setAttr(WITHDRAW_PREFIX + doc.clientID, true);
71
73
  return true;
72
74
  }
73
- return below === item || (below.parentSub !== CONTENT && below.parentSub !== CLAIMS);
74
- };
75
- // ── placement write ──────────────────────────────────────────────────
76
- /**
77
- * Atomically append one placement candidate `{p, r}` to a block's `at`
78
- * map, stamped `seq = localMax+1, client = doc.clientID`, then compact:
79
- * candidates below the top-2 are tombstoned (the runner-up survives as
80
- * the acyclic fallback; deeper history is re-created by undo anyway).
81
- * Must run inside a transaction.
82
- */
83
- export const writePlacement = (doc, node, p, r) => {
84
- const at = node.getAttr(AT);
85
- if (!isNodeLike(at))
86
- throw new Error(`writePlacement: block missing at-map`);
87
- const cands = candidatesOf(node);
88
- const seq = (cands[0]?.seq ?? 0) + 1;
89
- // Tombstone everything below the runner-up (top-2 kept).
90
- for (const c of cands.slice(2))
91
- at.deleteAttr(c.key);
92
- at.setAttr(`${seq}.${doc.clientID}`, { p, r });
75
+ // The created node's own attrs stay (its content node among them); the records and data
76
+ // slots of its sequence go, and so does what the step wrote inside the content node.
77
+ if (below === item)
78
+ return below.parentSub !== null;
79
+ return below.parentSub !== CONTENT;
93
80
  };
94
81
  // ── derived state ───────────────────────────────────────────────────
95
82
  /** The doc's index as a `ModelView` — folded up to the open transaction's last write. */
@@ -100,7 +87,7 @@ export const buildInline = (atom) => {
100
87
  const node = newNode(INLINE_NODE);
101
88
  node.setAttr(ID, atom.id);
102
89
  node.setAttr(TYPE, atom.type);
103
- writeLeaves(node, dataLeaves(atom.data));
90
+ writeData(node, dataWrites(atom.data));
104
91
  return node;
105
92
  };
106
93
  // ── structural predicates ───────────────────────────────────────────
@@ -110,7 +97,7 @@ export const buildInline = (atom) => {
110
97
  * (`displayParentOf`), not the raw placement chain. A merge claim can
111
98
  * place a block inside another's display subtree without appearing in
112
99
  * its raw ancestry, so the merge refusal must see through the claim
113
- * redirect — this is exactly the relation `resolvePlacements` keeps
100
+ * redirect — this is exactly the relation the index's placements keep
114
101
  * acyclic: a merge that would close a cycle is refused without mutation.
115
102
  */
116
103
  export const isSelfOrDescendant = (placements, own, id, maybeAncestor) => {
@@ -133,62 +120,152 @@ export const isSelfOrDescendant = (placements, own, id, maybeAncestor) => {
133
120
  // ── write primitives ────────────────────────────────────────────────
134
121
  // The document's prepared plans decide every write; these only
135
122
  // perform one planned step and never refuse. Must run inside a transaction.
123
+ /** The sequence node of `parent` (`null`: the root's). */
124
+ const sequenceOf = (doc, parent) => parent === null ? registryOf(doc) : blockNodeOf(doc, parent);
136
125
  /**
137
- * `count` consecutive insertion ranks at `index` into the sibling list
138
- * `siblings`.
139
- *
140
- * Degenerate seams (`left >= right`) are legal replicated states, not
141
- * caller errors: equal-rank neighbours occur after cycle-fallback
142
- * rehoming (two blocks may carry the same rank string minted in
143
- * different sibling lists), and `rankBetween` THROWS on them — so
144
- * callers must guard. No string sorts strictly between equal strings,
145
- * so each new member takes `left` verbatim: it JOINS the tie and the
146
- * `(rank, id)` display sort orders the whole group deterministically.
147
- * On a valid seam the emitted chain is `rankBetween(left, right)` then
148
- * `rankBetween(prev, right)` — with `run`, `rankAfter` (an insert: after a
149
- * block this client ranked, in its run there).
126
+ * The block slots item `it` shows as `v` reads it: a record's, the units
127
+ * whose block's accepted edge is it, in the order it names them; a slot
128
+ * item's, those shown at home (moved away or not: a cycle may send one back).
150
129
  */
151
- export const ranksAt = (siblings, index, count, clientId, rand, run = false) => {
152
- let left = siblings[index - 1]?.rank;
153
- const right = siblings[index]?.rank;
154
- if (left !== undefined && right !== undefined && left >= right) {
155
- return new Array(count).fill(left);
130
+ const shownIn = (doc, v, it) => {
131
+ const registry = registryOf(doc);
132
+ const via = isRecord(it) ? it : null;
133
+ return unitsOf(doc, it, (_, value, unit) => {
134
+ if (!isBlockSlot(value))
135
+ return false;
136
+ const b = slotOwner(registry, isKeptReplaced, value.b, unit.c);
137
+ const home = v.blocks.get(b)?.home;
138
+ return !!home && sameUnit(home, unit) && v.accepted(b)?.via === via;
139
+ });
140
+ };
141
+ /** The entry block `id` displays as in `v` (its home slot, or its unit at the record it shows at), or `null`. */
142
+ const anchorIn = (doc, v, id) => {
143
+ const home = v.blocks.get(id)?.home;
144
+ if (home === undefined || home === null)
145
+ return null;
146
+ const edge = v.accepted(id);
147
+ // At the root's tail (a cycle): the engine's place, the nearest one there is.
148
+ if (edge === null)
149
+ return anchorOf(doc, home);
150
+ if (edge.via === null) {
151
+ const u = unitOf(doc, home);
152
+ return u === null ? null : { value: u.value, unit: home, via: null, at: 0 };
156
153
  }
157
- const out = [];
158
- for (let i = 0; i < count; i++) {
159
- // An insert extends this client's run after a block it ranked.
160
- const r = run
161
- ? rankAfter(left, right, clientId, rand)
162
- : rankBetween(left, right, clientId, rand);
163
- out.push(r);
164
- left = r;
154
+ return shownIn(doc, v, edge.via).find((e) => sameUnit(e.unit, home)) ?? null;
155
+ };
156
+ /** The entry right before `e` in its sequence as `v` reads it (`e`'s record's previous unit, else the nearest shown to the left). */
157
+ const beforeIn = (doc, v, e) => {
158
+ const start = e.via ?? unitOf(doc, e.unit).item;
159
+ const own = shownIn(doc, v, start);
160
+ // A record's unit before `e`'s place; a slot item's last unit before `e`.
161
+ const before = e.via === null
162
+ ? own.findLast((x) => x.unit.k < e.unit.k)
163
+ : e.at > 0
164
+ ? (own[e.at - 1] ?? null)
165
+ : undefined;
166
+ if (before !== undefined)
167
+ return before;
168
+ for (let p = start.left; p !== null; p = p.left) {
169
+ const list = shownIn(doc, v, p);
170
+ if (list.length > 0)
171
+ return list[list.length - 1];
172
+ }
173
+ return null;
174
+ };
175
+ /**
176
+ * Write `slots` (new blocks' home slots) and/or a move of `moved` (their
177
+ * home units, as `client, clock, len` triples) under `parent`, right after
178
+ * the displayed child `left` (`null`: first) and before `right`: into the
179
+ * sequence that holds `left`'s displaying unit (a promoted or claimed
180
+ * neighbour's own parent), after it — or before `right`'s — else into
181
+ * `parent`'s own sequence. `side` is the record's class among the items of
182
+ * its origin (`placement/slots.ts`: `0` after, `1` before).
183
+ */
184
+ export const placeBlocks = (doc, v, parent, left, right, slots, moved = null, side = 0, part = 0, at = null) => {
185
+ // Where `v` shows its blocks: a unit a cycle sent back elsewhere is neither an anchor nor carried.
186
+ const shown = (r) => shownIn(doc, v, r);
187
+ // The record stands at `at` (else its first unit), named with the home slots above it.
188
+ const chain = (first) => unitChain(doc, v, at ?? first);
189
+ const l = left === null ? null : anchorIn(doc, v, left);
190
+ if (l !== null)
191
+ return void placeAfter(doc, nodeOf(doc, l), l, null, slots, moved, side, part, chain, shown);
192
+ const r = right === null ? null : anchorIn(doc, v, right);
193
+ if (r !== null)
194
+ return void placeAfter(doc, nodeOf(doc, r), beforeIn(doc, v, r), r, slots, moved, side, part, chain, shown);
195
+ placeAfter(doc, sequenceOf(doc, parent), null, null, slots, moved, side, part, chain, shown);
196
+ };
197
+ /**
198
+ * The chain a move record names for the engine's sort (fork patch YP22):
199
+ * the home slots from the root sequence down to unit `u`, outermost first —
200
+ * the slot of the block owning `u`'s sequence, that block's parent's, …
201
+ * Home slots never move, so every writer names one chain for one unit.
202
+ */
203
+ const unitChain = (doc, v, u) => {
204
+ const out = [u];
205
+ const item = unitOf(doc, u)?.item;
206
+ let owner = item === undefined
207
+ ? null
208
+ : ownerOfNode(registryOf(doc), isKeptReplaced, item.parent);
209
+ for (let n = 0; owner != null && n < 4096; n++) {
210
+ const home = v.blocks.get(owner)?.home;
211
+ if (!home)
212
+ break;
213
+ out.unshift({ c: home.c, k: home.k });
214
+ owner = home.parent;
165
215
  }
166
216
  return out;
167
217
  };
168
218
  /**
169
- * One registry entry: the block node with its `claims` list (holding
170
- * `claims`), its own backing text holding `items` (none for a split-born
171
- * block: its stream lives in the text it was split from), and the atomic
172
- * first placement candidate `{p, r}` stamped `1.clientID`. Pre-integration
173
- * writes materialize when the node integrates (the registry write comes last).
219
+ * The displayed children of `parent` in `v` around display index `index`
220
+ * (the `exclude`d left out): the one before it and the one at it, the
221
+ * neighbours {@link placeBlocks} writes between.
174
222
  */
175
- const createBlock = (doc, id, type, data, place, claims, items, n = nonceOf(doc)) => {
223
+ const around = (v, parent, index, exclude = []) => {
224
+ const all = v.kids.get(parent) ?? [];
225
+ const list = exclude.length === 0 ? all : all.filter((k) => !exclude.includes(k.id));
226
+ const i = Math.max(0, Math.min(index, list.length));
227
+ return [list[i - 1]?.id ?? null, list[i]?.id ?? null];
228
+ };
229
+ /**
230
+ * Move `ids` (in order) to display index `index` among `parent`'s children
231
+ * (themselves left out): one record naming their home slots, after the
232
+ * child before that index. `at` is the block the record stands at for the
233
+ * engine's sort of two records at one origin (its home slot; `null`: the
234
+ * first moved) and `part` its order among the records standing there.
235
+ */
236
+ export const moveBlocksTo = (doc, v, ids, parent, index, side = 0, part = 0, at = null) => {
237
+ const units = ids.flatMap((id) => {
238
+ const home = v.blocks.get(id)?.home;
239
+ return home ? [home] : [];
240
+ });
241
+ if (units.length === 0)
242
+ return;
243
+ const anchor = at === null ? null : (v.blocks.get(at)?.home ?? null);
244
+ placeBlocks(doc, v, parent, ...around(v, parent, index, ids), null, triples(units), side, part, anchor);
245
+ };
246
+ /**
247
+ * One registry entry: the block node with its own backing text holding
248
+ * `items` (none for a split-born block: its stream lives in the text it was
249
+ * split from) and, in its sequence, its children's home slots in order (each
250
+ * child's node created the same way). Pre-integration writes materialize
251
+ * when the node integrates (the registry write comes last). The block's own
252
+ * home slot is the caller's ({@link placeBlocks}).
253
+ */
254
+ const createBlock = (doc, sp, items, n = nonceOf(doc)) => {
255
+ // The block's id is its registry key: the node writes none of its own.
176
256
  const node = newNode(BLOCK_NODE);
177
- node.setAttr(ID, id);
178
257
  node.setAttr(NONCE, n);
179
- node.setAttr(TYPE, type);
180
- writeLeaves(node, dataLeaves(data));
258
+ node.setAttr(TYPE, sp.type);
259
+ writeData(node, dataWrites(sp.data));
181
260
  const content = items === null ? null : textOf(items);
182
261
  if (content !== null)
183
262
  node.setAttr(CONTENT, content);
184
- const list = newNode(CLAIMS_NODE);
185
- node.setAttr(CLAIMS, list);
186
- if (claims.length > 0)
187
- list.insert(0, claims);
188
- const at = newNode(AT_NODE);
189
- node.setAttr(AT, at);
190
- at.setAttr(`1.${doc.clientID}`, place);
191
- registryOf(doc).setAttr(id, node);
263
+ const children = sp.children ?? [];
264
+ if (children.length > 0)
265
+ node.insert(node.length, children.map((c) => ({ b: c.id })));
266
+ for (const child of children)
267
+ createBlock(doc, child, child.content ?? []);
268
+ registryOf(doc).setAttr(sp.id, node);
192
269
  if (content !== null)
193
270
  markText(doc, content, items);
194
271
  };
@@ -211,36 +288,55 @@ const textOf = (items) => {
211
288
  const markText = (doc, content, items) => writeRunMarks(doc, content, 0, items.map((item) => item.kind === 'text'
212
289
  ? { length: item.text.length, marks: item.marks }
213
290
  : { length: 1 }));
291
+ /**
292
+ * Materialize `specs` (whole subtrees, in order) under `parent` at display
293
+ * index `index`: each spec's node with its children, then their home slots
294
+ * in one run after the child before that index. `split`: the boundary of
295
+ * the split these blocks come with (a paste's lines at a split point), their
296
+ * slots' sort key among the slots of one origin ({@link BlockSlot}).
297
+ */
298
+ export const insertSpecsAt = (doc, v, parent, index, specs, split = null) => {
299
+ if (specs.length === 0)
300
+ return;
301
+ const [left, right] = around(v, parent, index);
302
+ for (const sp of specs)
303
+ createBlock(doc, sp, sp.content ?? []);
304
+ placeBlocks(doc, v, parent, left, right, specs.map((sp) => (split === null ? { b: sp.id } : { b: sp.id, s: [split.c, split.k] })));
305
+ };
214
306
  /**
215
307
  * Restore definition (migration only; a first import
216
308
  * restores into an empty doc): make `specs` the whole visible document
217
309
  * under their own ids, in ONE transaction. An existing id keeps its
218
310
  * registry entry: every delete mark is cleared and its type, data,
219
- * placement and content are rewritten in place — a fresh own text holding
220
- * the spec's content and an empty claims list; a block whose stream still
311
+ * place and content are rewritten in place — its claims deleted, a
312
+ * fresh own text holding the spec's content; a block whose stream still
221
313
  * starts at a live boundary gets a new nonce, so that boundary goes inert
222
314
  * and the own text is its stream. An absent id is created. Every other
223
- * block gets this writer's delete mark. Ranks are derived from the tree
224
- * alone and the rewrites are last-writer-wins attrs, so two replicas
315
+ * block gets this writer's delete mark. The places are written in the
316
+ * tree's order and the rewrites are last-writer-wins attrs, so two replicas
225
317
  * restoring the same specs converge on one copy.
226
318
  */
227
319
  export const restoreBlocks = (doc, specs) => doc.transact(() => {
228
- const { own } = view(doc);
320
+ const { blocks, own } = view(doc);
229
321
  const keep = new Set();
230
322
  const restore = (list, parent) => {
231
- let rank;
323
+ let left = null;
232
324
  for (const sp of list) {
233
325
  keep.add(sp.id);
234
326
  let node = blockNodeOf(doc, sp.id);
327
+ const v = view(doc);
235
328
  if (node === null) {
236
- node = newNode(BLOCK_NODE);
237
- node.setAttr(ID, sp.id);
238
- node.setAttr(NONCE, nonceOf(doc));
239
- node.setAttr(AT, newNode(AT_NODE));
240
- registryOf(doc).setAttr(sp.id, node);
329
+ createBlock(doc, { ...sp, children: [] }, []);
330
+ placeBlocks(doc, v, parent, left, null, [{ b: sp.id }]);
331
+ node = blockNodeOf(doc, sp.id);
241
332
  }
242
- else if (own.streamOf(sp.id)?.home !== sp.id && own.streamOf(sp.id) !== undefined) {
243
- node.setAttr(NONCE, nonceOf(doc));
333
+ else {
334
+ deleteClaims(doc, blocks.get(sp.id)?.claims ?? []);
335
+ if (own.streamOf(sp.id)?.home !== sp.id && own.streamOf(sp.id) !== undefined)
336
+ node.setAttr(NONCE, nonceOf(doc));
337
+ const home = v.blocks.get(sp.id)?.home;
338
+ if (home)
339
+ placeBlocks(doc, v, parent, left, null, null, triples([home]));
244
340
  }
245
341
  for (const key of [...node.attrKeys()]) {
246
342
  if (key.startsWith(DEL_PREFIX) || key.startsWith(WITHDRAW_PREFIX))
@@ -248,13 +344,11 @@ export const restoreBlocks = (doc, specs) => doc.transact(() => {
248
344
  }
249
345
  if (node.getAttr(TYPE) !== sp.type)
250
346
  node.setAttr(TYPE, sp.type);
251
- writeLeaves(node, patchWrites(node, [{ path: [], value: sp.data ?? {} }]) ?? []);
347
+ writeData(node, patchWrites(node, [{ path: [], value: sp.data ?? {} }]) ?? []);
252
348
  const text = textOf(sp.content ?? []);
253
349
  node.setAttr(CONTENT, text);
254
350
  markText(doc, text, sp.content ?? []);
255
- node.setAttr(CLAIMS, newNode(CLAIMS_NODE));
256
- rank = rankBetween(rank, undefined, 0, () => 0);
257
- writePlacement(doc, node, parent, rank);
351
+ left = sp.id;
258
352
  restore(sp.children ?? [], sp.id);
259
353
  }
260
354
  };
@@ -265,20 +359,6 @@ export const restoreBlocks = (doc, specs) => doc.transact(() => {
265
359
  }
266
360
  });
267
361
  });
268
- /**
269
- * Materialize one spec subtree: the block owns its backing text (its
270
- * stream from index 0); children recurse under a fresh sequential rank
271
- * chain (a new block has no siblings to interleave with).
272
- */
273
- export const materializeSpec = (doc, sp, parent, rank) => {
274
- createBlock(doc, sp.id, sp.type, sp.data, { p: parent, r: rank }, [], sp.content ?? []);
275
- let left;
276
- for (const child of sp.children ?? []) {
277
- const r = rankBetween(left, undefined, doc.clientID, randOf(doc));
278
- materializeSpec(doc, child, sp.id, r);
279
- left = r;
280
- }
281
- };
282
362
  /**
283
363
  * Does any id of `specs` (whole subtrees) collide — with another spec id,
284
364
  * or with any registry entry, live or deleted? The registry is keyed by
@@ -313,30 +393,35 @@ export const insertBlocks = (doc, dest, specs) => {
313
393
  if (collides(doc, specs))
314
394
  return false;
315
395
  return doc.transact(() => {
316
- const sibs = view(doc).kids.get(dest.parent) ?? [];
317
- const at = Math.max(0, Math.min(dest.index, sibs.length));
318
- const ranks = ranksAt(sibs, at, specs.length, doc.clientID, randOf(doc));
319
- specs.forEach((sp, i) => materializeSpec(doc, sp, dest.parent, ranks[i]));
396
+ insertSpecsAt(doc, view(doc), dest.parent, dest.index, specs);
320
397
  return true;
321
398
  });
322
399
  };
323
400
  /** {@link insertBlocks} of one spec. */
324
401
  export const insertBlock = (doc, dest, spec) => insertBlocks(doc, dest, [spec]);
325
402
  /**
326
- * Split `id` at content `offset` into the new block `newId` placed at
327
- * `place`: one boundary item for `newId` at the end of the gap at `offset`
328
- * (fork patch YP7) and the merge claims that follow it re-inserted on the new block —
329
- * no text is copied, so an offline edit to the tail keeps landing on the
330
- * same items and displays in the new block after convergence. `tail` is
331
- * the new block's type/data, decided once by the plan. Children are a
332
- * separate step. `id` must have a stream or a claim ({@link ownText}).
403
+ * Split `id` at content `offset` into the new block `newId` placed under
404
+ * `parent` at display `index`: one boundary item for `newId` at the end of
405
+ * the gap at `offset` (fork patch YP7) and the merge claims that follow it
406
+ * written again in the new block's stream — no text is copied, so an
407
+ * offline edit to the tail keeps landing on the same items and displays in
408
+ * the new block after convergence. `tail` is the new block's type/data,
409
+ * decided once by the plan. Children are a separate step. `id` must have a
410
+ * stream or a claim ({@link ownText}).
411
+ * Returns the split's boundary unit (the key of the slots a paste adds at it).
333
412
  */
334
- export const writeSplit = (doc, id, offset, newId, tail, place) => {
335
- const { blocks, own } = view(doc);
413
+ export const writeSplit = (doc, id, offset, newId, tail, parent, index) => {
414
+ const v = view(doc);
336
415
  const n = nonceOf(doc);
337
- const claims = splitAt(blocks, own, id, offset, newId, n);
338
- const data = tail.data === undefined ? undefined : cloneJson(tail.data);
339
- createBlock(doc, newId, tail.type, data, place, claims, null, n);
416
+ // The boundary is the split's first write: its id is this writer's next.
417
+ const bound = { c: doc.clientID, k: storeOf(doc).getClock(doc.clientID) };
418
+ splitAt(doc, v.blocks, v.own, id, offset, newId, n);
419
+ const data = tail.data === undefined ? undefined : cloneJsonSafe(tail.data);
420
+ createBlock(doc, { id: newId, type: tail.type, data }, null, n);
421
+ // The tail's slot carries its boundary (`BlockSlot.s`): two peers' splits of
422
+ // one block sort by their boundaries' order in the text, in the gap after it.
423
+ placeBlocks(doc, v, parent, ...around(v, parent, index), [{ b: newId, s: [bound.c, bound.k] }]);
424
+ return bound;
340
425
  };
341
426
  /**
342
427
  * Give the streamless block `id` its own text (`T.ownText`: a derived
@@ -346,7 +431,7 @@ export const ownText = (doc, id) => ownTextOf(doc, view(doc).blocks.get(id));
346
431
  // ── queries ─────────────────────────────────────────────────────────
347
432
  /**
348
433
  * Canonical projection: pure derivation from replicated state — the
349
- * visible tree of live blocks ordered by `(rank, id)`, children of a
434
+ * visible tree of live blocks in their sequences' order, children of a
350
435
  * merged-away parent under its owner, those of a deleted parent in its
351
436
  * slot ({@link displaySlotOf}). Content is each block's display.
352
437
  */
@@ -402,6 +487,22 @@ export const crdtId = (doc, id) => {
402
487
  };
403
488
  /** Engine handle for a logical id, or null when absent or delete-marked (debug surface). */
404
489
  export const resolveBlock = (doc, id) => liveNodeOf(doc, id);
490
+ /**
491
+ * The live move records of `node`'s sequence (the room's purge, tests):
492
+ * each with its stamp and whether some unit still shows at it — the
493
+ * engine's mover of a unit, or (with `v`) the record a block shows at in
494
+ * `v` (a cycle sent it back to an older one).
495
+ */
496
+ export const recordsOf = (doc, node, v) => {
497
+ const out = [];
498
+ for (let it = firstItem(node); it !== null; it = it.right) {
499
+ if (it.deleted || !isRecord(it))
500
+ continue;
501
+ const wins = (v !== undefined && shownIn(doc, v, it).length > 0) || shownBy(doc, it).length > 0;
502
+ out.push({ item: it, stamp: stampOf(it), wins });
503
+ }
504
+ return out;
505
+ };
405
506
  /**
406
507
  * The placement model as one object (the public binding's `model`): the
407
508
  * primitives above. Edytor's own modules import them directly.
@@ -414,22 +515,21 @@ export const bindModel = (_Y) => ({
414
515
  blockNodeOf,
415
516
  liveNodeOf,
416
517
  isLive,
417
- candidatesOf,
418
518
  /** The doc's index as a `ModelView`. */
419
519
  view,
420
- resolvePlacements,
421
520
  childrenIndex,
422
521
  positionInView,
423
522
  // write primitives (the document's prepared plans apply these)
424
523
  buildInline,
425
524
  isSelfOrDescendant,
426
- ranksAt,
427
- writePlacement,
525
+ placeBlocks,
526
+ moveBlocksTo,
527
+ insertSpecsAt,
428
528
  withdrawOnUndo,
429
- materializeSpec,
430
529
  collides,
431
530
  writeSplit,
432
531
  ownText,
532
+ recordsOf,
433
533
  // the seed writer's bulk insert
434
534
  insertBlocks,
435
535
  insertBlock,