@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
@@ -6,30 +6,30 @@
6
6
  // `page` cascade layer, so a theme is one plain stylesheet that sets tokens on
7
7
  // .page-shell (and adds scoped rules where it needs to); unlayered theme rules
8
8
  // always win. Contract: plans/.../default-page/v1/look/THEME-CONTRACT.md
9
- import { state, view as pmView, keymap, commands, history, gapcursor, inputrules } from "../vendor/prosemirror.vendor.js";
9
+ import { state, view as pmView, model, keymap, commands, history, gapcursor, inputrules } from "../vendor/prosemirror.vendor.js";
10
10
  import { schema } from "./schema.js";
11
11
  import { identityPlugin, docFromBlocks, blockMap, keyBetween, newId } from "./model.js";
12
12
  import { blockKeys, structurePlugin, collapsePlugin, pastePlugin, BlockSelection, STEP } from "./blocks.js";
13
- import { chromePlugin } from "./chrome.js";
14
- import { announcementPlugin } from "./announce.js";
13
+ import { chromePlugin, slashRangePlugin } from "./chrome.js";
14
+ import { announcementPlugin, announce } from "./announce.js";
15
15
  import { linkPlugin } from "./link.js";
16
- import { blockSelectionPlugin } from "./blocksel.js";
17
- import { markdownPlugin, markdownKeys, backspaceToParagraph } from "./markdown.js";
16
+ import { blockSelectionPlugin, asStep } from "./blocksel.js";
17
+ import { markdownPlugin, markdownKeys, backspaceToParagraph, composing } from "./markdown.js";
18
+ import { markable } from "./format-native.js";
18
19
  import { placeholderPlugin } from "./placeholder.js";
19
- import { mediaPlugin, ImageView, FileView } from "./media.js";
20
- import { highlightPlugin, CodeBlockView } from "./highlight.js";
21
- import { tableKeys, tablePlugin, TableView, TableCellView } from "./table.js";
20
+ import { titlePlugin } from "./document-title-runtime.js";
21
+ import { mediaPlugin } from "./media.js";
22
+ import { highlightSlot } from "./highlight-slot.js";
23
+ import { tableKeys, tablePlugin } from "./table.js";
22
24
  import { tableSelectPlugin } from "./table-select.js";
23
- import { chartPlugin, ChartView } from "./chart.js";
24
- import { BoardView } from "./board-view.js";
25
- import { boardPlugin, cardPanel } from "./board-panel.js";
26
- import { ToolView } from "./tool-view.js";
27
- import { createToolHost } from "./tools.js";
25
+ import { viewFeatures, viewHookPlugin } from "./view-hooks.js";
28
26
  import { checklistPlugin, checklistKeys } from "./checklist.js";
29
27
  import { csvImportPlugin } from "./csv-import.js";
30
- import { TABLE_CSS } from "./table-style.js";
31
- import { BOARD_CSS } from "./board-style.js";
32
28
  import { styleWithLegacyId } from "../lib/style.js";
29
+ import { historyIntentPlugin } from "./history-intent.js";
30
+ import { createFeatureLoader } from "./feature-loader.js";
31
+ import { COMMAND_FEATURES, FEATURES, featureForType } from "./features/registry.js";
32
+ import { toolViews } from "./tool-views.js";
33
33
 
34
34
  const STYLE_ID = "page-editor-style";
35
35
  const INDENTS = [1, 2, 3, 4, 5, 6, 7, 8];
@@ -39,6 +39,11 @@ const LIGHT = `
39
39
  --page-rule: rgba(35, 34, 31, 0.10); --page-accent: #2F5E9E; --page-link: #164583; --page-selection: rgba(88, 120, 170, 0.20);
40
40
  --page-check-border: rgba(35, 34, 31, 0.35); --page-check-fill: #23221F; --page-check-ink: #F7F6F2;
41
41
  --page-callout-bg: rgba(35, 34, 31, 0.04); --page-code-bg: rgba(35, 34, 31, 0.05);
42
+ --page-color-gray: #5B5953; --page-color-brown: #8A5A1E; --page-color-orange: #B4531A; --page-color-yellow: #7A6210; --page-color-green: #3E6A28;
43
+ --page-color-blue: #1F5A8A; --page-color-purple: #5E4596; --page-color-pink: #A23E6E; --page-color-red: #9E3B2F;
44
+ --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);
45
+ --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);
46
+ --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);
42
47
  --page-code-token-keyword: #8A3B6E; --page-code-token-constant: #1F5A8A; --page-code-token-string: #3E6A28; --page-code-token-string-expression: #3E6A28; --page-code-token-comment: #8B877E; --page-code-token-function: #5E4596; --page-code-token-parameter: #8A5A1E; --page-code-token-punctuation: var(--page-ink-soft); --page-code-token-link: var(--page-link);
43
48
  --page-chrome-ink-rest: rgba(35, 34, 31, 0.20); --page-chrome-ink-moved: rgba(35, 34, 31, 0.34);
44
49
  --page-chrome-bg-rest: rgba(35, 34, 31, 0.02); --page-chrome-bg-moved: rgba(35, 34, 31, 0.04);
@@ -53,6 +58,11 @@ const DARK = `
53
58
  --page-rule: rgba(231, 228, 220, 0.10); --page-accent: #8DB0E6; --page-link: #8DB0E6; --page-selection: rgba(120, 155, 210, 0.28);
54
59
  --page-check-border: rgba(231, 228, 220, 0.35); --page-check-fill: #E7E4DC; --page-check-ink: #1A1A18;
55
60
  --page-callout-bg: rgba(231, 228, 220, 0.05); --page-code-bg: rgba(231, 228, 220, 0.06);
