@panphora/clayjs 1.15.2 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/README.md +4 -5
  2. package/THIRD-PARTY-NOTICES.md +16 -0
  3. package/compat/highlight-11.9.0.min.js +1213 -0
  4. package/compat/highlight-LICENSE +29 -0
  5. package/compat/journal-assets.lock.json +22 -0
  6. package/compat/overtype-2.6.1.min.js +116 -0
  7. package/compat/overtype-LICENSE +21 -0
  8. package/dist/all.js +90 -0
  9. package/dist/clay-dom.js +84 -0
  10. package/dist/clay-events.js +103 -0
  11. package/dist/clay-internals.js +85 -0
  12. package/dist/clay-options.js +93 -0
  13. package/dist/clay-router.js +98 -0
  14. package/dist/clay-ui.js +95 -0
  15. package/dist/clay-utils.js +84 -0
  16. package/dist/clay.js +146 -0
  17. package/dist/clay.standalone.js +83090 -58706
  18. package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
  19. package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
  20. package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +1 -0
  21. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +1 -0
  22. package/dist/m/c-2AZDZWCK.js +1 -0
  23. package/dist/m/c-2JL6E75N.js +1 -0
  24. package/dist/m/c-2M73252O.js +1 -0
  25. package/dist/m/c-2SPVQSHC.js +1 -0
  26. package/dist/m/c-33HUYEDI.js +1 -0
  27. package/dist/m/c-33JJ6CBX.js +2 -0
  28. package/dist/m/c-35NBGMZB.js +1 -0
  29. package/dist/m/c-3GJ4SDE2.js +1 -0
  30. package/dist/m/c-3IQRECYS.js +1 -0
  31. package/dist/m/c-3OWDMNJR.js +2 -0
  32. package/dist/m/c-46XYM55L.js +1 -0
  33. package/dist/m/c-4B6H4G5V.js +1 -0
  34. package/dist/m/c-4DRMS4QG.js +9 -0
  35. package/dist/m/c-4FFHZM32.js +1 -0
  36. package/dist/m/c-4JZDZFF6.js +1 -0
  37. package/dist/m/c-4L3BTFRK.js +1 -0
  38. package/dist/m/c-4Q2XTQ2N.js +2 -0
  39. package/dist/m/c-4WKFKEKF.js +1 -0
  40. package/dist/m/c-4YSCRICO.js +1 -0
  41. package/dist/m/c-54RCDCBU.js +1 -0
  42. package/dist/m/c-5M4YEKCX.js +4 -0
  43. package/dist/m/c-5R4PTRG2.js +1 -0
  44. package/dist/m/c-5V3MOVJT.js +1 -0
  45. package/dist/m/c-5VYOWDTI.js +1 -0
  46. package/dist/m/c-5Y6TNX5W.js +34 -0
  47. package/dist/m/c-5YIHJ3TJ.js +1 -0
  48. package/dist/m/c-66O32MV3.js +28 -0
  49. package/dist/m/c-6HMB24EV.js +1 -0
  50. package/dist/m/c-7DJDKPGL.js +1 -0
  51. package/dist/m/c-7O6AFR65.js +1 -0
  52. package/dist/m/c-7SWMYKAI.js +1 -0
  53. package/dist/m/c-7VGZ7RU5.js +1 -0
  54. package/dist/m/c-A7LS5UZW.js +1 -0
  55. package/dist/m/c-ANQTJ3B5.js +2 -0
  56. package/dist/m/c-AQLFEATB.js +1 -0
  57. package/dist/m/c-AYVOREBE.js +1 -0
  58. package/dist/m/c-B2ZXPOS3.js +3 -0
  59. package/dist/m/c-BBVGTQG7.js +1 -0
  60. package/dist/m/c-BMIZKMPV.js +1 -0
  61. package/dist/m/c-BTDI4Y23.js +1 -0
  62. package/dist/m/c-BWFVSBDF.js +1 -0
  63. package/dist/m/c-BYTKL6UX.js +1 -0
  64. package/dist/m/c-CFHW5AMY.js +6 -0
  65. package/dist/m/c-CJZSLMVQ.js +1 -0
  66. package/dist/m/c-CYWLPPUS.js +1 -0
  67. package/dist/m/c-CZUSOB7V.js +1 -0
  68. package/dist/m/c-DBE6ORSI.js +1 -0
  69. package/dist/m/c-DCFSEJZG.js +1 -0
  70. package/dist/m/c-DNZ5IFM6.js +1 -0
  71. package/dist/m/c-EDHUR4MX.js +1 -0
  72. package/dist/m/c-EJTGYSLN.js +1 -0
  73. package/dist/m/c-EY6UOM54.js +1 -0
  74. package/dist/m/c-F3CKJTJS.js +1 -0
  75. package/dist/m/c-F7Y6FLPY.js +1 -0
  76. package/dist/m/c-FA4DZVTC.js +1 -0
  77. package/dist/m/c-FILLTJA7.js +1 -0
  78. package/dist/m/c-G2BUZITR.js +1 -0
  79. package/dist/m/c-GB3JTV6E.js +1 -0
  80. package/dist/m/c-GBGWXTX4.js +1 -0
  81. package/dist/m/c-GHBCCVG4.js +1 -0
  82. package/dist/m/c-GJAEGGXY.js +1 -0
  83. package/dist/m/c-GOWBDM6J.js +1 -0
  84. package/dist/m/c-GQ6F6IDK.js +1 -0
  85. package/dist/m/c-H4LG442Q.js +1 -0
  86. package/dist/m/c-HA6INT6J.js +1 -0
  87. package/dist/m/c-HPURYVZS.js +2 -0
  88. package/dist/m/c-HRDCD3WN.js +67 -0
  89. package/dist/m/c-HT33EQRN.js +1 -0
  90. package/dist/m/c-HVYXIS72.js +1 -0
  91. package/dist/m/c-IB52R237.js +1 -0
  92. package/dist/m/c-IJUK2GPW.js +1 -0
  93. package/dist/m/c-IKWNE4R4.js +1 -0
  94. package/dist/m/c-IU3IYEU6.js +1 -0
  95. package/dist/m/c-IZ2EBRAJ.js +1 -0
  96. package/dist/m/c-J7CA54IP.js +1 -0
  97. package/dist/m/c-JAX2667S.js +6 -0
  98. package/dist/m/c-KI7M4AY3.js +1 -0
  99. package/dist/m/c-KIPQJX4B.js +1 -0
  100. package/dist/m/c-KKWLDOPM.js +1 -0
  101. package/dist/m/c-KR4UW4KW.js +1 -0
  102. package/dist/m/c-KY3VJGUA.js +8 -0
  103. package/dist/m/c-L4WQSC27.js +1 -0
  104. package/dist/m/c-LFPHCXY3.js +1 -0
  105. package/dist/m/c-LR4CGEPT.js +91 -0
  106. package/dist/m/c-LRPUO2MX.js +1 -0
  107. package/dist/m/c-LTGMXXKO.js +1 -0
  108. package/dist/m/c-LXENEBV7.js +1 -0
  109. package/dist/m/c-LZJPNR2W.js +1 -0
  110. package/dist/m/c-MMUIUUZG.js +2 -0
  111. package/dist/m/c-MTQHWQC3.js +1 -0
  112. package/dist/m/c-N326R2JD.js +1 -0
  113. package/dist/m/c-N7JPUPGC.js +1 -0
  114. package/dist/m/c-NMJRXKPC.js +8 -0
  115. package/dist/m/c-NRPJEVJS.js +1 -0
  116. package/dist/m/c-NUHP2XDB.js +1 -0
  117. package/dist/m/c-NZMNXVF6.js +1 -0
  118. package/dist/m/c-OCSZ4QGN.js +1 -0
  119. package/dist/m/c-OIBUTDXI.js +1 -0
  120. package/dist/m/c-OIDRZKAF.js +1 -0
  121. package/dist/m/c-OYTFP5QN.js +1 -0
  122. package/dist/m/c-P7KNDG34.js +1 -0
  123. package/dist/m/c-PPOSCAQD.js +1 -0
  124. package/dist/m/c-PQ5LK6QE.js +993 -0
  125. package/dist/m/c-PZ4XEPUP.js +2 -0
  126. package/dist/m/c-QHIWXHRO.js +1 -0
  127. package/dist/m/c-QHWJ5ZIJ.js +1 -0
  128. package/dist/m/c-QIX5BJYN.js +1 -0
  129. package/dist/m/c-QNKREOL6.js +1 -0
  130. package/dist/m/c-QQHEX6HA.js +1 -0
  131. package/dist/m/c-R7RMANJI.js +9 -0
  132. package/dist/m/c-RI4NT75Y.js +1 -0
  133. package/dist/m/c-RM7J7BV2.js +1 -0
  134. package/dist/m/c-RUJKSUQW.js +1 -0
  135. package/dist/m/c-RVMWGXEJ.js +1 -0
  136. package/dist/m/c-S7H2DRCB.js +1 -0
  137. package/dist/m/c-S7LK4KUE.js +1 -0
  138. package/dist/m/c-SGCCC4FW.js +25 -0
  139. package/dist/m/c-SGIA4IEQ.js +188 -0
  140. package/dist/m/c-SWRJFCNN.js +1 -0
  141. package/dist/m/c-T4VO4OCB.js +1 -0
  142. package/dist/m/c-TCCA5BZN.js +2 -0
  143. package/dist/m/c-TQEMVJCS.js +1 -0
  144. package/dist/m/c-TQZ2JYHD.js +1 -0
  145. package/dist/m/c-TR6RMDQ7.js +3 -0
  146. package/dist/m/c-TRS6TM6O.js +1 -0
  147. package/dist/m/c-UGHH4MB3.js +1 -0
  148. package/dist/m/c-UKHTLWUW.js +1 -0
  149. package/dist/m/c-UL2YUY52.js +1 -0
  150. package/dist/m/c-UNN7LWCT.js +1 -0
  151. package/dist/m/c-UXBMRDZB.js +235 -0
  152. package/dist/m/c-V7PZZRG7.js +1 -0
  153. package/dist/m/c-VAK7SG37.js +1 -0
  154. package/dist/m/c-VDSS4K3Z.js +1 -0
  155. package/dist/m/c-VFX45IZQ.js +1 -0
  156. package/dist/m/c-VHNSBD2M.js +1 -0
  157. package/dist/m/c-VKEDWYDF.js +1 -0
  158. package/dist/m/c-VL52ADDR.js +1 -0
  159. package/dist/m/c-WN5AY3OZ.js +1 -0
  160. package/dist/m/c-WNYBLIOZ.js +1 -0
  161. package/dist/m/c-WRBKEJD4.js +1 -0
  162. package/dist/m/c-WWDBYXLK.js +1 -0
  163. package/dist/m/c-WYFDLITL.js +1 -0
  164. package/dist/m/c-XN6MBKI6.js +67 -0
  165. package/dist/m/c-XNNW2CWK.js +1 -0
  166. package/dist/m/c-XPELB6E5.js +1 -0
  167. package/dist/m/c-XQXZAO7N.js +1 -0
  168. package/dist/m/c-Y6USGTS5.js +5 -0
  169. package/dist/m/c-YDH7Q3NY.js +1 -0
  170. package/dist/m/c-YG2NJYS6.js +135 -0
  171. package/dist/m/c-Z36J63PP.js +1 -0
  172. package/dist/m/c-Z4VRC7GU.js +7 -0
  173. package/dist/m/c-Z6K52OLP.js +19 -0
  174. package/dist/m/c-ZDY2MDAA.js +21 -0
  175. package/dist/m/c-ZE46QDIH.js +1 -0
  176. package/dist/m/c-ZUCOKOHT.js +1 -0
  177. package/dist/m/core/admin-attrs-VGNLAUDK.js +1 -0
  178. package/dist/m/core/autosave-E4PEFPZ6.js +1 -0
  179. package/dist/m/core/conflict-notice-VQ6Y64PH.js +1 -0
  180. package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
  181. package/dist/m/core/host-permission-XN3K2IW3.js +1 -0
  182. package/dist/m/core/is-edit-mode-JXHDVGUL.js +1 -0
  183. package/dist/m/core/persist-HKHA66IY.js +3 -0
  184. package/dist/m/core/recovery-TBBSICE2.js +1 -0
  185. package/dist/m/core/save-WUCHTQBE.js +1 -0
  186. package/dist/m/core/save-core-FJVHT5M3.js +1 -0
  187. package/dist/m/core/snapshot-F3SWDZMM.js +1 -0
  188. package/dist/m/core/unsaved-warning-STKBHE6G.js +1 -0
  189. package/dist/m/dom/all-APS5ZSKP.js +1 -0
  190. package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
  191. package/dist/m/events/index-23DPTUT4.js +1 -0
  192. package/dist/m/internals/index-WOVAP3RU.js +1 -0
  193. package/dist/m/lib/cache-bust-CGWT6ZJI.js +1 -0
  194. package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
  195. package/dist/m/loader-HXX6YNH5.js +1 -0
  196. package/dist/m/manifest-F22DEDAC.js +1 -0
  197. package/dist/m/manifest-edit-C3C7376B.js +1 -0
  198. package/dist/m/manifest.json +3491 -0
  199. package/dist/m/options/options-2LH76JQ3.js +1 -0
  200. package/dist/m/page/editor-XUTB2HB2.js +189 -0
  201. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +6 -0
  202. package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
  203. package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
  204. package/dist/m/plugins/demo-ZIBFAA3R.js +1 -0
  205. package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
  206. package/dist/m/plugins/indicator-PV4YW2ID.js +1 -0
  207. package/dist/m/plugins/live-NUTTXSBS.js +1 -0
  208. package/dist/m/plugins/menu-HCKO3DSP.js +138 -0
  209. package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
  210. package/dist/m/plugins/page-KSUH7G6J.js +2 -0
  211. package/dist/m/plugins/page-view-W73OW7WI.js +1 -0
  212. package/dist/m/plugins/people-AJJ32YCG.js +1 -0
  213. package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
  214. package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
  215. package/dist/m/plugins/richclay-RC3L2F6X.js +517 -0
  216. package/dist/m/plugins/richclay-RC3L2F6X.js.LEGAL.txt +1 -0
  217. package/dist/m/plugins/sortable-YRMQX3ZU.js +1 -0
  218. package/dist/m/plugins/source-KHK5UUSK.js +1 -0
  219. package/dist/m/plugins/undo-OF2L3CXU.js +1 -0
  220. package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
  221. package/dist/m/plugins/upgrade-ZNYODP5Y.js +1 -0
  222. package/dist/m/plugins/upload-XSGNILUY.js +1 -0
  223. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +21 -0
  224. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +1 -0
  225. package/dist/m/plugins/wire-ZWVWKF76.js +1 -0
  226. package/dist/m/reader-core-ILWLXVMP.js +68 -0
  227. package/dist/m/route/route-KOCZSEGN.js +1 -0
  228. package/dist/m/sync/live-sync-4AUVXVJU.js +1 -0
  229. package/dist/m/ui/index-EVHKMKBL.js +1 -0
  230. package/dist/m/utils/index-4TJCVLT7.js +1 -0
  231. package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
  232. package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
  233. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
  234. package/dist/offline-manifest.json +3800 -0
  235. package/dist/page/themes/light-1.css +406 -0
  236. package/dist/server/core.cjs +23662 -22860
  237. package/dist/server/offline-runtime.cjs +4954 -0
  238. package/dist/server/offline-runtime.mjs +4923 -0
  239. package/dist/server/rooms.mjs +9 -0
  240. package/docs/guide.md +1078 -669
  241. package/entries/all.js +2 -0
  242. package/entries/clay-data.js +1 -1
  243. package/entries/clay-dom.js +2 -0
  244. package/entries/clay-events.js +2 -0
  245. package/entries/clay-internals.js +2 -0
  246. package/entries/clay-options.js +2 -0
  247. package/entries/clay-router.js +2 -0
  248. package/entries/clay-ui.js +2 -0
  249. package/entries/clay-utils.js +2 -0
  250. package/entries/clay.js +2 -0
  251. package/entries/sap.js +6 -6
  252. package/fonts/ibm-plex-mono-LICENSE +93 -0
  253. package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
  254. package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
  255. package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  256. package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  257. package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  258. package/fonts/ibm-plex-sans-LICENSE +93 -0
  259. package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  260. package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  261. package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  262. package/fonts/ibm-plex.css +71 -0
  263. package/fonts/inter-LICENSE +93 -0
  264. package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
  265. package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
  266. package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
  267. package/fonts/inter-greek-wght-normal.woff2 +0 -0
  268. package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  269. package/fonts/inter-latin-wght-normal.woff2 +0 -0
  270. package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
  271. package/fonts/inter.css +62 -0
  272. package/fonts/newsreader-LICENSE +93 -0
  273. package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
  274. package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  275. package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
  276. package/fonts/newsreader.css +26 -0
  277. package/fonts/work-sans-LICENSE +93 -0
  278. package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
  279. package/fonts/work-sans.css +8 -0
  280. package/package.json +20 -3
  281. package/packed-contract.json +1 -0
  282. package/src/attrs/add-from.js +27 -4
  283. package/src/attrs/save-freeze.js +7 -52
  284. package/src/core/admin-attrs.js +119 -12
  285. package/src/core/admin-contenteditable.js +12 -10
  286. package/src/core/admin-inputs.js +14 -15
  287. package/src/core/admin-onclick.js +2 -0
  288. package/src/core/autosave.js +74 -21
  289. package/src/core/conflict-download.js +22 -0
  290. package/src/core/conflict-notice-ui.js +1173 -0
  291. package/src/core/conflict-notice.js +140 -697
  292. package/src/core/edit-mode.js +147 -14
  293. package/src/core/etag.js +30 -12
  294. package/src/core/host-meta.js +72 -8
  295. package/src/core/host-permission.js +29 -0
  296. package/src/core/is-edit-mode.js +145 -12
  297. package/src/core/mode-key.js +125 -0
  298. package/src/core/mode-lifecycle.js +544 -0
  299. package/src/core/mode-receipt.js +30 -0
  300. package/src/core/mode-refusal.js +13 -0
  301. package/src/core/mode-resources.js +52 -0
  302. package/src/core/mode-support.js +82 -0
  303. package/src/core/mode-switch.js +259 -0
  304. package/src/core/mode-transition.js +112 -0
  305. package/src/core/page-data-write.js +305 -0
  306. package/src/core/page-data.js +26 -227
  307. package/src/core/page-format-wire.js +4 -0
  308. package/src/core/page-format.js +58 -0
  309. package/src/core/persist.js +37 -9
  310. package/src/core/preserve-authored.js +114 -0
  311. package/src/core/recovery.js +772 -0
  312. package/src/core/runtime-url.js +105 -0
  313. package/src/core/save-core.js +109 -12
  314. package/src/core/save-replace.js +72 -0
  315. package/src/core/save-size-notice.js +134 -0
  316. package/src/core/save-size.js +140 -0
  317. package/src/core/save.js +805 -118
  318. package/src/core/snapshot.js +121 -11
  319. package/src/core/source-map.js +157 -9
  320. package/src/core/unsaved-warning.js +14 -4
  321. package/src/core/upgrade-run.js +408 -0
  322. package/src/core/version.js +18 -0
  323. package/src/lib/assets.js +30 -0
  324. package/src/lib/autosave-debug-gate.js +50 -0
  325. package/src/lib/autosave-scheduler.js +141 -0
  326. package/src/lib/boot-imports.js +8 -0
  327. package/src/lib/derived.js +3 -1
  328. package/src/lib/dirty-gate.js +15 -0
  329. package/src/lib/doc-storage.js +33 -0
  330. package/src/lib/effect.js +9 -4
  331. package/src/lib/mutation.js +4 -2
  332. package/src/lib/offline-runtime.js +226 -0
  333. package/src/lib/preload.js +48 -0
  334. package/src/lib/root-attrs.js +1 -1
  335. package/src/lib/trash.js +18 -0
  336. package/src/lib/undo/capture-move.js +152 -0
  337. package/src/live/local-channel.js +61 -0
  338. package/src/live/local-lane.js +60 -9
  339. package/src/live/server-lane.js +6 -3
  340. package/src/loader-logic.js +76 -15
  341. package/src/loader.js +218 -30
  342. package/src/page/adopt-boot.js +76 -22
  343. package/src/page/adopt.js +25 -4
  344. package/src/page/awareness.js +230 -0
  345. package/src/page/blocks.js +79 -5
  346. package/src/page/blocksel.js +20 -4
  347. package/src/page/board-face.js +13 -0
  348. package/src/page/board-panel.js +5 -17
  349. package/src/page/board-style.js +2 -13
  350. package/src/page/board-view.js +1 -11
  351. package/src/page/board-views.js +17 -0
  352. package/src/page/board.js +2 -2
  353. package/src/page/boot-pack.js +26 -0
  354. package/src/page/boot.js +237 -0
  355. package/src/page/checklist.js +1 -1
  356. package/src/page/chrome.js +411 -61
  357. package/src/page/csv-import.js +17 -2
  358. package/src/page/document-title-dom.js +40 -0
  359. package/src/page/document-title-runtime.js +76 -0
  360. package/src/page/document-title.js +9 -0
  361. package/src/page/drag.js +48 -13
  362. package/src/page/drop.js +2 -1
  363. package/src/page/editor.js +330 -86
  364. package/src/page/feature-loader.js +72 -0
  365. package/src/page/features/board.js +12 -0
  366. package/src/page/features/code.js +13 -0
  367. package/src/page/features/media.js +290 -0
  368. package/src/page/features/registry.js +23 -0
  369. package/src/page/features/table.js +383 -0
  370. package/src/page/{tool-view.js → features/tool.js} +33 -2
  371. package/src/page/format-codec.js +215 -0
  372. package/src/page/format-gate.js +179 -0
  373. package/src/page/format-level.js +157 -0
  374. package/src/page/highlight-slot.js +23 -0
  375. package/src/page/highlight.js +30 -16
  376. package/src/page/history-intent.js +78 -0
  377. package/src/page/inline.js +18 -25
  378. package/src/page/items.js +52 -11
  379. package/src/page/link.js +1 -0
  380. package/src/page/markdown.js +86 -1
  381. package/src/page/media-views.js +3 -0
  382. package/src/page/media.js +33 -245
  383. package/src/page/outline-cache.js +11 -0
  384. package/src/page/placeholder.js +27 -7
  385. package/src/page/projection.js +196 -50
  386. package/src/page/reader-patch.js +306 -0
  387. package/src/page/recover.js +1 -1
  388. package/src/page/rich/page-tool.js +1 -4
  389. package/src/page/runtime-owner.js +27 -0
  390. package/src/page/save-projection.js +7 -4
  391. package/src/page/schema.js +14 -0
  392. package/src/page/table-rect-key.js +3 -0
  393. package/src/page/table-select.js +4 -3
  394. package/src/page/table-style.js +1 -1
  395. package/src/page/table-views.js +3 -0
  396. package/src/page/table.js +8 -293
  397. package/src/page/themes/light-1.css +139 -2
  398. package/src/page/themes/light-1.source.css +269 -0
  399. package/src/page/tool-capability.js +3 -0
  400. package/src/page/tool-views.js +3 -0
  401. package/src/page/view.js +390 -107
  402. package/src/page/ybind/awareness.js +172 -0
  403. package/src/page/ybind/binding.js +9 -1
  404. package/src/page/ybind/bridge.js +50 -47
  405. package/src/page/ybind/decode.js +52 -19
  406. package/src/page/ybind/doc.js +27 -2
  407. package/src/page/ybind/group.js +12 -3
  408. package/src/page/ybind/index.js +2 -0
  409. package/src/page/ybind/lineage.js +71 -0
  410. package/src/page/ybind/marks.js +25 -1
  411. package/src/page/ybind/migrate.js +48 -13
  412. package/src/page/ybind/moves.js +32 -21
  413. package/src/page/ybind/positions.js +50 -0
  414. package/src/page/ybind/provider.js +84 -22
  415. package/src/page/ybind/recovery-proof.js +191 -0
  416. package/src/page/ybind/recovery-replay.js +130 -0
  417. package/src/page/ybind/tokens.js +15 -6
  418. package/src/page/ybind/walk.js +10 -3
  419. package/src/page/ybind/writer.js +48 -4
  420. package/src/plugins/ai-edit.js +167 -32
  421. package/src/plugins/bind-text.js +218 -0
  422. package/src/plugins/cms.js +93 -0
  423. package/src/plugins/indicator.js +57 -5
  424. package/src/plugins/live.js +282 -34
  425. package/src/plugins/menu/claygoo.svg.js +1 -0
  426. package/src/plugins/menu/seo-head.js +323 -0
  427. package/src/plugins/menu/seo.js +849 -0
  428. package/src/plugins/menu.js +425 -0
  429. package/src/plugins/page-view.js +131 -0
  430. package/src/plugins/page.js +1022 -275
  431. package/src/plugins/people.js +158 -31
  432. package/src/plugins/richclay.js +119 -9
  433. package/src/plugins/sortable.js +370 -60
  434. package/src/plugins/source.js +189 -366
  435. package/src/plugins/undo.js +289 -19
  436. package/src/plugins/unsent.js +132 -0
  437. package/src/plugins/upgrade-run.js +1 -0
  438. package/src/plugins/upgrade.js +210 -0
  439. package/src/plugins/upload-task.js +571 -0
  440. package/src/plugins/upload-ui.js +265 -0
  441. package/src/plugins/upload.js +34 -153
  442. package/src/plugins/visitor-drafts/inert.js +32 -0
  443. package/src/plugins/visitor-drafts/owner-chip.js +3 -0
  444. package/src/plugins/visitor-drafts/prompt.js +2 -2
  445. package/src/plugins/visitor-drafts-owner.js +85 -0
  446. package/src/plugins/visitor-drafts.js +31 -42
  447. package/src/plugins/wire.js +82 -1
  448. package/src/reader-core.js +26 -0
  449. package/src/recovery/conflicts.js +287 -0
  450. package/src/recovery/identity.js +154 -0
  451. package/src/recovery/store.js +792 -0
  452. package/src/recovery/undo.js +257 -0
  453. package/src/server/checkpoint/index.js +68 -4
  454. package/src/server/offline-runtime/index.js +214 -0
  455. package/src/server/rooms/index.js +2 -1
  456. package/src/server/rooms/live-data-rooms.js +2 -2
  457. package/src/server/rooms/page-admission.js +33 -0
  458. package/src/server/rooms/page-lineage.js +56 -0
  459. package/src/server/rooms/page-rebase.js +11 -5
  460. package/src/server/rooms/page-state.js +2 -0
  461. package/src/server/rooms/page-write-admission.js +9 -3
  462. package/src/source/session.js +705 -0
  463. package/src/sync/conflict-revert.js +5 -4
  464. package/src/sync/conflicts-edit.js +119 -0
  465. package/src/sync/conflicts-facade.js +49 -0
  466. package/src/sync/conflicts.js +99 -1
  467. package/src/sync/editing-marker.js +37 -6
  468. package/src/sync/editing-sender.js +8 -1
  469. package/src/sync/live-sync-edit.js +63 -0
  470. package/src/sync/live-sync.js +364 -119
  471. package/src/sync/local-presence.js +187 -0
  472. package/src/sync/merge-declarations.js +10 -0
  473. package/src/sync/merge-engine.js +12 -0
  474. package/src/sync/presence-store.js +412 -0
  475. package/src/sync/presence-target.js +243 -0
  476. package/src/sync/presence.js +72 -41
  477. package/src/sync/saved-handoff.js +200 -0
  478. package/src/sync/saved-stream.js +535 -0
  479. package/src/sync/version-guard.js +92 -0
  480. package/src/sync/wire-profile.js +81 -0
  481. package/src/ui/bevel-controls.js +2 -8
  482. package/src/ui/page-scheme.js +7 -0
  483. package/src/ui/skins/cms.js +1 -1
  484. package/src/ui/theme-config.js +150 -0
  485. package/src/ui/theme.js +6 -138
  486. package/src/ui/toast-lazy.js +11 -0
  487. package/src/vendor/LICENSES-optional.md +253 -0
  488. package/src/vendor/highlight.vendor.js +3 -0
  489. package/src/vendor/hljs-bash.vendor.js +2 -0
  490. package/src/vendor/hljs-css.vendor.js +2 -0
  491. package/src/vendor/hljs-javascript.vendor.js +2 -0
  492. package/src/vendor/hljs-json.vendor.js +2 -0
  493. package/src/vendor/hljs-python.vendor.js +2 -0
  494. package/src/vendor/hljs-xml.vendor.js +2 -0
  495. package/src/vendor/hyper-html-api-data.vendor.js +1 -1
  496. package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
  497. package/src/vendor/hyper-morph-identity.vendor.js +2 -0
  498. package/src/vendor/hyper-morph-json.vendor.js +3 -0
  499. package/src/vendor/hyper-morph.vendor.js +6 -6
  500. package/src/vendor/hyper-undo.vendor.js +1 -1
  501. package/src/vendor/hypercms.vendor.js +169 -84
  502. package/src/vendor/marked.vendor.js +68 -0
  503. package/src/vendor/overtype.vendor.js +994 -0
  504. package/src/vendor/richclay.vendor.js +17 -17
  505. package/src/vendor/sap-core.vendor.js +5 -5
  506. package/src/vendor/sap-ownership.vendor.js +11 -7
