@panphora/clayjs 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) 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 +7917 -5621
  11. package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
  12. package/dist/m/attrs/save-freeze-MYNVXREN.js +1 -0
  13. package/dist/m/c-2YYDMLNZ.js +1 -0
  14. package/dist/m/c-336SGU5W.js +1 -0
  15. package/dist/m/c-3H6BZDNJ.js +6 -0
  16. package/dist/m/{c-LR4CGEPT.js → c-3RUU4TPX.js} +1 -1
  17. package/dist/m/c-3XIRD4NU.js +1 -0
  18. package/dist/m/c-4DFEJ2PO.js +3 -0
  19. package/dist/m/c-56QRZTZH.js +1 -0
  20. package/dist/m/c-5J4E7O4Y.js +4 -0
  21. package/dist/m/c-5LKDWQTL.js +19 -0
  22. package/dist/m/c-5QWQ7A5L.js +1 -0
  23. package/dist/m/c-5RJFTLSF.js +1 -0
  24. package/dist/m/c-6AZOGAPE.js +1 -0
  25. package/dist/m/{c-Y6USGTS5.js → c-6CRUUXOX.js} +5 -5
  26. package/dist/m/{c-54RCDCBU.js → c-6KEVEQ7L.js} +1 -1
  27. package/dist/m/{c-LTGMXXKO.js → c-6NC5I2TV.js} +1 -1
  28. package/dist/m/{c-YDH7Q3NY.js → c-74MGYWZF.js} +1 -1
  29. package/dist/m/{c-Z36J63PP.js → c-7OLLJPWP.js} +1 -1
  30. package/dist/m/c-A3E3NIMC.js +1 -0
  31. package/dist/m/{c-5R4PTRG2.js → c-AMSNPOCX.js} +1 -1
  32. package/dist/m/c-B3JM5ZXC.js +1 -0
  33. package/dist/m/c-BPF2NSPV.js +1 -0
  34. package/dist/m/c-BQ6LOBEE.js +1 -0
  35. package/dist/m/c-CJ3A7ZKE.js +1 -0
  36. package/dist/m/c-CQHPGKX2.js +1 -0
  37. package/dist/m/c-DCXOXBF3.js +1 -0
  38. package/dist/m/c-DFS4XZJV.js +2 -0
  39. package/dist/m/{c-F3CKJTJS.js → c-DNENWY4O.js} +1 -1
  40. package/dist/m/c-E4LIR5X5.js +1 -0
  41. package/dist/m/c-EVCQ3BKA.js +1 -0
  42. package/dist/m/{c-5Y6TNX5W.js → c-EZU56NQV.js} +2 -2
  43. package/dist/m/c-FDNHHZY5.js +1 -0
  44. package/dist/m/{c-SWRJFCNN.js → c-FRLHSY7E.js} +1 -1
  45. package/dist/m/c-FROJUZMV.js +1 -0
  46. package/dist/m/c-G2JPKCTP.js +1 -0
  47. package/dist/m/c-GB6TU7WV.js +1 -0
  48. package/dist/m/{c-GB3JTV6E.js → c-GFJJEBTU.js} +1 -1
  49. package/dist/m/{c-J7CA54IP.js → c-GSFIGKOP.js} +1 -1
  50. package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
  51. package/dist/m/c-HAWFNBYG.js +1 -0
  52. package/dist/m/{c-33HUYEDI.js → c-HDDEEL77.js} +1 -1
  53. package/dist/m/c-HFKUVACH.js +1 -0
  54. package/dist/m/c-HHDFCNHV.js +1 -0
  55. package/dist/m/c-I2ZATQCD.js +2 -0
  56. package/dist/m/c-IEM4NTCS.js +1 -0
  57. package/dist/m/c-IW6GGOU2.js +1 -0
  58. package/dist/m/{c-SGCCC4FW.js → c-JDCX26UP.js} +2 -2
  59. package/dist/m/c-JRKY44DK.js +1 -0
  60. package/dist/m/c-JU3NK25H.js +1 -0
  61. package/dist/m/{c-VFX45IZQ.js → c-KAU6E43D.js} +1 -1
  62. package/dist/m/c-KBS3UV6B.js +1 -0
  63. package/dist/m/c-KGVGROZT.js +1 -0
  64. package/dist/m/{c-YG2NJYS6.js → c-KQ6TYJAP.js} +2 -2
  65. package/dist/m/{c-2JL6E75N.js → c-KU3ZFADW.js} +1 -1
  66. package/dist/m/c-LWLBHLVC.js +3 -0
  67. package/dist/m/c-M2L47S2X.js +7 -0
  68. package/dist/m/c-MFP2ZWD4.js +1 -0
  69. package/dist/m/{c-4YSCRICO.js → c-MKSQ4Y57.js} +1 -1
  70. package/dist/m/{c-2M73252O.js → c-NUFFJVVJ.js} +1 -1
  71. package/dist/m/c-NUHSPT5P.js +1 -0
  72. package/dist/m/c-O72HVYSA.js +1 -0
  73. package/dist/m/c-OW5QIGG3.js +1 -0
  74. package/dist/m/c-PGRCK73F.js +1 -0
  75. package/dist/m/{c-4DRMS4QG.js → c-PS5ARVWR.js} +1 -1
  76. package/dist/m/c-Q622NS2Y.js +1 -0
  77. package/dist/m/c-Q7NQD65V.js +1 -0
  78. package/dist/m/c-QH63LQOS.js +1 -0
  79. package/dist/m/{c-TQZ2JYHD.js → c-QICP2YR5.js} +1 -1
  80. package/dist/m/{c-7VGZ7RU5.js → c-QJ4OM3G6.js} +1 -1
  81. package/dist/m/{c-MMUIUUZG.js → c-R3L7ZJGB.js} +1 -1
  82. package/dist/m/c-R7IWVINQ.js +1 -0
  83. package/dist/m/{c-HVYXIS72.js → c-RSMOODDU.js} +1 -1
  84. package/dist/m/{c-KKWLDOPM.js → c-RYUXUUBX.js} +1 -1
  85. package/dist/m/c-S4ZEH46B.js +9 -0
  86. package/dist/m/c-SDAXDM2D.js +1 -0
  87. package/dist/m/c-SMBZG5X4.js +1 -0
  88. package/dist/m/{c-UXBMRDZB.js → c-SMGU7RT3.js} +1 -1
  89. package/dist/m/c-SPIORRYS.js +1 -0
  90. package/dist/m/c-SWLRT66R.js +1 -0
  91. package/dist/m/c-SZGYW5HT.js +1 -0
  92. package/dist/m/c-TCJTVAFN.js +1 -0
  93. package/dist/m/{c-HRDCD3WN.js → c-TJ6WOJL3.js} +1 -1
  94. package/dist/m/c-TYLXX4NF.js +21 -0
  95. package/dist/m/c-U62OB4BT.js +1 -0
  96. package/dist/m/{c-NMJRXKPC.js → c-U6YWUAR3.js} +1 -1
  97. package/dist/m/c-UDCSU5G5.js +2 -0
  98. package/dist/m/{c-NZMNXVF6.js → c-UDUUJPFZ.js} +1 -1
  99. package/dist/m/c-VE6ZMYMC.js +1 -0
  100. package/dist/m/c-VG5XIN4T.js +1 -0
  101. package/dist/m/c-VHM5XQHK.js +1 -0
  102. package/dist/m/c-VI5RMSVY.js +1 -0
  103. package/dist/m/c-VRZN56XY.js +1 -0
  104. package/dist/m/c-W5MCHEGA.js +1 -0
  105. package/dist/m/c-WDC5GVKU.js +1 -0
  106. package/dist/m/c-WWXIDH4O.js +28 -0
  107. package/dist/m/{c-QIX5BJYN.js → c-XCNTK76U.js} +1 -1
  108. package/dist/m/c-XMS6DPBR.js +1 -0
  109. package/dist/m/c-Y5HBBIQ4.js +1 -0
  110. package/dist/m/{c-5M4YEKCX.js → c-ZVPVXTJF.js} +1 -1
  111. package/dist/m/c-ZVRKXKM7.js +1 -0
  112. package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
  113. package/dist/m/core/admin-attrs-QHOKPJ7W.js +1 -0
  114. package/dist/m/core/autosave-HRYGUWV6.js +1 -0
  115. package/dist/m/core/conflict-notice-6MROJN2L.js +1 -0
  116. package/dist/m/core/host-permission-FNXVEFNE.js +1 -0
  117. package/dist/m/core/is-edit-mode-KTWA3O5N.js +1 -0
  118. package/dist/m/core/{persist-HKHA66IY.js → persist-JFYRHMRJ.js} +1 -1
  119. package/dist/m/core/recovery-FLTCC2OC.js +1 -0
  120. package/dist/m/core/save-HQZ4S667.js +1 -0
  121. package/dist/m/core/save-core-KGQWFUNH.js +1 -0
  122. package/dist/m/core/snapshot-EGQZNT45.js +1 -0
  123. package/dist/m/core/unsaved-warning-IDJLD66H.js +1 -0
  124. package/dist/m/events/index-RN2JNGBV.js +1 -0
  125. package/dist/m/internals/index-PVXGPUDA.js +1 -0
  126. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  127. package/dist/m/loader-S4I7GKXR.js +1 -0
  128. package/dist/m/manifest-4C419D67.js +1 -0
  129. package/dist/m/manifest-edit-B077F288.js +1 -0
  130. package/dist/m/manifest.json +1555 -1058
  131. package/dist/m/options/options-EJMS3K24.js +1 -0
  132. package/dist/m/page/{editor-XUTB2HB2.js → editor-NNJA4OKD.js} +21 -8
  133. package/dist/m/plugins/{ai-edit-ZQ4CGWIP.js → ai-edit-QN4PMMNL.js} +1 -1
  134. package/dist/m/plugins/demo-RM5IR6AO.js +1 -0
  135. package/dist/m/plugins/indicator-URDBBOQO.js +1 -0
  136. package/dist/m/plugins/live-HFWD4OIH.js +1 -0
  137. package/dist/m/plugins/{menu-HCKO3DSP.js → menu-3HIEQSI6.js} +2 -2
  138. package/dist/m/plugins/page-JOKB6XPR.js +2 -0
  139. package/dist/m/plugins/page-view-NXOJHNW6.js +1 -0
  140. package/dist/m/plugins/people-SGPJKANK.js +1 -0
  141. package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-NRQDSWX6.js} +2 -2
  142. package/dist/m/plugins/sortable-GWT4PWDV.js +1 -0
  143. package/dist/m/plugins/source-4ZRSAIYM.js +1 -0
  144. package/dist/m/plugins/undo-VDA5P7FH.js +1 -0
  145. package/dist/m/plugins/{upgrade-ZNYODP5Y.js → upgrade-W7FG5VFG.js} +1 -1
  146. package/dist/m/plugins/upload-WLYKK6TL.js +1 -0
  147. package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +21 -0
  148. package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +1 -0
  149. package/dist/m/plugins/wire-4N6GYAF6.js +1 -0
  150. package/dist/m/{reader-core-ILWLXVMP.js → reader-core-DXYCURFF.js} +2 -2
  151. package/dist/m/sync/live-sync-J7VMYTKS.js +1 -0
  152. package/dist/m/ui/{index-EVHKMKBL.js → index-3JLVI27O.js} +1 -1
  153. package/dist/offline-manifest.json +857 -623
  154. package/dist/page/themes/light-1.css +25 -1
  155. package/dist/server/core.cjs +115 -24
  156. package/package.json +1 -1
  157. package/src/core/boot-phase.js +2 -1
  158. package/src/core/conflict-download.js +3 -29
  159. package/src/core/download.js +32 -0
  160. package/src/core/host-meta.js +18 -0
  161. package/src/core/save-core.js +8 -4
  162. package/src/core/save.js +4 -4
  163. package/src/core/snapshot.js +39 -1
  164. package/src/lib/autosave-scheduler.js +3 -4
  165. package/src/page/blocks.js +34 -1
  166. package/src/page/board-views.js +2 -16
  167. package/src/page/chart-tables.js +11 -0
  168. package/src/page/chart.js +6 -10
  169. package/src/page/checkpoint-proof.js +289 -0
  170. package/src/page/chrome.js +24 -6
  171. package/src/page/document-title-dom.js +12 -0
  172. package/src/page/document-title-runtime.js +15 -1
  173. package/src/page/document-title.js +4 -1
  174. package/src/page/editor.js +77 -12
  175. package/src/page/emoji-data.js +162 -0
  176. package/src/page/features/board.js +12 -3
  177. package/src/page/features/chart.js +11 -0
  178. package/src/page/features/dates.js +180 -0
  179. package/src/page/features/emoji.js +187 -0
  180. package/src/page/features/mentions.js +188 -0
  181. package/src/page/features/registry.js +6 -1
  182. package/src/page/features/table.js +2 -2
  183. package/src/page/format-command.js +9 -0
  184. package/src/page/format-host.js +54 -0
  185. package/src/page/format-native-commands.js +142 -0
  186. package/src/page/format-native.js +42 -0
  187. package/src/page/inline.js +32 -8
  188. package/src/page/items.js +15 -3
  189. package/src/page/markdown.js +26 -2
  190. package/src/page/mention-picker.js +99 -0
  191. package/src/page/merge.js +1 -1
  192. package/src/page/projection.js +58 -15
  193. package/src/page/save-projection.js +39 -0
  194. package/src/page/save-states.js +1 -1
  195. package/src/page/schema.js +33 -0
  196. package/src/page/themes/content.css +25 -1
  197. package/src/page/themes/light-1.css +25 -1
  198. package/src/page/view-hooks.js +32 -0
  199. package/src/page/ybind/binding.js +31 -4
  200. package/src/page/ybind/marks.js +2 -1
  201. package/src/page/ybind/provider.js +13 -2
  202. package/src/page/ybind/recovery-replay.js +362 -4
  203. package/src/plugins/live.js +37 -1
  204. package/src/plugins/page.js +117 -3
  205. package/src/plugins/visitor-drafts/intro.js +2 -1
  206. package/src/server/checkpoint/index.js +2 -2
  207. package/src/sync/saved-stream.js +17 -8
  208. package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
  209. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
  210. package/dist/m/c-35NBGMZB.js +0 -1
  211. package/dist/m/c-3IQRECYS.js +0 -1
  212. package/dist/m/c-4B6H4G5V.js +0 -1
  213. package/dist/m/c-4FFHZM32.js +0 -1
  214. package/dist/m/c-66O32MV3.js +0 -28
  215. package/dist/m/c-7O6AFR65.js +0 -1
  216. package/dist/m/c-ANQTJ3B5.js +0 -2
  217. package/dist/m/c-AYVOREBE.js +0 -1
  218. package/dist/m/c-B2ZXPOS3.js +0 -3
  219. package/dist/m/c-BMIZKMPV.js +0 -1
  220. package/dist/m/c-CJZSLMVQ.js +0 -1
  221. package/dist/m/c-DBE6ORSI.js +0 -1
  222. package/dist/m/c-EDHUR4MX.js +0 -1
  223. package/dist/m/c-EY6UOM54.js +0 -1
  224. package/dist/m/c-F7Y6FLPY.js +0 -1
  225. package/dist/m/c-FA4DZVTC.js +0 -1
  226. package/dist/m/c-G2BUZITR.js +0 -1
  227. package/dist/m/c-GBGWXTX4.js +0 -1
  228. package/dist/m/c-GHBCCVG4.js +0 -1
  229. package/dist/m/c-IB52R237.js +0 -1
  230. package/dist/m/c-IKWNE4R4.js +0 -1
  231. package/dist/m/c-IU3IYEU6.js +0 -1
  232. package/dist/m/c-IZ2EBRAJ.js +0 -1
  233. package/dist/m/c-KR4UW4KW.js +0 -1
  234. package/dist/m/c-KY3VJGUA.js +0 -8
  235. package/dist/m/c-LXENEBV7.js +0 -1
  236. package/dist/m/c-PPOSCAQD.js +0 -1
  237. package/dist/m/c-QQHEX6HA.js +0 -1
  238. package/dist/m/c-R7RMANJI.js +0 -9
  239. package/dist/m/c-RI4NT75Y.js +0 -1
  240. package/dist/m/c-RUJKSUQW.js +0 -1
  241. package/dist/m/c-T4VO4OCB.js +0 -1
  242. package/dist/m/c-TR6RMDQ7.js +0 -3
  243. package/dist/m/c-UKHTLWUW.js +0 -1
  244. package/dist/m/c-UNN7LWCT.js +0 -1
  245. package/dist/m/c-V7PZZRG7.js +0 -1
  246. package/dist/m/c-VL52ADDR.js +0 -1
  247. package/dist/m/c-WWDBYXLK.js +0 -1
  248. package/dist/m/c-XNNW2CWK.js +0 -1
  249. package/dist/m/c-Z4VRC7GU.js +0 -7
  250. package/dist/m/c-Z6K52OLP.js +0 -19
  251. package/dist/m/c-ZDY2MDAA.js +0 -21
  252. package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
  253. package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
  254. package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
  255. package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
  256. package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
  257. package/dist/m/core/recovery-TBBSICE2.js +0 -1
  258. package/dist/m/core/save-WUCHTQBE.js +0 -1
  259. package/dist/m/core/save-core-FJVHT5M3.js +0 -1
  260. package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
  261. package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
  262. package/dist/m/events/index-23DPTUT4.js +0 -1
  263. package/dist/m/internals/index-WOVAP3RU.js +0 -1
  264. package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
  265. package/dist/m/loader-HXX6YNH5.js +0 -1
  266. package/dist/m/manifest-F22DEDAC.js +0 -1
  267. package/dist/m/manifest-edit-C3C7376B.js +0 -1
  268. package/dist/m/options/options-2LH76JQ3.js +0 -1
  269. package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
  270. package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
  271. package/dist/m/plugins/live-NUTTXSBS.js +0 -1
  272. package/dist/m/plugins/page-KSUH7G6J.js +0 -2
  273. package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
  274. package/dist/m/plugins/people-AJJ32YCG.js +0 -1
  275. package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
  276. package/dist/m/plugins/source-KHK5UUSK.js +0 -1
  277. package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
  278. package/dist/m/plugins/upload-XSGNILUY.js +0 -1
  279. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
  280. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
  281. package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
  282. package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
  283. /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-NRQDSWX6.js.LEGAL.txt} +0 -0
