@panphora/clayjs 1.23.0 → 1.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all.js +1 -1
- package/dist/blockeditor/themes/light-1.css +431 -0
- 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 +30285 -26613
- package/dist/m/c-277QP5ET.js +1 -0
- package/dist/m/c-2BLVA6IN.js +5 -0
- package/dist/m/c-2OJMOUPC.js +1 -0
- package/dist/m/c-2PVGJFI5.js +1 -0
- package/dist/m/{c-AYVGD6A6.js → c-3UW5EJ77.js} +1 -1
- package/dist/m/{c-T5NK2TVY.js → c-42ZNV7LI.js} +1 -1
- package/dist/m/{c-3TLG3OLG.js → c-46LJAZZJ.js} +1 -1
- package/dist/m/c-47IBJGPQ.js +1 -0
- package/dist/m/c-5FXEKU4Y.js +1 -0
- package/dist/m/c-5KXDKC5G.js +1 -0
- package/dist/m/{c-EUGJVHDD.js → c-5RZNFTWD.js} +1 -1
- package/dist/m/c-65LJU2H2.js +72 -0
- package/dist/m/{c-NQJRVJ56.js → c-6IJEVE6B.js} +1 -1
- package/dist/m/{c-NW54NHKE.js → c-6LEYVQJK.js} +1 -1
- package/dist/m/{c-GSRLXYWL.js → c-6Q6RGFBF.js} +1 -1
- package/dist/m/c-6RGEQNVV.js +1 -0
- package/dist/m/c-7CM4GMTS.js +6 -0
- package/dist/m/c-7NCWENHV.js +1 -0
- package/dist/m/c-7V6MGMX2.js +1 -0
- package/dist/m/{c-LKSL4AUJ.js → c-7WZSUTYP.js} +1 -1
- package/dist/m/c-A4L7APIU.js +1 -0
- package/dist/m/c-APGHGDZM.js +1 -0
- package/dist/m/c-AT7GLQGV.js +34 -0
- package/dist/m/{c-ZRXJD4G6.js → c-B7GOFGQR.js} +1 -1
- package/dist/m/{c-KDXIJEEF.js → c-BGN7RCTJ.js} +1 -1
- package/dist/m/c-BKM46LUJ.js +1 -0
- package/dist/m/c-BOMQIQ3V.js +1 -0
- package/dist/m/{c-62HZW5DH.js → c-C5AKJVRH.js} +1 -1
- package/dist/m/{c-BXYSDKAN.js → c-CJ7QYEWI.js} +1 -1
- package/dist/m/c-CTGRDT6E.js +1 -0
- package/dist/m/c-CV4T65JE.js +6 -0
- package/dist/m/c-CVSWLX3X.js +1 -0
- package/dist/m/c-CZHTXGJS.js +1 -0
- package/dist/m/c-D7NT5S4F.js +1 -0
- package/dist/m/c-DD26VXAP.js +1 -0
- package/dist/m/{c-ANNFZFLN.js → c-DDS23EY6.js} +1 -1
- package/dist/m/{c-ALDVFPWL.js → c-DFKBGSN2.js} +1 -1
- package/dist/m/c-DIKR7H57.js +147 -0
- package/dist/m/c-DXPOFLEJ.js +1 -0
- package/dist/m/c-EDE45NM7.js +1 -0
- package/dist/m/c-EILLN2H2.js +1 -0
- package/dist/m/c-EODTTNVA.js +1 -0
- package/dist/m/{c-G6COVIZB.js → c-ESK4S67I.js} +1 -1
- package/dist/m/c-FDWR3FL7.js +1 -0
- package/dist/m/{c-WQZTI5P2.js → c-FEC6LGFF.js} +1 -1
- package/dist/m/c-FLEIU6CG.js +1 -0
- package/dist/m/c-FQAQRXGK.js +1 -0
- package/dist/m/{c-W4JCVLAB.js → c-FSQXFG5T.js} +1 -1
- package/dist/m/c-FVPNX2GO.js +135 -0
- package/dist/m/c-FYDKYW6E.js +1 -0
- package/dist/m/c-GNI7WHKF.js +32 -0
- package/dist/m/c-GO635CRU.js +3 -0
- package/dist/m/c-H3FNLBHN.js +2 -0
- package/dist/m/c-HDVH3MVC.js +72 -0
- package/dist/m/c-HIX5H5L7.js +1 -0
- package/dist/m/c-HKSLOVU4.js +1 -0
- package/dist/m/{c-SGRIYOAJ.js → c-HL6BLJNA.js} +1 -1
- package/dist/m/c-HTLYXCUH.js +1 -0
- package/dist/m/{c-QIR5CAKM.js → c-IUL7PU4P.js} +1 -1
- package/dist/m/c-IXO7EI2Q.js +1 -0
- package/dist/m/{c-W5E5P2TZ.js → c-JQF2EUCG.js} +1 -1
- package/dist/m/{c-NEL7HQW3.js → c-JWDCQL6J.js} +1 -1
- package/dist/m/c-KQKKIVLW.js +20 -0
- package/dist/m/c-LATNQ6Q5.js +1 -0
- package/dist/m/c-LD6UVV5R.js +1 -0
- package/dist/m/c-LS7IVQVW.js +1 -0
- package/dist/m/{c-JCOQVZYS.js → c-LSVN3OLG.js} +1 -1
- package/dist/m/{c-JDZDF2QM.js → c-LTSPHVAK.js} +1 -1
- package/dist/m/c-MFOSMAVM.js +3 -0
- package/dist/m/c-MJ7GVE46.js +1 -0
- package/dist/m/c-MP6S4AZU.js +6 -0
- package/dist/m/c-MQKSKQHG.js +2 -0
- package/dist/m/{c-UHUNXWKN.js → c-MQYEDF3Q.js} +1 -1
- package/dist/m/{c-IHUWUKIU.js → c-MR75YPRT.js} +1 -1
- package/dist/m/c-ND3Q2G2I.js +1 -0
- package/dist/m/c-NEQRFC4O.js +1 -0
- package/dist/m/{c-5FKRZNJV.js → c-NFQFV7OP.js} +1 -1
- package/dist/m/c-NHXOBD6A.js +1 -0
- package/dist/m/{c-QIKT6SKM.js → c-NICICT4P.js} +1 -1
- package/dist/m/c-NQAPWM7O.js +1 -0
- package/dist/m/{c-QUGCKMWF.js → c-NVU3DORS.js} +1 -1
- package/dist/m/{c-LWIDDWAR.js → c-OCYG4NBQ.js} +22 -22
- package/dist/m/c-OH2FPYSY.js +19 -0
- package/dist/m/c-OYHI67B6.js +1 -0
- package/dist/m/{c-R2XUODVY.js → c-OYW5MFPZ.js} +1 -1
- package/dist/m/{c-5B4YBZXC.js → c-P2HKMJAW.js} +1 -1
- package/dist/m/{c-TSBVMWAH.js → c-PCNJETUA.js} +1 -1
- package/dist/m/c-PDBNCJCT.js +1 -0
- package/dist/m/c-PGSBXHUE.js +1 -0
- package/dist/m/{c-5TIZ2I64.js → c-PYG2NGWU.js} +1 -1
- package/dist/m/c-Q26XHBLQ.js +1 -0
- package/dist/m/c-Q6BG3ZLO.js +1 -0
- package/dist/m/c-QENKN7QI.js +1 -0
- package/dist/m/c-QQFHEODI.js +1 -0
- package/dist/m/c-QTEUO2K6.js +1 -0
- package/dist/m/c-R23UHMXG.js +1 -0
- package/dist/m/c-RETTCOWO.js +10 -0
- package/dist/m/c-SE3SIY7Y.js +7 -0
- package/dist/m/{c-XX75FFNL.js → c-SGGGCIVD.js} +1 -1
- package/dist/m/c-SGH3UTF3.js +1 -0
- package/dist/m/c-SIM7Z3U7.js +1 -0
- package/dist/m/{c-X5LNHET5.js → c-SKMIR5HQ.js} +1 -1
- package/dist/m/{c-R27DWQKX.js → c-STRI73GQ.js} +1 -1
- package/dist/m/c-SUXHUL7U.js +1 -0
- package/dist/m/c-SXGEWPFC.js +10 -0
- package/dist/m/c-SXNSSHPP.js +1 -0
- package/dist/m/c-T3P3AXEW.js +1 -0
- package/dist/m/{c-PXLTUYZC.js → c-TFLRT5JX.js} +1 -1
- package/dist/m/{c-YCLU4UNT.js → c-TGIMBAWE.js} +1 -1
- package/dist/m/c-TGSHHFO6.js +1 -0
- package/dist/m/c-U4NTUVDZ.js +1 -0
- package/dist/m/c-UOOIF2AL.js +1 -0
- package/dist/m/{c-6TYGD5AB.js → c-UOUUI265.js} +1 -1
- package/dist/m/c-V2U3F4QX.js +1 -0
- package/dist/m/c-V2V5FKXX.js +1 -0
- package/dist/m/c-VBXRXTVS.js +26 -0
- package/dist/m/c-VHSMPITY.js +1 -0
- package/dist/m/c-VPXCXBDT.js +1 -0
- package/dist/m/c-WCG6AKA7.js +1 -0
- package/dist/m/c-WNOLMHI2.js +1 -0
- package/dist/m/{c-CRPIDGXC.js → c-X6MOYATI.js} +1 -1
- package/dist/m/{c-FQGHR32O.js → c-X75G43SZ.js} +1 -1
- package/dist/m/c-XB6EAIAR.js +13 -0
- package/dist/m/c-XMIDHEKF.js +1 -0
- package/dist/m/c-XUZMRQ7O.js +4 -0
- package/dist/m/{c-2Z22FCFK.js → c-XW5TIYYB.js} +1 -1
- package/dist/m/c-YDW4VCHP.js +1 -0
- package/dist/m/{c-QGAPG7OM.js → c-YLHTD3KX.js} +1 -1
- package/dist/m/c-YNIEIPBS.js +1 -0
- package/dist/m/c-Z4G2UK6T.js +4 -0
- package/dist/m/c-Z6MROEQ4.js +1 -0
- package/dist/m/c-ZFO5RW6G.js +2 -0
- package/dist/m/c-ZGCO4J3H.js +8 -0
- package/dist/m/c-ZKKAF4UT.js +3 -0
- package/dist/m/c-ZRGEN6QQ.js +4 -0
- package/dist/m/core/{admin-attrs-U7IJNYBG.js → admin-attrs-ONRWVXPK.js} +1 -1
- package/dist/m/core/{autosave-F5YQCKVL.js → autosave-TBKAAAFB.js} +1 -1
- package/dist/m/core/conflict-notice-FOEE6I7E.js +1 -0
- package/dist/m/core/draft-mode-4MCL3LYI.js +1 -0
- package/dist/m/core/{host-permission-6EVJGNPL.js → host-permission-X4BENZEU.js} +1 -1
- package/dist/m/core/{is-edit-mode-GCX6767E.js → is-edit-mode-FFHCQ5DR.js} +1 -1
- package/dist/m/core/{persist-CJXIL7D4.js → persist-6ILUR34S.js} +1 -1
- package/dist/m/core/{recovery-DRFZRJED.js → recovery-APWADU4K.js} +1 -1
- package/dist/m/core/save-6HT2LABE.js +1 -0
- package/dist/m/core/save-core-EVMNZQ4V.js +1 -0
- package/dist/m/core/site-edits-G4BOZMV4.js +1 -0
- package/dist/m/core/{unsaved-warning-HNQVTA2D.js → unsaved-warning-ITZDIZ2I.js} +1 -1
- package/dist/m/internals/{index-NRPNDTSA.js → index-HS3CQDFW.js} +1 -1
- package/dist/m/loader-GLLVDUZA.js +1 -0
- package/dist/m/manifest-50FF1C45.js +1 -0
- package/dist/m/manifest-edit-BE1A621D.js +1 -0
- package/dist/m/manifest.json +2072 -1811
- package/dist/m/page/editor-KAZDJNND.js +307 -0
- package/dist/m/plugins/ai-edit-LABECCSN.js +6 -0
- package/dist/m/plugins/{cms-H7DTMUQX.js → cms-T2HLI7JG.js} +16 -16
- package/dist/m/plugins/{demo-42BSYQ7T.js → demo-T7B4RIKL.js} +1 -1
- package/dist/m/plugins/{derive-QVI3LI3A.js → derive-6LPJOCAV.js} +1 -1
- package/dist/m/plugins/indicator-J4ABFARX.js +1 -0
- package/dist/m/plugins/{live-DCUBKXY4.js → live-PGBIEMGB.js} +1 -1
- package/dist/m/plugins/menu-YZL3X2AJ.js +1 -0
- package/dist/m/plugins/page-WOCNX2IN.js +2 -0
- package/dist/m/plugins/page-view-SJODOOGE.js +1 -0
- package/dist/m/plugins/{people-5XRNRP5U.js → people-C4TCWFOQ.js} +1 -1
- package/dist/m/plugins/{quickcrop-WWF5I422.js → quickcrop-X5MRIBAP.js} +2 -2
- package/dist/m/plugins/richclay-Y2NV44NA.js +549 -0
- package/dist/m/plugins/sortable-XCHQG65Z.js +1 -0
- package/dist/m/plugins/{source-6BKMK4PO.js → source-YK7MO23W.js} +1 -1
- package/dist/m/plugins/{undo-3JEYK5QY.js → undo-VLRBQNPN.js} +1 -1
- package/dist/m/plugins/upgrade-NNTJBSK2.js +1 -0
- package/dist/m/plugins/{upload-TTCQRIM5.js → upload-ZKKJV76X.js} +1 -1
- package/dist/m/plugins/visitor-drafts-RTZNONHT.js +20 -0
- package/dist/m/plugins/{visitor-drafts-owner-Z5B2OQBR.js → visitor-drafts-owner-LHUHFU3L.js} +1 -1
- package/dist/m/plugins/{visitor-menu-4LQMKLPP.js → visitor-menu-FY4KM2SW.js} +1 -1
- package/dist/m/plugins/wire-M2NR354P.js +1 -0
- package/dist/m/{reader-core-POFQCKRV.js → reader-core-T2S3JC4E.js} +2 -2
- package/dist/m/sync/live-sync-Z64YREWQ.js +1 -0
- package/dist/m/ui/index-MGTWOW2C.js +1 -0
- package/dist/m/vendor/{sap-core.vendor-OA5SKL7Q.js → sap-core.vendor-QAEPMETX.js} +1 -1
- package/dist/offline-manifest.json +1250 -1082
- package/dist/server/core.cjs +132 -109
- package/dist/server/page-compaction-worker.cjs +5 -5
- package/dist/server/rooms.mjs +3 -1
- package/docs/guide.md +89 -44
- package/entries/clay-data.js +1 -1
- package/entries/sap.js +1 -1
- package/package.json +5 -3
- package/src/blockeditor/themes/content.css +70 -0
- package/src/blockeditor/themes/light-1.css +431 -0
- package/src/blockeditor/themes/light-1.source.css +270 -0
- package/src/blockeditor/themes/light-2.css +211 -0
- package/src/blockeditor/themes/light-3.css +130 -0
- package/src/blockeditor/themes/light-4.css +299 -0
- package/src/core/conflict-notice.js +28 -0
- package/src/core/draft-mode.js +6 -0
- package/src/core/host-meta.js +6 -6
- package/src/core/is-edit-mode.js +9 -3
- package/src/core/mode-support.js +3 -0
- package/src/core/mode-switch.js +4 -4
- package/src/core/mode-transition.js +3 -1
- package/src/core/page-format-wire.js +1 -1
- package/src/core/page-format.js +8 -8
- package/src/core/save-accepted.js +6 -7
- package/src/core/save-core.js +52 -23
- package/src/core/save-failure-notice.js +37 -0
- package/src/core/save-feedback.js +6 -0
- package/src/core/save-replace.js +1 -1
- package/src/core/save-size.js +2 -2
- package/src/core/save.js +78 -15
- package/src/core/site-edits.js +166 -0
- package/src/core/unsaved-warning.js +2 -2
- package/src/core/upgrade-run.js +4 -4
- package/src/lib/ai-comment.js +23 -0
- package/src/lib/effect.js +8 -0
- package/src/lib/idb-error-code.js +20 -0
- package/src/live/fast-lane.js +10 -10
- package/src/live/local-lane.js +10 -10
- package/src/live/page-compaction.js +8 -8
- package/src/live/server-lane.js +1 -1
- package/src/loader-logic.js +48 -18
- package/src/loader.js +11 -6
- package/src/page/adopt-boot.js +1 -1
- package/src/page/adopt.js +2 -2
- package/src/page/ai-area.js +182 -0
- package/src/page/ai-preview.js +80 -0
- package/src/page/ai-surface.js +257 -0
- package/src/page/ai-tool.js +308 -145
- package/src/page/announce.js +2 -2
- package/src/page/awareness.js +18 -18
- package/src/page/blocks.js +35 -34
- package/src/page/blocksel.js +2 -2
- package/src/page/board-drag.js +11 -11
- package/src/page/board-face.js +1 -1
- package/src/page/board-panel.js +26 -26
- package/src/page/board-style.js +127 -127
- package/src/page/board-view.js +48 -48
- package/src/page/chart.js +29 -29
- package/src/page/checkpoint-proof.js +19 -19
- package/src/page/chrome.js +74 -65
- package/src/page/clipboard-shape.js +1 -1
- package/src/page/csv-import.js +4 -4
- package/src/page/drag.js +5 -5
- package/src/page/editor.js +301 -296
- package/src/page/export-html.js +8 -8
- package/src/page/features/checklist.js +36 -36
- package/src/page/features/export-html.js +19 -19
- package/src/page/features/export-markdown.js +10 -10
- package/src/page/features/import-markdown.js +13 -13
- package/src/page/features/media.js +50 -50
- package/src/page/features/table.js +17 -17
- package/src/page/features/title-cover.js +17 -17
- package/src/page/features/title-icon.js +7 -7
- package/src/page/features/toc.js +1 -1
- package/src/page/features/tool.js +42 -18
- package/src/page/format-command.js +4 -4
- package/src/page/format-gate.js +6 -6
- package/src/page/format-host.js +6 -6
- package/src/page/format-level.js +3 -3
- package/src/page/format-native-commands.js +2 -2
- package/src/page/highlight-slot.js +1 -1
- package/src/page/highlight.js +4 -4
- package/src/page/history-intent.js +2 -2
- package/src/page/image-actions.js +4 -4
- package/src/page/link.js +10 -10
- package/src/page/media.js +3 -3
- package/src/page/mention-picker.js +7 -7
- package/src/page/merge.js +1 -1
- package/src/page/placeholder.js +3 -3
- package/src/page/projection.js +11 -11
- package/src/page/reader-patch.js +2 -2
- package/src/page/recover.js +26 -26
- package/src/page/recovery-ui.js +28 -28
- package/src/page/rich/chart.js +1 -1
- package/src/page/rich/page-tool.js +21 -6
- package/src/page/rich/tool-runtime.js +3 -2
- package/src/page/save-projection.js +2 -2
- package/src/page/save-states.js +9 -9
- package/src/page/schema.js +9 -9
- package/src/page/table-style.js +21 -21
- package/src/page/toc-link.js +1 -1
- package/src/page/tool-capability.js +8 -3
- package/src/page/tool-transport.js +52 -0
- package/src/page/tool-views.js +4 -0
- package/src/page/tools.js +6 -1
- package/src/page/view.js +6 -6
- package/src/page/ybind/binding.js +1 -1
- package/src/page/ybind/conversion.js +1 -1
- package/src/page/ybind/layout.js +4 -4
- package/src/page/ybind/migrate.js +1 -1
- package/src/page/ybind/origins.js +2 -2
- package/src/page/ybind/provider.js +2 -2
- package/src/page/ybind/recovery-proof.js +3 -3
- package/src/page/ybind/recovery-replay.js +2 -2
- package/src/plugins/ai-edit/picker.js +235 -0
- package/src/plugins/ai-edit.js +220 -34
- package/src/plugins/indicator-chip.js +39 -34
- package/src/plugins/indicator.js +13 -4
- package/src/plugins/live.js +21 -13
- package/src/plugins/menu/actions.js +89 -17
- package/src/plugins/menu/seo.js +32 -6
- package/src/plugins/menu/shell-style.js +26 -7
- package/src/plugins/menu/shell.js +12 -7
- package/src/plugins/menu/variants.js +6 -10
- package/src/plugins/menu/visitor.js +44 -34
- package/src/plugins/menu.js +3 -2
- package/src/plugins/page-view.js +2 -2
- package/src/plugins/page.js +257 -98
- package/src/plugins/sortable.js +7 -0
- package/src/plugins/upgrade.js +2 -1
- package/src/plugins/visitor-drafts/commit.js +303 -0
- package/src/plugins/visitor-drafts/idb.js +221 -0
- package/src/plugins/visitor-drafts/intro-style.js +1 -1
- package/src/plugins/visitor-drafts/intro.js +8 -25
- package/src/plugins/visitor-drafts/prompt.js +30 -4
- package/src/plugins/visitor-drafts/session.js +345 -0
- package/src/plugins/visitor-drafts/store.js +351 -32
- package/src/plugins/visitor-drafts.js +158 -27
- package/src/server/checkpoint/index.js +8 -8
- package/src/server/rooms/index.js +2 -2
- package/src/server/rooms/live-data-rooms.js +35 -35
- package/src/server/rooms/live-page-history.js +2 -2
- package/src/server/rooms/page-admission.js +34 -6
- package/src/server/rooms/page-conversion.js +1 -1
- package/src/server/rooms/page-lineage.js +4 -4
- package/src/server/rooms/page-rebase.js +4 -4
- package/src/server/rooms/page-room-conformance.js +14 -14
- package/src/server/rooms/page-state.js +2 -2
- package/src/server/rooms/page-write-admission.js +7 -7
- package/src/server/rooms/page-write-guard.js +3 -3
- package/src/sync/data-guard-notice.js +265 -0
- package/src/sync/live-sync.js +54 -11
- package/src/sync/page-relay.js +4 -4
- package/src/sync/presence-store.js +1 -1
- package/src/sync/presence-target.js +3 -3
- package/src/sync/presence.js +1 -1
- package/src/sync/stream.js +54 -13
- package/src/sync/wire-profile.js +8 -3
- package/src/ui/bevel.js +1 -1
- package/src/ui/index.js +9 -10
- package/src/ui/skins/cms.js +1 -1
- package/src/ui/skins/quickcrop.js +1 -1
- package/src/ui/skins/richclay.js +2 -2
- package/src/ui/toast-lazy.js +12 -3
- package/src/ui/toast.js +9 -3
- package/src/vendor/hyper-html-api-data.vendor.js +1 -1
- package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
- package/src/vendor/hypercms.vendor.js +16 -16
- package/src/vendor/richclay.vendor.js +48 -20
- package/src/vendor/sap-core.vendor.js +1 -1
- package/tool-generation/context.md +44 -0
- package/tool-generation/system.md +23 -0
- package/dist/m/c-24PMUJIV.js +0 -2
- package/dist/m/c-2AYVYEI4.js +0 -1
- package/dist/m/c-2AZDZWCK.js +0 -1
- package/dist/m/c-2L6O5PDS.js +0 -6
- package/dist/m/c-2RYOGUQM.js +0 -2
- package/dist/m/c-2ZVOEU4M.js +0 -1
- package/dist/m/c-37NAONZA.js +0 -1
- package/dist/m/c-3GJ4SDE2.js +0 -1
- package/dist/m/c-3O7OBDCM.js +0 -1
- package/dist/m/c-5OJ7YF2N.js +0 -1
- package/dist/m/c-5WMXEKFH.js +0 -135
- package/dist/m/c-6RDOKLN7.js +0 -1
- package/dist/m/c-A4IP6MBC.js +0 -1
- package/dist/m/c-A6L3FMUS.js +0 -1
- package/dist/m/c-ATKAOH23.js +0 -1
- package/dist/m/c-B6O7AJYG.js +0 -32
- package/dist/m/c-BCGWJYXW.js +0 -1
- package/dist/m/c-BGKAT2FL.js +0 -7
- package/dist/m/c-BYTKL6UX.js +0 -1
- package/dist/m/c-DHBRA6DL.js +0 -1
- package/dist/m/c-DTMZ3ZHI.js +0 -1
- package/dist/m/c-E3ZXARWA.js +0 -1
- package/dist/m/c-EK63G3CG.js +0 -1
- package/dist/m/c-ELYIIYRS.js +0 -1
- package/dist/m/c-EXKHPOVC.js +0 -3
- package/dist/m/c-FCSZ3DC5.js +0 -20
- package/dist/m/c-FTWGYI5E.js +0 -1
- package/dist/m/c-G4PWUDD5.js +0 -1
- package/dist/m/c-GBKB5WDL.js +0 -2
- package/dist/m/c-GBTVCJXC.js +0 -1
- package/dist/m/c-GC2HICFJ.js +0 -1
- package/dist/m/c-GRWMD6O5.js +0 -1
- package/dist/m/c-HCNWJE4A.js +0 -4
- package/dist/m/c-HG2C77BM.js +0 -4
- package/dist/m/c-JGHEACGK.js +0 -1
- package/dist/m/c-JGLX6TBK.js +0 -1
- package/dist/m/c-JNRKHYWO.js +0 -1
- package/dist/m/c-KBS3UV6B.js +0 -1
- package/dist/m/c-KQ7E3PYY.js +0 -1
- package/dist/m/c-L3F5ZLIV.js +0 -1
- package/dist/m/c-L4KMT7FO.js +0 -6
- package/dist/m/c-L5VFATT6.js +0 -1
- package/dist/m/c-LCYPMULG.js +0 -1
- package/dist/m/c-LIGQYZ7M.js +0 -6
- package/dist/m/c-LKIWEPAV.js +0 -1
- package/dist/m/c-MPSKIU2I.js +0 -1
- package/dist/m/c-N7JPUPGC.js +0 -1
- package/dist/m/c-NK33MMYF.js +0 -1
- package/dist/m/c-NQLWOHLH.js +0 -10
- package/dist/m/c-NTHUTABN.js +0 -1
- package/dist/m/c-NWX3MFT4.js +0 -1
- package/dist/m/c-OKXI52N7.js +0 -1
- package/dist/m/c-OPONJ2UK.js +0 -128
- package/dist/m/c-OWQWOGYI.js +0 -1
- package/dist/m/c-P6Q2ZTFQ.js +0 -1
- package/dist/m/c-QEN6HZCA.js +0 -1
- package/dist/m/c-QIXT3Q7C.js +0 -28
- package/dist/m/c-QM5LXRDT.js +0 -9
- package/dist/m/c-QYYUVLVG.js +0 -1
- package/dist/m/c-RQRMPZXD.js +0 -1
- package/dist/m/c-SBCBXKOZ.js +0 -26
- package/dist/m/c-SKF5HL4I.js +0 -4
- package/dist/m/c-SRPC3VBE.js +0 -1
- package/dist/m/c-TANBH7FI.js +0 -1
- package/dist/m/c-THGQQDYY.js +0 -1
- package/dist/m/c-UD5JZTDL.js +0 -1
- package/dist/m/c-UDGEFWNU.js +0 -1
- package/dist/m/c-UGNV5MFB.js +0 -1
- package/dist/m/c-VIRFF5HG.js +0 -8
- package/dist/m/c-VJYEVRYK.js +0 -1
- package/dist/m/c-VQCEOGKH.js +0 -13
- package/dist/m/c-VYJ7O4RI.js +0 -34
- package/dist/m/c-W7IPVCAA.js +0 -1
- package/dist/m/c-WBGWGOM7.js +0 -1
- package/dist/m/c-WJ5DRBHT.js +0 -72
- package/dist/m/c-X5WC4PX5.js +0 -1
- package/dist/m/c-XCFQEWW6.js +0 -3
- package/dist/m/c-XGH4GS2T.js +0 -1
- package/dist/m/c-XLQ3BDMM.js +0 -1
- package/dist/m/c-ZQXNHZZT.js +0 -72
- package/dist/m/core/conflict-notice-42H4ASLA.js +0 -1
- package/dist/m/core/draft-mode-WSQAT67S.js +0 -1
- package/dist/m/core/save-ESPSQPLV.js +0 -1
- package/dist/m/core/save-core-6SQHOR6W.js +0 -1
- package/dist/m/loader-FBDE2UAB.js +0 -1
- package/dist/m/manifest-1D2F5CA5.js +0 -1
- package/dist/m/manifest-edit-F7CC5CFD.js +0 -1
- package/dist/m/page/editor-OSKOW6RS.js +0 -304
- package/dist/m/plugins/ai-edit-VV4QTINH.js +0 -6
- package/dist/m/plugins/indicator-WVPGMOZ7.js +0 -1
- package/dist/m/plugins/menu-Q4MFFQ6E.js +0 -1
- package/dist/m/plugins/page-OAWUJQEL.js +0 -2
- package/dist/m/plugins/page-view-DZ26ZDDQ.js +0 -1
- package/dist/m/plugins/richclay-PGOOJ4OK.js +0 -517
- package/dist/m/plugins/sortable-266HY6TR.js +0 -1
- package/dist/m/plugins/upgrade-BNZVFHMZ.js +0 -1
- package/dist/m/plugins/visitor-drafts-2AUYKAIJ.js +0 -20
- package/dist/m/plugins/wire-W2EV36KZ.js +0 -1
- package/dist/m/sync/live-sync-XGUPHI3I.js +0 -1
- package/dist/m/ui/index-VGYAKAKI.js +0 -1
- package/dist/page/themes/light-1.css +0 -431
- package/src/page/themes/content.css +0 -70
- package/src/page/themes/light-1.css +0 -431
- package/src/page/themes/light-1.source.css +0 -270
- package/src/page/themes/light-2.css +0 -211
- package/src/page/themes/light-3.css +0 -130
- package/src/page/themes/light-4.css +0 -299
- /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
- /package/dist/m/plugins/{quickcrop-WWF5I422.js.LEGAL.txt → quickcrop-X5MRIBAP.js.LEGAL.txt} +0 -0
- /package/dist/m/plugins/{richclay-PGOOJ4OK.js.LEGAL.txt → richclay-Y2NV44NA.js.LEGAL.txt} +0 -0
package/src/core/save-core.js
CHANGED
|
@@ -85,6 +85,20 @@ export function setSaveHeader(name, value) {
|
|
|
85
85
|
else extraSaveHeaders.set(name, String(value));
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// Headers that depend on the bytes of one save. Each source gets the exact string being
|
|
89
|
+
// sent and returns `{ name: value }` or null. A source that throws is skipped: a save
|
|
90
|
+
// must not fail because a header could not be computed.
|
|
91
|
+
const saveHeaderSources = new Set();
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {(content: string) => (Object<string, string>|null)} source
|
|
95
|
+
* @returns {() => void} removes the source
|
|
96
|
+
*/
|
|
97
|
+
export function addSaveHeaderSource(source) {
|
|
98
|
+
saveHeaderSources.add(source);
|
|
99
|
+
return () => saveHeaderSources.delete(source);
|
|
100
|
+
}
|
|
101
|
+
|
|
88
102
|
/**
|
|
89
103
|
* Send the next save with `keepalive`, so a request whose page is going away (a tab
|
|
90
104
|
* closing, a navigation) is still finished by the transport.
|
|
@@ -229,7 +243,10 @@ function successResult(data) {
|
|
|
229
243
|
msg: data.msg ?? 'Saved',
|
|
230
244
|
msgType: data.msgType || 'success',
|
|
231
245
|
code: data.code ?? null,
|
|
232
|
-
etag: data.etag ?? null
|
|
246
|
+
etag: data.etag ?? null,
|
|
247
|
+
// A multi-page host's answer about shared values: which declared keys it stored,
|
|
248
|
+
// which it refused, and which values this tab is behind on. Absent on every other save.
|
|
249
|
+
...(data.siteData ? { siteData: data.siteData } : {})
|
|
233
250
|
};
|
|
234
251
|
}
|
|
235
252
|
|
|
@@ -359,7 +376,7 @@ function errorResult(err) {
|
|
|
359
376
|
// An attempt of this save timed out, so it may have landed whatever the
|
|
360
377
|
// final answer says.
|
|
361
378
|
hadTimeout: timedOut || err.hadTimeout === true,
|
|
362
|
-
// The typed refusal behind `code`, kept for the caller: `
|
|
379
|
+
// The typed refusal behind `code`, kept for the caller: `blockeditor-capacity` carries
|
|
363
380
|
// which limit was hit, the conversion codes carry which condition failed.
|
|
364
381
|
reason: err.reason ?? null,
|
|
365
382
|
// Only meaningful while a save can still retry; a caller sees how long the
|
|
@@ -428,7 +445,7 @@ function invalidHeaderResult() {
|
|
|
428
445
|
}
|
|
429
446
|
|
|
430
447
|
// How many extra sends one logical save may make while the host is still
|
|
431
|
-
// converting the page. `
|
|
448
|
+
// converting the page. `blockeditor-converting` is a pre-commit refusal, so the same
|
|
432
449
|
// body is worth offering again; anything else is surfaced on the first answer.
|
|
433
450
|
const PAGE_CONVERTING_MAX_RETRIES = 3;
|
|
434
451
|
const HTTP_DATE = /^(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT|(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), \d{2}-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d{2} \d{2}:\d{2}:\d{2} GMT|(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?: \d|\d{2}) \d{2}:\d{2}:\d{2} \d{4})$/;
|
|
@@ -452,7 +469,7 @@ function parseRetryAfter(value) {
|
|
|
452
469
|
return Math.max(0, Math.min(3600, Math.round((when - Date.now()) / 1000)));
|
|
453
470
|
}
|
|
454
471
|
|
|
455
|
-
// The host grammar for `
|
|
472
|
+
// The host grammar for `Blockeditor-Conversion`, from `server-lib/page-conversion.js`:
|
|
456
473
|
// `file` takes a 64-lowercase-hex baseline and no epoch; `room` takes the same
|
|
457
474
|
// baseline plus an epoch of [A-Za-z0-9-]{1,64}. Validating here means a caller
|
|
458
475
|
// with bad metadata gets a typed answer instead of a wasted round trip.
|
|
@@ -518,10 +535,11 @@ function formatConversionHeader(conversion) {
|
|
|
518
535
|
* @param {string} saveId - this attempt's §6 receipt id
|
|
519
536
|
* @param {?string} etag - the stamp to send as If-Match, or null
|
|
520
537
|
* @param {boolean} keepalive - whether to ask the transport to see the request through
|
|
521
|
-
* @param {?string} conversionHeader - the `
|
|
538
|
+
* @param {?string} conversionHeader - the `Blockeditor-Conversion` value for this logical save, or null
|
|
539
|
+
* @param {boolean} perSaveHeaders - false for a lane that sends bytes this tab did not edit (replace), which declares nothing about them
|
|
522
540
|
* @returns {{url: string, options: Object}}
|
|
523
541
|
*/
|
|
524
|
-
export function buildSaveRequest(content, userDriven, signal, saveId, etag, keepalive = false, conversionHeader = null) {
|
|
542
|
+
export function buildSaveRequest(content, userDriven, signal, saveId, etag, keepalive = false, conversionHeader = null, perSaveHeaders = true) {
|
|
525
543
|
const token = saveToken();
|
|
526
544
|
const path = token ? `${SAVE_PATH}/${token}` : SAVE_PATH;
|
|
527
545
|
const options = {
|
|
@@ -552,6 +570,18 @@ export function buildSaveRequest(content, userDriven, signal, saveId, etag, keep
|
|
|
552
570
|
|
|
553
571
|
for (const [name, value] of extraSaveHeaders) options.headers[name] = value;
|
|
554
572
|
|
|
573
|
+
if (perSaveHeaders) {
|
|
574
|
+
for (const source of saveHeaderSources) {
|
|
575
|
+
let extra = null;
|
|
576
|
+
try {
|
|
577
|
+
extra = source(content);
|
|
578
|
+
} catch (err) {
|
|
579
|
+
console.error('clayjs: a save header source failed:', err);
|
|
580
|
+
}
|
|
581
|
+
if (extra) for (const [name, value] of Object.entries(extra)) options.headers[name] = String(value);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
555
585
|
// Set after the process-global headers so the type describing THESE bytes is the one
|
|
556
586
|
// that goes out, the same rule the conversion header below follows.
|
|
557
587
|
if (typeof options.body !== 'string') options.headers['Content-Type'] = SAVE_BODY_TYPE;
|
|
@@ -559,7 +589,7 @@ export function buildSaveRequest(content, userDriven, signal, saveId, etag, keep
|
|
|
559
589
|
// The conversion request belongs to ONE logical save, and goes on every attempt
|
|
560
590
|
// of it. Set after the process-global headers so the capture-bound value is the
|
|
561
591
|
// one that describes these bytes.
|
|
562
|
-
if (conversionHeader) options.headers['
|
|
592
|
+
if (conversionHeader) options.headers['Blockeditor-Conversion'] = conversionHeader;
|
|
563
593
|
|
|
564
594
|
if (etag) options.headers['If-Match'] = etag;
|
|
565
595
|
|
|
@@ -638,14 +668,13 @@ function draftResponse(status, body) {
|
|
|
638
668
|
function sendToDraftStore(content) {
|
|
639
669
|
const send = draftTransport();
|
|
640
670
|
if (!send) return Promise.resolve(draftResponse(503, { msg: "Saving in this browser is not ready yet", msgType: "error", code: "draft-not-ready" }));
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
});
|
|
671
|
+
// The transport's promise settles once the draft database holds the save.
|
|
672
|
+
return Promise.resolve()
|
|
673
|
+
.then(() => send(content))
|
|
674
|
+
.then(
|
|
675
|
+
() => draftResponse(200, { msg: "Saved in this browser", msgType: "success" }),
|
|
676
|
+
(error) => draftResponse(507, { msg: "Couldn't save in this browser. Storage is full or turned off.", msgType: "error", code: error?.code || "draft-storage" }),
|
|
677
|
+
);
|
|
649
678
|
}
|
|
650
679
|
|
|
651
680
|
/**
|
|
@@ -876,7 +905,7 @@ async function sendSave(content, { captureId = null, conversionHeader = null, at
|
|
|
876
905
|
// is fast and conclusive, so it gets its own turn regardless.
|
|
877
906
|
let reSent = false;
|
|
878
907
|
let duplicateResolved = false;
|
|
879
|
-
// `
|
|
908
|
+
// `blockeditor-converting` is the one refusal that is worth offering the same bytes
|
|
880
909
|
// again: the host has written nothing and is telling us when it expects to be
|
|
881
910
|
// ready. The capture these bytes came from is pinned here, so a capture made
|
|
882
911
|
// while we wait — newer edits, a disk frame — cancels the retry instead of
|
|
@@ -916,7 +945,7 @@ async function sendSave(content, { captureId = null, conversionHeader = null, at
|
|
|
916
945
|
continue;
|
|
917
946
|
}
|
|
918
947
|
|
|
919
|
-
if (err.code === '
|
|
948
|
+
if (err.code === 'blockeditor-converting' && convertingRetries < PAGE_CONVERTING_MAX_RETRIES) {
|
|
920
949
|
if (currentCaptureId() !== retryGuard) throw err;
|
|
921
950
|
convertingRetries += 1;
|
|
922
951
|
// Default a missing or unparseable header to one second, so a host that
|
|
@@ -939,14 +968,14 @@ async function sendSave(content, { captureId = null, conversionHeader = null, at
|
|
|
939
968
|
// thing whether the bytes came from a bound capture or a raw caller. `captureId`
|
|
940
969
|
// is null for a raw save: it belongs to no capture and can confirm none.
|
|
941
970
|
//
|
|
942
|
-
// `
|
|
971
|
+
// `blockeditorFormatFloor` is internal: one save may carry a floor this tab has not
|
|
943
972
|
// noted yet, which is how the format gate's activation writes the marker. It
|
|
944
973
|
// raises these bytes only, never what the tab knows the document at.
|
|
945
|
-
function runSave(html, captureId, callback, conversion, capture = null,
|
|
974
|
+
function runSave(html, captureId, callback, conversion, capture = null, blockeditorFormatFloor = 0, lane = null) {
|
|
946
975
|
// A capture taken before the floor rose cannot drop the marker by omitting it:
|
|
947
976
|
// the bytes that go out carry the floor this tab last saw. At floor 0 this is
|
|
948
977
|
// the string itself, unchanged.
|
|
949
|
-
html = withPageFormatFloor(html, Math.max(knownPageFormatFloor(),
|
|
978
|
+
html = withPageFormatFloor(html, Math.max(knownPageFormatFloor(), blockeditorFormatFloor));
|
|
950
979
|
return new Promise((resolve) => {
|
|
951
980
|
const attempts = [];
|
|
952
981
|
// Read once, before the first request: the artifact belongs to the capture that
|
|
@@ -1021,7 +1050,7 @@ function runSave(html, captureId, callback, conversion, capture = null, pageForm
|
|
|
1021
1050
|
// same measurement. Its own answer about the size wins over nothing: the bytes
|
|
1022
1051
|
// are this attempt's, and the limit is still only what discovery advertised.
|
|
1023
1052
|
if (result.code === 'save-too-large' && !result.size) result.size = size;
|
|
1024
|
-
if (result.ok) notifySaveAccepted(html, captureId ?? null, artifact);
|
|
1053
|
+
if (result.ok) notifySaveAccepted(html, captureId ?? null, artifact, result.siteData ?? null);
|
|
1025
1054
|
finished(result);
|
|
1026
1055
|
});
|
|
1027
1056
|
});
|
|
@@ -1059,8 +1088,8 @@ export function saveHtml(html, callback = () => {}) {
|
|
|
1059
1088
|
* @param {Object} [options.conversion] - {source:'file', base} or {source:'room', base, epoch}
|
|
1060
1089
|
* @returns {Promise<Object>}
|
|
1061
1090
|
*/
|
|
1062
|
-
export function saveCapturedHtml(html, captureId, callback = () => {}, { conversion, capture = null,
|
|
1063
|
-
return runSave(html, captureId, callback, conversion, capture,
|
|
1091
|
+
export function saveCapturedHtml(html, captureId, callback = () => {}, { conversion, capture = null, blockeditorFormatFloor, lane = null } = {}) {
|
|
1092
|
+
return runSave(html, captureId, callback, conversion, capture, blockeditorFormatFloor, lane);
|
|
1064
1093
|
}
|
|
1065
1094
|
|
|
1066
1095
|
/**
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { saveFeedback } from "./save-feedback.js";
|
|
2
|
+
import { toastPersistent, dismissPersistent, preload } from "../ui/toast-lazy.js";
|
|
3
|
+
|
|
4
|
+
// A failed save is never silent. core/save.js fetches this file at the person's first
|
|
5
|
+
// input and hands it every failed save no listener claimed. A page with the indicator chip, clay-ui or its own
|
|
6
|
+
// notices (`clay.saveFeedback = "own"`) already reports failures, so nothing is added
|
|
7
|
+
// there. Everywhere else the toast stack shows what clay-ui would have shown. Core keeps
|
|
8
|
+
// calling in for every later save, so a failure the block editor claims stays its own,
|
|
9
|
+
// and the next save that lands takes the toast down. The toast stack is the block
|
|
10
|
+
// editor's too, so its dismissals reach these toasts.
|
|
11
|
+
const TOASTS = {
|
|
12
|
+
error: ["Couldn't save", "error"],
|
|
13
|
+
offline: ["Offline, will retry", "warning"],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let shown = false;
|
|
17
|
+
|
|
18
|
+
const unheard = () => !(saveFeedback.chip || saveFeedback.toasts || window.clay?.saveFeedback === "own");
|
|
19
|
+
|
|
20
|
+
// core/save.js calls this at the person's first input, so the toast can still show offline.
|
|
21
|
+
export function warm() {
|
|
22
|
+
if (unheard()) preload();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function reportFailure(state) {
|
|
26
|
+
if (!TOASTS[state] || !unheard()) return;
|
|
27
|
+
shown = true;
|
|
28
|
+
// One failure notice at a time: a connection that comes and goes swaps them.
|
|
29
|
+
for (const [other, [message]] of Object.entries(TOASTS)) if (other !== state) dismissPersistent(message);
|
|
30
|
+
toastPersistent(...TOASTS[state]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function clearFailures() {
|
|
34
|
+
if (!shown) return;
|
|
35
|
+
shown = false;
|
|
36
|
+
for (const [message] of Object.values(TOASTS)) dismissPersistent(message);
|
|
37
|
+
}
|
|
@@ -4,3 +4,9 @@
|
|
|
4
4
|
// read them only when an event arrives, so load order does not matter. savedToast is
|
|
5
5
|
// the page's `clay.saveToast` switch: Saved goes to a toast instead of the chip.
|
|
6
6
|
export const saveFeedback = { chip: false, toasts: false, savedToast: false };
|
|
7
|
+
|
|
8
|
+
// Set on a failed save's event detail by a listener that put its own notice up (the block
|
|
9
|
+
// editor's retry and blocked notices), so the chip and the fallback toast stay out of it.
|
|
10
|
+
// core/save.js writes the same key without importing this file, which the edit boot does
|
|
11
|
+
// not otherwise fetch.
|
|
12
|
+
export const FAILURE_CLAIMED = Symbol.for("clay.saveFailureClaimed");
|
package/src/core/save-replace.js
CHANGED
|
@@ -46,7 +46,7 @@ export async function replace(html, { etag } = {}) {
|
|
|
46
46
|
const controller = new AbortController();
|
|
47
47
|
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
48
48
|
try {
|
|
49
|
-
const { url, options } = buildSaveRequest(html, true, controller.signal, mintSaveId(), stamp);
|
|
49
|
+
const { url, options } = buildSaveRequest(html, true, controller.signal, mintSaveId(), stamp, false, null, false);
|
|
50
50
|
const res = await fetch(url, options);
|
|
51
51
|
const text = await res.text();
|
|
52
52
|
let data = {};
|
package/src/core/save-size.js
CHANGED
|
@@ -32,13 +32,13 @@ function adopt(meta) {
|
|
|
32
32
|
return limit;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// A Document's live state rides in one <script merge="
|
|
35
|
+
// A Document's live state rides in one <script merge="blockeditor"> tag that Hyperclay
|
|
36
36
|
// stores apart from the file, and its limit (`maxBytesMeasures: 'file'`) is judged on
|
|
37
37
|
// the body without that tag, the way the host's splitPage() cuts it: the whole line
|
|
38
38
|
// when the tag stands alone on it, otherwise the tag itself. Where the host would not
|
|
39
39
|
// split (no tag, two tags), the cut here can only make the count smaller, so the
|
|
40
40
|
// host's own 413 stays the answer and nothing is refused that the host would store.
|
|
41
|
-
const PAGE_TAG = /<script\b[^>]*\bmerge=(["'])
|
|
41
|
+
const PAGE_TAG = /<script\b[^>]*\bmerge=(["'])blockeditor\1[^>]*>[\s\S]*?<\/script\s*>/i;
|
|
42
42
|
|
|
43
43
|
export function storedFile(html, measure = measures) {
|
|
44
44
|
if (measure !== 'file' || typeof html !== 'string') return html;
|
package/src/core/save.js
CHANGED
|
@@ -111,6 +111,43 @@ function setSaveState(state, msg = '', msgType = '', extra = null) {
|
|
|
111
111
|
detail: { msg, msgType, timestamp: Date.now(), ...(extra || {}) }
|
|
112
112
|
});
|
|
113
113
|
document.dispatchEvent(event);
|
|
114
|
+
reportIfUnheard(state, event.detail);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// A failed save is never silent. Every listener has seen the event by now; a listener
|
|
118
|
+
// that put its own notice up (the block editor's retry and blocked notices) claimed it.
|
|
119
|
+
// The rest goes to save-failure-notice.js, which shows a toast when nothing else on the
|
|
120
|
+
// page reports failures and takes it down when a later save lands. The claim key is
|
|
121
|
+
// save-feedback.js's FAILURE_CLAIMED.
|
|
122
|
+
const FAILURE_CLAIMED = Symbol.for('clay.saveFailureClaimed');
|
|
123
|
+
export const claimFailure = (event) => { if (event?.detail) event.detail[FAILURE_CLAIMED] = true; };
|
|
124
|
+
|
|
125
|
+
let failureNotice = null;
|
|
126
|
+
const loadFailureNotice = () => {
|
|
127
|
+
failureNotice ??= import('./save-failure-notice.js').catch((error) => {
|
|
128
|
+
failureNotice = null;
|
|
129
|
+
throw error;
|
|
130
|
+
});
|
|
131
|
+
return failureNotice;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// A save fails offline most of all, when nothing more can be downloaded. So the notice and
|
|
135
|
+
// the toast stack it needs arrive at the person's first input, as the indicator's chip
|
|
136
|
+
// does, never at boot.
|
|
137
|
+
const warmFailureNotice = () => { loadFailureNotice().then((m) => m.warm(), () => {}); };
|
|
138
|
+
for (const type of ['beforeinput', 'keydown', 'pointerdown']) {
|
|
139
|
+
document.addEventListener(type, warmFailureNotice, { once: true, capture: true });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function reportIfUnheard(state, detail) {
|
|
143
|
+
if (state === 'saved') {
|
|
144
|
+
failureNotice?.then((m) => m.clearFailures(), () => {});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if ((state !== 'error' && state !== 'offline') || detail?.[FAILURE_CLAIMED]) return;
|
|
148
|
+
loadFailureNotice().then((m) => m.reportFailure(state), (error) => {
|
|
149
|
+
console.error('clayjs: a save failed and the notice for it could not be downloaded', error);
|
|
150
|
+
});
|
|
114
151
|
}
|
|
115
152
|
|
|
116
153
|
/**
|
|
@@ -931,7 +968,7 @@ function prepareInvalidation(ctx, epoch, morph) {
|
|
|
931
968
|
if (!savesAdmitted(ctx.permit)) return 'session';
|
|
932
969
|
if (refusesSave()) return 'refused';
|
|
933
970
|
if (baselineRevision !== epoch.baselines) return 'newer save';
|
|
934
|
-
if (Math.max(knownPageFormatFloor(), ctx.
|
|
971
|
+
if (Math.max(knownPageFormatFloor(), ctx.blockeditorFormatFloor) > epoch.floor) return 'floor';
|
|
935
972
|
return null;
|
|
936
973
|
}
|
|
937
974
|
|
|
@@ -969,7 +1006,7 @@ async function settleCaptured({
|
|
|
969
1006
|
captureId,
|
|
970
1007
|
gateToken,
|
|
971
1008
|
costMs,
|
|
972
|
-
|
|
1009
|
+
blockeditorFormatFloor = 0,
|
|
973
1010
|
quietConflict = false,
|
|
974
1011
|
laneHeld = false,
|
|
975
1012
|
lane = null,
|
|
@@ -1069,7 +1106,7 @@ async function settleCaptured({
|
|
|
1069
1106
|
// lane busy and waits that request out, which is what its 'clean' answer describes.
|
|
1070
1107
|
// A prepared save holds the lane itself, so its caller does the draining instead.
|
|
1071
1108
|
if (!laneHeld) drainPendingSave();
|
|
1072
|
-
}, { capture,
|
|
1109
|
+
}, { capture, blockeditorFormatFloor, lane });
|
|
1073
1110
|
});
|
|
1074
1111
|
}
|
|
1075
1112
|
|
|
@@ -1092,7 +1129,7 @@ async function runSlicedSave(ctx, lane) {
|
|
|
1092
1129
|
const epoch = {
|
|
1093
1130
|
generation: saveGenerationAt,
|
|
1094
1131
|
baselines: baselineRevision,
|
|
1095
|
-
floor: Math.max(knownPageFormatFloor(), ctx.
|
|
1132
|
+
floor: Math.max(knownPageFormatFloor(), ctx.blockeditorFormatFloor),
|
|
1096
1133
|
};
|
|
1097
1134
|
const job = prepDriver.beginSaveCapture({
|
|
1098
1135
|
requestRefresh: true,
|
|
@@ -1135,7 +1172,7 @@ async function runSlicedSave(ctx, lane) {
|
|
|
1135
1172
|
captureId: captured.captureId,
|
|
1136
1173
|
gateToken,
|
|
1137
1174
|
costMs: prepared.activeMs,
|
|
1138
|
-
|
|
1175
|
+
blockeditorFormatFloor: ctx.blockeditorFormatFloor,
|
|
1139
1176
|
quietConflict: ctx.quietConflict,
|
|
1140
1177
|
laneHeld: true,
|
|
1141
1178
|
lane,
|
|
@@ -1225,6 +1262,32 @@ function attemptSave(options, callback = () => {}) {
|
|
|
1225
1262
|
return adoptionQueue.then(() => attemptSaveNow(options, callback));
|
|
1226
1263
|
}
|
|
1227
1264
|
|
|
1265
|
+
// A save waiting on something it needs before its capture: the source map's install, or
|
|
1266
|
+
// the preparer loaded on demand. The chip moves only on save events, so without this it
|
|
1267
|
+
// kept its last word, usually "Saved", for the whole wait, which on a host whose every
|
|
1268
|
+
// connection was held open lasted until a tab closed. Past the usual 500 ms it says
|
|
1269
|
+
// 'saving'; if the attempt then sends nothing and the page has nothing left to save, it
|
|
1270
|
+
// goes back to 'saved' through `clay:save-status`, never `clay:save-saved`, which would
|
|
1271
|
+
// run every [onaftersave] handler as though a save had happened.
|
|
1272
|
+
function afterWait(wait, attempt) {
|
|
1273
|
+
let shown = false;
|
|
1274
|
+
const timer = setTimeout(() => {
|
|
1275
|
+
shown = true;
|
|
1276
|
+
setSaveState('saving');
|
|
1277
|
+
}, 500);
|
|
1278
|
+
return wait.then(() => {
|
|
1279
|
+
clearTimeout(timer);
|
|
1280
|
+
return attempt();
|
|
1281
|
+
}).then((result) => {
|
|
1282
|
+
if (shown && !isSaveInProgress() && !pageMaybeDirty()
|
|
1283
|
+
&& document.documentElement.getAttribute('savestatus') === 'saving') {
|
|
1284
|
+
document.documentElement.setAttribute('savestatus', 'saved');
|
|
1285
|
+
document.dispatchEvent(new CustomEvent('clay:save-status', { detail: { state: 'saved' } }));
|
|
1286
|
+
}
|
|
1287
|
+
return result;
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1228
1291
|
function attemptSaveNow({ kind, permit = null }, callback = () => {}) {
|
|
1229
1292
|
const explicit = kind === 'explicit';
|
|
1230
1293
|
|
|
@@ -1321,10 +1384,10 @@ function attemptSaveNow({ kind, permit = null }, callback = () => {}) {
|
|
|
1321
1384
|
|
|
1322
1385
|
// The source map asks a save to wait here: after the gates, before the capture.
|
|
1323
1386
|
const prepare = window.clay?.source?.ensureForSave?.();
|
|
1324
|
-
if (prepare) return prepare
|
|
1387
|
+
if (prepare) return resolve(afterWait(prepare, () => attemptSaveNow({ kind, permit }, callback)));
|
|
1325
1388
|
|
|
1326
1389
|
if (preparesSliced() && !prepDriver) {
|
|
1327
|
-
loadPreparer()
|
|
1390
|
+
resolve(afterWait(loadPreparer(), () => attemptSaveNow({ kind, permit }, callback)));
|
|
1328
1391
|
return;
|
|
1329
1392
|
}
|
|
1330
1393
|
|
|
@@ -1348,7 +1411,7 @@ function attemptSaveNow({ kind, permit = null }, callback = () => {}) {
|
|
|
1348
1411
|
explicit,
|
|
1349
1412
|
forced: false,
|
|
1350
1413
|
permit,
|
|
1351
|
-
|
|
1414
|
+
blockeditorFormatFloor: 0,
|
|
1352
1415
|
quietConflict: false,
|
|
1353
1416
|
settleSoonQueued,
|
|
1354
1417
|
callback,
|
|
@@ -1433,8 +1496,8 @@ function savePagePermitted(permit, callback = () => {}) {
|
|
|
1433
1496
|
* @param {Function} callback - Optional callback for custom handling
|
|
1434
1497
|
* @returns {Promise<{msg: string, msgType: string}>}
|
|
1435
1498
|
*/
|
|
1436
|
-
export function savePageForce(callback = () => {}, { quietConflict = false,
|
|
1437
|
-
if (capabilityPending) return capabilityKnown.then(() => savePageForce(callback, { quietConflict,
|
|
1499
|
+
export function savePageForce(callback = () => {}, { quietConflict = false, blockeditorFormatFloor = 0, permit = null } = {}) {
|
|
1500
|
+
if (capabilityPending) return capabilityKnown.then(() => savePageForce(callback, { quietConflict, blockeditorFormatFloor, permit }));
|
|
1438
1501
|
return new Promise((resolve) => {
|
|
1439
1502
|
if (!isEditMode && !window.clay?.testMode) {
|
|
1440
1503
|
const skipped = skipped_('Not in edit mode');
|
|
@@ -1488,7 +1551,7 @@ export function savePageForce(callback = () => {}, { quietConflict = false, page
|
|
|
1488
1551
|
}
|
|
1489
1552
|
|
|
1490
1553
|
const prepare = window.clay?.source?.ensureForSave?.();
|
|
1491
|
-
if (prepare) return prepare
|
|
1554
|
+
if (prepare) return resolve(afterWait(prepare, () => savePageForce(callback, { quietConflict, blockeditorFormatFloor, permit })));
|
|
1492
1555
|
|
|
1493
1556
|
const wasOffline = !navigator.onLine;
|
|
1494
1557
|
if (wasOffline) {
|
|
@@ -1498,7 +1561,7 @@ export function savePageForce(callback = () => {}, { quietConflict = false, page
|
|
|
1498
1561
|
// A forced save prepares like an ordinary one when the document is this big; it only
|
|
1499
1562
|
// skips the dirty question, which `forced` says here.
|
|
1500
1563
|
if (preparesSliced() && !prepDriver) {
|
|
1501
|
-
loadPreparer()
|
|
1564
|
+
resolve(afterWait(loadPreparer(), () => savePageForce(callback, { quietConflict, blockeditorFormatFloor, permit })));
|
|
1502
1565
|
return;
|
|
1503
1566
|
}
|
|
1504
1567
|
|
|
@@ -1512,7 +1575,7 @@ export function savePageForce(callback = () => {}, { quietConflict = false, page
|
|
|
1512
1575
|
explicit: true,
|
|
1513
1576
|
forced: true,
|
|
1514
1577
|
permit,
|
|
1515
|
-
|
|
1578
|
+
blockeditorFormatFloor,
|
|
1516
1579
|
quietConflict,
|
|
1517
1580
|
settleSoonQueued,
|
|
1518
1581
|
callback,
|
|
@@ -1546,7 +1609,7 @@ export function savePageForce(callback = () => {}, { quietConflict = false, page
|
|
|
1546
1609
|
captureId,
|
|
1547
1610
|
gateToken,
|
|
1548
1611
|
costMs: 0,
|
|
1549
|
-
|
|
1612
|
+
blockeditorFormatFloor,
|
|
1550
1613
|
quietConflict,
|
|
1551
1614
|
capture,
|
|
1552
1615
|
noteRecovery: () => noteRecoveryCapture({ captureId, html: forSave }),
|
|
@@ -1627,7 +1690,7 @@ export async function flushSave({ keepalive = false, timeoutMs = 15000, permit =
|
|
|
1627
1690
|
const run = { over: false };
|
|
1628
1691
|
let timer = null;
|
|
1629
1692
|
const timeout = new Promise((_, reject) => {
|
|
1630
|
-
timer = setTimeout(() => reject(flushError('Save timed out.', 'failed')), Math.max(0, timeoutMs));
|
|
1693
|
+
timer = setTimeout(() => reject(Object.assign(flushError('Save timed out.', 'failed'), { timedOut: true })), Math.max(0, timeoutMs));
|
|
1631
1694
|
});
|
|
1632
1695
|
try {
|
|
1633
1696
|
return await Promise.race([flushBytes(keepalive, deadline, held, run), timeout]);
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared site values edited on this page.
|
|
3
|
+
*
|
|
4
|
+
* On a multi-page host a page's `site` rule names values every page of the site shows:
|
|
5
|
+
* a nav, a footer. The host overwrites them with the site store's on every save, which
|
|
6
|
+
* keeps a stale tab from putting old values back and would also undo an edit made right
|
|
7
|
+
* here. So each save says which top-level `site` keys the person changed in this tab
|
|
8
|
+
* (`Site-Data: keys=nav,brand`), and the host writes exactly those to the store before
|
|
9
|
+
* it overwrites the rest.
|
|
10
|
+
*
|
|
11
|
+
* Both sides of the comparison are read from captured bytes, never from the live DOM:
|
|
12
|
+
* the baseline from the save pipeline's own record of what the file held at boot (which
|
|
13
|
+
* leaves out anything typed while the editor was still loading), moved on by this tab's
|
|
14
|
+
* accepted saves and by any other change to that record (a clean live-sync frame), and
|
|
15
|
+
* each save from the string being sent. Whatever the save pipeline normalizes is then normalized on both sides,
|
|
16
|
+
* so a value nobody touched cannot read as edited. That matters most in a stale tab,
|
|
17
|
+
* where a wrongly declared key would write old content over newer.
|
|
18
|
+
*/
|
|
19
|
+
import { addSaveHeaderSource } from "./save-core.js";
|
|
20
|
+
import { onSaveAccepted } from "./save-accepted.js";
|
|
21
|
+
import { getLastSavedContents, savePageThrottled } from "./save.js";
|
|
22
|
+
import Mutation from "../lib/mutation.js";
|
|
23
|
+
|
|
24
|
+
export const SITE_DATA_HEADER = "Site-Data";
|
|
25
|
+
|
|
26
|
+
const RULES_TAG = 'script[data-rules-name~="api"]';
|
|
27
|
+
// A top-level key: it opens the object or follows a comma, so `.site:first-child` in a
|
|
28
|
+
// selector string is not a declaration.
|
|
29
|
+
const DECLARES_SITE = /(?:^|[{,])\s*["']?site["']?\s*:/;
|
|
30
|
+
const EMPTY = { html: null, now: null, keys: [] };
|
|
31
|
+
|
|
32
|
+
let engine = null;
|
|
33
|
+
let baseline = null;
|
|
34
|
+
let last = EMPTY;
|
|
35
|
+
// The save pipeline's record the baseline was last taken from. Whenever it changes (this
|
|
36
|
+
// tab's accepted save, a clean live-sync frame), every key the host did not refuse takes
|
|
37
|
+
// the record's value: for this tab's own save that is the value it sent, so an edit typed
|
|
38
|
+
// while the save was in flight still differs, and for a frame it is what the file now holds.
|
|
39
|
+
let knownSaved = null;
|
|
40
|
+
const refusedKeys = new Set();
|
|
41
|
+
// Values the host refreshed, with the value this tab had sent. The accepted hook runs before
|
|
42
|
+
// the save pipeline installs that save's record, so the record then still holds the sent
|
|
43
|
+
// value: the refreshed one stands. A record holding anything else is newer, and wins.
|
|
44
|
+
const refreshed = new Map();
|
|
45
|
+
|
|
46
|
+
// False while there is no record yet: no baseline, so no header.
|
|
47
|
+
function followSavedRecord() {
|
|
48
|
+
const record = getLastSavedContents();
|
|
49
|
+
if (typeof record !== "string" || !record) return false;
|
|
50
|
+
if (record === knownSaved) return true;
|
|
51
|
+
const values = valuesOf(parse(record));
|
|
52
|
+
if (!baseline) baseline = values;
|
|
53
|
+
else {
|
|
54
|
+
for (const [key, value] of Object.entries(values)) {
|
|
55
|
+
if (refusedKeys.has(key)) continue;
|
|
56
|
+
const host = refreshed.get(key);
|
|
57
|
+
baseline[key] = host && host.sent === value ? host.value : value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
refreshed.clear();
|
|
61
|
+
knownSaved = record;
|
|
62
|
+
last = EMPTY;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function siteRule(root) {
|
|
67
|
+
const rule = engine.engine.findRulesIn(root, "api")?.rules?.site;
|
|
68
|
+
return rule !== null && typeof rule === "object" && !Array.isArray(rule) ? rule : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function valuesOf(root) {
|
|
72
|
+
const rule = siteRule(root);
|
|
73
|
+
if (!rule) return {};
|
|
74
|
+
const site = engine.extractData(root, { site: rule }).site ?? {};
|
|
75
|
+
return Object.fromEntries(Object.entries(site).map(([key, value]) => [key, JSON.stringify(value)]));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const parse = (html) => new DOMParser().parseFromString(html, "text/html").documentElement;
|
|
79
|
+
|
|
80
|
+
function read(html) {
|
|
81
|
+
if (last.html !== html) {
|
|
82
|
+
const now = valuesOf(parse(html));
|
|
83
|
+
last = { html, now, keys: Object.keys(now).filter((key) => now[key] !== baseline[key]) };
|
|
84
|
+
}
|
|
85
|
+
return last;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// No header until the baseline exists: without one this tab cannot tell an edit from a
|
|
89
|
+
// stale value, and a host that sees no header treats every shared value as stale.
|
|
90
|
+
function siteHeader(html) {
|
|
91
|
+
if (!engine || typeof html !== "string" || !followSavedRecord()) return null;
|
|
92
|
+
return { [SITE_DATA_HEADER]: `keys=${read(html).keys.map(encodeURIComponent).join(",")}` };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Values the host stored that this tab was behind on. Taken only for a key the person has
|
|
96
|
+
// not touched since the save was captured, and never for a key the host refused: that
|
|
97
|
+
// one still shows the edit that was not stored, and the page says so.
|
|
98
|
+
function takeCurrent(current, sent, refused) {
|
|
99
|
+
const root = document.documentElement;
|
|
100
|
+
const rule = siteRule(root);
|
|
101
|
+
const live = valuesOf(root);
|
|
102
|
+
const applied = [];
|
|
103
|
+
for (const [key, value] of Object.entries(current)) {
|
|
104
|
+
if (refused.has(key) || !rule || !Object.hasOwn(rule, key) || live[key] !== sent[key]) continue;
|
|
105
|
+
// Outside undo history and the edit observers, the way a sync frame lands: an undo
|
|
106
|
+
// must not bring back the value this tab was behind on, and then declare it.
|
|
107
|
+
const fields = new Map([...root.querySelectorAll("[editable]")].map((field) => [field, field.innerHTML]));
|
|
108
|
+
Mutation.pause();
|
|
109
|
+
try {
|
|
110
|
+
engine.applyData(root, { site: { [key]: value } }, { site: { [key]: rule[key] } });
|
|
111
|
+
baseline[key] = valuesOf(root)[key];
|
|
112
|
+
refreshed.set(key, { sent: sent[key], value: baseline[key] });
|
|
113
|
+
applied.push(key);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error(`clayjs: the shared value "${key}" could not be refreshed on this page`, error);
|
|
116
|
+
} finally {
|
|
117
|
+
Mutation.resume();
|
|
118
|
+
}
|
|
119
|
+
// An editor that keeps its own undo history (RichClay) is told its field was rewritten,
|
|
120
|
+
// so its undo starts from the new value rather than stepping back to the old one.
|
|
121
|
+
for (const [field, before] of fields) {
|
|
122
|
+
if (field.isConnected && field.innerHTML !== before) field.dispatchEvent(new CustomEvent("clay:external-write"));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// The writes above ran with the edit observers paused, so the save pipeline still
|
|
126
|
+
// records the older value: typing that value back would then look like no change and
|
|
127
|
+
// never be saved. Ask for the same automatic save live sync asks for after it applies a
|
|
128
|
+
// frame, which stores what the page now shows and moves the record to it.
|
|
129
|
+
if (applied.length) savePageThrottled();
|
|
130
|
+
return applied;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function accept(html, answer) {
|
|
134
|
+
if (!engine || !followSavedRecord()) return;
|
|
135
|
+
refreshed.clear();
|
|
136
|
+
const sent = read(html).now;
|
|
137
|
+
// A `stale` key was edited on a value older than the store's: the host keeps the newer one
|
|
138
|
+
// and sends it as `current`, which this tab takes instead of declaring its edit again.
|
|
139
|
+
const refused = new Set((answer?.refused ?? []).filter((entry) => entry.code !== "stale").map((entry) => entry.key));
|
|
140
|
+
for (const key of Object.keys(sent)) {
|
|
141
|
+
if (refused.has(key)) refusedKeys.add(key);
|
|
142
|
+
else {
|
|
143
|
+
baseline[key] = sent[key];
|
|
144
|
+
refusedKeys.delete(key);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
last = EMPTY;
|
|
148
|
+
if (!answer) return;
|
|
149
|
+
const applied = answer.current ? takeCurrent(answer.current, sent, refused) : [];
|
|
150
|
+
document.dispatchEvent(new CustomEvent("clay:site-data", {
|
|
151
|
+
detail: { written: answer.written ?? [], refused: answer.refused ?? [], applied },
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Resolves once the engine has loaded, or at once on a page that declares no `site` key. The
|
|
156
|
+
// baseline itself is read from the save pipeline's record when it is first needed.
|
|
157
|
+
export const ready = DECLARES_SITE.test(document.querySelector(RULES_TAG)?.textContent ?? "")
|
|
158
|
+
? (() => {
|
|
159
|
+
const loading = import("../vendor/hyper-html-api-data.vendor.js");
|
|
160
|
+
addSaveHeaderSource(siteHeader);
|
|
161
|
+
onSaveAccepted((html, accepted) => accept(html, accepted.siteData ?? null));
|
|
162
|
+
return loading.then((loaded) => {
|
|
163
|
+
engine = loaded;
|
|
164
|
+
}).catch((error) => console.error("clayjs: shared site edits are not tracked on this page", error));
|
|
165
|
+
})()
|
|
166
|
+
: Promise.resolve();
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { isEditMode, isDraftMode } from "./is-edit-mode.js";
|
|
19
|
-
import { draftStoreFailed, flushDraft } from "./draft-mode.js";
|
|
19
|
+
import { draftStoreFailed, draftUnconfirmed, flushDraft } from "./draft-mode.js";
|
|
20
20
|
import { captureForDirtyCheck } from "./snapshot.js";
|
|
21
21
|
import { getLastSavedDirty, sessionParked, styleAdoptionPending } from "./save.js";
|
|
22
22
|
import { logUnloadDiffSync, preloadIfEnabled } from "../lib/autosave-debug-gate.js";
|
|
@@ -57,7 +57,7 @@ export function hasUnsavedChanges() {
|
|
|
57
57
|
// Work outside the DOM first: it needs no capture, and a capture that throws
|
|
58
58
|
// must not hide it. The demo plugin does not save it either, so it warns there too.
|
|
59
59
|
if (hasUnsavedState()) return true;
|
|
60
|
-
if (isDraftMode) return draftStoreFailed();
|
|
60
|
+
if (isDraftMode) return draftStoreFailed() || draftUnconfirmed();
|
|
61
61
|
|
|
62
62
|
// The demo plugin saves the page's bytes into this browser's own storage, so
|
|
63
63
|
// leaving loses none of them.
|