@panphora/clayjs 1.15.1 → 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,849 @@
1
+ // The SEO sheet: a panel that slides down from the top and edits the document's
2
+ // head fields. Everything it draws is `clay="editor-ui"`, so none of it reaches
3
+ // the saved file, a sync frame, the dirty check or undo. Nothing is written until
4
+ // Apply: opening and cancelling change only the sheet.
5
+ import { applyHead, FIELDS, readHead } from "./seo-head.js";
6
+ import { hostMeta } from "../../core/host-meta.js";
7
+ import { documentTitle } from "../../page/document-title.js";
8
+ import { registerUnsavedState } from "../../lib/unsaved-state.js";
9
+ import { modeRefusalText } from "../../core/mode-refusal.js";
10
+
11
+ const STYLE_ID = "clay-seo-style";
12
+ const REOPEN_KEY = "reopen-seo";
13
+ const LABEL = "SEO settings";
14
+ const SCHEME = /^[a-z][a-z0-9+.-]*:/i;
15
+ const ROBOTS_NOTE =
16
+ "This site is hidden from search engines in your account settings, so this page stays hidden whatever you choose here.";
17
+ const PUBLIC_NOTE = "Uploaded images are public to anyone with the link.";
18
+ const PASTE_NOTE = "Paste the address of an image on the web.";
19
+
20
+ const GROUPS = [
21
+ {
22
+ legend: "Basics",
23
+ fields: [
24
+ { key: "title", label: "Title", kind: "text" },
25
+ { key: "description", label: "Description", kind: "textarea" },
26
+ { key: "lang", label: "Language", kind: "text" },
27
+ { key: "canonical", label: "Canonical URL", kind: "text" },
28
+ ],
29
+ },
30
+ {
31
+ legend: "Search",
32
+ fields: [
33
+ { key: "index", label: "Show in search results", kind: "checkbox" },
34
+ { key: "follow", label: "Let search engines follow links", kind: "checkbox" },
35
+ ],
36
+ },
37
+ {
38
+ legend: "Link preview",
39
+ fields: [
40
+ { key: "ogTitle", label: "Preview title", kind: "text" },
41
+ { key: "ogDescription", label: "Preview description", kind: "textarea" },
42
+ { key: "ogImage", label: "Preview image", kind: "text" },
43
+ { key: "ogImageAlt", label: "Image alt text", kind: "text" },
44
+ { key: "siteName", label: "Site name", kind: "text" },
45
+ ],
46
+ },
47
+ {
48
+ legend: "Social card",
49
+ fields: [{ key: "twitterSite", label: "Handle", kind: "text" }],
50
+ },
51
+ ];
52
+
53
+ const SHEET_CSS = `.clay-seo {
54
+ position: fixed;
55
+ top: 0;
56
+ right: 0;
57
+ left: 0;
58
+ z-index: 2147482100;
59
+ box-sizing: border-box;
60
+ width: 100%;
61
+ max-height: 100vh;
62
+ margin: 0;
63
+ padding: 16px;
64
+ overflow-y: auto;
65
+ border-bottom: 1px solid rgba(23, 28, 36, .16);
66
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
67
+ font-size: 14px;
68
+ line-height: 1.4;
69
+ color: var(--clay-ui-fg, #1F2328);
70
+ background: var(--clay-ui-bg, #F4F4F5);
71
+ box-shadow: 0 18px 40px -24px rgba(23, 28, 36, .55);
72
+ animation: clay-seo-slide 200ms ease-out;
73
+ }
74
+ @keyframes clay-seo-slide {
75
+ from { transform: translateY(-100%); }
76
+ to { transform: translateY(0); }
77
+ }
78
+ .clay-seo-top {
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: space-between;
82
+ gap: 12px;
83
+ }
84
+ .clay-seo-heading {
85
+ margin: 0;
86
+ font-size: 15px;
87
+ font-weight: 600;
88
+ line-height: 1.2;
89
+ }
90
+ .clay-seo-x {
91
+ width: 32px;
92
+ height: 32px;
93
+ margin: 0;
94
+ padding: 0;
95
+ border: 0;
96
+ border-radius: 50%;
97
+ font: inherit;
98
+ font-size: 18px;
99
+ line-height: 1;
100
+ color: inherit;
101
+ background: none;
102
+ cursor: pointer;
103
+ }
104
+ .clay-seo-x:hover,
105
+ .clay-seo-x:focus-visible { background: rgba(23, 28, 36, .08); outline: none; }
106
+ .clay-seo-preview {
107
+ display: flex;
108
+ gap: 12px;
109
+ margin: 12px 0;
110
+ padding: 10px;
111
+ border: 1px solid rgba(23, 28, 36, .16);
112
+ border-radius: 8px;
113
+ }
114
+ .clay-seo-preview-image {
115
+ flex: none;
116
+ width: 88px;
117
+ height: 62px;
118
+ border-radius: 6px;
119
+ object-fit: cover;
120
+ }
121
+ .clay-seo-preview-text { min-width: 0; }
122
+ .clay-seo-preview-title {
123
+ margin: 0;
124
+ font-weight: 600;
125
+ overflow: hidden;
126
+ text-overflow: ellipsis;
127
+ white-space: nowrap;
128
+ }
129
+ .clay-seo-preview-description,
130
+ .clay-seo-preview-host {
131
+ margin: 2px 0 0;
132
+ font-size: 12px;
133
+ opacity: .8;
134
+ }
135
+ .clay-seo-group {
136
+ margin: 0 0 14px;
137
+ padding: 0;
138
+ border: 0;
139
+ }
140
+ .clay-seo-legend {
141
+ padding: 0;
142
+ margin-bottom: 6px;
143
+ font-size: 12px;
144
+ font-weight: 600;
145
+ letter-spacing: .04em;
146
+ text-transform: uppercase;
147
+ opacity: .7;
148
+ }
149
+ .clay-seo-row {
150
+ display: block;
151
+ margin-bottom: 8px;
152
+ }
153
+ .clay-seo-caption {
154
+ display: block;
155
+ margin-bottom: 4px;
156
+ font-size: 12px;
157
+ opacity: .8;
158
+ }
159
+ .clay-seo-row-check {
160
+ display: flex;
161
+ align-items: center;
162
+ gap: 8px;
163
+ }
164
+ .clay-seo-row-check .clay-seo-caption { margin-bottom: 0; }
165
+ .clay-seo-input {
166
+ box-sizing: border-box;
167
+ width: 100%;
168
+ margin: 0;
169
+ padding: 7px 9px;
170
+ border: 1px solid rgba(23, 28, 36, .24);
171
+ border-radius: 6px;
172
+ font: inherit;
173
+ color: inherit;
174
+ background: rgba(250, 251, 252, .7);
175
+ }
176
+ .clay-seo-input:focus-visible { outline: 2px solid var(--clay-ui-accent, #2F6FEB); outline-offset: 1px; }
177
+ textarea.clay-seo-input { min-height: 64px; resize: vertical; }
178
+ .clay-seo-note {
179
+ margin: 4px 0 0;
180
+ font-size: 12px;
181
+ opacity: .8;
182
+ }
183
+ .clay-seo-error {
184
+ margin: 4px 0 0;
185
+ font-size: 12px;
186
+ color: #A63A2B;
187
+ }
188
+ .clay-seo-derived {
189
+ margin: 4px 0 0;
190
+ padding: 0;
191
+ border: 0;
192
+ font: inherit;
193
+ font-size: 12px;
194
+ color: var(--clay-ui-accent, #2F6FEB);
195
+ background: none;
196
+ text-decoration: underline;
197
+ cursor: pointer;
198
+ }
199
+ .clay-seo-conflict {
200
+ margin: 6px 0 0;
201
+ padding: 8px;
202
+ border: 1px solid rgba(23, 28, 36, .16);
203
+ border-radius: 6px;
204
+ font-size: 12px;
205
+ }
206
+ .clay-seo-conflict p { margin: 0 0 6px; }
207
+ .clay-seo-upload {
208
+ display: flex;
209
+ flex-wrap: wrap;
210
+ align-items: center;
211
+ gap: 8px;
212
+ margin: 4px 0 8px;
213
+ }
214
+ .clay-seo-upload .clay-seo-note { flex: 1 1 100%; margin: 0; }
215
+ .clay-seo-actions {
216
+ display: flex;
217
+ justify-content: flex-end;
218
+ gap: 8px;
219
+ margin-top: 12px;
220
+ }
221
+ .clay-seo-actions button {
222
+ padding: 8px 14px;
223
+ border: 1px solid rgba(23, 28, 36, .24);
224
+ border-radius: 6px;
225
+ font: inherit;
226
+ color: inherit;
227
+ background: none;
228
+ cursor: pointer;
229
+ }
230
+ .clay-seo-actions button:hover,
231
+ .clay-seo-actions button:focus-visible { outline: none; background: rgba(23, 28, 36, .08); }
232
+ .clay-seo-apply,
233
+ .clay-seo-edit {
234
+ border-color: transparent;
235
+ color: #F7F8FA;
236
+ background: var(--clay-ui-accent, #2F6FEB);
237
+ }
238
+ .clay-seo-actions .clay-seo-apply:hover,
239
+ .clay-seo-actions .clay-seo-apply:focus-visible,
240
+ .clay-seo-actions .clay-seo-edit:hover,
241
+ .clay-seo-actions .clay-seo-edit:focus-visible { background: var(--clay-ui-accent, #2F6FEB); opacity: .9; }
242
+ .clay-seo-upload-button {
243
+ padding: 6px 10px;
244
+ border: 1px solid rgba(23, 28, 36, .24);
245
+ border-radius: 6px;
246
+ font: inherit;
247
+ font-size: 12px;
248
+ color: inherit;
249
+ background: none;
250
+ cursor: pointer;
251
+ }
252
+ @media (min-width: 600px) {
253
+ .clay-seo {
254
+ width: 560px;
255
+ margin: 0 auto;
256
+ padding: 16px;
257
+ border: 1px solid rgba(23, 28, 36, .16);
258
+ border-top: 0;
259
+ border-radius: 0 0 12px 12px;
260
+ }
261
+ }
262
+ @media (pointer: coarse) {
263
+ .clay-seo-actions button,
264
+ .clay-seo-upload-button { min-height: 44px; }
265
+ }
266
+ @media (prefers-color-scheme: dark) {
267
+ .clay-seo {
268
+ color: var(--clay-ui-fg, #E9E9EC);
269
+ background: var(--clay-ui-bg, #26282C);
270
+ border-color: rgba(233, 233, 236, .16);
271
+ }
272
+ .clay-seo-preview { border-color: rgba(233, 233, 236, .16); }
273
+ .clay-seo-input {
274
+ border-color: rgba(233, 233, 236, .16);
275
+ background: rgba(233, 233, 236, .08);
276
+ }
277
+ .clay-seo-x:hover,
278
+ .clay-seo-x:focus-visible,
279
+ .clay-seo-actions button:hover,
280
+ .clay-seo-actions button:focus-visible { background: rgba(233, 233, 236, .12); }
281
+ .clay-seo-actions button { border-color: rgba(233, 233, 236, .24); }
282
+ .clay-seo-conflict { border-color: rgba(233, 233, 236, .16); }
283
+ .clay-seo-error { color: #F0A192; }
284
+ }
285
+ @media (prefers-reduced-motion: reduce) {
286
+ .clay-seo { animation: none; }
287
+ }`;
288
+
289
+ const text = (value) => (value == null ? "" : String(value));
290
+
291
+ function element(tag, className, content) {
292
+ const el = document.createElement(tag);
293
+ if (className) el.className = className;
294
+ if (content != null) el.textContent = content;
295
+ return el;
296
+ }
297
+
298
+ function stylesOnce() {
299
+ if (document.getElementById(STYLE_ID)) return;
300
+ const style = document.createElement("style");
301
+ style.id = STYLE_ID;
302
+ style.setAttribute("clay", "editor-ui");
303
+ style.textContent = SHEET_CSS;
304
+ document.head.append(style);
305
+ }
306
+
307
+ const parseUrl = (value) => {
308
+ try {
309
+ return new URL(value);
310
+ } catch {
311
+ return null;
312
+ }
313
+ };
314
+
315
+ const isWebUrl = (url) => !!url && (url.protocol === "http:" || url.protocol === "https:");
316
+
317
+ const isLocalHostname = (url) => {
318
+ const host = url.hostname.toLowerCase();
319
+ return host === "localhost" || host.endsWith(".localhost") || host === "127.0.0.1";
320
+ };
321
+
322
+ function canonicalProblem(value) {
323
+ const raw = text(value).trim();
324
+ if (!raw) return "";
325
+ const url = parseUrl(raw);
326
+ if (!url || !isWebUrl(url)) return "Enter a full http: or https: address.";
327
+ if (isLocalHostname(url)) return "A localhost address cannot be a canonical URL.";
328
+ return "";
329
+ }
330
+
331
+ function imageProblem(value, base) {
332
+ const raw = text(value).trim();
333
+ if (!raw) return "";
334
+ if (SCHEME.test(raw)) {
335
+ const url = parseUrl(raw);
336
+ if (!url || !isWebUrl(url)) return "Use an http: or https: image address.";
337
+ return "";
338
+ }
339
+ if (base) {
340
+ try {
341
+ new URL(raw, base);
342
+ } catch {
343
+ return "Use an http: or https: image address.";
344
+ }
345
+ }
346
+ return "";
347
+ }
348
+
349
+ function pageBase() {
350
+ try {
351
+ const url = new URL(location.href);
352
+ return url.origin + url.pathname;
353
+ } catch {
354
+ return "";
355
+ }
356
+ }
357
+
358
+ function isDocumentPage() {
359
+ return document.querySelectorAll("article[data-page]").length === 1;
360
+ }
361
+
362
+ const ATOM = "\uFFFC";
363
+
364
+ const rootHeading = (c) => [{ t: "heading", level: 1, parent: null, c }];
365
+
366
+ // The first root H1's items as the projection orders them: a heading that names a
367
+ // parent is nested and is not a title, marks flatten away and a hard break is one
368
+ // character. A mounted editor keeps its blocks flat, so its model holds the parents;
369
+ // a saved article nests them inside the container that holds them.
370
+ function titleItems(view) {
371
+ if (view && !view.isDestroyed) {
372
+ const doc = view.state.doc;
373
+ for (let i = 0; i < doc.childCount; i++) {
374
+ const node = doc.child(i);
375
+ if (node.type.name !== "heading" || node.attrs.level !== 1 || (node.attrs.parent ?? null) !== null) continue;
376
+ let c = "";
377
+ node.forEach((child) => { c += child.isText ? child.text : child.type.name === "hard_break" ? ATOM : ""; });
378
+ return rootHeading(c);
379
+ }
380
+ return [];
381
+ }
382
+ const article = document.querySelector("article[data-page]");
383
+ const heading = article && [...article.children].find((node) => node.tagName === "H1");
384
+ if (!heading) return [];
385
+ let c = "";
386
+ const walk = (parent) => {
387
+ for (const child of parent.childNodes) {
388
+ if (child.nodeType === 3) c += child.data;
389
+ else if (child.tagName === "BR") c += ATOM;
390
+ else walk(child);
391
+ }
392
+ };
393
+ walk(heading);
394
+ return rootHeading(c);
395
+ }
396
+
397
+ // The same rule the runtime and the checkpoint use: the first root H1, and Untitled
398
+ // when it is empty or missing.
399
+ function firstHeadingText() {
400
+ const view = window.clay && window.clay.page && window.clay.page.view;
401
+ return documentTitle(titleItems(view));
402
+ }
403
+
404
+ function hostOf(value) {
405
+ const url = parseUrl(text(value).trim());
406
+ return url ? url.hostname : "";
407
+ }
408
+
409
+ function showValue(key, value) {
410
+ if (typeof value === "boolean") {
411
+ if (key === "index") return value ? "Shown in search" : "Hidden from search";
412
+ if (key === "follow") return value ? "Links followed" : "Links not followed";
413
+ return value ? "Yes" : "No";
414
+ }
415
+ const raw = text(value).trim();
416
+ return raw || "(empty)";
417
+ }
418
+
419
+ function run(label, fn) {
420
+ const undo = window.clay && window.clay.undo;
421
+ if (undo && typeof undo.commit === "function") return undo.commit(label, fn);
422
+ return fn();
423
+ }
424
+
425
+ export function open() {
426
+ if (document.querySelector(".clay-seo")) return;
427
+ stylesOnce();
428
+
429
+ const opened = readHead(document).values;
430
+ const restoreTo = document.activeElement;
431
+ const inputs = new Map();
432
+ const errors = new Map();
433
+ const conflicts = new Map();
434
+ const chosen = new Set();
435
+ let closed = false;
436
+ let uploadAvailable = false;
437
+ let pendingTitleMode = "";
438
+
439
+ // Fields typed and not applied are work this page holds that its saved bytes cannot
440
+ // show. The registry is the edit boot's own, so the sheet is pending whether or not
441
+ // the page asked for the optional `unsent` plugin, and a refused exit leaves every
442
+ // value on screen rather than closing over them.
443
+ const pending = registerUnsavedState({
444
+ id: "seo",
445
+ isPending: () => !closed && Object.keys(changes()).length > 0,
446
+ });
447
+
448
+ const root = element("div", "clay-seo");
449
+ root.setAttribute("clay", "editor-ui");
450
+ root.setAttribute("role", "dialog");
451
+ root.setAttribute("aria-modal", "true");
452
+ root.setAttribute("aria-labelledby", "clay-seo-heading");
453
+
454
+ const title = element("h2", "clay-seo-heading", LABEL);
455
+ title.id = "clay-seo-heading";
456
+ const closeButton = element("button", "clay-seo-x", "\u00d7");
457
+ closeButton.type = "button";
458
+ closeButton.setAttribute("aria-label", "Close");
459
+ const top = element("div", "clay-seo-top");
460
+ top.append(title, closeButton);
461
+
462
+ const previewImage = element("img", "clay-seo-preview-image");
463
+ previewImage.alt = "";
464
+ const previewTitle = element("p", "clay-seo-preview-title");
465
+ const previewDescription = element("p", "clay-seo-preview-description");
466
+ const previewHost = element("p", "clay-seo-preview-host");
467
+ const previewText = element("div", "clay-seo-preview-text");
468
+ previewText.append(previewTitle, previewDescription, previewHost);
469
+ const preview = element("div", "clay-seo-preview");
470
+ preview.append(previewImage, previewText);
471
+
472
+ const form = element("div", "clay-seo-form");
473
+ const robotNote = element("p", "clay-seo-note");
474
+ robotNote.hidden = true;
475
+ const cardSize = element("p", "clay-seo-note");
476
+
477
+ function buildField(field) {
478
+ const wrap = element("div", "clay-seo-field");
479
+ const row = element("label", "clay-seo-row");
480
+ const caption = element("span", "clay-seo-caption", field.label);
481
+ const input =
482
+ field.kind === "textarea" ? element("textarea", "clay-seo-input") : element("input", field.kind === "checkbox" ? "clay-seo-check" : "clay-seo-input");
483
+ if (field.kind === "checkbox") {
484
+ row.classList.add("clay-seo-row-check");
485
+ input.type = "checkbox";
486
+ input.checked = opened[field.key] !== false;
487
+ row.append(input, caption);
488
+ } else {
489
+ if (field.kind !== "textarea") input.type = "text";
490
+ input.value = text(opened[field.key]);
491
+ row.append(caption, input);
492
+ }
493
+ input.dataset.key = field.key;
494
+ if (window.clay && window.clay.unsent) input.setAttribute("unsent", `seo-${field.key}`);
495
+ inputs.set(field.key, input);
496
+
497
+ const error = element("p", "clay-seo-error");
498
+ error.hidden = true;
499
+ errors.set(field.key, error);
500
+
501
+ const conflict = element("div", "clay-seo-conflict");
502
+ conflict.hidden = true;
503
+ const theirsLine = element("p");
504
+ const theirs = element("span", "clay-seo-theirs");
505
+ theirsLine.append("Changed elsewhere: ", theirs);
506
+ const theirsButton = element("button", "clay-seo-theirs-button", "Use theirs");
507
+ theirsButton.type = "button";
508
+ const mineButton = element("button", "clay-seo-mine-button", "Keep mine");
509
+ mineButton.type = "button";
510
+ conflict.append(theirsLine, theirsButton, mineButton);
511
+ conflicts.set(field.key, { row: conflict, theirs });
512
+
513
+ theirsButton.addEventListener("click", () => {
514
+ const value = readHead(document).values[field.key];
515
+ if (field.kind === "checkbox") input.checked = value !== false;
516
+ else input.value = text(value);
517
+ chosen.add(field.key);
518
+ conflict.hidden = true;
519
+ refresh();
520
+ });
521
+ mineButton.addEventListener("click", () => {
522
+ chosen.add(field.key);
523
+ conflict.hidden = true;
524
+ });
525
+
526
+ wrap.append(row, error, conflict);
527
+ return wrap;
528
+ }
529
+
530
+ for (const group of GROUPS) {
531
+ const section = element("fieldset", "clay-seo-group");
532
+ section.append(element("legend", "clay-seo-legend", group.legend));
533
+ for (const field of group.fields) section.append(buildField(field));
534
+ if (group.legend === "Search") section.append(robotNote);
535
+ if (group.legend === "Social card") section.append(cardSize);
536
+ form.append(section);
537
+ }
538
+
539
+ const derivedButton = element("button", "clay-seo-derived", "Use the first heading");
540
+ derivedButton.type = "button";
541
+ derivedButton.hidden = true;
542
+ inputs.get("title").closest(".clay-seo-field").append(derivedButton);
543
+
544
+ const imageInput = inputs.get("ogImage");
545
+ const uploadButton = element("button", "clay-seo-upload-button", "Upload image");
546
+ uploadButton.type = "button";
547
+ const fileInput = element("input", "clay-seo-file");
548
+ fileInput.type = "file";
549
+ fileInput.accept = "image/*";
550
+ fileInput.hidden = true;
551
+ fileInput.tabIndex = -1;
552
+ const publicNote = element("p", "clay-seo-note", PUBLIC_NOTE);
553
+ publicNote.hidden = true;
554
+ const pasteNote = element("p", "clay-seo-note", PASTE_NOTE);
555
+ const uploadRow = element("div", "clay-seo-upload");
556
+ uploadRow.append(uploadButton, fileInput, pasteNote, publicNote);
557
+ imageInput.closest(".clay-seo-field").append(uploadRow);
558
+
559
+ const editButton = element("button", "clay-seo-edit", "Edit these settings");
560
+ editButton.type = "button";
561
+ const cancelButton = element("button", "clay-seo-cancel", "Cancel");
562
+ cancelButton.type = "button";
563
+ const applyButton = element("button", "clay-seo-apply", "Apply");
564
+ applyButton.type = "button";
565
+ const actions = element("div", "clay-seo-actions");
566
+ actions.append(editButton, cancelButton, applyButton);
567
+
568
+ root.append(top, preview, form, actions);
569
+
570
+ const readField = (key) => {
571
+ const input = inputs.get(key);
572
+ return input.type === "checkbox" ? input.checked : input.value;
573
+ };
574
+
575
+ function imageBase() {
576
+ const canonical = text(readField("canonical")).trim();
577
+ if (canonical && !canonicalProblem(canonical)) return canonical;
578
+ return pageBase();
579
+ }
580
+
581
+ function imageSrc() {
582
+ const raw = text(readField("ogImage")).trim();
583
+ if (!raw || imageProblem(raw, imageBase())) return "";
584
+ if (SCHEME.test(raw)) return raw;
585
+ try {
586
+ return new URL(raw, imageBase() || location.href).href;
587
+ } catch {
588
+ return "";
589
+ }
590
+ }
591
+
592
+ function refresh() {
593
+ const titleValue = text(readField("title")).trim();
594
+ const descriptionValue = text(readField("description")).trim();
595
+ inputs.get("ogTitle").placeholder = titleValue;
596
+ inputs.get("ogDescription").placeholder = descriptionValue;
597
+
598
+ const image = imageSrc();
599
+ if (image) {
600
+ previewImage.src = image;
601
+ previewImage.hidden = false;
602
+ } else {
603
+ previewImage.removeAttribute("src");
604
+ previewImage.hidden = true;
605
+ }
606
+ previewTitle.textContent = text(readField("ogTitle")).trim() || titleValue || "Untitled page";
607
+ const description = text(readField("ogDescription")).trim() || descriptionValue;
608
+ previewDescription.textContent = description;
609
+ previewDescription.hidden = !description;
610
+ previewHost.textContent = hostOf(readField("canonical")) || location.host || "";
611
+ cardSize.textContent = image ? "Card size: Summary with a large image" : "Card size: Summary";
612
+ derivedButton.hidden = !(isDocumentPage() && FIELDS.titleMode.read(document) === "custom");
613
+ }
614
+
615
+ function changes() {
616
+ const out = {};
617
+ for (const [key, input] of inputs) {
618
+ const now = input.type === "checkbox" ? input.checked : input.value;
619
+ if (now !== opened[key]) out[key] = now;
620
+ }
621
+ return out;
622
+ }
623
+
624
+ function problemsFor(wanted) {
625
+ const found = {};
626
+ if ("canonical" in wanted) {
627
+ const problem = canonicalProblem(wanted.canonical);
628
+ if (problem) found.canonical = problem;
629
+ }
630
+ if ("ogImage" in wanted) {
631
+ const problem = imageProblem(wanted.ogImage, imageBase());
632
+ if (problem) found.ogImage = problem;
633
+ }
634
+ return found;
635
+ }
636
+
637
+ function conflictsFor(wanted) {
638
+ const current = readHead(document).values;
639
+ const found = {};
640
+ for (const key of Object.keys(wanted)) {
641
+ if (chosen.has(key)) continue;
642
+ if (current[key] === opened[key]) continue;
643
+ if (current[key] === wanted[key]) continue;
644
+ found[key] = current[key];
645
+ }
646
+ return found;
647
+ }
648
+
649
+ function showProblems(found) {
650
+ for (const [key, el] of errors) {
651
+ const message = found[key] || "";
652
+ el.textContent = message;
653
+ el.hidden = !message;
654
+ }
655
+ }
656
+
657
+ function showConflicts(found) {
658
+ for (const [key, entry] of conflicts) {
659
+ const current = found[key];
660
+ const visible = current !== undefined;
661
+ entry.row.hidden = !visible;
662
+ entry.theirs.textContent = visible ? showValue(key, current) : "";
663
+ }
664
+ }
665
+
666
+ function apply() {
667
+ const wanted = changes();
668
+ const documentPage = isDocumentPage();
669
+ const marker = documentPage ? pendingTitleMode || ("title" in wanted ? "custom" : "") : "";
670
+ for (const entry of conflicts.values()) entry.row.hidden = true;
671
+ showProblems({});
672
+ if (!Object.keys(wanted).length && !marker) {
673
+ close();
674
+ return;
675
+ }
676
+ const problems = problemsFor(wanted);
677
+ if (Object.keys(problems).length) {
678
+ showProblems(problems);
679
+ return;
680
+ }
681
+ const found = conflictsFor(wanted);
682
+ if (Object.keys(found).length) {
683
+ showConflicts(found);
684
+ return;
685
+ }
686
+ run(LABEL, () => applyHead(document, marker ? { ...wanted, titleMode: marker } : wanted));
687
+ close();
688
+ }
689
+
690
+ function applyUploadVisibility() {
691
+ uploadButton.hidden = !uploadAvailable;
692
+ pasteNote.hidden = uploadAvailable;
693
+ publicNote.hidden = !uploadAvailable || !/(^|\.)hyperclay\.com$/i.test(location.hostname);
694
+ }
695
+
696
+ function layout() {
697
+ const editing = !!(window.clay && window.clay.mode === "edit");
698
+ for (const input of inputs.values()) {
699
+ if (input.type === "checkbox") input.disabled = !editing;
700
+ else input.readOnly = !editing;
701
+ }
702
+ editButton.hidden = editing;
703
+ cancelButton.hidden = !editing;
704
+ applyButton.hidden = !editing;
705
+ uploadRow.hidden = !editing || !uploadAvailable;
706
+ applyUploadVisibility();
707
+ }
708
+
709
+ // A live entry commits edit without this page going anywhere, so the sheet's own
710
+ // read-only layout has to follow the mode rather than the mode it opened in.
711
+ function followMode() {
712
+ if (!closed) layout();
713
+ }
714
+
715
+ function close() {
716
+ if (closed) return;
717
+ closed = true;
718
+ pending.dispose();
719
+ document.removeEventListener("clay:modechange", followMode);
720
+ root.remove();
721
+ if (restoreTo && restoreTo.isConnected && typeof restoreTo.focus === "function") restoreTo.focus();
722
+ }
723
+
724
+ async function checkHostedRobots() {
725
+ let response;
726
+ try {
727
+ response = await fetch(location.href, { method: "HEAD" });
728
+ } catch {
729
+ return;
730
+ }
731
+ if (closed || !root.isConnected) return;
732
+ const tag = response && response.headers && typeof response.headers.get === "function" ? response.headers.get("x-robots-tag") : "";
733
+ if (!tag || !/noindex/i.test(tag)) return;
734
+ robotNote.textContent = ROBOTS_NOTE;
735
+ robotNote.hidden = false;
736
+ }
737
+
738
+ async function checkUploads() {
739
+ const call = window.clay && window.clay.upload;
740
+ if (typeof call !== "function") return;
741
+ let meta = null;
742
+ try {
743
+ meta = await hostMeta();
744
+ } catch {
745
+ meta = null;
746
+ }
747
+ if (closed || !root.isConnected) return;
748
+ uploadAvailable = !!(meta && Array.isArray(meta.extensions) && meta.extensions.includes("upload"));
749
+ layout();
750
+ }
751
+
752
+ function focusables() {
753
+ return Array.from(root.querySelectorAll("button, input, textarea")).filter(
754
+ (el) => !el.disabled && !el.hidden && el.tabIndex >= 0 && !el.closest("[hidden]"),
755
+ );
756
+ }
757
+
758
+ form.addEventListener("input", () => {
759
+ refresh();
760
+ pending.changed();
761
+ });
762
+
763
+ inputs.get("title").addEventListener("input", () => {
764
+ pendingTitleMode = "";
765
+ });
766
+
767
+ derivedButton.addEventListener("click", () => {
768
+ pendingTitleMode = "derived";
769
+ inputs.get("title").value = firstHeadingText();
770
+ refresh();
771
+ });
772
+
773
+ uploadButton.addEventListener("click", () => fileInput.click());
774
+
775
+ fileInput.addEventListener("change", async () => {
776
+ const file = fileInput.files && fileInput.files[0];
777
+ if (!file) return;
778
+ const call = window.clay && window.clay.upload;
779
+ if (typeof call !== "function") return;
780
+ uploadButton.disabled = true;
781
+ let result;
782
+ try {
783
+ result = await call(file);
784
+ } catch {
785
+ result = null;
786
+ }
787
+ uploadButton.disabled = false;
788
+ if (closed || !root.isConnected) return;
789
+ const upload = result && result.ok && Array.isArray(result.uploads) ? result.uploads[0] : null;
790
+ if (!upload || !upload.url) return;
791
+ inputs.get("ogImage").value = upload.url;
792
+ refresh();
793
+ });
794
+
795
+ applyButton.addEventListener("click", apply);
796
+ cancelButton.addEventListener("click", close);
797
+ closeButton.addEventListener("click", close);
798
+
799
+ editButton.addEventListener("click", async () => {
800
+ const clay = window.clay;
801
+ if (!clay || typeof clay.setMode !== "function") return;
802
+ const storage = clay.storage && clay.storage.session;
803
+ // The flag is the reload path's receipt: the page comes back in edit and this sheet
804
+ // comes back with it. A live entry never leaves the page, so the sheet is still here
805
+ // and the flag would outlive the reason it was set.
806
+ if (storage) storage.set(REOPEN_KEY, true);
807
+ let result;
808
+ try {
809
+ result = await clay.setMode("edit");
810
+ } catch {
811
+ result = null;
812
+ }
813
+ if (closed) return;
814
+ const reloading = !!result && result.ok !== false && !!result.reloading;
815
+ if (reloading) return;
816
+ if (storage) storage.remove(REOPEN_KEY);
817
+ const refusal = modeRefusalText(result);
818
+ if (refusal) window.clay?.toast?.(refusal, "error");
819
+ if (result && result.ok !== false) layout();
820
+ });
821
+
822
+ root.addEventListener("keydown", (event) => {
823
+ if (event.key === "Escape") {
824
+ event.preventDefault();
825
+ close();
826
+ return;
827
+ }
828
+ if (event.key !== "Tab") return;
829
+ const all = focusables();
830
+ if (!all.length) return;
831
+ const first = all[0];
832
+ const last = all[all.length - 1];
833
+ if (event.shiftKey && document.activeElement === first) {
834
+ event.preventDefault();
835
+ last.focus();
836
+ } else if (!event.shiftKey && document.activeElement === last) {
837
+ event.preventDefault();
838
+ first.focus();
839
+ }
840
+ });
841
+
842
+ document.addEventListener("clay:modechange", followMode);
843
+ document.body.append(root);
844
+ refresh();
845
+ layout();
846
+ inputs.get("title").focus();
847
+ checkHostedRobots();
848
+ checkUploads();
849
+ }