61
+ --page-color-gray: #A3A098; --page-color-brown: #E0B47A; --page-color-orange: #E8A177; --page-color-yellow: #DEC97A; --page-color-green: #A9C98B;
62
+ --page-color-blue: #8DB0E6; --page-color-purple: #BBA6E6; --page-color-pink: #E3A3C4; --page-color-red: #E08A7A;
63
+ --page-mark-bg-gray: rgba(231, 228, 220, 0.14); --page-mark-bg-brown: rgba(224, 180, 122, 0.22); --page-mark-bg-orange: rgba(232, 161, 119, 0.22);
64
+ --page-mark-bg-yellow: rgba(222, 201, 122, 0.24); --page-mark-bg-green: rgba(169, 201, 139, 0.22); --page-mark-bg-blue: rgba(141, 176, 230, 0.22);
65
+ --page-mark-bg-purple: rgba(187, 166, 230, 0.22); --page-mark-bg-pink: rgba(227, 163, 196, 0.22); --page-mark-bg-red: rgba(224, 138, 122, 0.22);
56
66
  --page-code-token-keyword: #D9A0C8; --page-code-token-constant: #8DB0E6; --page-code-token-string: #A9C98B; --page-code-token-string-expression: #A9C98B; --page-code-token-comment: #75726B; --page-code-token-function: #BBA6E6; --page-code-token-parameter: #E0B47A; --page-code-token-punctuation: var(--page-ink-soft); --page-code-token-link: var(--page-link);
57
67
  --page-chrome-ink-rest: rgba(231, 228, 220, 0.18); --page-chrome-ink-moved: rgba(231, 228, 220, 0.30);
58
68
  --page-chrome-bg-rest: rgba(231, 228, 220, 0.02); --page-chrome-bg-moved: rgba(231, 228, 220, 0.04);
