@panphora/clayjs 1.15.2 → 1.16.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 (506) 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 +83090 -58706
  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-NWPPXOCZ.js +1 -0
  21. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +1 -0
  22. package/dist/m/c-2AZDZWCK.js +1 -0
  23. package/dist/m/c-2JL6E75N.js +1 -0
  24. package/dist/m/c-2M73252O.js +1 -0
  25. package/dist/m/c-2SPVQSHC.js +1 -0
  26. package/dist/m/c-33HUYEDI.js +1 -0
  27. package/dist/m/c-33JJ6CBX.js +2 -0
  28. package/dist/m/c-35NBGMZB.js +1 -0
  29. package/dist/m/c-3GJ4SDE2.js +1 -0
  30. package/dist/m/c-3IQRECYS.js +1 -0
  31. package/dist/m/c-3OWDMNJR.js +2 -0
  32. package/dist/m/c-46XYM55L.js +1 -0
  33. package/dist/m/c-4B6H4G5V.js +1 -0
  34. package/dist/m/c-4DRMS4QG.js +9 -0
  35. package/dist/m/c-4FFHZM32.js +1 -0
  36. package/dist/m/c-4JZDZFF6.js +1 -0
  37. package/dist/m/c-4L3BTFRK.js +1 -0
  38. package/dist/m/c-4Q2XTQ2N.js +2 -0
  39. package/dist/m/c-4WKFKEKF.js +1 -0
  40. package/dist/m/c-4YSCRICO.js +1 -0
  41. package/dist/m/c-54RCDCBU.js +1 -0
  42. package/dist/m/c-5M4YEKCX.js +4 -0
  43. package/dist/m/c-5R4PTRG2.js +1 -0
  44. package/dist/m/c-5V3MOVJT.js +1 -0
  45. package/dist/m/c-5VYOWDTI.js +1 -0
  46. package/dist/m/c-5Y6TNX5W.js +34 -0
  47. package/dist/m/c-5YIHJ3TJ.js +1 -0
  48. package/dist/m/c-66O32MV3.js +28 -0
  49. package/dist/m/c-6HMB24EV.js +1 -0
  50. package/dist/m/c-7DJDKPGL.js +1 -0
  51. package/dist/m/c-7O6AFR65.js +1 -0
  52. package/dist/m/c-7SWMYKAI.js +1 -0
  53. package/dist/m/c-7VGZ7RU5.js +1 -0
  54. package/dist/m/c-A7LS5UZW.js +1 -0
  55. package/dist/m/c-ANQTJ3B5.js +2 -0
  56. package/dist/m/c-AQLFEATB.js +1 -0
  57. package/dist/m/c-AYVOREBE.js +1 -0
  58. package/dist/m/c-B2ZXPOS3.js +3 -0
  59. package/dist/m/c-BBVGTQG7.js +1 -0
  60. package/dist/m/c-BMIZKMPV.js +1 -0
  61. package/dist/m/c-BTDI4Y23.js +1 -0
  62. package/dist/m/c-BWFVSBDF.js +1 -0
  63. package/dist/m/c-BYTKL6UX.js +1 -0
  64. package/dist/m/c-CFHW5AMY.js +6 -0
  65. package/dist/m/c-CJZSLMVQ.js +1 -0
  66. package/dist/m/c-CYWLPPUS.js +1 -0
  67. package/dist/m/c-CZUSOB7V.js +1 -0
  68. package/dist/m/c-DBE6ORSI.js +1 -0
  69. package/dist/m/c-DCFSEJZG.js +1 -0
  70. package/dist/m/c-DNZ5IFM6.js +1 -0
  71. package/dist/m/c-EDHUR4MX.js +1 -0
  72. package/dist/m/c-EJTGYSLN.js +1 -0
  73. package/dist/m/c-EY6UOM54.js +1 -0
  74. package/dist/m/c-F3CKJTJS.js +1 -0
  75. package/dist/m/c-F7Y6FLPY.js +1 -0
  76. package/dist/m/c-FA4DZVTC.js +1 -0
  77. package/dist/m/c-FILLTJA7.js +1 -0
  78. package/dist/m/c-G2BUZITR.js +1 -0
  79. package/dist/m/c-GB3JTV6E.js +1 -0
  80. package/dist/m/c-GBGWXTX4.js +1 -0
  81. package/dist/m/c-GHBCCVG4.js +1 -0
  82. package/dist/m/c-GJAEGGXY.js +1 -0
  83. package/dist/m/c-GOWBDM6J.js +1 -0
  84. package/dist/m/c-GQ6F6IDK.js +1 -0
  85. package/dist/m/c-H4LG442Q.js +1 -0
  86. package/dist/m/c-HA6INT6J.js +1 -0
  87. package/dist/m/c-HPURYVZS.js +2 -0
  88. package/dist/m/c-HRDCD3WN.js +67 -0
  89. package/dist/m/c-HT33EQRN.js +1 -0
  90. package/dist/m/c-HVYXIS72.js +1 -0
  91. package/dist/m/c-IB52R237.js +1 -0
  92. package/dist/m/c-IJUK2GPW.js +1 -0
  93. package/dist/m/c-IKWNE4R4.js +1 -0
  94. package/dist/m/c-IU3IYEU6.js +1 -0
  95. package/dist/m/c-IZ2EBRAJ.js +1 -0
  96. package/dist/m/c-J7CA54IP.js +1 -0
  97. package/dist/m/c-JAX2667S.js +6 -0
  98. package/dist/m/c-KI7M4AY3.js +1 -0
  99. package/dist/m/c-KIPQJX4B.js +1 -0
  100. package/dist/m/c-KKWLDOPM.js +1 -0
  101. package/dist/m/c-KR4UW4KW.js +1 -0
  102. package/dist/m/c-KY3VJGUA.js +8 -0
  103. package/dist/m/c-L4WQSC27.js +1 -0
  104. package/dist/m/c-LFPHCXY3.js +1 -0
  105. package/dist/m/c-LR4CGEPT.js +91 -0
  106. package/dist/m/c-LRPUO2MX.js +1 -0
  107. package/dist/m/c-LTGMXXKO.js +1 -0
  108. package/dist/m/c-LXENEBV7.js +1 -0
  109. package/dist/m/c-LZJPNR2W.js +1 -0
  110. package/dist/m/c-MMUIUUZG.js +2 -0
  111. package/dist/m/c-MTQHWQC3.js +1 -0
  112. package/dist/m/c-N326R2JD.js +1 -0
  113. package/dist/m/c-N7JPUPGC.js +1 -0
  114. package/dist/m/c-NMJRXKPC.js +8 -0
  115. package/dist/m/c-NRPJEVJS.js +1 -0
  116. package/dist/m/c-NUHP2XDB.js +1 -0
  117. package/dist/m/c-NZMNXVF6.js +1 -0
  118. package/dist/m/c-OCSZ4QGN.js +1 -0
  119. package/dist/m/c-OIBUTDXI.js +1 -0
  120. package/dist/m/c-OIDRZKAF.js +1 -0
  121. package/dist/m/c-OYTFP5QN.js +1 -0
  122. package/dist/m/c-P7KNDG34.js +1 -0
  123. package/dist/m/c-PPOSCAQD.js +1 -0
  124. package/dist/m/c-PQ5LK6QE.js +993 -0
  125. package/dist/m/c-PZ4XEPUP.js +2 -0
  126. package/dist/m/c-QHIWXHRO.js +1 -0
  127. package/dist/m/c-QHWJ5ZIJ.js +1 -0
  128. package/dist/m/c-QIX5BJYN.js +1 -0
  129. package/dist/m/c-QNKREOL6.js +1 -0
  130. package/dist/m/c-QQHEX6HA.js +1 -0
  131. package/dist/m/c-R7RMANJI.js +9 -0
  132. package/dist/m/c-RI4NT75Y.js +1 -0
  133. package/dist/m/c-RM7J7BV2.js +1 -0
  134. package/dist/m/c-RUJKSUQW.js +1 -0
  135. package/dist/m/c-RVMWGXEJ.js +1 -0
  136. package/dist/m/c-S7H2DRCB.js +1 -0
  137. package/dist/m/c-S7LK4KUE.js +1 -0
  138. package/dist/m/c-SGCCC4FW.js +25 -0
  139. package/dist/m/c-SGIA4IEQ.js +188 -0
  140. package/dist/m/c-SWRJFCNN.js +1 -0
  141. package/dist/m/c-T4VO4OCB.js +1 -0
  142. package/dist/m/c-TCCA5BZN.js +2 -0
  143. package/dist/m/c-TQEMVJCS.js +1 -0
  144. package/dist/m/c-TQZ2JYHD.js +1 -0
  145. package/dist/m/c-TR6RMDQ7.js +3 -0
  146. package/dist/m/c-TRS6TM6O.js +1 -0
  147. package/dist/m/c-UGHH4MB3.js +1 -0
  148. package/dist/m/c-UKHTLWUW.js +1 -0
  149. package/dist/m/c-UL2YUY52.js +1 -0
  150. package/dist/m/c-UNN7LWCT.js +1 -0
  151. package/dist/m/c-UXBMRDZB.js +235 -0
  152. package/dist/m/c-V7PZZRG7.js +1 -0
  153. package/dist/m/c-VAK7SG37.js +1 -0
  154. package/dist/m/c-VDSS4K3Z.js +1 -0
  155. package/dist/m/c-VFX45IZQ.js +1 -0
  156. package/dist/m/c-VHNSBD2M.js +1 -0
  157. package/dist/m/c-VKEDWYDF.js +1 -0
  158. package/dist/m/c-VL52ADDR.js +1 -0
  159. package/dist/m/c-WN5AY3OZ.js +1 -0
  160. package/dist/m/c-WNYBLIOZ.js +1 -0
  161. package/dist/m/c-WRBKEJD4.js +1 -0
  162. package/dist/m/c-WWDBYXLK.js +1 -0
  163. package/dist/m/c-WYFDLITL.js +1 -0
  164. package/dist/m/c-XN6MBKI6.js +67 -0
  165. package/dist/m/c-XNNW2CWK.js +1 -0
  166. package/dist/m/c-XPELB6E5.js +1 -0
  167. package/dist/m/c-XQXZAO7N.js +1 -0
  168. package/dist/m/c-Y6USGTS5.js +5 -0
  169. package/dist/m/c-YDH7Q3NY.js +1 -0
  170. package/dist/m/c-YG2NJYS6.js +135 -0
  171. package/dist/m/c-Z36J63PP.js +1 -0
  172. package/dist/m/c-Z4VRC7GU.js +7 -0
  173. package/dist/m/c-Z6K52OLP.js +19 -0
  174. package/dist/m/c-ZDY2MDAA.js +21 -0
  175. package/dist/m/c-ZE46QDIH.js +1 -0
  176. package/dist/m/c-ZUCOKOHT.js +1 -0
  177. package/dist/m/core/admin-attrs-VGNLAUDK.js +1 -0
  178. package/dist/m/core/autosave-E4PEFPZ6.js +1 -0
  179. package/dist/m/core/conflict-notice-VQ6Y64PH.js +1 -0
  180. package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
  181. package/dist/m/core/host-permission-XN3K2IW3.js +1 -0
  182. package/dist/m/core/is-edit-mode-JXHDVGUL.js +1 -0
  183. package/dist/m/core/persist-HKHA66IY.js +3 -0
  184. package/dist/m/core/recovery-TBBSICE2.js +1 -0
  185. package/dist/m/core/save-WUCHTQBE.js +1 -0
  186. package/dist/m/core/save-core-FJVHT5M3.js +1 -0
  187. package/dist/m/core/snapshot-F3SWDZMM.js +1 -0
  188. package/dist/m/core/unsaved-warning-STKBHE6G.js +1 -0
  189. package/dist/m/dom/all-APS5ZSKP.js +1 -0
  190. package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
  191. package/dist/m/events/index-23DPTUT4.js +1 -0
  192. package/dist/m/internals/index-WOVAP3RU.js +1 -0
  193. package/dist/m/lib/cache-bust-CGWT6ZJI.js +1 -0
  194. package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
  195. package/dist/m/loader-HXX6YNH5.js +1 -0
  196. package/dist/m/manifest-F22DEDAC.js +1 -0
  197. package/dist/m/manifest-edit-C3C7376B.js +1 -0
  198. package/dist/m/manifest.json +3491 -0
  199. package/dist/m/options/options-2LH76JQ3.js +1 -0
  200. package/dist/m/page/editor-XUTB2HB2.js +189 -0
  201. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +6 -0
  202. package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
  203. package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
  204. package/dist/m/plugins/demo-ZIBFAA3R.js +1 -0
  205. package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
  206. package/dist/m/plugins/indicator-PV4YW2ID.js +1 -0
  207. package/dist/m/plugins/live-NUTTXSBS.js +1 -0
  208. package/dist/m/plugins/menu-HCKO3DSP.js +138 -0
  209. package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
  210. package/dist/m/plugins/page-KSUH7G6J.js +2 -0
  211. package/dist/m/plugins/page-view-W73OW7WI.js +1 -0
  212. package/dist/m/plugins/people-AJJ32YCG.js +1 -0
  213. package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
  214. package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
  215. package/dist/m/plugins/richclay-RC3L2F6X.js +517 -0
  216. package/dist/m/plugins/richclay-RC3L2F6X.js.LEGAL.txt +1 -0
  217. package/dist/m/plugins/sortable-YRMQX3ZU.js +1 -0
  218. package/dist/m/plugins/source-KHK5UUSK.js +1 -0
  219. package/dist/m/plugins/undo-OF2L3CXU.js +1 -0
  220. package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
  221. package/dist/m/plugins/upgrade-ZNYODP5Y.js +1 -0
  222. package/dist/m/plugins/upload-XSGNILUY.js +1 -0
  223. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +21 -0
  224. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +1 -0
  225. package/dist/m/plugins/wire-ZWVWKF76.js +1 -0
  226. package/dist/m/reader-core-ILWLXVMP.js +68 -0
  227. package/dist/m/route/route-KOCZSEGN.js +1 -0
  228. package/dist/m/sync/live-sync-4AUVXVJU.js +1 -0
  229. package/dist/m/ui/index-EVHKMKBL.js +1 -0
  230. package/dist/m/utils/index-4TJCVLT7.js +1 -0
  231. package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
  232. package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
  233. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
  234. package/dist/offline-manifest.json +3800 -0
  235. package/dist/page/themes/light-1.css +406 -0
  236. package/dist/server/core.cjs +23662 -22860
  237. package/dist/server/offline-runtime.cjs +4954 -0
  238. package/dist/server/offline-runtime.mjs +4923 -0
  239. package/dist/server/rooms.mjs +9 -0
  240. package/docs/guide.md +1078 -669
  241. package/entries/all.js +2 -0
  242. package/entries/clay-data.js +1 -1
  243. package/entries/clay-dom.js +2 -0
  244. package/entries/clay-events.js +2 -0
  245. package/entries/clay-internals.js +2 -0
  246. package/entries/clay-options.js +2 -0
  247. package/entries/clay-router.js +2 -0
  248. package/entries/clay-ui.js +2 -0
  249. package/entries/clay-utils.js +2 -0
  250. package/entries/clay.js +2 -0
  251. package/entries/sap.js +6 -6
  252. package/fonts/ibm-plex-mono-LICENSE +93 -0
  253. package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
  254. package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
  255. package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  256. package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  257. package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  258. package/fonts/ibm-plex-sans-LICENSE +93 -0
  259. package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  260. package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  261. package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  262. package/fonts/ibm-plex.css +71 -0
  263. package/fonts/inter-LICENSE +93 -0
  264. package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
  265. package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
  266. package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
  267. package/fonts/inter-greek-wght-normal.woff2 +0 -0
  268. package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  269. package/fonts/inter-latin-wght-normal.woff2 +0 -0
  270. package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
  271. package/fonts/inter.css +62 -0
  272. package/fonts/newsreader-LICENSE +93 -0
  273. package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
  274. package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  275. package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
  276. package/fonts/newsreader.css +26 -0
  277. package/fonts/work-sans-LICENSE +93 -0
  278. package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
  279. package/fonts/work-sans.css +8 -0
  280. package/package.json +20 -3
  281. package/packed-contract.json +1 -0
  282. package/src/attrs/add-from.js +27 -4
  283. package/src/attrs/save-freeze.js +7 -52
  284. package/src/core/admin-attrs.js +119 -12
  285. package/src/core/admin-contenteditable.js +12 -10
  286. package/src/core/admin-inputs.js +14 -15
  287. package/src/core/admin-onclick.js +2 -0
  288. package/src/core/autosave.js +74 -21
  289. package/src/core/conflict-download.js +22 -0
  290. package/src/core/conflict-notice-ui.js +1173 -0
  291. package/src/core/conflict-notice.js +140 -697
  292. package/src/core/edit-mode.js +147 -14
  293. package/src/core/etag.js +30 -12
  294. package/src/core/host-meta.js +72 -8
  295. package/src/core/host-permission.js +29 -0
  296. package/src/core/is-edit-mode.js +145 -12
  297. package/src/core/mode-key.js +125 -0
  298. package/src/core/mode-lifecycle.js +544 -0
  299. package/src/core/mode-receipt.js +30 -0
  300. package/src/core/mode-refusal.js +13 -0
  301. package/src/core/mode-resources.js +52 -0
  302. package/src/core/mode-support.js +82 -0
  303. package/src/core/mode-switch.js +259 -0
  304. package/src/core/mode-transition.js +112 -0
  305. package/src/core/page-data-write.js +305 -0
  306. package/src/core/page-data.js +26 -227
  307. package/src/core/page-format-wire.js +4 -0
  308. package/src/core/page-format.js +58 -0
  309. package/src/core/persist.js +37 -9
  310. package/src/core/preserve-authored.js +114 -0
  311. package/src/core/recovery.js +772 -0
  312. package/src/core/runtime-url.js +105 -0
  313. package/src/core/save-core.js +109 -12
  314. package/src/core/save-replace.js +72 -0
  315. package/src/core/save-size-notice.js +134 -0
  316. package/src/core/save-size.js +140 -0
  317. package/src/core/save.js +805 -118
  318. package/src/core/snapshot.js +121 -11
  319. package/src/core/source-map.js +157 -9
  320. package/src/core/unsaved-warning.js +14 -4
  321. package/src/core/upgrade-run.js +408 -0
  322. package/src/core/version.js +18 -0
  323. package/src/lib/assets.js +30 -0
  324. package/src/lib/autosave-debug-gate.js +50 -0
  325. package/src/lib/autosave-scheduler.js +141 -0
  326. package/src/lib/boot-imports.js +8 -0
  327. package/src/lib/derived.js +3 -1
  328. package/src/lib/dirty-gate.js +15 -0
  329. package/src/lib/doc-storage.js +33 -0
  330. package/src/lib/effect.js +9 -4
  331. package/src/lib/mutation.js +4 -2
  332. package/src/lib/offline-runtime.js +226 -0
  333. package/src/lib/preload.js +48 -0
  334. package/src/lib/root-attrs.js +1 -1
  335. package/src/lib/trash.js +18 -0
  336. package/src/lib/undo/capture-move.js +152 -0
  337. package/src/live/local-channel.js +61 -0
  338. package/src/live/local-lane.js +60 -9
  339. package/src/live/server-lane.js +6 -3
  340. package/src/loader-logic.js +76 -15
  341. package/src/loader.js +218 -30
  342. package/src/page/adopt-boot.js +76 -22
  343. package/src/page/adopt.js +25 -4
  344. package/src/page/awareness.js +230 -0
  345. package/src/page/blocks.js +79 -5
  346. package/src/page/blocksel.js +20 -4
  347. package/src/page/board-face.js +13 -0
  348. package/src/page/board-panel.js +5 -17
  349. package/src/page/board-style.js +2 -13
  350. package/src/page/board-view.js +1 -11
  351. package/src/page/board-views.js +17 -0
  352. package/src/page/board.js +2 -2
  353. package/src/page/boot-pack.js +26 -0
  354. package/src/page/boot.js +237 -0
  355. package/src/page/checklist.js +1 -1
  356. package/src/page/chrome.js +411 -61
  357. package/src/page/csv-import.js +17 -2
  358. package/src/page/document-title-dom.js +40 -0
  359. package/src/page/document-title-runtime.js +76 -0
  360. package/src/page/document-title.js +9 -0
  361. package/src/page/drag.js +48 -13
  362. package/src/page/drop.js +2 -1
  363. package/src/page/editor.js +330 -86
  364. package/src/page/feature-loader.js +72 -0
  365. package/src/page/features/board.js +12 -0
  366. package/src/page/features/code.js +13 -0
  367. package/src/page/features/media.js +290 -0
  368. package/src/page/features/registry.js +23 -0
  369. package/src/page/features/table.js +383 -0
  370. package/src/page/{tool-view.js → features/tool.js} +33 -2
  371. package/src/page/format-codec.js +215 -0
  372. package/src/page/format-gate.js +179 -0
  373. package/src/page/format-level.js +157 -0
  374. package/src/page/highlight-slot.js +23 -0
  375. package/src/page/highlight.js +30 -16
  376. package/src/page/history-intent.js +78 -0
  377. package/src/page/inline.js +18 -25
  378. package/src/page/items.js +52 -11
  379. package/src/page/link.js +1 -0
  380. package/src/page/markdown.js +86 -1
  381. package/src/page/media-views.js +3 -0
  382. package/src/page/media.js +33 -245
  383. package/src/page/outline-cache.js +11 -0
  384. package/src/page/placeholder.js +27 -7
  385. package/src/page/projection.js +196 -50
  386. package/src/page/reader-patch.js +306 -0
  387. package/src/page/recover.js +1 -1
  388. package/src/page/rich/page-tool.js +1 -4
  389. package/src/page/runtime-owner.js +27 -0
  390. package/src/page/save-projection.js +7 -4
  391. package/src/page/schema.js +14 -0
  392. package/src/page/table-rect-key.js +3 -0
  393. package/src/page/table-select.js +4 -3
  394. package/src/page/table-style.js +1 -1
  395. package/src/page/table-views.js +3 -0
  396. package/src/page/table.js +8 -293
  397. package/src/page/themes/light-1.css +139 -2
  398. package/src/page/themes/light-1.source.css +269 -0
  399. package/src/page/tool-capability.js +3 -0
  400. package/src/page/tool-views.js +3 -0
  401. package/src/page/view.js +390 -107
  402. package/src/page/ybind/awareness.js +172 -0
  403. package/src/page/ybind/binding.js +9 -1
  404. package/src/page/ybind/bridge.js +50 -47
  405. package/src/page/ybind/decode.js +52 -19
  406. package/src/page/ybind/doc.js +27 -2
  407. package/src/page/ybind/group.js +12 -3
  408. package/src/page/ybind/index.js +2 -0
  409. package/src/page/ybind/lineage.js +71 -0
  410. package/src/page/ybind/marks.js +25 -1
  411. package/src/page/ybind/migrate.js +48 -13
  412. package/src/page/ybind/moves.js +32 -21
  413. package/src/page/ybind/positions.js +50 -0
  414. package/src/page/ybind/provider.js +84 -22
  415. package/src/page/ybind/recovery-proof.js +191 -0
  416. package/src/page/ybind/recovery-replay.js +130 -0
  417. package/src/page/ybind/tokens.js +15 -6
  418. package/src/page/ybind/walk.js +10 -3
  419. package/src/page/ybind/writer.js +48 -4
  420. package/src/plugins/ai-edit.js +167 -32
  421. package/src/plugins/bind-text.js +218 -0
  422. package/src/plugins/cms.js +93 -0
  423. package/src/plugins/indicator.js +57 -5
  424. package/src/plugins/live.js +282 -34
  425. package/src/plugins/menu/claygoo.svg.js +1 -0
  426. package/src/plugins/menu/seo-head.js +323 -0
  427. package/src/plugins/menu/seo.js +849 -0
  428. package/src/plugins/menu.js +425 -0
  429. package/src/plugins/page-view.js +131 -0
  430. package/src/plugins/page.js +1022 -275
  431. package/src/plugins/people.js +158 -31
  432. package/src/plugins/richclay.js +119 -9
  433. package/src/plugins/sortable.js +370 -60
  434. package/src/plugins/source.js +189 -366
  435. package/src/plugins/undo.js +289 -19
  436. package/src/plugins/unsent.js +132 -0
  437. package/src/plugins/upgrade-run.js +1 -0
  438. package/src/plugins/upgrade.js +210 -0
  439. package/src/plugins/upload-task.js +571 -0
  440. package/src/plugins/upload-ui.js +265 -0
  441. package/src/plugins/upload.js +34 -153
  442. package/src/plugins/visitor-drafts/inert.js +32 -0
  443. package/src/plugins/visitor-drafts/owner-chip.js +3 -0
  444. package/src/plugins/visitor-drafts/prompt.js +2 -2
  445. package/src/plugins/visitor-drafts-owner.js +85 -0
  446. package/src/plugins/visitor-drafts.js +31 -42
  447. package/src/plugins/wire.js +82 -1
  448. package/src/reader-core.js +26 -0
  449. package/src/recovery/conflicts.js +287 -0
  450. package/src/recovery/identity.js +154 -0
  451. package/src/recovery/store.js +792 -0
  452. package/src/recovery/undo.js +257 -0
  453. package/src/server/checkpoint/index.js +68 -4
  454. package/src/server/offline-runtime/index.js +214 -0
  455. package/src/server/rooms/index.js +2 -1
  456. package/src/server/rooms/live-data-rooms.js +2 -2
  457. package/src/server/rooms/page-admission.js +33 -0
  458. package/src/server/rooms/page-lineage.js +56 -0
  459. package/src/server/rooms/page-rebase.js +11 -5
  460. package/src/server/rooms/page-state.js +2 -0
  461. package/src/server/rooms/page-write-admission.js +9 -3
  462. package/src/source/session.js +705 -0
  463. package/src/sync/conflict-revert.js +5 -4
  464. package/src/sync/conflicts-edit.js +119 -0
  465. package/src/sync/conflicts-facade.js +49 -0
  466. package/src/sync/conflicts.js +99 -1
  467. package/src/sync/editing-marker.js +37 -6
  468. package/src/sync/editing-sender.js +8 -1
  469. package/src/sync/live-sync-edit.js +63 -0
  470. package/src/sync/live-sync.js +364 -119
  471. package/src/sync/local-presence.js +187 -0
  472. package/src/sync/merge-declarations.js +10 -0
  473. package/src/sync/merge-engine.js +12 -0
  474. package/src/sync/presence-store.js +412 -0
  475. package/src/sync/presence-target.js +243 -0
  476. package/src/sync/presence.js +72 -41
  477. package/src/sync/saved-handoff.js +200 -0
  478. package/src/sync/saved-stream.js +535 -0
  479. package/src/sync/version-guard.js +92 -0
  480. package/src/sync/wire-profile.js +81 -0
  481. package/src/ui/bevel-controls.js +2 -8
  482. package/src/ui/page-scheme.js +7 -0
  483. package/src/ui/skins/cms.js +1 -1
  484. package/src/ui/theme-config.js +150 -0
  485. package/src/ui/theme.js +6 -138
  486. package/src/ui/toast-lazy.js +11 -0
  487. package/src/vendor/LICENSES-optional.md +253 -0
  488. package/src/vendor/highlight.vendor.js +3 -0
  489. package/src/vendor/hljs-bash.vendor.js +2 -0
  490. package/src/vendor/hljs-css.vendor.js +2 -0
  491. package/src/vendor/hljs-javascript.vendor.js +2 -0
  492. package/src/vendor/hljs-json.vendor.js +2 -0
  493. package/src/vendor/hljs-python.vendor.js +2 -0
  494. package/src/vendor/hljs-xml.vendor.js +2 -0
  495. package/src/vendor/hyper-html-api-data.vendor.js +1 -1
  496. package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
  497. package/src/vendor/hyper-morph-identity.vendor.js +2 -0
  498. package/src/vendor/hyper-morph-json.vendor.js +3 -0
  499. package/src/vendor/hyper-morph.vendor.js +6 -6
  500. package/src/vendor/hyper-undo.vendor.js +1 -1
  501. package/src/vendor/hypercms.vendor.js +169 -84
  502. package/src/vendor/marked.vendor.js +68 -0
  503. package/src/vendor/overtype.vendor.js +994 -0
  504. package/src/vendor/richclay.vendor.js +17 -17
  505. package/src/vendor/sap-core.vendor.js +5 -5
  506. package/src/vendor/sap-ownership.vendor.js +11 -7
