@panphora/clayjs 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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-F449FF98.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
@@ -8,15 +8,15 @@ import { schema } from "./schema.js";
8
8
  import { BlockSelection, siblingSpan, moveBlock, indentBlocks, outdentBlocks } from "./blocks.js";
9
9
  import { outlineOf } from "./outline-cache.js";
10
10
  import { currentSpan, selectBlocks, duplicateBlocks, deleteBlocks, asStep, typeIntoBlocks } from "./blocksel.js";
11
- import { openAltEditor, setImageAttrs } from "./media.js";
12
11
  import { BlockDrag } from "./drag.js";
13
12
  import { openLinkEditor } from "./link.js";
14
- import { BLOCK_TYPES, MENU_ITEMS, blockTypeOf, turnInto, insertAfter, rankItems, runItem, retype, canTurnInto, insertItemsFor, loadChartPart } from "./items.js";
13
+ import { BLOCK_TYPES, MENU_ITEMS, blockTypeOf, turnBlocks, turnInto, insertAfter, rankItems, runItem, retype, canTurnInto, insertItemsFor, loadChartPart, runMenuCommand } from "./items.js";
15
14
  import toast from "../ui/toast-lazy.js";
16
15
  import { pickCsv } from "./csv-import.js";
17
16
  import { HISTORY_INTENT, typingIntent } from "./history-intent.js";
18
17
  import { composing } from "./markdown.js";
19
18
  import { announce } from "./announce.js";
19
+ import { REMOTE } from "./model.js";
20
20
 
21
21
  export { BLOCK_TYPES, blockTypeOf, turnInto, insertAfter } from "./items.js";
22
22
 
@@ -73,20 +73,24 @@ function copyBlockLink(id) {
73
73
  }
74
74
 
75
75
  export const ACTION_ITEMS = [
76
- { key: "up", label: "Move up", icon: "↑", hint: `${MOD}⇧↑`, run: (view) => moveBlock(-1)(view.state, view.dispatch) },
77
- { key: "down", label: "Move down", icon: "↓", hint: `${MOD}⇧↓`, run: (view) => moveBlock(1)(view.state, view.dispatch) },
78
- { key: "indent", label: "Indent", icon: "→", hint: "Tab", run: (view) => indentBlocks(view.state, view.dispatch) },
79
- { key: "outdent", label: "Outdent", icon: "←", hint: "⇧Tab", run: (view) => outdentBlocks(view.state, view.dispatch) },
80
- { key: "duplicate", label: "Duplicate", icon: "⧉", hint: `${MOD}D`, run: (view) => duplicateBlocks(view.state, view.dispatch) },
81
- { key: "delete", label: "Delete", icon: "×", hint: "Del", run: (view) => deleteBlocks(view.state, view.dispatch) },
82
- { key: "turn", label: "Turn into", icon: "↻", hint: "›", only: "text", run: (view, chrome) => chrome.openMenu("turn", chrome.plus) },
83
- { key: "link", label: "Copy link", icon: "🔗", run: (view, chrome, index) => copyBlockLink(view.state.doc.child(index).attrs.id) },
84
- { key: "alt", label: "Alt text…", icon: "Aa", only: "image", focus: false, run: (view, chrome, index) => openAltEditor(view, imageId(view, index)) },
85
- { key: "small", label: "Small", icon: "▫", only: "image", run: (view, chrome, index) => setImageAttrs(view, imageId(view, index), { width: 240 }) },
86
- { key: "medium", label: "Medium", icon: "◻", only: "image", run: (view, chrome, index) => setImageAttrs(view, imageId(view, index), { width: 480 }) },
87
- { key: "full", label: "Full width", icon: "⬜", only: "image", run: (view, chrome, index) => setImageAttrs(view, imageId(view, index), { width: null }) },
88
- { key: "chart", label: "Chart this table", icon: "📊", only: "table", run: (view, chrome, index) => { const at = blockStart(view.state.doc, index); return loadChartPart(view).then(({ insertChartAfterTable }) => insertChartAfterTable(view, at)); } },
89
- { key: "csv", label: "Import CSV…", icon: "⇧", only: "table", focus: false, run: (view, chrome, index) => pickCsv(view, null, view.state.doc.child(index).attrs.id) },
76
+ { key: "up", label: "Move up", icon: "up", hint: `${MOD}⇧↑`, run: (view) => moveBlock(-1)(view.state, view.dispatch) },
77
+ { key: "down", label: "Move down", icon: "down", hint: `${MOD}⇧↓`, run: (view) => moveBlock(1)(view.state, view.dispatch) },
78
+ { key: "indent", label: "Indent", icon: "indent", hint: "Tab", run: (view) => {
79
+ const before = view.state.doc;
80
+ indentBlocks(view.state, view.dispatch);
81
+ if (view.state.doc === before) { announce(view, "Indent works on list items and inside toggles."); toast("Indent works on list items and inside toggles."); }
82
+ } },
83
+ { key: "outdent", label: "Outdent", icon: "outdent", hint: "⇧Tab", run: (view) => outdentBlocks(view.state, view.dispatch) },
84
+ { key: "duplicate", label: "Duplicate", icon: "duplicate", hint: `${MOD}D`, run: (view) => duplicateBlocks(view.state, view.dispatch) },
85
+ { key: "delete", label: "Delete", icon: "trash", hint: "Del", run: (view) => deleteBlocks(view.state, view.dispatch) },
86
+ { key: "turn", label: "Turn into", icon: "turn", hint: "›", only: "text", run: (view, chrome) => chrome.openMenu("turn", chrome.plus) },
87
+ { key: "link", label: "Copy link", icon: "link", run: (view, chrome, index) => copyBlockLink(view.state.doc.child(index).attrs.id) },
88
+ { key: "alt", label: "Alt text…", icon: "alt", only: "image", focus: false, run: (view, chrome, index) => { const id = imageId(view, index); return import("./image-actions.js").then((part) => part.openAltEditor(view, id)); } },
89
+ { key: "small", label: "Small", icon: "small", only: "image", run: (view, chrome, index) => { const id = imageId(view, index); return import("./image-actions.js").then((part) => part.setImageAttrs(view, id, { width: 240 })); } },
90
+ { key: "medium", label: "Medium", icon: "medium", only: "image", run: (view, chrome, index) => { const id = imageId(view, index); return import("./image-actions.js").then((part) => part.setImageAttrs(view, id, { width: 480 })); } },
91
+ { key: "full", label: "Full width", icon: "full", only: "image", run: (view, chrome, index) => { const id = imageId(view, index); return import("./image-actions.js").then((part) => part.setImageAttrs(view, id, { width: null })); } },
92
+ { key: "chart", label: "Chart this table", icon: "chart", only: "table", run: (view, chrome, index) => { const at = blockStart(view.state.doc, index); return loadChartPart(view).then(({ insertChartAfterTable }) => insertChartAfterTable(view, at)); } },
93
+ { key: "csv", label: "Import CSV…", icon: "csv", only: "table", focus: false, run: (view, chrome, index) => pickCsv(view, null, view.state.doc.child(index).attrs.id) },
90
94
  ];
