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
@@ -5,11 +5,15 @@ import { AbstractStruct, addStructToIdSet } from '../structs/AbstractStruct.js'
5
5
 
6
6
  import { ID, createID, compareIDs, findRootTypeKey } from '../utils/ID.js'
7
7
  import { GC } from '../structs/GC.js'
8
- import { sortsBefore, pairedRole } from '../utils/marks.js' // YP13
8
+ import { sortsBefore, pairedRole, markClass } from '../utils/marks.js' // YP13, YP21
9
+
10
+ import * as math from 'lib0-v14/math' // YP20
9
11
 
10
12
  import {
11
13
  replaceStruct,
12
14
  getItemCleanEnd,
15
+ getItemCleanStart, // YP20
16
+ findIndexSS, // YP20
13
17
  addChangedTypeToTransaction,
14
18
  isKeptReplaced // YP14
15
19
  } from '../utils/transaction-helpers.js'
@@ -107,6 +111,21 @@ export class Item extends AbstractStruct {
107
111
  * @type {number} byte
108
112
  */
109
113
  this.info = this.content.isCountable() ? binary.BIT2 : 0
114
+ /**
115
+ * YP20: how many live hold records (`ContentHold`) name this item. A
116
+ * held item reads as deleted; the count is local, never encoded (each
117
+ * replica counts the records it integrated).
118
+ * @type {number}
119
+ */
120
+ this.holds = 0
121
+ /**
122
+ * YP22: the live move records (`ContentMove`) naming this item, the
123
+ * strongest first — the greatest `(priority, client, clock)` — or `null`.
124
+ * The first is its mover: the item shows there, hidden at home. Local,
125
+ * never encoded.
126
+ * @type {Array<Item>|null}
127
+ */
128
+ this.movers = null
110
129
  }
111
130
 
112
131
  /**
@@ -142,10 +161,38 @@ export class Item extends AbstractStruct {
142
161
  }
143
162
 
144
163
  /**
145
- * Whether this item was deleted or not.
164
+ * Whether this item is hidden at home: removed, held by a live hold record
165
+ * (YP20), or moved by a live move record (YP22: it shows at its mover).
146
166
  * @type {Boolean}
147
167
  */
148
168
  get deleted () {
169
+ return (this.info & binary.BIT3) > 0 || this.holds > 0 || this.movers !== null
170
+ }
171
+
172
+ /**
173
+ * Whether this item shows nowhere: removed or held (YP22; a moved item
174
+ * shows at its mover).
175
+ * @type {Boolean}
176
+ */
177
+ get gone () {
178
+ return (this.info & binary.BIT3) > 0 || this.holds > 0
179
+ }
180
+
181
+ /**
182
+ * YP22: the live move record this item shows at (the strongest of
183
+ * `movers`), or `null` at home.
184
+ * @type {Item|null}
185
+ */
186
+ get mover () {
187
+ return this.movers === null ? null : this.movers[0]
188
+ }
189
+
190
+ /**
191
+ * Whether the engine deleted this item (the delete set's fact; YP20: a held
192
+ * item is hidden, not removed).
193
+ * @type {Boolean}
194
+ */
195
+ get removed () {
149
196
  return (this.info & binary.BIT3) > 0
150
197
  }
151
198
 
