@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
package/src/plugins/quickcrop.js
CHANGED
|
@@ -13,6 +13,9 @@ import { bevelDialog, dismissOf, keepFocusIn } from "../ui/bevel-dialog.js";
|
|
|
13
13
|
import { bevelButton } from "../ui/bevel-controls.js";
|
|
14
14
|
import { installSkin } from "../ui/vendor-skin.js";
|
|
15
15
|
import { CSS as SKIN, ROOTS as SKIN_ROOTS } from "../ui/skins/quickcrop.js";
|
|
16
|
+
import { resolveTheme } from "../ui/theme.js";
|
|
17
|
+
import { themed, partRules, markParts } from "../ui/theme-parts.js";
|
|
18
|
+
import { style } from "../lib/hostile-css.js";
|
|
16
19
|
|
|
17
20
|
const WIDTH = 844;
|
|
18
21
|
|
|
@@ -24,80 +27,136 @@ function isThemodal(m) {
|
|
|
24
27
|
typeof m.onYes === "function" && "html" in m;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
30
|
+
const TITLE = "Crop image";
|
|
31
|
+
|
|
32
|
+
// The frame for one crop, in the theme captured for that crop. Measuring and opening
|
|
33
|
+
// build the same frame, so the stage is sized for the padding, borders, heading and
|
|
34
|
+
// button the person will actually see.
|
|
35
|
+
function cropFrame(theme, confirmLabel, onConfirm = null) {
|
|
36
|
+
const frame = bevelDialog({ zIndex: "2147483001", width: `${WIDTH}px`, closable: true, titled: true, theme, surface: "crop" });
|
|
37
|
+
frame.heading.textContent = TITLE;
|
|
38
|
+
frame.panel.setAttribute("aria-label", TITLE);
|
|
39
|
+
const confirm = bevelButton(confirmLabel, {
|
|
40
|
+
variant: "primary", onClick: onConfirm, theme,
|
|
41
|
+
parts: ["dialog.button", "crop.button"], labelParts: ["dialog.buttonLabel", "crop.buttonLabel"],
|
|
42
|
+
});
|
|
43
|
+
frame.footer.append(confirm);
|
|
44
|
+
return { ...frame, confirm };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The stage is quickcrop's; its parts take only a page's explicit crop.* declarations,
|
|
48
|
+
// so dimming and crop lines keep their contrast unless the page asks otherwise.
|
|
49
|
+
function themeStage(stage, theme) {
|
|
50
|
+
if (!themed(theme)) return;
|
|
51
|
+
const paint = (el, names) => {
|
|
52
|
+
if (!el) return;
|
|
53
|
+
markParts(el, theme, names);
|
|
54
|
+
style(el, partRules(theme, names, {}));
|
|
55
|
+
};
|
|
56
|
+
paint(stage, ["crop.stage"]);
|
|
57
|
+
paint(stage.querySelector(":scope > img"), ["crop.image"]);
|
|
58
|
+
paint(stage.querySelector(".qc-dim"), ["crop.dim"]);
|
|
59
|
+
paint(stage.querySelector(".qc-box"), ["crop.selection"]);
|
|
60
|
+
for (const corner of ["nw", "ne", "sw", "se"]) paint(stage.querySelector(`.qc-handle.qc-${corner}`), ["crop.handle", `crop.handle.${corner}`]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// A themed frame is measured: a hidden copy is mounted for one layout read and removed,
|
|
64
|
+
// without touching focus or the scroll lock. The room left for the stage is the body's
|
|
65
|
+
// inner width, and the overlay's inner height less the empty panel. The exact Bevel
|
|
66
|
+
// frame keeps its fixed allowances.
|
|
67
|
+
function measure(theme, confirmLabel) {
|
|
68
|
+
const { root, overlay, panel, body } = cropFrame(theme, confirmLabel);
|
|
69
|
+
root.setAttribute("aria-hidden", "true");
|
|
70
|
+
style(root, ["visibility:hidden", "pointer-events:none"]);
|
|
71
|
+
document.body.append(root);
|
|
72
|
+
try {
|
|
73
|
+
const px = (value) => parseFloat(value) || 0;
|
|
74
|
+
const o = getComputedStyle(overlay), b = getComputedStyle(body);
|
|
75
|
+
const width = body.clientWidth - px(b.paddingLeft) - px(b.paddingRight);
|
|
76
|
+
const height = overlay.clientHeight - px(o.paddingTop) - px(o.paddingBottom) - panel.offsetHeight;
|
|
77
|
+
return { width: Math.max(1, Math.floor(width)), height: Math.max(1, Math.floor(height)) };
|
|
78
|
+
} finally {
|
|
79
|
+
root.remove();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function createCropAdapter({ theme = null, confirmLabel = "Crop" } = {}) {
|
|
84
|
+
return {
|
|
85
|
+
// Unthemed, the stage fits inside the panel by fixed allowances: the overlay's 16px
|
|
86
|
+
// side inset, the panel's 1px border and the body's 22px padding a side; below it the
|
|
87
|
+
// header, the footer and the overlay's top inset. Widths come from clientWidth, which
|
|
88
|
+
// leaves out a classic scrollbar as the overlay does.
|
|
89
|
+
fit() {
|
|
90
|
+
if (themed(theme)) return measure(theme, confirmLabel);
|
|
91
|
+
const width = document.documentElement.clientWidth;
|
|
92
|
+
return {
|
|
93
|
+
width: Math.max(120, Math.min(WIDTH, width - 32) - 2 - 44),
|
|
94
|
+
height: Math.max(120, window.innerHeight - Math.min(96, window.innerHeight * 0.1) - 200),
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
open({ content, confirmLabel: label, onConfirm, onCancel }) {
|
|
98
|
+
const { root, overlay, panel, body, close, confirm } = cropFrame(theme, label, () => onConfirm());
|
|
99
|
+
themeStage(content, theme);
|
|
100
|
+
body.append(content);
|
|
101
|
+
|
|
102
|
+
close.addEventListener("click", () => onCancel());
|
|
103
|
+
panel.addEventListener("submit", (event) => event.preventDefault());
|
|
104
|
+
let pressedOnBackdrop = false;
|
|
105
|
+
overlay.addEventListener("mousedown", (event) => { pressedOnBackdrop = event.target === overlay; });
|
|
106
|
+
overlay.addEventListener("click", (event) => {
|
|
107
|
+
if (pressedOnBackdrop && event.target === overlay) onCancel();
|
|
108
|
+
pressedOnBackdrop = false;
|
|
109
|
+
});
|
|
110
|
+
// Capture, and stopped there, so a dialog underneath (a CMS confirm through
|
|
111
|
+
// clay.modal) does not take the same key as its own: Escape cancels this crop,
|
|
112
|
+
// Tab stays inside it.
|
|
113
|
+
const onKey = (event) => {
|
|
114
|
+
if (event.key === "Tab") {
|
|
115
|
+
event.stopPropagation();
|
|
116
|
+
keepFocusIn(panel, event);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (event.key !== "Escape") return;
|
|
62
120
|
event.stopPropagation();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
};
|
|
121
|
+
onCancel();
|
|
122
|
+
};
|
|
123
|
+
document.addEventListener("keydown", onKey, true);
|
|
124
|
+
|
|
125
|
+
// The page does not scroll behind the crop, as it did not under themodal. The
|
|
126
|
+
// previous value comes back, so a modal underneath keeps its own lock.
|
|
127
|
+
const overflow = document.body.style.overflow;
|
|
128
|
+
document.body.style.overflow = "hidden";
|
|
129
|
+
|
|
130
|
+
let done = false;
|
|
131
|
+
const finish = () => {
|
|
132
|
+
if (done) return;
|
|
133
|
+
done = true;
|
|
134
|
+
document.removeEventListener("keydown", onKey, true);
|
|
135
|
+
document.body.style.overflow = overflow;
|
|
136
|
+
root.remove();
|
|
137
|
+
};
|
|
138
|
+
// A modal opened on top supersedes this crop the way it supersedes itself: the
|
|
139
|
+
// crop is cancelled, so its promise settles and the next crop can open.
|
|
140
|
+
dismissOf.set(root, () => { finish(); onCancel(); });
|
|
141
|
+
|
|
142
|
+
root.setAttribute("aria-hidden", "false");
|
|
143
|
+
document.body.append(root);
|
|
144
|
+
confirm.focus();
|
|
145
|
+
|
|
146
|
+
return { close: finish };
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// The exact Bevel frame, for callers that want ClayJS's crop without the page's theme.
|
|
152
|
+
export const bevelCropAdapter = createCropAdapter();
|
|
96
153
|
|
|
97
154
|
export function quickcrop(file, options = {}) {
|
|
98
155
|
const modal = options.modal ?? "auto";
|
|
99
156
|
const framed = modal === "auto" || isThemodal(modal);
|
|
100
|
-
return vendorQuickcrop(file,
|
|
157
|
+
if (!framed) return vendorQuickcrop(file, options);
|
|
158
|
+
const adapter = createCropAdapter({ theme: resolveTheme(), confirmLabel: options.labels?.confirm ?? "Crop" });
|
|
159
|
+
return vendorQuickcrop(file, { ...options, modal: adapter });
|
|
101
160
|
}
|
|
102
161
|
|
|
103
162
|
export default quickcrop;
|
package/src/plugins/richclay.js
CHANGED
|
@@ -5,9 +5,145 @@ import { installSkin } from "../ui/vendor-skin.js";
|
|
|
5
5
|
import { CSS, ROOTS } from "../ui/skins/richclay.js";
|
|
6
6
|
import { RichClay } from "../vendor/richclay.vendor.js";
|
|
7
7
|
import { startsAtImport } from "../core/is-edit-mode.js";
|
|
8
|
+
import { stripEphemeral } from "../lib/ephemeral.js";
|
|
9
|
+
import { loadedEngine, mergeEngine } from "../sync/merge-engine.js";
|
|
8
10
|
|
|
9
11
|
installSkin("richclay", CSS, { roots: ROOTS });
|
|
10
12
|
|
|
13
|
+
// A field the author kept out of page undo: its own literal no-undo (not the one RichClay
|
|
14
|
+
// adds while it runs), a clay="no-undo" token on it, or an ancestor region with no-undo.
|
|
15
|
+
const authoredNoUndo = (node) => {
|
|
16
|
+
if (node.hasAttribute("no-undo") && !node.hasAttribute("data-richclay-runtime-no-undo")) return true;
|
|
17
|
+
if ((node.getAttribute("clay") || "").split(/\s+/).includes("no-undo")) return true;
|
|
18
|
+
const parent = node.parentElement;
|
|
19
|
+
return !!parent && window.clay?.region?.resolveRegionPolicy?.(parent)?.undoable === false;
|
|
20
|
+
};
|
|
21
|
+
// The editor tidies a field's source whitespace the first time it is typed in. Whitespace
|
|
22
|
+
// that sits beside a block, or at the start or end of one, shows nothing, so it never
|
|
23
|
+
// counts as a change: a person's first letter is one step, and the tidy is not a step of
|
|
24
|
+
// its own or part of another field's.
|
|
25
|
+
const BLOCK = /^(P|DIV|UL|OL|LI|H[1-6]|BLOCKQUOTE|TABLE|THEAD|TBODY|TR|TD|TH|FIGURE|FIGCAPTION|HR|SECTION|ARTICLE|ASIDE|HEADER|FOOTER|DL|DT|DD)$/;
|
|
26
|
+
const isBlock = (node) => node?.nodeType === 1 && BLOCK.test(node.nodeName);
|
|
27
|
+
const dropLayoutWhitespace = (root) => {
|
|
28
|
+
const walker = root.ownerDocument.createTreeWalker(root, 4);
|
|
29
|
+
const blank = [];
|
|
30
|
+
for (let text = walker.nextNode(); text; text = walker.nextNode()) {
|
|
31
|
+
if (/\S/.test(text.data) || text.parentElement?.closest("pre")) continue;
|
|
32
|
+
const parent = text.parentNode;
|
|
33
|
+
const edge = !text.previousSibling || !text.nextSibling;
|
|
34
|
+
if ((edge && (parent === root || isBlock(parent))) || isBlock(text.previousSibling) || isBlock(text.nextSibling)) blank.push(text);
|
|
35
|
+
}
|
|
36
|
+
blank.forEach((text) => text.remove());
|
|
37
|
+
};
|
|
38
|
+
// Ephemeral attributes (selection marks and the like) never save, so they come off a
|
|
39
|
+
// copy before the comparison, as they do for every other node undo certifies.
|
|
40
|
+
const savedForm = (node) => {
|
|
41
|
+
const copy = node.cloneNode(true);
|
|
42
|
+
stripEphemeral(copy);
|
|
43
|
+
dropLayoutWhitespace(copy);
|
|
44
|
+
return RichClay.savedContent(copy);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Parts of a field that history does not own (editor chrome, a widget the author marked
|
|
48
|
+
// no-undo) never count as a change: the sample holds a placeholder for each, and a replay
|
|
49
|
+
// puts back what each one holds now.
|
|
50
|
+
const UNOWNED = '[editor-ui], [clay~="editor-ui"], [no-undo]:not([data-richclay-runtime-no-undo]), [clay~="no-undo"]';
|
|
51
|
+
const outermost = (root) => [...root.querySelectorAll(UNOWNED)].filter((el) => {
|
|
52
|
+
for (let at = el.parentElement; at && at !== root; at = at.parentElement) if (at.matches(UNOWNED)) return false;
|
|
53
|
+
return true;
|
|
54
|
+
});
|
|
55
|
+
const historyForm = (node) => {
|
|
56
|
+
const copy = node.cloneNode(true);
|
|
57
|
+
outermost(copy).forEach((el) => el.replaceWith(copy.ownerDocument.createElement("clay-kept")));
|
|
58
|
+
return savedForm(copy);
|
|
59
|
+
};
|
|
60
|
+
const withUnowned = (element, html) => {
|
|
61
|
+
if (!html.includes("<clay-kept")) return html;
|
|
62
|
+
const template = element.ownerDocument.createElement("template");
|
|
63
|
+
template.innerHTML = html;
|
|
64
|
+
const live = outermost(element);
|
|
65
|
+
[...template.content.querySelectorAll("clay-kept")].forEach((slot, at) => {
|
|
66
|
+
if (live[at]) slot.replaceWith(live[at].cloneNode(true));
|
|
67
|
+
else slot.remove();
|
|
68
|
+
});
|
|
69
|
+
return template.innerHTML;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Undo certifies a rich field by the content it would save, so editor runtime state
|
|
73
|
+
// never refuses a step and a real text change always does. A field the author marked
|
|
74
|
+
// no-undo keeps its protection.
|
|
75
|
+
const richForm = (node) => (!node.matches?.(RichClay.selector) || authoredNoUndo(node) ? undefined : savedForm(node));
|
|
76
|
+
|
|
77
|
+
// The field's own typing is a step of the page history: the engine reads the content the
|
|
78
|
+
// field would save and, on a replay, hands it back through the host write. The editor's own
|
|
79
|
+
// history is off while the page owns it, so a field's Cmd+Z and its toolbar reach the same
|
|
80
|
+
// stack as everything else.
|
|
81
|
+
// The engine rebaselines the field right after the write, so the write must land before
|
|
82
|
+
// it returns. A loaded merge engine morphs at once. The first undo asks for it, so a boot
|
|
83
|
+
// pays nothing; until it lands a plain innerHTML write is exact for saved content (checked
|
|
84
|
+
// in Chrome), except in a field holding editor chrome, which only the morph keeps.
|
|
85
|
+
const writeInner = (root, html) => { root.innerHTML = html; };
|
|
86
|
+
const morphNow = (field) => {
|
|
87
|
+
const engine = loadedEngine();
|
|
88
|
+
if (engine) return engine.morph;
|
|
89
|
+
mergeEngine().catch(() => {});
|
|
90
|
+
return field.querySelector('[editor-ui], [clay~="editor-ui"]') ? undefined : writeInner;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Rich fields belong to the page history only when the page records typing: in commands
|
|
94
|
+
// mode the page's history holds commands alone, and a field's own history is left to the
|
|
95
|
+
// editor, which is then the only history that has the typing.
|
|
96
|
+
const pageOwnsTyping = () => window.clay?.undo?.pageHistory === true && window.clay.undo.recordingMode !== "commands";
|
|
97
|
+
|
|
98
|
+
const tracked = new Map();
|
|
99
|
+
|
|
100
|
+
const track = (field) => {
|
|
101
|
+
const undo = window.clay?.undo;
|
|
102
|
+
if (!pageOwnsTyping() || authoredNoUndo(field)) return;
|
|
103
|
+
tracked.set(field, undo.trackContent(field, { read: historyForm, write: (element, html) => { RichClay.setSavedContent(element, withUnowned(element, html), { morph: morphNow(element) }); } }));
|
|
104
|
+
if (!loadedEngine() && field.querySelector('[editor-ui], [clay~="editor-ui"]') && typeof undo.hold === "function") {
|
|
105
|
+
const release = undo.hold();
|
|
106
|
+
mergeEngine().catch(() => {}).finally(release);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const untrackAll = () => {
|
|
111
|
+
for (const dispose of tracked.values()) dispose();
|
|
112
|
+
tracked.clear();
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
document.addEventListener("richclay:mount", (event) => track(event.target));
|
|
116
|
+
|
|
117
|
+
// The rich form is handed to the page's undo when its session starts, rather than through
|
|
118
|
+
// a shared module, so a page without undo loads nothing for it. Each undo engine is asked
|
|
119
|
+
// once.
|
|
120
|
+
let formedFor = null;
|
|
121
|
+
const followHistory = () => {
|
|
122
|
+
const undo = window.clay?.undo;
|
|
123
|
+
if (undo?.addDurableForm && formedFor !== undo) {
|
|
124
|
+
undo.addDurableForm(richForm);
|
|
125
|
+
formedFor = undo;
|
|
126
|
+
}
|
|
127
|
+
RichClay.hostHistory = pageOwnsTyping();
|
|
128
|
+
RichClay.keepsOwnHistory = authoredNoUndo;
|
|
129
|
+
if (!RichClay.hostHistory) untrackAll();
|
|
130
|
+
else document.querySelectorAll("[data-richclay-active]").forEach(track);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
document.addEventListener("clay:undo-session", followHistory);
|
|
134
|
+
document.addEventListener("clay:undo-recording", followHistory);
|
|
135
|
+
// A boot starts the undo session while its own module is still being imported, so the
|
|
136
|
+
// session event can land before the loader has put `clay.undo` on the page: once every
|
|
137
|
+
// plugin is attached the same answer is asked for again.
|
|
138
|
+
document.addEventListener("clay:ready", followHistory);
|
|
139
|
+
|
|
140
|
+
document.addEventListener("richclay:history-exhausted", (event) => {
|
|
141
|
+
const undo = window.clay?.undo;
|
|
142
|
+
if (!undo || window.clay.modeLifecycle?.canWrite?.() === false) return;
|
|
143
|
+
if (event.detail?.direction === "redo") undo.redo();
|
|
144
|
+
else undo.undo();
|
|
145
|
+
});
|
|
146
|
+
|
|
11
147
|
let session = null;
|
|
12
148
|
|
|
13
149
|
const fault = (message, code) => Object.assign(new Error(`clay:richclay: ${message}`), { code });
|
package/src/plugins/sortable.js
CHANGED
|
@@ -217,15 +217,31 @@ function makeSortable(sortableElem, Sortable, record = loose) {
|
|
|
217
217
|
// Add onsorting callback if attribute exists (fires during drag)
|
|
218
218
|
const onsortingCode = sortableElem.getAttribute('onsorting');
|
|
219
219
|
if (onsortingCode) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
220
|
+
// Sortable reads onMove's return value (false cancels the move, 1 or -1 picks a
|
|
221
|
+
// side), so the handler runs synchronously and its value goes back. A handler that
|
|
222
|
+
// awaits cannot answer in time; it still runs, as an async function, and Sortable
|
|
223
|
+
// gets no answer from it.
|
|
224
|
+
// Code that awaits compiles as async: a plain function would read `await (x)` as a
|
|
225
|
+
// call to a function named await and throw on every move.
|
|
226
|
+
let onsorting = null;
|
|
227
|
+
try {
|
|
228
|
+
onsorting = /\bawait\b/.test(onsortingCode)
|
|
229
|
+
? new Function(`return (async function(evt) { ${onsortingCode} })`)()
|
|
230
|
+
: new Function('evt', onsortingCode);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error('Error in onsorting compilation:', error);
|
|
233
|
+
}
|
|
234
|
+
if (onsorting) {
|
|
235
|
+
options.onMove = function(evt) {
|
|
236
|
+
if (!record.admitted) return;
|
|
237
|
+
try {
|
|
238
|
+
const answer = onsorting.call(sortableElem, evt);
|
|
239
|
+
return answer instanceof Promise ? undefined : answer;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error('Error in onsorting execution:', error);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
229
245
|
}
|
|
230
246
|
|
|
231
247
|
// After a drop, run any author onsorted code, then notify reactive libraries.
|