@panphora/clayjs 1.17.0 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +72474 -62090
- package/dist/m/attrs/{save-freeze-MYNVXREN.js → save-freeze-LWGF6XUN.js} +1 -1
- package/dist/m/c-2GRUM77Q.js +30 -0
- package/dist/m/c-2KIEGNC4.js +1 -0
- package/dist/m/{c-AMSNPOCX.js → c-2KL3BD52.js} +1 -1
- 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-KAU6E43D.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-3XIRD4NU.js → c-5T6NZHNM.js} +1 -1
- package/dist/m/{c-74MGYWZF.js → c-5ZQV7KC4.js} +1 -1
- package/dist/m/{c-7OLLJPWP.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-Y5HBBIQ4.js → c-AMYEFA2E.js} +1 -1
- package/dist/m/c-AQYCG7QW.js +1 -0
- package/dist/m/c-ATKAOH23.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-VHM5XQHK.js → c-BIJALSRD.js} +1 -1
- package/dist/m/c-BJ7YQ5QZ.js +1 -0
- package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
- package/dist/m/{c-VE6ZMYMC.js → c-BUAYFEV5.js} +1 -1
- package/dist/m/c-C6IPRG34.js +1 -0
- package/dist/m/{c-KU3ZFADW.js → c-CAS3NND4.js} +1 -1
- package/dist/m/{c-PS5ARVWR.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-QJ4OM3G6.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-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-TJ6WOJL3.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-WDC5GVKU.js → c-IVTRVENZ.js} +1 -1
- package/dist/m/c-JKZJIUIP.js +1 -0
- package/dist/m/{c-UDUUJPFZ.js → c-JNLX3RJB.js} +1 -1
- package/dist/m/c-K3ADYNLU.js +1 -0
- package/dist/m/c-KGHVFRR3.js +1 -0
- package/dist/m/{c-5QWQ7A5L.js → c-KJJPV77R.js} +1 -1
- package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
- package/dist/m/{c-EZU56NQV.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-3RUU4TPX.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-5RJFTLSF.js → c-NEL7HQW3.js} +1 -1
- package/dist/m/{c-DCXOXBF3.js → c-NQJRVJ56.js} +1 -1
- 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-R3L7ZJGB.js → c-OPLUZR3A.js} +1 -1
- package/dist/m/{c-GSFIGKOP.js → c-OTMKN2OO.js} +1 -1
- package/dist/m/{c-6NC5I2TV.js → c-OYRZ7XG2.js} +1 -1
- package/dist/m/{c-EVCQ3BKA.js → c-P3OFYUMA.js} +1 -1
- 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-FRLHSY7E.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-JU3NK25H.js → c-R27DWQKX.js} +1 -1
- package/dist/m/{c-RSMOODDU.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-Q622NS2Y.js → c-S2U6NJU4.js} +1 -1
- package/dist/m/c-SPZPGD3R.js +1 -0
- package/dist/m/c-SW4YEG2D.js +8 -0
- package/dist/m/c-T637T7SN.js +1 -0
- package/dist/m/c-TJAXGIRV.js +1 -0
- package/dist/m/{c-RYUXUUBX.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-QICP2YR5.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-VILENZTD.js +1 -0
- package/dist/m/{c-HDDEEL77.js → c-VJYEVRYK.js} +1 -1
- package/dist/m/{c-SMBZG5X4.js → c-VR2BJ4AN.js} +1 -1
- 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-I2ZATQCD.js → c-X5TSXZYJ.js} +2 -2
- 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/core/{admin-attrs-QHOKPJ7W.js → admin-attrs-YY7T5FGU.js} +1 -1
- 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-FNXVEFNE.js → host-permission-6EVJGNPL.js} +1 -1
- package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
- package/dist/m/core/{persist-JFYRHMRJ.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/internals/{index-PVXGPUDA.js → index-TGLQINN3.js} +1 -1
- package/dist/m/loader-3BBNZZGM.js +1 -0
- package/dist/m/manifest-26C55578.js +1 -0
- package/dist/m/manifest-edit-4396A009.js +1 -0
- package/dist/m/manifest.json +2111 -1573
- package/dist/m/page/{editor-NNJA4OKD.js → editor-6LRB3GE4.js} +123 -21
- 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-RM5IR6AO.js → demo-OYO5GCAO.js} +1 -1
- 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-SGPJKANK.js → people-JCHVPF7P.js} +1 -1
- package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
- package/dist/m/plugins/{richclay-NRQDSWX6.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-WLYKK6TL.js → upload-NHGJXZK2.js} +1 -1
- 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-4N6GYAF6.js → wire-VBWXEXU2.js} +1 -1
- 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 +1367 -930
- package/dist/page/themes/light-1.css +8 -7
- package/dist/server/core.cjs +1440 -288
- 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/conflict-notice-ui.js +4 -7
- package/src/core/download.js +2 -2
- package/src/core/host-meta.js +38 -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 +106 -31
- 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 +170 -39
- 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/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 +89 -27
- package/src/page/boot-pack.js +1 -1
- package/src/page/chart-tables.js +21 -3
- package/src/page/chart.js +406 -63
- package/src/page/checklist.js +10 -135
- package/src/page/checkpoint-proof.js +7 -1
- package/src/page/chrome.js +250 -51
- package/src/page/clipboard-shape.js +126 -0
- package/src/page/drag.js +33 -3
- package/src/page/editor.js +136 -25
- 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/checklist.js +428 -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/registry.js +18 -0
- package/src/page/features/table.js +373 -83
- 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 +25 -1
- package/src/page/format-gate.js +68 -18
- package/src/page/format-host.js +17 -0
- package/src/page/format-native-commands.js +21 -5
- package/src/page/image-actions.js +64 -0
- package/src/page/import-markdown.js +262 -0
- package/src/page/items.js +135 -17
- package/src/page/markdown.js +6 -2
- package/src/page/media.js +35 -119
- package/src/page/placeholder.js +14 -1
- package/src/page/projection.js +98 -10
- 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 +28 -0
- package/src/page/save-states.js +13 -3
- package/src/page/schema.js +19 -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/light-1.css +8 -7
- 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 +38 -12
- package/src/page/view.js +13 -0
- package/src/page/ybind/binding.js +88 -5
- 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 +1 -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 +113 -17
- package/src/page/ybind/recovery-proof.js +32 -9
- package/src/page/ybind/recovery-replay.js +558 -15
- 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 +89 -14
- 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 +249 -44
- 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 +5 -3
- 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/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 +11 -6
- 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/c-2SPVQSHC.js +0 -1
- package/dist/m/c-336SGU5W.js +0 -1
- package/dist/m/c-3H6BZDNJ.js +0 -6
- package/dist/m/c-4DFEJ2PO.js +0 -3
- package/dist/m/c-56QRZTZH.js +0 -1
- package/dist/m/c-5J4E7O4Y.js +0 -4
- package/dist/m/c-5LKDWQTL.js +0 -19
- package/dist/m/c-5V3MOVJT.js +0 -1
- package/dist/m/c-6AZOGAPE.js +0 -1
- package/dist/m/c-6CRUUXOX.js +0 -5
- package/dist/m/c-6HMB24EV.js +0 -1
- package/dist/m/c-6KEVEQ7L.js +0 -1
- package/dist/m/c-A3E3NIMC.js +0 -1
- package/dist/m/c-BQ6LOBEE.js +0 -1
- package/dist/m/c-BTDI4Y23.js +0 -1
- package/dist/m/c-CJ3A7ZKE.js +0 -1
- package/dist/m/c-CQHPGKX2.js +0 -1
- package/dist/m/c-DFS4XZJV.js +0 -2
- package/dist/m/c-DNENWY4O.js +0 -1
- package/dist/m/c-E4LIR5X5.js +0 -1
- package/dist/m/c-FDNHHZY5.js +0 -1
- package/dist/m/c-FROJUZMV.js +0 -1
- package/dist/m/c-G2JPKCTP.js +0 -1
- package/dist/m/c-GB6TU7WV.js +0 -1
- package/dist/m/c-GFJJEBTU.js +0 -1
- package/dist/m/c-GJAEGGXY.js +0 -1
- package/dist/m/c-GOWBDM6J.js +0 -1
- package/dist/m/c-HFKUVACH.js +0 -1
- package/dist/m/c-HHDFCNHV.js +0 -1
- package/dist/m/c-IEM4NTCS.js +0 -1
- package/dist/m/c-JDCX26UP.js +0 -25
- package/dist/m/c-KGVGROZT.js +0 -1
- package/dist/m/c-KQ6TYJAP.js +0 -135
- package/dist/m/c-LFPHCXY3.js +0 -1
- package/dist/m/c-LRPUO2MX.js +0 -1
- package/dist/m/c-LWLBHLVC.js +0 -3
- package/dist/m/c-M2L47S2X.js +0 -7
- package/dist/m/c-MFP2ZWD4.js +0 -1
- package/dist/m/c-MKSQ4Y57.js +0 -1
- package/dist/m/c-NUFFJVVJ.js +0 -1
- package/dist/m/c-NUHSPT5P.js +0 -1
- package/dist/m/c-O72HVYSA.js +0 -1
- package/dist/m/c-OIBUTDXI.js +0 -1
- package/dist/m/c-OW5QIGG3.js +0 -1
- package/dist/m/c-PGRCK73F.js +0 -1
- package/dist/m/c-Q7NQD65V.js +0 -1
- package/dist/m/c-QH63LQOS.js +0 -1
- package/dist/m/c-R7IWVINQ.js +0 -1
- package/dist/m/c-RM7J7BV2.js +0 -1
- package/dist/m/c-S4ZEH46B.js +0 -9
- package/dist/m/c-SDAXDM2D.js +0 -1
- package/dist/m/c-SMGU7RT3.js +0 -235
- package/dist/m/c-SPIORRYS.js +0 -1
- package/dist/m/c-SWLRT66R.js +0 -1
- package/dist/m/c-TCJTVAFN.js +0 -1
- package/dist/m/c-TQEMVJCS.js +0 -1
- package/dist/m/c-TYLXX4NF.js +0 -21
- package/dist/m/c-U62OB4BT.js +0 -1
- package/dist/m/c-U6YWUAR3.js +0 -8
- package/dist/m/c-UDCSU5G5.js +0 -2
- package/dist/m/c-VI5RMSVY.js +0 -1
- package/dist/m/c-VRZN56XY.js +0 -1
- package/dist/m/c-W5MCHEGA.js +0 -1
- package/dist/m/c-WNYBLIOZ.js +0 -1
- package/dist/m/c-WWXIDH4O.js +0 -28
- package/dist/m/c-XCNTK76U.js +0 -1
- package/dist/m/c-XMS6DPBR.js +0 -1
- package/dist/m/c-ZUCOKOHT.js +0 -1
- package/dist/m/c-ZVPVXTJF.js +0 -4
- package/dist/m/c-ZVRKXKM7.js +0 -1
- package/dist/m/core/autosave-HRYGUWV6.js +0 -1
- package/dist/m/core/conflict-notice-6MROJN2L.js +0 -1
- package/dist/m/core/is-edit-mode-KTWA3O5N.js +0 -1
- package/dist/m/core/recovery-FLTCC2OC.js +0 -1
- package/dist/m/core/save-HQZ4S667.js +0 -1
- package/dist/m/core/save-core-KGQWFUNH.js +0 -1
- package/dist/m/core/snapshot-EGQZNT45.js +0 -1
- package/dist/m/core/unsaved-warning-IDJLD66H.js +0 -1
- package/dist/m/loader-S4I7GKXR.js +0 -1
- package/dist/m/manifest-4C419D67.js +0 -1
- package/dist/m/manifest-edit-B077F288.js +0 -1
- package/dist/m/plugins/ai-edit-QN4PMMNL.js +0 -6
- package/dist/m/plugins/indicator-URDBBOQO.js +0 -1
- package/dist/m/plugins/live-HFWD4OIH.js +0 -1
- package/dist/m/plugins/menu-3HIEQSI6.js +0 -138
- package/dist/m/plugins/page-JOKB6XPR.js +0 -2
- package/dist/m/plugins/page-view-NXOJHNW6.js +0 -1
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
- package/dist/m/plugins/sortable-GWT4PWDV.js +0 -1
- package/dist/m/plugins/source-4ZRSAIYM.js +0 -1
- package/dist/m/plugins/undo-VDA5P7FH.js +0 -1
- package/dist/m/plugins/upgrade-W7FG5VFG.js +0 -1
- package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +0 -21
- package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +0 -1
- package/dist/m/reader-core-DXYCURFF.js +0 -68
- package/dist/m/route/route-KOCZSEGN.js +0 -1
- package/dist/m/sync/live-sync-J7VMYTKS.js +0 -1
- package/dist/m/ui/index-3JLVI27O.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-NRQDSWX6.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,189 @@
|
|
|
1
|
+
// The self-contained reading copy, as a command. One snapshot of the live model is
|
|
2
|
+
// taken and rendered through the projection, then each image and attachment the
|
|
3
|
+
// snapshot names is read once and written inside the file. Nothing downloads until
|
|
4
|
+
// the person has seen the captured title, the time it was taken and every resource
|
|
5
|
+
// the copy could not carry: a file that is missing something is never offered, and
|
|
6
|
+
// one that is offered has no address in it that would be fetched again.
|
|
7
|
+
import { project } from "../projection.js";
|
|
8
|
+
import { blockMap } from "../model.js";
|
|
9
|
+
import { PageExportError, renderExportHtml } from "../export-html.js";
|
|
10
|
+
import { inlineExportResources } from "../export-assets.js";
|
|
11
|
+
import { downloadHtml, documentName } from "../../core/download.js";
|
|
12
|
+
import { mountEditorUI } from "../../ui/editor-ui.js";
|
|
13
|
+
|
|
14
|
+
const MAX_NAME = 80;
|
|
15
|
+
const UNSAFE_NAME = /[\u0000-\u001f\u007f<>:"/\\|?*]+/g;
|
|
16
|
+
|
|
17
|
+
// The captured title as a file name: one line, no path of its own, and the page's own
|
|
18
|
+
// name when a title has nothing a file name can keep.
|
|
19
|
+
function fileName(title) {
|
|
20
|
+
const base = String(title ?? "").replace(UNSAFE_NAME, " ").replace(/\s+/g, " ").replace(/^\.+/, "").trim().slice(0, MAX_NAME).trim();
|
|
21
|
+
if (base) return `${base}.html`;
|
|
22
|
+
try { return `${documentName()}.html`; } catch { return "page.html"; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// The panel sits in its own shadow root beside the editor, so the page's CSS cannot restyle
|
|
26
|
+
// it. The page's --page-* tokens still reach it, and it keeps the editor's font and colour.
|
|
27
|
+
const PANEL_CSS = `
|
|
28
|
+
:host{font:inherit !important;color:inherit !important}
|
|
29
|
+
.page-export-panel{max-width:46ch;margin:8px 0;padding:10px 12px;border:1px solid var(--page-pop-line);border-radius:8px;background:var(--page-pop-bg);color:var(--page-pop-ink);box-shadow:var(--page-pop-shadow);font-size:.88em}
|
|
30
|
+
.page-export-title{margin:0 0 2px;font-weight:600}
|
|
31
|
+
.page-export-when{margin:0 0 6px;color:var(--page-ink-soft)}
|
|
32
|
+
.page-export-losses{margin:6px 0;padding-left:1.1em}
|
|
33
|
+
.page-export-losses li{margin:2px 0}
|
|
34
|
+
.page-export-acts{display:flex;gap:6px;margin-top:8px}
|
|
35
|
+
.page-export-btn{border:0;padding:4px 8px;border-radius:6px;background:none;color:inherit;font:inherit;cursor:pointer}
|
|
36
|
+
.page-export-btn:hover{background:var(--page-pop-hover)}
|
|
37
|
+
.page-export-panel[data-state="loss"] .page-export-title{color:var(--page-danger, #9E3B2F)}
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
export function createFeature({ view = null, download = downloadHtml, fetch: fetchImpl = null, now = () => new Date() } = {}) {
|
|
41
|
+
if (!view) return { run: () => ({ ok: false, code: "not-editing" }), dispose() {} };
|
|
42
|
+
const doc = view.dom.ownerDocument;
|
|
43
|
+
let session = null;
|
|
44
|
+
let disposed = false;
|
|
45
|
+
|
|
46
|
+
const listen = (s, target, type, fn) => {
|
|
47
|
+
target.addEventListener(type, fn);
|
|
48
|
+
s.listen.push([target, type, fn]);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const button = (act, label) => {
|
|
52
|
+
const b = doc.createElement("button");
|
|
53
|
+
b.type = "button";
|
|
54
|
+
b.className = "page-export-btn";
|
|
55
|
+
b.dataset.act = act;
|
|
56
|
+
b.textContent = label;
|
|
57
|
+
return b;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function close() {
|
|
61
|
+
const s = session;
|
|
62
|
+
if (!s) return;
|
|
63
|
+
session = null;
|
|
64
|
+
for (const [target, type, fn] of s.listen) target.removeEventListener(type, fn);
|
|
65
|
+
s.abort?.abort();
|
|
66
|
+
s.ui?.remove();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// One panel for the whole session: what was captured, what could not be carried, and
|
|
70
|
+
// the one action that does anything — a download, and only when nothing is missing.
|
|
71
|
+
function draw(s) {
|
|
72
|
+
const box = s.panel ?? (s.panel = doc.createElement("aside"));
|
|
73
|
+
box.className = "page-export-panel";
|
|
74
|
+
box.dataset.state = s.ready ? "ready" : "loss";
|
|
75
|
+
box.setAttribute("contenteditable", "false");
|
|
76
|
+
box.setAttribute("role", "dialog");
|
|
77
|
+
box.setAttribute("aria-modal", "false");
|
|
78
|
+
box.setAttribute("aria-labelledby", "page-export-title");
|
|
79
|
+
box.dataset.at = s.at.toISOString();
|
|
80
|
+
box.setAttribute("tabindex", "-1");
|
|
81
|
+
box.textContent = "";
|
|
82
|
+
const title = doc.createElement("p");
|
|
83
|
+
title.className = "page-export-title";
|
|
84
|
+
title.id = "page-export-title";
|
|
85
|
+
const heading = s.ready ? "Reading copy" : "This page cannot be a reading copy yet";
|
|
86
|
+
title.textContent = s.title ? `${heading}: ${s.title}` : heading;
|
|
87
|
+
const when = doc.createElement("p");
|
|
88
|
+
when.className = "page-export-when";
|
|
89
|
+
const said = `${s.at.toLocaleString()} · ${s.embedded} ${s.embedded === 1 ? "resource" : "resources"} inside the file`;
|
|
90
|
+
when.textContent = said;
|
|
91
|
+
box.append(title, when);
|
|
92
|
+
if (s.losses.length) {
|
|
93
|
+
const list = doc.createElement("ul");
|
|
94
|
+
list.className = "page-export-losses";
|
|
95
|
+
for (const entry of s.losses) {
|
|
96
|
+
const item = doc.createElement("li");
|
|
97
|
+
item.textContent = entry.message;
|
|
98
|
+
list.append(item);
|
|
99
|
+
}
|
|
100
|
+
box.append(list);
|
|
101
|
+
}
|
|
102
|
+
const acts = doc.createElement("div");
|
|
103
|
+
acts.className = "page-export-acts";
|
|
104
|
+
if (s.ready) acts.append(button("download", "Download"), button("close", "Close"));
|
|
105
|
+
else acts.append(button("retry", "Try again"), button("close", "Close"));
|
|
106
|
+
box.append(acts);
|
|
107
|
+
const first = acts.firstChild;
|
|
108
|
+
if (!s.ui) s.ui = mountEditorUI({ name: "page-export", content: box, css: [PANEL_CSS], parent: view.dom.parentElement ?? doc.body });
|
|
109
|
+
// The panel is what a person lands on: the download when there is one to take,
|
|
110
|
+
// and the list of what stopped it when there is not.
|
|
111
|
+
(s.ready ? first : box).focus?.();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function decide(s) {
|
|
115
|
+
listen(s, s.panel, "click", (event) => {
|
|
116
|
+
const b = event.target.closest?.("[data-act]");
|
|
117
|
+
if (!b) return;
|
|
118
|
+
event.preventDefault();
|
|
119
|
+
if (b.dataset.act === "close") close();
|
|
120
|
+
else if (b.dataset.act === "download") save(s);
|
|
121
|
+
else if (b.dataset.act === "retry") run().catch((error) => console.error("clayjs: the reading copy could not be made", error));
|
|
122
|
+
});
|
|
123
|
+
listen(s, doc, "keydown", (event) => {
|
|
124
|
+
if (event.key !== "Escape" || session !== s) return;
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
event.stopPropagation();
|
|
127
|
+
close();
|
|
128
|
+
}, true);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// The one place a reading copy becomes a file. The download is handed the bytes that
|
|
132
|
+
// were shown, never a second render, so what a person saw is what they get.
|
|
133
|
+
function save(s) {
|
|
134
|
+
if (!s.ready || disposed) return { ok: false, code: "not-editing" };
|
|
135
|
+
const saved = download({ html: s.ready.html, name: s.ready.name });
|
|
136
|
+
close();
|
|
137
|
+
return { ok: saved === true, downloaded: saved === true };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function run() {
|
|
141
|
+
if (disposed || view.isDestroyed) return { ok: false, code: "not-editing" };
|
|
142
|
+
close();
|
|
143
|
+
const s = { at: now(), title: "", panel: null, listen: [], abort: new AbortController(), losses: [], embedded: 0, ready: null };
|
|
144
|
+
// The model is read once, before anything is awaited: a peer's edit while the
|
|
145
|
+
// resources arrive cannot change the file being made.
|
|
146
|
+
const blocks = blockMap(view.state.doc);
|
|
147
|
+
const model = project(blocks);
|
|
148
|
+
const base = doc.baseURI;
|
|
149
|
+
let rendered;
|
|
150
|
+
try {
|
|
151
|
+
rendered = renderExportHtml(model, {
|
|
152
|
+
fallbackOf: (id) => (typeof blocks[id]?.artifact?.fallbackHtml === "string" ? blocks[id].artifact.fallbackHtml : ""),
|
|
153
|
+
doc,
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (!(error instanceof PageExportError)) throw error;
|
|
157
|
+
s.title = null;
|
|
158
|
+
s.losses = [{ code: error.code, message: error.message }];
|
|
159
|
+
session = s;
|
|
160
|
+
draw(s);
|
|
161
|
+
decide(s);
|
|
162
|
+
return { ok: false, code: error.code, message: error.message };
|
|
163
|
+
}
|
|
164
|
+
s.title = rendered.title || "Untitled";
|
|
165
|
+
session = s;
|
|
166
|
+
const transport = fetchImpl ?? globalThis.fetch;
|
|
167
|
+
const result = await inlineExportResources(rendered.html, { resources: rendered.resources, base, fetch: transport, signal: s.abort.signal });
|
|
168
|
+
if (disposed || view.isDestroyed || session !== s) return { ok: false, code: "not-editing" };
|
|
169
|
+
s.embedded = result.embedded;
|
|
170
|
+
s.losses = result.losses.slice();
|
|
171
|
+
for (const entry of result.remote ?? []) {
|
|
172
|
+
s.losses.push({ code: "remote-reference", message: `The ${entry.tag} in the reading copy still points at ${String(entry.value).slice(0, 120)}` });
|
|
173
|
+
}
|
|
174
|
+
if (!s.losses.length) s.ready = { html: result.html, name: fileName(s.title) };
|
|
175
|
+
draw(s);
|
|
176
|
+
decide(s);
|
|
177
|
+
return s.ready
|
|
178
|
+
? { ok: true, title: s.title, name: s.ready.name, embedded: s.embedded }
|
|
179
|
+
: { ok: false, code: "resource-losses", losses: s.losses };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
run,
|
|
184
|
+
dispose() {
|
|
185
|
+
disposed = true;
|
|
186
|
+
close();
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|