@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.
- package/dist/all.js +1 -1
- package/dist/clay-dom.js +1 -1
- package/dist/clay-events.js +1 -1
- package/dist/clay-internals.js +1 -1
- package/dist/clay-options.js +1 -1
- package/dist/clay-router.js +1 -1
- package/dist/clay-ui.js +1 -1
- package/dist/clay-utils.js +1 -1
- package/dist/clay.js +1 -1
- package/dist/clay.standalone.js +72519 -59839
- package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
- package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
- package/dist/m/c-2GRUM77Q.js +30 -0
- package/dist/m/c-2KIEGNC4.js +1 -0
- package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
- package/dist/m/c-2YYDMLNZ.js +1 -0
- package/dist/m/c-2ZZBC45O.js +1 -0
- package/dist/m/c-372N6H3B.js +1 -0
- package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
- package/dist/m/c-3LDGIBMN.js +1 -0
- package/dist/m/c-3O7OBDCM.js +1 -0
- package/dist/m/c-4ASQPRQL.js +1 -0
- package/dist/m/c-4TXNLX4K.js +6 -0
- package/dist/m/c-4YU3BR4D.js +6 -0
- package/dist/m/c-5734MF2P.js +1 -0
- package/dist/m/c-57XDY6HC.js +1 -0
- package/dist/m/{c-VFX45IZQ.js → c-5B4YBZXC.js} +1 -1
- package/dist/m/c-5ONTYIUI.js +1 -0
- package/dist/m/c-5OOVFCJM.js +1 -0
- package/dist/m/c-5T6NZHNM.js +1 -0
- package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
- package/dist/m/{c-Z36J63PP.js → c-6DB2OLRJ.js} +1 -1
- package/dist/m/c-6DNRCPNM.js +1 -0
- package/dist/m/c-6UGWFMQC.js +2 -0
- package/dist/m/c-7K4SCQB7.js +1 -0
- package/dist/m/c-7MG5RU63.js +1 -0
- package/dist/m/c-7PRJCA3E.js +1 -0
- package/dist/m/c-A6L3FMUS.js +1 -0
- package/dist/m/c-AMU3BPPR.js +4 -0
- package/dist/m/c-AMYEFA2E.js +1 -0
- package/dist/m/c-AQYCG7QW.js +1 -0
- package/dist/m/c-ATKAOH23.js +1 -0
- package/dist/m/c-B3JM5ZXC.js +1 -0
- package/dist/m/c-B5CS55WR.js +1 -0
- package/dist/m/c-B5UJQT7W.js +1 -0
- package/dist/m/c-B7J4K2AE.js +3 -0
- package/dist/m/c-BHZYSHW4.js +1 -0
- package/dist/m/c-BIJALSRD.js +1 -0
- package/dist/m/c-BJ7YQ5QZ.js +1 -0
- package/dist/m/c-BPF2NSPV.js +1 -0
- package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
- package/dist/m/c-BUAYFEV5.js +1 -0
- package/dist/m/c-C6IPRG34.js +1 -0
- package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
- package/dist/m/{c-4DRMS4QG.js → c-CCQ25GFY.js} +1 -1
- package/dist/m/c-CJFDLD43.js +1 -0
- package/dist/m/c-COPTL5WB.js +1 -0
- package/dist/m/c-D3E4WJ4M.js +166 -0
- package/dist/m/c-D5IKEKXW.js +7 -0
- package/dist/m/c-DHBRA6DL.js +1 -0
- package/dist/m/c-DKLS74XB.js +72 -0
- package/dist/m/c-DVYGFTSK.js +3 -0
- package/dist/m/c-E3ZXARWA.js +1 -0
- package/dist/m/c-EK63G3CG.js +1 -0
- package/dist/m/c-ERQSNR6K.js +13 -0
- package/dist/m/c-EVYAISP3.js +1 -0
- package/dist/m/c-EXKHPOVC.js +3 -0
- package/dist/m/{c-7VGZ7RU5.js → c-FWKHM4E7.js} +1 -1
- package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
- package/dist/m/c-GNDL3TKB.js +1 -0
- package/dist/m/c-GRWMD6O5.js +1 -0
- package/dist/m/c-GRXHHFPO.js +1 -0
- package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
- package/dist/m/c-HAWFNBYG.js +1 -0
- package/dist/m/c-HG2C77BM.js +4 -0
- package/dist/m/c-HHGJBU4D.js +1 -0
- package/dist/m/c-HMV6P4RX.js +1 -0
- package/dist/m/c-HMYWIHFG.js +1 -0
- package/dist/m/c-HT2DKBV2.js +1 -0
- package/dist/m/c-I2SCCUEW.js +1 -0
- package/dist/m/{c-HRDCD3WN.js → c-ICJYHABF.js} +2 -2
- package/dist/m/c-IMZCWYHR.js +7 -0
- package/dist/m/c-IVMD3FEM.js +1 -0
- package/dist/m/c-IVTRVENZ.js +1 -0
- package/dist/m/c-IW6GGOU2.js +1 -0
- package/dist/m/c-JKZJIUIP.js +1 -0
- package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
- package/dist/m/c-JRKY44DK.js +1 -0
- package/dist/m/c-K3ADYNLU.js +1 -0
- package/dist/m/c-KBS3UV6B.js +1 -0
- package/dist/m/c-KGHVFRR3.js +1 -0
- package/dist/m/c-KJJPV77R.js +1 -0
- package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
- package/dist/m/{c-5Y6TNX5W.js → c-KP64LT2A.js} +2 -2
- package/dist/m/c-KQ7E3PYY.js +1 -0
- package/dist/m/c-L3F5ZLIV.js +1 -0
- package/dist/m/{c-LR4CGEPT.js → c-L42OLXWM.js} +3 -2
- package/dist/m/c-L5VFATT6.js +1 -0
- package/dist/m/c-L7JL5TL3.js +3 -0
- package/dist/m/c-LKSL4AUJ.js +1 -0
- package/dist/m/c-M6FNQCTQ.js +1 -0
- package/dist/m/c-MCOOHCSG.js +1 -0
- package/dist/m/c-MHI3GYKF.js +1 -0
- package/dist/m/c-MWXFLW4J.js +1 -0
- package/dist/m/c-NEL7HQW3.js +1 -0
- package/dist/m/c-NQJRVJ56.js +1 -0
- package/dist/m/c-NWX3MFT4.js +1 -0
- package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
- package/dist/m/c-OKXI52N7.js +1 -0
- package/dist/m/{c-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
- package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
- package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
- package/dist/m/c-P3OFYUMA.js +1 -0
- package/dist/m/c-PLN76GF3.js +6 -0
- package/dist/m/c-PLP4JKFC.js +1 -0
- package/dist/m/c-Q26MME6N.js +1 -0
- package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
- package/dist/m/{c-SWRJFCNN.js → c-QIKT6SKM.js} +1 -1
- package/dist/m/c-QIXT3Q7C.js +28 -0
- package/dist/m/c-QO4HNGCQ.js +1 -0
- package/dist/m/c-R27DWQKX.js +1 -0
- package/dist/m/{c-HVYXIS72.js → c-R2XUODVY.js} +1 -1
- package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
- package/dist/m/c-RND3G2S2.js +1 -0
- package/dist/m/c-S2U6NJU4.js +1 -0
- package/dist/m/c-SPZPGD3R.js +1 -0
- package/dist/m/c-SW4YEG2D.js +8 -0
- package/dist/m/c-SZGYW5HT.js +1 -0
- package/dist/m/c-T637T7SN.js +1 -0
- package/dist/m/c-TJAXGIRV.js +1 -0
- package/dist/m/{c-KKWLDOPM.js → c-TNEM3RNM.js} +1 -1
- package/dist/m/c-TTB5EK4S.js +32 -0
- package/dist/m/c-U7SHNWDH.js +1 -0
- package/dist/m/c-UDHIVLYH.js +20 -0
- package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
- package/dist/m/{c-TQZ2JYHD.js → c-UORXLSD3.js} +1 -1
- package/dist/m/c-UPD33ZPE.js +1 -0
- package/dist/m/c-URSMSFCB.js +1 -0
- package/dist/m/c-UY4QX42B.js +1 -0
- package/dist/m/c-V44KWI2L.js +4 -0
- package/dist/m/c-VG5XIN4T.js +1 -0
- package/dist/m/c-VILENZTD.js +1 -0
- package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
- package/dist/m/c-VR2BJ4AN.js +1 -0
- package/dist/m/c-W4JCVLAB.js +1 -0
- package/dist/m/c-W6DQSHXI.js +135 -0
- package/dist/m/c-WBGWGOM7.js +1 -0
- package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
- package/dist/m/c-WGW764WX.js +1 -0
- package/dist/m/c-WMRJKQ2V.js +5 -0
- package/dist/m/c-WNTYKCDG.js +1 -0
- package/dist/m/c-WQWIRWO3.js +72 -0
- package/dist/m/c-X5LNHET5.js +1 -0
- package/dist/m/c-X5TSXZYJ.js +2 -0
- package/dist/m/c-XGH4GS2T.js +1 -0
- package/dist/m/c-Y6F2FORL.js +1 -0
- package/dist/m/c-YBKHUAK7.js +10 -0
- package/dist/m/c-YBSS55KU.js +20 -0
- package/dist/m/c-YCLU4UNT.js +2 -0
- package/dist/m/c-YVZ2L7CE.js +1 -0
- package/dist/m/c-ZK4GIZH2.js +1 -0
- package/dist/m/c-ZRXJD4G6.js +1 -0
- package/dist/m/c-ZWBI4HYK.js +1 -0
- package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
- package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
- package/dist/m/core/autosave-S6OMNDCK.js +1 -0
- package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
- package/dist/m/core/host-permission-6EVJGNPL.js +1 -0
- package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
- package/dist/m/core/{persist-HKHA66IY.js → persist-UKXV5KLA.js} +1 -1
- package/dist/m/core/recovery-DRFZRJED.js +1 -0
- package/dist/m/core/save-LYY7K6U7.js +1 -0
- package/dist/m/core/save-core-MKRPSEEO.js +1 -0
- package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
- package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
- package/dist/m/events/index-RN2JNGBV.js +1 -0
- package/dist/m/internals/index-TGLQINN3.js +1 -0
- package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
- package/dist/m/loader-3BBNZZGM.js +1 -0
- package/dist/m/manifest-F449FF98.js +1 -0
- package/dist/m/manifest-edit-4396A009.js +1 -0
- package/dist/m/manifest.json +2509 -1474
- package/dist/m/options/options-EJMS3K24.js +1 -0
- package/dist/m/page/editor-6LRB3GE4.js +304 -0
- package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
- package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
- package/dist/m/plugins/demo-OYO5GCAO.js +1 -0
- package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
- package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
- package/dist/m/plugins/live-AD4H6XSL.js +1 -0
- package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
- package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
- package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
- package/dist/m/plugins/people-JCHVPF7P.js +1 -0
- package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
- package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-KLGETK77.js} +2 -2
- package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
- package/dist/m/plugins/source-TJXOIOYY.js +1 -0
- package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
- package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
- package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
- package/dist/m/plugins/upload-NHGJXZK2.js +1 -0
- package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
- package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
- package/dist/m/plugins/wire-VBWXEXU2.js +1 -0
- package/dist/m/reader-core-4W2KYEWQ.js +77 -0
- package/dist/m/route/route-62CHXL5B.js +1 -0
- package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
- package/dist/m/ui/index-IVWN2QC4.js +1 -0
- package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
- package/dist/offline-manifest.json +1560 -889
- package/dist/page/themes/light-1.css +33 -8
- package/dist/server/core.cjs +1554 -311
- package/dist/server/offline-runtime.cjs +63 -63
- package/dist/server/offline-runtime.mjs +63 -63
- package/dist/server/page-compaction-worker.cjs +3710 -0
- package/dist/server/rooms.mjs +5 -0
- package/docs/guide.md +653 -101
- package/entries/sap.js +5 -5
- package/package.json +1 -1
- package/src/core/admin-contenteditable.js +5 -2
- package/src/core/admin-inputs.js +1 -0
- package/src/core/admin-onclick.js +2 -1
- package/src/core/boot-phase.js +2 -1
- package/src/core/conflict-download.js +3 -29
- package/src/core/conflict-notice-ui.js +4 -7
- package/src/core/download.js +32 -0
- package/src/core/host-meta.js +56 -0
- package/src/core/is-edit-mode.js +17 -1
- package/src/core/mode-key.js +125 -19
- package/src/core/mode-support.js +5 -0
- package/src/core/mode-transition.js +20 -6
- package/src/core/preserve-authored.js +4 -4
- package/src/core/recovery.js +121 -33
- package/src/core/save-accepted.js +33 -0
- package/src/core/save-core.js +111 -32
- package/src/core/save-job.js +108 -0
- package/src/core/save-prepare.js +53 -0
- package/src/core/save-size-notice.js +3 -1
- package/src/core/save-slices.js +15 -0
- package/src/core/save.js +557 -127
- package/src/core/serialize-steps.js +109 -0
- package/src/core/snapshot.js +209 -40
- package/src/core/source-map.js +83 -38
- package/src/core/stale-host-notice.js +1 -0
- package/src/core/upgrade-run.js +59 -8
- package/src/dom/active-element.js +5 -0
- package/src/lib/assets.js +1 -0
- package/src/lib/autosave-scheduler.js +3 -4
- package/src/lib/slice.js +65 -0
- package/src/lib/uimotion.js +774 -0
- package/src/lib/unsaved-state.js +2 -2
- package/src/live/fast-lane.js +10 -3
- package/src/live/local-lane.js +202 -11
- package/src/live/ops.js +16 -0
- package/src/live/page-compaction.js +162 -0
- package/src/live/server-lane.js +1 -0
- package/src/live/store.js +11 -3
- package/src/loader-logic.js +3 -1
- package/src/loader.js +14 -4
- package/src/page/adopt-boot.js +2 -1
- package/src/page/awareness.js +33 -2
- package/src/page/blocks.js +122 -27
- package/src/page/board-views.js +2 -16
- package/src/page/boot-pack.js +1 -1
- package/src/page/chart-tables.js +29 -0
- package/src/page/chart.js +411 -72
- package/src/page/checklist.js +10 -135
- package/src/page/checkpoint-proof.js +295 -0
- package/src/page/chrome.js +257 -40
- package/src/page/clipboard-shape.js +126 -0
- package/src/page/document-title-dom.js +12 -0
- package/src/page/document-title-runtime.js +15 -1
- package/src/page/document-title.js +4 -1
- package/src/page/drag.js +33 -3
- package/src/page/editor.js +207 -31
- package/src/page/emoji-data.js +162 -0
- package/src/page/export-assets.js +357 -0
- package/src/page/export-html.js +209 -0
- package/src/page/export-markdown.js +361 -0
- package/src/page/features/board.js +12 -3
- package/src/page/features/chart.js +11 -0
- package/src/page/features/checklist.js +428 -0
- package/src/page/features/dates.js +180 -0
- package/src/page/features/emoji.js +187 -0
- package/src/page/features/export-html.js +189 -0
- package/src/page/features/export-markdown.js +169 -0
- package/src/page/features/import-markdown.js +349 -0
- package/src/page/features/media.js +61 -5
- package/src/page/features/mentions.js +188 -0
- package/src/page/features/registry.js +24 -1
- package/src/page/features/table.js +375 -85
- package/src/page/features/title-cover.js +331 -0
- package/src/page/features/title-icon.js +213 -0
- package/src/page/features/toc.js +87 -0
- package/src/page/format-command.js +33 -0
- package/src/page/format-gate.js +68 -18
- package/src/page/format-host.js +71 -0
- package/src/page/format-native-commands.js +158 -0
- package/src/page/format-native.js +42 -0
- package/src/page/image-actions.js +64 -0
- package/src/page/import-markdown.js +262 -0
- package/src/page/inline.js +32 -8
- package/src/page/items.js +141 -11
- package/src/page/markdown.js +30 -2
- package/src/page/media.js +35 -119
- package/src/page/mention-picker.js +99 -0
- package/src/page/merge.js +1 -1
- package/src/page/placeholder.js +14 -1
- package/src/page/projection.js +156 -25
- package/src/page/recover.js +10 -12
- package/src/page/recovery-ui.js +464 -0
- package/src/page/rich/chart.js +63 -7
- package/src/page/save-projection.js +67 -0
- package/src/page/save-states.js +14 -4
- package/src/page/schema.js +52 -3
- package/src/page/table-paste.js +3 -1
- package/src/page/table-style.js +4 -4
- package/src/page/table.js +44 -2
- package/src/page/themes/content.css +25 -1
- package/src/page/themes/light-1.css +33 -8
- package/src/page/themes/light-1.source.css +8 -7
- package/src/page/toc-link.js +31 -0
- package/src/page/toc.js +5 -0
- package/src/page/view-hooks.js +58 -0
- package/src/page/view.js +13 -0
- package/src/page/ybind/binding.js +116 -6
- package/src/page/ybind/bridge.js +187 -20
- package/src/page/ybind/containment.js +162 -0
- package/src/page/ybind/doc.js +17 -9
- package/src/page/ybind/file-debt.js +63 -0
- package/src/page/ybind/layout.js +7 -2
- package/src/page/ybind/marks.js +2 -1
- package/src/page/ybind/moves.js +5 -4
- package/src/page/ybind/origins.js +106 -4
- package/src/page/ybind/parts.js +8 -1
- package/src/page/ybind/provider.js +125 -18
- package/src/page/ybind/recovery-proof.js +32 -9
- package/src/page/ybind/recovery-replay.js +905 -4
- package/src/page/ybind/recovery.js +174 -45
- package/src/page/ybind/table.js +67 -11
- package/src/page/ybind/walk-delta.js +258 -0
- package/src/page/ybind/walk.js +54 -29
- package/src/page/ybind/writer.js +1 -1
- package/src/plugins/ai-edit.js +13 -9
- package/src/plugins/indicator-chip.js +91 -0
- package/src/plugins/indicator.js +13 -80
- package/src/plugins/live.js +126 -15
- package/src/plugins/menu/actions.js +196 -0
- package/src/plugins/menu/people.js +279 -0
- package/src/plugins/menu/placement.js +121 -0
- package/src/plugins/menu/preferences.js +9 -0
- package/src/plugins/menu/seo.js +316 -397
- package/src/plugins/menu/style.js +171 -0
- package/src/plugins/menu/variants.js +296 -0
- package/src/plugins/menu.js +186 -183
- package/src/plugins/page-view.js +3 -2
- package/src/plugins/page.js +362 -43
- package/src/plugins/sortable.js +8 -1
- package/src/plugins/source.js +6 -0
- package/src/plugins/upgrade.js +51 -17
- package/src/plugins/upload-ui.js +12 -17
- package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
- package/src/plugins/visitor-drafts/intro-style.js +7 -2
- package/src/plugins/visitor-drafts/intro.js +7 -4
- package/src/plugins/visitor-drafts/owner-chip.js +8 -12
- package/src/plugins/visitor-drafts/prompt.js +11 -9
- package/src/plugins/visitor-drafts/source.js +4 -3
- package/src/plugins/visitor-drafts/store.js +138 -9
- package/src/plugins/visitor-drafts/ui-memory.js +2 -2
- package/src/plugins/visitor-drafts-owner.js +4 -6
- package/src/plugins/visitor-drafts.js +5 -3
- package/src/recovery/store.js +1 -1
- package/src/route/route.js +4 -0
- package/src/server/checkpoint/index.js +2 -2
- package/src/server/rooms/index.js +5 -1
- package/src/server/rooms/live-data-rooms.js +302 -50
- package/src/server/rooms/page-compaction-worker.js +12 -0
- package/src/server/rooms/page-compactor.js +92 -0
- package/src/server/rooms/page-room-conformance.js +461 -0
- package/src/source/parse-steps.js +94 -0
- package/src/source/session.js +233 -57
- package/src/sync/editing-sender.js +12 -0
- package/src/sync/live-sync-edit.js +22 -2
- package/src/sync/live-sync.js +150 -23
- package/src/sync/page-relay.js +52 -0
- package/src/sync/presence-store.js +44 -4
- package/src/sync/presence.js +6 -3
- package/src/sync/saved-stream.js +28 -14
- package/src/ui/bevel-controls.js +1 -0
- package/src/ui/bevel-dialog.js +5 -2
- package/src/ui/editor-ui.js +77 -0
- package/src/ui/page-colour.js +281 -0
- package/src/ui/toast.js +16 -7
- package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +9 -9
- package/src/vendor/parse5.vendor.js +2 -2
- package/src/vendor/sap-core.vendor.js +5 -5
- package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
- package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
- package/dist/m/c-2M73252O.js +0 -1
- package/dist/m/c-2SPVQSHC.js +0 -1
- package/dist/m/c-35NBGMZB.js +0 -1
- package/dist/m/c-3IQRECYS.js +0 -1
- package/dist/m/c-4B6H4G5V.js +0 -1
- package/dist/m/c-4FFHZM32.js +0 -1
- package/dist/m/c-4YSCRICO.js +0 -1
- package/dist/m/c-54RCDCBU.js +0 -1
- package/dist/m/c-5M4YEKCX.js +0 -4
- package/dist/m/c-5V3MOVJT.js +0 -1
- package/dist/m/c-66O32MV3.js +0 -28
- package/dist/m/c-6HMB24EV.js +0 -1
- package/dist/m/c-7O6AFR65.js +0 -1
- package/dist/m/c-ANQTJ3B5.js +0 -2
- package/dist/m/c-AYVOREBE.js +0 -1
- package/dist/m/c-B2ZXPOS3.js +0 -3
- package/dist/m/c-BMIZKMPV.js +0 -1
- package/dist/m/c-BTDI4Y23.js +0 -1
- package/dist/m/c-CJZSLMVQ.js +0 -1
- package/dist/m/c-DBE6ORSI.js +0 -1
- package/dist/m/c-EDHUR4MX.js +0 -1
- package/dist/m/c-EY6UOM54.js +0 -1
- package/dist/m/c-F3CKJTJS.js +0 -1
- package/dist/m/c-F7Y6FLPY.js +0 -1
- package/dist/m/c-FA4DZVTC.js +0 -1
- package/dist/m/c-G2BUZITR.js +0 -1
- package/dist/m/c-GB3JTV6E.js +0 -1
- package/dist/m/c-GBGWXTX4.js +0 -1
- package/dist/m/c-GHBCCVG4.js +0 -1
- package/dist/m/c-GJAEGGXY.js +0 -1
- package/dist/m/c-GOWBDM6J.js +0 -1
- package/dist/m/c-IB52R237.js +0 -1
- package/dist/m/c-IKWNE4R4.js +0 -1
- package/dist/m/c-IU3IYEU6.js +0 -1
- package/dist/m/c-IZ2EBRAJ.js +0 -1
- package/dist/m/c-KR4UW4KW.js +0 -1
- package/dist/m/c-KY3VJGUA.js +0 -8
- package/dist/m/c-LFPHCXY3.js +0 -1
- package/dist/m/c-LRPUO2MX.js +0 -1
- package/dist/m/c-LXENEBV7.js +0 -1
- package/dist/m/c-NMJRXKPC.js +0 -8
- package/dist/m/c-OIBUTDXI.js +0 -1
- package/dist/m/c-PPOSCAQD.js +0 -1
- package/dist/m/c-QIX5BJYN.js +0 -1
- package/dist/m/c-QQHEX6HA.js +0 -1
- package/dist/m/c-R7RMANJI.js +0 -9
- package/dist/m/c-RI4NT75Y.js +0 -1
- package/dist/m/c-RM7J7BV2.js +0 -1
- package/dist/m/c-RUJKSUQW.js +0 -1
- package/dist/m/c-SGCCC4FW.js +0 -25
- package/dist/m/c-T4VO4OCB.js +0 -1
- package/dist/m/c-TQEMVJCS.js +0 -1
- package/dist/m/c-TR6RMDQ7.js +0 -3
- package/dist/m/c-UKHTLWUW.js +0 -1
- package/dist/m/c-UNN7LWCT.js +0 -1
- package/dist/m/c-UXBMRDZB.js +0 -235
- package/dist/m/c-V7PZZRG7.js +0 -1
- package/dist/m/c-VL52ADDR.js +0 -1
- package/dist/m/c-WNYBLIOZ.js +0 -1
- package/dist/m/c-WWDBYXLK.js +0 -1
- package/dist/m/c-XNNW2CWK.js +0 -1
- package/dist/m/c-Y6USGTS5.js +0 -5
- package/dist/m/c-YG2NJYS6.js +0 -135
- package/dist/m/c-Z4VRC7GU.js +0 -7
- package/dist/m/c-Z6K52OLP.js +0 -19
- package/dist/m/c-ZDY2MDAA.js +0 -21
- package/dist/m/c-ZUCOKOHT.js +0 -1
- package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
- package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
- package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
- package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
- package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
- package/dist/m/core/recovery-TBBSICE2.js +0 -1
- package/dist/m/core/save-WUCHTQBE.js +0 -1
- package/dist/m/core/save-core-FJVHT5M3.js +0 -1
- package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
- package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
- package/dist/m/events/index-23DPTUT4.js +0 -1
- package/dist/m/internals/index-WOVAP3RU.js +0 -1
- package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
- package/dist/m/loader-HXX6YNH5.js +0 -1
- package/dist/m/manifest-F22DEDAC.js +0 -1
- package/dist/m/manifest-edit-C3C7376B.js +0 -1
- package/dist/m/options/options-2LH76JQ3.js +0 -1
- package/dist/m/page/editor-XUTB2HB2.js +0 -189
- package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
- package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
- package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
- package/dist/m/plugins/live-NUTTXSBS.js +0 -1
- package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
- package/dist/m/plugins/page-KSUH7G6J.js +0 -2
- package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
- package/dist/m/plugins/people-AJJ32YCG.js +0 -1
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
- package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
- package/dist/m/plugins/source-KHK5UUSK.js +0 -1
- package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
- package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
- package/dist/m/plugins/upload-XSGNILUY.js +0 -1
- package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
- package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
- package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
- package/dist/m/reader-core-ILWLXVMP.js +0 -68
- package/dist/m/route/route-KOCZSEGN.js +0 -1
- package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
- package/dist/m/ui/index-EVHKMKBL.js +0 -1
- package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
- /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
- /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
- /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
|
+
}
|