@panphora/clayjs 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (467) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/clay-dom.js +1 -1
  3. package/dist/clay-events.js +1 -1
  4. package/dist/clay-internals.js +1 -1
  5. package/dist/clay-options.js +1 -1
  6. package/dist/clay-router.js +1 -1
  7. package/dist/clay-ui.js +1 -1
  8. package/dist/clay-utils.js +1 -1
  9. package/dist/clay.js +1 -1
  10. package/dist/clay.standalone.js +72474 -62090
  11. package/dist/m/attrs/{save-freeze-MYNVXREN.js → save-freeze-LWGF6XUN.js} +1 -1
  12. package/dist/m/c-2GRUM77Q.js +30 -0
  13. package/dist/m/c-2KIEGNC4.js +1 -0
  14. package/dist/m/{c-AMSNPOCX.js → c-2KL3BD52.js} +1 -1
  15. package/dist/m/c-2ZZBC45O.js +1 -0
  16. package/dist/m/c-372N6H3B.js +1 -0
  17. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  18. package/dist/m/c-3LDGIBMN.js +1 -0
  19. package/dist/m/c-3O7OBDCM.js +1 -0
  20. package/dist/m/c-4ASQPRQL.js +1 -0
  21. package/dist/m/c-4TXNLX4K.js +6 -0
  22. package/dist/m/c-4YU3BR4D.js +6 -0
  23. package/dist/m/c-5734MF2P.js +1 -0
  24. package/dist/m/c-57XDY6HC.js +1 -0
  25. package/dist/m/{c-KAU6E43D.js → c-5B4YBZXC.js} +1 -1
  26. package/dist/m/c-5ONTYIUI.js +1 -0
  27. package/dist/m/c-5OOVFCJM.js +1 -0
  28. package/dist/m/{c-3XIRD4NU.js → c-5T6NZHNM.js} +1 -1
  29. package/dist/m/{c-74MGYWZF.js → c-5ZQV7KC4.js} +1 -1
  30. package/dist/m/{c-7OLLJPWP.js → c-6DB2OLRJ.js} +1 -1
  31. package/dist/m/c-6DNRCPNM.js +1 -0
  32. package/dist/m/c-6UGWFMQC.js +2 -0
  33. package/dist/m/c-7K4SCQB7.js +1 -0
  34. package/dist/m/c-7MG5RU63.js +1 -0
  35. package/dist/m/c-7PRJCA3E.js +1 -0
  36. package/dist/m/c-A6L3FMUS.js +1 -0
  37. package/dist/m/c-AMU3BPPR.js +4 -0
  38. package/dist/m/{c-Y5HBBIQ4.js → c-AMYEFA2E.js} +1 -1
  39. package/dist/m/c-AQYCG7QW.js +1 -0
  40. package/dist/m/c-ATKAOH23.js +1 -0
  41. package/dist/m/c-B5CS55WR.js +1 -0
  42. package/dist/m/c-B5UJQT7W.js +1 -0
  43. package/dist/m/c-B7J4K2AE.js +3 -0
  44. package/dist/m/c-BHZYSHW4.js +1 -0
  45. package/dist/m/{c-VHM5XQHK.js → c-BIJALSRD.js} +1 -1
  46. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  47. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  48. package/dist/m/{c-VE6ZMYMC.js → c-BUAYFEV5.js} +1 -1
  49. package/dist/m/c-C6IPRG34.js +1 -0
  50. package/dist/m/{c-KU3ZFADW.js → c-CAS3NND4.js} +1 -1
  51. package/dist/m/{c-PS5ARVWR.js → c-CCQ25GFY.js} +1 -1
  52. package/dist/m/c-CJFDLD43.js +1 -0
  53. package/dist/m/c-COPTL5WB.js +1 -0
  54. package/dist/m/c-D3E4WJ4M.js +166 -0
  55. package/dist/m/c-D5IKEKXW.js +7 -0
  56. package/dist/m/c-DHBRA6DL.js +1 -0
  57. package/dist/m/c-DKLS74XB.js +72 -0
  58. package/dist/m/c-DVYGFTSK.js +3 -0
  59. package/dist/m/c-E3ZXARWA.js +1 -0
  60. package/dist/m/c-EK63G3CG.js +1 -0
  61. package/dist/m/c-ERQSNR6K.js +13 -0
  62. package/dist/m/c-EVYAISP3.js +1 -0
  63. package/dist/m/c-EXKHPOVC.js +3 -0
  64. package/dist/m/{c-QJ4OM3G6.js → c-FWKHM4E7.js} +1 -1
  65. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  66. package/dist/m/c-GNDL3TKB.js +1 -0
  67. package/dist/m/c-GRWMD6O5.js +1 -0
  68. package/dist/m/c-GRXHHFPO.js +1 -0
  69. package/dist/m/c-HG2C77BM.js +4 -0
  70. package/dist/m/c-HHGJBU4D.js +1 -0
  71. package/dist/m/c-HMV6P4RX.js +1 -0
  72. package/dist/m/c-HMYWIHFG.js +1 -0
  73. package/dist/m/c-HT2DKBV2.js +1 -0
  74. package/dist/m/c-I2SCCUEW.js +1 -0
  75. package/dist/m/{c-TJ6WOJL3.js → c-ICJYHABF.js} +2 -2
  76. package/dist/m/c-IMZCWYHR.js +7 -0
  77. package/dist/m/c-IVMD3FEM.js +1 -0
  78. package/dist/m/{c-WDC5GVKU.js → c-IVTRVENZ.js} +1 -1
  79. package/dist/m/c-JKZJIUIP.js +1 -0
  80. package/dist/m/{c-UDUUJPFZ.js → c-JNLX3RJB.js} +1 -1
  81. package/dist/m/c-K3ADYNLU.js +1 -0
  82. package/dist/m/c-KGHVFRR3.js +1 -0
  83. package/dist/m/{c-5QWQ7A5L.js → c-KJJPV77R.js} +1 -1
  84. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  85. package/dist/m/{c-EZU56NQV.js → c-KP64LT2A.js} +2 -2
  86. package/dist/m/c-KQ7E3PYY.js +1 -0
  87. package/dist/m/c-L3F5ZLIV.js +1 -0
  88. package/dist/m/{c-3RUU4TPX.js → c-L42OLXWM.js} +3 -2
  89. package/dist/m/c-L5VFATT6.js +1 -0
  90. package/dist/m/c-L7JL5TL3.js +3 -0
  91. package/dist/m/c-LKSL4AUJ.js +1 -0
  92. package/dist/m/c-M6FNQCTQ.js +1 -0
  93. package/dist/m/c-MCOOHCSG.js +1 -0
  94. package/dist/m/c-MHI3GYKF.js +1 -0
  95. package/dist/m/c-MWXFLW4J.js +1 -0
  96. package/dist/m/{c-5RJFTLSF.js → c-NEL7HQW3.js} +1 -1
  97. package/dist/m/{c-DCXOXBF3.js → c-NQJRVJ56.js} +1 -1
  98. package/dist/m/c-NWX3MFT4.js +1 -0
  99. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  100. package/dist/m/c-OKXI52N7.js +1 -0
  101. package/dist/m/{c-R3L7ZJGB.js → c-OPLUZR3A.js} +1 -1
  102. package/dist/m/{c-GSFIGKOP.js → c-OTMKN2OO.js} +1 -1
  103. package/dist/m/{c-6NC5I2TV.js → c-OYRZ7XG2.js} +1 -1
  104. package/dist/m/{c-EVCQ3BKA.js → c-P3OFYUMA.js} +1 -1
  105. package/dist/m/c-PLN76GF3.js +6 -0
  106. package/dist/m/c-PLP4JKFC.js +1 -0
  107. package/dist/m/c-Q26MME6N.js +1 -0
  108. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  109. package/dist/m/{c-FRLHSY7E.js → c-QIKT6SKM.js} +1 -1
  110. package/dist/m/c-QIXT3Q7C.js +28 -0
  111. package/dist/m/c-QO4HNGCQ.js +1 -0
  112. package/dist/m/{c-JU3NK25H.js → c-R27DWQKX.js} +1 -1
  113. package/dist/m/{c-RSMOODDU.js → c-R2XUODVY.js} +1 -1
  114. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  115. package/dist/m/c-RND3G2S2.js +1 -0
  116. package/dist/m/{c-Q622NS2Y.js → c-S2U6NJU4.js} +1 -1
  117. package/dist/m/c-SPZPGD3R.js +1 -0
  118. package/dist/m/c-SW4YEG2D.js +8 -0
  119. package/dist/m/c-T637T7SN.js +1 -0
  120. package/dist/m/c-TJAXGIRV.js +1 -0
  121. package/dist/m/{c-RYUXUUBX.js → c-TNEM3RNM.js} +1 -1
  122. package/dist/m/c-TTB5EK4S.js +32 -0
  123. package/dist/m/c-U7SHNWDH.js +1 -0
  124. package/dist/m/c-UDHIVLYH.js +20 -0
  125. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  126. package/dist/m/{c-QICP2YR5.js → c-UORXLSD3.js} +1 -1
  127. package/dist/m/c-UPD33ZPE.js +1 -0
  128. package/dist/m/c-URSMSFCB.js +1 -0
  129. package/dist/m/c-UY4QX42B.js +1 -0
  130. package/dist/m/c-V44KWI2L.js +4 -0
  131. package/dist/m/c-VILENZTD.js +1 -0
  132. package/dist/m/{c-HDDEEL77.js → c-VJYEVRYK.js} +1 -1
  133. package/dist/m/{c-SMBZG5X4.js → c-VR2BJ4AN.js} +1 -1
  134. package/dist/m/c-W4JCVLAB.js +1 -0
  135. package/dist/m/c-W6DQSHXI.js +135 -0
  136. package/dist/m/c-WBGWGOM7.js +1 -0
  137. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  138. package/dist/m/c-WGW764WX.js +1 -0
  139. package/dist/m/c-WMRJKQ2V.js +5 -0
  140. package/dist/m/c-WNTYKCDG.js +1 -0
  141. package/dist/m/c-WQWIRWO3.js +72 -0
  142. package/dist/m/c-X5LNHET5.js +1 -0
  143. package/dist/m/{c-I2ZATQCD.js → c-X5TSXZYJ.js} +2 -2
  144. package/dist/m/c-XGH4GS2T.js +1 -0
  145. package/dist/m/c-Y6F2FORL.js +1 -0
  146. package/dist/m/c-YBKHUAK7.js +10 -0
  147. package/dist/m/c-YBSS55KU.js +20 -0
  148. package/dist/m/c-YCLU4UNT.js +2 -0
  149. package/dist/m/c-YVZ2L7CE.js +1 -0
  150. package/dist/m/c-ZK4GIZH2.js +1 -0
  151. package/dist/m/c-ZRXJD4G6.js +1 -0
  152. package/dist/m/c-ZWBI4HYK.js +1 -0
  153. package/dist/m/core/{admin-attrs-QHOKPJ7W.js → admin-attrs-YY7T5FGU.js} +1 -1
  154. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  155. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  156. package/dist/m/core/{host-permission-FNXVEFNE.js → host-permission-6EVJGNPL.js} +1 -1
  157. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  158. package/dist/m/core/{persist-JFYRHMRJ.js → persist-UKXV5KLA.js} +1 -1
  159. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  160. package/dist/m/core/save-LYY7K6U7.js +1 -0
  161. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  162. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  163. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  164. package/dist/m/internals/{index-PVXGPUDA.js → index-TGLQINN3.js} +1 -1
  165. package/dist/m/loader-3BBNZZGM.js +1 -0
  166. package/dist/m/manifest-F449FF98.js +1 -0
  167. package/dist/m/manifest-edit-4396A009.js +1 -0
  168. package/dist/m/manifest.json +2111 -1573
  169. package/dist/m/page/{editor-NNJA4OKD.js → editor-6LRB3GE4.js} +123 -21
  170. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  171. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  172. package/dist/m/plugins/{demo-RM5IR6AO.js → demo-OYO5GCAO.js} +1 -1
  173. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  174. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  175. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  176. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  177. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  178. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  179. package/dist/m/plugins/{people-SGPJKANK.js → people-JCHVPF7P.js} +1 -1
  180. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  181. package/dist/m/plugins/{richclay-NRQDSWX6.js → richclay-KLGETK77.js} +2 -2
  182. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  183. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  184. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  185. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  186. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  187. package/dist/m/plugins/{upload-WLYKK6TL.js → upload-NHGJXZK2.js} +1 -1
  188. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  189. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  190. package/dist/m/plugins/{wire-4N6GYAF6.js → wire-VBWXEXU2.js} +1 -1
  191. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  192. package/dist/m/route/route-62CHXL5B.js +1 -0
  193. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  194. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  195. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  196. package/dist/offline-manifest.json +1367 -930
  197. package/dist/page/themes/light-1.css +8 -7
  198. package/dist/server/core.cjs +1440 -288
  199. package/dist/server/offline-runtime.cjs +63 -63
  200. package/dist/server/offline-runtime.mjs +63 -63
  201. package/dist/server/page-compaction-worker.cjs +3710 -0
  202. package/dist/server/rooms.mjs +5 -0
  203. package/docs/guide.md +653 -101
  204. package/entries/sap.js +5 -5
  205. package/package.json +1 -1
  206. package/src/core/admin-contenteditable.js +5 -2
  207. package/src/core/admin-inputs.js +1 -0
  208. package/src/core/admin-onclick.js +2 -1
  209. package/src/core/conflict-notice-ui.js +4 -7
  210. package/src/core/download.js +2 -2
  211. package/src/core/host-meta.js +38 -0
  212. package/src/core/is-edit-mode.js +17 -1
  213. package/src/core/mode-key.js +125 -19
  214. package/src/core/mode-support.js +5 -0
  215. package/src/core/mode-transition.js +20 -6
  216. package/src/core/preserve-authored.js +4 -4
  217. package/src/core/recovery.js +121 -33
  218. package/src/core/save-accepted.js +33 -0
  219. package/src/core/save-core.js +106 -31
  220. package/src/core/save-job.js +108 -0
  221. package/src/core/save-prepare.js +53 -0
  222. package/src/core/save-size-notice.js +3 -1
  223. package/src/core/save-slices.js +15 -0
  224. package/src/core/save.js +557 -127
  225. package/src/core/serialize-steps.js +109 -0
  226. package/src/core/snapshot.js +170 -39
  227. package/src/core/source-map.js +83 -38
  228. package/src/core/stale-host-notice.js +1 -0
  229. package/src/core/upgrade-run.js +59 -8
  230. package/src/dom/active-element.js +5 -0
  231. package/src/lib/assets.js +1 -0
  232. package/src/lib/slice.js +65 -0
  233. package/src/lib/uimotion.js +774 -0
  234. package/src/lib/unsaved-state.js +2 -2
  235. package/src/live/fast-lane.js +10 -3
  236. package/src/live/local-lane.js +202 -11
  237. package/src/live/ops.js +16 -0
  238. package/src/live/page-compaction.js +162 -0
  239. package/src/live/server-lane.js +1 -0
  240. package/src/live/store.js +11 -3
  241. package/src/loader-logic.js +3 -1
  242. package/src/loader.js +14 -4
  243. package/src/page/adopt-boot.js +2 -1
  244. package/src/page/awareness.js +33 -2
  245. package/src/page/blocks.js +89 -27
  246. package/src/page/boot-pack.js +1 -1
  247. package/src/page/chart-tables.js +21 -3
  248. package/src/page/chart.js +406 -63
  249. package/src/page/checklist.js +10 -135
  250. package/src/page/checkpoint-proof.js +7 -1
  251. package/src/page/chrome.js +250 -51
  252. package/src/page/clipboard-shape.js +126 -0
  253. package/src/page/drag.js +33 -3
  254. package/src/page/editor.js +136 -25
  255. package/src/page/export-assets.js +357 -0
  256. package/src/page/export-html.js +209 -0
  257. package/src/page/export-markdown.js +361 -0
  258. package/src/page/features/checklist.js +428 -0
  259. package/src/page/features/export-html.js +189 -0
  260. package/src/page/features/export-markdown.js +169 -0
  261. package/src/page/features/import-markdown.js +349 -0
  262. package/src/page/features/media.js +61 -5
  263. package/src/page/features/registry.js +18 -0
  264. package/src/page/features/table.js +373 -83
  265. package/src/page/features/title-cover.js +331 -0
  266. package/src/page/features/title-icon.js +213 -0
  267. package/src/page/features/toc.js +87 -0
  268. package/src/page/format-command.js +25 -1
  269. package/src/page/format-gate.js +68 -18
  270. package/src/page/format-host.js +17 -0
  271. package/src/page/format-native-commands.js +21 -5
  272. package/src/page/image-actions.js +64 -0
  273. package/src/page/import-markdown.js +262 -0
  274. package/src/page/items.js +135 -17
  275. package/src/page/markdown.js +6 -2
  276. package/src/page/media.js +35 -119
  277. package/src/page/placeholder.js +14 -1
  278. package/src/page/projection.js +98 -10
  279. package/src/page/recover.js +10 -12
  280. package/src/page/recovery-ui.js +464 -0
  281. package/src/page/rich/chart.js +63 -7
  282. package/src/page/save-projection.js +28 -0
  283. package/src/page/save-states.js +13 -3
  284. package/src/page/schema.js +19 -3
  285. package/src/page/table-paste.js +3 -1
  286. package/src/page/table-style.js +4 -4
  287. package/src/page/table.js +44 -2
  288. package/src/page/themes/light-1.css +8 -7
  289. package/src/page/themes/light-1.source.css +8 -7
  290. package/src/page/toc-link.js +31 -0
  291. package/src/page/toc.js +5 -0
  292. package/src/page/view-hooks.js +38 -12
  293. package/src/page/view.js +13 -0
  294. package/src/page/ybind/binding.js +88 -5
  295. package/src/page/ybind/bridge.js +187 -20
  296. package/src/page/ybind/containment.js +162 -0
  297. package/src/page/ybind/doc.js +17 -9
  298. package/src/page/ybind/file-debt.js +63 -0
  299. package/src/page/ybind/layout.js +7 -2
  300. package/src/page/ybind/marks.js +1 -1
  301. package/src/page/ybind/moves.js +5 -4
  302. package/src/page/ybind/origins.js +106 -4
  303. package/src/page/ybind/parts.js +8 -1
  304. package/src/page/ybind/provider.js +113 -17
  305. package/src/page/ybind/recovery-proof.js +32 -9
  306. package/src/page/ybind/recovery-replay.js +558 -15
  307. package/src/page/ybind/recovery.js +174 -45
  308. package/src/page/ybind/table.js +67 -11
  309. package/src/page/ybind/walk-delta.js +258 -0
  310. package/src/page/ybind/walk.js +54 -29
  311. package/src/page/ybind/writer.js +1 -1
  312. package/src/plugins/ai-edit.js +13 -9
  313. package/src/plugins/indicator-chip.js +91 -0
  314. package/src/plugins/indicator.js +13 -80
  315. package/src/plugins/live.js +89 -14
  316. package/src/plugins/menu/actions.js +196 -0
  317. package/src/plugins/menu/people.js +279 -0
  318. package/src/plugins/menu/placement.js +121 -0
  319. package/src/plugins/menu/preferences.js +9 -0
  320. package/src/plugins/menu/seo.js +316 -397
  321. package/src/plugins/menu/style.js +171 -0
  322. package/src/plugins/menu/variants.js +296 -0
  323. package/src/plugins/menu.js +186 -183
  324. package/src/plugins/page-view.js +3 -2
  325. package/src/plugins/page.js +249 -44
  326. package/src/plugins/sortable.js +8 -1
  327. package/src/plugins/source.js +6 -0
  328. package/src/plugins/upgrade.js +51 -17
  329. package/src/plugins/upload-ui.js +12 -17
  330. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  331. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  332. package/src/plugins/visitor-drafts/intro.js +5 -3
  333. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  334. package/src/plugins/visitor-drafts/prompt.js +11 -9
  335. package/src/plugins/visitor-drafts/source.js +4 -3
  336. package/src/plugins/visitor-drafts/store.js +138 -9
  337. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  338. package/src/plugins/visitor-drafts-owner.js +4 -6
  339. package/src/plugins/visitor-drafts.js +5 -3
  340. package/src/recovery/store.js +1 -1
  341. package/src/route/route.js +4 -0
  342. package/src/server/rooms/index.js +5 -1
  343. package/src/server/rooms/live-data-rooms.js +302 -50
  344. package/src/server/rooms/page-compaction-worker.js +12 -0
  345. package/src/server/rooms/page-compactor.js +92 -0
  346. package/src/server/rooms/page-room-conformance.js +461 -0
  347. package/src/source/parse-steps.js +94 -0
  348. package/src/source/session.js +233 -57
  349. package/src/sync/editing-sender.js +12 -0
  350. package/src/sync/live-sync-edit.js +22 -2
  351. package/src/sync/live-sync.js +150 -23
  352. package/src/sync/page-relay.js +52 -0
  353. package/src/sync/presence-store.js +44 -4
  354. package/src/sync/presence.js +6 -3
  355. package/src/sync/saved-stream.js +11 -6
  356. package/src/ui/bevel-controls.js +1 -0
  357. package/src/ui/bevel-dialog.js +5 -2
  358. package/src/ui/editor-ui.js +77 -0
  359. package/src/ui/page-colour.js +281 -0
  360. package/src/ui/toast.js +16 -7
  361. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  362. package/src/vendor/hyper-undo.vendor.js +1 -1
  363. package/src/vendor/hypercms.vendor.js +9 -9
  364. package/src/vendor/parse5.vendor.js +2 -2
  365. package/src/vendor/sap-core.vendor.js +5 -5
  366. package/dist/m/c-2SPVQSHC.js +0 -1
  367. package/dist/m/c-336SGU5W.js +0 -1
  368. package/dist/m/c-3H6BZDNJ.js +0 -6
  369. package/dist/m/c-4DFEJ2PO.js +0 -3
  370. package/dist/m/c-56QRZTZH.js +0 -1
  371. package/dist/m/c-5J4E7O4Y.js +0 -4
  372. package/dist/m/c-5LKDWQTL.js +0 -19
  373. package/dist/m/c-5V3MOVJT.js +0 -1
  374. package/dist/m/c-6AZOGAPE.js +0 -1
  375. package/dist/m/c-6CRUUXOX.js +0 -5
  376. package/dist/m/c-6HMB24EV.js +0 -1
  377. package/dist/m/c-6KEVEQ7L.js +0 -1
  378. package/dist/m/c-A3E3NIMC.js +0 -1
  379. package/dist/m/c-BQ6LOBEE.js +0 -1
  380. package/dist/m/c-BTDI4Y23.js +0 -1
  381. package/dist/m/c-CJ3A7ZKE.js +0 -1
  382. package/dist/m/c-CQHPGKX2.js +0 -1
  383. package/dist/m/c-DFS4XZJV.js +0 -2
  384. package/dist/m/c-DNENWY4O.js +0 -1
  385. package/dist/m/c-E4LIR5X5.js +0 -1
  386. package/dist/m/c-FDNHHZY5.js +0 -1
  387. package/dist/m/c-FROJUZMV.js +0 -1
  388. package/dist/m/c-G2JPKCTP.js +0 -1
  389. package/dist/m/c-GB6TU7WV.js +0 -1
  390. package/dist/m/c-GFJJEBTU.js +0 -1
  391. package/dist/m/c-GJAEGGXY.js +0 -1
  392. package/dist/m/c-GOWBDM6J.js +0 -1
  393. package/dist/m/c-HFKUVACH.js +0 -1
  394. package/dist/m/c-HHDFCNHV.js +0 -1
  395. package/dist/m/c-IEM4NTCS.js +0 -1
  396. package/dist/m/c-JDCX26UP.js +0 -25
  397. package/dist/m/c-KGVGROZT.js +0 -1
  398. package/dist/m/c-KQ6TYJAP.js +0 -135
  399. package/dist/m/c-LFPHCXY3.js +0 -1
  400. package/dist/m/c-LRPUO2MX.js +0 -1
  401. package/dist/m/c-LWLBHLVC.js +0 -3
  402. package/dist/m/c-M2L47S2X.js +0 -7
  403. package/dist/m/c-MFP2ZWD4.js +0 -1
  404. package/dist/m/c-MKSQ4Y57.js +0 -1
  405. package/dist/m/c-NUFFJVVJ.js +0 -1
  406. package/dist/m/c-NUHSPT5P.js +0 -1
  407. package/dist/m/c-O72HVYSA.js +0 -1
  408. package/dist/m/c-OIBUTDXI.js +0 -1
  409. package/dist/m/c-OW5QIGG3.js +0 -1
  410. package/dist/m/c-PGRCK73F.js +0 -1
  411. package/dist/m/c-Q7NQD65V.js +0 -1
  412. package/dist/m/c-QH63LQOS.js +0 -1
  413. package/dist/m/c-R7IWVINQ.js +0 -1
  414. package/dist/m/c-RM7J7BV2.js +0 -1
  415. package/dist/m/c-S4ZEH46B.js +0 -9
  416. package/dist/m/c-SDAXDM2D.js +0 -1
  417. package/dist/m/c-SMGU7RT3.js +0 -235
  418. package/dist/m/c-SPIORRYS.js +0 -1
  419. package/dist/m/c-SWLRT66R.js +0 -1
  420. package/dist/m/c-TCJTVAFN.js +0 -1
  421. package/dist/m/c-TQEMVJCS.js +0 -1
  422. package/dist/m/c-TYLXX4NF.js +0 -21
  423. package/dist/m/c-U62OB4BT.js +0 -1
  424. package/dist/m/c-U6YWUAR3.js +0 -8
  425. package/dist/m/c-UDCSU5G5.js +0 -2
  426. package/dist/m/c-VI5RMSVY.js +0 -1
  427. package/dist/m/c-VRZN56XY.js +0 -1
  428. package/dist/m/c-W5MCHEGA.js +0 -1
  429. package/dist/m/c-WNYBLIOZ.js +0 -1
  430. package/dist/m/c-WWXIDH4O.js +0 -28
  431. package/dist/m/c-XCNTK76U.js +0 -1
  432. package/dist/m/c-XMS6DPBR.js +0 -1
  433. package/dist/m/c-ZUCOKOHT.js +0 -1
  434. package/dist/m/c-ZVPVXTJF.js +0 -4
  435. package/dist/m/c-ZVRKXKM7.js +0 -1
  436. package/dist/m/core/autosave-HRYGUWV6.js +0 -1
  437. package/dist/m/core/conflict-notice-6MROJN2L.js +0 -1
  438. package/dist/m/core/is-edit-mode-KTWA3O5N.js +0 -1
  439. package/dist/m/core/recovery-FLTCC2OC.js +0 -1
  440. package/dist/m/core/save-HQZ4S667.js +0 -1
  441. package/dist/m/core/save-core-KGQWFUNH.js +0 -1
  442. package/dist/m/core/snapshot-EGQZNT45.js +0 -1
  443. package/dist/m/core/unsaved-warning-IDJLD66H.js +0 -1
  444. package/dist/m/loader-S4I7GKXR.js +0 -1
  445. package/dist/m/manifest-4C419D67.js +0 -1
  446. package/dist/m/manifest-edit-B077F288.js +0 -1
  447. package/dist/m/plugins/ai-edit-QN4PMMNL.js +0 -6
  448. package/dist/m/plugins/indicator-URDBBOQO.js +0 -1
  449. package/dist/m/plugins/live-HFWD4OIH.js +0 -1
  450. package/dist/m/plugins/menu-3HIEQSI6.js +0 -138
  451. package/dist/m/plugins/page-JOKB6XPR.js +0 -2
  452. package/dist/m/plugins/page-view-NXOJHNW6.js +0 -1
  453. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  454. package/dist/m/plugins/sortable-GWT4PWDV.js +0 -1
  455. package/dist/m/plugins/source-4ZRSAIYM.js +0 -1
  456. package/dist/m/plugins/undo-VDA5P7FH.js +0 -1
  457. package/dist/m/plugins/upgrade-W7FG5VFG.js +0 -1
  458. package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +0 -21
  459. package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +0 -1
  460. package/dist/m/reader-core-DXYCURFF.js +0 -68
  461. package/dist/m/route/route-KOCZSEGN.js +0 -1
  462. package/dist/m/sync/live-sync-J7VMYTKS.js +0 -1
  463. package/dist/m/ui/index-3JLVI27O.js +0 -1
  464. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  465. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  466. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  467. /package/dist/m/plugins/{richclay-NRQDSWX6.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
@@ -9,7 +9,7 @@ import { contains, parseBy, frameSet, descendants, unionRanges, formatBy } from
9
9
  import { createStore } from "../live/store.js";
10
10
  import { serializeLive, stampOf, parseStamp, textHash } from "../live/ops.js";
11
11
  import { capturePageState, onSnapshot } from "../core/snapshot.js";
12
- import { onSaveAccepted, onSaveAttempt, onSaveOutcome, isSaveInProgress } from "../core/save-core.js";
12
+ import { onSaveAccepted, onSaveAttempt, onSaveOutcome, isSaveInProgress, saveFateIsUnknown, exitSave } from "../core/save-core.js";
13
13
  import { savePageForce, baselineSettled, isSaveConflicted, autosaveIsSuspended } from "../core/save.js";
14
14
  import { withKeepalive } from "../core/save-core.js";
15
15
  import { isEditMode } from "../core/is-edit-mode.js";
@@ -17,13 +17,13 @@ import { registerUnsavedState } from "../lib/unsaved-state.js";
17
17
  import { createCheckpoints } from "../live/checkpoint.js";
18
18
  import { parseJsonRelaxed } from "../vendor/hyper-morph-json.vendor.js";
19
19
  import * as hostMeta from "../core/host-meta.js";
20
- import { createFastLane } from "../live/fast-lane.js";
21
20
  import { createSaveHistoryRecovery, RECOVERY_WINDOW_MS } from "../live/save-history.js";
22
21
  import { serverTransport } from "../live/server-lane.js";
23
- import { localTransport, localLaneSupported } from "../live/local-lane.js";
24
22
  import { captureYValue } from "../page/ybind/parts.js";
25
23
  import { lastSeenEtag, recordEtag } from "../core/etag.js";
26
24
 
25
+ const lanes = () => Promise.all([import("../live/fast-lane.js"), import("../live/local-lane.js")]).then(([f, l]) => ({ createFastLane: f.createFastLane, localTransport: l.localTransport, localLaneSupported: l.localLaneSupported }));
26
+
27
27
  const NAME = /^[A-Za-z0-9_.:-]{1,64}$/;
28
28
  const entries = new Map();
29
29
 
@@ -755,6 +755,9 @@ function seedLocalRoom() {
755
755
  if (value !== null && typeof value === "object") values.set(name, { value, ephemeral: tag.ephemeral });
756
756
  } catch {}
757
757
  }
