@panphora/clayjs 1.15.2 → 1.17.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 (551) hide show
  1. package/README.md +4 -5
  2. package/THIRD-PARTY-NOTICES.md +16 -0
  3. package/compat/highlight-11.9.0.min.js +1213 -0
  4. package/compat/highlight-LICENSE +29 -0
  5. package/compat/journal-assets.lock.json +22 -0
  6. package/compat/overtype-2.6.1.min.js +116 -0
  7. package/compat/overtype-LICENSE +21 -0
  8. package/dist/all.js +90 -0
  9. package/dist/clay-dom.js +84 -0
  10. package/dist/clay-events.js +103 -0
  11. package/dist/clay-internals.js +85 -0
  12. package/dist/clay-options.js +93 -0
  13. package/dist/clay-router.js +98 -0
  14. package/dist/clay-ui.js +95 -0
  15. package/dist/clay-utils.js +84 -0
  16. package/dist/clay.js +146 -0
  17. package/dist/clay.standalone.js +84291 -57611
  18. package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
  19. package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
  20. package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
  21. package/dist/m/attrs/save-freeze-MYNVXREN.js +1 -0
  22. package/dist/m/c-2AZDZWCK.js +1 -0
  23. package/dist/m/c-2SPVQSHC.js +1 -0
  24. package/dist/m/c-2YYDMLNZ.js +1 -0
  25. package/dist/m/c-336SGU5W.js +1 -0
  26. package/dist/m/c-33JJ6CBX.js +2 -0
  27. package/dist/m/c-3GJ4SDE2.js +1 -0
  28. package/dist/m/c-3H6BZDNJ.js +6 -0
  29. package/dist/m/c-3OWDMNJR.js +2 -0
  30. package/dist/m/c-3RUU4TPX.js +91 -0
  31. package/dist/m/c-3XIRD4NU.js +1 -0
  32. package/dist/m/c-46XYM55L.js +1 -0
  33. package/dist/m/c-4DFEJ2PO.js +3 -0
  34. package/dist/m/c-4JZDZFF6.js +1 -0
  35. package/dist/m/c-4L3BTFRK.js +1 -0
  36. package/dist/m/c-4Q2XTQ2N.js +2 -0
  37. package/dist/m/c-4WKFKEKF.js +1 -0
  38. package/dist/m/c-56QRZTZH.js +1 -0
  39. package/dist/m/c-5J4E7O4Y.js +4 -0
  40. package/dist/m/c-5LKDWQTL.js +19 -0
  41. package/dist/m/c-5QWQ7A5L.js +1 -0
  42. package/dist/m/c-5RJFTLSF.js +1 -0
  43. package/dist/m/c-5V3MOVJT.js +1 -0
  44. package/dist/m/c-5VYOWDTI.js +1 -0
  45. package/dist/m/c-5YIHJ3TJ.js +1 -0
  46. package/dist/m/c-6AZOGAPE.js +1 -0
  47. package/dist/m/c-6CRUUXOX.js +5 -0
  48. package/dist/m/c-6HMB24EV.js +1 -0
  49. package/dist/m/c-6KEVEQ7L.js +1 -0
  50. package/dist/m/c-6NC5I2TV.js +1 -0
  51. package/dist/m/c-74MGYWZF.js +1 -0
  52. package/dist/m/c-7DJDKPGL.js +1 -0
  53. package/dist/m/c-7OLLJPWP.js +1 -0
  54. package/dist/m/c-7SWMYKAI.js +1 -0
  55. package/dist/m/c-A3E3NIMC.js +1 -0
  56. package/dist/m/c-A7LS5UZW.js +1 -0
  57. package/dist/m/c-AMSNPOCX.js +1 -0
  58. package/dist/m/c-AQLFEATB.js +1 -0
  59. package/dist/m/c-B3JM5ZXC.js +1 -0
  60. package/dist/m/c-BBVGTQG7.js +1 -0
  61. package/dist/m/c-BPF2NSPV.js +1 -0
  62. package/dist/m/c-BQ6LOBEE.js +1 -0
  63. package/dist/m/c-BTDI4Y23.js +1 -0
  64. package/dist/m/c-BWFVSBDF.js +1 -0
  65. package/dist/m/c-BYTKL6UX.js +1 -0
  66. package/dist/m/c-CFHW5AMY.js +6 -0
  67. package/dist/m/c-CJ3A7ZKE.js +1 -0
  68. package/dist/m/c-CQHPGKX2.js +1 -0
  69. package/dist/m/c-CYWLPPUS.js +1 -0
  70. package/dist/m/c-CZUSOB7V.js +1 -0
  71. package/dist/m/c-DCFSEJZG.js +1 -0
  72. package/dist/m/c-DCXOXBF3.js +1 -0
  73. package/dist/m/c-DFS4XZJV.js +2 -0
  74. package/dist/m/c-DNENWY4O.js +1 -0
  75. package/dist/m/c-DNZ5IFM6.js +1 -0
  76. package/dist/m/c-E4LIR5X5.js +1 -0
  77. package/dist/m/c-EJTGYSLN.js +1 -0
  78. package/dist/m/c-EVCQ3BKA.js +1 -0
  79. package/dist/m/c-EZU56NQV.js +34 -0
  80. package/dist/m/c-FDNHHZY5.js +1 -0
  81. package/dist/m/c-FILLTJA7.js +1 -0
  82. package/dist/m/c-FRLHSY7E.js +1 -0
  83. package/dist/m/c-FROJUZMV.js +1 -0
  84. package/dist/m/c-G2JPKCTP.js +1 -0
  85. package/dist/m/c-GB6TU7WV.js +1 -0
  86. package/dist/m/c-GFJJEBTU.js +1 -0
  87. package/dist/m/c-GJAEGGXY.js +1 -0
  88. package/dist/m/c-GOWBDM6J.js +1 -0
  89. package/dist/m/c-GQ6F6IDK.js +1 -0
  90. package/dist/m/c-GSFIGKOP.js +1 -0
  91. package/dist/m/c-GY422N3Q.js +1 -0
  92. package/dist/m/c-H4LG442Q.js +1 -0
  93. package/dist/m/c-HA6INT6J.js +1 -0
  94. package/dist/m/c-HAWFNBYG.js +1 -0
  95. package/dist/m/c-HDDEEL77.js +1 -0
  96. package/dist/m/c-HFKUVACH.js +1 -0
  97. package/dist/m/c-HHDFCNHV.js +1 -0
  98. package/dist/m/c-HPURYVZS.js +2 -0
  99. package/dist/m/c-I2ZATQCD.js +2 -0
  100. package/dist/m/c-IEM4NTCS.js +1 -0
  101. package/dist/m/c-IJUK2GPW.js +1 -0
  102. package/dist/m/c-IW6GGOU2.js +1 -0
  103. package/dist/m/c-JAX2667S.js +6 -0
  104. package/dist/m/c-JDCX26UP.js +25 -0
  105. package/dist/m/c-JRKY44DK.js +1 -0
  106. package/dist/m/c-JU3NK25H.js +1 -0
  107. package/dist/m/c-KAU6E43D.js +1 -0
  108. package/dist/m/c-KBS3UV6B.js +1 -0
  109. package/dist/m/c-KGVGROZT.js +1 -0
  110. package/dist/m/c-KI7M4AY3.js +1 -0
  111. package/dist/m/c-KIPQJX4B.js +1 -0
  112. package/dist/m/c-KQ6TYJAP.js +135 -0
  113. package/dist/m/c-KU3ZFADW.js +1 -0
  114. package/dist/m/c-L4WQSC27.js +1 -0
  115. package/dist/m/c-LFPHCXY3.js +1 -0
  116. package/dist/m/c-LRPUO2MX.js +1 -0
  117. package/dist/m/c-LWLBHLVC.js +3 -0
  118. package/dist/m/c-LZJPNR2W.js +1 -0
  119. package/dist/m/c-M2L47S2X.js +7 -0
  120. package/dist/m/c-MFP2ZWD4.js +1 -0
  121. package/dist/m/c-MKSQ4Y57.js +1 -0
  122. package/dist/m/c-MTQHWQC3.js +1 -0
  123. package/dist/m/c-N326R2JD.js +1 -0
  124. package/dist/m/c-N7JPUPGC.js +1 -0
  125. package/dist/m/c-NRPJEVJS.js +1 -0
  126. package/dist/m/c-NUFFJVVJ.js +1 -0
  127. package/dist/m/c-NUHP2XDB.js +1 -0
  128. package/dist/m/c-NUHSPT5P.js +1 -0
  129. package/dist/m/c-O72HVYSA.js +1 -0
  130. package/dist/m/c-OCSZ4QGN.js +1 -0
  131. package/dist/m/c-OIBUTDXI.js +1 -0
  132. package/dist/m/c-OIDRZKAF.js +1 -0
  133. package/dist/m/c-OW5QIGG3.js +1 -0
  134. package/dist/m/c-P7KNDG34.js +1 -0
  135. package/dist/m/c-PGRCK73F.js +1 -0
  136. package/dist/m/c-PQ5LK6QE.js +993 -0
  137. package/dist/m/c-PS5ARVWR.js +9 -0
  138. package/dist/m/c-PZ4XEPUP.js +2 -0
  139. package/dist/m/c-Q622NS2Y.js +1 -0
  140. package/dist/m/c-Q7NQD65V.js +1 -0
  141. package/dist/m/c-QH63LQOS.js +1 -0
  142. package/dist/m/c-QHIWXHRO.js +1 -0
  143. package/dist/m/c-QHWJ5ZIJ.js +1 -0
  144. package/dist/m/c-QICP2YR5.js +1 -0
  145. package/dist/m/c-QJ4OM3G6.js +1 -0
  146. package/dist/m/c-QNKREOL6.js +1 -0
  147. package/dist/m/c-R3L7ZJGB.js +2 -0
  148. package/dist/m/c-R7IWVINQ.js +1 -0
  149. package/dist/m/c-RM7J7BV2.js +1 -0
  150. package/dist/m/c-RSMOODDU.js +1 -0
  151. package/dist/m/c-RVMWGXEJ.js +1 -0
  152. package/dist/m/c-RYUXUUBX.js +1 -0
  153. package/dist/m/c-S4ZEH46B.js +9 -0
  154. package/dist/m/c-S7H2DRCB.js +1 -0
  155. package/dist/m/c-S7LK4KUE.js +1 -0
  156. package/dist/m/c-SDAXDM2D.js +1 -0
  157. package/dist/m/c-SGIA4IEQ.js +188 -0
  158. package/dist/m/c-SMBZG5X4.js +1 -0
  159. package/dist/m/c-SMGU7RT3.js +235 -0
  160. package/dist/m/c-SPIORRYS.js +1 -0
  161. package/dist/m/c-SWLRT66R.js +1 -0
  162. package/dist/m/c-SZGYW5HT.js +1 -0
  163. package/dist/m/c-TCCA5BZN.js +2 -0
  164. package/dist/m/c-TCJTVAFN.js +1 -0
  165. package/dist/m/c-TJ6WOJL3.js +67 -0
  166. package/dist/m/c-TQEMVJCS.js +1 -0
  167. package/dist/m/c-TRS6TM6O.js +1 -0
  168. package/dist/m/c-TYLXX4NF.js +21 -0
  169. package/dist/m/c-U62OB4BT.js +1 -0
  170. package/dist/m/c-U6YWUAR3.js +8 -0
  171. package/dist/m/c-UDCSU5G5.js +2 -0
  172. package/dist/m/c-UDUUJPFZ.js +1 -0
  173. package/dist/m/c-UGHH4MB3.js +1 -0
  174. package/dist/m/c-UL2YUY52.js +1 -0
  175. package/dist/m/c-VAK7SG37.js +1 -0
  176. package/dist/m/c-VDSS4K3Z.js +1 -0
  177. package/dist/m/c-VE6ZMYMC.js +1 -0
  178. package/dist/m/c-VG5XIN4T.js +1 -0
  179. package/dist/m/c-VHM5XQHK.js +1 -0
  180. package/dist/m/c-VHNSBD2M.js +1 -0
  181. package/dist/m/c-VI5RMSVY.js +1 -0
  182. package/dist/m/c-VKEDWYDF.js +1 -0
  183. package/dist/m/c-VRZN56XY.js +1 -0
  184. package/dist/m/c-W5MCHEGA.js +1 -0
  185. package/dist/m/c-WDC5GVKU.js +1 -0
  186. package/dist/m/c-WN5AY3OZ.js +1 -0
  187. package/dist/m/c-WNYBLIOZ.js +1 -0
  188. package/dist/m/c-WRBKEJD4.js +1 -0
  189. package/dist/m/c-WWXIDH4O.js +28 -0
  190. package/dist/m/c-WYFDLITL.js +1 -0
  191. package/dist/m/c-XCNTK76U.js +1 -0
  192. package/dist/m/c-XMS6DPBR.js +1 -0
  193. package/dist/m/c-XN6MBKI6.js +67 -0
  194. package/dist/m/c-XPELB6E5.js +1 -0
  195. package/dist/m/c-XQXZAO7N.js +1 -0
  196. package/dist/m/c-Y5HBBIQ4.js +1 -0
  197. package/dist/m/c-ZE46QDIH.js +1 -0
  198. package/dist/m/c-ZUCOKOHT.js +1 -0
  199. package/dist/m/c-ZVPVXTJF.js +4 -0
  200. package/dist/m/c-ZVRKXKM7.js +1 -0
  201. package/dist/m/c-ZWIOJGMT.js +1 -0
  202. package/dist/m/core/admin-attrs-QHOKPJ7W.js +1 -0
  203. package/dist/m/core/autosave-HRYGUWV6.js +1 -0
  204. package/dist/m/core/conflict-notice-6MROJN2L.js +1 -0
  205. package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
  206. package/dist/m/core/host-permission-FNXVEFNE.js +1 -0
  207. package/dist/m/core/is-edit-mode-KTWA3O5N.js +1 -0
  208. package/dist/m/core/persist-JFYRHMRJ.js +3 -0
  209. package/dist/m/core/recovery-FLTCC2OC.js +1 -0
  210. package/dist/m/core/save-HQZ4S667.js +1 -0
  211. package/dist/m/core/save-core-KGQWFUNH.js +1 -0
  212. package/dist/m/core/snapshot-EGQZNT45.js +1 -0
  213. package/dist/m/core/unsaved-warning-IDJLD66H.js +1 -0
  214. package/dist/m/dom/all-APS5ZSKP.js +1 -0
  215. package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
  216. package/dist/m/events/index-RN2JNGBV.js +1 -0
  217. package/dist/m/internals/index-PVXGPUDA.js +1 -0
  218. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  219. package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
  220. package/dist/m/loader-S4I7GKXR.js +1 -0
  221. package/dist/m/manifest-4C419D67.js +1 -0
  222. package/dist/m/manifest-edit-B077F288.js +1 -0
  223. package/dist/m/manifest.json +3988 -0
  224. package/dist/m/options/options-EJMS3K24.js +1 -0
  225. package/dist/m/page/editor-NNJA4OKD.js +202 -0
  226. package/dist/m/plugins/ai-edit-QN4PMMNL.js +6 -0
  227. package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
  228. package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
  229. package/dist/m/plugins/demo-RM5IR6AO.js +1 -0
  230. package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
  231. package/dist/m/plugins/indicator-URDBBOQO.js +1 -0
  232. package/dist/m/plugins/live-HFWD4OIH.js +1 -0
  233. package/dist/m/plugins/menu-3HIEQSI6.js +138 -0
  234. package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
  235. package/dist/m/plugins/page-JOKB6XPR.js +2 -0
  236. package/dist/m/plugins/page-view-NXOJHNW6.js +1 -0
  237. package/dist/m/plugins/people-SGPJKANK.js +1 -0
  238. package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
  239. package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
  240. package/dist/m/plugins/richclay-NRQDSWX6.js +517 -0
  241. package/dist/m/plugins/richclay-NRQDSWX6.js.LEGAL.txt +1 -0
  242. package/dist/m/plugins/sortable-GWT4PWDV.js +1 -0
  243. package/dist/m/plugins/source-4ZRSAIYM.js +1 -0
  244. package/dist/m/plugins/undo-VDA5P7FH.js +1 -0
  245. package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
  246. package/dist/m/plugins/upgrade-W7FG5VFG.js +1 -0
  247. package/dist/m/plugins/upload-WLYKK6TL.js +1 -0
  248. package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +21 -0
  249. package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +1 -0
  250. package/dist/m/plugins/wire-4N6GYAF6.js +1 -0
  251. package/dist/m/reader-core-DXYCURFF.js +68 -0
  252. package/dist/m/route/route-KOCZSEGN.js +1 -0
  253. package/dist/m/sync/live-sync-J7VMYTKS.js +1 -0
  254. package/dist/m/ui/index-3JLVI27O.js +1 -0
  255. package/dist/m/utils/index-4TJCVLT7.js +1 -0
  256. package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
  257. package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
  258. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
  259. package/dist/offline-manifest.json +4034 -0
  260. package/dist/page/themes/light-1.css +430 -0
  261. package/dist/server/core.cjs +23748 -22855
  262. package/dist/server/offline-runtime.cjs +4954 -0
  263. package/dist/server/offline-runtime.mjs +4923 -0
  264. package/dist/server/rooms.mjs +9 -0
  265. package/docs/guide.md +1078 -669
  266. package/entries/all.js +2 -0
  267. package/entries/clay-data.js +1 -1
  268. package/entries/clay-dom.js +2 -0
  269. package/entries/clay-events.js +2 -0
  270. package/entries/clay-internals.js +2 -0
  271. package/entries/clay-options.js +2 -0
  272. package/entries/clay-router.js +2 -0
  273. package/entries/clay-ui.js +2 -0
  274. package/entries/clay-utils.js +2 -0
  275. package/entries/clay.js +2 -0
  276. package/entries/sap.js +6 -6
  277. package/fonts/ibm-plex-mono-LICENSE +93 -0
  278. package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
  279. package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
  280. package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  281. package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  282. package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  283. package/fonts/ibm-plex-sans-LICENSE +93 -0
  284. package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  285. package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  286. package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  287. package/fonts/ibm-plex.css +71 -0
  288. package/fonts/inter-LICENSE +93 -0
  289. package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
  290. package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
  291. package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
  292. package/fonts/inter-greek-wght-normal.woff2 +0 -0
  293. package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  294. package/fonts/inter-latin-wght-normal.woff2 +0 -0
  295. package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
  296. package/fonts/inter.css +62 -0
  297. package/fonts/newsreader-LICENSE +93 -0
  298. package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
  299. package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  300. package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
  301. package/fonts/newsreader.css +26 -0
  302. package/fonts/work-sans-LICENSE +93 -0
  303. package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
  304. package/fonts/work-sans.css +8 -0
  305. package/package.json +20 -3
  306. package/packed-contract.json +1 -0
  307. package/src/attrs/add-from.js +27 -4
  308. package/src/attrs/save-freeze.js +7 -52
  309. package/src/core/admin-attrs.js +119 -12
  310. package/src/core/admin-contenteditable.js +12 -10
  311. package/src/core/admin-inputs.js +14 -15
  312. package/src/core/admin-onclick.js +2 -0
  313. package/src/core/autosave.js +74 -21
  314. package/src/core/boot-phase.js +2 -1
  315. package/src/core/conflict-download.js +19 -23
  316. package/src/core/conflict-notice-ui.js +1173 -0
  317. package/src/core/conflict-notice.js +140 -697
  318. package/src/core/download.js +32 -0
  319. package/src/core/edit-mode.js +147 -14
  320. package/src/core/etag.js +30 -12
  321. package/src/core/host-meta.js +90 -8
  322. package/src/core/host-permission.js +29 -0
  323. package/src/core/is-edit-mode.js +145 -12
  324. package/src/core/mode-key.js +125 -0
  325. package/src/core/mode-lifecycle.js +544 -0
  326. package/src/core/mode-receipt.js +30 -0
  327. package/src/core/mode-refusal.js +13 -0
  328. package/src/core/mode-resources.js +52 -0
  329. package/src/core/mode-support.js +82 -0
  330. package/src/core/mode-switch.js +259 -0
  331. package/src/core/mode-transition.js +112 -0
  332. package/src/core/page-data-write.js +305 -0
  333. package/src/core/page-data.js +26 -227
  334. package/src/core/page-format-wire.js +4 -0
  335. package/src/core/page-format.js +58 -0
  336. package/src/core/persist.js +37 -9
  337. package/src/core/preserve-authored.js +114 -0
  338. package/src/core/recovery.js +772 -0
  339. package/src/core/runtime-url.js +105 -0
  340. package/src/core/save-core.js +113 -12
  341. package/src/core/save-replace.js +72 -0
  342. package/src/core/save-size-notice.js +134 -0
  343. package/src/core/save-size.js +140 -0
  344. package/src/core/save.js +806 -119
  345. package/src/core/snapshot.js +159 -11
  346. package/src/core/source-map.js +157 -9
  347. package/src/core/unsaved-warning.js +14 -4
  348. package/src/core/upgrade-run.js +408 -0
  349. package/src/core/version.js +18 -0
  350. package/src/lib/assets.js +30 -0
  351. package/src/lib/autosave-debug-gate.js +50 -0
  352. package/src/lib/autosave-scheduler.js +140 -0
  353. package/src/lib/boot-imports.js +8 -0
  354. package/src/lib/derived.js +3 -1
  355. package/src/lib/dirty-gate.js +15 -0
  356. package/src/lib/doc-storage.js +33 -0
  357. package/src/lib/effect.js +9 -4
  358. package/src/lib/mutation.js +4 -2
  359. package/src/lib/offline-runtime.js +226 -0
  360. package/src/lib/preload.js +48 -0
  361. package/src/lib/root-attrs.js +1 -1
  362. package/src/lib/trash.js +18 -0
  363. package/src/lib/undo/capture-move.js +152 -0
  364. package/src/live/local-channel.js +61 -0
  365. package/src/live/local-lane.js +60 -9
  366. package/src/live/server-lane.js +6 -3
  367. package/src/loader-logic.js +76 -15
  368. package/src/loader.js +218 -30
  369. package/src/page/adopt-boot.js +76 -22
  370. package/src/page/adopt.js +25 -4
  371. package/src/page/awareness.js +230 -0
  372. package/src/page/blocks.js +113 -6
  373. package/src/page/blocksel.js +20 -4
  374. package/src/page/board-face.js +13 -0
  375. package/src/page/board-panel.js +5 -17
  376. package/src/page/board-style.js +2 -13
  377. package/src/page/board-view.js +1 -11
  378. package/src/page/board-views.js +3 -0
  379. package/src/page/board.js +2 -2
  380. package/src/page/boot-pack.js +26 -0
  381. package/src/page/boot.js +237 -0
  382. package/src/page/chart-tables.js +11 -0
  383. package/src/page/chart.js +6 -10
  384. package/src/page/checklist.js +1 -1
  385. package/src/page/checkpoint-proof.js +289 -0
  386. package/src/page/chrome.js +431 -63
  387. package/src/page/csv-import.js +17 -2
  388. package/src/page/document-title-dom.js +52 -0
  389. package/src/page/document-title-runtime.js +90 -0
  390. package/src/page/document-title.js +12 -0
  391. package/src/page/drag.js +48 -13
  392. package/src/page/drop.js +2 -1
  393. package/src/page/editor.js +399 -90
  394. package/src/page/emoji-data.js +162 -0
  395. package/src/page/feature-loader.js +72 -0
  396. package/src/page/features/board.js +21 -0
  397. package/src/page/features/chart.js +11 -0
  398. package/src/page/features/code.js +13 -0
  399. package/src/page/features/dates.js +180 -0
  400. package/src/page/features/emoji.js +187 -0
  401. package/src/page/features/media.js +290 -0
  402. package/src/page/features/mentions.js +188 -0
  403. package/src/page/features/registry.js +28 -0
  404. package/src/page/features/table.js +383 -0
  405. package/src/page/{tool-view.js → features/tool.js} +33 -2
  406. package/src/page/format-codec.js +215 -0
  407. package/src/page/format-command.js +9 -0
  408. package/src/page/format-gate.js +179 -0
  409. package/src/page/format-host.js +54 -0
  410. package/src/page/format-level.js +157 -0
  411. package/src/page/format-native-commands.js +142 -0
  412. package/src/page/format-native.js +42 -0
  413. package/src/page/highlight-slot.js +23 -0
  414. package/src/page/highlight.js +30 -16
  415. package/src/page/history-intent.js +78 -0
  416. package/src/page/inline.js +45 -28
  417. package/src/page/items.js +67 -14
  418. package/src/page/link.js +1 -0
  419. package/src/page/markdown.js +112 -3
  420. package/src/page/media-views.js +3 -0
  421. package/src/page/media.js +33 -245
  422. package/src/page/mention-picker.js +99 -0
  423. package/src/page/merge.js +1 -1
  424. package/src/page/outline-cache.js +11 -0
  425. package/src/page/placeholder.js +27 -7
  426. package/src/page/projection.js +240 -51
  427. package/src/page/reader-patch.js +306 -0
  428. package/src/page/recover.js +1 -1
  429. package/src/page/rich/page-tool.js +1 -4
  430. package/src/page/runtime-owner.js +27 -0
  431. package/src/page/save-projection.js +46 -4
  432. package/src/page/save-states.js +1 -1
  433. package/src/page/schema.js +47 -0
  434. package/src/page/table-rect-key.js +3 -0
  435. package/src/page/table-select.js +4 -3
  436. package/src/page/table-style.js +1 -1
  437. package/src/page/table-views.js +3 -0
  438. package/src/page/table.js +8 -293
  439. package/src/page/themes/content.css +25 -1
  440. package/src/page/themes/light-1.css +163 -2
  441. package/src/page/themes/light-1.source.css +269 -0
  442. package/src/page/tool-capability.js +3 -0
  443. package/src/page/tool-views.js +3 -0
  444. package/src/page/view-hooks.js +32 -0
  445. package/src/page/view.js +390 -107
  446. package/src/page/ybind/awareness.js +172 -0
  447. package/src/page/ybind/binding.js +40 -5
  448. package/src/page/ybind/bridge.js +50 -47
  449. package/src/page/ybind/decode.js +52 -19
  450. package/src/page/ybind/doc.js +27 -2
  451. package/src/page/ybind/group.js +12 -3
  452. package/src/page/ybind/index.js +2 -0
  453. package/src/page/ybind/lineage.js +71 -0
  454. package/src/page/ybind/marks.js +26 -1
  455. package/src/page/ybind/migrate.js +48 -13
  456. package/src/page/ybind/moves.js +32 -21
  457. package/src/page/ybind/positions.js +50 -0
  458. package/src/page/ybind/provider.js +97 -24
  459. package/src/page/ybind/recovery-proof.js +191 -0
  460. package/src/page/ybind/recovery-replay.js +488 -0
  461. package/src/page/ybind/tokens.js +15 -6
  462. package/src/page/ybind/walk.js +10 -3
  463. package/src/page/ybind/writer.js +48 -4
  464. package/src/plugins/ai-edit.js +167 -32
  465. package/src/plugins/bind-text.js +218 -0
  466. package/src/plugins/cms.js +93 -0
  467. package/src/plugins/indicator.js +57 -5
  468. package/src/plugins/live.js +319 -35
  469. package/src/plugins/menu/claygoo.svg.js +1 -0
  470. package/src/plugins/menu/seo-head.js +323 -0
  471. package/src/plugins/menu/seo.js +849 -0
  472. package/src/plugins/menu.js +425 -0
  473. package/src/plugins/page-view.js +131 -0
  474. package/src/plugins/page.js +1131 -270
  475. package/src/plugins/people.js +158 -31
  476. package/src/plugins/richclay.js +119 -9
  477. package/src/plugins/sortable.js +370 -60
  478. package/src/plugins/source.js +189 -366
  479. package/src/plugins/undo.js +289 -19
  480. package/src/plugins/unsent.js +132 -0
  481. package/src/plugins/upgrade-run.js +1 -0
  482. package/src/plugins/upgrade.js +210 -0
  483. package/src/plugins/upload-task.js +571 -0
  484. package/src/plugins/upload-ui.js +265 -0
  485. package/src/plugins/upload.js +34 -153
  486. package/src/plugins/visitor-drafts/inert.js +32 -0
  487. package/src/plugins/visitor-drafts/intro.js +2 -1
  488. package/src/plugins/visitor-drafts/owner-chip.js +3 -0
  489. package/src/plugins/visitor-drafts/prompt.js +2 -2
  490. package/src/plugins/visitor-drafts-owner.js +85 -0
  491. package/src/plugins/visitor-drafts.js +31 -42
  492. package/src/plugins/wire.js +82 -1
  493. package/src/reader-core.js +26 -0
  494. package/src/recovery/conflicts.js +287 -0
  495. package/src/recovery/identity.js +154 -0
  496. package/src/recovery/store.js +792 -0
  497. package/src/recovery/undo.js +257 -0
  498. package/src/server/checkpoint/index.js +68 -4
  499. package/src/server/offline-runtime/index.js +214 -0
  500. package/src/server/rooms/index.js +2 -1
  501. package/src/server/rooms/live-data-rooms.js +2 -2
  502. package/src/server/rooms/page-admission.js +33 -0
  503. package/src/server/rooms/page-lineage.js +56 -0
  504. package/src/server/rooms/page-rebase.js +11 -5
  505. package/src/server/rooms/page-state.js +2 -0
  506. package/src/server/rooms/page-write-admission.js +9 -3
  507. package/src/source/session.js +705 -0
  508. package/src/sync/conflict-revert.js +5 -4
  509. package/src/sync/conflicts-edit.js +119 -0
  510. package/src/sync/conflicts-facade.js +49 -0
  511. package/src/sync/conflicts.js +99 -1
  512. package/src/sync/editing-marker.js +37 -6
  513. package/src/sync/editing-sender.js +8 -1
  514. package/src/sync/live-sync-edit.js +63 -0
  515. package/src/sync/live-sync.js +364 -119
  516. package/src/sync/local-presence.js +187 -0
  517. package/src/sync/merge-declarations.js +10 -0
  518. package/src/sync/merge-engine.js +12 -0
  519. package/src/sync/presence-store.js +412 -0
  520. package/src/sync/presence-target.js +243 -0
  521. package/src/sync/presence.js +72 -41
  522. package/src/sync/saved-handoff.js +200 -0
  523. package/src/sync/saved-stream.js +544 -0
  524. package/src/sync/version-guard.js +92 -0
  525. package/src/sync/wire-profile.js +81 -0
  526. package/src/ui/bevel-controls.js +2 -8
  527. package/src/ui/page-scheme.js +7 -0
  528. package/src/ui/skins/cms.js +1 -1
  529. package/src/ui/theme-config.js +150 -0
  530. package/src/ui/theme.js +6 -138
  531. package/src/ui/toast-lazy.js +11 -0
  532. package/src/vendor/LICENSES-optional.md +253 -0
  533. package/src/vendor/highlight.vendor.js +3 -0
  534. package/src/vendor/hljs-bash.vendor.js +2 -0
  535. package/src/vendor/hljs-css.vendor.js +2 -0
  536. package/src/vendor/hljs-javascript.vendor.js +2 -0
  537. package/src/vendor/hljs-json.vendor.js +2 -0
  538. package/src/vendor/hljs-python.vendor.js +2 -0
  539. package/src/vendor/hljs-xml.vendor.js +2 -0
  540. package/src/vendor/hyper-html-api-data.vendor.js +1 -1
  541. package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
  542. package/src/vendor/hyper-morph-identity.vendor.js +2 -0
  543. package/src/vendor/hyper-morph-json.vendor.js +3 -0
  544. package/src/vendor/hyper-morph.vendor.js +6 -6
  545. package/src/vendor/hyper-undo.vendor.js +1 -1
  546. package/src/vendor/hypercms.vendor.js +169 -84
  547. package/src/vendor/marked.vendor.js +68 -0
  548. package/src/vendor/overtype.vendor.js +994 -0
  549. package/src/vendor/richclay.vendor.js +17 -17
  550. package/src/vendor/sap-core.vendor.js +5 -5
  551. package/src/vendor/sap-ownership.vendor.js +11 -7