@@ -95,6 +95,11 @@
95
95
  article[data-page] {
96
96
  --page-link: #164583;
97
97
  --page-font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
98
+ --page-color-gray: #5B5953; --page-color-brown: #8A5A1E; --page-color-orange: #B4531A; --page-color-yellow: #7A6210; --page-color-green: #3E6A28;
99
+ --page-color-blue: #1F5A8A; --page-color-purple: #5E4596; --page-color-pink: #A23E6E; --page-color-red: #9E3B2F;
100
+ --page-mark-bg-gray: rgba(35, 34, 31, 0.12); --page-mark-bg-brown: rgba(138, 90, 30, 0.20); --page-mark-bg-orange: rgba(180, 83, 26, 0.20);
101
+ --page-mark-bg-yellow: rgba(122, 98, 16, 0.22); --page-mark-bg-green: rgba(62, 106, 40, 0.20); --page-mark-bg-blue: rgba(31, 90, 138, 0.18);
102
+ --page-mark-bg-purple: rgba(94, 69, 150, 0.18); --page-mark-bg-pink: rgba(162, 62, 110, 0.18); --page-mark-bg-red: rgba(158, 59, 47, 0.18);
98
103
  min-height: 100vh;
99
104
  box-sizing: border-box;
100
105
  padding: var(--page-pad-top) 16px 30vh;
@@ -135,7 +140,26 @@
135
140
  article[data-page] th, article[data-page] td { min-width: 120px; padding: 6px 10px; border-inline-end: 1px solid var(--page-rule); border-block-end: 1px solid var(--page-rule); text-align: start; white-space: pre-wrap; }
136
141
  article[data-page] th { background: var(--page-code-bg); color: var(--page-ink-soft); font-weight: 600; }
137
142
  article[data-page] th[data-kind="number"], article[data-page] td[data-kind="number"] { text-align: end; }
138
- article[data-page] blockquote { color: var(--page-ink-soft); border-inline-start: 1px solid var(--page-rule); padding-inline-start: var(--page-indent); }
143
+ article[data-page] blockquote { color: var(--page-ink-soft); padding-inline-start: var(--page-indent); }
144
+ article[data-page] span[data-color="gray"] { color: var(--page-color-gray); }
145
+ article[data-page] span[data-color="brown"] { color: var(--page-color-brown); }
146
+ article[data-page] span[data-color="orange"] { color: var(--page-color-orange); }
147
+ article[data-page] span[data-color="yellow"] { color: var(--page-color-yellow); }
148
+ article[data-page] span[data-color="green"] { color: var(--page-color-green); }
149
+ article[data-page] span[data-color="blue"] { color: var(--page-color-blue); }
150
+ article[data-page] span[data-color="purple"] { color: var(--page-color-purple); }
151
+ article[data-page] span[data-color="pink"] { color: var(--page-color-pink); }
152
+ article[data-page] span[data-color="red"] { color: var(--page-color-red); }
153
+ article[data-page] mark[data-color="gray"] { background: var(--page-mark-bg-gray); }
154
+ article[data-page] mark[data-color="brown"] { background: var(--page-mark-bg-brown); }
155
+ article[data-page] mark[data-color="orange"] { background: var(--page-mark-bg-orange); }
156
+ article[data-page] mark[data-color="yellow"] { background: var(--page-mark-bg-yellow); }
157
+ article[data-page] mark[data-color="green"] { background: var(--page-mark-bg-green); }
158
+ article[data-page] mark[data-color="blue"] { background: var(--page-mark-bg-blue); }
159
+ article[data-page] mark[data-color="purple"] { background: var(--page-mark-bg-purple); }
160
+ article[data-page] mark[data-color="pink"] { background: var(--page-mark-bg-pink); }
161
+ article[data-page] mark[data-color="red"] { background: var(--page-mark-bg-red); }
162
+ article[data-page] mark { color: inherit; }
139
163
  }
