@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/core/save-core.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* For full save system with state management, use save.js instead.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { notifySaveAccepted } from "./save-accepted.js";
|
|
11
|
+
export { onSaveAccepted } from "./save-accepted.js";
|
|
10
12
|
import { isEditMode, isDraftMode } from "./is-edit-mode.js";
|
|
11
13
|
import { draftTransport } from "./draft-mode.js";
|
|
12
14
|
import { consumeUserDriven, consumeExplicitSave, markUserDriven } from "../lib/user-gesture.js";
|
|
@@ -15,6 +17,10 @@ import { lastSeenEtag, conditionalSaves, recordEtag } from "./etag.js";
|
|
|
15
17
|
import { hostMeta } from "./host-meta.js";
|
|
16
18
|
import { saveSize, storedFile, knownSaveLimit, saveLimit, announceSaveSize } from "./save-size.js";
|
|
17
19
|
import { PAGE_FORMAT, PAGE_FORMAT_HEADER, knownPageFormatFloor, withPageFormatFloor } from "./page-format.js";
|
|
20
|
+
|
|
21
|
+
// Re-exported for the save lane: it reads the floor and writes the marker these bytes
|
|
22
|
+
// carry, without a second import of the format module.
|
|
23
|
+
export { PAGE_FORMAT_META, knownPageFormatFloor } from "./page-format.js";
|
|
18
24
|
import "./runtime-url.js";
|
|
19
25
|
import {
|
|
20
26
|
getPageContents,
|
|
@@ -30,11 +36,27 @@ import {
|
|
|
30
36
|
// =============================================================================
|
|
31
37
|
|
|
32
38
|
let saveInProgress = false;
|
|
39
|
+
// Who holds the lane while a save prepares. A capture that will send later takes the lane
|
|
40
|
+
// before its first await, so no second save can start work on the page it is reading; the
|
|
41
|
+
// token is what lets that same save enter `runSave` while its own claim still stands.
|
|
42
|
+
let laneHolder = null;
|
|
33
43
|
const SAVE_PATH = '/_/save';
|
|
34
44
|
const SAVE_TIMEOUT_MS = 12000;
|
|
35
45
|
// The browser caps a keepalive body (Chromium refuses one over 64 KB), and it counts
|
|
36
46
|
// BYTES: the hint is only attached to a save whose UTF-8 encoding fits well inside that.
|
|
37
47
|
const KEEPALIVE_MAX_BODY = 60000;
|
|
48
|
+
// Above this, a save body travels as a Blob instead of a string, so the document is
|
|
49
|
+
// encoded once here rather than again inside fetch, and a retry of the same attempt
|
|
50
|
+
// reuses that Blob. Counted in UTF-16 units, which are never more than the UTF-8 bytes
|
|
51
|
+
// they encode to, so a body under the threshold by this measure is under it in bytes.
|
|
52
|
+
const SAVE_BLOB_MIN_BYTES = 1024 * 1024;
|
|
53
|
+
// The media type the string body has always arrived with, written out explicitly
|
|
54
|
+
// because a Blob's own type never reaches the wire as a request header. text/plain is
|
|
55
|
+
// CORS-safelisted, so this is still the same un-preflighted request.
|
|
56
|
+
const SAVE_BODY_TYPE = 'text/plain;charset=UTF-8';
|
|
57
|
+
// The one body a Blob was last built for, kept so the retry of a large save sends the
|
|
58
|
+
// bytes it already encoded instead of encoding the document a second time.
|
|
59
|
+
let lastBodyBlob = null;
|
|
38
60
|
// Set by `withKeepalive`, read once by the next `sendOnce`: the hint belongs to one
|
|
39
61
|
// request, not to the save pipeline.
|
|
40
62
|
let nextSaveKeepalive = false;
|
|
@@ -48,7 +70,6 @@ let nextSaveKeepalive = false;
|
|
|
48
70
|
//
|
|
49
71
|
// Not a DOM event, deliberately. The whole document rides in the argument, and a
|
|
50
72
|
// CustomEvent would put it on a bus any script on the page can listen to.
|
|
51
|
-
const saveAcceptedHooks = [];
|
|
52
73
|
|
|
53
74
|
// Headers another module asks every save to carry, by name.
|
|
54
75
|
const extraSaveHeaders = new Map();
|
|
@@ -93,32 +114,37 @@ export function isSaveInProgress() {
|
|
|
93
114
|
}
|
|
94
115
|
|
|
95
116
|
/**
|
|
96
|
-
*
|
|
117
|
+
* Take the save lane for a capture that will prepare before it can send.
|
|
97
118
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
119
|
+
* Runs in the caller's own task, before its first await, so a second save asked for while
|
|
120
|
+
* the first is preparing records intent instead of capturing the page again. The token
|
|
121
|
+
* comes back opaque: only the holder can enter `runSave` with it, and only the holder
|
|
122
|
+
* (or `runSave` settling that request) gives the lane back.
|
|
101
123
|
*
|
|
102
|
-
* @
|
|
124
|
+
* @returns {?Object} the owner token, or null when a save already holds the lane
|
|
103
125
|
*/
|
|
104
|
-
export function
|
|
105
|
-
|
|
126
|
+
export function reserveSaveLane() {
|
|
127
|
+
if (saveInProgress) return null;
|
|
128
|
+
saveInProgress = true;
|
|
129
|
+
laneHolder = { reserved: true };
|
|
130
|
+
return laneHolder;
|
|
106
131
|
}
|
|
107
132
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Give the lane back after a preparation that took it. Idempotent, and harmless once the
|
|
135
|
+
* save it was reserved for has settled the lane itself.
|
|
136
|
+
*
|
|
137
|
+
* @param {Object} owner - the token `reserveSaveLane` returned
|
|
138
|
+
*/
|
|
139
|
+
export function releaseSaveLane(owner) {
|
|
140
|
+
if (!owner || laneHolder !== owner) return;
|
|
141
|
+
laneHolder = null;
|
|
142
|
+
saveInProgress = false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Whether the next save rides a keepalive hint, which an unload flush cannot wait on. */
|
|
146
|
+
export function keepaliveHeld() {
|
|
147
|
+
return nextSaveKeepalive;
|
|
122
148
|
}
|
|
123
149
|
|
|
124
150
|
// Listeners for "this exact body is about to leave". One call per real fetch, with
|
|
@@ -482,6 +508,10 @@ function formatConversionHeader(conversion) {
|
|
|
482
508
|
* carrying the document alongside an unstripped snapshot; the snapshot's home is
|
|
483
509
|
* the §10 relay, which the live-sync plugin already posts it to.
|
|
484
510
|
*
|
|
511
|
+
* Above `SAVE_BLOB_MIN_BYTES` the body is a Blob wrapping that same one string, with the
|
|
512
|
+
* same media type written out explicitly: a different container for identical bytes,
|
|
513
|
+
* which is what keeps the encoding off the main thread's critical path.
|
|
514
|
+
*
|
|
485
515
|
* @param {string} content - HTML to save
|
|
486
516
|
* @param {boolean} userDriven - Whether a human gesture is behind this save
|
|
487
517
|
* @param {AbortSignal} signal
|
|
@@ -517,11 +547,15 @@ export function buildSaveRequest(content, userDriven, signal, saveId, etag, keep
|
|
|
517
547
|
// authorization, and refuses a lower one before mutation.
|
|
518
548
|
[PAGE_FORMAT_HEADER]: String(PAGE_FORMAT)
|
|
519
549
|
},
|
|
520
|
-
body: content
|
|
550
|
+
body: bodyForSave(content)
|
|
521
551
|
};
|
|
522
552
|
|
|
523
553
|
for (const [name, value] of extraSaveHeaders) options.headers[name] = value;
|
|
524
554
|
|
|
555
|
+
// Set after the process-global headers so the type describing THESE bytes is the one
|
|
556
|
+
// that goes out, the same rule the conversion header below follows.
|
|
557
|
+
if (typeof options.body !== 'string') options.headers['Content-Type'] = SAVE_BODY_TYPE;
|
|
558
|
+
|
|
525
559
|
// The conversion request belongs to ONE logical save, and goes on every attempt
|
|
526
560
|
// of it. Set after the process-global headers so the capture-bound value is the
|
|
527
561
|
// one that describes these bytes.
|
|
@@ -529,11 +563,34 @@ export function buildSaveRequest(content, userDriven, signal, saveId, etag, keep
|
|
|
529
563
|
|
|
530
564
|
if (etag) options.headers['If-Match'] = etag;
|
|
531
565
|
|
|
532
|
-
|
|
566
|
+
// The length test comes first because it is a floor on the byte count: a body too
|
|
567
|
+
// long by this measure is too big in bytes too, and never has to be encoded to find
|
|
568
|
+
// out. A keepalive body is always under 64 KB, so it never reaches the Blob path.
|
|
569
|
+
if (keepalive && content.length < KEEPALIVE_MAX_BODY && new Blob([content]).size < KEEPALIVE_MAX_BODY) options.keepalive = true;
|
|
533
570
|
|
|
534
571
|
return { url: resolveSaveUrl(path), options };
|
|
535
572
|
}
|
|
536
573
|
|
|
574
|
+
/**
|
|
575
|
+
* The body of one save: the string itself, or, above `SAVE_BLOB_MIN_BYTES`, a Blob
|
|
576
|
+
* wrapping that one string with no further joining.
|
|
577
|
+
*
|
|
578
|
+
* The point is where the encoding happens. A string body is encoded to UTF-8 inside
|
|
579
|
+
* `fetch`, synchronously and on the main thread; at 11 MB that is the visible stall.
|
|
580
|
+
* A Blob carries the same bytes with the same media type, and building it once here
|
|
581
|
+
* means the retry of the same attempt sends the very Blob it already built.
|
|
582
|
+
*
|
|
583
|
+
* @param {string} content - HTML to save
|
|
584
|
+
* @returns {string|Blob}
|
|
585
|
+
*/
|
|
586
|
+
function bodyForSave(content) {
|
|
587
|
+
if (content.length < SAVE_BLOB_MIN_BYTES) return content;
|
|
588
|
+
if (lastBodyBlob && lastBodyBlob.content === content) return lastBodyBlob.blob;
|
|
589
|
+
const blob = new Blob([content], { type: SAVE_BODY_TYPE });
|
|
590
|
+
lastBodyBlob = { content, blob };
|
|
591
|
+
return blob;
|
|
592
|
+
}
|
|
593
|
+
|
|
537
594
|
/**
|
|
538
595
|
* The stamp to send as If-Match, or null for a save that goes out unconditional.
|
|
539
596
|
*
|
|
@@ -885,7 +942,7 @@ async function sendSave(content, { captureId = null, conversionHeader = null, at
|
|
|
885
942
|
// `pageFormatFloor` is internal: one save may carry a floor this tab has not
|
|
886
943
|
// noted yet, which is how the format gate's activation writes the marker. It
|
|
887
944
|
// raises these bytes only, never what the tab knows the document at.
|
|
888
|
-
function runSave(html, captureId, callback, conversion, capture = null, pageFormatFloor = 0) {
|
|
945
|
+
function runSave(html, captureId, callback, conversion, capture = null, pageFormatFloor = 0, lane = null) {
|
|
889
946
|
// A capture taken before the floor rose cannot drop the marker by omitting it:
|
|
890
947
|
// the bytes that go out carry the floor this tab last saw. At floor 0 this is
|
|
891
948
|
// the string itself, unchanged.
|
|
@@ -897,6 +954,10 @@ function runSave(html, captureId, callback, conversion, capture = null, pageForm
|
|
|
897
954
|
// recovered through the host's receipt reaches `accepted` like any other success,
|
|
898
955
|
// so it adopts the artifact of the request that landed.
|
|
899
956
|
const artifact = captureArtifact(capture);
|
|
957
|
+
// A prepared save already holds the lane: `saveInProgress` is this call's own claim,
|
|
958
|
+
// and its preparation gives it back once this save's bookkeeping is done.
|
|
959
|
+
const holdsLane = () => lane !== null && laneHolder === lane;
|
|
960
|
+
const releaseOwnLane = () => { if (!holdsLane()) saveInProgress = false; };
|
|
900
961
|
const finished = (result, accepted = false) => {
|
|
901
962
|
if (accepted) notifySaveAccepted(html, captureId ?? null, artifact);
|
|
902
963
|
releaseCaptureArtifact(capture);
|
|
@@ -913,7 +974,7 @@ function runSave(html, captureId, callback, conversion, capture = null, pageForm
|
|
|
913
974
|
if (!isEditMode && !window.clay?.testMode) {
|
|
914
975
|
return finished(skippedResult('Not in edit mode'));
|
|
915
976
|
}
|
|
916
|
-
if (saveInProgress) {
|
|
977
|
+
if (saveInProgress && !holdsLane()) {
|
|
917
978
|
return finished(skippedResult('Save already in progress'));
|
|
918
979
|
}
|
|
919
980
|
|
|
@@ -929,7 +990,7 @@ function runSave(html, captureId, callback, conversion, capture = null, pageForm
|
|
|
929
990
|
// Test mode: skip network request, return mock success
|
|
930
991
|
if (window.clay?.testMode) {
|
|
931
992
|
setTimeout(() => {
|
|
932
|
-
|
|
993
|
+
releaseOwnLane();
|
|
933
994
|
finished(successResult({ msg: 'Test mode: save skipped' }));
|
|
934
995
|
}, 0);
|
|
935
996
|
return;
|
|
@@ -945,7 +1006,7 @@ function runSave(html, captureId, callback, conversion, capture = null, pageForm
|
|
|
945
1006
|
const size = saveSize(storedFile(html), knownSaveLimit());
|
|
946
1007
|
announceSaveSize(size);
|
|
947
1008
|
if (size.state === 'over') {
|
|
948
|
-
|
|
1009
|
+
releaseOwnLane();
|
|
949
1010
|
return finished(tooLargeResult(size));
|
|
950
1011
|
}
|
|
951
1012
|
|
|
@@ -955,7 +1016,7 @@ function runSave(html, captureId, callback, conversion, capture = null, pageForm
|
|
|
955
1016
|
// follow-up save can start it immediately rather than being told the lane
|
|
956
1017
|
// is busy by the save that just finished.
|
|
957
1018
|
.then(result => {
|
|
958
|
-
|
|
1019
|
+
releaseOwnLane();
|
|
959
1020
|
// A host's 413 is the same refusal the guard above makes, so it carries the
|
|
960
1021
|
// same measurement. Its own answer about the size wins over nothing: the bytes
|
|
961
1022
|
// are this attempt's, and the limit is still only what discovery advertised.
|
|
@@ -998,8 +1059,8 @@ export function saveHtml(html, callback = () => {}) {
|
|
|
998
1059
|
* @param {Object} [options.conversion] - {source:'file', base} or {source:'room', base, epoch}
|
|
999
1060
|
* @returns {Promise<Object>}
|
|
1000
1061
|
*/
|
|
1001
|
-
export function saveCapturedHtml(html, captureId, callback = () => {}, { conversion, capture = null, pageFormatFloor } = {}) {
|
|
1002
|
-
return runSave(html, captureId, callback, conversion, capture, pageFormatFloor);
|
|
1062
|
+
export function saveCapturedHtml(html, captureId, callback = () => {}, { conversion, capture = null, pageFormatFloor, lane = null } = {}) {
|
|
1063
|
+
return runSave(html, captureId, callback, conversion, capture, pageFormatFloor, lane);
|
|
1003
1064
|
}
|
|
1004
1065
|
|
|
1005
1066
|
/**
|
|
@@ -1063,3 +1124,17 @@ export function replacePageWith(url, callback = () => {}) {
|
|
|
1063
1124
|
});
|
|
1064
1125
|
});
|
|
1065
1126
|
}
|
|
1127
|
+
|
|
1128
|
+
// The exit's own page checkpoint, published by save.js and read by the live plugin's
|
|
1129
|
+
// exit flush. It lives here because save.js is mocked by name in most page suites,
|
|
1130
|
+
// while this module is not: the slot lets an exit save a page without either module
|
|
1131
|
+
// importing the other's internals.
|
|
1132
|
+
let exitSaveFn = null;
|
|
1133
|
+
|
|
1134
|
+
export function setExitSave(fn) {
|
|
1135
|
+
exitSaveFn = fn;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
export function exitSave() {
|
|
1139
|
+
return exitSaveFn;
|
|
1140
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* save-job.js — a save capture's remaining work as a resumable job.
|
|
3
|
+
*
|
|
4
|
+
* `beginSaveCapture` takes the one atomic look at the live page (`prepareSaveCapture`,
|
|
5
|
+
* which stays on the eager side because every save needs it) and hands back a job whose
|
|
6
|
+
* rest — the three serializations, the format marker and the renderer's verification — is
|
|
7
|
+
* a generator. Nothing else here decides anything: what the job must not do while it is
|
|
8
|
+
* half verified, and whether its bytes may go out, are the caller's (`core/save.js`).
|
|
9
|
+
*
|
|
10
|
+
* This module is reached only through the dynamic import that a save first takes the
|
|
11
|
+
* sliced path with, so a page whose documents are small never loads it and the Document
|
|
12
|
+
* editor's ready path never pays for it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
completeSaveCapture,
|
|
17
|
+
prepareSaveCapture,
|
|
18
|
+
readCapturePageState,
|
|
19
|
+
releaseSaveCapture,
|
|
20
|
+
runSaveRenderer
|
|
21
|
+
} from './snapshot.js';
|
|
22
|
+
import { serializeCloneSteps, serializeForCompareSteps } from './serialize-steps.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Begin one save capture: everything that must see the live page in this call, then a job
|
|
26
|
+
* whose remaining work is `job.steps()`. The job holds every value that work reads, so a
|
|
27
|
+
* page edited between two steps cannot change the answer; `job.drop()` releases what it
|
|
28
|
+
* captured.
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} options
|
|
31
|
+
* @param {boolean} options.emitForSync - Whether to emit snapshot-ready event (default: true)
|
|
32
|
+
* @param {boolean} options.forRequest - These bytes go to a host, so the renderer may
|
|
33
|
+
* attach the verified model they came from, for the accepted save to adopt
|
|
34
|
+
* @param {?{name: string, content: string}} options.pageFormatMarker - The document format
|
|
35
|
+
* these bytes must declare, as the attributes of the marker element. It goes into the
|
|
36
|
+
* save clone after the comparison domains are read and before the renderer verifies, so
|
|
37
|
+
* the bytes verified are the bytes sent; the comparison baselines stay marker-free,
|
|
38
|
+
* exactly as they do on the one-task path
|
|
39
|
+
* @returns {{ captureId: number, steps: function(): Generator, drop: function(): void }}
|
|
40
|
+
*/
|
|
41
|
+
export function beginSaveCapture(options = {}) {
|
|
42
|
+
const pin = prepareSaveCapture({ ...options, sliced: true });
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
captureId: pin.captureId,
|
|
45
|
+
steps: () => saveSteps(pin),
|
|
46
|
+
drop: () => releaseSaveCapture(pin),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Declare the document format these bytes carry, in the save clone only.
|
|
52
|
+
*
|
|
53
|
+
* The same marker `withPageFormatFloor` puts in the final string, applied to the tree the
|
|
54
|
+
* renderer verifies so a prepared save cannot verify one document and send another. A
|
|
55
|
+
* clone already at that floor is left exactly as it is, because the string transform
|
|
56
|
+
* leaves such a document unchanged too.
|
|
57
|
+
*/
|
|
58
|
+
function setFormatMarker(clone, marker) {
|
|
59
|
+
const head = clone.querySelector('head');
|
|
60
|
+
if (!head) return;
|
|
61
|
+
const existing = clone.querySelector(`meta[name="${marker.name}"]`);
|
|
62
|
+
if (existing) {
|
|
63
|
+
if (existing.getAttribute('content') !== marker.content) existing.setAttribute('content', marker.content);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const meta = clone.ownerDocument.createElement('meta');
|
|
67
|
+
meta.setAttribute('name', marker.name);
|
|
68
|
+
meta.setAttribute('content', marker.content);
|
|
69
|
+
head.appendChild(meta);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The renderer as a generator, resumed with the capture it belongs to active and put back
|
|
74
|
+
* before every yield: this capture is never the active one while this task is not running
|
|
75
|
+
* it.
|
|
76
|
+
*/
|
|
77
|
+
function* rendererSteps(clone, today, pin) {
|
|
78
|
+
if (!pin.renderer) return today;
|
|
79
|
+
let gen = null;
|
|
80
|
+
for (;;) {
|
|
81
|
+
let step;
|
|
82
|
+
try {
|
|
83
|
+
step = runSaveRenderer(pin.context, () => {
|
|
84
|
+
gen ||= pin.rendererSteps ? pin.rendererSteps(clone, today) : false;
|
|
85
|
+
return gen ? gen.next() : { done: true, value: pin.renderer(clone, today) };
|
|
86
|
+
});
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error('clayjs: save renderer failed, sending the full serialization', err);
|
|
89
|
+
return today;
|
|
90
|
+
}
|
|
91
|
+
if (step.done) return step.value;
|
|
92
|
+
yield;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The resumable half of a save capture: three serializations that each yield too, then the renderer. */
|
|
97
|
+
function* saveSteps(pin) {
|
|
98
|
+
readCapturePageState(pin);
|
|
99
|
+
const today = yield* serializeCloneSteps(pin.clone);
|
|
100
|
+
yield;
|
|
101
|
+
const forComparison = pin.compareClone ? (yield* serializeForCompareSteps(pin.compareClone)) : null;
|
|
102
|
+
yield;
|
|
103
|
+
const forDirty = pin.dirtyClone ? (yield* serializeForCompareSteps(pin.dirtyClone)) : forComparison;
|
|
104
|
+
yield;
|
|
105
|
+
if (pin.pageFormatMarker) setFormatMarker(pin.clone, pin.pageFormatMarker);
|
|
106
|
+
const forSave = yield* rendererSteps(pin.clone, today, pin);
|
|
107
|
+
return completeSaveCapture(pin, { forSave, forComparison, forDirty });
|
|
108
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* save-prepare.js — a save capture's remaining work, run across tasks.
|
|
3
|
+
*
|
|
4
|
+
* A large document's verification is seconds of uninterrupted work, so a save that has
|
|
5
|
+
* to do it prepares across tasks instead: `beginSaveCapture` takes the one atomic
|
|
6
|
+
* snapshot in its caller's task, and this module resumes the job's steps, handing the
|
|
7
|
+
* event loop back whenever a slice has spent its budget. Typing during that wait saves
|
|
8
|
+
* the checkpoint that was captured and leaves later edits dirty.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here decides whether a save may go out: the caller owns the lane, the epochs
|
|
11
|
+
* and the request. This is the driver alone, so it stays off the editor's ready path —
|
|
12
|
+
* a page whose documents are small never loads it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { defaultYieldTask, runSliced } from "../lib/slice.js";
|
|
16
|
+
|
|
17
|
+
// The job itself is in the same lazily loaded graph: a save that prepares across tasks
|
|
18
|
+
// takes its capture here, so the generator it resumes is never on the ready path.
|
|
19
|
+
export { beginSaveCapture } from "./save-job.js";
|
|
20
|
+
|
|
21
|
+
const monotonic = () => (globalThis.performance ? globalThis.performance.now() : Date.now());
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resume a capture's steps, measuring the main-thread time they spend.
|
|
25
|
+
*
|
|
26
|
+
* `activeMs` is the sum of the slices' own execution time, never the wait between them:
|
|
27
|
+
* it is what the autosave scheduler is told about, so a slow host or a busy tab does not
|
|
28
|
+
* lengthen the cooldown.
|
|
29
|
+
*
|
|
30
|
+
* @param {Generator} steps - `job.steps()` from `beginSaveCapture`
|
|
31
|
+
* @param {Object} [options]
|
|
32
|
+
* @param {number} [options.budgetMs] - main-thread time one slice may spend before yielding
|
|
33
|
+
* @param {{aborted: boolean}} [options.signal] - read before every step; a job whose page
|
|
34
|
+
* changed underneath it stops here and hands back nothing
|
|
35
|
+
* @returns {Promise<{result: ?Object, aborted: boolean, activeMs: number}>}
|
|
36
|
+
*/
|
|
37
|
+
export async function runPreparedSteps(steps, { budgetMs = 4, signal = null } = {}) {
|
|
38
|
+
let activeMs = 0;
|
|
39
|
+
let mark = monotonic();
|
|
40
|
+
const yieldTask = async () => {
|
|
41
|
+
activeMs += monotonic() - mark;
|
|
42
|
+
await defaultYieldTask();
|
|
43
|
+
mark = monotonic();
|
|
44
|
+
};
|
|
45
|
+
let outcome;
|
|
46
|
+
try {
|
|
47
|
+
outcome = await runSliced(steps, { budgetMs, signal, yieldTask });
|
|
48
|
+
} finally {
|
|
49
|
+
activeMs += monotonic() - mark;
|
|
50
|
+
}
|
|
51
|
+
const aborted = outcome !== null && typeof outcome === "object" && outcome.aborted === true;
|
|
52
|
+
return { result: aborted ? null : outcome, aborted, activeMs };
|
|
53
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isEditMode } from "./is-edit-mode.js";
|
|
2
2
|
import { downloadStored } from "./conflict-download.js";
|
|
3
|
-
import { bevelSurface, bevelText, bevelButton, pageScheme, setShown } from "../ui/bevel-controls.js";
|
|
3
|
+
import { bevelSurface, bevelText, bevelButton, pageScheme, paintLook, setShown } from "../ui/bevel-controls.js";
|
|
4
|
+
import { TOKENS } from "../ui/bevel.js";
|
|
4
5
|
import { set } from "../lib/hostile-css.js";
|
|
5
6
|
|
|
6
7
|
// The one quiet word about how large this document is.
|
|
@@ -90,6 +91,7 @@ function render(size) {
|
|
|
90
91
|
// already recommends downloading one.
|
|
91
92
|
setShown(copy, size.state === "over", "inline-flex");
|
|
92
93
|
place();
|
|
94
|
+
paintLook(root, { background: TOKENS.surface });
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
function clear() {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* save-slices.js — how big a document has to be before its work hands the page back.
|
|
3
|
+
*
|
|
4
|
+
* Two walks ask the same question and must answer it the same way: a save preparing its
|
|
5
|
+
* bytes (core/save.js) and the source map installing or repairing its model
|
|
6
|
+
* (source/session.js). The number lives here, on its own, because core/save.js
|
|
7
|
+
* initializes the save system when it is imported: a source module that read the
|
|
8
|
+
* threshold from it would start the editor's discovery requests behind the source
|
|
9
|
+
* plugin's back, which is a fetch that plugin never asked for.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// A document whose last save was at least this big prepares its next one across tasks.
|
|
13
|
+
// Verifying a file that size is seconds of uninterrupted work, and every keystroke behind
|
|
14
|
+
// it waits; below the threshold the one-task capture is cheaper than the scheduling.
|
|
15
|
+
export const SLICED_SAVE_MIN_BYTES = 300 * 1024;
|