@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
@@ -0,0 +1,187 @@
1
+ // Emoji: the Emoji action opens a picker of a small bundled glyph list and choosing one
2
+ // writes that glyph into the document as ordinary Unicode text. The glyph is the whole
3
+ // record — no mark, no node, no attribute and no format floor — so a reader shows it with
4
+ // nothing of this loaded, and a variation selector, a skin tone, a flag's regional pair, a
5
+ // keycap or a joined family goes in as the one grapheme a person sees, never split. The
6
+ // list is fetched the first time the picker opens and kept; a fetch that failed is not
7
+ // kept, so the next opening asks again.
8
+ import { state } from "../../vendor/prosemirror.vendor.js";
9
+ import { asStep } from "../blocksel.js";
10
+ import { composing } from "../markdown.js";
11
+ import { createMentionPicker } from "../mention-picker.js";
12
+
13
+ // One glyph, as a person sees it: counting code points or UTF-16 units would call a
14
+ // joined family seven things and a keycap several, and cut one in half.
15
+ export function graphemes(text) {
16
+ const value = String(text ?? "");
17
+ const segmenter = typeof Intl !== "undefined" && typeof Intl.Segmenter === "function" ? new Intl.Segmenter("en", { granularity: "grapheme" }) : null;
18
+ return segmenter ? [...segmenter.segment(value)].map((part) => part.segment) : [...value];
19
+ }
20
+
21
+ export const isEmojiText = (value) => graphemes(value).length === 1 && !/^\s*$/.test(String(value ?? ""));
22
+
23
+ // The data module's groups, flattened into the rows a picker lists: the glyph first, so the
24
+ // row reads as the emoji and its name, and the searchable words after it.
25
+ export function emojiEntries(groups) {
26
+ const list = [];
27
+ for (const [group, rows] of Object.entries(groups ?? {})) {
28
+ for (const row of Array.isArray(rows) ? rows : []) {
29
+ const [char, name, keywords = ""] = row ?? [];
30
+ if (typeof char !== "string" || typeof name !== "string") continue;
31
+ list.push({ id: `${group}:${name}`, char, name: `${char} ${name}`, keywords: `${name} ${keywords}` });
32
+ }
33
+ }
34
+ return list;
35
+ }
36
+
37
+ export const filterEmoji = (list, query) => {
38
+ const q = String(query ?? "").trim().toLowerCase();
39
+ if (!q) return list;
40
+ return list.filter((entry) => entry.char === q || entry.keywords.toLowerCase().includes(q));
41
+ };
42
+
43
+ // The list as one fetch for the life of a session: the first opening loads it, the next
44
+ // openings reuse it, and a fetch that failed leaves nothing cached so it is asked for again.
45
+ export function emojiLibrary(load = () => import("../emoji-data.js")) {
46
+ let pending = null;
47
+ return () => {
48
+ pending ??= Promise.resolve()
49
+ .then(() => load())
50
+ .then((mod) => emojiEntries(mod.EMOJI))
51
+ .catch((error) => { pending = null; throw error; });
52
+ return pending;
53
+ };
54
+ }
55
+
56
+ export function createFeature({ view = null, load = () => import("../emoji-data.js") } = {}) {
57
+ if (!view) return { run: () => ({ ok: false, code: "not-editing" }), dispose() {} };
58
+ const doc = view.dom.ownerDocument;
59
+ const library = emojiLibrary(load);
60
+ let session = null;
61
+
62
+ // The text the command belongs to, read from the document again at the commit: the picker
63
+ // holds no focus and writes nothing, so a caret that moved, a selection that is no longer
64
+ // the one the command was asked for or a place that takes no text all mean the bookmark no
65
+ // longer holds, and the command refuses rather than writing the glyph somewhere else.
66
+ function bookmark(s) {
67
+ const st = view.state;
68
+ const sel = st.selection;
69
+ if (sel.from !== s.from || sel.to !== s.to) return null;
70
+ if (s.from < 0 || s.to > st.doc.content.size) return null;
71
+ const $from = st.doc.resolve(s.from);
72
+ const $to = st.doc.resolve(s.to);
73
+ if (!$from.parent.isTextblock || $from.parent !== $to.parent) return null;
74
+ return { from: s.from, to: s.to };
75
+ }
76
+
77
+ // Where the list hangs, in the page's own placement variables. A view that cannot measure
78
+ // the caret (one outside a laid-out document) still gets its list, just not positioned.
79
+ function place(s) {
80
+ try {
81
+ const box = view.dom.parentElement?.getBoundingClientRect?.() ?? { left: 0, top: 0 };
82
+ const at = view.coordsAtPos(s.from);
83
+ return { x: Math.max(8, at.left - box.left), y: at.bottom + 6 - box.top };
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+
89
+ function anchor(s) {
90
+ const id = s.picker.activeId();
91
+ if (id) view.dom.setAttribute("aria-activedescendant", id);
92
+ else if (view.dom.getAttribute("aria-activedescendant")?.startsWith(s.picker.el.id)) view.dom.removeAttribute("aria-activedescendant");
93
+ }
94
+
95
+ function close() {
96
+ const s = session;
97
+ if (!s) return;
98
+ session = null;
99
+ for (const [target, type, fn, options] of s.listen) target.removeEventListener(type, fn, options);
100
+ if (view.dom.getAttribute("aria-activedescendant")?.startsWith(`${s.picker.el.id}-`)) view.dom.removeAttribute("aria-activedescendant");
101
+ s.picker.destroy();
102
+ }
103
+
104
+ // The one place a glyph reaches the document: the selection's text is replaced by it in a
105
+ // single transaction, with no mark and nothing else added.
106
+ function insertEmoji(at, char) {
107
+ const tr = view.state.tr.insertText(char, at.from, at.to);
108
+ tr.setSelection(state.TextSelection.create(tr.doc, at.from + char.length));
109
+ view.dispatch(asStep(tr));
110
+ view.focus();
111
+ return { ok: true, emoji: char };
112
+ }
113
+
114
+ function choose(index) {
115
+ const s = session;
116
+ if (!s) return { ok: false, code: "not-editing" };
117
+ const entry = s.picker.personAt(index);
118
+ const at = bookmark(s);
119
+ if (!entry || at === null || !isEmojiText(entry.char)) {
120
+ close();
121
+ if (!entry) return { ok: false, code: "no-emoji" };
122
+ if (at === null) return { ok: false, code: "stale-target" };
123
+ return { ok: false, code: "invalid-emoji" };
124
+ }
125
+ close();
126
+ return insertEmoji(at, entry.char);
127
+ }
128
+
129
+ function key(event) {
130
+ const s = session;
131
+ if (!s || composing(event, view)) return;
132
+ const stop = () => { event.preventDefault(); event.stopPropagation(); };
133
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
134
+ stop();
135
+ s.picker.move(event.key === "ArrowDown" ? 1 : -1);
136
+ anchor(s);
137
+ } else if (event.key === "Enter") {
138
+ stop();
139
+ choose(s.picker.index());
140
+ } else if (event.key === "Escape" || event.key === "Tab") {
141
+ stop();
142
+ close();
143
+ }
144
+ }
145
+
146
+ async function open(args = {}) {
147
+ close();
148
+ const s = { from: view.state.selection.from, to: view.state.selection.to, picker: null, listen: [] };
149
+ s.picker = createMentionPicker({ document: doc, label: "Insert an emoji", empty: "No emoji found", onPick: (index) => choose(index) });
150
+ session = s;
151
+ const listen = (target, type, fn, options) => {
152
+ target.addEventListener(type, fn, options);
153
+ s.listen.push([target, type, fn, options]);
154
+ };
155
+ listen(doc, "keydown", (event) => key(event), true);
156
+ // Typing closes the list: the caret has moved on, and the command that opened it has
157
+ // nothing to write where it no longer holds.
158
+ listen(view.dom, "input", () => close());
159
+ listen(doc, "mousedown", (event) => { if (!s.picker.el.contains(event.target)) close(); }, true);
160
+ (view.dom.parentElement ?? doc.body).appendChild(s.picker.el);
161
+
162
+ let list;
163
+ try {
164
+ list = await library();
165
+ } catch (error) {
166
+ close();
167
+ return { ok: false, code: "unavailable", error };
168
+ }
169
+ if (session !== s) return { ok: false, code: "not-editing" };
170
+ if (bookmark(s) === null) {
171
+ close();
172
+ return { ok: false, code: "stale-target" };
173
+ }
174
+ s.picker.show(filterEmoji(list, args?.query), place(s));
175
+ anchor(s);
176
+ return { ok: true, picker: true };
177
+ }
178
+
179
+ return {
180
+ // The Emoji action: the picker at the caret, offering the bundled list. It writes
181
+ // nothing until one is chosen.
182
+ async run(args) {
183
+ return open(args);
184
+ },
185
+ dispose() { close(); },
186
+ };
187
+ }
@@ -0,0 +1,189 @@
1
+ // The self-contained reading copy, as a command. One snapshot of the live model is
2
+ // taken and rendered through the projection, then each image and attachment the
3
+ // snapshot names is read once and written inside the file. Nothing downloads until
4
+ // the person has seen the captured title, the time it was taken and every resource
5
+ // the copy could not carry: a file that is missing something is never offered, and
6
+ // one that is offered has no address in it that would be fetched again.
7
+ import { project } from "../projection.js";
8
+ import { blockMap } from "../model.js";
9
+ import { PageExportError, renderExportHtml } from "../export-html.js";
10
+ import { inlineExportResources } from "../export-assets.js";
11
+ import { downloadHtml, documentName } from "../../core/download.js";
12
+ import { mountEditorUI } from "../../ui/editor-ui.js";
13
+
14
+ const MAX_NAME = 80;
15
+ const UNSAFE_NAME = /[\u0000-\u001f\u007f<>:"/\\|?*]+/g;
16
+
17
+ // The captured title as a file name: one line, no path of its own, and the page's own
18
+ // name when a title has nothing a file name can keep.
19
+ function fileName(title) {
20
+ const base = String(title ?? "").replace(UNSAFE_NAME, " ").replace(/\s+/g, " ").replace(/^\.+/, "").trim().slice(0, MAX_NAME).trim();
21
+ if (base) return `${base}.html`;
22
+ try { return `${documentName()}.html`; } catch { return "page.html"; }
23
+ }
24
+
25
+ // The panel sits in its own shadow root beside the editor, so the page's CSS cannot restyle
26
+ // it. The page's --page-* tokens still reach it, and it keeps the editor's font and colour.
27
+ const PANEL_CSS = `
28
+ :host{font:inherit !important;color:inherit !important}
29
+ .page-export-panel{max-width:46ch;margin:8px 0;padding:10px 12px;border:1px solid var(--page-pop-line);border-radius:8px;background:var(--page-pop-bg);color:var(--page-pop-ink);box-shadow:var(--page-pop-shadow);font-size:.88em}
30
+ .page-export-title{margin:0 0 2px;font-weight:600}
31
+ .page-export-when{margin:0 0 6px;color:var(--page-ink-soft)}
32
+ .page-export-losses{margin:6px 0;padding-left:1.1em}
33
+ .page-export-losses li{margin:2px 0}
34
+ .page-export-acts{display:flex;gap:6px;margin-top:8px}
35
+ .page-export-btn{border:0;padding:4px 8px;border-radius:6px;background:none;color:inherit;font:inherit;cursor:pointer}
36
+ .page-export-btn:hover{background:var(--page-pop-hover)}
37
+ .page-export-panel[data-state="loss"] .page-export-title{color:var(--page-danger, #9E3B2F)}
38
+ `;
39
+
40
+ export function createFeature({ view = null, download = downloadHtml, fetch: fetchImpl = null, now = () => new Date() } = {}) {
41
+ if (!view) return { run: () => ({ ok: false, code: "not-editing" }), dispose() {} };
42
+ const doc = view.dom.ownerDocument;
43
+ let session = null;
44
+ let disposed = false;
45
+
46
+ const listen = (s, target, type, fn) => {
47
+ target.addEventListener(type, fn);
48
+ s.listen.push([target, type, fn]);
49
+ };
50
+
51
+ const button = (act, label) => {
52
+ const b = doc.createElement("button");
53
+ b.type = "button";
54
+ b.className = "page-export-btn";
55
+ b.dataset.act = act;
56
+ b.textContent = label;
57
+ return b;
58
+ };
59
+
60
+ function close() {
61
+ const s = session;
62
+ if (!s) return;
63
+ session = null;
64
+ for (const [target, type, fn] of s.listen) target.removeEventListener(type, fn);
65
+ s.abort?.abort();
66
+ s.ui?.remove();
67
+ }
68
+
69
+ // One panel for the whole session: what was captured, what could not be carried, and
70
+ // the one action that does anything — a download, and only when nothing is missing.
71
+ function draw(s) {
72
+ const box = s.panel ?? (s.panel = doc.createElement("aside"));
73
+ box.className = "page-export-panel";
74
+ box.dataset.state = s.ready ? "ready" : "loss";
75
+ box.setAttribute("contenteditable", "false");
76
+ box.setAttribute("role", "dialog");
77
+ box.setAttribute("aria-modal", "false");
78
+ box.setAttribute("aria-labelledby", "page-export-title");
79
+ box.dataset.at = s.at.toISOString();
80
+ box.setAttribute("tabindex", "-1");
81
+ box.textContent = "";
82
+ const title = doc.createElement("p");
83
+ title.className = "page-export-title";
84
+ title.id = "page-export-title";
85
+ const heading = s.ready ? "Reading copy" : "This page cannot be a reading copy yet";
86
+ title.textContent = s.title ? `${heading}: ${s.title}` : heading;
87
+ const when = doc.createElement("p");
88
+ when.className = "page-export-when";
89
+ const said = `${s.at.toLocaleString()} · ${s.embedded} ${s.embedded === 1 ? "resource" : "resources"} inside the file`;
90
+ when.textContent = said;
91
+ box.append(title, when);
92
+ if (s.losses.length) {
93
+ const list = doc.createElement("ul");
94
+ list.className = "page-export-losses";
95
+ for (const entry of s.losses) {
96
+ const item = doc.createElement("li");
97
+ item.textContent = entry.message;
98
+ list.append(item);
99
+ }
100
+ box.append(list);
101
+ }
102
+ const acts = doc.createElement("div");
103
+ acts.className = "page-export-acts";
104
+ if (s.ready) acts.append(button("download", "Download"), button("close", "Close"));
105
+ else acts.append(button("retry", "Try again"), button("close", "Close"));
106
+ box.append(acts);
107
+ const first = acts.firstChild;
108
+ if (!s.ui) s.ui = mountEditorUI({ name: "page-export", content: box, css: [PANEL_CSS], parent: view.dom.parentElement ?? doc.body });
109
+ // The panel is what a person lands on: the download when there is one to take,
110
+ // and the list of what stopped it when there is not.
111
+ (s.ready ? first : box).focus?.();
112
+ }
113
+
114
+ function decide(s) {
115
+ listen(s, s.panel, "click", (event) => {
116
+ const b = event.target.closest?.("[data-act]");
117
+ if (!b) return;
118
+ event.preventDefault();
119
+ if (b.dataset.act === "close") close();
120
+ else if (b.dataset.act === "download") save(s);
121
+ else if (b.dataset.act === "retry") run().catch((error) => console.error("clayjs: the reading copy could not be made", error));
122
+ });
123
+ listen(s, doc, "keydown", (event) => {
124
+ if (event.key !== "Escape" || session !== s) return;
125
+ event.preventDefault();
126
+ event.stopPropagation();
127
+ close();
128
+ }, true);
129
+ }
130
+
131
+ // The one place a reading copy becomes a file. The download is handed the bytes that
132
+ // were shown, never a second render, so what a person saw is what they get.
133
+ function save(s) {
134
+ if (!s.ready || disposed) return { ok: false, code: "not-editing" };
135
+ const saved = download({ html: s.ready.html, name: s.ready.name });
136
+ close();
137
+ return { ok: saved === true, downloaded: saved === true };
138
+ }
139
+
140
+ async function run() {
141
+ if (disposed || view.isDestroyed) return { ok: false, code: "not-editing" };
142
+ close();
143
+ const s = { at: now(), title: "", panel: null, listen: [], abort: new AbortController(), losses: [], embedded: 0, ready: null };
144
+ // The model is read once, before anything is awaited: a peer's edit while the
145
+ // resources arrive cannot change the file being made.
146
+ const blocks = blockMap(view.state.doc);
147
+ const model = project(blocks);
148
+ const base = doc.baseURI;
149
+ let rendered;
150
+ try {
151
+ rendered = renderExportHtml(model, {
152
+ fallbackOf: (id) => (typeof blocks[id]?.artifact?.fallbackHtml === "string" ? blocks[id].artifact.fallbackHtml : ""),
153
+ doc,
154
+ });
155
+ } catch (error) {
156
+ if (!(error instanceof PageExportError)) throw error;
157
+ s.title = null;
158
+ s.losses = [{ code: error.code, message: error.message }];
159
+ session = s;
160
+ draw(s);
161
+ decide(s);
162
+ return { ok: false, code: error.code, message: error.message };
163
+ }
164
+ s.title = rendered.title || "Untitled";
165
+ session = s;
166
+ const transport = fetchImpl ?? globalThis.fetch;
167
+ const result = await inlineExportResources(rendered.html, { resources: rendered.resources, base, fetch: transport, signal: s.abort.signal });
168
+ if (disposed || view.isDestroyed || session !== s) return { ok: false, code: "not-editing" };
169
+ s.embedded = result.embedded;
170
+ s.losses = result.losses.slice();
171
+ for (const entry of result.remote ?? []) {
172
+ s.losses.push({ code: "remote-reference", message: `The ${entry.tag} in the reading copy still points at ${String(entry.value).slice(0, 120)}` });
173
+ }
174
+ if (!s.losses.length) s.ready = { html: result.html, name: fileName(s.title) };
175
+ draw(s);
176
+ decide(s);
177
+ return s.ready
178
+ ? { ok: true, title: s.title, name: s.ready.name, embedded: s.embedded }
179
+ : { ok: false, code: "resource-losses", losses: s.losses };
180
+ }
181
+
182
+ return {
183
+ run,
184
+ dispose() {
185
+ disposed = true;
186
+ close();
187
+ },
188
+ };
189
+ }
@@ -0,0 +1,169 @@
1
+ // Markdown export: the Download Markdown action. It snapshots the live record once, so a
2
+ // peer's edit while the report is open cannot change the file, and it shows what the
3
+ // format could not carry before anything downloads: each block written as semantic HTML
4
+ // and each construct whose meaning goes away, named per block. No parser is fetched to
5
+ // export. The file goes through the page copy's own download helper, nothing here writes,
6
+ // enters history or marks the page saved, and an active composition is waited out rather
7
+ // than forced.
8
+ import { blockMap } from "../model.js";
9
+ import { downloadHtml, documentName } from "../../core/download.js";
10
+ import { exportMarkdown, markdownFileName } from "../export-markdown.js";
11
+
12
+ export function createFeature({ view = null, download = downloadHtml } = {}) {
13
+ if (!view) return { run: () => Promise.resolve({ ok: false, code: "not-editing" }), dispose() {} };
14
+ const doc = view.dom.ownerDocument;
15
+ let session = null;
16
+ let disposed = false;
17
+
18
+ function close() {
19
+ const s = session;
20
+ if (!s) return false;
21
+ session = null;
22
+ for (const [target, type, fn, options] of s.listen) target.removeEventListener(type, fn, options);
23
+ s.panel.remove();
24
+ return true;
25
+ }
26
+
27
+ function cancel() {
28
+ if (!close()) return { ok: false, code: "cancelled" };
29
+ view.focus?.();
30
+ return { ok: true, cancelled: true };
31
+ }
32
+
33
+ // The report: how much was written, every construct kept as HTML and every loss, then
34
+ // Download and Cancel. It uses the editor's own menu markup, so it needs no stylesheet
35
+ // of its own.
36
+ function draw(s) {
37
+ const parts = [];
38
+ const line = (className, text) => {
39
+ const el = doc.createElement("div");
40
+ el.className = className;
41
+ el.textContent = text;
42
+ return el;
43
+ };
44
+ parts.push(line("page-menu-title", "Export as Markdown"));
45
+ const counts = [`${s.report.blocks} block${s.report.blocks === 1 ? "" : "s"}`];
46
+ if (s.report.extensions.length) counts.push(`${s.report.extensions.length} kept as HTML`);
47
+ if (s.report.losses.length) counts.push(`${s.report.losses.length} lost`);
48
+ parts.push(line("page-menu-title page-export-summary", counts.join(" \u00b7 ")));
49
+ const section = (className, label, list) => {
50
+ if (!list.length) return;
51
+ const box = doc.createElement("div");
52
+ box.className = className;
53
+ box.append(line("page-menu-title", label));
54
+ for (const one of list) {
55
+ const row = line("page-export-row", one.name);
56
+ row.dataset.block = one.block ?? "";
57
+ row.dataset.type = one.type ?? "";
58
+ box.append(row);
59
+ }
60
+ parts.push(box);
61
+ };
62
+ section("page-export-extensions", "Kept as HTML", s.report.extensions);
63
+ section("page-export-losses", "Not kept in Markdown", s.report.losses);
64
+ if (!s.report.extensions.length && !s.report.losses.length) parts.push(line("page-export-row", "Everything here is written as Markdown."));
65
+ if (s.notice) {
66
+ const failed = doc.createElement("p");
67
+ failed.className = "page-export-error";
68
+ failed.setAttribute("role", "alert");
69
+ failed.textContent = s.notice;
70
+ parts.push(failed);
71
+ }
72
+ const button = (what, label) => {
73
+ const el = doc.createElement("button");
74
+ el.type = "button";
75
+ el.className = "page-menu-item";
76
+ el.dataset.export = what;
77
+ el.textContent = label;
78
+ el.addEventListener("mousedown", (event) => event.preventDefault());
79
+ el.addEventListener("click", () => (what === "download" ? save() : cancel()));
80
+ return el;
81
+ };
82
+ parts.push(button("download", `Download ${s.name}`), button("cancel", "Cancel"));
83
+ s.panel.replaceChildren(...parts);
84
+ }
85
+
86
+ function save() {
87
+ const s = session;
88
+ if (!s) return { ok: false, code: "no-export" };
89
+ if (download({ html: s.report.text, name: s.name, type: "text/markdown" })) {
90
+ close();
91
+ view.focus?.();
92
+ return { ok: true, downloaded: true };
93
+ }
94
+ s.notice = "That download could not be made.";
95
+ draw(s);
96
+ return { ok: false, code: "download-failed" };
97
+ }
98
+
99
+ function key(event) {
100
+ const s = session;
101
+ if (!s || event.isComposing || event.keyCode === 229) return;
102
+ if (event.key === "Escape") {
103
+ event.preventDefault();
104
+ event.stopPropagation();
105
+ cancel();
106
+ } else if (event.key === "Enter" && !event.target?.closest?.("[data-export]")) {
107
+ event.preventDefault();
108
+ event.stopPropagation();
109
+ save();
110
+ }
111
+ }
112
+
113
+ function open(report, name) {
114
+ const panel = doc.createElement("div");
115
+ panel.className = "page-menu page-export-preview";
116
+ panel.setAttribute("role", "dialog");
117
+ panel.setAttribute("aria-label", "Export Markdown");
118
+ const s = { report, name, panel, notice: null, listen: [] };
119
+ session = s;
120
+ const listen = (target, type, fn, options) => {
121
+ target.addEventListener(type, fn, options);
122
+ s.listen.push([target, type, fn, options]);
123
+ };
124
+ listen(doc, "keydown", (event) => key(event), true);
125
+ listen(doc, "mousedown", (event) => { if (!s.panel.contains(event.target)) cancel(); }, true);
126
+ (view.dom.parentElement ?? doc.body).appendChild(panel);
127
+ draw(s);
128
+ panel.querySelector('[data-export="download"]')?.focus?.();
129
+ return { ok: true, preview: true };
130
+ }
131
+
132
+ // A composition is never forced: the export waits for the person to finish it, because
133
+ // the record it reads is the one the composition is still writing into.
134
+ function composed() {
135
+ if (!view.composing) return Promise.resolve();
136
+ return new Promise((resolve) => {
137
+ const done = () => {
138
+ view.dom.removeEventListener("compositionend", done);
139
+ resolve();
140
+ };
141
+ view.dom.addEventListener("compositionend", done);
142
+ });
143
+ }
144
+
145
+ async function run() {
146
+ if (disposed || view.isDestroyed) return { ok: false, code: "not-editing" };
147
+ close();
148
+ await composed();
149
+ if (disposed || view.isDestroyed) return { ok: false, code: "not-editing" };
150
+ const snapshot = blockMap(view.state.doc);
151
+ let report;
152
+ try {
153
+ report = exportMarkdown(snapshot);
154
+ } catch (error) {
155
+ return { ok: false, code: "export-failed", error };
156
+ }
157
+ return open(report, markdownFileName(documentName()));
158
+ }
159
+
160
+ return {
161
+ async run(args) {
162
+ return run(args);
163
+ },
164
+ dispose() {
165
+ disposed = true;
166
+ close();
167
+ },
168
+ };
169
+ }