140
164
 
141
165
 
@@ -53849,6 +53849,12 @@ var schema = new Qr.Schema({
53849
53849
  "data-checked": n.attrs.checked ? "true" : "false"
53850
53850
  }), 0]
53851
53851
  },
53852
+ quote: {
53853
+ group: "block",
53854
+ content: "inline*",
53855
+ attrs: block,
53856
+ toDOM: (n) => ["blockquote", ref(n), 0]
53857
+ },
53852
53858
  callout: {
53853
53859
  group: "block",
53854
53860
  content: "inline*",
@@ -53977,6 +53983,33 @@ var schema = new Qr.Schema({
53977
53983
  attrs: { href: {} },
53978
53984
  inclusive: false,
53979
53985
  toDOM: (m2) => ["a", { href: safeHref(m2.attrs.href) }, 0]
53986
+ },
53987
+ // A person mention is ordinary text with this mark over it: the name is the label
53988
+ // and `id` is the people registry's reference. It links nowhere and notifies nobody.
53989
+ mention: {
53990
+ attrs: { id: {} },
53991
+ inclusive: false,
53992
+ excludes: "mention date link",
53993
+ toDOM: (m2) => ["span", { "data-page-mention": m2.attrs.id }, 0]
53994
+ },
53995
+ // A date mention is the calendar date as ordinary ISO text with this mark over it:
53996
+ // `date` is YYYY-MM-DD, and no time, zone, reminder or relative word is stored.
53997
+ date: {
53998
+ attrs: { date: {} },
53999
+ inclusive: false,
54000
+ excludes: "mention date link",
54001
+ toDOM: (m2) => ["time", { datetime: m2.attrs.date, "data-page-date": "" }, 0]
54002
+ },
54003
+ // The floor-1 formats (format-native.js): written as named descriptors,
54004
+ // drawn as the elements the saved article keeps for them.
54005
+ underline: { toDOM: () => ["u", 0] },
54006
+ text_color: {
54007
+ attrs: { color: {} },
54008
+ toDOM: (m2) => ["span", { "data-color": m2.attrs.color }, 0]
54009
+ },
54010
+ highlight: {
54011
+ attrs: { color: {} },
54012
+ toDOM: (m2) => ["mark", { "data-color": m2.attrs.color }, 0]
53980
54013
  }
53981
54014
  }
53982
54015
  });
