@panphora/clayjs 1.16.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (511) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/clay-dom.js +1 -1
  3. package/dist/clay-events.js +1 -1
  4. package/dist/clay-internals.js +1 -1
  5. package/dist/clay-options.js +1 -1
  6. package/dist/clay-router.js +1 -1
  7. package/dist/clay-ui.js +1 -1
  8. package/dist/clay-utils.js +1 -1
  9. package/dist/clay.js +1 -1
  10. package/dist/clay.standalone.js +72519 -59839
  11. package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
  12. package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
  13. package/dist/m/c-2GRUM77Q.js +30 -0
  14. package/dist/m/c-2KIEGNC4.js +1 -0
  15. package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
  16. package/dist/m/c-2YYDMLNZ.js +1 -0
  17. package/dist/m/c-2ZZBC45O.js +1 -0
  18. package/dist/m/c-372N6H3B.js +1 -0
  19. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  20. package/dist/m/c-3LDGIBMN.js +1 -0
  21. package/dist/m/c-3O7OBDCM.js +1 -0
  22. package/dist/m/c-4ASQPRQL.js +1 -0
  23. package/dist/m/c-4TXNLX4K.js +6 -0
  24. package/dist/m/c-4YU3BR4D.js +6 -0
  25. package/dist/m/c-5734MF2P.js +1 -0
  26. package/dist/m/c-57XDY6HC.js +1 -0
  27. package/dist/m/{c-VFX45IZQ.js → c-5B4YBZXC.js} +1 -1
  28. package/dist/m/c-5ONTYIUI.js +1 -0
  29. package/dist/m/c-5OOVFCJM.js +1 -0
  30. package/dist/m/c-5T6NZHNM.js +1 -0
  31. package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
  32. package/dist/m/{c-Z36J63PP.js → c-6DB2OLRJ.js} +1 -1
  33. package/dist/m/c-6DNRCPNM.js +1 -0
  34. package/dist/m/c-6UGWFMQC.js +2 -0
  35. package/dist/m/c-7K4SCQB7.js +1 -0
  36. package/dist/m/c-7MG5RU63.js +1 -0
  37. package/dist/m/c-7PRJCA3E.js +1 -0
  38. package/dist/m/c-A6L3FMUS.js +1 -0
  39. package/dist/m/c-AMU3BPPR.js +4 -0
  40. package/dist/m/c-AMYEFA2E.js +1 -0
  41. package/dist/m/c-AQYCG7QW.js +1 -0
  42. package/dist/m/c-ATKAOH23.js +1 -0
  43. package/dist/m/c-B3JM5ZXC.js +1 -0
  44. package/dist/m/c-B5CS55WR.js +1 -0
  45. package/dist/m/c-B5UJQT7W.js +1 -0
  46. package/dist/m/c-B7J4K2AE.js +3 -0
  47. package/dist/m/c-BHZYSHW4.js +1 -0
  48. package/dist/m/c-BIJALSRD.js +1 -0
  49. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  50. package/dist/m/c-BPF2NSPV.js +1 -0
  51. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  52. package/dist/m/c-BUAYFEV5.js +1 -0
  53. package/dist/m/c-C6IPRG34.js +1 -0
  54. package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
  55. package/dist/m/{c-4DRMS4QG.js → c-CCQ25GFY.js} +1 -1
  56. package/dist/m/c-CJFDLD43.js +1 -0
  57. package/dist/m/c-COPTL5WB.js +1 -0
  58. package/dist/m/c-D3E4WJ4M.js +166 -0
  59. package/dist/m/c-D5IKEKXW.js +7 -0
  60. package/dist/m/c-DHBRA6DL.js +1 -0
  61. package/dist/m/c-DKLS74XB.js +72 -0
  62. package/dist/m/c-DVYGFTSK.js +3 -0
  63. package/dist/m/c-E3ZXARWA.js +1 -0
  64. package/dist/m/c-EK63G3CG.js +1 -0
  65. package/dist/m/c-ERQSNR6K.js +13 -0
  66. package/dist/m/c-EVYAISP3.js +1 -0
  67. package/dist/m/c-EXKHPOVC.js +3 -0
  68. package/dist/m/{c-7VGZ7RU5.js → c-FWKHM4E7.js} +1 -1
  69. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  70. package/dist/m/c-GNDL3TKB.js +1 -0
  71. package/dist/m/c-GRWMD6O5.js +1 -0
  72. package/dist/m/c-GRXHHFPO.js +1 -0
  73. package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
  74. package/dist/m/c-HAWFNBYG.js +1 -0
  75. package/dist/m/c-HG2C77BM.js +4 -0
  76. package/dist/m/c-HHGJBU4D.js +1 -0
  77. package/dist/m/c-HMV6P4RX.js +1 -0
  78. package/dist/m/c-HMYWIHFG.js +1 -0
  79. package/dist/m/c-HT2DKBV2.js +1 -0
  80. package/dist/m/c-I2SCCUEW.js +1 -0
  81. package/dist/m/{c-HRDCD3WN.js → c-ICJYHABF.js} +2 -2
  82. package/dist/m/c-IMZCWYHR.js +7 -0
  83. package/dist/m/c-IVMD3FEM.js +1 -0
  84. package/dist/m/c-IVTRVENZ.js +1 -0
  85. package/dist/m/c-IW6GGOU2.js +1 -0
  86. package/dist/m/c-JKZJIUIP.js +1 -0
  87. package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
  88. package/dist/m/c-JRKY44DK.js +1 -0
  89. package/dist/m/c-K3ADYNLU.js +1 -0
  90. package/dist/m/c-KBS3UV6B.js +1 -0
  91. package/dist/m/c-KGHVFRR3.js +1 -0
  92. package/dist/m/c-KJJPV77R.js +1 -0
  93. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  94. package/dist/m/{c-5Y6TNX5W.js → c-KP64LT2A.js} +2 -2
  95. package/dist/m/c-KQ7E3PYY.js +1 -0
  96. package/dist/m/c-L3F5ZLIV.js +1 -0
  97. package/dist/m/{c-LR4CGEPT.js → c-L42OLXWM.js} +3 -2
  98. package/dist/m/c-L5VFATT6.js +1 -0
  99. package/dist/m/c-L7JL5TL3.js +3 -0
  100. package/dist/m/c-LKSL4AUJ.js +1 -0
  101. package/dist/m/c-M6FNQCTQ.js +1 -0
  102. package/dist/m/c-MCOOHCSG.js +1 -0
  103. package/dist/m/c-MHI3GYKF.js +1 -0
  104. package/dist/m/c-MWXFLW4J.js +1 -0
  105. package/dist/m/c-NEL7HQW3.js +1 -0
  106. package/dist/m/c-NQJRVJ56.js +1 -0
  107. package/dist/m/c-NWX3MFT4.js +1 -0
  108. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  109. package/dist/m/c-OKXI52N7.js +1 -0
  110. package/dist/m/{c-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
  111. package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
  112. package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
  113. package/dist/m/c-P3OFYUMA.js +1 -0
  114. package/dist/m/c-PLN76GF3.js +6 -0
  115. package/dist/m/c-PLP4JKFC.js +1 -0
  116. package/dist/m/c-Q26MME6N.js +1 -0
  117. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  118. package/dist/m/{c-SWRJFCNN.js → c-QIKT6SKM.js} +1 -1
  119. package/dist/m/c-QIXT3Q7C.js +28 -0
  120. package/dist/m/c-QO4HNGCQ.js +1 -0
  121. package/dist/m/c-R27DWQKX.js +1 -0
  122. package/dist/m/{c-HVYXIS72.js → c-R2XUODVY.js} +1 -1
  123. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  124. package/dist/m/c-RND3G2S2.js +1 -0
  125. package/dist/m/c-S2U6NJU4.js +1 -0
  126. package/dist/m/c-SPZPGD3R.js +1 -0
  127. package/dist/m/c-SW4YEG2D.js +8 -0
  128. package/dist/m/c-SZGYW5HT.js +1 -0
  129. package/dist/m/c-T637T7SN.js +1 -0
  130. package/dist/m/c-TJAXGIRV.js +1 -0
  131. package/dist/m/{c-KKWLDOPM.js → c-TNEM3RNM.js} +1 -1
  132. package/dist/m/c-TTB5EK4S.js +32 -0
  133. package/dist/m/c-U7SHNWDH.js +1 -0
  134. package/dist/m/c-UDHIVLYH.js +20 -0
  135. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  136. package/dist/m/{c-TQZ2JYHD.js → c-UORXLSD3.js} +1 -1
  137. package/dist/m/c-UPD33ZPE.js +1 -0
  138. package/dist/m/c-URSMSFCB.js +1 -0
  139. package/dist/m/c-UY4QX42B.js +1 -0
  140. package/dist/m/c-V44KWI2L.js +4 -0
  141. package/dist/m/c-VG5XIN4T.js +1 -0
  142. package/dist/m/c-VILENZTD.js +1 -0
  143. package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
  144. package/dist/m/c-VR2BJ4AN.js +1 -0
  145. package/dist/m/c-W4JCVLAB.js +1 -0
  146. package/dist/m/c-W6DQSHXI.js +135 -0
  147. package/dist/m/c-WBGWGOM7.js +1 -0
  148. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  149. package/dist/m/c-WGW764WX.js +1 -0
  150. package/dist/m/c-WMRJKQ2V.js +5 -0
  151. package/dist/m/c-WNTYKCDG.js +1 -0
  152. package/dist/m/c-WQWIRWO3.js +72 -0
  153. package/dist/m/c-X5LNHET5.js +1 -0
  154. package/dist/m/c-X5TSXZYJ.js +2 -0
  155. package/dist/m/c-XGH4GS2T.js +1 -0
  156. package/dist/m/c-Y6F2FORL.js +1 -0
  157. package/dist/m/c-YBKHUAK7.js +10 -0
  158. package/dist/m/c-YBSS55KU.js +20 -0
  159. package/dist/m/c-YCLU4UNT.js +2 -0
  160. package/dist/m/c-YVZ2L7CE.js +1 -0
  161. package/dist/m/c-ZK4GIZH2.js +1 -0
  162. package/dist/m/c-ZRXJD4G6.js +1 -0
  163. package/dist/m/c-ZWBI4HYK.js +1 -0
  164. package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
  165. package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
  166. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  167. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  168. package/dist/m/core/host-permission-6EVJGNPL.js +1 -0
  169. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  170. package/dist/m/core/{persist-HKHA66IY.js → persist-UKXV5KLA.js} +1 -1
  171. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  172. package/dist/m/core/save-LYY7K6U7.js +1 -0
  173. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  174. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  175. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  176. package/dist/m/events/index-RN2JNGBV.js +1 -0
  177. package/dist/m/internals/index-TGLQINN3.js +1 -0
  178. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  179. package/dist/m/loader-3BBNZZGM.js +1 -0
  180. package/dist/m/manifest-F449FF98.js +1 -0
  181. package/dist/m/manifest-edit-4396A009.js +1 -0
  182. package/dist/m/manifest.json +2509 -1474
  183. package/dist/m/options/options-EJMS3K24.js +1 -0
  184. package/dist/m/page/editor-6LRB3GE4.js +304 -0
  185. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  186. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  187. package/dist/m/plugins/demo-OYO5GCAO.js +1 -0
  188. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  189. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  190. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  191. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  192. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  193. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  194. package/dist/m/plugins/people-JCHVPF7P.js +1 -0
  195. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  196. package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-KLGETK77.js} +2 -2
  197. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  198. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  199. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  200. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  201. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  202. package/dist/m/plugins/upload-NHGJXZK2.js +1 -0
  203. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  204. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  205. package/dist/m/plugins/wire-VBWXEXU2.js +1 -0
  206. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  207. package/dist/m/route/route-62CHXL5B.js +1 -0
  208. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  209. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  210. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  211. package/dist/offline-manifest.json +1560 -889
  212. package/dist/page/themes/light-1.css +33 -8
  213. package/dist/server/core.cjs +1554 -311
  214. package/dist/server/offline-runtime.cjs +63 -63
  215. package/dist/server/offline-runtime.mjs +63 -63
  216. package/dist/server/page-compaction-worker.cjs +3710 -0
  217. package/dist/server/rooms.mjs +5 -0
  218. package/docs/guide.md +653 -101
  219. package/entries/sap.js +5 -5
  220. package/package.json +1 -1
  221. package/src/core/admin-contenteditable.js +5 -2
  222. package/src/core/admin-inputs.js +1 -0
  223. package/src/core/admin-onclick.js +2 -1
  224. package/src/core/boot-phase.js +2 -1
  225. package/src/core/conflict-download.js +3 -29
  226. package/src/core/conflict-notice-ui.js +4 -7
  227. package/src/core/download.js +32 -0
  228. package/src/core/host-meta.js +56 -0
  229. package/src/core/is-edit-mode.js +17 -1
  230. package/src/core/mode-key.js +125 -19
  231. package/src/core/mode-support.js +5 -0
  232. package/src/core/mode-transition.js +20 -6
  233. package/src/core/preserve-authored.js +4 -4
  234. package/src/core/recovery.js +121 -33
  235. package/src/core/save-accepted.js +33 -0
  236. package/src/core/save-core.js +111 -32
  237. package/src/core/save-job.js +108 -0
  238. package/src/core/save-prepare.js +53 -0
  239. package/src/core/save-size-notice.js +3 -1
  240. package/src/core/save-slices.js +15 -0
  241. package/src/core/save.js +557 -127
  242. package/src/core/serialize-steps.js +109 -0
  243. package/src/core/snapshot.js +209 -40
  244. package/src/core/source-map.js +83 -38
  245. package/src/core/stale-host-notice.js +1 -0
  246. package/src/core/upgrade-run.js +59 -8
  247. package/src/dom/active-element.js +5 -0
  248. package/src/lib/assets.js +1 -0
  249. package/src/lib/autosave-scheduler.js +3 -4
  250. package/src/lib/slice.js +65 -0
  251. package/src/lib/uimotion.js +774 -0
  252. package/src/lib/unsaved-state.js +2 -2
  253. package/src/live/fast-lane.js +10 -3
  254. package/src/live/local-lane.js +202 -11
  255. package/src/live/ops.js +16 -0
  256. package/src/live/page-compaction.js +162 -0
  257. package/src/live/server-lane.js +1 -0
  258. package/src/live/store.js +11 -3
  259. package/src/loader-logic.js +3 -1
  260. package/src/loader.js +14 -4
  261. package/src/page/adopt-boot.js +2 -1
  262. package/src/page/awareness.js +33 -2
  263. package/src/page/blocks.js +122 -27
  264. package/src/page/board-views.js +2 -16
  265. package/src/page/boot-pack.js +1 -1
  266. package/src/page/chart-tables.js +29 -0
  267. package/src/page/chart.js +411 -72
  268. package/src/page/checklist.js +10 -135
  269. package/src/page/checkpoint-proof.js +295 -0
  270. package/src/page/chrome.js +257 -40
  271. package/src/page/clipboard-shape.js +126 -0
  272. package/src/page/document-title-dom.js +12 -0
  273. package/src/page/document-title-runtime.js +15 -1
  274. package/src/page/document-title.js +4 -1
  275. package/src/page/drag.js +33 -3
  276. package/src/page/editor.js +207 -31
  277. package/src/page/emoji-data.js +162 -0
  278. package/src/page/export-assets.js +357 -0
  279. package/src/page/export-html.js +209 -0
  280. package/src/page/export-markdown.js +361 -0
  281. package/src/page/features/board.js +12 -3
  282. package/src/page/features/chart.js +11 -0
  283. package/src/page/features/checklist.js +428 -0
  284. package/src/page/features/dates.js +180 -0
  285. package/src/page/features/emoji.js +187 -0
  286. package/src/page/features/export-html.js +189 -0
  287. package/src/page/features/export-markdown.js +169 -0
  288. package/src/page/features/import-markdown.js +349 -0
  289. package/src/page/features/media.js +61 -5
  290. package/src/page/features/mentions.js +188 -0
  291. package/src/page/features/registry.js +24 -1
  292. package/src/page/features/table.js +375 -85
  293. package/src/page/features/title-cover.js +331 -0
  294. package/src/page/features/title-icon.js +213 -0
  295. package/src/page/features/toc.js +87 -0
  296. package/src/page/format-command.js +33 -0
  297. package/src/page/format-gate.js +68 -18
  298. package/src/page/format-host.js +71 -0
  299. package/src/page/format-native-commands.js +158 -0
  300. package/src/page/format-native.js +42 -0
  301. package/src/page/image-actions.js +64 -0
  302. package/src/page/import-markdown.js +262 -0
  303. package/src/page/inline.js +32 -8
  304. package/src/page/items.js +141 -11
  305. package/src/page/markdown.js +30 -2
  306. package/src/page/media.js +35 -119
  307. package/src/page/mention-picker.js +99 -0
  308. package/src/page/merge.js +1 -1
  309. package/src/page/placeholder.js +14 -1
  310. package/src/page/projection.js +156 -25
  311. package/src/page/recover.js +10 -12
  312. package/src/page/recovery-ui.js +464 -0
  313. package/src/page/rich/chart.js +63 -7
  314. package/src/page/save-projection.js +67 -0
  315. package/src/page/save-states.js +14 -4
  316. package/src/page/schema.js +52 -3
  317. package/src/page/table-paste.js +3 -1
  318. package/src/page/table-style.js +4 -4
  319. package/src/page/table.js +44 -2
  320. package/src/page/themes/content.css +25 -1
  321. package/src/page/themes/light-1.css +33 -8
  322. package/src/page/themes/light-1.source.css +8 -7
  323. package/src/page/toc-link.js +31 -0
  324. package/src/page/toc.js +5 -0
  325. package/src/page/view-hooks.js +58 -0
  326. package/src/page/view.js +13 -0
  327. package/src/page/ybind/binding.js +116 -6
  328. package/src/page/ybind/bridge.js +187 -20
  329. package/src/page/ybind/containment.js +162 -0
  330. package/src/page/ybind/doc.js +17 -9
  331. package/src/page/ybind/file-debt.js +63 -0
  332. package/src/page/ybind/layout.js +7 -2
  333. package/src/page/ybind/marks.js +2 -1
  334. package/src/page/ybind/moves.js +5 -4
  335. package/src/page/ybind/origins.js +106 -4
  336. package/src/page/ybind/parts.js +8 -1
  337. package/src/page/ybind/provider.js +125 -18
  338. package/src/page/ybind/recovery-proof.js +32 -9
  339. package/src/page/ybind/recovery-replay.js +905 -4
  340. package/src/page/ybind/recovery.js +174 -45
  341. package/src/page/ybind/table.js +67 -11
  342. package/src/page/ybind/walk-delta.js +258 -0
  343. package/src/page/ybind/walk.js +54 -29
  344. package/src/page/ybind/writer.js +1 -1
  345. package/src/plugins/ai-edit.js +13 -9
  346. package/src/plugins/indicator-chip.js +91 -0
  347. package/src/plugins/indicator.js +13 -80
  348. package/src/plugins/live.js +126 -15
  349. package/src/plugins/menu/actions.js +196 -0
  350. package/src/plugins/menu/people.js +279 -0
  351. package/src/plugins/menu/placement.js +121 -0
  352. package/src/plugins/menu/preferences.js +9 -0
  353. package/src/plugins/menu/seo.js +316 -397
  354. package/src/plugins/menu/style.js +171 -0
  355. package/src/plugins/menu/variants.js +296 -0
  356. package/src/plugins/menu.js +186 -183
  357. package/src/plugins/page-view.js +3 -2
  358. package/src/plugins/page.js +362 -43
  359. package/src/plugins/sortable.js +8 -1
  360. package/src/plugins/source.js +6 -0
  361. package/src/plugins/upgrade.js +51 -17
  362. package/src/plugins/upload-ui.js +12 -17
  363. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  364. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  365. package/src/plugins/visitor-drafts/intro.js +7 -4
  366. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  367. package/src/plugins/visitor-drafts/prompt.js +11 -9
  368. package/src/plugins/visitor-drafts/source.js +4 -3
  369. package/src/plugins/visitor-drafts/store.js +138 -9
  370. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  371. package/src/plugins/visitor-drafts-owner.js +4 -6
  372. package/src/plugins/visitor-drafts.js +5 -3
  373. package/src/recovery/store.js +1 -1
  374. package/src/route/route.js +4 -0
  375. package/src/server/checkpoint/index.js +2 -2
  376. package/src/server/rooms/index.js +5 -1
  377. package/src/server/rooms/live-data-rooms.js +302 -50
  378. package/src/server/rooms/page-compaction-worker.js +12 -0
  379. package/src/server/rooms/page-compactor.js +92 -0
  380. package/src/server/rooms/page-room-conformance.js +461 -0
  381. package/src/source/parse-steps.js +94 -0
  382. package/src/source/session.js +233 -57
  383. package/src/sync/editing-sender.js +12 -0
  384. package/src/sync/live-sync-edit.js +22 -2
  385. package/src/sync/live-sync.js +150 -23
  386. package/src/sync/page-relay.js +52 -0
  387. package/src/sync/presence-store.js +44 -4
  388. package/src/sync/presence.js +6 -3
  389. package/src/sync/saved-stream.js +28 -14
  390. package/src/ui/bevel-controls.js +1 -0
  391. package/src/ui/bevel-dialog.js +5 -2
  392. package/src/ui/editor-ui.js +77 -0
  393. package/src/ui/page-colour.js +281 -0
  394. package/src/ui/toast.js +16 -7
  395. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  396. package/src/vendor/hyper-undo.vendor.js +1 -1
  397. package/src/vendor/hypercms.vendor.js +9 -9
  398. package/src/vendor/parse5.vendor.js +2 -2
  399. package/src/vendor/sap-core.vendor.js +5 -5
  400. package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
  401. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
  402. package/dist/m/c-2M73252O.js +0 -1
  403. package/dist/m/c-2SPVQSHC.js +0 -1
  404. package/dist/m/c-35NBGMZB.js +0 -1
  405. package/dist/m/c-3IQRECYS.js +0 -1
  406. package/dist/m/c-4B6H4G5V.js +0 -1
  407. package/dist/m/c-4FFHZM32.js +0 -1
  408. package/dist/m/c-4YSCRICO.js +0 -1
  409. package/dist/m/c-54RCDCBU.js +0 -1
  410. package/dist/m/c-5M4YEKCX.js +0 -4
  411. package/dist/m/c-5V3MOVJT.js +0 -1
  412. package/dist/m/c-66O32MV3.js +0 -28
  413. package/dist/m/c-6HMB24EV.js +0 -1
  414. package/dist/m/c-7O6AFR65.js +0 -1
  415. package/dist/m/c-ANQTJ3B5.js +0 -2
  416. package/dist/m/c-AYVOREBE.js +0 -1
  417. package/dist/m/c-B2ZXPOS3.js +0 -3
  418. package/dist/m/c-BMIZKMPV.js +0 -1
  419. package/dist/m/c-BTDI4Y23.js +0 -1
  420. package/dist/m/c-CJZSLMVQ.js +0 -1
  421. package/dist/m/c-DBE6ORSI.js +0 -1
  422. package/dist/m/c-EDHUR4MX.js +0 -1
  423. package/dist/m/c-EY6UOM54.js +0 -1
  424. package/dist/m/c-F3CKJTJS.js +0 -1
  425. package/dist/m/c-F7Y6FLPY.js +0 -1
  426. package/dist/m/c-FA4DZVTC.js +0 -1
  427. package/dist/m/c-G2BUZITR.js +0 -1
  428. package/dist/m/c-GB3JTV6E.js +0 -1
  429. package/dist/m/c-GBGWXTX4.js +0 -1
  430. package/dist/m/c-GHBCCVG4.js +0 -1
  431. package/dist/m/c-GJAEGGXY.js +0 -1
  432. package/dist/m/c-GOWBDM6J.js +0 -1
  433. package/dist/m/c-IB52R237.js +0 -1
  434. package/dist/m/c-IKWNE4R4.js +0 -1
  435. package/dist/m/c-IU3IYEU6.js +0 -1
  436. package/dist/m/c-IZ2EBRAJ.js +0 -1
  437. package/dist/m/c-KR4UW4KW.js +0 -1
  438. package/dist/m/c-KY3VJGUA.js +0 -8
  439. package/dist/m/c-LFPHCXY3.js +0 -1
  440. package/dist/m/c-LRPUO2MX.js +0 -1
  441. package/dist/m/c-LXENEBV7.js +0 -1
  442. package/dist/m/c-NMJRXKPC.js +0 -8
  443. package/dist/m/c-OIBUTDXI.js +0 -1
  444. package/dist/m/c-PPOSCAQD.js +0 -1
  445. package/dist/m/c-QIX5BJYN.js +0 -1
  446. package/dist/m/c-QQHEX6HA.js +0 -1
  447. package/dist/m/c-R7RMANJI.js +0 -9
  448. package/dist/m/c-RI4NT75Y.js +0 -1
  449. package/dist/m/c-RM7J7BV2.js +0 -1
  450. package/dist/m/c-RUJKSUQW.js +0 -1
  451. package/dist/m/c-SGCCC4FW.js +0 -25
  452. package/dist/m/c-T4VO4OCB.js +0 -1
  453. package/dist/m/c-TQEMVJCS.js +0 -1
  454. package/dist/m/c-TR6RMDQ7.js +0 -3
  455. package/dist/m/c-UKHTLWUW.js +0 -1
  456. package/dist/m/c-UNN7LWCT.js +0 -1
  457. package/dist/m/c-UXBMRDZB.js +0 -235
  458. package/dist/m/c-V7PZZRG7.js +0 -1
  459. package/dist/m/c-VL52ADDR.js +0 -1
  460. package/dist/m/c-WNYBLIOZ.js +0 -1
  461. package/dist/m/c-WWDBYXLK.js +0 -1
  462. package/dist/m/c-XNNW2CWK.js +0 -1
  463. package/dist/m/c-Y6USGTS5.js +0 -5
  464. package/dist/m/c-YG2NJYS6.js +0 -135
  465. package/dist/m/c-Z4VRC7GU.js +0 -7
  466. package/dist/m/c-Z6K52OLP.js +0 -19
  467. package/dist/m/c-ZDY2MDAA.js +0 -21
  468. package/dist/m/c-ZUCOKOHT.js +0 -1
  469. package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
  470. package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
  471. package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
  472. package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
  473. package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
  474. package/dist/m/core/recovery-TBBSICE2.js +0 -1
  475. package/dist/m/core/save-WUCHTQBE.js +0 -1
  476. package/dist/m/core/save-core-FJVHT5M3.js +0 -1
  477. package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
  478. package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
  479. package/dist/m/events/index-23DPTUT4.js +0 -1
  480. package/dist/m/internals/index-WOVAP3RU.js +0 -1
  481. package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
  482. package/dist/m/loader-HXX6YNH5.js +0 -1
  483. package/dist/m/manifest-F22DEDAC.js +0 -1
  484. package/dist/m/manifest-edit-C3C7376B.js +0 -1
  485. package/dist/m/options/options-2LH76JQ3.js +0 -1
  486. package/dist/m/page/editor-XUTB2HB2.js +0 -189
  487. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
  488. package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
  489. package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
  490. package/dist/m/plugins/live-NUTTXSBS.js +0 -1
  491. package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
  492. package/dist/m/plugins/page-KSUH7G6J.js +0 -2
  493. package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
  494. package/dist/m/plugins/people-AJJ32YCG.js +0 -1
  495. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  496. package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
  497. package/dist/m/plugins/source-KHK5UUSK.js +0 -1
  498. package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
  499. package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
  500. package/dist/m/plugins/upload-XSGNILUY.js +0 -1
  501. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
  502. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
  503. package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
  504. package/dist/m/reader-core-ILWLXVMP.js +0 -68
  505. package/dist/m/route/route-KOCZSEGN.js +0 -1
  506. package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
  507. package/dist/m/ui/index-EVHKMKBL.js +0 -1
  508. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  509. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  510. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  511. /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
