@panphora/clayjs 1.19.0 → 1.21.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 +8349 -8125
- package/dist/m/attrs/{refetch-on-save-ZUE7PSCI.js → refetch-on-save-DPGJJMGN.js} +1 -1
- package/dist/m/attrs/save-freeze-MTFVVYV5.js +1 -0
- package/dist/m/{c-DHUCEMXE.js → c-2VB555FG.js} +1 -1
- package/dist/m/{c-KGHVFRR3.js → c-3AXJDL2P.js} +1 -1
- package/dist/m/{c-FOOE4GML.js → c-3GJ4XARY.js} +1 -1
- package/dist/m/c-3TWCKFOP.js +1 -0
- package/dist/m/{c-LPCZOYTX.js → c-3YH6JSIL.js} +1 -1
- package/dist/m/{c-AIWN4QN4.js → c-44B5ODN7.js} +2 -2
- package/dist/m/{c-WQWIRWO3.js → c-5FEU5MMY.js} +1 -1
- package/dist/m/{c-S2U6NJU4.js → c-62HZW5DH.js} +1 -1
- package/dist/m/{c-ZGDYHKG3.js → c-67ILSNPR.js} +1 -1
- package/dist/m/c-67LGZ52V.js +20 -0
- package/dist/m/{c-ZRUQ5FSU.js → c-7SXYQCN4.js} +1 -1
- package/dist/m/{c-LACAAQFB.js → c-AL4RBJF2.js} +1 -1
- package/dist/m/{c-L42OLXWM.js → c-ANNFZFLN.js} +1 -1
- package/dist/m/{c-QO4HNGCQ.js → c-B4VYNXTQ.js} +1 -1
- package/dist/m/{c-7PRJCA3E.js → c-BJPZCWAV.js} +1 -1
- package/dist/m/{c-V52FE5FI.js → c-BTLFLLML.js} +1 -1
- package/dist/m/c-C4IIQZGJ.js +1 -0
- package/dist/m/{c-5T6NZHNM.js → c-CAWNFER6.js} +1 -1
- package/dist/m/c-CSNE5EIF.js +1 -0
- package/dist/m/{c-DVYGFTSK.js → c-DDDOKPI6.js} +2 -2
- package/dist/m/{c-2RARVIAK.js → c-DHXPDLW6.js} +1 -1
- package/dist/m/{c-WCXLG3MV.js → c-E4IBSAQM.js} +1 -1
- package/dist/m/{c-IVTRVENZ.js → c-EDSPKYMA.js} +1 -1
- package/dist/m/{c-2Y4DOHSY.js → c-EFD6QNTE.js} +1 -1
- package/dist/m/{c-7MG5RU63.js → c-EGTIAL6P.js} +1 -1
- package/dist/m/{c-FCNAO2MI.js → c-EOCKLMKX.js} +1 -1
- package/dist/m/{c-UXFUJEZO.js → c-EOCZ2YVL.js} +1 -1
- package/dist/m/{c-K72SY2TL.js → c-EZ7B5AZT.js} +1 -1
- package/dist/m/{c-UPD33ZPE.js → c-FDAXZ2PM.js} +1 -1
- package/dist/m/{c-P3CK66NT.js → c-FNVQAACK.js} +1 -1
- package/dist/m/c-FSWRGBVQ.js +1 -0
- package/dist/m/c-FTWGYI5E.js +1 -0
- package/dist/m/{c-WKNRJX3C.js → c-GKEATME6.js} +1 -1
- package/dist/m/{c-QINHDETP.js → c-GPGYKER7.js} +1 -1
- package/dist/m/{c-FWKHM4E7.js → c-GSRLXYWL.js} +1 -1
- package/dist/m/{c-B7J4K2AE.js → c-GVPNDIKJ.js} +2 -2
- package/dist/m/{c-D3E4WJ4M.js → c-HDWZEJHM.js} +49 -32
- package/dist/m/{c-FTQKI7SP.js → c-I2IFKJX2.js} +1 -1
- package/dist/m/c-ICMAOOGK.js +1 -0
- package/dist/m/{c-HUISJXHF.js → c-J3BXFTZS.js} +1 -1
- package/dist/m/{c-BIJALSRD.js → c-JDZDF2QM.js} +1 -1
- package/dist/m/{c-UDHIVLYH.js → c-JH5FF4NL.js} +1 -1
- package/dist/m/{c-OYRZ7XG2.js → c-JHJR5BQX.js} +1 -1
- package/dist/m/{c-V44KWI2L.js → c-JOH753EJ.js} +2 -2
- package/dist/m/{c-P3OFYUMA.js → c-JUE7B4OX.js} +1 -1
- package/dist/m/c-K76AUL4I.js +7 -0
- package/dist/m/{c-ZWIOJGMT.js → c-LA2BW7UY.js} +1 -1
- package/dist/m/{c-ICJYHABF.js → c-LDUNOR57.js} +1 -1
- package/dist/m/{c-XSZKP646.js → c-LIBE4HPD.js} +1 -1
- package/dist/m/c-LPUJXRXW.js +1 -0
- package/dist/m/{c-JK3PHE7J.js → c-LQU7TXIH.js} +1 -1
- package/dist/m/{c-5ZQV7KC4.js → c-LV5MABYD.js} +1 -1
- package/dist/m/{c-GWNC3UGL.js → c-M4FSPNQ2.js} +1 -1
- package/dist/m/{c-2KIEGNC4.js → c-MEWUGR4D.js} +1 -1
- package/dist/m/c-MIL4B2JQ.js +1 -0
- package/dist/m/{c-IVMD3FEM.js → c-N7Y52HJH.js} +1 -1
- package/dist/m/{c-O4JMKN6U.js → c-NFR673TL.js} +2 -2
- package/dist/m/{c-ZKNBMNN3.js → c-NLZ5AN4L.js} +1 -1
- package/dist/m/{c-RRZJWFLE.js → c-OABVRYOT.js} +1 -1
- package/dist/m/c-PIVIYLTY.js +1 -0
- package/dist/m/{c-L7JL5TL3.js → c-QY7F6EN6.js} +1 -1
- package/dist/m/{c-UY4QX42B.js → c-RG6XUZFA.js} +1 -1
- package/dist/m/c-RHMPKZZ7.js +1 -0
- package/dist/m/{c-TJAXGIRV.js → c-RQRMPZXD.js} +1 -1
- package/dist/m/{c-GY422N3Q.js → c-RS77ODLY.js} +1 -1
- package/dist/m/{c-BAUBNYXK.js → c-RT3C74GY.js} +1 -1
- package/dist/m/{c-CAS3NND4.js → c-S5IEIZCN.js} +1 -1
- package/dist/m/{c-HXPHZN7Q.js → c-SBSMJIPC.js} +1 -1
- package/dist/m/c-SFKI6OLM.js +2 -0
- package/dist/m/{c-4FJTBE6I.js → c-SJPD46ZO.js} +4 -4
- package/dist/m/{c-JCP3XDSJ.js → c-SO63UGPQ.js} +1 -1
- package/dist/m/{c-PLP4JKFC.js → c-TUGVINJR.js} +1 -1
- package/dist/m/{c-Q26MME6N.js → c-TWCXBWS3.js} +1 -1
- package/dist/m/{c-GNDL3TKB.js → c-U5X57T4L.js} +1 -1
- package/dist/m/{c-ZS4US2XV.js → c-UXEVJ63W.js} +1 -1
- package/dist/m/{c-KJJPV77R.js → c-V2J5C5MT.js} +1 -1
- package/dist/m/{c-7PYNAULW.js → c-VBLFAQD2.js} +1 -1
- package/dist/m/{c-ERQSNR6K.js → c-VQCEOGKH.js} +2 -2
- package/dist/m/{c-YVZ2L7CE.js → c-VSPAFOKU.js} +1 -1
- package/dist/m/{c-L6FLIUXP.js → c-VY6XRBOW.js} +1 -1
- package/dist/m/{c-2KL3BD52.js → c-W23KVGUO.js} +1 -1
- package/dist/m/{c-AXGY2WTB.js → c-W3K23S72.js} +1 -1
- package/dist/m/c-WFMRX5RZ.js +1 -0
- package/dist/m/{c-BWFVSBDF.js → c-WN3MXOLT.js} +1 -1
- package/dist/m/{c-MIKZEG5I.js → c-XD5MOROJ.js} +1 -1
- package/dist/m/{c-CQQDL6O4.js → c-XQSXKIKM.js} +1 -1
- package/dist/m/{c-I4MQHOEY.js → c-Y33LUUIQ.js} +1 -1
- package/dist/m/{c-JDARJDOT.js → c-YQUJ74R3.js} +1 -1
- package/dist/m/{c-F6G2OGYG.js → c-YSB6JKV2.js} +2 -2
- package/dist/m/{c-BHZYSHW4.js → c-ZJLU56DV.js} +1 -1
- package/dist/m/{c-DKLS74XB.js → c-ZQXNHZZT.js} +2 -2
- package/dist/m/{c-TNEM3RNM.js → c-ZSCBEB7I.js} +1 -1
- package/dist/m/core/{admin-attrs-YY7T5FGU.js → admin-attrs-ZG7R5V3E.js} +1 -1
- package/dist/m/core/{autosave-RVJPYLRT.js → autosave-VPORYJSE.js} +1 -1
- package/dist/m/core/{conflict-notice-2WGT52LH.js → conflict-notice-JMAZLVRX.js} +1 -1
- package/dist/m/core/{persist-UKXV5KLA.js → persist-3OPWJKZS.js} +1 -1
- package/dist/m/core/{recovery-ZOUOJVIX.js → recovery-DRFZRJED.js} +1 -1
- package/dist/m/core/{save-A5JXV6ME.js → save-AOJBDSOK.js} +1 -1
- package/dist/m/core/{save-core-MKRPSEEO.js → save-core-IOCMXO5L.js} +1 -1
- package/dist/m/core/{snapshot-NU3YM6GR.js → snapshot-O7II2AGG.js} +1 -1
- package/dist/m/core/unsaved-warning-U5VUJFOE.js +1 -0
- package/dist/m/events/{index-RN2JNGBV.js → index-B6TGDYMG.js} +1 -1
- package/dist/m/internals/{index-TGLQINN3.js → index-HDRANDWE.js} +1 -1
- package/dist/m/lib/{cache-bust-MUMFXZK6.js → cache-bust-NH7VBWHO.js} +1 -1
- package/dist/m/lib/{region-policy-SB2AWVG5.js → region-policy-KWBPROWF.js} +1 -1
- package/dist/m/{loader-34YPFKOH.js → loader-ZVPK4ZN3.js} +1 -1
- package/dist/m/manifest-5498AFD3.js +1 -0
- package/dist/m/manifest-edit-62A5A00F.js +1 -0
- package/dist/m/manifest.json +1215 -1171
- package/dist/m/options/{options-EJMS3K24.js → options-55LQHSJ4.js} +1 -1
- package/dist/m/page/{editor-WRQGHDSG.js → editor-EY2WAAGB.js} +2 -2
- package/dist/m/plugins/{ai-edit-X2L2KIEP.js → ai-edit-J5VLSW5I.js} +1 -1
- package/dist/m/plugins/{demo-OYO5GCAO.js → demo-42BSYQ7T.js} +1 -1
- package/dist/m/plugins/{derive-FRE5JJQE.js → derive-QVI3LI3A.js} +1 -1
- package/dist/m/plugins/{live-7C36XEI2.js → live-5EZN2EYP.js} +1 -1
- package/dist/m/plugins/menu-DM2JHPSR.js +1 -0
- package/dist/m/plugins/{page-BSW6MGDO.js → page-LVW5DWA4.js} +2 -2
- package/dist/m/plugins/{page-view-WFZVTPQK.js → page-view-5KQ76IJB.js} +1 -1
- package/dist/m/plugins/{sortable-4S6SQC3D.js → sortable-WJZZBM7R.js} +1 -1
- package/dist/m/plugins/{source-TJXOIOYY.js → source-CCEUM3OR.js} +1 -1
- package/dist/m/plugins/{undo-NLPGCJQY.js → undo-3JEYK5QY.js} +1 -1
- package/dist/m/plugins/{upgrade-HSUNDNJD.js → upgrade-NW556WJD.js} +1 -1
- package/dist/m/plugins/{upload-NHGJXZK2.js → upload-TTCQRIM5.js} +1 -1
- package/dist/m/plugins/{visitor-drafts-BW43DGY3.js → visitor-drafts-2AWNT23K.js} +2 -2
- package/dist/m/plugins/{visitor-drafts-owner-7ODQG7JX.js → visitor-drafts-owner-MZP45XSG.js} +1 -1
- package/dist/m/plugins/{wire-6OSIL3I7.js → wire-VRYPY6EJ.js} +1 -1
- package/dist/m/{reader-core-PLZYLYLT.js → reader-core-2GXY7SHY.js} +2 -2
- package/dist/m/sync/{live-sync-UXNI6ZF6.js → live-sync-SKPMFYOO.js} +1 -1
- package/dist/m/ui/{index-IVWN2QC4.js → index-QNIPDHXQ.js} +1 -1
- package/dist/m/vendor/{sap-core.vendor-RN4ZVY4X.js → sap-core.vendor-OA5SKL7Q.js} +1 -1
- package/dist/offline-manifest.json +650 -626
- package/docs/guide.md +38 -14
- package/entries/sap.js +1 -1
- package/package.json +3 -2
- package/src/core/conflict-download.js +8 -7
- package/src/core/conflict-notice-ui.js +4 -4
- package/src/core/copy-content.js +103 -0
- package/src/core/download.js +14 -2
- package/src/core/save-size-notice.js +2 -2
- package/src/core/upgrade-run.js +5 -1
- package/src/lib/region-policy.js +1 -1
- package/src/page/features/export-html.js +4 -4
- package/src/page/features/export-markdown.js +2 -2
- package/src/page/recovery-ui.js +2 -2
- package/src/page/save-states.js +4 -4
- package/src/plugins/menu/actions.js +1 -1
- package/src/plugins/menu/style.js +59 -31
- package/src/plugins/menu.js +131 -28
- package/src/plugins/page.js +4 -4
- package/src/plugins/visitor-drafts/prompt.js +12 -12
- package/src/plugins/wire.js +3 -1
- package/src/vendor/sap-core.vendor.js +1 -1
- package/dist/m/attrs/save-freeze-LWGF6XUN.js +0 -1
- package/dist/m/c-DCFSEJZG.js +0 -1
- package/dist/m/c-EXEDYU4H.js +0 -1
- package/dist/m/c-GRXHHFPO.js +0 -1
- package/dist/m/c-HT2DKBV2.js +0 -1
- package/dist/m/c-MCOOHCSG.js +0 -1
- package/dist/m/c-MMXKAX2W.js +0 -1
- package/dist/m/c-VE7RKNKZ.js +0 -7
- package/dist/m/c-VR2BJ4AN.js +0 -1
- package/dist/m/c-WGW764WX.js +0 -1
- package/dist/m/c-WNTYKCDG.js +0 -1
- package/dist/m/c-YBSS55KU.js +0 -20
- package/dist/m/core/unsaved-warning-4ZPEV7BF.js +0 -1
- package/dist/m/manifest-0D166F4F.js +0 -1
- package/dist/m/manifest-edit-3877153E.js +0 -1
- package/dist/m/plugins/menu-MFC4DI5E.js +0 -1
- /package/dist/m/{c-QTTJYJMA.js → c-FILLTJA7.js} +0 -0
- /package/dist/m/{c-5JLFROUB.js → c-X5LNHET5.js} +0 -0
- /package/dist/m/plugins/{people-JCHVPF7P.js → people-C7TVBBMR.js} +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Copy-only content: marked regions that stay in the file, in normal saves, in snapshots
|
|
2
|
+
// and in recovery storage, and are removed only from the bytes of a copy someone asks for.
|
|
3
|
+
// The removal is a splice on the original string using parse5's source offsets, so every
|
|
4
|
+
// byte outside a removed range survives, templates and implicit end tags included.
|
|
5
|
+
|
|
6
|
+
import { parse } from "../vendor/parse5.vendor.js";
|
|
7
|
+
|
|
8
|
+
const CLAY = "clay";
|
|
9
|
+
const PAID_ONLY = "data-paid-only";
|
|
10
|
+
const NO_COPY = "no-copy";
|
|
11
|
+
const CONDITIONAL_FORMS = "no-copy-unless:paid-forms";
|
|
12
|
+
const CLAY_TOKENS = /[\t\n\f\r ]+/;
|
|
13
|
+
|
|
14
|
+
function attributeOf(node, name) {
|
|
15
|
+
return node.attrs?.find((attr) => attr.name === name)?.value ?? null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function childrenOf(node) {
|
|
19
|
+
return node.content ? [...(node.childNodes || []), node.content] : (node.childNodes || []);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function eachElement(node, visit) {
|
|
23
|
+
visit(node);
|
|
24
|
+
for (const child of childrenOf(node)) eachElement(child, visit);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function clayTokensOf(node) {
|
|
28
|
+
return (attributeOf(node, CLAY) ?? "").split(CLAY_TOKENS);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The marker is compared to a feature key exactly: no trim, no lowercase, so a typo, an
|
|
32
|
+
// empty value or `FORMS` strips nothing and the content stays, which is the direction
|
|
33
|
+
// that fails closed. `clay="no-copy-unless:paid-forms"` is the approved spelling of the
|
|
34
|
+
// same condition.
|
|
35
|
+
function paidMarkersOf(node) {
|
|
36
|
+
const markers = [];
|
|
37
|
+
const value = attributeOf(node, PAID_ONLY);
|
|
38
|
+
if (value !== null) markers.push(value);
|
|
39
|
+
if (clayTokensOf(node).includes(CONDITIONAL_FORMS)) markers.push("forms");
|
|
40
|
+
return [...new Set(markers)];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function lastDescendantEnd(node) {
|
|
44
|
+
let end = null;
|
|
45
|
+
eachElement(node, (entry) => {
|
|
46
|
+
const entryEnd = entry.sourceCodeLocation?.endOffset;
|
|
47
|
+
if (typeof entryEnd === "number" && entryEnd >= 0 && (end === null || entryEnd > end)) end = entryEnd;
|
|
48
|
+
});
|
|
49
|
+
return end;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function endOffsetFor(node) {
|
|
53
|
+
const location = node.sourceCodeLocation;
|
|
54
|
+
if (typeof location?.endOffset === "number" && location.endOffset >= 0) return location.endOffset;
|
|
55
|
+
const fallback = lastDescendantEnd(node) ?? location?.startTag?.endOffset ?? null;
|
|
56
|
+
if (fallback === null) return null;
|
|
57
|
+
console.warn(`clayjs: <${node.tagName}> has no end offset; removing through ${fallback}`);
|
|
58
|
+
return fallback;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function markedRanges(html, features) {
|
|
62
|
+
const ranges = [];
|
|
63
|
+
eachElement(parse(html, { sourceCodeLocationInfo: true }), (node) => {
|
|
64
|
+
const markers = paidMarkersOf(node);
|
|
65
|
+
const unconditional = clayTokensOf(node).includes(NO_COPY);
|
|
66
|
+
const conditional = features === null ? markers.length > 0 : markers.some((marker) => features.includes(marker));
|
|
67
|
+
if (!unconditional && !conditional) return;
|
|
68
|
+
const start = node.sourceCodeLocation?.startOffset;
|
|
69
|
+
const end = typeof start === "number" ? endOffsetFor(node) : null;
|
|
70
|
+
if (typeof start === "number" && start >= 0 && end !== null && end > start) ranges.push({ start, end });
|
|
71
|
+
});
|
|
72
|
+
return ranges.filter((range, index) => !ranges.some((other, otherIndex) =>
|
|
73
|
+
otherIndex !== index && other.start <= range.start && range.end <= other.end));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function leadingRunStart(html, start) {
|
|
77
|
+
const newline = html.lastIndexOf("\n", start - 1);
|
|
78
|
+
if (newline < 0 || /\S/.test(html.slice(newline + 1, start))) return start;
|
|
79
|
+
return html[newline - 1] === "\r" ? newline - 1 : newline;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function spliceRanges(html, ranges) {
|
|
83
|
+
const sorted = [...ranges].sort((a, b) => a.start - b.start);
|
|
84
|
+
let stripped = html;
|
|
85
|
+
let limit = html.length;
|
|
86
|
+
for (let index = sorted.length - 1; index >= 0; index -= 1) {
|
|
87
|
+
const { start, end } = sorted[index];
|
|
88
|
+
if (end > limit) continue;
|
|
89
|
+
const from = leadingRunStart(html, start);
|
|
90
|
+
if (from >= end) continue;
|
|
91
|
+
stripped = stripped.slice(0, from) + stripped.slice(end);
|
|
92
|
+
limit = from;
|
|
93
|
+
}
|
|
94
|
+
return stripped;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// `features` decides the conditional regions: null (the default, and the offline
|
|
98
|
+
// destination) strips every one of them, while a list strips only the missing features
|
|
99
|
+
// it names. Unconditional `no-copy` content always goes.
|
|
100
|
+
export function stripCopyContent(html, features = null) {
|
|
101
|
+
if (typeof html !== "string" || !/clay|data-paid-only/i.test(html)) return html;
|
|
102
|
+
return spliceRanges(html, markedRanges(html, features));
|
|
103
|
+
}
|
package/src/core/download.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
// Saving a page as a file the browser downloads. Kept apart from the conflict copy
|
|
2
2
|
// builders so an editor that only offers "download this page" loads none of them.
|
|
3
3
|
|
|
4
|
+
const HTML_TYPES = ["text/html", "application/xhtml+xml"];
|
|
5
|
+
|
|
6
|
+
function isHtml(type) {
|
|
7
|
+
if (typeof type !== "string") return false;
|
|
8
|
+
return HTML_TYPES.includes(type.split(";")[0].trim().toLowerCase());
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
export function documentName(pathname = location.pathname) {
|
|
5
12
|
const last = pathname.split("/").filter(Boolean).pop();
|
|
6
13
|
if (!last) return pathname.endsWith("/") || pathname === "" ? "index" : "page";
|
|
@@ -9,10 +16,15 @@ export function documentName(pathname = location.pathname) {
|
|
|
9
16
|
return name.replace(/\.(html?|htmlclay)$/i, "") || "page";
|
|
10
17
|
}
|
|
11
18
|
|
|
12
|
-
export function downloadHtml({ html, name, type = "text/html" }) {
|
|
19
|
+
export async function downloadHtml({ html, name, type = "text/html" }) {
|
|
13
20
|
let url = null;
|
|
14
21
|
try {
|
|
15
|
-
|
|
22
|
+
let contents = html;
|
|
23
|
+
if (isHtml(type) && typeof html === "string" && /clay|data-paid-only/i.test(html)) {
|
|
24
|
+
const { stripCopyContent } = await import("./copy-content.js");
|
|
25
|
+
contents = stripCopyContent(html);
|
|
26
|
+
}
|
|
27
|
+
url = URL.createObjectURL(new Blob([contents], { type }));
|
|
16
28
|
const a = document.createElement("a");
|
|
17
29
|
a.setAttribute("clay", "no-save no-watch no-snapshot");
|
|
18
30
|
a.href = url;
|
|
@@ -57,8 +57,8 @@ function place() {
|
|
|
57
57
|
// The safe copy the conflict notice offers, for the same reason: a file the person can
|
|
58
58
|
// open has to exist outside a host that will not store these bytes. It already reports
|
|
59
59
|
// its own failure.
|
|
60
|
-
function download() {
|
|
61
|
-
downloadStored({});
|
|
60
|
+
async function download() {
|
|
61
|
+
await downloadStored({});
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function build() {
|
package/src/core/upgrade-run.js
CHANGED
|
@@ -348,7 +348,11 @@ export async function pageBytes(clay, { loc = location, fetchFn = (url, init) =>
|
|
|
348
348
|
export async function downloadCopy({ clay, doc = document, loc = location, fetchFn } = {}) {
|
|
349
349
|
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return false;
|
|
350
350
|
try {
|
|
351
|
-
|
|
351
|
+
let bytes = await pageBytes(clay, { loc, fetchFn });
|
|
352
|
+
if (typeof bytes === "string" && /clay|data-paid-only/i.test(bytes)) {
|
|
353
|
+
const { stripCopyContent } = await import("./copy-content.js");
|
|
354
|
+
bytes = stripCopyContent(bytes);
|
|
355
|
+
}
|
|
352
356
|
const url = URL.createObjectURL(new Blob([bytes], { type: "text/html" }));
|
|
353
357
|
const a = doc.createElement("a");
|
|
354
358
|
a.setAttribute("clay", "editor-ui");
|
package/src/lib/region-policy.js
CHANGED
|
@@ -54,7 +54,7 @@ export const REGION_ATTRS = ['no-save', 'no-trigger-autosave', 'no-dirty', 'no-u
|
|
|
54
54
|
// Every canonical token spellable inside the space-separated `clay` attribute.
|
|
55
55
|
// Exported: this list used to exist as a private CLAY_TOKENS that nothing read,
|
|
56
56
|
// while callers hardcoded their own copies.
|
|
57
|
-
export const TOKENS = [...POLICY_TOKENS];
|
|
57
|
+
export const TOKENS = [...POLICY_TOKENS, 'no-copy', 'no-copy-unless:paid-forms'];
|
|
58
58
|
|
|
59
59
|
// True when a region marker is present, whether spelled as a `clay` token
|
|
60
60
|
// (whitespace-token semantics, matching [clay~=token]) or a legacy bare attribute.
|
|
@@ -112,12 +112,12 @@ export function createFeature({ view = null, download = downloadHtml, fetch: fet
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
function decide(s) {
|
|
115
|
-
listen(s, s.panel, "click", (event) => {
|
|
115
|
+
listen(s, s.panel, "click", async (event) => {
|
|
116
116
|
const b = event.target.closest?.("[data-act]");
|
|
117
117
|
if (!b) return;
|
|
118
118
|
event.preventDefault();
|
|
119
119
|
if (b.dataset.act === "close") close();
|
|
120
|
-
else if (b.dataset.act === "download") save(s);
|
|
120
|
+
else if (b.dataset.act === "download") await save(s);
|
|
121
121
|
else if (b.dataset.act === "retry") run().catch((error) => console.error("clayjs: the reading copy could not be made", error));
|
|
122
122
|
});
|
|
123
123
|
listen(s, doc, "keydown", (event) => {
|
|
@@ -130,9 +130,9 @@ export function createFeature({ view = null, download = downloadHtml, fetch: fet
|
|
|
130
130
|
|
|
131
131
|
// The one place a reading copy becomes a file. The download is handed the bytes that
|
|
132
132
|
// were shown, never a second render, so what a person saw is what they get.
|
|
133
|
-
function save(s) {
|
|
133
|
+
async function save(s) {
|
|
134
134
|
if (!s.ready || disposed) return { ok: false, code: "not-editing" };
|
|
135
|
-
const saved = download({ html: s.ready.html, name: s.ready.name });
|
|
135
|
+
const saved = await download({ html: s.ready.html, name: s.ready.name });
|
|
136
136
|
close();
|
|
137
137
|
return { ok: saved === true, downloaded: saved === true };
|
|
138
138
|
}
|
|
@@ -83,10 +83,10 @@ export function createFeature({ view = null, download = downloadHtml } = {}) {
|
|
|
83
83
|
s.panel.replaceChildren(...parts);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function save() {
|
|
86
|
+
async function save() {
|
|
87
87
|
const s = session;
|
|
88
88
|
if (!s) return { ok: false, code: "no-export" };
|
|
89
|
-
if (download({ html: s.report.text, name: s.name, type: "text/markdown" })) {
|
|
89
|
+
if (await download({ html: s.report.text, name: s.name, type: "text/markdown" })) {
|
|
90
90
|
close();
|
|
91
91
|
view.focus?.();
|
|
92
92
|
return { ok: true, downloaded: true };
|
package/src/page/recovery-ui.js
CHANGED
|
@@ -228,12 +228,12 @@ export function createRecoveryReview({
|
|
|
228
228
|
button.setAttribute("aria-expanded", "false");
|
|
229
229
|
};
|
|
230
230
|
|
|
231
|
-
const download = () => {
|
|
231
|
+
const download = async () => {
|
|
232
232
|
const edits = [];
|
|
233
233
|
for (const row of rows) edits.push({ id: row.id, kind: "recovered-page", name: row.message, yours: row.text, now: "", sentence: row.message });
|
|
234
234
|
const composing = "Text typed while composing never reached the page.";
|
|
235
235
|
for (const row of journaled) edits.push({ id: row.id, kind: "composing", name: "Typed text", yours: row.text, now: "", sentence: composing });
|
|
236
|
-
if (!downloadStored({ html: null, edits })) failure = "That download could not be made.";
|
|
236
|
+
if (!(await downloadStored({ html: null, edits }))) failure = "That download could not be made.";
|
|
237
237
|
draw();
|
|
238
238
|
};
|
|
239
239
|
|
package/src/page/save-states.js
CHANGED
|
@@ -67,14 +67,14 @@ function withoutPageTag(html) {
|
|
|
67
67
|
// One complete, openable file: the capture the save would have sent, with the
|
|
68
68
|
// page projection already applied by the live store's snapshot listener, and
|
|
69
69
|
// without the page editor's merge state, which no saved file or download carries.
|
|
70
|
-
function downloadPageCopy() {
|
|
70
|
+
async function downloadPageCopy() {
|
|
71
71
|
const { forSave } = captureForSaveAndComparison({ emitForSync: false });
|
|
72
|
-
return downloadHtml({ html: withoutPageTag(forSave), name: `${documentName()}.html` });
|
|
72
|
+
return await downloadHtml({ html: withoutPageTag(forSave), name: `${documentName()}.html` });
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// A copy of the page captured earlier (captureForSaveAndComparison().forSave), as a download.
|
|
76
|
-
function downloadSnapshot(forSave) {
|
|
77
|
-
return downloadHtml({ html: withoutPageTag(forSave), name: `${documentName()}.html` });
|
|
76
|
+
async function downloadSnapshot(forSave) {
|
|
77
|
+
return await downloadHtml({ html: withoutPageTag(forSave), name: `${documentName()}.html` });
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// The article's own plain text, never the editor's chrome: the capture carries
|
|
@@ -146,7 +146,7 @@ export function attachActions({ addItem, note, currentMode }) {
|
|
|
146
146
|
if (typeof html === "string" && /^https?:$/.test(location.protocol)) {
|
|
147
147
|
html = html.replace(/(^|[^\w./:-])\/_\/uploads\//g, `$1${location.origin}/_/uploads/`);
|
|
148
148
|
}
|
|
149
|
-
if (typeof html !== "string" || !downloadHtml({ html, name: `${documentName()}.html` })) {
|
|
149
|
+
if (typeof html !== "string" || !(await downloadHtml({ html, name: `${documentName()}.html` }))) {
|
|
150
150
|
note("Couldn't build the download. Try again.");
|
|
151
151
|
}
|
|
152
152
|
},
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
// The surface and muted ink below, per scheme, for ui/page-colour.js's tint.
|
|
4
4
|
export const MENU_SURFACE = { light: [0xFF, 0xFC, 0xF6], dark: [0x1A, 0x1D, 0x2C], mutedLight: [0x6B, 0x59, 0x42], mutedDark: [0xA3, 0xA9, 0xC2] };
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// The parts of the look a page may replace, through <style clay-menu-skin="1">. Everything
|
|
7
|
+
// else below stays: layout, hidden, focus rings, touch targets and reduced motion.
|
|
8
|
+
export const MENU_BASE = `.clay-menu-dock, .clay-menu-spots, .clay-seo {
|
|
7
9
|
--bevel-font-sans: system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
8
10
|
--bevel-font-serif: Newsreader, Georgia, "Times New Roman", serif;
|
|
9
11
|
--bevel-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
@@ -40,19 +42,11 @@ export default `.clay-menu-dock, .clay-menu-spots, .clay-seo {
|
|
|
40
42
|
text-size-adjust: 100%;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
.clay-menu-dock { position:
|
|
45
|
-
.clay-menu-dock[data-corner$="left"] { right: auto; left: max(12px, env(safe-area-inset-left)); flex-direction: row-reverse; }
|
|
46
|
-
.clay-menu-dock[data-corner^="bottom"] { top: auto; bottom: max(12px, env(safe-area-inset-bottom)); }
|
|
45
|
+
.clay-menu-dock { display: flex; align-items: center; gap: 6px; font-size: 13px; line-height: 1.4; }
|
|
46
|
+
.clay-menu-dock[data-embedded] { position: relative; display: inline-flex; }
|
|
47
47
|
.clay-menu-dock[hidden], .clay-menu-spots[hidden], .clay-menu-dock [hidden], .clay-seo [hidden] { display: none !important; }
|
|
48
48
|
|
|
49
|
-
.clay-menu { box-sizing: border-box; display: inline-flex; align-items: center; gap: 2px;
|
|
50
|
-
.clay-menu-dock[data-dragging] .clay-menu { border-color: var(--bevel-brass); cursor: grabbing; }
|
|
51
|
-
|
|
52
|
-
.clay-menu-grip { position: relative; display: inline-grid; place-items: center; width: 18px; height: 28px; margin: 0; padding: 0; border: 0; background: none; color: var(--bevel-faint); cursor: grab; touch-action: none; }
|
|
53
|
-
.clay-menu-grip::before { content: ""; position: absolute; inset: -8px -2px -8px -10px; }
|
|
54
|
-
.clay-menu-grip:hover { color: var(--bevel-ink-2); }
|
|
55
|
-
.clay-menu-grip svg { display: block; width: 6px; height: 12px; fill: currentColor; }
|
|
49
|
+
.clay-menu { box-sizing: border-box; display: inline-flex; align-items: center; gap: 2px; user-select: none; -webkit-user-select: none; }
|
|
56
50
|
|
|
57
51
|
.clay-menu-edit { position: relative; display: inline-flex; align-items: center; gap: 8px; height: 28px; margin: 0; padding: 0 8px 0 5px; border: 0; background: none; font: 500 13px/1 var(--bevel-font-sans); color: var(--bevel-ink); cursor: pointer; }
|
|
58
52
|
.clay-menu-edit::before { content: ""; position: absolute; inset: -8px 0; }
|
|
@@ -68,12 +62,8 @@ export default `.clay-menu-dock, .clay-menu-spots, .clay-seo {
|
|
|
68
62
|
|
|
69
63
|
.clay-menu-divider { width: 1px; height: 18px; margin: 0 2px; background: var(--bevel-line-2); }
|
|
70
64
|
|
|
71
|
-
.clay-menu-goo { position: relative; display: inline-grid; place-items: center; box-sizing: border-box;
|
|
72
|
-
.clay-menu-goo
|
|
73
|
-
.clay-menu-goo svg { display: block; width: 18px; height: auto; fill: currentColor; }
|
|
74
|
-
.clay-menu-goo:hover svg { translate: 0 -1px; }
|
|
75
|
-
.clay-menu-goo:active, .clay-menu-goo[aria-expanded="true"] { background: var(--bevel-face-in); border-color: var(--bevel-edge-in) var(--bevel-edge-in-hi) var(--bevel-edge-in-hi) var(--bevel-edge-in); }
|
|
76
|
-
.clay-menu-goo:active svg, .clay-menu-goo[aria-expanded="true"] svg { translate: 1px 1px; }
|
|
65
|
+
.clay-menu-goo { position: relative; display: inline-grid; place-items: center; box-sizing: border-box; margin: 0; padding: 0; cursor: pointer; }
|
|
66
|
+
.clay-menu-goo svg { display: block; }
|
|
77
67
|
.clay-menu-dot { position: absolute; top: -1px; right: -2px; width: 8px; height: 8px; border-radius: 50%; background: var(--bevel-brass); box-shadow: 0 0 0 2px var(--bevel-surface); }
|
|
78
68
|
|
|
79
69
|
.clay-menu :focus-visible, .clay-roster:focus-visible { outline: 2px solid var(--bevel-brass); outline-offset: 2px; }
|
|
@@ -98,22 +88,24 @@ export default `.clay-menu-dock, .clay-menu-spots, .clay-seo {
|
|
|
98
88
|
.clay-avatar[data-tone="teal"] { background: var(--bevel-teal-soft); color: var(--bevel-teal); border-color: color-mix(in srgb, var(--bevel-teal) 35%, var(--bevel-teal-soft)); }
|
|
99
89
|
|
|
100
90
|
/* Popovers: the menu, the people panel, the status note. Below the dock, above it in a bottom corner. */
|
|
101
|
-
.clay-menu-list, .clay-people, .clay-menu-note { position: absolute; z-index: 1; top: calc(100% + 8px); right: 0; box-sizing: border-box; margin: 0;
|
|
102
|
-
.clay-
|
|
103
|
-
.clay-menu-dock[data-corner^="bottom"] :is(.clay-menu-list, .clay-people, .clay-menu-note) { top: auto; bottom: calc(100% + 8px); }
|
|
91
|
+
.clay-menu-list, .clay-people, .clay-menu-note { position: absolute; z-index: 1; top: calc(100% + 8px); right: 0; box-sizing: border-box; margin: 0; color: var(--bevel-ink); text-align: left; }
|
|
92
|
+
.clay-people, .clay-menu-note { border: 1px solid var(--bevel-line-2); background: var(--bevel-surface); box-shadow: var(--clay-shadow); }
|
|
104
93
|
|
|
105
|
-
.clay-menu-list { width: max-content;
|
|
106
|
-
.clay-menu-list
|
|
94
|
+
.clay-menu-list { width: max-content; max-width: calc(100vw - 24px); max-height: calc(100dvh - 72px); overflow-y: auto; overscroll-behavior: contain; }
|
|
95
|
+
.clay-menu-list[popover] { position: fixed; inset: auto; margin: 0; }
|
|
96
|
+
.clay-menu-list [role="menuitem"] { box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; margin: 0; border: 0; background: none; text-align: left; text-decoration: none; cursor: pointer; }
|
|
107
97
|
.clay-menu-list [role="menuitem"]:hover:not([aria-disabled="true"]), .clay-menu-list [role="menuitem"]:focus-visible { outline: none; background: var(--bevel-sunk); }
|
|
108
|
-
.clay-menu-list [role="menuitem"][aria-disabled="true"] {
|
|
109
|
-
.clay-menu-list [role="menuitem"][aria-disabled="true"] kbd { opacity: .6; }
|
|
98
|
+
.clay-menu-list [role="menuitem"][aria-disabled="true"] { cursor: default; }
|
|
110
99
|
.clay-menu-pair { display: flex; gap: 12px; padding: 3px 9px; }
|
|
111
|
-
.clay-menu-list .clay-menu-pair [role="menuitem"] { flex: none; width: auto;
|
|
112
|
-
.clay-menu-list kbd { flex: none;
|
|
113
|
-
.clay-menu-
|
|
100
|
+
.clay-menu-list .clay-menu-pair [role="menuitem"] { flex: none; width: auto; justify-content: flex-start; gap: 9px; }
|
|
101
|
+
.clay-menu-list kbd { flex: none; white-space: nowrap; }
|
|
102
|
+
.clay-menu-row { display: flex; align-items: center; gap: 2px; }
|
|
103
|
+
.clay-menu-list .clay-menu-row > [part~="item"] { flex: 1; width: auto; min-width: 0; }
|
|
104
|
+
.clay-menu-list .clay-menu-row > [part~="dismiss"] { flex: none; width: 28px; justify-content: center; padding: 0; }
|
|
105
|
+
.clay-menu-row > [part~="dismiss"] svg { display: block; width: 10px; height: 10px; fill: currentColor; }
|
|
114
106
|
.clay-menu-upgrade { display: inline-flex; align-items: center; gap: 8px; }
|
|
115
107
|
.clay-menu-upgrade::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--bevel-brass); }
|
|
116
|
-
.clay-menu-version { margin: 0;
|
|
108
|
+
.clay-menu-version { margin: 0; }
|
|
117
109
|
|
|
118
110
|
.clay-menu-note { z-index: 2; width: max-content; max-width: 264px; padding: 8px 12px; font-size: 13px; line-height: 1.45; }
|
|
119
111
|
|
|
@@ -149,6 +141,19 @@ export default `.clay-menu-dock, .clay-menu-spots, .clay-seo {
|
|
|
149
141
|
.clay-menu-btn-small { height: 26px; padding: 0 9px; font-size: 12px; }
|
|
150
142
|
.clay-menu-btn-primary { --bevel-face: var(--bevel-ink); --bevel-edge-hi: color-mix(in srgb, var(--bevel-ink), light-dark(#FFFCF6, #FFFFFF) 22%); --bevel-edge-lo: color-mix(in srgb, var(--bevel-ink), light-dark(#0B0C13, #6A7090) 34%); color: var(--bevel-ground); }
|
|
151
143
|
.clay-menu-btn-primary:hover { background: color-mix(in srgb, var(--bevel-ink), var(--bevel-ground) 12%); }
|
|
144
|
+
`;
|
|
145
|
+
|
|
146
|
+
export const MENU_FLOATING = `/* The dock: the roster (when a page opts in) and the capsule, fixed in one corner. */
|
|
147
|
+
.clay-menu-dock { position: fixed; z-index: 2147482000; top: max(12px, env(safe-area-inset-top)); right: max(12px, env(safe-area-inset-right)); }
|
|
148
|
+
.clay-menu-dock[data-corner$="left"] { right: auto; left: max(12px, env(safe-area-inset-left)); flex-direction: row-reverse; }
|
|
149
|
+
.clay-menu-dock[data-corner^="bottom"] { top: auto; bottom: max(12px, env(safe-area-inset-bottom)); }
|
|
150
|
+
.clay-menu-dock[data-corner$="left"] :is(.clay-menu-list, .clay-people, .clay-menu-note) { right: auto; left: 0; }
|
|
151
|
+
.clay-menu-dock[data-corner^="bottom"] :is(.clay-menu-list, .clay-people, .clay-menu-note) { top: auto; bottom: calc(100% + 8px); }
|
|
152
|
+
.clay-menu-dock[data-dragging] .clay-menu { border-color: var(--bevel-brass); cursor: grabbing; }
|
|
153
|
+
.clay-menu-grip { position: relative; display: inline-grid; place-items: center; width: 18px; height: 28px; margin: 0; padding: 0; border: 0; background: none; color: var(--bevel-faint); cursor: grab; touch-action: none; }
|
|
154
|
+
.clay-menu-grip::before { content: ""; position: absolute; inset: -8px -2px -8px -10px; }
|
|
155
|
+
.clay-menu-grip:hover { color: var(--bevel-ink-2); }
|
|
156
|
+
.clay-menu-grip svg { display: block; width: 6px; height: 12px; fill: currentColor; }
|
|
152
157
|
|
|
153
158
|
/* Drop spots while dragging. */
|
|
154
159
|
.clay-menu-spots { position: fixed; inset: 0; z-index: 2147481999; pointer-events: none; }
|
|
@@ -158,8 +163,28 @@ export default `.clay-menu-dock, .clay-menu-spots, .clay-seo {
|
|
|
158
163
|
.clay-menu-spot[data-to$="left"] { left: 12px; }
|
|
159
164
|
.clay-menu-spot[data-to$="right"] { right: 12px; }
|
|
160
165
|
.clay-menu-spot[data-near] { border: 2px solid var(--bevel-brass); background: var(--bevel-brass-soft); }
|
|
166
|
+
`;
|
|
161
167
|
|
|
162
|
-
|
|
168
|
+
export const MENU_SKIN = `.clay-menu { height: 34px; padding: 0 4px 0 2px; border: 1px solid var(--bevel-line-3); background: var(--bevel-surface); box-shadow: var(--clay-shadow); }
|
|
169
|
+
.clay-menu-goo { width: 28px; height: 28px; min-height: 0; border-radius: 0; border: 2px solid transparent; background: none; color: var(--bevel-ink); }
|
|
170
|
+
.clay-menu-goo::before { content: ""; position: absolute; inset: -8px -8px -8px -4px; }
|
|
171
|
+
.clay-menu-goo svg { width: 18px; height: auto; fill: currentColor; }
|
|
172
|
+
.clay-menu-goo:hover svg { translate: 0 -1px; }
|
|
173
|
+
.clay-menu-goo:active, .clay-menu-goo[aria-expanded="true"] { background: var(--bevel-face-in); border-color: var(--bevel-edge-in) var(--bevel-edge-in-hi) var(--bevel-edge-in-hi) var(--bevel-edge-in); }
|
|
174
|
+
.clay-menu-goo:active svg, .clay-menu-goo[aria-expanded="true"] svg { translate: 1px 1px; }
|
|
175
|
+
.clay-menu-list { min-width: 284px; padding: 6px; border: 1px solid var(--bevel-line-2); background: var(--bevel-surface); box-shadow: var(--clay-shadow); }
|
|
176
|
+
.clay-menu-list [role="menuitem"] { min-height: 32px; padding: 5px 9px; font: 400 13px/1.35 var(--bevel-font-sans); color: var(--bevel-ink); }
|
|
177
|
+
.clay-menu-list [role="menuitem"][aria-disabled="true"] { color: var(--bevel-faint); }
|
|
178
|
+
.clay-menu-list [role="menuitem"][aria-disabled="true"] kbd { opacity: .6; }
|
|
179
|
+
.clay-menu-list .clay-menu-pair [role="menuitem"] { min-height: 30px; padding: 4px 6px 4px 9px; border: 1px solid var(--bevel-line); }
|
|
180
|
+
.clay-menu-list kbd { font: 500 11px/1 var(--bevel-font-mono); padding: 3px 5px; border: 1px solid var(--bevel-line-2); background: var(--bevel-sunk); color: var(--bevel-muted); }
|
|
181
|
+
.clay-menu-list [part~="quiet"] { font-size: 12px; color: var(--bevel-muted); }
|
|
182
|
+
.clay-menu-sep { height: 1px; margin: 4px 2px; background: var(--bevel-line); }
|
|
183
|
+
.clay-menu-version { padding: 6px 9px 4px; font: 400 11.5px/1.4 var(--bevel-font-mono); color: var(--bevel-muted); }
|
|
184
|
+
`;
|
|
185
|
+
|
|
186
|
+
// Last, so no skin can shrink a touch target or bring motion back.
|
|
187
|
+
export const MENU_TOUCH = `@media (pointer: coarse), (max-width: 680px) {
|
|
163
188
|
.clay-menu-list [role="menuitem"] { min-height: 44px; font-size: 15px; }
|
|
164
189
|
.clay-menu-list .clay-menu-pair [role="menuitem"] { min-height: 44px; }
|
|
165
190
|
:is(.clay-menu-dock, .clay-seo) :is(input, textarea, select) { font-size: 16px; }
|
|
@@ -168,4 +193,7 @@ export default `.clay-menu-dock, .clay-menu-spots, .clay-seo {
|
|
|
168
193
|
}
|
|
169
194
|
@media (prefers-reduced-motion: reduce) {
|
|
170
195
|
.clay-menu-track, .clay-menu-thumb { transition: none; }
|
|
171
|
-
}
|
|
196
|
+
}
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
export default MENU_BASE + MENU_FLOATING + MENU_SKIN + MENU_TOUCH;
|