@panphora/clayjs 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-F449FF98.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
package/src/plugins/live.js
CHANGED
|
@@ -9,7 +9,7 @@ import { contains, parseBy, frameSet, descendants, unionRanges, formatBy } from
|
|
|
9
9
|
import { createStore } from "../live/store.js";
|
|
10
10
|
import { serializeLive, stampOf, parseStamp, textHash } from "../live/ops.js";
|
|
11
11
|
import { capturePageState, onSnapshot } from "../core/snapshot.js";
|
|
12
|
-
import { onSaveAccepted, onSaveAttempt, onSaveOutcome, isSaveInProgress } from "../core/save-core.js";
|
|
12
|
+
import { onSaveAccepted, onSaveAttempt, onSaveOutcome, isSaveInProgress, saveFateIsUnknown, exitSave } from "../core/save-core.js";
|
|
13
13
|
import { savePageForce, baselineSettled, isSaveConflicted, autosaveIsSuspended } from "../core/save.js";
|
|
14
14
|
import { withKeepalive } from "../core/save-core.js";
|
|
15
15
|
import { isEditMode } from "../core/is-edit-mode.js";
|
|
@@ -17,13 +17,13 @@ import { registerUnsavedState } from "../lib/unsaved-state.js";
|
|
|
17
17
|
import { createCheckpoints } from "../live/checkpoint.js";
|
|
18
18
|
import { parseJsonRelaxed } from "../vendor/hyper-morph-json.vendor.js";
|
|
19
19
|
import * as hostMeta from "../core/host-meta.js";
|
|
20
|
-
import { createFastLane } from "../live/fast-lane.js";
|
|
21
20
|
import { createSaveHistoryRecovery, RECOVERY_WINDOW_MS } from "../live/save-history.js";
|
|
22
21
|
import { serverTransport } from "../live/server-lane.js";
|
|
23
|
-
import { localTransport, localLaneSupported } from "../live/local-lane.js";
|
|
24
22
|
import { captureYValue } from "../page/ybind/parts.js";
|
|
25
23
|
import { lastSeenEtag, recordEtag } from "../core/etag.js";
|
|
26
24
|
|
|
25
|
+
const lanes = () => Promise.all([import("../live/fast-lane.js"), import("../live/local-lane.js")]).then(([f, l]) => ({ createFastLane: f.createFastLane, localTransport: l.localTransport, localLaneSupported: l.localLaneSupported }));
|
|
26
|
+
|
|
27
27
|
const NAME = /^[A-Za-z0-9_.:-]{1,64}$/;
|
|
28
28
|
const entries = new Map();
|
|
29
29
|
|
|
@@ -755,6 +755,9 @@ function seedLocalRoom() {
|
|
|
755
755
|
if (value !== null && typeof value === "object") values.set(name, { value, ephemeral: tag.ephemeral });
|
|
756
756
|
} catch {}
|
|
757
757
|
}
|
|
758
|
+
for (const [name, entry] of entries) {
|
|
759
|
+
if (!entry.staged) values.set(name, { value: entry.store.authoritative, ephemeral: entry.store.ephemeral });
|
|
760
|
+
}
|
|
758
761
|
return values;
|
|
759
762
|
}
|
|
760
763
|
|
|
@@ -769,13 +772,22 @@ function ensureLane() {
|
|
|
769
772
|
if (request !== laneRequest) return;
|
|
770
773
|
serverLane = supported;
|
|
771
774
|
provenanceHost = provenance;
|
|
775
|
+
const lane = await lanes().catch(() => null);
|
|
776
|
+
if (request !== laneRequest) return;
|
|
777
|
+
if (!lane) {
|
|
778
|
+
settleLane?.(null);
|
|
779
|
+
settleWriter?.(null);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
772
782
|
if (supported) {
|
|
773
|
-
fast = createFastLane(laneOptions(serverTransport()));
|
|
774
|
-
} else if (localLaneSupported()) {
|
|
783
|
+
fast = lane.createFastLane(laneOptions(serverTransport()));
|
|
784
|
+
} else if (lane.localLaneSupported()) {
|
|
775
785
|
// The document's own floor picks the channel scope: a floor-1 document
|
|
776
|
-
// never shares the lane a tab that cannot read it is on.
|
|
777
|
-
|
|
778
|
-
|
|
786
|
+
// never shares the lane a tab that cannot read it is on. The room scope
|
|
787
|
+
// puts this tab's sequencer in the host-mode room, which a tab of the lane
|
|
788
|
+
// before it never joins.
|
|
789
|
+
local = lane.localTransport({ seed: seedLocalRoom, rooms: true, floor: document.querySelector('meta[name="clay-page-format"]')?.getAttribute("content") });
|
|
790
|
+
fast = lane.createFastLane(laneOptions(local));
|
|
779
791
|
} else {
|
|
780
792
|
settleLane?.(null);
|
|
781
793
|
settleWriter?.(null);
|
|
@@ -878,7 +890,9 @@ function receive(entry, text, source, dropIds) {
|
|
|
878
890
|
} catch {
|
|
879
891
|
return;
|
|
880
892
|
}
|
|
881
|
-
if (entry.store.rebase(value, { source, ...(dropIds?.length && { dropIds }) }))
|
|
893
|
+
if (!entry.store.rebase(value, { source, ...(dropIds?.length && { dropIds }) })) return false;
|
|
894
|
+
entry.framesSince = true;
|
|
895
|
+
return true;
|
|
882
896
|
}
|
|
883
897
|
|
|
884
898
|
// One open store and what the plugin follows about it. A staged store has no lane.
|
|
@@ -934,11 +948,12 @@ export function live(name) {
|
|
|
934
948
|
|
|
935
949
|
// A page whose file carries no state of its own yet: the seed in memory, no tag in
|
|
936
950
|
// the document, no request, until its first genuine op is written by the ordinary
|
|
937
|
-
// save.
|
|
938
|
-
export function stageStore(name, { value, represented = null, signal = null } = {}) {
|
|
951
|
+
// save. A value already stored by this tab or a peer can open its room.
|
|
952
|
+
export function stageStore(name, { value, represented = null, signal = null, persisted = false } = {}) {
|
|
939
953
|
const existing = entries.get(name);
|
|
940
954
|
if (existing && !existing.staged) throw new Error(`clay.live("${name}"): the store is open in a room`);
|
|
941
955
|
const entry = existing ?? openEntry(name, createStore({ name, value, ephemeral: false }), { staged: { represented, signal } });
|
|
956
|
+
if (persisted) activateStaged(entry);
|
|
942
957
|
return {
|
|
943
958
|
handle: entry.store.handle,
|
|
944
959
|
isPending: () => !!entry.staged && entry.store.pending.length > 0,
|
|
@@ -1359,9 +1374,7 @@ export function receiveFrame(html, seq = null) {
|
|
|
1359
1374
|
else if (pageTag.getAttribute("live-by") !== by) pageTag.setAttribute("live-by", by);
|
|
1360
1375
|
}
|
|
1361
1376
|
if (entry && !entry.store.ephemeral) {
|
|
1362
|
-
|
|
1363
|
-
if (entry.staged) continue;
|
|
1364
|
-
if (entry.store.handle.status.mode === "file") receive(entry, text, "remote", dropIds);
|
|
1377
|
+
if (entry.store.handle.status.mode === "file" && receive(entry, text, "remote", dropIds)) activateStaged(entry);
|
|
1365
1378
|
continue;
|
|
1366
1379
|
}
|
|
1367
1380
|
if (pageTag && pageTag.textContent !== text) pageTag.textContent = text;
|
|
@@ -1414,12 +1427,74 @@ export function fastLane() {
|
|
|
1414
1427
|
return fast;
|
|
1415
1428
|
}
|
|
1416
1429
|
|
|
1430
|
+
// The local lane's transport, for the format gate: a floor-1 document shares a
|
|
1431
|
+
// channel scope no tab below that floor is on, and moving this tab there is the
|
|
1432
|
+
// gate's call (page/format-gate.js). Null until this tab has such a lane, and a
|
|
1433
|
+
// gate asks again on its next retry.
|
|
1434
|
+
// Whether any live store of this tab holds ops the room has not confirmed. A tab with
|
|
1435
|
+
// none has nothing a scope switch could strand, so it may move without a receipt.
|
|
1436
|
+
export function lanePending() {
|
|
1437
|
+
return sessionStores().some((entry) => entry.store.pending.length > 0);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
export function localLane() {
|
|
1441
|
+
return local;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1417
1444
|
// The page editor's pause save: checkpoint within ms of now. Only the room's
|
|
1418
1445
|
// writer saves, so in other tabs this changes nothing.
|
|
1419
1446
|
export function checkpointWithin(ms) {
|
|
1420
1447
|
checkpoints.within(ms);
|
|
1421
1448
|
}
|
|
1422
1449
|
|
|
1450
|
+
// The exit's own checkpoint: this tab's page input, saved once, under the permit the
|
|
1451
|
+
// exit minted and inside its deadline. It is not the background checkpoint: no timer is
|
|
1452
|
+
// armed and writer election is untouched, because a leave is an explicit save of this
|
|
1453
|
+
// tab's work, like the force save behind Cmd+S.
|
|
1454
|
+
//
|
|
1455
|
+
// One attempt per exit. A caller whose page input is still unconfirmed gets the answer
|
|
1456
|
+
// that names it, never a second write: an expired exit must not send anything later.
|
|
1457
|
+
export async function requestExitCheckpoint({ name, needsSave, permit, deadline, signal = null, keepalive = false }) {
|
|
1458
|
+
const over = () => signal?.aborted || Date.now() >= deadline;
|
|
1459
|
+
// Abortable, and it clears its own timer, so nothing is left waiting after the exit.
|
|
1460
|
+
const pause = () => new Promise((resolve) => {
|
|
1461
|
+
const done = () => {
|
|
1462
|
+
clearTimeout(timer);
|
|
1463
|
+
signal?.removeEventListener("abort", done);
|
|
1464
|
+
resolve();
|
|
1465
|
+
};
|
|
1466
|
+
const timer = setTimeout(done, 5);
|
|
1467
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
1468
|
+
});
|
|
1469
|
+
const failed = (code) => ({ ok: false, code });
|
|
1470
|
+
for (;;) {
|
|
1471
|
+
if (over()) return failed("timeout");
|
|
1472
|
+
if (!needsSave()) return { ok: true };
|
|
1473
|
+
if (isSaveConflicted()) return failed("conflict");
|
|
1474
|
+
if (saveFateIsUnknown() || historyPending()) return failed("save-unknown");
|
|
1475
|
+
const entry = entries.get(name);
|
|
1476
|
+
if (!entry) return failed("disposed");
|
|
1477
|
+
if (!baselineSettled() || isSaveInProgress()) {
|
|
1478
|
+
await pause();
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
// A fast capture holds the room's value, so this tab's own ops have to reach the
|
|
1482
|
+
// room before the capture is taken.
|
|
1483
|
+
if (entry.store.handle.status.mode === "fast" && entry.store.pending.length) {
|
|
1484
|
+
fast?.release(name);
|
|
1485
|
+
await pause();
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
if (over()) return failed("timeout");
|
|
1489
|
+
const save = exitSave();
|
|
1490
|
+
if (!save) return failed("checkpoint-failed");
|
|
1491
|
+
const result = await withKeepalive(keepalive, () => save(permit));
|
|
1492
|
+
if (over()) return failed("timeout");
|
|
1493
|
+
if (!result?.ok) return failed(result?.code ?? "checkpoint-failed");
|
|
1494
|
+
return needsSave() ? failed("page-not-checkpointed") : { ok: true };
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1423
1498
|
// Resolves { ok: true } once the file holds every live change this tab knows
|
|
1424
1499
|
// of now, or { ok: false, code: "timeout" }. The writer saves as soon as it
|
|
1425
1500
|
// can; another tab waits for the writer's save to land.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// The menu's editing items: Undo and Redo, Ask AI, and Download a copy. Each one calls
|
|
2
|
+
// the service that already does the job and keeps no second copy of its state.
|
|
3
|
+
import { modeRefusalText } from "../../core/mode-refusal.js";
|
|
4
|
+
import { downloadHtml, documentName } from "../../core/download.js";
|
|
5
|
+
import { keys, revealLabel } from "./preferences.js";
|
|
6
|
+
|
|
7
|
+
const RESUME_KEY = "menu-resume";
|
|
8
|
+
const READY_MS = 5000;
|
|
9
|
+
|
|
10
|
+
const clay = () => window.clay || {};
|
|
11
|
+
const session = () => clay().storage && clay().storage.session;
|
|
12
|
+
const pageWants = (name) => {
|
|
13
|
+
const names = clay().editPlugins;
|
|
14
|
+
return Array.isArray(names) && names.includes(name);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// An edit-only plugin attaches after the mode change commits, so the item waits for it
|
|
18
|
+
// briefly instead of reporting it missing.
|
|
19
|
+
function whenPresent(get, ms = READY_MS) {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const start = Date.now();
|
|
22
|
+
const tick = () => {
|
|
23
|
+
const value = get();
|
|
24
|
+
if (value || Date.now() - start >= ms) resolve(value || null);
|
|
25
|
+
else setTimeout(tick, 50);
|
|
26
|
+
};
|
|
27
|
+
tick();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function attachActions({ addItem, note, currentMode }) {
|
|
32
|
+
const resumers = new Map();
|
|
33
|
+
|
|
34
|
+
// Runs `run` in edit mode, entering it first when the page is in view. A reload entry
|
|
35
|
+
// leaves a one-shot receipt in session storage so the action runs once the page is
|
|
36
|
+
// back; a refused entry says why and runs nothing.
|
|
37
|
+
async function inEditMode(action, run) {
|
|
38
|
+
if (currentMode() === "edit") return run();
|
|
39
|
+
const storage = session();
|
|
40
|
+
if (storage) storage.set(RESUME_KEY, action);
|
|
41
|
+
let result;
|
|
42
|
+
try {
|
|
43
|
+
result = await clay().setMode?.("edit");
|
|
44
|
+
} catch (err) {
|
|
45
|
+
result = { ok: false, message: err && err.message };
|
|
46
|
+
}
|
|
47
|
+
if (result && result.ok !== false && result.reloading) return;
|
|
48
|
+
if (storage) storage.remove(RESUME_KEY);
|
|
49
|
+
if (!result || result.ok === false) {
|
|
50
|
+
const text = result ? modeRefusalText(result) : "Couldn't switch modes.";
|
|
51
|
+
if (text) note(text);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
return run();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resumable(action, run) {
|
|
58
|
+
resumers.set(action, run);
|
|
59
|
+
return () => inEditMode(action, run);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let history = { canUndo: false, canRedo: false };
|
|
63
|
+
let heard = false;
|
|
64
|
+
|
|
65
|
+
function renderUndo() {
|
|
66
|
+
if (!pageWants("undo")) return;
|
|
67
|
+
const live = currentMode() === "edit" && clay().undo;
|
|
68
|
+
// The menu loads late, so edits made before it are read from the service itself.
|
|
69
|
+
if (!heard && live && typeof live.canUndo === "boolean") history = { canUndo: live.canUndo, canRedo: !!live.canRedo };
|
|
70
|
+
addItem({
|
|
71
|
+
id: "clay.undo",
|
|
72
|
+
pair: [
|
|
73
|
+
{
|
|
74
|
+
id: "clay.undo.undo",
|
|
75
|
+
label: "Undo",
|
|
76
|
+
keys: keys("⌘Z", "Ctrl+Z"),
|
|
77
|
+
disabled: !(live && history.canUndo),
|
|
78
|
+
run: () => clay().undo?.undo(),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "clay.undo.redo",
|
|
82
|
+
label: "Redo",
|
|
83
|
+
keys: keys("⌘⇧Z", "Ctrl+Shift+Z"),
|
|
84
|
+
disabled: !(live && history.canRedo),
|
|
85
|
+
run: () => clay().undo?.redo(),
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
document.addEventListener("clay:undo-change", (event) => {
|
|
92
|
+
const detail = event.detail || {};
|
|
93
|
+
heard = true;
|
|
94
|
+
history = { canUndo: !!detail.canUndo, canRedo: !!detail.canRedo };
|
|
95
|
+
renderUndo();
|
|
96
|
+
});
|
|
97
|
+
document.addEventListener("clay:modechange", renderUndo);
|
|
98
|
+
renderUndo();
|
|
99
|
+
|
|
100
|
+
if (pageWants("ai-edit")) {
|
|
101
|
+
addItem({
|
|
102
|
+
id: "clay.ai",
|
|
103
|
+
label: "Ask AI…",
|
|
104
|
+
keys: keys("⌘J", "Ctrl+J"),
|
|
105
|
+
run: resumable("ai", async () => {
|
|
106
|
+
const ai = await whenPresent(() => clay().aiEdit);
|
|
107
|
+
const opened = ai ? await ai.open(document.body) : false;
|
|
108
|
+
if (!opened) note("Ask AI isn't available on this page right now.");
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (pageWants("cms") && clay().cms?.hasRules?.(document)) {
|
|
114
|
+
addItem({
|
|
115
|
+
id: "clay.cms",
|
|
116
|
+
label: "Open the CMS (Edit content)",
|
|
117
|
+
run: resumable("cms", async () => {
|
|
118
|
+
const cms = await whenPresent(() => clay().cms);
|
|
119
|
+
let result = null;
|
|
120
|
+
try {
|
|
121
|
+
result = cms ? await cms.open() : { ok: false };
|
|
122
|
+
} catch {
|
|
123
|
+
result = { ok: false };
|
|
124
|
+
}
|
|
125
|
+
if (result && result.ok === false) note("Couldn't open the CMS. Try again.");
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// The copy is what a save would send now, unsaved edits included. A reader has no
|
|
131
|
+
// unsaved edits, so the saved file is the same document.
|
|
132
|
+
addItem({
|
|
133
|
+
id: "clay.download",
|
|
134
|
+
label: "Download a copy",
|
|
135
|
+
run: async () => {
|
|
136
|
+
let html = null;
|
|
137
|
+
try {
|
|
138
|
+
if (typeof clay().getHTML === "function") {
|
|
139
|
+
html = clay().getHTML();
|
|
140
|
+
} else {
|
|
141
|
+
const response = await fetch(location.href, { cache: "no-store", credentials: "same-origin" });
|
|
142
|
+
if (response.ok) html = await response.text();
|
|
143
|
+
}
|
|
144
|
+
} catch {}
|
|
145
|
+
// Upload paths only resolve on this host, so the copy names them in full.
|
|
146
|
+
if (typeof html === "string" && /^https?:$/.test(location.protocol)) {
|
|
147
|
+
html = html.replace(/(^|[^\w./:-])\/_\/uploads\//g, `$1${location.origin}/_/uploads/`);
|
|
148
|
+
}
|
|
149
|
+
if (typeof html !== "string" || !downloadHtml({ html, name: `${documentName()}.html` })) {
|
|
150
|
+
note("Couldn't build the download. Try again.");
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// What the host offers for this document. A page opens in a new tab so this one keeps
|
|
156
|
+
// whatever is unsaved; anything else the host does itself when asked, and may answer
|
|
157
|
+
// with a page to open (a duplicate) or a reason it could not.
|
|
158
|
+
async function askHost(url) {
|
|
159
|
+
let body = null;
|
|
160
|
+
try {
|
|
161
|
+
const response = await fetch(url, { method: "POST", credentials: "same-origin", headers: { "Document-URL": location.href } });
|
|
162
|
+
body = await response.json().catch(() => null);
|
|
163
|
+
if (response.ok && body?.ok !== false) {
|
|
164
|
+
if (typeof body?.href === "string") window.open(body.href, "_blank", "noopener");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
} catch {}
|
|
168
|
+
note(typeof body?.message === "string" && body.message ? body.message : "Couldn't do that. Try again.");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const hostItem = (id, label, action) => {
|
|
172
|
+
if (action?.href) addItem({ id, label, href: action.href });
|
|
173
|
+
else if (action?.post) addItem({ id, label, run: () => askHost(action.post) });
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
import("../../core/host-meta.js")
|
|
177
|
+
.then(({ hostActions }) => hostActions())
|
|
178
|
+
.then((host) => {
|
|
179
|
+
hostItem("clay.code", "Open the code editor", host.codeEditor);
|
|
180
|
+
hostItem("clay.duplicate", "Duplicate as a new page", host.duplicate);
|
|
181
|
+
hostItem("clay.export", "Export as zip", host.export);
|
|
182
|
+
hostItem("clay.reveal", revealLabel(), host.reveal);
|
|
183
|
+
hostItem("clay.versions", "Version history…", host.versions);
|
|
184
|
+
})
|
|
185
|
+
.catch(() => {});
|
|
186
|
+
|
|
187
|
+
const storage = session();
|
|
188
|
+
const pending = storage && storage.get(RESUME_KEY);
|
|
189
|
+
if (pending) {
|
|
190
|
+
storage.remove(RESUME_KEY);
|
|
191
|
+
const run = resumers.get(pending);
|
|
192
|
+
if (run && currentMode() === "edit") run();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { inEditMode, resumable, refresh: renderUndo };
|
|
196
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// Who's here: a menu item and, on a page that opts in with `<html clay-menu-roster>`, a
|
|
2
|
+
// roster beside the capsule. Either opens the people panel, which lists the people on
|
|
3
|
+
// this page and lets you change the name your edits are signed with when this browser
|
|
4
|
+
// chose it. The roster comes from the presence store; nothing here is ever saved.
|
|
5
|
+
import { initialsOf, personColor } from "../../lib/person-face.js";
|
|
6
|
+
|
|
7
|
+
const CROSS =
|
|
8
|
+
'<svg viewBox="0 0 256 256" aria-hidden="true" focusable="false"><path d="M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128 50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z"/></svg>';
|
|
9
|
+
const ROSTER_FACES = 3;
|
|
10
|
+
|
|
11
|
+
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
12
|
+
|
|
13
|
+
function face(person) {
|
|
14
|
+
const el = document.createElement("span");
|
|
15
|
+
el.className = "clay-avatar";
|
|
16
|
+
el.setAttribute("aria-hidden", "true");
|
|
17
|
+
const color = personColor(person.id);
|
|
18
|
+
el.style.setProperty("color", color);
|
|
19
|
+
el.style.setProperty("border-color", color);
|
|
20
|
+
el.style.setProperty("background", "var(--bevel-surface)");
|
|
21
|
+
el.textContent = initialsOf(person.name);
|
|
22
|
+
return el;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function attachPeople({ root, capsule, addItem, removeItem, closeMenu, note }) {
|
|
26
|
+
let snapshot = { supported: false, people: [], anonymous: 0 };
|
|
27
|
+
let panel = null;
|
|
28
|
+
let opener = null;
|
|
29
|
+
let roster = null;
|
|
30
|
+
let renaming = false;
|
|
31
|
+
|
|
32
|
+
const people = () => window.clay && window.clay.people;
|
|
33
|
+
const me = () => (window.clay && window.clay.me) || null;
|
|
34
|
+
const canRename = () => {
|
|
35
|
+
try {
|
|
36
|
+
return !!people()?.canRename?.();
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const total = () => snapshot.people.length + snapshot.anonymous;
|
|
42
|
+
const wantsRoster = () => document.documentElement.hasAttribute("clay-menu-roster");
|
|
43
|
+
|
|
44
|
+
function closePanel({ restore = true } = {}) {
|
|
45
|
+
if (!panel) return;
|
|
46
|
+
panel.remove();
|
|
47
|
+
panel = null;
|
|
48
|
+
renaming = false;
|
|
49
|
+
roster?.setAttribute("aria-expanded", "false");
|
|
50
|
+
if (restore && opener && opener.isConnected) opener.focus();
|
|
51
|
+
opener = null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function meSection() {
|
|
55
|
+
const mine = me();
|
|
56
|
+
if (!mine || !mine.name) return null;
|
|
57
|
+
const section = document.createElement("div");
|
|
58
|
+
section.className = "clay-people-me";
|
|
59
|
+
const label = document.createElement("span");
|
|
60
|
+
label.className = "clay-people-me-label";
|
|
61
|
+
label.textContent = "Your edits are signed as";
|
|
62
|
+
const row = document.createElement("div");
|
|
63
|
+
row.className = "clay-people-me-row";
|
|
64
|
+
section.append(label, row);
|
|
65
|
+
|
|
66
|
+
if (renaming) {
|
|
67
|
+
const input = document.createElement("input");
|
|
68
|
+
input.className = "clay-people-input";
|
|
69
|
+
input.type = "text";
|
|
70
|
+
input.value = mine.name;
|
|
71
|
+
input.setAttribute("aria-label", "Your name");
|
|
72
|
+
input.maxLength = 120;
|
|
73
|
+
const save = document.createElement("button");
|
|
74
|
+
save.type = "button";
|
|
75
|
+
save.className = "clay-menu-btn clay-menu-btn-small clay-menu-btn-primary";
|
|
76
|
+
save.textContent = "Save";
|
|
77
|
+
const commit = () => {
|
|
78
|
+
try {
|
|
79
|
+
people().rename(input.value);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
note(err && err.code === "not-renamable" ? "This name comes from your account, so it can't be changed here." : "Use a name of 1 to 120 characters that isn't an email address.");
|
|
82
|
+
input.focus();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
renaming = false;
|
|
86
|
+
draw();
|
|
87
|
+
panel?.querySelector(".clay-people-me .clay-menu-btn")?.focus();
|
|
88
|
+
};
|
|
89
|
+
save.addEventListener("click", commit);
|
|
90
|
+
input.addEventListener("keydown", (event) => {
|
|
91
|
+
if (event.key === "Enter") {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
commit();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
row.append(input, save);
|
|
97
|
+
queueMicrotask(() => {
|
|
98
|
+
if (!input.isConnected) return;
|
|
99
|
+
input.focus();
|
|
100
|
+
input.select();
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
const name = document.createElement("span");
|
|
104
|
+
name.className = "clay-people-me-name";
|
|
105
|
+
name.textContent = mine.name;
|
|
106
|
+
row.append(face(mine), name);
|
|
107
|
+
if (canRename()) {
|
|
108
|
+
const change = document.createElement("button");
|
|
109
|
+
change.type = "button";
|
|
110
|
+
change.className = "clay-menu-btn clay-menu-btn-small";
|
|
111
|
+
change.textContent = "Change";
|
|
112
|
+
change.addEventListener("click", () => {
|
|
113
|
+
renaming = true;
|
|
114
|
+
draw();
|
|
115
|
+
});
|
|
116
|
+
row.append(change);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return section;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function draw() {
|
|
123
|
+
if (!panel) return;
|
|
124
|
+
const count = total();
|
|
125
|
+
panel.textContent = "";
|
|
126
|
+
|
|
127
|
+
const head = document.createElement("div");
|
|
128
|
+
head.className = "clay-people-head";
|
|
129
|
+
const title = document.createElement("h3");
|
|
130
|
+
title.className = "clay-people-title";
|
|
131
|
+
title.textContent = "Who's here";
|
|
132
|
+
const sum = document.createElement("span");
|
|
133
|
+
sum.className = "clay-people-sum";
|
|
134
|
+
sum.textContent = plural(count, "person", "people");
|
|
135
|
+
const x = document.createElement("button");
|
|
136
|
+
x.type = "button";
|
|
137
|
+
x.className = "clay-people-close";
|
|
138
|
+
x.setAttribute("aria-label", "Close");
|
|
139
|
+
x.innerHTML = CROSS;
|
|
140
|
+
x.addEventListener("click", () => closePanel());
|
|
141
|
+
head.append(title, sum, x);
|
|
142
|
+
panel.append(head);
|
|
143
|
+
|
|
144
|
+
if (snapshot.people.length) {
|
|
145
|
+
const list = document.createElement("ul");
|
|
146
|
+
list.className = "clay-people-list";
|
|
147
|
+
for (const person of snapshot.people) {
|
|
148
|
+
const row = document.createElement("li");
|
|
149
|
+
row.className = "clay-people-row";
|
|
150
|
+
const who = document.createElement("span");
|
|
151
|
+
who.className = "clay-people-who";
|
|
152
|
+
const name = document.createElement("strong");
|
|
153
|
+
name.textContent = person.name;
|
|
154
|
+
if (person.you) {
|
|
155
|
+
const you = document.createElement("span");
|
|
156
|
+
you.textContent = "(you)";
|
|
157
|
+
name.append(you);
|
|
158
|
+
}
|
|
159
|
+
who.append(name);
|
|
160
|
+
row.append(face(person), who);
|
|
161
|
+
list.append(row);
|
|
162
|
+
}
|
|
163
|
+
panel.append(list);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (snapshot.anonymous > 0) {
|
|
167
|
+
const anon = document.createElement("p");
|
|
168
|
+
anon.className = "clay-people-anon";
|
|
169
|
+
anon.textContent =
|
|
170
|
+
snapshot.anonymous === 1
|
|
171
|
+
? "1 other person is here without a shared name."
|
|
172
|
+
: `${snapshot.anonymous} others are here without a shared name.`;
|
|
173
|
+
panel.append(anon);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const mine = meSection();
|
|
177
|
+
if (mine) panel.append(mine);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function openPanel(from) {
|
|
181
|
+
closeMenu();
|
|
182
|
+
if (panel) {
|
|
183
|
+
closePanel();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
opener = from;
|
|
187
|
+
panel = document.createElement("div");
|
|
188
|
+
panel.className = "clay-people";
|
|
189
|
+
panel.setAttribute("role", "dialog");
|
|
190
|
+
panel.setAttribute("aria-label", "Who's here");
|
|
191
|
+
panel.addEventListener("keydown", (event) => {
|
|
192
|
+
if (event.key !== "Escape") return;
|
|
193
|
+
event.preventDefault();
|
|
194
|
+
event.stopPropagation();
|
|
195
|
+
if (renaming) {
|
|
196
|
+
renaming = false;
|
|
197
|
+
draw();
|
|
198
|
+
panel.querySelector(".clay-people-me .clay-menu-btn")?.focus();
|
|
199
|
+
} else {
|
|
200
|
+
closePanel();
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
draw();
|
|
204
|
+
root.append(panel);
|
|
205
|
+
roster?.setAttribute("aria-expanded", from === roster ? "true" : "false");
|
|
206
|
+
panel.querySelector(".clay-people-close").focus();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function drawRoster() {
|
|
210
|
+
const show = wantsRoster() && snapshot.supported && total() > 1;
|
|
211
|
+
if (!show) {
|
|
212
|
+
roster?.remove();
|
|
213
|
+
roster = null;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!roster) {
|
|
217
|
+
roster = document.createElement("button");
|
|
218
|
+
roster.type = "button";
|
|
219
|
+
roster.className = "clay-roster";
|
|
220
|
+
roster.setAttribute("aria-haspopup", "dialog");
|
|
221
|
+
roster.setAttribute("aria-expanded", panel ? "true" : "false");
|
|
222
|
+
roster.addEventListener("click", () => openPanel(roster));
|
|
223
|
+
root.insertBefore(roster, capsule);
|
|
224
|
+
}
|
|
225
|
+
const shown = snapshot.people.slice(0, ROSTER_FACES);
|
|
226
|
+
const rest = total() - shown.length;
|
|
227
|
+
const faces = document.createElement("span");
|
|
228
|
+
faces.className = "clay-roster-faces";
|
|
229
|
+
for (const person of shown) faces.append(face(person));
|
|
230
|
+
roster.textContent = "";
|
|
231
|
+
roster.append(faces);
|
|
232
|
+
if (rest > 0) roster.append(plural(rest, "other", "others"));
|
|
233
|
+
const names = shown.map((person) => person.name);
|
|
234
|
+
roster.setAttribute("aria-label", `Who's here: ${names.join(", ")}${rest > 0 ? `${names.length ? " and " : ""}${plural(rest, "other", "others")}` : ""}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let itemLabel = null;
|
|
238
|
+
|
|
239
|
+
function update() {
|
|
240
|
+
const count = total();
|
|
241
|
+
if (snapshot.supported && (count > 1 || canRename())) {
|
|
242
|
+
const label = count > 1 ? `Who's here (${plural(count, "person", "people")})` : "Who's here";
|
|
243
|
+
// Re-adding redraws the open menu, so only a changed label does it.
|
|
244
|
+
if (label !== itemLabel) {
|
|
245
|
+
itemLabel = label;
|
|
246
|
+
addItem({ id: "clay.people", label, run: () => openPanel(capsule.querySelector(".clay-menu-goo")) });
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
if (itemLabel !== null) removeItem("clay.people");
|
|
250
|
+
itemLabel = null;
|
|
251
|
+
closePanel({ restore: false });
|
|
252
|
+
}
|
|
253
|
+
drawRoster();
|
|
254
|
+
if (panel && !renaming) {
|
|
255
|
+
const active = root.getRootNode().activeElement;
|
|
256
|
+
const focused = active && panel.contains(active) ? active.classList[0] : null;
|
|
257
|
+
draw();
|
|
258
|
+
if (focused) panel.querySelector(`.${focused}`)?.focus();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
document.addEventListener("click", (event) => {
|
|
263
|
+
if (panel && !event.composedPath().includes(root)) closePanel({ restore: false });
|
|
264
|
+
});
|
|
265
|
+
document.addEventListener("clay:people", () => {
|
|
266
|
+
if (!renaming) update();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
import("../../sync/presence-store.js")
|
|
270
|
+
.then(({ presence }) => {
|
|
271
|
+
presence.subscribe((next) => {
|
|
272
|
+
snapshot = next || snapshot;
|
|
273
|
+
update();
|
|
274
|
+
});
|
|
275
|
+
})
|
|
276
|
+
.catch(() => {});
|
|
277
|
+
|
|
278
|
+
return { close: closePanel };
|
|
279
|
+
}
|