@panphora/clayjs 1.17.0 → 1.18.1

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 (467) 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 +72474 -62090
  11. package/dist/m/attrs/{save-freeze-MYNVXREN.js → save-freeze-LWGF6XUN.js} +1 -1
  12. package/dist/m/c-2GRUM77Q.js +30 -0
  13. package/dist/m/c-2KIEGNC4.js +1 -0
  14. package/dist/m/{c-AMSNPOCX.js → c-2KL3BD52.js} +1 -1
  15. package/dist/m/c-2ZZBC45O.js +1 -0
  16. package/dist/m/c-372N6H3B.js +1 -0
  17. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  18. package/dist/m/c-3LDGIBMN.js +1 -0
  19. package/dist/m/c-3O7OBDCM.js +1 -0
  20. package/dist/m/c-4ASQPRQL.js +1 -0
  21. package/dist/m/c-4TXNLX4K.js +6 -0
  22. package/dist/m/c-4YU3BR4D.js +6 -0
  23. package/dist/m/c-5734MF2P.js +1 -0
  24. package/dist/m/c-57XDY6HC.js +1 -0
  25. package/dist/m/{c-KAU6E43D.js → c-5B4YBZXC.js} +1 -1
  26. package/dist/m/c-5ONTYIUI.js +1 -0
  27. package/dist/m/c-5OOVFCJM.js +1 -0
  28. package/dist/m/{c-3XIRD4NU.js → c-5T6NZHNM.js} +1 -1
  29. package/dist/m/{c-74MGYWZF.js → c-5ZQV7KC4.js} +1 -1
  30. package/dist/m/{c-7OLLJPWP.js → c-6DB2OLRJ.js} +1 -1
  31. package/dist/m/c-6DNRCPNM.js +1 -0
  32. package/dist/m/c-6UGWFMQC.js +2 -0
  33. package/dist/m/c-7K4SCQB7.js +1 -0
  34. package/dist/m/c-7MG5RU63.js +1 -0
  35. package/dist/m/c-7PRJCA3E.js +1 -0
  36. package/dist/m/c-A6L3FMUS.js +1 -0
  37. package/dist/m/c-AMU3BPPR.js +4 -0
  38. package/dist/m/{c-Y5HBBIQ4.js → c-AMYEFA2E.js} +1 -1
  39. package/dist/m/c-AQYCG7QW.js +1 -0
  40. package/dist/m/c-ATKAOH23.js +1 -0
  41. package/dist/m/c-B5CS55WR.js +1 -0
  42. package/dist/m/c-B5UJQT7W.js +1 -0
  43. package/dist/m/c-B7J4K2AE.js +3 -0
  44. package/dist/m/c-BHZYSHW4.js +1 -0
  45. package/dist/m/{c-VHM5XQHK.js → c-BIJALSRD.js} +1 -1
  46. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  47. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  48. package/dist/m/{c-VE6ZMYMC.js → c-BUAYFEV5.js} +1 -1
  49. package/dist/m/c-C6IPRG34.js +1 -0
  50. package/dist/m/{c-KU3ZFADW.js → c-CAS3NND4.js} +1 -1
  51. package/dist/m/{c-PS5ARVWR.js → c-CCQ25GFY.js} +1 -1
  52. package/dist/m/c-CJFDLD43.js +1 -0
  53. package/dist/m/c-COPTL5WB.js +1 -0
  54. package/dist/m/c-D3E4WJ4M.js +166 -0
  55. package/dist/m/c-D5IKEKXW.js +7 -0
  56. package/dist/m/c-DHBRA6DL.js +1 -0
  57. package/dist/m/c-DKLS74XB.js +72 -0
  58. package/dist/m/c-DVYGFTSK.js +3 -0
  59. package/dist/m/c-E3ZXARWA.js +1 -0
  60. package/dist/m/c-EK63G3CG.js +1 -0
  61. package/dist/m/c-ERQSNR6K.js +13 -0
  62. package/dist/m/c-EVYAISP3.js +1 -0
  63. package/dist/m/c-EXKHPOVC.js +3 -0
  64. package/dist/m/{c-QJ4OM3G6.js → c-FWKHM4E7.js} +1 -1
  65. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  66. package/dist/m/c-GNDL3TKB.js +1 -0
  67. package/dist/m/c-GRWMD6O5.js +1 -0
  68. package/dist/m/c-GRXHHFPO.js +1 -0
  69. package/dist/m/c-HG2C77BM.js +4 -0
  70. package/dist/m/c-HHGJBU4D.js +1 -0
  71. package/dist/m/c-HMV6P4RX.js +1 -0
  72. package/dist/m/c-HMYWIHFG.js +1 -0
  73. package/dist/m/c-HT2DKBV2.js +1 -0
  74. package/dist/m/c-I2SCCUEW.js +1 -0
  75. package/dist/m/{c-TJ6WOJL3.js → c-ICJYHABF.js} +2 -2
  76. package/dist/m/c-IMZCWYHR.js +7 -0
  77. package/dist/m/c-IVMD3FEM.js +1 -0
  78. package/dist/m/{c-WDC5GVKU.js → c-IVTRVENZ.js} +1 -1
  79. package/dist/m/c-JKZJIUIP.js +1 -0
  80. package/dist/m/{c-UDUUJPFZ.js → c-JNLX3RJB.js} +1 -1
  81. package/dist/m/c-K3ADYNLU.js +1 -0
  82. package/dist/m/c-KGHVFRR3.js +1 -0
  83. package/dist/m/{c-5QWQ7A5L.js → c-KJJPV77R.js} +1 -1
  84. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  85. package/dist/m/{c-EZU56NQV.js → c-KP64LT2A.js} +2 -2
  86. package/dist/m/c-KQ7E3PYY.js +1 -0
  87. package/dist/m/c-L3F5ZLIV.js +1 -0
  88. package/dist/m/{c-3RUU4TPX.js → c-L42OLXWM.js} +3 -2
  89. package/dist/m/c-L5VFATT6.js +1 -0
  90. package/dist/m/c-L7JL5TL3.js +3 -0
  91. package/dist/m/c-LKSL4AUJ.js +1 -0
  92. package/dist/m/c-M6FNQCTQ.js +1 -0
  93. package/dist/m/c-MCOOHCSG.js +1 -0
  94. package/dist/m/c-MHI3GYKF.js +1 -0
  95. package/dist/m/c-MWXFLW4J.js +1 -0
  96. package/dist/m/{c-5RJFTLSF.js → c-NEL7HQW3.js} +1 -1
  97. package/dist/m/{c-DCXOXBF3.js → c-NQJRVJ56.js} +1 -1
  98. package/dist/m/c-NWX3MFT4.js +1 -0
  99. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  100. package/dist/m/c-OKXI52N7.js +1 -0
  101. package/dist/m/{c-R3L7ZJGB.js → c-OPLUZR3A.js} +1 -1
  102. package/dist/m/{c-GSFIGKOP.js → c-OTMKN2OO.js} +1 -1
  103. package/dist/m/{c-6NC5I2TV.js → c-OYRZ7XG2.js} +1 -1
  104. package/dist/m/{c-EVCQ3BKA.js → c-P3OFYUMA.js} +1 -1
  105. package/dist/m/c-PLN76GF3.js +6 -0
  106. package/dist/m/c-PLP4JKFC.js +1 -0
  107. package/dist/m/c-Q26MME6N.js +1 -0
  108. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  109. package/dist/m/{c-FRLHSY7E.js → c-QIKT6SKM.js} +1 -1
  110. package/dist/m/c-QIXT3Q7C.js +28 -0
  111. package/dist/m/c-QO4HNGCQ.js +1 -0
  112. package/dist/m/{c-JU3NK25H.js → c-R27DWQKX.js} +1 -1
  113. package/dist/m/{c-RSMOODDU.js → c-R2XUODVY.js} +1 -1
  114. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  115. package/dist/m/c-RND3G2S2.js +1 -0
  116. package/dist/m/{c-Q622NS2Y.js → c-S2U6NJU4.js} +1 -1
  117. package/dist/m/c-SPZPGD3R.js +1 -0
  118. package/dist/m/c-SW4YEG2D.js +8 -0
  119. package/dist/m/c-T637T7SN.js +1 -0
  120. package/dist/m/c-TJAXGIRV.js +1 -0
  121. package/dist/m/{c-RYUXUUBX.js → c-TNEM3RNM.js} +1 -1
  122. package/dist/m/c-TTB5EK4S.js +32 -0
  123. package/dist/m/c-U7SHNWDH.js +1 -0
  124. package/dist/m/c-UDHIVLYH.js +20 -0
  125. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  126. package/dist/m/{c-QICP2YR5.js → c-UORXLSD3.js} +1 -1
  127. package/dist/m/c-UPD33ZPE.js +1 -0
  128. package/dist/m/c-URSMSFCB.js +1 -0
  129. package/dist/m/c-UY4QX42B.js +1 -0
  130. package/dist/m/c-V44KWI2L.js +4 -0
  131. package/dist/m/c-VILENZTD.js +1 -0
  132. package/dist/m/{c-HDDEEL77.js → c-VJYEVRYK.js} +1 -1
  133. package/dist/m/{c-SMBZG5X4.js → c-VR2BJ4AN.js} +1 -1
  134. package/dist/m/c-W4JCVLAB.js +1 -0
  135. package/dist/m/c-W6DQSHXI.js +135 -0
  136. package/dist/m/c-WBGWGOM7.js +1 -0
  137. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  138. package/dist/m/c-WGW764WX.js +1 -0
  139. package/dist/m/c-WMRJKQ2V.js +5 -0
  140. package/dist/m/c-WNTYKCDG.js +1 -0
  141. package/dist/m/c-WQWIRWO3.js +72 -0
  142. package/dist/m/c-X5LNHET5.js +1 -0
  143. package/dist/m/{c-I2ZATQCD.js → c-X5TSXZYJ.js} +2 -2
  144. package/dist/m/c-XGH4GS2T.js +1 -0
  145. package/dist/m/c-Y6F2FORL.js +1 -0
  146. package/dist/m/c-YBKHUAK7.js +10 -0
  147. package/dist/m/c-YBSS55KU.js +20 -0
  148. package/dist/m/c-YCLU4UNT.js +2 -0
  149. package/dist/m/c-YVZ2L7CE.js +1 -0
  150. package/dist/m/c-ZK4GIZH2.js +1 -0
  151. package/dist/m/c-ZRXJD4G6.js +1 -0
  152. package/dist/m/c-ZWBI4HYK.js +1 -0
  153. package/dist/m/core/{admin-attrs-QHOKPJ7W.js → admin-attrs-YY7T5FGU.js} +1 -1
  154. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  155. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  156. package/dist/m/core/{host-permission-FNXVEFNE.js → host-permission-6EVJGNPL.js} +1 -1
  157. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  158. package/dist/m/core/{persist-JFYRHMRJ.js → persist-UKXV5KLA.js} +1 -1
  159. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  160. package/dist/m/core/save-LYY7K6U7.js +1 -0
  161. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  162. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  163. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  164. package/dist/m/internals/{index-PVXGPUDA.js → index-TGLQINN3.js} +1 -1
  165. package/dist/m/loader-3BBNZZGM.js +1 -0
  166. package/dist/m/manifest-26C55578.js +1 -0
  167. package/dist/m/manifest-edit-4396A009.js +1 -0
  168. package/dist/m/manifest.json +2111 -1573
  169. package/dist/m/page/{editor-NNJA4OKD.js → editor-6LRB3GE4.js} +123 -21
  170. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  171. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  172. package/dist/m/plugins/{demo-RM5IR6AO.js → demo-OYO5GCAO.js} +1 -1
  173. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  174. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  175. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  176. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  177. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  178. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  179. package/dist/m/plugins/{people-SGPJKANK.js → people-JCHVPF7P.js} +1 -1
  180. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  181. package/dist/m/plugins/{richclay-NRQDSWX6.js → richclay-KLGETK77.js} +2 -2
  182. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  183. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  184. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  185. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  186. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  187. package/dist/m/plugins/{upload-WLYKK6TL.js → upload-NHGJXZK2.js} +1 -1
  188. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  189. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  190. package/dist/m/plugins/{wire-4N6GYAF6.js → wire-VBWXEXU2.js} +1 -1
  191. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  192. package/dist/m/route/route-62CHXL5B.js +1 -0
  193. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  194. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  195. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  196. package/dist/offline-manifest.json +1367 -930
  197. package/dist/page/themes/light-1.css +8 -7
  198. package/dist/server/core.cjs +1440 -288
  199. package/dist/server/offline-runtime.cjs +63 -63
  200. package/dist/server/offline-runtime.mjs +63 -63
  201. package/dist/server/page-compaction-worker.cjs +3710 -0
  202. package/dist/server/rooms.mjs +5 -0
  203. package/docs/guide.md +653 -101
  204. package/entries/sap.js +5 -5
  205. package/package.json +1 -1
  206. package/src/core/admin-contenteditable.js +5 -2
  207. package/src/core/admin-inputs.js +1 -0
  208. package/src/core/admin-onclick.js +2 -1
  209. package/src/core/conflict-notice-ui.js +4 -7
  210. package/src/core/download.js +2 -2
  211. package/src/core/host-meta.js +38 -0
  212. package/src/core/is-edit-mode.js +17 -1
  213. package/src/core/mode-key.js +125 -19
  214. package/src/core/mode-support.js +5 -0
  215. package/src/core/mode-transition.js +20 -6
  216. package/src/core/preserve-authored.js +4 -4
  217. package/src/core/recovery.js +121 -33
  218. package/src/core/save-accepted.js +33 -0
  219. package/src/core/save-core.js +106 -31
  220. package/src/core/save-job.js +108 -0
  221. package/src/core/save-prepare.js +53 -0
  222. package/src/core/save-size-notice.js +3 -1
  223. package/src/core/save-slices.js +15 -0
  224. package/src/core/save.js +557 -127
  225. package/src/core/serialize-steps.js +109 -0
  226. package/src/core/snapshot.js +170 -39
  227. package/src/core/source-map.js +83 -38
  228. package/src/core/stale-host-notice.js +1 -0
  229. package/src/core/upgrade-run.js +59 -8
  230. package/src/dom/active-element.js +5 -0
  231. package/src/lib/assets.js +1 -0
  232. package/src/lib/slice.js +65 -0
  233. package/src/lib/uimotion.js +774 -0
  234. package/src/lib/unsaved-state.js +2 -2
  235. package/src/live/fast-lane.js +10 -3
  236. package/src/live/local-lane.js +202 -11
  237. package/src/live/ops.js +16 -0
  238. package/src/live/page-compaction.js +162 -0
  239. package/src/live/server-lane.js +1 -0
  240. package/src/live/store.js +11 -3
  241. package/src/loader-logic.js +3 -1
  242. package/src/loader.js +14 -4
  243. package/src/page/adopt-boot.js +2 -1
  244. package/src/page/awareness.js +33 -2
  245. package/src/page/blocks.js +89 -27
  246. package/src/page/boot-pack.js +1 -1
  247. package/src/page/chart-tables.js +21 -3
  248. package/src/page/chart.js +406 -63
  249. package/src/page/checklist.js +10 -135
  250. package/src/page/checkpoint-proof.js +7 -1
  251. package/src/page/chrome.js +250 -51
  252. package/src/page/clipboard-shape.js +126 -0
  253. package/src/page/drag.js +33 -3
  254. package/src/page/editor.js +136 -25
  255. package/src/page/export-assets.js +357 -0
  256. package/src/page/export-html.js +209 -0
  257. package/src/page/export-markdown.js +361 -0
  258. package/src/page/features/checklist.js +428 -0
  259. package/src/page/features/export-html.js +189 -0
  260. package/src/page/features/export-markdown.js +169 -0
  261. package/src/page/features/import-markdown.js +349 -0
  262. package/src/page/features/media.js +61 -5
  263. package/src/page/features/registry.js +18 -0
  264. package/src/page/features/table.js +373 -83
  265. package/src/page/features/title-cover.js +331 -0
  266. package/src/page/features/title-icon.js +213 -0
  267. package/src/page/features/toc.js +87 -0
  268. package/src/page/format-command.js +25 -1
  269. package/src/page/format-gate.js +68 -18
  270. package/src/page/format-host.js +17 -0
  271. package/src/page/format-native-commands.js +21 -5
  272. package/src/page/image-actions.js +64 -0
  273. package/src/page/import-markdown.js +262 -0
  274. package/src/page/items.js +135 -17
  275. package/src/page/markdown.js +6 -2
  276. package/src/page/media.js +35 -119
  277. package/src/page/placeholder.js +14 -1
  278. package/src/page/projection.js +98 -10
  279. package/src/page/recover.js +10 -12
  280. package/src/page/recovery-ui.js +464 -0
  281. package/src/page/rich/chart.js +63 -7
  282. package/src/page/save-projection.js +28 -0
  283. package/src/page/save-states.js +13 -3
  284. package/src/page/schema.js +19 -3
  285. package/src/page/table-paste.js +3 -1
  286. package/src/page/table-style.js +4 -4
  287. package/src/page/table.js +44 -2
  288. package/src/page/themes/light-1.css +8 -7
  289. package/src/page/themes/light-1.source.css +8 -7
  290. package/src/page/toc-link.js +31 -0
  291. package/src/page/toc.js +5 -0
  292. package/src/page/view-hooks.js +38 -12
  293. package/src/page/view.js +13 -0
  294. package/src/page/ybind/binding.js +88 -5
  295. package/src/page/ybind/bridge.js +187 -20
  296. package/src/page/ybind/containment.js +162 -0
  297. package/src/page/ybind/doc.js +17 -9
  298. package/src/page/ybind/file-debt.js +63 -0
  299. package/src/page/ybind/layout.js +7 -2
  300. package/src/page/ybind/marks.js +1 -1
  301. package/src/page/ybind/moves.js +5 -4
  302. package/src/page/ybind/origins.js +106 -4
  303. package/src/page/ybind/parts.js +8 -1
  304. package/src/page/ybind/provider.js +113 -17
  305. package/src/page/ybind/recovery-proof.js +32 -9
  306. package/src/page/ybind/recovery-replay.js +558 -15
  307. package/src/page/ybind/recovery.js +174 -45
  308. package/src/page/ybind/table.js +67 -11
  309. package/src/page/ybind/walk-delta.js +258 -0
  310. package/src/page/ybind/walk.js +54 -29
  311. package/src/page/ybind/writer.js +1 -1
  312. package/src/plugins/ai-edit.js +13 -9
  313. package/src/plugins/indicator-chip.js +91 -0
  314. package/src/plugins/indicator.js +13 -80
  315. package/src/plugins/live.js +89 -14
  316. package/src/plugins/menu/actions.js +196 -0
  317. package/src/plugins/menu/people.js +279 -0
  318. package/src/plugins/menu/placement.js +121 -0
  319. package/src/plugins/menu/preferences.js +9 -0
  320. package/src/plugins/menu/seo.js +316 -397
  321. package/src/plugins/menu/style.js +171 -0
  322. package/src/plugins/menu/variants.js +296 -0
  323. package/src/plugins/menu.js +186 -183
  324. package/src/plugins/page-view.js +3 -2
  325. package/src/plugins/page.js +249 -44
  326. package/src/plugins/sortable.js +8 -1
  327. package/src/plugins/source.js +6 -0
  328. package/src/plugins/upgrade.js +51 -17
  329. package/src/plugins/upload-ui.js +12 -17
  330. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  331. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  332. package/src/plugins/visitor-drafts/intro.js +5 -3
  333. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  334. package/src/plugins/visitor-drafts/prompt.js +11 -9
  335. package/src/plugins/visitor-drafts/source.js +4 -3
  336. package/src/plugins/visitor-drafts/store.js +138 -9
  337. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  338. package/src/plugins/visitor-drafts-owner.js +4 -6
  339. package/src/plugins/visitor-drafts.js +5 -3
  340. package/src/recovery/store.js +1 -1
  341. package/src/route/route.js +4 -0
  342. package/src/server/rooms/index.js +5 -1
  343. package/src/server/rooms/live-data-rooms.js +302 -50
  344. package/src/server/rooms/page-compaction-worker.js +12 -0
  345. package/src/server/rooms/page-compactor.js +92 -0
  346. package/src/server/rooms/page-room-conformance.js +461 -0
  347. package/src/source/parse-steps.js +94 -0
  348. package/src/source/session.js +233 -57
  349. package/src/sync/editing-sender.js +12 -0
  350. package/src/sync/live-sync-edit.js +22 -2
  351. package/src/sync/live-sync.js +150 -23
  352. package/src/sync/page-relay.js +52 -0
  353. package/src/sync/presence-store.js +44 -4
  354. package/src/sync/presence.js +6 -3
  355. package/src/sync/saved-stream.js +11 -6
  356. package/src/ui/bevel-controls.js +1 -0
  357. package/src/ui/bevel-dialog.js +5 -2
  358. package/src/ui/editor-ui.js +77 -0
  359. package/src/ui/page-colour.js +281 -0
  360. package/src/ui/toast.js +16 -7
  361. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  362. package/src/vendor/hyper-undo.vendor.js +1 -1
  363. package/src/vendor/hypercms.vendor.js +9 -9
  364. package/src/vendor/parse5.vendor.js +2 -2
  365. package/src/vendor/sap-core.vendor.js +5 -5
  366. package/dist/m/c-2SPVQSHC.js +0 -1
  367. package/dist/m/c-336SGU5W.js +0 -1
  368. package/dist/m/c-3H6BZDNJ.js +0 -6
  369. package/dist/m/c-4DFEJ2PO.js +0 -3
  370. package/dist/m/c-56QRZTZH.js +0 -1
  371. package/dist/m/c-5J4E7O4Y.js +0 -4
  372. package/dist/m/c-5LKDWQTL.js +0 -19
  373. package/dist/m/c-5V3MOVJT.js +0 -1
  374. package/dist/m/c-6AZOGAPE.js +0 -1
  375. package/dist/m/c-6CRUUXOX.js +0 -5
  376. package/dist/m/c-6HMB24EV.js +0 -1
  377. package/dist/m/c-6KEVEQ7L.js +0 -1
  378. package/dist/m/c-A3E3NIMC.js +0 -1
  379. package/dist/m/c-BQ6LOBEE.js +0 -1
  380. package/dist/m/c-BTDI4Y23.js +0 -1
  381. package/dist/m/c-CJ3A7ZKE.js +0 -1
  382. package/dist/m/c-CQHPGKX2.js +0 -1
  383. package/dist/m/c-DFS4XZJV.js +0 -2
  384. package/dist/m/c-DNENWY4O.js +0 -1
  385. package/dist/m/c-E4LIR5X5.js +0 -1
  386. package/dist/m/c-FDNHHZY5.js +0 -1
  387. package/dist/m/c-FROJUZMV.js +0 -1
  388. package/dist/m/c-G2JPKCTP.js +0 -1
  389. package/dist/m/c-GB6TU7WV.js +0 -1
  390. package/dist/m/c-GFJJEBTU.js +0 -1
  391. package/dist/m/c-GJAEGGXY.js +0 -1
  392. package/dist/m/c-GOWBDM6J.js +0 -1
  393. package/dist/m/c-HFKUVACH.js +0 -1
  394. package/dist/m/c-HHDFCNHV.js +0 -1
  395. package/dist/m/c-IEM4NTCS.js +0 -1
  396. package/dist/m/c-JDCX26UP.js +0 -25
  397. package/dist/m/c-KGVGROZT.js +0 -1
  398. package/dist/m/c-KQ6TYJAP.js +0 -135
  399. package/dist/m/c-LFPHCXY3.js +0 -1
  400. package/dist/m/c-LRPUO2MX.js +0 -1
  401. package/dist/m/c-LWLBHLVC.js +0 -3
  402. package/dist/m/c-M2L47S2X.js +0 -7
  403. package/dist/m/c-MFP2ZWD4.js +0 -1
  404. package/dist/m/c-MKSQ4Y57.js +0 -1
  405. package/dist/m/c-NUFFJVVJ.js +0 -1
  406. package/dist/m/c-NUHSPT5P.js +0 -1
  407. package/dist/m/c-O72HVYSA.js +0 -1
  408. package/dist/m/c-OIBUTDXI.js +0 -1
  409. package/dist/m/c-OW5QIGG3.js +0 -1
  410. package/dist/m/c-PGRCK73F.js +0 -1
  411. package/dist/m/c-Q7NQD65V.js +0 -1
  412. package/dist/m/c-QH63LQOS.js +0 -1
  413. package/dist/m/c-R7IWVINQ.js +0 -1
  414. package/dist/m/c-RM7J7BV2.js +0 -1
  415. package/dist/m/c-S4ZEH46B.js +0 -9
  416. package/dist/m/c-SDAXDM2D.js +0 -1
  417. package/dist/m/c-SMGU7RT3.js +0 -235
  418. package/dist/m/c-SPIORRYS.js +0 -1
  419. package/dist/m/c-SWLRT66R.js +0 -1
  420. package/dist/m/c-TCJTVAFN.js +0 -1
  421. package/dist/m/c-TQEMVJCS.js +0 -1
  422. package/dist/m/c-TYLXX4NF.js +0 -21
  423. package/dist/m/c-U62OB4BT.js +0 -1
  424. package/dist/m/c-U6YWUAR3.js +0 -8
  425. package/dist/m/c-UDCSU5G5.js +0 -2
  426. package/dist/m/c-VI5RMSVY.js +0 -1
  427. package/dist/m/c-VRZN56XY.js +0 -1
  428. package/dist/m/c-W5MCHEGA.js +0 -1
  429. package/dist/m/c-WNYBLIOZ.js +0 -1
  430. package/dist/m/c-WWXIDH4O.js +0 -28
  431. package/dist/m/c-XCNTK76U.js +0 -1
  432. package/dist/m/c-XMS6DPBR.js +0 -1
  433. package/dist/m/c-ZUCOKOHT.js +0 -1
  434. package/dist/m/c-ZVPVXTJF.js +0 -4
  435. package/dist/m/c-ZVRKXKM7.js +0 -1
  436. package/dist/m/core/autosave-HRYGUWV6.js +0 -1
  437. package/dist/m/core/conflict-notice-6MROJN2L.js +0 -1
  438. package/dist/m/core/is-edit-mode-KTWA3O5N.js +0 -1
  439. package/dist/m/core/recovery-FLTCC2OC.js +0 -1
  440. package/dist/m/core/save-HQZ4S667.js +0 -1
  441. package/dist/m/core/save-core-KGQWFUNH.js +0 -1
  442. package/dist/m/core/snapshot-EGQZNT45.js +0 -1
  443. package/dist/m/core/unsaved-warning-IDJLD66H.js +0 -1
  444. package/dist/m/loader-S4I7GKXR.js +0 -1
  445. package/dist/m/manifest-4C419D67.js +0 -1
  446. package/dist/m/manifest-edit-B077F288.js +0 -1
  447. package/dist/m/plugins/ai-edit-QN4PMMNL.js +0 -6
  448. package/dist/m/plugins/indicator-URDBBOQO.js +0 -1
  449. package/dist/m/plugins/live-HFWD4OIH.js +0 -1
  450. package/dist/m/plugins/menu-3HIEQSI6.js +0 -138
  451. package/dist/m/plugins/page-JOKB6XPR.js +0 -2
  452. package/dist/m/plugins/page-view-NXOJHNW6.js +0 -1
  453. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  454. package/dist/m/plugins/sortable-GWT4PWDV.js +0 -1
  455. package/dist/m/plugins/source-4ZRSAIYM.js +0 -1
  456. package/dist/m/plugins/undo-VDA5P7FH.js +0 -1
  457. package/dist/m/plugins/upgrade-W7FG5VFG.js +0 -1
  458. package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +0 -21
  459. package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +0 -1
  460. package/dist/m/reader-core-DXYCURFF.js +0 -68
  461. package/dist/m/route/route-KOCZSEGN.js +0 -1
  462. package/dist/m/sync/live-sync-J7VMYTKS.js +0 -1
  463. package/dist/m/ui/index-3JLVI27O.js +0 -1
  464. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  465. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  466. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  467. /package/dist/m/plugins/{richclay-NRQDSWX6.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
package/src/page/items.js CHANGED
@@ -14,6 +14,7 @@ import { pickCsv } from "./csv-import.js";
14
14
  import { makeBoard, insertBoardNode } from "./board.js";
15
15
  import { chartTables } from "./chart-tables.js";
16
16
  import { viewFeatures } from "./view-hooks.js";
17
+ import { runFormatted } from "./format-command.js";
17
18
 
18
19
  const { TextSelection } = state;
19
20
  const N = schema.nodes;
@@ -27,6 +28,9 @@ export const BLOCK_TYPES = [
27
28
  { key: "ordered", label: "Numbered list", description: "A list with numbers", icon: "1.", type: "list_item", attrs: { kind: "ordered" }, hint: "1.", aliases: ["ol", "num", "numbered", "ordered"] },
28
29
  { key: "check", label: "Checklist", description: "Tasks to tick off", icon: "☐", type: "list_item", attrs: { kind: "check" }, hint: "[]", aliases: ["todo", "to-do", "task", "check", "checkbox"] },
29
30
  { key: "toggle", label: "Toggle", description: "Hide details under a line", icon: "▸", type: "toggle", attrs: { open: true }, hint: ">", aliases: ["collapse", "details", "fold"] },
31
+ { key: "toggle-h1", label: "Toggle heading 1", description: "Collapsible heading", icon: "H1", type: "toggle", attrs: { open: true, level: 1 }, aliases: ["toggle heading"] },
32
+ { key: "toggle-h2", label: "Toggle heading 2", description: "Collapsible heading", icon: "H2", type: "toggle", attrs: { open: true, level: 2 }, aliases: ["toggle heading"] },
33
+ { key: "toggle-h3", label: "Toggle heading 3", description: "Collapsible heading", icon: "H3", type: "toggle", attrs: { open: true, level: 3 }, aliases: ["toggle heading"] },
30
34
  { key: "callout", label: "Callout", description: "Make a note stand out", icon: "!", type: "callout", aliases: ["note", "aside", "info"] },
31
35
  { key: "quote", label: "Quote", description: "Set text apart as a quotation", icon: "\u201c", type: "quote", hint: "\"", aliases: ["blockquote", "quotation", "cite"] },
32
36
  { key: "code", label: "Code", description: "Code with highlighting", icon: "</>", type: "code_block", hint: "```", aliases: ["pre", "snippet"] },
@@ -34,18 +38,23 @@ export const BLOCK_TYPES = [
34
38
 
35
39
  export const INSERT_ITEMS = [
36
40
  { key: "divider", label: "Divider", description: "A line between sections", icon: "—", hint: "---", aliases: ["hr", "line", "separator", "rule"], insert: "divider" },
41
+ { key: "toc", label: "Table of contents", description: "Links to this page's headings", icon: "≡", aliases: ["toc", "contents", "outline", "index"], insert: "toc" },
37
42
  { key: "image", label: "Image", description: "Upload or paste a picture", icon: "▣", aliases: ["img", "picture", "photo"], insert: "image" },
38
43
  { key: "file", label: "File", description: "Attach any file", icon: "⎘", aliases: ["upload", "attachment", "pdf"], insert: "file" },
39
44
  { key: "table", label: "Table", description: "Rows and columns, paste from a spreadsheet", icon: "▦", aliases: ["grid", "spreadsheet"], insert: "table" },
40
45
  { key: "csv", label: "Import CSV…", description: "Turn a CSV file into a table", icon: "⇧", aliases: ["comma separated", "import"], insert: "csv" },
41
46
  { key: "board", label: "Board", description: "Cards in columns, like a kanban", icon: "▥", aliases: ["kanban", "cards", "columns", "status"], insert: "board" },
47
+ { key: "mention", label: "Mention", description: "Name someone on the page", icon: "@", aliases: ["person", "people", "assign"], command: "mentions" },
48
+ { key: "date", label: "Date", description: "Today, tomorrow or a calendar day", icon: "▧", aliases: ["calendar", "today", "tomorrow", "due"], command: "dates" },
49
+ { key: "emoji", label: "Emoji", description: "Insert one glyph", icon: "☺", aliases: ["smiley", "glyph", "symbol"], command: "emoji" },
50
+ { key: "import-markdown", label: "Import Markdown…", description: "Turn a Markdown file into blocks", icon: "M↓", aliases: ["md", "markdown", "import"], command: "importMarkdown" },
42
51
  ];
43
52
 
44
53
  export const CHART_ITEM = { key: "chart", label: "Chart", description: "A bar or line chart from a table", icon: "📊", aliases: ["graph", "plot", "bar", "line"], insert: "chart" };
45
54
 
46
55
  // The chart module and its view part load together, before the block draws: a
47
56
  // document that never takes a chart never fetches either.
48
- export const loadChartPart = (view) => (viewFeatures.get(view)?.ensure("chart") ?? Promise.resolve()).catch(() => {}).then(() => import("./chart.js"));
57
+ export const loadChartPart = (view) => (viewFeatures.get(view)?.ensure("chart") ?? Promise.resolve()).catch(() => {}).then(() => (view.isDestroyed ? null : import("./chart.js")));
49
58
 
50
59
  export const MENU_ITEMS = [...BLOCK_TYPES, ...INSERT_ITEMS];
51
60
 
@@ -55,7 +64,8 @@ export const insertItemsFor = (st, items = MENU_ITEMS) => (chartTables(st.doc).l
55
64
  export function blockTypeOf(node) {
56
65
  const t = node.type.name;
57
66
  return BLOCK_TYPES.find((b) => b.type === t && (!b.attrs
58
- || (t === "heading" ? b.attrs.level === node.attrs.level : t === "list_item" ? b.attrs.kind === node.attrs.kind : true))) ?? BLOCK_TYPES[0];
67
+ || (t === "heading" || t === "toggle" ? (b.attrs.level ?? null) === (node.attrs.level ?? null)
68
+ : t === "list_item" ? b.attrs.kind === node.attrs.kind : true))) ?? BLOCK_TYPES[0];
59
69
  }
60
70
 
61
71
  const startOf = (doc, index) => {
@@ -99,6 +109,40 @@ export function turnInto(st, index, item) {
99
109
  return canTurnInto(node, item) ? retype(st.tr, index, item) : REFUSED;
100
110
  }
101
111
 
112
+ // A toggle heading writes a `level` only a newer page format reads, so its first
113
+ // write asks the format gate.
114
+ export const isToggleHeading = (item) => item.type === "toggle" && item.attrs?.level != null;
115
+
116
+ const indexOfId = (doc, id) => {
117
+ let found = -1;
118
+ doc.forEach((n, _, i) => { if (n.attrs.id === id) found = i; });
119
+ return found;
120
+ };
121
+ const stale = { ok: false, code: "stale-target" };
122
+
123
+ // Turn every block named by `ids` into `item`. A block type from a later format,
124
+ // a quote or a toggle heading, asks the format gate first; after it answers the
125
+ // blocks are found again by id: one a peer moved away refuses instead of handing
126
+ // the type to whatever took its place.
127
+ export function turnBlocks(view, item, ids) {
128
+ const at = () => ids.map((id) => indexOfId(view.state.doc, id));
129
+ const write = () => {
130
+ const found = at();
131
+ if (found.includes(-1)) return stale;
132
+ if (found.some((i) => !canTurnInto(view.state.doc.child(i), item))) return REFUSED;
133
+ const tr = view.state.tr;
134
+ for (const i of found) retype(tr, i, item);
135
+ view.dispatch(asStep(tr).scrollIntoView());
136
+ view.focus();
137
+ return { ok: true };
138
+ };
139
+ if (isToggleHeading(item)) return runFormatted(view, write);
140
+ // A block type from a later format, like the turn into a quote, asks the gate
141
+ // before it writes, and a refusal is a notice rather than a change.
142
+ if (item.type === "quote") return import("./format-native-commands.js").then((menu) => (view.isDestroyed ? false : menu.gateTurn(view, write)));
143
+ return write();
144
+ }
145
+
102
146
  // A new empty paragraph after the block at `index` and its subtree, at the
103
147
  // block's own level, with the caret in it.
104
148
  export function insertAfter(st, index, tr = st.tr) {
@@ -144,6 +188,35 @@ export function insertDivider(st, index, tr = st.tr) {
144
188
  return tr.setSelection(TextSelection.create(tr.doc, to + 2));
145
189
  }
146
190
 
191
+ // The empty paragraph at `index` becomes a table of contents that keeps its id, pos and
192
+ // parent, followed by a new empty paragraph holding the caret.
193
+ export function replaceWithToc(tr, index) {
194
+ const at = startOf(tr.doc, index);
195
+ const node = tr.doc.child(index);
196
+ const next = tr.doc.maybeChild(index + 1);
197
+ const { id, pos, parent } = node.attrs;
198
+ tr.replaceWith(at, at + node.nodeSize, [
199
+ N.toc.create({ id, pos, parent, maxLevel: 3 }),
200
+ N.paragraph.create({ id: newId(), pos: keyAfter(pos, next ? next.attrs.pos : null), parent }),
201
+ ]);
202
+ return tr.setSelection(TextSelection.create(tr.doc, at + 2));
203
+ }
204
+
205
+ // Insert a table of contents for the block at `index`: in place of an empty paragraph,
206
+ // otherwise after the block's whole subtree at the block's own level.
207
+ export function insertToc(st, index, tr = st.tr) {
208
+ const node = tr.doc.child(index);
209
+ if (node.type === N.paragraph && node.content.size === 0) return replaceWithToc(tr, index);
210
+ const ents = outline(tr.doc), end = ents[index].subEnd;
211
+ const to = ents[end].end, next = ents[end + 1]?.node.attrs.pos ?? null;
212
+ const pos = keyAfter(ents[end].node.attrs.pos, next), parent = node.attrs.parent ?? null;
213
+ tr.insert(to, [
214
+ N.toc.create({ id: newId(), pos, parent, maxLevel: 3 }),
215
+ N.paragraph.create({ id: newId(), pos: keyAfter(pos, next), parent }),
216
+ ]);
217
+ return tr.setSelection(TextSelection.create(tr.doc, to + 2));
218
+ }
219
+
147
220
  // The skipped letters between a query and a candidate it can be read out of,
148
221
  // or Infinity when it cannot. Only queries of 2 to 32 letters count, so a single
149
222
  // letter never drags in half the menu.
@@ -180,22 +253,8 @@ export function rankItems(items, query) {
180
253
  return items.map((it, order) => ({ it, order, rank: rank(it) })).filter((x) => x.rank).sort((a, b) => a.rank[0] - b.rank[0] || a.rank[1] - b.rank[1] || a.order - b.order).map((x) => x.it);
181
254
  }
182
255
 
183
- // What a pick does in the insert menu for the block at `index`. A divider and
184
- // the file pickers have their own placement; a block type retypes an empty line
185
- // and otherwise goes into a new block below the line's whole subtree. A block
186
- // type from a later format asks the format gate before it writes anything.
187
- export function runItem(view, item, index, remove = null) {
188
- if (item.type !== "quote") return insertItem(view, item, index, remove);
189
- return import("./format-native-commands.js").then(({ runFormat }) => runFormat(view, () => insertItem(view, item, index, remove)));
190
- }
191
-
192
- function insertItem(view, item, index, remove = null) {
256
+ function writeItem(view, item, index, remove) {
193
257
  const st = view.state;
194
- if (item.insert === "image" || item.insert === "file") return pickFiles(view, item.insert === "image" ? "image/*" : "", hostPlace(st, index), remove);
195
- if (item.insert === "csv") return pickCsv(view, remove);
196
- if (item.insert === "table") return insertTableNode(st, view.dispatch, makeTable(), remove, null, index);
197
- if (item.insert === "board") return insertBoardNode(st, view.dispatch, makeBoard(), remove, index);
198
- if (item.insert === "chart") return loadChartPart(view).then(({ chartFromSlash }) => chartFromSlash(view, index, remove));
199
258
  let tr = st.tr;
200
259
  if (remove) tr.delete(remove.from, remove.to);
201
260
  const node = tr.doc.child(index);
@@ -210,3 +269,62 @@ function insertItem(view, item, index, remove = null) {
210
269
  view.focus();
211
270
  return true;
212
271
  }
272
+
273
+ // A table of contents is a block only a newer page format reads, so its first write asks
274
+ // the format gate; the block the pick was made on is found again after it answers, so one
275
+ // a peer moved away refuses instead of handing the toc its place.
276
+ export function runTocItem(view, index, remove = null) {
277
+ const st = view.state;
278
+ const id = st.doc.maybeChild(index)?.attrs.id ?? null;
279
+ const del = remove && { ...remove, text: st.doc.textBetween(remove.from, remove.to) };
280
+ return runFormatted(view, () => {
281
+ const now = view.state;
282
+ const at = id === null ? -1 : indexOfId(now.doc, id);
283
+ if (at < 0 || (del && now.doc.textBetween(del.from, del.to) !== del.text)) return stale;
284
+ let tr = del ? now.tr.delete(del.from, del.to) : now.tr;
285
+ tr = insertToc(now, indexOfId(tr.doc, id), tr);
286
+ view.dispatch(asStep(tr).scrollIntoView());
287
+ view.focus();
288
+ return { ok: true };
289
+ });
290
+ }
291
+
292
+ // The menus ask for an optional command through the editor's own entry point, at the moment
293
+ // somebody picks the row: importing it here keeps this module plain data, which the block
294
+ // menus, the Markdown rules and the editor all share, while the command still loads only on
295
+ // first use. `id` is the row it has in COMMAND_FEATURES.
296
+ export const runMenuCommand = (view, id, args) => import("./editor.js").then(({ runPageCommand }) => (view.isDestroyed ? { ok: false, code: "not-editing" } : runPageCommand(view, id, args)));
297
+
298
+ // A row whose work is an optional command: the typed query goes with the pick, and the
299
+ // command is asked for by its id.
300
+ function runCommandItem(view, id, remove) {
301
+ if (remove) view.dispatch(view.state.tr.delete(remove.from, remove.to));
302
+ return runMenuCommand(view, id, {});
303
+ }
304
+
305
+ // What a pick does in the insert menu for the block at `index`. A divider and
306
+ // the file pickers have their own placement; a block type retypes an empty line
307
+ // and otherwise goes into a new block below the line's whole subtree. A block
308
+ // type from a later format asks the format gate before it writes anything, and
309
+ // a toggle heading or a quote is found again after it answers: the block and the
310
+ // typed query that moved or changed refuse as a stale target.
311
+ export function runItem(view, item, index, remove = null) {
312
+ const st = view.state;
313
+ if (item.insert === "image" || item.insert === "file") return pickFiles(view, item.insert === "image" ? "image/*" : "", hostPlace(st, index), remove);
314
+ if (item.insert === "csv") return pickCsv(view, remove);
315
+ if (item.insert === "table") return insertTableNode(st, view.dispatch, makeTable(), remove, null, index);
316
+ if (item.insert === "board") return insertBoardNode(st, view.dispatch, makeBoard(), remove, index);
317
+ if (item.insert === "chart") return loadChartPart(view).then((part) => (part && !view.isDestroyed ? part.chartFromSlash(view, index, remove) : false));
318
+ if (item.insert === "toc") return runTocItem(view, index, remove);
319
+ if (item.command) return runCommandItem(view, item.command, remove);
320
+ if (!isToggleHeading(item) && item.type !== "quote") return writeItem(view, item, index, remove);
321
+ const id = st.doc.maybeChild(index)?.attrs.id ?? null;
322
+ const del = remove && { ...remove, text: st.doc.textBetween(remove.from, remove.to) };
323
+ return runFormatted(view, () => {
324
+ const now = view.state;
325
+ const at = id === null ? -1 : indexOfId(now.doc, id);
326
+ // The typed query is found again too: a line that changed underneath refuses.
327
+ if (at < 0 || (del && now.doc.textBetween(del.from, del.to) !== del.text)) return stale;
328
+ return writeItem(view, item, at, del);
329
+ });
330
+ }
@@ -129,7 +129,8 @@ export const MARKDOWN_RULES = [
129
129
  // converts only when the gate answered applied; the transaction it dispatches is
130
130
  // the input-rule one, recorded on this plugin so Backspace puts the literal `" `
131
131
  // back and undo keeps the conversion whole. The typed space is never swallowed:
132
- // when the format cannot be written, the space is inserted as itself.
132
+ // when the format cannot be written the space is inserted as itself, and a line
133
+ // that changed while the gate waited takes no write at all.
133
134
  function quoteShortcut(plugin, view, from, to, text) {
134
135
  if (text !== " " || from !== to) return false;
135
136
  const st = view.state;
@@ -137,7 +138,10 @@ function quoteShortcut(plugin, view, from, to, text) {
137
138
  const node = $from.parent;
138
139
  if ($from.depth !== 1 || node.type !== N.paragraph || $from.parentOffset !== 1 || node.textContent !== '"') return false;
139
140
  if (!view.editable || typeof view.runFormatCommand !== "function") return false;
140
- const plain = () => { if (!view.isDestroyed) view.dispatch(st.tr.insertText(" ", from, to)); };
141
+ const plain = () => {
142
+ if (view.isDestroyed || view.state.doc !== st.doc) return;
143
+ view.dispatch(view.state.tr.insertText(" ", from, to));
144
+ };
141
145
  const at = $from.before(1);
142
146
  import("./format-native-commands.js").then(({ quoteFromSpace }) => quoteFromSpace(plugin, view, { at, from, to }, plain), plain);
143
147
  return true;
package/src/page/media.js CHANGED
@@ -3,25 +3,31 @@
3
3
  // Nothing reaches the record until the host has answered with a URL; then one
4
4
  // transaction inserts the block at the bookmark. A cancelled or failed upload
5
5
  // leaves no trace in the record.
6
- import { state, view as pmView } from "../vendor/prosemirror.vendor.js";
6
+ //
7
+ // The paste, the drop, the picker and the upload itself are here, so a file lands
8
+ // where it was dropped without waiting for anything. What an upload can then offer
9
+ // (retry, embedding, the alt text and width the chrome sets) is in the media view
10
+ // part, which loads with the first paste, drop, insert or upload.
11
+ import { state } from "../vendor/prosemirror.vendor.js";
7
12
  import { schema } from "./schema.js";
8
13
  import { outline } from "./blocks.js";
9
14
  import { newId } from "./model.js";
10
15
  import { asStep } from "./blocksel.js";
11
16
  import { announce } from "./announce.js";
12
17
  import { safeFileHref } from "./address.js";
13
- import { mediaViews } from "./media-views.js";
14
18
 
15
19
  const { Plugin, PluginKey } = state;
16
- const { Decoration, DecorationSet } = pmView;
17
20
  const N = schema.nodes;
18
21
 
19
22
  export const mediaKey = new PluginKey("page-media");
20
23
  export const EMBED_MAX = 1.5 * 1024 * 1024;
21
- export const RETRY = new Set(["timeout", "network", "error", "bad-response"]);
22
- const runtime = new WeakMap();
24
+ export const mediaRuntime = new WeakMap();
23
25
 
24
- const boundary = (doc, pos) => {
26
+ // The view part hands its upload actions back here when it installs, so the
27
+ // bookmark's own buttons reach them without this module importing it.
28
+ export const mediaParts = new WeakMap();
29
+
30
+ export const boundary = (doc, pos) => {
25
31
  const $p = doc.resolve(Math.max(0, Math.min(pos, doc.content.size)));
26
32
  return $p.depth === 0 ? $p.pos : $p.after(1);
27
33
  };
@@ -109,31 +115,30 @@ export function insertFiles(view, files, place = hostPlace(view.state, view.stat
109
115
  return true;
110
116
  }
111
117
 
112
- const live = (view, id) => mediaKey.getState(view.state).items.find((x) => x.id === id);
113
- const update = (view, id, patch) => view.dispatch(view.state.tr.setMeta(mediaKey, { update: { id, ...patch } }));
114
- const readDataUrl = (file) => new Promise((ok, no) => { const r = new FileReader(); r.onload = () => ok(r.result); r.onerror = () => no(r.error); r.readAsDataURL(file); });
115
- const attrsFor = (it, src) => (it.kind === "image" ? { src, alt: "", caption: "" } : { src, name: it.name, size: it.file.size ?? null, mime: it.file.type || null });
118
+ export const live = (view, id) => mediaKey.getState(view.state).items.find((x) => x.id === id);
119
+ export const update = (view, id, patch) => view.dispatch(view.state.tr.setMeta(mediaKey, { update: { id, ...patch } }));
120
+ export const attrsFor = (it, src) => (it.kind === "image" ? { src, alt: "", caption: "" } : { src, name: it.name, size: it.file.size ?? null, mime: it.file.type || null });
116
121
 
117
- function settle(it, view, tr) {
122
+ export function settle(it, view, tr) {
118
123
  if (it.preview) URL.revokeObjectURL?.(it.preview);
119
124
  if (tr) view.dispatch(tr);
120
125
  }
121
126
 
122
- async function run(view, it) {
123
- const rt = runtime.get(view);
127
+ export async function run(view, it) {
128
+ const rt = mediaRuntime.get(view);
124
129
  const abort = new AbortController();
125
130
  rt.aborts.set(it.id, abort);
126
131
  let last = -5;
127
132
  const onProgress = ({ percent }) => {
128
133
  if (percent - last < 5 && percent !== 100) return;
129
134
  last = percent;
130
- if (runtime.has(view) && live(view, it.id)) update(view, it.id, { percent });
135
+ if (mediaRuntime.has(view) && live(view, it.id)) update(view, it.id, { percent });
131
136
  };
132
137
  let res;
133
138
  try { res = await rt.upload(it.file, { onProgress, signal: abort.signal }); }
134
139
  catch { res = { code: "network", msg: "Upload failed" }; }
135
140
  rt.aborts.delete(it.id);
136
- if (!runtime.has(view) || !live(view, it.id)) return;
141
+ if (!mediaRuntime.has(view) || !live(view, it.id)) return;
137
142
  if (res?.ok) {
138
143
  const src = safeFileHref(res.uploads?.[0]?.url);
139
144
  if (src) {
@@ -159,46 +164,19 @@ async function run(view, it) {
159
164
  announce(view, `${it.name}: ${message}`);
160
165
  }
161
166
 
162
- export async function embedUpload(view, id) {
163
- const it = live(view, id);
164
- if (!it || it.state !== "embed-choice") return;
165
- const src = await readDataUrl(it.file);
166
- if (!runtime.has(view) || !live(view, id)) return;
167
- settle(it, view, placeTr(view.state, id, attrsFor(it, src)));
168
- announce(view, "Saved inside the document");
169
- }
170
-
171
- export function retryUpload(view, id) {
172
- const it = live(view, id);
173
- if (!it) return;
174
- update(view, id, { state: "uploading", percent: 0, code: null, message: null });
175
- run(view, live(view, id));
176
- }
177
-
178
- export function cancelUpload(view, id) {
179
- const rt = runtime.get(view);
180
- const abort = rt?.aborts.get(id);
181
- const it = live(view, id);
182
- if (it) settle(it, view, removeTr(view.state, id));
183
- rt?.aborts.delete(id);
184
- abort?.abort();
185
- }
186
-
187
167
  const htmlHasText = (html) => html.replace(/<(meta|style|img)\b[^>]*>/gi, "").replace(/<[^>]+>/g, "").trim() !== "";
188
168
 
189
169
  export function mediaPlugin({ upload, features = null }) {
190
- let editorView = null;
191
170
  // Only the bookmark widget waits for the view part: the upload and its block
192
171
  // are core, and the block draws through the schema until the part arrives.
193
172
  const showBookmark = (view) => {
194
- if (!features || features.has("media") || !runtime.has(view)) return;
173
+ if (!features || features.has("media") || !mediaRuntime.has(view)) return;
195
174
  features.ensure("media").catch(() => {});
196
175
  };
197
176
  return new Plugin({
198
177
  key: mediaKey,
199
178
  state: mediaState,
200
179
  view(view) {
201
- editorView = view;
202
180
  const doc = view.dom.ownerDocument;
203
181
  const input = doc.createElement("input");
204
182
  input.type = "file";
@@ -206,7 +184,7 @@ export function mediaPlugin({ upload, features = null }) {
206
184
  input.hidden = true;
207
185
  input.className = "page-file-input";
208
186
  view.dom.parentElement.append(input);
209
- runtime.set(view, { upload, aborts: new Map(), input });
187
+ mediaRuntime.set(view, { upload, aborts: new Map(), input });
210
188
  input.addEventListener("change", () => {
211
189
  const files = [...(input.files ?? [])];
212
190
  const picker = mediaKey.getState(view.state).picker;
@@ -230,18 +208,18 @@ export function mediaPlugin({ upload, features = null }) {
230
208
  const b = e.target.closest?.(".page-upload-btn");
231
209
  if (!b) return;
232
210
  e.preventDefault();
211
+ const part = mediaParts.get(view);
233
212
  const id = b.dataset.upload;
234
- if (b.dataset.act === "retry") retryUpload(view, id);
235
- else if (b.dataset.act === "embed") embedUpload(view, id);
236
- else cancelUpload(view, id);
213
+ if (b.dataset.act === "retry") part?.retryUpload(view, id);
214
+ else if (b.dataset.act === "embed") part?.embedUpload(view, id);
215
+ else part?.cancelUpload(view, id);
237
216
  };
238
217
  view.dom.addEventListener("click", onClick);
239
218
  return {
240
219
  destroy() {
241
220
  view.dom.removeEventListener("click", onClick);
242
- if (editorView === view) editorView = null;
243
- const rt = runtime.get(view);
244
- runtime.delete(view);
221
+ const rt = mediaRuntime.get(view);
222
+ mediaRuntime.delete(view);
245
223
  for (const it of mediaKey.getState(view.state).items) if (it.preview) URL.revokeObjectURL?.(it.preview);
246
224
  for (const a of rt.aborts.values()) a.abort();
247
225
  input.remove();
@@ -249,14 +227,6 @@ export function mediaPlugin({ upload, features = null }) {
249
227
  };
250
228
  },
251
229
  props: {
252
- decorations(st) {
253
- const { items } = mediaKey.getState(st);
254
- const render = editorView ? mediaViews.get(editorView) : null;
255
- if (!items.length || !render) return null;
256
- return DecorationSet.create(st.doc, items.map((it) => Decoration.widget(boundary(st.doc, it.pos), (view) => render(view.dom.ownerDocument, it), {
257
- side: -1000 + it.order, key: `${it.id}:${it.state}:${it.percent}`, stopEvent: () => true, ignoreSelection: true,
258
- })));
259
- },
260
230
  handlePaste(view, event) {
261
231
  const data = event.clipboardData;
262
232
  const files = [...(data?.files ?? [])];
@@ -287,72 +257,18 @@ export function mediaPlugin({ upload, features = null }) {
287
257
  });
288
258
  }
289
259
 
290
- export function pickFiles(view, accept, place = hostPlace(view.state, view.state.selection.$head.index(0)), remove = null) {
291
- const rt = runtime.get(view);
292
- if (!rt) return false;
293
- const picker = { place, remove: remove && { ...remove, text: view.state.doc.textBetween(remove.from, remove.to) } };
294
- view.dispatch(view.state.tr.setMeta(mediaKey, { picker }));
295
- rt.input.accept = accept;
296
- rt.input.click();
297
- return true;
298
- }
299
-
300
260
  const indexOfId = (doc, id) => {
301
261
  let found = -1;
302
262
  doc.forEach((n, _, i) => { if (n.attrs.id === id) found = i; });
303
263
  return found;
304
264
  };
305
265
 
306
- export function setImageAttrs(view, id, patch) {
307
- const st = view.state;
308
- const index = indexOfId(st.doc, id);
309
- if (index < 0) return false;
310
- let at = 0;
311
- for (let i = 0; i < index; i++) at += st.doc.child(i).nodeSize;
312
- const node = st.doc.child(index);
313
- view.dispatch(asStep(st.tr.setNodeMarkup(at, null, { ...node.attrs, ...patch })));
314
- return true;
315
- }
316
-
317
- export function openAltEditor(view, id) {
318
- const index = indexOfId(view.state.doc, id);
319
- if (index < 0) return false;
320
- const shell = view.dom.parentElement;
321
- const doc = shell.ownerDocument;
322
- let at = 0;
323
- for (let i = 0; i < index; i++) at += view.state.doc.child(i).nodeSize;
324
- const box = doc.createElement("div");
325
- box.className = "page-link";
326
- box.dataset.mode = "alt";
327
- const input = doc.createElement("input");
328
- input.className = "page-link-input";
329
- input.type = "text";
330
- input.value = view.state.doc.child(index).attrs.alt || "";
331
- input.placeholder = "Describe the image";
332
- input.setAttribute("aria-label", "Alt text");
333
- box.append(input);
334
- const rect = view.nodeDOM(at)?.getBoundingClientRect?.();
335
- const shellBox = shell.getBoundingClientRect();
336
- if (rect) {
337
- box.style.setProperty("--page-pop-x", `${rect.left - shellBox.left}px`);
338
- box.style.setProperty("--page-pop-y", `${rect.bottom - shellBox.top + 6}px`);
339
- }
340
- shell.append(box);
341
- let done = false;
342
- const close = (commit, refocus = true) => {
343
- if (done) return;
344
- done = true;
345
- const value = input.value.trim();
346
- box.remove();
347
- const i = indexOfId(view.state.doc, id);
348
- if (commit && i >= 0 && value !== view.state.doc.child(i).attrs.alt) setImageAttrs(view, id, { alt: value });
349
- if (refocus) view.focus();
350
- };
351
- input.addEventListener("keydown", (e) => {
352
- if (e.key === "Enter") { e.preventDefault(); close(true); }
353
- else if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); close(false); }
354
- });
355
- input.addEventListener("blur", () => close(true, false));
356
- input.focus();
266
+ export function pickFiles(view, accept, place = hostPlace(view.state, view.state.selection.$head.index(0)), remove = null) {
267
+ const rt = mediaRuntime.get(view);
268
+ if (!rt) return false;
269
+ const picker = { place, remove: remove && { ...remove, text: view.state.doc.textBetween(remove.from, remove.to) } };
270
+ view.dispatch(view.state.tr.setMeta(mediaKey, { picker }));
271
+ rt.input.accept = accept;
272
+ rt.input.click();
357
273
  return true;
358
274
  }
@@ -5,6 +5,7 @@ const { Decoration, DecorationSet } = pmView;
5
5
 
6
6
  export const PLACEHOLDER_TEXT = "Type / for blocks";
7
7
  export const TITLE_PLACEHOLDER = "Untitled";
8
+ export const CHECK_PLACEHOLDER = "To do";
8
9
 
9
10
  function titlePlaceholder(doc) {
10
11
  let done = false;
@@ -18,11 +19,23 @@ function titlePlaceholder(doc) {
18
19
  return decoration;
19
20
  }
20
21
 
22
+ // An empty check item keeps its tags, so the line it leaves is the whole click area;
23
+ // the words say where to click.
24
+ function checkPlaceholders(doc) {
25
+ const decorations = [];
26
+ doc.forEach((node, offset) => {
27
+ if (node.type !== schema.nodes.list_item || node.attrs.kind !== "check") return;
28
+ if (node.content.size || node.attrs.checked) return;
29
+ decorations.push(Decoration.node(offset, offset + node.nodeSize, { class: "page-check-placeholder", "data-placeholder": CHECK_PLACEHOLDER }));
30
+ });
31
+ return decorations;
32
+ }
33
+
21
34
  export function placeholderPlugin(text = PLACEHOLDER_TEXT) {
22
35
  return new state.Plugin({
23
36
  props: {
24
37
  decorations(st) {
25
- const decorations = [];
38
+ const decorations = checkPlaceholders(st.doc);
26
39
  const title = titlePlaceholder(st.doc);
27
40
  if (title) decorations.push(title);
28
41
  const { selection } = st;