@panphora/clayjs 1.24.1 → 1.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all.js +1 -1
- package/dist/clay-dom.js +1 -1
- package/dist/clay-events.js +1 -1
- package/dist/clay-internals.js +1 -1
- package/dist/clay-options.js +1 -1
- package/dist/clay-router.js +1 -1
- package/dist/clay-ui.js +1 -1
- package/dist/clay-utils.js +1 -1
- package/dist/clay.js +1 -1
- package/dist/clay.standalone.js +6060 -4662
- package/dist/m/attrs/{refetch-on-save-DPGJJMGN.js → refetch-on-save-5CBFUJUF.js} +1 -1
- package/dist/m/attrs/save-freeze-PUYX32AK.js +1 -0
- package/dist/m/{c-HIX5H5L7.js → c-2A2ATCIH.js} +1 -1
- package/dist/m/{c-BOMQIQ3V.js → c-2EFMJYGJ.js} +1 -1
- package/dist/m/{c-MP6S4AZU.js → c-2GB34XL4.js} +2 -2
- package/dist/m/c-2TJCFPX4.js +4 -0
- package/dist/m/c-33SS5A4Y.js +1 -0
- package/dist/m/{c-PCNJETUA.js → c-34J5HC7N.js} +1 -1
- package/dist/m/{c-AT7GLQGV.js → c-3AAQJAKJ.js} +1 -1
- package/dist/m/c-3F3AADKG.js +1 -0
- package/dist/m/{c-HL6BLJNA.js → c-3WX5FABT.js} +1 -1
- package/dist/m/c-3XNTQQEJ.js +1 -0
- package/dist/m/{c-ZRGEN6QQ.js → c-3Y234AUB.js} +2 -2
- package/dist/m/{c-SUXHUL7U.js → c-42RLKGEY.js} +1 -1
- package/dist/m/{c-2BLVA6IN.js → c-44WITKWQ.js} +1 -1
- package/dist/m/c-47M4QHCK.js +1 -0
- package/dist/m/{c-UOUUI265.js → c-4M2T676V.js} +1 -1
- package/dist/m/{c-SXGEWPFC.js → c-4NAYDOWH.js} +1 -1
- package/dist/m/{c-OCYG4NBQ.js → c-4OZZ7ENT.js} +2 -2
- package/dist/m/{c-UOOIF2AL.js → c-5IIEOPIV.js} +1 -1
- package/dist/m/{c-YDW4VCHP.js → c-755ZOHOM.js} +1 -1
- package/dist/m/c-7Q4XYDF4.js +1 -0
- package/dist/m/c-7RHOMAMR.js +1 -0
- package/dist/m/{c-7V6MGMX2.js → c-7Z2625LX.js} +1 -1
- package/dist/m/{c-CTGRDT6E.js → c-A3OKSYRI.js} +1 -1
- package/dist/m/{c-GNI7WHKF.js → c-A5IYL7JI.js} +1 -1
- package/dist/m/c-A5LASUEZ.js +1 -0
- package/dist/m/c-AQVQKFKB.js +1 -0
- package/dist/m/{c-FLEIU6CG.js → c-ASGHHLFX.js} +1 -1
- package/dist/m/c-ASMLERFA.js +7 -0
- package/dist/m/{c-LUXAB2XI.js → c-AZM34SPO.js} +1 -1
- package/dist/m/{c-ZFO5RW6G.js → c-BHLYQGTA.js} +1 -1
- package/dist/m/{c-C5AKJVRH.js → c-BJ2RRQBQ.js} +1 -1
- package/dist/m/{c-FVPNX2GO.js → c-C27EVUQ6.js} +1 -1
- package/dist/m/{c-DFKBGSN2.js → c-C5UGNOC2.js} +1 -1
- package/dist/m/c-C6DXZIXD.js +1 -0
- package/dist/m/{c-EDE45NM7.js → c-C7SUMNMI.js} +1 -1
- package/dist/m/{c-STRI73GQ.js → c-CL5LSATX.js} +1 -1
- package/dist/m/c-CPRGR2II.js +1 -0
- package/dist/m/{c-NFQFV7OP.js → c-D2OPWOWO.js} +1 -1
- package/dist/m/{c-ZKKAF4UT.js → c-D4N4UJCQ.js} +2 -2
- package/dist/m/{c-6LEYVQJK.js → c-DAUQ6JZ3.js} +1 -1
- package/dist/m/{c-ZGCO4J3H.js → c-DDCYVLGG.js} +1 -1
- package/dist/m/{c-KQKKIVLW.js → c-DVMDYRAE.js} +1 -1
- package/dist/m/{c-OG6GFSUX.js → c-DYTBSJBI.js} +1 -1
- package/dist/m/{c-P2HKMJAW.js → c-EAV7A6BT.js} +1 -1
- package/dist/m/{c-X75G43SZ.js → c-EEWPNUM2.js} +1 -1
- package/dist/m/c-EHAR4RJJ.js +3 -0
- package/dist/m/c-EIDZHQHZ.js +1 -0
- package/dist/m/c-ELXMSEN5.js +1 -0
- package/dist/m/{c-QQFHEODI.js → c-EQ5NBOGA.js} +1 -1
- package/dist/m/c-FC5RUFPR.js +1 -0
- package/dist/m/{c-ND3Q2G2I.js → c-GFASNR55.js} +1 -1
- package/dist/m/{c-A4L7APIU.js → c-GNFDX6SU.js} +1 -1
- package/dist/m/{c-LATNQ6Q5.js → c-H3M6UDNV.js} +1 -1
- package/dist/m/{c-YLHTD3KX.js → c-H4M2T4A6.js} +1 -1
- package/dist/m/{c-DDS23EY6.js → c-HXGNH6GY.js} +1 -1
- package/dist/m/{c-PDBNCJCT.js → c-HYNM5U6B.js} +1 -1
- package/dist/m/c-J2YYLEVM.js +1 -0
- package/dist/m/c-J2ZW4HMR.js +1 -0
- package/dist/m/{c-WCG6AKA7.js → c-JC4RZW4E.js} +1 -1
- package/dist/m/c-K2ZHQMPF.js +1 -0
- package/dist/m/{c-6IJEVE6B.js → c-KU3PX7LL.js} +1 -1
- package/dist/m/{c-YYMZWQLR.js → c-L6KVXPBO.js} +1 -1
- package/dist/m/{c-CV4T65JE.js → c-LUBZU4AZ.js} +4 -4
- package/dist/m/{c-5FXEKU4Y.js → c-LYNLY3MR.js} +1 -1
- package/dist/m/{c-NQAPWM7O.js → c-MHTO3QAD.js} +1 -1
- package/dist/m/{c-HDVH3MVC.js → c-MKAUC6DB.js} +1 -1
- package/dist/m/c-MLMJUNRV.js +1 -0
- package/dist/m/c-MMEVDN6D.js +1 -0
- package/dist/m/c-MOP5KBWZ.js +1 -0
- package/dist/m/{c-LA2BW7UY.js → c-MZCOGNZL.js} +1 -1
- package/dist/m/{c-XW5TIYYB.js → c-N4GEJMXW.js} +1 -1
- package/dist/m/{c-RS77ODLY.js → c-NL4I4KNG.js} +1 -1
- package/dist/m/{c-2PVGJFI5.js → c-NW4ZFKCY.js} +1 -1
- package/dist/m/{c-PYG2NGWU.js → c-O3DPFZCE.js} +1 -1
- package/dist/m/c-O4YIDULY.js +1 -0
- package/dist/m/{c-LTSPHVAK.js → c-OBMZASYQ.js} +1 -1
- package/dist/m/c-OZOACVUD.js +1 -0
- package/dist/m/{c-YK2L436K.js → c-OZVK3TFI.js} +1 -1
- package/dist/m/c-P73XPAOA.js +1 -0
- package/dist/m/c-PP3CUF3U.js +1 -0
- package/dist/m/{c-B7GOFGQR.js → c-Q2CB57UG.js} +1 -1
- package/dist/m/{c-TWCXBWS3.js → c-QCDVDZIR.js} +1 -1
- package/dist/m/{c-LSVN3OLG.js → c-QL4NYM2L.js} +1 -1
- package/dist/m/c-RES2S5Q3.js +1 -0
- package/dist/m/c-RFGELYEU.js +2 -0
- package/dist/m/c-RGFPLQGV.js +1 -0
- package/dist/m/c-SMLAJASY.js +1 -0
- package/dist/m/{c-MQKSKQHG.js → c-STAPQ2X6.js} +1 -1
- package/dist/m/c-SYVF6ZMF.js +1 -0
- package/dist/m/{c-SE3SIY7Y.js → c-T5IWJOMW.js} +1 -1
- package/dist/m/{c-DD26VXAP.js → c-T7LOTEWO.js} +1 -1
- package/dist/m/{c-47IBJGPQ.js → c-TWB5ZQU4.js} +1 -1
- package/dist/m/c-U35T5NAY.js +1 -0
- package/dist/m/c-U4TWEIKZ.js +1 -0
- package/dist/m/c-UW6MMZ4V.js +1 -0
- package/dist/m/{c-JWDCQL6J.js → c-V5L6HWVL.js} +1 -1
- package/dist/m/c-VGG3UR7I.js +1 -0
- package/dist/m/{c-CZHTXGJS.js → c-VO6RSFCO.js} +1 -1
- package/dist/m/c-VU4WEWPH.js +4 -0
- package/dist/m/c-W7LWCJHD.js +1 -0
- package/dist/m/c-WOJ2RTV4.js +1 -0
- package/dist/m/{c-T3P3AXEW.js → c-WOWGGNCJ.js} +1 -1
- package/dist/m/{c-6RGEQNVV.js → c-XCOQNISP.js} +1 -1
- package/dist/m/{c-SGGGCIVD.js → c-XS7G6U3Z.js} +1 -1
- package/dist/m/{c-4PO3YGDC.js → c-Y3J5LDKB.js} +1 -1
- package/dist/m/c-Y3MPVURA.js +1 -0
- package/dist/m/c-Y6HH7LTM.js +1 -0
- package/dist/m/{c-RO73MKKR.js → c-YRDKVEGR.js} +1 -1
- package/dist/m/{c-FEC6LGFF.js → c-YZGWSCMT.js} +1 -1
- package/dist/m/{c-7CM4GMTS.js → c-ZJ7726FO.js} +1 -1
- package/dist/m/{c-SKMIR5HQ.js → c-ZPI7M5U5.js} +1 -1
- package/dist/m/core/{admin-attrs-ONRWVXPK.js → admin-attrs-43JBA6JB.js} +1 -1
- package/dist/m/core/{autosave-TBKAAAFB.js → autosave-HD7Y7UNV.js} +1 -1
- package/dist/m/core/{conflict-notice-FOEE6I7E.js → conflict-notice-Y2NTMDGJ.js} +1 -1
- package/dist/m/core/draft-mode-M5XNYBMY.js +1 -0
- package/dist/m/core/{host-permission-X4BENZEU.js → host-permission-5BYP24GC.js} +1 -1
- package/dist/m/core/{is-edit-mode-FFHCQ5DR.js → is-edit-mode-73OXKK5H.js} +1 -1
- package/dist/m/core/{persist-6ILUR34S.js → persist-MX3VYVBN.js} +1 -1
- package/dist/m/core/{recovery-APWADU4K.js → recovery-VZF7RJZF.js} +1 -1
- package/dist/m/core/{save-6HT2LABE.js → save-JZ7R4ET2.js} +1 -1
- package/dist/m/core/{save-core-EVMNZQ4V.js → save-core-I4N6S4ED.js} +1 -1
- package/dist/m/core/{site-edits-G4BOZMV4.js → site-edits-TIJDPG6H.js} +1 -1
- package/dist/m/core/{snapshot-FJP5IJL3.js → snapshot-N5MKDGNI.js} +1 -1
- package/dist/m/core/unsaved-warning-BP6ZY3PG.js +1 -0
- package/dist/m/events/{index-B6TGDYMG.js → index-YKQIBZE5.js} +1 -1
- package/dist/m/internals/index-3LAVREAU.js +1 -0
- package/dist/m/lib/{cache-bust-NH7VBWHO.js → cache-bust-BL4KUQAJ.js} +1 -1
- package/dist/m/{loader-GLLVDUZA.js → loader-7ORQVRCK.js} +1 -1
- package/dist/m/manifest-A27A4278.js +1 -0
- package/dist/m/manifest-edit-E8A61EB4.js +1 -0
- package/dist/m/manifest.json +1608 -1498
- package/dist/m/options/{options-55LQHSJ4.js → options-LKIZ7YTG.js} +1 -1
- package/dist/m/page/{editor-KAZDJNND.js → editor-HS2CAV3W.js} +5 -5
- package/dist/m/plugins/{ai-edit-LABECCSN.js → ai-edit-WDV2GLDU.js} +2 -2
- package/dist/m/plugins/{cms-T2HLI7JG.js → cms-4KET6RZT.js} +9 -9
- package/dist/m/plugins/{demo-T7B4RIKL.js → demo-LMCMBILC.js} +1 -1
- package/dist/m/plugins/{derive-6LPJOCAV.js → derive-R4USHZ7P.js} +1 -1
- package/dist/m/plugins/{indicator-J4ABFARX.js → indicator-OLPZ4WFI.js} +1 -1
- package/dist/m/plugins/{live-PGBIEMGB.js → live-CO4VJS46.js} +1 -1
- package/dist/m/plugins/{menu-YZL3X2AJ.js → menu-ESEFQARM.js} +1 -1
- package/dist/m/plugins/{page-WOCNX2IN.js → page-L3VKIQU2.js} +2 -2
- package/dist/m/plugins/{page-view-SJODOOGE.js → page-view-6KNTXABH.js} +1 -1
- package/dist/m/plugins/{people-C4TCWFOQ.js → people-EBWE337I.js} +1 -1
- package/dist/m/plugins/quickcrop-VNRXGRPC.js +26 -0
- package/dist/m/plugins/{richclay-Y2NV44NA.js → richclay-V6WKJTUF.js} +17 -17
- package/dist/m/plugins/sortable-BAG3MSWS.js +1 -0
- package/dist/m/plugins/{source-YK7MO23W.js → source-OYHDXKTB.js} +1 -1
- package/dist/m/plugins/undo-WUK2NLBA.js +1 -0
- package/dist/m/plugins/{upgrade-NNTJBSK2.js → upgrade-SZQJYEQQ.js} +1 -1
- package/dist/m/plugins/{upload-ZKKJV76X.js → upload-NZET3DNQ.js} +1 -1
- package/dist/m/plugins/visitor-drafts-ZE6PCAFP.js +20 -0
- package/dist/m/plugins/{visitor-drafts-owner-LHUHFU3L.js → visitor-drafts-owner-TLECTYWS.js} +1 -1
- package/dist/m/plugins/{visitor-menu-FY4KM2SW.js → visitor-menu-5XY57JQA.js} +1 -1
- package/dist/m/plugins/wire-S5ATC4QI.js +1 -0
- package/dist/m/reader-core-AHOAP5DH.js +77 -0
- package/dist/m/sync/live-sync-DX4K5GDB.js +1 -0
- package/dist/m/ui/index-G7ZJXNMG.js +1 -0
- package/dist/m/vendor/sap-core.vendor-5TWOJYVM.js +7 -0
- package/dist/offline-manifest.json +844 -784
- package/dist/server/core.cjs +4245 -2614
- package/dist/server/markdown.cjs +1 -0
- package/dist/server/markdown.mjs +7 -0
- package/docs/guide.md +27 -37
- package/entries/clay-data.js +1 -1
- package/entries/sap.js +6 -5
- package/package.json +6 -2
- package/packed-contract.json +3 -0
- package/src/attrs/save-freeze.js +1 -1
- package/src/core/admin-contenteditable.js +1 -1
- package/src/core/admin-inputs.js +1 -1
- package/src/core/admin-onclick.js +1 -1
- package/src/core/admin-resources.js +5 -2
- package/src/core/conflict-notice-ui.js +59 -34
- package/src/core/draft-mode.js +5 -0
- package/src/core/save-core.js +10 -10
- package/src/core/save.js +10 -10
- package/src/core/snapshot.js +56 -43
- package/src/core/stale-host-notice.js +20 -7
- package/src/lib/dirty-gate.js +19 -0
- package/src/page/editor.js +10 -4
- package/src/page/import-markdown.js +22 -8
- package/src/page/save-projection.js +1 -1
- package/src/plugins/menu/visitor.js +33 -1
- package/src/plugins/quickcrop.js +128 -69
- package/src/plugins/sortable.js +25 -9
- package/src/plugins/visitor-drafts/commit.js +50 -3
- package/src/plugins/visitor-drafts/idb.js +8 -2
- package/src/plugins/visitor-drafts/recovery-menu.js +118 -0
- package/src/plugins/visitor-drafts/recovery.js +64 -0
- package/src/plugins/visitor-drafts/session.js +61 -13
- package/src/plugins/visitor-drafts/store.js +44 -8
- package/src/plugins/visitor-drafts.js +34 -5
- package/src/server/markdown/index.js +23 -0
- package/src/sync/conflicts.js +3 -1
- package/src/sync/data-guard-notice.js +137 -18
- package/src/sync/live-sync.js +1 -1
- package/src/sync/wire-profile.js +9 -4
- package/src/ui/bevel-controls.js +4 -1
- package/src/ui/dialogs.js +11 -7
- package/src/ui/modal.js +9 -7
- package/src/ui/toast.js +67 -32
- 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/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +7 -7
- package/src/vendor/richclay.vendor.js +16 -16
- package/src/vendor/sap-core.vendor.js +6 -5
- package/src/vendor/sap-ownership.vendor.js +181 -11
- package/tool-generation/context.md +69 -0
- package/tool-generation/system.md +2 -2
- package/dist/m/attrs/save-freeze-JZHKRQLR.js +0 -1
- package/dist/m/c-277QP5ET.js +0 -1
- package/dist/m/c-3UW5EJ77.js +0 -1
- package/dist/m/c-42ZNV7LI.js +0 -7
- package/dist/m/c-6Q6RGFBF.js +0 -1
- package/dist/m/c-APGHGDZM.js +0 -1
- package/dist/m/c-BGN7RCTJ.js +0 -1
- package/dist/m/c-CJ7QYEWI.js +0 -1
- package/dist/m/c-CVSWLX3X.js +0 -1
- package/dist/m/c-DXPOFLEJ.js +0 -1
- package/dist/m/c-EILLN2H2.js +0 -1
- package/dist/m/c-EODTTNVA.js +0 -1
- package/dist/m/c-ESK4S67I.js +0 -1
- package/dist/m/c-FDWR3FL7.js +0 -1
- package/dist/m/c-FYDKYW6E.js +0 -1
- package/dist/m/c-HKSLOVU4.js +0 -1
- package/dist/m/c-HTLYXCUH.js +0 -1
- package/dist/m/c-IUL7PU4P.js +0 -1
- package/dist/m/c-JQF2EUCG.js +0 -1
- package/dist/m/c-MFOSMAVM.js +0 -3
- package/dist/m/c-MQYEDF3Q.js +0 -1
- package/dist/m/c-MR75YPRT.js +0 -1
- package/dist/m/c-NHXOBD6A.js +0 -1
- package/dist/m/c-NVU3DORS.js +0 -1
- package/dist/m/c-OYHI67B6.js +0 -1
- package/dist/m/c-PWJSJEJU.js +0 -1
- package/dist/m/c-QENKN7QI.js +0 -1
- package/dist/m/c-R23UHMXG.js +0 -1
- package/dist/m/c-SGH3UTF3.js +0 -1
- package/dist/m/c-SXNSSHPP.js +0 -1
- package/dist/m/c-TFLRT5JX.js +0 -1
- package/dist/m/c-U4NTUVDZ.js +0 -1
- package/dist/m/c-V2U3F4QX.js +0 -1
- package/dist/m/c-VG5XIN4T.js +0 -1
- package/dist/m/c-VHSMPITY.js +0 -1
- package/dist/m/c-Z4G2UK6T.js +0 -4
- package/dist/m/core/draft-mode-4MCL3LYI.js +0 -1
- package/dist/m/core/unsaved-warning-ITZDIZ2I.js +0 -1
- package/dist/m/internals/index-HS3CQDFW.js +0 -1
- package/dist/m/manifest-50FF1C45.js +0 -1
- package/dist/m/manifest-edit-BE1A621D.js +0 -1
- package/dist/m/plugins/quickcrop-X5MRIBAP.js +0 -26
- package/dist/m/plugins/sortable-XCHQG65Z.js +0 -1
- package/dist/m/plugins/undo-VLRBQNPN.js +0 -1
- package/dist/m/plugins/visitor-drafts-RTZNONHT.js +0 -20
- package/dist/m/plugins/wire-M2NR354P.js +0 -1
- package/dist/m/reader-core-T2S3JC4E.js +0 -77
- package/dist/m/sync/live-sync-Z64YREWQ.js +0 -1
- package/dist/m/ui/index-MGTWOW2C.js +0 -1
- package/dist/m/vendor/sap-core.vendor-QAEPMETX.js +0 -6
- /package/dist/m/{c-QTTJYJMA.js → c-FILLTJA7.js} +0 -0
- /package/dist/m/plugins/{quickcrop-X5MRIBAP.js.LEGAL.txt → quickcrop-VNRXGRPC.js.LEGAL.txt} +0 -0
- /package/dist/m/plugins/{richclay-Y2NV44NA.js.LEGAL.txt → richclay-V6WKJTUF.js.LEGAL.txt} +0 -0
|
@@ -2,12 +2,14 @@ import { isEditMode } from "./is-edit-mode.js";
|
|
|
2
2
|
import { rowOf, plain } from "./conflict-presentation.js";
|
|
3
3
|
import { downloadRecovery, downloadStored } from "./conflict-download.js";
|
|
4
4
|
import { pendingConflicts, listConflicts, dismissConflicts } from "../recovery/conflicts.js";
|
|
5
|
-
import { set, make } from "../lib/hostile-css.js";
|
|
5
|
+
import { set, make, style } from "../lib/hostile-css.js";
|
|
6
6
|
import { isSaveConflicted } from "./save.js";
|
|
7
7
|
import { gateCaptureToken } from "../lib/dirty-gate.js";
|
|
8
8
|
import { reloadAfterDiscard } from "./unsaved-warning.js";
|
|
9
9
|
import { bevelButton, bevelIconButton, bevelSurface, bevelWell, bevelText, protectIcon, paintLook, RUNTIME_ONLY } from "../ui/bevel-controls.js";
|
|
10
10
|
import { TOKENS, FONT_SANS, FONT_MONO, GLYPHS } from "../ui/bevel.js";
|
|
11
|
+
import { resolveTheme } from "../ui/theme.js";
|
|
12
|
+
import { themed, partRules, markParts } from "../ui/theme-parts.js";
|
|
11
13
|
|
|
12
14
|
// One notice for everything this tab holds that the page no longer shows: edits
|
|
13
15
|
// another save replaced (the ledger, clay.conflicts) and a save the host refused.
|
|
@@ -72,6 +74,9 @@ let copiedId = null;
|
|
|
72
74
|
let copiedTimer = null;
|
|
73
75
|
|
|
74
76
|
let root = null;
|
|
77
|
+
// The theme every surface is drawn with: captured once, when the notice draws, and
|
|
78
|
+
// carried into each control it builds.
|
|
79
|
+
let theme = null;
|
|
75
80
|
let wellEl = null;
|
|
76
81
|
let keyed = new Map();
|
|
77
82
|
|
|
@@ -114,6 +119,15 @@ function key(node, name) {
|
|
|
114
119
|
return node;
|
|
115
120
|
}
|
|
116
121
|
|
|
122
|
+
// An element built with el() carries no part machinery of its own: mark it, and let the
|
|
123
|
+
// page's explicit declarations land after everything it was drawn with.
|
|
124
|
+
function part(node, names) {
|
|
125
|
+
if (!themed(theme)) return node;
|
|
126
|
+
markParts(node, theme, names);
|
|
127
|
+
style(node, partRules(theme, names, {}));
|
|
128
|
+
return node;
|
|
129
|
+
}
|
|
130
|
+
|
|
117
131
|
// A phone keyboard shrinks the visual viewport but leaves fixed elements pinned to
|
|
118
132
|
// the layout viewport, which parks a bottom-anchored bar behind the keyboard at the
|
|
119
133
|
// exact moment somebody is typing. Lift it by the difference instead.
|
|
@@ -147,6 +161,7 @@ export function schedule() {
|
|
|
147
161
|
}
|
|
148
162
|
|
|
149
163
|
function render() {
|
|
164
|
+
theme = resolveTheme();
|
|
150
165
|
if (checkRefusal) {
|
|
151
166
|
checkRefusal = false;
|
|
152
167
|
if (refused && !isSaveConflicted()) refused = null;
|
|
@@ -196,11 +211,18 @@ function render() {
|
|
|
196
211
|
// A request for a control the toast is standing in for waits until the toast goes.
|
|
197
212
|
if (!toastText) pendingFocus = null;
|
|
198
213
|
place();
|
|
199
|
-
|
|
214
|
+
if (themed(theme)) {
|
|
215
|
+
markParts(root, theme, ["conflict.root"]);
|
|
216
|
+
style(root, partRules(theme, ["conflict.root"], {}));
|
|
217
|
+
set(root, "color-scheme", theme.scheme === "light dark" ? "light" : theme.scheme);
|
|
218
|
+
} else {
|
|
219
|
+
root.removeAttribute("data-clay-part");
|
|
220
|
+
paintLook(root);
|
|
221
|
+
}
|
|
200
222
|
}
|
|
201
223
|
|
|
202
224
|
function dot() {
|
|
203
|
-
return el("span", ["all:initial", "display:inline-block", "flex:none", "width:8px", "height:8px", "border-radius:50%", `background:${WARN}`]);
|
|
225
|
+
return part(el("span", ["all:initial", "display:inline-block", "flex:none", "width:8px", "height:8px", "border-radius:50%", `background:${WARN}`]), ["conflict.dot"]);
|
|
204
226
|
}
|
|
205
227
|
|
|
206
228
|
function refusedSource() {
|
|
@@ -230,7 +252,8 @@ function titleOf(m, n) {
|
|
|
230
252
|
}
|
|
231
253
|
|
|
232
254
|
function drawToast(text) {
|
|
233
|
-
const t = bevelSurface("div", ["display:block", `
|
|
255
|
+
const t = bevelSurface("div", ["display:block", `background:${TOKENS["teal-soft"]}`, "padding:10px 14px", "width:max-content", "max-width:calc(100vw - 24px)"], { theme, parts: ["conflict.panel"] });
|
|
256
|
+
if (themed(theme)) style(t, [`background:${TOKENS["teal-soft"]}`]);
|
|
234
257
|
t.setAttribute("role", "status");
|
|
235
258
|
t.textContent = text;
|
|
236
259
|
return t;
|
|
@@ -238,19 +261,19 @@ function drawToast(text) {
|
|
|
238
261
|
|
|
239
262
|
function drawBar(m) {
|
|
240
263
|
const n = m === "failed" ? failures().length : m === "recovered" ? storedCount() : losses().length;
|
|
241
|
-
const bar = bevelSurface("div", ["display:flex", "flex-wrap:wrap", "align-items:center", "gap:10px 12px", "padding:8px 8px 8px 14px", "width:max-content", "max-width:calc(100vw - 24px)"]);
|
|
264
|
+
const bar = bevelSurface("div", ["display:flex", "flex-wrap:wrap", "align-items:center", "gap:10px 12px", "padding:8px 8px 8px 14px", "width:max-content", "max-width:calc(100vw - 24px)"], { theme, parts: ["conflict.panel"] });
|
|
242
265
|
// A redraw rebuilds the bar; only a new message is announced again.
|
|
243
266
|
const message = barMessage(m, n);
|
|
244
267
|
if (message !== lastAnnounced) {
|
|
245
268
|
bar.setAttribute("role", m === "refused" ? "alert" : "status");
|
|
246
269
|
lastAnnounced = message;
|
|
247
270
|
}
|
|
248
|
-
bar.append(dot(), bevelText("span", [], message));
|
|
271
|
+
bar.append(dot(), bevelText("span", [], message, { theme, parts: ["conflict.text"] }));
|
|
249
272
|
if (m === "upgraded") {
|
|
250
|
-
bar.append(key(bevelButton("Reload", { variant: "primary", onClick: () => window.location.reload() }), "reload"));
|
|
273
|
+
bar.append(key(bevelButton("Reload", { variant: "primary", onClick: () => window.location.reload(), theme, parts: ["conflict.button"], labelParts: ["conflict.buttonLabel"] }), "reload"));
|
|
251
274
|
return bar;
|
|
252
275
|
}
|
|
253
|
-
bar.append(key(bevelButton("Review", { onClick: openPanel }), "review"));
|
|
276
|
+
bar.append(key(bevelButton("Review", { onClick: openPanel, theme, parts: ["conflict.button"], labelParts: ["conflict.buttonLabel"] }), "review"));
|
|
254
277
|
return bar;
|
|
255
278
|
}
|
|
256
279
|
|
|
@@ -259,11 +282,11 @@ function action(label, name, { variant = "default", onClick, push = false } = {}
|
|
|
259
282
|
const extra = phone
|
|
260
283
|
? ["flex:1 1 100%", ...(variant === "primary" ? ["order:-1"] : [])]
|
|
261
284
|
: (push ? ["margin-left:auto"] : []);
|
|
262
|
-
return key(bevelButton(label, { variant, onClick, extra }), name);
|
|
285
|
+
return key(bevelButton(label, { variant, onClick, extra, theme, parts: ["conflict.button"], labelParts: ["conflict.buttonLabel"] }), name);
|
|
263
286
|
}
|
|
264
287
|
|
|
265
288
|
function lede(text) {
|
|
266
|
-
return bevelText("div", ["display:block", "padding:0 16px 12px", `color:${TOKENS["ink-2"]}`, "font-size:13.5px"], text);
|
|
289
|
+
return bevelText("div", ["display:block", "padding:0 16px 12px", `color:${TOKENS["ink-2"]}`, "font-size:13.5px"], text, { theme, parts: ["conflict.text"] });
|
|
267
290
|
}
|
|
268
291
|
|
|
269
292
|
function drawPanel(m) {
|
|
@@ -275,13 +298,13 @@ function drawPanel(m) {
|
|
|
275
298
|
|
|
276
299
|
const title = titleOf(m, count);
|
|
277
300
|
const maxHeight = Math.max(160, (window.visualViewport?.height || window.innerHeight) - 32);
|
|
278
|
-
const panel = bevelSurface("div", ["display:flex", "flex-direction:column", "width:520px", "max-width:calc(100vw - 24px)", `max-height:${maxHeight}px`]);
|
|
301
|
+
const panel = bevelSurface("div", ["display:flex", "flex-direction:column", "width:520px", "max-width:calc(100vw - 24px)", `max-height:${maxHeight}px`], { theme, parts: ["conflict.panel"] });
|
|
279
302
|
panelEl = panel;
|
|
280
303
|
panel.setAttribute("role", "dialog");
|
|
281
304
|
panel.setAttribute("aria-label", title);
|
|
282
305
|
|
|
283
|
-
const head = el("div", ["all:initial", "display:flex", "align-items:center", "gap:8px", "padding:12px 12px 8px 16px", `font:14px/1.5 ${FONT_SANS}`, `color:${TOKENS.ink}`]);
|
|
284
|
-
const t = key(bevelText("b", ["font-weight:600", "font-size:14.5px", "outline:none"], title), "title");
|
|
306
|
+
const head = part(el("div", ["all:initial", "display:flex", "align-items:center", "gap:8px", "padding:12px 12px 8px 16px", `font:14px/1.5 ${FONT_SANS}`, `color:${TOKENS.ink}`]), ["conflict.header"]);
|
|
307
|
+
const t = key(bevelText("b", ["font-weight:600", "font-size:14.5px", "outline:none"], title, { theme, parts: ["conflict.title"] }), "title");
|
|
285
308
|
t.setAttribute("tabindex", "-1");
|
|
286
309
|
head.append(dot(), t);
|
|
287
310
|
if (m === "replaced" && freshIds.size) {
|
|
@@ -291,7 +314,7 @@ function drawPanel(m) {
|
|
|
291
314
|
], `${freshIds.size} new`));
|
|
292
315
|
}
|
|
293
316
|
const minimizeWrap = el("span", ["all:initial", "display:inline-flex", "margin-left:auto"]);
|
|
294
|
-
const minimize = key(bevelIconButton(MINIMIZE_SVG, { label: "Minimize", onClick: minimizePanel }), "minimize");
|
|
317
|
+
const minimize = key(bevelIconButton(MINIMIZE_SVG, { label: "Minimize", onClick: minimizePanel, theme, parts: ["close", "conflict.close"] }), "minimize");
|
|
295
318
|
minimize.title = "Minimize to the bar. Nothing is accepted until you choose.";
|
|
296
319
|
minimizeWrap.append(minimize);
|
|
297
320
|
head.append(minimizeWrap);
|
|
@@ -310,11 +333,11 @@ function drawPanel(m) {
|
|
|
310
333
|
if (keepFailed) panel.append(lede("That did not save either. Your edits here are still here."));
|
|
311
334
|
if (armLost) panel.append(lede("Something changed on the page after your first press, so nothing was dropped. Press Accept theirs again if you still want their version."));
|
|
312
335
|
if (armed) {
|
|
313
|
-
const warn = el("div", [
|
|
336
|
+
const warn = part(el("div", [
|
|
314
337
|
"all:initial", "display:block", "margin:0 16px 12px", "padding:8px 10px",
|
|
315
|
-
`
|
|
338
|
+
`background:color-mix(in srgb, ${WARN} 14%, transparent)`,
|
|
316
339
|
`color:${TOKENS.ink}`, `font:13px/1.5 ${FONT_SANS}`,
|
|
317
|
-
]);
|
|
340
|
+
]), ["conflict.warning"]);
|
|
318
341
|
warn.append(el("b", ["all:initial", "font:inherit", "font-weight:700", "color:inherit"], "This drops your unsaved edits."), document.createTextNode(" Download your copy first if you might want them. Press again to confirm."));
|
|
319
342
|
panel.append(warn);
|
|
320
343
|
}
|
|
@@ -326,7 +349,7 @@ function drawPanel(m) {
|
|
|
326
349
|
panel.append(lede("The page is showing the other edit. This stays until you choose. You can also save a copy of the page as you had it."));
|
|
327
350
|
}
|
|
328
351
|
|
|
329
|
-
const actions = el("div", ["all:initial", "display:flex", "flex-wrap:wrap", "gap:8px", "padding:0 16px 14px"]);
|
|
352
|
+
const actions = part(el("div", ["all:initial", "display:flex", "flex-wrap:wrap", "gap:8px", "padding:0 16px 14px"]), ["conflict.actions"]);
|
|
330
353
|
if (m === "upgraded") {
|
|
331
354
|
actions.append(action("Reload", "reload", { variant: "primary", onClick: () => window.location.reload(), push: true }));
|
|
332
355
|
panel.append(actions);
|
|
@@ -354,7 +377,7 @@ function drawPanel(m) {
|
|
|
354
377
|
if (!listOpen) {
|
|
355
378
|
const more = el("div", ["all:initial", "display:flex", "align-items:center", "gap:8px", `border-top:1px solid ${TOKENS.line}`, "padding:8px 10px 8px 16px", `color:${TOKENS.muted}`, `font:13px/1.5 ${FONT_SANS}`]);
|
|
356
379
|
const seeWrap = el("span", ["all:initial", "display:inline-flex", "margin-left:auto"]);
|
|
357
|
-
seeWrap.append(key(bevelButton("See the edits", { small: true, onClick: toggleList }), "list"));
|
|
380
|
+
seeWrap.append(key(bevelButton("See the edits", { small: true, onClick: toggleList, theme, parts: ["conflict.button"], labelParts: ["conflict.buttonLabel"] }), "list"));
|
|
358
381
|
more.append(el("span", ["all:initial", "font:inherit", "color:inherit"], plural(rows.length, "edit")), seeWrap);
|
|
359
382
|
panel.append(more);
|
|
360
383
|
} else {
|
|
@@ -369,10 +392,10 @@ function drawPanel(m) {
|
|
|
369
392
|
|
|
370
393
|
function drawWell(rows) {
|
|
371
394
|
// The sunk ground, not the recess: the recess's lighter blue glares against the panel in dark mode.
|
|
372
|
-
const well = bevelWell(["flex:1 1 auto", "margin:0 16px 12px", "min-height:0", "overflow:auto", `background:${TOKENS.sunk}`, `border:1px solid ${TOKENS["line-2"]}`]);
|
|
395
|
+
const well = bevelWell(["flex:1 1 auto", "margin:0 16px 12px", "min-height:0", "overflow:auto", `background:${TOKENS.sunk}`, `border:1px solid ${TOKENS["line-2"]}`], { theme, parts: ["conflict.well", "conflict.list"] });
|
|
373
396
|
const head = el("div", ["all:initial", "position:sticky", "top:0", "z-index:1", "display:flex", "align-items:center", "gap:8px", "padding:8px 12px", `background:${TOKENS.sunk}`, `border-bottom:1px solid ${TOKENS["line-2"]}`, `color:${TOKENS.muted}`, `font:600 10.5px/1 ${FONT_MONO}`, "letter-spacing:.12em", "text-transform:uppercase"]);
|
|
374
397
|
const hideWrap = el("span", ["all:initial", "display:inline-flex", "margin-left:auto"]);
|
|
375
|
-
hideWrap.append(key(bevelButton("Hide", { small: true, variant: "quiet", onClick: toggleList }), "list"));
|
|
398
|
+
hideWrap.append(key(bevelButton("Hide", { small: true, variant: "quiet", onClick: toggleList, theme, parts: ["conflict.button"], labelParts: ["conflict.buttonLabel"] }), "list"));
|
|
376
399
|
head.append(el("span", ["all:initial", "font:inherit", "color:inherit", "letter-spacing:inherit", "text-transform:inherit"], "Replaced edits"), hideWrap);
|
|
377
400
|
well.append(head);
|
|
378
401
|
rows.forEach((r, i) => well.append(buildRow(r, i)));
|
|
@@ -483,8 +506,8 @@ function undoActionsOf(entry) {
|
|
|
483
506
|
const storedSide = (text) => ({ before: "", hit: text, after: "", cutBefore: false, cutAfter: false });
|
|
484
507
|
|
|
485
508
|
function storedRow(entry) {
|
|
486
|
-
const box = bevelText("div", ["display:block", "padding:8px 12px 10px"]);
|
|
487
|
-
const where = bevelText("div", ["display:flex", "align-items:center", "gap:8px", "font-size:12.5px", "font-weight:600", `color:${TOKENS["ink-2"]}`, "margin-bottom:4px"]);
|
|
509
|
+
const box = bevelText("div", ["display:block", "padding:8px 12px 10px"], undefined, { theme, parts: ["conflict.row"], role: null });
|
|
510
|
+
const where = bevelText("div", ["display:flex", "align-items:center", "gap:8px", "font-size:12.5px", "font-weight:600", `color:${TOKENS["ink-2"]}`, "margin-bottom:4px"], undefined, { theme, parts: ["conflict.location"], role: null });
|
|
488
511
|
// No eye: there is no live element to ring, and an old recovery path may name a
|
|
489
512
|
// place today's page no longer has.
|
|
490
513
|
where.append(bevelText("span", [], storedName(entry)));
|
|
@@ -514,44 +537,44 @@ function drawRecovered(panel) {
|
|
|
514
537
|
? "These copies were replaced before this page loaded. The page never puts them back on its own. Copy or download the ones you want; dismiss one when you no longer need it."
|
|
515
538
|
: "These copies were kept because an undo could not be put back or the page set them aside. The page never puts them back on its own. Copy or download the ones you want; dismiss one when you no longer need it."));
|
|
516
539
|
if (actionFailed) panel.append(lede("That action did not run. The copy is still here."));
|
|
517
|
-
const well = bevelWell(["flex:1 1 auto", "margin:0 16px 12px", "min-height:0", "overflow:auto", `background:${TOKENS.sunk}`, `border:1px solid ${TOKENS["line-2"]}`]);
|
|
540
|
+
const well = bevelWell(["flex:1 1 auto", "margin:0 16px 12px", "min-height:0", "overflow:auto", `background:${TOKENS.sunk}`, `border:1px solid ${TOKENS["line-2"]}`], { theme, parts: ["conflict.well", "conflict.list"] });
|
|
518
541
|
for (const record of recovered) for (const entry of record.conflicts) well.append(storedRow(entry));
|
|
519
542
|
for (const entry of recoveredUndo) well.append(storedRow(entry));
|
|
520
543
|
wellEl = well;
|
|
521
544
|
panel.append(well);
|
|
522
|
-
const actions = el("div", ["all:initial", "display:flex", "flex-wrap:wrap", "gap:8px", "padding:0 16px 14px"]);
|
|
545
|
+
const actions = part(el("div", ["all:initial", "display:flex", "flex-wrap:wrap", "gap:8px", "padding:0 16px 14px"]), ["conflict.actions"]);
|
|
523
546
|
if (recovered.some((record) => record.root) || recoveredUndo.length) actions.append(action(downloaded ? "Downloaded" : "Download my copy", "download", { onClick: onDownloadRecovered }));
|
|
524
547
|
panel.append(actions);
|
|
525
548
|
renderedIds = [];
|
|
526
549
|
}
|
|
527
550
|
|
|
528
551
|
function valueLine(label, value, { mine }) {
|
|
529
|
-
const cells = [bevelText("span", [`color:${TOKENS.faint}`, `font-family:${FONT_SANS}`], label)];
|
|
552
|
+
const cells = [bevelText("span", [`color:${TOKENS.faint}`, `font-family:${FONT_SANS}`], label, { theme, parts: ["conflict.valueLabel"], role: null })];
|
|
530
553
|
const box = bevelText("span", [
|
|
531
554
|
"display:-webkit-box", "-webkit-line-clamp:2", "-webkit-box-orient:vertical", "overflow:hidden",
|
|
532
555
|
"overflow-wrap:anywhere",
|
|
533
556
|
...(mine
|
|
534
557
|
? [`color:${TOKENS.ink}`]
|
|
535
558
|
: [`color:${TOKENS.muted}`]),
|
|
536
|
-
]);
|
|
559
|
+
], undefined, { theme, parts: [mine ? "conflict.mine" : "conflict.theirs", "conflict.value"], role: null });
|
|
537
560
|
const full = (value.cutBefore ? "…" : "") + value.before + value.hit + value.after + (value.cutAfter ? "…" : "");
|
|
538
561
|
box.title = full;
|
|
539
|
-
if (value.before) box.append(bevelText("span", [`color:${TOKENS.faint}`], (value.cutBefore ? "…" : "") + value.before));
|
|
562
|
+
if (value.before) box.append(bevelText("span", [`color:${TOKENS.faint}`], (value.cutBefore ? "…" : "") + value.before, { theme, parts: ["conflict.before"], role: null }));
|
|
540
563
|
const hit = bevelText("span", mine
|
|
541
564
|
? [`background:color-mix(in srgb, ${WARN} 18%, transparent)`, "padding:0 4px"]
|
|
542
565
|
: (value.before || value.after
|
|
543
566
|
? ["text-decoration:underline", `text-decoration-color:color-mix(in srgb, ${WARN} 70%, transparent)`, "text-underline-offset:3px"]
|
|
544
|
-
: []), value.hit || "(empty)");
|
|
567
|
+
: []), value.hit || "(empty)", { theme, parts: ["conflict.value"], role: null });
|
|
545
568
|
box.append(hit);
|
|
546
|
-
if (value.after) box.append(bevelText("span", [`color:${TOKENS.faint}`], value.after + (value.cutAfter ? "…" : "")));
|
|
569
|
+
if (value.after) box.append(bevelText("span", [`color:${TOKENS.faint}`], value.after + (value.cutAfter ? "…" : ""), { theme, parts: ["conflict.after"], role: null }));
|
|
547
570
|
cells.push(box);
|
|
548
571
|
return cells;
|
|
549
572
|
}
|
|
550
573
|
|
|
551
574
|
function buildRow(record, index) {
|
|
552
575
|
const row = rowOf(record);
|
|
553
|
-
const box = bevelText("div", ["display:block", "padding:8px 12px 10px", ...(index ? [`border-top:1px solid ${TOKENS.line}`] : [])]);
|
|
554
|
-
const where = bevelText("div", ["display:flex", "align-items:center", "gap:4px", "font-size:12.5px", "font-weight:600", `color:${TOKENS["ink-2"]}`, "margin-bottom:4px"]);
|
|
576
|
+
const box = bevelText("div", ["display:block", "padding:8px 12px 10px", ...(index ? [`border-top:1px solid ${TOKENS.line}`] : [])], undefined, { theme, parts: ["conflict.row"], role: null });
|
|
577
|
+
const where = bevelText("div", ["display:flex", "align-items:center", "gap:4px", "font-size:12.5px", "font-weight:600", `color:${TOKENS["ink-2"]}`, "margin-bottom:4px"], undefined, { theme, parts: ["conflict.location"], role: null });
|
|
555
578
|
where.append(bevelText("span", [], row.name));
|
|
556
579
|
if (row.target && row.target.isConnected) where.append(key(eyeButton(row), `eye:${record.id}`));
|
|
557
580
|
box.append(where);
|
|
@@ -574,7 +597,9 @@ function eyeButton(row) {
|
|
|
574
597
|
protectIcon(b.firstElementChild, 16);
|
|
575
598
|
b.setAttribute("aria-label", `Show ${row.name} on page`);
|
|
576
599
|
b.title = "Show on page";
|
|
577
|
-
const
|
|
600
|
+
const names = ["button", "conflict.button"];
|
|
601
|
+
markParts(b, theme, names);
|
|
602
|
+
const paint = (extra) => { b.style.cssText = ""; const rules = themed(theme) ? [...base, ...partRules(theme, names, {}), ...extra] : [...base, ...extra]; for (const r of rules) { const i = r.indexOf(":"); b.style.setProperty(r.slice(0, i), r.slice(i + 1), "important"); } };
|
|
578
603
|
b.addEventListener("pointerenter", () => paint([`background:color-mix(in srgb, ${WARN} 16%, transparent)`]));
|
|
579
604
|
b.addEventListener("pointerleave", () => paint([]));
|
|
580
605
|
b.addEventListener("focus", () => paint([`outline:2px solid ${TOKENS.brass}`, "outline-offset:1px"]));
|
|
@@ -630,7 +655,7 @@ function ring(target) {
|
|
|
630
655
|
window.scrollBy?.({ top: shift, behavior });
|
|
631
656
|
}
|
|
632
657
|
ringTarget = target;
|
|
633
|
-
ringEl = el("div", ["all:initial", "color-scheme:inherit", "position:fixed", "pointer-events:none", "z-index:2147483001", `outline:2px solid ${WARN}`, "outline-offset:3px", `background:color-mix(in srgb, ${WARN} 14%, transparent)`]);
|
|
658
|
+
ringEl = part(el("div", ["all:initial", "color-scheme:inherit", "position:fixed", "pointer-events:none", "z-index:2147483001", `outline:2px solid ${WARN}`, "outline-offset:3px", `background:color-mix(in srgb, ${WARN} 14%, transparent)`]), ["conflict.previewRing"]);
|
|
634
659
|
document.body.appendChild(ringEl);
|
|
635
660
|
placeRing();
|
|
636
661
|
window.addEventListener("scroll", placeRing, true);
|
package/src/core/draft-mode.js
CHANGED
|
@@ -45,6 +45,11 @@ export function draftEligible(doc = document, loc = window.location) {
|
|
|
45
45
|
&& rulesReady(doc);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// The dirty gate once it counts this page's edits: `token()` for the page as it is now,
|
|
49
|
+
// and `unchangedSince(token)`. Null until then, and a store that cannot ask assumes edits.
|
|
50
|
+
let gate = null;
|
|
51
|
+
export function setDraftGate(value) { gate = value; }
|
|
52
|
+
export function draftGate() { return gate; }
|
|
48
53
|
export function setDraftTransport(fn) { transport = fn; }
|
|
49
54
|
export function draftTransport() { return transport; }
|
|
50
55
|
let flushHook = null;
|
package/src/core/save-core.js
CHANGED
|
@@ -665,12 +665,12 @@ function draftResponse(status, body) {
|
|
|
665
665
|
return { ok: status >= 200 && status < 300, status, statusText: status === 200 ? "OK" : "Insufficient Storage", text: () => Promise.resolve(text) };
|
|
666
666
|
}
|
|
667
667
|
|
|
668
|
-
function sendToDraftStore(content) {
|
|
668
|
+
function sendToDraftStore(content, gateToken) {
|
|
669
669
|
const send = draftTransport();
|
|
670
670
|
if (!send) return Promise.resolve(draftResponse(503, { msg: "Saving in this browser is not ready yet", msgType: "error", code: "draft-not-ready" }));
|
|
671
671
|
// The transport's promise settles once the draft database holds the save.
|
|
672
672
|
return Promise.resolve()
|
|
673
|
-
.then(() => send(content))
|
|
673
|
+
.then(() => send(content, gateToken))
|
|
674
674
|
.then(
|
|
675
675
|
() => draftResponse(200, { msg: "Saved in this browser", msgType: "success" }),
|
|
676
676
|
(error) => draftResponse(507, { msg: "Couldn't save in this browser. Storage is full or turned off.", msgType: "error", code: error?.code || "draft-storage" }),
|
|
@@ -698,7 +698,7 @@ function sendToDraftStore(content) {
|
|
|
698
698
|
* @param {Array} [context.attempts] - collects each unique actual send
|
|
699
699
|
* @returns {Promise<Object>} The server's response body
|
|
700
700
|
*/
|
|
701
|
-
function sendOnce(content, saveId, etag, { captureId = null, conversionHeader = null, attempts = [] } = {}) {
|
|
701
|
+
function sendOnce(content, saveId, etag, { captureId = null, conversionHeader = null, attempts = [], gateToken = null } = {}) {
|
|
702
702
|
const controller = new AbortController();
|
|
703
703
|
const timeoutId = setTimeout(() => controller.abort(), SAVE_TIMEOUT_MS);
|
|
704
704
|
|
|
@@ -727,7 +727,7 @@ function sendOnce(content, saveId, etag, { captureId = null, conversionHeader =
|
|
|
727
727
|
notifySaveAttempt({ captureId, saveId, html: content, etag: etag ?? null });
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
-
return (isDraftMode ? sendToDraftStore(content) : fetch(url, options))
|
|
730
|
+
return (isDraftMode ? sendToDraftStore(content, gateToken) : fetch(url, options))
|
|
731
731
|
.then(res => res.text().then(text => {
|
|
732
732
|
let data = {};
|
|
733
733
|
if (text) {
|
|
@@ -891,7 +891,7 @@ async function recoverUnknownSave() {
|
|
|
891
891
|
* @param {Array} [context.attempts] - collects each unique actual send
|
|
892
892
|
* @returns {Promise<Object>} The server's response body
|
|
893
893
|
*/
|
|
894
|
-
async function sendSave(content, { captureId = null, conversionHeader = null, attempts = [] } = {}) {
|
|
894
|
+
async function sendSave(content, { captureId = null, conversionHeader = null, attempts = [], gateToken = null } = {}) {
|
|
895
895
|
// The re-send below is a RETRY of this attempt, so it carries this same id:
|
|
896
896
|
// whichever of the two requests the host ends up storing, its receipt then
|
|
897
897
|
// answers for this save. A fresh id on the re-send would leave the first
|
|
@@ -916,7 +916,7 @@ async function sendSave(content, { captureId = null, conversionHeader = null, at
|
|
|
916
916
|
const retryGuard = captureId ?? currentCaptureId();
|
|
917
917
|
for (;;) {
|
|
918
918
|
try {
|
|
919
|
-
return await sendOnce(content, saveId, etag, { captureId, conversionHeader, attempts });
|
|
919
|
+
return await sendOnce(content, saveId, etag, { captureId, conversionHeader, attempts, gateToken });
|
|
920
920
|
} catch (err) {
|
|
921
921
|
if (reSent) err.hadTimeout = true;
|
|
922
922
|
if (err.name === 'AbortError') {
|
|
@@ -971,7 +971,7 @@ async function sendSave(content, { captureId = null, conversionHeader = null, at
|
|
|
971
971
|
// `blockeditorFormatFloor` is internal: one save may carry a floor this tab has not
|
|
972
972
|
// noted yet, which is how the format gate's activation writes the marker. It
|
|
973
973
|
// raises these bytes only, never what the tab knows the document at.
|
|
974
|
-
function runSave(html, captureId, callback, conversion, capture = null, blockeditorFormatFloor = 0, lane = null) {
|
|
974
|
+
function runSave(html, captureId, callback, conversion, capture = null, blockeditorFormatFloor = 0, lane = null, gateToken = null) {
|
|
975
975
|
// A capture taken before the floor rose cannot drop the marker by omitting it:
|
|
976
976
|
// the bytes that go out carry the floor this tab last saw. At floor 0 this is
|
|
977
977
|
// the string itself, unchanged.
|
|
@@ -1039,7 +1039,7 @@ function runSave(html, captureId, callback, conversion, capture = null, blockedi
|
|
|
1039
1039
|
return finished(tooLargeResult(size));
|
|
1040
1040
|
}
|
|
1041
1041
|
|
|
1042
|
-
sendSave(html, { captureId, conversionHeader: formatted.header, attempts })
|
|
1042
|
+
sendSave(html, { captureId, conversionHeader: formatted.header, attempts, gateToken })
|
|
1043
1043
|
.then(successResult, errorResult)
|
|
1044
1044
|
// Clear the flag BEFORE handing the result back, so a caller that queues a
|
|
1045
1045
|
// follow-up save can start it immediately rather than being told the lane
|
|
@@ -1088,8 +1088,8 @@ export function saveHtml(html, callback = () => {}) {
|
|
|
1088
1088
|
* @param {Object} [options.conversion] - {source:'file', base} or {source:'room', base, epoch}
|
|
1089
1089
|
* @returns {Promise<Object>}
|
|
1090
1090
|
*/
|
|
1091
|
-
export function saveCapturedHtml(html, captureId, callback = () => {}, { conversion, capture = null, blockeditorFormatFloor, lane = null } = {}) {
|
|
1092
|
-
return runSave(html, captureId, callback, conversion, capture, blockeditorFormatFloor, lane);
|
|
1091
|
+
export function saveCapturedHtml(html, captureId, callback = () => {}, { conversion, capture = null, blockeditorFormatFloor, lane = null, gateToken = null } = {}) {
|
|
1092
|
+
return runSave(html, captureId, callback, conversion, capture, blockeditorFormatFloor, lane, gateToken);
|
|
1093
1093
|
}
|
|
1094
1094
|
|
|
1095
1095
|
/**
|
package/src/core/save.js
CHANGED
|
@@ -63,7 +63,7 @@ import { beginRecoveryCapture, finishRecoveryCapture, noteRecoveryCapture, noteR
|
|
|
63
63
|
// content. Both clones get this, so the dirty comparison sees no difference.
|
|
64
64
|
addDocumentTransform(clone => {
|
|
65
65
|
for (const name of ROOT_LIBRARY_ATTRS) clone.removeAttribute(name);
|
|
66
|
-
});
|
|
66
|
+
}, { required: true });
|
|
67
67
|
|
|
68
68
|
// Keep the host's save token out of the saved bytes, both spellings, and the response
|
|
69
69
|
// metadata with it.
|
|
@@ -83,7 +83,7 @@ addDocumentTransform(clone => {
|
|
|
83
83
|
// delete page content.
|
|
84
84
|
addDocumentTransform(clone => {
|
|
85
85
|
for (const name of [...SAVE_TOKEN_ATTRS, ...LEGACY_SAVE_TOKEN_ATTRS, ...HOST_RESPONSE_ATTRS]) clone.removeAttribute(name);
|
|
86
|
-
});
|
|
86
|
+
}, { required: true });
|
|
87
87
|
|
|
88
88
|
// ============================================
|
|
89
89
|
// SAVE STATE MANAGEMENT
|
|
@@ -826,9 +826,9 @@ function applySaveResult(result, forComparison, forDirty, label, gateToken, forS
|
|
|
826
826
|
captureId,
|
|
827
827
|
};
|
|
828
828
|
if (!navigator.onLine) {
|
|
829
|
-
setSaveState('offline', result.msg, '', extra);
|
|
829
|
+
setSaveState('offline', result.msg, result.msgType || 'error', extra);
|
|
830
830
|
} else {
|
|
831
|
-
setSaveState('error', result.msg, '', extra);
|
|
831
|
+
setSaveState('error', result.msg, result.msgType || 'error', extra);
|
|
832
832
|
}
|
|
833
833
|
}
|
|
834
834
|
}
|
|
@@ -1106,7 +1106,7 @@ async function settleCaptured({
|
|
|
1106
1106
|
// lane busy and waits that request out, which is what its 'clean' answer describes.
|
|
1107
1107
|
// A prepared save holds the lane itself, so its caller does the draining instead.
|
|
1108
1108
|
if (!laneHeld) drainPendingSave();
|
|
1109
|
-
}, { capture, blockeditorFormatFloor, lane });
|
|
1109
|
+
}, { capture, blockeditorFormatFloor, lane, gateToken });
|
|
1110
1110
|
});
|
|
1111
1111
|
}
|
|
1112
1112
|
|
|
@@ -1432,11 +1432,11 @@ function attemptSaveNow({ kind, permit = null }, callback = () => {}) {
|
|
|
1432
1432
|
console.error(`${explicit ? 'savePage' : 'savePageThrottled'}: captureForSaveAndComparison failed`, err);
|
|
1433
1433
|
if (explicit) {
|
|
1434
1434
|
clearExplicitSave();
|
|
1435
|
-
setSaveState('error', err.message, '', { sent: false, code: err.code ?? null });
|
|
1435
|
+
setSaveState('error', err.message, 'error', { sent: false, code: err.code ?? null });
|
|
1436
1436
|
} else {
|
|
1437
|
-
setSaveState('error', err.message);
|
|
1437
|
+
setSaveState('error', err.message, 'error');
|
|
1438
1438
|
}
|
|
1439
|
-
const result = { msg: err.message, msgType: 'error', code: explicit ? (err.code ?? null) : null, etag: null };
|
|
1439
|
+
const result = { ok: false, msg: err.message, msgType: 'error', code: explicit ? (err.code ?? null) : null, etag: null };
|
|
1440
1440
|
if (typeof callback === 'function') {
|
|
1441
1441
|
callback(result);
|
|
1442
1442
|
}
|
|
@@ -1590,8 +1590,8 @@ export function savePageForce(callback = () => {}, { quietConflict = false, bloc
|
|
|
1590
1590
|
({ forSave, forComparison, forDirty, captureId } = capture);
|
|
1591
1591
|
} catch (err) {
|
|
1592
1592
|
console.error('savePageForce: captureForSaveAndComparison failed', err);
|
|
1593
|
-
setSaveState('error', err.message, '', { sent: false, code: err.code ?? null });
|
|
1594
|
-
const result = { msg: err.message, msgType: 'error', code: err.code ?? null, etag: null };
|
|
1593
|
+
setSaveState('error', err.message, 'error', { sent: false, code: err.code ?? null });
|
|
1594
|
+
const result = { ok: false, msg: err.message, msgType: 'error', code: err.code ?? null, etag: null };
|
|
1595
1595
|
if (typeof callback === 'function') {
|
|
1596
1596
|
callback(result);
|
|
1597
1597
|
}
|
package/src/core/snapshot.js
CHANGED
|
@@ -196,6 +196,45 @@ function runAuthoredHandlers(clone, attr) {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
// A registered callback that throws is skipped, so one broken hook cannot stop every
|
|
200
|
+
// save. It is reported once per hook, not on every autosave. Two kinds still stop the
|
|
201
|
+
// save: ClayJS's own required steps (skipping one, such as making admin-only scripts
|
|
202
|
+
// inert, would write the wrong bytes), and an error with a `code`, which is a hook
|
|
203
|
+
// refusing the capture on purpose (clay.live does this).
|
|
204
|
+
const reportedHooks = new WeakSet();
|
|
205
|
+
const requiredHooks = new WeakSet();
|
|
206
|
+
function reportHookError(kind, hook, err) {
|
|
207
|
+
if (requiredHooks.has(hook) || typeof err?.code === "string") throw err;
|
|
208
|
+
if (reportedHooks.has(hook)) return;
|
|
209
|
+
reportedHooks.add(hook);
|
|
210
|
+
console.error(`clayjs: a ${kind} callback threw; the save goes on without it`, err);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Skipping a callback that threw is only safe when it left the clone as it found it.
|
|
214
|
+
// One that changed the clone first stops the capture, because its half-done changes
|
|
215
|
+
// (rows removed but not yet rebuilt) would otherwise be saved.
|
|
216
|
+
function runHook(kind, hook, clone, call) {
|
|
217
|
+
const watch = typeof MutationObserver === 'function' ? new MutationObserver(() => {}) : null;
|
|
218
|
+
watch?.observe(clone, { subtree: true, childList: true, attributes: true, characterData: true });
|
|
219
|
+
try {
|
|
220
|
+
call();
|
|
221
|
+
} catch (err) {
|
|
222
|
+
if (!watch || watch.takeRecords().length > 0) throw err;
|
|
223
|
+
reportHookError(kind, hook, err);
|
|
224
|
+
} finally {
|
|
225
|
+
watch?.disconnect();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Every registered transform, each on a clone with the strip-on-save regions removed.
|
|
230
|
+
// One that throws is skipped and reported once.
|
|
231
|
+
function runDocumentTransforms(clone) {
|
|
232
|
+
for (const hook of documentTransforms) {
|
|
233
|
+
stripSnapshotRegions(clone);
|
|
234
|
+
runHook('addDocumentTransform', hook, clone, () => hook(clone));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
199
238
|
let cloneCanonicalizer = null;
|
|
200
239
|
|
|
201
240
|
export function setCloneCanonicalizer(canonicalize) {
|
|
@@ -214,9 +253,11 @@ export function canonicalizeReceived(doc) {
|
|
|
214
253
|
* Use for: syncing form values to the clone.
|
|
215
254
|
*
|
|
216
255
|
* @param {Function} callback - Receives the cloned document element
|
|
256
|
+
* @param {{required?: boolean}} [options] - required: a throw stops the save instead of being skipped
|
|
217
257
|
*/
|
|
218
|
-
export function onSnapshot(callback) {
|
|
258
|
+
export function onSnapshot(callback, { required = false } = {}) {
|
|
219
259
|
snapshotHooks.push(callback);
|
|
260
|
+
if (required) requiredHooks.add(callback);
|
|
220
261
|
}
|
|
221
262
|
|
|
222
263
|
/**
|
|
@@ -381,9 +422,11 @@ function serializeSaveClone(clone, context = null) {
|
|
|
381
422
|
* different visibility rules, which is why neither run can be skipped.
|
|
382
423
|
*
|
|
383
424
|
* @param {Function} callback - Receives the cloned document element
|
|
425
|
+
* @param {{required?: boolean}} [options] - required: a throw stops the save instead of being skipped
|
|
384
426
|
*/
|
|
385
|
-
export function addDocumentTransform(callback) {
|
|
427
|
+
export function addDocumentTransform(callback, { required = false } = {}) {
|
|
386
428
|
documentTransforms.push(callback);
|
|
429
|
+
if (required) requiredHooks.add(callback);
|
|
387
430
|
}
|
|
388
431
|
|
|
389
432
|
// =============================================================================
|
|
@@ -439,7 +482,7 @@ export function captureSnapshot({ flushUndo = true, authored = true, forSave = f
|
|
|
439
482
|
|
|
440
483
|
for (const hook of snapshotHooks) {
|
|
441
484
|
stripSnapshotRegions(clone);
|
|
442
|
-
hook(clone, { original: originalSnapshotNode, forSave, requestRefresh });
|
|
485
|
+
runHook('onSnapshot', hook, clone, () => hook(clone, { original: originalSnapshotNode, forSave, requestRefresh }));
|
|
443
486
|
}
|
|
444
487
|
|
|
445
488
|
// Put back any URL clay rewrote at runtime (cache-bust, refetch-on-save) so
|
|
@@ -489,10 +532,7 @@ function prepareCloneForSave(clone, { authored = true } = {}) {
|
|
|
489
532
|
if (authored) runAuthoredHandlers(clone, 'onbeforesave');
|
|
490
533
|
|
|
491
534
|
// Run registered prepare hooks ([freeze]/[save-freeze] innerHTML restore lives here)
|
|
492
|
-
|
|
493
|
-
stripSnapshotRegions(clone);
|
|
494
|
-
hook(clone);
|
|
495
|
-
}
|
|
535
|
+
runDocumentTransforms(clone);
|
|
496
536
|
|
|
497
537
|
stripSnapshotRegions(clone);
|
|
498
538
|
|
|
@@ -575,10 +615,7 @@ export function captureForComparison({ flushUndo = true } = {}) {
|
|
|
575
615
|
}
|
|
576
616
|
|
|
577
617
|
// Run registered prepare hooks
|
|
578
|
-
|
|
579
|
-
stripSnapshotRegions(clone);
|
|
580
|
-
hook(clone);
|
|
581
|
-
}
|
|
618
|
+
runDocumentTransforms(clone);
|
|
582
619
|
|
|
583
620
|
stripSnapshotRegions(clone);
|
|
584
621
|
|
|
@@ -608,10 +645,7 @@ export function captureForDirtyCheck({ flushUndo = true } = {}) {
|
|
|
608
645
|
el.remove();
|
|
609
646
|
}
|
|
610
647
|
|
|
611
|
-
|
|
612
|
-
stripSnapshotRegions(clone);
|
|
613
|
-
hook(clone);
|
|
614
|
-
}
|
|
648
|
+
runDocumentTransforms(clone);
|
|
615
649
|
|
|
616
650
|
stripSnapshotRegions(clone);
|
|
617
651
|
|
|
@@ -642,10 +676,7 @@ export function captureForComparisonAndDirty({ flushUndo = true } = {}) {
|
|
|
642
676
|
for (const el of clone.querySelectorAll(STRIP_FROM_COMPARISON)) {
|
|
643
677
|
el.remove();
|
|
644
678
|
}
|
|
645
|
-
|
|
646
|
-
stripSnapshotRegions(clone);
|
|
647
|
-
hook(clone);
|
|
648
|
-
}
|
|
679
|
+
runDocumentTransforms(clone);
|
|
649
680
|
stripSnapshotRegions(clone);
|
|
650
681
|
const forComparison = serializeForCompare(clone);
|
|
651
682
|
|
|
@@ -654,10 +685,7 @@ export function captureForComparisonAndDirty({ flushUndo = true } = {}) {
|
|
|
654
685
|
for (const el of dirtyClone.querySelectorAll(STRIP_FROM_DIRTY_CHECK)) {
|
|
655
686
|
el.remove();
|
|
656
687
|
}
|
|
657
|
-
|
|
658
|
-
stripSnapshotRegions(dirtyClone);
|
|
659
|
-
hook(dirtyClone);
|
|
660
|
-
}
|
|
688
|
+
runDocumentTransforms(dirtyClone);
|
|
661
689
|
stripSnapshotRegions(dirtyClone);
|
|
662
690
|
forDirty = serializeForCompare(dirtyClone);
|
|
663
691
|
}
|
|
@@ -726,10 +754,7 @@ export function prepareSaveCapture({ emitForSync = true, flushUndo = true, reque
|
|
|
726
754
|
|
|
727
755
|
// Save clone: run hooks (freeze restore lives here), THEN strip [no-save]/[save-remove]
|
|
728
756
|
// LAST (snapshot-algorithm step 7) so freeze-restored [no-save] content can't leak to disk.
|
|
729
|
-
|
|
730
|
-
stripSnapshotRegions(clone);
|
|
731
|
-
hook(clone);
|
|
732
|
-
}
|
|
757
|
+
runDocumentTransforms(clone);
|
|
733
758
|
stripSnapshotRegions(clone);
|
|
734
759
|
for (const el of clone.querySelectorAll(STRIP_FROM_SAVE)) {
|
|
735
760
|
el.remove();
|
|
@@ -740,10 +765,7 @@ export function prepareSaveCapture({ emitForSync = true, flushUndo = true, reque
|
|
|
740
765
|
for (const el of compareClone.querySelectorAll(STRIP_FROM_COMPARISON)) {
|
|
741
766
|
el.remove();
|
|
742
767
|
}
|
|
743
|
-
|
|
744
|
-
stripSnapshotRegions(compareClone);
|
|
745
|
-
hook(compareClone);
|
|
746
|
-
}
|
|
768
|
+
runDocumentTransforms(compareClone);
|
|
747
769
|
stripSnapshotRegions(compareClone);
|
|
748
770
|
|
|
749
771
|
// Dirty clone: same shape as the compare clone, one selector weaker.
|
|
@@ -751,10 +773,7 @@ export function prepareSaveCapture({ emitForSync = true, flushUndo = true, reque
|
|
|
751
773
|
for (const el of dirtyClone.querySelectorAll(STRIP_FROM_DIRTY_CHECK)) {
|
|
752
774
|
el.remove();
|
|
753
775
|
}
|
|
754
|
-
|
|
755
|
-
stripSnapshotRegions(dirtyClone);
|
|
756
|
-
hook(dirtyClone);
|
|
757
|
-
}
|
|
776
|
+
runDocumentTransforms(dirtyClone);
|
|
758
777
|
stripSnapshotRegions(dirtyClone);
|
|
759
778
|
}
|
|
760
779
|
|
|
@@ -882,10 +901,7 @@ export function captureForMerge() {
|
|
|
882
901
|
}
|
|
883
902
|
})(compareClone, clone);
|
|
884
903
|
|
|
885
|
-
|
|
886
|
-
stripSnapshotRegions(clone);
|
|
887
|
-
hook(clone);
|
|
888
|
-
}
|
|
904
|
+
runDocumentTransforms(clone);
|
|
889
905
|
stripSnapshotRegions(clone);
|
|
890
906
|
for (const el of clone.querySelectorAll(STRIP_FROM_SAVE)) {
|
|
891
907
|
el.remove();
|
|
@@ -895,10 +911,7 @@ export function captureForMerge() {
|
|
|
895
911
|
for (const el of compareClone.querySelectorAll(STRIP_FROM_DIRTY_CHECK)) {
|
|
896
912
|
el.remove();
|
|
897
913
|
}
|
|
898
|
-
|
|
899
|
-
stripSnapshotRegions(compareClone);
|
|
900
|
-
hook(compareClone);
|
|
901
|
-
}
|
|
914
|
+
runDocumentTransforms(compareClone);
|
|
902
915
|
stripSnapshotRegions(compareClone);
|
|
903
916
|
|
|
904
917
|
return { saveClone: clone, compareClone, pairMap };
|