@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,265 @@
1
+ /**
2
+ * upload-ui.js — the shared row for one upload task: name, progress, actions.
3
+ *
4
+ * `upload-task.js` owns the upload; this module only draws what that task says and
5
+ * forwards a person's clicks back to it. Journal and Kanban mount the same row, so
6
+ * neither duplicates the controls and neither can drift from the other's behaviour.
7
+ *
8
+ * const unmount = await clay.upload.mount(task, container);
9
+ * unmount();
10
+ *
11
+ * Everything it draws is `clay="editor-ui"`, so none of it reaches a saved file or a
12
+ * sync frame. Nothing here is imported by `upload.js` statically: the row and its
13
+ * stylesheet load the first time somebody mounts one, so a document that never
14
+ * uploads never downloads them.
15
+ *
16
+ * The look is a set of `--clay-upload-*` custom properties, each with a default in
17
+ * its own `var()`. A container that sets one — `.journal-drop { --clay-upload-ink:
18
+ * #333 }` — restyles the row without replacing any of its behaviour.
19
+ */
20
+
21
+ import { canRetry } from "./upload-task.js";
22
+
23
+ const CSS = `.clay-upload {
24
+ display: flex;
25
+ flex-wrap: wrap;
26
+ align-items: center;
27
+ gap: var(--clay-upload-gap, 8px);
28
+ padding: var(--clay-upload-padding, 8px 10px);
29
+ font-family: var(--clay-upload-font, inherit);
30
+ font-size: var(--clay-upload-font-size, 13px);
31
+ line-height: var(--clay-upload-line-height, 1.4);
32
+ color: var(--clay-upload-ink, #171A1F);
33
+ text-align: left;
34
+ background: var(--clay-upload-bg, transparent);
35
+ border: var(--clay-upload-border-width, 0) solid var(--clay-upload-border, #D8D8DE);
36
+ border-radius: var(--clay-upload-radius, 6px);
37
+ box-sizing: border-box;
38
+ max-width: 100%;
39
+ }
40
+ .clay-upload *, .clay-upload *::before, .clay-upload *::after { box-sizing: border-box; }
41
+ .clay-upload__name {
42
+ flex: 1 1 8em;
43
+ min-width: 0;
44
+ overflow: hidden;
45
+ text-overflow: ellipsis;
46
+ white-space: nowrap;
47
+ font-weight: var(--clay-upload-name-weight, 500);
48
+ color: var(--clay-upload-ink, #171A1F);
49
+ }
50
+ .clay-upload__progress {
51
+ flex: 2 1 6em;
52
+ width: auto;
53
+ height: var(--clay-upload-progress-height, 6px);
54
+ margin: 0;
55
+ appearance: none;
56
+ -webkit-appearance: none;
57
+ border: 0;
58
+ border-radius: calc(var(--clay-upload-progress-height, 6px) / 2);
59
+ background: var(--clay-upload-track, rgba(23, 26, 31, .14));
60
+ overflow: hidden;
61
+ }
62
+ .clay-upload__progress::-webkit-progress-bar { background: var(--clay-upload-track, rgba(23, 26, 31, .14)); }
63
+ .clay-upload__progress::-webkit-progress-value { background: var(--clay-upload-fill, #2F6FEB); }
64
+ .clay-upload__progress::-moz-progress-bar { background: var(--clay-upload-fill, #2F6FEB); }
65
+ .clay-upload__label {
66
+ flex: 0 1 auto;
67
+ font-variant-numeric: tabular-nums;
68
+ color: var(--clay-upload-muted, #5C6069);
69
+ }
70
+ .clay-upload__status {
71
+ position: absolute;
72
+ width: 1px;
73
+ height: 1px;
74
+ margin: -1px;
75
+ padding: 0;
76
+ overflow: hidden;
77
+ clip-path: inset(50%);
78
+ white-space: nowrap;
79
+ border: 0;
80
+ }
81
+ .clay-upload__actions { display: flex; flex: none; align-items: center; gap: var(--clay-upload-action-gap, 4px); }
82
+ .clay-upload__button {
83
+ min-height: var(--clay-upload-button-height, 28px);
84
+ padding: var(--clay-upload-button-padding, 4px 10px);
85
+ font: inherit;
86
+ font-weight: var(--clay-upload-button-weight, 500);
87
+ color: var(--clay-upload-button-ink, #2F6FEB);
88
+ background: var(--clay-upload-button-bg, transparent);
89
+ border: 1px solid var(--clay-upload-button-border, transparent);
90
+ border-radius: var(--clay-upload-button-radius, 4px);
91
+ cursor: pointer;
92
+ white-space: nowrap;
93
+ }
94
+ .clay-upload__button:hover { background: var(--clay-upload-button-bg-hover, rgba(47, 111, 235, .1)); }
95
+ .clay-upload__button:focus-visible { outline: 2px solid var(--clay-upload-fill, #2F6FEB); outline-offset: 2px; }
96
+ .clay-upload__button[hidden] { display: none; }
97
+ .clay-upload[data-clay-upload-state="failed"] .clay-upload__label { color: var(--clay-upload-failure-ink, #B3261E); }
98
+ @media (pointer: coarse) {
99
+ .clay-upload__button { min-height: var(--clay-upload-button-height, 44px); }
100
+ }
101
+ @media (prefers-reduced-motion: reduce) {
102
+ .clay-upload__progress { transition: none; }
103
+ }
104
+ @media (prefers-color-scheme: dark) {
105
+ .clay-upload { color: var(--clay-upload-ink, #E9E9EC); border-color: var(--clay-upload-border, #3A3D45); }
106
+ .clay-upload__name { color: var(--clay-upload-ink, #E9E9EC); }
107
+ .clay-upload__label { color: var(--clay-upload-muted, #A2A6AE); }
108
+ .clay-upload__progress { background: var(--clay-upload-track, rgba(233, 233, 236, .2)); }
109
+ .clay-upload__progress::-webkit-progress-bar { background: var(--clay-upload-track, rgba(233, 233, 236, .2)); }
110
+ .clay-upload__button { color: var(--clay-upload-button-ink, #8AB4FF); }
111
+ .clay-upload[data-clay-upload-state="failed"] .clay-upload__label { color: var(--clay-upload-failure-ink, #FFB4AB); }
112
+ }
113
+ @media print { .clay-upload { display: none; } }`;
114
+
115
+ function stylesOnce() {
116
+ if (document.getElementById("clay-upload-style")) return;
117
+ const style = document.createElement("style");
118
+ style.id = "clay-upload-style";
119
+ style.setAttribute("clay", "editor-ui");
120
+ style.textContent = CSS;
121
+ document.head.append(style);
122
+ }
123
+
124
+ function percentOf({ loaded, total, percent }) {
125
+ if (typeof percent === "number") return percent;
126
+ if (typeof total === "number" && total > 0) return Math.round((loaded / total) * 100);
127
+ return null;
128
+ }
129
+
130
+ /**
131
+ * Draw one upload task into a container, and stop when told to.
132
+ *
133
+ * @param {Object} task - from `clay.upload.start`
134
+ * @param {Element|string} container - where the row goes
135
+ * @returns {{unmount: function}}
136
+ */
137
+ export function mount(task, container) {
138
+ if (!task || typeof task.subscribe !== "function") {
139
+ throw new TypeError("clay.upload.mount needs an upload task");
140
+ }
141
+ const host = typeof container === "string" ? document.querySelector(container) : container;
142
+ if (!host || host.nodeType !== 1) {
143
+ throw new TypeError("clay.upload.mount needs a container");
144
+ }
145
+
146
+ stylesOnce();
147
+
148
+ const name = task.file?.name || "File";
149
+
150
+ const root = document.createElement("div");
151
+ root.className = "clay-upload";
152
+ root.setAttribute("clay", "editor-ui");
153
+ root.setAttribute("data-clay-upload-state", "uploading");
154
+ root.innerHTML =
155
+ `<span class="clay-upload__name"></span>` +
156
+ `<progress class="clay-upload__progress"></progress>` +
157
+ `<span class="clay-upload__label"></span>` +
158
+ `<span class="clay-upload__status" role="status" aria-live="polite" aria-atomic="true"></span>` +
159
+ `<span class="clay-upload__actions">` +
160
+ `<button type="button" class="clay-upload__button clay-upload__cancel">Cancel</button>` +
161
+ `<button type="button" class="clay-upload__button clay-upload__retry" hidden>Retry</button>` +
162
+ `<button type="button" class="clay-upload__button clay-upload__dismiss">Dismiss</button>` +
163
+ `</span>`;
164
+
165
+ const nameEl = root.querySelector(".clay-upload__name");
166
+ const progressEl = root.querySelector(".clay-upload__progress");
167
+ const labelEl = root.querySelector(".clay-upload__label");
168
+ const statusEl = root.querySelector(".clay-upload__status");
169
+ const cancelEl = root.querySelector(".clay-upload__cancel");
170
+ const retryEl = root.querySelector(".clay-upload__retry");
171
+ const dismissEl = root.querySelector(".clay-upload__dismiss");
172
+
173
+ nameEl.textContent = name;
174
+ progressEl.setAttribute("aria-label", `Upload progress for ${name}`);
175
+
176
+ // The announcement key, not the text: progress events repeat the same state, and a
177
+ // live region that speaks on every byte is unusable. Only a real transition —
178
+ // started, finishing, done, failed, cancelled — changes what it says.
179
+ let announced = null;
180
+ let lastError = null;
181
+
182
+ function announce(text) {
183
+ if (text === announced) return;
184
+ announced = text;
185
+ statusEl.textContent = text;
186
+ }
187
+
188
+ function draw(snapshot) {
189
+ const { state } = snapshot;
190
+ const uploading = state === "uploading";
191
+ const percent = percentOf(snapshot);
192
+ const finishing = uploading && percent !== null && percent >= 100;
193
+
194
+ root.setAttribute("data-clay-upload-state", state);
195
+
196
+ if (uploading) {
197
+ // A retry draws a row that was showing a failure a moment ago: the progress
198
+ // element comes back, and the old error must not colour the retry button.
199
+ progressEl.hidden = false;
200
+ lastError = null;
201
+ if (percent === null) {
202
+ progressEl.removeAttribute("value");
203
+ progressEl.removeAttribute("max");
204
+ labelEl.textContent = "Uploading";
205
+ } else {
206
+ progressEl.setAttribute("max", "100");
207
+ progressEl.setAttribute("value", String(percent));
208
+ labelEl.textContent = finishing ? "Finishing upload" : `${percent}%`;
209
+ }
210
+ announce(finishing ? `Finishing upload of ${name}` : `Uploading ${name}`);
211
+ } else {
212
+ const message = snapshot.result?.msg || (state === "succeeded" ? "Uploaded" : state === "cancelled" ? "Upload cancelled" : "Upload failed");
213
+ if (state === "succeeded") {
214
+ progressEl.setAttribute("max", "100");
215
+ progressEl.setAttribute("value", "100");
216
+ } else {
217
+ progressEl.removeAttribute("value");
218
+ progressEl.removeAttribute("max");
219
+ }
220
+ progressEl.hidden = state !== "succeeded";
221
+ labelEl.textContent = message;
222
+ if (state === "failed") {
223
+ lastError = message;
224
+ announce(message);
225
+ } else if (state === "cancelled") {
226
+ lastError = null;
227
+ announce("Upload cancelled");
228
+ } else {
229
+ announce(message);
230
+ }
231
+ }
232
+
233
+ cancelEl.hidden = !uploading;
234
+ // Dismissing a running upload would hide it while it still finishes and lands; Cancel is the way out then.
235
+ dismissEl.hidden = uploading;
236
+ retryEl.hidden = !canRetry(snapshot);
237
+ if (!retryEl.hidden) retryEl.setAttribute("aria-label", lastError ? `Retry ${name}: ${lastError}` : `Retry ${name}`);
238
+ }
239
+
240
+ function unmount() {
241
+ unsubscribe();
242
+ root.remove();
243
+ }
244
+
245
+ cancelEl.addEventListener("click", () => {
246
+ if (cancelEl.hidden) return;
247
+ task.cancel();
248
+ });
249
+ retryEl.addEventListener("click", () => {
250
+ if (retryEl.hidden) return;
251
+ retryEl.hidden = true;
252
+ task.retry();
253
+ });
254
+ dismissEl.addEventListener("click", () => {
255
+ if (dismissEl.hidden) return;
256
+ unmount();
257
+ });
258
+
259
+ const unsubscribe = task.subscribe(draw);
260
+ host.append(root);
261
+
262
+ return { unmount };
263
+ }
264
+
265
+ export default mount;
@@ -10,6 +10,12 @@
10
10
  * const result = await clay.upload(file, { onProgress, signal });
