@panphora/clayjs 1.25.0 → 1.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all.js +1 -1
- package/dist/clay-dom.js +1 -1
- package/dist/clay-events.js +1 -1
- package/dist/clay-internals.js +1 -1
- package/dist/clay-options.js +1 -1
- package/dist/clay-router.js +1 -1
- package/dist/clay-ui.js +1 -1
- package/dist/clay-utils.js +1 -1
- package/dist/clay.js +1 -1
- package/dist/clay.standalone.js +5570 -4173
- package/dist/m/attrs/{save-freeze-MH3AOD56.js → save-freeze-PUYX32AK.js} +1 -1
- package/dist/m/{c-BOMQIQ3V.js → c-2EFMJYGJ.js} +1 -1
- package/dist/m/c-2EPHRF47.js +1 -0
- package/dist/m/{c-JD2VFAYG.js → c-2SUFMUZX.js} +2 -2
- package/dist/m/{c-CITLTVQ4.js → c-2TJCFPX4.js} +1 -1
- package/dist/m/c-33SS5A4Y.js +1 -0
- package/dist/m/{c-V2FPBRO6.js → c-34J5HC7N.js} +1 -1
- package/dist/m/c-3F3AADKG.js +1 -0
- package/dist/m/{c-TCFVG3I4.js → c-3S3647J6.js} +1 -1
- package/dist/m/{c-GY4BREDT.js → c-3S5TNRZA.js} +1 -1
- package/dist/m/{c-GDNZEOOZ.js → c-3WX5FABT.js} +1 -1
- package/dist/m/{c-AI3H6ETU.js → c-3XGLJAS7.js} +2 -2
- package/dist/m/c-42WBOKFM.js +1 -0
- package/dist/m/c-47M4QHCK.js +1 -0
- package/dist/m/{c-UOUUI265.js → c-4M2T676V.js} +1 -1
- package/dist/m/{c-GEAG5XAK.js → c-4TP3UXBT.js} +1 -1
- package/dist/m/{c-IESDKFO6.js → c-5ELELG2F.js} +1 -1
- package/dist/m/{c-X3V4RWMA.js → c-5ZYLTN3Q.js} +1 -1
- package/dist/m/{c-5PZYDWTW.js → c-67TBU56I.js} +1 -1
- package/dist/m/{c-WNOLMHI2.js → c-6BGJAJ67.js} +1 -1
- package/dist/m/{c-4Q6QIODW.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-A5LASUEZ.js +1 -0
- package/dist/m/{c-IL5IVTOG.js → c-ABOBD7F3.js} +1 -1
- package/dist/m/c-AQVQKFKB.js +1 -0
- package/dist/m/{c-QDVG4NAZ.js → c-ARO6CPMA.js} +1 -1
- package/dist/m/{c-ORLPQFNU.js → c-AZM34SPO.js} +1 -1
- package/dist/m/{c-XUZMRQ7O.js → c-BCS3AZQZ.js} +1 -1
- package/dist/m/c-BOS7GONO.js +1 -0
- package/dist/m/{c-SUAC5RSG.js → c-C5UGNOC2.js} +1 -1
- package/dist/m/c-C6DXZIXD.js +1 -0
- package/dist/m/{c-WSTUCBHV.js → c-CPRGR2II.js} +1 -1
- package/dist/m/{c-NICICT4P.js → c-DEYHCYJB.js} +1 -1
- package/dist/m/{c-WGQVSKKC.js → c-DYTBSJBI.js} +1 -1
- package/dist/m/{c-DT7E76Z2.js → c-DZT6W2NG.js} +1 -1
- package/dist/m/{c-O2QZDVZ2.js → c-EEWPNUM2.js} +1 -1
- package/dist/m/{c-MFB74XVN.js → c-EHAR4RJJ.js} +2 -2
- package/dist/m/c-EHJWIGWN.js +1 -0
- package/dist/m/c-EIDZHQHZ.js +1 -0
- package/dist/m/{c-V2V5FKXX.js → c-EIUKME3W.js} +1 -1
- package/dist/m/c-ELXMSEN5.js +1 -0
- package/dist/m/c-EXZOA6XY.js +7 -0
- package/dist/m/{c-FKTALXNV.js → c-EYFMAR3M.js} +1 -1
- package/dist/m/c-FC5RUFPR.js +1 -0
- package/dist/m/{c-FBOO2UY3.js → c-FUN6AKFE.js} +1 -1
- package/dist/m/{c-V4B3RUWB.js → c-GFASNR55.js} +1 -1
- package/dist/m/{c-XMIDHEKF.js → c-GY36NO7C.js} +1 -1
- package/dist/m/{c-UD4VDBGJ.js → c-H3M6UDNV.js} +1 -1
- package/dist/m/{c-SFZRY6RT.js → c-H47C6PJZ.js} +1 -1
- package/dist/m/{c-MUM53J5J.js → c-H4M2T4A6.js} +1 -1
- package/dist/m/{c-U7F66I64.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-KD2ME5UN.js +2 -0
- package/dist/m/{c-Z2OZFX3N.js → c-L6KVXPBO.js} +1 -1
- package/dist/m/{c-GO635CRU.js → c-LAKBBJOU.js} +2 -2
- package/dist/m/{c-4AE2A2LK.js → c-LETPPG2F.js} +1 -1
- package/dist/m/{c-SFYTKRU7.js → c-MKAUC6DB.js} +1 -1
- package/dist/m/{c-WDFQ7PBU.js → c-MMEVDN6D.js} +1 -1
- package/dist/m/{c-LS7IVQVW.js → c-MMV5JULA.js} +1 -1
- package/dist/m/{c-NEQRFC4O.js → c-MVVEIJUU.js} +1 -1
- package/dist/m/{c-SXZYVXD6.js → c-N362HNGO.js} +1 -1
- package/dist/m/{c-7K4SCQB7.js → c-ND3W7D5U.js} +1 -1
- package/dist/m/{c-3E2IK5HU.js → c-NNHNXD6M.js} +1 -1
- package/dist/m/{c-D62HRSZK.js → c-NYJ577NL.js} +1 -1
- package/dist/m/{c-YFO6ZJLQ.js → c-O3DPFZCE.js} +1 -1
- package/dist/m/c-OZOACVUD.js +1 -0
- package/dist/m/{c-MV7TEUTD.js → c-OZVK3TFI.js} +1 -1
- package/dist/m/{c-ZEA3DCG3.js → c-PWILUN4T.js} +1 -1
- package/dist/m/{c-BCWYEG6P.js → c-QA3Q3MDI.js} +1 -1
- package/dist/m/{c-XHZIKRCV.js → c-QKOKMQXA.js} +1 -1
- package/dist/m/{c-TGIMBAWE.js → c-RBWVF4N5.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-RGAALCDU.js → c-SPOEDIMP.js} +1 -1
- package/dist/m/{c-YX2MC7YL.js → c-SRW5OBCF.js} +1 -1
- package/dist/m/{c-C6YZNU6E.js → c-SYOA4T7V.js} +1 -1
- package/dist/m/c-SYVF6ZMF.js +1 -0
- package/dist/m/{c-NADLYVAH.js → c-TJ5PFOJ2.js} +1 -1
- package/dist/m/{c-KYTVZT4D.js → c-TKV273YU.js} +1 -1
- package/dist/m/c-U35T5NAY.js +1 -0
- package/dist/m/{c-PRZNJUYZ.js → c-UEKJWXCB.js} +2 -2
- package/dist/m/c-UW6MMZ4V.js +1 -0
- package/dist/m/{c-XQTERKQA.js → c-V4DBV5ZV.js} +1 -1
- package/dist/m/c-VGG3UR7I.js +1 -0
- package/dist/m/c-VU4WEWPH.js +4 -0
- package/dist/m/{c-5Q5XBN56.js → c-W7LWCJHD.js} +1 -1
- package/dist/m/c-WHMZCRQD.js +6 -0
- package/dist/m/{c-WMRJKQ2V.js → c-WHQUZM2V.js} +1 -1
- package/dist/m/c-WOJ2RTV4.js +1 -0
- package/dist/m/{c-ZJG6C2WL.js → c-WOWGGNCJ.js} +1 -1
- package/dist/m/{c-ZORVIXUR.js → c-XHD7XJTE.js} +1 -1
- package/dist/m/c-XONFK3WC.js +1 -0
- package/dist/m/{c-HGG7M2S3.js → c-Y3J5LDKB.js} +1 -1
- package/dist/m/{c-JB5YU57S.js → c-Y3MPVURA.js} +1 -1
- package/dist/m/c-Y6HH7LTM.js +1 -0
- package/dist/m/{c-52OC3OH3.js → c-YRDKVEGR.js} +1 -1
- package/dist/m/c-YYIN4NAF.js +30 -0
- package/dist/m/{c-WVCT3ITX.js → c-YZGWSCMT.js} +1 -1
- package/dist/m/c-ZFAPQQIB.js +1 -0
- package/dist/m/core/{admin-attrs-ZI6K6UGM.js → admin-attrs-43JBA6JB.js} +1 -1
- package/dist/m/core/{autosave-BSM2ABJL.js → autosave-HD7Y7UNV.js} +1 -1
- package/dist/m/core/{conflict-notice-7EQLDYPA.js → conflict-notice-UNDOD4BL.js} +1 -1
- package/dist/m/core/{persist-2SZO3PCE.js → persist-MX3VYVBN.js} +1 -1
- package/dist/m/core/{save-K6Q47J2X.js → save-JZ7R4ET2.js} +1 -1
- package/dist/m/core/{save-core-U3Z44XBO.js → save-core-I4N6S4ED.js} +1 -1
- package/dist/m/core/site-edits-C22O53GS.js +1 -0
- package/dist/m/core/{snapshot-PWCHYFL6.js → snapshot-N5MKDGNI.js} +1 -1
- package/dist/m/core/{unsaved-warning-2WRZLJH5.js → unsaved-warning-BP6ZY3PG.js} +1 -1
- package/dist/m/internals/{index-FSLC4M7J.js → index-3LAVREAU.js} +1 -1
- package/dist/m/loader-DCKCZKY6.js +1 -0
- package/dist/m/manifest-7954CBE3.js +1 -0
- package/dist/m/manifest-edit-93AA79D7.js +1 -0
- package/dist/m/manifest.json +1240 -1179
- package/dist/m/page/{editor-PKSPJ2XJ.js → editor-UF3CIA6V.js} +5 -5
- package/dist/m/plugins/{ai-edit-3LG6MGS3.js → ai-edit-WDV2GLDU.js} +2 -2
- package/dist/m/plugins/{cms-T2HLI7JG.js → cms-KG6TCTR3.js} +9 -9
- package/dist/m/plugins/{derive-7DR5TUDH.js → derive-R4USHZ7P.js} +1 -1
- package/dist/m/plugins/{indicator-CTKX4EY7.js → indicator-OLPZ4WFI.js} +1 -1
- package/dist/m/plugins/{live-IFLL6YN5.js → live-DF5YS5AH.js} +1 -1
- package/dist/m/plugins/{menu-YJZ3JWBC.js → menu-ESEFQARM.js} +1 -1
- package/dist/m/plugins/page-GZSU77JS.js +2 -0
- package/dist/m/plugins/{page-view-NMCCU6MF.js → page-view-7MM3FEAR.js} +1 -1
- package/dist/m/plugins/{people-6WIWCZEG.js → people-EBWE337I.js} +1 -1
- package/dist/m/plugins/quickcrop-UHURKA2F.js +26 -0
- package/dist/m/plugins/richclay-MXH2GFAZ.js +549 -0
- package/dist/m/plugins/sortable-BAG3MSWS.js +1 -0
- package/dist/m/plugins/{source-7LSGOV46.js → source-OYHDXKTB.js} +1 -1
- package/dist/m/plugins/undo-KLHAGV75.js +1 -0
- package/dist/m/plugins/{upgrade-MMPZUCDM.js → upgrade-SZQJYEQQ.js} +1 -1
- package/dist/m/plugins/{visitor-drafts-V5YB7XDH.js → visitor-drafts-ZE6PCAFP.js} +2 -2
- package/dist/m/plugins/{visitor-menu-QDV2MYJN.js → visitor-menu-5XY57JQA.js} +1 -1
- package/dist/m/plugins/{wire-3YGUO3GE.js → wire-S5ATC4QI.js} +1 -1
- package/dist/m/reader-core-AHOAP5DH.js +77 -0
- package/dist/m/sync/{live-sync-XMXXZS3G.js → live-sync-RZQSDMZW.js} +1 -1
- package/dist/m/ui/index-G7ZJXNMG.js +1 -0
- package/dist/m/vendor/{sap-core.vendor-JANUQULT.js → sap-core.vendor-5TWOJYVM.js} +4 -4
- package/dist/offline-manifest.json +780 -750
- 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 +26 -37
- package/entries/clay-data.js +1 -1
- package/entries/sap.js +5 -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/save.js +9 -9
- package/src/core/site-edits.js +1 -1
- package/src/core/snapshot.js +56 -43
- package/src/core/stale-host-notice.js +20 -7
- package/src/loader-logic.js +0 -13
- package/src/page/boot-pack.js +1 -0
- package/src/page/editor.js +10 -4
- package/src/page/history-intent.js +111 -4
- package/src/page/import-markdown.js +22 -8
- package/src/page/save-projection.js +1 -1
- package/src/page/ybind/bridge.js +14 -7
- package/src/page/ybind/moves.js +437 -3
- package/src/page/ybind/origins.js +1 -1
- package/src/page/ybind/recovery.js +1 -1
- package/src/plugins/page.js +48 -4
- package/src/plugins/quickcrop.js +128 -69
- package/src/plugins/richclay.js +136 -0
- package/src/plugins/sortable.js +25 -9
- package/src/plugins/undo.js +169 -7
- package/src/recovery/undo.js +14 -7
- 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 +24 -0
- 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/ui/vendor-skin.js +9 -4
- 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 +4 -4
- package/tool-generation/context.md +69 -0
- package/tool-generation/system.md +1 -1
- package/dist/m/c-277QP5ET.js +0 -1
- package/dist/m/c-5SZ6KFR5.js +0 -1
- package/dist/m/c-CU33V6RW.js +0 -4
- package/dist/m/c-CVSWLX3X.js +0 -1
- package/dist/m/c-DXPOFLEJ.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-FJDTDGDA.js +0 -1
- package/dist/m/c-FYDKYW6E.js +0 -1
- package/dist/m/c-HKSLOVU4.js +0 -1
- package/dist/m/c-IJHF4IO6.js +0 -1
- package/dist/m/c-IUL7PU4P.js +0 -1
- package/dist/m/c-JWW6BHWD.js +0 -6
- package/dist/m/c-K4M76VPM.js +0 -7
- package/dist/m/c-M7ITYKMF.js +0 -1
- package/dist/m/c-MR75YPRT.js +0 -1
- package/dist/m/c-NFQFV7OP.js +0 -2
- package/dist/m/c-NHXOBD6A.js +0 -1
- package/dist/m/c-NVU3DORS.js +0 -1
- package/dist/m/c-PNZIJFG7.js +0 -1
- package/dist/m/c-QWB53KRW.js +0 -1
- package/dist/m/c-RHPHMTF2.js +0 -2
- package/dist/m/c-RWJ3OSCP.js +0 -1
- package/dist/m/c-SEVOS4UE.js +0 -1
- package/dist/m/c-SGG6MUXW.js +0 -1
- package/dist/m/c-SXNSSHPP.js +0 -1
- package/dist/m/c-TFLRT5JX.js +0 -1
- package/dist/m/c-TLQINES7.js +0 -30
- package/dist/m/c-U65PREMC.js +0 -1
- package/dist/m/c-V5L6HWVL.js +0 -1
- package/dist/m/c-VMXCX52Q.js +0 -1
- package/dist/m/c-YYGUHSZR.js +0 -1
- package/dist/m/core/site-edits-CFMF6GHD.js +0 -1
- package/dist/m/loader-RC54UV7H.js +0 -1
- package/dist/m/manifest-3B84C829.js +0 -1
- package/dist/m/manifest-edit-1CB79AD4.js +0 -1
- package/dist/m/plugins/page-GTDPYGE4.js +0 -2
- package/dist/m/plugins/quickcrop-X5MRIBAP.js +0 -26
- package/dist/m/plugins/richclay-L3BZMX4I.js +0 -549
- package/dist/m/plugins/sortable-365SRRL3.js +0 -1
- package/dist/m/plugins/undo-UES4KS7B.js +0 -1
- package/dist/m/reader-core-VR7EL4WV.js +0 -77
- package/dist/m/ui/index-MGTWOW2C.js +0 -1
- /package/dist/m/{c-QTTJYJMA.js → c-FILLTJA7.js} +0 -0
- /package/dist/m/plugins/{quickcrop-X5MRIBAP.js.LEGAL.txt → quickcrop-UHURKA2F.js.LEGAL.txt} +0 -0
- /package/dist/m/plugins/{richclay-L3BZMX4I.js.LEGAL.txt → richclay-MXH2GFAZ.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/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
|
}
|
|
@@ -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/site-edits.js
CHANGED
|
@@ -104,7 +104,7 @@ function takeCurrent(current, sent, refused) {
|
|
|
104
104
|
if (refused.has(key) || !rule || !Object.hasOwn(rule, key) || live[key] !== sent[key]) continue;
|
|
105
105
|
// Outside undo history and the edit observers, the way a sync frame lands: an undo
|
|
106
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]));
|
|
107
|
+
const fields = new Map([...root.querySelectorAll("[data-richclay], [richclay], [editable], [clay-editable]")].map((field) => [field, field.innerHTML]));
|
|
108
108
|
Mutation.pause();
|
|
109
109
|
try {
|
|
110
110
|
engine.applyData(root, { site: { [key]: value } }, { site: { [key]: rule[key] } });
|
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 };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { servedStaleToken } from "./host-attrs.js";
|
|
2
2
|
import { isEditMode } from "./is-edit-mode.js";
|
|
3
3
|
import onDomReady from "../lib/dom-ready.js";
|
|
4
|
-
import { set, make } from "../lib/hostile-css.js";
|
|
4
|
+
import { set, style, make } from "../lib/hostile-css.js";
|
|
5
5
|
|
|
6
6
|
// The page's only visible word on a host too old to save to.
|
|
7
7
|
//
|
|
@@ -47,12 +47,13 @@ function dismiss() {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function build() {
|
|
50
|
+
const theme = ui.resolveTheme();
|
|
50
51
|
root = ui.bevelSurface("div", [
|
|
51
52
|
"position:fixed", "left:50%", "transform:translateX(-50%)",
|
|
52
53
|
"z-index:2147483001", "display:flex", "align-items:center", "gap:10px 12px",
|
|
53
54
|
"width:max-content", "max-width:calc(100vw - 24px)", "flex-wrap:wrap",
|
|
54
55
|
"padding:8px 8px 8px 14px", "text-align:left", `color-scheme:${ui.pageScheme()}`,
|
|
55
|
-
]);
|
|
56
|
+
], { theme, parts: ["notice.root", "notice.panel", "staleHost.root", "staleHost.panel"] });
|
|
56
57
|
// Three markers, and each is load-bearing on a page that CAN save: this element is
|
|
57
58
|
// injected, so it is in no document on disk and must never reach one, never wake the
|
|
58
59
|
// watcher, and never ride out in a snapshot to somebody else's browser.
|
|
@@ -62,13 +63,23 @@ function build() {
|
|
|
62
63
|
|
|
63
64
|
const dot = make("span", ["all:initial", "color-scheme:inherit", "display:inline-block", "flex:none", "width:8px", "height:8px", "border-radius:50%", `background:${ui.TOKENS.ox}`]);
|
|
64
65
|
dot.setAttribute("clay", ui.RUNTIME_ONLY);
|
|
65
|
-
const
|
|
66
|
+
const dotNames = ["notice.dot", "staleHost.dot"];
|
|
67
|
+
if (ui.themed(theme)) {
|
|
68
|
+
ui.markParts(dot, theme, dotNames);
|
|
69
|
+
style(dot, ui.partRules(theme, dotNames, {}));
|
|
70
|
+
}
|
|
71
|
+
const close = ui.bevelButton("Dismiss", {
|
|
72
|
+
small: true, variant: "quiet", onClick: dismiss, theme,
|
|
73
|
+
parts: ["notice.button", "staleHost.button"],
|
|
74
|
+
labelParts: ["notice.buttonLabel", "staleHost.buttonLabel"],
|
|
75
|
+
});
|
|
66
76
|
close.setAttribute("aria-label", "Dismiss this message");
|
|
67
|
-
root.append(dot, ui.bevelText("span", [], MESSAGE), close);
|
|
77
|
+
root.append(dot, ui.bevelText("span", [], MESSAGE, { theme, parts: ["notice.text", "staleHost.text"] }), close);
|
|
68
78
|
|
|
69
79
|
document.body.appendChild(root);
|
|
70
80
|
place();
|
|
71
|
-
ui.
|
|
81
|
+
if (ui.themed(theme)) set(root, "color-scheme", theme.scheme === "light dark" ? "light" : theme.scheme);
|
|
82
|
+
else ui.paintLook(root, { background: ui.TOKENS.surface });
|
|
72
83
|
window.visualViewport?.addEventListener("resize", place);
|
|
73
84
|
window.visualViewport?.addEventListener("scroll", place);
|
|
74
85
|
}
|
|
@@ -81,8 +92,10 @@ async function init() {
|
|
|
81
92
|
// told its reader the opposite. The message is only true while editing is actually
|
|
82
93
|
// off, so it is shown only then.
|
|
83
94
|
if (isEditMode) return;
|
|
84
|
-
const [controls, bevel] = await Promise.all([
|
|
85
|
-
|
|
95
|
+
const [controls, bevel, themeConfig, parts] = await Promise.all([
|
|
96
|
+
import("../ui/bevel-controls.js"), import("../ui/bevel.js"), import("../ui/theme.js"), import("../ui/theme-parts.js"),
|
|
97
|
+
]);
|
|
98
|
+
ui = { ...controls, ...parts, TOKENS: bevel.TOKENS, resolveTheme: themeConfig.resolveTheme };
|
|
86
99
|
build();
|
|
87
100
|
}
|
|
88
101
|
|
package/src/loader-logic.js
CHANGED
|
@@ -195,10 +195,6 @@ const IMPLIES = { cms: ["quickcrop", "upload", "menu"], "ai-edit": ["wire"], blo
|
|
|
195
195
|
// A Document's editor owns its article's rich text, so richclay (for [editable]) has
|
|
196
196
|
// nothing to do there; plugins=blockeditor,richclay still loads it.
|
|
197
197
|
const DISPLACES = { blockeditor: ["richclay"] };
|
|
198
|
-
// A Document's editor keeps its own history, the same one Cmd+Z in the editor uses, and
|
|
199
|
-
// hyper-undo takes Cmd+Z for the whole window: loaded together the two would fight over
|
|
200
|
-
// every press. This is the one pair where a name the page asked for by hand still loses.
|
|
201
|
-
const CANNOT_RUN_WITH = { blockeditor: ["undo"] };
|
|
202
198
|
// No first paint or keystroke waits for these. source keeps the file's bytes outside
|
|
203
199
|
// the edited parts stable; a save before it installs is the ordinary full serialization.
|
|
204
200
|
// upgrade only asks the host for a newer version once `clay.ready` resolves.
|
|
@@ -296,15 +292,6 @@ export function resolveModules(params, isEditMode, { isDraftMode = false, hasSap
|
|
|
296
292
|
if (enabled.has(owner)) for (const name of names) if (!explicit.has(name)) enabled.delete(name);
|
|
297
293
|
}
|
|
298
294
|
|
|
299
|
-
for (const [owner, names] of Object.entries(CANNOT_RUN_WITH)) {
|
|
300
|
-
if (!enabled.has(owner)) continue;
|
|
301
|
-
for (const name of names) {
|
|
302
|
-
if (!enabled.has(name)) continue;
|
|
303
|
-
enabled.delete(name);
|
|
304
|
-
console.warn(`clayjs: plugin "${name}" cannot run with "${owner}" and was left out`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
295
|
const plugins = [];
|
|
309
296
|
const late = [];
|
|
310
297
|
for (const name of PLUGIN_ORDER) {
|
package/src/page/boot-pack.js
CHANGED
|
@@ -20,6 +20,7 @@ export { createYPageBinding } from "./ybind/binding.js";
|
|
|
20
20
|
export { yValueOf } from "./ybind/provider.js";
|
|
21
21
|
export { seedPageFromArticle } from "./ybind/migrate.js";
|
|
22
22
|
export { holdSlashRange, heldSlashRange } from "./chrome.js";
|
|
23
|
+
export { createHistoryBridge } from "./history-intent.js";
|
|
23
24
|
export { createRecoverList, focusBlock } from "./recover.js";
|
|
24
25
|
export { observeDocumentTitle } from "./document-title-runtime.js";
|
|
25
26
|
export { createFeatureLoader } from "./feature-loader.js";
|