@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
@@ -8,8 +8,8 @@
8
8
  import { contains, parseBy, frameSet, descendants, unionRanges, formatBy } from "../live/provenance.js";
9
9
  import { createStore } from "../live/store.js";
10
10
  import { serializeLive, stampOf, parseStamp, textHash } from "../live/ops.js";
11
- import { onSnapshot } from "../core/snapshot.js";
12
- import { onSaveAccepted, onSaveAttempt, onSaveOutcome, isSaveInProgress } from "../core/save-core.js";
11
+ import { capturePageState, onSnapshot } from "../core/snapshot.js";
12
+ import { onSaveAccepted, onSaveAttempt, onSaveOutcome, isSaveInProgress, saveFateIsUnknown, exitSave } from "../core/save-core.js";
13
13
  import { savePageForce, baselineSettled, isSaveConflicted, autosaveIsSuspended } from "../core/save.js";
14
14
  import { withKeepalive } from "../core/save-core.js";
15
15
  import { isEditMode } from "../core/is-edit-mode.js";
@@ -17,13 +17,13 @@ import { registerUnsavedState } from "../lib/unsaved-state.js";
17
17
  import { createCheckpoints } from "../live/checkpoint.js";
18
18
  import { parseJsonRelaxed } from "../vendor/hyper-morph-json.vendor.js";
19
19
  import * as hostMeta from "../core/host-meta.js";
20
- import { createFastLane } from "../live/fast-lane.js";
21
20
  import { createSaveHistoryRecovery, RECOVERY_WINDOW_MS } from "../live/save-history.js";
22
21
  import { serverTransport } from "../live/server-lane.js";
23
- import { localTransport, localLaneSupported } from "../live/local-lane.js";
24
22
  import { captureYValue } from "../page/ybind/parts.js";
25
23
  import { lastSeenEtag, recordEtag } from "../core/etag.js";
26
24
 
25
+ const lanes = () => Promise.all([import("../live/fast-lane.js"), import("../live/local-lane.js")]).then(([f, l]) => ({ createFastLane: f.createFastLane, localTransport: l.localTransport, localLaneSupported: l.localLaneSupported }));
26
+
27
27
  const NAME = /^[A-Za-z0-9_.:-]{1,64}$/;
28
28
  const entries = new Map();
29
29
 
@@ -755,6 +755,9 @@ function seedLocalRoom() {
755
755
  if (value !== null && typeof value === "object") values.set(name, { value, ephemeral: tag.ephemeral });
756
756
  } catch {}
757
757
  }
758
+ for (const [name, entry] of entries) {
759
+ if (!entry.staged) values.set(name, { value: entry.store.authoritative, ephemeral: entry.store.ephemeral });
760
+ }
758
761
  return values;
759
762
  }
760
763
 
