@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
@@ -29,6 +29,9 @@
29
29
  // lands between `capabilities()` and `activate()` is refused rather than missed.
30
30
 
31
31
  import { PAGE_FORMAT, PAGE_FORMAT_EXTENSION, PAGE_FORMAT_UPGRADE, formatCapability, notePageFormatFloor, pageFormatDecision, pageFormatFloor } from "./format-level.js";
32
+ import { registerUnsavedState } from "../lib/unsaved-state.js";
33
+
34
+ const PENDING_COMMAND = "page-format-command";
32
35
 
33
36
  const UPGRADE_MESSAGE = "This document uses a newer page format; update the editor in the other tabs to keep writing";
34
37
 
@@ -46,6 +49,10 @@ export function createFormatGate({
46
49
  let floor = 0;
47
50
  let pending = null;
48
51
  let lanePending = false;
52
+ let laneReceipt = null;
53
+ let laneAsked = false;
54
+ let laneJoin = Promise.resolve(false);
55
+ let laneJoining = null;
49
56
  let disposed = false;
50
57
 
51
58
  // The floor this gate knows the document at. A save or a room answer can tell
@@ -54,9 +61,22 @@ export function createFormatGate({
54
61
  function note(level) {
55
62
  floor = Math.max(floor, formatCapability(level));
56
63
  notePageFormatFloor(floor);
64
+ if (floor >= PAGE_FORMAT) requestLane();
57
65
  return floor;
58
66
  }
59
67
 
68
+ // A raised floor is one lane switch per tab, however this tab learned of it:
69
+ // its own accepted save (activateNow joins with that receipt), another tab's
70
+ // write that came in with the document, or a relay's meta. A switch the lane
71
+ // has not accepted yet stays pending, and the retry path covers it.
72
+ function requestLane() {
73
+ if (laneAsked || disposed) return laneJoin;
74
+ laneAsked = true;
75
+ lanePending = true;
76
+ laneJoin = joinLane(null);
77
+ return laneJoin;
78
+ }
79
+
60
80
  async function read() {
61
81
  try {
62
82
  const answer = await host.read();
@@ -88,7 +108,10 @@ export function createFormatGate({
88
108
  if (disposed) return { status: "disposed", retry: false };
89
109
  if (!(await supportedHere())) return { status: "unsupported-host", code: "page-format-unsupported", extension: PAGE_FORMAT_EXTENSION };
90
110
  await read();
91
- if (floor >= PAGE_FORMAT) return { status: "active", floor };
111
+ if (floor >= PAGE_FORMAT) {
112
+ await requestLane();
113
+ return { status: "active", floor };
114
+ }
92
115
  const admitted = await admittance();
93
116
  const early = pageFormatDecision({ floor, required: PAGE_FORMAT, capability, admitted });
94
117
  if (!early.ok) return upgrade(early.minimum);
@@ -106,28 +129,45 @@ export function createFormatGate({
106
129
  if (disposed) {
107
130
  // The save was accepted, so the document is at floor 1 either way; this
108
131
  // tab is gone and must not act on it.
132
+ laneAsked = true;
109
133
  note(PAGE_FORMAT);
110
134
  return { status: "disposed", retry: false };
111
135
  }
136
+ laneAsked = true;
112
137
  note(PAGE_FORMAT);
113
138
  lanePending = true;
114
- await joinLane(answer.receipt ?? null);
115
- return { status: "active", floor, receipt: answer.receipt ?? null };
139
+ laneReceipt = answer.receipt ?? null;
140
+ await joinLane(laneReceipt);
141
+ return { status: "active", floor, receipt: laneReceipt };
116
142
  }
117
143
 
118
144
  // Joining the new lane scope happens after the receipt is accepted, and only
119
145
  // then: an interrupted switch leaves this tab on floor 0's scope with every
120
- // store intact and the switch still pending, never a lane it cannot cover.
146
+ // store intact and the switch still pending, never a lane it cannot cover. The
147
+ // receipt is kept, so the retry of a refused switch is covered by the same write.
121
148
  async function joinLane(receipt) {
122
149
  if (!lane || !lanePending) return false;
150
+ if (laneJoining) return laneJoining;
151
+ const covering = receipt ?? laneReceipt;
152
+ laneJoining = (async () => {
153
+ try {
154
+ // The receipt is what says the pending work went with an accepted write.
155
+ const result = await lane.switchScope(PAGE_FORMAT, { covered: Boolean(covering), receipt: covering });
156
+ // "already" is the lane answering that this tab is on that scope: the
157
+ // join held, and there is nothing left pending.
158
+ if (result && (result.status === "switched" || result.status === "already")) {
159
+ lanePending = false;
160
+ laneReceipt = null;
161
+ return true;
162
+ }
163
+ } catch {}
164
+ return false;
165
+ })();
123
166
  try {
124
- const result = await lane.switchTo({ floor: PAGE_FORMAT, receipt });
125
- if (result && result.status === "switched") {
126
- lanePending = false;
127
- return true;
128
- }
129
- } catch {}
130
- return false;
167
+ return await laneJoining;
168
+ } finally {
169
+ laneJoining = null;
170
+ }
131
171
  }
132
172
 
133
173
  function activate() {
@@ -150,16 +190,25 @@ export function createFormatGate({
150
190
  // same attempt, and a retry after a failure starts a new one.
151
191
  activate,
152
192
  // A new-format command: capture what it applies to, ask the gate, and apply
153
- // only when activation succeeded and nothing moved underneath it.
193
+ // only when activation succeeded and nothing moved underneath it. While it waits on
194
+ // activation the work is unsaved and belongs to the person who asked for it: the
195
+ // exit asks the unsaved-state registry, and this command answers to its own name
196
+ // until it applies or fails, so an exit is refused rather than committing and
197
+ // disposing the editor underneath it.
154
198
  async run(command) {
155
199
  const stamp = { revision: revision(), installed: installed() };
156
- const result = await activate();
157
- if (result.status !== "active") return result;
158
- if (disposed) return { status: "disposed", retry: false };
159
- if (revision() !== stamp.revision || installed() !== stamp.installed) {
160
- return { status: "retry", code: "format-gate-stale", retry: true };
200
+ const unsaved = registerUnsavedState({ id: PENDING_COMMAND, isPending: () => true });
201
+ try {
202
+ const result = await activate();
203
+ if (result.status !== "active") return result;
204
+ if (disposed) return { status: "disposed", retry: false };
205
+ if (revision() !== stamp.revision || installed() !== stamp.installed) {
206
+ return { status: "retry", code: "format-gate-stale", retry: true };
207
+ }
208
+ return { status: "applied", floor, result: await command?.apply?.(stamp) };
209
+ } finally {
210
+ unsaved.dispose();
161
211
  }
162
- return { status: "applied", floor, result: await command?.apply?.(stamp) };
163
212
  },
164
213
  // Retry what is still pending: a lane switch whose receipt is in hand, or an
165
214
  // activation that failed on the wire. Unsaved work is never part of this.
@@ -173,6 +222,7 @@ export function createFormatGate({
173
222
  dispose() {
174
223
  disposed = true;
175
224
  lanePending = false;
225
+ laneReceipt = null;
176
226
  return { status: "disposed" };
177
227
  },
178
228
  };
@@ -27,8 +27,21 @@ function servedFloor() {
27
27
  // never uses a newer format never downloads the lane to raise one.
28
28
  const savePage = async (options) => (await import("../core/save.js")).savePageForce(() => {}, options);
29
29
 
30
+ // A forced save asked for while another is on the wire is skipped rather than queued.
31
+ // The activation is the person's own request, so it waits for the save ahead of it to
32
+ // settle and asks again, instead of answering "try again" for a save it never sent.
33
+ const SKIPPED_ATTEMPTS = 4;
34
+ const SKIPPED_WAIT_MS = 10000;
35
+ const skippedBehindSave = (result) => result?.ok === false && result?.msgType === "skipped" && /in progress/i.test(result?.msg ?? "");
36
+ const saveSettles = async () => {
37
+ const { isSaveInProgress } = await import("../core/save-core.js");
38
+ const until = Date.now() + SKIPPED_WAIT_MS;
39
+ while (isSaveInProgress() && Date.now() < until) await new Promise((r) => setTimeout(r, 50));
40
+ };
41
+
30
42
  export function createFormatHost({
31
43
  save = savePage,
44
+ settles = saveSettles,
32
45
  supported = () => hostPageFormat().then((floor) => floor === PAGE_FORMAT),
33
46
  } = {}) {
34
47
  return {
@@ -43,6 +56,10 @@ export function createFormatHost({
43
56
  let result;
44
57
  try {
45
58
  result = await save({ pageFormatFloor: floor });
59
+ for (let attempt = 1; attempt < SKIPPED_ATTEMPTS && skippedBehindSave(result); attempt += 1) {
60
+ await settles();
61
+ result = await save({ pageFormatFloor: floor });
62
+ }
46
63
  } catch (error) {
47
64
  return { accepted: false, code: error?.code ?? FAILED };
48
65
  }
@@ -12,6 +12,7 @@ import { history } from "../vendor/prosemirror.vendor.js";
12
12
  import { schema } from "./schema.js";
13
13
  import { PALETTE, markable } from "./format-native.js";
14
14
  import { announce } from "./announce.js";
15
+ import toast from "../ui/toast-lazy.js";
15
16
  import { openPageMenu } from "./chrome.js";
16
17
 
17
18
  // blocks.js's STEP, read here rather than imported from it: this module is part
@@ -72,8 +73,11 @@ export function formatMark(view, name, attrs = {}, { reset = false } = {}) {
72
73
  // The four formats pasted in from elsewhere are floor-1 content too: the slice is
73
74
  // inserted only once the gate answered applied. A refusal keeps the text and
74
75
  // writes no descriptor; a selection that moved while the answer came back takes
75
- // neither.
76
+ // neither, and neither does a document that changed underneath the paste: the
77
+ // plain fallback would write at a place a peer edit has moved, so the text stays
78
+ // in the clipboard instead.
76
79
  export function pasteFormats(view, slice, from, to) {
80
+ const before = view.state.doc;
77
81
  const place = () => {
78
82
  if (view.isDestroyed) return false;
79
83
  const st = view.state;
@@ -83,12 +87,12 @@ export function pasteFormats(view, slice, from, to) {
83
87
  };
84
88
  const plain = () => {
85
89
  const text = slice.content.textBetween(0, slice.content.size, "\n", " ");
86
- if (!view.isDestroyed && text) view.dispatch(view.state.tr.insertText(text, from, to));
90
+ if (!view.isDestroyed && text && view.state.doc === before) view.dispatch(view.state.tr.insertText(text, from, to));
87
91
  return false;
88
92
  };
89
93
  runFormat(view, place).then((result) => {
90
94
  if (result?.status === "applied") return;
91
- if (!view.isDestroyed) announce(view, result?.message ?? "That format isn\u2019t available here.");
95
+ refuse(view, result?.message ?? "That format isn\u2019t available here.");
92
96
  plain();
93
97
  }, plain);
94
98
  }
@@ -106,7 +110,11 @@ export function quoteFromSpace(plugin, view, { at, from, to }, plain) {
106
110
  view.dispatch(asStep(tr.setMeta(plugin, { transform: tr, from, to, text: " " })));
107
111
  return true;
108
112
  };
109
- view.runFormatCommand(convert).then((result) => { if (result?.status !== "applied") plain(); }, plain);
113
+ view.runFormatCommand(convert).then((result) => {
114
+ if (result?.status === "applied") return;
115
+ if (result) refuse(view, refusalText(result));
116
+ plain();
117
+ }, plain);
110
118
  }
111
119
 
112
120
  // The More menu's rows: the underline command, the palette of a color mark and
@@ -115,8 +123,16 @@ export function quoteFromSpace(plugin, view, { at, from, to }, plain) {
115
123
  const CANNOT_MARK = "This text can\u2019t take that format.";
116
124
  const refusalText = (result) => (result.status === "upgrade-required" ? result.message : result.code === "mark-not-allowed" ? CANNOT_MARK : "That format isn\u2019t available here.");
117
125
 
126
+ // A refusal is said to a screen reader and shown, since a sighted reader whose
127
+ // click did nothing otherwise has no way to know why.
128
+ export const refuse = (view, text) => {
129
+ if (view.isDestroyed) return;
130
+ announce(view, text);
131
+ toast(text, "error");
132
+ };
133
+
118
134
  const notice = (view, result) => {
119
- if (result?.status !== "applied" && !view.isDestroyed) announce(view, refusalText(result ?? {}));
135
+ if (result?.status !== "applied") refuse(view, refusalText(result ?? {}));
120
136
  };
121
137
 
122
138
  export const runNative = (view, name, attrs = {}, options = undefined) => {
@@ -0,0 +1,64 @@
1
+ // The image menu's own actions: the alt text field and the width presets, reached
2
+ // by the chrome's actions menu. Its own module, so a document that never sets
3
+ // either downloads neither.
4
+ import { asStep } from "./blocksel.js";
5
+
6
+ const indexOfId = (doc, id) => {
7
+ let found = -1;
8
+ doc.forEach((n, _, i) => { if (n.attrs.id === id) found = i; });
9
+ return found;
10
+ };
11
+
12
+ export function setImageAttrs(view, id, patch) {
13
+ const st = view.state;
14
+ const index = indexOfId(st.doc, id);
15
+ if (index < 0) return false;
16
+ let at = 0;
17
+ for (let i = 0; i < index; i++) at += st.doc.child(i).nodeSize;
18
+ const node = st.doc.child(index);
19
+ view.dispatch(asStep(st.tr.setNodeMarkup(at, null, { ...node.attrs, ...patch })));
20
+ return true;
21
+ }
22
+
23
+ export function openAltEditor(view, id) {
24
+ const index = indexOfId(view.state.doc, id);
25
+ if (index < 0) return false;
26
+ const shell = view.dom.parentElement;
27
+ const doc = shell.ownerDocument;
28
+ let at = 0;
29
+ for (let i = 0; i < index; i++) at += view.state.doc.child(i).nodeSize;
30
+ const box = doc.createElement("div");
31
+ box.className = "page-link";
32
+ box.dataset.mode = "alt";
33
+ const input = doc.createElement("input");
34
+ input.className = "page-link-input";
35
+ input.type = "text";
36
+ input.value = view.state.doc.child(index).attrs.alt || "";
37
+ input.placeholder = "Describe the image";
38
+ input.setAttribute("aria-label", "Alt text");
39
+ box.append(input);
40
+ const rect = view.nodeDOM(at)?.getBoundingClientRect?.();
41
+ const shellBox = shell.getBoundingClientRect();
42
+ if (rect) {
43
+ box.style.setProperty("--page-pop-x", `${rect.left - shellBox.left}px`);
44
+ box.style.setProperty("--page-pop-y", `${rect.bottom - shellBox.top + 6}px`);
45
+ }
46
+ shell.append(box);
47
+ let done = false;
48
+ const close = (commit, refocus = true) => {
49
+ if (done) return;
50
+ done = true;
51
+ const value = input.value.trim();
52
+ box.remove();
53
+ const i = indexOfId(view.state.doc, id);
54
+ if (commit && i >= 0 && value !== view.state.doc.child(i).attrs.alt) setImageAttrs(view, id, { alt: value });
55
+ if (refocus) view.focus();
56
+ };
57
+ input.addEventListener("keydown", (e) => {
58
+ if (e.key === "Enter") { e.preventDefault(); close(true); }
59
+ else if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); close(false); }
60
+ });
61
+ input.addEventListener("blur", () => close(true, false));
62
+ input.focus();
63
+ return true;
64
+ }
@@ -0,0 +1,262 @@
1
+ // Markdown import: the tokens marked's lexer returns become the page's flat block
2
+ // records (model.js), so a file arrives without marked rendering anything. Raw HTML
3
+ // stays inert: a block of it is a preserved block, cleaned of scripts, event handlers
4
+ // and script URLs, and inline markup is literal text. A construct the schema cannot
5
+ // express is kept as a literal code block and named in `unsupported`; a link or
6
+ // image address the editor refuses is left out and named in `refused`, its text
7
+ // kept. Nothing is dropped, and a limit refuses with a code rather than mutating.
8
+ import { schema } from "./schema.js";
9
+ import { markKey, runsOf } from "./inline.js";
10
+ import { keysBetween, newId } from "./model.js";
11
+ import { fragmentCleaner } from "./projection.js";
12
+ import { safeHref, safeSrc } from "./address.js";
13
+
14
+ // HTML in a file someone sent is kept for its formatting, never for what it can load or
15
+ // run: anything that embeds or fetches another document goes, and so does every address
16
+ // that runs script, whichever attribute holds it.
17
+ const EMBEDS = "iframe, frame, frameset, object, embed, applet, meta, base, link, form, style, animate, set, animateMotion, animateTransform, portal";
18
+ const LOADERS = /^(srcdoc|data|formaction|codebase|archive|ping|background|poster|lowsrc|dynsrc)$/i;
19
+ const SCRIPTED = /^\s*(javascript|vbscript|data):/i;
20
+ function cleanImported(html) {
21
+ const t = document.createElement("template");
22
+ t.innerHTML = html;
23
+ for (const el of t.content.querySelectorAll(EMBEDS)) el.remove();
24
+ for (const el of t.content.querySelectorAll("*")) {
25
+ for (const { name, value } of [...el.attributes]) {
26
+ if (LOADERS.test(name) || SCRIPTED.test(value.replace(/[\u0000-\u0020]/g, ""))) el.removeAttribute(name);
27
+ }
28
+ }
29
+ return t.innerHTML;
30
+ }
31
+
32
+ export const MAX_BYTES = 2 * 1024 * 1024;
33
+ export const MAX_DEPTH = 32;
34
+ export const MAX_BLOCKS = 10000;
35
+
36
+ export class MarkdownImportError extends Error {
37
+ constructor(code, message) {
38
+ super(message);
39
+ this.name = "MarkdownImportError";
40
+ this.code = code;
41
+ }
42
+ }
43
+
44
+ const ATOM = "\uFFFC";
45
+ const BREAK = { type: "hard_break" };
46
+ const BLOCK = new Set(["paragraph", "heading", "list", "blockquote", "code", "table", "hr", "html", "def", "space"]);
47
+ const encoder = new TextEncoder();
48
+ const M = schema.marks;
49
+
50
+ const sorted = (marks) => marks.slice().sort((a, b) => a.type.rank - b.type.rank);
51
+
52
+ function added(marks, name, attrs) {
53
+ if (marks.some((mark) => mark.type === M[name])) return marks;
54
+ return sorted([...marks, M[name].create(attrs)]);
55
+ }
56
+
57
+ // One text block's inline content as the record keeps it: c, and m as runs (one
58
+ // key per UTF-16 unit, packed), exactly as flatten and runsOf write it; `parts`
59
+ // splits around an image, which the schema only carries as a block of its own.
60
+ function inlineOf(tokens, { images = true, refused = [], unsupported = [] } = {}) {
61
+ const parts = [];
62
+ let c = "";
63
+ let m = [];
64
+ const flush = () => {
65
+ if (c) parts.push({ c, m });
66
+ c = "";
67
+ m = [];
68
+ };
69
+ const text = (value, marks) => {
70
+ const str = String(value ?? "");
71
+ if (!str) return;
72
+ const key = markKey(sorted(marks));
73
+ c += str;
74
+ for (let i = 0; i < str.length; i++) m.push(key);
75
+ };
76
+ const atom = (json) => { c += ATOM; m.push("@" + JSON.stringify(json)); };
77
+ const walk = (list, marks) => {
78
+ for (const token of list ?? []) {
79
+ const type = token?.type;
80
+ if (typeof type !== "string") throw new MarkdownImportError("malformed-input", "A Markdown token has no type.");
81
+ switch (type) {
82
+ case "text":
83
+ if (Array.isArray(token.tokens)) walk(token.tokens.filter((child) => typeof child?.type === "string" && !BLOCK.has(child.type)), marks);
84
+ else text(token.text, marks);
85
+ break;
86
+ case "escape": text(token.text, marks); break;
87
+ case "strong": walk(token.tokens, added(marks, "strong")); break;
88
+ case "em": walk(token.tokens, added(marks, "em")); break;
89
+ case "del":
90
+ case "strike": walk(token.tokens, added(marks, "strike")); break;
91
+ case "codespan": text(token.text, added(marks, "code")); break;
92
+ case "br": atom(BREAK); break;
93
+ case "link": {
94
+ const href = safeHref(token.href);
95
+ if (href) walk(token.tokens, added(marks, "link", { href }));
96
+ else {
97
+ refused.push({ type: "link", href: String(token.href ?? "") });
98
+ walk(token.tokens, marks);
99
+ }
100
+ break;
101
+ }
102
+ case "image": {
103
+ const image = { src: String(token.href ?? ""), alt: String(token.text ?? ""), title: token.title ?? "" };
104
+ if (!images) { text(image.alt, marks); break; }
105
+ flush();
106
+ parts.push({ image });
107
+ break;
108
+ }
109
+ // Raw HTML is inert: its text stays text, never a node or a mark.
110
+ case "html": text(token.text ?? token.raw, marks); break;
111
+ case "checkbox": break;
112
+ default:
113
+ unsupported.push({ type, text: String(token.raw ?? token.text ?? "") });
114
+ text(token.raw ?? token.text, marks);
115
+ break;
116
+ }
117
+ }
118
+ };
119
+ walk(tokens, []);
120
+ flush();
121
+ return { parts };
122
+ }
123
+
124
+ export function importMarkdownTokens(tokens) {
125
+ if (!Array.isArray(tokens)) throw new MarkdownImportError("malformed-input", "Markdown import needs the lexer's token list.");
126
+ let size = 0;
127
+ for (const token of tokens) {
128
+ if (!token || typeof token !== "object" || typeof token.type !== "string") throw new MarkdownImportError("malformed-input", "A Markdown token has no type.");
129
+ size += encoder.encode(String(token.raw ?? "")).length;
130
+ }
131
+ if (size > MAX_BYTES) throw new MarkdownImportError("input-too-large", `Markdown import is limited to ${MAX_BYTES} bytes; this file is ${size}.`);
132
+
133
+ const blocks = [];
134
+ const unsupported = [];
135
+ const refused = [];
136
+ const used = new Set();
137
+ const fresh = (prefix) => {
138
+ let id = newId(prefix);
139
+ while (used.has(id)) id = newId(prefix);
140
+ used.add(id);
141
+ return id;
142
+ };
143
+ const push = (value) => {
144
+ if (blocks.length + 1 > MAX_BLOCKS) throw new MarkdownImportError("too-many-blocks", `Markdown import is limited to ${MAX_BLOCKS} blocks.`);
145
+ blocks.push(value);
146
+ };
147
+ const deeper = (ctx) => {
148
+ const depth = ctx.depth + 1;
149
+ if (depth > MAX_DEPTH) throw new MarkdownImportError("nesting-too-deep", `Markdown import is limited to ${MAX_DEPTH} levels of nesting.`);
150
+ return depth;
151
+ };
152
+ const block = (t, attrs, { c, m }) => {
153
+ const value = { t, ...attrs, c };
154
+ if (m.some(Boolean)) value.m = runsOf(m);
155
+ return value;
156
+ };
157
+ const imageOf = (image) => {
158
+ const src = safeSrc(image.src);
159
+ if (src) return { t: "image", src, ...(image.alt ? { alt: image.alt } : {}), ...(image.title ? { caption: image.title } : {}) };
160
+ refused.push({ type: "image", href: image.src });
161
+ return image.alt ? block("paragraph", {}, { c: image.alt, m: [] }) : null;
162
+ };
163
+ const textBlocks = (t, attrs, inline) => {
164
+ for (const part of inline.parts) {
165
+ if (!part.image) { push(block(t, attrs, part)); continue; }
166
+ const value = imageOf(part.image);
167
+ if (value) push(value);
168
+ }
169
+ };
170
+ const paragraph = (inline, ctx) => textBlocks(ctx.quote ? "quote" : "paragraph", {}, inline);
171
+ const inline = (tokens, options = {}) => inlineOf(tokens, { refused, unsupported, ...options });
172
+ const literal = (token) => {
173
+ const raw = String(token.raw ?? token.text ?? "");
174
+ unsupported.push({ type: token.type, text: raw });
175
+ push({ t: "code_block", lang: "markdown", c: raw });
176
+ };
177
+
178
+ const walk = (list, ctx) => {
179
+ for (const token of list ?? []) {
180
+ const type = token?.type;
181
+ if (typeof type !== "string") throw new MarkdownImportError("malformed-input", "A Markdown token has no type.");
182
+ switch (type) {
183
+ case "space": break;
184
+ case "heading": {
185
+ const level = Math.min(6, Math.max(1, Number.parseInt(token.depth, 10) || 1));
186
+ textBlocks("heading", level === 2 ? {} : { level }, inline(token.tokens, { images: false }));
187
+ break;
188
+ }
189
+ case "paragraph":
190
+ case "text": paragraph(inline(token.tokens ?? [token]), ctx); break;
191
+ case "hr": push({ t: "divider" }); break;
192
+ case "code": push({ t: "code_block", ...(token.lang ? { lang: String(token.lang) } : {}), c: String(token.text ?? "") }); break;
193
+ case "html": {
194
+ const raw = String(token.text ?? token.raw ?? "");
195
+ unsupported.push({ type: "HTML block cleaned", text: raw });
196
+ push({ t: "preserved", html: cleanImported(fragmentCleaner(document).fallback(raw)) });
197
+ break;
198
+ }
199
+ case "blockquote": {
200
+ const depth = deeper(ctx);
201
+ walk(token.tokens, { ...ctx, depth, quote: true });
202
+ break;
203
+ }
204
+ case "list": {
205
+ const depth = deeper(ctx);
206
+ const level = ctx.indent ?? 0;
207
+ const kind = token.ordered ? "ordered" : "bullet";
208
+ for (const item of token.items ?? []) {
209
+ let own = { c: "", m: [] };
210
+ const rest = [];
211
+ for (const child of item?.tokens ?? []) {
212
+ const childType = child?.type;
213
+ if (childType === "checkbox" || childType === "space") continue;
214
+ if (childType === "text" || childType === "paragraph") {
215
+ if (own.c) own = { c: own.c + ATOM, m: [...own.m, "@" + JSON.stringify(BREAK)] };
216
+ const tokens = childType === "paragraph" ? child.tokens ?? [] : [child];
217
+ for (const part of inline(tokens, { images: false }).parts) {
218
+ own = { c: own.c + part.c, m: [...own.m, ...part.m] };
219
+ }
220
+ rest.push(...(child.tokens ?? []).filter((nested) => typeof nested?.type === "string" && BLOCK.has(nested.type) && nested.type !== "space"));
221
+ } else rest.push(child);
222
+ }
223
+ const itemKind = item?.task ? "check" : kind;
224
+ const attrs = {
225
+ ...(itemKind === "bullet" ? {} : { kind: itemKind }),
226
+ ...(level ? { indent: level } : {}),
227
+ ...(item?.task && item.checked ? { checked: true } : {}),
228
+ };
229
+ push(block("list_item", attrs, own));
230
+ if (rest.length) walk(rest, { ...ctx, depth, indent: level + 1, quote: false });
231
+ }
232
+ break;
233
+ }
234
+ case "table": {
235
+ const header = token.header ?? [];
236
+ if (!header.length) { literal(token); break; }
237
+ const cell = (tokens) => inline(tokens, { images: false }).parts[0] ?? { c: "", m: [] };
238
+ const colIds = header.map(() => fresh("c"));
239
+ const colPos = keysBetween(null, null, header.length);
240
+ const cols = Object.fromEntries(colIds.map((id, i) => [id, { pos: colPos[i], name: cell(header[i]?.tokens).c }]));
241
+ // The header line is the column names, drawn as <thead>; the body holds the data rows.
242
+ const rows = token.rows?.length ? token.rows : [[]];
243
+ const rowPos = keysBetween(null, null, rows.length);
244
+ const values = rows.map((row) => Object.fromEntries(colIds.map((id, i) => {
245
+ const part = cell(row?.[i]?.tokens);
246
+ return [id, part.m.some(Boolean) ? { c: part.c, m: runsOf(part.m) } : part.c];
247
+ })));
248
+ push({ t: "table", cols, rows: Object.fromEntries(values.map((cells, i) => [fresh("r"), { pos: rowPos[i], cells }])) });
249
+ break;
250
+ }
251
+ default: literal(token); break;
252
+ }
253
+ }
254
+ };
255
+
256
+ walk(tokens, { depth: 0, indent: 0, quote: false });
257
+
258
+ const positions = keysBetween(null, null, blocks.length);
259
+ const out = {};
260
+ blocks.forEach((value, i) => { out[fresh("b")] = { ...value, pos: positions[i] }; });
261
+ return { blocks: out, unsupported, refused };
262
+ }