@@ -0,0 +1,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,9 @@ 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 { DEFAULTS, MARKS, OPAQUE, envelopeAttrs, envelopeRecord, marksAttrs, marksEntries } from "./projection.js";
9
12
 
10
13
  const alignOf = (a) => (a === "left" || a === "right" ? a : null);
11
14
 
@@ -340,9 +343,12 @@ export function indentBlocks(st, dispatch) {
340
343
  const tr = st.tr;
341
344
  let end = b;
342
345
  for (let k = a; k <= end; k++) end = Math.max(end, ents[k].subEnd);
346
+ let deep = a - 1;
343
347
  for (let k = a; k <= end && sameRun(ents[k], first); k++) {
344
348
  tr.setNodeMarkup(ents[k].at, null, { ...ents[k].node.attrs, indent: ents[k].node.attrs.indent + 1 });
349
+ deep = k;
345
350
  }
351
+ 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
352
  dispatch(tr);
347
353
  }
348
354
  return true;
@@ -352,10 +358,15 @@ export function indentBlocks(st, dispatch) {
352
358
  if (dispatch) {
353
359
  const { hi } = siblingSpan(ents, a, b);
354
360
  const tr = st.tr, from = parentOf(first.node), to = ents[s].node.attrs.id;
361
+ const adopted = [];
355
362
  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 });