11
11
  * // { ok, msg, msgType, code, uploads }
12
12
  *
13
+ * const task = clay.upload.start(file, { requiredAccess: "document-or-local" });
14
+ * const off = task.subscribe(({ state, percent }) => draw(state, percent));
15
+ * await task.result;
16
+ * task.retry();
17
+ * task.dispose();
18
+ *
13
19
  * The result NEVER rejects, matching the save lane. One shape from every exit,
14
20
  * because two channels for three outcomes (stored, cannot store here, failed) is
15
21
  * the exact pattern save-core.js documents as the source of its own past bugs: a
@@ -18,27 +24,20 @@
18
24
  * embedded.
19
25
  *
20
26
  * Codes a caller branches on:
21
- * unsupported this host does not store files; embedding is correct
22
- * payment-required the host stores files, but not for this account
23
- * too-large over the host's cap, refused here or by the host
24
- * unsupported-type the host will not store this kind of file
25
- * timeout no answer; the upload may or may not have landed
27
+ * unsupported this host does not store files; embedding is correct
28
+ * payment-required the host stores files, but not for this account
29
+ * too-large over the host's cap, refused here or by the host
30
+ * unsupported-type the host will not store this kind of file
31
+ * timeout no answer; the upload may or may not have landed
32
+ * privacy-unsupported the host stored (or would store) the file where others can
33
+ * read it, so a URL from it must not be inserted anywhere
34
+ *
35
+ * The single-shot call and the task are the same transport and the same checks; the
36
+ * task adds an identity so progress, cancel and an explicit retry belong to one
37
+ * upload. `upload-task.js` holds both.
26
38
  */