@@ -0,0 +1,305 @@
1
+ const DATA_WRITE = "data-write";
2
+ const WRITE_HOLD_REASON = "Waiting for the page data write to finish";
3
+ const SYNC_WAIT_MS = 12000;
4
+ const HTTP_WAIT_MS = 30000;
5
+
6
+ function refusal(code, message, details) {
7
+ const error = new Error(message);
8
+ error.code = code;
9
+ error.details = details;
10
+ return error;
11
+ }
12
+
13
+ async function responseBody(response) {
14
+ if (typeof response.json === "function") return response.json();
15
+ const text = await response.text();
16
+ return text ? JSON.parse(text) : null;
17
+ }
18
+
19
+ async function responseError(response) {
20
+ let body = {};
21
+ try {
22
+ body = (await responseBody(response)) || {};
23
+ } catch {}
24
+
25
+ const message = body.message || body.error || response.statusText || `Request failed (${response.status})`;
26
+ const error = new Error(message);
27
+ error.status = response.status;
28
+ error.error = body.error;
29
+ error.details = body.details;
30
+ return error;
31
+ }
32
+
33
+ function responseEtag(response) {
34
+ const value = response.headers?.get?.("ETag");
35
+ return typeof value === "string" && value ? value : null;
36
+ }
37
+
38
+ function dataUrl(rules, location) {
39
+ const base = `${location.origin}${location.pathname}`;
40
+ if (rules !== undefined) return `${base}?data=${encodeURIComponent(JSON.stringify(rules))}`;
41
+ return `${location.origin}/_/api${location.pathname}`;
42
+ }
43
+
44
+ function connected(sync) {
45
+ return !!sync && !sync.isDestroyed && sync.sse?.readyState === 1;
46
+ }
47
+
48
+ function syncWaiter(eventTarget, timeoutMs) {
49
+ const seen = [];
50
+ let expected = null;
51
+ let resolvePending = null;
52
+ let timer = null;
53
+
54
+ const matchesExact = (detail) =>
55
+ detail?.source === "disk" && (expected === null || detail.etag === expected);
56
+
57
+ const matchesPending = (detail) =>
58
+ detail?.source === "disk" && (detail.etag == null || expected === null || detail.etag === expected);
59
+
60
+ const cleanup = () => {
61
+ clearTimeout(timer);
62
+ eventTarget.removeEventListener("clay:sync-applied", onApplied);
63
+ };
64
+
65
+ const onApplied = (event) => {
66
+ seen.push(event.detail);
67
+ if (!resolvePending || !matchesPending(event.detail)) return;
68
+ const resolve = resolvePending;
69
+ resolvePending = null;
70
+ cleanup();
71
+ resolve(true);
72
+ };
73
+
74
+ eventTarget.addEventListener("clay:sync-applied", onApplied);
75
+
76
+ return {
77
+ waitFor(etag) {
78
+ expected = etag;
79
+ if (seen.some(matchesExact)) {
80
+ cleanup();
81
+ return Promise.resolve(true);
82
+ }
83
+ return new Promise((resolve) => {
84
+ resolvePending = resolve;
85
+ timer = setTimeout(() => {
86
+ resolvePending = null;
87
+ cleanup();
88
+ resolve(false);
89
+ }, timeoutMs);
90
+ });
91
+ },
92
+ cancel: cleanup,
93
+ };
94
+ }
95
+
96
+ function httpDeadline(timeoutMs) {
97
+ const controller = new AbortController();
98
+ let rejectDeadline;
99
+ const expired = new Promise((_, reject) => {
100
+ rejectDeadline = reject;
101
+ });
102
+ const timer = setTimeout(() => {
103
+ const error = new Error("The page data write timed out.");
104
+ error.name = "TimeoutError";
105
+ controller.abort(error);
106
+ rejectDeadline(error);
107
+ }, timeoutMs);
108
+
109
+ return {
110
+ signal: controller.signal,
111
+ waitFor(promise) {
112
+ return Promise.race([promise, expired]);
113
+ },
114
+ cancel() {
115
+ clearTimeout(timer);
116
+ },
117
+ };
118
+ }
119
+
120
+ async function defaultWriteModules() {
121
+ const [save, saveCore, etag, warning, editMode] = await Promise.all([
122
+ import("./save.js"),
123
+ import("./save-core.js"),
124
+ import("./etag.js"),
125
+ import("./unsaved-warning.js"),
126
+ import("./is-edit-mode.js"),
127
+ ]);
128
+ return { ...save, ...saveCore, ...etag, ...warning, ...editMode };
129
+ }
130
+
131
+ async function draftRefusal() {
132
+ const { isDraftMode } = await import("./is-edit-mode.js");
133
+ if (!isDraftMode) return null;
134
+ return refusal("DataUnavailableInDraft", "Page data is kept in this browser for this visitor.");
135
+ }
136
+
137
+ export function createPageDataWrite({
138
+ supports,
139
+ token,
140
+ fetchImpl,
141
+ getLocation,
142
+ eventTarget,
143
+ getLiveSync,
144
+ loadWriteModules = defaultWriteModules,
145
+ syncTimeoutMs = SYNC_WAIT_MS,
146
+ httpTimeoutMs = HTTP_WAIT_MS,
147
+ }) {
148
+ let writeRunning = false;
149
+ let writeOutcomeUnknown = false;
150
+
151
+ function assertWriteState(modules, { checkHold = true } = {}) {
152
+ // A save session with admission closed is a page between modes. Its own exit is the
153
+ // only write that may go out until it commits or resumes, and page data is not it.
154
+ if (modules.savesAdmitted && !modules.savesAdmitted()) {
155
+ throw refusal("ModeTransition", "The page is switching modes. Write page data again when it has settled.");
156
+ }
157
+ if (writeOutcomeUnknown) {
158
+ throw refusal("DataWriteOutcomeUnknown", "A previous page data write has an unknown outcome. Reload before writing again.");
159
+ }
160
+ if (writeRunning) {
161
+ throw refusal("DataWriteInProgress", "Another page data write is already running.");
162
+ }
163
+ if (modules.isSaveInProgress()) {
164
+ throw refusal("SaveInProgress", "The page is already being saved.");
165
+ }
166
+ if (modules.saveFateIsUnknown()) {
167
+ throw refusal("SaveOutcomeUnknown", "The page's last save has an unknown outcome.");
168
+ }
169
+ if (checkHold && (modules.savesHeld() || modules.isSaveConflicted())) {
170
+ throw refusal("SavesHeld", "The page cannot write data while another save hold is active.");
171
+ }
172
+ if (modules.hasUnsavedChanges()) {
173
+ throw refusal("UnsavedChanges", "Save or discard the page's unsaved changes before writing data.");
174
+ }
175
+ if (!connected(getLiveSync())) {
176
+ throw refusal("SyncRequired", "Page data writes require the sync plugin to be connected.");
177
+ }
178
+ }
179
+
180
+ async function waitForBaseline(modules) {
181
+ if (modules.baselineSettled()) return;
182
+ await new Promise((resolve) => eventTarget.addEventListener("clay:baseline-settled", resolve, { once: true }));
183
+ }
184
+
185
+ async function writeData(values, { rules, ifMatch } = {}) {
186
+ const draft = await draftRefusal();
187
+ if (draft) throw draft;
188
+
189
+ if (!(await supports(DATA_WRITE))) {
190
+ throw refusal("DataWriteUnsupported", "This host does not support writing page data.");
191
+ }
192
+
193
+ const modules = await loadWriteModules();
194
+ if (!modules.isEditMode) {
195
+ throw refusal("DataWriteUnavailable", "Page data writes require an editable page.");
196
+ }
197
+
198
+ await waitForBaseline(modules);
199
+ assertWriteState(modules);
200
+
201
+ let match = ifMatch;
202
+ if (match == null) match = modules.lastSeenEtag() || await modules.seedEtag();
203
+ if (typeof match !== "string" || match === "") {
204
+ throw refusal("DataWriteVersionRequired", "Page data writes require a trustworthy document ETag.");
205
+ }
206
+
207
+ assertWriteState(modules);
208
+ writeRunning = true;
209
+ modules.holdAllSaves(WRITE_HOLD_REASON);
210
+
211
+ let waiter = null;
212
+ try {
213
+ if (modules.isSaveInProgress()) {
214
+ throw refusal("SaveInProgress", "The page is already being saved.");
215
+ }
216
+ if (modules.saveFateIsUnknown()) {
217
+ throw refusal("SaveOutcomeUnknown", "The page's last save has an unknown outcome.");
218
+ }
219
+ if (modules.isSaveConflicted()) {
220
+ throw refusal("SavesHeld", "The page cannot write data while another save hold is active.");
221
+ }
222
+ if (modules.hasUnsavedChanges()) {
223
+ throw refusal("UnsavedChanges", "Save or discard the page's unsaved changes before writing data.");
224
+ }
225
+ // Rechecked immediately before the POST: a mode switch can close admission while
226
+ // this write was waiting for the sync and the stamp.
227
+ if (modules.savesAdmitted && !modules.savesAdmitted()) {
228
+ throw refusal("ModeTransition", "The page is switching modes. Write page data again when it has settled.");
229
+ }
230
+ if (!connected(getLiveSync())) {
231
+ throw refusal("SyncRequired", "Page data writes require the sync plugin to be connected.");
232
+ }
233
+
234
+ const headers = {
235
+ "Content-Type": "application/json",
236
+ "If-Match": match,
237
+ };
238
+ const currentToken = token();
239
+ if (currentToken) headers["Save-Token"] = currentToken;
240
+
241
+ const body = JSON.stringify(values);
242
+ waiter = syncWaiter(eventTarget, syncTimeoutMs);
243
+ const deadline = httpDeadline(httpTimeoutMs);
244
+
245
+ let response;
246
+ try {
247
+ response = await deadline.waitFor(fetchImpl(dataUrl(rules, getLocation()), {
248
+ method: "POST",
249
+ credentials: "same-origin",
250
+ redirect: "error",
251
+ headers,
252
+ body,
253
+ signal: deadline.signal,
254
+ }));
255
+ } catch (cause) {
256
+ deadline.cancel();
257
+ writeOutcomeUnknown = true;
258
+ throw Object.assign(
259
+ refusal("DataWriteOutcomeUnknown", "The page data write has an unknown outcome. Do not retry automatically."),
260
+ { cause, unknownOutcome: true }
261
+ );
262
+ }
263
+
264
+ let data;
265
+ try {
266
+ if (!response.ok) throw await deadline.waitFor(responseError(response));
267
+ data = await deadline.waitFor(responseBody(response));
268
+ } catch (cause) {
269
+ if (cause?.status) throw cause;
270
+ writeOutcomeUnknown = true;
271
+ throw Object.assign(
272
+ refusal("DataWriteOutcomeUnknown", "The page data was written, but its response could not be read."),
273
+ { cause, unknownOutcome: true }
274
+ );
275
+ } finally {
276
+ deadline.cancel();
277
+ }
278
+
279
+ const etag = responseEtag(response);
280
+ if (etag && etag === modules.lastSeenEtag()) {
281
+ waiter.cancel();
282
+ waiter = null;
283
+ return { data, etag };
284
+ }
285
+
286
+ const applied = await waiter.waitFor(etag);
287
+ waiter = null;
288
+ if (!applied) {
289
+ return {
290
+ data,
291
+ etag,
292
+ pageUpdatePending: true,
293
+ message: "Written, page update pending.",
294
+ };
295
+ }
296
+ return { data, etag };
297
+ } finally {
298
+ waiter?.cancel();
299
+ writeRunning = false;
300
+ modules.releaseAllSaves({ reason: WRITE_HOLD_REASON });
301
+ }
302
+ }
303
+
304
+ return { writeData };
305
+ }
@@ -3,9 +3,6 @@ import { saveToken } from "./host-attrs.js";
3
3
 