363
+ if (parentOf(ents[k].node) !== from) continue;
364
+ tr.setNodeMarkup(ents[k].at, null, { ...ents[k].node.attrs, parent: to });
365
+ adopted.push(ents[k].node.attrs.id);
357
366
  }
358
367
  if (!ents[s].node.attrs.open) tr.setNodeMarkup(ents[s].at, null, { ...ents[s].node.attrs, open: true });
368
+ const intent = moveIntent(ents, a, hi, ents[a].at, (e) => (e.i >= a && e.i <= hi && parentOf(e.node) === from ? { parent: to } : null));
369
+ tr.setMeta(MOVE, { ...intent, ids: adopted, attrs: ents[s].node.attrs.open ? intent.attrs : { ...intent.attrs, [to]: { open: true } } });
359
370
  dispatch(tr);
360
371
  }
361
372
  return true;
@@ -369,16 +380,22 @@ export function outdentBlocks(st, dispatch) {
369
380
  const first = ents[a];
370
381
  if (isList(first.node) && first.node.attrs.indent > 0) {
371
382
  if (dispatch) {
383
+ const { lo, hi } = siblingSpan(ents, a, b);
372
384
  const tr = st.tr;
385
+ const lowered = new Set();
373
386
  for (let k = a; k <= b;) {
374
387
  const e = ents[k];
375
388
  if (isList(e.node) && e.node.attrs.indent > 0) {
376
389
  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 });
390
+ if (isList(ents[m].node)) {
391
+ tr.setNodeMarkup(ents[m].at, null, { ...ents[m].node.attrs, indent: ents[m].node.attrs.indent - 1 });
392
+ lowered.add(m);
393
+ }
378
394
  }
379
395
  }
380
396
  k = e.subEnd + 1;
381
397
  }