27
39
 
28
- import { hostMeta } from "../core/host-meta.js";
29
- import { saveToken } from "../core/host-attrs.js";
30
-
31
- const UPLOAD_PATH = "/_/upload";
32
- const UPLOAD_TIMEOUT_MS = 120000;
33
-
34
- function result(ok, msg, msgType, code, uploads = []) {
35
- return { ok, msg, msgType, code, uploads };
36
- }
37
-
38
- function emit(name, detail) {
39
- if (typeof document === "undefined") return;
40
- document.dispatchEvent(new CustomEvent(`clay:upload-${name}`, { detail }));
41
- }
40
+ import { checkRequiredAccess, runUpload, start, uploadParticipant } from "./upload-task.js";
42
41
 
43
42
  /**
44
43
  * Store one file on the host.
@@ -47,145 +46,27 @@ function emit(name, detail) {
47
46
  * @param {Object} [options]
48
47
  * @param {function} [options.onProgress] - ({loaded, total, percent})
49
48
  * @param {AbortSignal} [options.signal]
49
+ * @param {?string} [options.requiredAccess] - "document", "local" or "document-or-local":
50
+ * the access the host must promise before the bytes are sent and report afterwards
50
51
  * @returns {Promise<{ok: boolean, msg: string, msgType: string, code: ?string, uploads: Array}>}
51
52
  */
