@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
@@ -29,6 +29,9 @@
29
29
  // lands between `capabilities()` and `activate()` is refused rather than missed.
30
30
 
31
31
  import { PAGE_FORMAT, PAGE_FORMAT_EXTENSION, PAGE_FORMAT_UPGRADE, formatCapability, notePageFormatFloor, pageFormatDecision, pageFormatFloor } from "./format-level.js";
32
+ import { registerUnsavedState } from "../lib/unsaved-state.js";
33
+
34
+ const PENDING_COMMAND = "page-format-command";
32
35
 
33
36
  const UPGRADE_MESSAGE = "This document uses a newer page format; update the editor in the other tabs to keep writing";
34
37
 
@@ -46,6 +49,10 @@ export function createFormatGate({
46
49
  let floor = 0;
47
50
  let pending = null;
48
51
  let lanePending = false;
52
+ let laneReceipt = null;
53
+ let laneAsked = false;
54
+ let laneJoin = Promise.resolve(false);
55
+ let laneJoining = null;
49
56
  let disposed = false;
50
57
 
51
58
  // The floor this gate knows the document at. A save or a room answer can tell
@@ -54,9 +61,22 @@ export function createFormatGate({
54
61
  function note(level) {
55
62
  floor = Math.max(floor, formatCapability(level));
56
63
  notePageFormatFloor(floor);
64
+ if (floor >= PAGE_FORMAT) requestLane();
57
65
  return floor;
58
66
  }
59
67
 
68
+ // A raised floor is one lane switch per tab, however this tab learned of it:
69
+ // its own accepted save (activateNow joins with that receipt), another tab's
70
+ // write that came in with the document, or a relay's meta. A switch the lane
71
+ // has not accepted yet stays pending, and the retry path covers it.
72
+ function requestLane() {
73
+ if (laneAsked || disposed) return laneJoin;
74
+ laneAsked = true;
75
+ lanePending = true;
76
+ laneJoin = joinLane(null);
77
+ return laneJoin;
78
+ }
79
+
60
80
  async function read() {
61
81
  try {
62
82
  const answer = await host.read();
@@ -88,7 +108,10 @@ export function createFormatGate({
88
108
  if (disposed) return { status: "disposed", retry: false };
89
109
  if (!(await supportedHere())) return { status: "unsupported-host", code: "page-format-unsupported", extension: PAGE_FORMAT_EXTENSION };
90
110
  await read();
91
- if (floor >= PAGE_FORMAT) return { status: "active", floor };
111
+ if (floor >= PAGE_FORMAT) {
112
+ await requestLane();
113
+ return { status: "active", floor };
114
+ }
92
115
  const admitted = await admittance();
93
116
  const early = pageFormatDecision({ floor, required: PAGE_FORMAT, capability, admitted });
94
117
  if (!early.ok) return upgrade(early.minimum);
@@ -106,28 +129,45 @@ export function createFormatGate({
106
129
  if (disposed) {
107
130
  // The save was accepted, so the document is at floor 1 either way; this
108
131
  // tab is gone and must not act on it.
132
+ laneAsked = true;
109
133
  note(PAGE_FORMAT);
110
134
  return { status: "disposed", retry: false };
111
135
  }
136
+ laneAsked = true;
112
137
  note(PAGE_FORMAT);
113
138
  lanePending = true;
114
- await joinLane(answer.receipt ?? null);
115
- return { status: "active", floor, receipt: answer.receipt ?? null };
139
+ laneReceipt = answer.receipt ?? null;
140
+ await joinLane(laneReceipt);
141
+ return { status: "active", floor, receipt: laneReceipt };
116
142
  }
117
143
 
118
144
  // Joining the new lane scope happens after the receipt is accepted, and only
119
145
  // then: an interrupted switch leaves this tab on floor 0's scope with every
120
- // store intact and the switch still pending, never a lane it cannot cover.
146
+ // store intact and the switch still pending, never a lane it cannot cover. The
147
+ // receipt is kept, so the retry of a refused switch is covered by the same write.
121
148
  async function joinLane(receipt) {
122
149
  if (!lane || !lanePending) return false;
150
+ if (laneJoining) return laneJoining;
151
+ const covering = receipt ?? laneReceipt;
152
+ laneJoining = (async () => {
153
+ try {
154
+ // The receipt is what says the pending work went with an accepted write.
155
+ const result = await lane.switchScope(PAGE_FORMAT, { covered: Boolean(covering), receipt: covering });
156
+ // "already" is the lane answering that this tab is on that scope: the
157
+ // join held, and there is nothing left pending.
158
+ if (result && (result.status === "switched" || result.status === "already")) {
159
+ lanePending = false;
160
+ laneReceipt = null;
161
+ return true;
162
+ }
163
+ } catch {}
164
+ return false;
165
+ })();
123
166
  try {
124
- const result = await lane.switchTo({ floor: PAGE_FORMAT, receipt });
125
- if (result && result.status === "switched") {
126
- lanePending = false;
127
- return true;
128
- }
129
- } catch {}
130
- return false;
167
+ return await laneJoining;
168
+ } finally {
169
+ laneJoining = null;
170
+ }
131
171
  }
132
172
 
133
173
  function activate() {
@@ -150,16 +190,25 @@ export function createFormatGate({
150
190
  // same attempt, and a retry after a failure starts a new one.
151
191
  activate,
152
192
  // A new-format command: capture what it applies to, ask the gate, and apply
153
- // only when activation succeeded and nothing moved underneath it.
193
+ // only when activation succeeded and nothing moved underneath it. While it waits on
194
+ // activation the work is unsaved and belongs to the person who asked for it: the
195
+ // exit asks the unsaved-state registry, and this command answers to its own name
196
+ // until it applies or fails, so an exit is refused rather than committing and
197
+ // disposing the editor underneath it.
154
198
  async run(command) {
155
199
  const stamp = { revision: revision(), installed: installed() };
156
- const result = await activate();
157
- if (result.status !== "active") return result;
158
- if (disposed) return { status: "disposed", retry: false };
159
- if (revision() !== stamp.revision || installed() !== stamp.installed) {
160
- return { status: "retry", code: "format-gate-stale", retry: true };
200
+ const unsaved = registerUnsavedState({ id: PENDING_COMMAND, isPending: () => true });
201
+ try {
202
+ const result = await activate();
203
+ if (result.status !== "active") return result;
204
+ if (disposed) return { status: "disposed", retry: false };
205
+ if (revision() !== stamp.revision || installed() !== stamp.installed) {
206
+ return { status: "retry", code: "format-gate-stale", retry: true };
207
+ }
208
+ return { status: "applied", floor, result: await command?.apply?.(stamp) };
209
+ } finally {
210
+ unsaved.dispose();
161
211
  }
162
- return { status: "applied", floor, result: await command?.apply?.(stamp) };
163
212
  },
164
213
  // Retry what is still pending: a lane switch whose receipt is in hand, or an
165
214
  // activation that failed on the wire. Unsaved work is never part of this.
@@ -173,6 +222,7 @@ export function createFormatGate({
173
222
  dispose() {
174
223
  disposed = true;
175
224
  lanePending = false;
225
+ laneReceipt = null;
176
226
  return { status: "disposed" };
177
227
  },
178
228
  };
@@ -0,0 +1,71 @@
1
+ // The production host adapter behind the format gate: the floor the document is
2
+ // already at, the host's own admission answer, and the one explicit save that
3
+ // raises it.
4
+ //
5
+ // The host is the authority, so `capabilities()` answers nothing here: the editors
6
+ // admitted to the document are checked under its admission/write lock when the save
7
+ // carrying the marker arrives, which is the check no registration can race. A save
8
+ // refused there reports `page-format-upgrade-required` like any other refusal, and
9
+ // this tab stays at the floor it already knew.
10
+ //
11
+ // Nothing is noted before the host answers: `notePageFormatFloor` runs when the gate
12
+ // accepts the activation, so a refusal leaves every later save at floor 0.
13
+
14
+ import { PAGE_FORMAT, PAGE_FORMAT_META, PAGE_FORMAT_UPGRADE, knownPageFormatFloor, pageFormatFloor } from "./format-level.js";
15
+ import { hostPageFormat } from "../core/host-meta.js";
16
+
17
+ const FAILED = "page-format-activation-failed";
18
+
19
+ // The document's own marker, as it was served. A tab that joined a document already
20
+ // at floor 1 must not ask to activate it again.
21
+ function servedFloor() {
22
+ const tag = document.querySelector(`meta[name="${PAGE_FORMAT_META}"]`);
23
+ return tag ? pageFormatFloor(tag.outerHTML) : 0;
24
+ }
25
+
26
+ // The editor's own explicit save, fetched when a floor is really raised: a tab that
27
+ // never uses a newer format never downloads the lane to raise one.
28
+ const savePage = async (options) => (await import("../core/save.js")).savePageForce(() => {}, options);
29
+
30
+ // A forced save asked for while another is on the wire is skipped rather than queued.
31
+ // The activation is the person's own request, so it waits for the save ahead of it to
32
+ // settle and asks again, instead of answering "try again" for a save it never sent.
33
+ const SKIPPED_ATTEMPTS = 4;
34
+ const SKIPPED_WAIT_MS = 10000;
35
+ const skippedBehindSave = (result) => result?.ok === false && result?.msgType === "skipped" && /in progress/i.test(result?.msg ?? "");
36
+ const saveSettles = async () => {
37
+ const { isSaveInProgress } = await import("../core/save-core.js");
38
+ const until = Date.now() + SKIPPED_WAIT_MS;
39
+ while (isSaveInProgress() && Date.now() < until) await new Promise((r) => setTimeout(r, 50));
40
+ };
41
+
42
+ export function createFormatHost({
43
+ save = savePage,
44
+ settles = saveSettles,
45
+ supported = () => hostPageFormat().then((floor) => floor === PAGE_FORMAT),
46
+ } = {}) {
47
+ return {
48
+ read() {
49
+ return { floor: Math.max(knownPageFormatFloor(), servedFloor()), revision: null };
50
+ },
51
+ capabilities() {
52
+ return [];
53
+ },
54
+ supported,
55
+ async activate({ floor = PAGE_FORMAT } = {}) {
56
+ let result;
57
+ try {
58
+ result = await save({ pageFormatFloor: floor });
59
+ for (let attempt = 1; attempt < SKIPPED_ATTEMPTS && skippedBehindSave(result); attempt += 1) {
60
+ await settles();
61
+ result = await save({ pageFormatFloor: floor });
62
+ }
63
+ } catch (error) {
64
+ return { accepted: false, code: error?.code ?? FAILED };
65
+ }
66
+ if (result?.ok) return { accepted: true, revision: null, receipt: result.receipt ?? result.etag ?? null };
67
+ if (result?.code === PAGE_FORMAT_UPGRADE) return { accepted: false, code: PAGE_FORMAT_UPGRADE };
68
+ return { accepted: false, code: result?.code ?? FAILED };
69
+ },
70
+ };
71
+ }
@@ -0,0 +1,158 @@
1
+ // The commands behind the four formats this release adds — underline, text color,
2
+ // highlight and the quote block — and the rows the More menu offers them by.
3
+ //
4
+ // Nothing here is on the ready path. Every caller loads this module the first
5
+ // time one of the commands runs, so reading and drawing an existing document
6
+ // never fetches a transaction, a palette or a refusal notice.
7
+ //
8
+ // Every command that writes one of them goes through runFormat, which is the
9
+ // editor's own format gate: nothing is applied and no descriptor is written
10
+ // unless the gate answered "applied".
11
+ import { history } from "../vendor/prosemirror.vendor.js";
12
+ import { schema } from "./schema.js";
13
+ import { PALETTE, markable } from "./format-native.js";
14
+ import { announce } from "./announce.js";
15
+ import toast from "../ui/toast-lazy.js";
16
+ import { openPageMenu } from "./chrome.js";
17
+
18
+ // blocks.js's STEP, read here rather than imported from it: this module is part
19
+ // of the projection's graph, and blocks.js reads the projection's constants as it
20
+ // loads. A command of ours is one undo item by itself, like every other command.
21
+ const STEP = "page-step";
22
+ const asStep = (tr) => history.closeHistory(tr).setMeta(STEP, true);
23
+
24
+ const holds = (mark, attrs) => !!mark && Object.keys(mark.type.attrs).every((name) => mark.attrs[name] === attrs[name]);
25
+
26
+ function toggle(view, type, attrs) {
27
+ const st = view.state;
28
+ const { from, to, empty, $from } = st.selection;
29
+ const tr = st.tr;
30
+ if (empty) {
31
+ if (holds(type.isInSet(st.storedMarks || $from.marks()), attrs)) tr.removeStoredMark(type);
32
+ else tr.addStoredMark(type.create(attrs));
33
+ } else {
34
+ let all = true;
35
+ st.doc.nodesBetween(from, to, (node) => {
36
+ if (all && node.isText && !holds(type.isInSet(node.marks), attrs)) all = false;
37
+ });
38
+ if (all) tr.removeMark(from, to, type);
39
+ else tr.addMark(from, to, type.create(attrs));
40
+ }
41
+ view.dispatch(asStep(tr));
42
+ return true;
43
+ }
44
+
45
+ function remove(view, type) {
46
+ const st = view.state;
47
+ const tr = st.tr;
48
+ if (st.selection.empty) tr.removeStoredMark(type);
49
+ else tr.removeMark(st.selection.from, st.selection.to, type);
50
+ view.dispatch(asStep(tr));
51
+ return true;
52
+ }
53
+
54
+ // The gate, in one place: an editor mounted without one cannot write a floor-1
55
+ // format, and a refusal leaves the content exactly as it was.
56
+ export function runFormat(view, apply) {
57
+ const run = view.runFormatCommand;
58
+ if (typeof run !== "function") return Promise.resolve({ status: "unsupported-host", code: "page-format-unsupported", retry: false });
59
+ return run(apply);
60
+ }
61
+
62
+ // A mark command: toggle it on the selection, or with reset take it off. A
63
+ // selection the mark cannot reach is refused before the gate is asked, so a code
64
+ // block never raises the document's floor.
65
+ export function formatMark(view, name, attrs = {}, { reset = false } = {}) {
66
+ const type = schema.marks[name];
67
+ if (!type) return Promise.resolve({ status: "refused", code: "unsupported-format" });
68
+ if (!view.editable) return Promise.resolve({ status: "refused", code: "not-editable" });
69
+ if (!markable(view.state, type)) return Promise.resolve({ status: "refused", code: "mark-not-allowed" });
70
+ return runFormat(view, () => (reset ? remove(view, type) : toggle(view, type, attrs)));
71
+ }
72
+
73
+ // The four formats pasted in from elsewhere are floor-1 content too: the slice is
74
+ // inserted only once the gate answered applied. A refusal keeps the text and
75
+ // writes no descriptor; a selection that moved while the answer came back takes
76
+ // neither, and neither does a document that changed underneath the paste: the
77
+ // plain fallback would write at a place a peer edit has moved, so the text stays
78
+ // in the clipboard instead.
79
+ export function pasteFormats(view, slice, from, to) {
80
+ const before = view.state.doc;
81
+ const place = () => {
82
+ if (view.isDestroyed) return false;
83
+ const st = view.state;
84
+ if (st.selection.from !== from || st.selection.to !== to) return false;
85
+ view.dispatch(st.tr.replaceSelection(slice).scrollIntoView());
86
+ return true;
87
+ };
88
+ const plain = () => {
89
+ const text = slice.content.textBetween(0, slice.content.size, "\n", " ");
90
+ if (!view.isDestroyed && text && view.state.doc === before) view.dispatch(view.state.tr.insertText(text, from, to));
91
+ return false;
92
+ };
93
+ runFormat(view, place).then((result) => {
94
+ if (result?.status === "applied") return;
95
+ refuse(view, result?.message ?? "That format isn\u2019t available here.");
96
+ plain();
97
+ }, plain);
98
+ }
99
+
100
+ // The typed `" ` shortcut: the paragraph the reader is typing in became a quote,
101
+ // and the space that typed it goes with it. A refusal puts the space back as
102
+ // itself, so the typed character is never swallowed.
103
+ export function quoteFromSpace(plugin, view, { at, from, to }, plain) {
104
+ const convert = () => {
105
+ if (view.isDestroyed) return false;
106
+ const now = view.state;
107
+ const current = now.doc.nodeAt(at);
108
+ if (!current || current.type !== schema.nodes.paragraph || current.textContent !== '"') return plain();
109
+ const tr = now.tr.delete(at + 1, at + 2).setNodeMarkup(at, schema.nodes.quote, current.attrs);
110
+ view.dispatch(asStep(tr.setMeta(plugin, { transform: tr, from, to, text: " " })));
111
+ return true;
112
+ };
113
+ view.runFormatCommand(convert).then((result) => {
114
+ if (result?.status === "applied") return;
115
+ if (result) refuse(view, refusalText(result));
116
+ plain();
117
+ }, plain);
118
+ }
119
+
120
+ // The More menu's rows: the underline command, the palette of a color mark and
121
+ // the notice a refusal raises. The palette is fixed: nine keys and the way back
122
+ // to none, and no arbitrary color picker is offered.
123
+ const CANNOT_MARK = "This text can\u2019t take that format.";
124
+ const refusalText = (result) => (result.status === "upgrade-required" ? result.message : result.code === "mark-not-allowed" ? CANNOT_MARK : "That format isn\u2019t available here.");
125
+
126
+ // A refusal is said to a screen reader and shown, since a sighted reader whose
127
+ // click did nothing otherwise has no way to know why.
128
+ export const refuse = (view, text) => {
129
+ if (view.isDestroyed) return;
130
+ announce(view, text);
131
+ toast(text, "error");
132
+ };
133
+
134
+ const notice = (view, result) => {
135
+ if (result?.status !== "applied") refuse(view, refusalText(result ?? {}));
136
+ };
137
+
138
+ export const runNative = (view, name, attrs = {}, options = undefined) => {
139
+ formatMark(view, name, attrs, options).then((result) => notice(view, result));
140
+ };
141
+
142
+ // A block command from a later format, like the turn into a quote: the write asks
143
+ // the gate first, and a refusal is a notice rather than a change.
144
+ export const gateTurn = (view, turn) => {
145
+ runFormat(view, turn).then((result) => notice(view, result));
146
+ };
147
+
148
+ // The palette of one color mark, as its own short menu: the nine fixed keys and
149
+ // the way back to none.
150
+ export function nativeMenu(view, name, anchor) {
151
+ if (name === "underline") return runNative(view, name);
152
+ const colorLabel = name === "text_color" ? "Text" : "Highlight";
153
+ const noneLabel = name === "text_color" ? "Automatic" : "No highlight";
154
+ return openPageMenu(view, anchor, [
155
+ ...PALETTE.map((color) => ({ key: `${name}_${color}`, label: `${colorLabel}: ${color[0].toUpperCase()}${color.slice(1)}`, icon: "\u25CF", run: (v) => runNative(v, name, { color }) })),
156
+ { key: `${name}_none`, label: noneLabel, icon: "\u00d7", run: (v) => runNative(v, name, {}, { reset: true }) },
157
+ ]);
158
+ }
@@ -0,0 +1,42 @@
1
+ // The four formats this release adds: underline, text color, highlight and the
2
+ // quote block.
3
+ //
4
+ // Each is written as the named descriptor an older runtime already keeps as an
5
+ // opaque mark, `x["text_color",{"color":"red"}]`, so R reads back exactly what F
6
+ // wrote. The palette is fixed and its keys are the only colors a descriptor may
7
+ // name: one naming any other color stays opaque rather than being read as a
8
+ // style, and no command accepts arbitrary CSS.
9
+ //
10
+ // This half is the reading one: what the schema, inline.js and the projection need
11
+ // to read a descriptor back and draw it. The commands that write one are in
12
+ // format-native-commands.js, which a page loads the first time one runs.
13
+ export const NATIVE_NAMES = new Set(["underline", "text_color", "highlight"]);
14
+
15
+ export const PALETTE = ["gray", "brown", "orange", "yellow", "green", "blue", "purple", "pink", "red"];
16
+ const COLORS = new Set(PALETTE);
17
+
18
+ // The descriptor attrs of a native mark, or null when this name and these attrs
19
+ // are not one of the formats this runtime writes.
20
+ export function nativeAttrs(name, attrs) {
21
+ const plain = attrs !== null && typeof attrs === "object" && !Array.isArray(attrs);
22
+ if (name === "underline") return plain && Object.keys(attrs).length === 0 ? {} : null;
23
+ if (name === "text_color" || name === "highlight") {
24
+ const keys = plain ? Object.keys(attrs) : [];
25
+ return keys.length === 1 && keys[0] === "color" && COLORS.has(attrs.color) ? { color: attrs.color } : null;
26
+ }
27
+ return null;
28
+ }
29
+
30
+ // Whether this selection holds text the mark may be added to: a code block takes
31
+ // no marks, and a block selection is not text at all. It stays here, beside the
32
+ // reading half, because the keyboard decides whether Mod-u is its own before any
33
+ // command is loaded.
34
+ export function markable(st, type) {
35
+ const { $from, $to, empty } = st.selection;
36
+ if (empty) return $from.parent.type.allowsMarkType(type);
37
+ let ok = false;
38
+ st.doc.nodesBetween($from.pos, $to.pos, (node, _at, parent) => {
39
+ if (!ok && node.isText && parent?.type.allowsMarkType(type)) ok = true;
40
+ });
41
+ return ok;
42
+ }
@@ -0,0 +1,64 @@
1
+ // The image menu's own actions: the alt text field and the width presets, reached
2
+ // by the chrome's actions menu. Its own module, so a document that never sets
3
+ // either downloads neither.
4
+ import { asStep } from "./blocksel.js";
5
+
6
+ const indexOfId = (doc, id) => {
7
+ let found = -1;
8
+ doc.forEach((n, _, i) => { if (n.attrs.id === id) found = i; });
9
+ return found;
10
+ };
11
+
12
+ export function setImageAttrs(view, id, patch) {
13
+ const st = view.state;
14
+ const index = indexOfId(st.doc, id);
15
+ if (index < 0) return false;
16
+ let at = 0;
17
+ for (let i = 0; i < index; i++) at += st.doc.child(i).nodeSize;
18
+ const node = st.doc.child(index);
19
+ view.dispatch(asStep(st.tr.setNodeMarkup(at, null, { ...node.attrs, ...patch })));
20
+ return true;
21
+ }
22
+
23
+ export function openAltEditor(view, id) {
24
+ const index = indexOfId(view.state.doc, id);
25
+ if (index < 0) return false;
26
+ const shell = view.dom.parentElement;
27
+ const doc = shell.ownerDocument;
28
+ let at = 0;
29
+ for (let i = 0; i < index; i++) at += view.state.doc.child(i).nodeSize;
30
+ const box = doc.createElement("div");
31
+ box.className = "page-link";
32
+ box.dataset.mode = "alt";
33
+ const input = doc.createElement("input");
34
+ input.className = "page-link-input";
35
+ input.type = "text";
36
+ input.value = view.state.doc.child(index).attrs.alt || "";
37
+ input.placeholder = "Describe the image";
38
+ input.setAttribute("aria-label", "Alt text");
39
+ box.append(input);
40
+ const rect = view.nodeDOM(at)?.getBoundingClientRect?.();
41
+ const shellBox = shell.getBoundingClientRect();
42
+ if (rect) {
43
+ box.style.setProperty("--page-pop-x", `${rect.left - shellBox.left}px`);
44
+ box.style.setProperty("--page-pop-y", `${rect.bottom - shellBox.top + 6}px`);
45
+ }
46
+ shell.append(box);
47
+ let done = false;
48
+ const close = (commit, refocus = true) => {
49
+ if (done) return;
50
+ done = true;
51
+ const value = input.value.trim();
52
+ box.remove();
53
+ const i = indexOfId(view.state.doc, id);
54
+ if (commit && i >= 0 && value !== view.state.doc.child(i).attrs.alt) setImageAttrs(view, id, { alt: value });
55
+ if (refocus) view.focus();
56
+ };
57
+ input.addEventListener("keydown", (e) => {
58
+ if (e.key === "Enter") { e.preventDefault(); close(true); }
59
+ else if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); close(false); }
60
+ });
61
+ input.addEventListener("blur", () => close(true, false));
62
+ input.focus();
63
+ return true;
64
+ }