@@ -54125,23 +54158,44 @@ function entriesOfYjs(fields) {
54125
54158
  return normalizeEntries(entries);
54126
54159
  }
54127
54160
 
54161
+ // src/page/format-native.js
54162
+ var NATIVE_NAMES = /* @__PURE__ */ new Set(["underline", "text_color", "highlight"]);
54163
+ var PALETTE = ["gray", "brown", "orange", "yellow", "green", "blue", "purple", "pink", "red"];
54164
+ var COLORS = new Set(PALETTE);
54165
+ function nativeAttrs(name, attrs) {
54166
+ const plain4 = attrs !== null && typeof attrs === "object" && !Array.isArray(attrs);
54167
+ if (name === "underline") return plain4 && Object.keys(attrs).length === 0 ? {} : null;
54168
+ if (name === "text_color" || name === "highlight") {
54169
+ const keys = plain4 ? Object.keys(attrs) : [];
54170
+ return keys.length === 1 && keys[0] === "color" && COLORS.has(attrs.color) ? { color: attrs.color } : null;
54171
+ }
54172
+ return null;
54173
+ }
54174
+
54128
54175
  // src/page/inline.js
54129
54176
  var CODES = { strong: "b", em: "i", strike: "s", code: "c", link: "a" };
54177
+ var NATIVE = /* @__PURE__ */ new Set(["underline", "text_color", "highlight"]);
54130
54178
  var NAMES = Object.fromEntries(Object.entries(CODES).map(([name, code]) => [code, name]));
54131
54179
  function markKey(marks) {
54132
54180
  let key = "";
54181
+ const named = [];
54133
54182
  for (const mark of marks) {
54134
54183
  if (isOpaque(mark)) {
54135
54184
  key += keyOf(mark.attrs.entries);
54136
54185
  continue;
54137
54186
  }
54138
54187
  const code = CODES[mark.type.name];
54139
- if (!code) throw new TypeError(`page: the mark ${mark.type.name} has no key code`);
54188
+ if (!code) {
54189
+ const native = nativeAttrs(mark.type.name, mark.attrs);
54190
+ if (!native && NATIVE.has(mark.type.name)) throw new TypeError(`page: the mark ${mark.type.name} has no key code`);
54191
+ named.push({ name: mark.type.name, attrs: native ?? mark.attrs });
54192
+ continue;
54193
+ }
54140
54194
  key += code;
54141
54195
  const names = Object.keys(mark.type.attrs);
54142
54196
  if (names.length) key += JSON.stringify(names.map((name) => mark.attrs[name]));
54143
54197
  }
54144
- return key;
54198
+ return named.length ? key + keyOf(named) : key;
54145
54199
  }
54146
54200
  var parsed = /* @__PURE__ */ new WeakMap();