758
+ for (const [name, entry] of entries) {
759
+ if (!entry.staged) values.set(name, { value: entry.store.authoritative, ephemeral: entry.store.ephemeral });
760
+ }
758
761
  return values;
759
762
  }
760
763
 
@@ -769,13 +772,22 @@ function ensureLane() {
769
772
  if (request !== laneRequest) return;
770
773
  serverLane = supported;
771
774
  provenanceHost = provenance;
775
+ const lane = await lanes().catch(() => null);
776
+ if (request !== laneRequest) return;
777
+ if (!lane) {
778
+ settleLane?.(null);
779
+ settleWriter?.(null);
780
+ return;
781
+ }
772
782
  if (supported) {
773
- fast = createFastLane(laneOptions(serverTransport()));
774
- } else if (localLaneSupported()) {
783
+ fast = lane.createFastLane(laneOptions(serverTransport()));
784
+ } else if (lane.localLaneSupported()) {
775
785
  // The document's own floor picks the channel scope: a floor-1 document
776
- // never shares the lane a tab that cannot read it is on.
777
- local = localTransport({ seed: seedLocalRoom, floor: document.querySelector('meta[name="clay-page-format"]')?.getAttribute("content") });
778
- fast = createFastLane(laneOptions(local));
786
+ // never shares the lane a tab that cannot read it is on. The room scope
787
+ // puts this tab's sequencer in the host-mode room, which a tab of the lane
788
+ // before it never joins.
789
+ local = lane.localTransport({ seed: seedLocalRoom, rooms: true, floor: document.querySelector('meta[name="clay-page-format"]')?.getAttribute("content") });
790
+ fast = lane.createFastLane(laneOptions(local));
779
791
  } else {
780
792
  settleLane?.(null);
781
793
  settleWriter?.(null);
@@ -878,7 +890,9 @@ function receive(entry, text, source, dropIds) {
878
890
  } catch {
879
891
  return;
880
892
  }
881
- if (entry.store.rebase(value, { source, ...(dropIds?.length && { dropIds }) })) entry.framesSince = true;
893
+ if (!entry.store.rebase(value, { source, ...(dropIds?.length && { dropIds }) })) return false;
894
+ entry.framesSince = true;
895
+ return true;
882
896
  }
883
897
 
884
898
  // One open store and what the plugin follows about it. A staged store has no lane.
@@ -934,11 +948,12 @@ export function live(name) {
934
948
 
935
949
  // A page whose file carries no state of its own yet: the seed in memory, no tag in
936
950
  // the document, no request, until its first genuine op is written by the ordinary
937
- // save. Only an accepted write opens its room.
938
- export function stageStore(name, { value, represented = null, signal = null } = {}) {
951
+ // save. A value already stored by this tab or a peer can open its room.
952
+ export function stageStore(name, { value, represented = null, signal = null, persisted = false } = {}) {
939
953
  const existing = entries.get(name);
940
954
  if (existing && !existing.staged) throw new Error(`clay.live("${name}"): the store is open in a room`);
941
955
  const entry = existing ?? openEntry(name, createStore({ name, value, ephemeral: false }), { staged: { represented, signal } });
956
+ if (persisted) activateStaged(entry);
942
957
  return {
943
958
  handle: entry.store.handle,
944
959
  isPending: () => !!entry.staged && entry.store.pending.length > 0,
@@ -1359,9 +1374,7 @@ export function receiveFrame(html, seq = null) {
1359
1374
  else if (pageTag.getAttribute("live-by") !== by) pageTag.setAttribute("live-by", by);
1360
1375
  }
1361
1376
  if (entry && !entry.store.ephemeral) {
1362
- // An old seed is never merged into a newer article before its first write.
1363
- if (entry.staged) continue;
1364
- if (entry.store.handle.status.mode === "file") receive(entry, text, "remote", dropIds);
1377
+ if (entry.store.handle.status.mode === "file" && receive(entry, text, "remote", dropIds)) activateStaged(entry);
1365
1378
  continue;
1366
1379
  }
1367
1380
  if (pageTag && pageTag.textContent !== text) pageTag.textContent = text;
@@ -1414,12 +1427,74 @@ export function fastLane() {
1414
1427
  return fast;
1415
1428
  }
1416
1429
 
1430
+ // The local lane's transport, for the format gate: a floor-1 document shares a
1431
+ // channel scope no tab below that floor is on, and moving this tab there is the
1432
+ // gate's call (page/format-gate.js). Null until this tab has such a lane, and a
1433
+ // gate asks again on its next retry.
1434
+ // Whether any live store of this tab holds ops the room has not confirmed. A tab with
1435
+ // none has nothing a scope switch could strand, so it may move without a receipt.
1436
+ export function lanePending() {
1437
+ return sessionStores().some((entry) => entry.store.pending.length > 0);
1438
+ }
1439
+
1440
+ export function localLane() {
1441
+ return local;
1442
+ }
1443
+
1417
1444
  // The page editor's pause save: checkpoint within ms of now. Only the room's
1418
1445
  // writer saves, so in other tabs this changes nothing.
1419
1446
  export function checkpointWithin(ms) {
1420
1447
  checkpoints.within(ms);
1421
1448
  }
1422
1449
 
1450
+ // The exit's own checkpoint: this tab's page input, saved once, under the permit the
1451
+ // exit minted and inside its deadline. It is not the background checkpoint: no timer is
1452
+ // armed and writer election is untouched, because a leave is an explicit save of this
1453
+ // tab's work, like the force save behind Cmd+S.
1454
+ //
1455
+ // One attempt per exit. A caller whose page input is still unconfirmed gets the answer
1456
+ // that names it, never a second write: an expired exit must not send anything later.
1457
+ export async function requestExitCheckpoint({ name, needsSave, permit, deadline, signal = null, keepalive = false }) {
1458
+ const over = () => signal?.aborted || Date.now() >= deadline;
1459
+ // Abortable, and it clears its own timer, so nothing is left waiting after the exit.
1460
+ const pause = () => new Promise((resolve) => {
1461
+ const done = () => {
1462
+ clearTimeout(timer);
1463
+ signal?.removeEventListener("abort", done);
1464
+ resolve();
1465
+ };
1466
+ const timer = setTimeout(done, 5);
1467
+ signal?.addEventListener("abort", done, { once: true });
1468
+ });
1469
+ const failed = (code) => ({ ok: false, code });
1470
+ for (;;) {
1471
+ if (over()) return failed("timeout");
1472
+ if (!needsSave()) return { ok: true };
1473
+ if (isSaveConflicted()) return failed("conflict");
1474
+ if (saveFateIsUnknown() || historyPending()) return failed("save-unknown");
1475
+ const entry = entries.get(name);
1476
+ if (!entry) return failed("disposed");
1477
+ if (!baselineSettled() || isSaveInProgress()) {
1478
+ await pause();
1479
+ continue;
1480
+ }
1481
+ // A fast capture holds the room's value, so this tab's own ops have to reach the
1482
+ // room before the capture is taken.
1483
+ if (entry.store.handle.status.mode === "fast" && entry.store.pending.length) {
1484
+ fast?.release(name);
1485
+ await pause();
1486
+ continue;
1487
+ }
1488
+ if (over()) return failed("timeout");
1489
+ const save = exitSave();
1490
+ if (!save) return failed("checkpoint-failed");
1491
+ const result = await withKeepalive(keepalive, () => save(permit));
1492
+ if (over()) return failed("timeout");
1493
+ if (!result?.ok) return failed(result?.code ?? "checkpoint-failed");
1494
+ return needsSave() ? failed("page-not-checkpointed") : { ok: true };
1495
+ }
1496
+ }
1497
+
1423
1498
  // Resolves { ok: true } once the file holds every live change this tab knows
1424
1499
  // of now, or { ok: false, code: "timeout" }. The writer saves as soon as it
1425
1500
  // can; another tab waits for the writer's save to land.
@@ -0,0 +1,196 @@
1
+ // The menu's editing items: Undo and Redo, Ask AI, and Download a copy. Each one calls
2
+ // the service that already does the job and keeps no second copy of its state.
3
+ import { modeRefusalText } from "../../core/mode-refusal.js";
4
+ import { downloadHtml, documentName } from "../../core/download.js";
5
+ import { keys, revealLabel } from "./preferences.js";
6
+
7
+ const RESUME_KEY = "menu-resume";
8
+ const READY_MS = 5000;
9
+
10
+ const clay = () => window.clay || {};
11
+ const session = () => clay().storage && clay().storage.session;
12
+ const pageWants = (name) => {
13
+ const names = clay().editPlugins;
14
+ return Array.isArray(names) && names.includes(name);
15
+ };
16
+
17
+ // An edit-only plugin attaches after the mode change commits, so the item waits for it
18
+ // briefly instead of reporting it missing.
19
+ function whenPresent(get, ms = READY_MS) {
20
+ return new Promise((resolve) => {
21
+ const start = Date.now();
22
+ const tick = () => {
23
+ const value = get();
24
+ if (value || Date.now() - start >= ms) resolve(value || null);
25
+ else setTimeout(tick, 50);
26
+ };
27
+ tick();
28
+ });
29
+ }
30
+
31
+ export function attachActions({ addItem, note, currentMode }) {
32
+ const resumers = new Map();
33
+
34
+ // Runs `run` in edit mode, entering it first when the page is in view. A reload entry
35
+ // leaves a one-shot receipt in session storage so the action runs once the page is
36
+ // back; a refused entry says why and runs nothing.
37
+ async function inEditMode(action, run) {
38
+ if (currentMode() === "edit") return run();
39
+ const storage = session();
40
+ if (storage) storage.set(RESUME_KEY, action);
41
+ let result;
42
+ try {
43
+ result = await clay().setMode?.("edit");
44
+ } catch (err) {
45
+ result = { ok: false, message: err && err.message };
46
+ }
47
+ if (result && result.ok !== false && result.reloading) return;
48
+ if (storage) storage.remove(RESUME_KEY);
49
+ if (!result || result.ok === false) {
50
+ const text = result ? modeRefusalText(result) : "Couldn't switch modes.";
51
+ if (text) note(text);
52
+ return;
53
+ }
54
+ return run();
55
+ }
56
+
57
+ function resumable(action, run) {
58
+ resumers.set(action, run);
59
+ return () => inEditMode(action, run);
60
+ }
61
+
62
+ let history = { canUndo: false, canRedo: false };
63
+ let heard = false;
64
+
65
+ function renderUndo() {
66
+ if (!pageWants("undo")) return;
67
+ const live = currentMode() === "edit" && clay().undo;
68
+ // The menu loads late, so edits made before it are read from the service itself.
69
+ if (!heard && live && typeof live.canUndo === "boolean") history = { canUndo: live.canUndo, canRedo: !!live.canRedo };
70
+ addItem({
71
+ id: "clay.undo",
72
+ pair: [
73
+ {
74
+ id: "clay.undo.undo",
75
+ label: "Undo",
76
+ keys: keys("⌘Z", "Ctrl+Z"),
77
+ disabled: !(live && history.canUndo),
78
+ run: () => clay().undo?.undo(),
79
+ },
80
+ {
81
+ id: "clay.undo.redo",
82
+ label: "Redo",
83
+ keys: keys("⌘⇧Z", "Ctrl+Shift+Z"),
84
+ disabled: !(live && history.canRedo),
85
+ run: () => clay().undo?.redo(),
86
+ },
87
+ ],
88
+ });
89
+ }
90
+
91
+ document.addEventListener("clay:undo-change", (event) => {
92
+ const detail = event.detail || {};
93
+ heard = true;
94
+ history = { canUndo: !!detail.canUndo, canRedo: !!detail.canRedo };
95
+ renderUndo();
96
+ });
97
+ document.addEventListener("clay:modechange", renderUndo);
98
+ renderUndo();
99
+
100
+ if (pageWants("ai-edit")) {
101
+ addItem({
102
+ id: "clay.ai",
103
+ label: "Ask AI…",
104
+ keys: keys("⌘J", "Ctrl+J"),
105
+ run: resumable("ai", async () => {
106
+ const ai = await whenPresent(() => clay().aiEdit);
107
+ const opened = ai ? await ai.open(document.body) : false;
108
+ if (!opened) note("Ask AI isn't available on this page right now.");
109
+ }),
110
+ });
111
+ }
112
+
113
+ if (pageWants("cms") && clay().cms?.hasRules?.(document)) {
114
+ addItem({
115
+ id: "clay.cms",
116
+ label: "Open the CMS (Edit content)",
117
+ run: resumable("cms", async () => {
118
+ const cms = await whenPresent(() => clay().cms);
119
+ let result = null;
120
+ try {
121
+ result = cms ? await cms.open() : { ok: false };
122
+ } catch {
123
+ result = { ok: false };
124
+ }
125
+ if (result && result.ok === false) note("Couldn't open the CMS. Try again.");
126
+ }),
127
+ });
128
+ }
129
+
130
+ // The copy is what a save would send now, unsaved edits included. A reader has no
131
+ // unsaved edits, so the saved file is the same document.
132
+ addItem({
133
+ id: "clay.download",
134
+ label: "Download a copy",
135
+ run: async () => {
136
+ let html = null;
137
+ try {
138
+ if (typeof clay().getHTML === "function") {
139
+ html = clay().getHTML();
140
+ } else {
141
+ const response = await fetch(location.href, { cache: "no-store", credentials: "same-origin" });
142
+ if (response.ok) html = await response.text();
143
+ }
144
+ } catch {}
145
+ // Upload paths only resolve on this host, so the copy names them in full.
146
+ if (typeof html === "string" && /^https?:$/.test(location.protocol)) {
147
+ html = html.replace(/(^|[^\w./:-])\/_\/uploads\//g, `$1${location.origin}/_/uploads/`);
148
+ }
149
+ if (typeof html !== "string" || !downloadHtml({ html, name: `${documentName()}.html` })) {
150
+ note("Couldn't build the download. Try again.");
151
+ }
152
+ },
153
+ });
154
+
155
+ // What the host offers for this document. A page opens in a new tab so this one keeps
156
+ // whatever is unsaved; anything else the host does itself when asked, and may answer
157
+ // with a page to open (a duplicate) or a reason it could not.
158
+ async function askHost(url) {
159
+ let body = null;
160
+ try {
161
+ const response = await fetch(url, { method: "POST", credentials: "same-origin", headers: { "Document-URL": location.href } });
162
+ body = await response.json().catch(() => null);
163
+ if (response.ok && body?.ok !== false) {
164
+ if (typeof body?.href === "string") window.open(body.href, "_blank", "noopener");
165
+ return;
166
+ }
167
+ } catch {}
168
+ note(typeof body?.message === "string" && body.message ? body.message : "Couldn't do that. Try again.");
169
+ }
170
+
171
+ const hostItem = (id, label, action) => {
172
+ if (action?.href) addItem({ id, label, href: action.href });
173
+ else if (action?.post) addItem({ id, label, run: () => askHost(action.post) });
174
+ };
175
+
176
+ import("../../core/host-meta.js")
177
+ .then(({ hostActions }) => hostActions())
178
+ .then((host) => {
179
+ hostItem("clay.code", "Open the code editor", host.codeEditor);
180
+ hostItem("clay.duplicate", "Duplicate as a new page", host.duplicate);
181
+ hostItem("clay.export", "Export as zip", host.export);
182
+ hostItem("clay.reveal", revealLabel(), host.reveal);
183
+ hostItem("clay.versions", "Version history…", host.versions);
184
+ })
185
+ .catch(() => {});
186
+
187
+ const storage = session();
188
+ const pending = storage && storage.get(RESUME_KEY);
189
+ if (pending) {
190
+ storage.remove(RESUME_KEY);
191
+ const run = resumers.get(pending);
192
+ if (run && currentMode() === "edit") run();
193
+ }
194
+
195
+ return { inEditMode, resumable, refresh: renderUndo };
196
+ }
@@ -0,0 +1,279 @@
1
+ // Who's here: a menu item and, on a page that opts in with `<html clay-menu-roster>`, a
2
+ // roster beside the capsule. Either opens the people panel, which lists the people on
3
+ // this page and lets you change the name your edits are signed with when this browser
4
+ // chose it. The roster comes from the presence store; nothing here is ever saved.
5
+ import { initialsOf, personColor } from "../../lib/person-face.js";
6
+
7
+ const CROSS =
8
+ '<svg viewBox="0 0 256 256" aria-hidden="true" focusable="false"><path d="M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128 50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z"/></svg>';
9
+ const ROSTER_FACES = 3;
10
+
11
+ const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
12
+
13
+ function face(person) {
14
+ const el = document.createElement("span");
15
+ el.className = "clay-avatar";
16
+ el.setAttribute("aria-hidden", "true");
17
+ const color = personColor(person.id);
18
+ el.style.setProperty("color", color);
19
+ el.style.setProperty("border-color", color);
20
+ el.style.setProperty("background", "var(--bevel-surface)");
21
+ el.textContent = initialsOf(person.name);
22
+ return el;
23
+ }
24
+
25
+ export function attachPeople({ root, capsule, addItem, removeItem, closeMenu, note }) {
26
+ let snapshot = { supported: false, people: [], anonymous: 0 };
27
+ let panel = null;
28
+ let opener = null;
29
+ let roster = null;
30
+ let renaming = false;
31
+
32
+ const people = () => window.clay && window.clay.people;
33
+ const me = () => (window.clay && window.clay.me) || null;
34
+ const canRename = () => {
35
+ try {
36
+ return !!people()?.canRename?.();
37
+ } catch {
38
+ return false;
39
+ }
40
+ };
41
+ const total = () => snapshot.people.length + snapshot.anonymous;
42
+ const wantsRoster = () => document.documentElement.hasAttribute("clay-menu-roster");
43
+
44
+ function closePanel({ restore = true } = {}) {
45
+ if (!panel) return;
46
+ panel.remove();
47
+ panel = null;
48
+ renaming = false;
49
+ roster?.setAttribute("aria-expanded", "false");
50
+ if (restore && opener && opener.isConnected) opener.focus();
51
+ opener = null;
52
+ }
53
+
54
+ function meSection() {
55
+ const mine = me();
56
+ if (!mine || !mine.name) return null;
57
+ const section = document.createElement("div");
58
+ section.className = "clay-people-me";
59
+ const label = document.createElement("span");
60
+ label.className = "clay-people-me-label";
61
+ label.textContent = "Your edits are signed as";
62
+ const row = document.createElement("div");
63
+ row.className = "clay-people-me-row";
64
+ section.append(label, row);
65
+
66
+ if (renaming) {
67
+ const input = document.createElement("input");
68
+ input.className = "clay-people-input";
69
+ input.type = "text";
70
+ input.value = mine.name;
71
+ input.setAttribute("aria-label", "Your name");
72
+ input.maxLength = 120;
73
+ const save = document.createElement("button");
74
+ save.type = "button";
75
+ save.className = "clay-menu-btn clay-menu-btn-small clay-menu-btn-primary";
76
+ save.textContent = "Save";
77
+ const commit = () => {
78
+ try {
79
+ people().rename(input.value);
80
+ } catch (err) {
81
+ note(err && err.code === "not-renamable" ? "This name comes from your account, so it can't be changed here." : "Use a name of 1 to 120 characters that isn't an email address.");
82
+ input.focus();
83
+ return;
84
+ }
85
+ renaming = false;
86
+ draw();
87
+ panel?.querySelector(".clay-people-me .clay-menu-btn")?.focus();
88
+ };
89
+ save.addEventListener("click", commit);
90
+ input.addEventListener("keydown", (event) => {
91
+ if (event.key === "Enter") {
92
+ event.preventDefault();
93
+ commit();
94
+ }
95
+ });
96
+ row.append(input, save);
97
+ queueMicrotask(() => {
98
+ if (!input.isConnected) return;
99
+ input.focus();
100
+ input.select();
101
+ });
102
+ } else {
103
+ const name = document.createElement("span");
104
+ name.className = "clay-people-me-name";
105
+ name.textContent = mine.name;
106
+ row.append(face(mine), name);
107
+ if (canRename()) {
108
+ const change = document.createElement("button");
109
+ change.type = "button";
110
+ change.className = "clay-menu-btn clay-menu-btn-small";
111
+ change.textContent = "Change";
112
+ change.addEventListener("click", () => {
113
+ renaming = true;
114
+ draw();
115
+ });
116
+ row.append(change);
117
+ }
118
+ }
119
+ return section;
120
+ }
121
+
122
+ function draw() {
123
+ if (!panel) return;
124
+ const count = total();
125
+ panel.textContent = "";
126
+
127
+ const head = document.createElement("div");
128
+ head.className = "clay-people-head";
129
+ const title = document.createElement("h3");
130
+ title.className = "clay-people-title";
131
+ title.textContent = "Who's here";
132
+ const sum = document.createElement("span");
133
+ sum.className = "clay-people-sum";
134
+ sum.textContent = plural(count, "person", "people");
135
+ const x = document.createElement("button");
136
+ x.type = "button";
137
+ x.className = "clay-people-close";
138
+ x.setAttribute("aria-label", "Close");
139
+ x.innerHTML = CROSS;
140
+ x.addEventListener("click", () => closePanel());
141
+ head.append(title, sum, x);
142
+ panel.append(head);
143
+
144
+ if (snapshot.people.length) {
145
+ const list = document.createElement("ul");
146
+ list.className = "clay-people-list";
147
+ for (const person of snapshot.people) {
148
+ const row = document.createElement("li");
149
+ row.className = "clay-people-row";
150
+ const who = document.createElement("span");
151
+ who.className = "clay-people-who";
152
+ const name = document.createElement("strong");
153
+ name.textContent = person.name;
154
+ if (person.you) {
155
+ const you = document.createElement("span");
156
+ you.textContent = "(you)";
157
+ name.append(you);
158
+ }
159
+ who.append(name);
160
+ row.append(face(person), who);
161
+ list.append(row);
162
+ }
163
+ panel.append(list);
164
+ }
165
+
166
+ if (snapshot.anonymous > 0) {
167
+ const anon = document.createElement("p");
168
+ anon.className = "clay-people-anon";
169
+ anon.textContent =
170
+ snapshot.anonymous === 1
171
+ ? "1 other person is here without a shared name."
172
+ : `${snapshot.anonymous} others are here without a shared name.`;
173
+ panel.append(anon);
174
+ }
175
+
176
+ const mine = meSection();
177
+ if (mine) panel.append(mine);
178
+ }
179
+
180
+ function openPanel(from) {
181
+ closeMenu();
182
+ if (panel) {
183
+ closePanel();
184
+ return;
185
+ }
186
+ opener = from;
187
+ panel = document.createElement("div");
188
+ panel.className = "clay-people";
189
+ panel.setAttribute("role", "dialog");
190
+ panel.setAttribute("aria-label", "Who's here");
191
+ panel.addEventListener("keydown", (event) => {
192
+ if (event.key !== "Escape") return;
193
+ event.preventDefault();
194
+ event.stopPropagation();
195
+ if (renaming) {
196
+ renaming = false;
197
+ draw();
198
+ panel.querySelector(".clay-people-me .clay-menu-btn")?.focus();
199
+ } else {
200
+ closePanel();
201
+ }
202
+ });
203
+ draw();
204
+ root.append(panel);
205
+ roster?.setAttribute("aria-expanded", from === roster ? "true" : "false");
206
+ panel.querySelector(".clay-people-close").focus();
207
+ }
208
+
209
+ function drawRoster() {
210
+ const show = wantsRoster() && snapshot.supported && total() > 1;
211
+ if (!show) {
212
+ roster?.remove();
213
+ roster = null;
214
+ return;
215
+ }
216
+ if (!roster) {
217
+ roster = document.createElement("button");
218
+ roster.type = "button";
219
+ roster.className = "clay-roster";
220
+ roster.setAttribute("aria-haspopup", "dialog");
221
+ roster.setAttribute("aria-expanded", panel ? "true" : "false");
222
+ roster.addEventListener("click", () => openPanel(roster));
223
+ root.insertBefore(roster, capsule);
224
+ }
225
+ const shown = snapshot.people.slice(0, ROSTER_FACES);
226
+ const rest = total() - shown.length;
227
+ const faces = document.createElement("span");
228
+ faces.className = "clay-roster-faces";
229
+ for (const person of shown) faces.append(face(person));
230
+ roster.textContent = "";
231
+ roster.append(faces);
232
+ if (rest > 0) roster.append(plural(rest, "other", "others"));
233
+ const names = shown.map((person) => person.name);
234
+ roster.setAttribute("aria-label", `Who's here: ${names.join(", ")}${rest > 0 ? `${names.length ? " and " : ""}${plural(rest, "other", "others")}` : ""}`);
235
+ }
236
+
237
+ let itemLabel = null;
238
+
239
+ function update() {
240
+ const count = total();
241
+ if (snapshot.supported && (count > 1 || canRename())) {
242
+ const label = count > 1 ? `Who's here (${plural(count, "person", "people")})` : "Who's here";
243
+ // Re-adding redraws the open menu, so only a changed label does it.
244
+ if (label !== itemLabel) {
245
+ itemLabel = label;
246
+ addItem({ id: "clay.people", label, run: () => openPanel(capsule.querySelector(".clay-menu-goo")) });
247
+ }
248
+ } else {
249
+ if (itemLabel !== null) removeItem("clay.people");
250
+ itemLabel = null;
251
+ closePanel({ restore: false });
252
+ }
253
+ drawRoster();
254
+ if (panel && !renaming) {
255
+ const active = root.getRootNode().activeElement;
256
+ const focused = active && panel.contains(active) ? active.classList[0] : null;
257
+ draw();
258
+ if (focused) panel.querySelector(`.${focused}`)?.focus();
259
+ }
260
+ }
261
+
262
+ document.addEventListener("click", (event) => {
263
+ if (panel && !event.composedPath().includes(root)) closePanel({ restore: false });
264
+ });
265
+ document.addEventListener("clay:people", () => {
266
+ if (!renaming) update();
267
+ });
268
+
269
+ import("../../sync/presence-store.js")
270
+ .then(({ presence }) => {
271
+ presence.subscribe((next) => {
272
+ snapshot = next || snapshot;
273
+ update();
274
+ });
275
+ })
276
+ .catch(() => {});
277
+
278
+ return { close: closePanel };
279
+ }