@panphora/clayjs 1.16.0 → 1.18.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 (511) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/clay-dom.js +1 -1
  3. package/dist/clay-events.js +1 -1
  4. package/dist/clay-internals.js +1 -1
  5. package/dist/clay-options.js +1 -1
  6. package/dist/clay-router.js +1 -1
  7. package/dist/clay-ui.js +1 -1
  8. package/dist/clay-utils.js +1 -1
  9. package/dist/clay.js +1 -1
  10. package/dist/clay.standalone.js +72519 -59839
  11. package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
  12. package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
  13. package/dist/m/c-2GRUM77Q.js +30 -0
  14. package/dist/m/c-2KIEGNC4.js +1 -0
  15. package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
  16. package/dist/m/c-2YYDMLNZ.js +1 -0
  17. package/dist/m/c-2ZZBC45O.js +1 -0
  18. package/dist/m/c-372N6H3B.js +1 -0
  19. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  20. package/dist/m/c-3LDGIBMN.js +1 -0
  21. package/dist/m/c-3O7OBDCM.js +1 -0
  22. package/dist/m/c-4ASQPRQL.js +1 -0
  23. package/dist/m/c-4TXNLX4K.js +6 -0
  24. package/dist/m/c-4YU3BR4D.js +6 -0
  25. package/dist/m/c-5734MF2P.js +1 -0
  26. package/dist/m/c-57XDY6HC.js +1 -0
  27. package/dist/m/{c-VFX45IZQ.js → c-5B4YBZXC.js} +1 -1
  28. package/dist/m/c-5ONTYIUI.js +1 -0
  29. package/dist/m/c-5OOVFCJM.js +1 -0
  30. package/dist/m/c-5T6NZHNM.js +1 -0
  31. package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
  32. package/dist/m/{c-Z36J63PP.js → c-6DB2OLRJ.js} +1 -1
  33. package/dist/m/c-6DNRCPNM.js +1 -0
  34. package/dist/m/c-6UGWFMQC.js +2 -0
  35. package/dist/m/c-7K4SCQB7.js +1 -0
  36. package/dist/m/c-7MG5RU63.js +1 -0
  37. package/dist/m/c-7PRJCA3E.js +1 -0
  38. package/dist/m/c-A6L3FMUS.js +1 -0
  39. package/dist/m/c-AMU3BPPR.js +4 -0
  40. package/dist/m/c-AMYEFA2E.js +1 -0
  41. package/dist/m/c-AQYCG7QW.js +1 -0
  42. package/dist/m/c-ATKAOH23.js +1 -0
  43. package/dist/m/c-B3JM5ZXC.js +1 -0
  44. package/dist/m/c-B5CS55WR.js +1 -0
  45. package/dist/m/c-B5UJQT7W.js +1 -0
  46. package/dist/m/c-B7J4K2AE.js +3 -0
  47. package/dist/m/c-BHZYSHW4.js +1 -0
  48. package/dist/m/c-BIJALSRD.js +1 -0
  49. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  50. package/dist/m/c-BPF2NSPV.js +1 -0
  51. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  52. package/dist/m/c-BUAYFEV5.js +1 -0
  53. package/dist/m/c-C6IPRG34.js +1 -0
  54. package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
  55. package/dist/m/{c-4DRMS4QG.js → c-CCQ25GFY.js} +1 -1
  56. package/dist/m/c-CJFDLD43.js +1 -0
  57. package/dist/m/c-COPTL5WB.js +1 -0
  58. package/dist/m/c-D3E4WJ4M.js +166 -0
  59. package/dist/m/c-D5IKEKXW.js +7 -0
  60. package/dist/m/c-DHBRA6DL.js +1 -0
  61. package/dist/m/c-DKLS74XB.js +72 -0
  62. package/dist/m/c-DVYGFTSK.js +3 -0
  63. package/dist/m/c-E3ZXARWA.js +1 -0
  64. package/dist/m/c-EK63G3CG.js +1 -0
  65. package/dist/m/c-ERQSNR6K.js +13 -0
  66. package/dist/m/c-EVYAISP3.js +1 -0
  67. package/dist/m/c-EXKHPOVC.js +3 -0
  68. package/dist/m/{c-7VGZ7RU5.js → c-FWKHM4E7.js} +1 -1
  69. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  70. package/dist/m/c-GNDL3TKB.js +1 -0
  71. package/dist/m/c-GRWMD6O5.js +1 -0
  72. package/dist/m/c-GRXHHFPO.js +1 -0
  73. package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
  74. package/dist/m/c-HAWFNBYG.js +1 -0
  75. package/dist/m/c-HG2C77BM.js +4 -0
  76. package/dist/m/c-HHGJBU4D.js +1 -0
  77. package/dist/m/c-HMV6P4RX.js +1 -0
  78. package/dist/m/c-HMYWIHFG.js +1 -0
  79. package/dist/m/c-HT2DKBV2.js +1 -0
  80. package/dist/m/c-I2SCCUEW.js +1 -0
  81. package/dist/m/{c-HRDCD3WN.js → c-ICJYHABF.js} +2 -2
  82. package/dist/m/c-IMZCWYHR.js +7 -0
  83. package/dist/m/c-IVMD3FEM.js +1 -0
  84. package/dist/m/c-IVTRVENZ.js +1 -0
  85. package/dist/m/c-IW6GGOU2.js +1 -0
  86. package/dist/m/c-JKZJIUIP.js +1 -0
  87. package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
  88. package/dist/m/c-JRKY44DK.js +1 -0
  89. package/dist/m/c-K3ADYNLU.js +1 -0
  90. package/dist/m/c-KBS3UV6B.js +1 -0
  91. package/dist/m/c-KGHVFRR3.js +1 -0
  92. package/dist/m/c-KJJPV77R.js +1 -0
  93. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  94. package/dist/m/{c-5Y6TNX5W.js → c-KP64LT2A.js} +2 -2
  95. package/dist/m/c-KQ7E3PYY.js +1 -0
  96. package/dist/m/c-L3F5ZLIV.js +1 -0
  97. package/dist/m/{c-LR4CGEPT.js → c-L42OLXWM.js} +3 -2
  98. package/dist/m/c-L5VFATT6.js +1 -0
  99. package/dist/m/c-L7JL5TL3.js +3 -0
  100. package/dist/m/c-LKSL4AUJ.js +1 -0
  101. package/dist/m/c-M6FNQCTQ.js +1 -0
  102. package/dist/m/c-MCOOHCSG.js +1 -0
  103. package/dist/m/c-MHI3GYKF.js +1 -0
  104. package/dist/m/c-MWXFLW4J.js +1 -0
  105. package/dist/m/c-NEL7HQW3.js +1 -0
  106. package/dist/m/c-NQJRVJ56.js +1 -0
  107. package/dist/m/c-NWX3MFT4.js +1 -0
  108. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  109. package/dist/m/c-OKXI52N7.js +1 -0
  110. package/dist/m/{c-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
  111. package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
  112. package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
  113. package/dist/m/c-P3OFYUMA.js +1 -0
  114. package/dist/m/c-PLN76GF3.js +6 -0
  115. package/dist/m/c-PLP4JKFC.js +1 -0
  116. package/dist/m/c-Q26MME6N.js +1 -0
  117. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  118. package/dist/m/{c-SWRJFCNN.js → c-QIKT6SKM.js} +1 -1
  119. package/dist/m/c-QIXT3Q7C.js +28 -0
  120. package/dist/m/c-QO4HNGCQ.js +1 -0
  121. package/dist/m/c-R27DWQKX.js +1 -0
  122. package/dist/m/{c-HVYXIS72.js → c-R2XUODVY.js} +1 -1
  123. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  124. package/dist/m/c-RND3G2S2.js +1 -0
  125. package/dist/m/c-S2U6NJU4.js +1 -0
  126. package/dist/m/c-SPZPGD3R.js +1 -0
  127. package/dist/m/c-SW4YEG2D.js +8 -0
  128. package/dist/m/c-SZGYW5HT.js +1 -0
  129. package/dist/m/c-T637T7SN.js +1 -0
  130. package/dist/m/c-TJAXGIRV.js +1 -0
  131. package/dist/m/{c-KKWLDOPM.js → c-TNEM3RNM.js} +1 -1
  132. package/dist/m/c-TTB5EK4S.js +32 -0
  133. package/dist/m/c-U7SHNWDH.js +1 -0
  134. package/dist/m/c-UDHIVLYH.js +20 -0
  135. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  136. package/dist/m/{c-TQZ2JYHD.js → c-UORXLSD3.js} +1 -1
  137. package/dist/m/c-UPD33ZPE.js +1 -0
  138. package/dist/m/c-URSMSFCB.js +1 -0
  139. package/dist/m/c-UY4QX42B.js +1 -0
  140. package/dist/m/c-V44KWI2L.js +4 -0
  141. package/dist/m/c-VG5XIN4T.js +1 -0
  142. package/dist/m/c-VILENZTD.js +1 -0
  143. package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
  144. package/dist/m/c-VR2BJ4AN.js +1 -0
  145. package/dist/m/c-W4JCVLAB.js +1 -0
  146. package/dist/m/c-W6DQSHXI.js +135 -0
  147. package/dist/m/c-WBGWGOM7.js +1 -0
  148. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  149. package/dist/m/c-WGW764WX.js +1 -0
  150. package/dist/m/c-WMRJKQ2V.js +5 -0
  151. package/dist/m/c-WNTYKCDG.js +1 -0
  152. package/dist/m/c-WQWIRWO3.js +72 -0
  153. package/dist/m/c-X5LNHET5.js +1 -0
  154. package/dist/m/c-X5TSXZYJ.js +2 -0
  155. package/dist/m/c-XGH4GS2T.js +1 -0
  156. package/dist/m/c-Y6F2FORL.js +1 -0
  157. package/dist/m/c-YBKHUAK7.js +10 -0
  158. package/dist/m/c-YBSS55KU.js +20 -0
  159. package/dist/m/c-YCLU4UNT.js +2 -0
  160. package/dist/m/c-YVZ2L7CE.js +1 -0
  161. package/dist/m/c-ZK4GIZH2.js +1 -0
  162. package/dist/m/c-ZRXJD4G6.js +1 -0
  163. package/dist/m/c-ZWBI4HYK.js +1 -0
  164. package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
  165. package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
  166. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  167. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  168. package/dist/m/core/host-permission-6EVJGNPL.js +1 -0
  169. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  170. package/dist/m/core/{persist-HKHA66IY.js → persist-UKXV5KLA.js} +1 -1
  171. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  172. package/dist/m/core/save-LYY7K6U7.js +1 -0
  173. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  174. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  175. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  176. package/dist/m/events/index-RN2JNGBV.js +1 -0
  177. package/dist/m/internals/index-TGLQINN3.js +1 -0
  178. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  179. package/dist/m/loader-3BBNZZGM.js +1 -0
  180. package/dist/m/manifest-F449FF98.js +1 -0
  181. package/dist/m/manifest-edit-4396A009.js +1 -0
  182. package/dist/m/manifest.json +2509 -1474
  183. package/dist/m/options/options-EJMS3K24.js +1 -0
  184. package/dist/m/page/editor-6LRB3GE4.js +304 -0
  185. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  186. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  187. package/dist/m/plugins/demo-OYO5GCAO.js +1 -0
  188. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  189. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  190. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  191. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  192. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  193. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  194. package/dist/m/plugins/people-JCHVPF7P.js +1 -0
  195. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  196. package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-KLGETK77.js} +2 -2
  197. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  198. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  199. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  200. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  201. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  202. package/dist/m/plugins/upload-NHGJXZK2.js +1 -0
  203. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  204. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  205. package/dist/m/plugins/wire-VBWXEXU2.js +1 -0
  206. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  207. package/dist/m/route/route-62CHXL5B.js +1 -0
  208. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  209. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  210. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  211. package/dist/offline-manifest.json +1560 -889
  212. package/dist/page/themes/light-1.css +33 -8
  213. package/dist/server/core.cjs +1554 -311
  214. package/dist/server/offline-runtime.cjs +63 -63
  215. package/dist/server/offline-runtime.mjs +63 -63
  216. package/dist/server/page-compaction-worker.cjs +3710 -0
  217. package/dist/server/rooms.mjs +5 -0
  218. package/docs/guide.md +653 -101
  219. package/entries/sap.js +5 -5
  220. package/package.json +1 -1
  221. package/src/core/admin-contenteditable.js +5 -2
  222. package/src/core/admin-inputs.js +1 -0
  223. package/src/core/admin-onclick.js +2 -1
  224. package/src/core/boot-phase.js +2 -1
  225. package/src/core/conflict-download.js +3 -29
  226. package/src/core/conflict-notice-ui.js +4 -7
  227. package/src/core/download.js +32 -0
  228. package/src/core/host-meta.js +56 -0
  229. package/src/core/is-edit-mode.js +17 -1
  230. package/src/core/mode-key.js +125 -19
  231. package/src/core/mode-support.js +5 -0
  232. package/src/core/mode-transition.js +20 -6
  233. package/src/core/preserve-authored.js +4 -4
  234. package/src/core/recovery.js +121 -33
  235. package/src/core/save-accepted.js +33 -0
  236. package/src/core/save-core.js +111 -32
  237. package/src/core/save-job.js +108 -0
  238. package/src/core/save-prepare.js +53 -0
  239. package/src/core/save-size-notice.js +3 -1
  240. package/src/core/save-slices.js +15 -0
  241. package/src/core/save.js +557 -127
  242. package/src/core/serialize-steps.js +109 -0
  243. package/src/core/snapshot.js +209 -40
  244. package/src/core/source-map.js +83 -38
  245. package/src/core/stale-host-notice.js +1 -0
  246. package/src/core/upgrade-run.js +59 -8
  247. package/src/dom/active-element.js +5 -0
  248. package/src/lib/assets.js +1 -0
  249. package/src/lib/autosave-scheduler.js +3 -4
  250. package/src/lib/slice.js +65 -0
  251. package/src/lib/uimotion.js +774 -0
  252. package/src/lib/unsaved-state.js +2 -2
  253. package/src/live/fast-lane.js +10 -3
  254. package/src/live/local-lane.js +202 -11
  255. package/src/live/ops.js +16 -0
  256. package/src/live/page-compaction.js +162 -0
  257. package/src/live/server-lane.js +1 -0
  258. package/src/live/store.js +11 -3
  259. package/src/loader-logic.js +3 -1
  260. package/src/loader.js +14 -4
  261. package/src/page/adopt-boot.js +2 -1
  262. package/src/page/awareness.js +33 -2
  263. package/src/page/blocks.js +122 -27
  264. package/src/page/board-views.js +2 -16
  265. package/src/page/boot-pack.js +1 -1
  266. package/src/page/chart-tables.js +29 -0
  267. package/src/page/chart.js +411 -72
  268. package/src/page/checklist.js +10 -135
  269. package/src/page/checkpoint-proof.js +295 -0
  270. package/src/page/chrome.js +257 -40
  271. package/src/page/clipboard-shape.js +126 -0
  272. package/src/page/document-title-dom.js +12 -0
  273. package/src/page/document-title-runtime.js +15 -1
  274. package/src/page/document-title.js +4 -1
  275. package/src/page/drag.js +33 -3
  276. package/src/page/editor.js +207 -31
  277. package/src/page/emoji-data.js +162 -0
  278. package/src/page/export-assets.js +357 -0
  279. package/src/page/export-html.js +209 -0
  280. package/src/page/export-markdown.js +361 -0
  281. package/src/page/features/board.js +12 -3
  282. package/src/page/features/chart.js +11 -0
  283. package/src/page/features/checklist.js +428 -0
  284. package/src/page/features/dates.js +180 -0
  285. package/src/page/features/emoji.js +187 -0
  286. package/src/page/features/export-html.js +189 -0
  287. package/src/page/features/export-markdown.js +169 -0
  288. package/src/page/features/import-markdown.js +349 -0
  289. package/src/page/features/media.js +61 -5
  290. package/src/page/features/mentions.js +188 -0
  291. package/src/page/features/registry.js +24 -1
  292. package/src/page/features/table.js +375 -85
  293. package/src/page/features/title-cover.js +331 -0
  294. package/src/page/features/title-icon.js +213 -0
  295. package/src/page/features/toc.js +87 -0
  296. package/src/page/format-command.js +33 -0
  297. package/src/page/format-gate.js +68 -18
  298. package/src/page/format-host.js +71 -0
  299. package/src/page/format-native-commands.js +158 -0
  300. package/src/page/format-native.js +42 -0
  301. package/src/page/image-actions.js +64 -0
  302. package/src/page/import-markdown.js +262 -0
  303. package/src/page/inline.js +32 -8
  304. package/src/page/items.js +141 -11
  305. package/src/page/markdown.js +30 -2
  306. package/src/page/media.js +35 -119
  307. package/src/page/mention-picker.js +99 -0
  308. package/src/page/merge.js +1 -1
  309. package/src/page/placeholder.js +14 -1
  310. package/src/page/projection.js +156 -25
  311. package/src/page/recover.js +10 -12
  312. package/src/page/recovery-ui.js +464 -0
  313. package/src/page/rich/chart.js +63 -7
  314. package/src/page/save-projection.js +67 -0
  315. package/src/page/save-states.js +14 -4
  316. package/src/page/schema.js +52 -3
  317. package/src/page/table-paste.js +3 -1
  318. package/src/page/table-style.js +4 -4
  319. package/src/page/table.js +44 -2
  320. package/src/page/themes/content.css +25 -1
  321. package/src/page/themes/light-1.css +33 -8
  322. package/src/page/themes/light-1.source.css +8 -7
  323. package/src/page/toc-link.js +31 -0
  324. package/src/page/toc.js +5 -0
  325. package/src/page/view-hooks.js +58 -0
  326. package/src/page/view.js +13 -0
  327. package/src/page/ybind/binding.js +116 -6
  328. package/src/page/ybind/bridge.js +187 -20
  329. package/src/page/ybind/containment.js +162 -0
  330. package/src/page/ybind/doc.js +17 -9
  331. package/src/page/ybind/file-debt.js +63 -0
  332. package/src/page/ybind/layout.js +7 -2
  333. package/src/page/ybind/marks.js +2 -1
  334. package/src/page/ybind/moves.js +5 -4
  335. package/src/page/ybind/origins.js +106 -4
  336. package/src/page/ybind/parts.js +8 -1
  337. package/src/page/ybind/provider.js +125 -18
  338. package/src/page/ybind/recovery-proof.js +32 -9
  339. package/src/page/ybind/recovery-replay.js +905 -4
  340. package/src/page/ybind/recovery.js +174 -45
  341. package/src/page/ybind/table.js +67 -11
  342. package/src/page/ybind/walk-delta.js +258 -0
  343. package/src/page/ybind/walk.js +54 -29
  344. package/src/page/ybind/writer.js +1 -1
  345. package/src/plugins/ai-edit.js +13 -9
  346. package/src/plugins/indicator-chip.js +91 -0
  347. package/src/plugins/indicator.js +13 -80
  348. package/src/plugins/live.js +126 -15
  349. package/src/plugins/menu/actions.js +196 -0
  350. package/src/plugins/menu/people.js +279 -0
  351. package/src/plugins/menu/placement.js +121 -0
  352. package/src/plugins/menu/preferences.js +9 -0
  353. package/src/plugins/menu/seo.js +316 -397
  354. package/src/plugins/menu/style.js +171 -0
  355. package/src/plugins/menu/variants.js +296 -0
  356. package/src/plugins/menu.js +186 -183
  357. package/src/plugins/page-view.js +3 -2
  358. package/src/plugins/page.js +362 -43
  359. package/src/plugins/sortable.js +8 -1
  360. package/src/plugins/source.js +6 -0
  361. package/src/plugins/upgrade.js +51 -17
  362. package/src/plugins/upload-ui.js +12 -17
  363. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  364. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  365. package/src/plugins/visitor-drafts/intro.js +7 -4
  366. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  367. package/src/plugins/visitor-drafts/prompt.js +11 -9
  368. package/src/plugins/visitor-drafts/source.js +4 -3
  369. package/src/plugins/visitor-drafts/store.js +138 -9
  370. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  371. package/src/plugins/visitor-drafts-owner.js +4 -6
  372. package/src/plugins/visitor-drafts.js +5 -3
  373. package/src/recovery/store.js +1 -1
  374. package/src/route/route.js +4 -0
  375. package/src/server/checkpoint/index.js +2 -2
  376. package/src/server/rooms/index.js +5 -1
  377. package/src/server/rooms/live-data-rooms.js +302 -50
  378. package/src/server/rooms/page-compaction-worker.js +12 -0
  379. package/src/server/rooms/page-compactor.js +92 -0
  380. package/src/server/rooms/page-room-conformance.js +461 -0
  381. package/src/source/parse-steps.js +94 -0
  382. package/src/source/session.js +233 -57
  383. package/src/sync/editing-sender.js +12 -0
  384. package/src/sync/live-sync-edit.js +22 -2
  385. package/src/sync/live-sync.js +150 -23
  386. package/src/sync/page-relay.js +52 -0
  387. package/src/sync/presence-store.js +44 -4
  388. package/src/sync/presence.js +6 -3
  389. package/src/sync/saved-stream.js +28 -14
  390. package/src/ui/bevel-controls.js +1 -0
  391. package/src/ui/bevel-dialog.js +5 -2
  392. package/src/ui/editor-ui.js +77 -0
  393. package/src/ui/page-colour.js +281 -0
  394. package/src/ui/toast.js +16 -7
  395. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  396. package/src/vendor/hyper-undo.vendor.js +1 -1
  397. package/src/vendor/hypercms.vendor.js +9 -9
  398. package/src/vendor/parse5.vendor.js +2 -2
  399. package/src/vendor/sap-core.vendor.js +5 -5
  400. package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
  401. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
  402. package/dist/m/c-2M73252O.js +0 -1
  403. package/dist/m/c-2SPVQSHC.js +0 -1
  404. package/dist/m/c-35NBGMZB.js +0 -1
  405. package/dist/m/c-3IQRECYS.js +0 -1
  406. package/dist/m/c-4B6H4G5V.js +0 -1
  407. package/dist/m/c-4FFHZM32.js +0 -1
  408. package/dist/m/c-4YSCRICO.js +0 -1
  409. package/dist/m/c-54RCDCBU.js +0 -1
  410. package/dist/m/c-5M4YEKCX.js +0 -4
  411. package/dist/m/c-5V3MOVJT.js +0 -1
  412. package/dist/m/c-66O32MV3.js +0 -28
  413. package/dist/m/c-6HMB24EV.js +0 -1
  414. package/dist/m/c-7O6AFR65.js +0 -1
  415. package/dist/m/c-ANQTJ3B5.js +0 -2
  416. package/dist/m/c-AYVOREBE.js +0 -1
  417. package/dist/m/c-B2ZXPOS3.js +0 -3
  418. package/dist/m/c-BMIZKMPV.js +0 -1
  419. package/dist/m/c-BTDI4Y23.js +0 -1
  420. package/dist/m/c-CJZSLMVQ.js +0 -1
  421. package/dist/m/c-DBE6ORSI.js +0 -1
  422. package/dist/m/c-EDHUR4MX.js +0 -1
  423. package/dist/m/c-EY6UOM54.js +0 -1
  424. package/dist/m/c-F3CKJTJS.js +0 -1
  425. package/dist/m/c-F7Y6FLPY.js +0 -1
  426. package/dist/m/c-FA4DZVTC.js +0 -1
  427. package/dist/m/c-G2BUZITR.js +0 -1
  428. package/dist/m/c-GB3JTV6E.js +0 -1
  429. package/dist/m/c-GBGWXTX4.js +0 -1
  430. package/dist/m/c-GHBCCVG4.js +0 -1
  431. package/dist/m/c-GJAEGGXY.js +0 -1
  432. package/dist/m/c-GOWBDM6J.js +0 -1
  433. package/dist/m/c-IB52R237.js +0 -1
  434. package/dist/m/c-IKWNE4R4.js +0 -1
  435. package/dist/m/c-IU3IYEU6.js +0 -1
  436. package/dist/m/c-IZ2EBRAJ.js +0 -1
  437. package/dist/m/c-KR4UW4KW.js +0 -1
  438. package/dist/m/c-KY3VJGUA.js +0 -8
  439. package/dist/m/c-LFPHCXY3.js +0 -1
  440. package/dist/m/c-LRPUO2MX.js +0 -1
  441. package/dist/m/c-LXENEBV7.js +0 -1
  442. package/dist/m/c-NMJRXKPC.js +0 -8
  443. package/dist/m/c-OIBUTDXI.js +0 -1
  444. package/dist/m/c-PPOSCAQD.js +0 -1
  445. package/dist/m/c-QIX5BJYN.js +0 -1
  446. package/dist/m/c-QQHEX6HA.js +0 -1
  447. package/dist/m/c-R7RMANJI.js +0 -9
  448. package/dist/m/c-RI4NT75Y.js +0 -1
  449. package/dist/m/c-RM7J7BV2.js +0 -1
  450. package/dist/m/c-RUJKSUQW.js +0 -1
  451. package/dist/m/c-SGCCC4FW.js +0 -25
  452. package/dist/m/c-T4VO4OCB.js +0 -1
  453. package/dist/m/c-TQEMVJCS.js +0 -1
  454. package/dist/m/c-TR6RMDQ7.js +0 -3
  455. package/dist/m/c-UKHTLWUW.js +0 -1
  456. package/dist/m/c-UNN7LWCT.js +0 -1
  457. package/dist/m/c-UXBMRDZB.js +0 -235
  458. package/dist/m/c-V7PZZRG7.js +0 -1
  459. package/dist/m/c-VL52ADDR.js +0 -1
  460. package/dist/m/c-WNYBLIOZ.js +0 -1
  461. package/dist/m/c-WWDBYXLK.js +0 -1
  462. package/dist/m/c-XNNW2CWK.js +0 -1
  463. package/dist/m/c-Y6USGTS5.js +0 -5
  464. package/dist/m/c-YG2NJYS6.js +0 -135
  465. package/dist/m/c-Z4VRC7GU.js +0 -7
  466. package/dist/m/c-Z6K52OLP.js +0 -19
  467. package/dist/m/c-ZDY2MDAA.js +0 -21
  468. package/dist/m/c-ZUCOKOHT.js +0 -1
  469. package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
  470. package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
  471. package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
  472. package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
  473. package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
  474. package/dist/m/core/recovery-TBBSICE2.js +0 -1
  475. package/dist/m/core/save-WUCHTQBE.js +0 -1
  476. package/dist/m/core/save-core-FJVHT5M3.js +0 -1
  477. package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
  478. package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
  479. package/dist/m/events/index-23DPTUT4.js +0 -1
  480. package/dist/m/internals/index-WOVAP3RU.js +0 -1
  481. package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
  482. package/dist/m/loader-HXX6YNH5.js +0 -1
  483. package/dist/m/manifest-F22DEDAC.js +0 -1
  484. package/dist/m/manifest-edit-C3C7376B.js +0 -1
  485. package/dist/m/options/options-2LH76JQ3.js +0 -1
  486. package/dist/m/page/editor-XUTB2HB2.js +0 -189
  487. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
  488. package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
  489. package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
  490. package/dist/m/plugins/live-NUTTXSBS.js +0 -1
  491. package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
  492. package/dist/m/plugins/page-KSUH7G6J.js +0 -2
  493. package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
  494. package/dist/m/plugins/people-AJJ32YCG.js +0 -1
  495. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  496. package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
  497. package/dist/m/plugins/source-KHK5UUSK.js +0 -1
  498. package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
  499. package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
  500. package/dist/m/plugins/upload-XSGNILUY.js +0 -1
  501. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
  502. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
  503. package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
  504. package/dist/m/reader-core-ILWLXVMP.js +0 -68
  505. package/dist/m/route/route-KOCZSEGN.js +0 -1
  506. package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
  507. package/dist/m/ui/index-EVHKMKBL.js +0 -1
  508. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  509. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  510. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  511. /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