@@ -206,7 +253,7 @@ export class Item extends AbstractStruct {
206
253
  conflictingItems.add(o)
207
254
  if (compareIDs(this.origin, o.origin)) {
208
255
  // case 1
209
- if (sortsBefore(o, this)) { // YP13: paired marks sort by side, then by client id
256
+ if (sortsBefore(o, this, transaction)) { // YP13: paired marks sort by side, then by client id
210
257
  left = o
211
258
  conflictingItems.clear()
212
259
  } else if (compareIDs(this.rightOrigin, o.rightOrigin)) {
@@ -266,7 +313,7 @@ export class Item extends AbstractStruct {
266
313
  addChangedTypeToTransaction(transaction, /** @type {YNode} */ (this.parent), this.parentSub)
267
314
  const parentItem = /** @type {YNode} */ (this.parent)._item
268
315
  // YP14: a node a concurrent value replaced keeps its subtree: what lands in it stays
269
- if ((parentItem !== null && parentItem.deleted && !isKeptReplaced(parentItem)) || (this.parentSub !== null && this.right !== null)) {
316
+ if ((parentItem !== null && parentItem.removed && !isKeptReplaced(parentItem)) || (this.parentSub !== null && this.right !== null)) { // YP20: a held parent is alive
270
317
  // delete if parent is deleted or if this is not the current attribute value of parent
271
318
  this.delete(transaction)
272
319
  }
@@ -298,7 +345,9 @@ export class Item extends AbstractStruct {
298
345
  compareIDs(this.rightOrigin, right.rightOrigin) &&
299
346
  this.id.client === right.id.client &&
300
347
  this.id.clock + this.length === right.id.clock &&
301
- this.deleted === right.deleted &&
348
+ this.removed === right.removed &&
349
+ this.holds === right.holds && // YP20
350
+ this.movers === null && right.movers === null && // YP22: a moved unit stands alone
302
351
  this.redone === null &&
303
352
  right.redone === null &&
304
353
  this.content.constructor === right.content.constructor &&
@@ -339,10 +388,16 @@ export class Item extends AbstractStruct {
339
388
  * @param {Transaction} transaction
340
389
  */
341
390
  delete (transaction) {
342
- if (!this.deleted) {
391
+ if (!this.removed) {
343
392
  const parent = /** @type {YNode} */ (this.parent)
344
393
  // adjust the length of parent
345
- if (this.countable && this.parentSub === null) {
394
+ if (this.holds > 0) {
395
+ // YP20: already hidden — the delete set counts it again, the log gives it back
396
+ logHold(transaction, this, this.length)
397
+ } else if (this.movers !== null) {
398
+ // YP22: hidden at home already; its mover shows it no more (`gone`)
399
+ logMove(transaction, this, this.mover)
400
+ } else if (this.countable && this.parentSub === null) {
346
401
  parent._length -= this.length
347
402
  }
348
403
  // WU9 format-aware markers: tombstoning a format item changes the format
@@ -358,7 +413,7 @@ export class Item extends AbstractStruct {
358
413
  transaction.deleteSet.add(this.id.client, this.id.clock, this.length)
359
414
  addChangedTypeToTransaction(transaction, parent, this.parentSub)
360
415
  // YP14: a node a concurrent value replaced keeps its subtree (the document asked)
361
- if (!isKeptReplaced(this)) this.content.delete(transaction)
416
+ if (!isKeptReplaced(this)) this.content.delete(transaction, this) // YP22: a move record drops itself from its units' movers
362
417
  }
363
418
  }
364
419
 
@@ -367,16 +422,28 @@ export class Item extends AbstractStruct {
367
422
  * @param {boolean} parentGCd
368
423
  */
369
424
  gc (tr, parentGCd) {
370
- if (!this.deleted) {
425
+ if (!this.removed) { // YP20
371
426
  throw error.unexpectedCase()
372
427
  }
373
428
  this.content.gc(tr)
374
429
  if (parentGCd) {
375
430
  replaceStruct(tr, this, new GC(this.id, this.length))
376
- } else if (!(this.content.constructor === ContentFormat && pairedRole(/** @type {ContentFormat} */ (this.content)) >= 0)) {
377
- // YP13: a paired mark item keeps its content: its side orders
378
- // concurrent inserts (`markClass`) on every replica, collected or not.
379
- this.content = new ContentDeleted(this.length)
431
+ } else {
432
+ // YP13, YP21, YP22, YP29: an item of another class than content keeps
433
+ // what orders concurrent inserts at its origin (`markClass`) on every
434
+ // replica, collected or not: a paired mark item or a claim item its
435
+ // class, a move record its content (its sort reads its chain). A block
436
+ // slot a split made keeps its split key the same way (`splitKey`),
437
+ // trimmed to it.
438
+ const cls = markClass(this)
439
+ const arr = /** @type {any} */ (this.content).arr
440
+ if (cls === 1) {
441
+ const keys = arr?.map((/** @type {any} */ v) => (Array.isArray(v?.s) ? { s: v.s } : null))
442
+ this.content = keys?.some((/** @type {any} */ k) => k !== null) ? new ContentAny(keys) : new ContentDeleted(this.length)
443
+ } else if (cls < 3 || (cls === 3 && arr?.every((/** @type {any} */ v) => typeof v?.m === 'string'))) {
444
+ // every unit of the item has its class (a mark is one unit; merged claims all are claims)
445
+ this.content = new ContentDeleted(this.length, cls)
446
+ }
380
447
  }
381
448
  }
382
449
 
@@ -399,9 +466,11 @@ export class Item extends AbstractStruct {
399
466
  this.parentSub,
400
467
  this.content.splice(diff)
401
468
  )
402
- if (this.deleted) {
469
+ if (this.removed) { // YP20
403
470
  rightItem.markDeleted()
404
471
  }
472
+ rightItem.holds = this.holds // YP20
473
+ rightItem.movers = this.movers?.slice() ?? null // YP22
405
474
  if (this.keep) {
406
475
  rightItem.keep = true
407
476
  }
@@ -443,10 +512,15 @@ export class Item extends AbstractStruct {
443
512
  const origin = offset > 0 ? createID(this.id.client, this.id.clock + offset - 1) : this.origin
444
513
  const rightOrigin = this.rightOrigin
445
514
  const parentSub = this.parentSub
446
- const info = (this.content.getRef() & binary.BITS5) |
515
+ const parent = /** @type {any} */ (this.parent)
516
+ // YP27: an item written with its parent says in bit 5 whether the parent is
517
+ // a root (its name follows) or an item (its id follows): no parentInfo column
518
+ const named = origin === null && rightOrigin === null && (parent.constructor === String || parent._item === null)
519
+ const info = (this.content.getRef() & binary.BITS4) | // YP27: content refs fit four bits
447
520
  (origin === null ? 0 : binary.BIT8) | // origin is defined
448
521
  (rightOrigin === null ? 0 : binary.BIT7) | // right origin is defined
449
- (parentSub === null ? 0 : binary.BIT6) // parentSub is non-null
522
+ (parentSub === null ? 0 : binary.BIT6) | // parentSub is non-null
523
+ (named ? binary.BIT5 : 0) // YP27: the parent is a root, by name
450
524
  encoder.writeInfo(info)
451
525
  if (origin !== null) {
452
526
  encoder.writeLeftID(origin)
@@ -455,33 +529,21 @@ export class Item extends AbstractStruct {
455
529
  encoder.writeRightID(rightOrigin)
456
530
  }
457
531
  if (origin === null && rightOrigin === null) {
458
- const parent = /** @type {YNode} */ (this.parent)
459
- if (parent._item !== undefined) {
460
- const parentItem = parent._item
461
- if (parentItem === null) {
462
- // parent type on y._map
463
- // find the correct key
464
- const ykey = findRootTypeKey(parent)
465
- encoder.writeParentInfo(true) // write parentYKey
466
- encoder.writeString(ykey)
467
- } else {
468
- encoder.writeParentInfo(false) // write parent id
469
- encoder.writeLeftID(parentItem.id)
470
- }
471
- } else if (parent.constructor === String) { // this edge case was added by differential updates
472
- encoder.writeParentInfo(true) // write parentYKey
473
- encoder.writeString(parent)
532
+ if (named) {
533
+ // a root's name (a string parent: an edge case of differential updates)
534
+ encoder.writeKey(parent.constructor === String ? /** @type {string} */ (parent) : findRootTypeKey(parent)) // YP24
474
535
  } else if (parent.constructor === ID) {
475
- encoder.writeParentInfo(false) // write parent id
476
536
  encoder.writeLeftID(parent)
537
+ } else if (parent._item) {
538
+ encoder.writeLeftID(parent._item.id)
477
539
  } else {
478
540
  error.unexpectedCase()
479
541
  }
480
542
  if (parentSub !== null) {
481
- encoder.writeString(parentSub)
543
+ encoder.writeKey(parentSub) // YP24
482
544
  }
483
545
  }
484
- this.content.write(encoder, offset, offsetEnd)
546
+ this.content.write(encoder, offset, offsetEnd, this) // YP25: a format item reads its own id
485
547
  }
486
548
 
487
549
  get ref () {
@@ -558,8 +620,9 @@ export class AbstractContent {
558
620
 
559
621
  /**
560
622
  * @param {Transaction} _transaction
623
+ * @param {Item} [_item] the item deleted (YP22: a move record reads it)
561
624
  */
562
- delete (_transaction) {
625
+ delete (_transaction, _item) {
563
626
  throw error.methodUnimplemented()
564
627
  }
565
628
 
@@ -574,15 +637,16 @@ export class AbstractContent {
574
637
  * @param {UpdateEncoder} _encoder
575
638
  * @param {number} _offset
576
639
  * @param {number} _offsetEnd
640
+ * @param {Item} [_item] the item written (YP25: a format item reads its id)
577
641
  */
578
- write (_encoder, _offset, _offsetEnd) {
642
+ write (_encoder, _offset, _offsetEnd, _item) {
579
643
  throw error.methodUnimplemented()
580
644
  }
581
645
 
582
646
  /**
583
- * @return {1|2|3|4|5|6|7|8|9}
647
+ * @return {1|2|3|4|5|6|7|8|9|11|12|13}
584
648
  */
585
- getRef () {
649
+ getRef () { // YP20: 11, a hold record; YP22: 12, a move record; YP29: 13, a collected mark's class
586
650
  throw error.methodUnimplemented()
587
651
  }
588
652
  }
@@ -766,9 +830,19 @@ export class ContentBinary {
766
830
  export class ContentDeleted {
767
831
  /**
768
832
  * @param {number} len
833
+ * @param {number} [cls] YP29: a collected mark's or claim's sort class
769
834
  */
770
- constructor (len) {
835
+ constructor (len, cls) {
771
836
  this.len = len
837
+ /**
838
+ * YP29: what a collected paired mark item or claim item keeps, for each
839
+ * of its units: its sort class (`markClass`: `0` a left-side mark, `2` a
840
+ * right-side one, `3` a claim), which orders concurrent inserts at its
841
+ * origin on every replica (`undefined`: none, any other deleted content).
842
+ *
843
+ * @type {number|undefined}
844
+ */
845
+ this.cls = cls
772
846
  }
773
847
 
774
848
  /**
@@ -789,14 +863,14 @@ export class ContentDeleted {
789
863
  * @return {boolean}
790
864
  */
791
865
  isCountable () {
792
- return false
866
+ return this.cls === 3 // YP29: a claim is a unit of its text
793
867
  }
794
868
 
795
869
  /**
796
870
  * @return {ContentDeleted}
797
871
  */
798
872
  copy () {
799
- return new ContentDeleted(this.len)
873
+ return new ContentDeleted(this.len, this.cls)
800
874
  }
801
875
 
802
876
  /**
@@ -804,7 +878,7 @@ export class ContentDeleted {
804
878
  * @return {ContentDeleted}
805
879
  */
806
880
  splice (offset) {
807
- const right = new ContentDeleted(this.len - offset)
881
+ const right = new ContentDeleted(this.len - offset, this.cls)
808
882
  this.len = offset
809
883
  return right
810
884
  }
@@ -814,6 +888,7 @@ export class ContentDeleted {
814
888
  * @return {boolean}
815
889
  */
816
890
  mergeWith (right) {
891
+ if (right.cls !== this.cls) return false // YP29
817
892
  this.len += right.len
818
893
  return true
819
894
  }
@@ -841,20 +916,541 @@ export class ContentDeleted {
841
916
  * @param {number} offsetEnd
842
917
  */
843
918
  write (encoder, offset, offsetEnd) {
844
- encoder.writeLen(this.len - offset - offsetEnd)
919
+ const len = this.len - offset - offsetEnd
920
+ encoder.writeLen(this.cls === undefined ? len : len * 4 + this.cls) // YP29
921
+ }
922
+
923
+ /**
924
+ * @return {1|13}
925
+ */
926
+ getRef () {
927
+ return this.cls === undefined ? 1 : 13 // YP29: 13, a collected mark or claim
928
+ }
929
+ }
930
+
931
+ // YP20 begin (edytor fork: hold records — see UPSTREAM.md YP20)
932
+ /**
933
+ * Log that a hold changed `item`'s visibility (`len > 0`: shown, `< 0`:
934
+ * hidden) in `tr.holds`, in order, and its parent as changed.
935
+ *
936
+ * @param {Transaction} tr
937
+ * @param {Item} item
938
+ * @param {number} len
939
+ */
940
+ const logHold = (tr, item, len) => {
941
+ tr.holds.push(item.id.client, item.id.clock, len)
942
+ addChangedTypeToTransaction(tr, /** @type {YNode} */ (item.parent), item.parentSub)
943
+ }
944
+
945
+ /**
946
+ * Add `delta` (1, or -1: release) of a hold record (`record` null: `holds`)
947
+ * or of the move record `record` (its `movers`, YP22; a record never moves a
948
+ * record) to `item`. An item that is not removed and starts or stops being
949
+ * hidden moves its parent's length and drops its search markers; a hold that
950
+ * does is logged (`tr.holds`), and so is a unit whose mover changed
951
+ * (`tr.moves`).
952
+ *
953
+ * @param {Transaction} tr
954
+ * @param {Item} item
955
+ * @param {number} delta
956
+ * @param {Item|null} record
957
+ */
958
+ const holdItem = (tr, item, delta, record) => {
959
+ const was = item.deleted
960
+ const before = item.mover
961
+ if (record === null) item.holds += delta
962
+ else if (item.content.constructor === ContentMove) return
963
+ else if (delta > 0) {
964
+ const m = (item.movers ??= [])
965
+ const i = m.findIndex(r => stronger(record, r))
966
+ m.splice(i < 0 ? m.length : i, 0, record)
967
+ }
968
+ else if (item.movers !== null) {
969
+ const i = item.movers.indexOf(record)
970
+ if (i >= 0) item.movers.splice(i, 1)
971
+ if (item.movers.length === 0) item.movers = null
972
+ }
973
+ if (was !== item.deleted) {
974
+ const parent = /** @type {YNode} */ (item.parent)
975
+ const len = was ? item.length : -item.length
976
+ if (item.countable && item.parentSub === null) parent._length += len
977
+ // the indices the search markers counted moved (a held delete moves them itself)
978
+ if (parent._searchMarker) parent._searchMarker.length = 0
979
+ if (record === null) logHold(tr, item, len)
980
+ }
981
+ const after = item.mover
982
+ if (before !== after) {
983
+ logMove(tr, item, after)
984
+ if (before !== null) addChangedTypeToTransaction(tr, /** @type {YNode} */ (before.parent), null)
985
+ }
986
+ }
987
+
988
+ /**
989
+ * A hold this replica cannot apply yet (its items have not arrived): it waits
990
+ * in `store.pendingHolds` (`client → [clock, len, delta, …]`).
991
+ *
992
+ * @param {import('../utils/StructStore.js').StructStore} store
993
+ * @param {number} client
994
+ * @param {number} from
995
+ * @param {number} to
996
+ * @param {number} delta
997
+ */
998
+ const pendHold = (store, client, from, to, delta) => {
999
+ const pending = store.pendingHolds ?? (store.pendingHolds = new Map())
1000
+ const list = pending.get(client)
1001
+ if (list === undefined) pending.set(client, [from, to - from, delta])
1002
+ else list.push(from, to - from, delta)
1003
+ }
1004
+
1005
+ /**
1006
+ * Hold (`delta` 1) or release (-1) the ids `client:clock … clock+len-1`, or
1007
+ * add or drop the move record `record` on them (YP22): the items here take
1008
+ * it now, split at the edges; what this replica lacks (past its clock, or a
1009
+ * Skip) waits until it integrates (`applyPending`) — a hold's only: a move
1010
+ * record integrates once every unit it names has, and no Skip stands for
1011
+ * one (`getMissing`). A GC struct takes nothing (it is gone everywhere).
1012
+ *
1013
+ * @param {Transaction} tr
1014
+ * @param {number} client
1015
+ * @param {number} clock
1016
+ * @param {number} len
1017
+ * @param {number} delta
1018
+ * @param {Item|null} [record]
1019
+ */
1020
+ const holdRange = (tr, client, clock, len, delta, record = null) => {
1021
+ const store = tr.doc.store
1022
+ const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
1023
+ const end = clock + len
1024
+ const state = store.getClock(client)
1025
+ if (end > state) pendHold(store, client, math.max(clock, state), end, delta)
1026
+ for (let k = clock; k < math.min(end, state);) {
1027
+ let struct = structs[findIndexSS(structs, k)]
1028
+ const to = math.min(struct.id.clock + struct.length, end)
1029
+ if (struct.isItem) {
1030
+ if (struct.id.clock < k) struct = getItemCleanStart(tr, createID(client, k))
1031
+ if (struct.id.clock + struct.length > to) getItemCleanStart(tr, createID(client, to))
1032
+ holdItem(tr, /** @type {Item} */ (struct), delta, record)
1033
+ } else if (!struct.deleted) {
1034
+ pendHold(store, client, k, to, delta) // a Skip
1035
+ }
1036
+ k = to
1037
+ }
1038
+ }
1039
+
1040
+ /**
1041
+ * Hold or release (`delta`) the items `ids` names (`client, clock, len`
1042
+ * triples), or add or drop the move record `record` on them.
1043
+ *
1044
+ * @param {Transaction} tr
1045
+ * @param {Array<number>} ids
1046
+ * @param {number} delta
1047
+ * @param {Item|null} [record]
1048
+ */
1049
+ const holdIds = (tr, ids, delta, record = null) => {
1050
+ for (let i = 0; i < ids.length; i += 3) holdRange(tr, ids[i], ids[i + 1], ids[i + 2], delta, record)
1051
+ }
1052
+
1053
+ /**
1054
+ * Write the id ranges `ids[from … to)` (`client, clock, len` triples): their
1055
+ * count, then each as a left id and a length (a hold record's and a move
1056
+ * record's ranges).
1057
+ *
1058
+ * @param {UpdateEncoder} encoder
1059
+ * @param {Array<number>} ids
1060
+ * @param {number} from
1061
+ * @param {number} to
1062
+ */
1063
+ const writeIds = (encoder, ids, from, to) => {
1064
+ encoder.writeLen((to - from) / 3)
1065
+ for (let i = from; i < to; i += 3) {
1066
+ encoder.writeLeftID(createID(ids[i], ids[i + 1]))
1067
+ encoder.writeLen(ids[i + 2])
1068
+ }
1069
+ }
1070
+
1071
+ /**
1072
+ * A hold record: one unit per held id range (`client, clock, len`). While the
1073
+ * record is live, the items it names read as deleted; deleting the record
1074
+ * shows them again (an undo), unless another live record holds them. A record
1075
+ * integrates at once; what it names that has not arrived takes its hold when
1076
+ * it does (`applyPending`).
1077
+ */
1078
+ export class ContentHold {
1079
+ /**
1080
+ * @param {Array<number>} ids
1081
+ */
1082
+ constructor (ids) {
1083
+ this.ids = ids
1084
+ }
1085
+
1086
+ /**
1087
+ * @return {number}
1088
+ */
1089
+ getLength () {
1090
+ return this.ids.length / 3
1091
+ }
1092
+
1093
+ /**
1094
+ * @return {Array<any>}
1095
+ */
1096
+ getContent () {
1097
+ const out = []
1098
+ for (let i = 0; i < this.ids.length; i += 3) out.push(this.ids.slice(i, i + 3))
1099
+ return out
1100
+ }
1101
+
1102
+ /**
1103
+ * @return {boolean}
1104
+ */
1105
+ isCountable () {
1106
+ return true
1107
+ }
1108
+
1109
+ /**
1110
+ * @return {ContentHold}
1111
+ */
1112
+ copy () {
1113
+ return new ContentHold(this.ids)
1114
+ }
1115
+
1116
+ /**
1117
+ * @param {number} offset
1118
+ * @return {ContentHold}
1119
+ */
1120
+ splice (offset) {
1121
+ const right = new ContentHold(this.ids.slice(offset * 3))
1122
+ this.ids = this.ids.slice(0, offset * 3)
1123
+ return right
1124
+ }
1125
+
1126
+ /**
1127
+ * @param {ContentHold} right
1128
+ * @return {boolean}
1129
+ */
1130
+ mergeWith (right) {
1131
+ this.ids = this.ids.concat(right.ids)
1132
+ return true
1133
+ }
1134
+
1135
+ /**
1136
+ * @param {Transaction} transaction
1137
+ * @param {Item} item
1138
+ */
1139
+ integrate (transaction, item) {
1140
+ if (holding(item)) holdIds(transaction, this.ids, 1) // YP31
1141
+ }
1142
+
1143
+ /**
1144
+ * @param {Transaction} transaction
1145
+ * @param {Item} item
1146
+ */
1147
+ delete (transaction, item) {
1148
+ if (holding(item)) holdIds(transaction, this.ids, -1) // YP31
1149
+ }
1150
+
1151
+ /**
1152
+ * @param {Transaction} _tr
1153
+ */
1154
+ gc (_tr) {}
1155
+
1156
+ /**
1157
+ * @param {UpdateEncoder} encoder
1158
+ * @param {number} offset
1159
+ * @param {number} offsetEnd
1160
+ */
1161
+ write (encoder, offset, offsetEnd) {
1162
+ writeIds(encoder, this.ids, offset * 3, this.ids.length - offsetEnd * 3)
845
1163
  }
846
1164
 
847
1165
  /**
848
- * @return {1}
1166
+ * @return {11}
849
1167
  */
850
1168
  getRef () {
1169
+ return 11
1170
+ }
1171
+ }
1172
+ // YP20 end
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
+
1212
+ // YP22 begin (edytor fork: move records — see UPSTREAM.md YP22)
1213
+ /**
1214
+ * Whether move record `a` beats `b`: the greater `(priority, client, clock)`
1215
+ * (a unit's `movers` are kept in that order, the strongest first).
1216
+ *
1217
+ * @param {Item} a
1218
+ * @param {Item} b
1219
+ * @return {boolean}
1220
+ */
1221
+ const stronger = (a, b) =>
1222
+ (/** @type {ContentMove} */ (a.content).priority - /** @type {ContentMove} */ (b.content).priority || a.id.client - b.id.client || a.id.clock - b.id.clock) > 0
1223
+
1224
+ /**
1225
+ * Log that `item`'s mover changed (or it left its mover: removed) in
1226
+ * `tr.moves`, in order, and its home and its new mover's sequences as changed.
1227
+ *
1228
+ * @param {Transaction} tr
1229
+ * @param {Item} item
1230
+ * @param {Item|null} mover
1231
+ */
1232
+ const logMove = (tr, item, mover) => {
1233
+ tr.moves.push(item.id.client, item.id.clock, item.length)
1234
+ addChangedTypeToTransaction(tr, /** @type {YNode} */ (item.parent), item.parentSub)
1235
+ if (mover !== null) addChangedTypeToTransaction(tr, /** @type {YNode} */ (mover.parent), null)
1236
+ }
1237
+
1238
+ /**
1239
+ * Apply the holds (YP20) that waited for items of `clients` an update just
1240
+ * integrated (what is still missing waits on).
1241
+ *
1242
+ * @param {Transaction} tr
1243
+ * @param {Array<number>} clients
1244
+ */
1245
+ export const applyPending = (tr, clients) => {
1246
+ const store = tr.doc.store
1247
+ for (const client of clients) {
1248
+ const holds = store.pendingHolds?.get(client)
1249
+ if (holds !== undefined) {
1250
+ /** @type {Map<number, Array<number>>} */ (store.pendingHolds).delete(client)
1251
+ for (let i = 0; i < holds.length; i += 3) holdRange(tr, client, holds[i], holds[i + 1], holds[i + 2])
1252
+ }
1253
+ }
1254
+ if (store.pendingHolds?.size === 0) store.pendingHolds = null
1255
+ }
1256
+
1257
+ /**
1258
+ * A move record: one countable unit at its destination, naming the units
1259
+ * it moves (`client, clock, len` ranges, in the order they show) with a
1260
+ * Lamport `priority`, a `side` (`0`: it stands after what precedes it,
1261
+ * `1`: before what follows it — its sort class among the items of one
1262
+ * origin, `markClass`), and for the sort of two records at one origin
1263
+ * (`utils/marks.js` `sourceOrder`) the chain of units it stands at (`at`:
1264
+ * the home slots from the root sequence down to that unit, else the first
1265
+ * unit it names) and its `part` there. While the record is live, each named unit shows at
1266
+ * the record that wins its movers and is hidden at home; deleting the
1267
+ * record (an undo) hands the unit to the next record, else home. A record
1268
+ * waits for the units it names as an item waits for its origin
1269
+ * (`getMissing`), so two records at one origin sort by their units' home
1270
+ * order on every replica (`sourceOrder`). Immutable once written, so
1271
+ * replicas converge in any order. The record's item is the one integrating
1272
+ * or deleting it (it is what the units' `movers` hold).
1273
+ */
1274
+ export class ContentMove {
1275
+ /**
1276
+ * @param {number} priority
1277
+ * @param {number} side
1278
+ * @param {Array<number>} ids
1279
+ * @param {number} [part] the writer's order among the records standing at one unit (0–7)
1280
+ * @param {Array<ID>|null} [at] the chain the record stands at, outermost first (`null`: the first unit it names)
1281
+ */
1282
+ constructor (priority, side, ids, part = 0, at = null) {
1283
+ this.priority = priority
1284
+ this.side = side
1285
+ this.ids = ids
1286
+ this.part = part
1287
+ this.at = at
1288
+ }
1289
+
1290
+ /**
1291
+ * @return {number}
1292
+ */
1293
+ getLength () {
851
1294
  return 1
852
1295
  }
1296
+
1297
+ /**
1298
+ * @return {Array<any>}
1299
+ */
1300
+ getContent () {
1301
+ return [this]
1302
+ }
1303
+
1304
+ /**
1305
+ * @return {boolean}
1306
+ */
1307
+ isCountable () {
1308
+ return true
1309
+ }
1310
+
1311
+ /**
1312
+ * @return {ContentMove}
1313
+ */
1314
+ copy () {
1315
+ return new ContentMove(this.priority, this.side, this.ids, this.part, this.at)
1316
+ }
1317
+
1318
+ /**
1319
+ * @param {number} _offset
1320
+ * @return {ContentMove}
1321
+ */
1322
+ splice (_offset) {
1323
+ throw error.methodUnimplemented()
1324
+ }
1325
+
1326
+ /**
1327
+ * @param {ContentMove} _right
1328
+ * @return {boolean}
1329
+ */
1330
+ mergeWith (_right) {
1331
+ return false
1332
+ }
1333
+
1334
+ /**
1335
+ * @param {Transaction} transaction
1336
+ * @param {Item} item
1337
+ */
1338
+ integrate (transaction, item) {
1339
+ const doc = /** @type {any} */ (transaction.doc)
1340
+ // A peer's record raises the clock; this writer's own leave it, so its
1341
+ // next records stay level with these until it sees a peer's.
1342
+ if (!transaction.local && this.priority > doc._clock) doc._clock = this.priority
1343
+ holdIds(transaction, this.ids, 1, item)
1344
+ }
1345
+
1346
+ /**
1347
+ * @param {Transaction} transaction
1348
+ * @param {Item} item
1349
+ */
1350
+ delete (transaction, item) {
1351
+ holdIds(transaction, this.ids, -1, item)
1352
+ }
1353
+
1354
+ /**
1355
+ * @param {Transaction} _tr
1356
+ */
1357
+ gc (_tr) {}
1358
+
1359
+ /**
1360
+ * @param {UpdateEncoder} encoder
1361
+ * @param {number} _offset
1362
+ * @param {number} _offsetEnd
1363
+ */
1364
+ write (encoder, _offset, _offsetEnd) {
1365
+ // One varint: the priority, whether an anchor chain follows, the part, the side.
1366
+ encoder.writeLen(this.priority * 32 + (this.at === null ? 0 : 16) + this.part * 2 + this.side)
1367
+ if (this.at !== null) {
1368
+ encoder.writeLen(this.at.length)
1369
+ for (const id of this.at) encoder.writeLeftID(id)
1370
+ }
1371
+ writeIds(encoder, this.ids, 0, this.ids.length)
1372
+ }
1373
+
1374
+ /**
1375
+ * @return {12}
1376
+ */
1377
+ getRef () {
1378
+ return 12
1379
+ }
853
1380
  }
1381
+ // YP22 end
854
1382
 
855
1383
  /**
856
1384
  * @private
857
1385
  */
1386
+ // YP25 begin (edytor fork: paired marks written natively — see UPSTREAM.md YP25)
1387
+ /**
1388
+ * Whether `x` is a non-negative integer (a clock, a client id, a stamp).
1389
+ *
1390
+ * @param {any} x
1391
+ * @return {boolean}
1392
+ */
1393
+ const natural = x => Number.isInteger(x) && x >= 0
1394
+
1395
+ /**
1396
+ * Write a format item's value. A paired mark's (YP13) is written in parts: a
1397
+ * flags length — bit 0 its side; bit 1 a start's operation id is the item's
1398
+ * own, an end's start is the item's own client (its clock follows as a
1399
+ * distance); bit 2 a start's value is `true`, bit 3 `null` — then a start's
1400
+ * Lamport stamp, the operation id where the flags do not imply it, and a
1401
+ * start's value where it is neither `true` nor `null`. Any other value (a
1402
+ * plain format, a value of another shape) is flagged `16` and written whole.
1403
+ *
1404
+ * @param {UpdateEncoder} encoder
1405
+ * @param {ContentFormat} content
1406
+ * @param {Item} [item]
1407
+ */
1408
+ const writeFormatValue = (encoder, content, item) => {
1409
+ const v = /** @type {any} */ (content.value)
1410
+ const role = pairedRole(content)
1411
+ if (role === 0 && Array.isArray(v) && v.length === 5 && natural(v[1]) && natural(v[2]) && natural(v[3]) && (v[4] === 0 || v[4] === 1)) {
1412
+ const own = item !== undefined && v[2] === item.id.client && v[3] === item.id.clock
1413
+ const tail = v[0] === true ? 4 : v[0] === null ? 8 : 0
1414
+ encoder.writeLen(v[4] | (own ? 2 : 0) | tail)
1415
+ encoder.writeLen(v[1])
1416
+ if (!own) encoder.writeLeftID(createID(v[2], v[3]))
1417
+ if (tail === 0) encoder.writeJSON(v[0])
1418
+ } else if (role === 1 && Array.isArray(v) && v.length === 3 && natural(v[0]) && natural(v[1]) && (v[2] === 0 || v[2] === 1)) {
1419
+ const near = item !== undefined && v[0] === item.id.client && v[1] <= item.id.clock
1420
+ encoder.writeLen(v[2] | (near ? 2 : 0))
1421
+ if (near) encoder.writeLen(/** @type {Item} */ (item).id.clock - v[1])
1422
+ else encoder.writeLeftID(createID(v[0], v[1]))
1423
+ } else {
1424
+ encoder.writeLen(16)
1425
+ encoder.writeJSON(v)
1426
+ }
1427
+ }
1428
+
1429
+ /**
1430
+ * Read a format item's value (`writeFormatValue`): `id` is the item's.
1431
+ *
1432
+ * @param {UpdateDecoder} decoder
1433
+ * @param {string} key
1434
+ * @param {ID} id
1435
+ * @return {any}
1436
+ */
1437
+ export const readFormatValue = (decoder, key, id) => {
1438
+ const flags = decoder.readLen()
1439
+ if ((flags & 16) !== 0) return decoder.readJSON()
1440
+ const side = flags & 1
1441
+ const role = pairedRole({ key })
1442
+ if (role === 0) {
1443
+ const l = decoder.readLen()
1444
+ const at = (flags & 2) !== 0 ? id : decoder.readLeftID()
1445
+ const v = (flags & 4) !== 0 ? true : (flags & 8) !== 0 ? null : decoder.readJSON()
1446
+ return [v, l, at.client, at.clock, side]
1447
+ }
1448
+ if (role !== 1) error.unexpectedCase()
1449
+ const at = (flags & 2) !== 0 ? createID(id.client, id.clock - decoder.readLen()) : decoder.readLeftID()
1450
+ return [at.client, at.clock, side]
1451
+ }
1452
+ // YP25 end
1453
+
858
1454
  export class ContentFormat {
859
1455
  /**
860
1456
  * @param {string} key
@@ -916,11 +1512,12 @@ export class ContentFormat {
916
1512
  integrate (_transaction, item) {
917
1513
  // Format items are non-countable: no marker index shifts (YP4).
918
1514
  // YP13: a paired mark's start carries its Lamport timestamp; the document
919
- // keeps the greatest it saw, so the next mark it writes wins over it.
1515
+ // keeps the greatest it saw (YP22: one clock with the move records), so the
1516
+ // next mark or move it writes wins over it.
920
1517
  if (pairedRole(this) === 0) {
921
1518
  const doc = /** @type {any} */ (_transaction.doc)
922
1519
  const l = /** @type {Array<any>} */ (this.value)[1]
923
- if (l > doc._markClock) doc._markClock = l
1520
+ if (l > doc._clock) doc._clock = l
924
1521
  }
925
1522
  }
926
1523
 
@@ -936,10 +1533,11 @@ export class ContentFormat {
936
1533
  * @param {UpdateEncoder} encoder
937
1534
  * @param {number} _offset
938
1535
  * @param {number} _offsetEnd
1536
+ * @param {Item} [item] YP25: the item, whose id a mark's operation id usually is
939
1537
  */
940
- write (encoder, _offset, _offsetEnd) {
1538
+ write (encoder, _offset, _offsetEnd, item) {
941
1539
  encoder.writeKey(this.key)
942
- encoder.writeJSON(this.value)
1540
+ writeFormatValue(encoder, this, item) // YP25
943
1541
  }
944
1542
 
945
1543
  /**
@@ -1132,7 +1730,7 @@ export class ContentType {
1132
1730
  delete (transaction) {
1133
1731
  let item = this.type._start
1134
1732
  while (item !== null) {
1135
- if (!item.deleted) {
1733
+ if (!item.removed) { // YP20: a held child goes with its node
1136
1734
  item.delete(transaction)
1137
1735
  } else if (!transaction.insertSet.hasId(item.id)) {
1138
1736
  // This will be gc'd later and we want to merge it if possible
@@ -1144,7 +1742,7 @@ export class ContentType {
1144
1742
  item = item.right
1145
1743
  }
1146
1744
  this.type._map.forEach(item => {
1147
- if (!item.deleted) {
1745
+ if (!item.removed) { // YP20
1148
1746
  item.delete(transaction)
1149
1747
  } else if (!transaction.insertSet.hasId(item.id)) {
1150
1748
  // same as above