52
- export async function upload(file, { onProgress, signal } = {}) {
53
- if (!file) return result(false, "No file to upload", "error", "bad-request");
54
-
55
- const meta = await hostMeta();
56
- if (!meta.extensions.includes("upload")) {
57
- // Not an error. This is a document open from a plain file server, or a bare
58
- // core host, and embedding is what it should do.
59
- return result(false, "This host does not store uploaded files", "skipped", "unsupported");
60
- }
61
- // The host stores files, but not for this caller on this document: the spec withholds the
62
- // document's upload block by omission, and a client that finds none does not upload. Not
63
- // "unsupported", because embedding instead would route around the host's own decision.
64
- if (!meta.document?.upload) {
65
- return result(false, "You can't add files to this document", "error", "forbidden");
66
- }
67
-
68
- // The one local pre-check the spec asks for, and the only reason `maxBytes` is
69
- // published: refusing a 40 MB photo here costs nothing, and sending it to be
70
- // refused costs the person the whole upload first. Deliberately the ONLY thing
71
- // decided locally. `document.upload.allowed` is not read as a veto: it can be
72
- // stale (a plan upgraded in another tab, a share link granted since load), and
73
- // the host answers the real reason with its own code on the request itself.
74
- const cap = meta.document?.upload?.maxBytes;
75
- if (typeof cap === "number" && cap > 0 && file.size > cap) {
76
- return result(false, `That file is larger than this host accepts (${cap} bytes)`, "error", "too-large");
77
- }
78
-
79
- return send(file, { onProgress, signal });
53
+ export function upload(file, { requiredAccess, ...rest } = {}) {
54
+ // A misspelt requirement is the caller's bug, not the host's answer, and it throws
55
+ // here — before a request exists — rather than resolving as a failure the caller
56
+ // might read as "the host refused" and embed around.
57
+ checkRequiredAccess(requiredAccess);
58
+ return runUpload(file, { ...rest, requiredAccess });
80
59
  }
81
60
 
82
- function send(file, { onProgress, signal }) {
83
- return new Promise((resolve) => {
84
- const token = saveToken();
85
- const path = token ? `${UPLOAD_PATH}/${token}` : UPLOAD_PATH;
86
- // Absolute against the real origin, never left relative: a <base href> in the
87
- // authored document would otherwise redirect this request, and the
88
- // per-document token in its path, to an origin the document chose.
89
- const url = new URL(path, window.location.origin).href;
90
-
91
- // XMLHttpRequest for one reason: fetch cannot report upload progress. A photo
92
- // over a phone connection is the case this whole capability is for, and a
93
- // picker that sits there saying nothing for twenty seconds reads as broken.
94
- const xhr = new XMLHttpRequest();
95
- let settled = false;
96
-
97
- const finish = (value) => {
98
- if (settled) return;
99
- settled = true;
100
- clearTimeout(timeoutId);
101
- if (signal) signal.removeEventListener("abort", onAbort);
102
- resolve(value);
103
- };
61
+ // The shared row (upload-ui.js) loads on first use, so a document that never uploads
62
+ // never downloads it, and clay.upload(file) keeps working untouched.
63
+ upload.mount = async (task, container) => (await import("./upload-ui.js")).mount(task, container);
104
64
 
105
- const fail = (msg, msgType, code) => {
106
- emit("error", { msg, code });
107
- finish(result(false, msg, msgType, code));
108
- };
109
-
110
- const timeoutId = setTimeout(() => {
111
- xhr.abort();
112
- // A timeout is not evidence the upload failed. The bytes may well have
113
- // landed, so this says "we do not know" rather than asserting a failure,
114
- // and the content-hash name makes the retry idempotent if the caller resends.
115
- fail("Upload timed out", "unknown", "timeout");
116
- }, UPLOAD_TIMEOUT_MS);
117
-
118
- const onAbort = () => {
119
- xhr.abort();
120
- finish(result(false, "Upload cancelled", "skipped", "aborted"));
121
- };
122
- if (signal) {
123
- if (signal.aborted) return onAbort();
124
- signal.addEventListener("abort", onAbort);
125
- }
126
-
127
- xhr.upload.addEventListener("progress", (event) => {
128
- if (!event.lengthComputable) return;
129
- const detail = {
130
- loaded: event.loaded,
131
- total: event.total,
132
- percent: Math.round((event.loaded / event.total) * 100)
133
- };
134
- emit("progress", detail);
135
- if (onProgress) {
136
- try { onProgress(detail); } catch (_) { /* a caller's drawing must not kill the upload */ }
137
- }
138
- });
139
-
140
- xhr.addEventListener("load", () => {
141
- let body = {};
142
- try { body = JSON.parse(xhr.responseText || "{}"); } catch (_) { body = {}; }
143
-
144
- if (xhr.status >= 200 && xhr.status < 300) {
145
- const uploads = Array.isArray(body.uploads) ? body.uploads : [];
146
- if (!uploads.length || typeof uploads[0]?.url !== "string") {
147
- return fail("The host accepted the file but did not say where it put it", "error", "bad-response");
148
- }
149
- emit("done", { uploads });
150
- return finish(result(true, body.msg ?? "Uploaded", body.msgType || "success", body.code ?? null, uploads));
151
- }
152
-
153
- // The host's own code when it sent one, so a caller branches on the reason
154
- // rather than pattern-matching a message. Falling back to the status keeps
155
- // a host that answers a bare 413 readable.
156
- const code = body.code || STATUS_CODES[xhr.status] || "error";
157
- return fail(body.msg || `Upload failed (${xhr.status})`, "error", code);
158
- });
159
-
160
- xhr.addEventListener("error", () => fail("Upload failed", "error", "network"));
161
- xhr.addEventListener("abort", () => finish(result(false, "Upload cancelled", "skipped", "aborted")));
162
-
163
- xhr.open("POST", url, true);
164
- // The exact equivalent of the save lane's `credentials: 'omit'` in the case
165
- // where the two differ: a sandboxed document is cross-origin to its own host,
166
- // and asking for credentials there needs Access-Control-Allow-Credentials
167
- // back, which a token-minting host must never send.
168
- xhr.withCredentials = false;
169
- xhr.setRequestHeader("Document-URL", window.location.href);
170
-
171
- // FormData, so the file streams as bytes. No FileReader and no base64: a
172
- // whole-file string costs a third more bytes and holds the entire file in
173
- // memory twice.
174
- const form = new FormData();
175
- form.append("file", file, file.name || "upload");
176
-
177
- emit("start", { name: file.name || null, size: file.size ?? null });
178
- xhr.send(form);
179
- });
180
- }
65
+ // On the same function, so anything holding `clay.upload` has both and every existing
66
+ // `clay.upload(file)` call keeps working untouched.
67
+ upload.start = start;
181
68
 
182
- const STATUS_CODES = {
183
- 402: "payment-required",
184
- 413: "too-large",
185
- 415: "unsupported-type",
186
- 401: "unauthorized",
187
- 403: "forbidden",
188
- 404: "not-found"
189
- };
69
+ // The factory lives with the transport it gates, and this is the path a plan resolves.
70
+ export { uploadParticipant as modeParticipant };
190
71
 
191
72
  export default upload;
@@ -0,0 +1,32 @@
1
+ export const DOWNLOAD_URL = "/_/download?as=htmlclay";
2
+
3
+ export function editOriginal() {
4
+ const url = new URL(window.location.href);
5
+ url.searchParams.set("editmode", "true");
6
+ window.location.href = url.toString();
7
+ return true;
8
+ }
9
+
10
+ export function backToDraft() {
11
+ const url = new URL(window.location.href);
12
+ url.searchParams.delete("editmode");
13
+ window.location.href = url.toString();
14
+ return true;
15
+ }
16
+
17
+ export function inertVisitorDrafts() {
18
+ return {
19
+ active: false,
20
+ slot: null,
21
+ key: null,
22
+ hasDraft: () => false,
23
+ restored: () => null,
24
+ saveNow: () => false,
25
+ reset: () => false,
26
+ onRestore: () => false,
27
+ editOriginal,
28
+ backToDraft,
29
+ downloadUrl: () => DOWNLOAD_URL,
30
+ source: null,
31
+ };
32
+ }
@@ -173,6 +173,9 @@ export function mountOwnerChip() {
173
173
  document.addEventListener("pointerup", lift, true);
174
174
  window.addEventListener("hashchange", lift);
175
175
  lift();
176
+ // Whoever removes the chip takes its listeners back with it: a chip mounted and unmounted
177
+ // once per mode session would otherwise leave six behind each time.
178
+ chip.liftOff = liftOff;
176
179
 
177
180
  return chip;
178
181
  }
@@ -1,4 +1,3 @@
1
- import { PROMPT_CSS } from "./prompt-style.js";
2
1
  import { announceBottomUi, bottomBarHeight } from "./bottom-ui.js";
3
2
 
4
3
  const FONT_HREF = "https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500&family=IBM+Plex+Sans:wght@400;500;600&family=Newsreader:opsz,wght@6..72,500&display=swap";
@@ -24,8 +23,9 @@ const ASIDE = `<aside class="hcp" id="hcp" hidden aria-label="Download this app"
24
23
  </div>
25
24
  </aside>`;
26
25
 
27
- export function mountPrompt({ memory, appName, link }) {
26
+ export async function mountPrompt({ memory, appName, link }) {
28
27
  if (!document.getElementById("hcp-style")) {
28
+ const { PROMPT_CSS } = await import("./prompt-style.js");
29
29
  document.head.insertAdjacentHTML("beforeend", `<link rel="stylesheet" clay="editor-ui" href="${FONT_HREF}">\n<style clay="editor-ui" id="hcp-style">${PROMPT_CSS}</style>`);
30
30
  }
31
31
  if (!document.getElementById("hcp")) document.body.insertAdjacentHTML("beforeend", ASIDE);
@@ -0,0 +1,85 @@
1
+ import { isEditMode, isOwner } from "../core/is-edit-mode.js";
2
+ import { hasSaveToken } from "../core/host-attrs.js";
3
+ import { draftSlot } from "../core/draft-mode.js";
4
+ import { inertVisitorDrafts } from "./visitor-drafts/inert.js";
5
+
6
+ const slot = draftSlot();
7
+ const rulesTag = document.querySelector('script[data-rules-name~="api"]');
8
+
9
+ // The one chip, the mounting on its way to it, and the session that asked: the boot and a
10
+ // mode lease both ask, so a second ask joins the first, and a chip that arrives for a
11
+ // session that has ended is taken down at once.
12
+ let mounted = null;
13
+ let mounting = null;
14
+ let sessions = 0;
15
+
16
+ const chipAllowed = () => isOwner && isEditMode && !hasSaveToken() && !!slot && !!rulesTag;
17
+
18
+ function mountChip() {
19
+ if (mounted) return Promise.resolve(mounted);
20
+ if (!mounting) {
21
+ const asked = sessions;
22
+ mounting = import("./visitor-drafts/owner-chip.js")
23
+ .then(({ mountOwnerChip }) => {
24
+ const chip = mountOwnerChip() || document.getElementById("clay-owner");
25
+ if (asked === sessions) {
26
+ mounted = chip;
27
+ return chip;
28
+ }
29
+ chip?.liftOff?.();
30
+ chip?.remove();
31
+ return null;
32
+ })
33
+ .finally(() => { mounting = null; });
34
+ }
35
+ return mounting;
36
+ }
37
+
38
+ function unmountChip() {
39
+ sessions += 1;
40
+ mounted?.liftOff?.();
41
+ mounted?.remove();
42
+ mounted = null;
43
+ document.getElementById("clay-owner-vars")?.remove();
44
+ }
45
+
46
+ if (!slot || !rulesTag) {
47
+ const missing = [];
48
+ if (!slot) missing.push('a visitor-drafts="<slot>" attribute on <html>');
49
+ if (!rulesTag) missing.push('a <script data-rules-name~="api"> rules tag');
50
+ console.warn(`visitor-drafts: this page keeps no draft (missing ${missing.join(" and ")})`);
51
+ }
52
+
53
+ const ownerChip = chipAllowed() ? mountChip() : Promise.resolve(null);
54
+
55
+ export const ready = ownerChip;
56
+
57
+ /**
58
+ * The owner chip as a mode lease: `enable` mounts it, `initial: true` adopts the one the
59
+ * boot armed and mounts nothing, and `dispose` (after the barrier) takes the chip and the
60
+ * room it published down. The chip holds no input anybody has committed, so `quiesce` and
61
+ * `resume` leave it exactly where it is.
62
+ */
63
+ export function draftsOwnerParticipant() {
64
+ return {
65
+ id: "clay:drafts-owner",
66
+ order: 400,
67
+ prepare(context = {}) {
68
+ return {
69
+ enable() {
70
+ if (context.initial === true) return;
71
+ if (chipAllowed()) mountChip();
72
+ },
73
+ quiesce() {},
74
+ resume() {},
75
+ dispose() {
76
+ unmountChip();
77
+ },
78
+ };
79
+ },
80
+ };
81
+ }
82
+
83
+ export { draftsOwnerParticipant as modeParticipant };
84
+
85
+ export const visitorDrafts = inertVisitorDrafts();