@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
@@ -29,7 +29,15 @@ export const schema = new model.Schema({
29
29
  group: "block",
30
30
  content: "inline*",
31
31
  defining: true,
32
- attrs: { ...block, level: { default: 2 } },
32
+ attrs: {
33
+ ...block,
34
+ level: { default: 2 },
35
+ pageIcon: { default: null },
36
+ pageCoverSrc: { default: null },
37
+ pageCoverAlt: { default: "" },
38
+ pageCoverX: { default: 50 },
39
+ pageCoverY: { default: 50 },
40
+ },
33
41
  toDOM: (n) => [`h${n.attrs.level}`, ref(n), 0],
34
42
  },
35
43
  list_item: {
@@ -50,6 +58,12 @@ export const schema = new model.Schema({
50
58
  "data-checked": n.attrs.checked ? "true" : "false",
51
59
  }), 0],
52
60
  },
61
+ quote: {
62
+ group: "block",
63
+ content: "inline*",
64
+ attrs: block,
65
+ toDOM: (n) => ["blockquote", ref(n), 0],
66
+ },
53
67
  callout: {
54
68
  group: "block",
55
69
  content: "inline*",
@@ -68,8 +82,8 @@ export const schema = new model.Schema({
68
82
  toggle: {
69
83
  group: "block",
70
84
  content: "inline*",
71
- attrs: { ...block, open: { default: false } },
72
- toDOM: (n) => ["div", ref(n, { class: "page-toggle", "data-open": n.attrs.open ? "true" : "false" }), 0],
85
+ attrs: { ...block, open: { default: false }, level: { default: null } },
86
+ toDOM: (n) => ["div", ref(n, { class: "page-toggle", "data-open": n.attrs.open ? "true" : "false", ...(n.attrs.level ? { "data-level": String(n.attrs.level) } : {}) }), 0],
73
87
  },
74
88
  divider: {
75
89
  group: "block",
@@ -77,6 +91,14 @@ export const schema = new model.Schema({
77
91
  attrs: block,
78
92
  toDOM: (n) => ["hr", ref(n)],
79
93
  },
94
+ // A table of contents holds no headings: its entries are derived from the document
95
+ // whenever it is drawn, so a rename, a move or a deletion needs no repair here.
96
+ toc: {
97
+ group: "block",
98
+ atom: true,
99
+ attrs: { ...block, maxLevel: { default: null } },
100
+ toDOM: (n) => ["div", ref(n, { class: "page-toc", "data-type": "toc", ...(n.attrs.maxLevel ? { "data-max-level": String(n.attrs.maxLevel) } : {}) })],
101
+ },
80
102
  image: {
81
103
  group: "block",
82
104
  atom: true,
@@ -174,5 +196,32 @@ export const schema = new model.Schema({
174
196
  inclusive: false,
175
197
  toDOM: (m) => ["a", { href: safeHref(m.attrs.href) }, 0],
176
198
  },
199
+ // A person mention is ordinary text with this mark over it: the name is the label
200
+ // and `id` is the people registry's reference. It links nowhere and notifies nobody.
201
+ mention: {
202
+ attrs: { id: {} },
203
+ inclusive: false,
204
+ excludes: "mention date link",
205
+ toDOM: (m) => ["span", { "data-page-mention": m.attrs.id }, 0],
206
+ },
207
+ // A date mention is the calendar date as ordinary ISO text with this mark over it:
208
+ // `date` is YYYY-MM-DD, and no time, zone, reminder or relative word is stored.
209
+ date: {
210
+ attrs: { date: {} },
211
+ inclusive: false,
212
+ excludes: "mention date link",
213
+ toDOM: (m) => ["time", { datetime: m.attrs.date, "data-page-date": "" }, 0],
214
+ },
215
+ // The floor-1 formats (format-native.js): written as named descriptors,
216
+ // drawn as the elements the saved article keeps for them.
217
+ underline: { toDOM: () => ["u", 0] },
218
+ text_color: {
219
+ attrs: { color: {} },
220
+ toDOM: (m) => ["span", { "data-color": m.attrs.color }, 0],
221
+ },
222
+ highlight: {
223
+ attrs: { color: {} },
224
+ toDOM: (m) => ["mark", { "data-color": m.attrs.color }, 0],
225
+ },
177
226
  },
178
227
  });
@@ -33,7 +33,9 @@ function textGrid(text) {
33
33
  export function gridFromClipboard(html, text, doc = globalThis.document) {
34
34
  let found = null;
35
35
  try { found = htmlGrid(html, doc); } catch { found = null; }
36
- if (!found) {
36
+ // An HTML table with other blocks around it is a document, not a sheet: its tab-separated
37
+ // text is no grid either, and the editor's own paste keeps every block.
38
+ if (!found && !/<table[\s>]/i.test(html || "")) {
37
39
  try { found = textGrid(text); } catch { found = null; }
38
40
  }
39
41
  if (!found || !found.grid.length) return null;
@@ -9,11 +9,11 @@ export const TABLE_CSS = `
9
9
  .page-table table.is-sized th, .page-table table.is-sized td { min-width: 0; overflow-wrap: anywhere; }
10
10
  .page-table-resize { position: absolute; top: 0; bottom: 0; inset-inline-end: -4px; width: 8px; z-index: 2; cursor: col-resize; touch-action: none; }
11
11
  .page-table-resize:hover, .page-table-resize:active, .page-table-resize:focus-visible { background: var(--page-accent); opacity: 0.4; }
12
- .page-table-add-row, .page-table-add-col { position: absolute; z-index: 1; border: 0; padding: 0; background: var(--page-chrome-bg-rest); color: var(--page-chrome-ink-rest); font: inherit; line-height: 1; cursor: pointer; opacity: 0; transition: opacity 120ms; }
13
- .page-table-add-row { left: 0; right: 0; top: 100%; height: 14px; border-radius: 0 0 6px 6px; }
14
- .page-table-add-col { top: 0; bottom: 0; left: 100%; width: 14px; border-radius: 0 6px 6px 0; }
12
+ .page-table-add-row, .page-table-add-col { position: absolute; z-index: 1; padding: 0; background: var(--page-chrome-bg-moved); color: var(--page-ink-soft); display: flex; align-items: center; justify-content: center; font: inherit; font-size: 14px; line-height: 1; cursor: pointer; opacity: 0; transition: opacity 120ms; }
13
+ .page-table-add-row { left: 0; right: 0; top: 100%; height: 16px; border-radius: 0 0 6px 6px; border: 1px solid var(--page-chrome-line-moved); border-top: 0; padding-bottom: 2px; box-sizing: border-box; }
14
+ .page-table-add-col { top: 0; bottom: 0; left: 100%; width: 16px; border-radius: 0 6px 6px 0; border: 1px solid var(--page-chrome-line-moved); border-left: 0; box-sizing: border-box; }
15
15
  .page-table:hover > .page-table-add-row, .page-table:hover > .page-table-add-col, .page-table-add-row:focus-visible, .page-table-add-col:focus-visible { opacity: 1; }
16
- .page-table-add-row:hover, .page-table-add-col:hover { background: var(--page-chrome-bg-moved); color: var(--page-ink-soft); }
16
+ .page-table-add-row:hover, .page-table-add-col:hover { background: var(--page-pop-hover); color: var(--page-ink); }
17
17
  .page-table-date { position: absolute; inset-inline-start: 0; top: 100%; z-index: 3; font: inherit; color: var(--page-pop-ink); background: var(--page-pop-bg); border: 1px solid var(--page-pop-line); border-radius: 6px; padding: 4px 6px; box-shadow: var(--page-pop-shadow); }
18
18
  @media (max-width: 560px) {
19
19
  .page-table-date { min-height: 44px; }
package/src/page/table.js CHANGED
@@ -213,6 +213,33 @@ export const moveColumn = (delta) => (st, dispatch) => changeTable(st, dispatch,
213
213
  export const MIN_WIDTH = 48, MAX_WIDTH = 1200, DEFAULT_WIDTH = 160;
214
214
  const widthOf = (value) => (Number.isFinite(value) && value > 0 ? Math.round(Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, value))) : null);
215
215
 
216
+ // Clears every stored width, so the table fills the page again.
217
+ export const clearColumnWidths = (colId) => (st, dispatch) => changeTable(st, dispatch, (table) => {
218
+ const ids = columns(table);
219
+ if (!ids.some((id) => table.attrs.cols[id].width)) return null;
220
+ const cols = {};
221
+ for (const id of ids) {
222
+ const { width: _old, ...rest } = table.attrs.cols[id];
223
+ cols[id] = rest;
224
+ }
225
+ return { table: copyTable(table, { cols }, table.content), cell: Math.max(0, ids.indexOf(colId)) };
226
+ });
227
+
228
+ // Several widths in one step: the first width set on a table that fills the page
229
+ // also fixes every other column at the width it is drawn at, so none of them jumps.
230
+ export const setColumnWidths = (widths, colId) => (st, dispatch) => changeTable(st, dispatch, (table) => {
231
+ const cols = { ...table.attrs.cols };
232
+ let changed = false;
233
+ for (const [id, width] of Object.entries(widths)) {
234
+ const next = widthOf(width);
235
+ if (!cols[id] || next === null || cols[id].width === next) continue;
236
+ cols[id] = { ...cols[id], width: next };
237
+ changed = true;
238
+ }
239
+ if (!changed) return null;
240
+ return { table: copyTable(table, { cols }, table.content), cell: columns(table).indexOf(colId) };
241
+ });
242
+
216
243
  export const setColumnWidth = (colId, width) => (st, dispatch) => changeTable(st, dispatch, (table) => {
217
244
  const col = table.attrs.cols[colId];
218
245
  if (!col) return null;
@@ -295,9 +322,9 @@ export function pasteTable(view, event) {
295
322
  if (found.width * found.height < 2) return false;
296
323
  return pasteGrid(found.grid)(st, view.dispatch);
297
324
  }
298
- if (found.width * found.height < 2) return false;
325
+ if (found.width * found.height < 2 || found.own) return false;
299
326
  if (found.height < 2 && !/<table[\s>]/i.test(data.getData("text/html"))) return false;
300
- const table = tableFromGrid(found.grid, { header: !found.own && found.height > 1 });
327
+ const table = tableFromGrid(found.grid, { header: found.height > 1 });
301
328
  if (!table) return false;
302
329
  if (tableSize(table) > TABLE_IMPORT_LIMIT) {
303
330
  toast("This paste is too large for one table.", "error");
@@ -317,10 +344,25 @@ export function pasteTable(view, event) {
317
344
 
318
345
  export const tableKeys = keymap.keymap({ Tab: nextCell, "Shift-Tab": prevCell, Enter: cellBelow, "Mod-Enter": addRow(true) });
319
346
 
347
+ function caretInCell(view, event) {
348
+ const cell = cellViews.get(event.target);
349
+ const pos = cell?.getPos();
350
+ if (pos == null || event.button || event.shiftKey || !view.editable) return false;
351
+ event.preventDefault();
352
+ const start = event.clientX < cell.contentDOM.getBoundingClientRect().left;
353
+ view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, pos + 1 + (start ? 0 : cell.node.content.size))));
354
+ view.focus();
355
+ cell.openDate();
356
+ return true;
357
+ }
358
+
320
359
  export function tablePlugin() {
321
360
  return new Plugin({
322
361
  props: {
323
362
  handlePaste: pasteTable,
363
+ handleDOMEvents: {
364
+ mousedown: caretInCell,
365
+ },
324
366
  handleClickOn(view, _pos, node, nodePos, event, direct) {
325
367
  if (node.type !== N.table_cell || !direct || event.button) return false;
326
368
  const desc = view.nodeDOM(nodePos);
@@ -2,6 +2,11 @@
2
2
  article[data-page] {
3
3
  --page-link: #164583;
4
4
  --page-font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
5
+ --page-color-gray: #5B5953; --page-color-brown: #8A5A1E; --page-color-orange: #B4531A; --page-color-yellow: #7A6210; --page-color-green: #3E6A28;
6
+ --page-color-blue: #1F5A8A; --page-color-purple: #5E4596; --page-color-pink: #A23E6E; --page-color-red: #9E3B2F;
7
+ --page-mark-bg-gray: rgba(35, 34, 31, 0.12); --page-mark-bg-brown: rgba(138, 90, 30, 0.20); --page-mark-bg-orange: rgba(180, 83, 26, 0.20);
8
+ --page-mark-bg-yellow: rgba(122, 98, 16, 0.22); --page-mark-bg-green: rgba(62, 106, 40, 0.20); --page-mark-bg-blue: rgba(31, 90, 138, 0.18);
9
+ --page-mark-bg-purple: rgba(94, 69, 150, 0.18); --page-mark-bg-pink: rgba(162, 62, 110, 0.18); --page-mark-bg-red: rgba(158, 59, 47, 0.18);
5
10
  min-height: 100vh;
6
11
  box-sizing: border-box;
7
12
  padding: var(--page-pad-top) 16px 30vh;
@@ -42,5 +47,24 @@
42
47
  article[data-page] th, article[data-page] td { min-width: 120px; padding: 6px 10px; border-inline-end: 1px solid var(--page-rule); border-block-end: 1px solid var(--page-rule); text-align: start; white-space: pre-wrap; }
43
48
  article[data-page] th { background: var(--page-code-bg); color: var(--page-ink-soft); font-weight: 600; }
44
49
  article[data-page] th[data-kind="number"], article[data-page] td[data-kind="number"] { text-align: end; }
45
- article[data-page] blockquote { color: var(--page-ink-soft); border-inline-start: 1px solid var(--page-rule); padding-inline-start: var(--page-indent); }
50
+ article[data-page] blockquote { color: var(--page-ink-soft); padding-inline-start: var(--page-indent); }
51
+ article[data-page] span[data-color="gray"] { color: var(--page-color-gray); }
52
+ article[data-page] span[data-color="brown"] { color: var(--page-color-brown); }
53
+ article[data-page] span[data-color="orange"] { color: var(--page-color-orange); }
54
+ article[data-page] span[data-color="yellow"] { color: var(--page-color-yellow); }
55
+ article[data-page] span[data-color="green"] { color: var(--page-color-green); }
56
+ article[data-page] span[data-color="blue"] { color: var(--page-color-blue); }
57
+ article[data-page] span[data-color="purple"] { color: var(--page-color-purple); }
58
+ article[data-page] span[data-color="pink"] { color: var(--page-color-pink); }
59
+ article[data-page] span[data-color="red"] { color: var(--page-color-red); }
60
+ article[data-page] mark[data-color="gray"] { background: var(--page-mark-bg-gray); }
61
+ article[data-page] mark[data-color="brown"] { background: var(--page-mark-bg-brown); }
62
+ article[data-page] mark[data-color="orange"] { background: var(--page-mark-bg-orange); }
63
+ article[data-page] mark[data-color="yellow"] { background: var(--page-mark-bg-yellow); }
64
+ article[data-page] mark[data-color="green"] { background: var(--page-mark-bg-green); }
65
+ article[data-page] mark[data-color="blue"] { background: var(--page-mark-bg-blue); }
66
+ article[data-page] mark[data-color="purple"] { background: var(--page-mark-bg-purple); }
67
+ article[data-page] mark[data-color="pink"] { background: var(--page-mark-bg-pink); }
68
+ article[data-page] mark[data-color="red"] { background: var(--page-mark-bg-red); }
69
+ article[data-page] mark { color: inherit; }
46
70
  }
@@ -95,6 +95,11 @@
95
95
  article[data-page] {
96
96
  --page-link: #164583;
97
97
  --page-font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
98
+ --page-color-gray: #5B5953; --page-color-brown: #8A5A1E; --page-color-orange: #B4531A; --page-color-yellow: #7A6210; --page-color-green: #3E6A28;
99
+ --page-color-blue: #1F5A8A; --page-color-purple: #5E4596; --page-color-pink: #A23E6E; --page-color-red: #9E3B2F;
100
+ --page-mark-bg-gray: rgba(35, 34, 31, 0.12); --page-mark-bg-brown: rgba(138, 90, 30, 0.20); --page-mark-bg-orange: rgba(180, 83, 26, 0.20);
101
+ --page-mark-bg-yellow: rgba(122, 98, 16, 0.22); --page-mark-bg-green: rgba(62, 106, 40, 0.20); --page-mark-bg-blue: rgba(31, 90, 138, 0.18);
102
+ --page-mark-bg-purple: rgba(94, 69, 150, 0.18); --page-mark-bg-pink: rgba(162, 62, 110, 0.18); --page-mark-bg-red: rgba(158, 59, 47, 0.18);
98
103
  min-height: 100vh;
99
104
  box-sizing: border-box;
100
105
  padding: var(--page-pad-top) 16px 30vh;
@@ -135,7 +140,26 @@
135
140
  article[data-page] th, article[data-page] td { min-width: 120px; padding: 6px 10px; border-inline-end: 1px solid var(--page-rule); border-block-end: 1px solid var(--page-rule); text-align: start; white-space: pre-wrap; }
136
141
  article[data-page] th { background: var(--page-code-bg); color: var(--page-ink-soft); font-weight: 600; }
137
142
  article[data-page] th[data-kind="number"], article[data-page] td[data-kind="number"] { text-align: end; }
138
- article[data-page] blockquote { color: var(--page-ink-soft); border-inline-start: 1px solid var(--page-rule); padding-inline-start: var(--page-indent); }
143
+ article[data-page] blockquote { color: var(--page-ink-soft); padding-inline-start: var(--page-indent); }
144
+ article[data-page] span[data-color="gray"] { color: var(--page-color-gray); }
145
+ article[data-page] span[data-color="brown"] { color: var(--page-color-brown); }
146
+ article[data-page] span[data-color="orange"] { color: var(--page-color-orange); }
147
+ article[data-page] span[data-color="yellow"] { color: var(--page-color-yellow); }
148
+ article[data-page] span[data-color="green"] { color: var(--page-color-green); }
149
+ article[data-page] span[data-color="blue"] { color: var(--page-color-blue); }
150
+ article[data-page] span[data-color="purple"] { color: var(--page-color-purple); }
151
+ article[data-page] span[data-color="pink"] { color: var(--page-color-pink); }
152
+ article[data-page] span[data-color="red"] { color: var(--page-color-red); }
153
+ article[data-page] mark[data-color="gray"] { background: var(--page-mark-bg-gray); }
154
+ article[data-page] mark[data-color="brown"] { background: var(--page-mark-bg-brown); }
155
+ article[data-page] mark[data-color="orange"] { background: var(--page-mark-bg-orange); }
156
+ article[data-page] mark[data-color="yellow"] { background: var(--page-mark-bg-yellow); }
157
+ article[data-page] mark[data-color="green"] { background: var(--page-mark-bg-green); }
158
+ article[data-page] mark[data-color="blue"] { background: var(--page-mark-bg-blue); }
159
+ article[data-page] mark[data-color="purple"] { background: var(--page-mark-bg-purple); }
160
+ article[data-page] mark[data-color="pink"] { background: var(--page-mark-bg-pink); }
161
+ article[data-page] mark[data-color="red"] { background: var(--page-mark-bg-red); }
162
+ article[data-page] mark { color: inherit; }
139
163
  }
140
164
 
141
165
 
@@ -191,12 +215,12 @@
191
215
  --page-code-bg: rgba(35, 34, 31, 0.045);
192
216
  --page-code-ink: #23221F;
193
217
 
194
- --page-chrome-ink-rest: rgba(35, 34, 31, 0.16);
195
- --page-chrome-ink-moved: rgba(35, 34, 31, 0.30);
196
- --page-chrome-bg-rest: rgba(35, 34, 31, 0.025);
197
- --page-chrome-bg-moved: rgba(35, 34, 31, 0.045);
198
- --page-chrome-line-rest: rgba(35, 34, 31, 0.16);
199
- --page-chrome-line-moved: rgba(35, 34, 31, 0.30);
218
+ --page-chrome-ink-rest: rgba(35, 34, 31, 0.42);
219
+ --page-chrome-ink-moved: rgba(35, 34, 31, 0.62);
220
+ --page-chrome-bg-rest: rgba(35, 34, 31, 0.035);
221
+ --page-chrome-bg-moved: rgba(35, 34, 31, 0.06);
222
+ --page-chrome-line-rest: rgba(35, 34, 31, 0.22);
223
+ --page-chrome-line-moved: rgba(35, 34, 31, 0.36);
200
224
  --page-chrome-shadow-rest: none;
201
225
  --page-chrome-shadow-moved: none;
202
226
  --page-chrome-radius: 7px;
@@ -283,7 +307,8 @@ article[data-page] figure[data-type="chart"] > svg { display: block; width: 100%
283
307
  height: 15px;
284
308
  }
285
309
 
286
- .page-shell .page-dock .page-tool-turn {
310
+ .page-shell .page-dock .page-tool-turn,
311
+ .page-shell .page-bubble .page-tool-turn {
287
312
  width: auto;
288
313
  gap: 4px;
289
314
  padding: 0 8px;
@@ -54,12 +54,12 @@
54
54
  --page-code-bg: rgba(35, 34, 31, 0.045);
55
55
  --page-code-ink: #23221F;
56
56
 
57
- --page-chrome-ink-rest: rgba(35, 34, 31, 0.16);
58
- --page-chrome-ink-moved: rgba(35, 34, 31, 0.30);
59
- --page-chrome-bg-rest: rgba(35, 34, 31, 0.025);
60
- --page-chrome-bg-moved: rgba(35, 34, 31, 0.045);
61
- --page-chrome-line-rest: rgba(35, 34, 31, 0.16);
62
- --page-chrome-line-moved: rgba(35, 34, 31, 0.30);
57
+ --page-chrome-ink-rest: rgba(35, 34, 31, 0.42);
58
+ --page-chrome-ink-moved: rgba(35, 34, 31, 0.62);
59
+ --page-chrome-bg-rest: rgba(35, 34, 31, 0.035);
60
+ --page-chrome-bg-moved: rgba(35, 34, 31, 0.06);
61
+ --page-chrome-line-rest: rgba(35, 34, 31, 0.22);
62
+ --page-chrome-line-moved: rgba(35, 34, 31, 0.36);
63
63
  --page-chrome-shadow-rest: none;
64
64
  --page-chrome-shadow-moved: none;
65
65
  --page-chrome-radius: 7px;
@@ -146,7 +146,8 @@ article[data-page] figure[data-type="chart"] > svg { display: block; width: 100%
146
146
  height: 15px;
147
147
  }
148
148
 
149
- .page-shell .page-dock .page-tool-turn {
149
+ .page-shell .page-dock .page-tool-turn,
150
+ .page-shell .page-bubble .page-tool-turn {
150
151
  width: auto;
151
152
  gap: 4px;
152
153
  padding: 0 8px;
@@ -0,0 +1,31 @@
1
+ // The saved table of contents' links: decode the anchor a link names, and reveal
2
+ // the block it points at in a reader. Loaded the first time a reader follows one.
3
+ const TOC_ANCHOR = "page-block-";
4
+
5
+ const fromB64url = (text) => {
6
+ const bin = atob(String(text).replace(/-/g, "+").replace(/_/g, "/"));
7
+ return new TextDecoder().decode(Uint8Array.from(bin, (ch) => ch.charCodeAt(0)));
8
+ };
9
+
10
+ export function idFromHref(href) {
11
+ const hash = String(href ?? "").replace(/^#/, "");
12
+ if (!hash) return null;
13
+ try {
14
+ return hash.startsWith(TOC_ANCHOR) ? fromB64url(hash.slice(TOC_ANCHOR.length)) : decodeURIComponent(hash);
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ // A table of contents link in a saved page shows its target and opens every collapsed
21
+ // body above it: reading only, so nothing is saved, no history moves and no peer is
22
+ // told. A link whose block is gone, or to anywhere else, changes nothing.
23
+ export function followTocLink(article, href) {
24
+ const id = idFromHref(href);
25
+ if (id === null) return null;
26
+ const target = [...article.querySelectorAll("[data-id]")].find((el) => el.getAttribute("data-id") === id) ?? null;
27
+ if (!target) return null;
28
+ for (let el = target.parentElement; el && el !== article; el = el.parentElement) if (el.tagName === "DETAILS") el.open = true;
29
+ target.scrollIntoView?.({ block: "start" });
30
+ return target;
31
+ }
@@ -0,0 +1,5 @@
1
+ // The table of contents: the headings a document has and the anchors that link to them.
2
+ // The saved renderer and the editor's node view ask one helper; a reader's reveal and
3
+ // the anchor decoder live in toc-link.js, which nothing but a followed link loads.
4
+ export { TOC_ANCHOR, TOC_LABEL, blockAnchor, tocHtml, tocEntries } from "./projection.js";
5
+ export { idFromHref } from "./toc-link.js";
@@ -0,0 +1,58 @@
1
+ import { state } from "../vendor/prosemirror.vendor.js";
2
+
3
+ // The update hooks and decorations a loaded view part registers for its editor
4
+ // view. The plugin below calls the hooks on every document change and collects the
5
+ // decorations on every draw, so a part keeps its blocks fresh without the editor
6
+ // importing it: the part adds its hook when it installs and removes it on dispose.
7
+ export const viewHooks = new WeakMap();
8
+
9
+ // The feature loader of each mounted editor, so an insert path can ask for a
10
+ // block's part before it draws the block.
11
+ export const viewFeatures = new WeakMap();
12
+
13
+ export function addViewHook(view, hook) {
14
+ const hooks = viewHooks.get(view) ?? new Set();
15
+ viewHooks.set(view, hooks);
16
+ hooks.add(hook);
17
+ return () => hooks.delete(hook);
18
+ }
19
+
20
+ export function viewHookPlugin() {
21
+ let editorView = null;
22
+ const mounted = new WeakMap();
23
+ return new state.Plugin({
24
+ view: (view) => {
25
+ editorView = view;
26
+ mounted.set(view, (mounted.get(view) ?? 0) + 1);
27
+ return {
28
+ update: (next) => { for (const hook of viewHooks.get(next) ?? []) hook.update?.(next); },
29
+ destroy: () => {
30
+ mounted.set(view, mounted.get(view) - 1);
31
+ if (editorView === view) editorView = null;
32
+ // A reconfigure destroys this plugin view and mounts a new one for the same editor in
33
+ // the same task; only an editor that stays without one is really going away.
34
+ queueMicrotask(() => {
35
+ if (mounted.get(view)) return;
36
+ const hooks = viewHooks.get(view);
37
+ if (!hooks) return;
38
+ for (const hook of hooks) hook.destroy?.(view);
39
+ hooks.clear();
40
+ });
41
+ },
42
+ };
43
+ },
44
+ props: {
45
+ decorations(st) {
46
+ const hooks = editorView ? viewHooks.get(editorView) : null;
47
+ if (!hooks) return null;
48
+ let found = null;
49
+ for (const hook of hooks) {
50
+ const set = hook.decorations?.(st);
51
+ if (!set) continue;
52
+ found = found ? found.add(st.doc, set.find()) : set;
53
+ }
54
+ return found;
55
+ },
56
+ },
57
+ });
58
+ }
package/src/page/view.js CHANGED
@@ -16,6 +16,10 @@ const LINEAGE = ["live-at", "live-by"];
16
16
 
17
17
  export { applyPreparedArticle, prepareSavedArticle };
18
18
 
19
+ // A table of contents link in a saved page reveals its target. The reveal code and
20
+ // the anchor decoder load the first time somebody follows one, never with the page.
21
+ export const followTocLink = (article, href) => import("./toc-link.js").then((m) => m.followTocLink(article, href));
22
+
19
23
  // The stamp of the file this reader accepted, recorded where a save reads it. The
20
24
  // stamp module is the write path's own lazy chunk: a reader paints without it, and
21
25
  // a save loads the one copy both paths then share.
@@ -262,6 +266,15 @@ function createReader({ doc, article, represented = null, feed = null, documentU
262
266
  doc.addEventListener("toggle", onToggle, true);
263
267
  listeners.push(["toggle", onToggle, true]);
264
268
 
269
+ const onTocLink = (event) => {
270
+ const link = event.target.closest?.('a[href^="#page-block-"], a[href^="#"]');
271
+ if (!link || !article.contains(link) || !link.closest('nav[aria-label="Table of contents"]')) return;
272
+ event.preventDefault();
273
+ followTocLink(article, link.getAttribute("href"));
274
+ };
275
+ doc.addEventListener("click", onTocLink);
276
+ listeners.push(["click", onTocLink]);
277
+
265
278
  if (represented) {
266
279
  const prepared = prepareSavedArticle(represented.bytes, doc);
267
280
  if (!prepared.ok) throw new Error(`clayjs: the saved file is not a readable page (${prepared.reason})`);
@@ -2,17 +2,45 @@ import * as Y from "../../vendor/yjs.vendor.js";
2
2
  import { NET, createPageDoc, walk, docOf } from "./core.js";
3
3
  import { createBridge, keepViewPos } from "./bridge.js";
4
4
  import { createYProvider, entriesOf, dec } from "./provider.js";
5
+ import { createPageFileDebt } from "./file-debt.js";
5
6
 
6
- export function createYPageBinding({ store, ydoc = new Y.Doc(), onNotice = () => {}, ownedTokens = [], authorToken = null, setTimer = setTimeout, clearTimer = clearTimeout, compact, captureTimeout = 500, checkpointWithin = () => {}, requestCheckpoint = null, registerUnsavedState = null, onStopped = () => {} }) {
7
+ export function createYPageBinding({ store, ydoc = new Y.Doc(), onNotice = () => {}, ownedTokens = [], authorToken = null, setTimer = setTimeout, clearTimer = clearTimeout, compact, captureTimeout = 500, checkpointWithin = () => {}, requestCheckpoint = null, requestExitCheckpoint = null, registerUnsavedState = null, onStopped = () => {}, journal = null, exitClosed = () => false, stats = { groups: 0, mismatch: 0, renders: 0, first: null } }) {
7
8
  for (const [, v] of entriesOf(store.value.y)) Y.applyUpdateV2(ydoc, dec(v), NET);
8
9
  const pd = createPageDoc(ydoc);
9
- const provider = createYProvider({ store, ydoc, setTimer, clearTimer, compact, onStopped });
10
- const stats = { groups: 0, mismatch: 0, renders: 0, first: null };
10
+ // The journal is installed before the provider, because the provider is what sees an
11
+ // update: nothing can reach the document without passing it first. Its own code lives
12
+ // with the recovery channel and is only loaded when a page has one to journal into.
13
+ const pageJournal = journal || null;
14
+ // The input this tab owes the file, recorded before the provider because the provider is
15
+ // what sees an update. The notification it fires through is attached once the binding's
16
+ // own registration exists, so an update that arrives while this function is still
17
+ // building cannot reach an uninitialized name.
18
+ let notifyDebt = () => {};
19
+ const debt = createPageFileDebt({ changed: () => notifyDebt() });
20
+ const provider = createYProvider({
21
+ store,
22
+ ydoc,
23
+ setTimer,
24
+ clearTimer,
25
+ compact,
26
+ // A full or failed room stops offering at once, but the checkpoint of what it already
27
+ // admitted goes out before the page locks and holds every save.
28
+ onStopped: (reason) => {
29
+ if (reason !== "page-full" && reason !== "compaction-failed") { onStopped(reason); return; }
30
+ Promise.resolve(requestCheckpoint?.({ timeoutMs: 10_000 })).catch(() => {}).finally(() => onStopped(reason));
31
+ },
32
+ onLocalUpdate: debt.local,
33
+ journal: { local: (u) => pageJournal?.local(u), dependency: (u) => pageJournal?.dependency(u) },
34
+ });
11
35
  const bridge = createBridge({ pd, onNotice, ownedTokens, authorToken, stats, captureTimeout });
12
36
  const waiters = new Set();
13
37
  let disposed = false;
14
38
  let checksQueued = false;
15
- const unsaved = registerUnsavedState?.({ id: "page:live", isPending: () => store.pending.length > 0 || provider.reconcilePending, flush });
39
+ const unsaved = registerUnsavedState?.({
40
+ id: "page:live",
41
+ isPending: () => (exitClosed() && debt.pending()) || store.pending.length > 0 || provider.pending || provider.reconcilePending || Boolean(pageJournal && pageJournal.pending() > (pageJournal.failure ? 1 : 0)),
42
+ flush,
43
+ });
16
44
  const changed = () => {
17
45
  unsaved?.changed();
18
46
  if (checksQueued) return;
@@ -22,12 +50,87 @@ export function createYPageBinding({ store, ydoc = new Y.Doc(), onNotice = () =>
22
50
  if (!disposed) for (const waiter of [...waiters]) waiter.check();
23
51
  });
24
52
  };
53
+ notifyDebt = changed;
25
54
  const offLocal = store.onLocal(() => { checkpointWithin(1500); changed(); });
26
55
  const offRebase = store.onRebase(changed);
27
- const offConfirm = store.onConfirm(changed);
56
+ const offConfirm = store.onConfirm(() => {
57
+ pageJournal?.acknowledged();
58
+ changed();
59
+ });
60
+ // Everything the journal reports while this session is the active one is unsaved state:
61
+ // a write in flight, a write the store refused, or a room that has not confirmed.
62
+ if (pageJournal) pageJournal.onChange = changed;
63
+
64
+ // Everything the file or the room still owes: this tab's own input, a store op no
65
+ // answer has covered, a reconcile, a journal write still in flight.
66
+ const exitPending = () =>
67
+ debt.pending() || store.pending.length > 0 || provider.pending || provider.reconcilePending ||
68
+ Boolean(pageJournal && pageJournal.pending() > (pageJournal.failure ? 1 : 0));
69
+
70
+ // The exit's own flush, under the permit the barrier minted.
71
+ //
72
+ // A fast store's capture holds the room's value, so this tab's ops travel through the
73
+ // room first; a file store's capture holds its pending ops as they stand, so the
74
+ // checkpoint goes while they are still pending and their acceptance is what confirms
75
+ // them. The checkpoint is asked for only when this tab owes the file input: a tab
76
+ // holding nothing of its own leaves without making the room's writer save for it.
77
+ function flushExit({ permit, deadline, keepalive, signal, timeoutMs }) {
78
+ const end = Math.min(deadline ?? Infinity, Date.now() + timeoutMs);
79
+ const over = () => signal?.aborted || Date.now() >= end;
80
+ return new Promise((resolve) => {
81
+ let done = false, timer = null, wake = null;
82
+ const waiter = {
83
+ check() { wake?.(); },
84
+ finish(result) {
85
+ if (done) return;
86
+ done = true;
87
+ waiters.delete(waiter);
88
+ clearTimer(timer);
89
+ signal?.removeEventListener("abort", aborted);
90
+ resolve(result);
91
+ },
92
+ };
93
+ const aborted = () => waiter.finish({ ok: false, code: "timeout" });
94
+ const wait = (ms) => new Promise((woken) => {
95
+ const wakeNow = () => {
96
+ clearTimer(timer);
97
+ wake = null;
98
+ woken();
99
+ };
100
+ timer = setTimer(wakeNow, ms);
101
+ wake = wakeNow;
102
+ });
103
+ const run = async () => {
104
+ provider.settle();
105
+ if (debt.pending() && requestExitCheckpoint) {
106
+ const result = await requestExitCheckpoint({
107
+ permit,
108
+ deadline: end,
109
+ signal,
110
+ keepalive,
111
+ needsSave: () => debt.pending(),
112
+ });
113
+ if (done) return;
114
+ if (!result?.ok) {
115
+ waiter.finish(result?.ok === false ? result : { ok: false, code: "checkpoint-failed" });
116
+ return;
117
+ }
118
+ }
119
+ while (!done) {
120
+ if (!exitPending()) { waiter.finish({ ok: true }); return; }
121
+ if (over()) { waiter.finish({ ok: false, code: "timeout" }); return; }
122
+ await wait(50);
123
+ }
124
+ };
125
+ waiters.add(waiter);
126
+ signal?.addEventListener("abort", aborted, { once: true });
127
+ run().catch((error) => waiter.finish({ ok: false, code: "flush-failed", message: error?.message || String(error) }));
128
+ });
129
+ }
28
130
 
29
- function flush({ timeoutMs = 10_000 } = {}) {
131
+ function flush({ permit = null, deadline: exitDeadline = null, keepalive = false, signal = null, timeoutMs = 10_000 } = {}) {
30
132
  if (disposed) return Promise.resolve({ ok: false, code: "disposed" });
133
+ if (permit) return flushExit({ permit, deadline: exitDeadline, keepalive, signal, timeoutMs });
31
134
  return new Promise((resolve) => {
32
135
  const deadline = Date.now() + timeoutMs;
33
136
  let done = false, checking = false, checkpointing = false, timer = null;
@@ -73,6 +176,9 @@ export function createYPageBinding({ store, ydoc = new Y.Doc(), onNotice = () =>
73
176
 
74
177
  return {
75
178
  ydoc, pd, provider, bridge, stats, flush, awareness,
179
+ get journal() { return pageJournal; },
180
+ filePending: () => debt.pending(),
181
+ acceptFile: (state) => debt.accepted(state),
76
182
  doc: () => keepViewPos(docOf(pd, walk(pd))),
77
183
  attach: (view) => bridge.attach(view),
78
184
  dispatch: (tr) => bridge.dispatch(tr),
@@ -92,6 +198,10 @@ export function createYPageBinding({ store, ydoc = new Y.Doc(), onNotice = () =>
92
198
  offRebase();
93
199
  offConfirm();
94
200
  unsaved?.dispose();
201
+ // Best effort, never the primary path: every append already resolved on its own
202
+ // commit, and what is left is only a write the store refused.
203
+ pageJournal?.close();
204
+ Promise.resolve(pageJournal?.channel?.flush?.()).catch(() => {});
95
205
  for (const waiter of [...waiters]) waiter.finish({ ok: false, code: "disposed" });
96
206
  bridge.dispose();
97
207
  provider.dispose();