@@ -4,25 +4,75 @@
4
4
  // flag.
5
5
 
6
6
  import { undo } from '../vendor/hyper-undo.vendor.js'
7
- import { isEditMode } from '../core/is-edit-mode.js'
7
+ import { isEditMode, startsAtImport } from '../core/is-edit-mode.js'
8
8
  import { isTabLocalAttr } from '../lib/root-attrs.js'
9
9
  import { isDerivedAttr } from '../lib/derived.js'
10
10
  import { PERSIST } from '../lib/attr-aliases.js'
11
+ import { isGestureItem, isRuntimeAttribute } from '../lib/undo/capture-move.js'
12
+ import { gateMarkDirty } from '../lib/dirty-gate.js'
11
13
 
12
14
  // Tab-local attributes (ephemeral state, route-active, root library state) are
13
- // never undone. The shared mutation hub already drops them for the main
14
- // instance; a scope made with create() runs its own observer, so it is told too.
15
+ // never undone, and neither is what a drag's vendor writes on the item it holds:
16
+ // `draggable` and `style` are the gesture's own bookkeeping, and a click that
17
+ // writes them is not an edit. The shared mutation hub already drops tab-local
18
+ // attributes for the main instance; a scope made with create() runs its own
19
+ // observer, so it is told too.
20
+ // The page's own maintenance attributes, kept outside history by name (a card number a
21
+ // repair rewrites, for example): saved like any attribute, never an edit to undo.
22
+ const pageIgnored = new Set()
15
23
  const withTabLocal = (opts = {}) => {
16
24
  const own = opts.ignoreAttribute
17
25
  return {
18
26
  ...opts,
19
- ignoreAttribute: (name, target) => isTabLocalAttr(name, target) || isDerivedAttr(target, name) || (own ? own(name, target) : false),
27
+ ignoreAttribute: (name, target, host) => pageIgnored.has(name) || isTabLocalAttr(name, target) || isDerivedAttr(target, name, host) || isRuntimeAttribute(name, target) || (own ? own(name, target, host) : false),
28
+ isProtected: opts.isProtected || keepsOwnHistory,
29
+ ignoreNode: opts.ignoreNode || isGestureItem,
20
30
  }
21
31
  }
