@panphora/clayjs 1.16.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 +72519 -59839
- package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
- package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
- package/dist/m/c-2GRUM77Q.js +30 -0
- package/dist/m/c-2KIEGNC4.js +1 -0
- package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
- package/dist/m/c-2YYDMLNZ.js +1 -0
- 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-VFX45IZQ.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-5T6NZHNM.js +1 -0
- package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
- package/dist/m/{c-Z36J63PP.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-AMYEFA2E.js +1 -0
- package/dist/m/c-AQYCG7QW.js +1 -0
- package/dist/m/c-ATKAOH23.js +1 -0
- package/dist/m/c-B3JM5ZXC.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-BIJALSRD.js +1 -0
- package/dist/m/c-BJ7YQ5QZ.js +1 -0
- package/dist/m/c-BPF2NSPV.js +1 -0
- package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
- package/dist/m/c-BUAYFEV5.js +1 -0
- package/dist/m/c-C6IPRG34.js +1 -0
- package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
- package/dist/m/{c-4DRMS4QG.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-7VGZ7RU5.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-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
- package/dist/m/c-HAWFNBYG.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-HRDCD3WN.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-IVTRVENZ.js +1 -0
- package/dist/m/c-IW6GGOU2.js +1 -0
- package/dist/m/c-JKZJIUIP.js +1 -0
- package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
- package/dist/m/c-JRKY44DK.js +1 -0
- package/dist/m/c-K3ADYNLU.js +1 -0
- package/dist/m/c-KBS3UV6B.js +1 -0
- package/dist/m/c-KGHVFRR3.js +1 -0
- package/dist/m/c-KJJPV77R.js +1 -0
- package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
- package/dist/m/{c-5Y6TNX5W.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-LR4CGEPT.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-NEL7HQW3.js +1 -0
- package/dist/m/c-NQJRVJ56.js +1 -0
- 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-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
- package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
- package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
- package/dist/m/c-P3OFYUMA.js +1 -0
- 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-SWRJFCNN.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-R27DWQKX.js +1 -0
- package/dist/m/{c-HVYXIS72.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-S2U6NJU4.js +1 -0
- package/dist/m/c-SPZPGD3R.js +1 -0
- package/dist/m/c-SW4YEG2D.js +8 -0
- package/dist/m/c-SZGYW5HT.js +1 -0
- package/dist/m/c-T637T7SN.js +1 -0
- package/dist/m/c-TJAXGIRV.js +1 -0
- package/dist/m/{c-KKWLDOPM.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-TQZ2JYHD.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-VG5XIN4T.js +1 -0
- package/dist/m/c-VILENZTD.js +1 -0
- package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
- package/dist/m/c-VR2BJ4AN.js +1 -0
- 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-X5TSXZYJ.js +2 -0
- 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/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
- package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
- 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-6EVJGNPL.js +1 -0
- package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
- package/dist/m/core/{persist-HKHA66IY.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/events/index-RN2JNGBV.js +1 -0
- package/dist/m/internals/index-TGLQINN3.js +1 -0
- package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
- 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 +2509 -1474
- package/dist/m/options/options-EJMS3K24.js +1 -0
- package/dist/m/page/editor-6LRB3GE4.js +304 -0
- 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-OYO5GCAO.js +1 -0
- 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-JCHVPF7P.js +1 -0
- package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
- package/dist/m/plugins/{richclay-RC3L2F6X.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-NHGJXZK2.js +1 -0
- 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-VBWXEXU2.js +1 -0
- 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 +1560 -889
- package/dist/page/themes/light-1.css +33 -8
- package/dist/server/core.cjs +1554 -311
- 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/boot-phase.js +2 -1
- package/src/core/conflict-download.js +3 -29
- package/src/core/conflict-notice-ui.js +4 -7
- package/src/core/download.js +32 -0
- package/src/core/host-meta.js +56 -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 +111 -32
- 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 +209 -40
- 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/autosave-scheduler.js +3 -4
- 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 +122 -27
- package/src/page/board-views.js +2 -16
- package/src/page/boot-pack.js +1 -1
- package/src/page/chart-tables.js +29 -0
- package/src/page/chart.js +411 -72
- package/src/page/checklist.js +10 -135
- package/src/page/checkpoint-proof.js +295 -0
- package/src/page/chrome.js +257 -40
- package/src/page/clipboard-shape.js +126 -0
- package/src/page/document-title-dom.js +12 -0
- package/src/page/document-title-runtime.js +15 -1
- package/src/page/document-title.js +4 -1
- package/src/page/drag.js +33 -3
- package/src/page/editor.js +207 -31
- package/src/page/emoji-data.js +162 -0
- 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/board.js +12 -3
- package/src/page/features/chart.js +11 -0
- package/src/page/features/checklist.js +428 -0
- package/src/page/features/dates.js +180 -0
- package/src/page/features/emoji.js +187 -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/mentions.js +188 -0
- package/src/page/features/registry.js +24 -1
- package/src/page/features/table.js +375 -85
- 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 +33 -0
- package/src/page/format-gate.js +68 -18
- package/src/page/format-host.js +71 -0
- package/src/page/format-native-commands.js +158 -0
- package/src/page/format-native.js +42 -0
- package/src/page/image-actions.js +64 -0
- package/src/page/import-markdown.js +262 -0
- package/src/page/inline.js +32 -8
- package/src/page/items.js +141 -11
- package/src/page/markdown.js +30 -2
- package/src/page/media.js +35 -119
- package/src/page/mention-picker.js +99 -0
- package/src/page/merge.js +1 -1
- package/src/page/placeholder.js +14 -1
- package/src/page/projection.js +156 -25
- 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 +67 -0
- package/src/page/save-states.js +14 -4
- package/src/page/schema.js +52 -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/content.css +25 -1
- package/src/page/themes/light-1.css +33 -8
- 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 +58 -0
- package/src/page/view.js +13 -0
- package/src/page/ybind/binding.js +116 -6
- 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 +2 -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 +125 -18
- package/src/page/ybind/recovery-proof.js +32 -9
- package/src/page/ybind/recovery-replay.js +905 -4
- 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 +126 -15
- 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 +362 -43
- 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 +7 -4
- 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/checkpoint/index.js +2 -2
- 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 +28 -14
- 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/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
- package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
- package/dist/m/c-2M73252O.js +0 -1
- package/dist/m/c-2SPVQSHC.js +0 -1
- package/dist/m/c-35NBGMZB.js +0 -1
- package/dist/m/c-3IQRECYS.js +0 -1
- package/dist/m/c-4B6H4G5V.js +0 -1
- package/dist/m/c-4FFHZM32.js +0 -1
- package/dist/m/c-4YSCRICO.js +0 -1
- package/dist/m/c-54RCDCBU.js +0 -1
- package/dist/m/c-5M4YEKCX.js +0 -4
- package/dist/m/c-5V3MOVJT.js +0 -1
- package/dist/m/c-66O32MV3.js +0 -28
- package/dist/m/c-6HMB24EV.js +0 -1
- package/dist/m/c-7O6AFR65.js +0 -1
- package/dist/m/c-ANQTJ3B5.js +0 -2
- package/dist/m/c-AYVOREBE.js +0 -1
- package/dist/m/c-B2ZXPOS3.js +0 -3
- package/dist/m/c-BMIZKMPV.js +0 -1
- package/dist/m/c-BTDI4Y23.js +0 -1
- package/dist/m/c-CJZSLMVQ.js +0 -1
- package/dist/m/c-DBE6ORSI.js +0 -1
- package/dist/m/c-EDHUR4MX.js +0 -1
- package/dist/m/c-EY6UOM54.js +0 -1
- package/dist/m/c-F3CKJTJS.js +0 -1
- package/dist/m/c-F7Y6FLPY.js +0 -1
- package/dist/m/c-FA4DZVTC.js +0 -1
- package/dist/m/c-G2BUZITR.js +0 -1
- package/dist/m/c-GB3JTV6E.js +0 -1
- package/dist/m/c-GBGWXTX4.js +0 -1
- package/dist/m/c-GHBCCVG4.js +0 -1
- package/dist/m/c-GJAEGGXY.js +0 -1
- package/dist/m/c-GOWBDM6J.js +0 -1
- package/dist/m/c-IB52R237.js +0 -1
- package/dist/m/c-IKWNE4R4.js +0 -1
- package/dist/m/c-IU3IYEU6.js +0 -1
- package/dist/m/c-IZ2EBRAJ.js +0 -1
- package/dist/m/c-KR4UW4KW.js +0 -1
- package/dist/m/c-KY3VJGUA.js +0 -8
- package/dist/m/c-LFPHCXY3.js +0 -1
- package/dist/m/c-LRPUO2MX.js +0 -1
- package/dist/m/c-LXENEBV7.js +0 -1
- package/dist/m/c-NMJRXKPC.js +0 -8
- package/dist/m/c-OIBUTDXI.js +0 -1
- package/dist/m/c-PPOSCAQD.js +0 -1
- package/dist/m/c-QIX5BJYN.js +0 -1
- package/dist/m/c-QQHEX6HA.js +0 -1
- package/dist/m/c-R7RMANJI.js +0 -9
- package/dist/m/c-RI4NT75Y.js +0 -1
- package/dist/m/c-RM7J7BV2.js +0 -1
- package/dist/m/c-RUJKSUQW.js +0 -1
- package/dist/m/c-SGCCC4FW.js +0 -25
- package/dist/m/c-T4VO4OCB.js +0 -1
- package/dist/m/c-TQEMVJCS.js +0 -1
- package/dist/m/c-TR6RMDQ7.js +0 -3
- package/dist/m/c-UKHTLWUW.js +0 -1
- package/dist/m/c-UNN7LWCT.js +0 -1
- package/dist/m/c-UXBMRDZB.js +0 -235
- package/dist/m/c-V7PZZRG7.js +0 -1
- package/dist/m/c-VL52ADDR.js +0 -1
- package/dist/m/c-WNYBLIOZ.js +0 -1
- package/dist/m/c-WWDBYXLK.js +0 -1
- package/dist/m/c-XNNW2CWK.js +0 -1
- package/dist/m/c-Y6USGTS5.js +0 -5
- package/dist/m/c-YG2NJYS6.js +0 -135
- package/dist/m/c-Z4VRC7GU.js +0 -7
- package/dist/m/c-Z6K52OLP.js +0 -19
- package/dist/m/c-ZDY2MDAA.js +0 -21
- package/dist/m/c-ZUCOKOHT.js +0 -1
- package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
- package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
- package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
- package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
- package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
- package/dist/m/core/recovery-TBBSICE2.js +0 -1
- package/dist/m/core/save-WUCHTQBE.js +0 -1
- package/dist/m/core/save-core-FJVHT5M3.js +0 -1
- package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
- package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
- package/dist/m/events/index-23DPTUT4.js +0 -1
- package/dist/m/internals/index-WOVAP3RU.js +0 -1
- package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
- package/dist/m/loader-HXX6YNH5.js +0 -1
- package/dist/m/manifest-F22DEDAC.js +0 -1
- package/dist/m/manifest-edit-C3C7376B.js +0 -1
- package/dist/m/options/options-2LH76JQ3.js +0 -1
- package/dist/m/page/editor-XUTB2HB2.js +0 -189
- package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
- package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
- package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
- package/dist/m/plugins/live-NUTTXSBS.js +0 -1
- package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
- package/dist/m/plugins/page-KSUH7G6J.js +0 -2
- package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
- package/dist/m/plugins/people-AJJ32YCG.js +0 -1
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
- package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
- package/dist/m/plugins/source-KHK5UUSK.js +0 -1
- package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
- package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
- package/dist/m/plugins/upload-XSGNILUY.js +0 -1
- package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
- package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
- package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
- package/dist/m/reader-core-ILWLXVMP.js +0 -68
- package/dist/m/route/route-KOCZSEGN.js +0 -1
- package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
- package/dist/m/ui/index-EVHKMKBL.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-RC3L2F6X.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
package/src/plugins/page.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { FEATURES, featureForType } from "../page/features/registry.js";
|
|
1
|
+
import { FEATURES, featureForNode, featureForType } from "../page/features/registry.js";
|
|
2
2
|
import { mergeEngine } from "../sync/merge-engine.js";
|
|
3
3
|
import { registerUnsavedState } from "../lib/unsaved-state.js";
|
|
4
4
|
import { hostMeta } from "../core/host-meta.js";
|
|
5
|
-
import { savePageForce, holdAllSaves, releaseAllSaves, suspendAutosave, resumeAutosave, getLastSavedBytes } from "../core/save.js";
|
|
6
|
-
import { setSaveHeader } from "../core/save-core.js";
|
|
5
|
+
import { savePageForce, holdAllSaves, releaseAllSaves, suspendAutosave, resumeAutosave, getLastSavedBytes, savesAdmitted } from "../core/save.js";
|
|
6
|
+
import { setSaveHeader, onSaveAccepted } from "../core/save-core.js";
|
|
7
7
|
import { lastSeenEtag, representedEtag, recordEtag } from "../core/etag.js";
|
|
8
8
|
import { captureForSaveAndComparison } from "../core/snapshot.js";
|
|
9
|
-
import { storeOf, stageStore, closeStore, checkpointWithin, requestCheckpoint, fastLane, pageLaneReady, pageWriterReady } from "./live.js";
|
|
9
|
+
import { storeOf, stageStore, closeStore, checkpointWithin, requestCheckpoint, requestExitCheckpoint, fastLane, localLane, lanePending, pageLaneReady, pageWriterReady, registerPageCheckpoints } from "./live.js";
|
|
10
10
|
import { serializeLive } from "../live/ops.js";
|
|
11
11
|
import { parseJsonRelaxed } from "../vendor/hyper-morph-json.vendor.js";
|
|
12
|
-
import { saveStateFor, BLOCKED_TEXT, RETRY_TEXT, downloadPageCopy, downloadSnapshot, copyPageText } from "../page/save-states.js";
|
|
12
|
+
import { saveStateFor, pageStateFor, BLOCKED_TEXT, RETRY_TEXT, downloadPageCopy, downloadSnapshot, copyPageText } from "../page/save-states.js";
|
|
13
13
|
import toast, { toastPersistent, dismissPersistent } from "../ui/toast-lazy.js";
|
|
14
14
|
import { claim } from "../page/runtime-owner.js";
|
|
15
15
|
import { authorIdentity } from "../page/ybind/author.js";
|
|
@@ -22,11 +22,14 @@ import { style } from "../lib/hostile-css.js";
|
|
|
22
22
|
const ARTICLE_SEL = "article[data-page]";
|
|
23
23
|
const TAG_SEL = 'script[type="application/json"][merge="page"][clay~="live"]';
|
|
24
24
|
const BLOCKED = { state: "blocked", code: "unsupported-host", message: "This host does not support page editing" };
|
|
25
|
+
const STORE_TEXT = "Changes cannot be stored on this device. Download a copy to keep them.";
|
|
25
26
|
const RETRY_BOUND_MS = 45000;
|
|
26
27
|
const GENERIC_ERROR = "Couldn't save";
|
|
27
28
|
const GENERIC_OFFLINE = "Offline, not saved";
|
|
28
29
|
const REPLACED_TEXT = "This page was changed outside the editor. Reload to see it.";
|
|
29
30
|
const TOO_LARGE_TEXT = "That change is too large to share, so it was not saved. Reload the page to keep editing.";
|
|
31
|
+
const CLIENT_KEY = /^[ur][0-9a-z]+(-[0-9a-z]+){2}$/;
|
|
32
|
+
export const PAGE_FULL_MESSAGE = "This document is full, though your changes are still here to download. To edit again, reload this tab after another editor, or the owner in another browser, deletes content.";
|
|
30
33
|
const OVERWRITTEN_TEXT = "Some changes you made were replaced by a change made outside the editor.";
|
|
31
34
|
const REPLACED_HOLD = "The page was changed outside the editor; reload to see it.";
|
|
32
35
|
const LOADING_MS = 150;
|
|
@@ -72,7 +75,7 @@ function pageCodeReady() {
|
|
|
72
75
|
function docFeatureIds(doc) {
|
|
73
76
|
const ids = new Set();
|
|
74
77
|
doc.forEach((node) => {
|
|
75
|
-
const id =
|
|
78
|
+
const id = featureForNode(node);
|
|
76
79
|
if (id) ids.add(id);
|
|
77
80
|
});
|
|
78
81
|
return [...ids];
|
|
@@ -129,7 +132,7 @@ function scrollToHashBlock(view, loc = globalThis.location) {
|
|
|
129
132
|
globalThis.requestAnimationFrame?.(() => view.nodeDOM(found)?.scrollIntoView?.({ block: "start" }));
|
|
130
133
|
}
|
|
131
134
|
|
|
132
|
-
function dispatchState(state, code, message) {
|
|
135
|
+
function dispatchState(state, code = null, message = "") {
|
|
133
136
|
document.dispatchEvent(new CustomEvent("clay:page-save-state", { detail: { state, code, message } }));
|
|
134
137
|
}
|
|
135
138
|
|
|
@@ -160,11 +163,24 @@ function armRetry(inst, code, message) {
|
|
|
160
163
|
}, RETRY_BOUND_MS);
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
|
|
166
|
+
// The persistent save-state toasts a session raises, taken down together: at the page's
|
|
167
|
+
// own teardown the fade plays, and at a session's end, where nobody is left to read it,
|
|
168
|
+
// the nodes go now.
|
|
169
|
+
function dismissSaveStateToasts(inst, now = false) {
|
|
170
|
+
if (inst.retryTimer) {
|
|
171
|
+
clearTimeout(inst.retryTimer);
|
|
172
|
+
inst.retryTimer = null;
|
|
173
|
+
}
|
|
174
|
+
dismissPersistent(RETRY_TEXT, { now });
|
|
175
|
+
dismissPersistent(BLOCKED_TEXT, { now });
|
|
176
|
+
dismissPersistent(STORE_TEXT, { now });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function showBlocked(inst, code, message, state = "blocked") {
|
|
164
180
|
clearRetry(inst);
|
|
165
181
|
dismissGeneric();
|
|
166
|
-
dispatchState(
|
|
167
|
-
toastPersistent(BLOCKED_TEXT, "error", {
|
|
182
|
+
dispatchState(state, code, message);
|
|
183
|
+
toastPersistent(state === "unavailable" ? STORE_TEXT : BLOCKED_TEXT, "error", {
|
|
168
184
|
detail: message,
|
|
169
185
|
actions: [
|
|
170
186
|
{ label: "Download a copy", onClick: downloadPageCopy },
|
|
@@ -176,6 +192,7 @@ function showBlocked(inst, code, message) {
|
|
|
176
192
|
function showSaved(inst) {
|
|
177
193
|
clearRetry(inst);
|
|
178
194
|
dismissPersistent(BLOCKED_TEXT);
|
|
195
|
+
dismissPersistent(STORE_TEXT);
|
|
179
196
|
dispatchState("saved", null, "");
|
|
180
197
|
}
|
|
181
198
|
|
|
@@ -218,7 +235,7 @@ function stopEditing(inst) {
|
|
|
218
235
|
// room refused it): the saved document with the article rendered from the tab's document.
|
|
219
236
|
function downloadTabCopy(inst) {
|
|
220
237
|
if (!inst.tabValue) return downloadPageCopy();
|
|
221
|
-
const { forSave } = captureForSaveAndComparison();
|
|
238
|
+
const { forSave } = captureForSaveAndComparison({ emitForSync: false });
|
|
222
239
|
const doc = new DOMParser().parseFromString(forSave, "text/html");
|
|
223
240
|
const articles = doc.querySelectorAll(ARTICLE_SEL);
|
|
224
241
|
if (articles.length !== 1) return downloadPageCopy();
|
|
@@ -229,20 +246,23 @@ function downloadTabCopy(inst) {
|
|
|
229
246
|
return downloadSnapshot(`<!DOCTYPE html>\n${doc.documentElement.outerHTML}`);
|
|
230
247
|
}
|
|
231
248
|
|
|
232
|
-
function stopWithNotice(inst, text) {
|
|
249
|
+
function stopWithNotice(inst, text, { reload = true } = {}) {
|
|
233
250
|
if (inst.replaced) return;
|
|
234
251
|
inst.replaced = true;
|
|
235
252
|
stopEditing(inst);
|
|
253
|
+
const download = { label: "Download your copy", onClick: () => downloadTabCopy(inst) };
|
|
236
254
|
toastPersistent(text, "warning", {
|
|
237
|
-
actions: [
|
|
238
|
-
{ label: "Reload", onClick: () => window.location.reload() },
|
|
239
|
-
{ label: "Download your copy", onClick: () => downloadTabCopy(inst) },
|
|
240
|
-
],
|
|
255
|
+
actions: reload ? [{ label: "Reload", onClick: () => window.location.reload() }, download] : [download],
|
|
241
256
|
});
|
|
242
257
|
}
|
|
243
258
|
|
|
244
259
|
const showReplaced = (inst) => stopWithNotice(inst, REPLACED_TEXT);
|
|
245
260
|
const showTooLarge = (inst) => stopWithNotice(inst, TOO_LARGE_TEXT);
|
|
261
|
+
const showFull = (inst, code) => {
|
|
262
|
+
dispatchState("blocked", code, PAGE_FULL_MESSAGE);
|
|
263
|
+
// A reload replays the refused edit and is refused again, so the notice offers only the download.
|
|
264
|
+
stopWithNotice(inst, PAGE_FULL_MESSAGE, { reload: false });
|
|
265
|
+
};
|
|
246
266
|
|
|
247
267
|
function applySaveState(inst, state, code, message) {
|
|
248
268
|
if (state === "saved") showSaved(inst);
|
|
@@ -290,8 +310,7 @@ function listenForSaves(inst) {
|
|
|
290
310
|
});
|
|
291
311
|
}
|
|
292
312
|
|
|
293
|
-
// The page the host stores now,
|
|
294
|
-
// again, and the version it is offered against.
|
|
313
|
+
// The page the host stores now, including any merge state it keeps beside the file.
|
|
295
314
|
async function readStoredArticle() {
|
|
296
315
|
const response = await fetch(new URL(globalThis.location.href).href, { cache: "no-store" });
|
|
297
316
|
if (!response?.ok) return null;
|
|
@@ -299,12 +318,17 @@ async function readStoredArticle() {
|
|
|
299
318
|
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
300
319
|
const article = doc.querySelector(ARTICLE_SEL);
|
|
301
320
|
if (!article) return null;
|
|
302
|
-
|
|
321
|
+
const tag = doc.querySelector(TAG_SEL);
|
|
322
|
+
// The served root attribute names the stored document (spec §4). A host may add a weak
|
|
323
|
+
// HTTP ETag of its own, which names the response, never the document.
|
|
324
|
+
const header = response.headers?.get?.("etag") || null;
|
|
325
|
+
const etag = doc.documentElement.getAttribute("documentetag") || (header && !/^W\//.test(header) ? header : null);
|
|
326
|
+
return { article, value: tag ? parseJsonRelaxed(tag.textContent) : null, etag };
|
|
303
327
|
}
|
|
304
328
|
|
|
305
329
|
// A refused first write: the stored document has moved on, or it does not descend
|
|
306
|
-
// from the seed this tab staged. The stored document is read once,
|
|
307
|
-
// from
|
|
330
|
+
// from the seed this tab staged. The stored document is read once, its merge state is
|
|
331
|
+
// kept (or seeded from the article), this tab's pending edit replays over it, and the write goes
|
|
308
332
|
// out once more against the version just read. A second refusal keeps the text in the
|
|
309
333
|
// tab and shows the conflict, exactly as a lone refusal does.
|
|
310
334
|
async function retryFirstWrite(inst, event) {
|
|
@@ -319,11 +343,11 @@ async function retryFirstWrite(inst, event) {
|
|
|
319
343
|
if (!found || !isCurrent(inst)) return;
|
|
320
344
|
const page = await pageCodeReady();
|
|
321
345
|
if (!isCurrent(inst)) return;
|
|
322
|
-
const seed = page.seedPageFromArticle(found.article.cloneNode(true));
|
|
346
|
+
const seed = found.value ?? page.seedPageFromArticle(found.article.cloneNode(true));
|
|
323
347
|
// This tab's next write is conditional on the version it has just read.
|
|
324
348
|
const at = found.etag ?? event?.detail?.etag ?? null;
|
|
325
349
|
if (at) recordEtag(at);
|
|
326
|
-
// The tab's own edit replays over the
|
|
350
|
+
// The tab's own edit replays over the stored page state; an op the
|
|
327
351
|
// stored article cannot hold is dropped by the store, never written over it.
|
|
328
352
|
inst.store.rebase(seed, { source: "state" });
|
|
329
353
|
await savePageForce();
|
|
@@ -410,14 +434,10 @@ function dispose() {
|
|
|
410
434
|
const inst = active;
|
|
411
435
|
active = null;
|
|
412
436
|
setSaveHeader("Page-Write", null);
|
|
437
|
+
registerPageCheckpoints(null);
|
|
413
438
|
if (!inst) return;
|
|
414
439
|
disposeInstall(inst);
|
|
415
|
-
|
|
416
|
-
clearTimeout(inst.retryTimer);
|
|
417
|
-
inst.retryTimer = null;
|
|
418
|
-
}
|
|
419
|
-
dismissPersistent(RETRY_TEXT);
|
|
420
|
-
dismissPersistent(BLOCKED_TEXT);
|
|
440
|
+
dismissSaveStateToasts(inst);
|
|
421
441
|
dismissPersistent(REPLACED_TEXT);
|
|
422
442
|
for (const message of inst.notices?.values() ?? []) dismissPersistent(message);
|
|
423
443
|
if (inst.savesHeld) {
|
|
@@ -438,6 +458,129 @@ function dispose() {
|
|
|
438
458
|
restoreArticle(inst);
|
|
439
459
|
}
|
|
440
460
|
|
|
461
|
+
// The page's journal, its replay and the proof that retires it, all imported the first
|
|
462
|
+
// time a page can actually journal: a reader, a page without a pinned identity and a host
|
|
463
|
+
// with no recovery service pay for none of it.
|
|
464
|
+
let recoveryModules = null;
|
|
465
|
+
|
|
466
|
+
function pageRecovery() {
|
|
467
|
+
if (!recoveryModules) {
|
|
468
|
+
recoveryModules = Promise.all([
|
|
469
|
+
import("../page/ybind/recovery-replay.js"),
|
|
470
|
+
import("../page/checkpoint-proof.js"),
|
|
471
|
+
import("../page/recovery-ui.js"),
|
|
472
|
+
])
|
|
473
|
+
.then(([replay, proof, ui]) => ({ replay, proof, ui }))
|
|
474
|
+
.catch((error) => {
|
|
475
|
+
console.warn("clayjs: page journal could not load", error);
|
|
476
|
+
return null;
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
return recoveryModules;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// What this session journals under, or null when this page cannot pin an identity or this
|
|
483
|
+
// host has no recovery service: the document is then edited with no local copy, as before.
|
|
484
|
+
async function openJournal(meta, store) {
|
|
485
|
+
const service = globalThis.window?.clay?.recovery ?? null;
|
|
486
|
+
if (!service || !store) return null;
|
|
487
|
+
const modules = await pageRecovery();
|
|
488
|
+
if (!modules) return null;
|
|
489
|
+
try {
|
|
490
|
+
return modules.replay.openPageJournal({
|
|
491
|
+
meta,
|
|
492
|
+
service,
|
|
493
|
+
base: modules.replay.journalBase(store.value),
|
|
494
|
+
});
|
|
495
|
+
} catch (error) {
|
|
496
|
+
console.warn("clayjs: page journal could not open", error);
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Replay once, after the authority snapshot is adopted and before input starts. A capsule
|
|
502
|
+
// this page may not replay is kept and named for review; nothing of it is applied.
|
|
503
|
+
async function replayJournal(inst) {
|
|
504
|
+
const journal = inst.binding?.journal;
|
|
505
|
+
if (!journal || !inst.store) return { replayed: [], review: [] };
|
|
506
|
+
const modules = await pageRecovery();
|
|
507
|
+
if (!modules) return { replayed: [], review: [] };
|
|
508
|
+
const outcome = await modules.replay.replayInto({ journal, state: inst.store.value, into: inst.binding.ydoc });
|
|
509
|
+
// The recovered page is this tab's page now: it is offered to the room exactly as an
|
|
510
|
+
// edit would be, and it is not one more journal and not one giant undo step.
|
|
511
|
+
installJournalStatus(inst, modules);
|
|
512
|
+
if (outcome.replayed.length) inst.binding.provider.settle();
|
|
513
|
+
if (outcome.review.length) inst.recoveryReview = outcome.review;
|
|
514
|
+
installRecoveryReview(inst, modules.ui)?.update({ rows: modules.replay.describeReview(outcome.review) });
|
|
515
|
+
return outcome;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// The review of what this page may not replay, drawn in the editor's own chrome once per
|
|
519
|
+
// session, with the composition shadow that keeps typing which never became a
|
|
520
|
+
// transaction: one object, alive exactly as long as the session that mounted it.
|
|
521
|
+
function installRecoveryReview(inst, ui) {
|
|
522
|
+
if (inst.recoveryPanel) return inst.recoveryPanel;
|
|
523
|
+
const view = inst.view;
|
|
524
|
+
const mount = view?.dom.closest(".page-shell") ?? inst.article;
|
|
525
|
+
if (!mount) return null;
|
|
526
|
+
const review = ui.createRecoveryReview({
|
|
527
|
+
mount,
|
|
528
|
+
getView: () => (inst.view && !inst.view.isDestroyed ? inst.view : null),
|
|
529
|
+
getJournal: () => inst.binding?.journal ?? null,
|
|
530
|
+
isCurrent: () => isCurrent(inst) && inst.inputOpen && !!inst.view && !inst.view.isDestroyed,
|
|
531
|
+
});
|
|
532
|
+
const detach = review.watch(view?.dom ?? null);
|
|
533
|
+
addDisposer(inst, () => {
|
|
534
|
+
detach();
|
|
535
|
+
review.destroy();
|
|
536
|
+
});
|
|
537
|
+
inst.recoveryPanel = review;
|
|
538
|
+
return review;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// One accepted save retires the records a proof covers, and no others.
|
|
542
|
+
function installCheckpointProof(inst) {
|
|
543
|
+
registerPageCheckpoints(async ({ capture, state, article, etag }) => {
|
|
544
|
+
const journal = inst.binding?.journal;
|
|
545
|
+
if (!journal) return;
|
|
546
|
+
const modules = await pageRecovery();
|
|
547
|
+
const context = modules ? await journal.context() : null;
|
|
548
|
+
if (!context) return;
|
|
549
|
+
// Where this host serves the page state, if it serves one at all: asked here, because
|
|
550
|
+
// a page that journals nothing never needs the answer.
|
|
551
|
+
const { hostPageStateHref } = await import("../core/host-meta.js");
|
|
552
|
+
const answer = await modules.proof.checkpointAccepted({
|
|
553
|
+
channel: journal.channel,
|
|
554
|
+
records: journal.records(),
|
|
555
|
+
capture,
|
|
556
|
+
accepted: context,
|
|
557
|
+
article,
|
|
558
|
+
state,
|
|
559
|
+
etag,
|
|
560
|
+
href: await hostPageStateHref(),
|
|
561
|
+
});
|
|
562
|
+
if (!answer.ok) return;
|
|
563
|
+
journal.retire(answer.retired);
|
|
564
|
+
inst.pageStatus?.checkpoint();
|
|
565
|
+
await journal.rebase?.(modules.replay.journalBase(answer.proof.state));
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Both live in the journal's module, which a page without a journal never loads.
|
|
570
|
+
function installJournalStatus(inst, modules) {
|
|
571
|
+
const journal = inst.binding?.journal;
|
|
572
|
+
if (!journal || inst.pageStatus) return inst.pageStatus ?? null;
|
|
573
|
+
inst.pageStatus = modules.replay.watchJournal({
|
|
574
|
+
journal,
|
|
575
|
+
store: inst.store,
|
|
576
|
+
reduce: pageStateFor,
|
|
577
|
+
onState: (state) => dispatchState(state),
|
|
578
|
+
onUnavailable: (code) => showBlocked(inst, code, "", "unavailable"),
|
|
579
|
+
});
|
|
580
|
+
addDisposer(inst, () => inst.pageStatus?.dispose());
|
|
581
|
+
return inst.pageStatus;
|
|
582
|
+
}
|
|
583
|
+
|
|
441
584
|
export function createSteps(inst, options) {
|
|
442
585
|
let meta = null;
|
|
443
586
|
let binding = null;
|
|
@@ -460,6 +603,42 @@ export function createSteps(inst, options) {
|
|
|
460
603
|
addDisposer(inst, () => editorReadyResolve?.());
|
|
461
604
|
addDisposer(inst, () => clearLoadingStatus(inst));
|
|
462
605
|
|
|
606
|
+
// The format gate, fetched when a newer-format command first asks for it: neither
|
|
607
|
+
// the gate nor its host adapter is on the editor's ready path. One gate per session,
|
|
608
|
+
// built against this editor installation, and disposed with the editor.
|
|
609
|
+
let formatGate = null;
|
|
610
|
+
let formatGateLoading = null;
|
|
611
|
+
inst.loadFormatGate = () => {
|
|
612
|
+
if (formatGate) return Promise.resolve(formatGate);
|
|
613
|
+
formatGateLoading ??= Promise.all([import("../page/format-gate.js"), import("../page/format-host.js")])
|
|
614
|
+
.then(([{ createFormatGate }, { createFormatHost }]) => {
|
|
615
|
+
const host = createFormatHost();
|
|
616
|
+
formatGate = createFormatGate({
|
|
617
|
+
host,
|
|
618
|
+
supported: host.supported,
|
|
619
|
+
installed: () => identity?.token ?? null,
|
|
620
|
+
revision: () => null,
|
|
621
|
+
// A floor rise moves this tab to the lane scope of the format it now
|
|
622
|
+
// writes, so a tab that cannot read that format never shares it. The
|
|
623
|
+
// transport is this tab's to create, on its own schedule, so the gate
|
|
624
|
+
// asks for it by the move rather than by the object.
|
|
625
|
+
lane: {
|
|
626
|
+
switchScope: (next, options) => {
|
|
627
|
+
const transport = localLane();
|
|
628
|
+
if (!transport) return { status: "refused", code: "lane-unavailable" };
|
|
629
|
+
return transport.switchScope(next, { ...options, covered: Boolean(options?.covered) || !lanePending() });
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
});
|
|
633
|
+
return formatGate;
|
|
634
|
+
})
|
|
635
|
+
.catch((error) => {
|
|
636
|
+
formatGateLoading = null;
|
|
637
|
+
throw error;
|
|
638
|
+
});
|
|
639
|
+
return formatGateLoading;
|
|
640
|
+
};
|
|
641
|
+
|
|
463
642
|
// A document with a tool block, at boot or when one arrives, opens the store its
|
|
464
643
|
// tools keep state in and the host that mounts them. A document with no tool
|
|
465
644
|
// never waits for either.
|
|
@@ -511,6 +690,81 @@ export function createSteps(inst, options) {
|
|
|
511
690
|
});
|
|
512
691
|
return toolsOpening;
|
|
513
692
|
};
|
|
693
|
+
|
|
694
|
+
// The document's own switches travel with it: the gear panel writes them into the
|
|
695
|
+
// page's live store, and a document that never touched one opens no store at all.
|
|
696
|
+
// Turning a switch off hides a tag; the owner and due values stay in the file.
|
|
697
|
+
const SETTINGS_SEL = 'script[type="application/json"][merge="page.settings"][clay~="live"]';
|
|
698
|
+
const SETTING_KEYS = ["assignees", "dates"];
|
|
699
|
+
let settingsOpening = null;
|
|
700
|
+
let settingsValue = { assignees: true, dates: true };
|
|
701
|
+
|
|
702
|
+
// Both switches default to on, and anything but an explicit false leaves them on.
|
|
703
|
+
const settingsFrom = (value) => ({
|
|
704
|
+
assignees: value?.assignees !== false,
|
|
705
|
+
dates: value?.dates !== false,
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
function readSettings() {
|
|
709
|
+
const tag = document.querySelector(SETTINGS_SEL);
|
|
710
|
+
if (!tag) return settingsFrom(null);
|
|
711
|
+
let value = null;
|
|
712
|
+
try { value = JSON.parse(tag.textContent); } catch (error) { value = null; }
|
|
713
|
+
return settingsFrom(value);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function applySettings(next) {
|
|
717
|
+
settingsValue = settingsFrom(next);
|
|
718
|
+
const dom = inst.view && !inst.view.isDestroyed ? inst.view.dom : null;
|
|
719
|
+
if (!dom) return;
|
|
720
|
+
for (const key of SETTING_KEYS) {
|
|
721
|
+
if (settingsValue[key]) dom.removeAttribute(`data-${key}`);
|
|
722
|
+
else dom.setAttribute(`data-${key}`, "false");
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const openSettings = () => {
|
|
727
|
+
if (settingsOpening) return settingsOpening;
|
|
728
|
+
settingsOpening = (async () => {
|
|
729
|
+
if (!document.querySelector(SETTINGS_SEL)) {
|
|
730
|
+
const tag = document.createElement("script");
|
|
731
|
+
tag.setAttribute("type", "application/json");
|
|
732
|
+
tag.setAttribute("merge", "page.settings");
|
|
733
|
+
tag.setAttribute("clay", "live");
|
|
734
|
+
tag.textContent = "{}";
|
|
735
|
+
document.head.appendChild(tag);
|
|
736
|
+
}
|
|
737
|
+
let handle = null;
|
|
738
|
+
try {
|
|
739
|
+
handle = window.clay.live("page.settings");
|
|
740
|
+
await handle.ready;
|
|
741
|
+
} catch (error) {
|
|
742
|
+
console.warn("clayjs: page plugin could not open the settings store", error);
|
|
743
|
+
settingsOpening = null;
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
if (!isCurrent(inst)) return null;
|
|
747
|
+
applySettings(handle.data);
|
|
748
|
+
// A peer's change hides or shows the tags here too, without the view remounting.
|
|
749
|
+
handle.on("change", () => applySettings(handle.data));
|
|
750
|
+
return handle;
|
|
751
|
+
})();
|
|
752
|
+
return settingsOpening;
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
// The gear panel's API: what the document shows now, and a switch that writes the
|
|
756
|
+
// document's store and takes effect here at once, without waiting for the change.
|
|
757
|
+
inst.settings = {
|
|
758
|
+
read: () => ({ ...settingsValue }),
|
|
759
|
+
set: async (key, on) => {
|
|
760
|
+
if (!SETTING_KEYS.includes(key)) throw new TypeError(`clayjs: "${key}" is not a page setting`);
|
|
761
|
+
const handle = await openSettings();
|
|
762
|
+
if (!handle) throw new Error("clayjs: the page settings store could not be opened");
|
|
763
|
+
const next = { ...settingsFrom(handle.data), [key]: on !== false };
|
|
764
|
+
handle.set([key], next[key]);
|
|
765
|
+
applySettings(next);
|
|
766
|
+
},
|
|
767
|
+
};
|
|
514
768
|
// One notice per feature a person needed and that failed, until Retry succeeds.
|
|
515
769
|
const notices = new Map();
|
|
516
770
|
const notifyFailed = (id) => {
|
|
@@ -588,9 +842,29 @@ export function createSteps(inst, options) {
|
|
|
588
842
|
const code = await pageCodeReady();
|
|
589
843
|
if (!isCurrent(inst)) return { ok: false, cancelled: true };
|
|
590
844
|
if (seeded) {
|
|
591
|
-
// The seed stays a value in memory: no tag and no write until someone edits.
|
|
592
845
|
try {
|
|
593
|
-
|
|
846
|
+
const meta = await hostMeta();
|
|
847
|
+
const href = meta?.extensions?.includes("page-recovery-v1") && meta.document?.pageState?.href;
|
|
848
|
+
const etag = inst.context?.represented ? inst.context.represented.etag : representedEtag() ?? lastSeenEtag();
|
|
849
|
+
if (href && etag) {
|
|
850
|
+
const { readPageState } = await import("../page/checkpoint-proof.js");
|
|
851
|
+
const stored = await readPageState({ href, etag });
|
|
852
|
+
if (!isCurrent(inst)) return { ok: false, cancelled: true };
|
|
853
|
+
if (stored.ok) {
|
|
854
|
+
if (stored.etag !== etag) return { ok: false, reason: "etag-mismatch" };
|
|
855
|
+
value = parseJsonRelaxed(stored.tag);
|
|
856
|
+
source.persisted = true;
|
|
857
|
+
} else if (stored.code !== "no-page-state") {
|
|
858
|
+
return { ok: false, reason: stored.code };
|
|
859
|
+
}
|
|
860
|
+
} else if (href) {
|
|
861
|
+
const stored = await readStoredArticle();
|
|
862
|
+
if (!isCurrent(inst)) return { ok: false, cancelled: true };
|
|
863
|
+
if (!stored || !stored.article.isEqualNode(source.article)) return { ok: false, reason: "stale-document" };
|
|
864
|
+
value = stored.value;
|
|
865
|
+
source.persisted = value !== null;
|
|
866
|
+
}
|
|
867
|
+
value = source.seedValue = value ?? code.seedPageFromArticle(source.article.cloneNode(true));
|
|
594
868
|
} catch (error) {
|
|
595
869
|
console.warn("clayjs: page plugin could not rebuild the page it found, leaving the page as saved HTML", error);
|
|
596
870
|
return { ok: false, reason: "invalid-page-source" };
|
|
@@ -714,6 +988,7 @@ function mergeRepresentedPage(store, value) {
|
|
|
714
988
|
const staged = stageStore("page", {
|
|
715
989
|
value: source.seedValue,
|
|
716
990
|
represented: options.represented ?? { bytes: source.article.outerHTML, etag: representedEtag() ?? lastSeenEtag() },
|
|
991
|
+
persisted: source.persisted,
|
|
717
992
|
});
|
|
718
993
|
inst.staged = staged;
|
|
719
994
|
handle = staged.handle;
|
|
@@ -762,6 +1037,11 @@ function mergeRepresentedPage(store, value) {
|
|
|
762
1037
|
return result;
|
|
763
1038
|
};
|
|
764
1039
|
identity = authorIdentity();
|
|
1040
|
+
// The journal is installed before the editor is enabled, and whether or not this tab
|
|
1041
|
+
// can reach the room: a refused offer, a lost room and an oversize update all leave
|
|
1042
|
+
// the work in a document that a crash must still be able to recover.
|
|
1043
|
+
const journal = await openJournal(meta, store);
|
|
1044
|
+
if (!isCurrent(inst)) return { ok: false, cancelled: true };
|
|
765
1045
|
binding = code.createYPageBinding({
|
|
766
1046
|
store,
|
|
767
1047
|
onStopped: (reason) => {
|
|
@@ -769,6 +1049,8 @@ function mergeRepresentedPage(store, value) {
|
|
|
769
1049
|
if (reason === "lineage") {
|
|
770
1050
|
fastLane()?.abandon?.("page");
|
|
771
1051
|
showReplaced(inst);
|
|
1052
|
+
} else if (reason === "page-full" || reason === "compaction-failed") {
|
|
1053
|
+
showFull(inst, reason);
|
|
772
1054
|
} else {
|
|
773
1055
|
showTooLarge(inst);
|
|
774
1056
|
}
|
|
@@ -777,16 +1059,23 @@ function mergeRepresentedPage(store, value) {
|
|
|
777
1059
|
ownedTokens: identity.owned,
|
|
778
1060
|
checkpointWithin,
|
|
779
1061
|
requestCheckpoint,
|
|
1062
|
+
requestExitCheckpoint: (options) => requestExitCheckpoint({ name: store.name, ...options }),
|
|
1063
|
+
journal,
|
|
1064
|
+
// An exit holds admission closed, which is the only time this tab's own input
|
|
1065
|
+
// still owing the file holds the leave: outside one, the binding reports what it
|
|
1066
|
+
// always reported.
|
|
1067
|
+
exitClosed: () => !savesAdmitted(),
|
|
780
1068
|
registerUnsavedState: (def) => {
|
|
781
1069
|
registered = registerUnsavedState({
|
|
782
1070
|
id: def.id,
|
|
783
1071
|
isPending: () => flushFailed || def.isPending(),
|
|
784
|
-
flush: () => trackFlush(() => def.flush()),
|
|
1072
|
+
flush: (options) => trackFlush(() => def.flush(options)),
|
|
785
1073
|
});
|
|
786
1074
|
return registered;
|
|
787
1075
|
},
|
|
788
1076
|
});
|
|
789
1077
|
inst.binding = binding;
|
|
1078
|
+
if (journal) installCheckpointProof(inst);
|
|
790
1079
|
inst.flush = (options) => trackFlush(() => binding.flush(options));
|
|
791
1080
|
listenForSaves(inst);
|
|
792
1081
|
return { ok: true, store, handle };
|
|
@@ -851,6 +1140,7 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
851
1140
|
features,
|
|
852
1141
|
onAiTool: aiRequested() ? (_view, remove) => { openAi({ remove }); } : null,
|
|
853
1142
|
reviseTool: aiRequested() ? (toolId, rev) => { openAi({ target: { toolId, rev } }); } : null,
|
|
1143
|
+
formatGate: inst.loadFormatGate,
|
|
854
1144
|
});
|
|
855
1145
|
} catch (error) {
|
|
856
1146
|
console.error("clayjs: page plugin could not mount the editor", error);
|
|
@@ -862,6 +1152,13 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
862
1152
|
}
|
|
863
1153
|
editorView = view;
|
|
864
1154
|
inst.view = view;
|
|
1155
|
+
applySettings(readSettings());
|
|
1156
|
+
// A document that states settings opens their store so a peer's change reaches this
|
|
1157
|
+
// tab; one that states none opens it when a switch is first written.
|
|
1158
|
+
if (document.querySelector(SETTINGS_SEL)) openSettings().catch(() => null);
|
|
1159
|
+
else listen(inst, "clay:sync-applied", () => {
|
|
1160
|
+
if (!settingsOpening && document.querySelector(SETTINGS_SEL)) openSettings().catch(() => null);
|
|
1161
|
+
});
|
|
865
1162
|
const shell = view.dom.closest(".page-shell");
|
|
866
1163
|
// A shell still waiting for its boot takes no input of any kind: a control inside
|
|
867
1164
|
// a non-editable editor would otherwise write before the boot says ready. It is
|
|
@@ -952,7 +1249,8 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
952
1249
|
// lane but no writer yet, this tab claims the room (a redundant set of an
|
|
953
1250
|
// existing Yjs update: a valid nonempty frame that changes nothing) and then
|
|
954
1251
|
// waits for the election, so exactly one tab adopts.
|
|
955
|
-
|
|
1252
|
+
// A seeded page was built from this article: nothing to adopt.
|
|
1253
|
+
const found = source.seeded ? { kind: "untouched" } : code.classifyFile(source.article, source.tags);
|
|
956
1254
|
const needsAdoption = found.kind === "adopt";
|
|
957
1255
|
const awaitWriter = async () => {
|
|
958
1256
|
const lane = await pageLaneReady();
|
|
@@ -961,8 +1259,13 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
961
1259
|
if (lane.status.writer === null) {
|
|
962
1260
|
const y = inst.store.value.y;
|
|
963
1261
|
const keys = Object.keys(y);
|
|
964
|
-
|
|
965
|
-
|
|
1262
|
+
// A compacting room admits only whole client keys: an existing one again, or this
|
|
1263
|
+
// tab's document under a fresh one.
|
|
1264
|
+
const me = binding.ydoc.clientID.toString(36);
|
|
1265
|
+
const key = inst.store.room?.compaction?.v === 1
|
|
1266
|
+
? keys.find((k) => CLIENT_KEY.test(k)) ?? `u${me}-claim-0`
|
|
1267
|
+
: keys.length ? keys[0] : `claim-${me}`;
|
|
1268
|
+
const value = Object.hasOwn(y, key) ? y[key] : code.yValueOf(binding.ydoc).y.seed;
|
|
966
1269
|
inst.store.handle.set(["y", key], value);
|
|
967
1270
|
// The session is not admitted until the commit, so its ops stay in the tab: the
|
|
968
1271
|
// claim alone is framed now, before adoption writes anything, or no election runs.
|
|
@@ -985,8 +1288,9 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
985
1288
|
return !lane || lane.status.writer === null ? true : lane.isWriter();
|
|
986
1289
|
},
|
|
987
1290
|
requestCheckpoint: () => checkpointWithin(0),
|
|
988
|
-
snapshot: () => captureForSaveAndComparison().forSave,
|
|
1291
|
+
snapshot: () => captureForSaveAndComparison({ emitForSync: false }).forSave,
|
|
989
1292
|
mergeTagless: inst.mergeTagless,
|
|
1293
|
+
seeded: source.seeded,
|
|
990
1294
|
writerReady: awaitWriter,
|
|
991
1295
|
adopted: (rev) => fastLane()?.adopted?.("page", rev, inst.store.mark),
|
|
992
1296
|
notice: (counts) => {
|
|
@@ -999,7 +1303,10 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
999
1303
|
inst.adoption = adoption;
|
|
1000
1304
|
|
|
1001
1305
|
let outcome = await adoption.idle();
|
|
1002
|
-
if (!outcome)
|
|
1306
|
+
if (!outcome) {
|
|
1307
|
+
await replayJournal(inst);
|
|
1308
|
+
return { ok: true, outcome: "unchanged" };
|
|
1309
|
+
}
|
|
1003
1310
|
// Another tab is the room's writer, so this tab's adoption is the one that tab
|
|
1004
1311
|
// lands: the wait ends when it reaches this tab, or as the timeout a refusal is.
|
|
1005
1312
|
if (outcome.outcome === "writer-adopts") outcome = await awaitWriterAdoption(inst, found);
|
|
@@ -1008,6 +1315,7 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1008
1315
|
// comes back from what the room accepted. A disposed install restores nothing:
|
|
1009
1316
|
// dispose already did.
|
|
1010
1317
|
if (!outcome.ok && isCurrent(inst)) abandonEditor(inst);
|
|
1318
|
+
else if (isCurrent(inst)) await replayJournal(inst);
|
|
1011
1319
|
return outcome;
|
|
1012
1320
|
}
|
|
1013
1321
|
|
|
@@ -1070,7 +1378,7 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1070
1378
|
// a `resume` puts the very same session back.
|
|
1071
1379
|
function closeInput(inst) {
|
|
1072
1380
|
if (!inst.inputOpen) return false;
|
|
1073
|
-
setEditorActive?.(inst.view, false);
|
|
1381
|
+
setEditorActive?.(inst.view, false, { quiesce: true });
|
|
1074
1382
|
return true;
|
|
1075
1383
|
}
|
|
1076
1384
|
|
|
@@ -1123,7 +1431,7 @@ function begin(options = {}, { activate = true, context = null } = {}) {
|
|
|
1123
1431
|
options.onPhase?.(result);
|
|
1124
1432
|
},
|
|
1125
1433
|
}),
|
|
1126
|
-
article: null, view: null, binding: null, adoption: null, source: null, store: null, toolHost: null, aiTool: null, bootHtml: "", bootRev: null, savedRev: null, staged: null, pageStore: null, releaseArticle: null, replaced: false, savesHeld: false, flush: null, listeners: [], retryTimer: null, retryShown: false, statusTimer: null, statusNote: null, notices: null, editorReadyResolve: null, context, inputOpen: false, sessionHeld: false, autosaveSuspended: false, staleRetry: false,
|
|
1434
|
+
article: null, view: null, binding: null, adoption: null, source: null, store: null, toolHost: null, aiTool: null, bootHtml: "", bootRev: null, savedRev: null, staged: null, pageStore: null, releaseArticle: null, replaced: false, savesHeld: false, flush: null, loadFormatGate: null, listeners: [], retryTimer: null, retryShown: false, statusTimer: null, statusNote: null, notices: null, editorReadyResolve: null, context, inputOpen: false, sessionHeld: false, autosaveSuspended: false, staleRetry: false,
|
|
1127
1435
|
};
|
|
1128
1436
|
active = inst;
|
|
1129
1437
|
const boot = run(inst, options, { activate }).catch((error) => {
|
|
@@ -1149,10 +1457,7 @@ function endSession(inst) {
|
|
|
1149
1457
|
}
|
|
1150
1458
|
setSaveHeader("Page-Write", null);
|
|
1151
1459
|
disposeInstall(inst);
|
|
1152
|
-
|
|
1153
|
-
clearTimeout(inst.retryTimer);
|
|
1154
|
-
inst.retryTimer = null;
|
|
1155
|
-
}
|
|
1460
|
+
dismissSaveStateToasts(inst, true);
|
|
1156
1461
|
inst.titleObserver?.dispose();
|
|
1157
1462
|
inst.titleObserver = null;
|
|
1158
1463
|
clearLoadingStatus(inst);
|
|
@@ -1285,8 +1590,22 @@ export const page = {
|
|
|
1285
1590
|
get view() {
|
|
1286
1591
|
return active?.view ?? null;
|
|
1287
1592
|
},
|
|
1593
|
+
// The document's settings, for the panel that switches them.
|
|
1594
|
+
get settings() {
|
|
1595
|
+
return active?.settings ?? null;
|
|
1596
|
+
},
|
|
1288
1597
|
};
|
|
1289
1598
|
|
|
1599
|
+
// The page's accepted bytes are the only thing that clears what this tab owes the file,
|
|
1600
|
+
// and one module-level observer is enough: the hook API has no unsubscribe, so a binding
|
|
1601
|
+
// registers none of its own and the current session's binding answers each save.
|
|
1602
|
+
onSaveAccepted((html) => {
|
|
1603
|
+
const binding = active?.binding;
|
|
1604
|
+
if (!binding?.filePending()) return;
|
|
1605
|
+
const state = pageCode?.acceptedPageState(html);
|
|
1606
|
+
if (state) binding.acceptFile(state);
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1290
1609
|
// A live entry imports this module while the committed mode is still view: the install
|
|
1291
1610
|
// belongs to the explicit edit boot alone, and a managed session starts its own.
|
|
1292
1611
|
page.ready = startsAtImport() ? install() : null;
|