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
@@ -481,11 +481,11 @@ export const createDeleteSetFromStructStore = ss => {
481
481
  const dsitems = []
482
482
  for (let i = 0; i < structs.length; i++) {
483
483
  const struct = structs[i]
484
- if (struct.deleted) {
484
+ if (struct.removed) { // YP20: a held item is not deleted
485
485
  const clock = struct.id.clock
486
486
  let len = struct.length
487
487
  if (i + 1 < structs.length) {
488
- for (let next = structs[i + 1]; i + 1 < structs.length && next.deleted; next = structs[++i + 1]) {
488
+ for (let next = structs[i + 1]; i + 1 < structs.length && next.removed; next = structs[++i + 1]) {
489
489
  len += next.length
490
490
  }
491
491
  }
@@ -514,7 +514,7 @@ export const writeIdSet = (encoder, idSet) => {
514
514
  .forEach(([client, _idRanges]) => {
515
515
  const idRanges = _idRanges.getIds()
516
516
  encoder.resetIdSetCurVal()
517
- encoding.writeVarUint(encoder.restEncoder, client)
517
+ encoder.writeDsClient(client) // YP26
518
518
  const len = idRanges.length
519
519
  encoding.writeVarUint(encoder.restEncoder, len)
520
520
  for (let i = 0; i < len; i++) {
@@ -537,7 +537,7 @@ export const readIdSet = decoder => {
537
537
  const numClients = decoding.readVarUint(decoder.restDecoder)
538
538
  for (let i = 0; i < numClients; i++) {
539
539
  decoder.resetDsCurVal()
540
- const client = decoding.readVarUint(decoder.restDecoder)
540
+ const client = decoder.readDsClient() // YP26
541
541
  const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
542
542
  if (numberOfDeletes > 0) {
543
543
  /**
@@ -571,7 +571,7 @@ export const readAndApplyDeleteSet = (decoder, transaction, store) => {
571
571
  const numClients = decoding.readVarUint(decoder.restDecoder)
572
572
  for (let i = 0; i < numClients; i++) {
573
573
  decoder.resetDsCurVal()
574
- const client = decoding.readVarUint(decoder.restDecoder)
574
+ const client = decoder.readDsClient() // YP26
575
575
  const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
576
576
  const structs = store.clients.get(client) || []
577
577
  const state = store.getClock(client)
@@ -589,7 +589,7 @@ export const readAndApplyDeleteSet = (decoder, transaction, store) => {
589
589
  */
590
590
  let struct = structs[index]
591
591
  // split the first item if necessary
592
- if (!struct.deleted && struct.id.clock < clock && struct.isItem) {
592
+ if (!struct.removed && struct.id.clock < clock && struct.isItem) { // YP20: removed, not held
593
593
  // increment index, we now want to use the next struct
594
594
  structs.splice(++index, 0, /** @type {Item} */ (struct).split(transaction, clock - struct.id.clock))
595
595
  }
@@ -597,7 +597,7 @@ export const readAndApplyDeleteSet = (decoder, transaction, store) => {
597
597
  // @ts-ignore
598
598
  struct = structs[index++]
599
599
  if (struct.id.clock < clockEnd) {
600
- if (!struct.deleted) {
600
+ if (!struct.removed) { // YP20
601
601
  if (struct.isItem) {
602
602
  if (clockEnd < struct.id.clock + struct.length) {
603
603
  structs.splice(index, 0, /** @type {Item} */ (struct).split(transaction, clockEnd - struct.id.clock))
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @typedef {import('./Transaction.js').Transaction} Transaction
3
+ */
4
+
1
5
  /**
2
6
  * Paired marks — an edytor fork addition (UPSTREAM.md YP13), not upstream source.
3
7
  *
@@ -117,33 +121,214 @@ export const formatValue = value => value instanceof MarkState ? value.value : v
117
121
  /**
118
122
  * The integration class of an item among the items of one origin: a
119
123
  * left-side mark item `0`, content and plain formats `1`, a right-side
120
- * mark item `2`. Deleted or collected items keep the class their content
121
- * gave them while it is there; a collected format item (its content gone)
122
- * would read `1`, which is why the fork never collects a paired mark item's
123
- * content (`ContentFormat#gc` keeps it).
124
+ * mark item `2`, a claim item `3` (YP21: a sequence unit `{ m }`, after
125
+ * everything else, so content and boundaries a concurrent writer puts at
126
+ * its origin land before it), a move record `4` or `5` by its side (YP22:
127
+ * after every slot of its origin — a split's pieces first — and a record
128
+ * standing after what precedes it before one standing before what follows
129
+ * it: the order every serial run of the gestures gives). Deleted or collected items keep the class
130
+ * their content gave them while it is there; a collected item (its content
131
+ * gone) would read `1`, which is why the fork never collects the content of
132
+ * an item of another class (`Item#gc` keeps it).
124
133
  *
125
134
  * @param {any} item
126
135
  * @return {number}
127
136
  */
128
137
  export const markClass = item => {
129
138
  const content = item.content
130
- if (content === undefined || typeof content.key !== 'string') return 1
139
+ if (content === undefined) return 1
140
+ if (content.cls !== undefined) return content.cls // YP29: a collected mark or claim (`ContentDeleted`)
141
+ if (typeof content.key !== 'string') {
142
+ // YP22: a move record (its side: 0 after, 1 before)
143
+ if (content.priority !== undefined && content.ids !== undefined) return 4 + content.side
144
+ // YP21: a claim item
145
+ return item.parentSub === null && typeof content.arr?.[0]?.m === 'string' ? 3 : 1
146
+ }
131
147
  const role = pairedRole(content)
132
148
  if (role < 0) return 1
133
149
  const side = role === 0 ? content.value[4] : content.value[2]
134
150
  return side === SIDE_LEFT ? 0 : 2
135
151
  }
136
152
 
153
+ /**
154
+ * YP22: the unit id `client:clock` names, as its item and clock, `null` where
155
+ * a GC struct replaced it.
156
+ *
157
+ * @param {import('./StructStore.js').StructStore} store
158
+ * @param {number} client
159
+ * @param {number} clock
160
+ * @return {{ item: any, clock: number } | null}
161
+ */
162
+ const unitAt = (store, client, clock) => {
163
+ const item = store.getItem({ client, clock })
164
+ return item.isItem ? { item, clock } : null
165
+ }
166
+
167
+ /**
168
+ * YP22: the order of two units (`unitAt`): by clock in one item, else by
169
+ * `sequenceOrder` (`NaN`: not in one sequence); `0` for one unit.
170
+ *
171
+ * @param {{ item: any, clock: number }} p
172
+ * @param {{ item: any, clock: number }} q
173
+ * @return {number}
174
+ */
175
+ const unitOrder = (p, q) => (p.item === q.item ? p.clock - q.clock : sequenceOrder(p.item, q.item))
176
+
177
+ /**
178
+ * YP22: a block slot's split key (`{ b, s: [client, clock] }`: the id of the
179
+ * boundary item of the split that made the block, kept when the slot is
180
+ * collected, `Item#gc`) as a unit (`unitAt`: `null` where a GC struct
181
+ * replaced the boundary), `undefined` for any other item (a slot no split
182
+ * made, a boundary, whose own `s` names a block).
183
+ *
184
+ * @param {any} item
185
+ * @param {import('./StructStore.js').StructStore} store
186
+ * @return {{ item: any, clock: number } | null | undefined}
187
+ */
188
+ const splitKey = (item, store) => {
189
+ const s = item.content.arr?.[0]?.s
190
+ return Array.isArray(s) ? unitAt(store, s[0], s[1]) : undefined
191
+ }
192
+
193
+ /**
194
+ * YP22: whether `item` is a list item outside a text (a block's or an
195
+ * array's slot: its parent is no `content` node) written by the writer of
196
+ * its origin: it extends that writer's run there, and sorts before every
197
+ * other writer's slot of the same origin (`order.insert.run`: the lines one
198
+ * writer types after its own line stay together around a peer's line
199
+ * inserted there meanwhile). Read from the item's ids and parent, which
200
+ * collection never changes, so every replica integrates alike.
201
+ *
202
+ * @param {any} item
203
+ * @return {boolean}
204
+ */
205
+ const extendsRun = (/** @type {Item} */ item) =>
206
+ item.parentSub === null && /** @type {YNode} */ (item.parent).name !== 'content' && item.origin !== null && item.origin.client === item.id.client
207
+
208
+ /**
209
+ * YP22: the order of two items of one sequence (one parent, one key):
210
+ * negative when `x` stands before `y`, positive after, `0` for one item,
211
+ * `NaN` when they are not in one sequence. The walk goes left from both at
212
+ * once, so it costs the distance between them.
213
+ *
214
+ * @param {any} x
215
+ * @param {any} y
216
+ * @return {number}
217
+ */
218
+ export const sequenceOrder = (x, y) => {
219
+ if (x === y) return 0
220
+ if (x.parent !== y.parent || x.parentSub !== y.parentSub) return NaN
221
+ for (let l = x, r = y; ;) {
222
+ l = l.left
223
+ r = r.left
224
+ if (l === y || r === null) return 1
225
+ if (r === x || l === null) return -1
226
+ }
227
+ }
228
+
229
+ /**
230
+ * YP22: the chain record `r` stands at, outermost first — the units its
231
+ * writer named (`at`: the home slots from the root sequence down to the unit
232
+ * it stands at), else the first unit it moves — each as its item and clock,
233
+ * `null` where a GC struct replaced it.
234
+ *
235
+ * @param {any} r
236
+ * @param {import('./StructStore.js').StructStore} store
237
+ * @return {Array<{ item: any, clock: number } | null>}
238
+ */
239
+ const chainOf = (r, store) => {
240
+ const c = r.content
241
+ return c.at !== null
242
+ ? c.at.map((/** @type {ID} */ id) => unitAt(store, id.client, id.clock))
243
+ : c.ids.length > 0 ? [unitAt(store, c.ids[0], c.ids[1])] : []
244
+ }
245
+
246
+ /**
247
+ * YP22: the order of two move records at one origin: by the chains they
248
+ * stand at (`chainOf`), level by level from the root — two units of one
249
+ * sequence by `sequenceOrder` (one item: by clock), a unit before the units
250
+ * inside its block (the shorter chain first); chains that meet no common
251
+ * sequence order nothing — then by their `part` (the writer's order among
252
+ * the records standing at one unit), then the stronger record first (the
253
+ * greater priority, then client: a unit both move shows at the first of the
254
+ * two). Two writers that move blocks out of one list into one gap at once (an
255
+ * outdent, a lift, a divider after an item) keep the list's order there and
256
+ * every serial order's placement of what stands at one item, whatever their
257
+ * client ids (the delete contract's CW-01 and DR-crdt-6 rules). It reads only
258
+ * the records and the units they name, all present when a record integrates
259
+ * (`getMissing`), so every replica sorts alike in any delivery order.
260
+ *
261
+ * @param {any} o
262
+ * @param {any} item
263
+ * @param {Transaction} tr
264
+ * @return {number}
265
+ */
266
+ const sourceOrder = (o, item, tr) => {
267
+ const store = tr.doc.store
268
+ const x = chainOf(o, store)
269
+ const y = chainOf(item, store)
270
+ let d = 0
271
+ for (let i = 0; ; i++) {
272
+ const p = x[i]
273
+ const q = y[i]
274
+ if (p === undefined || q === undefined) {
275
+ d = (p === undefined ? 0 : 1) - (q === undefined ? 0 : 1)
276
+ break
277
+ }
278
+ if (p === null || q === null) break
279
+ d = unitOrder(p, q)
280
+ if (d === 0) continue
281
+ if (Number.isNaN(d)) d = 0
282
+ break
283
+ }
284
+ if (d === 0) d = o.content.part - item.content.part
285
+ if (d === 0) d = item.content.priority - o.content.priority || item.id.client - o.id.client
286
+ return d
287
+ }
288
+
137
289
  /**
138
290
  * Whether `o` sorts before `item` among the items of one origin (the
139
- * integration's conflict rule): by class, then by client id as upstream.
291
+ * integration's conflict rule): by class, then (YP22) block slots by their
292
+ * split key and the origin writer's own first, records by `sourceOrder`,
293
+ * then by client id as upstream.
140
294
  *
141
295
  * @param {any} o
142
296
  * @param {any} item
297
+ * @param {Transaction} tr
143
298
  * @return {boolean}
144
299
  */
145
- export const sortsBefore = (o, item) => {
300
+ export const sortsBefore = (o, item, tr) => {
146
301
  const a = markClass(o)
147
302
  const b = markClass(item)
148
- return a !== b ? a < b : o.id.client < item.id.client
303
+ if (a !== b) return a < b
304
+ if (a === 1) {
305
+ // YP22: block slots a split made sort by their boundaries' order in the
306
+ // text (two writers' splits of one block keep its text's order), before
307
+ // the slots no split made; then the origin writer's own slots first.
308
+ const store = tr.doc.store
309
+ const x = splitKey(o, store)
310
+ const y = splitKey(item, store)
311
+ if (x !== undefined || y !== undefined) {
312
+ if (x === undefined || y === undefined) return y === undefined
313
+ if (x !== null && y !== null) {
314
+ // One total order over the keyed slots of an origin: the boundaries' text
315
+ // first (by the text node's item id, a root text first), then their place in
316
+ // it (two boundaries one item holds: their clocks).
317
+ const px = /** @type {YNode} */ (x.item.parent)._item
318
+ const py = /** @type {YNode} */ (y.item.parent)._item
319
+ if (x.item.parent !== y.item.parent) {
320
+ if (px === null || py === null) return px === null
321
+ return (px.id.client - py.id.client || px.id.clock - py.id.clock) < 0
322
+ }
323
+ const d = unitOrder(x, y)
324
+ if (d !== 0) return d < 0
325
+ }
326
+ }
327
+ const run = extendsRun(o)
328
+ if (run !== extendsRun(item)) return run
329
+ } else if (a >= 4) {
330
+ const d = sourceOrder(o, item, tr)
331
+ if (d !== 0) return d < 0
332
+ }
333
+ return o.id.client < item.id.client
149
334
  }
@@ -38,7 +38,7 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, deletedByH
38
38
  */
39
39
  let right
40
40
  // make sure that parent is redone
41
- if (parentItem !== null && parentItem.deleted === true) {
41
+ if (parentItem !== null && parentItem.removed) { // YP20: a held parent is alive
42
42
  // try to undo parent if it will be undone anyway
43
43
  if (parentItem.redone === null && (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems, itemsToDelete, deletedByHistory) === null)) {
44
44
  return null
@@ -245,7 +245,7 @@ export const tryGcDeleteSet = (tr, ds, gcFilter) => {
245
245
  if (deleteItem.clock + deleteItem.len <= struct.id.clock) {
246
246
  break
247
247
  }
248
- if (struct.isItem && struct.deleted && !(struct).keep && !isKeptReplaced(/** @type {Item} */ (struct)) && gcFilter(/** @type {Item} */ (struct))) { // YP14
248
+ if (struct.isItem && struct.removed && !(struct).keep && !isKeptReplaced(/** @type {Item} */ (struct)) && gcFilter(/** @type {Item} */ (struct))) { // YP14, YP20: removed
249
249
  /** @type {Item} */ (struct).gc(tr, false)
250
250
  }
251
251
  }
@@ -42,9 +42,9 @@ function * lazyStructReaderGenerator (decoder) {
42
42
  null, // right
43
43
  (info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null, // right origin
44
44
  // @ts-ignore Force writing a string here.
45
- cantCopyParentInfo ? (decoder.readParentInfo() ? decoder.readString() : decoder.readLeftID()) : null, // parent
46
- cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub
47
- readItemContent(decoder, info) // item content
45
+ cantCopyParentInfo ? ((info & binary.BIT5) === binary.BIT5 ? decoder.readKey() : decoder.readLeftID()) : null, // parent (YP27: bit 5 a root's name, YP24: a key)
46
+ cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readKey() : null, // parentSub (YP24: a key)
47
+ readItemContent(decoder, info, createID(client, clock)) // item content (YP25: with its id)
48
48
  )
49
49
  yield struct
50
50
  clock += struct.length
@@ -10,6 +10,10 @@ import {
10
10
  ContentFormat,
11
11
  ContentString,
12
12
  ContentType,
13
+ ContentHold, // YP20
14
+ ContentMove, // YP22
15
+ hideIds, // YP32
16
+ readFormatValue, // YP25
13
17
  YXmlFragmentRefID,
14
18
  YXmlElementRefID,
15
19
  YXmlHookRefID
@@ -248,7 +252,7 @@ const insertMarkItem = (transaction, parent, index, side, content) => {
248
252
  const markHelper = (transaction, parent, index, length, mark, value, startSide, endSide) => {
249
253
  const doc = transaction.doc
250
254
  const client = doc.clientID
251
- const l = /** @type {any} */ (doc)._markClock + 1
255
+ const l = /** @type {any} */ (doc)._clock + 1 // YP22: one clock with the move records
252
256
  const k = doc.store.getClock(client)
253
257
  insertMarkItem(transaction, parent, index, startSide, new ContentFormat(MARK_START + mark, [value, l, client, k, startSide]))
254
258
  insertMarkItem(transaction, parent, index + length, endSide, new ContentFormat(MARK_END + mark, [client, k, endSide]))
@@ -292,28 +296,83 @@ export const insertAtGapEndHelper = (transaction, parent, index, content) => {
292
296
  }
293
297
  // YP7 end
294
298
 
299
+ // YP20 begin (edytor fork: hold records — see UPSTREAM.md YP20)
295
300
  /**
301
+ * The first clock in `(clock, end)` where membership in `set` changes, else `end`.
302
+ *
303
+ * @param {import('./utils/ids.js').IdSet} set
304
+ * @param {number} client
305
+ * @param {number} clock
306
+ * @param {number} end
307
+ * @return {number}
308
+ */
309
+ const runEnd = (set, client, clock, end) => {
310
+ for (const r of set.clients.get(client)?.getIds() ?? []) {
311
+ if (r.clock > clock) return math.min(r.clock, end)
312
+ if (r.clock + r.len > clock) return math.min(r.clock + r.len, end)
313
+ }
314
+ return end
315
+ }
316
+
317
+ /**
318
+ * Append a hold record of `ids` to `list` (the document's records list).
319
+ *
320
+ * @param {Transaction} transaction
321
+ * @param {YNode} list
322
+ * @param {Array<number>} ids
323
+ */
324
+ const holdHelper = (transaction, list, ids) => {
325
+ insertInGapHelper(transaction, list, list._length, new ContentHold(ids))
326
+ }
327
+ // YP20 end
328
+
329
+ /**
330
+ * With `held`, the live units are not deleted: their ids go to `held`
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`.
334
+ *
296
335
  * @param {Transaction} transaction
297
336
  * @param {ItemTextListPosition} currPos
298
337
  * @param {number} length
338
+ * @param {Array<number>|null} [held]
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`
299
343
  * @return {ItemTextListPosition}
300
344
  *
301
345
  * @private
302
346
  * @function
303
347
  */
304
- export const deleteText = (transaction, currPos, length) => {
348
+ export const deleteText = (transaction, currPos, length, held = null, fresh = null, sort = null, proposed = null, hidden = null) => {
305
349
  const startLength = length
350
+ let kept = 0 // YP20
306
351
  while (length > 0 && currPos.right !== null) {
307
352
  const item = currPos.right
308
353
  if (!item.deleted && item.countable) {
309
- if (length < item.length) {
310
- getItemCleanStart(transaction, createID(item.id.client, item.id.clock + length))
354
+ const { client, clock } = item.id
355
+ // YP20: a held run ends where the open step's inserts start or end
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
360
+ if (n < item.length) {
361
+ getItemCleanStart(transaction, createID(client, clock + n))
311
362
  }
312
363
  length -= item.length
313
- item.delete(transaction)
364
+ if (kind === 0) {
365
+ item.delete(transaction)
366
+ } else {
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)
369
+ kept += item.length
370
+ }
314
371
  }
315
372
  currPos.forward()
316
373
  }
374
+ // YP20: the held units hide when their record integrates
375
+ currPos.index -= kept
317
376
  const parent = /** @type {YNode} */ (/** @type {Item} */ (currPos.left || currPos.right).parent)
318
377
  if (parent._searchMarker) {
319
378
  updateMarkerChanges(parent._searchMarker, currPos.index, -startLength + length)
@@ -939,11 +998,25 @@ export class YNode {
939
998
 
940
999
  /**
941
1000
  * Deletes elements starting from an index. A deleted node is left as it is.
1001
+ * YP20: with `into` (the document's records list), the live units are not
1002
+ * removed but held by one record appended to `into` (their writer's delete,
1003
+ * which an undo of the record takes back), except those `fresh` names (the
1004
+ * open undo step's inserts: removed, so that step's undo changes nothing).
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.
942
1012
  *
943
1013
  * @param {number} index Index at which to start deleting elements
944
1014
  * @param {number} length The number of elements to remove. Defaults to 1.
1015
+ * @param {YNode|null} [into]
1016
+ * @param {import('./utils/ids.js').IdSet|null} [fresh]
1017
+ * @param {{ into: YNode, sort: (client: number, clock: number) => number }|null} [propose]
945
1018
  */
946
- delete (index, length = 1) {
1019
+ delete (index, length = 1, into = null, fresh = null, propose = null) {
947
1020
  if (!(length > 0)) return
948
1021
  if (this.doc == null) {
949
1022
  const runs = prelimOf(this).runs
@@ -959,11 +1032,63 @@ export class YNode {
959
1032
  if (this._item !== null && this._item.deleted) return
960
1033
  transact(this.doc, transaction => {
961
1034
  const currPos = cursorAt(transaction, this, index)
962
- deleteText(transaction, currPos, length)
1035
+ const held = into === null ? null : [] // YP20
1036
+ const [proposed, hidden] = [[], []] // YP32
1037
+ deleteText(transaction, currPos, length, held, fresh, propose && propose.sort, proposed, hidden)
1038
+ if (held !== null && held.length > 0) {
1039
+ // YP20: `deleteText` moved the markers as for a delete; the hold keeps them
1040
+ const ms = this._searchMarker === null ? null : this._searchMarker.slice()
1041
+ holdHelper(transaction, /** @type {YNode} */ (into), held)
1042
+ if (ms !== null && this._searchMarker !== null) this._searchMarker.splice(0, this._searchMarker.length, ...ms)
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)
963
1047
  plantMarker(this, currPos)
964
1048
  })
965
1049
  }
966
1050
 
1051
+ /**
1052
+ * YP20: append to this node (the document's records list) one hold record of
1053
+ * the items `ids` names (`client, clock, len` triples): they read as deleted
1054
+ * while the record is live.
1055
+ *
1056
+ * @param {Array<number>} ids
1057
+ */
1058
+ hold (ids) {
1059
+ if (this.doc == null) throw new Error('hold: node is not integrated')
1060
+ if (ids.length > 0) transact(this.doc, transaction => holdHelper(transaction, this, ids))
1061
+ }
1062
+
1063
+ // YP22 begin (edytor fork: move records — see UPSTREAM.md YP22)
1064
+ /**
1065
+ * YP22: move the units `ids` (`client, clock, len` ranges, in the order
1066
+ * they will show) to live index `index` of this sequence: one move record
1067
+ * there (`ContentMove`); `side` is its class among the items of its origin
1068
+ * (`0` after, `1` before), `at` the chain it stands at for the sort of two
1069
+ * records at one origin — the units from the root sequence down to the
1070
+ * unit it stands at, outermost first (`null`: the first unit it names) —
1071
+ * and `part` the writer's order among the records standing at that unit.
1072
+ *
1073
+ * @param {number} index
1074
+ * @param {Array<number>} ids
1075
+ * @param {number} [side]
1076
+ * @param {number} [part]
1077
+ * @param {Array<ID>|null} [at]
1078
+ */
1079
+ move (index, ids, side = 0, part = 0, at = null) {
1080
+ if (this.doc == null) throw new Error('move: node is not integrated')
1081
+ if (ids.length === 0) return
1082
+ transact(this.doc, transaction => {
1083
+ // Above every record seen from a peer (`_clock`, raised by remote records
1084
+ // only): this writer's records keep one priority until it sees another's,
1085
+ // so its gestures win or lose together against a concurrent peer's.
1086
+ const priority = /** @type {any} */ (transaction.doc)._clock + 1
1087
+ insertInGapHelper(transaction, this, index, new ContentMove(priority, side, ids, part, at))
1088
+ })
1089
+ }
1090
+ // YP22 end
1091
+
967
1092
  /**
968
1093
  * Executes a provided function on once on every key-value pair.
969
1094
  *
@@ -1081,9 +1206,13 @@ export const readContentString = decoder => new ContentString(decoder.readString
1081
1206
 
1082
1207
  /**
1083
1208
  * @param {UpdateDecoder} decoder
1209
+ * @param {import('./utils/ID.js').ID} id YP25: the item's id (a paired mark's operation id is usually it)
1084
1210
  * @return {ContentFormat}
1085
1211
  */
1086
- export const readContentFormat = decoder => new ContentFormat(decoder.readKey(), decoder.readJSON())
1212
+ export const readContentFormat = (decoder, id) => {
1213
+ const key = decoder.readKey()
1214
+ return new ContentFormat(key, readFormatValue(decoder, key, id)) // YP25
1215
+ }
1087
1216
 
1088
1217
  /**
1089
1218
  * @param {UpdateDecoder} decoder
@@ -1112,14 +1241,68 @@ export const readContentBinary = decoder => new ContentBinary(decoder.readBuf())
1112
1241
  */
1113
1242
  export const readContentDeleted = decoder => new ContentDeleted(decoder.readLen())
1114
1243
 
1244
+ /**
1245
+ * YP20, YP22: a record's id ranges, a count then one `client, clock, len` each.
1246
+ *
1247
+ * @param {UpdateDecoder} decoder
1248
+ * @return {Array<number>}
1249
+ */
1250
+ const readIds = decoder => {
1251
+ const ids = []
1252
+ for (let n = decoder.readLen(); n > 0; n--) {
1253
+ const id = decoder.readLeftID()
1254
+ ids.push(id.client, id.clock, decoder.readLen())
1255
+ }
1256
+ return ids
1257
+ }
1258
+
1259
+ /**
1260
+ * YP20: a hold record, one `client, clock, len` per unit.
1261
+ *
1262
+ * @param {UpdateDecoder} decoder
1263
+ * @return {ContentHold}
1264
+ */
1265
+ export const readContentHold = decoder => new ContentHold(readIds(decoder))
1266
+
1267
+ /**
1268
+ * YP22: a move record: its priority, side, part and whether an anchor chain
1269
+ * follows in one word, the chain (a count, then its units), then one
1270
+ * `client, clock, len` per range.
1271
+ *
1272
+ * @param {UpdateDecoder} decoder
1273
+ * @return {ContentMove}
1274
+ */
1275
+ export const readContentMove = decoder => {
1276
+ const word = decoder.readLen()
1277
+ /** @type {Array<ID>|null} */
1278
+ let at = null
1279
+ if (word & 16) {
1280
+ at = []
1281
+ for (let n = decoder.readLen(); n > 0; n--) at.push(decoder.readLeftID())
1282
+ }
1283
+ return new ContentMove(word >> 5, word & 1, readIds(decoder), (word >> 1) & 7, at)
1284
+ }
1285
+
1286
+ /**
1287
+ * YP29: a collected mark or claim, its length and class.
1288
+ *
1289
+ * @param {UpdateDecoder} decoder
1290
+ * @return {ContentDeleted}
1291
+ */
1292
+ export const readContentClass = decoder => {
1293
+ const word = decoder.readLen()
1294
+ return new ContentDeleted(Math.floor(word / 4), word % 4)
1295
+ }
1296
+
1115
1297
  const refused = () => { error.unexpectedCase() }
1116
1298
 
1117
1299
  /**
1118
1300
  * A lookup map for reading Item content. YP19: JSON (2), embed (5) and
1119
1301
  * subdocument (9) contents are refused like GC (0) and Skip (10): edytor
1120
- * writes none, so an update holding one is malformed.
1302
+ * writes none, so an update holding one is malformed. YP20: 11 is a hold
1303
+ * record. YP22: 12 is a move record.
1121
1304
  *
1122
- * @type {Array<function(UpdateDecoder):AbstractContent>}
1305
+ * @type {Array<function(UpdateDecoder, import('./utils/ID.js').ID):AbstractContent>}
1123
1306
  */
1124
1307
  export const contentRefs = [
1125
1308
  refused, // 0 - GC is not ItemContent
@@ -1132,14 +1315,18 @@ export const contentRefs = [
1132
1315
  readContentType, // 7
1133
1316
  readContentAny, // 8
1134
1317
  refused, // 9 - subdocument
1135
- refused // 10 - Skip is not ItemContent
1318
+ refused, // 10 - Skip is not ItemContent
1319
+ readContentHold, // 11 - a hold record (YP20)
1320
+ readContentMove, // 12 - a move record (YP22)
1321
+ readContentClass // 13 - a collected mark or claim, its class (YP29)
1136
1322
  ]
1137
1323
 
1138
1324
  /**
1139
1325
  * @param {UpdateDecoder} decoder
1140
1326
  * @param {number} info
1327
+ * @param {import('./utils/ID.js').ID} id YP25: the item's id (a format item reads it)
1141
1328
  */
1142
- export const readItemContent = (decoder, info) => contentRefs[info & binary.BITS5](decoder)
1329
+ export const readItemContent = (decoder, info, id) => contentRefs[info & binary.BITS4](decoder, id) // YP27: four bits
1143
1330
 
1144
1331
  /**
1145
1332
  * @param {UpdateDecoder} decoder
@@ -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';