22
32
 
33
+ // Saved content that undo does not record (a richclay region, a no-undo block) has a
34
+ // history undo cannot restore, so a parent's undo or redo must not erase it. Chrome
35
+ // that is never saved (editor-ui) loses nothing and is not protected.
36
+ function keepsOwnHistory(node) {
37
+ const policy = window.clay?.region?.resolveRegionPolicy
38
+ if (!policy || node.nodeType !== 1) return false
39
+ const own = policy(node)
40
+ if (own.undoable !== false || own.persist === 'none') return false
41
+ const parent = node.parentElement
42
+ return !parent || policy(parent).undoable !== false
43
+ }
44
+
23
45
  const createScope = undo.create.bind(undo)
24
46
  undo.create = (opts) => createScope(withTabLocal(opts))
25
47
 
48
+ // The recording mode is the page's choice, and a session starts a new scope over the
49
+ // page as it stands: remember it here, so an exit and a re-entry come back in the mode
50
+ // the page picked instead of the default one.
51
+ let recording = null
52
+ const setRecording = undo.setRecording.bind(undo)
53
+ undo.setRecording = (mode) => {
54
+ recording = mode ?? null
55
+ return setRecording(mode)
56
+ }
57
+
58
+ // An authored history action is a user edit, and it writes the DOM while a sync
59
+ // frame's apply may hold both the mutation hub and the gate paused: neither feed
60
+ // records it then, and the apply would take the write into its saved baseline.
61
+ // So every commit, command, undo and redo that changed the document marks the
62
+ // page dirty at the gate's own entry point, whose count is what the capture token
63
+ // is compared against — an explicit mark advances it even while the gate is paused.
64
+ const lastEntryId = () => undo.history.at(-1)?.id ?? null
65
+
66
+ const command = undo.command.bind(undo)
67
+ undo.command = (label, fn) => {
68
+ const receipt = command(label, fn)
69
+ if (receipt) gateMarkDirty()
70
+ return receipt
71
+ }
72
+
73
+ // Undo and redo mark it in afterReplay, which the engine calls on every path that
74
+ // replays a step: the facade and the keyboard shortcut bound to the engine itself.
75
+
26
76
  // A commit inside clay.effect drains the paused hub's records, so the effect's