package/src/loader.js CHANGED
@@ -86,10 +86,11 @@ export async function boot(base, params, readyResolve, manifest = null, suppleme
86
86
  const regionPolicy = await MODULES["lib/region-policy.js"]();
87
87
 
88
88
  const hasSap = Boolean(document.querySelector("[sap]"));
89
+ const hasVariants = document.querySelectorAll("head style[clay-variant]").length >= 2;
89
90
  // derive.js imports the sapjs core only when no sap.js script took the page over.
90
91
  const hasSapJs = Boolean(document.querySelector('script[src*="sap.js"]'));
91
92
  bootPhase.holdSettle = hasSap && isEditMode;
92
- const plan = resolveModules(params, isEditMode, { isDraftMode, hasSap, canEdit, templateSource: templateSource() });
93
+ const plan = resolveModules(params, isEditMode, { isDraftMode, hasSap, hasVariants, canEdit, templateSource: templateSource() });
93
94
  // Where edits land, resolved once: a visitor draft saves into its draft slot, the
94
95
  // demo plugin into localStorage, and everything else to the host. setMode takes no
95
96
  // destination, and V4 has no draft-to-host transition.
@@ -126,16 +127,22 @@ export async function boot(base, params, readyResolve, manifest = null, suppleme
126
127
  // Nothing is fetched for it here: the decision and the controller behind it wait for the
127
128
  // first switch, so this costs a reader with a declaration no more than one attribute.
128
129
  if (document.documentElement.getAttribute("clay-mode-switch") === "live") {
129
- const editing = resolveModules(params, true, { isDraftMode, hasSap, canEdit, templateSource: templateSource() });
130
+ const editing = resolveModules(params, true, { isDraftMode, hasSap, hasVariants, canEdit, templateSource: templateSource() });
130
131
  loaded["core/edit-mode.js"].noteLiveSwitch({
131
132
  edit: editing,
132
133
  // The plan a view boot would have resolved, not the edit plan this boot resolved: an
133
134
  // exit imports it and starts its reader, and an edit-only module belongs to the
134
135
  // session being left.
135
- view: resolveModules(params, false, { isDraftMode, hasSap, canEdit, templateSource: templateSource() }),
136
+ view: resolveModules(params, false, { isDraftMode, hasSap, hasVariants, canEdit, templateSource: templateSource() }),
136
137
  loaded,
137
138
  modules: MODULES,
138
- attach: attachPluginMember,
139
+ // A live entry attaches the edit plugins after boot: clay.plugins names them from then on.
140
+ attach: (path, mod) => {
141
+ attachPluginMember(path, mod);
142
+ const name = PLUGIN_NAMES[path];
143
+ const named = window.clay.plugins;
144
+ if (name && Array.isArray(named) && !named.includes(name)) window.clay.plugins = Object.freeze([...named, name]);
145
+ },
139
146
  attachEditCore,
140
147
  announce,
141
148
  loadEditHints: loadEditHints ? editHints(loadEditHints, manifest, editing, hasSapJs) : null,
@@ -146,6 +153,9 @@ export async function boot(base, params, readyResolve, manifest = null, suppleme
146
153
  // leases before a plugin can take input. A reader returns at the first line.
147
154
  await loaded["core/edit-mode.js"].activateInitialMode();
148
155
  window.clay.plugins = Object.freeze([...plan.plugins, ...plan.late].map((path) => PLUGIN_NAMES[path]));
156
+ // What an edit session here would load, so view-mode chrome can offer an edit-only feature.
157
+ const editPlan = isEditMode ? plan : resolveModules(params, true, { isDraftMode, hasSap, hasVariants, canEdit, templateSource: templateSource() });
158
+ window.clay.editPlugins = Object.freeze([...editPlan.plugins, ...editPlan.late].map((path) => PLUGIN_NAMES[path]));
149
159
 
150
160
  for (const path of plan.plugins) {
151
161
  // A plugin that throws on evaluation degrades to absent. Unguarded, its
@@ -54,6 +54,7 @@ export function installAdoption({
54
54
  notice = () => {},
55
55
  snapshot = () => null,
56
56
  mergeTagless = false,
57
+ seeded = false,
57
58
  writerReady = () => Promise.resolve(),
58
59
  adopted = () => {},
59
60
  hold = suspendAutosave,
@@ -216,7 +217,7 @@ export function installAdoption({
216
217
  }
217
218
 
218
219
  doc.addEventListener("clay:sync-applied", onFrame);
219
- if (source) run(source, true);
220
+ if (source && !seeded) run(source, true);
220
221
 
221
222
  return {
222
223
  // Resolves once everything queued so far has been attempted, with the boot
@@ -54,6 +54,12 @@ const caretElement = (doc, name, color) => {
54
54
 
55
55
  const labelOf = (person) => (typeof person.name === "string" && person.name.trim() ? person.name.trim() : "Someone");
56
56
 
57
+ let barrier = false;
58
+ if (typeof document !== "undefined") {
59
+ document.addEventListener("clay:presence-quiesce", () => { barrier = true; });
60
+ document.addEventListener("clay:presence-resume", () => { barrier = false; });
61
+ }
62
+
57
63
  /**
58
64
  * The decorations one snapshot produces in one document.
59
65
  *
@@ -104,14 +110,20 @@ export function createPageAwareness({ binding, view, store = presenceStore, epoc
104
110
  styleWithLegacyId(STYLE_ID, STYLE_CSS, STYLE_ID, ["editor-ui"]);
105
111
  const doc = view.dom.ownerDocument;
106
112
  let disposed = false;
107
- let focused = true;
113
+ // The exit's barrier, and the clear that came with it: while it runs this tab
114
+ // publishes nothing, so a caret move or a doc change during the barrier cannot put
115
+ // a target back the session is about to release.
116
+ let quiesced = barrier;
117
+ // Focus is a fact about the document, not a guess: an editor mounted in a background
118
+ // tab is not focused, and publishing a target for it would claim a caret nobody holds.
119
+ let focused = typeof view.hasFocus === "function" ? view.hasFocus() === true : view.dom.contains(doc.activeElement);
108
120
  let pending = null;
109
121
  const isComposing = () => (composing ? composing() === true : view.composing === true);
110
122
 
111
123
  const supported = () => store.getSnapshot().pageAwareness !== false;
112
124
 
113
125
  const publish = (state = view.state) => {
114
- if (disposed) return;
126
+ if (disposed || quiesced) return;
115
127
  if (!supported() || !focused || isComposing()) {
116
128
  store.setTarget(null);
117
129
  return;
@@ -167,6 +179,19 @@ export function createPageAwareness({ binding, view, store = presenceStore, epoc
167
179
  schedule();
168
180
  });
169
181
 
182
+ // The edit participant reaches these, so the page's target goes quiet with the
183
+ // sender during the exit's barrier and comes back with it on a refused exit.
184
+ const quiesce = () => {
185
+ if (disposed) return;
186
+ quiesced = true;
187
+ store.setTarget(null);
188
+ };
189
+ const resume = () => {
190
+ if (disposed) return;
191
+ quiesced = false;
192
+ publish();
193
+ };
194
+
170
195
  const onFocus = () => { focused = true; publish(); schedule(); };
171
196
  const onBlur = () => { focused = false; store.setTarget(null); };
172
197
  const onHide = () => { store.setTarget(null); };
@@ -176,6 +201,8 @@ export function createPageAwareness({ binding, view, store = presenceStore, epoc
176
201
  view.dom.addEventListener("blur", onBlur);
177
202
  view.dom.addEventListener("compositionend", onCompositionEnd);
178
203
  doc.addEventListener("pagehide", onHide);
204
+ doc.addEventListener("clay:presence-quiesce", quiesce);
205
+ doc.addEventListener("clay:presence-resume", resume);
179
206
  view.updateState(view.state.reconfigure({ plugins: [...view.state.plugins, plugin] }));
180
207
  publish();
181
208
  schedule();
@@ -184,6 +211,8 @@ export function createPageAwareness({ binding, view, store = presenceStore, epoc
184
211
  epoch,
185
212
  refresh,
186
213
  plugin,
214
+ quiesce,
215
+ resume,
187
216
  get decorations() {
188
217
  return awarenessKey.getState(view.state) ?? DecorationSet.empty;
189
218
  },
@@ -199,6 +228,8 @@ export function createPageAwareness({ binding, view, store = presenceStore, epoc
199
228
  view.dom.removeEventListener("blur", onBlur);
200
229
  view.dom.removeEventListener("compositionend", onCompositionEnd);
201
230
  doc.removeEventListener("pagehide", onHide);
231
+ doc.removeEventListener("clay:presence-quiesce", quiesce);
232
+ doc.removeEventListener("clay:presence-resume", resume);
202
233
  store.setTarget(null);
203
234
  store.releaseTarget();
204
235
  if (!view.isDestroyed) {
@@ -8,7 +8,8 @@ import { REMOTE, newId } from "./model.js";
8
8
  import { safeHref, safeSrc } from "./link.js";
9
9
  import { flatten, runsOf, unflatten, unitsOf } from "./inline.js";
10
10
  import { OPAQUE_MARK, opaqueMark } from "./format-codec.js";
11
- import { DEFAULTS, MARKS, OPAQUE, envelopeAttrs, envelopeRecord, marksAttrs, marksEntries } from "./projection.js";
11
+ import { NATIVE_NAMES, nativeAttrs } from "./format-native.js";
12
+ import { DEFAULTS, MARKS, OPAQUE, envelopeAttrs, envelopeRecord, marksAttrs, marksEntries, toggleLevel } from "./projection.js";
12
13
 
13
14
  const alignOf = (a) => (a === "left" || a === "right" ? a : null);
14
15
 
@@ -36,7 +37,9 @@ const asStep = (dispatch) => dispatch && ((tr) => dispatch(history.closeHistory(
36
37
  // hidden some toggle above it is closed
37
38
  // subEnd index of the last block of its subtree (itself when it has none)
38
39
  // A subtree is always contiguous, so it is the range [ents[i].at, ents[subEnd].end].
39
- export function outline(doc) {
40
+ // `reveal` is a set of toggle ids a link opened for this reader only: a revealed
41
+ // toggle reads as open here, and its saved `open` is untouched.
42
+ export function outline(doc, reveal = null) {
40
43
  const ents = [];
41
44
  const toggleAt = new Map();
42
45
  let run = [];
@@ -52,8 +55,9 @@ export function outline(doc) {
52
55
  if (run.length) up = run[run.length - 1];
53
56
  run.push(i);
54
57
  } else run = [];
55
- const hidden = t >= 0 && (ents[t].hidden || !ents[t].node.attrs.open);
56
- ents.push({ node, at, end: at + node.nodeSize, i, up, depth: t >= 0 ? ents[t].depth + 1 : 0, hidden, subEnd: i });
58
+ const open = isToggle(node) && (node.attrs.open || !!reveal?.has(node.attrs.id));
59
+ const hidden = t >= 0 && (ents[t].hidden || !ents[t].open);
60
+ ents.push({ node, at, end: at + node.nodeSize, i, up, depth: t >= 0 ? ents[t].depth + 1 : 0, hidden, open, subEnd: i });
57
61
  if (isToggle(node) && node.attrs.id) toggleAt.set(node.attrs.id, i);
58
62
  });
59
63
  for (let j = ents.length - 1; j >= 0; j--) {
@@ -130,11 +134,19 @@ export function structureFixes(doc, oldDoc = null) {
130
134
  }
131
135
 
132
136
  export function structurePlugin() {
137
+ const valid = new WeakSet();
133
138
  return new Plugin({
134
139
  appendTransaction(trs, old, next) {
135
140
  if (!trs.some((tr) => tr.docChanged && !tr.getMeta(REMOTE))) return null;
141
+ if (valid.has(old.doc) && sameStructure(old.doc, next.doc)) {
142
+ valid.add(next.doc);
143
+ return null;
144
+ }
136
145
  const fixes = structureFixes(next.doc, old.doc);
137
- if (!fixes.length) return null;
146
+ if (!fixes.length) {
147
+ valid.add(next.doc);
148
+ return null;
149
+ }
138
150
  const tr = next.tr;
139
151
  for (const f of fixes) tr.setNodeMarkup(f.at, null, f.attrs);
140
152
  return tr;
@@ -642,9 +654,22 @@ export function backspaceBodyBlock(st, dispatch) {
642
654
 
643
655
  // ---- open / closed ----------------------------------------------------------
644
656
 
657
+ // A reveal is edit-mode state, never a document attribute: the toggle ids a followed link
658
+ // opened live in the collapse plugin's own state, so nothing is saved, no undo step is
659
+ // made and no peer is told.
660
+ export const REVEAL = "page-reveal";
661
+
662
+ export function revealToggles(tr, ids) {
663
+ return ids.length ? tr.setMeta(REVEAL, { add: ids }) : tr;
664
+ }
665
+
666
+ // A toggle the person opens or closes themselves drops any reveal a link left on it:
667
+ // the override never outlives the decision it saved them.
645
668
  export function setOpen(st, ti, open, ents = outline(st.doc)) {
646
669
  const e = ents[ti];
647
- return st.tr.setNodeMarkup(e.at, null, { ...e.node.attrs, open }).setMeta("addToHistory", false);
670
+ return st.tr.setNodeMarkup(e.at, null, { ...e.node.attrs, open })
671
+ .setMeta("addToHistory", false)
672
+ .setMeta(REVEAL, { clear: [e.node.attrs.id] });
648
673
  }
649
674
 
650
675
  export function toggleOpenAtSelection(st, dispatch) {
@@ -661,7 +686,7 @@ export function toggleOpenAtSelection(st, dispatch) {
661
686
  export function hiddenRanges(ents) {
662
687
  const out = [];
663
688
  for (const e of ents) {
664
- if (!isToggle(e.node) || e.hidden || e.node.attrs.open || e.subEnd === e.i) continue;
689
+ if (!isToggle(e.node) || e.hidden || e.open || e.subEnd === e.i) continue;
665
690
  out.push({ line: e.at + 1, sumEnd: e.end - 1, from: e.end, to: ents[e.subEnd].end });
666
691
  }
667
692
  return out;
@@ -758,35 +783,55 @@ export function sameStructure(a, b) {
758
783
  return true;
759
784
  }
760
785
 
761
- function collapseState(doc) {
762
- const ents = outline(doc);
763
- return { decos: decorationsFor(doc, ents), ranges: hiddenRanges(ents) };
786
+ const NO_REVEAL = new Set();
787
+
788
+ function collapseState(doc, reveal = NO_REVEAL) {
789
+ const ents = outline(doc, reveal);
790
+ return { decos: decorationsFor(doc, ents), ranges: hiddenRanges(ents), reveal };
764
791
  }
765
792
 
793
+ // The block decorations of a long document in one linear pass. ProseMirror's
794
+ // DecorationSet scans every top-level node decoration for each child the view
795
+ // visits and each decoration it maps, which is quadratic in blocks; this set
796
+ // holds only node decorations at the top and finds a toggle's widget by binary
797
+ // search. It is built directly in document order instead of through create().
798
+ let OutlineSet = null;
799
+ const outlineSet = () => OutlineSet ??= class extends pmView.DecorationSet {
800
+ forChild(offset, node) {
801
+ if (node.isLeaf) return pmView.DecorationSet.empty;
802
+ const c = this.children;
803
+ let lo = 0, hi = c.length / 3;
804
+ while (lo < hi) { const mid = (lo + hi) >> 1; if (c[mid * 3] < offset) lo = mid + 1; else hi = mid; }
805
+ return lo * 3 < c.length && c[lo * 3] === offset ? c[lo * 3 + 2] : pmView.DecorationSet.empty;
806
+ }
807
+ };
808
+
766
809
  export function decorationsFor(doc, ents = outline(doc)) {
767
810
  const { Decoration, DecorationSet } = pmView;
768
811
  const markers = orderedMarkers(ents);
769
- const decos = [];
812
+ const local = [], children = [];
770
813
  for (const e of ents) {
771
814
  const attrs = {};
772
815
  if (e.hidden) { attrs.class = "page-hidden"; attrs["aria-hidden"] = "true"; }
773
816
  if (e.depth) attrs.style = `--page-depth:${e.depth}`;
774
817
  if (markers.has(e.i)) attrs["data-marker"] = markers.get(e.i);
775
- if (isToggle(e.node)) attrs["aria-expanded"] = e.node.attrs.open ? "true" : "false";
776
- if (Object.keys(attrs).length) decos.push(Decoration.node(e.at, e.end, attrs));
818
+ if (isToggle(e.node)) attrs["aria-expanded"] = e.open ? "true" : "false";
819
+ if (Object.keys(attrs).length) local.push(Decoration.node(e.at, e.end, attrs));
777
820
  if (isToggle(e.node) && !e.hidden) {
778
- decos.push(Decoration.widget(e.at + 1, () => disclosure(e.node), { side: -1, ignoreSelection: true, key: `t:${e.node.attrs.id}:${e.node.attrs.open}` }));
821
+ const widget = Decoration.widget(0, () => disclosure(e.node, e.open), { side: -1, ignoreSelection: true, key: `t:${e.node.attrs.id}:${e.open}` });
822
+ children.push(e.at, e.end, new DecorationSet([widget], []));
779
823
  }
780
824
  }
781
- return DecorationSet.create(doc, decos);
825
+ if (!local.length && !children.length) return DecorationSet.empty;
826
+ return new (outlineSet())(local, children);
782
827
  }
783
828
 
784
- function disclosure(node) {
829
+ function disclosure(node, open = node.attrs.open) {
785
830
  const b = document.createElement("button");
786
831
  b.type = "button";
787
832
  b.className = "page-toggle-button";
788
833
  b.contentEditable = "false";
789
- b.setAttribute("aria-label", node.attrs.open ? "Collapse" : "Expand");
834
+ b.setAttribute("aria-label", open ? "Collapse" : "Expand");
790
835
  b.setAttribute("data-toggle", node.attrs.id || "");
791
836
  return b;
792
837
  }
@@ -798,13 +843,15 @@ export function collapsePlugin() {
798
843
  state: {
799
844
  init: (_, s) => collapseState(s.doc),
800
845
  apply(tr, value, old, s) {
846
+ const meta = tr.getMeta(REVEAL);
847
+ if (meta) {
848
+ const reveal = new Set(value.reveal);
849
+ for (const id of meta.add ?? []) reveal.add(id);
850
+ for (const id of meta.clear ?? []) reveal.delete(id);
851
+ return collapseState(s.doc, reveal);
852
+ }
801
853
  if (!tr.docChanged) return value;
802
- if (!sameStructure(old.doc, s.doc)) return collapseState(s.doc);
803
- const m = tr.mapping;
804
- return {
805
- decos: value.decos.map(m, s.doc),
806
- ranges: value.ranges.map((r) => ({ line: m.map(r.line, -1), sumEnd: m.map(r.sumEnd), from: m.map(r.from), to: m.map(r.to, -1) })),
807
- };
854
+ return collapseState(s.doc, value.reveal);
808
855
  },
809
856
  },
810
857
  appendTransaction(trs, old, next) {
@@ -1001,7 +1048,12 @@ export function flattenPastedHTML(html, doc = globalThis.document) {
1001
1048
  t.setAttribute("data-page-toggle", k);
1002
1049
  if (child.hasAttribute("open")) t.setAttribute("data-open", "true");
1003
1050
  if (parentKey) t.setAttribute("data-page-parent", parentKey);
1004
- if (summary) t.append(...summary.childNodes);
1051
+ // A toggle heading saves as a heading in its summary: keep the level, paste its text.
1052
+ const head = summary?.querySelector(":scope > h1, :scope > h2, :scope > h3");
1053
+ if (head) {
1054
+ t.setAttribute("data-level", head.tagName[1]);
1055
+ t.append(...head.childNodes);
1056
+ } else if (summary) t.append(...summary.childNodes);
1005
1057
  target.appendChild(t);
1006
1058
  for (const c of [...child.childNodes]) if (c !== summary) { const box = doc.createElement("div"); box.appendChild(c); walk(box, k, target); }
1007
1059
  continue;
@@ -1042,6 +1094,8 @@ export function flattenPastedHTML(html, doc = globalThis.document) {
1042
1094
  if (parent) for (const piece of pieces) piece.setAttribute("data-page-parent", parent);
1043
1095
  child.replaceWith(...pieces);
1044
1096
  }
1097
+ const sliced = root.querySelector("[data-pm-slice]");
1098
+ if (sliced) out.firstElementChild?.setAttribute("data-pm-slice", sliced.dataset.pmSlice);
1045
1099
  return out.innerHTML;
1046
1100
  }
1047
1101
 
@@ -1079,14 +1133,19 @@ const pastedOpaqueMarks = (dom) => {
1079
1133
  try { return { entries: marksEntries(dom.getAttribute(MARKS)) }; } catch { return false; }
1080
1134
  };
1081
1135
 
1136
+ // clipboard-shape.js: lists and tables on the clipboard.
1137
+ let shape = null;
1138
+ export const loadClipboardShape = () => import("./clipboard-shape.js").then((mod) => { shape = mod; }, () => {});
1139
+
1082
1140
  // The clipboard parser: the schema has toDOM only, so paste gets its own rules.
1083
1141
  export const clipboardParser = new model.DOMParser(parseSchema, [
1142
+ { tag: "table", node: "table", getAttrs: (d) => shape?.table(d) ?? false, getContent: (d) => shape.rows(d) },
1084
1143
  { tag: `div[${OPAQUE}]`, node: "opaque_block", getAttrs: pastedOpaque, getContent: pastedOpaqueContent },
1085
1144
  { tag: `span[${MARKS}]`, mark: OPAQUE_MARK, getAttrs: pastedOpaqueMarks },
1086
1145
  { 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) }) },
1087
1146
  { 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) }) },
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) }) },
1089
- { tag: "div.page-toggle", node: "toggle", getAttrs: (d) => ({ id: d.getAttribute("data-id"), open: d.getAttribute("data-open") === "true", parent: null }) },
1147
+ { tag: "div[data-page-toggle]", node: "toggle", getAttrs: (d) => ({ id: d.getAttribute("data-page-toggle"), open: d.getAttribute("data-open") === "true", parent: parentAttr(d), level: toggleLevel(Number(d.getAttribute("data-level"))) }) },
1148
+ { tag: "div.page-toggle", node: "toggle", getAttrs: (d) => ({ id: d.getAttribute("data-id"), open: d.getAttribute("data-open") === "true", parent: null, level: toggleLevel(Number(d.getAttribute("data-level"))) }) },
1090
1149
  { tag: "h1", node: "heading", getAttrs: (d) => ({ level: 1, parent: parentAttr(d) }) },
1091
1150
  { tag: "h2", node: "heading", getAttrs: (d) => ({ level: 2, parent: parentAttr(d) }) },
1092
1151
  { tag: "h3", node: "heading", getAttrs: (d) => ({ level: 3, parent: parentAttr(d) }) },
@@ -1118,6 +1177,10 @@ export const clipboardParser = new model.DOMParser(parseSchema, [
1118
1177
  { tag: "del", mark: "strike" },
1119
1178
  { tag: "code", mark: "code" },
1120
1179
  { tag: "a[href]", mark: "link", getAttrs: (d) => { const href = safeHref(d.getAttribute("href")); return href ? { href } : false; } },
1180
+ { tag: "blockquote", node: "quote", getAttrs: (d) => ({ parent: parentAttr(d) }) },
1181
+ { tag: "u", mark: "underline" },
1182
+ { tag: "mark", mark: "highlight", getAttrs: (d) => nativeAttrs("highlight", { color: d.getAttribute("data-color") }) ?? false },
1183
+ { tag: "span[data-color]", mark: "text_color", getAttrs: (d) => nativeAttrs("text_color", { color: d.getAttribute("data-color") }) ?? false },
1121
1184
  ]);
1122
1185
 
1123
1186
  // Every pasted block arrives with no id and no pos (identityPlugin mints and
@@ -1168,6 +1231,7 @@ class ClipboardSerializer extends model.DOMSerializer {
1168
1231
  if (isToggle(n) && n.attrs.id) dom.setAttribute("data-page-toggle", n.attrs.id);
1169
1232
  if (n.type === N.image && n.attrs.width) dom.setAttribute("data-width", String(n.attrs.width));
1170
1233
  });
1234
+ shape?.copy(fragment, doms);
1171
1235
  return out;
1172
1236
  }
1173
1237
  }
@@ -1202,11 +1266,42 @@ function pasteIntoClosedSummary(view, _event, slice) {
1202
1266
  return true;
1203
1267
  }
1204
1268
 
1269
+ // The four formats pasted in from elsewhere are floor-1 content too: a slice
1270
+ // that holds one asks the gate before it is inserted, and text waits in the
1271
+ // clipboard rather than being dropped when the answer is a refusal.
1272
+ function writesFormat(slice) {
1273
+ let found = false;
1274
+ slice?.content?.descendants((node) => {
1275
+ if (found) return false;
1276
+ if (node.type === N.quote) found = true;
1277
+ else if (node.isText && node.marks.some((mark) => NATIVE_NAMES.has(mark.type.name))) found = true;
1278
+ return !found;
1279
+ });
1280
+ return found;
1281
+ }
1282
+
1283
+ function pasteFormat(view, event, slice) {
1284
+ const from = view.state.selection.from, to = view.state.selection.to;
1285
+ const before = view.state.doc;
1286
+ const plain = () => {
1287
+ const text = slice.content.textBetween(0, slice.content.size, "\n", " ");
1288
+ if (!view.isDestroyed && text && view.state.doc === before) view.dispatch(view.state.tr.insertText(text, from, to));
1289
+ return false;
1290
+ };
1291
+ import("./format-native-commands.js").then(({ pasteFormats }) => pasteFormats(view, slice, from, to), plain);
1292
+ return true;
1293
+ }
1294
+
1205
1295
  export function pastePlugin() {
1206
1296
  return new Plugin({
1297
+ view() { loadClipboardShape(); return {}; },
1207
1298
  props: {
1208
- handlePaste: pasteIntoClosedSummary,
1299
+ handlePaste(view, event, slice) {
1300
+ if (writesFormat(slice)) return pasteFormat(view, event, slice);
1301
+ return pasteIntoClosedSummary(view, event, slice);
1302
+ },
1209
1303
  clipboardSerializer,
1304
+ clipboardTextSerializer: (slice) => shape?.text(slice),
1210
1305
  transformPastedHTML: (html) => flattenPastedHTML(html),
1211
1306
  clipboardParser,
1212
1307
  clipboardTextParser: (text, _$ctx, plain) => (plain ? null : parseListText(text)),
@@ -1,17 +1,3 @@
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.
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.
5
3
  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
- }
@@ -14,7 +14,7 @@ export { installAdoption, ADOPTION_TIMEOUT_MS } from "./adopt-boot.js";
14
14
  export { classifyFile, readBack } from "./adopt.js";
15
15
  export { mergePageModels } from "./merge.js";
16
16
  export { canon } from "./projection.js";
17
- export { renderFromTags } from "./save-projection.js";
17
+ export { renderFromTags, acceptedPageState } from "./save-projection.js";
18
18
  export { blocksFromYValue } from "./ybind/decode.js";
19
19
  export { createYPageBinding } from "./ybind/binding.js";
20
20
  export { yValueOf } from "./ybind/provider.js";
@@ -0,0 +1,29 @@
1
+ import { schema } from "./schema.js";
2
+ import { orderedIds } from "./model.js";
3
+
4
+ // The tables a chart can be drawn from: a top-level table with a labels column
5
+ // and at least one more to plot. The insert menu reads this without the chart module itself, which only
6
+ // a document that takes a chart loads.
7
+ export function chartTables(doc) {
8
+ const tables = [];
9
+ doc.forEach((node) => { if (node.type === schema.nodes.table && orderedIds(node.attrs.cols).length > 1) tables.push(node); });
10
+ return tables;
11
+ }
12
+
13
+ // A table's name for a person: the heading before it with no other table in
14
+ // between, else its place among the tables, so a chart and the picker name it
15
+ // without a stored title.
16
+ export function tableName(doc, tableId) {
17
+ let name = "";
18
+ let count = 0;
19
+ let found = null;
20
+ doc.forEach((node) => {
21
+ if (found !== null) return;
22
+ if (node.type === schema.nodes.heading) name = node.textContent;
23
+ if (node.type !== schema.nodes.table) return;
24
+ count += 1;
25
+ if (node.attrs.id === tableId) found = name || `Table ${count}`;
26
+ name = "";
27
+ });
28
+ return found;
29
+ }