@@ -0,0 +1,428 @@
1
+ // The checklist's decoration: the owner and due chips on every check item. It
2
+ // loads the first time a document holds or takes one, through the view hooks, so a
3
+ // document with no checklist never fetches it. The keymap stays in checklist.js.
4
+ import { view as pmView } from "../../vendor/prosemirror.vendor.js";
5
+ import { schema } from "../schema.js";
6
+ import { asStep } from "../blocksel.js";
7
+ import { addViewHook } from "../view-hooks.js";
8
+
9
+ const { Decoration, DecorationSet } = pmView;
10
+
11
+ // The owner chips draw the same initials face a board card does. These two are the
12
+ // parts of that face this module needs, kept here so the checklist part does not
13
+ // share a chunk with the board's parts and cost the board its one request.
14
+ const el = (doc, tag, cls, attrs = {}, text = null) => {
15
+ const node = doc.createElement(tag);
16
+ if (cls) node.className = cls;
17
+ for (const [key, value] of Object.entries(attrs)) if (value !== null && value !== undefined && value !== false) node.setAttribute(key, value === true ? "" : String(value));
18
+ if (text !== null) node.textContent = text;
19
+ return node;
20
+ };
21
+
22
+ const cardFace = (doc, name) => el(doc, "span", "page-board-face", { "aria-hidden": "true" }, ([...String(name).trim()][0] ?? "?").toUpperCase());
23
+
24
+ function itemAt(view, id) {
25
+ let found = null;
26
+ view.state.doc.forEach((node, pos) => { if (node.attrs.id === id && node.type === schema.nodes.list_item && node.attrs.kind === "check") found = { node, pos }; });
27
+ return found;
28
+ }
29
+
30
+ function setItem(view, id, attr, value) {
31
+ const found = itemAt(view, id);
32
+ if (!found) return;
33
+ view.dispatch(asStep(view.state.tr.setNodeMarkup(found.pos, null, { ...found.node.attrs, [attr]: value || null })));
34
+ }
35
+
36
+ // The design's three line icons, at the editor's 16px stroke-1.5 drawing.
37
+ const ICONS = {
38
+ person: '<circle cx="8" cy="5.4" r="2.3"/><path d="M3.6 13.2c.6-2.1 2.2-3.3 4.4-3.3s3.8 1.2 4.4 3.3"/>',
39
+ calendar: '<rect x="2.5" y="3.5" width="11" height="10"/><path d="M2.5 6.8h11M5.5 2v2.4M10.5 2v2.4"/>',
40
+ clock: '<circle cx="8" cy="8" r="5.5"/><path d="M8 4.8v3.4l2.3 1.4"/>',
41
+ tick: '<path d="M3.5 8.3l3 3 6-6.2"/>',
42
+ plus: '<path d="M8 3.5v9M3.5 8h9"/>',
43
+ clear: '<path d="M4.8 4.8l6.4 6.4M11.2 4.8l-6.4 6.4"/>',
44
+ "chevron-left": '<path d="M9.8 3.6L5.4 8l4.4 4.4"/>',
45
+ "chevron-right": '<path d="M6.2 3.6L10.6 8l-4.4 4.4"/>',
46
+ };
47
+
48
+ function icon(doc, paths) {
49
+ const node = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
50
+ node.setAttribute("viewBox", "0 0 16 16");
51
+ node.setAttribute("width", "14");
52
+ node.setAttribute("height", "14");
53
+ node.setAttribute("fill", "none");
54
+ node.setAttribute("stroke", "currentColor");
55
+ node.setAttribute("stroke-width", "1.5");
56
+ node.setAttribute("stroke-linecap", "round");
57
+ node.setAttribute("stroke-linejoin", "round");
58
+ node.setAttribute("aria-hidden", "true");
59
+ node.innerHTML = paths;
60
+ return node;
61
+ }
62
+
63
+ // A tag's words. Today, tomorrow and a short date for the day, as a board card
64
+ // reads its own due badge (board-view.js), with the one word a tag needs that the
65
+ // badge never shows.
66
+ const pad = (n) => String(n).padStart(2, "0");
67
+ const DUE = /^\d{4}-\d{2}-\d{2}$/;
68
+ const todayKey = (now = new Date()) => `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
69
+ const dayAfter = (key) => {
70
+ const day = new Date(`${key}T00:00:00Z`);
71
+ day.setUTCDate(day.getUTCDate() + 1);
72
+ return day.toISOString().slice(0, 10);
73
+ };
74
+ function dueLabel(due, today) {
75
+ if (!due) return "No date";
76
+ if (!DUE.test(due)) return due;
77
+ if (due === today) return "Today";
78
+ if (due === dayAfter(today)) return "Tomorrow";
79
+ const sameYear = due.slice(0, 4) === today.slice(0, 4);
80
+ return new Date(`${due}T00:00:00Z`).toLocaleDateString("en-US", { month: "short", day: "numeric", ...(sameYear ? {} : { year: "numeric" }), timeZone: "UTC" });
81
+ }
82
+
83
+ function tag(doc, { name, paths, label, aria, filled, overdue = false }) {
84
+ const button = el(doc, "button", "page-check-chip", {
85
+ type: "button",
86
+ "data-check": name,
87
+ "data-filled": String(filled),
88
+ "data-overdue": overdue ? "true" : null,
89
+ "aria-expanded": "false",
90
+ "aria-label": aria,
91
+ });
92
+ button.append(icon(doc, paths), el(doc, "span", "page-check-chip-label", {}, label));
93
+ return button;
94
+ }
95
+
96
+ // The floating panels: one is open at a time, and it hangs off the tag that opened
97
+ // it, right edges aligned, flipping above the tag when there is no room below. Escape
98
+ // closes it and hands focus back to the tag; a resize or a scroll moves it with the
99
+ // tag; a mousedown outside closes it.
100
+ let openPopup = null;
101
+
102
+ function popup(view, anchor, { className, label, check, onKey = null }) {
103
+ const doc = anchor.ownerDocument;
104
+ const win = doc.defaultView;
105
+ if (openPopup) openPopup.close();
106
+ for (const node of doc.querySelectorAll(".page-check-popup")) node.remove();
107
+ const parent = view.dom.parentNode;
108
+ const anchored = ["relative", "absolute", "fixed", "sticky"].includes(win?.getComputedStyle?.(parent)?.position ?? "");
109
+ const placement = parent && anchored ? parent : doc.body;
110
+ const panel = el(doc, "div", `page-check-popup ${className}`, { role: "dialog", "aria-label": label, "data-check": check });
111
+ let closed = false;
112
+ const place = () => {
113
+ const ring = anchor.getBoundingClientRect();
114
+ const base = placement === doc.body ? { top: -(win?.scrollY ?? 0), left: -(win?.scrollX ?? 0) } : placement.getBoundingClientRect();
115
+ const box = panel.getBoundingClientRect();
116
+ const width = box.width || Math.min(264, (win?.innerWidth || 292) - 28);
117
+ const room = (win?.innerHeight ?? 0) - ring.bottom - 2;
118
+ const flip = box.height > 0 && room < box.height;
119
+ panel.style.left = `${Math.round(ring.right - width - base.left)}px`;
120
+ panel.style.top = `${Math.round((flip ? ring.top - box.height - 2 : ring.bottom + 2) - base.top)}px`;
121
+ };
122
+ const outside = (event) => {
123
+ if (!panel.isConnected) return close();
124
+ if (panel.contains(event.target) || anchor.contains(event.target)) return;
125
+ close();
126
+ };
127
+ const close = ({ focusAnchor = false } = {}) => {
128
+ if (closed) return;
129
+ closed = true;
130
+ doc.removeEventListener("mousedown", outside, true);
131
+ win?.removeEventListener("resize", place);
132
+ win?.removeEventListener("scroll", place, true);
133
+ if (openPopup?.panel === panel) openPopup = null;
134
+ panel.remove();
135
+ anchor.setAttribute("aria-expanded", "false");
136
+ const fromKeys = anchor.dataset.fromKeys === "true";
137
+ delete anchor.dataset.fromKeys;
138
+ if (focusAnchor && fromKeys) view.focus();
139
+ else if (focusAnchor) anchor.focus();
140
+ };
141
+ panel.addEventListener("keydown", (event) => {
142
+ event.stopPropagation();
143
+ if (event.key === "Escape") { event.preventDefault(); close({ focusAnchor: true }); return; }
144
+ onKey?.(event, panel);
145
+ });
146
+ placement.append(panel);
147
+ anchor.setAttribute("aria-expanded", "true");
148
+ openPopup = { panel, close: () => close() };
149
+ doc.addEventListener("mousedown", outside, true);
150
+ win?.addEventListener("resize", place);
151
+ win?.addEventListener("scroll", place, true);
152
+ return { panel, close, place };
153
+ }
154
+
155
+ function peopleIn(view, extra) {
156
+ const found = new Map();
157
+ view.state.doc.forEach((node) => {
158
+ if (node.type !== schema.nodes.list_item || node.attrs.kind !== "check") return;
159
+ const name = String(node.attrs.owner ?? "").trim();
160
+ if (!name) return;
161
+ const key = name.toLowerCase();
162
+ const entry = found.get(key) ?? { name, count: 0 };
163
+ entry.count += 1;
164
+ found.set(key, entry);
165
+ });
166
+ for (const person of extra ?? []) {
167
+ const name = String((typeof person === "string" ? person : person?.name) ?? "").trim();
168
+ if (name && !found.has(name.toLowerCase())) found.set(name.toLowerCase(), { name, count: 0 });
169
+ }
170
+ return [...found.values()].sort((a, b) => a.name.localeCompare(b.name));
171
+ }
172
+
173
+ // The assignee panel: the document's people with their check-item counts, a name
174
+ // box for anyone new, and a row that clears the owner.
175
+ function ownerPicker(view, id, anchor) {
176
+ const doc = anchor.ownerDocument;
177
+ const current = itemAt(view, id)?.node.attrs.owner || "";
178
+ const { panel, close, place } = popup(view, anchor, {
179
+ className: "page-check-owner",
180
+ label: "Assignee",
181
+ check: "owner-panel",
182
+ onKey: (event, panel) => {
183
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
184
+ event.preventDefault();
185
+ const stops = [...panel.querySelectorAll("input, button:not([disabled])")];
186
+ const at = stops.indexOf(doc.activeElement);
187
+ const step = event.key === "ArrowDown" ? 1 : -1;
188
+ const next = at < 0 ? (step > 0 ? stops[0] : stops[stops.length - 1]) : stops[(at + step + stops.length) % stops.length];
189
+ next?.focus();
190
+ },
191
+ });
192
+ const input = el(doc, "input", "page-check-search", { type: "text", placeholder: "Find or type a name", "aria-label": "Find or type a name" });
193
+ const list = el(doc, "div", "page-check-people");
194
+ const remove = el(doc, "button", "page-check-person page-check-danger", { type: "button", "data-check": "clear-owner" });
195
+ remove.append(icon(doc, ICONS.clear), el(doc, "span", "page-check-person-name", {}, "Remove assignee"));
196
+ remove.disabled = !current;
197
+ remove.addEventListener("click", () => choose(null));
198
+ panel.append(input, el(doc, "div", "page-check-subhead", {}, "People in this document"), list, el(doc, "div", "page-check-divider"), remove);
199
+ let available = [];
200
+ const choose = (name) => { close(); setItem(view, id, "owner", name); };
201
+ const personRow = (name, count) => {
202
+ const selected = name.toLowerCase() === String(current).toLowerCase();
203
+ const row = el(doc, "button", "page-check-person", { type: "button", "data-check": "owner-option", "data-name": name, "aria-pressed": String(selected) });
204
+ row.append(cardFace(doc, name), el(doc, "span", "page-check-person-name", {}, name), el(doc, "span", "page-check-person-meta", {}, `${count} ${count === 1 ? "item" : "items"}`));
205
+ if (selected) row.append(icon(doc, ICONS.tick));
206
+ row.addEventListener("click", () => choose(name));
207
+ return row;
208
+ };
209
+ const addRow = (name) => {
210
+ const row = el(doc, "button", "page-check-person", { type: "button", "data-check": "owner-new", "data-name": name });
211
+ const label = el(doc, "span", "page-check-person-name", {}, "Assign to ");
212
+ label.append(el(doc, "strong", null, {}, name));
213
+ row.append(icon(doc, ICONS.plus), label, el(doc, "span", "page-check-person-meta", {}, "New"));
214
+ row.addEventListener("click", () => choose(name));
215
+ return row;
216
+ };
217
+ const draw = () => {
218
+ const typed = input.value.trim();
219
+ const needle = typed.toLowerCase();
220
+ const people = peopleIn(view, available);
221
+ const rows = [];
222
+ if (typed && !people.some((person) => person.name.toLowerCase() === needle)) rows.push(addRow(typed));
223
+ for (const person of people) {
224
+ if (needle && !person.name.toLowerCase().includes(needle)) continue;
225
+ rows.push(personRow(person.name, person.count));
226
+ }
227
+ list.replaceChildren(...rows);
228
+ };
229
+ input.addEventListener("keydown", (event) => {
230
+ event.stopPropagation();
231
+ if (event.key === "Escape") { event.preventDefault(); close({ focusAnchor: true }); return; }
232
+ if (event.key !== "Enter") return;
233
+ event.preventDefault();
234
+ list.querySelector("button[data-check]")?.click();
235
+ });
236
+ input.addEventListener("input", draw);
237
+ draw();
238
+ place();
239
+ input.focus();
240
+ const source = globalThis.clay?.people?.available;
241
+ if (source) Promise.resolve().then(() => source()).then((names) => { if (!openPopup || openPopup.panel !== panel) return; available = Array.isArray(names) ? names : []; draw(); }).catch(() => {});
242
+ }
243
+
244
+ // A day of the local calendar, its ISO key, and the words a quick row and a day
245
+ // button read it in.
246
+ const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
247
+ const dateOf = (year, month, day) => new Date(year, month, day);
248
+ const shortDay = (day) => day.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
249
+ const longDay = (day) => day.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", year: "numeric" });
250
+ const dueDate = (key) => {
251
+ if (!key || !DUE.test(key)) return null;
252
+ const [year, month, day] = key.split("-").map(Number);
253
+ return dateOf(year, month - 1, day);
254
+ };
255
+
256
+ // The due date panel: three quick days, a compact month calendar, and a row that
257
+ // clears the date. The calendar opens on the month of the item's own date, or on
258
+ // this month when it has none.
259
+ function datePicker(view, id, anchor) {
260
+ const doc = anchor.ownerDocument;
261
+ const current = itemAt(view, id)?.node.attrs.due || null;
262
+ const today = todayKey();
263
+ const clock = new Date();
264
+ const now = dateOf(clock.getFullYear(), clock.getMonth(), clock.getDate());
265
+ const opened = dueDate(current) ?? now;
266
+ let year = opened.getFullYear();
267
+ let month = opened.getMonth();
268
+
269
+ const { panel, close, place } = popup(view, anchor, {
270
+ className: "page-check-date",
271
+ label: "Due date",
272
+ check: "date-panel",
273
+ onKey: (event, panel) => gridKey(event, panel),
274
+ });
275
+ const pick = (key) => { close(); setItem(view, id, "due", key); };
276
+
277
+ const quick = el(doc, "div", "page-check-quick");
278
+ const quickRow = (label, offset) => {
279
+ const day = dateOf(now.getFullYear(), now.getMonth(), now.getDate() + offset);
280
+ const row = el(doc, "button", "page-check-quick-row", { type: "button", "data-check": "date-quick", "data-date": todayKey(day) });
281
+ row.append(icon(doc, ICONS.calendar), el(doc, "span", "page-check-quick-label", {}, label), el(doc, "span", "page-check-quick-meta", {}, shortDay(day)));
282
+ row.addEventListener("click", () => pick(todayKey(day)));
283
+ return row;
284
+ };
285
+ quick.append(quickRow("Today", 0), quickRow("Tomorrow", 1), quickRow("Next week", 7));
286
+
287
+ const calendar = el(doc, "div", "page-check-cal");
288
+ const head = el(doc, "div", "page-check-cal-head");
289
+ const monthLabel = el(doc, "span", "page-check-cal-month");
290
+ const nav = el(doc, "span", "page-check-cal-nav");
291
+ const step = (by) => {
292
+ const moved = dateOf(year, month + by, 1);
293
+ year = moved.getFullYear();
294
+ month = moved.getMonth();
295
+ draw();
296
+ };
297
+ const previous = el(doc, "button", "page-check-cal-btn", { type: "button", "aria-label": "Previous month" });
298
+ const next = el(doc, "button", "page-check-cal-btn", { type: "button", "aria-label": "Next month" });
299
+ previous.append(icon(doc, ICONS["chevron-left"]));
300
+ next.append(icon(doc, ICONS["chevron-right"]));
301
+ previous.addEventListener("click", () => step(-1));
302
+ next.addEventListener("click", () => step(1));
303
+ nav.append(previous, next);
304
+ head.append(monthLabel, nav);
305
+ const grid = el(doc, "div", "page-check-cal-grid");
306
+ calendar.append(head, grid);
307
+
308
+ const draw = () => {
309
+ monthLabel.replaceChildren(el(doc, "span", null, {}, `${MONTHS[month]} `), el(doc, "span", "page-check-cal-year", {}, String(year)));
310
+ const first = dateOf(year, month, 1);
311
+ const days = dateOf(year, month + 1, 0).getDate();
312
+ const cells = Math.ceil((first.getDay() + days) / 7) * 7;
313
+ const heads = ["S", "M", "T", "W", "T", "F", "S"].map((letter) => el(doc, "span", "page-check-cal-dow", {}, letter));
314
+ const buttons = [];
315
+ for (let i = 0; i < cells; i += 1) {
316
+ const day = dateOf(year, month, 1 - first.getDay() + i);
317
+ const key = todayKey(day);
318
+ const button = el(doc, "button", "page-check-cal-day", {
319
+ type: "button",
320
+ "data-check": "date-day",
321
+ "data-date": key,
322
+ "data-outside": String(day.getMonth() !== month),
323
+ "data-today": String(key === today),
324
+ "aria-pressed": String(key === current),
325
+ "aria-label": longDay(day),
326
+ }, String(day.getDate()));
327
+ button.addEventListener("click", () => pick(key));
328
+ buttons.push(button);
329
+ }
330
+ grid.replaceChildren(...heads, ...buttons);
331
+ };
332
+
333
+ const gridKey = (event, panel) => {
334
+ const button = doc.activeElement?.closest?.('[data-check="date-day"]');
335
+ if (!button) return;
336
+ if (event.key === "Enter" || event.key === " ") { event.preventDefault(); button.click(); return; }
337
+ const move = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[event.key];
338
+ if (!move) return;
339
+ event.preventDefault();
340
+ const from = dueDate(button.dataset.date);
341
+ const day = dateOf(from.getFullYear(), from.getMonth(), from.getDate() + move);
342
+ if (day.getMonth() !== month || day.getFullYear() !== year) {
343
+ year = day.getFullYear();
344
+ month = day.getMonth();
345
+ draw();
346
+ }
347
+ panel.querySelector(`[data-check="date-day"][data-date="${todayKey(day)}"]`)?.focus();
348
+ };
349
+
350
+ const clear = el(doc, "button", "page-check-quick-row page-check-danger", { type: "button", "data-check": "clear-date" });
351
+ clear.append(icon(doc, ICONS.clear), el(doc, "span", "page-check-quick-label", {}, "Clear date"));
352
+ clear.disabled = !current;
353
+ clear.addEventListener("click", () => pick(null));
354
+
355
+ panel.append(quick, calendar, el(doc, "div", "page-check-divider"), clear);
356
+ draw();
357
+ place();
358
+ (grid.querySelector('[data-check="date-day"][aria-pressed="true"]') ?? grid.querySelector('[data-check="date-day"][data-today="true"]'))?.focus();
359
+ }
360
+
361
+ function controls(view, node) {
362
+ const doc = view.dom.ownerDocument;
363
+ const box = doc.createElement("span");
364
+ box.className = "page-check-controls";
365
+ box.contentEditable = "false";
366
+ box.dataset.empty = String(!node.attrs.owner && !node.attrs.due);
367
+ const today = todayKey();
368
+ const ownerName = node.attrs.owner || "Unassigned";
369
+ const owner = tag(doc, { name: "owner", paths: ICONS.person, label: ownerName, aria: `Assignee: ${ownerName}`, filled: !!node.attrs.owner });
370
+ const dateName = dueLabel(node.attrs.due, today);
371
+ const overdue = !!node.attrs.due && !node.attrs.checked && DUE.test(node.attrs.due) && node.attrs.due < today;
372
+ const date = tag(doc, { name: "date", paths: overdue ? ICONS.clock : ICONS.calendar, label: dateName, aria: `Due date: ${dateName}`, filled: !!node.attrs.due, overdue });
373
+ owner.addEventListener("click", () => ownerPicker(view, node.attrs.id, owner));
374
+ date.addEventListener("click", () => datePicker(view, node.attrs.id, date));
375
+ box.append(owner, date);
376
+ return box;
377
+ }
378
+
379
+ export function focusControl(view, name) {
380
+ const $pos = view.state.selection.$head;
381
+ if ($pos.depth !== 1 || $pos.parent.type !== schema.nodes.list_item || $pos.parent.attrs.kind !== "check") return false;
382
+ const id = $pos.parent.attrs.id;
383
+ const widget = [...view.dom.querySelectorAll(".page-check-controls")].find((el) => el.dataset.item === id);
384
+ const button = widget?.querySelector(`[data-check="${name}"]`);
385
+ if (!button) return false;
386
+ button.dataset.fromKeys = "true";
387
+ button.click();
388
+ return true;
389
+ }
390
+
391
+ export function decorations(st) {
392
+ const widgets = [];
393
+ const today = todayKey();
394
+ st.doc.forEach((node, pos) => {
395
+ if (node.type !== schema.nodes.list_item || node.attrs.kind !== "check") return;
396
+ widgets.push(Decoration.widget(pos + node.nodeSize - 1, (view) => {
397
+ const box = controls(view, node);
398
+ box.dataset.item = node.attrs.id;
399
+ return box;
400
+ }, { key: `${node.attrs.id}:${node.attrs.owner}:${node.attrs.due}:${node.attrs.checked}:${today}`, side: -1, stopEvent: (event) => !!event.target.closest?.(".page-check-chip, .page-check-popup"), ignoreSelection: true }));
401
+ });
402
+ return DecorationSet.create(st.doc, widgets);
403
+ }
404
+
405
+ // A document left open overnight redraws its Today, Tomorrow and overdue tags when
406
+ // the day turns, checked once a minute and whenever the tab comes back.
407
+ function watchDay(view) {
408
+ let day = todayKey();
409
+ const check = () => {
410
+ if (view.isDestroyed || todayKey() === day) return;
411
+ day = todayKey();
412
+ view.dispatch(view.state.tr.setMeta("addToHistory", false));
413
+ };
414
+ const win = view.dom.ownerDocument.defaultView;
415
+ const timer = win.setInterval(check, 60000);
416
+ win.document.addEventListener("visibilitychange", check);
417
+ return () => {
418
+ win.clearInterval(timer);
419
+ win.document.removeEventListener("visibilitychange", check);
420
+ };
421
+ }
422
+
423
+ export function createFeature({ view = null } = {}) {
424
+ const off = view ? addViewHook(view, { decorations, focusControl }) : () => {};
425
+ const unwatch = view ? watchDay(view) : () => {};
426
+ if (view && !view.isDestroyed) view.dispatch(view.state.tr.setMeta("addToHistory", false));
427
+ return { id: "checklist", dispose() { off(); unwatch(); } };
428
+ }
@@ -0,0 +1,180 @@
1
+ // Date mentions: the Date action offers Today and Tomorrow, and choosing one writes that
2
+ // calendar day into the document as ordinary ISO text with the `date` mark over it. Both
3
+ // days are resolved once, when the command runs, from the local calendar rather than from a
4
+ // UTC instant, so the text a person reads is the day they picked; nothing here stores a
5
+ // time, a zone, a relative word or a reminder, and nothing speaks to the network. Loaded on
6
+ // the first Date action, and disposed with the exit.
7
+ import { state } from "../../vendor/prosemirror.vendor.js";
8
+ import { schema } from "../schema.js";
9
+ import { asStep } from "../blocksel.js";
10
+ import { composing } from "../markdown.js";
11
+ import { createMentionPicker } from "../mention-picker.js";
12
+ import { runFormatted } from "../format-command.js";
13
+
14
+ const pad = (n) => String(n).padStart(2, "0");
15
+
16
+ // A calendar date, as the record stores it: no year padding games, no leap second, no
17
+ // zone. A day that does not exist is not a date, so 2023-02-29 is refused.
18
+ export function isISODate(value) {
19
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
20
+ const parsed = new Date(`${value}T12:00:00Z`);
21
+ return Number.isFinite(parsed.valueOf()) && parsed.toISOString().slice(0, 10) === value;
22
+ }
23
+
24
+ // Today, from the local calendar: the fields a person reads on their own wall clock, never
25
+ // `toISOString()` of a local time, which is tomorrow or yesterday for part of the day.
26
+ export const localISODate = (now = new Date()) => `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
27
+
28
+ // A day after (or before) a date, by calendar arithmetic on the date alone: noon UTC keeps
29
+ // a daylight-saving shift from landing on the day before.
30
+ const dayAfter = (date, days) => {
31
+ const moved = new Date(`${date}T12:00:00Z`);
32
+ moved.setUTCDate(moved.getUTCDate() + days);
33
+ return moved.toISOString().slice(0, 10);
34
+ };
35
+
36
+ // What the picker lists: two days, both resolved here and once, labelled with the text each
37
+ // one writes. Nothing is stored and no day is invented later.
38
+ export const earlyDays = (today) => {
39
+ const tomorrow = dayAfter(today, 1);
40
+ return [
41
+ { id: "today", name: `Today (${today})`, date: today },
42
+ { id: "tomorrow", name: `Tomorrow (${tomorrow})`, date: tomorrow },
43
+ ];
44
+ };
45
+
46
+ export function createFeature({ view = null } = {}) {
47
+ if (!view) return { run: () => ({ ok: false, code: "not-editing" }), dispose() {} };
48
+ const doc = view.dom.ownerDocument;
49
+ let session = null;
50
+
51
+ // The caret the command belongs to, read from the document again at the commit: the
52
+ // picker holds no focus and writes nothing, so a caret that moved, a selection that is no
53
+ // longer empty or a block that refuses marks all mean the bookmark no longer holds and the
54
+ // command refuses rather than writing the date somewhere else.
55
+ function bookmark(s) {
56
+ const st = view.state;
57
+ if (!st.selection.empty || st.selection.from !== s.from) return null;
58
+ if (s.from < 0 || s.from > st.doc.content.size) return null;
59
+ const $from = st.doc.resolve(s.from);
60
+ if (!$from.parent.isTextblock || $from.parent.type.spec.code) return null;
61
+ if (!$from.parent.type.allowsMarkType(schema.marks.date)) return null;
62
+ return s.from;
63
+ }
64
+
65
+ // Where the list hangs, in the page's own placement variables. A view that cannot measure
66
+ // the caret (one outside a laid-out document) still gets its list, just not positioned.
67
+ function place(s) {
68
+ try {
69
+ const box = view.dom.parentElement?.getBoundingClientRect?.() ?? { left: 0, top: 0 };
70
+ const at = view.coordsAtPos(s.from);
71
+ return { x: Math.max(8, at.left - box.left), y: at.bottom + 6 - box.top };
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ function anchor(s) {
78
+ const id = s.picker.activeId();
79
+ if (id) view.dom.setAttribute("aria-activedescendant", id);
80
+ else if (view.dom.getAttribute("aria-activedescendant")?.startsWith(s.picker.el.id)) view.dom.removeAttribute("aria-activedescendant");
81
+ }
82
+
83
+ function close() {
84
+ const s = session;
85
+ if (!s) return;
86
+ session = null;
87
+ for (const [target, type, fn, options] of s.listen) target.removeEventListener(type, fn, options);
88
+ if (view.dom.getAttribute("aria-activedescendant")?.startsWith(`${s.picker.el.id}-`)) view.dom.removeAttribute("aria-activedescendant");
89
+ s.picker.destroy();
90
+ }
91
+
92
+ // The one place a date reaches the document. Document 14 Step 8a wraps this call in the
93
+ // format gate's `runFormatCommand`; nothing else in this module writes.
94
+ function insertDateMention(at, date) {
95
+ const label = String(date);
96
+ // One transaction: the ISO text and its mark, then one unmarked space so typing at the
97
+ // edge of a date stays plain text.
98
+ const tr = view.state.tr.insertText(label, at, at);
99
+ tr.addMark(at, at + label.length, schema.marks.date.create({ date: label }));
100
+ tr.insertText(" ", at + label.length);
101
+ tr.setSelection(state.TextSelection.create(tr.doc, at + label.length + 1));
102
+ view.dispatch(asStep(tr));
103
+ view.focus();
104
+ return { ok: true, date: label };
105
+ }
106
+
107
+ async function choose(index) {
108
+ const s = session;
109
+ if (!s) return { ok: false, code: "not-editing" };
110
+ const entry = s.picker.personAt(index);
111
+ const at = bookmark(s);
112
+ if (at === null || !entry || !isISODate(entry.date)) {
113
+ close();
114
+ if (!entry) return { ok: false, code: "no-date" };
115
+ if (at === null) return { ok: false, code: "stale-target" };
116
+ return { ok: false, code: "invalid-date" };
117
+ }
118
+ close();
119
+ // The gate awaits, so the caret is read again here: one that moved, a selection that is
120
+ // no longer a caret or a block that refuses marks means the target no longer holds, and
121
+ // the date is not written anywhere.
122
+ return runFormatted(view, () => {
123
+ const now = bookmark(s);
124
+ if (now === null) return { ok: false, code: "stale-target" };
125
+ return insertDateMention(now, entry.date);
126
+ });
127
+ }
128
+
129
+ function key(event) {
130
+ const s = session;
131
+ if (!s || composing(event, view)) return;
132
+ const stop = () => { event.preventDefault(); event.stopPropagation(); };
133
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
134
+ stop();
135
+ s.picker.move(event.key === "ArrowDown" ? 1 : -1);
136
+ anchor(s);
137
+ } else if (event.key === "Enter") {
138
+ stop();
139
+ choose(s.picker.index());
140
+ } else if (event.key === "Escape" || event.key === "Tab") {
141
+ stop();
142
+ close();
143
+ }
144
+ }
145
+
146
+ function open() {
147
+ close();
148
+ // Today is read once, here: a picker left open across midnight still writes the day the
149
+ // command was asked for, and choosing Tomorrow never reads the clock again.
150
+ const s = { from: view.state.selection.from, days: earlyDays(localISODate()), picker: null, listen: [] };
151
+ s.picker = createMentionPicker({ document: doc, label: "Insert a date", empty: "No date to insert", onPick: (index) => choose(index) });
152
+ session = s;
153
+ const listen = (target, type, fn, options) => {
154
+ target.addEventListener(type, fn, options);
155
+ s.listen.push([target, type, fn, options]);
156
+ };
157
+ listen(doc, "keydown", (event) => key(event), true);
158
+ // Typing closes the list: the caret has moved on, and the command that opened it has
159
+ // nothing to write where it no longer holds.
160
+ listen(view.dom, "input", () => close());
161
+ listen(doc, "mousedown", (event) => { if (!s.picker.el.contains(event.target)) close(); }, true);
162
+ (view.dom.parentElement ?? doc.body).appendChild(s.picker.el);
163
+ if (bookmark(s) === null) {
164
+ close();
165
+ return { ok: false, code: "stale-target" };
166
+ }
167
+ s.picker.show(s.days, place(s));
168
+ anchor(s);
169
+ return { ok: true, picker: true };
170
+ }
171
+
172
+ return {
173
+ // The Date action: the picker at the caret, offering the two days resolved now. It
174
+ // writes nothing until one is chosen.
175
+ async run() {
176
+ return open();
177
+ },
178
+ dispose() { close(); },
179
+ };
180
+ }