@panphora/clayjs 1.16.0 → 1.18.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 (511) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/clay-dom.js +1 -1
  3. package/dist/clay-events.js +1 -1
  4. package/dist/clay-internals.js +1 -1
  5. package/dist/clay-options.js +1 -1
  6. package/dist/clay-router.js +1 -1
  7. package/dist/clay-ui.js +1 -1
  8. package/dist/clay-utils.js +1 -1
  9. package/dist/clay.js +1 -1
  10. package/dist/clay.standalone.js +72519 -59839
  11. package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
  12. package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
  13. package/dist/m/c-2GRUM77Q.js +30 -0
  14. package/dist/m/c-2KIEGNC4.js +1 -0
  15. package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
  16. package/dist/m/c-2YYDMLNZ.js +1 -0
  17. package/dist/m/c-2ZZBC45O.js +1 -0
  18. package/dist/m/c-372N6H3B.js +1 -0
  19. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  20. package/dist/m/c-3LDGIBMN.js +1 -0
  21. package/dist/m/c-3O7OBDCM.js +1 -0
  22. package/dist/m/c-4ASQPRQL.js +1 -0
  23. package/dist/m/c-4TXNLX4K.js +6 -0
  24. package/dist/m/c-4YU3BR4D.js +6 -0
  25. package/dist/m/c-5734MF2P.js +1 -0
  26. package/dist/m/c-57XDY6HC.js +1 -0
  27. package/dist/m/{c-VFX45IZQ.js → c-5B4YBZXC.js} +1 -1
  28. package/dist/m/c-5ONTYIUI.js +1 -0
  29. package/dist/m/c-5OOVFCJM.js +1 -0
  30. package/dist/m/c-5T6NZHNM.js +1 -0
  31. package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
  32. package/dist/m/{c-Z36J63PP.js → c-6DB2OLRJ.js} +1 -1
  33. package/dist/m/c-6DNRCPNM.js +1 -0
  34. package/dist/m/c-6UGWFMQC.js +2 -0
  35. package/dist/m/c-7K4SCQB7.js +1 -0
  36. package/dist/m/c-7MG5RU63.js +1 -0
  37. package/dist/m/c-7PRJCA3E.js +1 -0
  38. package/dist/m/c-A6L3FMUS.js +1 -0
  39. package/dist/m/c-AMU3BPPR.js +4 -0
  40. package/dist/m/c-AMYEFA2E.js +1 -0
  41. package/dist/m/c-AQYCG7QW.js +1 -0
  42. package/dist/m/c-ATKAOH23.js +1 -0
  43. package/dist/m/c-B3JM5ZXC.js +1 -0
  44. package/dist/m/c-B5CS55WR.js +1 -0
  45. package/dist/m/c-B5UJQT7W.js +1 -0
  46. package/dist/m/c-B7J4K2AE.js +3 -0
  47. package/dist/m/c-BHZYSHW4.js +1 -0
  48. package/dist/m/c-BIJALSRD.js +1 -0
  49. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  50. package/dist/m/c-BPF2NSPV.js +1 -0
  51. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  52. package/dist/m/c-BUAYFEV5.js +1 -0
  53. package/dist/m/c-C6IPRG34.js +1 -0
  54. package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
  55. package/dist/m/{c-4DRMS4QG.js → c-CCQ25GFY.js} +1 -1
  56. package/dist/m/c-CJFDLD43.js +1 -0
  57. package/dist/m/c-COPTL5WB.js +1 -0
  58. package/dist/m/c-D3E4WJ4M.js +166 -0
  59. package/dist/m/c-D5IKEKXW.js +7 -0
  60. package/dist/m/c-DHBRA6DL.js +1 -0
  61. package/dist/m/c-DKLS74XB.js +72 -0
  62. package/dist/m/c-DVYGFTSK.js +3 -0
  63. package/dist/m/c-E3ZXARWA.js +1 -0
  64. package/dist/m/c-EK63G3CG.js +1 -0
  65. package/dist/m/c-ERQSNR6K.js +13 -0
  66. package/dist/m/c-EVYAISP3.js +1 -0
  67. package/dist/m/c-EXKHPOVC.js +3 -0
  68. package/dist/m/{c-7VGZ7RU5.js → c-FWKHM4E7.js} +1 -1
  69. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  70. package/dist/m/c-GNDL3TKB.js +1 -0
  71. package/dist/m/c-GRWMD6O5.js +1 -0
  72. package/dist/m/c-GRXHHFPO.js +1 -0
  73. package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
  74. package/dist/m/c-HAWFNBYG.js +1 -0
  75. package/dist/m/c-HG2C77BM.js +4 -0
  76. package/dist/m/c-HHGJBU4D.js +1 -0
  77. package/dist/m/c-HMV6P4RX.js +1 -0
  78. package/dist/m/c-HMYWIHFG.js +1 -0
  79. package/dist/m/c-HT2DKBV2.js +1 -0
  80. package/dist/m/c-I2SCCUEW.js +1 -0
  81. package/dist/m/{c-HRDCD3WN.js → c-ICJYHABF.js} +2 -2
  82. package/dist/m/c-IMZCWYHR.js +7 -0
  83. package/dist/m/c-IVMD3FEM.js +1 -0
  84. package/dist/m/c-IVTRVENZ.js +1 -0
  85. package/dist/m/c-IW6GGOU2.js +1 -0
  86. package/dist/m/c-JKZJIUIP.js +1 -0
  87. package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
  88. package/dist/m/c-JRKY44DK.js +1 -0
  89. package/dist/m/c-K3ADYNLU.js +1 -0
  90. package/dist/m/c-KBS3UV6B.js +1 -0
  91. package/dist/m/c-KGHVFRR3.js +1 -0
  92. package/dist/m/c-KJJPV77R.js +1 -0
  93. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  94. package/dist/m/{c-5Y6TNX5W.js → c-KP64LT2A.js} +2 -2
  95. package/dist/m/c-KQ7E3PYY.js +1 -0
  96. package/dist/m/c-L3F5ZLIV.js +1 -0
  97. package/dist/m/{c-LR4CGEPT.js → c-L42OLXWM.js} +3 -2
  98. package/dist/m/c-L5VFATT6.js +1 -0
  99. package/dist/m/c-L7JL5TL3.js +3 -0
  100. package/dist/m/c-LKSL4AUJ.js +1 -0
  101. package/dist/m/c-M6FNQCTQ.js +1 -0
  102. package/dist/m/c-MCOOHCSG.js +1 -0
  103. package/dist/m/c-MHI3GYKF.js +1 -0
  104. package/dist/m/c-MWXFLW4J.js +1 -0
  105. package/dist/m/c-NEL7HQW3.js +1 -0
  106. package/dist/m/c-NQJRVJ56.js +1 -0
  107. package/dist/m/c-NWX3MFT4.js +1 -0
  108. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  109. package/dist/m/c-OKXI52N7.js +1 -0
  110. package/dist/m/{c-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
  111. package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
  112. package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
  113. package/dist/m/c-P3OFYUMA.js +1 -0
  114. package/dist/m/c-PLN76GF3.js +6 -0
  115. package/dist/m/c-PLP4JKFC.js +1 -0
  116. package/dist/m/c-Q26MME6N.js +1 -0
  117. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  118. package/dist/m/{c-SWRJFCNN.js → c-QIKT6SKM.js} +1 -1
  119. package/dist/m/c-QIXT3Q7C.js +28 -0
  120. package/dist/m/c-QO4HNGCQ.js +1 -0
  121. package/dist/m/c-R27DWQKX.js +1 -0
  122. package/dist/m/{c-HVYXIS72.js → c-R2XUODVY.js} +1 -1
  123. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  124. package/dist/m/c-RND3G2S2.js +1 -0
  125. package/dist/m/c-S2U6NJU4.js +1 -0
  126. package/dist/m/c-SPZPGD3R.js +1 -0
  127. package/dist/m/c-SW4YEG2D.js +8 -0
  128. package/dist/m/c-SZGYW5HT.js +1 -0
  129. package/dist/m/c-T637T7SN.js +1 -0
  130. package/dist/m/c-TJAXGIRV.js +1 -0
  131. package/dist/m/{c-KKWLDOPM.js → c-TNEM3RNM.js} +1 -1
  132. package/dist/m/c-TTB5EK4S.js +32 -0
  133. package/dist/m/c-U7SHNWDH.js +1 -0
  134. package/dist/m/c-UDHIVLYH.js +20 -0
  135. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  136. package/dist/m/{c-TQZ2JYHD.js → c-UORXLSD3.js} +1 -1
  137. package/dist/m/c-UPD33ZPE.js +1 -0
  138. package/dist/m/c-URSMSFCB.js +1 -0
  139. package/dist/m/c-UY4QX42B.js +1 -0
  140. package/dist/m/c-V44KWI2L.js +4 -0
  141. package/dist/m/c-VG5XIN4T.js +1 -0
  142. package/dist/m/c-VILENZTD.js +1 -0
  143. package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
  144. package/dist/m/c-VR2BJ4AN.js +1 -0
  145. package/dist/m/c-W4JCVLAB.js +1 -0
  146. package/dist/m/c-W6DQSHXI.js +135 -0
  147. package/dist/m/c-WBGWGOM7.js +1 -0
  148. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  149. package/dist/m/c-WGW764WX.js +1 -0
  150. package/dist/m/c-WMRJKQ2V.js +5 -0
  151. package/dist/m/c-WNTYKCDG.js +1 -0
  152. package/dist/m/c-WQWIRWO3.js +72 -0
  153. package/dist/m/c-X5LNHET5.js +1 -0
  154. package/dist/m/c-X5TSXZYJ.js +2 -0
  155. package/dist/m/c-XGH4GS2T.js +1 -0
  156. package/dist/m/c-Y6F2FORL.js +1 -0
  157. package/dist/m/c-YBKHUAK7.js +10 -0
  158. package/dist/m/c-YBSS55KU.js +20 -0
  159. package/dist/m/c-YCLU4UNT.js +2 -0
  160. package/dist/m/c-YVZ2L7CE.js +1 -0
  161. package/dist/m/c-ZK4GIZH2.js +1 -0
  162. package/dist/m/c-ZRXJD4G6.js +1 -0
  163. package/dist/m/c-ZWBI4HYK.js +1 -0
  164. package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
  165. package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
  166. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  167. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  168. package/dist/m/core/host-permission-6EVJGNPL.js +1 -0
  169. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  170. package/dist/m/core/{persist-HKHA66IY.js → persist-UKXV5KLA.js} +1 -1
  171. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  172. package/dist/m/core/save-LYY7K6U7.js +1 -0
  173. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  174. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  175. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  176. package/dist/m/events/index-RN2JNGBV.js +1 -0
  177. package/dist/m/internals/index-TGLQINN3.js +1 -0
  178. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  179. package/dist/m/loader-3BBNZZGM.js +1 -0
  180. package/dist/m/manifest-F449FF98.js +1 -0
  181. package/dist/m/manifest-edit-4396A009.js +1 -0
  182. package/dist/m/manifest.json +2509 -1474
  183. package/dist/m/options/options-EJMS3K24.js +1 -0
  184. package/dist/m/page/editor-6LRB3GE4.js +304 -0
  185. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  186. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  187. package/dist/m/plugins/demo-OYO5GCAO.js +1 -0
  188. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  189. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  190. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  191. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  192. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  193. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  194. package/dist/m/plugins/people-JCHVPF7P.js +1 -0
  195. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  196. package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-KLGETK77.js} +2 -2
  197. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  198. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  199. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  200. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  201. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  202. package/dist/m/plugins/upload-NHGJXZK2.js +1 -0
  203. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  204. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  205. package/dist/m/plugins/wire-VBWXEXU2.js +1 -0
  206. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  207. package/dist/m/route/route-62CHXL5B.js +1 -0
  208. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  209. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  210. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  211. package/dist/offline-manifest.json +1560 -889
  212. package/dist/page/themes/light-1.css +33 -8
  213. package/dist/server/core.cjs +1554 -311
  214. package/dist/server/offline-runtime.cjs +63 -63
  215. package/dist/server/offline-runtime.mjs +63 -63
  216. package/dist/server/page-compaction-worker.cjs +3710 -0
  217. package/dist/server/rooms.mjs +5 -0
  218. package/docs/guide.md +653 -101
  219. package/entries/sap.js +5 -5
  220. package/package.json +1 -1
  221. package/src/core/admin-contenteditable.js +5 -2
  222. package/src/core/admin-inputs.js +1 -0
  223. package/src/core/admin-onclick.js +2 -1
  224. package/src/core/boot-phase.js +2 -1
  225. package/src/core/conflict-download.js +3 -29
  226. package/src/core/conflict-notice-ui.js +4 -7
  227. package/src/core/download.js +32 -0
  228. package/src/core/host-meta.js +56 -0
  229. package/src/core/is-edit-mode.js +17 -1
  230. package/src/core/mode-key.js +125 -19
  231. package/src/core/mode-support.js +5 -0
  232. package/src/core/mode-transition.js +20 -6
  233. package/src/core/preserve-authored.js +4 -4
  234. package/src/core/recovery.js +121 -33
  235. package/src/core/save-accepted.js +33 -0
  236. package/src/core/save-core.js +111 -32
  237. package/src/core/save-job.js +108 -0
  238. package/src/core/save-prepare.js +53 -0
  239. package/src/core/save-size-notice.js +3 -1
  240. package/src/core/save-slices.js +15 -0
  241. package/src/core/save.js +557 -127
  242. package/src/core/serialize-steps.js +109 -0
  243. package/src/core/snapshot.js +209 -40
  244. package/src/core/source-map.js +83 -38
  245. package/src/core/stale-host-notice.js +1 -0
  246. package/src/core/upgrade-run.js +59 -8
  247. package/src/dom/active-element.js +5 -0
  248. package/src/lib/assets.js +1 -0
  249. package/src/lib/autosave-scheduler.js +3 -4
  250. package/src/lib/slice.js +65 -0
  251. package/src/lib/uimotion.js +774 -0
  252. package/src/lib/unsaved-state.js +2 -2
  253. package/src/live/fast-lane.js +10 -3
  254. package/src/live/local-lane.js +202 -11
  255. package/src/live/ops.js +16 -0
  256. package/src/live/page-compaction.js +162 -0
  257. package/src/live/server-lane.js +1 -0
  258. package/src/live/store.js +11 -3
  259. package/src/loader-logic.js +3 -1
  260. package/src/loader.js +14 -4
  261. package/src/page/adopt-boot.js +2 -1
  262. package/src/page/awareness.js +33 -2
  263. package/src/page/blocks.js +122 -27
  264. package/src/page/board-views.js +2 -16
  265. package/src/page/boot-pack.js +1 -1
  266. package/src/page/chart-tables.js +29 -0
  267. package/src/page/chart.js +411 -72
  268. package/src/page/checklist.js +10 -135
  269. package/src/page/checkpoint-proof.js +295 -0
  270. package/src/page/chrome.js +257 -40
  271. package/src/page/clipboard-shape.js +126 -0
  272. package/src/page/document-title-dom.js +12 -0
  273. package/src/page/document-title-runtime.js +15 -1
  274. package/src/page/document-title.js +4 -1
  275. package/src/page/drag.js +33 -3
  276. package/src/page/editor.js +207 -31
  277. package/src/page/emoji-data.js +162 -0
  278. package/src/page/export-assets.js +357 -0
  279. package/src/page/export-html.js +209 -0
  280. package/src/page/export-markdown.js +361 -0
  281. package/src/page/features/board.js +12 -3
  282. package/src/page/features/chart.js +11 -0
  283. package/src/page/features/checklist.js +428 -0
  284. package/src/page/features/dates.js +180 -0
  285. package/src/page/features/emoji.js +187 -0
  286. package/src/page/features/export-html.js +189 -0
  287. package/src/page/features/export-markdown.js +169 -0
  288. package/src/page/features/import-markdown.js +349 -0
  289. package/src/page/features/media.js +61 -5
  290. package/src/page/features/mentions.js +188 -0
  291. package/src/page/features/registry.js +24 -1
  292. package/src/page/features/table.js +375 -85
  293. package/src/page/features/title-cover.js +331 -0
  294. package/src/page/features/title-icon.js +213 -0
  295. package/src/page/features/toc.js +87 -0
  296. package/src/page/format-command.js +33 -0
  297. package/src/page/format-gate.js +68 -18
  298. package/src/page/format-host.js +71 -0
  299. package/src/page/format-native-commands.js +158 -0
  300. package/src/page/format-native.js +42 -0
  301. package/src/page/image-actions.js +64 -0
  302. package/src/page/import-markdown.js +262 -0
  303. package/src/page/inline.js +32 -8
  304. package/src/page/items.js +141 -11
  305. package/src/page/markdown.js +30 -2
  306. package/src/page/media.js +35 -119
  307. package/src/page/mention-picker.js +99 -0
  308. package/src/page/merge.js +1 -1
  309. package/src/page/placeholder.js +14 -1
  310. package/src/page/projection.js +156 -25
  311. package/src/page/recover.js +10 -12
  312. package/src/page/recovery-ui.js +464 -0
  313. package/src/page/rich/chart.js +63 -7
  314. package/src/page/save-projection.js +67 -0
  315. package/src/page/save-states.js +14 -4
  316. package/src/page/schema.js +52 -3
  317. package/src/page/table-paste.js +3 -1
  318. package/src/page/table-style.js +4 -4
  319. package/src/page/table.js +44 -2
  320. package/src/page/themes/content.css +25 -1
  321. package/src/page/themes/light-1.css +33 -8
  322. package/src/page/themes/light-1.source.css +8 -7
  323. package/src/page/toc-link.js +31 -0
  324. package/src/page/toc.js +5 -0
  325. package/src/page/view-hooks.js +58 -0
  326. package/src/page/view.js +13 -0
  327. package/src/page/ybind/binding.js +116 -6
  328. package/src/page/ybind/bridge.js +187 -20
  329. package/src/page/ybind/containment.js +162 -0
  330. package/src/page/ybind/doc.js +17 -9
  331. package/src/page/ybind/file-debt.js +63 -0
  332. package/src/page/ybind/layout.js +7 -2
  333. package/src/page/ybind/marks.js +2 -1
  334. package/src/page/ybind/moves.js +5 -4
  335. package/src/page/ybind/origins.js +106 -4
  336. package/src/page/ybind/parts.js +8 -1
  337. package/src/page/ybind/provider.js +125 -18
  338. package/src/page/ybind/recovery-proof.js +32 -9
  339. package/src/page/ybind/recovery-replay.js +905 -4
  340. package/src/page/ybind/recovery.js +174 -45
  341. package/src/page/ybind/table.js +67 -11
  342. package/src/page/ybind/walk-delta.js +258 -0
  343. package/src/page/ybind/walk.js +54 -29
  344. package/src/page/ybind/writer.js +1 -1
  345. package/src/plugins/ai-edit.js +13 -9
  346. package/src/plugins/indicator-chip.js +91 -0
  347. package/src/plugins/indicator.js +13 -80
  348. package/src/plugins/live.js +126 -15
  349. package/src/plugins/menu/actions.js +196 -0
  350. package/src/plugins/menu/people.js +279 -0
  351. package/src/plugins/menu/placement.js +121 -0
  352. package/src/plugins/menu/preferences.js +9 -0
  353. package/src/plugins/menu/seo.js +316 -397
  354. package/src/plugins/menu/style.js +171 -0
  355. package/src/plugins/menu/variants.js +296 -0
  356. package/src/plugins/menu.js +186 -183
  357. package/src/plugins/page-view.js +3 -2
  358. package/src/plugins/page.js +362 -43
  359. package/src/plugins/sortable.js +8 -1
  360. package/src/plugins/source.js +6 -0
  361. package/src/plugins/upgrade.js +51 -17
  362. package/src/plugins/upload-ui.js +12 -17
  363. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  364. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  365. package/src/plugins/visitor-drafts/intro.js +7 -4
  366. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  367. package/src/plugins/visitor-drafts/prompt.js +11 -9
  368. package/src/plugins/visitor-drafts/source.js +4 -3
  369. package/src/plugins/visitor-drafts/store.js +138 -9
  370. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  371. package/src/plugins/visitor-drafts-owner.js +4 -6
  372. package/src/plugins/visitor-drafts.js +5 -3
  373. package/src/recovery/store.js +1 -1
  374. package/src/route/route.js +4 -0
  375. package/src/server/checkpoint/index.js +2 -2
  376. package/src/server/rooms/index.js +5 -1
  377. package/src/server/rooms/live-data-rooms.js +302 -50
  378. package/src/server/rooms/page-compaction-worker.js +12 -0
  379. package/src/server/rooms/page-compactor.js +92 -0
  380. package/src/server/rooms/page-room-conformance.js +461 -0
  381. package/src/source/parse-steps.js +94 -0
  382. package/src/source/session.js +233 -57
  383. package/src/sync/editing-sender.js +12 -0
  384. package/src/sync/live-sync-edit.js +22 -2
  385. package/src/sync/live-sync.js +150 -23
  386. package/src/sync/page-relay.js +52 -0
  387. package/src/sync/presence-store.js +44 -4
  388. package/src/sync/presence.js +6 -3
  389. package/src/sync/saved-stream.js +28 -14
  390. package/src/ui/bevel-controls.js +1 -0
  391. package/src/ui/bevel-dialog.js +5 -2
  392. package/src/ui/editor-ui.js +77 -0
  393. package/src/ui/page-colour.js +281 -0
  394. package/src/ui/toast.js +16 -7
  395. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  396. package/src/vendor/hyper-undo.vendor.js +1 -1
  397. package/src/vendor/hypercms.vendor.js +9 -9
  398. package/src/vendor/parse5.vendor.js +2 -2
  399. package/src/vendor/sap-core.vendor.js +5 -5
  400. package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
  401. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
  402. package/dist/m/c-2M73252O.js +0 -1
  403. package/dist/m/c-2SPVQSHC.js +0 -1
  404. package/dist/m/c-35NBGMZB.js +0 -1
  405. package/dist/m/c-3IQRECYS.js +0 -1
  406. package/dist/m/c-4B6H4G5V.js +0 -1
  407. package/dist/m/c-4FFHZM32.js +0 -1
  408. package/dist/m/c-4YSCRICO.js +0 -1
  409. package/dist/m/c-54RCDCBU.js +0 -1
  410. package/dist/m/c-5M4YEKCX.js +0 -4
  411. package/dist/m/c-5V3MOVJT.js +0 -1
  412. package/dist/m/c-66O32MV3.js +0 -28
  413. package/dist/m/c-6HMB24EV.js +0 -1
  414. package/dist/m/c-7O6AFR65.js +0 -1
  415. package/dist/m/c-ANQTJ3B5.js +0 -2
  416. package/dist/m/c-AYVOREBE.js +0 -1
  417. package/dist/m/c-B2ZXPOS3.js +0 -3
  418. package/dist/m/c-BMIZKMPV.js +0 -1
  419. package/dist/m/c-BTDI4Y23.js +0 -1
  420. package/dist/m/c-CJZSLMVQ.js +0 -1
  421. package/dist/m/c-DBE6ORSI.js +0 -1
  422. package/dist/m/c-EDHUR4MX.js +0 -1
  423. package/dist/m/c-EY6UOM54.js +0 -1
  424. package/dist/m/c-F3CKJTJS.js +0 -1
  425. package/dist/m/c-F7Y6FLPY.js +0 -1
  426. package/dist/m/c-FA4DZVTC.js +0 -1
  427. package/dist/m/c-G2BUZITR.js +0 -1
  428. package/dist/m/c-GB3JTV6E.js +0 -1
  429. package/dist/m/c-GBGWXTX4.js +0 -1
  430. package/dist/m/c-GHBCCVG4.js +0 -1
  431. package/dist/m/c-GJAEGGXY.js +0 -1
  432. package/dist/m/c-GOWBDM6J.js +0 -1
  433. package/dist/m/c-IB52R237.js +0 -1
  434. package/dist/m/c-IKWNE4R4.js +0 -1
  435. package/dist/m/c-IU3IYEU6.js +0 -1
  436. package/dist/m/c-IZ2EBRAJ.js +0 -1
  437. package/dist/m/c-KR4UW4KW.js +0 -1
  438. package/dist/m/c-KY3VJGUA.js +0 -8
  439. package/dist/m/c-LFPHCXY3.js +0 -1
  440. package/dist/m/c-LRPUO2MX.js +0 -1
  441. package/dist/m/c-LXENEBV7.js +0 -1
  442. package/dist/m/c-NMJRXKPC.js +0 -8
  443. package/dist/m/c-OIBUTDXI.js +0 -1
  444. package/dist/m/c-PPOSCAQD.js +0 -1
  445. package/dist/m/c-QIX5BJYN.js +0 -1
  446. package/dist/m/c-QQHEX6HA.js +0 -1
  447. package/dist/m/c-R7RMANJI.js +0 -9
  448. package/dist/m/c-RI4NT75Y.js +0 -1
  449. package/dist/m/c-RM7J7BV2.js +0 -1
  450. package/dist/m/c-RUJKSUQW.js +0 -1
  451. package/dist/m/c-SGCCC4FW.js +0 -25
  452. package/dist/m/c-T4VO4OCB.js +0 -1
  453. package/dist/m/c-TQEMVJCS.js +0 -1
  454. package/dist/m/c-TR6RMDQ7.js +0 -3
  455. package/dist/m/c-UKHTLWUW.js +0 -1
  456. package/dist/m/c-UNN7LWCT.js +0 -1
  457. package/dist/m/c-UXBMRDZB.js +0 -235
  458. package/dist/m/c-V7PZZRG7.js +0 -1
  459. package/dist/m/c-VL52ADDR.js +0 -1
  460. package/dist/m/c-WNYBLIOZ.js +0 -1
  461. package/dist/m/c-WWDBYXLK.js +0 -1
  462. package/dist/m/c-XNNW2CWK.js +0 -1
  463. package/dist/m/c-Y6USGTS5.js +0 -5
  464. package/dist/m/c-YG2NJYS6.js +0 -135
  465. package/dist/m/c-Z4VRC7GU.js +0 -7
  466. package/dist/m/c-Z6K52OLP.js +0 -19
  467. package/dist/m/c-ZDY2MDAA.js +0 -21
  468. package/dist/m/c-ZUCOKOHT.js +0 -1
  469. package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
  470. package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
  471. package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
  472. package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
  473. package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
  474. package/dist/m/core/recovery-TBBSICE2.js +0 -1
  475. package/dist/m/core/save-WUCHTQBE.js +0 -1
  476. package/dist/m/core/save-core-FJVHT5M3.js +0 -1
  477. package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
  478. package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
  479. package/dist/m/events/index-23DPTUT4.js +0 -1
  480. package/dist/m/internals/index-WOVAP3RU.js +0 -1
  481. package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
  482. package/dist/m/loader-HXX6YNH5.js +0 -1
  483. package/dist/m/manifest-F22DEDAC.js +0 -1
  484. package/dist/m/manifest-edit-C3C7376B.js +0 -1
  485. package/dist/m/options/options-2LH76JQ3.js +0 -1
  486. package/dist/m/page/editor-XUTB2HB2.js +0 -189
  487. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
  488. package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
  489. package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
  490. package/dist/m/plugins/live-NUTTXSBS.js +0 -1
  491. package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
  492. package/dist/m/plugins/page-KSUH7G6J.js +0 -2
  493. package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
  494. package/dist/m/plugins/people-AJJ32YCG.js +0 -1
  495. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  496. package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
  497. package/dist/m/plugins/source-KHK5UUSK.js +0 -1
  498. package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
  499. package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
  500. package/dist/m/plugins/upload-XSGNILUY.js +0 -1
  501. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
  502. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
  503. package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
  504. package/dist/m/reader-core-ILWLXVMP.js +0 -68
  505. package/dist/m/route/route-KOCZSEGN.js +0 -1
  506. package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
  507. package/dist/m/ui/index-EVHKMKBL.js +0 -1
  508. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  509. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  510. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  511. /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
