@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/page.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { FEATURES, featureForType } from "../page/features/registry.js";
|
|
1
|
+
import { FEATURES, featureForNode, featureForType } from "../page/features/registry.js";
|
|
2
2
|
import { mergeEngine } from "../sync/merge-engine.js";
|
|
3
3
|
import { registerUnsavedState } from "../lib/unsaved-state.js";
|
|
4
4
|
import { hostMeta } from "../core/host-meta.js";
|
|
5
|
-
import { savePageForce, holdAllSaves, releaseAllSaves, suspendAutosave, resumeAutosave, getLastSavedBytes } from "../core/save.js";
|
|
6
|
-
import { setSaveHeader } from "../core/save-core.js";
|
|
5
|
+
import { savePageForce, holdAllSaves, releaseAllSaves, suspendAutosave, resumeAutosave, getLastSavedBytes, savesAdmitted } from "../core/save.js";
|
|
6
|
+
import { setSaveHeader, onSaveAccepted } from "../core/save-core.js";
|
|
7
7
|
import { lastSeenEtag, representedEtag, recordEtag } from "../core/etag.js";
|
|
8
8
|
import { captureForSaveAndComparison } from "../core/snapshot.js";
|
|
9
|
-
import { storeOf, stageStore, closeStore, checkpointWithin, requestCheckpoint, fastLane, pageLaneReady, pageWriterReady, registerPageCheckpoints } from "./live.js";
|
|
9
|
+
import { storeOf, stageStore, closeStore, checkpointWithin, requestCheckpoint, requestExitCheckpoint, fastLane, localLane, lanePending, pageLaneReady, pageWriterReady, registerPageCheckpoints } from "./live.js";
|
|
10
10
|
import { serializeLive } from "../live/ops.js";
|
|
11
11
|
import { parseJsonRelaxed } from "../vendor/hyper-morph-json.vendor.js";
|
|
12
|
-
import { saveStateFor, BLOCKED_TEXT, RETRY_TEXT, downloadPageCopy, downloadSnapshot, copyPageText } from "../page/save-states.js";
|
|
12
|
+
import { saveStateFor, pageStateFor, BLOCKED_TEXT, RETRY_TEXT, downloadPageCopy, downloadSnapshot, copyPageText } from "../page/save-states.js";
|
|
13
13
|
import toast, { toastPersistent, dismissPersistent } from "../ui/toast-lazy.js";
|
|
14
14
|
import { claim } from "../page/runtime-owner.js";
|
|
15
15
|
import { authorIdentity } from "../page/ybind/author.js";
|
|
@@ -22,11 +22,14 @@ import { style } from "../lib/hostile-css.js";
|
|
|
22
22
|
const ARTICLE_SEL = "article[data-page]";
|
|
23
23
|
const TAG_SEL = 'script[type="application/json"][merge="page"][clay~="live"]';
|
|
24
24
|
const BLOCKED = { state: "blocked", code: "unsupported-host", message: "This host does not support page editing" };
|
|
25
|
+
const STORE_TEXT = "Changes cannot be stored on this device. Download a copy to keep them.";
|
|
25
26
|
const RETRY_BOUND_MS = 45000;
|
|
26
27
|
const GENERIC_ERROR = "Couldn't save";
|
|
27
28
|
const GENERIC_OFFLINE = "Offline, not saved";
|
|
28
29
|
const REPLACED_TEXT = "This page was changed outside the editor. Reload to see it.";
|
|
29
30
|
const TOO_LARGE_TEXT = "That change is too large to share, so it was not saved. Reload the page to keep editing.";
|
|
31
|
+
const CLIENT_KEY = /^[ur][0-9a-z]+(-[0-9a-z]+){2}$/;
|
|
32
|
+
export const PAGE_FULL_MESSAGE = "This document is full, though your changes are still here to download. To edit again, reload this tab after another editor, or the owner in another browser, deletes content.";
|
|
30
33
|
const OVERWRITTEN_TEXT = "Some changes you made were replaced by a change made outside the editor.";
|
|
31
34
|
const REPLACED_HOLD = "The page was changed outside the editor; reload to see it.";
|
|
32
35
|
const LOADING_MS = 150;
|
|
@@ -72,7 +75,7 @@ function pageCodeReady() {
|
|
|
72
75
|
function docFeatureIds(doc) {
|
|
73
76
|
const ids = new Set();
|
|
74
77
|
doc.forEach((node) => {
|
|
75
|
-
const id =
|
|
78
|
+
const id = featureForNode(node);
|
|
76
79
|
if (id) ids.add(id);
|
|
77
80
|
});
|
|
78
81
|
return [...ids];
|
|
@@ -129,7 +132,7 @@ function scrollToHashBlock(view, loc = globalThis.location) {
|
|
|
129
132
|
globalThis.requestAnimationFrame?.(() => view.nodeDOM(found)?.scrollIntoView?.({ block: "start" }));
|
|
130
133
|
}
|
|
131
134
|
|
|
132
|
-
function dispatchState(state, code, message) {
|
|
135
|
+
function dispatchState(state, code = null, message = "") {
|
|
133
136
|
document.dispatchEvent(new CustomEvent("clay:page-save-state", { detail: { state, code, message } }));
|
|
134
137
|
}
|
|
135
138
|
|
|
@@ -160,11 +163,24 @@ function armRetry(inst, code, message) {
|
|
|
160
163
|
}, RETRY_BOUND_MS);
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
|
|
166
|
+
// The persistent save-state toasts a session raises, taken down together: at the page's
|
|
167
|
+
// own teardown the fade plays, and at a session's end, where nobody is left to read it,
|
|
168
|
+
// the nodes go now.
|
|
169
|
+
function dismissSaveStateToasts(inst, now = false) {
|
|
170
|
+
if (inst.retryTimer) {
|
|
171
|
+
clearTimeout(inst.retryTimer);
|
|
172
|
+
inst.retryTimer = null;
|
|
173
|
+
}
|
|
174
|
+
dismissPersistent(RETRY_TEXT, { now });
|
|
175
|
+
dismissPersistent(BLOCKED_TEXT, { now });
|
|
176
|
+
dismissPersistent(STORE_TEXT, { now });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function showBlocked(inst, code, message, state = "blocked") {
|
|
164
180
|
clearRetry(inst);
|
|
165
181
|
dismissGeneric();
|
|
166
|
-
dispatchState(
|
|
167
|
-
toastPersistent(BLOCKED_TEXT, "error", {
|
|
182
|
+
dispatchState(state, code, message);
|
|
183
|
+
toastPersistent(state === "unavailable" ? STORE_TEXT : BLOCKED_TEXT, "error", {
|
|
168
184
|
detail: message,
|
|
169
185
|
actions: [
|
|
170
186
|
{ label: "Download a copy", onClick: downloadPageCopy },
|
|
@@ -176,6 +192,7 @@ function showBlocked(inst, code, message) {
|
|
|
176
192
|
function showSaved(inst) {
|
|
177
193
|
clearRetry(inst);
|
|
178
194
|
dismissPersistent(BLOCKED_TEXT);
|
|
195
|
+
dismissPersistent(STORE_TEXT);
|
|
179
196
|
dispatchState("saved", null, "");
|
|
180
197
|
}
|
|
181
198
|
|
|
@@ -218,7 +235,7 @@ function stopEditing(inst) {
|
|
|
218
235
|
// room refused it): the saved document with the article rendered from the tab's document.
|
|
219
236
|
function downloadTabCopy(inst) {
|
|
220
237
|
if (!inst.tabValue) return downloadPageCopy();
|
|
221
|
-
const { forSave } = captureForSaveAndComparison();
|
|
238
|
+
const { forSave } = captureForSaveAndComparison({ emitForSync: false });
|
|
222
239
|
const doc = new DOMParser().parseFromString(forSave, "text/html");
|
|
223
240
|
const articles = doc.querySelectorAll(ARTICLE_SEL);
|
|
224
241
|
if (articles.length !== 1) return downloadPageCopy();
|
|
@@ -229,20 +246,23 @@ function downloadTabCopy(inst) {
|
|
|
229
246
|
return downloadSnapshot(`<!DOCTYPE html>\n${doc.documentElement.outerHTML}`);
|
|
230
247
|
}
|
|
231
248
|
|
|
232
|
-
function stopWithNotice(inst, text) {
|
|
249
|
+
function stopWithNotice(inst, text, { reload = true } = {}) {
|
|
233
250
|
if (inst.replaced) return;
|
|
234
251
|
inst.replaced = true;
|
|
235
252
|
stopEditing(inst);
|
|
253
|
+
const download = { label: "Download your copy", onClick: () => downloadTabCopy(inst) };
|
|
236
254
|
toastPersistent(text, "warning", {
|
|
237
|
-
actions: [
|
|
238
|
-
{ label: "Reload", onClick: () => window.location.reload() },
|
|
239
|
-
{ label: "Download your copy", onClick: () => downloadTabCopy(inst) },
|
|
240
|
-
],
|
|
255
|
+
actions: reload ? [{ label: "Reload", onClick: () => window.location.reload() }, download] : [download],
|
|
241
256
|
});
|
|
242
257
|
}
|
|
243
258
|
|
|
244
259
|
const showReplaced = (inst) => stopWithNotice(inst, REPLACED_TEXT);
|
|
245
260
|
const showTooLarge = (inst) => stopWithNotice(inst, TOO_LARGE_TEXT);
|
|
261
|
+
const showFull = (inst, code) => {
|
|
262
|
+
dispatchState("blocked", code, PAGE_FULL_MESSAGE);
|
|
263
|
+
// A reload replays the refused edit and is refused again, so the notice offers only the download.
|
|
264
|
+
stopWithNotice(inst, PAGE_FULL_MESSAGE, { reload: false });
|
|
265
|
+
};
|
|
246
266
|
|
|
247
267
|
function applySaveState(inst, state, code, message) {
|
|
248
268
|
if (state === "saved") showSaved(inst);
|
|
@@ -290,8 +310,7 @@ function listenForSaves(inst) {
|
|
|
290
310
|
});
|
|
291
311
|
}
|
|
292
312
|
|
|
293
|
-
// The page the host stores now,
|
|
294
|
-
// again, and the version it is offered against.
|
|
313
|
+
// The page the host stores now, including any merge state it keeps beside the file.
|
|
295
314
|
async function readStoredArticle() {
|
|
296
315
|
const response = await fetch(new URL(globalThis.location.href).href, { cache: "no-store" });
|
|
297
316
|
if (!response?.ok) return null;
|
|
@@ -299,12 +318,17 @@ async function readStoredArticle() {
|
|
|
299
318
|
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
300
319
|
const article = doc.querySelector(ARTICLE_SEL);
|
|
301
320
|
if (!article) return null;
|
|
302
|
-
|
|
321
|
+
const tag = doc.querySelector(TAG_SEL);
|
|
322
|
+
// The served root attribute names the stored document (spec §4). A host may add a weak
|
|
323
|
+
// HTTP ETag of its own, which names the response, never the document.
|
|
324
|
+
const header = response.headers?.get?.("etag") || null;
|
|
325
|
+
const etag = doc.documentElement.getAttribute("documentetag") || (header && !/^W\//.test(header) ? header : null);
|
|
326
|
+
return { article, value: tag ? parseJsonRelaxed(tag.textContent) : null, etag };
|
|
303
327
|
}
|
|
304
328
|
|
|
305
329
|
// A refused first write: the stored document has moved on, or it does not descend
|
|
306
|
-
// from the seed this tab staged. The stored document is read once,
|
|
307
|
-
// from
|
|
330
|
+
// from the seed this tab staged. The stored document is read once, its merge state is
|
|
331
|
+
// kept (or seeded from the article), this tab's pending edit replays over it, and the write goes
|
|
308
332
|
// out once more against the version just read. A second refusal keeps the text in the
|
|
309
333
|
// tab and shows the conflict, exactly as a lone refusal does.
|
|
310
334
|
async function retryFirstWrite(inst, event) {
|
|
@@ -319,11 +343,11 @@ async function retryFirstWrite(inst, event) {
|
|
|
319
343
|
if (!found || !isCurrent(inst)) return;
|
|
320
344
|
const page = await pageCodeReady();
|
|
321
345
|
if (!isCurrent(inst)) return;
|
|
322
|
-
const seed = page.seedPageFromArticle(found.article.cloneNode(true));
|
|
346
|
+
const seed = found.value ?? page.seedPageFromArticle(found.article.cloneNode(true));
|
|
323
347
|
// This tab's next write is conditional on the version it has just read.
|
|
324
348
|
const at = found.etag ?? event?.detail?.etag ?? null;
|
|
325
349
|
if (at) recordEtag(at);
|
|
326
|
-
// The tab's own edit replays over the
|
|
350
|
+
// The tab's own edit replays over the stored page state; an op the
|
|
327
351
|
// stored article cannot hold is dropped by the store, never written over it.
|
|
328
352
|
inst.store.rebase(seed, { source: "state" });
|
|
329
353
|
await savePageForce();
|
|
@@ -413,12 +437,7 @@ function dispose() {
|
|
|
413
437
|
registerPageCheckpoints(null);
|
|
414
438
|
if (!inst) return;
|
|
415
439
|
disposeInstall(inst);
|
|
416
|
-
|
|
417
|
-
clearTimeout(inst.retryTimer);
|
|
418
|
-
inst.retryTimer = null;
|
|
419
|
-
}
|
|
420
|
-
dismissPersistent(RETRY_TEXT);
|
|
421
|
-
dismissPersistent(BLOCKED_TEXT);
|
|
440
|
+
dismissSaveStateToasts(inst);
|
|
422
441
|
dismissPersistent(REPLACED_TEXT);
|
|
423
442
|
for (const message of inst.notices?.values() ?? []) dismissPersistent(message);
|
|
424
443
|
if (inst.savesHeld) {
|
|
@@ -446,8 +465,12 @@ let recoveryModules = null;
|
|
|
446
465
|
|
|
447
466
|
function pageRecovery() {
|
|
448
467
|
if (!recoveryModules) {
|
|
449
|
-
recoveryModules = Promise.all([
|
|
450
|
-
|
|
468
|
+
recoveryModules = Promise.all([
|
|
469
|
+
import("../page/ybind/recovery-replay.js"),
|
|
470
|
+
import("../page/checkpoint-proof.js"),
|
|
471
|
+
import("../page/recovery-ui.js"),
|
|
472
|
+
])
|
|
473
|
+
.then(([replay, proof, ui]) => ({ replay, proof, ui }))
|
|
451
474
|
.catch((error) => {
|
|
452
475
|
console.warn("clayjs: page journal could not load", error);
|
|
453
476
|
return null;
|
|
@@ -485,11 +508,36 @@ async function replayJournal(inst) {
|
|
|
485
508
|
const outcome = await modules.replay.replayInto({ journal, state: inst.store.value, into: inst.binding.ydoc });
|
|
486
509
|
// The recovered page is this tab's page now: it is offered to the room exactly as an
|
|
487
510
|
// edit would be, and it is not one more journal and not one giant undo step.
|
|
511
|
+
installJournalStatus(inst, modules);
|
|
488
512
|
if (outcome.replayed.length) inst.binding.provider.settle();
|
|
489
513
|
if (outcome.review.length) inst.recoveryReview = outcome.review;
|
|
514
|
+
installRecoveryReview(inst, modules.ui)?.update({ rows: modules.replay.describeReview(outcome.review) });
|
|
490
515
|
return outcome;
|
|
491
516
|
}
|
|
492
517
|
|
|
518
|
+
// The review of what this page may not replay, drawn in the editor's own chrome once per
|
|
519
|
+
// session, with the composition shadow that keeps typing which never became a
|
|
520
|
+
// transaction: one object, alive exactly as long as the session that mounted it.
|
|
521
|
+
function installRecoveryReview(inst, ui) {
|
|
522
|
+
if (inst.recoveryPanel) return inst.recoveryPanel;
|
|
523
|
+
const view = inst.view;
|
|
524
|
+
const mount = view?.dom.closest(".page-shell") ?? inst.article;
|
|
525
|
+
if (!mount) return null;
|
|
526
|
+
const review = ui.createRecoveryReview({
|
|
527
|
+
mount,
|
|
528
|
+
getView: () => (inst.view && !inst.view.isDestroyed ? inst.view : null),
|
|
529
|
+
getJournal: () => inst.binding?.journal ?? null,
|
|
530
|
+
isCurrent: () => isCurrent(inst) && inst.inputOpen && !!inst.view && !inst.view.isDestroyed,
|
|
531
|
+
});
|
|
532
|
+
const detach = review.watch(view?.dom ?? null);
|
|
533
|
+
addDisposer(inst, () => {
|
|
534
|
+
detach();
|
|
535
|
+
review.destroy();
|
|
536
|
+
});
|
|
537
|
+
inst.recoveryPanel = review;
|
|
538
|
+
return review;
|
|
539
|
+
}
|
|
540
|
+
|
|
493
541
|
// One accepted save retires the records a proof covers, and no others.
|
|
494
542
|
function installCheckpointProof(inst) {
|
|
495
543
|
registerPageCheckpoints(async ({ capture, state, article, etag }) => {
|
|
@@ -511,10 +559,28 @@ function installCheckpointProof(inst) {
|
|
|
511
559
|
etag,
|
|
512
560
|
href: await hostPageStateHref(),
|
|
513
561
|
});
|
|
514
|
-
if (answer.ok)
|
|
562
|
+
if (!answer.ok) return;
|
|
563
|
+
journal.retire(answer.retired);
|
|
564
|
+
inst.pageStatus?.checkpoint();
|
|
565
|
+
await journal.rebase?.(modules.replay.journalBase(answer.proof.state));
|
|
515
566
|
});
|
|
516
567
|
}
|
|
517
568
|
|
|
569
|
+
// Both live in the journal's module, which a page without a journal never loads.
|
|
570
|
+
function installJournalStatus(inst, modules) {
|
|
571
|
+
const journal = inst.binding?.journal;
|
|
572
|
+
if (!journal || inst.pageStatus) return inst.pageStatus ?? null;
|
|
573
|
+
inst.pageStatus = modules.replay.watchJournal({
|
|
574
|
+
journal,
|
|
575
|
+
store: inst.store,
|
|
576
|
+
reduce: pageStateFor,
|
|
577
|
+
onState: (state) => dispatchState(state),
|
|
578
|
+
onUnavailable: (code) => showBlocked(inst, code, "", "unavailable"),
|
|
579
|
+
});
|
|
580
|
+
addDisposer(inst, () => inst.pageStatus?.dispose());
|
|
581
|
+
return inst.pageStatus;
|
|
582
|
+
}
|
|
583
|
+
|
|
518
584
|
export function createSteps(inst, options) {
|
|
519
585
|
let meta = null;
|
|
520
586
|
let binding = null;
|
|
@@ -552,6 +618,17 @@ export function createSteps(inst, options) {
|
|
|
552
618
|
supported: host.supported,
|
|
553
619
|
installed: () => identity?.token ?? null,
|
|
554
620
|
revision: () => null,
|
|
621
|
+
// A floor rise moves this tab to the lane scope of the format it now
|
|
622
|
+
// writes, so a tab that cannot read that format never shares it. The
|
|
623
|
+
// transport is this tab's to create, on its own schedule, so the gate
|
|
624
|
+
// asks for it by the move rather than by the object.
|
|
625
|
+
lane: {
|
|
626
|
+
switchScope: (next, options) => {
|
|
627
|
+
const transport = localLane();
|
|
628
|
+
if (!transport) return { status: "refused", code: "lane-unavailable" };
|
|
629
|
+
return transport.switchScope(next, { ...options, covered: Boolean(options?.covered) || !lanePending() });
|
|
630
|
+
},
|
|
631
|
+
},
|
|
555
632
|
});
|
|
556
633
|
return formatGate;
|
|
557
634
|
})
|
|
@@ -613,6 +690,81 @@ export function createSteps(inst, options) {
|
|
|
613
690
|
});
|
|
614
691
|
return toolsOpening;
|
|
615
692
|
};
|
|
693
|
+
|
|
694
|
+
// The document's own switches travel with it: the gear panel writes them into the
|
|
695
|
+
// page's live store, and a document that never touched one opens no store at all.
|
|
696
|
+
// Turning a switch off hides a tag; the owner and due values stay in the file.
|
|
697
|
+
const SETTINGS_SEL = 'script[type="application/json"][merge="page.settings"][clay~="live"]';
|
|
698
|
+
const SETTING_KEYS = ["assignees", "dates"];
|
|
699
|
+
let settingsOpening = null;
|
|
700
|
+
let settingsValue = { assignees: true, dates: true };
|
|
701
|
+
|
|
702
|
+
// Both switches default to on, and anything but an explicit false leaves them on.
|
|
703
|
+
const settingsFrom = (value) => ({
|
|
704
|
+
assignees: value?.assignees !== false,
|
|
705
|
+
dates: value?.dates !== false,
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
function readSettings() {
|
|
709
|
+
const tag = document.querySelector(SETTINGS_SEL);
|
|
710
|
+
if (!tag) return settingsFrom(null);
|
|
711
|
+
let value = null;
|
|
712
|
+
try { value = JSON.parse(tag.textContent); } catch (error) { value = null; }
|
|
713
|
+
return settingsFrom(value);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function applySettings(next) {
|
|
717
|
+
settingsValue = settingsFrom(next);
|
|
718
|
+
const dom = inst.view && !inst.view.isDestroyed ? inst.view.dom : null;
|
|
719
|
+
if (!dom) return;
|
|
720
|
+
for (const key of SETTING_KEYS) {
|
|
721
|
+
if (settingsValue[key]) dom.removeAttribute(`data-${key}`);
|
|
722
|
+
else dom.setAttribute(`data-${key}`, "false");
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const openSettings = () => {
|
|
727
|
+
if (settingsOpening) return settingsOpening;
|
|
728
|
+
settingsOpening = (async () => {
|
|
729
|
+
if (!document.querySelector(SETTINGS_SEL)) {
|
|
730
|
+
const tag = document.createElement("script");
|
|
731
|
+
tag.setAttribute("type", "application/json");
|
|
732
|
+
tag.setAttribute("merge", "page.settings");
|
|
733
|
+
tag.setAttribute("clay", "live");
|
|
734
|
+
tag.textContent = "{}";
|
|
735
|
+
document.head.appendChild(tag);
|
|
736
|
+
}
|
|
737
|
+
let handle = null;
|
|
738
|
+
try {
|
|
739
|
+
handle = window.clay.live("page.settings");
|
|
740
|
+
await handle.ready;
|
|
741
|
+
} catch (error) {
|
|
742
|
+
console.warn("clayjs: page plugin could not open the settings store", error);
|
|
743
|
+
settingsOpening = null;
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
if (!isCurrent(inst)) return null;
|
|
747
|
+
applySettings(handle.data);
|
|
748
|
+
// A peer's change hides or shows the tags here too, without the view remounting.
|
|
749
|
+
handle.on("change", () => applySettings(handle.data));
|
|
750
|
+
return handle;
|
|
751
|
+
})();
|
|
752
|
+
return settingsOpening;
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
// The gear panel's API: what the document shows now, and a switch that writes the
|
|
756
|
+
// document's store and takes effect here at once, without waiting for the change.
|
|
757
|
+
inst.settings = {
|
|
758
|
+
read: () => ({ ...settingsValue }),
|
|
759
|
+
set: async (key, on) => {
|
|
760
|
+
if (!SETTING_KEYS.includes(key)) throw new TypeError(`clayjs: "${key}" is not a page setting`);
|
|
761
|
+
const handle = await openSettings();
|
|
762
|
+
if (!handle) throw new Error("clayjs: the page settings store could not be opened");
|
|
763
|
+
const next = { ...settingsFrom(handle.data), [key]: on !== false };
|
|
764
|
+
handle.set([key], next[key]);
|
|
765
|
+
applySettings(next);
|
|
766
|
+
},
|
|
767
|
+
};
|
|
616
768
|
// One notice per feature a person needed and that failed, until Retry succeeds.
|
|
617
769
|
const notices = new Map();
|
|
618
770
|
const notifyFailed = (id) => {
|
|
@@ -690,9 +842,29 @@ export function createSteps(inst, options) {
|
|
|
690
842
|
const code = await pageCodeReady();
|
|
691
843
|
if (!isCurrent(inst)) return { ok: false, cancelled: true };
|
|
692
844
|
if (seeded) {
|
|
693
|
-
// The seed stays a value in memory: no tag and no write until someone edits.
|
|
694
845
|
try {
|
|
695
|
-
|
|
846
|
+
const meta = await hostMeta();
|
|
847
|
+
const href = meta?.extensions?.includes("page-recovery-v1") && meta.document?.pageState?.href;
|
|
848
|
+
const etag = inst.context?.represented ? inst.context.represented.etag : representedEtag() ?? lastSeenEtag();
|
|
849
|
+
if (href && etag) {
|
|
850
|
+
const { readPageState } = await import("../page/checkpoint-proof.js");
|
|
851
|
+
const stored = await readPageState({ href, etag });
|
|
852
|
+
if (!isCurrent(inst)) return { ok: false, cancelled: true };
|
|
853
|
+
if (stored.ok) {
|
|
854
|
+
if (stored.etag !== etag) return { ok: false, reason: "etag-mismatch" };
|
|
855
|
+
value = parseJsonRelaxed(stored.tag);
|
|
856
|
+
source.persisted = true;
|
|
857
|
+
} else if (stored.code !== "no-page-state") {
|
|
858
|
+
return { ok: false, reason: stored.code };
|
|
859
|
+
}
|
|
860
|
+
} else if (href) {
|
|
861
|
+
const stored = await readStoredArticle();
|
|
862
|
+
if (!isCurrent(inst)) return { ok: false, cancelled: true };
|
|
863
|
+
if (!stored || !stored.article.isEqualNode(source.article)) return { ok: false, reason: "stale-document" };
|
|
864
|
+
value = stored.value;
|
|
865
|
+
source.persisted = value !== null;
|
|
866
|
+
}
|
|
867
|
+
value = source.seedValue = value ?? code.seedPageFromArticle(source.article.cloneNode(true));
|
|
696
868
|
} catch (error) {
|
|
697
869
|
console.warn("clayjs: page plugin could not rebuild the page it found, leaving the page as saved HTML", error);
|
|
698
870
|
return { ok: false, reason: "invalid-page-source" };
|
|
@@ -816,6 +988,7 @@ function mergeRepresentedPage(store, value) {
|
|
|
816
988
|
const staged = stageStore("page", {
|
|
817
989
|
value: source.seedValue,
|
|
818
990
|
represented: options.represented ?? { bytes: source.article.outerHTML, etag: representedEtag() ?? lastSeenEtag() },
|
|
991
|
+
persisted: source.persisted,
|
|
819
992
|
});
|
|
820
993
|
inst.staged = staged;
|
|
821
994
|
handle = staged.handle;
|
|
@@ -876,6 +1049,8 @@ function mergeRepresentedPage(store, value) {
|
|
|
876
1049
|
if (reason === "lineage") {
|
|
877
1050
|
fastLane()?.abandon?.("page");
|
|
878
1051
|
showReplaced(inst);
|
|
1052
|
+
} else if (reason === "page-full" || reason === "compaction-failed") {
|
|
1053
|
+
showFull(inst, reason);
|
|
879
1054
|
} else {
|
|
880
1055
|
showTooLarge(inst);
|
|
881
1056
|
}
|
|
@@ -884,12 +1059,17 @@ function mergeRepresentedPage(store, value) {
|
|
|
884
1059
|
ownedTokens: identity.owned,
|
|
885
1060
|
checkpointWithin,
|
|
886
1061
|
requestCheckpoint,
|
|
1062
|
+
requestExitCheckpoint: (options) => requestExitCheckpoint({ name: store.name, ...options }),
|
|
887
1063
|
journal,
|
|
1064
|
+
// An exit holds admission closed, which is the only time this tab's own input
|
|
1065
|
+
// still owing the file holds the leave: outside one, the binding reports what it
|
|
1066
|
+
// always reported.
|
|
1067
|
+
exitClosed: () => !savesAdmitted(),
|
|
888
1068
|
registerUnsavedState: (def) => {
|
|
889
1069
|
registered = registerUnsavedState({
|
|
890
1070
|
id: def.id,
|
|
891
1071
|
isPending: () => flushFailed || def.isPending(),
|
|
892
|
-
flush: () => trackFlush(() => def.flush()),
|
|
1072
|
+
flush: (options) => trackFlush(() => def.flush(options)),
|
|
893
1073
|
});
|
|
894
1074
|
return registered;
|
|
895
1075
|
},
|
|
@@ -972,6 +1152,13 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
972
1152
|
}
|
|
973
1153
|
editorView = view;
|
|
974
1154
|
inst.view = view;
|
|
1155
|
+
applySettings(readSettings());
|
|
1156
|
+
// A document that states settings opens their store so a peer's change reaches this
|
|
1157
|
+
// tab; one that states none opens it when a switch is first written.
|
|
1158
|
+
if (document.querySelector(SETTINGS_SEL)) openSettings().catch(() => null);
|
|
1159
|
+
else listen(inst, "clay:sync-applied", () => {
|
|
1160
|
+
if (!settingsOpening && document.querySelector(SETTINGS_SEL)) openSettings().catch(() => null);
|
|
1161
|
+
});
|
|
975
1162
|
const shell = view.dom.closest(".page-shell");
|
|
976
1163
|
// A shell still waiting for its boot takes no input of any kind: a control inside
|
|
977
1164
|
// a non-editable editor would otherwise write before the boot says ready. It is
|
|
@@ -1062,7 +1249,8 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1062
1249
|
// lane but no writer yet, this tab claims the room (a redundant set of an
|
|
1063
1250
|
// existing Yjs update: a valid nonempty frame that changes nothing) and then
|
|
1064
1251
|
// waits for the election, so exactly one tab adopts.
|
|
1065
|
-
|
|
1252
|
+
// A seeded page was built from this article: nothing to adopt.
|
|
1253
|
+
const found = source.seeded ? { kind: "untouched" } : code.classifyFile(source.article, source.tags);
|
|
1066
1254
|
const needsAdoption = found.kind === "adopt";
|
|
1067
1255
|
const awaitWriter = async () => {
|
|
1068
1256
|
const lane = await pageLaneReady();
|
|
@@ -1071,8 +1259,13 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1071
1259
|
if (lane.status.writer === null) {
|
|
1072
1260
|
const y = inst.store.value.y;
|
|
1073
1261
|
const keys = Object.keys(y);
|
|
1074
|
-
|
|
1075
|
-
|
|
1262
|
+
// A compacting room admits only whole client keys: an existing one again, or this
|
|
1263
|
+
// tab's document under a fresh one.
|
|
1264
|
+
const me = binding.ydoc.clientID.toString(36);
|
|
1265
|
+
const key = inst.store.room?.compaction?.v === 1
|
|
1266
|
+
? keys.find((k) => CLIENT_KEY.test(k)) ?? `u${me}-claim-0`
|
|
1267
|
+
: keys.length ? keys[0] : `claim-${me}`;
|
|
1268
|
+
const value = Object.hasOwn(y, key) ? y[key] : code.yValueOf(binding.ydoc).y.seed;
|
|
1076
1269
|
inst.store.handle.set(["y", key], value);
|
|
1077
1270
|
// The session is not admitted until the commit, so its ops stay in the tab: the
|
|
1078
1271
|
// claim alone is framed now, before adoption writes anything, or no election runs.
|
|
@@ -1095,8 +1288,9 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1095
1288
|
return !lane || lane.status.writer === null ? true : lane.isWriter();
|
|
1096
1289
|
},
|
|
1097
1290
|
requestCheckpoint: () => checkpointWithin(0),
|
|
1098
|
-
snapshot: () => captureForSaveAndComparison().forSave,
|
|
1291
|
+
snapshot: () => captureForSaveAndComparison({ emitForSync: false }).forSave,
|
|
1099
1292
|
mergeTagless: inst.mergeTagless,
|
|
1293
|
+
seeded: source.seeded,
|
|
1100
1294
|
writerReady: awaitWriter,
|
|
1101
1295
|
adopted: (rev) => fastLane()?.adopted?.("page", rev, inst.store.mark),
|
|
1102
1296
|
notice: (counts) => {
|
|
@@ -1184,7 +1378,7 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1184
1378
|
// a `resume` puts the very same session back.
|
|
1185
1379
|
function closeInput(inst) {
|
|
1186
1380
|
if (!inst.inputOpen) return false;
|
|
1187
|
-
setEditorActive?.(inst.view, false);
|
|
1381
|
+
setEditorActive?.(inst.view, false, { quiesce: true });
|
|
1188
1382
|
return true;
|
|
1189
1383
|
}
|
|
1190
1384
|
|
|
@@ -1263,10 +1457,7 @@ function endSession(inst) {
|
|
|
1263
1457
|
}
|
|
1264
1458
|
setSaveHeader("Page-Write", null);
|
|
1265
1459
|
disposeInstall(inst);
|
|
1266
|
-
|
|
1267
|
-
clearTimeout(inst.retryTimer);
|
|
1268
|
-
inst.retryTimer = null;
|
|
1269
|
-
}
|
|
1460
|
+
dismissSaveStateToasts(inst, true);
|
|
1270
1461
|
inst.titleObserver?.dispose();
|
|
1271
1462
|
inst.titleObserver = null;
|
|
1272
1463
|
clearLoadingStatus(inst);
|
|
@@ -1399,8 +1590,22 @@ export const page = {
|
|
|
1399
1590
|
get view() {
|
|
1400
1591
|
return active?.view ?? null;
|
|
1401
1592
|
},
|
|
1593
|
+
// The document's settings, for the panel that switches them.
|
|
1594
|
+
get settings() {
|
|
1595
|
+
return active?.settings ?? null;
|
|
1596
|
+
},
|
|
1402
1597
|
};
|
|
1403
1598
|
|
|
1599
|
+
// The page's accepted bytes are the only thing that clears what this tab owes the file,
|
|
1600
|
+
// and one module-level observer is enough: the hook API has no unsubscribe, so a binding
|
|
1601
|
+
// registers none of its own and the current session's binding answers each save.
|
|
1602
|
+
onSaveAccepted((html) => {
|
|
1603
|
+
const binding = active?.binding;
|
|
1604
|
+
if (!binding?.filePending()) return;
|
|
1605
|
+
const state = pageCode?.acceptedPageState(html);
|
|
1606
|
+
if (state) binding.acceptFile(state);
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1404
1609
|
// A live entry imports this module while the committed mode is still view: the install
|
|
1405
1610
|
// belongs to the explicit edit boot alone, and a managed session starts its own.
|
|
1406
1611
|
page.ready = startsAtImport() ? install() : null;
|
package/src/plugins/sortable.js
CHANGED
|
@@ -250,6 +250,7 @@ function makeSortable(sortableElem, Sortable, record = loose) {
|
|
|
250
250
|
let press = null;
|
|
251
251
|
let restoring = null;
|
|
252
252
|
let started = false;
|
|
253
|
+
let native = false;
|
|
253
254
|
let ends = [];
|
|
254
255
|
// A press that ends without a drop (a click, a cancel) still has to hand the
|
|
255
256
|
// gesture's exclusion back: the drop itself releases it once its entry is recorded.
|
|
@@ -271,7 +272,11 @@ function makeSortable(sortableElem, Sortable, record = loose) {
|
|
|
271
272
|
ends = [];
|
|
272
273
|
if (pending) later(record, () => restoreRuntimeAttrs(pending));
|
|
273
274
|
};
|
|
274
|
-
const settleSoon = () => {
|
|
275
|
+
const settleSoon = (event) => {
|
|
276
|
+
// Chrome cancels the pointer as a native drag begins, just before Sortable's onStart:
|
|
277
|
+
// that press ends on the drop or dragend, not on the cancel.
|
|
278
|
+
if (native && event?.type === 'pointercancel') return;
|
|
279
|
+
native = false;
|
|
275
280
|
// Only the press that became a drag holds: a click has nothing to keep out of
|
|
276
281
|
// history, and its own handler's writes are the next entry.
|
|
277
282
|
if (started) {
|
|
@@ -291,9 +296,11 @@ function makeSortable(sortableElem, Sortable, record = loose) {
|
|
|
291
296
|
recorded = recordRuntimeAttrs(item);
|
|
292
297
|
press = beginMove(item);
|
|
293
298
|
started = false;
|
|
299
|
+
native = false;
|
|
294
300
|
record.presses.add(recorded);
|
|
295
301
|
pressed.add(recorded);
|
|
296
302
|
ends = ENDS.map(type => track(record, document, type, settleSoon, true));
|
|
303
|
+
ends.push(track(record, document, 'dragstart', () => { native = true; }, true));
|
|
297
304
|
}, true);
|
|
298
305
|
options.onUnchoose = restoreSoon;
|
|
299
306
|
// The one signal a click never makes: Sortable says the press became a drag, so this
|
package/src/plugins/source.js
CHANGED
|
@@ -191,6 +191,12 @@ export const source = {
|
|
|
191
191
|
unpaired: () => (session ? session.unpaired() : null),
|
|
192
192
|
/** The save pipeline's own ask: a promise to wait for before its capture, or null. */
|
|
193
193
|
ensureForSave,
|
|
194
|
+
/**
|
|
195
|
+
* How many times this session's map has been rebuilt, or 0 before it installs.
|
|
196
|
+
*/
|
|
197
|
+
get repairGeneration() {
|
|
198
|
+
return session ? session.repairGeneration() : 0;
|
|
199
|
+
},
|
|
194
200
|
};
|
|
195
201
|
|
|
196
202
|
/**
|