@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,230 @@
1
+ // Other people's carets on the page.
2
+ //
3
+ // A peer publishes where its selection sits (Document 10 Step 6): `presence`
4
+ // carries a page target, and this module draws it where that text is in *this*
5
+ // tab's document, through the binding's mapping. Nothing here is saved: the
6
+ // decorations are ProseMirror's, the labels are runtime elements, and the only
7
+ // thing this file sends is a target the presence store already coalesces.
8
+ //
9
+ // It loads on its own, after presence is available in an editor, so a reader and
10
+ // an editor that nobody shares with never fetch it.
11
+ import { state, view as pmView } from "../vendor/prosemirror.vendor.js";
12
+ import { encodeSelection, decodeSelection } from "./ybind/awareness.js";
13
+ import { presence as presenceStore } from "../sync/presence-store.js";
14
+ import { PRESENCE_VERSION } from "../sync/presence-target.js";
15
+ import { hostMeta } from "../core/host-meta.js";
16
+ import { styleWithLegacyId } from "../lib/style.js";
17
+
18
+ const { Plugin, PluginKey } = state;
19
+ const { Decoration, DecorationSet } = pmView;
20
+
21
+ export const awarenessKey = new PluginKey("page-awareness");
22
+
23
+ const STYLE_ID = "page-awareness-style";
24
+ const STYLE_CSS = `
25
+ .page-remote-selection { background-color: color-mix(in srgb, var(--page-remote-color, currentColor) 22%, transparent); }
26
+ .page-remote-caret { position: relative; display: inline-block; width: 0; height: 1.1em; vertical-align: text-bottom; }
27
+ .page-remote-caret::before { content: ""; position: absolute; inset-inline-start: -1px; top: 0; bottom: 0; width: 2px; background: var(--page-remote-color, currentColor); }
28
+ .page-remote-name { position: absolute; inset-inline-start: -1px; bottom: 100%; padding: 1px 4px; border-radius: 4px; background: var(--page-remote-color, currentColor); color: #fff; font-size: 10px; line-height: 1.4; white-space: nowrap; pointer-events: none; }
29
+ .page-remote-cell { outline: 2px solid var(--page-remote-color, currentColor); outline-offset: -2px; }
30
+ .page-remote-block { box-shadow: inset 2px 0 0 var(--page-remote-color, currentColor); }
31
+ `;
32
+
33
+ // A fixed palette, chosen for contrast against both page themes. A peer's id
34
+ // picks the colour; a peer never supplies one.
35
+ const PALETTE = ["#2F5E9E", "#8A3B6E", "#3E6A28", "#8A5A1E", "#5E4596", "#1F5A8A"];
36
+ export const colorOf = (id) => {
37
+ const text = typeof id === "string" ? id : "";
38
+ let hash = 0;
39
+ for (let i = 0; i < text.length; i++) hash = (hash * 31 + text.charCodeAt(i)) >>> 0;
40
+ return PALETTE[hash % PALETTE.length];
41
+ };
42
+
43
+ const caretElement = (doc, name, color) => {
44
+ const bar = doc.createElement("span");
45
+ bar.className = "page-remote-caret";
46
+ bar.setAttribute("aria-hidden", "true");
47
+ bar.style.setProperty("--page-remote-color", color);
48
+ const tag = doc.createElement("span");
49
+ tag.className = "page-remote-name";
50
+ tag.textContent = name;
51
+ bar.appendChild(tag);
52
+ return bar;
53
+ };
54
+
55
+ const labelOf = (person) => (typeof person.name === "string" && person.name.trim() ? person.name.trim() : "Someone");
56
+
57
+ /**
58
+ * The decorations one snapshot produces in one document.
59
+ *
60
+ * `decode` returns null for anything this tab cannot place — a hidden copy, a
61
+ * deleted block, a peer's relative position from another document, bytes that do
62
+ * not decode — and a null is a caret that simply is not drawn.
63
+ */
64
+ export function decorationsFor({ binding, doc, snapshot, epoch }) {
65
+ const generation = binding.awareness.generation();
66
+ const context = { ...binding.awareness.map(), doc };
67
+ const decorations = [];
68
+ for (const person of snapshot.people ?? []) {
69
+ if (person.you) continue;
70
+ const color = colorOf(person.id);
71
+ const name = labelOf(person);
72
+ for (const session of person.sessions ?? []) {
73
+ if (session.you) continue;
74
+ const target = session.editing;
75
+ if (!target || target.kind !== "page" || target.v !== PRESENCE_VERSION) continue;
76
+ if (target.epoch !== epoch || target.generation !== generation) continue;
77
+ const spot = decodeSelection(context, target.selection);
78
+ if (!spot) continue;
79
+ if (spot.kind === "text") {
80
+ if (spot.to > spot.from) decorations.push(Decoration.inline(spot.from, spot.to, { class: "page-remote-selection", style: `--page-remote-color:${color}` }));
81
+ decorations.push(Decoration.widget(spot.to, (view) => caretElement(view.dom.ownerDocument, name, color), { side: 1, key: `caret:${person.id}:${session.id}` }));
82
+ } else if (spot.kind === "cell") {
83
+ decorations.push(Decoration.node(spot.from, spot.to, { class: "page-remote-cell", style: `--page-remote-color:${color}` }));
84
+ decorations.push(Decoration.widget(spot.from + 1, (view) => caretElement(view.dom.ownerDocument, name, color), { side: -1, key: `cell:${person.id}:${session.id}` }));
85
+ } else if (spot.kind === "blocks") {
86
+ const [first] = spot.ranges;
87
+ for (const range of spot.ranges) decorations.push(Decoration.node(range.from, range.to, { class: "page-remote-block", style: `--page-remote-color:${color}` }));
88
+ decorations.push(Decoration.widget(first.from + 1, (view) => caretElement(view.dom.ownerDocument, name, color), { side: -1, key: `blocks:${person.id}:${session.id}` }));
89
+ }
90
+ }
91
+ }
92
+ return decorations.length ? DecorationSet.create(doc, decorations) : DecorationSet.empty;
93
+ }
94
+
95
+ /**
96
+ * Watch a mounted editor: publish this tab's selection while it is focused and
97
+ * draw every permitted peer's, one animation frame after anything changed.
98
+ *
99
+ * Returns the handle the page's teardown releases. In a reader, or on a host
100
+ * without page awareness, nothing is installed and nothing is sent.
101
+ */
102
+ export function createPageAwareness({ binding, view, store = presenceStore, epoch, composing = null, frame = (fn) => requestAnimationFrame(fn), cancel = (handle) => cancelAnimationFrame(handle) }) {
103
+ if (!binding?.awareness || !view || typeof epoch !== "string" || !epoch) return null;
104
+ styleWithLegacyId(STYLE_ID, STYLE_CSS, STYLE_ID, ["editor-ui"]);
105
+ const doc = view.dom.ownerDocument;
106
+ let disposed = false;
107
+ let focused = true;
108
+ let pending = null;
109
+ const isComposing = () => (composing ? composing() === true : view.composing === true);
110
+
111
+ const supported = () => store.getSnapshot().pageAwareness !== false;
112
+
113
+ const publish = (state = view.state) => {
114
+ if (disposed) return;
115
+ if (!supported() || !focused || isComposing()) {
116
+ store.setTarget(null);
117
+ return;
118
+ }
119
+ const selection = encodeSelection({ ...binding.awareness.map(), doc: state.doc }, state.selection);
120
+ if (!selection) {
121
+ store.setTarget(null);
122
+ return;
123
+ }
124
+ store.setTarget({ kind: "page", v: PRESENCE_VERSION, epoch, generation: binding.awareness.generation(), selection });
125
+ };
126
+
127
+ const rebuild = (next) => {
128
+ // During composition the peer's decorations stay where they were: replacing
129
+ // them mid-word would draw over text the person is still forming.
130
+ if (isComposing()) return awarenessKey.getState(view.state);
131
+ return decorationsFor({ binding, doc: next.doc, snapshot: store.getSnapshot(), epoch });
132
+ };
133
+
134
+ const plugin = new Plugin({
135
+ key: awarenessKey,
136
+ state: {
137
+ init: (_config, st) => decorationsFor({ binding, doc: st.doc, snapshot: store.getSnapshot(), epoch }),
138
+ apply: (tr, value, _old, next) => {
139
+ // Every transaction a person makes publishes, including one that only moved
140
+ // the caret: the page's dispatch hands the view a state, not a prop update.
141
+ if (tr.docChanged || tr.selectionSet) publish(next);
142
+ return tr.docChanged || tr.getMeta(awarenessKey) ? rebuild(next) : value;
143
+ },
144
+ },
145
+ props: {
146
+ decorations(st) {
147
+ return awarenessKey.getState(st) ?? DecorationSet.empty;
148
+ },
149
+ },
150
+ });
151
+
152
+ const refresh = () => {
153
+ if (disposed || view.isDestroyed) return;
154
+ view.dispatch(view.state.tr.setMeta(awarenessKey, true));
155
+ };
156
+
157
+ const schedule = () => {
158
+ if (disposed || pending !== null || view.isDestroyed) return;
159
+ pending = frame(() => {
160
+ pending = null;
161
+ refresh();
162
+ });
163
+ };
164
+
165
+ const unsubscribe = store.subscribe((snapshot) => {
166
+ if (snapshot.pageAwareness === false) store.setTarget(null);
167
+ schedule();
168
+ });
169
+
170
+ const onFocus = () => { focused = true; publish(); schedule(); };
171
+ const onBlur = () => { focused = false; store.setTarget(null); };
172
+ const onHide = () => { store.setTarget(null); };
173
+ const onCompositionEnd = () => { publish(); schedule(); };
174
+
175
+ view.dom.addEventListener("focus", onFocus);
176
+ view.dom.addEventListener("blur", onBlur);
177
+ view.dom.addEventListener("compositionend", onCompositionEnd);
178
+ doc.addEventListener("pagehide", onHide);
179
+ view.updateState(view.state.reconfigure({ plugins: [...view.state.plugins, plugin] }));
180
+ publish();
181
+ schedule();
182
+
183
+ return {
184
+ epoch,
185
+ refresh,
186
+ plugin,
187
+ get decorations() {
188
+ return awarenessKey.getState(view.state) ?? DecorationSet.empty;
189
+ },
190
+ get target() {
191
+ return store.target ?? null;
192
+ },
193
+ dispose() {
194
+ if (disposed) return;
195
+ disposed = true;
196
+ if (pending !== null) { cancel(pending); pending = null; }
197
+ unsubscribe();
198
+ view.dom.removeEventListener("focus", onFocus);
199
+ view.dom.removeEventListener("blur", onBlur);
200
+ view.dom.removeEventListener("compositionend", onCompositionEnd);
201
+ doc.removeEventListener("pagehide", onHide);
202
+ store.setTarget(null);
203
+ store.releaseTarget();
204
+ if (!view.isDestroyed) {
205
+ const plugins = view.state.plugins.filter((candidate) => candidate !== plugin);
206
+ view.updateState(view.state.reconfigure({ plugins }));
207
+ }
208
+ },
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Install page awareness on a mounted editor, if this host offers it: the
214
+ * document scope the host announced is what a target's epoch names, so peers on
215
+ * the same document agree without the tab guessing its identity.
216
+ */
217
+ export async function installPageAwareness({ binding, view, store = presenceStore, meta = null } = {}) {
218
+ if (!binding?.awareness || !view) return null;
219
+ const answer = meta ?? (await hostMeta());
220
+ const extensions = Array.isArray(answer?.extensions) ? answer.extensions : [];
221
+ store.setCapabilities({
222
+ supported: extensions.includes("presence"),
223
+ recordAwareness: extensions.includes("record-awareness-v1"),
224
+ pageAwareness: extensions.includes("page-awareness-v1"),
225
+ });
226
+ if (!extensions.includes("page-awareness-v1")) return null;
227
+ const epoch = answer.document?.awareness?.scope;
228
+ if (typeof epoch !== "string" || !epoch) return null;
229
+ return createPageAwareness({ binding, view, store, epoch });
230
+ }
@@ -6,6 +6,10 @@ import { model, state, view as pmView, commands, history } from "../vendor/prose
6
6
  import { schema } from "./schema.js";
7
7
  import { REMOTE, newId } from "./model.js";
8
8
  import { safeHref, safeSrc } from "./link.js";
9
+ import { flatten, runsOf, unflatten, unitsOf } from "./inline.js";
10
+ import { OPAQUE_MARK, opaqueMark } from "./format-codec.js";
11
+ import { NATIVE_NAMES, nativeAttrs } from "./format-native.js";
12
+ import { DEFAULTS, MARKS, OPAQUE, envelopeAttrs, envelopeRecord, marksAttrs, marksEntries } from "./projection.js";
9
13
 
10
14
  const alignOf = (a) => (a === "left" || a === "right" ? a : null);
11
15
 
@@ -340,9 +344,12 @@ export function indentBlocks(st, dispatch) {
340
344
  const tr = st.tr;
341
345
  let end = b;
342
346
  for (let k = a; k <= end; k++) end = Math.max(end, ents[k].subEnd);
347
+ let deep = a - 1;
343
348
  for (let k = a; k <= end && sameRun(ents[k], first); k++) {
344
349
  tr.setNodeMarkup(ents[k].at, null, { ...ents[k].node.attrs, indent: ents[k].node.attrs.indent + 1 });
350
+ deep = k;
345
351
  }
352
+ tr.setMeta(MOVE, moveIntent(ents, a, deep, ents[a].at, (e) => (e.i >= a && e.i <= deep ? { indent: e.node.attrs.indent + 1 } : null)));
346
353
  dispatch(tr);
347
354
  }
348
355
  return true;
@@ -352,10 +359,15 @@ export function indentBlocks(st, dispatch) {
352
359
  if (dispatch) {
353
360
  const { hi } = siblingSpan(ents, a, b);
354
361
  const tr = st.tr, from = parentOf(first.node), to = ents[s].node.attrs.id;
362
+ const adopted = [];
355
363
  for (let k = a; k <= hi; k++) {
356
- if (parentOf(ents[k].node) === from) tr.setNodeMarkup(ents[k].at, null, { ...ents[k].node.attrs, parent: to });
364
+ if (parentOf(ents[k].node) !== from) continue;
365
+ tr.setNodeMarkup(ents[k].at, null, { ...ents[k].node.attrs, parent: to });
366
+ adopted.push(ents[k].node.attrs.id);
357
367
  }
358
368
  if (!ents[s].node.attrs.open) tr.setNodeMarkup(ents[s].at, null, { ...ents[s].node.attrs, open: true });
369
+ const intent = moveIntent(ents, a, hi, ents[a].at, (e) => (e.i >= a && e.i <= hi && parentOf(e.node) === from ? { parent: to } : null));
370
+ tr.setMeta(MOVE, { ...intent, ids: adopted, attrs: ents[s].node.attrs.open ? intent.attrs : { ...intent.attrs, [to]: { open: true } } });
359
371
  dispatch(tr);
360
372
  }
361
373
  return true;
@@ -369,16 +381,22 @@ export function outdentBlocks(st, dispatch) {
369
381
  const first = ents[a];
370
382
  if (isList(first.node) && first.node.attrs.indent > 0) {
371
383
  if (dispatch) {
384
+ const { lo, hi } = siblingSpan(ents, a, b);
372
385
  const tr = st.tr;
386
+ const lowered = new Set();
373
387
  for (let k = a; k <= b;) {
374
388
  const e = ents[k];
375
389
  if (isList(e.node) && e.node.attrs.indent > 0) {
376
390
  for (let m = k; m <= e.subEnd; m++) {
377
- if (isList(ents[m].node)) tr.setNodeMarkup(ents[m].at, null, { ...ents[m].node.attrs, indent: ents[m].node.attrs.indent - 1 });
391
+ if (isList(ents[m].node)) {
392
+ tr.setNodeMarkup(ents[m].at, null, { ...ents[m].node.attrs, indent: ents[m].node.attrs.indent - 1 });
393
+ lowered.add(m);
394
+ }
378
395
  }
379
396
  }
380
397
  k = e.subEnd + 1;
381
398
  }
399
+ tr.setMeta(MOVE, moveIntent(ents, lo, hi, ents[lo].at, (e) => (lowered.has(e.i) ? { indent: e.node.attrs.indent - 1 } : null)));
382
400
  dispatch(tr);
383
401
  }
384
402
  return true;
@@ -395,6 +413,7 @@ export function outdentBlocks(st, dispatch) {
395
413
  if (hi === end) {
396
414
  const tr = st.tr;
397
415
  for (let k = lo; k <= hi; k++) { const c = attrs(ents[k]); if (c) tr.setNodeMarkup(ents[k].at, null, { ...ents[k].node.attrs, ...c }); }
416
+ tr.setMeta(MOVE, moveIntent(ents, lo, hi, ents[lo].at, attrs));
398
417
  dispatch(tr);
399
418
  } else dispatch(moveSpanTr(st, ents, lo, hi, ents[end].end, attrs).scrollIntoView());
400
419
  }
@@ -481,7 +500,13 @@ export function exitToggleOnEmpty(st, dispatch) {
481
500
  const i = $from.index(0);
482
501
  const ti = ents.findIndex((x) => x.node.attrs.id === t);
483
502
  if (ti < 0 || ents[ti].subEnd !== i) return false;
484
- if (dispatch) dispatch(st.tr.setNodeMarkup(ents[i].at, null, { ...$from.parent.attrs, parent: parentOf(ents[ti].node) }));
503
+ if (dispatch) {
504
+ const id = ents[i].node.attrs.id, to = parentOf(ents[ti].node);
505
+ const prev = i > 0 ? ents[i - 1].node : null;
506
+ const tr = st.tr.setNodeMarkup(ents[i].at, null, { ...$from.parent.attrs, parent: to });
507
+ tr.setMeta(MOVE, { ids: [id], afterId: prev ? prev.attrs.id : null, parent: parentOf(prev ?? ents[i].node), attrs: { [id]: { parent: to } } });
508
+ dispatch(tr);
509
+ }
485
510
  return true;
486
511
  }
487
512
 
@@ -1024,8 +1049,41 @@ export function flattenPastedHTML(html, doc = globalThis.document) {
1024
1049
  const numberAttr = (value, min = 0) => value !== null && value !== "" && Number.isInteger(+value) && +value >= min ? +value : null;
1025
1050
  const parentAttr = (dom) => dom.getAttribute("data-page-parent") || null;
1026
1051
 
1052
+ // A copied opaque mark is not one of the schema's marks, so the rules of the
1053
+ // clipboard parser resolve mark names through a view of the schema that holds
1054
+ // the internal one; every node a parse builds is the schema's own.
1055
+ const opaqueType = opaqueMark([]).type;
1056
+ const parseSchema = Object.create(schema, { marks: { value: { ...schema.marks, [OPAQUE_MARK]: opaqueType } } });
1057
+
1058
+ // The record of a pasted block envelope, or null when it is not one this
1059
+ // runtime may keep: a malformed, oversized or hostile envelope, one naming a
1060
+ // known block type, or one with no type of its own. A pasted element with no
1061
+ // usable record is plain text, never a record this runtime made up.
1062
+ const pastedRecord = (dom) => {
1063
+ let record;
1064
+ try { record = envelopeRecord(dom); } catch { return null; }
1065
+ if (!record || typeof record.t !== "string" || record.t === "opaque_block" || Object.hasOwn(DEFAULTS, record.t)) return null;
1066
+ return record;
1067
+ };
1068
+ const pastedOpaque = (dom) => {
1069
+ const record = pastedRecord(dom);
1070
+ if (!record) return false;
1071
+ return { raw: record, parent: parentAttr(dom) };
1072
+ };
1073
+ const pastedOpaqueContent = (dom) => {
1074
+ const record = pastedRecord(dom);
1075
+ if (!record || typeof record.c !== "string" || !record.c) return model.Fragment.empty;
1076
+ const units = unitsOf(record.m, record.c.length);
1077
+ return model.Fragment.from(unflatten(schema, record.c, units, schema.nodes.opaque_block));
1078
+ };
1079
+ const pastedOpaqueMarks = (dom) => {
1080
+ try { return { entries: marksEntries(dom.getAttribute(MARKS)) }; } catch { return false; }
1081
+ };
1082
+
1027
1083
  // The clipboard parser: the schema has toDOM only, so paste gets its own rules.
1028
- export const clipboardParser = new model.DOMParser(schema, [
1084
+ export const clipboardParser = new model.DOMParser(parseSchema, [
1085
+ { tag: `div[${OPAQUE}]`, node: "opaque_block", getAttrs: pastedOpaque, getContent: pastedOpaqueContent },
1086
+ { tag: `span[${MARKS}]`, mark: OPAQUE_MARK, getAttrs: pastedOpaqueMarks },
1029
1087
  { tag: "div[data-page-li]", node: "list_item", getAttrs: (d) => ({ kind: d.getAttribute("data-kind") || "bullet", indent: +d.getAttribute("data-indent") || 0, checked: d.getAttribute("data-checked") === "true", parent: parentAttr(d) }) },
1030
1088
  { tag: "div.page-li", node: "list_item", getAttrs: (d) => ({ kind: d.getAttribute("data-kind") || "bullet", indent: +d.getAttribute("data-indent") || 0, checked: d.getAttribute("data-checked") === "true", parent: parentAttr(d) }) },
1031
1089
  { tag: "div[data-page-toggle]", node: "toggle", getAttrs: (d) => ({ id: d.getAttribute("data-page-toggle"), open: d.getAttribute("data-open") === "true", parent: parentAttr(d) }) },
@@ -1061,6 +1119,10 @@ export const clipboardParser = new model.DOMParser(schema, [
1061
1119
  { tag: "del", mark: "strike" },
1062
1120
  { tag: "code", mark: "code" },
1063
1121
  { tag: "a[href]", mark: "link", getAttrs: (d) => { const href = safeHref(d.getAttribute("href")); return href ? { href } : false; } },
1122
+ { tag: "blockquote", node: "quote", getAttrs: (d) => ({ parent: parentAttr(d) }) },
1123
+ { tag: "u", mark: "underline" },
1124
+ { tag: "mark", mark: "highlight", getAttrs: (d) => nativeAttrs("highlight", { color: d.getAttribute("data-color") }) ?? false },
1125
+ { tag: "span[data-color]", mark: "text_color", getAttrs: (d) => nativeAttrs("text_color", { color: d.getAttribute("data-color") }) ?? false },
1064
1126
  ]);
1065
1127
 
1066
1128
  // Every pasted block arrives with no id and no pos (identityPlugin mints and
@@ -1115,7 +1177,24 @@ class ClipboardSerializer extends model.DOMSerializer {
1115
1177
  }
1116
1178
  }
1117
1179
  const base = model.DOMSerializer.fromSchema(schema);
1118
- export const clipboardSerializer = new ClipboardSerializer(base.nodes, base.marks);
1180
+
1181
+ // A copied block this runtime has no schema for keeps its record in the
1182
+ // projection's block envelope, with its own text as the readable fallback, and
1183
+ // a copied opaque mark keeps its descriptors in the marks envelope. The record
1184
+ // is the one the saved article keeps: its own type, every field it carried, and
1185
+ // its text with its marks.
1186
+ const opaqueRecord = (node) => {
1187
+ const { t, ...rest } = node.attrs.raw ?? {};
1188
+ const { c, m } = flatten(node.content);
1189
+ if (!c) return { t, ...rest };
1190
+ return { t, ...rest, c, ...(m.some(Boolean) ? { m: runsOf(m) } : {}) };
1191
+ };
1192
+ const opaqueNode = (node) => ["div", { "data-type": "newer-format", ...envelopeAttrs(opaqueRecord(node)) }, 0];
1193
+ const opaqueMarks = (mark) => ["span", marksAttrs(mark.attrs.entries), 0];
1194
+ const clipboardNodes = { ...base.nodes, opaque_block: opaqueNode };
1195
+ const clipboardMarks = { ...base.marks, [OPAQUE_MARK]: opaqueMarks };
1196
+
1197
+ export const clipboardSerializer = new ClipboardSerializer(clipboardNodes, clipboardMarks);
1119
1198
 
1120
1199
  // Several blocks pasted into a closed toggle's summary join its body, so the
1121
1200
  // toggle opens first instead of hiding what was just pasted.
@@ -1128,10 +1207,38 @@ function pasteIntoClosedSummary(view, _event, slice) {
1128
1207
  return true;
1129
1208
  }
1130
1209
 
1210
+ // The four formats pasted in from elsewhere are floor-1 content too: a slice
1211
+ // that holds one asks the gate before it is inserted, and text waits in the
1212
+ // clipboard rather than being dropped when the answer is a refusal.
1213
+ function writesFormat(slice) {
1214
+ let found = false;
1215
+ slice?.content?.descendants((node) => {
1216
+ if (found) return false;
1217
+ if (node.type === N.quote) found = true;
1218
+ else if (node.isText && node.marks.some((mark) => NATIVE_NAMES.has(mark.type.name))) found = true;
1219
+ return !found;
1220
+ });
1221
+ return found;
1222
+ }
1223
+
1224
+ function pasteFormat(view, event, slice) {
1225
+ const from = view.state.selection.from, to = view.state.selection.to;
1226
+ const plain = () => {
1227
+ const text = slice.content.textBetween(0, slice.content.size, "\n", " ");
1228
+ if (!view.isDestroyed && text) view.dispatch(view.state.tr.insertText(text, from, to));
1229
+ return false;
1230
+ };
1231
+ import("./format-native-commands.js").then(({ pasteFormats }) => pasteFormats(view, slice, from, to), plain);
1232
+ return true;
1233
+ }
1234
+
1131
1235
  export function pastePlugin() {
1132
1236
  return new Plugin({
1133
1237
  props: {
1134
- handlePaste: pasteIntoClosedSummary,
1238
+ handlePaste(view, event, slice) {
1239
+ if (writesFormat(slice)) return pasteFormat(view, event, slice);
1240
+ return pasteIntoClosedSummary(view, event, slice);
1241
+ },
1135
1242
  clipboardSerializer,
1136
1243
  transformPastedHTML: (html) => flattenPastedHTML(html),
1137
1244
  clipboardParser,
@@ -8,6 +8,7 @@ import { schema } from "./schema.js";
8
8
  import { outline, siblingSpan, touchedBlocks, BlockSelection, STEP } from "./blocks.js";
9
9
  import { keysBetween, newId, orderedIds, REMOTE } from "./model.js";
10
10
  import { announce } from "./announce.js";
11
+ import { rectKey } from "./table-rect-key.js";
11
12
 
12
13
  const { Selection, TextSelection, NodeSelection, Plugin, PluginKey } = state;
13
14
  const N = schema.nodes;
@@ -145,14 +146,29 @@ export function exitToText(st, dispatch) {
145
146
  return true;
146
147
  }
147
148
 
149
+ // The top-level table holding both ends of the selection, as block boundaries.
150
+ const tableAround = ({ $from, $to }) => {
151
+ if ($from.depth < 2 || $to.depth < 2) return null;
152
+ const node = $from.node(1);
153
+ return node.type.name === "table" && $to.node(1) === node ? { from: $from.before(1), to: $from.after(1) } : null;
154
+ };
155
+
156
+ // Mod-a widens one step at a time: the text of the block or table cell, then the table
157
+ // around a cell, then every block.
148
158
  export function selectAllBlocks(st, dispatch) {
149
- const { $from, $to } = st.selection;
150
- const one = $from.depth === 1 && $from.sameParent($to);
151
- const whole = one && $from.parentOffset === 0 && $to.parentOffset === $to.parent.content.size;
152
- if (one && !whole && $from.parent.content.size) {
159
+ const sel = st.selection;
160
+ const { $from, $to } = sel;
161
+ const inText = $from.parent.isTextblock && $from.sameParent($to) && !rectKey.getState(st);
162
+ const whole = inText && $from.parentOffset === 0 && $to.parentOffset === $to.parent.content.size;
163
+ if (inText && !whole && $from.parent.content.size) {
153
164
  if (dispatch) dispatch(st.tr.setSelection(TextSelection.create(st.doc, $from.start(), $from.end())));
154
165
  return true;
155
166
  }
167
+ const table = tableAround(sel);
168
+ if (table && !(sel instanceof BlockSelection && sel.from === table.from && sel.to === table.to)) {
169
+ if (dispatch) dispatch(st.tr.setSelection(new BlockSelection(st.doc.resolve(table.from), st.doc.resolve(table.to))));
170
+ return true;
171
+ }
156
172
  if (dispatch) dispatch(st.tr.setSelection(new BlockSelection(st.doc.resolve(0), st.doc.resolve(st.doc.content.size))));
157
173
  return true;
158
174
  }
@@ -0,0 +1,13 @@
1
+ // The small board pieces the checklist and the board view and panel share, so
2
+ // the board's own view part can load without them.
3
+ export function el(doc, tag, cls, attrs = {}, text = null) {
4
+ const node = doc.createElement(tag);
5
+ if (cls) node.className = cls;
6
+ for (const [key, value] of Object.entries(attrs)) if (value !== null && value !== undefined && value !== false) node.setAttribute(key, value === true ? "" : String(value));
7
+ if (text !== null) node.textContent = text;
8
+ return node;
9
+ }
10
+
11
+ export const initialOf = (name) => ([...String(name).trim()][0] ?? "?").toUpperCase();
12
+
13
+ export const cardFace = (doc, name) => el(doc, "span", "page-board-face", { "aria-hidden": "true" }, initialOf(name));
@@ -1,16 +1,16 @@
1
- import { state } from "../vendor/prosemirror.vendor.js";
2
1
  import { schema } from "./schema.js";
3
2
  import { announce } from "./announce.js";
4
- import { BOARD_ICONS, el, iconButton, boardOf, cardFace, dueLabel, dueState, todayKey, placeAfter } from "./board-view.js";
3
+ import { BOARD_ICONS, iconButton, boardOf, dueLabel, dueState, todayKey, placeAfter } from "./board-view.js";
4
+ import { el, cardFace } from "./board-face.js";
5
+ import { cardPanels } from "./board-views.js";
5
6
 
6
7
  const NARROW = "(max-width: 1039.98px)";
7
8
  const isTyping = (target) => /^(INPUT|TEXTAREA|SELECT)$/.test(target?.tagName ?? "");
8
- const panels = new WeakMap();
9
9
  let count = 0;
10
10
 
11
11
  export function cardPanel(view) {
12
- if (!panels.has(view)) panels.set(view, new CardPanel(view));
13
- return panels.get(view);
12
+ if (!cardPanels.has(view)) cardPanels.set(view, new CardPanel(view));
13
+ return cardPanels.get(view);
14
14
  }
15
15
 
16
16
  export class CardPanel {
@@ -339,15 +339,3 @@ export class CardPanel {
339
339
  }
340
340
  }
341
341
  }
342
-
343
- export function boardPlugin() {
344
- return new state.Plugin({
345
- view: (view) => ({
346
- update: () => panels.get(view)?.sync(),
347
- destroy: () => {
348
- panels.get(view)?.hide(false, false);
349
- panels.delete(view);
350
- },
351
- }),
352
- });
353
- }
@@ -21,7 +21,6 @@ export const BOARD_CSS = `
21
21
  .page-board-meta { display: flex; align-items: center; gap: 8px; margin-top: 8px; color: var(--page-ink-soft); font-size: 13px; line-height: 20px; }
22
22
  .page-board-owner { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
23
23
  .page-board-owner-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
24
- .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; }
25
24
  .page-board-due { flex: none; display: inline-flex; align-items: center; gap: 6px; margin-inline-start: auto; white-space: nowrap; font-variant-numeric: tabular-nums; }
26
25
  .page-board-due[data-due="today"] { color: var(--page-ink); font-weight: 500; }
27
26
  .page-board-due[data-due="overdue"] { color: var(--page-ink); }
@@ -95,14 +94,6 @@ export const BOARD_CSS = `
95
94
  .page-board-control .page-board-due + .page-board-due-note { margin-inline-start: auto; }
96
95
  .page-board-control .page-board-due-note + .i { margin-inline-start: 0; }
97
96
  .page-board-date { border: 1px solid var(--page-pop-line); cursor: text; }
98
- .page-board-owners { display: flex; flex-direction: column; gap: 6px; min-width: 0; padding-inline: 8px; }
99
- .page-board-people { display: flex; flex-wrap: wrap; gap: 4px; }
100
- .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); }
101
- .page-board-person.is-none { padding-inline-start: 10px; color: var(--page-pop-muted); }
102
- .page-board-person:hover { background: var(--page-pop-hover); }
103
- .page-board-person[aria-pressed="true"] { border-color: var(--page-accent); background: var(--page-selection); color: var(--page-pop-ink); }
104
- .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; }
105
- .page-board-owner-input::placeholder { color: var(--page-ink-faint); }
106
97
  .page-board-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 20px; }
107
98
  .page-board-delete { margin-inline-start: auto; border-color: transparent; color: var(--page-pop-muted); }
108
99
  .page-board-delete:hover { background: var(--page-pop-hover); color: var(--page-danger); }
@@ -135,12 +126,10 @@ export const BOARD_CSS = `
135
126
  .page-board-rename { height: 44px; margin-block: -12px; font-size: 16px; }
136
127
  .page-board-panel { border-width: 1px 0 0; }
137
128
  .page-board-panel-title { margin-top: -8px; padding-block: 8px; }
138
- .page-board-control, .page-board-owner-input { font-size: 16px; }
129
+ .page-board-control { font-size: 16px; }
139
130
  .page-board-label { font-size: 15px; }
140
- .page-board-person { height: 44px; border-radius: 22px; }
141
- .page-board-owner-input { height: 44px; }
142
131
  }
143
132
  @media (prefers-reduced-motion: reduce) {
144
- .page-board-icon, .page-board-card, .page-board-add, .page-board-addcol, .page-board-btn, .page-board-control, .page-board-person { transition: none; }
133
+ .page-board-icon, .page-board-card, .page-board-add, .page-board-addcol, .page-board-btn, .page-board-control { transition: none; }
145
134
  }
146
135
  `;
@@ -2,6 +2,7 @@ import { schema } from "./schema.js";
2
2
  import { orderedIds } from "./model.js";
3
3
  import { announce } from "./announce.js";
4
4
  import { BoardDrag } from "./board-drag.js";
5
+ import { el, cardFace } from "./board-face.js";
5
6
 
6
7
  const N = schema.nodes;
7
8
  const boardViews = new WeakMap();
@@ -24,14 +25,6 @@ export const BOARD_ICONS = {
24
25
  restore: svg('<path d="M3.5 8a4.5 4.5 0 1 0 1.32-3.18M3.5 2.75v2.5H6"/>'),
25
26
  };
26
27
 
27
- export function el(doc, tag, cls, attrs = {}, text = null) {
28
- const node = doc.createElement(tag);
29
- if (cls) node.className = cls;
30
- for (const [key, value] of Object.entries(attrs)) if (value !== null && value !== undefined && value !== false) node.setAttribute(key, value === true ? "" : String(value));
31
- if (text !== null) node.textContent = text;
32
- return node;
33
- }
34
-
35
28
  export function iconButton(doc, cls, icon, label, attrs = {}) {
36
29
  const button = el(doc, "button", cls, { type: "button", ...attrs });
37
30
  button.innerHTML = BOARD_ICONS[icon];
@@ -61,7 +54,6 @@ export function dueLabel(due, today = todayKey(), long = false) {
61
54
  const sameYear = due.slice(0, 4) === today.slice(0, 4);
62
55
  return new Date(`${due}T00:00:00Z`).toLocaleDateString("en-US", { ...(long ? { weekday: "short" } : {}), month: "short", day: "numeric", ...(sameYear ? {} : { year: "numeric" }), timeZone: "UTC" });
63
56
  }
64
- export const initialOf = (name) => ([...String(name).trim()][0] ?? "?").toUpperCase();
65
57
  export const placeAfter = (ids, index) => (index >= 2 ? ids[index - 2] : null);
66
58
  const countOf = (doc, n) => el(doc, "span", "page-board-count", { "aria-label": `${n} ${n === 1 ? "card" : "cards"}` }, String(n));
67
59
  const isTyping = (target) => /^(INPUT|TEXTAREA|SELECT)$/.test(target?.tagName ?? "");
@@ -75,8 +67,6 @@ function dueBadge(doc, due) {
75
67
  return badge;
76
68
  }
77
69
 
78
- export const cardFace = (doc, name) => el(doc, "span", "page-board-face", { "aria-hidden": "true" }, initialOf(name));
79
-
80
70
  export class BoardView {
81
71
  constructor(node, view, getPos, { commands = null, undo = null, redo = null, panel = null } = {}) {
82
72
  this.node = node;
@@ -0,0 +1,3 @@
1
+ // The card panels a board part can reach, filled by the board view part when it
2
+ // loads. A board without a panel does nothing on every change.
3
+ export const cardPanels = new WeakMap();
package/src/page/board.js CHANGED
@@ -20,10 +20,10 @@ export function makeBoard(names = BOARD_COLUMNS) {
20
20
  // The board takes the place of an empty line, otherwise it goes after the
21
21
  // line's whole subtree at the line's own level. An empty paragraph follows it
22
22
  // and holds the caret, so typing goes on below the board.
23
- export function insertBoardNode(st, dispatch, board = makeBoard(), remove = null) {
23
+ export function insertBoardNode(st, dispatch, board = makeBoard(), remove = null, index = null) {
24
24
  const tr = st.tr;
25
25
  if (remove) tr.delete(remove.from, remove.to);
26
- const index = tr.doc.resolve(tr.mapping.map(st.selection.head)).index(0);
26
+ index ??= tr.doc.resolve(tr.mapping.map(st.selection.head)).index(0);
27
27
  const ents = outline(tr.doc);
28
28
  const line = tr.doc.child(index);
29
29
  const empty = line.isTextblock && line.content.size === 0;
@@ -0,0 +1,26 @@
1
+ // The page's boot code in one module: the page code and the chrome, recovery and title
2
+ // code beside it arrive through this file's static graph, and the plugin fetches it with
3
+ // one dynamic import. Loading the parts together is what keeps a module they share from
4
+ // being linked by two dynamic imports at once, and it is one download for a boot that
5
+ // needs them all. The parts a page chooses from its own tag (a table, a board, media, a
6
+ // tool) stay separate dynamic imports, issued once this has resolved.
7
+ //
8
+ // The editor is a download of its own, and it starts here rather than after this file
9
+ // so its fetch runs beside the page code's. It is not awaited: the page code, and the
10
+ // room participant the binding registers, must be ready while the editor is still on
11
+ // the wire. Its static graph is linked by the time this body runs, so the vendor it
12
+ // shares with the page code is already in cache when its own link begins.
13
+ export { installAdoption, ADOPTION_TIMEOUT_MS } from "./adopt-boot.js";
14
+ export { classifyFile, readBack } from "./adopt.js";
15
+ export { mergePageModels } from "./merge.js";
16
+ export { canon } from "./projection.js";
17
+ export { renderFromTags } from "./save-projection.js";
18
+ export { blocksFromYValue } from "./ybind/decode.js";
19
+ export { createYPageBinding } from "./ybind/binding.js";
20
+ export { yValueOf } from "./ybind/provider.js";
21
+ export { seedPageFromArticle } from "./ybind/migrate.js";
22
+ export { holdSlashRange, heldSlashRange } from "./chrome.js";
23
+ export { createRecoverList, focusBlock } from "./recover.js";
24
+ export { observeDocumentTitle } from "./document-title-runtime.js";
25
+ export { createFeatureLoader } from "./feature-loader.js";
26
+ import("./editor.js").catch(() => null);