@@ -769,13 +772,22 @@ function ensureLane() {
769
772
  if (request !== laneRequest) return;
770
773
  serverLane = supported;
771
774
  provenanceHost = provenance;
775
+ const lane = await lanes().catch(() => null);
776
+ if (request !== laneRequest) return;
777
+ if (!lane) {
778
+ settleLane?.(null);
779
+ settleWriter?.(null);
780
+ return;
781
+ }
772
782
  if (supported) {
773
- fast = createFastLane(laneOptions(serverTransport()));
774
- } else if (localLaneSupported()) {
783
+ fast = lane.createFastLane(laneOptions(serverTransport()));
784
+ } else if (lane.localLaneSupported()) {
775
785
  // The document's own floor picks the channel scope: a floor-1 document
776
- // never shares the lane a tab that cannot read it is on.
777
- local = localTransport({ seed: seedLocalRoom, floor: document.querySelector('meta[name="clay-page-format"]')?.getAttribute("content") });
778
- fast = createFastLane(laneOptions(local));
786
+ // never shares the lane a tab that cannot read it is on. The room scope
787
+ // puts this tab's sequencer in the host-mode room, which a tab of the lane
788
+ // before it never joins.
789
+ local = lane.localTransport({ seed: seedLocalRoom, rooms: true, floor: document.querySelector('meta[name="clay-page-format"]')?.getAttribute("content") });
790
+ fast = lane.createFastLane(laneOptions(local));
779
791
  } else {
780
792
  settleLane?.(null);
781
793
  settleWriter?.(null);
@@ -878,7 +890,9 @@ function receive(entry, text, source, dropIds) {
878
890
  } catch {
879
891
  return;
880
892
  }
881
- if (entry.store.rebase(value, { source, ...(dropIds?.length && { dropIds }) })) entry.framesSince = true;
893
+ if (!entry.store.rebase(value, { source, ...(dropIds?.length && { dropIds }) })) return false;
894
+ entry.framesSince = true;
895
+ return true;
882
896
  }
883
897
 
884
898
  // One open store and what the plugin follows about it. A staged store has no lane.
@@ -934,11 +948,12 @@ export function live(name) {
934
948
 
935
949
  // A page whose file carries no state of its own yet: the seed in memory, no tag in
936
950
  // the document, no request, until its first genuine op is written by the ordinary
937
- // save. Only an accepted write opens its room.
938
- export function stageStore(name, { value, represented = null, signal = null } = {}) {
951
+ // save. A value already stored by this tab or a peer can open its room.
952
+ export function stageStore(name, { value, represented = null, signal = null, persisted = false } = {}) {
939
953
  const existing = entries.get(name);
940
954
  if (existing && !existing.staged) throw new Error(`clay.live("${name}"): the store is open in a room`);
941
955
  const entry = existing ?? openEntry(name, createStore({ name, value, ephemeral: false }), { staged: { represented, signal } });
956
+ if (persisted) activateStaged(entry);
942
957
  return {
943
958
  handle: entry.store.handle,
944
959
  isPending: () => !!entry.staged && entry.store.pending.length > 0,
@@ -1235,11 +1250,47 @@ onSaveOutcome(({ captureId, result, attempts, sent }) => {
1235
1250
  if (plan) startHistoryRecovery(plan, { result, attempts, startedAt: Date.now() });
1236
1251
  });
1237
1252
 
1253
+ // The recovery integration's turn at an accepted save, when one journals this page.
1254
+ // Registered by page code once it holds a `page.yjs` channel, so a page that journals
1255
+ // nothing loads neither the proof builder nor the conditional client, and pays one null
1256
+ // check per save.
1257
+ let pageCheckpoints = null;
1258
+
1259
+ /**
1260
+ * Hand every accepted save to a page checkpoint integration, or null to stop.
1261
+ *
1262
+ * The integration builds the proof from the capture's pinned page state and the bytes
1263
+ * the host took; nothing here decides what is covered. Its answer is not awaited: a save
1264
+ * that succeeded is saved whether or not a journal can be retired, and a failure there
1265
+ * must never reach the save's own outcome.
1266
+ *
1267
+ * @param {?Function} install - ({capture, state, article, etag}) => unknown
1268
+ */
1269
+ export function registerPageCheckpoints(install) {
1270
+ pageCheckpoints = install;
1271
+ }
1272
+
1273
+ function offerCheckpoint(captureId, html) {
1274
+ if (!pageCheckpoints) return;
1275
+ // Read synchronously, before this capture is released: the value is the one its page
1276
+ // tag carried, and the edits that followed it are not in these bytes.
1277
+ const state = capturePageState(captureId);
1278
+ if (!state) return;
1279
+ let article = null;
1280
+ try { article = parseHtml(html).querySelector("article[data-page]")?.innerHTML ?? null; }
1281
+ catch (_) { article = null; }
1282
+ const etag = lastSeenEtag();
1283
+ Promise.resolve()
1284
+ .then(() => pageCheckpoints({ capture: String(captureId), state, article, etag }))
1285
+ .catch(() => {});
1286
+ }
1287
+
1238
1288
  // An accepted body confirms the ops of the capture that sent it, and only those.
1239
1289
  // A null context (a raw save) cannot consume a captured pin.
1240
1290
  function handleAccepted(html, context) {
1241
1291
  const captureId = context?.captureId;
1242
1292
  if (captureId === null || captureId === undefined) return;
1293
+ offerCheckpoint(captureId, html);
1243
1294
  const pin = capturePins.get(captureId);
1244
1295
  if (!pin) return;
1245
1296
  dropCapture(captureId);
@@ -1323,9 +1374,7 @@ export function receiveFrame(html, seq = null) {
1323
1374
  else if (pageTag.getAttribute("live-by") !== by) pageTag.setAttribute("live-by", by);
1324
1375
  }
1325
1376
  if (entry && !entry.store.ephemeral) {
1326
- // An old seed is never merged into a newer article before its first write.
1327
- if (entry.staged) continue;
1328
- if (entry.store.handle.status.mode === "file") receive(entry, text, "remote", dropIds);
1377
+ if (entry.store.handle.status.mode === "file" && receive(entry, text, "remote", dropIds)) activateStaged(entry);
1329
1378
  continue;
1330
1379
  }
1331
1380
  if (pageTag && pageTag.textContent !== text) pageTag.textContent = text;
@@ -1378,12 +1427,74 @@ export function fastLane() {
1378
1427
  return fast;
1379
1428
  }
1380
1429
 
1430
+ // The local lane's transport, for the format gate: a floor-1 document shares a
1431
+ // channel scope no tab below that floor is on, and moving this tab there is the
1432
+ // gate's call (page/format-gate.js). Null until this tab has such a lane, and a
1433
+ // gate asks again on its next retry.
1434
+ // Whether any live store of this tab holds ops the room has not confirmed. A tab with
1435
+ // none has nothing a scope switch could strand, so it may move without a receipt.
1436
+ export function lanePending() {
1437
+ return sessionStores().some((entry) => entry.store.pending.length > 0);
1438
+ }
1439
+
1440
+ export function localLane() {
1441
+ return local;
1442
+ }
1443
+
1381
1444
  // The page editor's pause save: checkpoint within ms of now. Only the room's
1382
1445
  // writer saves, so in other tabs this changes nothing.
1383
1446
  export function checkpointWithin(ms) {
1384
1447
  checkpoints.within(ms);
1385
1448
  }
1386
1449
 
1450
+ // The exit's own checkpoint: this tab's page input, saved once, under the permit the
1451
+ // exit minted and inside its deadline. It is not the background checkpoint: no timer is
1452
+ // armed and writer election is untouched, because a leave is an explicit save of this
1453
+ // tab's work, like the force save behind Cmd+S.
1454
+ //
1455
+ // One attempt per exit. A caller whose page input is still unconfirmed gets the answer
1456
+ // that names it, never a second write: an expired exit must not send anything later.
1457
+ export async function requestExitCheckpoint({ name, needsSave, permit, deadline, signal = null, keepalive = false }) {
1458
+ const over = () => signal?.aborted || Date.now() >= deadline;
1459
+ // Abortable, and it clears its own timer, so nothing is left waiting after the exit.
1460
+ const pause = () => new Promise((resolve) => {
1461
+ const done = () => {
1462
+ clearTimeout(timer);
1463
+ signal?.removeEventListener("abort", done);
1464
+ resolve();
1465
+ };
1466
+ const timer = setTimeout(done, 5);
1467
+ signal?.addEventListener("abort", done, { once: true });
1468
+ });
1469
+ const failed = (code) => ({ ok: false, code });
1470
+ for (;;) {
1471
+ if (over()) return failed("timeout");
1472
+ if (!needsSave()) return { ok: true };
1473
+ if (isSaveConflicted()) return failed("conflict");
1474
+ if (saveFateIsUnknown() || historyPending()) return failed("save-unknown");
1475
+ const entry = entries.get(name);
1476
+ if (!entry) return failed("disposed");
1477
+ if (!baselineSettled() || isSaveInProgress()) {
1478
+ await pause();
1479
+ continue;
1480
+ }
1481
+ // A fast capture holds the room's value, so this tab's own ops have to reach the
1482
+ // room before the capture is taken.
1483
+ if (entry.store.handle.status.mode === "fast" && entry.store.pending.length) {
1484
+ fast?.release(name);
1485
+ await pause();
1486
+ continue;
1487
+ }
1488
+ if (over()) return failed("timeout");
1489
+ const save = exitSave();
1490
+ if (!save) return failed("checkpoint-failed");
1491
+ const result = await withKeepalive(keepalive, () => save(permit));
1492
+ if (over()) return failed("timeout");
1493
+ if (!result?.ok) return failed(result?.code ?? "checkpoint-failed");
1494
+ return needsSave() ? failed("page-not-checkpointed") : { ok: true };
1495
+ }
1496
+ }
1497
+
1387
1498
  // Resolves { ok: true } once the file holds every live change this tab knows
1388
1499
  // of now, or { ok: false, code: "timeout" }. The writer saves as soon as it
1389
1500
  // can; another tab waits for the writer's save to land.
@@ -0,0 +1,196 @@
1
+ // The menu's editing items: Undo and Redo, Ask AI, and Download a copy. Each one calls
2
+ // the service that already does the job and keeps no second copy of its state.
3
+ import { modeRefusalText } from "../../core/mode-refusal.js";
4
+ import { downloadHtml, documentName } from "../../core/download.js";
5
+ import { keys, revealLabel } from "./preferences.js";
6
+
7
+ const RESUME_KEY = "menu-resume";
8
+ const READY_MS = 5000;
9
+
10
+ const clay = () => window.clay || {};
11
+ const session = () => clay().storage && clay().storage.session;
12
+ const pageWants = (name) => {
13
+ const names = clay().editPlugins;
14
+ return Array.isArray(names) && names.includes(name);
15
+ };
16
+
17
+ // An edit-only plugin attaches after the mode change commits, so the item waits for it
18
+ // briefly instead of reporting it missing.
19
+ function whenPresent(get, ms = READY_MS) {
20
+ return new Promise((resolve) => {
21
+ const start = Date.now();
22
+ const tick = () => {
23
+ const value = get();
24
+ if (value || Date.now() - start >= ms) resolve(value || null);
25
+ else setTimeout(tick, 50);
26
+ };
27
+ tick();
28
+ });
29
+ }
30
+
31
+ export function attachActions({ addItem, note, currentMode }) {
32
+ const resumers = new Map();
33
+
34
+ // Runs `run` in edit mode, entering it first when the page is in view. A reload entry
35
+ // leaves a one-shot receipt in session storage so the action runs once the page is
36
+ // back; a refused entry says why and runs nothing.
37
+ async function inEditMode(action, run) {
38
+ if (currentMode() === "edit") return run();
39
+ const storage = session();
40
+ if (storage) storage.set(RESUME_KEY, action);
41
+ let result;
42
+ try {
43
+ result = await clay().setMode?.("edit");
44
+ } catch (err) {
45
+ result = { ok: false, message: err && err.message };
46
+ }
47
+ if (result && result.ok !== false && result.reloading) return;
48
+ if (storage) storage.remove(RESUME_KEY);
49
+ if (!result || result.ok === false) {
50
+ const text = result ? modeRefusalText(result) : "Couldn't switch modes.";
51
+ if (text) note(text);
52
+ return;
53
+ }
54
+ return run();
55
+ }
56
+
57
+ function resumable(action, run) {
58
+ resumers.set(action, run);
59
+ return () => inEditMode(action, run);
60
+ }
61
+
62
+ let history = { canUndo: false, canRedo: false };
63
+ let heard = false;
64
+
65
+ function renderUndo() {
66
+ if (!pageWants("undo")) return;
67
+ const live = currentMode() === "edit" && clay().undo;
68
+ // The menu loads late, so edits made before it are read from the service itself.
69
+ if (!heard && live && typeof live.canUndo === "boolean") history = { canUndo: live.canUndo, canRedo: !!live.canRedo };
70
+ addItem({
71
+ id: "clay.undo",
72
+ pair: [
73
+ {
74
+ id: "clay.undo.undo",
75
+ label: "Undo",
76
+ keys: keys("⌘Z", "Ctrl+Z"),
77
+ disabled: !(live && history.canUndo),
78
+ run: () => clay().undo?.undo(),
79
+ },
80
+ {
81
+ id: "clay.undo.redo",
82
+ label: "Redo",
83
+ keys: keys("⌘⇧Z", "Ctrl+Shift+Z"),
84
+ disabled: !(live && history.canRedo),
85
+ run: () => clay().undo?.redo(),
86
+ },
87
+ ],
88
+ });
89
+ }
90
+
91
+ document.addEventListener("clay:undo-change", (event) => {
92
+ const detail = event.detail || {};
93
+ heard = true;
94
+ history = { canUndo: !!detail.canUndo, canRedo: !!detail.canRedo };
95
+ renderUndo();
96
+ });
97
+ document.addEventListener("clay:modechange", renderUndo);
98
+ renderUndo();
99
+
100
+ if (pageWants("ai-edit")) {
101
+ addItem({
102
+ id: "clay.ai",
103
+ label: "Ask AI…",
104
+ keys: keys("⌘J", "Ctrl+J"),
105
+ run: resumable("ai", async () => {
106
+ const ai = await whenPresent(() => clay().aiEdit);
107
+ const opened = ai ? await ai.open(document.body) : false;
108
+ if (!opened) note("Ask AI isn't available on this page right now.");
109
+ }),
110
+ });
111
+ }
112
+
113
+ if (pageWants("cms") && clay().cms?.hasRules?.(document)) {
114
+ addItem({
115
+ id: "clay.cms",
116
+ label: "Open the CMS (Edit content)",
117
+ run: resumable("cms", async () => {
118
+ const cms = await whenPresent(() => clay().cms);
119
+ let result = null;
120
+ try {
121
+ result = cms ? await cms.open() : { ok: false };
122
+ } catch {
123
+ result = { ok: false };
124
+ }
125
+ if (result && result.ok === false) note("Couldn't open the CMS. Try again.");
126
+ }),
127
+ });
128
+ }
129
+
130
+ // The copy is what a save would send now, unsaved edits included. A reader has no
131
+ // unsaved edits, so the saved file is the same document.
132
+ addItem({
133
+ id: "clay.download",
134
+ label: "Download a copy",
135
+ run: async () => {
136
+ let html = null;
137
+ try {
138
+ if (typeof clay().getHTML === "function") {
139
+ html = clay().getHTML();
140
+ } else {
141
+ const response = await fetch(location.href, { cache: "no-store", credentials: "same-origin" });
142
+ if (response.ok) html = await response.text();
143
+ }
144
+ } catch {}
145
+ // Upload paths only resolve on this host, so the copy names them in full.
146
+ if (typeof html === "string" && /^https?:$/.test(location.protocol)) {
147
+ html = html.replace(/(^|[^\w./:-])\/_\/uploads\//g, `$1${location.origin}/_/uploads/`);
148
+ }
149
+ if (typeof html !== "string" || !downloadHtml({ html, name: `${documentName()}.html` })) {
150
+ note("Couldn't build the download. Try again.");
151
+ }
152
+ },
153
+ });
154
+
155
+ // What the host offers for this document. A page opens in a new tab so this one keeps
156
+ // whatever is unsaved; anything else the host does itself when asked, and may answer
157
+ // with a page to open (a duplicate) or a reason it could not.
158
+ async function askHost(url) {
159
+ let body = null;
160
+ try {
161
+ const response = await fetch(url, { method: "POST", credentials: "same-origin", headers: { "Document-URL": location.href } });
162
+ body = await response.json().catch(() => null);
163
+ if (response.ok && body?.ok !== false) {
164
+ if (typeof body?.href === "string") window.open(body.href, "_blank", "noopener");
165
+ return;
166
+ }
167
+ } catch {}
168
+ note(typeof body?.message === "string" && body.message ? body.message : "Couldn't do that. Try again.");
169
+ }
170
+
171
+ const hostItem = (id, label, action) => {
172
+ if (action?.href) addItem({ id, label, href: action.href });
173
+ else if (action?.post) addItem({ id, label, run: () => askHost(action.post) });
174
+ };
175
+
176
+ import("../../core/host-meta.js")
177
+ .then(({ hostActions }) => hostActions())
178
+ .then((host) => {
179
+ hostItem("clay.code", "Open the code editor", host.codeEditor);
180
+ hostItem("clay.duplicate", "Duplicate as a new page", host.duplicate);
181
+ hostItem("clay.export", "Export as zip", host.export);
182
+ hostItem("clay.reveal", revealLabel(), host.reveal);
183
+ hostItem("clay.versions", "Version history…", host.versions);
184
+ })
185
+ .catch(() => {});
186
+
187
+ const storage = session();
188
+ const pending = storage && storage.get(RESUME_KEY);
189
+ if (pending) {
190
+ storage.remove(RESUME_KEY);
191
+ const run = resumers.get(pending);
192
+ if (run && currentMode() === "edit") run();
193
+ }
194
+
195
+ return { inEditMode, resumable, refresh: renderUndo };
196
+ }