54147
54201
  function marksOf(schema2, key) {
@@ -54153,15 +54207,23 @@ function marksOf(schema2, key) {
54153
54207
  marks = [];
54154
54208
  const opaque = [];
54155
54209
  for (const desc of descriptors(key)) {
54156
- const name = desc.code === void 0 ? null : NAMES[desc.code];
54210
+ if (desc.code === void 0) {
54211
+ const native = nativeAttrs(desc.name, desc.attrs);
54212
+ const type = schema2.marks[desc.name];
54213
+ if (native) marks = type.create(native).addToSet(marks);
54214
+ else if (type && !NATIVE.has(desc.name) && Object.keys(desc.attrs ?? {}).every((field) => Object.hasOwn(type.attrs, field))) marks = type.create(desc.attrs).addToSet(marks);
54215
+ else opaque.push(desc);
54216
+ continue;
54217
+ }
54218
+ const name = NAMES[desc.code];
54157
54219
  if (!name) {
54158
54220
  opaque.push(desc);
54159
54221
  continue;
54160
54222
  }
54161
- const type = schema2.marks[name];
54162
- if (!type) throw new TypeError(`page: unknown mark code ${desc.code} in ${key}`);
54163
- const attrs = desc.attrs ? Object.fromEntries(Object.keys(type.attrs).map((field, j3) => [field, desc.attrs[j3]])) : null;
54164
- marks = type.create(attrs).addToSet(marks);
54223
+ const coded = schema2.marks[name];
54224
+ if (!coded) throw new TypeError(`page: unknown mark code ${desc.code} in ${key}`);
54225
+ const attrs = desc.attrs ? Object.fromEntries(Object.keys(coded.attrs).map((field, j3) => [field, desc.attrs[j3]])) : null;
54226
+ marks = coded.create(attrs).addToSet(marks);
54165
54227
  }
54166
54228
  if (opaque.length) marks = opaqueMark(entriesOf(opaque)).addToSet(marks);
54167
54229
  cache.set(key, marks);
@@ -54404,6 +54466,9 @@ var KNOWN = new Set(CODES2);
54404
54466
  var OPAQUE = "data-clay-page-opaque";
54405
54467
  var VALUE = "data-clay-page-value";
54406
54468
  var MARKS = "data-clay-page-marks";
54469
+ var MENTION = "data-page-mention";
54470
+ var DATE = "data-page-date";
54471
+ var DAY = /^\d{4}-\d{2}-\d{2}$/;
54407
54472
  var ENVELOPE = "1";
54408
54473
  var STRUCTURAL = /* @__PURE__ */ new Set(["id", "t", "parent", "c", "m"]);
54409
54474
  var knownType = (t) => Object.hasOwn(DEFAULTS, t);
@@ -54412,6 +54477,7 @@ var DEFAULTS = {
54412
54477
  heading: { parent: null, level: 2 },
54413
54478
  list_item: { parent: null, kind: "bullet", indent: 0, checked: false, owner: null, due: null },
54414
54479
  callout: { parent: null },
54480
+ quote: { parent: null },
54415
54481
  code_block: { parent: null, lang: "" },
54416
54482
  toggle: { parent: null, open: false },
54417
54483
  divider: { parent: null },
@@ -54423,7 +54489,7 @@ var DEFAULTS = {
54423
54489
  tool: { parent: null, artifact: null, rev: null, state: {} },
54424
54490
  preserved: { parent: null, html: "" }
54425
54491
  };
54426
- var TEXT = /* @__PURE__ */ new Set(["paragraph", "heading", "list_item", "callout", "code_block", "toggle"]);
54492
+ var TEXT = /* @__PURE__ */ new Set(["paragraph", "heading", "list_item", "callout", "quote", "code_block", "toggle"]);
54427
54493
  var same = (a, b2) => canon(a) === canon(b2);
54428
54494
  function orderedIds(map2) {
54429
54495
  return Object.keys(map2).sort((x3, y2) => {
@@ -54497,20 +54563,26 @@ function runsOf2(units) {
54497
54563
  return out;
54498
54564
  }
54499
54565
  function marksOfKey(key) {
54500
- const marks = {};
54501
- if (!key || key[0] === "@") return { marks, entries: [] };
54566
+ const marks = {}, native = [];
54567
+ if (!key || key[0] === "@") return { marks, native: [], entries: [] };
54502
54568
  const opaque = [];
54503
54569
  for (const desc of descriptors(key)) {
54504
- if (desc.name === void 0 && KNOWN.has(desc.code)) marks[desc.code] = desc.attrs ?? true;
54570
+ if (desc.name === void 0) {
54571
+ if (KNOWN.has(desc.code)) marks[desc.code] = desc.attrs ?? true;
54572
+ else opaque.push(desc);
54573
+ continue;
54574
+ }
54575
+ const attrs = nativeAttrs(desc.name, desc.attrs);
54576
+ if (attrs) native.push({ name: desc.name, attrs });
54505
54577
  else opaque.push(desc);
54506
54578
  }
54507
- return { marks, entries: opaque.length ? entriesOf(opaque) : [] };
54579
+ return { marks, native, entries: opaque.length ? entriesOf(opaque) : [] };
54508
54580
  }
54509
54581
  var unitKey = (key, text4, i) => {
54510
54582
  if (!key) return "";
54511
54583
  if (key[0] === "@") return key === BREAK2 && text4[i] === ATOM2 ? key : "";
54512
- const { marks, entries } = marksOfKey(key);
54513
- return keyOfMarks(safeMarks(marks), entries);
54584
+ const { marks, native, entries } = marksOfKey(key);
54585
+ return keyOfMarks(safeMarks(marks), native.length ? [...entries, ...entriesOf(native)] : entries);
54514
54586
  };
54515
54587
  var SCRIPT_URL = /^(javascript|vbscript|data):/i;
54516
54588
  var safeMarks = (marks) => {
@@ -54715,6 +54787,7 @@ var revOf = (model) => hash64(canon(model));
54715
54787
  var escText = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\r/g, "&#13;");
54716
54788
  var escAttr = (s) => escText(String(s)).replace(/"/g, "&quot;");
54717
54789
  var attr2 = (name, value) => value === null || value === void 0 ? "" : ` ${name}="${escAttr(value)}"`;
54790
+ var NATIVE_TAGS = { underline: "u", text_color: "span", highlight: "mark" };
54718
54791
  function renderInline(c, m2, marksEnvelopes = true) {
54719
54792
  const units = unitsOf2(m2, c.length) || [];
54720
54793
  let html3 = "";
@@ -54732,14 +54805,20 @@ function renderInline(c, m2, marksEnvelopes = true) {
54732
54805
  }
54733
54806
  let j3 = i + 1;
54734
54807
  while (j3 < c.length && (units[j3] || "") === key && !(c[j3] === ATOM2 && key[0] === "@")) j3++;
54735
- const { marks, entries } = marksOfKey(key);
54808
+ const { marks, native, entries } = marksOfKey(key);
54736
54809
  const want = NEST.filter((code) => marks[code]).map((code) => ({ code, sig: code + JSON.stringify(marks[code]), tag: code === "a" ? "a" : TAGS[code], href: code === "a" ? marks.a[0] : null }));
54737
- if (entries.length && marksEnvelopes) want.push({ sig: `marks${canon(entries)}`, tag: "span", entries });
54810
+ for (const n of native) want.push({ sig: `native${n.name}${canon(n.attrs)}`, tag: NATIVE_TAGS[n.name], color: n.attrs.color ?? null });
54811
+ const mention = entries.find((entry) => entry.name === "mention");
54812
+ const date = entries.find((entry) => entry.name === "date");
54813
+ const day = date && DAY.test(String(date.attrs.date)) ? String(date.attrs.date) : null;
54814
+ if (entries.length && marksEnvelopes) want.push({ sig: `marks${canon(entries)}`, tag: day ? "time" : "span", entries, mention: mention?.attrs.id, day });
54738
54815
  let keep = 0;
54739
54816
  while (keep < open.length && keep < want.length && open[keep].sig === want[keep].sig) keep++;
54740
54817
  closeTo(keep);
54741
54818
  for (const w3 of want.slice(keep)) {
54742
- html3 += w3.entries ? `<span${marksEnvelope(w3.entries)}>` : w3.tag === "a" ? `<a${attr2("href", w3.href)}>` : `<${w3.tag}>`;
54819
+ if (w3.entries) html3 += w3.day ? `<time${attr2("datetime", w3.day)}${attr2(DATE, "")}${marksEnvelope(w3.entries)}>` : `<span${marksEnvelope(w3.entries)}${attr2(MENTION, w3.mention)}>`;
54820
+ else if (w3.tag === "a") html3 += `<a${attr2("href", w3.href)}>`;
54821
+ else html3 += w3.color ? `<${w3.tag}${attr2("data-color", w3.color)}>` : `<${w3.tag}>`;
54743
54822
  open.push(w3);
54744
54823
  }
54745
54824
  html3 += escText(c.slice(i, j3));
@@ -54765,6 +54844,8 @@ function renderLeaf(item, clean, tables) {
54765
54844
  }
54766
54845
  case "callout":
54767
54846
  return `<aside${at2} class="page-callout">${inlineOf(item)}</aside>`;
54847
+ case "quote":
54848
+ return `<blockquote${at2}>${inlineOf(item)}</blockquote>`;
54768
54849
  case "code_block":
54769
54850
  return `<pre${at2}${attr2("data-lang", item.lang || null)}><code>${escText(item.c ?? "")}</code></pre>`;
54770
54851
  case "divider":
@@ -54894,6 +54975,13 @@ function renderBlocks(blocks, opts) {
54894
54975
  }
54895
54976
  var DROP_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "TEMPLATE", "NOSCRIPT"]);
54896
54977
  var INLINE_TAGS = /* @__PURE__ */ new Set(["A", "ABBR", "B", "BDI", "BDO", "BR", "CITE", "CODE", "DATA", "DEL", "DFN", "EM", "FONT", "I", "INS", "KBD", "LABEL", "MARK", "Q", "S", "SAMP", "SMALL", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "TIME", "U", "VAR", "WBR"]);
54978
+ function nativeOfEl(tag, el3) {
54979
+ if (tag === "u") return { name: "underline", attrs: {} };
54980
+ if (tag !== "mark" && tag !== "span") return null;
54981
+ if (tag === "span" && !el3.hasAttribute("data-color")) return null;
54982
+ const name = tag === "mark" ? "highlight" : "text_color";
54983
+ return { name, attrs: { color: el3.getAttribute("data-color") } };
54984
+ }
54897
54985
  function inlineOfEl(el3, skip = () => false) {
54898
54986
  let c = "";
54899
54987
  const units = [];
@@ -54919,7 +55007,7 @@ function inlineOfEl(el3, skip = () => false) {
54919
55007
  continue;
54920
55008
  }
54921
55009
  const next2 = { ...marks };
54922
- if (tag === "span" && child.hasAttribute(MARKS)) {
55010
+ if ((tag === "span" || tag === "time") && child.hasAttribute(MARKS)) {
54923
55011
  walk2(child, next2, normalizeEntries([...entries, ...entriesOfEnvelope(child.getAttribute(MARKS))]));
54924
55012
  continue;
54925
55013
  }
@@ -54928,7 +55016,8 @@ function inlineOfEl(el3, skip = () => false) {
54928
55016
  else if (tag === "s" || tag === "del" || tag === "strike") next2.s = true;
54929
55017
  else if (tag === "code") next2.c = true;
54930
55018
  else if (tag === "a" && child.hasAttribute("href")) next2.a = [child.getAttribute("href")];
54931
- walk2(child, next2, entries);
55019
+ const named = nativeOfEl(tag, child);
55020
+ walk2(child, next2, named ? [...entries, named] : entries);
54932
55021
  }
54933
55022
  };
54934
55023
  walk2(el3, {}, []);
@@ -55004,6 +55093,7 @@ function parseArticle(article) {
55004
55093
  if (tag === "P" && !onlyImage) return push(el3, "paragraph", parent2, { ...omitDefaults("paragraph", P3), ...tf2(el3) });
55005
55094
  if (/^H[1-6]$/.test(tag)) return push(el3, "heading", parent2, { ...omitDefaults("heading", { ...P3, level: +tag[1] }), ...tf2(el3) });
55006
55095
  if (tag === "ASIDE") return push(el3, "callout", parent2, { ...omitDefaults("callout", P3), ...tf2(el3) });
55096
+ if (tag === "BLOCKQUOTE") return push(el3, "quote", parent2, { ...omitDefaults("quote", P3), ...tf2(el3) });
55007
55097
  if (tag === "PRE") {
55008
55098
  const code = el3.querySelector(":scope > code");
55009
55099
  return push(el3, "code_block", parent2, { ...omitDefaults("code_block", { ...P3, lang: el3.getAttribute("data-lang") || "" }), c: (code || el3).textContent });
@@ -59100,7 +59190,7 @@ function marksFromAttributes(a, blockType) {
59100
59190
  const marks = [];
59101
59191
  let unknown = null;
59102
59192
  for (const [n, v3] of best) {
59103
- if (schema.marks[n]) marks.push(schema.marks[n].create(v3 === true ? null : v3));
59193
+ if (schema.marks[n] && (!NATIVE_NAMES.has(n) || nativeAttrs(n, v3))) marks.push(schema.marks[n].create(v3 === true ? null : v3));
59104
59194
  else (unknown ??= {})[n] = v3;
59105
59195
  }
59106
59196
  if (unknown) {
@@ -71394,7 +71484,7 @@ function merge32(base, local, remote, options = {}) {
71394
71484
  }
71395
71485
 
71396
71486
  // src/page/merge.js
71397
- var TEXT2 = /* @__PURE__ */ new Set(["paragraph", "heading", "list_item", "callout", "code_block", "toggle"]);
71487
+ var TEXT2 = /* @__PURE__ */ new Set(["paragraph", "heading", "list_item", "callout", "quote", "code_block", "toggle"]);
71398
71488
  var same2 = (a, b2) => canon(a) === canon(b2);
71399
71489
  function inlineOf2(item, doc) {
71400
71490
  const article = doc.createElement("article");
@@ -72757,11 +72847,12 @@ __export(checkpoint_exports, {
72757
72847
 
72758
72848
  // src/page/document-title.js
72759
72849
  var UNTITLED = "Untitled";
72760
- function documentTitle(items, { custom = false, currentTitle = "" } = {}) {
72850
+ function documentTitle(items, { custom = false, currentTitle = "", keepWithoutHeading = false } = {}) {
72761
72851
  if (custom) return currentTitle;
72762
72852
  const first2 = items.find(
72763
72853
  (item) => item.t === "heading" && (item.level ?? 2) === 1 && item.parent == null
72764
72854
  );
72855
+ if (!first2 && keepWithoutHeading && !["", UNTITLED].includes(String(currentTitle).trim())) return currentTitle;
72765
72856
  return String(first2?.c ?? "").replace(/[\s\uFFFC]+/gu, " ").trim() || UNTITLED;
72766
72857
  }
72767
72858
 
@@ -72973,7 +73064,7 @@ function renderCheckpoint(currentHtml, capture, { createDocument, recovery = fal
72973
73064
  edits.push({ start: articleLoc.startTag.startOffset, end: articleLoc.startTag.endOffset, text: articleStart.slice(0, -1) + rev + ">" });
72974
73065
  edits.push({ start: articleLoc.startTag.endOffset, end: articleLoc.endTag.startOffset, text: rendered.html });
72975
73066
  if (!customTitleOf(placement.titleModes)) {
72976
- const edit = titleEdit(placement, documentTitle(rendered.model));
73067
+ const edit = titleEdit(placement, documentTitle(rendered.model, { currentTitle: titleTextOf(placement.title), keepWithoutHeading: true }));
72977
73068
  if (edit) edits.push(edit);
72978
73069
  }
72979
73070
  return { kind: "ok", html: replace(currentHtml, edits), revision: rendered.rev };
@@ -72986,7 +73077,7 @@ function isCheckpointCurrent(currentHtml, capture) {
72986
73077
  const rendered = renderBlocks(decoded.blocks);
72987
73078
  const articleLoc = placement.article.sourceCodeLocation;
72988
73079
  if (attr3(placement.article, "data-rev") !== rendered.rev || currentHtml.slice(articleLoc.startTag.endOffset, articleLoc.endTag.startOffset) !== rendered.html) return false;
72989
- if (!customTitleOf(placement.titleModes) && titleTextOf(placement.title) !== documentTitle(rendered.model)) return false;
73080
+ if (!customTitleOf(placement.titleModes) && titleTextOf(placement.title) !== documentTitle(rendered.model, { currentTitle: titleTextOf(placement.title), keepWithoutHeading: true })) return false;
72990
73081
  return capture.stores.every((store) => {
72991
73082
  const node = placement.tags.get(store.name);
72992
73083
  const loc = node.sourceCodeLocation;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panphora/clayjs",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "type": "module",
5
5
  "description": "clayjs: malleable documents. Save lifecycle for self-saving HTML.",
6
6
  "license": "MIT-0",
@@ -3,4 +3,5 @@
3
3
  // engine's first pass has painted the page, then releases it (loader.js,
4
4
  // mountDerivedEngine). Every other page, and any test that imports save.js
5
5
  // directly, never sets the hold.
6
- export const bootPhase = { holdSettle: false };
6
+ // servedHash: the fragment the page was served with, read at this module's evaluation.
7
+ export const bootPhase = { holdSettle: false, servedHash: typeof window === "undefined" ? "" : window.location.hash };
@@ -1,5 +1,8 @@
1
1
  import { serializeRecoveryClone, captureRecoveryPage } from "./snapshot.js";
2
2
  import { rowOf } from "./conflict-presentation.js";
3
+ import { documentName, downloadHtml } from "./download.js";
4
+
5
+ export { documentName, downloadHtml };
3
6
 
4
7
  // Download my copy: one file, openable in a browser as the page this tab had, with
5
8
  // the replaced edits listed in a JSON block for anyone (or any tool) putting them
@@ -12,14 +15,6 @@ function stamp(d = new Date()) {
12
15
  return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
13
16
  }
14
17
 
15
- export function documentName(pathname = location.pathname) {
16
- const last = pathname.split("/").filter(Boolean).pop();
17
- if (!last) return pathname.endsWith("/") || pathname === "" ? "index" : "page";
18
- let name = last;
19
- try { name = decodeURIComponent(last); } catch {}
20
- return name.replace(/\.(html?|htmlclay)$/i, "") || "page";
21
- }
22
-
23
18
  function embed(html, data) {
24
19
  const json = JSON.stringify(data).replace(/</g, "\\u003c");
25
20
  const block = `<script type="application/json" id="clay-lost-edits" clay="no-save">${json}</script>`;
@@ -67,27 +62,6 @@ export function buildRecovery(records, { refused = false } = {}) {
67
62
  };
68
63
  }
69
64
 
70
- export function downloadHtml({ html, name }) {
71
- let url = null;
72
- try {
73
- url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
74
- const a = document.createElement("a");
75
- a.setAttribute("clay", "no-save no-watch no-snapshot");
76
- a.href = url;
77
- a.download = name;
78
- a.style.setProperty("display", "none", "important");
79
- document.body.appendChild(a);
80
- a.click();
81
- a.remove();
82
- // Revoked late: Safari cancels a download whose URL goes away too soon.
83
- setTimeout(() => URL.revokeObjectURL(url), 40000);
84
- return true;
85
- } catch (err) {
86
- console.error("clayjs: could not build the download", err);
87
- if (url) URL.revokeObjectURL(url);
88
- return false;
89
- }
90
- }
91
65
 
92
66
  // The review panel's own copy: the page a reload kept, when there is one, with every
93
67
  // stored row it drew listed beside it as JSON. The rows are already plain, so nothing
@@ -0,0 +1,32 @@
1
+ // Saving a page as a file the browser downloads. Kept apart from the conflict copy
2
+ // builders so an editor that only offers "download this page" loads none of them.
3
+
4
+ export function documentName(pathname = location.pathname) {
5
+ const last = pathname.split("/").filter(Boolean).pop();
6
+ if (!last) return pathname.endsWith("/") || pathname === "" ? "index" : "page";
7
+ let name = last;
8
+ try { name = decodeURIComponent(last); } catch {}
9
+ return name.replace(/\.(html?|htmlclay)$/i, "") || "page";
10
+ }
11
+
12
+ export function downloadHtml({ html, name }) {
13
+ let url = null;
14
+ try {
15
+ url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
16
+ const a = document.createElement("a");
17
+ a.setAttribute("clay", "no-save no-watch no-snapshot");
18
+ a.href = url;
19
+ a.download = name;
20
+ a.style.setProperty("display", "none", "important");
21
+ document.body.appendChild(a);
22
+ a.click();
23
+ a.remove();
24
+ // Revoked late: Safari cancels a download whose URL goes away too soon.
25
+ setTimeout(() => URL.revokeObjectURL(url), 40000);
26
+ return true;
27
+ } catch (err) {
28
+ console.error("clayjs: could not build the download", err);
29
+ if (url) URL.revokeObjectURL(url);
30
+ return false;
31
+ }
32
+ }
@@ -149,6 +149,24 @@ export async function hostPageFormat() {
149
149
  return meta.extensions.includes("page-format-v1") ? 1 : 0;
150
150
  }
151
151
 
152
+ /**
153
+ * Where this host serves the page state of one accepted version, or null.
154
+ *
155
+ * Two things have to be true: the host announced `page-recovery-v1` by name, and its
156
+ * document block named the resource as `pageState.href`. A host that has the resource
157
+ * but has not announced the extension is still launching it, and a client that inferred
158
+ * either one from the other would retire journaled work against a pair nobody promised.
159
+ * Absent, empty or not a string is null, which means no conditional read and no proof
160
+ * from one: the records stay.
161
+ *
162
+ * @returns {Promise<?string>}
163
+ */
164
+ export async function hostPageStateHref() {
165
+ const meta = await hostMeta();
166
+ if (!meta.extensions.includes("page-recovery-v1")) return null;
167
+ return meta.document?.pageState?.href || null;
168
+ }
169
+
152
170
  /**
153
171
  * Whether this host keeps a version history a person can go back to.
154
172
  *
@@ -881,11 +881,15 @@ async function sendSave(content, { captureId = null, conversionHeader = null, at
881
881
  // One private implementation behind every entry point, so the hooks see the same
882
882
  // thing whether the bytes came from a bound capture or a raw caller. `captureId`
883
883
  // is null for a raw save: it belongs to no capture and can confirm none.
884
- function runSave(html, captureId, callback, conversion, capture = null) {
884
+ //
885
+ // `pageFormatFloor` is internal: one save may carry a floor this tab has not
886
+ // noted yet, which is how the format gate's activation writes the marker. It
887
+ // raises these bytes only, never what the tab knows the document at.
888
+ function runSave(html, captureId, callback, conversion, capture = null, pageFormatFloor = 0) {
885
889
  // A capture taken before the floor rose cannot drop the marker by omitting it:
886
890
  // the bytes that go out carry the floor this tab last saw. At floor 0 this is
887
891
  // the string itself, unchanged.
888
- html = withPageFormatFloor(html, knownPageFormatFloor());
892
+ html = withPageFormatFloor(html, Math.max(knownPageFormatFloor(), pageFormatFloor));
889
893
  return new Promise((resolve) => {
890
894
  const attempts = [];
891
895
  // Read once, before the first request: the artifact belongs to the capture that
@@ -994,8 +998,8 @@ export function saveHtml(html, callback = () => {}) {
994
998
  * @param {Object} [options.conversion] - {source:'file', base} or {source:'room', base, epoch}
995
999
  * @returns {Promise<Object>}
996
1000
  */
997
- export function saveCapturedHtml(html, captureId, callback = () => {}, { conversion, capture = null } = {}) {
998
- return runSave(html, captureId, callback, conversion, capture);
1001
+ export function saveCapturedHtml(html, captureId, callback = () => {}, { conversion, capture = null, pageFormatFloor } = {}) {
1002
+ return runSave(html, captureId, callback, conversion, capture, pageFormatFloor);
999
1003
  }
1000
1004
 
1001
1005
  /**
package/src/core/save.js CHANGED
@@ -1083,8 +1083,8 @@ function savePagePermitted(permit, callback = () => {}) {
1083
1083
  * @param {Function} callback - Optional callback for custom handling
1084
1084
  * @returns {Promise<{msg: string, msgType: string}>}
1085
1085
  */
1086
- export function savePageForce(callback = () => {}, { quietConflict = false } = {}) {
1087
- if (capabilityPending) return capabilityKnown.then(() => savePageForce(callback, { quietConflict }));
1086
+ export function savePageForce(callback = () => {}, { quietConflict = false, pageFormatFloor = 0 } = {}) {
1087
+ if (capabilityPending) return capabilityKnown.then(() => savePageForce(callback, { quietConflict, pageFormatFloor }));
1088
1088
  return new Promise((resolve) => {
1089
1089
  if (!isEditMode && !window.clay?.testMode) {
1090
1090
  const skipped = skipped_('Not in edit mode');
@@ -1110,7 +1110,7 @@ export function savePageForce(callback = () => {}, { quietConflict = false } = {
1110
1110
  }
1111
1111
 
1112
1112
  const prepare = window.clay?.source?.ensureForSave?.();
1113
- if (prepare) return prepare.then(() => resolve(savePageForce(callback, { quietConflict })));
1113
+ if (prepare) return prepare.then(() => resolve(savePageForce(callback, { quietConflict, pageFormatFloor })));
1114
1114
 
1115
1115
  const wasOffline = !navigator.onLine;
1116
1116
  if (wasOffline) {
@@ -1148,7 +1148,7 @@ export function savePageForce(callback = () => {}, { quietConflict = false } = {
1148
1148
  }
1149
1149
  resolve(result);
1150
1150
  drainPendingSave();
1151
- }, { capture });
1151
+ }, { capture, pageFormatFloor });
1152
1152
  });
1153
1153
  }
1154
1154
 
@@ -105,6 +105,37 @@ export function attachCaptureArtifact(artifact) {
105
105
  if (activeCapture && artifact) activeCapture.artifact = artifact;
106
106
  }
107
107
 
108
+ // The page state of one save capture: the exact value its page tag carried when its
109
+ // bytes were serialized, copied so the edits that follow the capture can never reach it.
110
+ // Keyed by the capture id every accepted event carries, and dropped when the capture
111
+ // settles, so an accepted save reads the state it was captured from rather than the
112
+ // store's value at the moment the host answered.
113
+ let capturePageStateReader = null;
114
+ const capturePageStates = new Map();
115
+
116
+ /**
117
+ * Register the reader that finds a page state in a final save clone.
118
+ *
119
+ * A page's own code registers it (page/save-projection.js), so a document without a
120
+ * page pays one null check per capture and nothing else. Read from the clone rather
121
+ * than from a store because that tag is what the file receives, after every transform
122
+ * and every authored save handler has run.
123
+ */
124
+ export function setCapturePageState(reader) {
125
+ capturePageStateReader = reader;
126
+ }
127
+
128
+ /**
129
+ * The page state pinned to one capture, or null.
130
+ *
131
+ * @param {?number} captureId
132
+ * @returns {?Object}
133
+ */
134
+ export function capturePageState(captureId) {
135
+ if (captureId === null || captureId === undefined) return null;
136
+ return capturePageStates.get(captureId) ?? null;
137
+ }
138
+
108
139
  /** The artifact this capture's renderer attached, or null. */
109
140
  export function captureArtifact(result) {
110
141
  return result ? captureArtifacts.get(result) || null : null;
@@ -112,7 +143,9 @@ export function captureArtifact(result) {
112
143
 
113
144
  /** Forget a capture's artifact: it was skipped, refused, or has now settled. */
114
145
  export function releaseCaptureArtifact(result) {
115
- if (result) captureArtifacts.delete(result);
146
+ if (!result) return;
147
+ captureArtifacts.delete(result);
148
+ capturePageStates.delete(result.captureId);
116
149
  }
117
150
 
118
151
  /**
@@ -263,6 +296,11 @@ function serializeClone(clone) {
263
296
  * cost somebody a save.
264
297
  */
265
298
  function serializeSaveClone(clone, context = null) {
299
+ if (context && capturePageStateReader) {
300
+ let state = null;
301
+ try { state = capturePageStateReader(clone); } catch (_) {}
302
+ if (state) capturePageStates.set(context.captureId, state);
303
+ }
266
304
  const today = serializeClone(clone);
267
305
  if (!saveRenderer) return today;
268
306
  const previous = activeCapture;
@@ -16,10 +16,9 @@ export const MAX_COOLDOWN_MS = 30000;
16
16
 
17
17
  // Adaptive spacing: make the gap after a save at least COST_MULTIPLIER times that
18
18
  // save's synchronous cost, so a three-second save earns twelve seconds of quiet
19
- // rather than spending the quiet window doing the next capture. Off for now, and
20
- // the multiplier is zero while it is off, which makes the cooldown term exactly
21
- // MIN_GAP_MS and keeps today's 1,500 / 10,000 ms cadence.
22
- export const ADAPTIVE_SPACING = false;
19
+ // rather than spending the quiet window doing the next capture. On now that local
20
+ // recovery keeps every keystroke the longer gap could leave unsent.
21
+ export const ADAPTIVE_SPACING = true;
23
22
  export const COST_MULTIPLIER = ADAPTIVE_SPACING ? 4 : 0;
24
23
 
25
24
  // "No such time yet": before the first activity or the first completed save the