398
+ tr.setMeta(MOVE, moveIntent(ents, lo, hi, ents[lo].at, (e) => (lowered.has(e.i) ? { indent: e.node.attrs.indent - 1 } : null)));
382
399
  dispatch(tr);
383
400
  }
384
401
  return true;
@@ -395,6 +412,7 @@ export function outdentBlocks(st, dispatch) {
395
412
  if (hi === end) {
396
413
  const tr = st.tr;
397
414
  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 }); }
415
+ tr.setMeta(MOVE, moveIntent(ents, lo, hi, ents[lo].at, attrs));
398
416
  dispatch(tr);
399
417
  } else dispatch(moveSpanTr(st, ents, lo, hi, ents[end].end, attrs).scrollIntoView());
400
418
  }
@@ -481,7 +499,13 @@ export function exitToggleOnEmpty(st, dispatch) {
481
499
  const i = $from.index(0);
482
500
  const ti = ents.findIndex((x) => x.node.attrs.id === t);
483
501
  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) }));
502
+ if (dispatch) {
503
+ const id = ents[i].node.attrs.id, to = parentOf(ents[ti].node);
504
+ const prev = i > 0 ? ents[i - 1].node : null;
505
+ const tr = st.tr.setNodeMarkup(ents[i].at, null, { ...$from.parent.attrs, parent: to });
506
+ tr.setMeta(MOVE, { ids: [id], afterId: prev ? prev.attrs.id : null, parent: parentOf(prev ?? ents[i].node), attrs: { [id]: { parent: to } } });
507
+ dispatch(tr);
508
+ }
485
509
  return true;