@@ -139,15 +139,118 @@ const recordOf = (value, key, out) => {
139
139
  out.removals.push({ key, sourceKey: key, source, operationId: null, blockId: value.b, end, sv: [], legacy: true });
140
140
  };
141
141
 
142
- export function parseGoneIntents(gone, { strict = false } = {}) {
143
- const out = { removals: [], restores: [], copyActive: new Set(), attrs: new Map(), textDeletes: [] };
144
- gone.forEach((value, key) => {
145
- try {
146
- recordOf(value, key, out);
147
- } catch (error) {
148
- if (strict || !(error instanceof RecoveryError)) throw error;
142
+ const emptyIntents = () => ({ removals: [], restores: [], copyActive: new Set(), attrs: new Map(), textDeletes: [] });
143
+
144
+ const frozen = (value) => {
145
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
146
+ for (const key of Object.keys(value)) frozen(value[key]);
147
+ Object.freeze(value);
148
+ }
149
+ return value;
150
+ };
151
+
152
+ const parsedRecord = (value, key) => {
153
+ const out = emptyIntents();
154
+ try {
155
+ recordOf(value, key, out);
156
+ } catch (error) {
157
+ if (!(error instanceof RecoveryError)) throw error;
158
+ return null;
159
+ }
160
+ for (const list of [out.removals, out.restores, out.textDeletes]) for (const record of list) frozen(record);
161
+ for (const fields of out.attrs.values()) for (const fieldValue of fields.values()) frozen(fieldValue);
162
+ return out;
163
+ };
164
+
165
+ const parsedGone = new WeakMap();
166
+
167
+ const foldRecord = (out, record) => {
168
+ for (const removal of record.removals) out.removals.push(removal);
169
+ for (const restore of record.restores) out.restores.push(restore);
170
+ for (const textDelete of record.textDeletes) out.textDeletes.push(textDelete);
171
+ for (const sourceKey of record.copyActive) out.copyActive.add(sourceKey);
172
+ for (const [sourceKey, fields] of record.attrs) {
173
+ const into = out.attrs.get(sourceKey) ?? new Map();
174
+ for (const [field, fieldValue] of fields) into.set(field, fieldValue);
175
+ out.attrs.set(sourceKey, into);
176
+ }
177
+ };
178
+
179
+ // A document's gone map parsed once per write, in the map's own key order (Yjs keeps a key
180
+ // where it was first written, through deletes and rewrites). The observer keeps it current
181
+ // as transactions settle; keys a transaction that has not settled yet wrote are read again
182
+ // from the map. A new key adds its record to the totals; any other change rebuilds them
183
+ // from the records kept. Every call gets its own lists, as a fresh parse would.
184
+ const goneIndexes = new WeakMap();
185
+ const goneIndexOf = (gone) => {
186
+ const known = goneIndexes.get(gone);
187
+ if (known) return known;
188
+ const entries = new Map();
189
+ let totals = null;
190
+ let added = [];
191
+ const update = (key) => {
192
+ const live = gone.has(key);
193
+ const value = live ? gone.get(key) : undefined;
194
+ const entry = entries.get(key);
195
+ if (entry && entry.live === live && entry.value === value) return;
196
+ const record = live ? parsedRecord(value, key) : null;
197
+ if (!entry) {
198
+ const next = { live, value, record };
199
+ entries.set(key, next);
200
+ if (live && record && totals) added.push(record);
201
+ return;
149
202
  }
203
+ entry.live = live;
204
+ entry.value = value;
205
+ entry.record = record;
206
+ totals = null;
207
+ };
208
+ for (const key of gone._map.keys()) update(key);
209
+ gone.observe((event) => { for (const key of event.keysChanged) update(key); });
210
+ const index = {
211
+ intents() {
212
+ for (const t of gone.doc._transactionCleanups) for (const key of t.changed.get(gone) ?? []) update(key);
213
+ if (!totals) {
214
+ totals = emptyIntents();
215
+ for (const entry of entries.values()) if (entry.record) foldRecord(totals, entry.record);
216
+ added = [];
217
+ } else if (added.length) {
218
+ for (const record of added) foldRecord(totals, record);
219
+ added = [];
220
+ }
221
+ return {
222
+ removals: totals.removals.slice(),
223
+ restores: totals.restores.slice(),
224
+ copyActive: new Set(totals.copyActive),
225
+ attrs: new Map([...totals.attrs].map(([sourceKey, fields]) => [sourceKey, new Map(fields)])),
226
+ textDeletes: totals.textDeletes.slice(),
227
+ };
228
+ },
229
+ };
230
+ goneIndexes.set(gone, index);
231
+ return index;
232
+ };
233
+
234
+ export function parseGoneIntents(gone, { strict = false, kept = true } = {}) {
235
+ const out = emptyIntents();
236
+ if (strict) {
237
+ gone.forEach((value, key) => recordOf(value, key, out));
238
+ return out;
239
+ }
240
+ if (kept && gone instanceof Y.Map && gone.doc) return goneIndexOf(gone).intents();
241
+ if (!kept) {
242
+ gone.forEach((value, key) => { const record = parsedRecord(value, key); if (record) foldRecord(out, record); });
243
+ return out;
244
+ }
245
+ const previous = parsedGone.get(gone) ?? new Map();
246
+ const parsed = new Map();
247
+ gone.forEach((value, key) => {
248
+ const hit = previous.get(key);
249
+ const entry = hit && hit.value === value ? hit : { value, record: parsedRecord(value, key) };
250
+ parsed.set(key, entry);
251
+ if (entry.record) foldRecord(out, entry.record);
150
252
  });
253
+ parsedGone.set(gone, parsed);
151
254
  return out;
152
255
  }
153
256
 
@@ -220,18 +323,13 @@ const rawOf = (attrs) => {
220
323
 
221
324
  const displaced = (item, seen) => seen.some((vector) => item.clock >= (vector.get(item.client) ?? 0));
222
325
 
223
- const addClock = (spans, clock) => {
224
- const last = spans[spans.length - 1];
225
- if (last && clock <= last.end) {
226
- if (clock === last.end) last.end = clock + 1;
227
- return;
228
- }
229
- spans.push({ start: clock, end: clock + 1 });
230
- };
231
-
232
- const mergeClocks = (clocks) => {
326
+ const mergeRanges = (list) => {
233
327
  const spans = [];
234
- for (const clock of [...clocks].sort(compare)) addClock(spans, clock);
328
+ for (const [start, end] of list.sort((a, b) => a[0] - b[0])) {
329
+ const last = spans[spans.length - 1];
330
+ if (last && start <= last.end) last.end = Math.max(last.end, end);
331
+ else spans.push({ start, end });
332
+ }
235
333
  return spans;
236
334
  };
237
335
 
@@ -251,11 +349,10 @@ const visibleOriginIndex = (flow, walked, index) => {
251
349
  const ranges = createProjectionMap(walked, flow.length).physicalRanges(0, walked.tokens);
252
350
  const clocks = new Map();
253
351
  const collect = (item, from, to) => {
254
- for (let offset = from; offset < to; offset++) {
255
- const source = index.resolve({ client: item.id.client, clock: item.id.clock + offset });
256
- const list = clocks.get(source.client);
257
- if (list) list.push(source.clock);
258
- else clocks.set(source.client, [source.clock]);
352
+ for (const piece of index.resolveRange(item.id.client, item.id.clock + from, to - from)) {
353
+ const list = clocks.get(piece.client);
354
+ if (list) list.push([piece.clock, piece.clock + piece.length]);
355
+ else clocks.set(piece.client, [[piece.clock, piece.clock + piece.length]]);
259
356
  }
260
357
  };
261
358
  let at = 0, cursor = 0;
@@ -277,7 +374,7 @@ const visibleOriginIndex = (flow, walked, index) => {
277
374
  cursor = range;
278
375
  }
279
376
  const spans = new Map();
280
- for (const [client, list] of clocks) spans.set(client, mergeClocks(list));
377
+ for (const [client, list] of clocks) spans.set(client, mergeRanges(list));
281
378
  return (client, clock) => {
282
379
  const list = spans.get(client);
283
380
  return list ? holdsClock(list, clock) : false;
@@ -371,7 +468,7 @@ function sourceRecord(pd, index, sourceKey, removals, hidden, intents, visible,
371
468
  attrs: root.attrs,
372
469
  author: blocks[0].author,
373
470
  blocks: blocks.map((block) => ({ id: block.id, type: block.type, attrs: block.attrs, author: block.author, runs: block.runs.map((run) => runOut(run, authorOf)) })),
374
- removals: ordered.map((removal) => ({ key: removal.key, operationId: removal.operationId, end: removal.end, sv: removal.sv })),
471
+ removals: ordered.map((removal) => ({ key: removal.key, operationId: removal.operationId, end: removal.end, sv: removal.sv.map(([client, clock]) => [client, clock]) })),
375
472
  restores: intents.restores.filter((restore) => restore.covers.some((cover) => ordered.some((removal) => removal.key === cover))).sort((a, b) => compare(a.key, b.key)).map((restore) => ({ key: restore.key, covers: sortedList(restore.covers) })),
376
473
  copyActive: intents.copyActive.has(sourceKey),
377
474
  overrides,
@@ -380,31 +477,63 @@ function sourceRecord(pd, index, sourceKey, removals, hidden, intents, visible,
380
477
  return { ...out, revision: canonicalRevision(out) };
381
478
  }
382
479
 
480
+ const seenClock = (sv, client) => {
481
+ for (const [c, clock] of sv) if (c === client) return clock;
482
+ return 0;
483
+ };
484
+
485
+ const mayDisplace = (removal, entry) => {
486
+ for (const item of entry.items) if (item.kind !== "marker" && item.clock + item.length - 1 >= seenClock(removal.sv, item.client)) return true;
487
+ return false;
488
+ };
489
+
490
+ const bySource = (a, b) => {
491
+ const left = parsePhysicalKey(a.source), right = parsePhysicalKey(b.source);
492
+ return left.client - right.client || left.clock - right.clock;
493
+ };
494
+
495
+ function recoveryInputs(pd, index, options) {
496
+ const walked = options.walked ?? walk(pd, index);
497
+ const intents = options.intents ?? parseGoneIntents(pd.gone, { strict: options.strict === true });
498
+ const hidden = new Map();
499
+ for (const entry of walked.hidden ?? []) hidden.set(entry.key, entry);
500
+ const groups = new Map();
501
+ for (const removal of walked.removals ?? intents.removals) {
502
+ const list = groups.get(removal.sourceKey);
503
+ if (list) list.push(removal);
504
+ else groups.set(removal.sourceKey, [removal]);
505
+ }
506
+ let visibleIndex = null;
507
+ const visible = (client, clock) => {
508
+ if (!visibleIndex) visibleIndex = visibleOriginIndex(pd.flow, walked, index);
509
+ return visibleIndex(client, clock);
510
+ };
511
+ return { intents, hidden, groups, visible };
512
+ }
513
+
383
514
  export function reduceRecovery(pd, options = {}) {
384
515
  const index = options.index ?? createOriginIndex(pd.origins);
385
516
  try {
386
- const walked = options.walked ?? walk(pd, index);
387
- const intents = options.intents ?? parseGoneIntents(pd.gone, { strict: options.strict === true });
388
- const hidden = new Map();
389
- for (const entry of walked.hidden ?? []) hidden.set(entry.key, entry);
390
- const groups = new Map();
391
- for (const removal of walked.removals ?? intents.removals) {
392
- const list = groups.get(removal.sourceKey);
393
- if (list) list.push(removal);
394
- else groups.set(removal.sourceKey, [removal]);
395
- }
396
- let visibleIndex = null;
397
- const visible = (client, clock) => {
398
- if (!visibleIndex) visibleIndex = visibleOriginIndex(pd.flow, walked, index);
399
- return visibleIndex(client, clock);
400
- };
517
+ const { intents, hidden, groups, visible } = recoveryInputs(pd, index, options);
401
518
  const sources = new Map();
402
519
  for (const [sourceKey, removals] of groups) sources.set(sourceKey, sourceRecord(pd, index, sourceKey, removals, hidden, intents, visible, groups));
403
- const list = [...sources.values()].sort((a, b) => {
404
- const left = parsePhysicalKey(a.source), right = parsePhysicalKey(b.source);
405
- return left.client - right.client || left.clock - right.clock;
406
- });
407
- return { sources, list, intents };
520
+ return { sources, list: [...sources.values()].sort(bySource), intents };
521
+ } finally {
522
+ if (!options.index) index.dispose();
523
+ }
524
+ }
525
+
526
+ export function reduceRecoverable(pd, options = {}) {
527
+ const index = options.index ?? createOriginIndex(pd.origins);
528
+ try {
529
+ const { intents, hidden, groups, visible } = recoveryInputs(pd, index, options);
530
+ const list = [];
531
+ for (const [sourceKey, removals] of groups) {
532
+ if (!removals.some((removal) => hidden.has(removal.key) && mayDisplace(removal, hidden.get(removal.key)))) continue;
533
+ const record = sourceRecord(pd, index, sourceKey, removals, hidden, intents, visible, groups);
534
+ if (record.recoverable) list.push(record);
535
+ }
536
+ return { list: list.sort(bySource), intents };
408
537
  } finally {
409
538
  if (!options.index) index.dispose();
410
539
  }
@@ -1,3 +1,4 @@
1
+ import * as Y from "../../vendor/yjs.vendor.js";
1
2
  import { schema } from "../schema.js";
2
3
  import { marksOf } from "../inline.js";
3
4
  import { ATOM, BREAK, canon, unitsOf } from "../projection.js";
@@ -45,28 +46,83 @@ function keepOne(live, dead) {
45
46
  if (first) live[first] = dead[first];
46
47
  }
47
48
 
49
+ // The table a field belongs to, or null when the field is one resolveTableFields refuses.
50
+ export function tableFieldOwner(key, value) {
51
+ let tuple;
52
+ try { tuple = JSON.parse(key); } catch { return null; }
53
+ if (!Array.isArray(tuple) || tuple.length !== 4) return null;
54
+ const [owner, kind, entityId, field] = tuple;
55
+ if (!validId(owner) || !validId(entityId)) return null;
56
+ if (kind === "cell") return validId(field) && validCell(value) ? owner : null;
57
+ return Object.hasOwn(TABLE_FIELDS, kind) && TABLE_FIELDS[kind].includes(field) && validValue(field, value) ? owner : null;
58
+ }
59
+
60
+ // Each table's own fields of a document's tableFields map, in the map's own key order
61
+ // (Yjs keeps a key where it was first written, through deletes and rewrites), checked once
62
+ // per write. The observer keeps it current as transactions settle; keys a transaction that
63
+ // has not settled yet wrote are read again from the map.
64
+ const fieldIndexes = new WeakMap();
65
+ const fieldIndexOf = (fields) => {
66
+ const known = fieldIndexes.get(fields);
67
+ if (known) return known;
68
+ const entries = new Map(), byOwner = new Map(), invalid = new Set();
69
+ const apply = (key) => {
70
+ let entry = entries.get(key);
71
+ if (!entry) {
72
+ let tuple = null;
73
+ try { tuple = JSON.parse(key); } catch { tuple = null; }
74
+ const owner = Array.isArray(tuple) && tuple.length === 4 && validId(tuple[0]) ? tuple[0] : null;
75
+ entry = { key, tuple, live: false, value: undefined };
76
+ entries.set(key, entry);
77
+ if (owner !== null) (byOwner.get(owner) ?? byOwner.set(owner, []).get(owner)).push(entry);
78
+ }
79
+ entry.live = fields.has(key);
80
+ entry.value = entry.live ? fields.get(key) : undefined;
81
+ if (entry.live && tableFieldOwner(key, entry.value) === null) invalid.add(key);
82
+ else invalid.delete(key);
83
+ };
84
+ for (const key of fields._map.keys()) apply(key);
85
+ fields.observe((event) => { for (const key of event.keysChanged) apply(key); });
86
+ const index = {
87
+ owned(blockId) {
88
+ for (const t of fields.doc._transactionCleanups) for (const key of t.changed.get(fields) ?? []) apply(key);
89
+ return invalid.size ? null : byOwner.get(blockId) ?? [];
90
+ },
91
+ };
92
+ fieldIndexes.set(fields, index);
93
+ return index;
94
+ };
95
+
96
+ const ownedFields = (fields, blockId) => (blockId !== null && fields instanceof Y.Map && fields.doc ? fieldIndexOf(fields).owned(blockId) : null);
97
+
48
98
  export function resolveTableFields(fields, blockId = null, seed = null) {
49
99
  const rows = new Map(), cols = new Map(), cells = new Map();
50
- for (const [key, value] of fields) {
51
- let tuple;
52
- try { tuple = JSON.parse(key); } catch { fail(key); }
53
- if (!Array.isArray(tuple) || tuple.length !== 4) fail(key);
54
- const [owner, kind, entityId, field] = tuple;
55
- if (!validId(owner) || !validId(entityId)) fail(key);
56
- if (kind === "cell") {
57
- if (!validId(field) || !validCell(value)) fail(key);
58
- } else if (!Object.hasOwn(TABLE_FIELDS, kind) || !TABLE_FIELDS[kind].includes(field) || !validValue(field, value)) fail(key);
59
- if (owner !== blockId) continue;
100
+ const take = (kind, entityId, field, value) => {
60
101
  if (kind === "cell") {
61
102
  const row = cells.get(entityId) ?? new Map();
62
103
  row.set(field, value);
63
104
  cells.set(entityId, row);
64
- continue;
105
+ return;
65
106
  }
66
107
  const bucket = kind === "row" ? rows : cols;
67
108
  const entity = bucket.get(entityId) ?? {};
68
109
  entity[field] = value;
69
110
  bucket.set(entityId, entity);
111
+ };
112
+ const owned = ownedFields(fields, blockId);
113
+ if (owned) {
114
+ for (const entry of owned) if (entry.live) take(entry.tuple[1], entry.tuple[2], entry.tuple[3], entry.value);
115
+ } else for (const [key, value] of fields) {
116
+ let tuple;
117
+ try { tuple = JSON.parse(key); } catch { fail(key); }
118
+ if (!Array.isArray(tuple) || tuple.length !== 4) fail(key);
119
+ const [owner, kind, entityId, field] = tuple;
120
+ if (!validId(owner) || !validId(entityId)) fail(key);
121
+ if (kind === "cell") {
122
+ if (!validId(field) || !validCell(value)) fail(key);
123
+ } else if (!Object.hasOwn(TABLE_FIELDS, kind) || !TABLE_FIELDS[kind].includes(field) || !validValue(field, value)) fail(key);
124
+ if (owner !== blockId) continue;
125
+ take(kind, entityId, field, value);
70
126
  }
71
127
  const outCols = { ...(record(seed?.cols) ? seed.cols : {}) };
72
128
  const outRows = { ...(record(seed?.rows) ? seed.rows : {}) };
@@ -0,0 +1,258 @@
1
+ // One keystroke changes one block. The walk over the whole flow (walk.js) costs a
2
+ // few milliseconds at 2,000 blocks whatever the keystroke did, so a transaction that
3
+ // only changed text inside one live block is read from the transaction itself, that
4
+ // block is walked again from its own marker, and the result is spliced into the
5
+ // previous walk. Anything else (a marker, an attribute, a recovery record, a move,
6
+ // text inside a removed block's region) returns null and the caller walks from the
7
+ // start. The caller proves the splice against a full walk when asked (walkSignature).
8
+ import * as Y from "../../vendor/yjs.vendor.js";
9
+ import { idKey, sameJson } from "./layout.js";
10
+ import { marksFromAttributes, marksKey } from "./marks.js";
11
+ import { yAfter } from "./tokens.js";
12
+ import { canonicalRevision } from "./recovery.js";
13
+ import { tableFieldOwner } from "./table.js";
14
+ import { schema } from "../schema.js";
15
+
16
+ const TEXT_DELETE_PREFIX = '["text-delete",';
17
+
18
+ // The block whose attribute map a type is, or null.
19
+ const blockOfAttrs = (pd, type) => (type instanceof Y.AbstractType && type._item && type._item.parent === pd.attrs && typeof type._item.parentSub === "string" ? type._item.parentSub : null);
20
+ // The walk reads a block's type, and docOf reads every block's parent to break cycles.
21
+ const crossBlock = (key) => key === "t" || key === "parent";
22
+
23
+ const isMarker = (it) => it.content instanceof Y.ContentEmbed && it.content.embed.b !== undefined;
24
+ const textual = (it) => it.content instanceof Y.ContentString || it.content instanceof Y.ContentFormat || (it.content instanceof Y.ContentEmbed && it.content.embed.b === undefined);
25
+
26
+ // The deleted items the walk acts on (removal and legacy starts), by client. Yjs merges
27
+ // neighbouring deleted items and may have replaced a marker's content, so a deleted item
28
+ // is matched by id range, never by content. Any other deleted item is skipped, as the
29
+ // walk skips it.
30
+ const stopIndex = (w) => {
31
+ const by = new Map();
32
+ for (const stop of w.stops) (by.get(stop.client) ?? by.set(stop.client, []).get(stop.client)).push(stop);
33
+ return by;
34
+ };
35
+ const hitsStop = (it, stops) => {
36
+ const list = stops.get(it.id.client);
37
+ if (!list) return false;
38
+ const lo = it.id.clock, hi = lo + it.length;
39
+ return list.some((s) => s.clock < hi && lo < s.clock + s.len);
40
+ };
41
+
42
+ // The live marker whose block holds an item, or null when a stop lies between them.
43
+ const markerOf = (it, stops) => {
44
+ for (let left = it.left; left; left = left.left) {
45
+ if (left.deleted) { if (hitsStop(left, stops)) return null; continue; }
46
+ if (isMarker(left)) return left;
47
+ }
48
+ return null;
49
+ };
50
+
51
+ // Reads a finished Yjs transaction and returns the one live marker whose block alone it
52
+ // changed (or none), the marker of a block it split off that one (`split`, a new marker
53
+ // with a new attribute map), the blocks whose own fields it changed (table fields,
54
+ // attributes other than the type and the parent), whether it deleted anything and
55
+ // whether it wrote a format item; or null when the walk must start over. The walk never
56
+ // reads those fields: only those blocks' nodes are derived again.
57
+ export function captureDelta(pd, transaction, previous) {
58
+ const stops = stopIndex(previous);
59
+ const fields = new Set();
60
+ const born = new Set();
61
+ const tableKeys = transaction.changed.get(pd.tableFields) ?? new Set();
62
+ for (const [type, keys] of transaction.changed) {
63
+ if (type === pd.flow) continue;
64
+ if (type === pd.attrs) {
65
+ // A split writes the new block's first attribute map beside its marker. Only this
66
+ // tab's own: an id it just minted, so no removal, restore or copy record can name it.
67
+ if (!transaction.local) return null;
68
+ for (const key of keys) {
69
+ const map = pd.attrs.get(key);
70
+ if (!(map instanceof Y.Map) || map._item.left || map._item.id.clock < (transaction.beforeState.get(map._item.id.client) ?? 0)) return null;
71
+ born.add(key);
72
+ }
73
+ if (born.size > 1) return null;
74
+ continue;
75
+ }
76
+ if (type === pd.tableFields) {
77
+ for (const key of keys) {
78
+ const owner = pd.tableFields.has(key) ? tableFieldOwner(key, pd.tableFields.get(key)) : null;
79
+ if (owner === null) return null;
80
+ fields.add(owner);
81
+ }
82
+ continue;
83
+ }
84
+ const block = blockOfAttrs(pd, type);
85
+ if (block !== null) {
86
+ for (const key of keys) if (crossBlock(key)) return null;
87
+ fields.add(block);
88
+ continue;
89
+ }
90
+ if (type !== pd.gone) return null;
91
+ for (const key of keys) if (typeof key !== "string" || !key.startsWith(TEXT_DELETE_PREFIX)) return null;
92
+ }
93
+ const store = pd.ydoc.store;
94
+ let marker = null, split = null, formats = false;
95
+ const claim = (it) => {
96
+ if (!(it instanceof Y.Item)) return false;
97
+ // A text-delete record is written beside the deletion it describes; the walk never reads it.
98
+ if (it.parent === pd.gone) return typeof it.parentSub === "string" && it.parentSub.startsWith(TEXT_DELETE_PREFIX);
99
+ if (it.parent === pd.tableFields) return tableKeys.has(it.parentSub);
100
+ if (it.parent === pd.attrs) return born.has(it.parentSub);
101
+ if (it.parent !== pd.flow) {
102
+ const block = blockOfAttrs(pd, it.parent);
103
+ return block !== null && (born.has(block) || (fields.has(block) && !crossBlock(it.parentSub)));
104
+ }
105
+ if (isMarker(it)) {
106
+ if (it.deleted || !born.has(it.content.embed.b)) return false;
107
+ split = it;
108
+ } else if (!textual(it)) return false;
109
+ const owner = markerOf(it, stops);
110
+ if (!owner || (marker && owner !== marker)) return false;
111
+ marker = owner;
112
+ if (it.content instanceof Y.ContentFormat) formats = true;
113
+ return true;
114
+ };
115
+ for (const [client, clock] of transaction.afterState) {
116
+ for (let at = transaction.beforeState.get(client) ?? 0; at < clock;) {
117
+ const it = Y.getItem(store, { client, clock: at });
118
+ if (!claim(it)) return null;
119
+ at = it.id.clock + it.length;
120
+ }
121
+ }
122
+ let deleted = false;
123
+ for (const [client, ranges] of transaction.deleteSet.clients) {
124
+ for (const { clock, len } of ranges) {
125
+ for (let at = clock; at < clock + len;) {
126
+ const it = Y.getItem(store, { client, clock: at });
127
+ if (!claim(it)) return null;
128
+ deleted = true;
129
+ at = it.id.clock + it.length;
130
+ }
131
+ }
132
+ }
133
+ if (born.size && !split) return null;
134
+ return marker || fields.size ? { marker, split, deleted, formats, fields, remote: !transaction.local } : null;
135
+ }
136
+
137
+ const fmtOf = (cattrs) => { for (const key in cattrs) return { ...cattrs }; return null; };
138
+
139
+ // Walks one live block again from its marker and splices it into the previous walk,
140
+ // or returns null when that block is not a plain visible one. A block whose runs hold a
141
+ // hidden run (a removed or moved-away block's marker inside its extent) is walked again
142
+ // up to the first live marker, where its first hidden run starts; what follows is the
143
+ // previous walk's, moved by the change in length. That needs the format state there
144
+ // unchanged, so the transaction must not have written a format item. A split's new
145
+ // marker (`change.split`) ends the block there and opens the new block, which takes the
146
+ // rest of the extent.
147
+ export function rewalk(pd, previous, marker, change = null) {
148
+ const formats = change?.formats ?? true, split = change?.split ?? null;
149
+ const visible = previous.visible;
150
+ let index = -1, start = 0;
151
+ for (let i = 0; i < visible.length; i++) {
152
+ const seg = visible[i];
153
+ if (seg.brk && seg.brk.client === marker.id.client && seg.brk.clock === marker.id.clock) { index = i; break; }
154
+ start += seg.len;
155
+ }
156
+ if (index < 0) return null;
157
+ const old = visible[index];
158
+ if (old.implicit || old.atom || old.opaque || old.recovered || old.hide) return null;
159
+ const hole = old.runs.findIndex((r) => r.hole !== undefined);
160
+ if (hole >= 0 && formats) return null;
161
+ // A moved block keeps its old marker live under the removal that hides it, so its copy
162
+ // records its items. They only decide something when another copy of the block is
163
+ // visible too (walk.js, the post pass): that is the full walk's case.
164
+ if (old.items && previous.copies.has(old.baseId)) return null;
165
+ const stops = stopIndex(previous);
166
+ const cattrs = { ...(old.fmtIn ?? {}) };
167
+ let styled = null;
168
+ const first = { ...old, runs: [], len: 1 };
169
+ if (old.items) first.items = [];
170
+ let cur = first, bounded = false;
171
+ const origin = yAfter(previous.holes, start);
172
+ let at = origin + 1;
173
+ for (let it = marker.right; it; it = it.right) {
174
+ // A stop opens a hidden region inside this block's extent: that is the full walk's case.
175
+ if (it.deleted) { if (hitsStop(it, stops)) return null; continue; }
176
+ if (isMarker(it) && (it !== split || cur !== first)) { bounded = true; break; }
177
+ const here = at;
178
+ if (it.countable) at += it.length;
179
+ const c = it.content;
180
+ if (it === split) {
181
+ const id = c.embed.b;
182
+ const t = pd.attrs.get(id)?.get("t") || "paragraph";
183
+ if (!Object.hasOwn(schema.nodes, t) || !schema.nodes[t].isTextblock) return null;
184
+ const f = fmtOf(cattrs);
185
+ first.fmtOut = f;
186
+ cur = { kind: "b", id, baseId: id, brk: { client: it.id.client, clock: it.id.clock }, implicit: false, runs: [], len: 1, atom: false, items: null, fmtIn: f, fmtOut: null, t, opaque: false };
187
+ styled = null;
188
+ continue;
189
+ }
190
+ if (c instanceof Y.ContentFormat) { if (c.value === null) delete cattrs[c.key]; else cattrs[c.key] = c.value; styled = null; continue; }
191
+ if (c instanceof Y.ContentEmbed) {
192
+ cur.runs.push({ br: true });
193
+ if (cur.items) cur.items.push({ at: here, id: idKey(it.id), client: it.id.client, clock: it.id.clock, length: it.length, kind: "br", text: null, blockId: null, raw: { ...cattrs } });
194
+ cur.len += 1;
195
+ continue;
196
+ }
197
+ if (c instanceof Y.ContentString) {
198
+ if (!styled) { const marks = marksFromAttributes(cattrs, cur.t); styled = { marks, mk: marksKey(marks) }; }
199
+ cur.runs.push({ text: c.str, marks: styled.marks, mk: styled.mk });
200
+ if (cur.items) cur.items.push({ at: here, id: idKey(it.id), client: it.id.client, clock: it.id.clock, length: it.length, kind: "text", text: c.str, blockId: null, raw: { ...cattrs } });
201
+ cur.len += c.str.length;
202
+ } else if (it.countable) return null;
203
+ }
204
+ let prefix = old.len;
205
+ if (hole < 0) {
206
+ if (!sameJson(cattrs, old.fmtOut ?? {})) return null;
207
+ } else {
208
+ if (!bounded) return null;
209
+ // The tail after the hole keeps the marks it was read with, which follow the old
210
+ // block's type: a split that changes the type is the full walk's case.
211
+ if (cur.t !== old.t) return null;
212
+ prefix = 1;
213
+ for (let i = 0; i < hole; i++) prefix += old.runs[i].br ? 1 : old.runs[i].text.length;
214
+ }
215
+ const parts = cur === first ? [first] : [first, cur];
216
+ const delta = parts.reduce((n, part) => n + part.len, 0) - prefix;
217
+ const cut = origin + prefix;
218
+ const moved = (entry) => (entry.at >= cut ? { ...entry, at: entry.at + delta } : entry);
219
+ cur.fmtOut = old.fmtOut;
220
+ if (hole >= 0) {
221
+ cur.runs = cur.runs.concat(old.runs.slice(hole));
222
+ cur.len += old.len - prefix;
223
+ if (cur.items) cur.items = cur.items.concat(old.items.filter((item) => item.at >= cut).map(moved));
224
+ }
225
+ const next = { ...previous, visible: visible.slice(), tokens: previous.tokens + delta };
226
+ next.visible.splice(index, 1, ...parts);
227
+ if (delta) {
228
+ const end = start + prefix;
229
+ next.holes = previous.holes.map((h) => (h.at >= end ? { at: h.at + delta, len: h.len } : h));
230
+ next.hidden = previous.hidden.map((entry) => (entry.at >= cut ? { ...entry, at: entry.at + delta, items: entry.items.map(moved) } : entry));
231
+ for (let j = index + parts.length; j < next.visible.length; j += 1) {
232
+ const later = next.visible[j];
233
+ if (later.items?.length) next.visible[j] = { ...later, items: later.items.map(moved) };
234
+ }
235
+ }
236
+ return next;
237
+ }
238
+
239
+ const run = (r) => (r.br ? "\n" : r.hole !== undefined ? { hole: r.hole } : { text: r.text, mk: r.mk });
240
+ // Run boundaries follow Yjs item boundaries, and Yjs merges clock-adjacent live items
241
+ // after any transaction; only the text and its marks reach the document.
242
+ const joined = (runs) => {
243
+ const out = [];
244
+ for (const r of runs.map(run)) {
245
+ const last = out[out.length - 1];
246
+ if (last && typeof last === "object" && typeof r === "object" && last.text !== undefined && r.text !== undefined && last.mk === r.mk) out[out.length - 1] = { text: last.text + r.text, mk: r.mk };
247
+ else out.push(r);
248
+ }
249
+ return out;
250
+ };
251
+
252
+ // Everything a walk result holds, as one string, independent of key order and of the
253
+ // mark objects a run carries (their key stands for them).
254
+ // A fence or stop names a deleted item as it was when the walk ran; Yjs may since have
255
+ // merged it into a neighbour. The writer matches fences by containment (fenceAfter), so
256
+ // the item that holds the id now is what both walks mean.
257
+ const holder = (pd, id) => { const it = Y.getItem(pd.ydoc.store, { client: id.client, clock: id.clock }); return { client: it.id.client, clock: it.id.clock, len: it.length }; };
258
+ export const walkSignature = (pd, w) => canonicalRevision({ ...w, copies: [...w.copies], fences: w.fences.map((f) => holder(pd, f)), stops: w.stops.map((f) => holder(pd, f)), visible: w.visible.map((s) => ({ ...s, runs: joined(s.runs) })) });