@@ -100,6 +110,9 @@ export const PAGE_EDITOR_CSS = `
100
110
  .page-editor pre:hover .page-code-lang, .page-editor pre:focus-within .page-code-lang, .page-code-lang:focus-visible, .page-editor pre[data-lang] .page-code-lang { opacity: 1; }
101
111
  .page-code-lang:hover, .page-code-lang:focus-visible { background: var(--page-pop-hover); color: var(--page-ink-soft); }
102
112
  :where(article[data-page], .page-editor) .page-callout { background: var(--page-callout-bg); border: 1px solid var(--page-callout-line); border-radius: 8px; padding: 10px 14px; }
113
+ /* The four floor-1 formats. A quote is set apart by its own inset and ink, with
114
+ no left-border accent; the palette's keys are fixed tokens, in both themes. */
115
+ :where(article[data-page], .page-editor) blockquote { padding-inline-start: var(--page-indent); color: var(--page-ink-soft); }
103
116
  :where(article[data-page], .page-editor) hr { border: 0; border-top: 1px solid var(--page-rule); margin-block: 1.2em; }
104
117
  :where(article[data-page], .page-editor) .page-hidden { display: none; }
105
118
  :where(article[data-page], .page-editor) .page-li { position: relative; --li-indent: 0; padding-inline-start: calc((var(--li-indent) + 1) * var(--page-indent)); }
@@ -126,7 +139,10 @@ ${INDENTS.map((n) => `:where(article[data-page], .page-editor) .page-li[data-ind
126
139
  .page-tool { display: inline-flex; align-items: center; justify-content: center; min-width: 28px; height: 28px; padding: 0 6px; border: 0; border-radius: 5px; background: none; color: inherit; font: 500 13px/1 var(--page-font); cursor: pointer; }
127
140
  .page-tool:hover, .page-tool[aria-pressed="true"] { background: var(--page-pop-hover); }
128
141
  .page-tool-turn { gap: 4px; padding: 0 8px; }
142
+ /* The phone's fourth control: everything the narrow bar folds away. */
143
+ .page-tool-more { display: none; }
129
144
  .page-tool-sep { width: 1px; height: 16px; margin: 0 3px; background: currentColor; opacity: 0.5; }
145
+ .page-safe-probe { position: fixed; left: 0; bottom: 0; width: 0; height: 0; padding-bottom: env(safe-area-inset-bottom); visibility: hidden; pointer-events: none; }
130
146
  .page-plus { position: absolute; z-index: 2; top: calc(var(--page-plus-top, 0px) - var(--page-plus-size) / 2); left: calc(var(--page-plus-left, 0px) - var(--page-plus-size) - 12px); width: var(--page-plus-size); height: var(--page-plus-size); display: grid; place-items: center; padding: 0; cursor: pointer; }
131
147
  .page-plus[aria-expanded="true"] { color: var(--page-chrome-ink-moved); background: var(--page-chrome-bg-moved); }
132
148
  .page-grip { position: absolute; z-index: 2; top: calc(var(--page-plus-top, 0px) - var(--page-grip-size, var(--page-plus-size)) / 2); left: calc(var(--page-plus-left, 0px) - var(--page-plus-size) - 12px - var(--page-grip-size, var(--page-plus-size)) - 2px); width: var(--page-grip-size, var(--page-plus-size)); height: var(--page-grip-size, var(--page-plus-size)); display: grid; place-items: center; padding: 0; border: 0; border-radius: var(--page-chrome-radius); background: none; color: var(--page-chrome-ink-moved); opacity: 0; cursor: grab; touch-action: none; transition: opacity var(--page-fade); }
@@ -157,40 +173,8 @@ ${INDENTS.map((n) => `:where(article[data-page], .page-editor) .page-li[data-ind
157
173
  .page-link-btn { display: inline-grid; place-items: center; min-width: 30px; height: 30px; padding: 0 6px; border: 0; border-radius: 6px; background: none; color: inherit; font: inherit; cursor: pointer; }
158
174
  .page-link-btn:hover { background: var(--page-pop-hover); }
159
175
  .page-live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
160
- .page-editor .page-image { margin-inline: 0; }
161
- .page-image-frame { position: relative; margin: 0 auto; max-width: 100%; }
162
- .page-image-frame img { display: block; width: 100%; height: auto; border-radius: var(--page-image-radius, 6px); }
163
- .page-image[data-broken] .page-image-frame { min-height: 80px; background: var(--page-code-bg); border-radius: var(--page-image-radius, 6px); }
164
- .page-image-handle { position: absolute; top: 50%; inset-inline-end: 6px; width: 6px; height: 36px; margin-top: -18px; border-radius: 3px; background: var(--page-chrome-ink-moved); opacity: 0; cursor: ew-resize; touch-action: none; transition: opacity var(--page-fade); }
165
- .page-image:hover .page-image-handle, .page-image.is-selected .page-image-handle { opacity: 1; }
166
- .page-editor .page-image.page-block-selected { background: none; box-shadow: none; }
167
- .page-editor .page-image.page-block-selected .page-image-frame { box-shadow: 0 0 0 6px var(--page-block-selected, rgba(88, 120, 170, 0.12)); border-radius: var(--page-image-radius, 6px); }
168
- .page-image[data-align="left"] .page-image-frame { margin-left: 0; margin-right: auto; }
169
- .page-image[data-align="right"] .page-image-frame { margin-left: auto; margin-right: 0; }
170
- .page-image[data-align="left"] .page-image-caption { text-align: left; }
171
- .page-image[data-align="right"] .page-image-caption { text-align: right; }
172
- .page-image-align { position: absolute; top: 8px; inset-inline-end: 8px; display: none; gap: 1px; padding: 2px; background: var(--page-pop-bg); color: var(--page-pop-ink); border: 1px solid var(--page-pop-line); box-shadow: var(--page-pop-shadow); border-radius: 7px; }
173
- .page-image.is-selected .page-image-align, .page-image.page-block-selected .page-image-align { display: flex; }
174
- .page-image-align button { display: grid; place-items: center; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 5px; background: none; color: inherit; cursor: pointer; }
175
- .page-image-align button:hover, .page-image-align button[aria-pressed="true"] { background: var(--page-pop-hover); }
176
- .page-image.is-selected .page-image-frame img { outline: 2px solid var(--page-selection); outline-offset: 2px; }
177
- .page-image-caption { margin-top: 6px; color: var(--page-ink-soft); font-size: 0.88em; text-align: center; outline: none; }
178
- .page-image-caption[data-empty]::before { content: attr(data-placeholder); color: var(--page-ink-faint); }
179
176
  .ProseMirror-focused .page-placeholder::before { content: attr(data-placeholder); float: left; height: 0; color: var(--page-ink-faint); pointer-events: none; }
180
- .page-image:not(.is-selected) .page-image-caption[data-empty]:not(:focus) { display: none; }
181
- .page-file-link { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border: 1px solid var(--page-rule); border-radius: 8px; color: var(--page-ink); text-decoration: none; }
182
- .page-file-icon { color: var(--page-link, var(--page-accent)); }
183
- .page-file-name { color: var(--page-link, var(--page-accent)); min-width: 0; overflow-wrap: anywhere; }
184
- .page-file-meta { margin-inline-start: auto; color: var(--page-ink-faint); font-size: 0.88em; }
185
- .page-upload { display: flex; align-items: center; gap: 10px; margin: var(--page-block-gap) 0; padding: 8px 12px; border: 1px dashed var(--page-rule); border-radius: 8px; color: var(--page-ink-soft); font-size: 0.88em; }
186
- .page-upload-preview { width: 40px; height: 40px; object-fit: cover; border-radius: 4px; opacity: 0.6; }
187
- .page-upload-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
188
- .page-upload-bar { flex: 1; height: 2px; border-radius: 1px; background: var(--page-rule); overflow: hidden; }
189
- .page-upload-bar > span { display: block; height: 100%; background: var(--page-progress, var(--page-ink-soft)); transition: width 200ms; }
190
- .page-upload[data-state="error"] .page-upload-msg, .page-upload[data-state="embed-choice"] .page-upload-msg { flex: 1; }
191
- .page-upload[data-state="error"] .page-upload-msg { color: var(--page-danger, #9E3B2F); }
192
- .page-upload-btn { border: 0; padding: 4px 8px; border-radius: 6px; background: none; color: inherit; font: inherit; cursor: pointer; }
193
- .page-upload-btn:hover { background: var(--page-pop-hover); }
177
+ .page-editor .page-title-placeholder::before { content: attr(data-placeholder); float: left; height: 0; color: var(--page-ink-faint); pointer-events: none; }
194
178
  .page-csv-error { margin: 8px 0; color: var(--page-danger); font-size: 14px; }
195
179
  .page-editor .page-table { max-width: 100%; min-width: 0; border: 1px solid var(--page-rule); border-radius: 8px; }
196
180
  .page-editor .page-chart { position: relative; max-width: 100%; min-width: 0; margin-inline: 0; color: var(--page-ink); }
@@ -198,14 +182,6 @@ ${INDENTS.map((n) => `:where(article[data-page], .page-editor) .page-li[data-ind
198
182
  .page-chart figcaption { color: var(--page-ink-soft); font-size: 0.88em; text-align: center; }
199
183
  .page-chart-settings { display: block; margin: 8px auto 0; padding: 4px 10px; border: 1px solid var(--page-rule); border-radius: 6px; background: var(--page-pop-bg); color: var(--page-ink); font: inherit; cursor: pointer; }
200
184
  .page-chart-title-input { display: block; width: min(100%, 24em); box-sizing: border-box; margin: 6px auto; padding: 4px 8px; border: 1px solid var(--page-rule); border-radius: 6px; background: var(--page-pop-bg); color: var(--page-ink); font: inherit; }
201
- .page-tool-block { position: relative; min-height: 48px; border: 1px solid var(--page-rule); border-radius: 8px; padding: 12px; }
202
- .page-tool-settings { position: absolute; top: 4px; inset-inline-end: 4px; border: 0; border-radius: 5px; background: var(--page-pop-bg); color: var(--page-ink-soft); font: 12px var(--page-font); padding: 5px 8px; cursor: pointer; opacity: 0; }
203
- .page-tool-block:hover .page-tool-settings, .page-tool-block:focus-within .page-tool-settings { opacity: 1; }
204
- .page-ai-panel { position: relative; z-index: 5; width: min(520px, 100%); box-sizing: border-box; margin: 12px auto; padding: 14px; border: 1px solid var(--page-pop-line); border-radius: 10px; background: var(--page-pop-bg); color: var(--page-pop-ink); box-shadow: var(--page-pop-shadow); }
205
- .page-ai-panel textarea { width: 100%; min-height: 88px; box-sizing: border-box; padding: 8px; border: 1px solid var(--page-pop-line); border-radius: 6px; background: var(--page-pop-bg); color: inherit; font: inherit; resize: vertical; }
206
- .page-ai-actions { display: flex; gap: 8px; margin-top: 8px; }
207
- .page-ai-actions button { border: 1px solid var(--page-pop-line); border-radius: 6px; background: var(--page-pop-bg); color: inherit; font: inherit; padding: 5px 10px; cursor: pointer; }
208
- .page-ai-status, [data-tool-note], [data-tool-error] { color: var(--page-danger); font-size: 0.85em; }
209
185
  .page-table-scroll { max-width: 100%; overflow-x: auto; }
210
186
  .page-table table { border-collapse: collapse; min-width: max-content; width: 100%; table-layout: auto; }
211
187
  .page-table th, .page-table 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; }
@@ -223,18 +199,31 @@ ${INDENTS.map((n) => `:where(article[data-page], .page-editor) .page-li[data-ind
223
199
  .page-check-popup { display: inline-flex; gap: 4px; padding: 4px; background: var(--page-pop-bg); color: var(--page-pop-ink); border: 1px solid var(--page-pop-line); border-radius: var(--page-pop-radius); box-shadow: var(--page-pop-shadow); }
224
200
  .page-check-popup input { font: inherit; color: inherit; background: var(--page-pop-bg); border: 1px solid var(--page-pop-line); }
225
201
  .page-check-popup.page-check-owner { display: inline-block; width: min(280px, 80vw); padding: 8px 0; vertical-align: top; }
202
+ .page-board-owners { display: flex; flex-direction: column; gap: 6px; min-width: 0; padding-inline: 8px; }
203
+ .page-board-people { display: flex; flex-wrap: wrap; gap: 4px; }
204
+ .page-board-person { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px 0 4px; border: 1px solid var(--page-rule); border-radius: 14px; background: var(--page-pop-bg); color: var(--page-pop-ink); font: inherit; font-size: 13px; cursor: pointer; transition: background-color var(--page-fade), border-color var(--page-fade); }
205
+ .page-board-person.is-none { padding-inline-start: 10px; color: var(--page-pop-muted); }
206
+ .page-board-person:hover { background: var(--page-pop-hover); }
207
+ .page-board-person[aria-pressed="true"] { border-color: var(--page-accent); background: var(--page-selection); color: var(--page-pop-ink); }
208
+ .page-board-owner-input { width: 100%; height: 32px; box-sizing: border-box; padding: 0 8px; border: 1px solid var(--page-pop-line); border-radius: 7px; background: var(--page-pop-bg); color: var(--page-pop-ink); font: inherit; font-size: 14px; }
209
+ .page-board-owner-input::placeholder { color: var(--page-ink-faint); }
210
+ .page-board-face { flex: none; display: inline-grid; place-items: center; width: 20px; height: 20px; border-radius: 50%; background: var(--page-code-bg); box-shadow: inset 0 0 0 1px var(--page-rule); color: var(--page-ink-soft); font-size: 10.5px; font-weight: 600; line-height: 1; }
226
211
  .page-scrim { display: none; }
227
212
  .page-shell [hidden] { display: none !important; }
228
- ${TABLE_CSS}
229
- ${BOARD_CSS}
230
213
  @media (max-width: 560px) {
231
- .page-dock { display: none; }
214
+ /* One fixed bar over the visual viewport, with the four controls a phone keeps.
215
+ The bubble and the desktop-only tools stand down so nothing is doubled. */
216
+ .page-dock { display: flex; position: fixed; left: 0; top: 8px; width: 204px; min-width: 204px; box-sizing: border-box; align-items: center; justify-content: center; gap: 4px; margin: 0; padding: 8px; z-index: 3; }
217
+ .page-tool-desk { display: none; }
218
+ .page-dock .page-tool-more { display: inline-flex; }
219
+ .page-dock .page-tool { min-width: 44px; height: 44px; }
220
+ .page-shell:has(.page-bubble:not([hidden])) .page-dock { visibility: visible; }
221
+ .page-dock + .page-editor { padding-bottom: calc(72px + env(safe-area-inset-bottom)); }
232
222
  .page-plus { position: fixed; top: auto; left: 16px; bottom: calc(16px + env(safe-area-inset-bottom)); width: 44px; height: 44px; border-radius: 22px; }
233
- .page-grip { position: fixed; top: auto; left: 68px; bottom: calc(16px + env(safe-area-inset-bottom)); width: 44px; height: 44px; border-radius: 22px; opacity: 1; color: var(--page-chrome-ink-rest); background: var(--page-chrome-bg-rest); border: 1px solid var(--page-chrome-line-rest); }
234
- .page-bubble { position: fixed; left: 0; right: 0; top: auto; bottom: 0; transform: none; justify-content: center; gap: 4px; padding: 6px 16px calc(6px + env(safe-area-inset-bottom)); border-radius: 0; border-width: 1px 0 0; background: var(--page-bar-bg); color: var(--page-bar-ink); }
235
- .page-bubble .page-tool { min-width: 44px; height: 44px; }
223
+ .page-grip { position: fixed; top: auto; left: 68px; bottom: calc(16px + var(--page-grip-lift, 0px) + env(safe-area-inset-bottom)); width: 44px; height: 44px; border-radius: 22px; opacity: 1; color: var(--page-chrome-ink-rest); background: var(--page-chrome-bg-rest); border: 1px solid var(--page-chrome-line-rest); }
224
+ .page-bubble { display: none; }
236
225
  .page-scrim { display: block; position: fixed; inset: 0; z-index: 4; background: var(--page-scrim); }
237
- .page-menu { position: fixed; left: 0; right: 0; top: auto; bottom: 0; width: auto; max-height: 70vh; z-index: 5; padding: 8px 8px calc(8px + env(safe-area-inset-bottom)); border-radius: var(--page-pop-radius) var(--page-pop-radius) 0 0; border-width: 1px 0 0; }
226
+ .page-menu { position: fixed; left: 0; top: 0; width: var(--page-pop-width, min(280px, calc(100vw - 16px))); max-height: min(70vh, var(--page-pop-max, 70vh)); overflow-y: auto; transform: translate(var(--page-pop-x, 0px), var(--page-pop-y, 0px)); z-index: 5; }
238
227
  .page-menu-item { min-height: 48px; font-size: 16px; }
239
228
  .page-link { position: fixed; left: 0; right: 0; top: auto; bottom: 0; max-width: none; padding: 8px 16px calc(8px + env(safe-area-inset-bottom)); border-radius: 0; border-width: 1px 0 0; }
240
229
  .page-link-input { min-width: 0; height: 44px; font-size: 16px; }
@@ -242,9 +231,11 @@ ${BOARD_CSS}
242
231
  .page-image-frame { width: 100% !important; }
243
232
  .page-image-handle { display: none; }
244
233
  .page-table th button, .page-table-row-menu, .page-check-chip, .page-check-popup button, .page-menu-item { min-height: 44px; }
234
+ .page-board-owner-input { font-size: 16px; height: 44px; }
235
+ .page-board-person { height: 44px; border-radius: 22px; }
245
236
  }
246
237
  @media (prefers-reduced-motion: reduce) {
247
- .page-dock, .page-plus, .page-grip, .page-toggle-button, .page-image-handle, .page-upload-bar > span { transition: none; }
238
+ .page-dock, .page-plus, .page-grip, .page-toggle-button, .page-image-handle, .page-upload-bar > span, .page-board-person { transition: none; }
248
239
  }
249
240
  }
250
241
  `;
@@ -254,6 +245,19 @@ const insertHardBreak = (st, dispatch) => {
254
245
  return true;
255
246
  };
256
247
 
248
+ // Mod-u belongs to an editable page selection only: anywhere else the browser
249
+ // keeps its own meaning for the key. The write itself goes through the format
250
+ // gate, so a refused activation leaves the document exactly as it was.
251
+ const underlineShortcut = (st, dispatch, view) => {
252
+ if (!dispatch || !view?.editable || !markable(st, schema.marks.underline)) return false;
253
+ import("./format-native-commands.js")
254
+ .then(({ formatMark }) => formatMark(view, "underline"), () => null)
255
+ .then((result) => {
256
+ if (result?.status !== "applied" && !view.isDestroyed) announce(view, result?.message ?? "That format isn\u2019t available here.");
257
+ });
258
+ return true;
259
+ };
260
+
257
261
  const editKeys = ({ history: useHistory = true, undo, redo } = {}) => ({
258
262
  "Mod-z": useHistory ? history.undo : () => { undo?.(); return true; },
259
263
  "Shift-Mod-z": useHistory ? history.redo : () => { redo?.(); return true; },
@@ -261,32 +265,266 @@ const editKeys = ({ history: useHistory = true, undo, redo } = {}) => ({
261
265
  "Shift-Enter": insertHardBreak,
262
266
  "Mod-b": commands.toggleMark(schema.marks.strong),
263
267
  "Mod-i": commands.toggleMark(schema.marks.em),
268
+ "Mod-u": underlineShortcut,
264
269
  });
265
270
 
271
+ const restoreShortcut = (st, dispatch, view) => {
272
+ if (composing(null, view)) return false;
273
+ return inputrules.undoInputRule(st, dispatch && ((tr) => dispatch(asStep(tr))));
274
+ };
275
+
266
276
  const markKeys = {
267
- Backspace: commands.chainCommands(inputrules.undoInputRule, backspaceToParagraph),
277
+ Backspace: commands.chainCommands(restoreShortcut, backspaceToParagraph),
268
278
  "Mod-e": commands.toggleMark(schema.marks.code),
269
279
  "Mod-Shift-s": commands.toggleMark(schema.marks.strike),
270
280
  };
271
281
 
272
282
  const defaultUpload = (file, options) => globalThis.clay?.upload?.(file, options) ?? Promise.resolve({ ok: false, code: "unavailable", msg: "File uploads are not available in this editor" });
273
283
 
274
- export function pagePlugins({ upload = defaultUpload, highlight, history: useHistory = true, undo, redo, onAiTool = null, toolHost = null } = {}) {
284
+ const nodeViewRegistries = new WeakMap();
285
+
286
+ export const lazyViewsKey = new state.PluginKey("lazyViews");
287
+
288
+ // Stands in for a view part's node view until its type loads: the schema's own
289
+ // toDOM, and an update that asks to be recreated once the real view exists.
290
+ class PendingView {
291
+ constructor(node, registry) {
292
+ this.node = node;
293
+ this.type = node.type;
294
+ this.registry = registry;
295
+ const { dom, contentDOM } = model.DOMSerializer.renderSpec(document, node.type.spec.toDOM(node));
296
+ this.dom = dom;
297
+ this.contentDOM = contentDOM ?? null;
298
+ }
299
+ update(node) { return node.type === this.type && !this.registry.has(node.type.name); }
300
+ }
301
+
302
+ // Recreates only the node views of the types that just loaded: one node
303
+ // decoration makes ProseMirror rebuild those nodes and no others.
304
+ export function lazyViewsPlugin() {
305
+ return new state.Plugin({
306
+ key: lazyViewsKey,
307
+ state: {
308
+ init: () => pmView.DecorationSet.empty,
309
+ apply(tr) {
310
+ const meta = tr.getMeta(lazyViewsKey);
311
+ if (!meta) return pmView.DecorationSet.empty;
312
+ const decorations = [];
313
+ tr.doc.forEach((node, offset) => {
314
+ if (!meta.types.includes(node.type.name)) return;
315
+ decorations.push(pmView.Decoration.node(offset, offset + node.nodeSize, {}, { lazyView: decorations.length + 1 }));
316
+ });
317
+ return pmView.DecorationSet.create(tr.doc, decorations);
318
+ },
319
+ },
320
+ props: { decorations: (state) => lazyViewsKey.getState(state) },
321
+ });
322
+ }
323
+
324
+ // Adds a view part to a running editor: its CSS at once, its node views into the
325
+ // editor's registry, and a transaction that recreates only those block types.
326
+ export function installPageFeature({ view, upload, toolHost, board = null, undo = null, redo = null, revise = null, highlight }, id, mod) {
327
+ const feature = mod.createFeature({ view, upload, toolHost, board, undo, redo, revise, highlight });
328
+ const registry = nodeViewRegistries.get(view);
329
+ const apply = () => {
330
+ if (feature.css) styleWithLegacyId(`page-feature-${id}`, feature.css, `page-feature-${id}`, ["editor-ui"]);
331
+ if (!feature.nodeViews) return;
332
+ const types = Object.keys(feature.nodeViews);
333
+ for (const type of types) registry.set(type, feature.nodeViews[type]);
334
+ view.dispatch(view.state.tr.setMeta(lazyViewsKey, { types }).setMeta("addToHistory", false));
335
+ };
336
+ const afterComposition = () => {
337
+ if (view.isDestroyed) return;
338
+ if (view.composing) setTimeout(afterComposition, 25);
339
+ else apply();
340
+ };
341
+ if (view.composing) view.dom.addEventListener("compositionend", () => setTimeout(afterComposition, 25), { once: true });
342
+ else apply();
343
+ return feature;
344
+ }
345
+
346
+ // Loads the view part for a block type the editor shows but has no view for:
347
+ // the blocks that were there at boot, and any that arrive later.
348
+ function featureWatchPlugin(features) {
349
+ return new state.Plugin({
350
+ view(editorView) {
351
+ const failed = new Set();
352
+ const walk = () => {
353
+ for (const id of failed) if (features.has(id)) failed.delete(id);
354
+ editorView.state.doc.forEach((node) => {
355
+ const id = featureForType(node.type.name);
356
+ if (!id || features.has(id) || failed.has(id)) return;
357
+ features.ensure(id).catch(() => { failed.add(id); });
358
+ });
359
+ };
360
+ let doc = editorView.state.doc;
361
+ walk();
362
+ return {
363
+ update() {
364
+ if (editorView.state.doc === doc) return;
365
+ doc = editorView.state.doc;
366
+ walk();
367
+ },
368
+ };
369
+ },
370
+ });
371
+ }
372
+
373
+ // The optional commands. A command module exports `createFeature(context)` returning
374
+ // `{ run, dispose }`; the editor fetches it on the first ask and installs it once, and
375
+ // nothing here is preloaded by the block-type scan. A command is asked for with the state
376
+ // it was asked in: the module is fetched, and the command runs only if the edit is still
377
+ // open, the document revision has not moved and the selection is where it was. A failed
378
+ // fetch is reported and asked for again on the next request; an exit disposes what was
379
+ // installed and aborts whatever was still arriving, and the next entry installs fresh
380
+ // against the module the browser already holds.
381
+ function createCommandHost({ load = () => Promise.reject(new Error("page: this editor has no command module")), context = () => ({}), reportError } = {}) {
382
+ const modules = new Map();
383
+ const waiters = new Set();
384
+ let view = null;
385
+ let loader = null;
386
+ let active = true;
387
+ let disposed = false;
388
+ let revision = 0;
389
+
390
+ // One fetch per command for the life of the host, whatever the entry: a module that
391
+ // failed is the one thing not kept, so the next ask loads it again.
392
+ const moduleFor = (id) => {
393
+ let fetched = modules.get(id);
394
+ if (!fetched) {
395
+ fetched = Promise.resolve(load(id));
396
+ modules.set(id, fetched);
397
+ fetched.catch(() => { if (modules.get(id) === fetched) modules.delete(id); });
398
+ }
399
+ return fetched;
400
+ };
401
+
402
+ const loaderFor = () => {
403
+ loader ??= createFeatureLoader({
404
+ load: moduleFor,
405
+ install: (id, mod) => mod.createFeature(context(view)),
406
+ reportError: reportError ?? ((error, id) => console.error(`clayjs: the ${id} command failed to load`, error)),
407
+ });
408
+ return loader;
409
+ };
410
+
411
+ // The document revision and the selection a command was asked for. A revision moves
412
+ // when any transaction changes the document, whoever wrote it.
413
+ const leaseOf = () => {
414
+ const at = revision;
415
+ const selection = view.state.selection;
416
+ return {
417
+ revision: at,
418
+ selection,
419
+ valid: () => active && !disposed && !view.isDestroyed && revision === at && view.state.selection.eq(selection),
420
+ };
421
+ };
422
+
423
+ function observe(state, prevState) {
424
+ if (state.doc !== prevState.doc) revision += 1;
425
+ for (const waiter of waiters) {
426
+ if (waiter.lease.valid()) continue;
427
+ waiters.delete(waiter);
428
+ waiter.controller.abort({ ok: false, code: "stale-target" });
429
+ }
430
+ }
431
+
432
+ async function run(id, args) {
433
+ if (!active || disposed || !view || view.isDestroyed) return { ok: false, code: "not-editing" };
434
+ const waiter = { lease: leaseOf(), controller: new AbortController() };
435
+ waiters.add(waiter);
436
+ try {
437
+ const feature = await loaderFor().ensure(id, { signal: waiter.controller.signal });
438
+ if (!feature) return { ok: false, code: "not-editing" };
439
+ if (!waiter.lease.valid()) return { ok: false, code: "stale-target" };
440
+ return await feature.run(args);
441
+ } catch (error) {
442
+ if (waiter.controller.signal.aborted) return waiter.controller.signal.reason;
443
+ return { ok: false, code: "unavailable", error };
444
+ } finally {
445
+ waiters.delete(waiter);
446
+ }
447
+ }
448
+
449
+ function leave() {
450
+ if (!active) return;
451
+ active = false;
452
+ for (const waiter of waiters) waiter.controller.abort({ ok: false, code: "not-editing" });
453
+ waiters.clear();
454
+ loader?.dispose();
455
+ loader = null;
456
+ }
457
+
458
+ return {
459
+ attach(editorView) { view = editorView; },
460
+ observe,
461
+ run,
462
+ enter() { if (!disposed) active = true; },
463
+ leave,
464
+ dispose() {
465
+ leave();
466
+ disposed = true;
467
+ active = false;
468
+ modules.clear();
469
+ },
470
+ };
471
+ }
472
+
473
+ const commandHosts = new WeakMap();
474
+ // A command asks for its module with the state it was asked in, so the host has to hear
475
+ // about every transaction: one that moves the document revision or the selection refuses
476
+ // a command whose module is still arriving rather than running it where the person has
477
+ // since left. It also holds the one typed key a command owns: `@` asks for the mention
478
+ // picker, and the host types the character itself so the command is handed the bookmark it
479
+ // belongs to, with everything else about that picker left to the module that arrives for
480
+ // it. It is asked before the plugin that records ordinary typing, since it types it.
481
+ function commandWatchPlugin(host) {
482
+ return new state.Plugin({
483
+ view: () => ({ update: (editorView, prevState) => host.observe(editorView.state, prevState) }),
484
+ props: { handleTextInput(v, from, to, text) {
485
+ if (text !== "@" || v.composing || !Object.hasOwn(COMMAND_FEATURES, "mentions")) return false;
486
+ v.dispatch(v.state.tr.insertText("@", from, to));
487
+ host.run("mentions", { from, to: from + 1 });
488
+ return true;
489
+ } },
490
+ });
491
+ }
492
+
493
+ // Runs an optional command on a mounted editor, by the id its row has in COMMAND_FEATURES.
494
+ // A command nobody offers, or an editor with no command host, refuses rather than throws.
495
+ export function runPageCommand(view, id, args) {
496
+ const host = view ? commandHosts.get(view) : null;
497
+ if (!host) return Promise.resolve({ ok: false, code: "not-editing" });
498
+ if (!Object.hasOwn(COMMAND_FEATURES, id)) return Promise.resolve({ ok: false, code: "unknown-command" });
499
+ return host.run(id, args);
500
+ }
501
+
502
+ // Tools follow the tables a document holds; the host arrives with the tool view
503
+ // part on a document that had no tool at boot.
504
+ function toolFollowPlugin(toolHost) {
505
+ return new state.Plugin({
506
+ view(view) {
507
+ const changed = toolHost ? () => toolHost.tablesChanged() : () => toolViews.get(view)?.tablesChanged?.();
508
+ let previous = view.state.doc;
509
+ return { update(next) { if (next.state.doc !== previous) { previous = next.state.doc; changed(); } } };
510
+ },
511
+ });
512
+ }
513
+
514
+ export function pagePlugins({ upload = defaultUpload, highlight, history: useHistory = true, undo, redo, onAiTool = null, toolHost = null, features = null, commandHost = null } = {}) {
275
515
  return [
276
516
  announcementPlugin(),
277
517
  chromePlugin({ onAiTool }),
278
- toolHost && new state.Plugin({
279
- view(view) {
280
- let previous = view.state.doc;
281
- return { update(next) { if (next.state.doc !== previous) { previous = next.state.doc; toolHost.tablesChanged(); } } };
282
- },
283
- }),
518
+ commandHost ? commandWatchPlugin(commandHost) : null,
519
+ slashRangePlugin(),
520
+ toolFollowPlugin(toolHost),
284
521
  linkPlugin(),
285
522
  tableSelectPlugin(),
286
523
  blockSelectionPlugin(),
287
524
  markdownPlugin(),
288
525
  markdownKeys(),
289
526
  placeholderPlugin(),
527
+ titlePlugin(),
290
528
  keymap.keymap(markKeys),
291
529
  tableKeys,
292
530
  checklistKeys,
@@ -296,16 +534,18 @@ export function pagePlugins({ upload = defaultUpload, highlight, history: useHis
296
534
  structurePlugin(),
297
535
  identityPlugin(),
298
536
  tablePlugin(),
299
- chartPlugin(),
300
- boardPlugin(),
537
+ viewHookPlugin(),
301
538
  checklistPlugin(),
302
539
  csvImportPlugin({ upload }),
303
540
  collapsePlugin(),
304
- mediaPlugin({ upload }),
305
- highlightPlugin(highlight),
541
+ mediaPlugin({ upload, features }),
542
+ highlightSlot(),
306
543
  pastePlugin(),
544
+ historyIntentPlugin(),
307
545
  useHistory ? history.history() : null,
308
546
  gapcursor.gapCursor(),
547
+ lazyViewsPlugin(),
548
+ features ? featureWatchPlugin(features) : null,
309
549
  ].filter(Boolean);
310
550
  }
311
551
 
@@ -315,6 +555,29 @@ export function pageState(blocks, options = {}) {
315
555
  return state.EditorState.create({ doc, plugins: pagePlugins(options) });
316
556
  }
317
557
 
558
+ // The shell is built detached and moved into the article once it is complete, so
559
+ // the node views in it measured nothing while it was out of the document. One pass
560
+ // in place gives them a real box: the state is the same object it was, and no node
561
+ // view is rebuilt for it.
562
+ export function refreshMountedView(view) {
563
+ if (!view || view.isDestroyed) return;
564
+ view.updateState(view.state);
565
+ }
566
+
567
+ // Admission on a mounted editor, in one place: the commit that enters edit opens it,
568
+ // and the exit that holds the session closed shuts it without touching a buffer.
569
+ export function setEditorActive(view, active) {
570
+ if (!view || view.isDestroyed) return false;
571
+ const open = active === true;
572
+ view.setProps({ editable: () => open });
573
+ // A refused exit closes admission without destroying the view, so the commands are told
574
+ // here: the exit disposes what they installed, and the next entry installs fresh.
575
+ const host = commandHosts.get(view);
576
+ if (open) host?.enter();
577
+ else host?.leave();
578
+ return open;
579
+ }
580
+
318
581
  // Clicking a checklist box (drawn by ::before in the item's start gutter)
319
582
  // toggles `checked`.
320
583
  function checkboxClick(view, event) {
@@ -335,9 +598,8 @@ function checkboxClick(view, event) {
335
598
  // Mounts the editor in `mount`, inside a .page-shell wrapper (the surface a
336
599
  // theme styles). `onChange(record)` gets the block record (blockMap) after
337
600
  // every transaction that changed the document.
338
- export function createPageEditor(mount, { blocks = null, onChange = null, upload, highlight, doc: initialDoc, dispatch, history: useHistory = true, undo, redo, editable = true, toolHost: providedToolHost = null, onAiTool = null, reviseTool = null, board = null } = {}) {
601
+ export function createPageEditor(mount, { blocks = null, onChange = null, upload, highlight, doc: initialDoc, dispatch, history: useHistory = true, undo, redo, editable = true, toolHost = null, onAiTool = null, reviseTool = null, board = null, features: providedFeatures = null, formatGate = null } = {}) {
339
602
  const doc = mount.ownerDocument;
340
- const toolHost = providedToolHost ?? createToolHost({ mode: "edit" });
341
603
  // Editor chrome, injected on every edit-mode boot and kept out of the saved file:
342
604
  // the reader theme <link> styles the page for visitors.
343
605
  styleWithLegacyId(STYLE_ID, PAGE_EDITOR_CSS, STYLE_ID, ["editor-ui"]);
@@ -347,20 +609,30 @@ export function createPageEditor(mount, { blocks = null, onChange = null, upload
347
609
  shell.setAttribute("sap-ignore", "");
348
610
  mount.appendChild(shell);
349
611
  let afterStep = false;
350
- const view = new pmView.EditorView(shell, {
351
- state: pageState(blocks, { upload, highlight, doc: initialDoc, history: useHistory, undo, redo, onAiTool, toolHost }),
612
+ let view = null;
613
+ const features = providedFeatures ?? createFeatureLoader({
614
+ load: (id) => FEATURES[id].load(),
615
+ install: (id, mod) => installPageFeature({ view, upload, toolHost, board, undo, redo, revise: reviseTool, highlight }, id, mod),
616
+ });
617
+ // Commands are their own loader: a document never fetches a picker to be read or typed
618
+ // in, and the host outlives a refused exit so a re-entry installs once more.
619
+ const commandHost = createCommandHost({
620
+ load: (id) => COMMAND_FEATURES[id].load(),
621
+ context: (editorView) => ({ view: editorView, upload }),
622
+ });
623
+ const nodeViewRegistry = new Map();
624
+ const nodeViews = {};
625
+ for (const feature of Object.values(FEATURES)) for (const type of feature.views ?? feature.types) {
626
+ nodeViews[type] = (node, v, getPos, decorations, inner) => {
627
+ const make = nodeViewRegistry.get(node.type.name);
628
+ return make ? make(node, v, getPos, decorations, inner) : new PendingView(node, nodeViewRegistry);
629
+ };
630
+ }
631
+ view = new pmView.EditorView(shell, {
632
+ state: pageState(blocks, { upload, highlight, doc: initialDoc, history: useHistory, undo, redo, onAiTool, toolHost, features, commandHost }),
352
633
  attributes: { class: "page-editor" },
353
634
  editable: () => editable,
354
- nodeViews: {
355
- image: (node, v, getPos) => new ImageView(node, v, getPos),
356
- file: (node, v) => new FileView(node, v),
357
- code_block: (node, v, getPos) => new CodeBlockView(node, v, getPos),
358
- table: (node, v, getPos) => new TableView(node, v, getPos),
359
- chart: (node, v, getPos) => new ChartView(node, v, getPos),
360
- tool: (node, v, getPos) => new ToolView(node, v, getPos, { host: toolHost, revise: reviseTool }),
361
- table_cell: (node, v, getPos) => new TableCellView(node, v, getPos),
362
- ...(board ? { board: (node, v, getPos) => new BoardView(node, v, getPos, { commands: board, undo, redo, panel: cardPanel(v) }) } : {}),
363
- },
635
+ nodeViews,
364
636
  handleDOMEvents: {
365
637
  mousedown: checkboxClick,
366
638
  dragstart(v, event) {
@@ -382,5 +654,42 @@ export function createPageEditor(mount, { blocks = null, onChange = null, upload
382
654
  if (onChange && view.state.doc !== before) onChange(blockMap(view.state.doc));
383
655
  },
384
656
  });
657
+ nodeViewRegistries.set(view, nodeViewRegistry);
658
+ viewFeatures.set(view, features);
659
+ commandHost.attach(view);
660
+ commandHosts.set(view, commandHost);
661
+ const destroy = view.destroy.bind(view);
662
+ // A new-format command asks the format gate, which the page plugin hands over as a
663
+ // loader of its own: neither the gate nor its host adapter is on the editor's ready
664
+ // path, so a reader who never uses a newer format never fetches them. The gate is
665
+ // created once, on the first command, and belongs to this view.
666
+ let gateHeld = null;
667
+ let disposed = false;
668
+ const heldGate = () => {
669
+ if (!gateHeld) {
670
+ gateHeld = Promise.resolve().then(() => formatGate()).catch((error) => {
671
+ gateHeld = null;
672
+ throw error;
673
+ });
674
+ }
675
+ return gateHeld;
676
+ };
677
+ view.runFormatCommand = async (apply) => {
678
+ if (disposed) return { status: "disposed", retry: false };
679
+ if (!formatGate) return { status: "unsupported-host", code: "page-format-unsupported", retry: false };
680
+ const gate = await heldGate();
681
+ if (disposed) {
682
+ gate.dispose?.();
683
+ return { status: "disposed", retry: false };
684
+ }
685
+ return gate.run({ apply });
686
+ };
687
+ view.destroy = () => {
688
+ disposed = true;
689
+ if (gateHeld) gateHeld.then((gate) => gate?.dispose?.(), () => {});
690
+ commandHost.dispose();
691
+ features.dispose();
692
+ destroy();
693
+ };
385
694
  return view;
386
695
  }