@panphora/clayjs 1.15.2 → 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 (551) 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 +84291 -57611
  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-ZUE7PSCI.js +1 -0
  21. package/dist/m/attrs/save-freeze-MYNVXREN.js +1 -0
  22. package/dist/m/c-2AZDZWCK.js +1 -0
  23. package/dist/m/c-2SPVQSHC.js +1 -0
  24. package/dist/m/c-2YYDMLNZ.js +1 -0
  25. package/dist/m/c-336SGU5W.js +1 -0
  26. package/dist/m/c-33JJ6CBX.js +2 -0
  27. package/dist/m/c-3GJ4SDE2.js +1 -0
  28. package/dist/m/c-3H6BZDNJ.js +6 -0
  29. package/dist/m/c-3OWDMNJR.js +2 -0
  30. package/dist/m/c-3RUU4TPX.js +91 -0
  31. package/dist/m/c-3XIRD4NU.js +1 -0
  32. package/dist/m/c-46XYM55L.js +1 -0
  33. package/dist/m/c-4DFEJ2PO.js +3 -0
  34. package/dist/m/c-4JZDZFF6.js +1 -0
  35. package/dist/m/c-4L3BTFRK.js +1 -0
  36. package/dist/m/c-4Q2XTQ2N.js +2 -0
  37. package/dist/m/c-4WKFKEKF.js +1 -0
  38. package/dist/m/c-56QRZTZH.js +1 -0
  39. package/dist/m/c-5J4E7O4Y.js +4 -0
  40. package/dist/m/c-5LKDWQTL.js +19 -0
  41. package/dist/m/c-5QWQ7A5L.js +1 -0
  42. package/dist/m/c-5RJFTLSF.js +1 -0
  43. package/dist/m/c-5V3MOVJT.js +1 -0
  44. package/dist/m/c-5VYOWDTI.js +1 -0
  45. package/dist/m/c-5YIHJ3TJ.js +1 -0
  46. package/dist/m/c-6AZOGAPE.js +1 -0
  47. package/dist/m/c-6CRUUXOX.js +5 -0
  48. package/dist/m/c-6HMB24EV.js +1 -0
  49. package/dist/m/c-6KEVEQ7L.js +1 -0
  50. package/dist/m/c-6NC5I2TV.js +1 -0
  51. package/dist/m/c-74MGYWZF.js +1 -0
  52. package/dist/m/c-7DJDKPGL.js +1 -0
  53. package/dist/m/c-7OLLJPWP.js +1 -0
  54. package/dist/m/c-7SWMYKAI.js +1 -0
  55. package/dist/m/c-A3E3NIMC.js +1 -0
  56. package/dist/m/c-A7LS5UZW.js +1 -0
  57. package/dist/m/c-AMSNPOCX.js +1 -0
  58. package/dist/m/c-AQLFEATB.js +1 -0
  59. package/dist/m/c-B3JM5ZXC.js +1 -0
  60. package/dist/m/c-BBVGTQG7.js +1 -0
  61. package/dist/m/c-BPF2NSPV.js +1 -0
  62. package/dist/m/c-BQ6LOBEE.js +1 -0
  63. package/dist/m/c-BTDI4Y23.js +1 -0
  64. package/dist/m/c-BWFVSBDF.js +1 -0
  65. package/dist/m/c-BYTKL6UX.js +1 -0
  66. package/dist/m/c-CFHW5AMY.js +6 -0
  67. package/dist/m/c-CJ3A7ZKE.js +1 -0
  68. package/dist/m/c-CQHPGKX2.js +1 -0
  69. package/dist/m/c-CYWLPPUS.js +1 -0
  70. package/dist/m/c-CZUSOB7V.js +1 -0
  71. package/dist/m/c-DCFSEJZG.js +1 -0
  72. package/dist/m/c-DCXOXBF3.js +1 -0
  73. package/dist/m/c-DFS4XZJV.js +2 -0
  74. package/dist/m/c-DNENWY4O.js +1 -0
  75. package/dist/m/c-DNZ5IFM6.js +1 -0
  76. package/dist/m/c-E4LIR5X5.js +1 -0
  77. package/dist/m/c-EJTGYSLN.js +1 -0
  78. package/dist/m/c-EVCQ3BKA.js +1 -0
  79. package/dist/m/c-EZU56NQV.js +34 -0
  80. package/dist/m/c-FDNHHZY5.js +1 -0
  81. package/dist/m/c-FILLTJA7.js +1 -0
  82. package/dist/m/c-FRLHSY7E.js +1 -0
  83. package/dist/m/c-FROJUZMV.js +1 -0
  84. package/dist/m/c-G2JPKCTP.js +1 -0
  85. package/dist/m/c-GB6TU7WV.js +1 -0
  86. package/dist/m/c-GFJJEBTU.js +1 -0
  87. package/dist/m/c-GJAEGGXY.js +1 -0
  88. package/dist/m/c-GOWBDM6J.js +1 -0
  89. package/dist/m/c-GQ6F6IDK.js +1 -0
  90. package/dist/m/c-GSFIGKOP.js +1 -0
  91. package/dist/m/c-GY422N3Q.js +1 -0
  92. package/dist/m/c-H4LG442Q.js +1 -0
  93. package/dist/m/c-HA6INT6J.js +1 -0
  94. package/dist/m/c-HAWFNBYG.js +1 -0
  95. package/dist/m/c-HDDEEL77.js +1 -0
  96. package/dist/m/c-HFKUVACH.js +1 -0
  97. package/dist/m/c-HHDFCNHV.js +1 -0
  98. package/dist/m/c-HPURYVZS.js +2 -0
  99. package/dist/m/c-I2ZATQCD.js +2 -0
  100. package/dist/m/c-IEM4NTCS.js +1 -0
  101. package/dist/m/c-IJUK2GPW.js +1 -0
  102. package/dist/m/c-IW6GGOU2.js +1 -0
  103. package/dist/m/c-JAX2667S.js +6 -0
  104. package/dist/m/c-JDCX26UP.js +25 -0
  105. package/dist/m/c-JRKY44DK.js +1 -0
  106. package/dist/m/c-JU3NK25H.js +1 -0
  107. package/dist/m/c-KAU6E43D.js +1 -0
  108. package/dist/m/c-KBS3UV6B.js +1 -0
  109. package/dist/m/c-KGVGROZT.js +1 -0
  110. package/dist/m/c-KI7M4AY3.js +1 -0
  111. package/dist/m/c-KIPQJX4B.js +1 -0
  112. package/dist/m/c-KQ6TYJAP.js +135 -0
  113. package/dist/m/c-KU3ZFADW.js +1 -0
  114. package/dist/m/c-L4WQSC27.js +1 -0
  115. package/dist/m/c-LFPHCXY3.js +1 -0
  116. package/dist/m/c-LRPUO2MX.js +1 -0
  117. package/dist/m/c-LWLBHLVC.js +3 -0
  118. package/dist/m/c-LZJPNR2W.js +1 -0
  119. package/dist/m/c-M2L47S2X.js +7 -0
  120. package/dist/m/c-MFP2ZWD4.js +1 -0
  121. package/dist/m/c-MKSQ4Y57.js +1 -0
  122. package/dist/m/c-MTQHWQC3.js +1 -0
  123. package/dist/m/c-N326R2JD.js +1 -0
  124. package/dist/m/c-N7JPUPGC.js +1 -0
  125. package/dist/m/c-NRPJEVJS.js +1 -0
  126. package/dist/m/c-NUFFJVVJ.js +1 -0
  127. package/dist/m/c-NUHP2XDB.js +1 -0
  128. package/dist/m/c-NUHSPT5P.js +1 -0
  129. package/dist/m/c-O72HVYSA.js +1 -0
  130. package/dist/m/c-OCSZ4QGN.js +1 -0
  131. package/dist/m/c-OIBUTDXI.js +1 -0
  132. package/dist/m/c-OIDRZKAF.js +1 -0
  133. package/dist/m/c-OW5QIGG3.js +1 -0
  134. package/dist/m/c-P7KNDG34.js +1 -0
  135. package/dist/m/c-PGRCK73F.js +1 -0
  136. package/dist/m/c-PQ5LK6QE.js +993 -0
  137. package/dist/m/c-PS5ARVWR.js +9 -0
  138. package/dist/m/c-PZ4XEPUP.js +2 -0
  139. package/dist/m/c-Q622NS2Y.js +1 -0
  140. package/dist/m/c-Q7NQD65V.js +1 -0
  141. package/dist/m/c-QH63LQOS.js +1 -0
  142. package/dist/m/c-QHIWXHRO.js +1 -0
  143. package/dist/m/c-QHWJ5ZIJ.js +1 -0
  144. package/dist/m/c-QICP2YR5.js +1 -0
  145. package/dist/m/c-QJ4OM3G6.js +1 -0
  146. package/dist/m/c-QNKREOL6.js +1 -0
  147. package/dist/m/c-R3L7ZJGB.js +2 -0
  148. package/dist/m/c-R7IWVINQ.js +1 -0
  149. package/dist/m/c-RM7J7BV2.js +1 -0
  150. package/dist/m/c-RSMOODDU.js +1 -0
  151. package/dist/m/c-RVMWGXEJ.js +1 -0
  152. package/dist/m/c-RYUXUUBX.js +1 -0
  153. package/dist/m/c-S4ZEH46B.js +9 -0
  154. package/dist/m/c-S7H2DRCB.js +1 -0
  155. package/dist/m/c-S7LK4KUE.js +1 -0
  156. package/dist/m/c-SDAXDM2D.js +1 -0
  157. package/dist/m/c-SGIA4IEQ.js +188 -0
  158. package/dist/m/c-SMBZG5X4.js +1 -0
  159. package/dist/m/c-SMGU7RT3.js +235 -0
  160. package/dist/m/c-SPIORRYS.js +1 -0
  161. package/dist/m/c-SWLRT66R.js +1 -0
  162. package/dist/m/c-SZGYW5HT.js +1 -0
  163. package/dist/m/c-TCCA5BZN.js +2 -0
  164. package/dist/m/c-TCJTVAFN.js +1 -0
  165. package/dist/m/c-TJ6WOJL3.js +67 -0
  166. package/dist/m/c-TQEMVJCS.js +1 -0
  167. package/dist/m/c-TRS6TM6O.js +1 -0
  168. package/dist/m/c-TYLXX4NF.js +21 -0
  169. package/dist/m/c-U62OB4BT.js +1 -0
  170. package/dist/m/c-U6YWUAR3.js +8 -0
  171. package/dist/m/c-UDCSU5G5.js +2 -0
  172. package/dist/m/c-UDUUJPFZ.js +1 -0
  173. package/dist/m/c-UGHH4MB3.js +1 -0
  174. package/dist/m/c-UL2YUY52.js +1 -0
  175. package/dist/m/c-VAK7SG37.js +1 -0
  176. package/dist/m/c-VDSS4K3Z.js +1 -0
  177. package/dist/m/c-VE6ZMYMC.js +1 -0
  178. package/dist/m/c-VG5XIN4T.js +1 -0
  179. package/dist/m/c-VHM5XQHK.js +1 -0
  180. package/dist/m/c-VHNSBD2M.js +1 -0
  181. package/dist/m/c-VI5RMSVY.js +1 -0
  182. package/dist/m/c-VKEDWYDF.js +1 -0
  183. package/dist/m/c-VRZN56XY.js +1 -0
  184. package/dist/m/c-W5MCHEGA.js +1 -0
  185. package/dist/m/c-WDC5GVKU.js +1 -0
  186. package/dist/m/c-WN5AY3OZ.js +1 -0
  187. package/dist/m/c-WNYBLIOZ.js +1 -0
  188. package/dist/m/c-WRBKEJD4.js +1 -0
  189. package/dist/m/c-WWXIDH4O.js +28 -0
  190. package/dist/m/c-WYFDLITL.js +1 -0
  191. package/dist/m/c-XCNTK76U.js +1 -0
  192. package/dist/m/c-XMS6DPBR.js +1 -0
  193. package/dist/m/c-XN6MBKI6.js +67 -0
  194. package/dist/m/c-XPELB6E5.js +1 -0
  195. package/dist/m/c-XQXZAO7N.js +1 -0
  196. package/dist/m/c-Y5HBBIQ4.js +1 -0
  197. package/dist/m/c-ZE46QDIH.js +1 -0
  198. package/dist/m/c-ZUCOKOHT.js +1 -0
  199. package/dist/m/c-ZVPVXTJF.js +4 -0
  200. package/dist/m/c-ZVRKXKM7.js +1 -0
  201. package/dist/m/c-ZWIOJGMT.js +1 -0
  202. package/dist/m/core/admin-attrs-QHOKPJ7W.js +1 -0
  203. package/dist/m/core/autosave-HRYGUWV6.js +1 -0
  204. package/dist/m/core/conflict-notice-6MROJN2L.js +1 -0
  205. package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
  206. package/dist/m/core/host-permission-FNXVEFNE.js +1 -0
  207. package/dist/m/core/is-edit-mode-KTWA3O5N.js +1 -0
  208. package/dist/m/core/persist-JFYRHMRJ.js +3 -0
  209. package/dist/m/core/recovery-FLTCC2OC.js +1 -0
  210. package/dist/m/core/save-HQZ4S667.js +1 -0
  211. package/dist/m/core/save-core-KGQWFUNH.js +1 -0
  212. package/dist/m/core/snapshot-EGQZNT45.js +1 -0
  213. package/dist/m/core/unsaved-warning-IDJLD66H.js +1 -0
  214. package/dist/m/dom/all-APS5ZSKP.js +1 -0
  215. package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
  216. package/dist/m/events/index-RN2JNGBV.js +1 -0
  217. package/dist/m/internals/index-PVXGPUDA.js +1 -0
  218. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  219. package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
  220. package/dist/m/loader-S4I7GKXR.js +1 -0
  221. package/dist/m/manifest-4C419D67.js +1 -0
  222. package/dist/m/manifest-edit-B077F288.js +1 -0
  223. package/dist/m/manifest.json +3988 -0
  224. package/dist/m/options/options-EJMS3K24.js +1 -0
  225. package/dist/m/page/editor-NNJA4OKD.js +202 -0
  226. package/dist/m/plugins/ai-edit-QN4PMMNL.js +6 -0
  227. package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
  228. package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
  229. package/dist/m/plugins/demo-RM5IR6AO.js +1 -0
  230. package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
  231. package/dist/m/plugins/indicator-URDBBOQO.js +1 -0
  232. package/dist/m/plugins/live-HFWD4OIH.js +1 -0
  233. package/dist/m/plugins/menu-3HIEQSI6.js +138 -0
  234. package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
  235. package/dist/m/plugins/page-JOKB6XPR.js +2 -0
  236. package/dist/m/plugins/page-view-NXOJHNW6.js +1 -0
  237. package/dist/m/plugins/people-SGPJKANK.js +1 -0
  238. package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
  239. package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
  240. package/dist/m/plugins/richclay-NRQDSWX6.js +517 -0
  241. package/dist/m/plugins/richclay-NRQDSWX6.js.LEGAL.txt +1 -0
  242. package/dist/m/plugins/sortable-GWT4PWDV.js +1 -0
  243. package/dist/m/plugins/source-4ZRSAIYM.js +1 -0
  244. package/dist/m/plugins/undo-VDA5P7FH.js +1 -0
  245. package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
  246. package/dist/m/plugins/upgrade-W7FG5VFG.js +1 -0
  247. package/dist/m/plugins/upload-WLYKK6TL.js +1 -0
  248. package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +21 -0
  249. package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +1 -0
  250. package/dist/m/plugins/wire-4N6GYAF6.js +1 -0
  251. package/dist/m/reader-core-DXYCURFF.js +68 -0
  252. package/dist/m/route/route-KOCZSEGN.js +1 -0
  253. package/dist/m/sync/live-sync-J7VMYTKS.js +1 -0
  254. package/dist/m/ui/index-3JLVI27O.js +1 -0
  255. package/dist/m/utils/index-4TJCVLT7.js +1 -0
  256. package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
  257. package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
  258. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
  259. package/dist/offline-manifest.json +4034 -0
  260. package/dist/page/themes/light-1.css +430 -0
  261. package/dist/server/core.cjs +23748 -22855
  262. package/dist/server/offline-runtime.cjs +4954 -0
  263. package/dist/server/offline-runtime.mjs +4923 -0
  264. package/dist/server/rooms.mjs +9 -0
  265. package/docs/guide.md +1078 -669
  266. package/entries/all.js +2 -0
  267. package/entries/clay-data.js +1 -1
  268. package/entries/clay-dom.js +2 -0
  269. package/entries/clay-events.js +2 -0
  270. package/entries/clay-internals.js +2 -0
  271. package/entries/clay-options.js +2 -0
  272. package/entries/clay-router.js +2 -0
  273. package/entries/clay-ui.js +2 -0
  274. package/entries/clay-utils.js +2 -0
  275. package/entries/clay.js +2 -0
  276. package/entries/sap.js +6 -6
  277. package/fonts/ibm-plex-mono-LICENSE +93 -0
  278. package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
  279. package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
  280. package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  281. package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  282. package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  283. package/fonts/ibm-plex-sans-LICENSE +93 -0
  284. package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  285. package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  286. package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  287. package/fonts/ibm-plex.css +71 -0
  288. package/fonts/inter-LICENSE +93 -0
  289. package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
  290. package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
  291. package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
  292. package/fonts/inter-greek-wght-normal.woff2 +0 -0
  293. package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  294. package/fonts/inter-latin-wght-normal.woff2 +0 -0
  295. package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
  296. package/fonts/inter.css +62 -0
  297. package/fonts/newsreader-LICENSE +93 -0
  298. package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
  299. package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  300. package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
  301. package/fonts/newsreader.css +26 -0
  302. package/fonts/work-sans-LICENSE +93 -0
  303. package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
  304. package/fonts/work-sans.css +8 -0
  305. package/package.json +20 -3
  306. package/packed-contract.json +1 -0
  307. package/src/attrs/add-from.js +27 -4
  308. package/src/attrs/save-freeze.js +7 -52
  309. package/src/core/admin-attrs.js +119 -12
  310. package/src/core/admin-contenteditable.js +12 -10
  311. package/src/core/admin-inputs.js +14 -15
  312. package/src/core/admin-onclick.js +2 -0
  313. package/src/core/autosave.js +74 -21
  314. package/src/core/boot-phase.js +2 -1
  315. package/src/core/conflict-download.js +19 -23
  316. package/src/core/conflict-notice-ui.js +1173 -0
  317. package/src/core/conflict-notice.js +140 -697
  318. package/src/core/download.js +32 -0
  319. package/src/core/edit-mode.js +147 -14
  320. package/src/core/etag.js +30 -12
  321. package/src/core/host-meta.js +90 -8
  322. package/src/core/host-permission.js +29 -0
  323. package/src/core/is-edit-mode.js +145 -12
  324. package/src/core/mode-key.js +125 -0
  325. package/src/core/mode-lifecycle.js +544 -0
  326. package/src/core/mode-receipt.js +30 -0
  327. package/src/core/mode-refusal.js +13 -0
  328. package/src/core/mode-resources.js +52 -0
  329. package/src/core/mode-support.js +82 -0
  330. package/src/core/mode-switch.js +259 -0
  331. package/src/core/mode-transition.js +112 -0
  332. package/src/core/page-data-write.js +305 -0
  333. package/src/core/page-data.js +26 -227
  334. package/src/core/page-format-wire.js +4 -0
  335. package/src/core/page-format.js +58 -0
  336. package/src/core/persist.js +37 -9
  337. package/src/core/preserve-authored.js +114 -0
  338. package/src/core/recovery.js +772 -0
  339. package/src/core/runtime-url.js +105 -0
  340. package/src/core/save-core.js +113 -12
  341. package/src/core/save-replace.js +72 -0
  342. package/src/core/save-size-notice.js +134 -0
  343. package/src/core/save-size.js +140 -0
  344. package/src/core/save.js +806 -119
  345. package/src/core/snapshot.js +159 -11
  346. package/src/core/source-map.js +157 -9
  347. package/src/core/unsaved-warning.js +14 -4
  348. package/src/core/upgrade-run.js +408 -0
  349. package/src/core/version.js +18 -0
  350. package/src/lib/assets.js +30 -0
  351. package/src/lib/autosave-debug-gate.js +50 -0
  352. package/src/lib/autosave-scheduler.js +140 -0
  353. package/src/lib/boot-imports.js +8 -0
  354. package/src/lib/derived.js +3 -1
  355. package/src/lib/dirty-gate.js +15 -0
  356. package/src/lib/doc-storage.js +33 -0
  357. package/src/lib/effect.js +9 -4
  358. package/src/lib/mutation.js +4 -2
  359. package/src/lib/offline-runtime.js +226 -0
  360. package/src/lib/preload.js +48 -0
  361. package/src/lib/root-attrs.js +1 -1
  362. package/src/lib/trash.js +18 -0
  363. package/src/lib/undo/capture-move.js +152 -0
  364. package/src/live/local-channel.js +61 -0
  365. package/src/live/local-lane.js +60 -9
  366. package/src/live/server-lane.js +6 -3
  367. package/src/loader-logic.js +76 -15
  368. package/src/loader.js +218 -30
  369. package/src/page/adopt-boot.js +76 -22
  370. package/src/page/adopt.js +25 -4
  371. package/src/page/awareness.js +230 -0
  372. package/src/page/blocks.js +113 -6
  373. package/src/page/blocksel.js +20 -4
  374. package/src/page/board-face.js +13 -0
  375. package/src/page/board-panel.js +5 -17
  376. package/src/page/board-style.js +2 -13
  377. package/src/page/board-view.js +1 -11
  378. package/src/page/board-views.js +3 -0
  379. package/src/page/board.js +2 -2
  380. package/src/page/boot-pack.js +26 -0
  381. package/src/page/boot.js +237 -0
  382. package/src/page/chart-tables.js +11 -0
  383. package/src/page/chart.js +6 -10
  384. package/src/page/checklist.js +1 -1
  385. package/src/page/checkpoint-proof.js +289 -0
  386. package/src/page/chrome.js +431 -63
  387. package/src/page/csv-import.js +17 -2
  388. package/src/page/document-title-dom.js +52 -0
  389. package/src/page/document-title-runtime.js +90 -0
  390. package/src/page/document-title.js +12 -0
  391. package/src/page/drag.js +48 -13
  392. package/src/page/drop.js +2 -1
  393. package/src/page/editor.js +399 -90
  394. package/src/page/emoji-data.js +162 -0
  395. package/src/page/feature-loader.js +72 -0
  396. package/src/page/features/board.js +21 -0
  397. package/src/page/features/chart.js +11 -0
  398. package/src/page/features/code.js +13 -0
  399. package/src/page/features/dates.js +180 -0
  400. package/src/page/features/emoji.js +187 -0
  401. package/src/page/features/media.js +290 -0
  402. package/src/page/features/mentions.js +188 -0
  403. package/src/page/features/registry.js +28 -0
  404. package/src/page/features/table.js +383 -0
  405. package/src/page/{tool-view.js → features/tool.js} +33 -2
  406. package/src/page/format-codec.js +215 -0
  407. package/src/page/format-command.js +9 -0
  408. package/src/page/format-gate.js +179 -0
  409. package/src/page/format-host.js +54 -0
  410. package/src/page/format-level.js +157 -0
  411. package/src/page/format-native-commands.js +142 -0
  412. package/src/page/format-native.js +42 -0
  413. package/src/page/highlight-slot.js +23 -0
  414. package/src/page/highlight.js +30 -16
  415. package/src/page/history-intent.js +78 -0
  416. package/src/page/inline.js +45 -28
  417. package/src/page/items.js +67 -14
  418. package/src/page/link.js +1 -0
  419. package/src/page/markdown.js +112 -3
  420. package/src/page/media-views.js +3 -0
  421. package/src/page/media.js +33 -245
  422. package/src/page/mention-picker.js +99 -0
  423. package/src/page/merge.js +1 -1
  424. package/src/page/outline-cache.js +11 -0
  425. package/src/page/placeholder.js +27 -7
  426. package/src/page/projection.js +240 -51
  427. package/src/page/reader-patch.js +306 -0
  428. package/src/page/recover.js +1 -1
  429. package/src/page/rich/page-tool.js +1 -4
  430. package/src/page/runtime-owner.js +27 -0
  431. package/src/page/save-projection.js +46 -4
  432. package/src/page/save-states.js +1 -1
  433. package/src/page/schema.js +47 -0
  434. package/src/page/table-rect-key.js +3 -0
  435. package/src/page/table-select.js +4 -3
  436. package/src/page/table-style.js +1 -1
  437. package/src/page/table-views.js +3 -0
  438. package/src/page/table.js +8 -293
  439. package/src/page/themes/content.css +25 -1
  440. package/src/page/themes/light-1.css +163 -2
  441. package/src/page/themes/light-1.source.css +269 -0
  442. package/src/page/tool-capability.js +3 -0
  443. package/src/page/tool-views.js +3 -0
  444. package/src/page/view-hooks.js +32 -0
  445. package/src/page/view.js +390 -107
  446. package/src/page/ybind/awareness.js +172 -0
  447. package/src/page/ybind/binding.js +40 -5
  448. package/src/page/ybind/bridge.js +50 -47
  449. package/src/page/ybind/decode.js +52 -19
  450. package/src/page/ybind/doc.js +27 -2
  451. package/src/page/ybind/group.js +12 -3
  452. package/src/page/ybind/index.js +2 -0
  453. package/src/page/ybind/lineage.js +71 -0
  454. package/src/page/ybind/marks.js +26 -1
  455. package/src/page/ybind/migrate.js +48 -13
  456. package/src/page/ybind/moves.js +32 -21
  457. package/src/page/ybind/positions.js +50 -0
  458. package/src/page/ybind/provider.js +97 -24
  459. package/src/page/ybind/recovery-proof.js +191 -0
  460. package/src/page/ybind/recovery-replay.js +488 -0
  461. package/src/page/ybind/tokens.js +15 -6
  462. package/src/page/ybind/walk.js +10 -3
  463. package/src/page/ybind/writer.js +48 -4
  464. package/src/plugins/ai-edit.js +167 -32
  465. package/src/plugins/bind-text.js +218 -0
  466. package/src/plugins/cms.js +93 -0
  467. package/src/plugins/indicator.js +57 -5
  468. package/src/plugins/live.js +319 -35
  469. package/src/plugins/menu/claygoo.svg.js +1 -0
  470. package/src/plugins/menu/seo-head.js +323 -0
  471. package/src/plugins/menu/seo.js +849 -0
  472. package/src/plugins/menu.js +425 -0
  473. package/src/plugins/page-view.js +131 -0
  474. package/src/plugins/page.js +1131 -270
  475. package/src/plugins/people.js +158 -31
  476. package/src/plugins/richclay.js +119 -9
  477. package/src/plugins/sortable.js +370 -60
  478. package/src/plugins/source.js +189 -366
  479. package/src/plugins/undo.js +289 -19
  480. package/src/plugins/unsent.js +132 -0
  481. package/src/plugins/upgrade-run.js +1 -0
  482. package/src/plugins/upgrade.js +210 -0
  483. package/src/plugins/upload-task.js +571 -0
  484. package/src/plugins/upload-ui.js +265 -0
  485. package/src/plugins/upload.js +34 -153
  486. package/src/plugins/visitor-drafts/inert.js +32 -0
  487. package/src/plugins/visitor-drafts/intro.js +2 -1
  488. package/src/plugins/visitor-drafts/owner-chip.js +3 -0
  489. package/src/plugins/visitor-drafts/prompt.js +2 -2
  490. package/src/plugins/visitor-drafts-owner.js +85 -0
  491. package/src/plugins/visitor-drafts.js +31 -42
  492. package/src/plugins/wire.js +82 -1
  493. package/src/reader-core.js +26 -0
  494. package/src/recovery/conflicts.js +287 -0
  495. package/src/recovery/identity.js +154 -0
  496. package/src/recovery/store.js +792 -0
  497. package/src/recovery/undo.js +257 -0
  498. package/src/server/checkpoint/index.js +68 -4
  499. package/src/server/offline-runtime/index.js +214 -0
  500. package/src/server/rooms/index.js +2 -1
  501. package/src/server/rooms/live-data-rooms.js +2 -2
  502. package/src/server/rooms/page-admission.js +33 -0
  503. package/src/server/rooms/page-lineage.js +56 -0
  504. package/src/server/rooms/page-rebase.js +11 -5
  505. package/src/server/rooms/page-state.js +2 -0
  506. package/src/server/rooms/page-write-admission.js +9 -3
  507. package/src/source/session.js +705 -0
  508. package/src/sync/conflict-revert.js +5 -4
  509. package/src/sync/conflicts-edit.js +119 -0
  510. package/src/sync/conflicts-facade.js +49 -0
  511. package/src/sync/conflicts.js +99 -1
  512. package/src/sync/editing-marker.js +37 -6
  513. package/src/sync/editing-sender.js +8 -1
  514. package/src/sync/live-sync-edit.js +63 -0
  515. package/src/sync/live-sync.js +364 -119
  516. package/src/sync/local-presence.js +187 -0
  517. package/src/sync/merge-declarations.js +10 -0
  518. package/src/sync/merge-engine.js +12 -0
  519. package/src/sync/presence-store.js +412 -0
  520. package/src/sync/presence-target.js +243 -0
  521. package/src/sync/presence.js +72 -41
  522. package/src/sync/saved-handoff.js +200 -0
  523. package/src/sync/saved-stream.js +544 -0
  524. package/src/sync/version-guard.js +92 -0
  525. package/src/sync/wire-profile.js +81 -0
  526. package/src/ui/bevel-controls.js +2 -8
  527. package/src/ui/page-scheme.js +7 -0
  528. package/src/ui/skins/cms.js +1 -1
  529. package/src/ui/theme-config.js +150 -0
  530. package/src/ui/theme.js +6 -138
  531. package/src/ui/toast-lazy.js +11 -0
  532. package/src/vendor/LICENSES-optional.md +253 -0
  533. package/src/vendor/highlight.vendor.js +3 -0
  534. package/src/vendor/hljs-bash.vendor.js +2 -0
  535. package/src/vendor/hljs-css.vendor.js +2 -0
  536. package/src/vendor/hljs-javascript.vendor.js +2 -0
  537. package/src/vendor/hljs-json.vendor.js +2 -0
  538. package/src/vendor/hljs-python.vendor.js +2 -0
  539. package/src/vendor/hljs-xml.vendor.js +2 -0
  540. package/src/vendor/hyper-html-api-data.vendor.js +1 -1
  541. package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
  542. package/src/vendor/hyper-morph-identity.vendor.js +2 -0
  543. package/src/vendor/hyper-morph-json.vendor.js +3 -0
  544. package/src/vendor/hyper-morph.vendor.js +6 -6
  545. package/src/vendor/hyper-undo.vendor.js +1 -1
  546. package/src/vendor/hypercms.vendor.js +169 -84
  547. package/src/vendor/marked.vendor.js +68 -0
  548. package/src/vendor/overtype.vendor.js +994 -0
  549. package/src/vendor/richclay.vendor.js +17 -17
  550. package/src/vendor/sap-core.vendor.js +5 -5
  551. package/src/vendor/sap-ownership.vendor.js +11 -7