91
95
 
92
96
  const actionsFor = (st) => {
@@ -111,6 +115,21 @@ const ICONS = {
111
115
  ordered: svg('<path d="M7 4.5h6.5M7 8h6.5M7 11.5h6.5M2.5 3.5l1-.5v3M2.5 9.3c.2-.5.6-.8 1.1-.8.6 0 1 .4 1 .9 0 .9-2.1 1.4-2.1 2.6h2.1"/>'),
112
116
  check: svg('<rect x="2.5" y="2.5" width="11" height="11" rx="2.5"/><path d="M5.5 8.2l1.8 1.8 3.4-4"/>'),
113
117
  more: svg('<path stroke-width="2.2" d="M3 8h.01M8 8h.01M13 8h.01"/>'),
118
+ gear: svg('<g transform="scale(0.6667)" stroke-width="2.25"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></g>'),
119
+ close: svg('<path d="M4 4l8 8M12 4l-8 8"/>'),
120
+ up: svg('<path d="M8 13V3.5M4 7.5 8 3.5l4 4"/>'),
121
+ down: svg('<path d="M8 3v9.5M4 8.5l4 4 4-4"/>'),
122
+ indent: svg('<path d="M2.5 3.5h11M7 8h6.5M2.5 12.5h11M2.5 6l2.5 2-2.5 2"/>'),
123
+ outdent: svg('<path d="M2.5 3.5h11M7 8h6.5M2.5 12.5h11M5 6 2.5 8 5 10"/>'),
124
+ duplicate: svg('<rect x="5.5" y="5.5" width="8" height="8" rx="1.5"/><path d="M10.5 3.5v-.5A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v5A1.5 1.5 0 0 0 4 9.5h.5"/>'),
125
+ trash: svg('<path d="M3 4.5h10M6.5 4.5V3h3v1.5M4.5 4.5l.6 8.5h5.8l.6-8.5"/>'),
126
+ turn: svg('<path d="M3 5.5h9.5M10 3l2.5 2.5L10 8M13 10.5H3.5M6 8l-2.5 2.5L6 13"/>'),
127
+ chart: svg('<path d="M3 13V3M3 13h10.5M6 10.5V8M9 10.5V5.5M12 10.5V7"/>'),
128
+ csv: svg('<path d="M4 1.5h5L12.5 5v9.5h-8.5zM9 1.5V5h3.5M8 7.5v5M6 10.5l2 2 2-2"/>'),
129
+ alt: svg('<path d="M2.5 12.5 5.5 4l3 8.5M3.6 9.5h3.8M10 12.5V7.5M10 8.5c.4-.7 1.1-1 2-1h.5"/>'),
130
+ small: svg('<rect x="5" y="5" width="6" height="6" rx="1"/>'),
131
+ medium: svg('<rect x="3.5" y="3.5" width="9" height="9" rx="1.2"/>'),
132
+ full: svg('<rect x="1.5" y="3.5" width="13" height="9" rx="1.2"/>'),
114
133
  };
115
134
  const TOOLS = [
116
135
  ["strong", "Bold"],
@@ -128,6 +147,7 @@ const PHONE_TOOLS = new Set(["strong", "em", "link"]);
128
147
  const MORE_ACTIONS = new Set(["up", "down", "indent", "outdent"]);
129
148
  const PHONE_BAR_WIDTH = 204, PHONE_BAR_HEIGHT = 52, PHONE_INSET = 8;
130
149
  const LIST_KINDS = new Set(["bullet", "ordered", "check"]);
150
+ const SETTING_ROWS = [["assignees", "Checklist assignees"], ["dates", "Checklist due dates"]];
131
151
  const LINE_TYPES = new Set(["paragraph", "heading", "list_item"]);
132
152
 
133
153
  // The top-level blocks a toolbar command acts on. A nonempty selection's end is
@@ -219,7 +239,7 @@ function runList(view, kind) {
219
239
  // The four formats this release adds are commands the format gate may refuse, and
220
240
  // a refusal is a notice rather than a change. The menu that offers them, and the
221
241
  // commands behind it, load the first time one is chosen (format-native-commands.js).
222
- const nativeMenu = (view, chrome, name) => import("./format-native-commands.js").then((menu) => menu.nativeMenu(view, name, chrome.more));
242
+ const nativeMenu = (view, chrome, name) => import("./format-native-commands.js").then((menu) => menu.nativeMenu(view, name, chrome.pickedFrom ?? chrome.more));
223
243
 
224
244
  function runMark(view, name) {
225
245
  if (name === "link") return openLinkEditor(view);
@@ -281,15 +301,28 @@ export const heldSlashRange = (st) => {
281
301
  return held ? { from: held.from, to: held.to } : null;
282
302
  };
283
303
 
284
- export function openPageMenu(view, anchor, items) {
304
+ export function openPageMenu(view, anchor, items, { persist = false } = {}) {
285
305
  const chrome = chromeFor.get(view);
286
306
  if (!chrome) return false;
287
307
  chrome.openMenu("external", anchor);
288
308
  chrome.menu.externalItems = items;
309
+ chrome.menu.persist = persist;
289
310
  chrome.renderMenu();
290
311
  return true;
291
312
  }
292
313
 
314
+ // A menu whose rows name their targets by id outlives a render of the control that
315
+ // opened it: the control that takes the old one's place keeps the popup open.
316
+ export function reanchorPageMenu(view, from, to) {
317
+ const chrome = chromeFor.get(view);
318
+ const menu = chrome?.menu;
319
+ if (!menu || menu.anchor !== from || !to || to === from) return false;
320
+ menu.anchor = to;
321
+ to.setAttribute("aria-expanded", "true");
322
+ view.dom.setAttribute("aria-controls", chrome.menuId);
323
+ return true;
324
+ }
325
+
293
326
  class Chrome {
294
327
  constructor(view, onAiTool) {
295
328
  this.view = view;
@@ -305,18 +338,32 @@ class Chrome {
305
338
  this.destroyed = false;
306
339
  this.probe = null;
307
340
  this.roving = null;
341
+ this.remote = false;
308
342
 
309
343
  this.dock = el("div", "page-dock", { role: "toolbar", "aria-label": "Formatting" });
310
344
  this.turn = el("button", "page-tool page-tool-turn page-tool-desk", { type: "button", "aria-haspopup": "menu" });
311
345
  this.more = el("button", "page-tool page-tool-primary page-tool-more", { type: "button", "aria-label": "More", title: "More", "aria-haspopup": "menu" });
312
346
  this.more.innerHTML = ICONS.more;
347
+ this.settingsBtn = el("button", "page-tool page-tool-desk page-tool-settings", { type: "button", "aria-label": "Document settings", title: "Document settings", "aria-haspopup": "dialog", "aria-expanded": "false" });
348
+ this.settingsBtn.innerHTML = ICONS.gear;
349
+ this.settingsEl = null;
350
+ this.settingsOpen = false;
351
+ this.settingsAnchor = null;
352
+ this.settingSwitches = {};
313
353
  this.dock.append(this.turn, el("span", "page-tool-sep page-tool-desk"), ...TOOLS.map(([n, l]) => toolButton(n, l)), this.more);
354
+ // The loader publishes the page facade when its module arrives, which can be after
355
+ // this toolbar is built, so the gear asks for the settings each time it is drawn.
356
+ this.dock.insertBefore(this.settingsBtn, this.more);
357
+ this.settingsBtn.hidden = !this.settingsAPI;
314
358
  this.plus = el("button", "page-plus", { type: "button", "aria-label": "Add a block", title: "Add a block" });
315
359
  this.plus.innerHTML = ICONS.plus;
316
360
  this.grip = el("button", "page-grip", { type: "button", "aria-label": "Block actions", title: "Drag to move, click for actions", "aria-haspopup": "menu" });
317
361
  this.grip.innerHTML = ICONS.grip;
318
362
  this.bubble = el("div", "page-bubble", { role: "toolbar", "aria-label": "Selection", hidden: "" });
319
- this.bubble.append(...TOOLS.map(([n, l]) => toolButton(n, l)));
363
+ this.bubbleTurn = el("button", "page-tool page-tool-turn", { type: "button", "aria-haspopup": "menu" });
364
+ this.bubbleMore = el("button", "page-tool page-tool-primary page-tool-more", { type: "button", "aria-label": "More", title: "More", "aria-haspopup": "menu" });
365
+ this.bubbleMore.innerHTML = ICONS.more;
366
+ this.bubble.append(this.bubbleTurn, el("span", "page-tool-sep"), ...TOOLS.map(([n, l]) => toolButton(n, l)), this.bubbleMore);
320
367
  this.menuId = `page-menu-${++menuCount}`;
321
368
  this.menuEl = el("div", "page-menu", { role: "menu", id: this.menuId, hidden: "" });
322
369
  this.scrim = el("div", "page-scrim", { hidden: "" });
@@ -333,13 +380,23 @@ class Chrome {
333
380
  }
334
381
  this.turn.addEventListener("click", () => this.toggleMenu("turn", this.turn));
335
382
  this.more.addEventListener("click", () => this.toggleMenu("more", this.more));
383
+ this.settingsBtn.addEventListener("click", () => this.toggleSettings());
384
+ this.bubbleTurn.addEventListener("click", () => this.toggleMenu("turn", this.bubbleTurn));
385
+ this.bubbleMore.addEventListener("click", () => this.toggleMenu("more", this.bubbleMore));
336
386
  this.dock.addEventListener("keydown", (e) => this.dockKey(e));
337
387
  this.plus.addEventListener("click", () => this.fromPlus());
338
388
  this.grip.addEventListener("click", (e) => {
339
389
  if (e.detail === 0) this.gripAction(this.gutterIndex());
340
390
  });
341
391
  this.drag = new BlockDrag(view, this.grip, { index: () => this.gutterIndex(), onClick: (i) => this.gripAction(i) });
342
- this.scrim.addEventListener("click", () => this.closeMenu());
392
+ // A tap that opens a menu ends in a click over the scrim it just showed: only a press
393
+ // that began on the scrim closes it.
394
+ let scrimPressed = false;
395
+ this.scrim.addEventListener("pointerdown", () => { scrimPressed = true; });
396
+ this.scrim.addEventListener("click", () => {
397
+ if (scrimPressed) this.closeMenu();
398
+ scrimPressed = false;
399
+ });
343
400
  this.menuEl.addEventListener("click", (e) => {
344
401
  const row = e.target.closest(".page-menu-item");
345
402
  if (row) this.pick(Number(row.dataset.index));
@@ -360,13 +417,21 @@ class Chrome {
360
417
  this.setHover(null);
361
418
  };
362
419
  this.onLeave = () => { this.pendingHover = null; this.setHover(null); };
363
- this.onResize = () => this.scheduleLayout();
420
+ this.onResize = () => { this.scheduleLayout(); this.placeSettings(); };
364
421
  this.onDocDown = (e) => {
422
+ if (this.settingsOpen && ![this.settingsEl, this.settingsAnchor].some((part) => part?.contains(e.target))) this.closeSettings();
365
423
  if (!this.menu) return;
366
- if ([this.menuEl, this.plus, this.grip, this.turn].some((part) => part.contains(e.target))) return;
424
+ if ([this.menuEl, this.plus, this.grip, this.turn, this.bubbleTurn, this.bubbleMore].some((part) => part.contains(e.target))) return;
367
425
  this.closeMenu();
368
426
  };
427
+ this.onSettingsKey = (e) => {
428
+ if (e.key !== "Escape" || !this.settingsOpen) return;
429
+ e.preventDefault();
430
+ e.stopPropagation();
431
+ this.closeSettings(true);
432
+ };
369
433
  document.addEventListener("mousedown", this.onDocDown, true);
434
+ document.addEventListener("keydown", this.onSettingsKey, true);
370
435
  this.shell.addEventListener("mousemove", this.onMove);
371
436
  this.shell.addEventListener("mouseleave", this.onLeave);
372
437
  this.shell.addEventListener("keydown", this.onKey);
@@ -471,7 +536,7 @@ class Chrome {
471
536
  const m = this.menu;
472
537
  if (!m || m.slashAt !== null) return this.view.dom;
473
538
  const a = m.anchor;
474
- return a && a !== this.view.dom && (a.tagName === "BUTTON" || a.hasAttribute("tabindex")) ? a : this.view.dom;
539
+ return a && a !== this.view.dom && a.isConnected && (a.tagName === "BUTTON" || a.hasAttribute("tabindex")) ? a : this.view.dom;
475
540
  }
476
541
 
477
542
  // A block action from the phone menu acts on the caret's block when nothing
@@ -526,13 +591,116 @@ class Chrome {
526
591
  this.openMenu("insert", this.plus, null, null, this.view.state.doc.child(i).attrs.id);
527
592
  }
528
593
 
594
+ // The gear's panel: built once, filled from the document's own settings, and
595
+ // placed under the control that opened it.
596
+ buildSettings() {
597
+ if (this.settingsEl) return this.settingsEl;
598
+ const panel = el("div", "page-settings", { role: "dialog", "aria-label": "Document settings", hidden: "" });
599
+ const head = el("div", "page-settings-head");
600
+ const title = el("span", "page-settings-title");
601
+ title.textContent = "Document settings";
602
+ const close = el("button", "page-settings-close", { type: "button", "aria-label": "Close settings" });
603
+ close.innerHTML = ICONS.close;
604
+ close.addEventListener("click", () => this.closeSettings(true));
605
+ head.append(title, close);
606
+ const rows = SETTING_ROWS.map(([key, label]) => {
607
+ const row = el("div", "page-settings-row");
608
+ const name = el("span", "page-settings-name", { id: `${this.menuId}-set-${key}` });
609
+ name.textContent = label;
610
+ const sw = el("button", "page-settings-switch", { type: "button", role: "switch", "aria-checked": "true", "aria-labelledby": name.id, "data-setting": key });
611
+ sw.append(el("span", "page-settings-knob"));
612
+ sw.addEventListener("click", () => this.flipSetting(key));
613
+ this.settingSwitches[key] = sw;
614
+ row.append(name, sw);
615
+ return row;
616
+ });
617
+ const note = el("div", "page-settings-note");
618
+ note.textContent = "Hidden values stay saved and come back when turned on.";
619
+ panel.append(head, ...rows, note);
620
+ this.settingsEl = panel;
621
+ this.shell.append(panel);
622
+ return panel;
623
+ }
624
+
625
+ flipSetting(key) {
626
+ const sw = this.settingSwitches[key];
627
+ const on = sw.getAttribute("aria-checked") !== "true";
628
+ sw.setAttribute("aria-checked", String(on));
629
+ Promise.resolve().then(() => this.settingsAPI.set(key, on)).catch(() => {
630
+ sw.setAttribute("aria-checked", String(!on));
631
+ toast("That setting could not be saved.", "error");
632
+ });
633
+ }
634
+
635
+ openSettings(anchor = this.settingsBtn) {
636
+ if (!this.settingsAPI) return;
637
+ if (this.menu) this.closeMenu();
638
+ const panel = this.buildSettings();
639
+ const now = this.settingsAPI.read();
640
+ for (const [key, sw] of Object.entries(this.settingSwitches)) sw.setAttribute("aria-checked", String(now[key] !== false));
641
+ this.settingsOpen = true;
642
+ this.settingsAnchor = anchor;
643
+ anchor.setAttribute("aria-expanded", "true");
644
+ panel.hidden = false;
645
+ this.placeSettings();
646
+ panel.querySelector(".page-settings-close")?.focus({ preventScroll: true });
647
+ }
648
+
649
+ closeSettings(restoreFocus = false) {
650
+ if (!this.settingsOpen) return;
651
+ const anchor = this.settingsAnchor;
652
+ this.settingsOpen = false;
653
+ this.settingsAnchor = null;
654
+ if (this.settingsEl) this.settingsEl.hidden = true;
655
+ anchor?.setAttribute("aria-expanded", "false");
656
+ if (restoreFocus && anchor?.isConnected) anchor.focus({ preventScroll: true });
657
+ }
658
+
659
+ toggleSettings() {
660
+ if (this.settingsOpen) this.closeSettings(true);
661
+ else this.openSettings(this.settingsBtn);
662
+ }
663
+
664
+ // The panel's right edge sits on its control's right edge, under it, and it
665
+ // flips above the control when there is no room below.
666
+ placeSettings() {
667
+ const panel = this.settingsEl;
668
+ if (!panel || panel.hidden || !this.settingsAnchor) return;
669
+ const r = this.settingsAnchor.getBoundingClientRect();
670
+ const width = panel.offsetWidth || 264;
671
+ const height = panel.offsetHeight || 0;
672
+ if (this.isPhone()) {
673
+ const v = phoneViewport(this.win);
674
+ const x = Math.max(v.left + PHONE_INSET, Math.min(r.right - width, v.left + v.width - PHONE_INSET - width));
675
+ let y = r.top - 6 - height;
676
+ if (y < v.top + PHONE_INSET) y = Math.min(r.bottom + 6, v.top + v.height - PHONE_INSET - height);
677
+ panel.style.setProperty("--page-pop-x", `${x}px`);
678
+ panel.style.setProperty("--page-pop-y", `${Math.max(v.top + PHONE_INSET, y)}px`);
679
+ return;
680
+ }
681
+ const shellBox = this.shell.getBoundingClientRect();
682
+ const x = Math.max(8, Math.min(r.right - shellBox.left - width, Math.max(8, shellBox.width - width - 8)));
683
+ let y = r.bottom - shellBox.top + 6;
684
+ if (y + height > this.win.innerHeight - 8 && r.top - shellBox.top - 6 - height >= 8) y = r.top - shellBox.top - 6 - height;
685
+ panel.style.setProperty("--page-pop-x", `${x}px`);
686
+ panel.style.setProperty("--page-pop-y", `${y}px`);
687
+ }
688
+
529
689
  toggleMenu(mode, anchor) {
530
690
  if (this.menu) this.closeMenu();
531
691
  else this.openMenu(mode, anchor);
532
692
  }
533
693
 
694
+ // A slash dismissed with Escape stays text: it is known by its block and the text
695
+ // before it, so typing after it does not bring the menu back.
696
+ slashKey(slashAt) {
697
+ const $at = this.view.state.doc.resolve(slashAt);
698
+ return `${$at.parent.attrs.id}\u0000${this.view.state.doc.textBetween($at.start(), slashAt - 1)}`;
699
+ }
700
+
534
701
  openMenu(mode, anchor, slashAt = null, ids = null, anchorId = null) {
535
- this.menu = { mode, anchor, slashAt, anchorId, ids: ids ?? (mode === "insert" ? [] : this.selectionIds()), query: "", active: 0, items: [] };
702
+ this.closeSettings();
703
+ this.menu = { mode, anchor, slashAt, anchorId, ids: ids ?? (mode === "insert" ? [] : this.selectionIds()), query: "", active: 0, items: [], persist: false };
536
704
  anchor.setAttribute("aria-expanded", "true");
537
705
  if (slashAt !== null) {
538
706
  if (!this.priorAria) this.priorAria = POPUP_ATTRS.map((name) => [name, this.view.dom.getAttribute(name)]);
@@ -559,17 +727,23 @@ class Chrome {
559
727
  // Everything the phone toolbar folds away: the rest of the formatting, and the
560
728
  // block actions the desktop menu carries. The move commands are the same ones.
561
729
  moreItems() {
562
- const tools = TOOLS.filter(([name]) => !PHONE_TOOLS.has(name))
563
- .map(([name, label]) => ({ key: name, label, icon: ICONS[name], run: (view) => runMark(view, name) }));
730
+ const phone = this.isPhone();
731
+ const tools = phone ? TOOLS.filter(([name]) => !PHONE_TOOLS.has(name))
732
+ .map(([name, label]) => ({ key: name, label, icon: name, run: (view) => runMark(view, name) })) : [];
564
733
  const actions = ACTION_ITEMS.filter((a) => MORE_ACTIONS.has(a.key)).map((a) => ({ ...a, blocks: true }));
565
734
  return [
566
735
  ...tools,
567
736
  { key: "underline", label: "Underline", icon: "U", hint: `${MOD}u`, run: (view, chrome) => nativeMenu(view, chrome, "underline") },
568
737
  { key: "text_color", label: "Text color", icon: "A", hint: "\u203a", run: (view, chrome) => nativeMenu(view, chrome, "text_color") },
569
738
  { key: "highlight", label: "Highlight", icon: "\u25A0", hint: "\u203a", run: (view, chrome) => nativeMenu(view, chrome, "highlight") },
570
- { key: "turn", label: "Turn into", icon: "\u21bb", run: (view, chrome) => chrome.openMenu("turn", chrome.more) },
571
- { key: "insert", label: "Insert", icon: "+", run: (view, chrome) => chrome.openMenu("insert", chrome.more, null, null, view.state.doc.child(chrome.blockIndex()).attrs.id) },
739
+ ...(phone ? [{ key: "turn", label: "Turn into", icon: "turn", run: (view, chrome) => chrome.openMenu("turn", chrome.more) }] : []),
740
+ { key: "insert", label: "Insert", icon: "+", run: (view, chrome) => chrome.openMenu("insert", chrome.pickedFrom ?? chrome.more, null, null, view.state.doc.child(chrome.blockIndex()).attrs.id) },
572
741
  ...actions,
742
+ ...(phone && this.settingsAPI ? [{ key: "settings", label: "Document settings", icon: "gear", focus: false, run: (view, chrome) => chrome.openSettings(chrome.more) }] : []),
743
+ { key: "titleIcon", label: "Page icon", icon: "\u263A", run: (view) => runMenuCommand(view, "titleIcon") },
744
+ { key: "titleCover", label: "Add cover", icon: "\u25A4", run: (view) => runMenuCommand(view, "titleCover") },
745
+ { key: "exportHtml", label: "Export reading copy", icon: "\u21E9", run: (view) => runMenuCommand(view, "exportHtml") },
746
+ { key: "exportMarkdown", label: "Export as Markdown", icon: "M\u21E9", run: (view) => runMenuCommand(view, "exportMarkdown") },
573
747
  ];
574
748
  }
575
749
 
@@ -626,7 +800,8 @@ class Chrome {
626
800
  const role = m.mode === "turn" ? "menuitemradio" : typeof checked === "boolean" ? "menuitemcheckbox" : slash ? "option" : "menuitem";
627
801
  const row = el("button", `page-menu-item${active ? " is-active" : ""}`, { type: "button", role, tabindex: !slash && active ? "0" : "-1", id: `${this.menuId}-${i}`, "data-index": String(i), ...(role === "menuitemradio" ? {} : { "aria-selected": active ? "true" : "false" }), ...(typeof checked === "boolean" ? { "aria-checked": String(checked) } : {}) });
628
802
  const icon = el("span", "page-menu-icon");
629
- icon.textContent = b.icon || "";
803
+ if (ICONS[b.icon]) icon.innerHTML = ICONS[b.icon];
804
+ else icon.textContent = b.icon || "";
630
805
  const label = el("span", "page-menu-label");
631
806
  label.textContent = b.label;
632
807
  if (b.description) {
@@ -649,7 +824,7 @@ class Chrome {
649
824
  this.menuEl.hidden = false;
650
825
  this.scrim.hidden = false;
651
826
  this.scheduleLayout();
652
- if (!slash) this.menuEl.querySelectorAll("button")[m.active]?.focus();
827
+ if (!slash) this.menuEl.querySelectorAll("button")[m.active]?.focus({ preventScroll: true });
653
828
  }
654
829
 
655
830
  pick(i) {
@@ -663,6 +838,8 @@ class Chrome {
663
838
  const wanted = m.mode === "insert" ? (m.anchorId ? [m.anchorId] : []) : m.ids;
664
839
  const stale = wanted.some((id) => !id || this.idIndex(id) < 0);
665
840
  const index = wanted.length ? this.idIndex(wanted[0]) : -1;
841
+ // A submenu a row opens (text color, highlight) anchors where this menu was opened.
842
+ this.pickedFrom = m.anchor;
666
843
  this.closeMenu();
667
844
  if (stale && m.mode !== "external") {
668
845
  announce(this.view, TARGET_GONE);
@@ -675,21 +852,7 @@ class Chrome {
675
852
  this.view.focus();
676
853
  return;
677
854
  }
678
- const turn = () => {
679
- const tr = this.view.state.tr;
680
- for (const id of wanted) {
681
- const at = this.idIndex(id);
682
- if (at >= 0) retype(tr, at, item);
683
- }
684
- if (tr.docChanged) this.view.dispatch(asStep(tr).scrollIntoView());
685
- return true;
686
- };
687
- // A block type from a later format asks the gate before it writes.
688
- if (item.type === "quote") {
689
- const view = this.view;
690
- import("./format-native-commands.js").then((menu) => menu.gateTurn(view, turn));
691
- } else turn();
692
- this.view.focus();
855
+ turnBlocks(this.view, item, wanted);
693
856
  } else if (m.mode === "actions" || m.mode === "more" || m.mode === "external") {
694
857
  if (item.blocks) this.selectCaretBlock(wanted);
695
858
  item.run(this.view, this, index >= 0 ? index : this.blockIndex());
@@ -727,8 +890,13 @@ class Chrome {
727
890
  if (m.items.length) this.pick(m.active);
728
891
  return true;
729
892
  }
893
+ if (event.key === "Tab" && m.slashAt === null) {
894
+ this.closeMenu();
895
+ return false;
896
+ }
730
897
  if (event.key === "Escape") {
731
898
  const target = this.invoker();
899
+ if (m.slashAt !== null) this.dismissedSlash = this.slashKey(m.slashAt);
732
900
  this.closeMenu();
733
901
  if (target === this.view.dom) this.view.focus();
734
902
  else target?.focus?.();
@@ -836,6 +1004,8 @@ class Chrome {
836
1004
  const r = m.anchor === this.plus ? (dom?.getBoundingClientRect?.() ?? edBox) : m.anchor.getBoundingClientRect();
837
1005
  left = r.left;
838
1006
  top = r.bottom + 6;
1007
+ const height = this.menuEl.offsetHeight;
1008
+ if (top + height > this.win.innerHeight - 8 && r.top - 6 - height >= 8) top = r.top - 6 - height;
839
1009
  }
840
1010
  out.menu = { x: Math.max(8, Math.min(left - shellBox.left, shellBox.width - 280)), y: top - shellBox.top };
841
1011
  }
@@ -874,22 +1044,46 @@ class Chrome {
874
1044
  }
875
1045
  }
876
1046
 
1047
+ get settingsAPI() {
1048
+ return globalThis.clay?.page?.settings ?? null;
1049
+ }
1050
+
877
1051
  update(view, prev) {
1052
+ const settingsHidden = !this.settingsAPI;
1053
+ if (this.settingsBtn.hidden !== settingsHidden) this.settingsBtn.hidden = settingsHidden;
878
1054
  this.view = view;
879
1055
  if (this.menu && this.menu.slashAt === null && prev && prev.doc !== view.state.doc) {
880
- const hadFocus = this.menuEl.contains(document.activeElement);
881
- this.closeMenu();
882
- if (hadFocus) view.focus();
1056
+ // A menu whose rows resolve their targets by id, from a control the page keeps,
1057
+ // is still the right menu after a peer's edit.
1058
+ if (this.menu.persist && this.menu.anchor?.isConnected) this.scheduleLayout();
1059
+ else {
1060
+ const hadFocus = this.menuEl.contains(document.activeElement);
1061
+ this.closeMenu();
1062
+ if (hadFocus) view.focus();
1063
+ }
883
1064
  }
884
1065
  const st = view.state;
885
1066
  const node = st.doc.child(this.blockIndex());
886
1067
  this.turn.textContent = `${blockTypeOf(node).label} ▾`;
1068
+ this.bubbleTurn.textContent = this.turn.textContent;
887
1069
  for (const b of this.dock.querySelectorAll("[data-cmd]")) b.setAttribute("aria-pressed", String(toolActive(st, b.dataset.cmd)));
888
- const showBubble = !st.selection.empty && st.selection instanceof TextSelection && view.hasFocus();
1070
+ const bubbleMenu = !!this.menu && [this.bubbleTurn, this.bubbleMore].includes(this.menu.anchor);
1071
+ const showBubble = bubbleMenu || (!st.selection.empty && st.selection instanceof TextSelection && view.hasFocus());
889
1072
  this.bubble.hidden = !showBubble;
890
1073
  if (showBubble) for (const b of this.bubble.querySelectorAll("[data-cmd]")) b.setAttribute("aria-pressed", String(toolActive(st, b.dataset.cmd)));
891
1074
  this.grip.classList.toggle("is-on", st.selection instanceof BlockSelection);
892
1075
  if (!this.inRoving(this.roving)) this.setRoving(this.dockControls()[0]);
1076
+ // A slash word typed before the caret left the block reopens when its own word is
1077
+ // edited again, not when the caret merely returns to it.
1078
+ if (!this.menu && prev && prev.doc !== st.doc && !this.remote && view.hasFocus() && st.selection.empty) {
1079
+ const $head = st.selection.$head;
1080
+ if ($head.depth === 1 && $head.parent.isTextblock && !$head.parent.type.spec.code) {
1081
+ const before = st.doc.textBetween($head.start(), $head.pos);
1082
+ const match = /(?:^|\s)\/([a-z]*)$/i.exec(before);
1083
+ const slashAt = match ? $head.start() + match.index + match[0].indexOf("/") + 1 : null;
1084
+ if (match && this.slashKey(slashAt) !== this.dismissedSlash) this.openMenu("insert", this.plus, slashAt, null, $head.parent.attrs.id);
1085
+ }
1086
+ }
893
1087
  this.slashQuery();
894
1088
  this.scheduleLayout();
895
1089
  }
@@ -908,7 +1102,8 @@ class Chrome {
908
1102
  this.win.visualViewport?.removeEventListener("resize", this.onResize);
909
1103
  this.win.visualViewport?.removeEventListener("scroll", this.onResize);
910
1104
  document.removeEventListener("mousedown", this.onDocDown, true);
911
- for (const part of [this.dock, this.plus, this.grip, this.bubble, this.scrim, this.menuEl, this.probe]) part?.remove();
1105
+ document.removeEventListener("keydown", this.onSettingsKey, true);
1106
+ for (const part of [this.dock, this.plus, this.grip, this.bubble, this.scrim, this.menuEl, this.settingsEl, this.probe]) part?.remove();
912
1107
  }
913
1108
  }
914
1109
 
@@ -921,6 +1116,10 @@ export function chromePlugin({ onAiTool = null } = {}) {
921
1116
  chromeFor.set(view, chrome);
922
1117
  return chrome;
923
1118
  },
1119
+ filterTransaction(tr) {
1120
+ if (chrome && tr.docChanged) chrome.remote = !!tr.getMeta(REMOTE);
1121
+ return true;
1122
+ },
924
1123
  props: {
925
1124
  handleKeyDown(view, event) {
926
1125
  if (!chrome) return false;
@@ -946,7 +1145,7 @@ export function chromePlugin({ onAiTool = null } = {}) {
946
1145
  handleDOMEvents: {
947
1146
  keydown(view, event) { return composing(event, view); },
948
1147
  blur(view) {
949
- if (chrome) chrome.bubble.hidden = true;
1148
+ if (chrome && !(chrome.menu && [chrome.bubbleTurn, chrome.bubbleMore].includes(chrome.menu.anchor))) chrome.bubble.hidden = true;
950
1149
  return false;
951
1150
  },
952
1151
  },
@@ -0,0 +1,126 @@
1
+ // Lists and tables on the clipboard in the saved article's shape, so another app
2
+ // gets real lists and a header row and a Document reads them back as the same
3
+ // blocks. A copy nests list items as <ul>/<ol> by indent, leads a check item with
4
+ // its checkbox and names a table's columns in a <thead>; a toggle's own list items
5
+ // keep the editor's flat form, which carries their toggle. On paste,
6
+ // flattenPastedHTML turns the lists back into items and moves the slice's open
7
+ // depths (data-pm-slice, left on the dropped <ul>) to the first block, and the
8
+ // table rule in blocks.js reads a table through table() and rows() below; a
9
+ // Document's own table skips pasteTable's grid path for it. The text flavor is
10
+ // markdown list lines (parseListText reads them) and tab-separated table rows.
11
+ // blocks.js loads this module when an editor mounts, off the input-ready path.
12
+ import { model } from "../vendor/prosemirror.vendor.js";
13
+ import { schema } from "./schema.js";
14
+ import { keysBetween, newId, orderedIds } from "./model.js";
15
+ import { clipboardParser } from "./blocks.js";
16
+
17
+ const N = schema.nodes;
18
+ const parentOf = (n) => n.attrs.parent ?? null;
19
+
20
+ const copiedCols = (table) => {
21
+ const used = new Set();
22
+ table.forEach((row) => row.forEach((cell) => used.add(cell.attrs.col)));
23
+ return orderedIds(table.attrs.cols).filter((id) => used.has(id));
24
+ };
25
+
26
+ function tableHead(table, dom) {
27
+ const doc = dom.ownerDocument;
28
+ const tr = doc.createElement("thead").appendChild(doc.createElement("tr"));
29
+ for (const id of copiedCols(table)) {
30
+ const col = table.attrs.cols[id], th = tr.appendChild(doc.createElement("th"));
31
+ th.setAttribute("data-col", id);
32
+ th.setAttribute("scope", "col");
33
+ if (col.kind) th.setAttribute("data-kind", col.kind);
34
+ if (col.width) { th.setAttribute("data-width", String(col.width)); th.style.width = `${col.width}px`; }
35
+ th.textContent = col.name ?? "";
36
+ }
37
+ dom.prepend(tr.parentNode);
38
+ if (table.attrs.sort) dom.setAttribute("data-sort", JSON.stringify(table.attrs.sort));
39
+ }
40
+
41
+ // The serializer's top-level elements, one per block of the fragment, rewritten in place.
42
+ export function copy(fragment, doms) {
43
+ let lists = [];
44
+ fragment.forEach((n, _, i) => {
45
+ const dom = doms[i];
46
+ if (!dom || dom.nodeType !== 1) return;
47
+ if (n.type === N.table) tableHead(n, dom);
48
+ if (n.type !== N.list_item || parentOf(n) !== null) { lists = []; return; }
49
+ const doc = dom.ownerDocument, { indent, kind } = n.attrs, tag = kind === "ordered" ? "OL" : "UL";
50
+ while (lists.length && lists.at(-1).indent > indent) lists.pop();
51
+ if (lists.at(-1)?.indent === indent && lists.at(-1).list.tagName !== tag) lists.pop();
52
+ if (!lists.length || lists.at(-1).indent < indent) {
53
+ const list = doc.createElement(tag);
54
+ if (lists.length) lists.at(-1).li.append(list); else dom.before(list);
55
+ lists.push({ indent, list, li: null });
56
+ }
57
+ const li = doc.createElement("li");
58
+ if (kind === "check") li.innerHTML = `<input type="checkbox" disabled${n.attrs.checked ? " checked" : ""}>`;
59
+ li.append(...dom.childNodes);
60
+ lists.at(-1).list.append(li);
61
+ lists.at(-1).li = li;
62
+ dom.remove();
63
+ });
64
+ }
65
+
66
+ const quoteCell = (text) => (/[\t\n\r"]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text);
67
+
68
+ export function text(slice) {
69
+ if (!slice.content.firstChild?.type.spec.group?.includes("block")) return null;
70
+ let out = "", runs = [], prevList = false;
71
+ slice.content.forEach((n, _, i) => {
72
+ const list = n.type === N.list_item;
73
+ let line = n.textBetween(0, n.content.size, "\n\n");
74
+ if (list) {
75
+ const { indent, kind } = n.attrs;
76
+ while (runs.length && runs.at(-1).indent > indent) runs.pop();
77
+ if (runs.at(-1)?.indent === indent && runs.at(-1).kind !== kind) runs.pop();
78
+ if (!runs.length || runs.at(-1).indent < indent) runs.push({ indent, kind, n: 0 });
79
+ const run = runs.at(-1);
80
+ const marker = kind === "ordered" ? `${++run.n}.` : kind === "check" ? `- [${n.attrs.checked ? "x" : " "}]` : "-";
81
+ line = `${" ".repeat(runs.length - 1)}${marker} ${line}`;
82
+ } else runs = [];
83
+ if (n.type === N.table) {
84
+ const rows = [copiedCols(n).map((id) => quoteCell(n.attrs.cols[id].name ?? ""))];
85
+ n.forEach((row) => { const cells = []; row.forEach((cell) => cells.push(quoteCell(cell.textContent))); rows.push(cells); });
86
+ line = rows.map((row) => row.join("\t")).join("\n");
87
+ }
88
+ out += (i ? (prevList && list ? "\n" : "\n\n") : "") + line;
89
+ prevList = list;
90
+ });
91
+ return out;
92
+ }
93
+
94
+ // A pasted table: its header row (a <thead> row, or a first row of <th>) names
95
+ // the columns, and a copied one keeps each column's id, kind and width; every
96
+ // other row is a row of cells. Rows get their ids and order keys from identityPlugin.
97
+ const tables = new WeakMap();
98
+ function pastedTable(dom) {
99
+ if (tables.has(dom)) return tables.get(dom);
100
+ const cellsOf = (tr) => [...tr.children].filter((c) => c.tagName === "TD" || c.tagName === "TH");
101
+ const trs = [...dom.querySelectorAll("tr")].filter((tr) => tr.closest("table") === dom);
102
+ const head = trs.find((tr) => tr.parentElement.tagName === "THEAD") ?? (trs.length > 1 && cellsOf(trs[0]).every((c) => c.tagName === "TH") ? trs[0] : null);
103
+ const body = trs.filter((tr) => tr !== head);
104
+ const names = head ? cellsOf(head) : [];
105
+ const width = Math.max(1, names.length, ...body.map((tr) => cellsOf(tr).length));
106
+ const keys = keysBetween(null, null, width);
107
+ const ids = [], cols = {};
108
+ for (let i = 0; i < width; i++) {
109
+ const th = names[i];
110
+ let id = th?.getAttribute("data-col");
111
+ if (!id || Object.hasOwn(cols, id)) id = newId("c");
112
+ const kind = th?.getAttribute("data-kind"), w = Math.round(Number(th?.getAttribute("data-width")));
113
+ cols[id] = { pos: keys[i], name: th?.textContent.replace(/\s+/g, " ").trim() || `Column ${i + 1}`, ...(kind ? { kind } : {}), ...(w > 0 ? { width: w } : {}) };
114
+ ids.push(id);
115
+ }
116
+ const cell = (td, col) => (td ? clipboardParser.parse(td, { topNode: N.table_cell.create({ col }) }) : N.table_cell.create({ col }));
117
+ const rows = (body.length ? body : [null]).map((tr) => N.table_row.create(null, ids.map((col, i) => cell(tr && cellsOf(tr)[i], col))));
118
+ let sort = null;
119
+ try { sort = JSON.parse(dom.getAttribute("data-sort")); } catch {}
120
+ const found = { cols, sort: sort && ids.includes(sort.col) && (sort.dir === "asc" || sort.dir === "desc") ? { col: sort.col, dir: sort.dir } : null, rows: model.Fragment.from(rows) };
121
+ tables.set(dom, found);
122
+ return found;
123
+ }
124
+
125
+ export const table = (dom) => ({ cols: pastedTable(dom).cols, sort: pastedTable(dom).sort, parent: dom.getAttribute("data-page-parent") || null });
126
+ export const rows = (dom) => pastedTable(dom).rows;