@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/sync/live-sync.js
CHANGED
|
@@ -36,20 +36,23 @@
|
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
import { createIdentityStore } from "../vendor/hyper-morph-identity.vendor.js";
|
|
39
|
+
import { pageRelay } from "./page-relay.js";
|
|
39
40
|
import { loadedEngine, mergeEngine } from "./merge-engine.js";
|
|
40
41
|
import Mutation from "../lib/mutation.js";
|
|
41
42
|
import { NO_DIRTY_SELECTOR, LIVE_SELECTOR } from "../lib/region-policy.js";
|
|
42
43
|
import { EXTENSION_NODE_SELECTOR, EXTENSION_ATTR_PATTERN } from '../lib/extension-noise.js';
|
|
43
44
|
import { isEditMode, modeState } from "../core/is-edit-mode.js";
|
|
44
45
|
import { attributeMerge } from "./merge-declarations.js";
|
|
45
|
-
import { serializeForSync, captureForComparisonAndDirty, canonicalizeReceived, captureSnapshot, originalSnapshotNode, captureForMerge, captureForSaveAndComparison } from '../core/snapshot.js';
|
|
46
|
+
import { serializeForSync, copyCaptureClone, captureForComparisonAndDirty, canonicalizeReceived, captureSnapshot, originalSnapshotNode, captureForMerge, captureForSaveAndComparison } from '../core/snapshot.js';
|
|
46
47
|
import { mergeIgnoresAttr, mergeVetoesAttr } from '../lib/root-attrs.js';
|
|
47
48
|
import { autosaveActive } from '../lib/autosave-state.js';
|
|
48
49
|
import { hostSupports } from '../core/host-meta.js';
|
|
49
50
|
import { PAGE_FORMAT, PAGE_FORMAT_HEADER } from '../core/page-format-wire.js';
|
|
51
|
+
import { knownPageFormatFloor, pageFormatFloor, withPageFormatFloor } from '../core/page-format.js';
|
|
52
|
+
import { onSaveAccepted } from '../core/save-accepted.js';
|
|
50
53
|
import { servedDocumentEtag } from '../core/host-attrs.js';
|
|
51
54
|
import { recordEtag, seedEtag, lastSeenEtag, conditionalSaves, representedEtag, forgetRepresentedEtag } from '../core/etag.js';
|
|
52
|
-
import { pageMaybeDirty, pauseGate, resumeGate, gateCaptureToken, gateClearIfUnchanged } from '../lib/dirty-gate.js';
|
|
55
|
+
import { pageMaybeDirty, pauseGate, resumeGate, gateCaptureToken, gateClearIfUnchanged, gateMarkDirty } from '../lib/dirty-gate.js';
|
|
53
56
|
import { gestureSeen } from '../lib/user-gesture.js';
|
|
54
57
|
import { SyncStream } from './stream.js';
|
|
55
58
|
import { streamOpened, streamClosed, streamEvent, LIVE_EVENTS } from '../live/stream-bridge.js';
|
|
@@ -289,6 +292,21 @@ function pairedBaseline() {
|
|
|
289
292
|
*/
|
|
290
293
|
import { WIRE_PROFILES, resolveWireProfile } from './wire-profile.js';
|
|
291
294
|
|
|
295
|
+
// Above this the relay body is a Blob built from parts, so the snapshot is never joined
|
|
296
|
+
// into one more full-size string on the main thread. The save body's own threshold (5h).
|
|
297
|
+
const RELAY_BODY_BLOB_MIN_BYTES = 1024 * 1024;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* The body of one relay POST: the bytes `JSON.stringify` of this object has always
|
|
301
|
+
* produced, from parts when the snapshot is large. An absent value still leaves its key
|
|
302
|
+
* out, exactly as before.
|
|
303
|
+
*/
|
|
304
|
+
function relayBody(profile, html, sender, identityMap, etag) {
|
|
305
|
+
const rest = { sender, identityMap, ...(etag ? { etag } : {}) };
|
|
306
|
+
if (html.length < RELAY_BODY_BLOB_MIN_BYTES) return JSON.stringify({ [profile.snapshotKey]: html, ...rest });
|
|
307
|
+
return new Blob([`{${JSON.stringify(profile.snapshotKey)}:`, JSON.stringify(html), `,${JSON.stringify(rest).slice(1)}`], { type: 'application/json' });
|
|
308
|
+
}
|
|
309
|
+
|
|
292
310
|
class LiveSync {
|
|
293
311
|
constructor() {
|
|
294
312
|
this.sse = null;
|
|
@@ -333,6 +351,7 @@ class LiveSync {
|
|
|
333
351
|
// Never reconstructed from lastHtml: that is the newest accepted save or
|
|
334
352
|
// applied frame, which can still lag the save in flight, and pairing a fresh
|
|
335
353
|
// stamp with older bytes is the one thing spec §10 forbids.
|
|
354
|
+
this._savedSnapshot?.abort?.abort();
|
|
336
355
|
this._savedSnapshot = null;
|
|
337
356
|
this._sendInFlight = false;
|
|
338
357
|
this._queuedSend = null;
|
|
@@ -347,6 +366,9 @@ class LiveSync {
|
|
|
347
366
|
this._liveData = false;
|
|
348
367
|
this._editing = null;
|
|
349
368
|
this._presenceLease = null;
|
|
369
|
+
// Set by the exit barrier: a sender discovered while it runs is built but stays
|
|
370
|
+
// quiet until resume, so the exit publishes nobody.
|
|
371
|
+
this._presenceQuiesced = false;
|
|
350
372
|
this._ready = null;
|
|
351
373
|
// Bumped by every start(), so a discovery that resolves after a stop/restart
|
|
352
374
|
// cannot open a stream for the run that has already been torn down.
|
|
@@ -578,6 +600,7 @@ class LiveSync {
|
|
|
578
600
|
this._diskBase = null;
|
|
579
601
|
this._diskBaseTicket = this._ticket();
|
|
580
602
|
this._saveTicket = 0;
|
|
603
|
+
this._savedSnapshot?.abort?.abort();
|
|
581
604
|
this._savedSnapshot = null;
|
|
582
605
|
this._applyGen++;
|
|
583
606
|
this.lastSeenSeq = 0;
|
|
@@ -662,7 +685,7 @@ class LiveSync {
|
|
|
662
685
|
// one sender behind it: the generic marker publishes through the store, so a
|
|
663
686
|
// page target and a caret path never talk over each other.
|
|
664
687
|
loadPresence().then((store) => this._startPresence(store, gen), () => {});
|
|
665
|
-
this._saveSavedHandler = () => {
|
|
688
|
+
this._saveSavedHandler = (event) => {
|
|
666
689
|
this._saveEpoch++;
|
|
667
690
|
// The file now holds what this save wrote: the disk lane's new base,
|
|
668
691
|
// dated at the save's capture, which is when the document held these
|
|
@@ -674,7 +697,7 @@ class LiveSync {
|
|
|
674
697
|
if (ticket > this._acceptedSaveTicket) this._acceptedSaveTicket = ticket;
|
|
675
698
|
const bytes = edit ? edit.getLastSavedBytes() : null;
|
|
676
699
|
if (bytes != null) this._setDiskBase(bytes, ticket);
|
|
677
|
-
this._relayCommit();
|
|
700
|
+
this._relayCommit(event?.detail?.captureId, bytes);
|
|
678
701
|
};
|
|
679
702
|
document.addEventListener('clay:save-saved', this._saveSavedHandler);
|
|
680
703
|
// A 412 can arrive after this tab already merged the version that beat it:
|
|
@@ -721,10 +744,14 @@ class LiveSync {
|
|
|
721
744
|
this._editing.stop();
|
|
722
745
|
this._editing = null;
|
|
723
746
|
}
|
|
724
|
-
// Only this run's lease: a successor that already attached keeps its roster.
|
|
747
|
+
// Only this run's lease: a successor that already attached keeps its roster. A
|
|
748
|
+
// broadcast session is stopped, not just detached, so its heartbeat, channel and
|
|
749
|
+
// goodbye go with the session that opened them.
|
|
725
750
|
if (this._presenceLease) {
|
|
726
|
-
this._presenceLease
|
|
751
|
+
const transport = this._presenceLease;
|
|
727
752
|
this._presenceLease = null;
|
|
753
|
+
if (typeof transport.stop === 'function') transport.stop();
|
|
754
|
+
else transport.detach();
|
|
728
755
|
}
|
|
729
756
|
this._lastRoster = null;
|
|
730
757
|
|
|
@@ -1136,13 +1163,45 @@ class LiveSync {
|
|
|
1136
1163
|
// after dispatchEvent returns, so reading outerHTML and walking children
|
|
1137
1164
|
// must happen now.
|
|
1138
1165
|
this._log('snapshot-ready received, preparing to send');
|
|
1139
|
-
const html = serializeForSync(clone);
|
|
1140
1166
|
const identityMap = this.identity.exportMap(clone, originalSnapshotNode);
|
|
1141
1167
|
|
|
1142
1168
|
// The save that follows this capture stores these bytes, so this is the
|
|
1143
1169
|
// content its stamp will describe. Held for _relayCommit, and overwritten
|
|
1144
1170
|
// by the next capture, so it always names the save currently in flight.
|
|
1145
|
-
this._savedSnapshot
|
|
1171
|
+
this._savedSnapshot?.abort?.abort();
|
|
1172
|
+
const held = {
|
|
1173
|
+
html: null,
|
|
1174
|
+
steps: null,
|
|
1175
|
+
copy: null,
|
|
1176
|
+
abort: null,
|
|
1177
|
+
identityMap,
|
|
1178
|
+
ticket: this._saveTicket,
|
|
1179
|
+
captureId: event.detail.captureId ?? null,
|
|
1180
|
+
};
|
|
1181
|
+
if (event.detail.sliced) {
|
|
1182
|
+
// The save strips this clone as soon as the event returns, so a serialization
|
|
1183
|
+
// that runs across tasks reads a private copy. Cloning is much cheaper than
|
|
1184
|
+
// serializing, and the copy is what keeps the bytes exactly a one-call pass's.
|
|
1185
|
+
// The generator records its own result, so whichever finishes it, the background
|
|
1186
|
+
// run or the relay, leaves the bytes on `held`. The step serializers and the slicer
|
|
1187
|
+
// load lazily; the sliced save that raised this event has already fetched them.
|
|
1188
|
+
held.copy = copyCaptureClone(clone);
|
|
1189
|
+
held.abort = new AbortController();
|
|
1190
|
+
const { signal } = held.abort;
|
|
1191
|
+
Promise.all([import('../core/serialize-steps.js'), import('../lib/slice.js')]).then(([{ serializeForSyncSteps }, { runSliced }]) => {
|
|
1192
|
+
if (signal.aborted || typeof held.html === 'string') return;
|
|
1193
|
+
const steps = (function* () {
|
|
1194
|
+
const html = yield* serializeForSyncSteps(held.copy);
|
|
1195
|
+
held.html = html;
|
|
1196
|
+
held.steps = null;
|
|
1197
|
+
})();
|
|
1198
|
+
held.steps = steps;
|
|
1199
|
+
return runSliced(steps, { signal });
|
|
1200
|
+
}).catch(() => {});
|
|
1201
|
+
} else {
|
|
1202
|
+
held.html = serializeForSync(clone);
|
|
1203
|
+
}
|
|
1204
|
+
this._savedSnapshot = held;
|
|
1146
1205
|
};
|
|
1147
1206
|
|
|
1148
1207
|
document.addEventListener('clay:snapshot-ready', this._snapshotHandler);
|
|
@@ -1190,37 +1249,81 @@ class LiveSync {
|
|
|
1190
1249
|
* peers only ever see versions the host accepted, each descending from the one
|
|
1191
1250
|
* before. A host that returns no stamp still gets the relay, without one.
|
|
1192
1251
|
*/
|
|
1193
|
-
_relayCommit() {
|
|
1252
|
+
_relayCommit(captureId = null, acceptedHtml = null) {
|
|
1194
1253
|
if (this.isDestroyed) return;
|
|
1195
1254
|
const etag = lastSeenEtag();
|
|
1196
1255
|
|
|
1256
|
+
const held = this._savedSnapshot;
|
|
1257
|
+
// A capture taken after this save's own is somebody else's content: relay it
|
|
1258
|
+
// under its own save's stamp, never this one's. The legacy null-id shape keeps
|
|
1259
|
+
// matching, so a fixture that names no capture on either side still relays.
|
|
1260
|
+
if (held && (held.captureId ?? null) !== (captureId ?? null)) {
|
|
1261
|
+
this._log('Skipping send - snapshot belongs to a different capture');
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1197
1265
|
// The content this save stored, captured with it. Consumed rather than left
|
|
1198
1266
|
// behind, so a save-saved with no capture of its own can never reuse an
|
|
1199
1267
|
// older save's bytes under a newer stamp.
|
|
1200
|
-
const pending =
|
|
1268
|
+
const pending = held;
|
|
1201
1269
|
this._savedSnapshot = null;
|
|
1202
1270
|
|
|
1271
|
+
// A serialization still running in the background: this task finishes it, so the
|
|
1272
|
+
// relayed bytes are the ones the save stored however the two raced.
|
|
1273
|
+
if (pending?.copy && typeof pending.html !== 'string') {
|
|
1274
|
+
pending.abort?.abort();
|
|
1275
|
+
if (pending.steps) for (let step = pending.steps.next(); !step.done; step = pending.steps.next());
|
|
1276
|
+
else pending.html = serializeForSync(pending.copy);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1203
1279
|
// No captured snapshot means nothing here knows which bytes this save
|
|
1204
1280
|
// stored, and §10 is explicit that a stamp must never travel on its own.
|
|
1205
1281
|
if (!pending || typeof pending.html !== 'string') return;
|
|
1206
1282
|
|
|
1283
|
+
// The capture is taken before the document transforms run, and the page
|
|
1284
|
+
// projection is one of them: a page's relay has to carry what the file now
|
|
1285
|
+
// holds, not the editor DOM the capture saw. Everything outside the page's
|
|
1286
|
+
// own elements stays the captured sync domain, `no-save` regions included.
|
|
1287
|
+
let payload = pending;
|
|
1288
|
+
if (typeof acceptedHtml === 'string') {
|
|
1289
|
+
try {
|
|
1290
|
+
payload = pageRelay(pending, acceptedHtml, this.identity);
|
|
1291
|
+
} catch (err) {
|
|
1292
|
+
// Stale page bytes are worse than no relay: the save stays accepted, and
|
|
1293
|
+
// peers get the next one.
|
|
1294
|
+
this._log(`Skipping send - page relay failed: ${err && err.message}`);
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
if (!payload) {
|
|
1298
|
+
this._log('Skipping send - accepted capture has no page article');
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// The relay cites this save's stamp, so it declares the floor those bytes went out
|
|
1304
|
+
// at: the save adds the format marker on its way out (core/save-core.js runSave),
|
|
1305
|
+
// after the capture this relay was built from. At floor 0 the html is unchanged.
|
|
1306
|
+
const sent = acceptedBytesFor(captureId) ?? acceptedHtml;
|
|
1307
|
+
const floor = typeof sent === 'string' ? pageFormatFloor(sent) : 0;
|
|
1308
|
+
payload = { ...payload, html: asSent(withPageFormatFloor(payload.html, Math.max(knownPageFormatFloor(), floor)), sent) };
|
|
1309
|
+
|
|
1207
1310
|
// Skip only an unstamped repeat, judged against the base as it stood. A
|
|
1208
1311
|
// landed save is news even when its bytes equal the last relay: "Keep mine"
|
|
1209
1312
|
// can restore exactly those bytes after the peers moved on, and they must
|
|
1210
1313
|
// hear it.
|
|
1211
|
-
const repeat = !etag &&
|
|
1314
|
+
const repeat = !etag && payload.html === this.lastHtml;
|
|
1212
1315
|
|
|
1213
1316
|
// The host accepted these bytes, so every later accepted save descends from
|
|
1214
1317
|
// them: they are the peer lane's base from this moment, not once the relay
|
|
1215
1318
|
// returns. A frame whose morph ran after this capture installs its own,
|
|
1216
1319
|
// newer base when its resources settle, and the ticket keeps it there.
|
|
1217
|
-
this._setPeerBase(
|
|
1320
|
+
this._setPeerBase(payload.html, payload.identityMap, payload.ticket);
|
|
1218
1321
|
|
|
1219
1322
|
if (repeat) {
|
|
1220
1323
|
this._log('Skipping send - HTML unchanged');
|
|
1221
1324
|
return;
|
|
1222
1325
|
}
|
|
1223
|
-
this._enqueueSend(
|
|
1326
|
+
this._enqueueSend(payload.html, payload.identityMap, etag || null);
|
|
1224
1327
|
}
|
|
1225
1328
|
|
|
1226
1329
|
_postUpdate(html, identityMap, etag = null, attempt = 0) {
|
|
@@ -1239,12 +1342,7 @@ class LiveSync {
|
|
|
1239
1342
|
[profile.documentHeader]: window.location.href,
|
|
1240
1343
|
[PAGE_FORMAT_HEADER]: String(PAGE_FORMAT),
|
|
1241
1344
|
},
|
|
1242
|
-
body:
|
|
1243
|
-
[profile.snapshotKey]: html,
|
|
1244
|
-
sender: this.clientId,
|
|
1245
|
-
identityMap: identityMap,
|
|
1246
|
-
...(etag ? { etag } : {})
|
|
1247
|
-
})
|
|
1345
|
+
body: relayBody(profile, html, this.clientId, identityMap, etag)
|
|
1248
1346
|
}).then(response => {
|
|
1249
1347
|
if (!response.ok) {
|
|
1250
1348
|
console.warn('[LiveSync] Save returned status:', response.status);
|
|
@@ -1288,7 +1386,8 @@ class LiveSync {
|
|
|
1288
1386
|
return store.connect({ send: (body) => this._postPresence(body) })
|
|
1289
1387
|
.then((transport) => {
|
|
1290
1388
|
if (this.isDestroyed || this._startGen !== gen) {
|
|
1291
|
-
transport?.
|
|
1389
|
+
if (typeof transport?.stop === 'function') transport.stop();
|
|
1390
|
+
else transport?.detach();
|
|
1292
1391
|
return null;
|
|
1293
1392
|
}
|
|
1294
1393
|
this._presenceLease = transport || null;
|
|
@@ -1299,8 +1398,11 @@ class LiveSync {
|
|
|
1299
1398
|
return loadEdit().then((mod) => {
|
|
1300
1399
|
if (this.isDestroyed || this._startGen !== gen || this._editing) return;
|
|
1301
1400
|
this._editing = new mod.EditingSender({});
|
|
1302
|
-
this._editing.
|
|
1303
|
-
|
|
1401
|
+
if (this._presenceQuiesced) this._editing.stop();
|
|
1402
|
+
else {
|
|
1403
|
+
this._editing.start();
|
|
1404
|
+
this._editing.roster(this._lastRoster);
|
|
1405
|
+
}
|
|
1304
1406
|
});
|
|
1305
1407
|
});
|
|
1306
1408
|
}
|
|
@@ -1917,6 +2019,9 @@ class LiveSync {
|
|
|
1917
2019
|
ignoreAttribute: (el, name, merged) => isRootLocalAttr(el, name, merged) || (derivedActive() && isDerivedAttr(merged || el, name)),
|
|
1918
2020
|
attributeMerge,
|
|
1919
2021
|
protectFocusedValue: true,
|
|
2022
|
+
// Focus inside ClayJS's own UI (a shadow root) has nothing on the page to put back,
|
|
2023
|
+
// and restoring the stale page selection would pull focus out of the field.
|
|
2024
|
+
restoreFocus: document.activeElement?.localName !== 'clay-editor-ui',
|
|
1920
2025
|
keepLiveOnly: source === 'peer',
|
|
1921
2026
|
scripts: { mergeTags: loadedEngine().mergeTagRecognizers },
|
|
1922
2027
|
hooks: { beforeAttributeUpdated },
|
|
@@ -2020,7 +2125,9 @@ class LiveSync {
|
|
|
2020
2125
|
} finally {
|
|
2021
2126
|
lineageCapture.finish(conflicts.list());
|
|
2022
2127
|
}
|
|
2023
|
-
if (
|
|
2128
|
+
if (this.lane === 'live' && report.localDiverged) {
|
|
2129
|
+
gateMarkDirty();
|
|
2130
|
+
} else if (dirty && !report.localDiverged) {
|
|
2024
2131
|
gateClearIfUnchanged(token);
|
|
2025
2132
|
}
|
|
2026
2133
|
// Synthetic ids converge: every live element the frame named takes the
|
|
@@ -2499,6 +2606,26 @@ class LiveSync {
|
|
|
2499
2606
|
}
|
|
2500
2607
|
}
|
|
2501
2608
|
|
|
2609
|
+
// The bytes the host just accepted, exactly as they went out, and the capture they came
|
|
2610
|
+
// from: a save adds the format marker on its way out, so only these say the floor.
|
|
2611
|
+
let lastAccepted = null;
|
|
2612
|
+
onSaveAccepted((html, accepted) => {
|
|
2613
|
+
lastAccepted = typeof html === 'string' ? { html, captureId: accepted?.captureId ?? null } : null;
|
|
2614
|
+
});
|
|
2615
|
+
|
|
2616
|
+
const acceptedBytesFor = (captureId) =>
|
|
2617
|
+
lastAccepted && (lastAccepted.captureId ?? null) === (captureId ?? null) ? lastAccepted.html : null;
|
|
2618
|
+
|
|
2619
|
+
// A relay re-serializes the DOM, and that spelling is not the file's: a bare empty
|
|
2620
|
+
// attribute comes back as `=""`. When the relay describes the same document the host
|
|
2621
|
+
// stored, it carries the stored bytes themselves, so a peer reads what a reload would.
|
|
2622
|
+
// One that holds regions the file leaves out (`no-save`) keeps its own serialization.
|
|
2623
|
+
function asSent(html, sent) {
|
|
2624
|
+
if (typeof sent !== 'string' || sent === html) return html;
|
|
2625
|
+
const root = (source) => new DOMParser().parseFromString(source, 'text/html').documentElement;
|
|
2626
|
+
return root(html).isEqualNode(root(sent)) ? sent : html;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2502
2629
|
// Singleton instance
|
|
2503
2630
|
const liveSync = new LiveSync();
|
|
2504
2631
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { importMap } from "../vendor/hyper-morph-identity.vendor.js";
|
|
2
|
+
import { serializeForSync } from "../core/snapshot.js";
|
|
3
|
+
|
|
4
|
+
const PAGE = /^page(\.[0-9]+)?$/;
|
|
5
|
+
const tags = (root) => [...root.querySelectorAll('script[merge][clay~="live"]')]
|
|
6
|
+
.filter((el) => PAGE.test(el.getAttribute("merge")));
|
|
7
|
+
|
|
8
|
+
export function pageRelay(pending, acceptedHtml, identity) {
|
|
9
|
+
// A capture with no page article has nothing to overlay, and a document without
|
|
10
|
+
// one is not parsed at all: nothing outside the article would change.
|
|
11
|
+
if (!pending.html.includes("data-page")) return pending;
|
|
12
|
+
const parse = (html) => new DOMParser().parseFromString(html, "text/html");
|
|
13
|
+
const outgoing = parse(pending.html);
|
|
14
|
+
const article = outgoing.querySelector("article[data-page]");
|
|
15
|
+
if (!article) return pending;
|
|
16
|
+
const accepted = parse(acceptedHtml);
|
|
17
|
+
const savedArticle = accepted.querySelector("article[data-page]");
|
|
18
|
+
if (!savedArticle) return null;
|
|
19
|
+
const oldIds = importMap(outgoing.documentElement, pending.identityMap);
|
|
20
|
+
|
|
21
|
+
const replace = (old, source) => {
|
|
22
|
+
const next = outgoing.importNode(source, true);
|
|
23
|
+
const id = oldIds.get(old);
|
|
24
|
+
if (id) oldIds.set(next, id);
|
|
25
|
+
old.replaceWith(next);
|
|
26
|
+
return next;
|
|
27
|
+
};
|
|
28
|
+
const nextArticle = replace(article, savedArticle);
|
|
29
|
+
const prior = new Map(tags(outgoing).map((el) => [el.getAttribute("merge"), el]));
|
|
30
|
+
for (const saved of tags(accepted)) {
|
|
31
|
+
const name = saved.getAttribute("merge");
|
|
32
|
+
const old = prior.get(name);
|
|
33
|
+
if (old) replace(old, saved);
|
|
34
|
+
else nextArticle.before(outgoing.importNode(saved, true));
|
|
35
|
+
prior.delete(name);
|
|
36
|
+
}
|
|
37
|
+
for (const old of prior.values()) old.remove();
|
|
38
|
+
const title = outgoing.querySelector("head > title");
|
|
39
|
+
const savedTitle = accepted.querySelector("head > title");
|
|
40
|
+
if (title && savedTitle) replace(title, savedTitle);
|
|
41
|
+
else if (savedTitle) outgoing.head.append(outgoing.importNode(savedTitle, true));
|
|
42
|
+
else title?.remove();
|
|
43
|
+
|
|
44
|
+
const root = outgoing.documentElement;
|
|
45
|
+
const identityMap = identity.exportMap(root, (node) => {
|
|
46
|
+
const id = oldIds.get(node);
|
|
47
|
+
if (!id) return null;
|
|
48
|
+
identity.adopt(node, id);
|
|
49
|
+
return node;
|
|
50
|
+
});
|
|
51
|
+
return { ...pending, html: serializeForSync(root), identityMap };
|
|
52
|
+
}
|
|
@@ -60,6 +60,11 @@ export class PresenceStore {
|
|
|
60
60
|
this.timer = null;
|
|
61
61
|
this.timerKind = null;
|
|
62
62
|
this.inflight = false;
|
|
63
|
+
// The lease the request in flight belongs to, and the target it carried. A
|
|
64
|
+
// request a lease left behind is its own: giving the lease back resets these, so
|
|
65
|
+
// the next one starts unblocked and a late answer changes nothing.
|
|
66
|
+
this.inflightOwner = null;
|
|
67
|
+
this.inflightKey = null;
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
/**
|
|
@@ -253,21 +258,37 @@ export class PresenceStore {
|
|
|
253
258
|
},
|
|
254
259
|
detach() {
|
|
255
260
|
if (store.owner !== lease) return;
|
|
261
|
+
// A clear this lease asked for and the host never heard: the last word it
|
|
262
|
+
// holds is a target, and nothing after this lease will take it back.
|
|
263
|
+
const unsent = store.heldKey === '' && typeof store.inflightKey === 'string' && store.inflightKey !== '';
|
|
256
264
|
store.owner = null;
|
|
257
265
|
store.session = null;
|
|
258
266
|
store.n = 0;
|
|
259
267
|
store.sentKey = null;
|
|
268
|
+
// The request this lease left behind is its own: the next lease starts
|
|
269
|
+
// unblocked, and the answer that arrives late releases nothing.
|
|
270
|
+
store.inflight = false;
|
|
271
|
+
store.inflightOwner = null;
|
|
272
|
+
store.inflightKey = null;
|
|
260
273
|
store.cancel();
|
|
261
274
|
store.target = null;
|
|
262
275
|
store.heldKey = '';
|
|
263
276
|
store.explicit = false;
|
|
264
277
|
store.clear();
|
|
278
|
+
if (unsent) {
|
|
279
|
+
try {
|
|
280
|
+
Promise.resolve(lease.send({ editing: null })).catch(() => {});
|
|
281
|
+
} catch {}
|
|
282
|
+
}
|
|
265
283
|
},
|
|
266
284
|
};
|
|
267
285
|
this.owner = lease;
|
|
268
286
|
this.session = null;
|
|
269
287
|
this.n = 0;
|
|
270
288
|
this.sentKey = null;
|
|
289
|
+
this.inflight = false;
|
|
290
|
+
this.inflightOwner = null;
|
|
291
|
+
this.inflightKey = null;
|
|
271
292
|
this.cancel();
|
|
272
293
|
// A fresh connection starts from nothing, so the target this tab holds is sent
|
|
273
294
|
// again rather than assumed delivered.
|
|
@@ -284,7 +305,7 @@ export class PresenceStore {
|
|
|
284
305
|
*
|
|
285
306
|
* @param {{send?: ?function(Object): any}} [options] - the relay's send, when
|
|
286
307
|
* this tab may publish at all. A reader passes none.
|
|
287
|
-
* @returns {Promise<?{active: boolean, detach: function(): void}>}
|
|
308
|
+
* @returns {Promise<?{active: boolean, detach: function(): void, stop?: function(): void}>}
|
|
288
309
|
*/
|
|
289
310
|
connect({ send = null } = {}) {
|
|
290
311
|
if (this.connecting) return this.connecting;
|
|
@@ -297,8 +318,15 @@ export class PresenceStore {
|
|
|
297
318
|
(extensions.includes('page-awareness-v1') || extensions.includes('record-awareness-v1'))
|
|
298
319
|
) {
|
|
299
320
|
const scope = typeof awareness.scope === 'string' && awareness.scope ? awareness.scope : null;
|
|
300
|
-
return import('./local-presence.js')
|
|
301
|
-
|
|
321
|
+
return import('./local-presence.js').then(({ localPresence }) => {
|
|
322
|
+
const session = localPresence({ scope, store: this });
|
|
323
|
+
const lease = session.start();
|
|
324
|
+
if (!lease) return null;
|
|
325
|
+
// The lease stays the transport this document holds, so it is still the
|
|
326
|
+
// store's one owner; `stop` is the session's, and stopping it is what
|
|
327
|
+
// takes the heartbeat, the channel and the goodbye with the session.
|
|
328
|
+
return Object.defineProperty(lease, 'stop', { value: () => session.stop() });
|
|
329
|
+
});
|
|
302
330
|
}
|
|
303
331
|
if (typeof send === 'function' && extensions.includes('presence-editing')) {
|
|
304
332
|
return this.attachTransport({ send });
|
|
@@ -382,10 +410,17 @@ export class PresenceStore {
|
|
|
382
410
|
this.cancel();
|
|
383
411
|
this.sentKey = this.heldKey;
|
|
384
412
|
this.inflight = true;
|
|
413
|
+
this.inflightOwner = owner;
|
|
414
|
+
this.inflightKey = this.sentKey;
|
|
385
415
|
if (body.v === PRESENCE_VERSION) this.n = n;
|
|
386
416
|
|
|
387
417
|
const settle = (ok) => {
|
|
418
|
+
// A request a lease that has since let the tab go is not this store's to
|
|
419
|
+
// answer for: the newer owner's request, or the final clear, is what stands.
|
|
420
|
+
if (this.inflightOwner !== owner) return;
|
|
388
421
|
this.inflight = false;
|
|
422
|
+
this.inflightOwner = null;
|
|
423
|
+
this.inflightKey = null;
|
|
389
424
|
if (this.owner !== owner) return;
|
|
390
425
|
if (this.heldKey !== this.sentKey) {
|
|
391
426
|
// A newer target arrived while this one was in flight: the latest wins.
|
|
@@ -408,5 +443,10 @@ export default presence;
|
|
|
408
443
|
// sender an editor starts with — and both find the same store here.
|
|
409
444
|
if (typeof window !== 'undefined') {
|
|
410
445
|
window.clay = window.clay || {};
|
|
411
|
-
if (!window.clay.presence)
|
|
446
|
+
if (!window.clay.presence) {
|
|
447
|
+
window.clay.presence = presence;
|
|
448
|
+
try {
|
|
449
|
+
document.dispatchEvent(new CustomEvent('clay:presence-ready', { detail: { presence } }));
|
|
450
|
+
} catch {}
|
|
451
|
+
}
|
|
412
452
|
}
|
package/src/sync/presence.js
CHANGED
|
@@ -62,6 +62,7 @@ function loadUI() {
|
|
|
62
62
|
uiLoad ??= Promise.all([import('../ui/bevel-controls.js'), import('../ui/bevel.js')])
|
|
63
63
|
.then(([controls, bevel]) => (ui = {
|
|
64
64
|
pageScheme: controls.pageScheme,
|
|
65
|
+
paintLook: controls.paintLook,
|
|
65
66
|
RUNTIME_ONLY: controls.RUNTIME_ONLY,
|
|
66
67
|
TOKENS: bevel.TOKENS,
|
|
67
68
|
SHADOW: bevel.SHADOW,
|
|
@@ -149,7 +150,7 @@ class Presence {
|
|
|
149
150
|
this.roster = next;
|
|
150
151
|
if (!(await this.supported())) return;
|
|
151
152
|
if (same) {
|
|
152
|
-
|
|
153
|
+
ui.paintLook(this.root);
|
|
153
154
|
return;
|
|
154
155
|
}
|
|
155
156
|
await this.render();
|
|
@@ -174,7 +175,8 @@ class Presence {
|
|
|
174
175
|
// total either way: a recipient the host would not name gets people: [] and
|
|
175
176
|
// everyone in `anonymous`, so this sum is the same participant count in both
|
|
176
177
|
// directions.
|
|
177
|
-
|
|
178
|
+
// The page menu draws who is here inside its own dock, so the corner stays empty.
|
|
179
|
+
if (people.length + anonymous <= 1 || window.clay?.menu) {
|
|
178
180
|
this.hide();
|
|
179
181
|
return;
|
|
180
182
|
}
|
|
@@ -182,7 +184,6 @@ class Presence {
|
|
|
182
184
|
if (!this.build()) return;
|
|
183
185
|
|
|
184
186
|
this.hideTip();
|
|
185
|
-
set(this.root, 'color-scheme', ui.pageScheme());
|
|
186
187
|
this.stack.textContent = '';
|
|
187
188
|
for (const person of people) {
|
|
188
189
|
this.stack.appendChild(this.avatar(person));
|
|
@@ -199,6 +200,7 @@ class Presence {
|
|
|
199
200
|
set(this.count, 'display', 'none');
|
|
200
201
|
}
|
|
201
202
|
set(this.root, 'display', 'flex');
|
|
203
|
+
ui.paintLook(this.root);
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
hide() {
|
|
@@ -307,6 +309,7 @@ class Presence {
|
|
|
307
309
|
}
|
|
308
310
|
|
|
309
311
|
const presence = new Presence();
|
|
312
|
+
if (typeof document !== 'undefined') document.addEventListener('clay:menu-ready', () => presence.render());
|
|
310
313
|
|
|
311
314
|
export { presence, Presence };
|
|
312
315
|
export default presence;
|
package/src/sync/saved-stream.js
CHANGED
|
@@ -206,7 +206,7 @@ export function createSavedStream({ documentURL, servedEtag = null, represented
|
|
|
206
206
|
|
|
207
207
|
// A frame that lands while the download is in flight waits in order, and a
|
|
208
208
|
// download that failed keeps the queue so the next frame asks again.
|
|
209
|
-
function takePresence(data) {
|
|
209
|
+
function takePresence(data, gen) {
|
|
210
210
|
if (presenceRoster) {
|
|
211
211
|
presenceRoster.update(data);
|
|
212
212
|
return;
|
|
@@ -215,6 +215,9 @@ export function createSavedStream({ documentURL, servedEtag = null, represented
|
|
|
215
215
|
if (presenceLoad) return;
|
|
216
216
|
presenceLoad = Promise.all([import('./presence-store.js'), import('./presence.js')])
|
|
217
217
|
.then(([storeMod, rosterMod]) => {
|
|
218
|
+
// The download outlived the reader that asked for it: a stop (or a newer start)
|
|
219
|
+
// took the stream away, so this load attaches no lease and draws no roster.
|
|
220
|
+
if (!active(gen)) return null;
|
|
218
221
|
presenceStore = storeMod.presence;
|
|
219
222
|
presenceRoster = rosterMod.presence;
|
|
220
223
|
presenceLease = presenceStore.attachTransport({ send: () => Promise.resolve(false) });
|
|
@@ -345,10 +348,12 @@ export function createSavedStream({ documentURL, servedEtag = null, represented
|
|
|
345
348
|
}
|
|
346
349
|
|
|
347
350
|
function acceptBody(html, reason, responseEtag) {
|
|
348
|
-
// The
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
|
|
351
|
+
// The served root attribute names the body; the response's own stamp only when the
|
|
352
|
+
// body carries none, since a server or proxy may add an HTTP ETag of its own. A weak
|
|
353
|
+
// ETag is never a document stamp (spec §4): it is how Express names any response. A
|
|
354
|
+
// discovery stamp is never attached, and identical bytes already named by a stamp
|
|
355
|
+
// keep that name.
|
|
356
|
+
const bodyStamp = readBodyStamp(html) ?? (/^W\//.test(responseEtag ?? '') ? null : stampOf(responseEtag));
|
|
352
357
|
const etag = bodyStamp ?? (html === receipt.bytes ? receipt.etag : null);
|
|
353
358
|
const seq = reason.kind === 'startup' ? receipt.seq : reason.seq ?? receipt.seq;
|
|
354
359
|
if (!accept({ bytes: html, etag, seq })) return;
|
|
@@ -523,7 +528,7 @@ export function createSavedStream({ documentURL, servedEtag = null, represented
|
|
|
523
528
|
} catch {
|
|
524
529
|
return;
|
|
525
530
|
}
|
|
526
|
-
if (data && typeof data === 'object' && !Array.isArray(data)) takePresence(data);
|
|
531
|
+
if (data && typeof data === 'object' && !Array.isArray(data)) takePresence(data, gen);
|
|
527
532
|
});
|
|
528
533
|
return handle;
|
|
529
534
|
});
|
package/src/ui/bevel-controls.js
CHANGED
package/src/ui/bevel-dialog.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// close. It holds no behaviour. The modal and the crop adapter own focus, Escape,
|
|
3
3
|
// backdrop clicks and settling, so each keeps the rules it already had.
|
|
4
4
|
import { style, set } from "../lib/hostile-css.js";
|
|
5
|
-
import { bevelBox, bevelSurface, bevelText,
|
|
5
|
+
import { bevelBox, bevelSurface, bevelText, uiLook, protectIcon, restyleBox } from "./bevel-controls.js";
|
|
6
6
|
import { TOKENS, FONT_SANS, RULES, GLYPHS } from "./bevel.js";
|
|
7
7
|
import { themed, tokenRulesFor, partRules, markParts } from "./theme-parts.js";
|
|
8
8
|
|
|
@@ -103,7 +103,10 @@ export function bevelDialog({ zIndex = "100", width = "600px", closable = false,
|
|
|
103
103
|
root.setAttribute("data-clay-modal", "");
|
|
104
104
|
if (on) root.setAttribute("data-clay-ui", surface);
|
|
105
105
|
root.setAttribute("aria-hidden", "true");
|
|
106
|
-
|
|
106
|
+
const known = on && theme.scheme !== "light dark";
|
|
107
|
+
const panelWidth = Math.min(parseFloat(width) || 600, innerWidth - 32);
|
|
108
|
+
const area = { x: (innerWidth - panelWidth) / 2, y: Math.min(96, innerHeight * 0.1), width: panelWidth, height: 160 };
|
|
109
|
+
set(root, "color-scheme", known ? theme.scheme : uiLook(area).scheme);
|
|
107
110
|
|
|
108
111
|
const overlay = bevelBox("div", [
|
|
109
112
|
"position:fixed", "inset:0", `z-index:${zIndex}`, "overflow:auto",
|