486
510
  }
487
511
 
@@ -1024,8 +1048,41 @@ export function flattenPastedHTML(html, doc = globalThis.document) {
1024
1048
  const numberAttr = (value, min = 0) => value !== null && value !== "" && Number.isInteger(+value) && +value >= min ? +value : null;
1025
1049
  const parentAttr = (dom) => dom.getAttribute("data-page-parent") || null;
1026
1050
 
1051
+ // A copied opaque mark is not one of the schema's marks, so the rules of the
1052
+ // clipboard parser resolve mark names through a view of the schema that holds
1053
+ // the internal one; every node a parse builds is the schema's own.
1054
+ const opaqueType = opaqueMark([]).type;
1055
+ const parseSchema = Object.create(schema, { marks: { value: { ...schema.marks, [OPAQUE_MARK]: opaqueType } } });
1056
+
1057
+ // The record of a pasted block envelope, or null when it is not one this
1058
+ // runtime may keep: a malformed, oversized or hostile envelope, one naming a
1059
+ // known block type, or one with no type of its own. A pasted element with no
1060
+ // usable record is plain text, never a record this runtime made up.
1061
+ const pastedRecord = (dom) => {
1062
+ let record;
1063
+ try { record = envelopeRecord(dom); } catch { return null; }
1064
+ if (!record || typeof record.t !== "string" || record.t === "opaque_block" || Object.hasOwn(DEFAULTS, record.t)) return null;
1065
+ return record;
1066
+ };
1067
+ const pastedOpaque = (dom) => {
1068
+ const record = pastedRecord(dom);
1069
+ if (!record) return false;
1070
+ return { raw: record, parent: parentAttr(dom) };
1071
+ };
1072
+ const pastedOpaqueContent = (dom) => {
1073
+ const record = pastedRecord(dom);
1074
+ if (!record || typeof record.c !== "string" || !record.c) return model.Fragment.empty;
1075
+ const units = unitsOf(record.m, record.c.length);
1076
+ return model.Fragment.from(unflatten(schema, record.c, units, schema.nodes.opaque_block));
1077
+ };
1078
+ const pastedOpaqueMarks = (dom) => {
1079
+ try { return { entries: marksEntries(dom.getAttribute(MARKS)) }; } catch { return false; }
1080
+ };
1081
+
1027
1082
  // The clipboard parser: the schema has toDOM only, so paste gets its own rules.
1028
- export const clipboardParser = new model.DOMParser(schema, [
1083
+ export const clipboardParser = new model.DOMParser(parseSchema, [
1084
+ { tag: `div[${OPAQUE}]`, node: "opaque_block", getAttrs: pastedOpaque, getContent: pastedOpaqueContent },
1085
+ { tag: `span[${MARKS}]`, mark: OPAQUE_MARK, getAttrs: pastedOpaqueMarks },
1029
1086
  { 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
1087
  { 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
1088
  { tag: "div[data-page-toggle]", node: "toggle", getAttrs: (d) => ({ id: d.getAttribute("data-page-toggle"), open: d.getAttribute("data-open") === "true", parent: parentAttr(d) }) },
@@ -1115,7 +1172,24 @@ class ClipboardSerializer extends model.DOMSerializer {
1115
1172
  }
1116
1173
  }
1117
1174
  const base = model.DOMSerializer.fromSchema(schema);
1118
- export const clipboardSerializer = new ClipboardSerializer(base.nodes, base.marks);
1175
+
1176
+ // A copied block this runtime has no schema for keeps its record in the
1177
+ // projection's block envelope, with its own text as the readable fallback, and
1178
+ // a copied opaque mark keeps its descriptors in the marks envelope. The record
1179
+ // is the one the saved article keeps: its own type, every field it carried, and
1180
+ // its text with its marks.
1181
+ const opaqueRecord = (node) => {
1182
+ const { t, ...rest } = node.attrs.raw ?? {};
1183
+ const { c, m } = flatten(node.content);
1184
+ if (!c) return { t, ...rest };
1185
+ return { t, ...rest, c, ...(m.some(Boolean) ? { m: runsOf(m) } : {}) };
1186
+ };
1187
+ const opaqueNode = (node) => ["div", { "data-type": "newer-format", ...envelopeAttrs(opaqueRecord(node)) }, 0];
1188
+ const opaqueMarks = (mark) => ["span", marksAttrs(mark.attrs.entries), 0];
1189
+ const clipboardNodes = { ...base.nodes, opaque_block: opaqueNode };
1190
+ const clipboardMarks = { ...base.marks, [OPAQUE_MARK]: opaqueMarks };
1191
+
1192
+ export const clipboardSerializer = new ClipboardSerializer(clipboardNodes, clipboardMarks);
1119
1193
 
1120
1194
  // Several blocks pasted into a closed toggle's summary join its body, so the
1121
1195
  // toggle opens first instead of hiding what was just pasted.
@@ -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,17 @@
1
+ import { state } from "../vendor/prosemirror.vendor.js";
2
+
3
+ // The card panels a board plugin can reach, filled by the board view part when it
4
+ // loads. The plugin works, and does nothing, for a board without a panel.
5
+ export const cardPanels = new WeakMap();
6
+
7
+ export function boardPlugin() {
8
+ return new state.Plugin({
9
+ view: (view) => ({
10
+ update: () => cardPanels.get(view)?.sync(),
11
+ destroy: () => {
12
+ cardPanels.get(view)?.hide(false, false);
13
+ cardPanels.delete(view);
14
+ },
15
+ }),
16
+ });
17
+ }
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);