@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
package/src/plugins/upgrade.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// a visitor, a draft, no network, a failed fetch, a bad digest or an equal version.
|
|
10
10
|
// The 24-hour cache is hyper-html-api's own; the dotted-version comparison is
|
|
11
11
|
// core/version.js, so the rules engine that upgrades a document stays the click's.
|
|
12
|
-
import { isNewerVersion } from "../core/version.js";
|
|
12
|
+
import { isNewerVersion, parseVersion } from "../core/version.js";
|
|
13
13
|
|
|
14
14
|
const CACHE_PREFIX = "hha:upgrade-check:";
|
|
15
15
|
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
@@ -77,12 +77,9 @@ async function latestManifest(sourceHref, currentVersion, { fetchFn, now }) {
|
|
|
77
77
|
return { latest, latestUrl };
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
fetchFn = (url, init) => fetch(url, init),
|
|
84
|
-
now = Date.now(),
|
|
85
|
-
} = {}) {
|
|
80
|
+
// The copy this page says it is: its version and the source it was copied from, or
|
|
81
|
+
// null for a page that is not a copy (or a person who cannot act on an offer).
|
|
82
|
+
function copyOf({ doc, loc }) {
|
|
86
83
|
const clay = (typeof window !== "undefined" && window.clay) || {};
|
|
87
84
|
// A visitor cannot act on the offer, and a draft is not the document the source
|
|
88
85
|
// would replace. Neither fetches.
|
|
@@ -101,32 +98,64 @@ export async function findUpdate({
|
|
|
101
98
|
if (!source.pathname.endsWith(".html")) return null;
|
|
102
99
|
// A pristine published copy points at itself: it is the source, not a copy of one.
|
|
103
100
|
if (loc && source.origin === loc.origin && source.pathname === loc.pathname) return null;
|
|
101
|
+
return { source, currentVersion };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// What the check learned. "current" is said only after the source answered and named
|
|
105
|
+
// nothing newer; anything that stopped the check short is "unknown", and so is an
|
|
106
|
+
// update the person already declined.
|
|
107
|
+
export async function checkUpdate({
|
|
108
|
+
doc = document,
|
|
109
|
+
loc = location,
|
|
110
|
+
fetchFn = (url, init) => fetch(url, init),
|
|
111
|
+
now = Date.now(),
|
|
112
|
+
} = {}) {
|
|
113
|
+
const copy = copyOf({ doc, loc });
|
|
114
|
+
if (!copy) return { status: "none", version: null, update: null };
|
|
115
|
+
const { source, currentVersion } = copy;
|
|
116
|
+
const result = (status, update = null) => ({ status, version: currentVersion, update });
|
|
104
117
|
const latestUrl = new URL(source.href);
|
|
105
118
|
latestUrl.pathname = latestUrl.pathname.replace(/\.html$/, ".json");
|
|
106
119
|
|
|
107
120
|
const { latest } = await latestManifest(source.href, currentVersion, { fetchFn, now });
|
|
108
|
-
if (!latest || typeof latest !== "object") return
|
|
109
|
-
if (
|
|
121
|
+
if (!latest || typeof latest !== "object" || typeof latest.version !== "string") return result("unknown");
|
|
122
|
+
if (!parseVersion(latest.version) || !parseVersion(currentVersion)) return result("unknown");
|
|
123
|
+
if (!isNewerVersion(latest.version, currentVersion)) return result("current");
|
|
110
124
|
|
|
125
|
+
const clay = (typeof window !== "undefined" && window.clay) || {};
|
|
111
126
|
const storage = clay.storage && clay.storage.local;
|
|
112
|
-
if (storage && storage.get(DISMISSED_KEY) === latest.version) return
|
|
127
|
+
if (storage && storage.get(DISMISSED_KEY) === latest.version) return result("unknown");
|
|
113
128
|
|
|
114
|
-
if (typeof latest.html !== "string" || !latest.html) return
|
|
129
|
+
if (typeof latest.html !== "string" || !latest.html) return result("unknown");
|
|
115
130
|
const htmlUrl = new URL(latest.html, latestUrl).href;
|
|
116
131
|
const res = await fetchOrNull(fetchFn, htmlUrl);
|
|
117
|
-
if (!res) return
|
|
132
|
+
if (!res) return result("unknown");
|
|
118
133
|
let bytes;
|
|
119
134
|
try {
|
|
120
135
|
bytes = await res.arrayBuffer();
|
|
121
136
|
} catch {
|
|
122
|
-
return
|
|
137
|
+
return result("unknown");
|
|
123
138
|
}
|
|
124
139
|
const digest = await digestOf(bytes);
|
|
125
140
|
// A manifest whose digest does not match its HTML is a host that served the wrong
|
|
126
141
|
// bytes. Nothing is offered, because nothing can be trusted to be that version.
|
|
127
|
-
if (!digest || digest !== String(latest.sha256 || "").toLowerCase()) return
|
|
142
|
+
if (!digest || digest !== String(latest.sha256 || "").toLowerCase()) return result("unknown");
|
|
128
143
|
|
|
129
|
-
return { version: latest.version, html: htmlUrl, sha256: digest, latestUrl: latestUrl.href };
|
|
144
|
+
return result("update", { version: latest.version, html: htmlUrl, sha256: digest, latestUrl: latestUrl.href });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function findUpdate(options) {
|
|
148
|
+
return (await checkUpdate(options)).update;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// The menu's footer while and after the check: the bare version until the check proves
|
|
152
|
+
// more, then the latest-version line or, when there is one, the upgrade itself.
|
|
153
|
+
async function showVersion(status, version) {
|
|
154
|
+
const clay = window.clay;
|
|
155
|
+
await whenMenuReady(clay);
|
|
156
|
+
if (!menuReady(clay) || !version) return;
|
|
157
|
+
const label = status === "current" ? `This doc is at latest version (${version})` : `Version ${version}`;
|
|
158
|
+
clay.menu.addItem({ id: "upgrade", label, text: true });
|
|
130
159
|
}
|
|
131
160
|
|
|
132
161
|
function dismiss(version) {
|
|
@@ -184,8 +213,13 @@ export async function start(options = {}) {
|
|
|
184
213
|
const clay = window.clay;
|
|
185
214
|
// Toasts and the menu are drawn by satellites that may land after boot settles.
|
|
186
215
|
await Promise.resolve(clay && clay.loaded && clay.loaded.ui).catch(() => {});
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
216
|
+
const copy = copyOf({ doc: options.doc || document, loc: options.loc || location });
|
|
217
|
+
if (copy) showVersion("checking", copy.currentVersion);
|
|
218
|
+
const { status, version, update } = await checkUpdate(options).catch(() => ({ status: "unknown", version: copy && copy.currentVersion, update: null }));
|
|
219
|
+
if (!update) {
|
|
220
|
+
if (status !== "none") await showVersion(status, version);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
189
223
|
await announce(update);
|
|
190
224
|
return update;
|
|
191
225
|
}
|
package/src/plugins/upload-ui.js
CHANGED
|
@@ -13,12 +13,15 @@
|
|
|
13
13
|
* stylesheet load the first time somebody mounts one, so a document that never
|
|
14
14
|
* uploads never downloads them.
|
|
15
15
|
*
|
|
16
|
-
* The
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* The row draws inside its own shadow root, so the page's CSS cannot restyle it. Its
|
|
17
|
+
* look is a set of `--clay-upload-*` custom properties, each with a default in its own
|
|
18
|
+
* `var()`, and those do reach in: a container that sets one — `.journal-drop {
|
|
19
|
+
* --clay-upload-ink: #333 }` — restyles the row without replacing any of its
|
|
20
|
+
* behaviour. The font family is the one thing it still inherits from the page.
|
|
19
21
|
*/
|
|
20
22
|
|
|
21
23
|
import { canRetry } from "./upload-task.js";
|
|
24
|
+
import { mountEditorUI } from "../ui/editor-ui.js";
|
|
22
25
|
|
|
23
26
|
const CSS = `.clay-upload {
|
|
24
27
|
display: flex;
|
|
@@ -110,16 +113,8 @@ const CSS = `.clay-upload {
|
|
|
110
113
|
.clay-upload__button { color: var(--clay-upload-button-ink, #8AB4FF); }
|
|
111
114
|
.clay-upload[data-clay-upload-state="failed"] .clay-upload__label { color: var(--clay-upload-failure-ink, #FFB4AB); }
|
|
112
115
|
}
|
|
113
|
-
@media print { .clay-upload { display: none; } }
|
|
114
|
-
|
|
115
|
-
function stylesOnce() {
|
|
116
|
-
if (document.getElementById("clay-upload-style")) return;
|
|
117
|
-
const style = document.createElement("style");
|
|
118
|
-
style.id = "clay-upload-style";
|
|
119
|
-
style.setAttribute("clay", "editor-ui");
|
|
120
|
-
style.textContent = CSS;
|
|
121
|
-
document.head.append(style);
|
|
122
|
-
}
|
|
116
|
+
@media print { .clay-upload { display: none; } }
|
|
117
|
+
:host { font-family: inherit !important; }`;
|
|
123
118
|
|
|
124
119
|
function percentOf({ loaded, total, percent }) {
|
|
125
120
|
if (typeof percent === "number") return percent;
|
|
@@ -143,8 +138,6 @@ export function mount(task, container) {
|
|
|
143
138
|
throw new TypeError("clay.upload.mount needs a container");
|
|
144
139
|
}
|
|
145
140
|
|
|
146
|
-
stylesOnce();
|
|
147
|
-
|
|
148
141
|
const name = task.file?.name || "File";
|
|
149
142
|
|
|
150
143
|
const root = document.createElement("div");
|
|
@@ -237,9 +230,11 @@ export function mount(task, container) {
|
|
|
237
230
|
if (!retryEl.hidden) retryEl.setAttribute("aria-label", lastError ? `Retry ${name}: ${lastError}` : `Retry ${name}`);
|
|
238
231
|
}
|
|
239
232
|
|
|
233
|
+
let ui = null;
|
|
234
|
+
|
|
240
235
|
function unmount() {
|
|
241
236
|
unsubscribe();
|
|
242
|
-
|
|
237
|
+
ui?.remove();
|
|
243
238
|
}
|
|
244
239
|
|
|
245
240
|
cancelEl.addEventListener("click", () => {
|
|
@@ -257,7 +252,7 @@ export function mount(task, container) {
|
|
|
257
252
|
});
|
|
258
253
|
|
|
259
254
|
const unsubscribe = task.subscribe(draw);
|
|
260
|
-
|
|
255
|
+
ui = mountEditorUI({ name: "upload", content: root, css: [CSS], parent: host });
|
|
261
256
|
|
|
262
257
|
return { unmount };
|
|
263
258
|
}
|
|
@@ -4,12 +4,20 @@ export function announceBottomUi() {
|
|
|
4
4
|
document.dispatchEvent(new CustomEvent(BOTTOM_UI_CHANGED));
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
// ClayJS's own interfaces sit in shadow roots, where a hit test stops at the host, and the
|
|
8
|
+
// host takes no box of its own. So a hit on one looks inside it too.
|
|
9
|
+
function hitsAt(scope, x, y) {
|
|
10
|
+
const hits = scope.elementsFromPoint ? scope.elementsFromPoint(x, y) : [];
|
|
11
|
+
return hits
|
|
12
|
+
.filter((hit) => hit.getRootNode() === scope)
|
|
13
|
+
.flatMap((hit) => (hit.localName === "clay-editor-ui" && hit.shadowRoot ? hitsAt(hit.shadowRoot, x, y) : [hit]));
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
export function bottomBarHeight(x, skip) {
|
|
8
17
|
const h = window.innerHeight;
|
|
9
18
|
let raise = 0;
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
for (let el = hit; el && el !== document.body && el !== document.documentElement; el = el.parentElement) {
|
|
19
|
+
for (const hit of hitsAt(document, x, h - 3)) {
|
|
20
|
+
for (let el = hit; el && el !== document.body && el !== document.documentElement; el = el.parentElement ?? el.getRootNode().host) {
|
|
13
21
|
if (skip(el)) break;
|
|
14
22
|
const position = getComputedStyle(el).position;
|
|
15
23
|
if (position === "fixed" || position === "sticky") {
|
|
@@ -23,7 +31,7 @@ export function bottomBarHeight(x, skip) {
|
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
export function promptPanel(box) {
|
|
26
|
-
const panel = document.querySelector("#hcp:not([hidden])");
|
|
34
|
+
const panel = document.querySelector('clay-editor-ui[data-clay-ui="download-prompt"]')?.shadowRoot.querySelector("#hcp:not([hidden])");
|
|
27
35
|
if (!panel) return 0;
|
|
28
36
|
const r = panel.getBoundingClientRect();
|
|
29
37
|
if (!r.width || !r.height || r.right <= box.left || r.left >= box.right) return 0;
|
|
@@ -4,6 +4,12 @@ body:has(> .clay-intro) { height: auto !important; overflow: visible !important;
|
|
|
4
4
|
position: relative;
|
|
5
5
|
display: block;
|
|
6
6
|
}
|
|
7
|
+
@media print { .clay-intro { display: none; } }
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
// The close button sits in its own shadow root inside the author's strip, so only it is
|
|
11
|
+
// shielded from the page's CSS. It still takes the strip's text colour.
|
|
12
|
+
export const CLOSE_CSS = `:host { color: inherit !important; }
|
|
7
13
|
.clay-intro__close {
|
|
8
14
|
position: absolute; top: 10px; right: 10px; z-index: 1;
|
|
9
15
|
display: inline-flex; align-items: center; justify-content: center;
|
|
@@ -11,9 +17,8 @@ body:has(> .clay-intro) { height: auto !important; overflow: visible !important;
|
|
|
11
17
|
border: 0; border-radius: 4px; background: none; box-shadow: none;
|
|
12
18
|
color: inherit; opacity: .55; cursor: pointer; line-height: 1;
|
|
13
19
|
}
|
|
14
|
-
.clay-
|
|
20
|
+
.clay-intro__close:hover { opacity: 1; background: none; color: inherit; }
|
|
15
21
|
.clay-intro__close:focus-visible { opacity: 1; outline: 2px solid currentColor; outline-offset: 2px; }
|
|
16
22
|
.clay-intro__close svg { display: block; width: 12px; height: 12px; fill: currentColor; stroke: none; }
|
|
17
23
|
@media (pointer: coarse) { .clay-intro__close { width: 44px; height: 44px; top: 4px; right: 4px; } }
|
|
18
|
-
@media print { .clay-intro { display: none; } }
|
|
19
24
|
`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { INTRO_CSS } from "./intro-style.js";
|
|
1
|
+
import { CLOSE_CSS, INTRO_CSS } from "./intro-style.js";
|
|
2
|
+
import { mountEditorUI } from "../../ui/editor-ui.js";
|
|
2
3
|
import { announceBottomUi } from "./bottom-ui.js";
|
|
3
4
|
import { bootPhase } from "../../core/boot-phase.js";
|
|
4
5
|
|
|
@@ -67,7 +68,8 @@ function mount({ memory, template, appName }) {
|
|
|
67
68
|
close.className = "clay-intro__close";
|
|
68
69
|
close.setAttribute("aria-label", COPY.close);
|
|
69
70
|
close.innerHTML = '<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><path d="M1.4 0 6 4.6 10.6 0 12 1.4 7.4 6 12 10.6 10.6 12 6 7.4 1.4 12 0 10.6 4.6 6 0 1.4z"/></svg>';
|
|
70
|
-
|
|
71
|
+
close.addEventListener("click", () => dismiss("closed"));
|
|
72
|
+
mountEditorUI({ name: "intro-close", content: close, css: [CLOSE_CSS], parent: section });
|
|
71
73
|
|
|
72
74
|
let gone = false;
|
|
73
75
|
let frame = 0;
|
|
@@ -101,7 +103,7 @@ function mount({ memory, template, appName }) {
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
section.addEventListener("click", (event) => {
|
|
104
|
-
if (event.target.closest("
|
|
106
|
+
if (event.target.closest("[visitor-intro-dismiss]")) {
|
|
105
107
|
dismiss("closed");
|
|
106
108
|
return;
|
|
107
109
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isDraftMode } from "../../core/is-edit-mode.js";
|
|
2
2
|
import { BOTTOM_UI_CHANGED, announceBottomUi, bottomBarHeight, promptPanel } from "./bottom-ui.js";
|
|
3
|
+
import { mountEditorUI } from "../../ui/editor-ui.js";
|
|
3
4
|
|
|
4
5
|
export const COPY = {
|
|
5
6
|
draft: "You're seeing a private draft, like a visitor.",
|
|
@@ -77,18 +78,10 @@ const CHIP_CSS = `.clay-owner {
|
|
|
77
78
|
@media print { .clay-owner { display: none; } }
|
|
78
79
|
`;
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
if (document.getElementById("clay-owner-style")) return;
|
|
82
|
-
const style = document.createElement("style");
|
|
83
|
-
style.id = "clay-owner-style";
|
|
84
|
-
style.setAttribute("clay", "editor-ui");
|
|
85
|
-
style.textContent = CHIP_CSS;
|
|
86
|
-
document.head.appendChild(style);
|
|
87
|
-
}
|
|
81
|
+
export const ownerChip = () => document.querySelector('clay-editor-ui[data-clay-ui="owner"]')?.shadowRoot.getElementById("clay-owner") ?? null;
|
|
88
82
|
|
|
89
83
|
export function mountOwnerChip() {
|
|
90
|
-
if (
|
|
91
|
-
stylesOnce();
|
|
84
|
+
if (ownerChip()) return null;
|
|
92
85
|
|
|
93
86
|
const draft = isDraftMode;
|
|
94
87
|
const chip = document.createElement("div");
|
|
@@ -118,10 +111,10 @@ export function mountOwnerChip() {
|
|
|
118
111
|
close.className = "clay-owner__x";
|
|
119
112
|
close.setAttribute("aria-label", COPY.close);
|
|
120
113
|
close.textContent = "\u00d7";
|
|
121
|
-
close.addEventListener("click", () => {
|
|
114
|
+
close.addEventListener("click", () => { ui.remove(); liftOff(); release(); });
|
|
122
115
|
|
|
123
116
|
chip.append(lead, action, close);
|
|
124
|
-
|
|
117
|
+
const ui = mountEditorUI({ name: "owner", content: chip, css: [CHIP_CSS], attributes: { "sap-ignore": "" } });
|
|
125
118
|
|
|
126
119
|
const vars = document.createElement("style");
|
|
127
120
|
vars.id = "clay-owner-vars";
|
|
@@ -172,10 +165,13 @@ export function mountOwnerChip() {
|
|
|
172
165
|
document.addEventListener("transitionend", lift, true);
|
|
173
166
|
document.addEventListener("pointerup", lift, true);
|
|
174
167
|
window.addEventListener("hashchange", lift);
|
|
168
|
+
// Transition events stop at a shadow root, so the chip's own changes are heard here.
|
|
169
|
+
chip.addEventListener("transitionend", lift);
|
|
175
170
|
lift();
|
|
176
171
|
// Whoever removes the chip takes its listeners back with it: a chip mounted and unmounted
|
|
177
172
|
// once per mode session would otherwise leave six behind each time.
|
|
178
173
|
chip.liftOff = liftOff;
|
|
174
|
+
chip.unmount = () => { liftOff(); ui.remove(); };
|
|
179
175
|
|
|
180
176
|
return chip;
|
|
181
177
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { announceBottomUi, bottomBarHeight } from "./bottom-ui.js";
|
|
2
|
-
|
|
3
|
-
const FONT_HREF = "https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500&family=IBM+Plex+Sans:wght@400;500;600&family=Newsreader:opsz,wght@6..72,500&display=swap";
|
|
4
|
-
|
|
2
|
+
import { mountEditorUI } from "../../ui/editor-ui.js";
|
|
5
3
|
const ASIDE = `<aside class="hcp" id="hcp" hidden aria-label="Download this app" data-hcp-state="tab" data-hcp-reason="open" sap-ignore clay="editor-ui">
|
|
6
4
|
<section class="hcp-card" id="hcp-card" role="dialog" aria-modal="false" aria-labelledby="hcp-title">
|
|
7
5
|
<button type="button" class="hcp-x" data-hcp="minimize" aria-label="Minimize"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" aria-hidden="true"><path d="M5 12h14"/></svg></button>
|
|
@@ -24,13 +22,15 @@ const ASIDE = `<aside class="hcp" id="hcp" hidden aria-label="Download this app"
|
|
|
24
22
|
</aside>`;
|
|
25
23
|
|
|
26
24
|
export async function mountPrompt({ memory, appName, link }) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
const { PROMPT_CSS } = await import("./prompt-style.js");
|
|
26
|
+
const existing = document.querySelector('clay-editor-ui[data-clay-ui="download-prompt"]');
|
|
27
|
+
let root = existing?.shadowRoot.getElementById("hcp");
|
|
28
|
+
if (!root) {
|
|
29
|
+
const parsed = document.createElement("template");
|
|
30
|
+
parsed.innerHTML = ASIDE;
|
|
31
|
+
root = parsed.content.firstElementChild;
|
|
32
|
+
mountEditorUI({ name: "download-prompt", content: root, css: [PROMPT_CSS], attributes: { "sap-ignore": "" } });
|
|
30
33
|
}
|
|
31
|
-
if (!document.getElementById("hcp")) document.body.insertAdjacentHTML("beforeend", ASIDE);
|
|
32
|
-
|
|
33
|
-
const root = document.getElementById("hcp");
|
|
34
34
|
const ui = memory.get();
|
|
35
35
|
if (!ui.prompt || typeof ui.prompt !== "object") ui.prompt = {};
|
|
36
36
|
const mem = ui.prompt;
|
|
@@ -158,6 +158,8 @@ export async function mountPrompt({ memory, appName, link }) {
|
|
|
158
158
|
addEventListener("scroll", lift, { passive: true });
|
|
159
159
|
addEventListener("resize", lift);
|
|
160
160
|
document.addEventListener("transitionend", lift, true);
|
|
161
|
+
// Transition events stop at a shadow root, so the prompt's own slides are heard here.
|
|
162
|
+
root.addEventListener("transitionend", lift);
|
|
161
163
|
|
|
162
164
|
set(mem.shown || mem.dismissed ? "mini" : "tab");
|
|
163
165
|
if (!mem.shown && (mem.uses || 0) >= USES_TO_SHOW) maybeShow("return");
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { draftPath } from "./store.js";
|
|
2
|
+
|
|
1
3
|
const SRC = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
2
4
|
|
|
3
5
|
export function rememberSource(memory, search = window.location.search) {
|
|
@@ -9,8 +11,7 @@ export function rememberSource(memory, search = window.location.search) {
|
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export function downloadUrl(src, loc = window.location) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return `/_/download${path}?as=htmlclay${src ? `&src=${encodeURIComponent(src)}` : ""}`;
|
|
14
|
+
const path = draftPath(loc.pathname);
|
|
15
|
+
return `/_/download${path === "/" ? "" : path}?as=htmlclay${src ? `&src=${encodeURIComponent(src)}` : ""}`;
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -4,6 +4,20 @@ import { draftSlot, setDraftStoreFailed } from "../../core/draft-mode.js";
|
|
|
4
4
|
export const KEY_PREFIX = "clay:visitor-drafts:";
|
|
5
5
|
export const RECORD_VERSION = 1;
|
|
6
6
|
|
|
7
|
+
// The document a pathname names, spelled the way the hosts resolve it: the path up to
|
|
8
|
+
// and including the first segment that ends in .html or .htmlclay (anything after it is
|
|
9
|
+
// a route inside that document), repeated slashes as one, the root index.html as "/",
|
|
10
|
+
// and "/" for a host that serves one document at every path.
|
|
11
|
+
export function draftPath(pathname = window.location.pathname) {
|
|
12
|
+
const clean = String(pathname || "/").replace(/\/{2,}/g, "/");
|
|
13
|
+
const file = clean.match(/^(.*?\/[^/]+\.(?:html|htmlclay))(?:\/|$)/i);
|
|
14
|
+
const path = file ? file[1] : "/";
|
|
15
|
+
return path.toLowerCase() === "/index.html" ? "/" : path;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const legacyKey = (slot) => KEY_PREFIX + slot;
|
|
19
|
+
export const draftKey = (slot, path = draftPath()) => `${KEY_PREFIX}${slot}@${path}`;
|
|
20
|
+
|
|
7
21
|
export function safeStorage() {
|
|
8
22
|
try { return window.localStorage; } catch { return null; }
|
|
9
23
|
}
|
|
@@ -66,26 +80,88 @@ function complete(draft, page, top = false) {
|
|
|
66
80
|
return draft;
|
|
67
81
|
}
|
|
68
82
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
|
|
83
|
+
const sameIds = (a, b) => a.length === b.length && a.every((id, i) => id === b[i]);
|
|
84
|
+
|
|
85
|
+
// Rows matched by id, when neither tab reordered the rows all three still have: a row
|
|
86
|
+
// one tab added is kept where that tab put it, a row one tab removed goes unless the
|
|
87
|
+
// other tab changed it, and a row both kept merges field by field. Anything else is
|
|
88
|
+
// null, and the caller treats the list as one value.
|
|
89
|
+
function mergeRows(base, mine, theirs) {
|
|
90
|
+
const index = (rows) => new Map(rows.map((row) => [row.id, row]));
|
|
91
|
+
const was = index(base);
|
|
92
|
+
const mineById = index(mine);
|
|
93
|
+
const theirsById = index(theirs);
|
|
94
|
+
const shared = (rows) => rows.filter((row) => was.has(row.id) && mineById.has(row.id) && theirsById.has(row.id)).map((row) => row.id);
|
|
95
|
+
const order = shared(base);
|
|
96
|
+
if (!sameIds(order, shared(mine)) || !sameIds(order, shared(theirs))) return null;
|
|
97
|
+
for (const [id, row] of was) {
|
|
98
|
+
if (mineById.has(id) && !theirsById.has(id) && !same(mineById.get(id), row)) return null;
|
|
99
|
+
if (!mineById.has(id) && theirsById.has(id) && !same(theirsById.get(id), row)) return null;
|
|
100
|
+
}
|
|
101
|
+
let conflict = false;
|
|
102
|
+
const out = [];
|
|
103
|
+
for (const row of mine) {
|
|
104
|
+
if (was.has(row.id) && !theirsById.has(row.id)) continue;
|
|
105
|
+
if (!theirsById.has(row.id)) { out.push(row); continue; }
|
|
106
|
+
const part = mergeValue(was.get(row.id), row, theirsById.get(row.id), true);
|
|
107
|
+
conflict ||= part.conflict;
|
|
108
|
+
out.push(part.value);
|
|
109
|
+
}
|
|
110
|
+
theirs.forEach((row, i) => {
|
|
111
|
+
if (was.has(row.id) || mineById.has(row.id)) return;
|
|
112
|
+
let at = 0;
|
|
113
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
114
|
+
const k = out.findIndex((kept) => kept.id === theirs[j].id);
|
|
115
|
+
if (k !== -1) { at = k + 1; break; }
|
|
116
|
+
}
|
|
117
|
+
out.splice(at, 0, row);
|
|
118
|
+
});
|
|
119
|
+
return { value: out, conflict };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function mergeValue(base, mine, theirs, rows) {
|
|
74
123
|
if (same(mine, theirs) || same(theirs, base)) return { value: mine, conflict: false };
|
|
75
124
|
if (same(mine, base)) return { value: theirs, conflict: false };
|
|
76
125
|
if (isPlain(mine) && isPlain(theirs)) {
|
|
77
126
|
const value = {};
|
|
78
127
|
let conflict = false;
|
|
79
128
|
for (const name of new Set([...Object.keys(mine), ...Object.keys(theirs)])) {
|
|
80
|
-
const part =
|
|
129
|
+
const part = mergeValue(isPlain(base) ? base[name] : undefined, mine[name], theirs[name], rows);
|
|
81
130
|
conflict ||= part.conflict;
|
|
82
131
|
if (part.value !== undefined) value[name] = part.value;
|
|
83
132
|
}
|
|
84
133
|
return { value, conflict };
|
|
85
134
|
}
|
|
135
|
+
if (rows && hasIds(base) && hasIds(mine) && hasIds(theirs)) {
|
|
136
|
+
const merged = mergeRows(base, mine, theirs);
|
|
137
|
+
if (merged) return merged;
|
|
138
|
+
}
|
|
86
139
|
return { value: mine, conflict: true };
|
|
87
140
|
}
|
|
88
141
|
|
|
142
|
+
function countIds(value, counts = new Map()) {
|
|
143
|
+
if (Array.isArray(value)) {
|
|
144
|
+
if (hasIds(value)) for (const row of value) counts.set(row.id, (counts.get(row.id) || 0) + 1);
|
|
145
|
+
for (const item of value) countIds(item, counts);
|
|
146
|
+
} else if (isPlain(value)) {
|
|
147
|
+
for (const item of Object.values(value)) countIds(item, counts);
|
|
148
|
+
}
|
|
149
|
+
return counts;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Three-way merge of this tab's value with another tab's, from the value both started
|
|
153
|
+
// at. Objects merge per field and lists of rows with ids merge per row. A list merge that
|
|
154
|
+
// would hold one id in two places (a card moved to different lists in each tab) is
|
|
155
|
+
// undone, and that list is a conflict this tab wins, as is any reorder.
|
|
156
|
+
export function merge3(base, mine, theirs) {
|
|
157
|
+
const merged = mergeValue(base, mine, theirs, true);
|
|
158
|
+
const before = countIds(mine);
|
|
159
|
+
for (const [id, count] of countIds(merged.value)) {
|
|
160
|
+
if (count > 1 && count > (before.get(id) || 0)) return mergeValue(base, mine, theirs, false);
|
|
161
|
+
}
|
|
162
|
+
return merged;
|
|
163
|
+
}
|
|
164
|
+
|
|
89
165
|
function storageFailure(doc, error) {
|
|
90
166
|
setDraftStoreFailed(true);
|
|
91
167
|
doc.dispatchEvent(new CustomEvent("clay:drafts-store-failed", { detail: { error } }));
|
|
@@ -94,9 +170,58 @@ function storageFailure(doc, error) {
|
|
|
94
170
|
return failure;
|
|
95
171
|
}
|
|
96
172
|
|
|
97
|
-
|
|
173
|
+
const savedAtOf = (raw) => {
|
|
174
|
+
try { return String(JSON.parse(raw)?.savedAt ?? ""); } catch { return ""; }
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// A draft saved before drafts were scoped to the document's path lives under the slot
|
|
178
|
+
// alone. It is copied into this path's key with its UI memory, and never written or
|
|
179
|
+
// removed: a tab still on an older ClayJS may write it again, and another document on
|
|
180
|
+
// this origin with the same slot may still need it. The marker keeps the old record's
|
|
181
|
+
// savedAt, so a later write by such a tab is caught up on the next load, and a reset
|
|
182
|
+
// never brings the old draft back.
|
|
183
|
+
export function adoptLegacy(slot, { path = draftPath(), storage = safeStorage() } = {}) {
|
|
184
|
+
const from = legacyKey(slot);
|
|
185
|
+
const to = draftKey(slot, path);
|
|
186
|
+
const marker = to + ":legacy";
|
|
187
|
+
try {
|
|
188
|
+
const generation = storage?.getItem(to);
|
|
189
|
+
const old = storage?.getItem(from);
|
|
190
|
+
if (old == null) {
|
|
191
|
+
// A visitor who only dismissed the intro has UI memory and no draft; it is copied once too.
|
|
192
|
+
const ui = storage?.getItem(from + ":ui");
|
|
193
|
+
if (ui != null && storage.getItem(to + ":ui") == null && storage.getItem(to + ":ui-legacy") == null) {
|
|
194
|
+
storage.setItem(to + ":ui", ui);
|
|
195
|
+
storage.setItem(to + ":ui-legacy", "1");
|
|
196
|
+
}
|
|
197
|
+
return "none";
|
|
198
|
+
}
|
|
199
|
+
const stamp = savedAtOf(old);
|
|
200
|
+
const seen = storage.getItem(marker);
|
|
201
|
+
if (seen === stamp) return "none";
|
|
202
|
+
// A visitor who wrote to this document while the copy ran keeps what they typed.
|
|
203
|
+
const current = storage.getItem(to);
|
|
204
|
+
if (current !== generation) return "none";
|
|
205
|
+
if (current == null || (seen != null && savedAtOf(current) === seen)) {
|
|
206
|
+
storage.setItem(to, old);
|
|
207
|
+
} else if (current !== old) {
|
|
208
|
+
storage.setItem(to + ":conflict", old);
|
|
209
|
+
console.warn("visitor-drafts: an older tab changed this draft after it moved; its copy was kept under", to + ":conflict");
|
|
210
|
+
}
|
|
211
|
+
if (seen == null) {
|
|
212
|
+
const ui = storage.getItem(from + ":ui");
|
|
213
|
+
if (ui != null && storage.getItem(to + ":ui") == null) storage.setItem(to + ":ui", ui);
|
|
214
|
+
}
|
|
215
|
+
storage.setItem(marker, stamp);
|
|
216
|
+
return "copied";
|
|
217
|
+
} catch {
|
|
218
|
+
return "failed";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function createStore({ doc = document, storage = safeStorage(), now = () => new Date(), path = draftPath(), fallback = null } = {}) {
|
|
98
223
|
const slot = draftSlot(doc);
|
|
99
|
-
const key =
|
|
224
|
+
const key = draftKey(slot, path);
|
|
100
225
|
const rules = engine.findRulesIn(doc, "api").rules;
|
|
101
226
|
|
|
102
227
|
let lastRaw = null;
|
|
@@ -105,7 +230,11 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
|
|
|
105
230
|
let stopped = false;
|
|
106
231
|
|
|
107
232
|
function getRaw() {
|
|
108
|
-
try {
|
|
233
|
+
try {
|
|
234
|
+
if (!storage) return null;
|
|
235
|
+
const raw = storage.getItem(key);
|
|
236
|
+
return raw == null && fallback ? storage.getItem(fallback) : raw;
|
|
237
|
+
} catch { return null; }
|
|
109
238
|
}
|
|
110
239
|
|
|
111
240
|
function parse(raw) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export function createUiMemory(
|
|
2
|
-
const key =
|
|
1
|
+
export function createUiMemory(draftKey, storage) {
|
|
2
|
+
const key = `${draftKey}:ui`;
|
|
3
3
|
const blank = () => ({ v: 1, prompt: {}, intro: { done: null }, src: null });
|
|
4
4
|
let mem = blank();
|
|
5
5
|
try {
|
|
@@ -20,14 +20,13 @@ function mountChip() {
|
|
|
20
20
|
if (!mounting) {
|
|
21
21
|
const asked = sessions;
|
|
22
22
|
mounting = import("./visitor-drafts/owner-chip.js")
|
|
23
|
-
.then(({ mountOwnerChip }) => {
|
|
24
|
-
const chip = mountOwnerChip() ||
|
|
23
|
+
.then(({ mountOwnerChip, ownerChip }) => {
|
|
24
|
+
const chip = mountOwnerChip() || ownerChip();
|
|
25
25
|
if (asked === sessions) {
|
|
26
26
|
mounted = chip;
|
|
27
27
|
return chip;
|
|
28
28
|
}
|
|
29
|
-
chip?.
|
|
30
|
-
chip?.remove();
|
|
29
|
+
chip?.unmount?.();
|
|
31
30
|
return null;
|
|
32
31
|
})
|
|
33
32
|
.finally(() => { mounting = null; });
|
|
@@ -37,8 +36,7 @@ function mountChip() {
|
|
|
37
36
|
|
|
38
37
|
function unmountChip() {
|
|
39
38
|
sessions += 1;
|
|
40
|
-
mounted?.
|
|
41
|
-
mounted?.remove();
|
|
39
|
+
mounted?.unmount?.();
|
|
42
40
|
mounted = null;
|
|
43
41
|
document.getElementById("clay-owner-vars")?.remove();
|
|
44
42
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isDraftMode, isEditMode, isOwner } from "../core/is-edit-mode.js";
|
|
2
2
|
import { hasSaveToken } from "../core/host-attrs.js";
|
|
3
3
|
import { draftSlot, setDraftTransport, setDraftFlush } from "../core/draft-mode.js";
|
|
4
|
-
import { createStore, safeStorage } from "./visitor-drafts/store.js";
|
|
4
|
+
import { createStore, adoptLegacy, draftPath, legacyKey, safeStorage } from "./visitor-drafts/store.js";
|
|
5
5
|
import { createUiMemory } from "./visitor-drafts/ui-memory.js";
|
|
6
6
|
import { rememberSource, downloadUrl } from "./visitor-drafts/source.js";
|
|
7
7
|
import { mountPrompt } from "./visitor-drafts/prompt.js";
|
|
@@ -39,12 +39,14 @@ const slot = draftSlot();
|
|
|
39
39
|
const rulesTag = document.querySelector('script[data-rules-name~="api"]');
|
|
40
40
|
|
|
41
41
|
if (isDraftMode && slot && rulesTag) {
|
|
42
|
-
|
|
42
|
+
const path = draftPath();
|
|
43
|
+
const moved = adoptLegacy(slot, { path });
|
|
44
|
+
store = createStore({ path, fallback: moved === "failed" ? legacyKey(slot) : null });
|
|
43
45
|
setDraftTransport((html) => store.write(html));
|
|
44
46
|
const result = store.restore();
|
|
45
47
|
restoredDetail = { restored: result.restored, hadDraft: result.hadDraft, parked: Boolean(result.parked), ...(result.partial && { partial: true }), ...(result.blocked && { blocked: true }) };
|
|
46
48
|
document.dispatchEvent(new CustomEvent("clay:drafts-restored", { detail: restoredDetail }));
|
|
47
|
-
const memory = createUiMemory(
|
|
49
|
+
const memory = createUiMemory(store.key, safeStorage());
|
|
48
50
|
source = rememberSource(memory);
|
|
49
51
|
downloadLink = downloadUrl(source);
|
|
50
52
|
const appName = document.querySelector('meta[name="application-name"]')?.content?.trim() || document.title.split(/:|\s\|\s/)[0].trim() || "This app";
|
package/src/recovery/store.js
CHANGED
|
@@ -751,7 +751,7 @@ export function createStore({
|
|
|
751
751
|
if (!sameValue(existing.capsuleId, capsule.id) || existing.revision !== input.revision) return { ok: false, code: "stale" };
|
|
752
752
|
doomed.push({ slot, existing });
|
|
753
753
|
}
|
|
754
|
-
const updated = { ...capsule, revision: replacement.revision, payload: replacement.payload, updated: replacement.updated };
|
|
754
|
+
const updated = { ...capsule, revision: replacement.revision, context: replacement.context ?? capsule.context, payload: replacement.payload, updated: replacement.updated };
|
|
755
755
|
updated.bytes = recordBytes(updated);
|
|
756
756
|
let delta = updated.bytes - capsule.bytes;
|
|
757
757
|
for (const { existing } of doomed) delta -= existing.bytes;
|