27
77
  // own writes reach autosave and re-trigger it. Warn once; the commit still runs.
28
78
  const commit = undo.commit.bind(undo)
@@ -32,7 +82,10 @@ undo.commit = (label, fn) => {
32
82
  warnedEffectCommit = true
33
83
  console.warn(`clay.effect: undo.commit("${label}") was called inside an effect. Run commands from event handlers, not from an effect.`)
34
84
  }
35
- return commit(label, fn)
85
+ const before = lastEntryId()
86
+ const receipt = commit(label, fn)
87
+ if (receipt || lastEntryId() !== before) gateMarkDirty()
88
+ return receipt
36
89
  }
37
90
 
38
91
  // Undo and redo restore attributes. A control the user has touched shows its
@@ -74,23 +127,240 @@ function restoreControls() {
74
127
  })
75
128
  }
76
129
 
77
- function init() {
78
- if (!isEditMode) return
79
- // Defer until the body exists so the default scope (document.body) is valid.
80
- if (typeof document === 'undefined') return
81
- const start = () => {
82
- undo.start(withTabLocal({ bindKeys: true }))
83
- undo.on('undo', restoreControls)
84
- undo.on('redo', restoreControls)
130
+ // The engine emits its receipt only after a whole replay applied, so a refused
131
+ // undo or redo never reaches the control restore.
132
+ const afterReplay = (receipt) => {
133
+ if (!receipt?.ok) return
134
+ gateMarkDirty()
135
+ restoreControls()
136
+ }
137
+
138
+ // A refusal is announced for whoever presents it: the engine's receipt, once.
139
+ const refuse = ({ entryId, label, direction, code }) => {
140
+ document.dispatchEvent(new CustomEvent('clay:undo-refused', {
141
+ detail: { ok: false, code, entryId, label, direction },
142
+ }))
143
+ keepRefused({ ok: false, code, entryId, label, direction })
144
+ }
145
+
146
+ // A refused step is kept as recovery before a reload can take it away: the refusal is
147
+ // announced synchronously above and the copy is made behind it, so a store that will
148
+ // not answer can never hold up the receipt. The module is fetched on the first refusal
149
+ // or the first app keep; no reader and no boot pays for it, and nothing it writes is a
150
+ // save.
151
+ let recoveryLoad = null
152
+
153
+ function loadRecovery() {
154
+ recoveryLoad ??= import('../recovery/undo.js').catch((error) => {
155
+ recoveryLoad = null
156
+ console.error('clayjs: the undo recovery module could not be downloaded; a refused step stays in this tab only', error)
157
+ return null
158
+ })
159
+ return recoveryLoad
160
+ }
161
+
162
+ async function keepRefused(receipt) {
163
+ let written = { ok: false, code: 'unavailable' }
164
+ try {
165
+ const module = await loadRecovery()
166
+ if (module) written = await module.keepRefusal(undo, receipt)
167
+ } catch (_) {
168
+ written = { ok: false, code: 'error' }
85
169
  }
86
- if (document.readyState === 'loading') {
87
- document.addEventListener('DOMContentLoaded', start)
88
- } else {
89
- start()
170
+ const detail = written?.ok === true
171
+ ? { ok: true, entryId: receipt.entryId, direction: receipt.direction }
172
+ : { ok: false, code: written?.code || 'error', entryId: receipt.entryId, direction: receipt.direction }
173
+ document.dispatchEvent(new CustomEvent('clay:undo-kept', { detail }))
174
+ }
175
+
176
+ // The page's own records on the same channel and in the same store: `keep` stages one,
177
+ // `kept` is what a later session can review, and `dismissKept` is the explicit,
178
+ // revision-checked way one leaves. All three fetch the recovery module on demand.
179
+ const withRecovery = (call, fallback) => (...args) =>
180
+ loadRecovery().then(
181
+ (module) => (module ? module[call](...args) : fallback),
182
+ () => fallback
183
+ )
184
+
185
+ // An app's kept copy is announced like a refused step's, so the review notice shows it
186
+ // in this session instead of only after a reload.
187
+ const keepApp = withRecovery('keep', { ok: false, code: 'unavailable' })
188
+ undo.ignoreAttribute = (name) => {
189
+ const key = String(name || '')
190
+ if (!key) return () => {}
191
+ pageIgnored.add(key)
192
+ return () => { pageIgnored.delete(key) }
193
+ }
194
+
195
+ undo.keep = async (spec) => {
196
+ const written = await keepApp(spec)
197
+ if (written?.ok === true) {
198
+ document.dispatchEvent(new CustomEvent('clay:undo-kept', { detail: { ok: true, key: spec?.key == null ? null : `app:${spec.key}` } }))
90
199
  }
200
+ return written
201
+ }
202
+ undo.kept = withRecovery('kept', { ok: false, code: 'unavailable', records: [] })
203
+ undo.dismissKept = withRecovery('dismissKept', { ok: false, code: 'unavailable' })
204
+
205
+ // The page's own actions on a kept copy. A provider is asked about every record the
206
+ // review panel draws, and each action it returns becomes a button on that row, so a
207
+ // template offers Keep as new without the panel knowing what a card is. A provider
208
+ // that throws costs its own buttons only, never the row or the copy.
209
+ const reviewProviders = new Set()
210
+
211
+ undo.reviewActions = (fn) => {
212
+ if (typeof fn !== 'function') return () => {}
213
+ reviewProviders.add(fn)
214
+ return () => { reviewProviders.delete(fn) }
215
+ }
216
+
217
+ undo.reviewActionsFor = (record) => {
218
+ const actions = []
219
+ for (const fn of reviewProviders) {
220
+ let offered
221
+ try {
222
+ offered = fn(record)
223
+ } catch (error) {
224
+ console.error('clayjs: a review action could not describe itself; the copy stays reviewable', error)
225
+ continue
226
+ }
227
+ for (const item of Array.isArray(offered) ? offered : []) {
228
+ if (item && typeof item.run === 'function' && item.label) actions.push(item)
229
+ }
230
+ }
231
+ return actions
232
+ }
233
+
234
+ // One event for whatever presents the history (Undo and Redo buttons, a refusal banner):
235
+ // after every entry, replay, refusal, dismissal and clear, and when a session starts or
236
+ // ends. `undo` and `redo` are the entries the next press would replay, with `refused`
237
+ // set on the one that was just refused.
238
+ const HISTORY_EVENTS = ['commit', 'undo', 'redo', 'refused', 'discard', 'clear']
239
+ const announceChange = () => {
240
+ const live = !!session?.started
241
+ document.dispatchEvent(new CustomEvent('clay:undo-change', {
242
+ detail: {
243
+ canUndo: live && undo.canUndo,
244
+ canRedo: live && undo.canRedo,
245
+ undo: (live && undo.history.at(-1)) || null,
246
+ redo: (live && undo.future?.[0]) || null,
247
+ },
248
+ }))
249
+ }
250
+
251
+ // One edit session owns the page's DOM history. A session starts its own over the page as
252
+ // it stands, so nothing an earlier session recorded, and nothing a reader painted while the
253
+ // page was in view, is this session's to undo. The durable recovery records belong to the
254
+ // recovery service and outlive every session; nothing here reads, clears or rewrites them.
255
+ let session = null
256
+
257
+ function ownership() {
258
+ return { started: false, admitted: false, subscriptions: [] }
259
+ }
260
+
261
+ const isStale = (context) => {
262
+ if (!context) return false
263
+ if (context.signal?.aborted) return true
264
+ return typeof context.isCurrent === 'function' && !context.isCurrent()
265
+ }
266
+
267
+ // The default scope is document.body, so a boot that is still parsing waits for the body
268
+ // to exist before the session starts.
269
+ function bodyReady() {
270
+ if (typeof document === 'undefined') return Promise.resolve()
271
+ if (document.readyState !== 'loading') return Promise.resolve()
272
+ return new Promise((resolve) => document.addEventListener('DOMContentLoaded', resolve, { once: true }))
273
+ }
274
+
275
+ // One edit session over the live page. `enable` starts a fresh history and the control
276
+ // restore that follows it; `quiesce` closes recording so the exit barrier's writes are not
277
+ // entries, keeping the history itself; `resume` is the refused exit; `dispose` detaches
278
+ // exactly the subscriptions this session added and takes the history and its keydown
279
+ // binding back, so a later entry starts from nothing.
280
+ function undoLease(record) {
281
+ return {
282
+ enable() {
283
+ if (record.started) return
284
+ record.started = true
285
+ record.admitted = true
286
+ undo.start(withTabLocal({ bindKeys: true, ...(recording ? { recording } : {}) }))
287
+ for (const type of ['undo', 'redo']) {
288
+ undo.on(type, afterReplay)
289
+ record.subscriptions.push({ type, handler: afterReplay })
290
+ }
291
+ undo.on('refused', refuse)
292
+ record.subscriptions.push({ type: 'refused', handler: refuse })
293
+ for (const type of HISTORY_EVENTS) {
294
+ undo.on(type, announceChange)
295
+ record.subscriptions.push({ type, handler: announceChange })
296
+ }
297
+ announceChange()
298
+ },
299
+ quiesce() {
300
+ if (!record.admitted) return
301
+ record.admitted = false
302
+ undo.pause()
303
+ },
304
+ resume() {
305
+ if (record.admitted) return
306
+ record.admitted = true
307
+ undo.resume()
308
+ },
309
+ dispose() {
310
+ record.admitted = false
311
+ if (session === record) session = null
312
+ if (!record.started) return
313
+ record.started = false
314
+ for (const { type, handler } of record.subscriptions.splice(0)) undo.off(type, handler)
315
+ undo.stop()
316
+ announceChange()
317
+ },
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Undo as a mode participant: the history the commit that enters edit starts, and the one
323
+ * the exit takes back.
324
+ *
325
+ * `prepare` waits for the body the default scope needs, and answers with the session's
326
+ * lease; that wait can outlive the session that asked for it, so the generation is asked
327
+ * again once the body exists. `enable` starts the history. `quiesce` stops recording
328
+ * without losing what the session already holds. `dispose` hands back the subscriptions,
329
+ * the history and the keydown binding.
330
+ */
331
+ export function undoParticipant() {
332
+ return {
333
+ id: 'clay:undo',
334
+ order: 350,
335
+ async prepare(context = {}) {
336
+ if (context.initial === true) return adopt()
337
+ await bodyReady()
338
+ if (isStale(context)) {
339
+ throw Object.assign(new Error('clay:undo prepared for a stale generation'), { code: 'stale-document' })
340
+ }
341
+ return undoLease(session || (session = ownership()))
342
+ },
343
+ }
344
+ }
345
+
346
+ // The boot this tab already made, adopted instead of prepared: it is the history `init`
347
+ // below started, or, on a boot that started none, nothing to hand back.
348
+ async function adopt() {
349
+ const lease = await ready
350
+ return { enable() {}, quiesce: () => lease?.quiesce(), resume: () => lease?.resume(), dispose: () => lease?.dispose() }
351
+ }
352
+
353
+ // Auto-init when the module is imported; the loader awaits `ready` before resolving clay.ready
354
+ async function init() {
355
+ if (!startsAtImport()) return null
356
+ if (typeof document === 'undefined') return null
357
+ const lease = await undoParticipant().prepare()
358
+ lease.enable()
359
+ return lease
91
360
  }
92
361
 
93
- init()
362
+ const ready = init()
94
363
 
95
- export { undo, restoreControls }
364
+ export { undo, restoreControls, init, ready }
365
+ export { undoParticipant as modeParticipant }
96
366
  export default undo
@@ -0,0 +1,132 @@
1
+ // Text typed into chrome and not yet sent (a comment box, a description being edited),
2
+ // kept per record and name, outside the file. A redraw or a reload brings it back.
3
+ //
4
+ // The record a field belongs to is named by an `unsent-key` attribute on its nearest
5
+ // such ancestor, and by the nearest `[data-id]` ancestor when there is none: a view
6
+ // with two draft fields under one record names each scope it wants kept apart. The
7
+ // name is the field's own `unsent` attribute. A draft is pending while the field's
8
+ // value differs from the base it started at, an emptied field included.
9
+ import { registerUnsavedState } from "../lib/unsaved-state.js";
10
+ import { storage } from "../lib/doc-storage.js";
11
+
12
+ const KEY = "unsent";
13
+ const drafts = new Map(Object.entries(storage.session.get(KEY) || {}));
14
+ const bases = new WeakMap();
15
+ const pending = registerUnsavedState({ id: "unsent", isPending: () => drafts.size > 0 });
16
+
17
+ const keyOf = (el) => {
18
+ const scope = el.closest("[unsent-key]");
19
+ const record = (scope && scope.getAttribute("unsent-key")) || el.closest("[data-id]")?.dataset.id;
20
+ const name = el.getAttribute("unsent");
21
+ return record && name ? `${record}|${name}` : null;
22
+ };
23
+ const split = (key) => { const at = key.indexOf("|"); return [key.slice(0, at), key.slice(at + 1)]; };
24
+
25
+ function persist() {
26
+ storage.session.set(KEY, Object.fromEntries(drafts));
27
+ pending.changed();
28
+ document.dispatchEvent(new CustomEvent("clay:unsent-changed", { detail: { count: drafts.size } }));
29
+ }
30
+
31
+ function onBeforeInput(e) {
32
+ const el = e.target.closest?.("[unsent]");
33
+ if (el && !bases.has(el)) bases.set(el, el.value);
34
+ }
35
+
36
+ function onInput(e) {
37
+ const el = e.target.closest?.("[unsent]");
38
+ const key = el && keyOf(el);
39
+ if (!key) return;
40
+ const base = bases.get(el) ?? drafts.get(key)?.base ?? "";
41
+ if (el.value === base) {
42
+ drafts.delete(key);
43
+ bases.delete(el);
44
+ } else {
45
+ drafts.set(key, { base, text: el.value });
46
+ }
47
+ persist();
48
+ }
49
+
50
+ function restore(el) {
51
+ const key = keyOf(el);
52
+ const draft = key && drafts.get(key);
53
+ if (!draft) return;
54
+ const current = el.value;
55
+ bases.set(el, draft.base);
56
+ el.value = draft.text;
57
+ el.dispatchEvent(new CustomEvent("clay:unsent-restored", {
58
+ bubbles: true,
59
+ detail: { base: draft.base, current, stale: current !== draft.base },
60
+ }));
61
+ }
62
+
63
+ const observer = new MutationObserver((records) => {
64
+ for (const record of records) for (const node of record.addedNodes) {
65
+ if (node.nodeType !== 1) continue;
66
+ if (node.matches("[unsent]")) restore(node);
67
+ for (const el of node.querySelectorAll("[unsent]")) restore(el);
68
+ }
69
+ });
70
+
71
+ function restoreAll() {
72
+ for (const el of document.querySelectorAll("[unsent]")) restore(el);
73
+ }
74
+
75
+ // The listeners and the observer belong to the page, not to an edit session: a reader
76
+ // repaint while the page is in view has to bring the drafts back too. Installing twice
77
+ // adds nothing, which is what keeps a second entry from recording one keystroke twice.
78
+ let installed = false;
79
+
80
+ function install() {
81
+ if (installed) return;
82
+ installed = true;
83
+ document.addEventListener("beforeinput", onBeforeInput, true);
84
+ document.addEventListener("input", onInput, true);
85
+ observer.observe(document.body, { childList: true, subtree: true });
86
+ }
87
+
88
+ install();
89
+ restoreAll();
90
+
91
+ /**
92
+ * Unsent drafts as a mode participant.
93
+ *
94
+ * Nothing here is the session's to lose: a draft is a revision of one control (its base
95
+ * and its text, per record and name) that the page's bytes cannot show, and the restore
96
+ * that brings it back runs in view as well. So `enable` installs and restores, and
97
+ * `quiesce`, `resume` and `dispose` leave every draft, its base and the pending check
98
+ * exactly where they are — an exit that teardown could acknowledge would report a view
99
+ * while a person's half-written text still waits outside the file.
100
+ */
101
+ export function unsentParticipant() {
102
+ return {
103
+ id: "clay:unsent",
104
+ order: 320,
105
+ prepare(context = {}) {
106
+ // Adoption asks for a lease over the boot's session, and this module has none to
107
+ // hand back: it installs once at import and keeps every draft.
108
+ if (context.initial === true) {
109
+ return { enable() {}, quiesce() {}, resume() {}, dispose() {} };
110
+ }
111
+ install();
112
+ return {
113
+ enable() {
114
+ install();
115
+ restoreAll();
116
+ },
117
+ quiesce() {},
118
+ resume() {},
119
+ dispose() {},
120
+ };
121
+ },
122
+ };
123
+ }
124
+
125
+ export { unsentParticipant as modeParticipant };
126
+
127
+ export const unsent = {
128
+ get size() { return drafts.size; },
129
+ list: () => [...drafts].map(([key, d]) => { const [record, name] = split(key); return { record, name, ...d }; }),
130
+ clear(el) { const key = keyOf(el); if (key && drafts.delete(key)) persist(); bases.delete(el); },
131
+ drop(record, name) { if (drafts.delete(`${record}|${name}`)) persist(); },
132
+ };
@@ -0,0 +1 @@
1
+ export { start, apply, prepare } from "../core/upgrade-run.js";
@@ -0,0 +1,210 @@
1
+ // A copy made from a starter template says where it came from: its `hyper-source`
2
+ // and `hyper-version` metas. Once per load, the person who can edit such a copy is
3
+ // asked whether a newer version was published, and gets one offer when it was: an
4
+ // item with a dot in the menu, or a notice with Upgrade and Not now when the page
5
+ // has no menu. Nothing here writes the document; the click lands on
6
+ // `clay:upgrade-requested`, which is where the upgrade itself picks it up.
7
+ //
8
+ // Silent on every other page: no metas (a copy made before templates carried them),
9
+ // a visitor, a draft, no network, a failed fetch, a bad digest or an equal version.
10
+ // The 24-hour cache is hyper-html-api's own; the dotted-version comparison is
11
+ // core/version.js, so the rules engine that upgrades a document stays the click's.
12
+ import { isNewerVersion } from "../core/version.js";
13
+
14
+ const CACHE_PREFIX = "hha:upgrade-check:";
15
+ const TTL_MS = 24 * 60 * 60 * 1000;
16
+ const DISMISSED_KEY = "upgrade-dismissed";
17
+ const NOTICE = "A new version of this template is available.";
18
+
19
+ function readMeta(doc, name) {
20
+ const el = doc.querySelector(`meta[name="${name}"]`);
21
+ const content = el ? el.getAttribute("content") : null;
22
+ return content && content.trim() ? content.trim() : null;
23
+ }
24
+
25
+ function readCache(key) {
26
+ try {
27
+ const raw = globalThis.localStorage.getItem(key);
28
+ return raw ? JSON.parse(raw) : null;
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ function writeCache(key, value) {
35
+ try {
36
+ globalThis.localStorage.setItem(key, JSON.stringify(value));
37
+ } catch {
38
+ // No storage (a private window): the next load asks again.
39
+ }
40
+ }
41
+
42
+ async function digestOf(bytes) {
43
+ const subtle = globalThis.crypto && globalThis.crypto.subtle;
44
+ if (!subtle) return null;
45
+ const digest = await subtle.digest("SHA-256", bytes);
46
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
47
+ }
48
+
49
+ async function fetchOrNull(fetchFn, url) {
50
+ try {
51
+ const res = await fetchFn(url, { credentials: "omit" });
52
+ if (!res.ok) return null;
53
+ return res;
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ // `hyper-source` names the published HTML; its manifest is the same URL as JSON.
60
+ async function latestManifest(sourceHref, currentVersion, { fetchFn, now }) {
61
+ const latestUrl = new URL(sourceHref);
62
+ latestUrl.pathname = latestUrl.pathname.replace(/\.html$/, ".json");
63
+ const cacheKey = `${CACHE_PREFIX}${latestUrl.href}:${currentVersion}`;
64
+ const cached = readCache(cacheKey);
65
+ if (cached && typeof cached.checkedAt === "number" && now - cached.checkedAt < TTL_MS) {
66
+ return { latest: cached.latest, latestUrl };
67
+ }
68
+ const res = await fetchOrNull(fetchFn, latestUrl.href);
69
+ if (!res) return { latest: null, latestUrl };
70
+ let latest = null;
71
+ try {
72
+ latest = await res.json();
73
+ } catch {
74
+ return { latest: null, latestUrl };
75
+ }
76
+ writeCache(cacheKey, { checkedAt: now, latest });
77
+ return { latest, latestUrl };
78
+ }
79
+
80
+ export async function findUpdate({
81
+ doc = document,
82
+ loc = location,
83
+ fetchFn = (url, init) => fetch(url, init),
84
+ now = Date.now(),
85
+ } = {}) {
86
+ const clay = (typeof window !== "undefined" && window.clay) || {};
87
+ // A visitor cannot act on the offer, and a draft is not the document the source
88
+ // would replace. Neither fetches.
89
+ if (!clay.canEdit || clay.isDraftMode) return null;
90
+
91
+ const sourceUrl = readMeta(doc, "hyper-source");
92
+ const currentVersion = readMeta(doc, "hyper-version");
93
+ if (!sourceUrl || !currentVersion) return null;
94
+
95
+ let source;
96
+ try {
97
+ source = new URL(sourceUrl, loc ? loc.href : undefined);
98
+ } catch {
99
+ return null;
100
+ }
101
+ if (!source.pathname.endsWith(".html")) return null;
102
+ // A pristine published copy points at itself: it is the source, not a copy of one.
103
+ if (loc && source.origin === loc.origin && source.pathname === loc.pathname) return null;
104
+ const latestUrl = new URL(source.href);
105
+ latestUrl.pathname = latestUrl.pathname.replace(/\.html$/, ".json");
106
+
107
+ const { latest } = await latestManifest(source.href, currentVersion, { fetchFn, now });
108
+ if (!latest || typeof latest !== "object") return null;
109
+ if (typeof latest.version !== "string" || !isNewerVersion(latest.version, currentVersion)) return null;
110
+
111
+ const storage = clay.storage && clay.storage.local;
112
+ if (storage && storage.get(DISMISSED_KEY) === latest.version) return null;
113
+
114
+ if (typeof latest.html !== "string" || !latest.html) return null;
115
+ const htmlUrl = new URL(latest.html, latestUrl).href;
116
+ const res = await fetchOrNull(fetchFn, htmlUrl);
117
+ if (!res) return null;
118
+ let bytes;
119
+ try {
120
+ bytes = await res.arrayBuffer();
121
+ } catch {
122
+ return null;
123
+ }
124
+ const digest = await digestOf(bytes);
125
+ // A manifest whose digest does not match its HTML is a host that served the wrong
126
+ // bytes. Nothing is offered, because nothing can be trusted to be that version.
127
+ if (!digest || digest !== String(latest.sha256 || "").toLowerCase()) return null;
128
+
129
+ return { version: latest.version, html: htmlUrl, sha256: digest, latestUrl: latestUrl.href };
130
+ }
131
+
132
+ function dismiss(version) {
133
+ const clay = window.clay;
134
+ const storage = clay && clay.storage && clay.storage.local;
135
+ if (storage) storage.set(DISMISSED_KEY, version);
136
+ }
137
+
138
+ function menuPlanned(clay) {
139
+ return Boolean(clay && Array.isArray(clay.plugins) && clay.plugins.includes("menu"));
140
+ }
141
+
142
+ function menuReady(clay) {
143
+ return Boolean(clay && clay.menu && typeof clay.menu.addItem === "function");
144
+ }
145
+
146
+ function whenMenuReady(clay) {
147
+ if (menuReady(clay) || !menuPlanned(clay)) return Promise.resolve();
148
+ return new Promise((resolve) => {
149
+ const onSettled = (event) => {
150
+ if (!event.detail || event.detail.plugin !== "menu") return;
151
+ document.removeEventListener("clay:plugin-ready", onSettled);
152
+ document.removeEventListener("clay:plugin-failed", onSettled);
153
+ resolve();
154
+ };
155
+ document.addEventListener("clay:plugin-ready", onSettled);
156
+ document.addEventListener("clay:plugin-failed", onSettled);
157
+ });
158
+ }
159
+
160
+ export async function announce(update, clay = typeof window !== "undefined" ? window.clay : null) {
161
+ const run = () => document.dispatchEvent(new CustomEvent("clay:upgrade-requested", { detail: update }));
162
+ await whenMenuReady(clay);
163
+ if (menuReady(clay)) {
164
+ clay.menu.addItem({ id: "upgrade", label: `Upgrade to version ${update.version}`, badge: true, run });
165
+ return "menu";
166
+ }
167
+ if (clay && typeof clay.toastPersistent === "function") {
168
+ clay.toastPersistent(NOTICE, "info", {
169
+ actions: [
170
+ { label: "Upgrade", onClick: run },
171
+ { label: "Not now", onClick: () => dismiss(update.version) },
172
+ ],
173
+ });
174
+ return "notice";
175
+ }
176
+ return null;
177
+ }
178
+
179
+ let started = false;
180
+
181
+ export async function start(options = {}) {
182
+ if (started) return null;
183
+ started = true;
184
+ const clay = window.clay;
185
+ // Toasts and the menu are drawn by satellites that may land after boot settles.
186
+ await Promise.resolve(clay && clay.loaded && clay.loaded.ui).catch(() => {});
187
+ const update = await findUpdate(options).catch(() => null);
188
+ if (!update) return null;
189
+ await announce(update);
190
+ return update;
191
+ }
192
+
193
+ export async function replace(html, options) {
194
+ const mod = await import("../core/save-replace.js");
195
+ return mod.replace(html, options);
196
+ }
197
+
198
+ function onUpgradeRequested(event) {
199
+ import("./upgrade-run.js")
200
+ .then((mod) => mod.start(event.detail))
201
+ .catch((err) => console.error("clayjs: the upgrade could not start:", err));
202
+ }
203
+
204
+ const bootClay = typeof window !== "undefined" ? window.clay : null;
205
+ if (bootClay && bootClay.canEdit && !bootClay.isDraftMode) {
206
+ document.addEventListener("clay:upgrade-requested", onUpgradeRequested);
207
+ const ready = bootClay.ready;
208
+ if (ready && typeof ready.then === "function") ready.then(() => start(), () => {});
209
+ else start();
210
+ }