4
4
  const DATA_READ = "data-read";
5
5
  const DATA_WRITE = "data-write";
6
- const WRITE_HOLD_REASON = "Waiting for the page data write to finish";
7
- const SYNC_WAIT_MS = 12000;
8
- const HTTP_WAIT_MS = 30000;
9
6
 
10
7
  function refusal(code, message, details) {
11
8
  const error = new Error(message);
@@ -45,93 +42,6 @@ function dataUrl(rules, location) {
45
42
  return `${location.origin}/_/api${location.pathname}`;
46
43
  }
47
44
 
48
- function connected(sync) {
49
- return !!sync && !sync.isDestroyed && sync.sse?.readyState === 1;
50
- }
51
-
52
- function syncWaiter(eventTarget, timeoutMs) {
53
- const seen = [];
54
- let expected = null;
55
- let resolvePending = null;
56
- let timer = null;
57
-
58
- const matchesExact = (detail) =>
59
- detail?.source === "disk" && (expected === null || detail.etag === expected);
60
-
61
- const matchesPending = (detail) =>
62
- detail?.source === "disk" && (detail.etag == null || expected === null || detail.etag === expected);
63
-
64
- const cleanup = () => {
65
- clearTimeout(timer);
66
- eventTarget.removeEventListener("clay:sync-applied", onApplied);
67
- };
68
-
69
- const onApplied = (event) => {
70
- seen.push(event.detail);
71
- if (!resolvePending || !matchesPending(event.detail)) return;
72
- const resolve = resolvePending;
73
- resolvePending = null;
74
- cleanup();
75
- resolve(true);
76
- };
77
-
78
- eventTarget.addEventListener("clay:sync-applied", onApplied);
79
-
80
- return {
81
- waitFor(etag) {
82
- expected = etag;
83
- if (seen.some(matchesExact)) {
84
- cleanup();
85
- return Promise.resolve(true);
86
- }
87
- return new Promise((resolve) => {
88
- resolvePending = resolve;
89
- timer = setTimeout(() => {
90
- resolvePending = null;
91
- cleanup();
92
- resolve(false);
93
- }, timeoutMs);
94
- });
95
- },
96
- cancel: cleanup,
97
- };
98
- }
99
-
100
- function httpDeadline(timeoutMs) {
101
- const controller = new AbortController();
102
- let rejectDeadline;
103
- const expired = new Promise((_, reject) => {
104
- rejectDeadline = reject;
105
- });
106
- const timer = setTimeout(() => {
107
- const error = new Error("The page data write timed out.");
108
- error.name = "TimeoutError";
109
- controller.abort(error);
110
- rejectDeadline(error);
111
- }, timeoutMs);
112
-
113
- return {
114
- signal: controller.signal,
115
- waitFor(promise) {
116
- return Promise.race([promise, expired]);
117
- },
118
- cancel() {
119
- clearTimeout(timer);
120
- },
121
- };
122
- }
123
-
124
- async function defaultWriteModules() {
125
- const [save, saveCore, etag, warning, editMode] = await Promise.all([
126
- import("./save.js"),
127
- import("./save-core.js"),
128
- import("./etag.js"),
129
- import("./unsaved-warning.js"),
130
- import("./is-edit-mode.js"),
131
- ]);
132
- return { ...save, ...saveCore, ...etag, ...warning, ...editMode };
133
- }
134
-
135
45
  async function draftRefusal() {
136
46
  const { isDraftMode } = await import("./is-edit-mode.js");
137
47
  if (!isDraftMode) return null;
@@ -144,13 +54,12 @@ export function createPageDataApi({
144
54
  fetchImpl = (...args) => fetch(...args),
145
55
  getLocation = () => window.location,
146
56
  eventTarget = document,
147
- loadWriteModules = defaultWriteModules,
148
- syncTimeoutMs = SYNC_WAIT_MS,
149
- httpTimeoutMs = HTTP_WAIT_MS,
57
+ loadWriteModules,
58
+ syncTimeoutMs,
59
+ httpTimeoutMs,
150
60
  } = {}) {
151
61
  let liveSync = null;
152
- let writeRunning = false;
153
- let writeOutcomeUnknown = false;
62
+ let writeEngine = null;
154
63
 
155
64
  function setLiveSync(sync) {
156
65
  liveSync = sync;
@@ -180,36 +89,28 @@ export function createPageDataApi({
180
89
  return { data: await responseBody(response), etag: responseEtag(response) };
181
90
  }
182
91
 
183
- function assertWriteState(modules, { checkHold = true } = {}) {
184
- if (writeOutcomeUnknown) {
185
- throw refusal("DataWriteOutcomeUnknown", "A previous page data write has an unknown outcome. Reload before writing again.");
186
- }
187
- if (writeRunning) {
188
- throw refusal("DataWriteInProgress", "Another page data write is already running.");
189
- }
190
- if (modules.isSaveInProgress()) {
191
- throw refusal("SaveInProgress", "The page is already being saved.");
192
- }
193
- if (modules.saveFateIsUnknown()) {
194
- throw refusal("SaveOutcomeUnknown", "The page's last save has an unknown outcome.");
195
- }
196
- if (checkHold && (modules.savesHeld() || modules.isSaveConflicted())) {
197
- throw refusal("SavesHeld", "The page cannot write data while another save hold is active.");
198
- }
199
- if (modules.hasUnsavedChanges()) {
200
- throw refusal("UnsavedChanges", "Save or discard the page's unsaved changes before writing data.");
201
- }
202
- if (!connected(liveSync)) {
203
- throw refusal("SyncRequired", "Page data writes require the sync plugin to be connected.");
204
- }
205
- }
206
-
207
- async function waitForBaseline(modules) {
208
- if (modules.baselineSettled()) return;
209
- await new Promise((resolve) => eventTarget.addEventListener("clay:baseline-settled", resolve, { once: true }));
92
+ function loadWriteEngine() {
93
+ if (writeEngine) return writeEngine;
94
+
95
+ writeEngine = import("./page-data-write.js").then(({ createPageDataWrite }) => createPageDataWrite({
96
+ supports,
97
+ token,
98
+ fetchImpl,
99
+ getLocation,
100
+ eventTarget,
101
+ getLiveSync: () => liveSync,
102
+ loadWriteModules,
103
+ syncTimeoutMs,
104
+ httpTimeoutMs,
105
+ })).catch((error) => {
106
+ writeEngine = null;
107
+ throw error;
108
+ });
109
+
110
+ return writeEngine;
210
111
  }
211
112
 
212
- async function writeData(values, { rules, ifMatch } = {}) {
113
+ async function writeData(values, options) {
213
114
  const draft = await draftRefusal();
214
115
  if (draft) throw draft;
215
116
 
@@ -217,110 +118,8 @@ export function createPageDataApi({
217
118
  throw refusal("DataWriteUnsupported", "This host does not support writing page data.");
218
119
  }
219
120
 
220
- const modules = await loadWriteModules();
221
- if (!modules.isEditMode) {
222
- throw refusal("DataWriteUnavailable", "Page data writes require an editable page.");
223
- }
224
-
225
- await waitForBaseline(modules);
226
- assertWriteState(modules);
227
-
228
- let match = ifMatch;
229
- if (match == null) match = modules.lastSeenEtag() || await modules.seedEtag();
230
- if (typeof match !== "string" || match === "") {
231
- throw refusal("DataWriteVersionRequired", "Page data writes require a trustworthy document ETag.");
232
- }
233
-
234
- assertWriteState(modules);
235
- writeRunning = true;
236
- modules.holdAllSaves(WRITE_HOLD_REASON);
237
-
238
- let waiter = null;
239
- try {
240
- if (modules.isSaveInProgress()) {
241
- throw refusal("SaveInProgress", "The page is already being saved.");
242
- }
243
- if (modules.saveFateIsUnknown()) {
244
- throw refusal("SaveOutcomeUnknown", "The page's last save has an unknown outcome.");
245
- }
246
- if (modules.isSaveConflicted()) {
247
- throw refusal("SavesHeld", "The page cannot write data while another save hold is active.");
248
- }
249
- if (modules.hasUnsavedChanges()) {
250
- throw refusal("UnsavedChanges", "Save or discard the page's unsaved changes before writing data.");
251
- }
252
- if (!connected(liveSync)) {
253
- throw refusal("SyncRequired", "Page data writes require the sync plugin to be connected.");
254
- }
255
-
256
- const headers = {
257
- "Content-Type": "application/json",
258
- "If-Match": match,
259
- };
260
- const currentToken = token();
261
- if (currentToken) headers["Save-Token"] = currentToken;
262
-
263
- const body = JSON.stringify(values);
264
- waiter = syncWaiter(eventTarget, syncTimeoutMs);
265
- const deadline = httpDeadline(httpTimeoutMs);
266
-
267
- let response;
268
- try {
269
- response = await deadline.waitFor(fetchImpl(dataUrl(rules, getLocation()), {
270
- method: "POST",
271
- credentials: "same-origin",
272
- redirect: "error",
273
- headers,
274
- body,
275
- signal: deadline.signal,
276
- }));
277
- } catch (cause) {
278
- deadline.cancel();
279
- writeOutcomeUnknown = true;
280
- throw Object.assign(
281
- refusal("DataWriteOutcomeUnknown", "The page data write has an unknown outcome. Do not retry automatically."),
282
- { cause, unknownOutcome: true }
283
- );
284
- }
285
-
286
- let data;
287
- try {
288
- if (!response.ok) throw await deadline.waitFor(responseError(response));
289
- data = await deadline.waitFor(responseBody(response));
290
- } catch (cause) {
291
- if (cause?.status) throw cause;
292
- writeOutcomeUnknown = true;
293
- throw Object.assign(
294
- refusal("DataWriteOutcomeUnknown", "The page data was written, but its response could not be read."),
295
- { cause, unknownOutcome: true }
296
- );
297
- } finally {
298
- deadline.cancel();
299
- }
300
-
301
- const etag = responseEtag(response);
302
- if (etag && etag === modules.lastSeenEtag()) {
303
- waiter.cancel();
304
- waiter = null;
305
- return { data, etag };
306
- }
307
-
308
- const applied = await waiter.waitFor(etag);
309
- waiter = null;
310
- if (!applied) {
311
- return {
312
- data,
313
- etag,
314
- pageUpdatePending: true,
315
- message: "Written, page update pending.",
316
- };
317
- }
318
- return { data, etag };
319
- } finally {
320
- waiter?.cancel();
321
- writeRunning = false;
322
- modules.releaseAllSaves({ reason: WRITE_HOLD_REASON });
323
- }
121
+ const engine = await loadWriteEngine();
122
+ return engine.writeData(values, options);
324
123
  }
325
124
 
326
125
  return { readData, writeData, setLiveSync };
@@ -0,0 +1,4 @@
1
+ // The page format this runtime writes, and the header every page write declares it
2
+ // in. No imports, so the server bundle and any reader can carry it.
3
+ export const PAGE_FORMAT = 1;
4
+ export const PAGE_FORMAT_HEADER = "X-Clay-Page-Format";
@@ -0,0 +1,58 @@
1
+ // The document format floor as the save wire carries it: the canonical
2
+ // <meta name="clay-page-format" content="1">
3
+ // marker (absence is floor 0, a floor only rises) and the `X-Clay-Page-Format`
4
+ // capability header every page write declares. Core code because every save sends
5
+ // the header; what content needs which floor lives in `page/format-level.js`.
6
+
7
+ import { PAGE_FORMAT } from "./page-format-wire.js";
8
+ export { PAGE_FORMAT, PAGE_FORMAT_HEADER } from "./page-format-wire.js";
9
+ export const PAGE_FORMAT_META = "clay-page-format";
10
+ export const PAGE_FORMAT_EXTENSION = "page-format-v1";
11
+ export const PAGE_FORMAT_UPGRADE = "page-format-upgrade-required";
12
+
13
+ // Every helper here is a plain function with no top-level work, so a client that
14
+ // imports one of them carries only that one.
15
+ const level = (value) => (Number.isSafeInteger(value) && value > 0 ? value : 0);
16
+
17
+ const metaTag = (html) => /<meta\b[^>]*\bname\s*=\s*["']?clay-page-format["']?[^>]*>/i.exec(html);
18
+
19
+ // A capability as a request declares it: a missing or unreadable header is no
20
+ // capability at all, so it can never authorize a write.
21
+ export function formatCapability(value) {
22
+ if (typeof value === "number") return level(value);
23
+ if (typeof value !== "string" || !/^\s*\d+\s*$/.test(value)) return 0;
24
+ return level(Number(value));
25
+ }
26
+
27
+ // The floor a document carries. Absent or unreadable metadata is floor 0.
28
+ export function pageFormatFloor(html) {
29
+ if (typeof html !== "string") return 0;
30
+ const tag = metaTag(html);
31
+ if (!tag) return 0;
32
+ const content = /\bcontent\s*=\s*["']?(\d+)/i.exec(tag[0]);
33
+ return content ? level(Number(content[1])) : 0;
34
+ }
35
+
36
+ // The same document with its floor marker set to `floor`, or with every marker
37
+ // gone at 0. Canonical by construction, so it is idempotent: a document already
38
+ // at that floor comes back unchanged.
39
+ export function withPageFormatFloor(html, floor) {
40
+ const want = level(floor);
41
+ if (typeof html !== "string" || !want) return html;
42
+ const tag = metaTag(html);
43
+ if (tag && pageFormatFloor(tag[0]) === want) return html;
44
+ const marker = `<meta name="${PAGE_FORMAT_META}" content="${want}">`;
45
+ if (tag) return html.slice(0, tag.index) + marker + html.slice(tag.index + tag[0].length);
46
+ const at = /<\/head\s*>/i.exec(html) || /<body\b/i.exec(html);
47
+ return at ? html.slice(0, at.index) + marker + html.slice(at.index) : marker + html;
48
+ }
49
+
50
+ // The floor this tab last saw the document at. A save goes out with its capture
51
+ // repaired to this floor, so a capture taken before activation cannot drop the
52
+ // marker by omitting it.
53
+ let known = 0;
54
+ export const knownPageFormatFloor = () => known;
55
+ export function notePageFormatFloor(value) {
56
+ known = Math.max(known, level(value));
57
+ return known;
58
+ }