@@ -0,0 +1,179 @@
1
+ // The format gate: the one place a new-format command asks whether the document
2
+ // may use that format yet.
3
+ //
4
+ // R understands floor 1 and sends that capability on every page write, but has
5
+ // no floor-1 command of its own. F's first new-format command calls this gate
6
+ // before it touches content:
7
+ //
8
+ // const gate = createFormatGate({ host, installed, revision, lane });
9
+ // const result = await gate.run({ apply: (selection) => command(selection) });
10
+ //
11
+ // Activation adds the canonical marker to an otherwise equivalent capture and
12
+ // nothing else. It is idempotent, it is refused while an admitted page editor
13
+ // could not read floor 1, and a refusal leaves ordinary writing and every
14
+ // unsaved store exactly as they were. The command that asked only applies if the
15
+ // same editor installation and document revision still exist afterwards;
16
+ // otherwise the answer is retryable and the content is untouched.
17
+ //
18
+ // The host seam is deliberately one object, because the floor is read, checked
19
+ // and persisted under one per-document lock:
20
+ //
21
+ // read() -> Promise<{ floor, revision }> the canonical floor as it stands
22
+ // capabilities()-> Promise<number[]> admitted page editors' declared capabilities
23
+ // activate({ floor, capability, revision })
24
+ // -> Promise<{ accepted: true, revision, receipt }
25
+ // | { accepted: false, code }>
26
+ //
27
+ // `activate` holds the document's admission/write lock while it checks the
28
+ // admitted editors again and persists the marker, so a legacy registration that
29
+ // lands between `capabilities()` and `activate()` is refused rather than missed.
30
+
31
+ import { PAGE_FORMAT, PAGE_FORMAT_EXTENSION, PAGE_FORMAT_UPGRADE, formatCapability, notePageFormatFloor, pageFormatDecision, pageFormatFloor } from "./format-level.js";
32
+
33
+ const UPGRADE_MESSAGE = "This document uses a newer page format; update the editor in the other tabs to keep writing";
34
+
35
+ const upgrade = (required) => ({ status: "upgrade-required", code: PAGE_FORMAT_UPGRADE, required, minimum: required, message: UPGRADE_MESSAGE });
36
+
37
+ export function createFormatGate({
38
+ host,
39
+ capability = PAGE_FORMAT,
40
+ supported = () => true,
41
+ installed = () => null,
42
+ revision = () => null,
43
+ lane = null,
44
+ } = {}) {
45
+ if (!host || typeof host.activate !== "function") throw new TypeError("format-gate: a host adapter is required");
46
+ let floor = 0;
47
+ let pending = null;
48
+ let lanePending = false;
49
+ let disposed = false;
50
+
51
+ // The floor this gate knows the document at. A save or a room answer can tell
52
+ // it, so a gate opened on an already-floor-1 document never asks to activate
53
+ // again.
54
+ function note(level) {
55
+ floor = Math.max(floor, formatCapability(level));
56
+ notePageFormatFloor(floor);
57
+ return floor;
58
+ }
59
+
60
+ async function read() {
61
+ try {
62
+ const answer = await host.read();
63
+ note(answer?.floor ?? pageFormatFloor(answer?.html));
64
+ } catch {}
65
+ return floor;
66
+ }
67
+
68
+ async function admittance() {
69
+ try {
70
+ const admitted = await host.capabilities();
71
+ return Array.isArray(admitted) ? admitted : [];
72
+ } catch {
73
+ return [];
74
+ }
75
+ }
76
+
77
+ // Whether a floor rise may happen at all: the host must have announced the
78
+ // format contract, and this runtime must declare the capability it writes with.
79
+ async function supportedHere() {
80
+ try {
81
+ return (await supported()) !== false;
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+
87
+ async function activateNow() {
88
+ if (disposed) return { status: "disposed", retry: false };
89
+ if (!(await supportedHere())) return { status: "unsupported-host", code: "page-format-unsupported", extension: PAGE_FORMAT_EXTENSION };
90
+ await read();
91
+ if (floor >= PAGE_FORMAT) return { status: "active", floor };
92
+ const admitted = await admittance();
93
+ const early = pageFormatDecision({ floor, required: PAGE_FORMAT, capability, admitted });
94
+ if (!early.ok) return upgrade(early.minimum);
95
+ const stamp = { revision: revision(), installed: installed() };
96
+ let answer;
97
+ try {
98
+ answer = await host.activate({ floor: PAGE_FORMAT, capability, revision: stamp.revision, installed: stamp.installed });
99
+ } catch (error) {
100
+ return { status: "failed", code: "page-format-activation-failed", error, retry: true };
101
+ }
102
+ if (!answer || answer.accepted !== true) {
103
+ if (answer?.code === PAGE_FORMAT_UPGRADE) return upgrade(PAGE_FORMAT);
104
+ return { status: "failed", code: answer?.code ?? "page-format-activation-failed", retry: true };
105
+ }
106
+ if (disposed) {
107
+ // The save was accepted, so the document is at floor 1 either way; this
108
+ // tab is gone and must not act on it.
109
+ note(PAGE_FORMAT);
110
+ return { status: "disposed", retry: false };
111
+ }
112
+ note(PAGE_FORMAT);
113
+ lanePending = true;
114
+ await joinLane(answer.receipt ?? null);
115
+ return { status: "active", floor, receipt: answer.receipt ?? null };
116
+ }
117
+
118
+ // Joining the new lane scope happens after the receipt is accepted, and only
119
+ // then: an interrupted switch leaves this tab on floor 0's scope with every
120
+ // store intact and the switch still pending, never a lane it cannot cover.
121
+ async function joinLane(receipt) {
122
+ if (!lane || !lanePending) return false;
123
+ try {
124
+ const result = await lane.switchTo({ floor: PAGE_FORMAT, receipt });
125
+ if (result && result.status === "switched") {
126
+ lanePending = false;
127
+ return true;
128
+ }
129
+ } catch {}
130
+ return false;
131
+ }
132
+
133
+ function activate() {
134
+ if (disposed) return Promise.resolve({ status: "disposed", retry: false });
135
+ if (floor >= PAGE_FORMAT) return joinLane(null).then(() => ({ status: "active", floor }));
136
+ if (!pending) pending = activateNow().finally(() => { pending = null; });
137
+ return pending;
138
+ }
139
+
140
+ return {
141
+ get floor() {
142
+ return floor;
143
+ },
144
+ get disposed() {
145
+ return disposed;
146
+ },
147
+ note,
148
+ read,
149
+ // Activation, once: a second caller while one is in flight waits on the
150
+ // same attempt, and a retry after a failure starts a new one.
151
+ activate,
152
+ // A new-format command: capture what it applies to, ask the gate, and apply
153
+ // only when activation succeeded and nothing moved underneath it.
154
+ async run(command) {
155
+ const stamp = { revision: revision(), installed: installed() };
156
+ const result = await activate();
157
+ if (result.status !== "active") return result;
158
+ if (disposed) return { status: "disposed", retry: false };
159
+ if (revision() !== stamp.revision || installed() !== stamp.installed) {
160
+ return { status: "retry", code: "format-gate-stale", retry: true };
161
+ }
162
+ return { status: "applied", floor, result: await command?.apply?.(stamp) };
163
+ },
164
+ // Retry what is still pending: a lane switch whose receipt is in hand, or an
165
+ // activation that failed on the wire. Unsaved work is never part of this.
166
+ async retry() {
167
+ if (disposed) return { status: "disposed", retry: false };
168
+ if (await joinLane(null)) return { status: "active", floor };
169
+ return activate();
170
+ },
171
+ // Disposal while awaiting the save: the answer is not applied, the lane is
172
+ // not switched, and the deferred command does not run.
173
+ dispose() {
174
+ disposed = true;
175
+ lanePending = false;
176
+ return { status: "disposed" };
177
+ },
178
+ };
179
+ }
@@ -0,0 +1,54 @@
1
+ // The production host adapter behind the format gate: the floor the document is
2
+ // already at, the host's own admission answer, and the one explicit save that
3
+ // raises it.
4
+ //
5
+ // The host is the authority, so `capabilities()` answers nothing here: the editors
6
+ // admitted to the document are checked under its admission/write lock when the save
7
+ // carrying the marker arrives, which is the check no registration can race. A save
8
+ // refused there reports `page-format-upgrade-required` like any other refusal, and
9
+ // this tab stays at the floor it already knew.
10
+ //
11
+ // Nothing is noted before the host answers: `notePageFormatFloor` runs when the gate
12
+ // accepts the activation, so a refusal leaves every later save at floor 0.
13
+
14
+ import { PAGE_FORMAT, PAGE_FORMAT_META, PAGE_FORMAT_UPGRADE, knownPageFormatFloor, pageFormatFloor } from "./format-level.js";
15
+ import { hostPageFormat } from "../core/host-meta.js";
16
+
17
+ const FAILED = "page-format-activation-failed";
18
+
19
+ // The document's own marker, as it was served. A tab that joined a document already
20
+ // at floor 1 must not ask to activate it again.
21
+ function servedFloor() {
22
+ const tag = document.querySelector(`meta[name="${PAGE_FORMAT_META}"]`);
23
+ return tag ? pageFormatFloor(tag.outerHTML) : 0;
24
+ }
25
+
26
+ // The editor's own explicit save, fetched when a floor is really raised: a tab that
27
+ // never uses a newer format never downloads the lane to raise one.
28
+ const savePage = async (options) => (await import("../core/save.js")).savePageForce(() => {}, options);
29
+
30
+ export function createFormatHost({
31
+ save = savePage,
32
+ supported = () => hostPageFormat().then((floor) => floor === PAGE_FORMAT),
33
+ } = {}) {
34
+ return {
35
+ read() {
36
+ return { floor: Math.max(knownPageFormatFloor(), servedFloor()), revision: null };
37
+ },
38
+ capabilities() {
39
+ return [];
40
+ },
41
+ supported,
42
+ async activate({ floor = PAGE_FORMAT } = {}) {
43
+ let result;
44
+ try {
45
+ result = await save({ pageFormatFloor: floor });
46
+ } catch (error) {
47
+ return { accepted: false, code: error?.code ?? FAILED };
48
+ }
49
+ if (result?.ok) return { accepted: true, revision: null, receipt: result.receipt ?? result.etag ?? null };
50
+ if (result?.code === PAGE_FORMAT_UPGRADE) return { accepted: false, code: PAGE_FORMAT_UPGRADE };
51
+ return { accepted: false, code: result?.code ?? FAILED };
52
+ },
53
+ };
54
+ }
@@ -0,0 +1,157 @@
1
+ // The document format floor: how much of the page format a runtime must know
2
+ // before it may write.
3
+ //
4
+ // The canonical marker is document metadata, outside the unchanged `{v,g,y}`
5
+ // page value:
6
+ // <meta name="clay-page-format" content="1">
7
+ // Absence means floor 0. A document's floor only ever rises, and every path that
8
+ // writes a page declares the capability it writes with in
9
+ // `X-Clay-Page-Format`. The value is protocol capability, not authorization: the
10
+ // host still applies every permission check.
11
+ //
12
+ // `requiredPageFormat` answers the other direction: what floor does THIS content
13
+ // need? Its registry is frozen at R (floor 1), so it does not shrink back as a
14
+ // later release adds native schema support.
15
+
16
+ export {
17
+ PAGE_FORMAT, PAGE_FORMAT_HEADER, PAGE_FORMAT_META, PAGE_FORMAT_EXTENSION, PAGE_FORMAT_UPGRADE,
18
+ formatCapability, pageFormatFloor, withPageFormatFloor, knownPageFormatFloor, notePageFormatFloor,
19
+ } from "../core/page-format.js";
20
+ import { PAGE_FORMAT, PAGE_FORMAT_UPGRADE, formatCapability } from "../core/page-format.js";
21
+
22
+ const level = (value) => (Number.isSafeInteger(value) && value > 0 ? value : 0);
23
+
24
+ // The floor-0 registry, frozen: every block type, mark code and block field R
25
+ // understood when the floor was introduced. Built on first ask, so a client that
26
+ // never checks content for its floor never carries the list.
27
+ const REGISTRY = {
28
+ types: [
29
+ "paragraph", "heading", "list_item", "callout", "code_block", "toggle",
30
+ "divider", "image", "file", "table", "chart", "board", "tool", "preserved",
31
+ ],
32
+ marks: ["a", "b", "c", "i", "s"],
33
+ fields: [
34
+ "id", "t", "parent", "c", "m", "level", "kind", "indent", "checked", "owner",
35
+ "due", "lang", "open", "src", "alt", "caption", "width", "align", "name",
36
+ "size", "mime", "cols", "rows", "sort", "source", "series", "title",
37
+ "highlight", "columns", "cards", "artifact", "rev", "state", "html", "pos",
38
+ ],
39
+ };
40
+ let frozen = null;
41
+
42
+ export function floorZero() {
43
+ if (!frozen) {
44
+ frozen = Object.freeze({
45
+ types: Object.freeze([...REGISTRY.types]),
46
+ marks: Object.freeze([...REGISTRY.marks]),
47
+ fields: Object.freeze([...REGISTRY.fields]),
48
+ });
49
+ }
50
+ return frozen;
51
+ }
52
+
53
+ const CODE = /^[A-Za-z]$/;
54
+
55
+ // A mark key holds one code per mark, each followed by its attrs as a JSON array
56
+ // when it carries them. Anything this registry has no code for is a newer
57
+ // runtime's: an unfamiliar compact token, or `x` with a named extension.
58
+ function newerMark(key) {
59
+ if (typeof key !== "string") throw new TypeError("page: a mark key is not a string");
60
+ if (!key || key[0] === "@") return false;
61
+ for (let i = 0; i < key.length;) {
62
+ const code = key[i];
63
+ if (!CODE.test(code)) throw new SyntaxError(`page: malformed mark key ${key}`);
64
+ if (!floorZero().marks.includes(code)) return true;
65
+ i += 1;
66
+ if (key[i] !== "[") continue;
67
+ let depth = 0, quoted = false;
68
+ const start = i;
69
+ for (; i < key.length; i++) {
70
+ const ch = key[i];
71
+ if (quoted) {
72
+ if (ch === "\\") i++;
73
+ else if (ch === '"') quoted = false;
74
+ } else if (ch === '"') quoted = true;
75
+ else if (ch === "[") depth++;
76
+ else if (ch === "]" && --depth === 0) { i++; break; }
77
+ }
78
+ if (i >= key.length && key[key.length - 1] !== "]") throw new SyntaxError(`page: unterminated attrs in mark key ${key.slice(start)}`);
79
+ }
80
+ return false;
81
+ }
82
+
83
+ const newerRuns = (m) => {
84
+ if (!Array.isArray(m)) return false;
85
+ for (const run of m) {
86
+ if (Array.isArray(run)) {
87
+ if (run.length === 2 && Number.isInteger(run[0]) && newerMark(run[1])) return true;
88
+ } else if (newerMark(run)) return true;
89
+ }
90
+ return false;
91
+ };
92
+
93
+ /**
94
+ * The floor a prospective semantic model needs: 1 when it holds a named
95
+ * extension mark, an unfamiliar compact token, an unfamiliar block or a block
96
+ * field the floor-0 registry does not know, 0 otherwise.
97
+ *
98
+ * @param {Array<Object>} model - items as projection.js projects them
99
+ * @returns {number}
100
+ */
101
+ export function requiredPageFormat(model) {
102
+ if (!Array.isArray(model)) throw new TypeError("page: requiredPageFormat expects a model");
103
+ for (const item of model) {
104
+ if (!item || typeof item !== "object") continue;
105
+ if (!floorZero().types.includes(item.t)) return PAGE_FORMAT;
106
+ for (const key of Object.keys(item)) {
107
+ if (!floorZero().fields.includes(key)) return PAGE_FORMAT;
108
+ }
109
+ if (newerRuns(item.m)) return PAGE_FORMAT;
110
+ if (!Array.isArray(item.rows)) continue;
111
+ for (const row of item.rows) {
112
+ for (const cell of Object.values(row?.cells ?? {})) {
113
+ if (cell && typeof cell === "object" && newerRuns(cell.m)) return PAGE_FORMAT;
114
+ }
115
+ }
116
+ }
117
+ return 0;
118
+ }
119
+
120
+ /**
121
+ * One floor decision for every writer: the client's activation gate and the
122
+ * room's admission helper ask the same question and get the same answer.
123
+ *
124
+ * `floor` is the document's active floor, `required` what the prospective model
125
+ * needs, `capability` what this writer declares, and `admitted` the capabilities
126
+ * of the page editors already in the room. A write is refused before any
127
+ * mutation when this writer is below what the content needs, and a floor rise is
128
+ * refused while an admitted editor could not read the result.
129
+ *
130
+ * @param {Object} input
131
+ * @param {number} [input.floor]
132
+ * @param {number} [input.required]
133
+ * @param {number} [input.capability]
134
+ * @param {number[]} [input.admitted]
135
+ * @returns {{ok: boolean, activate: boolean, floor: number, required: number}}
136
+ */
137
+ export function pageFormatDecision({ floor = 0, required = 0, capability = 0, admitted = [] } = {}) {
138
+ const active = level(floor);
139
+ const needs = Math.max(level(required), active);
140
+ const refuse = (minimum) => ({ ok: false, activate: false, floor: active, required: needs, minimum });
141
+ if (formatCapability(capability) < needs) return refuse(needs);
142
+ const activate = needs > active;
143
+ if (activate && admitted.some((value) => formatCapability(value) < needs)) return refuse(needs);
144
+ return { ok: true, activate, floor: needs, required: needs };
145
+ }
146
+
147
+ // What a refused writer is told: which floor the document needs and that its own
148
+ // unsaved work is still its own. Nothing here is a reload.
149
+ export function pageFormatRefusal(required, message = null) {
150
+ const minimum = level(required) || PAGE_FORMAT;
151
+ const error = new Error(message ?? `This document uses a newer page format (floor ${minimum}); update the editor to keep writing`);
152
+ error.name = "PageFormatUpgradeRequired";
153
+ error.code = PAGE_FORMAT_UPGRADE;
154
+ error.statusCode = 409;
155
+ error.minimum = minimum;
156
+ return error;
157
+ }
@@ -0,0 +1,142 @@
1
+ // The commands behind the four formats this release adds — underline, text color,
2
+ // highlight and the quote block — and the rows the More menu offers them by.
3
+ //
4
+ // Nothing here is on the ready path. Every caller loads this module the first
5
+ // time one of the commands runs, so reading and drawing an existing document
6
+ // never fetches a transaction, a palette or a refusal notice.
7
+ //
8
+ // Every command that writes one of them goes through runFormat, which is the
9
+ // editor's own format gate: nothing is applied and no descriptor is written
10
+ // unless the gate answered "applied".
11
+ import { history } from "../vendor/prosemirror.vendor.js";
12
+ import { schema } from "./schema.js";
13
+ import { PALETTE, markable } from "./format-native.js";
14
+ import { announce } from "./announce.js";
15
+ import { openPageMenu } from "./chrome.js";
16
+
17
+ // blocks.js's STEP, read here rather than imported from it: this module is part
18
+ // of the projection's graph, and blocks.js reads the projection's constants as it
19
+ // loads. A command of ours is one undo item by itself, like every other command.
20
+ const STEP = "page-step";
21
+ const asStep = (tr) => history.closeHistory(tr).setMeta(STEP, true);
22
+
23
+ const holds = (mark, attrs) => !!mark && Object.keys(mark.type.attrs).every((name) => mark.attrs[name] === attrs[name]);
24
+
25
+ function toggle(view, type, attrs) {
26
+ const st = view.state;
27
+ const { from, to, empty, $from } = st.selection;
28
+ const tr = st.tr;
29
+ if (empty) {
30
+ if (holds(type.isInSet(st.storedMarks || $from.marks()), attrs)) tr.removeStoredMark(type);
31
+ else tr.addStoredMark(type.create(attrs));
32
+ } else {
33
+ let all = true;
34
+ st.doc.nodesBetween(from, to, (node) => {
35
+ if (all && node.isText && !holds(type.isInSet(node.marks), attrs)) all = false;
36
+ });
37
+ if (all) tr.removeMark(from, to, type);
38
+ else tr.addMark(from, to, type.create(attrs));
39
+ }
40
+ view.dispatch(asStep(tr));
41
+ return true;
42
+ }
43
+
44
+ function remove(view, type) {
45
+ const st = view.state;
46
+ const tr = st.tr;
47
+ if (st.selection.empty) tr.removeStoredMark(type);
48
+ else tr.removeMark(st.selection.from, st.selection.to, type);
49
+ view.dispatch(asStep(tr));
50
+ return true;
51
+ }
52
+
53
+ // The gate, in one place: an editor mounted without one cannot write a floor-1
54
+ // format, and a refusal leaves the content exactly as it was.
55
+ export function runFormat(view, apply) {
56
+ const run = view.runFormatCommand;
57
+ if (typeof run !== "function") return Promise.resolve({ status: "unsupported-host", code: "page-format-unsupported", retry: false });
58
+ return run(apply);
59
+ }
60
+
61
+ // A mark command: toggle it on the selection, or with reset take it off. A
62
+ // selection the mark cannot reach is refused before the gate is asked, so a code
63
+ // block never raises the document's floor.
64
+ export function formatMark(view, name, attrs = {}, { reset = false } = {}) {
65
+ const type = schema.marks[name];
66
+ if (!type) return Promise.resolve({ status: "refused", code: "unsupported-format" });
67
+ if (!view.editable) return Promise.resolve({ status: "refused", code: "not-editable" });
68
+ if (!markable(view.state, type)) return Promise.resolve({ status: "refused", code: "mark-not-allowed" });
69
+ return runFormat(view, () => (reset ? remove(view, type) : toggle(view, type, attrs)));
70
+ }
71
+
72
+ // The four formats pasted in from elsewhere are floor-1 content too: the slice is
73
+ // inserted only once the gate answered applied. A refusal keeps the text and
74
+ // writes no descriptor; a selection that moved while the answer came back takes
75
+ // neither.
76
+ export function pasteFormats(view, slice, from, to) {
77
+ const place = () => {
78
+ if (view.isDestroyed) return false;
79
+ const st = view.state;
80
+ if (st.selection.from !== from || st.selection.to !== to) return false;
81
+ view.dispatch(st.tr.replaceSelection(slice).scrollIntoView());
82
+ return true;
83
+ };
84
+ const plain = () => {
85
+ const text = slice.content.textBetween(0, slice.content.size, "\n", " ");
86
+ if (!view.isDestroyed && text) view.dispatch(view.state.tr.insertText(text, from, to));
87
+ return false;
88
+ };
89
+ runFormat(view, place).then((result) => {
90
+ if (result?.status === "applied") return;
91
+ if (!view.isDestroyed) announce(view, result?.message ?? "That format isn\u2019t available here.");
92
+ plain();
93
+ }, plain);
94
+ }
95
+
96
+ // The typed `" ` shortcut: the paragraph the reader is typing in became a quote,
97
+ // and the space that typed it goes with it. A refusal puts the space back as
98
+ // itself, so the typed character is never swallowed.
99
+ export function quoteFromSpace(plugin, view, { at, from, to }, plain) {
100
+ const convert = () => {
101
+ if (view.isDestroyed) return false;
102
+ const now = view.state;
103
+ const current = now.doc.nodeAt(at);
104
+ if (!current || current.type !== schema.nodes.paragraph || current.textContent !== '"') return plain();
105
+ const tr = now.tr.delete(at + 1, at + 2).setNodeMarkup(at, schema.nodes.quote, current.attrs);
106
+ view.dispatch(asStep(tr.setMeta(plugin, { transform: tr, from, to, text: " " })));
107
+ return true;
108
+ };
109
+ view.runFormatCommand(convert).then((result) => { if (result?.status !== "applied") plain(); }, plain);
110
+ }
111
+
112
+ // The More menu's rows: the underline command, the palette of a color mark and
113
+ // the notice a refusal raises. The palette is fixed: nine keys and the way back
114
+ // to none, and no arbitrary color picker is offered.
115
+ const CANNOT_MARK = "This text can\u2019t take that format.";
116
+ const refusalText = (result) => (result.status === "upgrade-required" ? result.message : result.code === "mark-not-allowed" ? CANNOT_MARK : "That format isn\u2019t available here.");
117
+
118
+ const notice = (view, result) => {
119
+ if (result?.status !== "applied" && !view.isDestroyed) announce(view, refusalText(result ?? {}));
120
+ };
121
+
122
+ export const runNative = (view, name, attrs = {}, options = undefined) => {
123
+ formatMark(view, name, attrs, options).then((result) => notice(view, result));
124
+ };
125
+
126
+ // A block command from a later format, like the turn into a quote: the write asks
127
+ // the gate first, and a refusal is a notice rather than a change.
128
+ export const gateTurn = (view, turn) => {
129
+ runFormat(view, turn).then((result) => notice(view, result));
130
+ };
131
+
132
+ // The palette of one color mark, as its own short menu: the nine fixed keys and
133
+ // the way back to none.
134
+ export function nativeMenu(view, name, anchor) {
135
+ if (name === "underline") return runNative(view, name);
136
+ const colorLabel = name === "text_color" ? "Text" : "Highlight";
137
+ const noneLabel = name === "text_color" ? "Automatic" : "No highlight";
138
+ return openPageMenu(view, anchor, [
139
+ ...PALETTE.map((color) => ({ key: `${name}_${color}`, label: `${colorLabel}: ${color[0].toUpperCase()}${color.slice(1)}`, icon: "\u25CF", run: (v) => runNative(v, name, { color }) })),
140
+ { key: `${name}_none`, label: noneLabel, icon: "\u00d7", run: (v) => runNative(v, name, {}, { reset: true }) },
141
+ ]);
142
+ }
@@ -0,0 +1,42 @@
1
+ // The four formats this release adds: underline, text color, highlight and the
2
+ // quote block.
3
+ //
4
+ // Each is written as the named descriptor an older runtime already keeps as an
5
+ // opaque mark, `x["text_color",{"color":"red"}]`, so R reads back exactly what F
6
+ // wrote. The palette is fixed and its keys are the only colors a descriptor may
7
+ // name: one naming any other color stays opaque rather than being read as a
8
+ // style, and no command accepts arbitrary CSS.
9
+ //
10
+ // This half is the reading one: what the schema, inline.js and the projection need
11
+ // to read a descriptor back and draw it. The commands that write one are in
12
+ // format-native-commands.js, which a page loads the first time one runs.
13
+ export const NATIVE_NAMES = new Set(["underline", "text_color", "highlight"]);
14
+
15
+ export const PALETTE = ["gray", "brown", "orange", "yellow", "green", "blue", "purple", "pink", "red"];
16
+ const COLORS = new Set(PALETTE);
17
+
18
+ // The descriptor attrs of a native mark, or null when this name and these attrs
19
+ // are not one of the formats this runtime writes.
20
+ export function nativeAttrs(name, attrs) {
21
+ const plain = attrs !== null && typeof attrs === "object" && !Array.isArray(attrs);
22
+ if (name === "underline") return plain && Object.keys(attrs).length === 0 ? {} : null;
23
+ if (name === "text_color" || name === "highlight") {
24
+ const keys = plain ? Object.keys(attrs) : [];
25
+ return keys.length === 1 && keys[0] === "color" && COLORS.has(attrs.color) ? { color: attrs.color } : null;
26
+ }
27
+ return null;
28
+ }
29
+
30
+ // Whether this selection holds text the mark may be added to: a code block takes
31
+ // no marks, and a block selection is not text at all. It stays here, beside the
32
+ // reading half, because the keyboard decides whether Mod-u is its own before any
33
+ // command is loaded.
34
+ export function markable(st, type) {
35
+ const { $from, $to, empty } = st.selection;
36
+ if (empty) return $from.parent.type.allowsMarkType(type);
37
+ let ok = false;
38
+ st.doc.nodesBetween($from.pos, $to.pos, (node, _at, parent) => {
39
+ if (!ok && node.isText && parent?.type.allowsMarkType(type)) ok = true;
40
+ });
41
+ return ok;
42
+ }
@@ -0,0 +1,23 @@
1
+ // The decoration slot code colours go into: code blocks highlight themselves
2
+ // from a separate part, so the editor carries this empty plugin from boot and
3
+ // the highlighter fills it once it loads.
4
+ import { state, view as pmView } from "../vendor/prosemirror.vendor.js";
5
+
6
+ const { Plugin, PluginKey } = state;
7
+ const { DecorationSet } = pmView;
8
+
9
+ export const highlightKey = new PluginKey("page-highlight");
10
+
11
+ export function highlightSlot() {
12
+ return new Plugin({
13
+ key: highlightKey,
14
+ state: {
15
+ init: () => DecorationSet.empty,
16
+ apply(tr, set) {
17
+ const decos = tr.getMeta(highlightKey);
18
+ return decos ? DecorationSet.create(tr.doc, decos) : set.map(tr.mapping, tr.doc);
19
+ },
20
+ },
21
+ props: { decorations: (st) => highlightKey.getState(st) },
22
+ });
23
+ }
@@ -5,9 +5,12 @@
5
5
  import { state, view as pmView } from "../vendor/prosemirror.vendor.js";
6
6
  import { schema } from "./schema.js";
7
7
  import { asStep } from "./blocksel.js";
8
+ import { highlightKey, highlightSlot } from "./highlight-slot.js";
8
9
 
9
- const { Plugin, PluginKey } = state;
10
- const { Decoration, DecorationSet } = pmView;
10
+ const { Plugin } = state;
11
+ const { Decoration } = pmView;
12
+
13
+ export { highlightKey };
11
14
 
12
15
  export const LANGS = [
13
16
  ["", "Plain text"],
@@ -106,9 +109,6 @@ const styleOf = (t) => {
106
109
  return s.join("; ");
107
110
  };
108
111
 
109
- export const highlightKey = new PluginKey("page-highlight");
110
-
111
-
112
112
  const CACHE_SIZE = 200;
113
113
 
114
114
  // Each pass rebuilds every code block's colours from a cache of token runs keyed
@@ -206,18 +206,32 @@ class Highlighter {
206
206
  }
207
207
  }
208
208
 
209
- export function highlightPlugin({ tokenize: run = tokenize, delay = 300 } = {}) {
209
+ // The highlighter is no longer a plugin view, so nothing calls its update: it
210
+ // watches the editor's own dispatches and stops watching when it is destroyed.
211
+ export function startHighlighter(view, { tokenize: run = tokenize, delay = 300 } = {}) {
212
+ const highlighter = new Highlighter(view, run, delay);
213
+ const dispatch = view.dispatch.bind(view);
214
+ let doc = view.state.doc;
215
+ const wrapped = (tr) => {
216
+ dispatch(tr);
217
+ if (view.state.doc === doc) return;
218
+ const prev = { doc };
219
+ doc = view.state.doc;
220
+ highlighter.update(view, prev);
221
+ };
222
+ view.dispatch = wrapped;
223
+ const stop = highlighter.destroy.bind(highlighter);
224
+ highlighter.destroy = () => {
225
+ if (view.dispatch === wrapped) view.dispatch = dispatch;
226
+ stop();
227
+ };
228
+ return highlighter;
229
+ }
230
+
231
+ export function highlightPlugin(options) {
210
232
  return new Plugin({
211
- key: highlightKey,
212
- state: {
213
- init: () => DecorationSet.empty,
214
- apply(tr, set) {
215
- const decos = tr.getMeta(highlightKey);
216
- return decos ? DecorationSet.create(tr.doc, decos) : set.map(tr.mapping, tr.doc);
217
- },
218
- },
219
- props: { decorations: (st) => highlightKey.getState(st) },
220
- view: (view) => new Highlighter(view, run, delay),
233
+ ...highlightSlot().spec,
234
+ view: (view) => startHighlighter(view, options),
221
235
  });
222
236
  }
223
237