@maizzle/framework 6.0.0-rc.18 → 6.0.0-rc.19
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/build.d.ts.map +1 -1
- package/dist/build.js +10 -6
- package/dist/build.js.map +1 -1
- package/dist/components/Button.vue +17 -39
- package/dist/components/Container.vue +6 -6
- package/dist/components/Hr.vue +20 -120
- package/dist/components/Html.vue +19 -1
- package/dist/components/NotPlaintext.vue +14 -0
- package/dist/components/{Vml.vue → OutlookBg.vue} +1 -1
- package/dist/components/Plaintext.vue +14 -0
- package/dist/components/QrCode.vue +157 -0
- package/dist/components/Spacer.vue +28 -27
- package/dist/components/utils.js +1 -1
- package/dist/components/utils.js.map +1 -1
- package/dist/composables/defineConfig.js +1 -2
- package/dist/composables/defineConfig.js.map +1 -1
- package/dist/composables/renderContext.js +1 -1
- package/dist/composables/useBaseUrl.js +3 -4
- package/dist/composables/useBaseUrl.js.map +1 -1
- package/dist/composables/useConfig.js +1 -2
- package/dist/composables/useConfig.js.map +1 -1
- package/dist/composables/useDoctype.js +1 -2
- package/dist/composables/useDoctype.js.map +1 -1
- package/dist/composables/useEvent.js +1 -2
- package/dist/composables/useEvent.js.map +1 -1
- package/dist/composables/useFont.js +1 -2
- package/dist/composables/useFont.js.map +1 -1
- package/dist/composables/useOutlookFallback.js +1 -2
- package/dist/composables/useOutlookFallback.js.map +1 -1
- package/dist/composables/usePlaintext.d.ts +2 -0
- package/dist/composables/usePlaintext.d.ts.map +1 -1
- package/dist/composables/usePlaintext.js +2 -2
- package/dist/composables/usePlaintext.js.map +1 -1
- package/dist/composables/usePreheader.js +1 -2
- package/dist/composables/usePreheader.js.map +1 -1
- package/dist/composables/useTransformers.d.ts +3 -3
- package/dist/composables/useTransformers.js +4 -5
- package/dist/composables/useTransformers.js.map +1 -1
- package/dist/composables/useUrlQuery.js +3 -4
- package/dist/composables/useUrlQuery.js.map +1 -1
- package/dist/config/defaults.js +1 -1
- package/dist/config/index.js +1 -2
- package/dist/config/index.js.map +1 -1
- package/dist/events/index.js +1 -1
- package/dist/events/index.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +4 -5
- package/dist/plaintext.js +3 -4
- package/dist/plaintext.js.map +1 -1
- package/dist/plugin.js +1 -2
- package/dist/plugin.js.map +1 -1
- package/dist/plugins/postcss/mergeMediaQueries.js +1 -2
- package/dist/plugins/postcss/mergeMediaQueries.js.map +1 -1
- package/dist/plugins/postcss/pruneVars.js +1 -1
- package/dist/plugins/postcss/pruneVars.js.map +1 -1
- package/dist/plugins/postcss/quoteFontFamilies.js +1 -1
- package/dist/plugins/postcss/quoteFontFamilies.js.map +1 -1
- package/dist/plugins/postcss/removeDeclarations.js +1 -1
- package/dist/plugins/postcss/removeDeclarations.js.map +1 -1
- package/dist/plugins/postcss/resolveMaizzleImports.js +1 -2
- package/dist/plugins/postcss/resolveMaizzleImports.js.map +1 -1
- package/dist/plugins/postcss/resolveProps.js +1 -1
- package/dist/plugins/postcss/resolveProps.js.map +1 -1
- package/dist/plugins/postcss/tailwindCleanup.js +1 -1
- package/dist/plugins/postcss/tailwindCleanup.js.map +1 -1
- package/dist/prepare.js +1 -2
- package/dist/prepare.js.map +1 -1
- package/dist/render/active.d.ts +8 -0
- package/dist/render/active.d.ts.map +1 -0
- package/dist/render/active.js +12 -0
- package/dist/render/active.js.map +1 -0
- package/dist/render/createRenderer.d.ts.map +1 -1
- package/dist/render/createRenderer.js +6 -9
- package/dist/render/createRenderer.js.map +1 -1
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +13 -6
- package/dist/render/index.js.map +1 -1
- package/dist/render/injectFonts.js +1 -2
- package/dist/render/injectFonts.js.map +1 -1
- package/dist/render/plugins/codeBlockExtract.js +1 -1
- package/dist/render/plugins/codeBlockExtract.js.map +1 -1
- package/dist/render/plugins/markdownExtract.js +1 -2
- package/dist/render/plugins/markdownExtract.js.map +1 -1
- package/dist/render/plugins/rawExtract.js +1 -1
- package/dist/render/plugins/rawExtract.js.map +1 -1
- package/dist/render/plugins/rowSourceLocation.js +1 -1
- package/dist/render/plugins/rowSourceLocation.js.map +1 -1
- package/dist/serve.d.ts +2 -0
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +12 -7
- package/dist/serve.js.map +1 -1
- package/dist/server/compatibility.js +1 -2
- package/dist/server/compatibility.js.map +1 -1
- package/dist/server/email.js +1 -2
- package/dist/server/email.js.map +1 -1
- package/dist/server/linter.js +1 -2
- package/dist/server/linter.js.map +1 -1
- package/dist/server/sfc-utils.js +1 -2
- package/dist/server/sfc-utils.js.map +1 -1
- package/dist/tests/render/_helpers.d.ts +6 -0
- package/dist/tests/render/_helpers.d.ts.map +1 -0
- package/dist/tests/render/_helpers.js +16 -0
- package/dist/tests/render/_helpers.js.map +1 -0
- package/dist/transformers/addAttributes.js +3 -4
- package/dist/transformers/addAttributes.js.map +1 -1
- package/dist/transformers/attributeToStyle.d.ts +27 -14
- package/dist/transformers/attributeToStyle.d.ts.map +1 -1
- package/dist/transformers/attributeToStyle.js +34 -20
- package/dist/transformers/attributeToStyle.js.map +1 -1
- package/dist/transformers/base.d.ts +66 -3
- package/dist/transformers/base.d.ts.map +1 -1
- package/dist/transformers/base.js +50 -24
- package/dist/transformers/base.js.map +1 -1
- package/dist/transformers/columnWidth.js +1 -2
- package/dist/transformers/columnWidth.js.map +1 -1
- package/dist/transformers/entities.d.ts +31 -2
- package/dist/transformers/entities.d.ts.map +1 -1
- package/dist/transformers/entities.js +39 -7
- package/dist/transformers/entities.js.map +1 -1
- package/dist/transformers/filters/defaults.js +1 -1
- package/dist/transformers/filters/defaults.js.map +1 -1
- package/dist/transformers/filters/index.d.ts +31 -10
- package/dist/transformers/filters/index.d.ts.map +1 -1
- package/dist/transformers/filters/index.js +36 -14
- package/dist/transformers/filters/index.js.map +1 -1
- package/dist/transformers/format.d.ts +14 -7
- package/dist/transformers/format.d.ts.map +1 -1
- package/dist/transformers/format.js +15 -11
- package/dist/transformers/format.js.map +1 -1
- package/dist/transformers/index.js +49 -29
- package/dist/transformers/index.js.map +1 -1
- package/dist/transformers/inlineCss.d.ts +84 -0
- package/dist/transformers/inlineCss.d.ts.map +1 -0
- package/dist/transformers/{inlineCSS.js → inlineCss.js} +24 -14
- package/dist/transformers/inlineCss.js.map +1 -0
- package/dist/transformers/inlineLink.d.ts +26 -5
- package/dist/transformers/inlineLink.d.ts.map +1 -1
- package/dist/transformers/inlineLink.js +31 -7
- package/dist/transformers/inlineLink.js.map +1 -1
- package/dist/transformers/minify.d.ts +13 -9
- package/dist/transformers/minify.d.ts.map +1 -1
- package/dist/transformers/minify.js +14 -13
- package/dist/transformers/minify.js.map +1 -1
- package/dist/transformers/msoPlaceholders.js +1 -2
- package/dist/transformers/msoPlaceholders.js.map +1 -1
- package/dist/transformers/purgeCss.d.ts +43 -0
- package/dist/transformers/purgeCss.d.ts.map +1 -0
- package/dist/transformers/{purgeCSS.js → purgeCss.js} +32 -25
- package/dist/transformers/purgeCss.js.map +1 -0
- package/dist/transformers/removeAttributes.d.ts +43 -20
- package/dist/transformers/removeAttributes.d.ts.map +1 -1
- package/dist/transformers/removeAttributes.js +34 -27
- package/dist/transformers/removeAttributes.js.map +1 -1
- package/dist/transformers/replaceStrings.js +1 -1
- package/dist/transformers/replaceStrings.js.map +1 -1
- package/dist/transformers/safeClassNames.js +1 -2
- package/dist/transformers/safeClassNames.js.map +1 -1
- package/dist/transformers/shorthandCss.d.ts +47 -0
- package/dist/transformers/shorthandCss.d.ts.map +1 -0
- package/dist/transformers/shorthandCss.js +61 -0
- package/dist/transformers/shorthandCss.js.map +1 -0
- package/dist/transformers/sixHex.d.ts +16 -7
- package/dist/transformers/sixHex.d.ts.map +1 -1
- package/dist/transformers/sixHex.js +21 -9
- package/dist/transformers/sixHex.js.map +1 -1
- package/dist/transformers/tailwindComponent.js +1 -2
- package/dist/transformers/tailwindComponent.js.map +1 -1
- package/dist/transformers/tailwindcss.js +1 -2
- package/dist/transformers/tailwindcss.js.map +1 -1
- package/dist/transformers/urlQuery.d.ts +26 -14
- package/dist/transformers/urlQuery.d.ts.map +1 -1
- package/dist/transformers/urlQuery.js +32 -20
- package/dist/transformers/urlQuery.js.map +1 -1
- package/dist/types/config.d.ts +71 -19
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +1 -1
- package/dist/utils/ast/index.js +1 -2
- package/dist/utils/ast/parser.js +1 -2
- package/dist/utils/ast/parser.js.map +1 -1
- package/dist/utils/ast/serializer.js +1 -2
- package/dist/utils/ast/serializer.js.map +1 -1
- package/dist/utils/ast/walker.js +1 -1
- package/dist/utils/ast/walker.js.map +1 -1
- package/dist/utils/compileTailwindCss.js +1 -2
- package/dist/utils/compileTailwindCss.js.map +1 -1
- package/dist/utils/decodeStyleEntities.js +1 -1
- package/dist/utils/decodeStyleEntities.js.map +1 -1
- package/dist/utils/detect.js +1 -2
- package/dist/utils/detect.js.map +1 -1
- package/dist/utils/output-markers.d.ts +29 -0
- package/dist/utils/output-markers.d.ts.map +1 -0
- package/dist/utils/output-markers.js +68 -0
- package/dist/utils/output-markers.js.map +1 -0
- package/dist/utils/url.js +1 -2
- package/dist/utils/url.js.map +1 -1
- package/node_modules/maizzle/README.md +24 -0
- package/node_modules/maizzle/dist/commands/make/component.mjs +1 -1
- package/node_modules/maizzle/dist/commands/make/config.mjs +1 -1
- package/node_modules/maizzle/dist/commands/make/layout.mjs +3 -3
- package/node_modules/maizzle/dist/commands/make/scaffold.mjs +1 -1
- package/node_modules/maizzle/dist/commands/make/stubs/Layout.vue +146 -0
- package/node_modules/maizzle/dist/commands/make/stubs/component.vue +2 -4
- package/node_modules/maizzle/dist/commands/make/stubs/config.ts +1 -5
- package/node_modules/maizzle/dist/commands/make/template.mjs +1 -1
- package/node_modules/maizzle/dist/commands/new.mjs +29 -24
- package/node_modules/maizzle/dist/index.mjs +28 -8
- package/node_modules/maizzle/package.json +1 -1
- package/package.json +2 -2
- package/dist/transformers/inlineCSS.d.ts +0 -17
- package/dist/transformers/inlineCSS.d.ts.map +0 -1
- package/dist/transformers/inlineCSS.js.map +0 -1
- package/dist/transformers/purgeCSS.d.ts +0 -23
- package/dist/transformers/purgeCSS.d.ts.map +0 -1
- package/dist/transformers/purgeCSS.js.map +0 -1
- package/dist/transformers/shorthandCSS.d.ts +0 -24
- package/dist/transformers/shorthandCSS.d.ts.map +0 -1
- package/dist/transformers/shorthandCSS.js +0 -48
- package/dist/transformers/shorthandCSS.js.map +0 -1
- package/node_modules/maizzle/dist/commands/make/stubs/layout.vue +0 -39
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ChildNode } from "domhandler";
|
|
2
|
+
import { Opts } from "email-comb";
|
|
3
|
+
|
|
4
|
+
//#region src/transformers/purgeCss.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Options for the `purgeCss` transformer.
|
|
7
|
+
*/
|
|
8
|
+
interface PurgeCssOptions extends Partial<Omit<Opts, 'whitelist'>> {
|
|
9
|
+
/**
|
|
10
|
+
* Selectors to preserve regardless of whether they're matched in the
|
|
11
|
+
* markup. Appended to Maizzle's built-in safelist (Gmail, Apple Mail,
|
|
12
|
+
* Outlook.com hooks, etc). Mapped to email-comb's `whitelist` option.
|
|
13
|
+
*/
|
|
14
|
+
safelist?: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Remove unused CSS from an HTML string.
|
|
18
|
+
*
|
|
19
|
+
* Uses `email-comb` together with a DOM-aware deep-purge step to strip
|
|
20
|
+
* CSS selectors and class/id references that are not matched anywhere
|
|
21
|
+
* in the document body.
|
|
22
|
+
*
|
|
23
|
+
* @param html HTML string to transform.
|
|
24
|
+
* @param options Email-comb options plus a Maizzle `safelist`.
|
|
25
|
+
* @returns The transformed HTML string.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* import { purgeCss } from '@maizzle/framework'
|
|
29
|
+
*
|
|
30
|
+
* const out = purgeCss('<style>.a{}.b{}</style><p class="a">x</p>', {
|
|
31
|
+
* safelist: ['.keep'],
|
|
32
|
+
* })
|
|
33
|
+
*/
|
|
34
|
+
declare function purgeCss(html: string, options?: PurgeCssOptions): string;
|
|
35
|
+
/**
|
|
36
|
+
* DOM-form of {@link purgeCss} used by the internal transformer
|
|
37
|
+
* pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant
|
|
38
|
+
* serialize/parse round-trips when chained with other transformers.
|
|
39
|
+
*/
|
|
40
|
+
declare function purgeCssDom(dom: ChildNode[], options?: PurgeCssOptions): ChildNode[];
|
|
41
|
+
//#endregion
|
|
42
|
+
export { PurgeCssOptions, purgeCss, purgeCssDom };
|
|
43
|
+
//# sourceMappingURL=purgeCss.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"purgeCss.d.ts","names":[],"sources":["../../src/transformers/purgeCss.ts"],"mappings":";;;;;;AAqCA;UAAiB,eAAA,SAAwB,OAAA,CAAQ,IAAA,CAAK,IAAA;;;;;;EAMpD,QAAA;AAAA;;;;;;AAqBF;;;;;;;;;AASA;;;;iBATgB,QAAA,CAAS,IAAA,UAAc,OAAA,GAAS,eAAA;;;;;;iBAShC,WAAA,CAAY,GAAA,EAAK,SAAA,IAAa,OAAA,GAAS,eAAA,GAAuB,SAAA"}
|
|
@@ -2,12 +2,11 @@ import { parse } from "../utils/ast/parser.js";
|
|
|
2
2
|
import { walk } from "../utils/ast/walker.js";
|
|
3
3
|
import { serialize } from "../utils/ast/serializer.js";
|
|
4
4
|
import "../utils/ast/index.js";
|
|
5
|
-
import { defu } from "defu";
|
|
5
|
+
import { defu as defu$1 } from "defu";
|
|
6
6
|
import safeParser from "postcss-safe-parser";
|
|
7
7
|
import { selectAll } from "css-select";
|
|
8
8
|
import { comb } from "email-comb";
|
|
9
|
-
|
|
10
|
-
//#region src/transformers/purgeCSS.ts
|
|
9
|
+
//#region src/transformers/purgeCss.ts
|
|
11
10
|
const DEFAULT_SAFELIST = [
|
|
12
11
|
"*body*",
|
|
13
12
|
".gmail*",
|
|
@@ -36,27 +35,35 @@ const DEFAULT_OPTIONS = {
|
|
|
36
35
|
whitelist: [...DEFAULT_SAFELIST]
|
|
37
36
|
};
|
|
38
37
|
/**
|
|
39
|
-
* Remove unused CSS
|
|
38
|
+
* Remove unused CSS from an HTML string.
|
|
39
|
+
*
|
|
40
|
+
* Uses `email-comb` together with a DOM-aware deep-purge step to strip
|
|
41
|
+
* CSS selectors and class/id references that are not matched anywhere
|
|
42
|
+
* in the document body.
|
|
40
43
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
44
|
+
* @param html HTML string to transform.
|
|
45
|
+
* @param options Email-comb options plus a Maizzle `safelist`.
|
|
46
|
+
* @returns The transformed HTML string.
|
|
43
47
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* `safelist` values are **appended** to the built-in safelist rather
|
|
47
|
-
* than replacing it.
|
|
48
|
+
* @example
|
|
49
|
+
* import { purgeCss } from '@maizzle/framework'
|
|
48
50
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
51
|
+
* const out = purgeCss('<style>.a{}.b{}</style><p class="a">x</p>', {
|
|
52
|
+
* safelist: ['.keep'],
|
|
53
|
+
* })
|
|
52
54
|
*/
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
function purgeCss(html, options = {}) {
|
|
56
|
+
return serialize(purgeCssDom(parse(html), options));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* DOM-form of {@link purgeCss} used by the internal transformer
|
|
60
|
+
* pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant
|
|
61
|
+
* serialize/parse round-trips when chained with other transformers.
|
|
62
|
+
*/
|
|
63
|
+
function purgeCssDom(dom, options = {}) {
|
|
64
|
+
const userSafelist = Array.isArray(options.safelist) ? options.safelist : [];
|
|
65
|
+
const { safelist: _discard, ...restUserOptions } = options;
|
|
66
|
+
const combOptions = defu$1({
|
|
60
67
|
...restUserOptions,
|
|
61
68
|
whitelist: [...DEFAULT_SAFELIST, ...userSafelist]
|
|
62
69
|
}, DEFAULT_OPTIONS);
|
|
@@ -89,8 +96,8 @@ function purgeCSS(dom, config = {}) {
|
|
|
89
96
|
});
|
|
90
97
|
textNode.data = `${token}{}`;
|
|
91
98
|
});
|
|
92
|
-
if (extraWhitelist.length)
|
|
93
|
-
const { result } = comb(serialize(dom),
|
|
99
|
+
if (extraWhitelist.length) combOptions.whitelist = [...combOptions.whitelist ?? [], ...extraWhitelist];
|
|
100
|
+
const { result } = comb(serialize(dom), combOptions);
|
|
94
101
|
/**
|
|
95
102
|
* Comb returns a fresh string, so we work off the post-parse tree:
|
|
96
103
|
* find each embed style tag whose body still starts with the stub
|
|
@@ -168,7 +175,7 @@ function deepPurge(dom, safelist) {
|
|
|
168
175
|
});
|
|
169
176
|
return dom;
|
|
170
177
|
}
|
|
171
|
-
|
|
172
178
|
//#endregion
|
|
173
|
-
export {
|
|
174
|
-
|
|
179
|
+
export { purgeCss, purgeCssDom };
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=purgeCss.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"purgeCss.js","names":["merge"],"sources":["../../src/transformers/purgeCss.ts"],"sourcesContent":["import { comb } from 'email-comb'\nimport { defu as merge } from 'defu'\nimport safeParser from 'postcss-safe-parser'\nimport { selectAll } from 'css-select'\nimport type { ChildNode, Element } from 'domhandler'\nimport type { Opts as CombOptions } from 'email-comb'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\nconst DEFAULT_SAFELIST: string[] = [\n '*body*', // Gmail\n '.gmail*', // Gmail\n '.apple*', // Apple Mail\n '.ios*', // Mail on iOS\n '.ox-*', // Open-Xchange\n '.outlook*', // Outlook.com\n '[data-ogs*', // Outlook.com\n '.bloop_container', // Airmail\n '.Singleton', // Apple Mail 10\n '.unused', // Notes 8\n '.moz-text-html', // Thunderbird\n '.mail-detail-content', // Comcast, Libero webmail\n '*edo*', // Edison (all)\n '#*', // Freenet uses #msgBody\n '.lang*', // Fenced code blocks\n]\n\nconst DEFAULT_OPTIONS = {\n backend: [\n { heads: '{{', tails: '}}' },\n { heads: '{%', tails: '%}' },\n ],\n whitelist: [...DEFAULT_SAFELIST],\n}\n\n/**\n * Options for the `purgeCss` transformer.\n */\nexport interface PurgeCssOptions extends Partial<Omit<CombOptions, 'whitelist'>> {\n /**\n * Selectors to preserve regardless of whether they're matched in the\n * markup. Appended to Maizzle's built-in safelist (Gmail, Apple Mail,\n * Outlook.com hooks, etc). Mapped to email-comb's `whitelist` option.\n */\n safelist?: string[]\n}\n\n/**\n * Remove unused CSS from an HTML string.\n *\n * Uses `email-comb` together with a DOM-aware deep-purge step to strip\n * CSS selectors and class/id references that are not matched anywhere\n * in the document body.\n *\n * @param html HTML string to transform.\n * @param options Email-comb options plus a Maizzle `safelist`.\n * @returns The transformed HTML string.\n *\n * @example\n * import { purgeCss } from '@maizzle/framework'\n *\n * const out = purgeCss('<style>.a{}.b{}</style><p class=\"a\">x</p>', {\n * safelist: ['.keep'],\n * })\n */\nexport function purgeCss(html: string, options: PurgeCssOptions = {}): string {\n return serialize(purgeCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link purgeCss} used by the internal transformer\n * pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function purgeCssDom(dom: ChildNode[], options: PurgeCssOptions = {}): ChildNode[] {\n const userSafelist = Array.isArray(options.safelist) ? options.safelist : []\n\n const { safelist: _discard, ...restUserOptions } = options\n\n // Merge user options on top of defaults.\n // defu merges objects deeply; for arrays it appends user values.\n // We want the user safelist appended to the default safelist,\n // so we build whitelist manually.\n const combOptions = merge(\n { ...restUserOptions, whitelist: [...DEFAULT_SAFELIST, ...userSafelist] },\n DEFAULT_OPTIONS,\n )\n\n // Deep purge first: DOM-aware selector removal using PostCSS + css-select.\n // Runs before email-comb so that email-comb can clean up orphaned classes\n // in HTML attributes left behind by removed CSS rules.\n const safelist = [...DEFAULT_SAFELIST, ...userSafelist]\n dom = deepPurge(dom, safelist)\n\n /**\n * Shield embed style tags from email-comb. Comb has no skip option,\n * so it strips CSS comments and drops class refs it can't match\n * against visible CSS. Swap each embed tag's body for a unique\n * stub rule (`.maizzle-keep-N{}`) so comb keeps the tag, then\n * whitelist that stub plus every selector from the original\n * CSS so comb leaves matching refs alone elsewhere — and\n * finally restore the original CSS once comb has run.\n */\n const stash: { token: string; original: string; textNode: any }[] = []\n const extraWhitelist: string[] = []\n walk(dom, (node) => {\n const el = node as Element\n if (el.name !== 'style' || !el.attribs) return\n if (!('embed' in el.attribs) && !('data-embed' in el.attribs)) return\n const textNode = el.children?.find((c: any) => c.type === 'text') as any\n if (!textNode?.data) return\n const idx = stash.length\n const token = `.maizzle-keep-${idx}`\n extraWhitelist.push(token)\n for (const m of textNode.data.matchAll(/(?<![\\w-])[.#][a-zA-Z_][\\w-]*/g)) {\n extraWhitelist.push(m[0])\n }\n stash.push({ token, original: textNode.data, textNode })\n textNode.data = `${token}{}`\n })\n\n if (extraWhitelist.length) {\n combOptions.whitelist = [...(combOptions.whitelist as string[] ?? []), ...extraWhitelist]\n }\n\n const { result } = comb(serialize(dom), combOptions)\n\n /**\n * Comb returns a fresh string, so we work off the post-parse tree:\n * find each embed style tag whose body still starts with the stub\n * token we planted earlier and swap the original CSS back in.\n */\n let purgedDom = parse(result)\n\n if (stash.length) {\n walk(purgedDom, (node) => {\n const el = node as Element\n if (el.name !== 'style' || !el.attribs) return\n if (!('embed' in el.attribs) && !('data-embed' in el.attribs)) return\n const textNode = el.children?.find((c: any) => c.type === 'text') as any\n if (!textNode?.data) return\n const trimmed = textNode.data.trim()\n const match = stash.find(s => trimmed === `${s.token}{}` || trimmed.startsWith(`${s.token}{`))\n if (match) textNode.data = match.original\n })\n }\n\n // Clean up data-embed/embed attributes — no longer needed after purging\n walk(purgedDom, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs) {\n delete el.attribs['data-embed']\n delete el.attribs.embed\n }\n })\n\n return purgedDom\n}\n\n/**\n * Deep purge: uses PostCSS to parse CSS in non-embedded style tags,\n * then checks each selector against the DOM with css-select.\n * Removes rules where no selector matches any element.\n */\nfunction isSafelisted(selector: string, safelist: string[]): boolean {\n return safelist.some((pattern) => {\n if (pattern.startsWith('*') && pattern.endsWith('*')) {\n return selector.includes(pattern.slice(1, -1))\n }\n if (pattern.endsWith('*')) {\n return selector.startsWith(pattern.slice(0, -1))\n }\n if (pattern.startsWith('*')) {\n return selector.endsWith(pattern.slice(1))\n }\n return selector === pattern\n })\n}\n\nfunction deepPurge(dom: ChildNode[], safelist: string[]): ChildNode[] {\n walk(dom, (node) => {\n const el = node as Element\n\n if (el.name !== 'style' || !el.attribs) return\n if ('data-embed' in el.attribs || 'embed' in el.attribs) return\n\n const textNode = el.children?.find((c: any) => c.type === 'text') as any\n if (!textNode?.data?.trim()) return\n\n const root = safeParser(textNode.data)\n\n root.walkRules((rule) => {\n // Skip rules inside @media or other at-rules — those may target\n // states we can't match statically (hover, responsive, etc.)\n if (rule.parent?.type === 'atrule') return\n\n const selectors = rule.selectors ?? [rule.selector]\n const matched = selectors.filter((sel) => {\n // Keep safelisted selectors\n if (isSafelisted(sel, safelist)) return true\n\n // Skip pseudo-classes/elements that can't be matched statically.\n // Functional pseudos like :not(), :is(), :where(), :has() are\n // matchable by css-select, so we only skip dynamic/state ones.\n if (/::[\\w-]/.test(sel)) return true\n if (/(?<!:):(?!not\\b|is\\b|where\\b|has\\b)[\\w-]/.test(sel.replace(/\\\\./g, ''))) return true\n\n try {\n return selectAll(sel, dom).length > 0\n } catch {\n // If css-select can't parse the selector, keep it\n return true\n }\n })\n\n if (matched.length === 0) {\n rule.remove()\n } else if (matched.length < selectors.length) {\n rule.selectors = matched\n }\n })\n\n // Remove empty at-rules\n root.walkAtRules((atRule) => {\n if (atRule.nodes?.length === 0) {\n atRule.remove()\n }\n })\n\n const purgedCss = root.toString()\n\n if (purgedCss.trim()) {\n textNode.data = purgedCss\n } else {\n // Remove the style tag entirely if empty\n const parent = el.parent\n if (parent && 'children' in parent) {\n const idx = parent.children.indexOf(el as any)\n if (idx !== -1) parent.children.splice(idx, 1)\n }\n }\n })\n\n return dom\n}\n"],"mappings":"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"}
|
|
@@ -1,31 +1,54 @@
|
|
|
1
|
-
import { AttributesConfig } from "../types/config.js";
|
|
2
1
|
import { ChildNode } from "domhandler";
|
|
3
2
|
|
|
4
3
|
//#region src/transformers/removeAttributes.d.ts
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
5
|
+
* Single attribute-removal rule.
|
|
6
|
+
*/
|
|
7
|
+
interface RemoveAttributeRule {
|
|
8
|
+
/** Attribute name to match. */
|
|
9
|
+
name: string;
|
|
10
|
+
/**
|
|
11
|
+
* Match condition for the attribute's value:
|
|
12
|
+
* - `string` — remove when the value matches exactly.
|
|
13
|
+
* - `RegExp` — remove when the value matches the regex.
|
|
14
|
+
* - `boolean` / omitted — remove when the value is empty.
|
|
15
|
+
*/
|
|
16
|
+
value?: string | RegExp | boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Entry passed to {@link removeAttributes}. A bare string targets the named
|
|
20
|
+
* attribute and removes it when its value is empty.
|
|
21
|
+
*/
|
|
22
|
+
type RemoveAttributeOption = string | RemoveAttributeRule;
|
|
23
|
+
/**
|
|
24
|
+
* Remove HTML attributes from elements.
|
|
7
25
|
*
|
|
8
|
-
*
|
|
26
|
+
* Empty `style` and `class` attributes are always stripped, regardless of
|
|
27
|
+
* what you pass. Your entries are appended to those defaults.
|
|
9
28
|
*
|
|
10
|
-
*
|
|
29
|
+
* - `'data-src'` — remove when the value is empty.
|
|
30
|
+
* - `{ name: 'id', value: 'test' }` — remove when the value matches exactly.
|
|
31
|
+
* - `{ name: 'data-id', value: /\d/ }` — remove when the value matches the regex.
|
|
11
32
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* - Object with name and RegExp value: removes when attribute value matches regex
|
|
33
|
+
* @param html HTML string to transform.
|
|
34
|
+
* @param attributes Additional attribute-removal rules to apply on top of the defaults.
|
|
35
|
+
* @returns The transformed HTML string.
|
|
16
36
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
* @example
|
|
38
|
+
* import { removeAttributes } from '@maizzle/framework'
|
|
39
|
+
*
|
|
40
|
+
* const out = removeAttributes('<p style="" data-x="">x</p>', [
|
|
41
|
+
* 'data-x',
|
|
42
|
+
* { name: 'role', value: 'none' },
|
|
43
|
+
* ])
|
|
44
|
+
*/
|
|
45
|
+
declare function removeAttributes(html: string, attributes?: RemoveAttributeOption[]): string;
|
|
46
|
+
/**
|
|
47
|
+
* DOM-form of {@link removeAttributes} used by the internal transformer
|
|
48
|
+
* pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant
|
|
49
|
+
* serialize/parse round-trips when chained with other transformers.
|
|
27
50
|
*/
|
|
28
|
-
declare function
|
|
51
|
+
declare function removeAttributesDom(dom: ChildNode[], attributes?: RemoveAttributeOption[]): ChildNode[];
|
|
29
52
|
//#endregion
|
|
30
|
-
export { removeAttributes };
|
|
53
|
+
export { RemoveAttributeOption, RemoveAttributeRule, removeAttributes, removeAttributesDom };
|
|
31
54
|
//# sourceMappingURL=removeAttributes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"removeAttributes.d.ts","names":[],"sources":["../../src/transformers/removeAttributes.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"removeAttributes.d.ts","names":[],"sources":["../../src/transformers/removeAttributes.ts"],"mappings":";;;;;AAMA;UAAiB,mBAAA;;EAEf,IAAA;EAAA;;;;;AAcF;EAPE,KAAA,YAAiB,MAAA;AAAA;;;AA+BnB;;KAxBY,qBAAA,YAAiC,mBAAA;;;;;;;AAiC7C;;;;;;;;;;;;;;;;iBATgB,gBAAA,CAAiB,IAAA,UAAc,UAAA,GAAY,qBAAA;;;;;;iBAS3C,mBAAA,CAAoB,GAAA,EAAK,SAAA,IAAa,UAAA,GAAY,qBAAA,KAA+B,SAAA"}
|
|
@@ -1,37 +1,44 @@
|
|
|
1
|
+
import { parse } from "../utils/ast/parser.js";
|
|
1
2
|
import { walk } from "../utils/ast/walker.js";
|
|
3
|
+
import { serialize } from "../utils/ast/serializer.js";
|
|
2
4
|
import "../utils/ast/index.js";
|
|
3
|
-
|
|
4
5
|
//#region src/transformers/removeAttributes.ts
|
|
5
6
|
/**
|
|
6
|
-
* Remove attributes
|
|
7
|
+
* Remove HTML attributes from elements.
|
|
8
|
+
*
|
|
9
|
+
* Empty `style` and `class` attributes are always stripped, regardless of
|
|
10
|
+
* what you pass. Your entries are appended to those defaults.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
12
|
+
* - `'data-src'` — remove when the value is empty.
|
|
13
|
+
* - `{ name: 'id', value: 'test' }` — remove when the value matches exactly.
|
|
14
|
+
* - `{ name: 'data-id', value: /\d/ }` — remove when the value matches the regex.
|
|
9
15
|
*
|
|
10
|
-
*
|
|
16
|
+
* @param html HTML string to transform.
|
|
17
|
+
* @param attributes Additional attribute-removal rules to apply on top of the defaults.
|
|
18
|
+
* @returns The transformed HTML string.
|
|
11
19
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* - Object with name and value: removes when attribute matches exactly
|
|
15
|
-
* - Object with name and RegExp value: removes when attribute value matches regex
|
|
20
|
+
* @example
|
|
21
|
+
* import { removeAttributes } from '@maizzle/framework'
|
|
16
22
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* 'data-src', // Remove empty data-src attributes
|
|
22
|
-
* { name: 'id', value: 'test' }, // Remove id="test" exactly
|
|
23
|
-
* { name: 'data-id', value: /\d/ } // Remove data-id when value contains digits
|
|
24
|
-
* ]
|
|
25
|
-
* }
|
|
26
|
-
* ```
|
|
23
|
+
* const out = removeAttributes('<p style="" data-x="">x</p>', [
|
|
24
|
+
* 'data-x',
|
|
25
|
+
* { name: 'role', value: 'none' },
|
|
26
|
+
* ])
|
|
27
27
|
*/
|
|
28
|
-
function removeAttributes(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
function removeAttributes(html, attributes = []) {
|
|
29
|
+
return serialize(removeAttributesDom(parse(html), attributes));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* DOM-form of {@link removeAttributes} used by the internal transformer
|
|
33
|
+
* pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant
|
|
34
|
+
* serialize/parse round-trips when chained with other transformers.
|
|
35
|
+
*/
|
|
36
|
+
function removeAttributesDom(dom, attributes = []) {
|
|
37
|
+
const attributesToRemove = [
|
|
38
|
+
"style",
|
|
39
|
+
"class",
|
|
40
|
+
...attributes
|
|
41
|
+
];
|
|
35
42
|
walk(dom, (node) => {
|
|
36
43
|
const el = node;
|
|
37
44
|
if (!el.attribs) return;
|
|
@@ -57,7 +64,7 @@ function removeAttributes(dom, config = {}) {
|
|
|
57
64
|
});
|
|
58
65
|
return dom;
|
|
59
66
|
}
|
|
60
|
-
|
|
61
67
|
//#endregion
|
|
62
|
-
export { removeAttributes };
|
|
68
|
+
export { removeAttributes, removeAttributesDom };
|
|
69
|
+
|
|
63
70
|
//# sourceMappingURL=removeAttributes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"removeAttributes.js","names":[],"sources":["../../src/transformers/removeAttributes.ts"],"sourcesContent":["import type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\
|
|
1
|
+
{"version":3,"file":"removeAttributes.js","names":[],"sources":["../../src/transformers/removeAttributes.ts"],"sourcesContent":["import type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\n/**\n * Single attribute-removal rule.\n */\nexport interface RemoveAttributeRule {\n /** Attribute name to match. */\n name: string\n /**\n * Match condition for the attribute's value:\n * - `string` — remove when the value matches exactly.\n * - `RegExp` — remove when the value matches the regex.\n * - `boolean` / omitted — remove when the value is empty.\n */\n value?: string | RegExp | boolean\n}\n\n/**\n * Entry passed to {@link removeAttributes}. A bare string targets the named\n * attribute and removes it when its value is empty.\n */\nexport type RemoveAttributeOption = string | RemoveAttributeRule\n\n/**\n * Remove HTML attributes from elements.\n *\n * Empty `style` and `class` attributes are always stripped, regardless of\n * what you pass. Your entries are appended to those defaults.\n *\n * - `'data-src'` — remove when the value is empty.\n * - `{ name: 'id', value: 'test' }` — remove when the value matches exactly.\n * - `{ name: 'data-id', value: /\\d/ }` — remove when the value matches the regex.\n *\n * @param html HTML string to transform.\n * @param attributes Additional attribute-removal rules to apply on top of the defaults.\n * @returns The transformed HTML string.\n *\n * @example\n * import { removeAttributes } from '@maizzle/framework'\n *\n * const out = removeAttributes('<p style=\"\" data-x=\"\">x</p>', [\n * 'data-x',\n * { name: 'role', value: 'none' },\n * ])\n */\nexport function removeAttributes(html: string, attributes: RemoveAttributeOption[] = []): string {\n return serialize(removeAttributesDom(parse(html), attributes))\n}\n\n/**\n * DOM-form of {@link removeAttributes} used by the internal transformer\n * pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function removeAttributesDom(dom: ChildNode[], attributes: RemoveAttributeOption[] = []): ChildNode[] {\n // Empty style/class are always stripped; user entries are appended.\n const attributesToRemove: RemoveAttributeOption[] = ['style', 'class', ...attributes]\n\n walk(dom, (node) => {\n const el = node as Element\n if (!el.attribs) return\n\n for (const attr of attributesToRemove) {\n let attrName: string\n let attrValue: string | RegExp | boolean | undefined\n\n if (typeof attr === 'string') {\n attrName = attr\n attrValue = true // Remove when value is empty (boolean true or empty string)\n } else {\n attrName = attr.name\n attrValue = attr.value\n }\n\n const currentValue = el.attribs[attrName]\n\n // Skip if attribute doesn't exist\n if (currentValue === undefined) continue\n\n let shouldRemove = false\n\n if (typeof attrValue === 'boolean') {\n // Remove if value is empty (boolean true is treated as no-value attribute)\n shouldRemove = currentValue === '' || (currentValue as unknown) === true\n } else if (typeof attrValue === 'string') {\n // Remove if value matches exactly\n shouldRemove = currentValue === attrValue\n } else if (attrValue instanceof RegExp) {\n // Remove if value matches regex\n shouldRemove = attrValue.test(currentValue)\n } else {\n // Default: remove if empty\n shouldRemove = currentValue === ''\n }\n\n if (shouldRemove) {\n delete el.attribs[attrName]\n }\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAgB,iBAAiB,MAAc,aAAsC,EAAE,EAAU;CAC/F,OAAO,UAAU,oBAAoB,MAAM,KAAK,EAAE,WAAW,CAAC;;;;;;;AAQhE,SAAgB,oBAAoB,KAAkB,aAAsC,EAAE,EAAe;CAE3G,MAAM,qBAA8C;EAAC;EAAS;EAAS,GAAG;EAAW;CAErF,KAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EACX,IAAI,CAAC,GAAG,SAAS;EAEjB,KAAK,MAAM,QAAQ,oBAAoB;GACrC,IAAI;GACJ,IAAI;GAEJ,IAAI,OAAO,SAAS,UAAU;IAC5B,WAAW;IACX,YAAY;UACP;IACL,WAAW,KAAK;IAChB,YAAY,KAAK;;GAGnB,MAAM,eAAe,GAAG,QAAQ;GAGhC,IAAI,iBAAiB,KAAA,GAAW;GAEhC,IAAI,eAAe;GAEnB,IAAI,OAAO,cAAc,WAEvB,eAAe,iBAAiB,MAAO,iBAA6B;QAC/D,IAAI,OAAO,cAAc,UAE9B,eAAe,iBAAiB;QAC3B,IAAI,qBAAqB,QAE9B,eAAe,UAAU,KAAK,aAAa;QAG3C,eAAe,iBAAiB;GAGlC,IAAI,cACF,OAAO,GAAG,QAAQ;;GAGtB;CAEF,OAAO"}
|
|
@@ -13,7 +13,7 @@ function replaceStrings(html, config = {}) {
|
|
|
13
13
|
if (!replacements || Object.keys(replacements).length === 0) return html;
|
|
14
14
|
return Object.entries(replacements).reduce((result, [pattern, replacement]) => result.replace(new RegExp(pattern, "gi"), replacement), html);
|
|
15
15
|
}
|
|
16
|
-
|
|
17
16
|
//#endregion
|
|
18
17
|
export { replaceStrings };
|
|
18
|
+
|
|
19
19
|
//# sourceMappingURL=replaceStrings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replaceStrings.js","names":[],"sources":["../../src/transformers/replaceStrings.ts"],"sourcesContent":["import type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Replace strings transformer.\n *\n * Replaces strings in the HTML using the key-value pairs defined in\n * `config.replaceStrings`. Each key is treated as a regular expression\n * pattern (case-insensitive, global), and the value is the replacement.\n *\n * Character classes must be escaped in keys, e.g. `\\\\s` for `\\s`.\n */\nexport function replaceStrings(html: string, config: MaizzleConfig = {}): string {\n const replacements = config.replaceStrings\n\n if (!replacements || Object.keys(replacements).length === 0) return html\n\n return Object.entries(replacements).reduce(\n (result, [pattern, replacement]) => result.replace(new RegExp(pattern, 'gi'), replacement),\n html,\n )\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAgB,eAAe,MAAc,SAAwB,EAAE,EAAU;CAC/E,MAAM,eAAe,OAAO;
|
|
1
|
+
{"version":3,"file":"replaceStrings.js","names":[],"sources":["../../src/transformers/replaceStrings.ts"],"sourcesContent":["import type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Replace strings transformer.\n *\n * Replaces strings in the HTML using the key-value pairs defined in\n * `config.replaceStrings`. Each key is treated as a regular expression\n * pattern (case-insensitive, global), and the value is the replacement.\n *\n * Character classes must be escaped in keys, e.g. `\\\\s` for `\\s`.\n */\nexport function replaceStrings(html: string, config: MaizzleConfig = {}): string {\n const replacements = config.replaceStrings\n\n if (!replacements || Object.keys(replacements).length === 0) return html\n\n return Object.entries(replacements).reduce(\n (result, [pattern, replacement]) => result.replace(new RegExp(pattern, 'gi'), replacement),\n html,\n )\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAgB,eAAe,MAAc,SAAwB,EAAE,EAAU;CAC/E,MAAM,eAAe,OAAO;CAE5B,IAAI,CAAC,gBAAgB,OAAO,KAAK,aAAa,CAAC,WAAW,GAAG,OAAO;CAEpE,OAAO,OAAO,QAAQ,aAAa,CAAC,QACjC,QAAQ,CAAC,SAAS,iBAAiB,OAAO,QAAQ,IAAI,OAAO,SAAS,KAAK,EAAE,YAAY,EAC1F,KACD"}
|
|
@@ -2,7 +2,6 @@ import { walk } from "../utils/ast/walker.js";
|
|
|
2
2
|
import "../utils/ast/index.js";
|
|
3
3
|
import postcss from "postcss";
|
|
4
4
|
import safeParser from "postcss-safe-parser";
|
|
5
|
-
|
|
6
5
|
//#region src/transformers/safeClassNames.ts
|
|
7
6
|
const DEFAULT_REPLACEMENTS = {
|
|
8
7
|
":": "-",
|
|
@@ -97,7 +96,7 @@ function safeClassNames(dom, config = {}) {
|
|
|
97
96
|
});
|
|
98
97
|
return dom;
|
|
99
98
|
}
|
|
100
|
-
|
|
101
99
|
//#endregion
|
|
102
100
|
export { safeClassNames };
|
|
101
|
+
|
|
103
102
|
//# sourceMappingURL=safeClassNames.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safeClassNames.js","names":[],"sources":["../../src/transformers/safeClassNames.ts"],"sourcesContent":["import postcss from 'postcss'\nimport safeParser from 'postcss-safe-parser'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport type { CssConfig } from '../types/config.ts'\n\nconst DEFAULT_REPLACEMENTS: Record<string, string> = {\n ':': '-',\n '/': '-',\n '%': 'pc',\n '.': '_',\n ',': '_',\n '#': '_',\n '[': '',\n ']': '',\n '(': '',\n ')': '',\n '{': '',\n '}': '',\n '!': '-i',\n '&': 'and-',\n '<': 'lt-',\n '=': 'eq-',\n '>': 'gt-',\n '|': 'or-',\n '@': 'at-',\n '?': 'q-',\n '\\\\': '-',\n '\"': '-',\n \"'\": '-',\n '*': '-',\n '+': '-',\n ';': '-',\n '^': '-',\n '`': '-',\n '~': '-',\n '$': '-',\n}\n\nfunction escapeForRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Replace escaped special characters in CSS selectors.\n *\n * Tailwind generates selectors like `.sm\\:text-base`. This function\n * replaces the `\\:` with `-` (or whatever the configured replacement is)\n * so the selector becomes `.sm-text-base`, which is safe for email clients.\n */\nfunction processCssSelectors(css: string, replacements: Record<string, string>): string {\n // Matches \\<char> in CSS selectors — e.g. \\: \\/ \\. \\[ etc.\n const selectorRegex = new RegExp(\n `\\\\\\\\(${Object.keys(replacements).map(escapeForRegex).join('|')})`,\n 'g',\n )\n\n return postcss([\n (root: postcss.Root) => {\n root.walkRules((rule: postcss.Rule) => {\n rule.selector = rule.selector\n .replace(selectorRegex, (_matched, char) => replacements[char] ?? _matched)\n // Handle CSS unicode escape for comma (\\2c → _)\n .replaceAll('\\\\2c ', '_')\n })\n },\n ]).process(css, { parser: safeParser }).css\n}\n\n/**\n * Replace unsafe special characters in a class attribute value.\n *\n * Splits on whitespace and replaces each char from the replacements map\n * in each class token individually.\n */\nfunction processClassAttr(classStr: string, replacements: Record<string, string>): string {\n return classStr\n .split(/\\s+/)\n .filter(Boolean)\n .map((cls) => {\n for (const [from, to] of Object.entries(replacements)) {\n cls = cls.split(from).join(to)\n }\n return cls\n })\n .join(' ')\n}\n\n/**\n * Safe class names transformer.\n *\n * Replaces unsafe characters (`:`, `/`, `[`, `]`, etc.) in:\n * - CSS selectors inside `<style>` tags\n * - HTML `class` attributes\n *\n * This makes Tailwind utility classes like `sm:text-base` safe for\n * email clients that cannot handle escaped characters in class names.\n *\n * Enabled by default. Disable by setting `css.safe` to `false`.\n * Customize replacements by passing a `Record<string, string>` — user\n * values are merged on top of the defaults.\n */\nexport function safeClassNames(dom: ChildNode[], config: CssConfig = {}): ChildNode[] {\n const option = config.safe ?? true\n\n if (!option) return dom\n\n const replacements: Record<string, string> =\n option && typeof option === 'object'\n ? { ...DEFAULT_REPLACEMENTS, ...option }\n : DEFAULT_REPLACEMENTS\n\n walk(dom, (node) => {\n const el = node as Element\n\n // Process CSS selectors inside <style> tags\n if (el.name === 'style' && el.children?.length) {\n const text = el.children.find((c) => c.type === 'text') as any\n if (text?.data?.trim()) {\n text.data = processCssSelectors(text.data, replacements)\n }\n }\n\n // Replace special chars in class attributes\n if ('attribs' in el && el.attribs?.class) {\n el.attribs.class = processClassAttr(el.attribs.class, replacements)\n }\n })\n\n return dom\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"safeClassNames.js","names":[],"sources":["../../src/transformers/safeClassNames.ts"],"sourcesContent":["import postcss from 'postcss'\nimport safeParser from 'postcss-safe-parser'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport type { CssConfig } from '../types/config.ts'\n\nconst DEFAULT_REPLACEMENTS: Record<string, string> = {\n ':': '-',\n '/': '-',\n '%': 'pc',\n '.': '_',\n ',': '_',\n '#': '_',\n '[': '',\n ']': '',\n '(': '',\n ')': '',\n '{': '',\n '}': '',\n '!': '-i',\n '&': 'and-',\n '<': 'lt-',\n '=': 'eq-',\n '>': 'gt-',\n '|': 'or-',\n '@': 'at-',\n '?': 'q-',\n '\\\\': '-',\n '\"': '-',\n \"'\": '-',\n '*': '-',\n '+': '-',\n ';': '-',\n '^': '-',\n '`': '-',\n '~': '-',\n '$': '-',\n}\n\nfunction escapeForRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Replace escaped special characters in CSS selectors.\n *\n * Tailwind generates selectors like `.sm\\:text-base`. This function\n * replaces the `\\:` with `-` (or whatever the configured replacement is)\n * so the selector becomes `.sm-text-base`, which is safe for email clients.\n */\nfunction processCssSelectors(css: string, replacements: Record<string, string>): string {\n // Matches \\<char> in CSS selectors — e.g. \\: \\/ \\. \\[ etc.\n const selectorRegex = new RegExp(\n `\\\\\\\\(${Object.keys(replacements).map(escapeForRegex).join('|')})`,\n 'g',\n )\n\n return postcss([\n (root: postcss.Root) => {\n root.walkRules((rule: postcss.Rule) => {\n rule.selector = rule.selector\n .replace(selectorRegex, (_matched, char) => replacements[char] ?? _matched)\n // Handle CSS unicode escape for comma (\\2c → _)\n .replaceAll('\\\\2c ', '_')\n })\n },\n ]).process(css, { parser: safeParser }).css\n}\n\n/**\n * Replace unsafe special characters in a class attribute value.\n *\n * Splits on whitespace and replaces each char from the replacements map\n * in each class token individually.\n */\nfunction processClassAttr(classStr: string, replacements: Record<string, string>): string {\n return classStr\n .split(/\\s+/)\n .filter(Boolean)\n .map((cls) => {\n for (const [from, to] of Object.entries(replacements)) {\n cls = cls.split(from).join(to)\n }\n return cls\n })\n .join(' ')\n}\n\n/**\n * Safe class names transformer.\n *\n * Replaces unsafe characters (`:`, `/`, `[`, `]`, etc.) in:\n * - CSS selectors inside `<style>` tags\n * - HTML `class` attributes\n *\n * This makes Tailwind utility classes like `sm:text-base` safe for\n * email clients that cannot handle escaped characters in class names.\n *\n * Enabled by default. Disable by setting `css.safe` to `false`.\n * Customize replacements by passing a `Record<string, string>` — user\n * values are merged on top of the defaults.\n */\nexport function safeClassNames(dom: ChildNode[], config: CssConfig = {}): ChildNode[] {\n const option = config.safe ?? true\n\n if (!option) return dom\n\n const replacements: Record<string, string> =\n option && typeof option === 'object'\n ? { ...DEFAULT_REPLACEMENTS, ...option }\n : DEFAULT_REPLACEMENTS\n\n walk(dom, (node) => {\n const el = node as Element\n\n // Process CSS selectors inside <style> tags\n if (el.name === 'style' && el.children?.length) {\n const text = el.children.find((c) => c.type === 'text') as any\n if (text?.data?.trim()) {\n text.data = processCssSelectors(text.data, replacements)\n }\n }\n\n // Replace special chars in class attributes\n if ('attribs' in el && el.attribs?.class) {\n el.attribs.class = processClassAttr(el.attribs.class, replacements)\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;AAMA,MAAM,uBAA+C;CACnD,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CACN,MAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACN;AAED,SAAS,eAAe,GAAmB;CACzC,OAAO,EAAE,QAAQ,uBAAuB,OAAO;;;;;;;;;AAUjD,SAAS,oBAAoB,KAAa,cAA8C;CAEtF,MAAM,gBAAgB,IAAI,OACxB,QAAQ,OAAO,KAAK,aAAa,CAAC,IAAI,eAAe,CAAC,KAAK,IAAI,CAAC,IAChE,IACD;CAED,OAAO,QAAQ,EACZ,SAAuB;EACtB,KAAK,WAAW,SAAuB;GACrC,KAAK,WAAW,KAAK,SAClB,QAAQ,gBAAgB,UAAU,SAAS,aAAa,SAAS,SAAS,CAE1E,WAAW,SAAS,IAAI;IAC3B;GAEL,CAAC,CAAC,QAAQ,KAAK,EAAE,QAAQ,YAAY,CAAC,CAAC;;;;;;;;AAS1C,SAAS,iBAAiB,UAAkB,cAA8C;CACxF,OAAO,SACJ,MAAM,MAAM,CACZ,OAAO,QAAQ,CACf,KAAK,QAAQ;EACZ,KAAK,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,aAAa,EACnD,MAAM,IAAI,MAAM,KAAK,CAAC,KAAK,GAAG;EAEhC,OAAO;GACP,CACD,KAAK,IAAI;;;;;;;;;;;;;;;;AAiBd,SAAgB,eAAe,KAAkB,SAAoB,EAAE,EAAe;CACpF,MAAM,SAAS,OAAO,QAAQ;CAE9B,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,eACJ,UAAU,OAAO,WAAW,WACxB;EAAE,GAAG;EAAsB,GAAG;EAAQ,GACtC;CAEN,KAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EAGX,IAAI,GAAG,SAAS,WAAW,GAAG,UAAU,QAAQ;GAC9C,MAAM,OAAO,GAAG,SAAS,MAAM,MAAM,EAAE,SAAS,OAAO;GACvD,IAAI,MAAM,MAAM,MAAM,EACpB,KAAK,OAAO,oBAAoB,KAAK,MAAM,aAAa;;EAK5D,IAAI,aAAa,MAAM,GAAG,SAAS,OACjC,GAAG,QAAQ,QAAQ,iBAAiB,GAAG,QAAQ,OAAO,aAAa;GAErE;CAEF,OAAO"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ChildNode } from "domhandler";
|
|
2
|
+
|
|
3
|
+
//#region src/transformers/shorthandCss.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Options for the `shorthandCss` transformer.
|
|
6
|
+
*/
|
|
7
|
+
interface ShorthandCssOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Restrict the transform to a list of HTML tag names. Omit to apply to
|
|
10
|
+
* every element with a `style` attribute.
|
|
11
|
+
*
|
|
12
|
+
* @example ['td', 'div']
|
|
13
|
+
*/
|
|
14
|
+
tags?: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Rewrite longhand CSS inside inline `style` attributes with shorthand
|
|
18
|
+
* syntax. Works with margin, padding, and border when all sides are
|
|
19
|
+
* specified.
|
|
20
|
+
*
|
|
21
|
+
* For example:
|
|
22
|
+
* `margin-left: 2px; margin-right: 2px; margin-top: 4px; margin-bottom: 4px`
|
|
23
|
+
* becomes:
|
|
24
|
+
* `margin: 4px 2px`
|
|
25
|
+
*
|
|
26
|
+
* @param html HTML string to transform.
|
|
27
|
+
* @param options Optional Maizzle options (`tags`).
|
|
28
|
+
* @returns The transformed HTML string.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* import { shorthandCss } from '@maizzle/framework'
|
|
32
|
+
*
|
|
33
|
+
* const out = shorthandCss(
|
|
34
|
+
* '<p style="margin-top: 4px; margin-right: 2px; margin-bottom: 4px; margin-left: 2px;">x</p>',
|
|
35
|
+
* { tags: ['p'] },
|
|
36
|
+
* )
|
|
37
|
+
*/
|
|
38
|
+
declare function shorthandCss(html: string, options?: ShorthandCssOptions): string;
|
|
39
|
+
/**
|
|
40
|
+
* DOM-form of {@link shorthandCss} used by the internal transformer
|
|
41
|
+
* pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant
|
|
42
|
+
* serialize/parse round-trips when chained with other transformers.
|
|
43
|
+
*/
|
|
44
|
+
declare function shorthandCssDom(dom: ChildNode[], options?: ShorthandCssOptions): ChildNode[];
|
|
45
|
+
//#endregion
|
|
46
|
+
export { ShorthandCssOptions, shorthandCss, shorthandCssDom };
|
|
47
|
+
//# sourceMappingURL=shorthandCss.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shorthandCss.d.ts","names":[],"sources":["../../src/transformers/shorthandCss.ts"],"mappings":";;;;;AASA;UAAiB,mBAAA;;;;AAgCjB;;;EAzBE,IAAA;AAAA;;;;;AAkCF;;;;;;;;;;;;;;;;;;iBATgB,YAAA,CAAa,IAAA,UAAc,OAAA,GAAS,mBAAA;;;;;;iBASpC,eAAA,CAAgB,GAAA,EAAK,SAAA,IAAa,OAAA,GAAS,mBAAA,GAA2B,SAAA"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { parse } from "../utils/ast/parser.js";
|
|
2
|
+
import { walk } from "../utils/ast/walker.js";
|
|
3
|
+
import { serialize } from "../utils/ast/serializer.js";
|
|
4
|
+
import "../utils/ast/index.js";
|
|
5
|
+
import postcss from "postcss";
|
|
6
|
+
import safeParser from "postcss-safe-parser";
|
|
7
|
+
import mergeLonghand from "postcss-merge-longhand";
|
|
8
|
+
//#region src/transformers/shorthandCss.ts
|
|
9
|
+
/**
|
|
10
|
+
* Rewrite longhand CSS inside inline `style` attributes with shorthand
|
|
11
|
+
* syntax. Works with margin, padding, and border when all sides are
|
|
12
|
+
* specified.
|
|
13
|
+
*
|
|
14
|
+
* For example:
|
|
15
|
+
* `margin-left: 2px; margin-right: 2px; margin-top: 4px; margin-bottom: 4px`
|
|
16
|
+
* becomes:
|
|
17
|
+
* `margin: 4px 2px`
|
|
18
|
+
*
|
|
19
|
+
* @param html HTML string to transform.
|
|
20
|
+
* @param options Optional Maizzle options (`tags`).
|
|
21
|
+
* @returns The transformed HTML string.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* import { shorthandCss } from '@maizzle/framework'
|
|
25
|
+
*
|
|
26
|
+
* const out = shorthandCss(
|
|
27
|
+
* '<p style="margin-top: 4px; margin-right: 2px; margin-bottom: 4px; margin-left: 2px;">x</p>',
|
|
28
|
+
* { tags: ['p'] },
|
|
29
|
+
* )
|
|
30
|
+
*/
|
|
31
|
+
function shorthandCss(html, options = {}) {
|
|
32
|
+
return serialize(shorthandCssDom(parse(html), options));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* DOM-form of {@link shorthandCss} used by the internal transformer
|
|
36
|
+
* pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant
|
|
37
|
+
* serialize/parse round-trips when chained with other transformers.
|
|
38
|
+
*/
|
|
39
|
+
function shorthandCssDom(dom, options = {}) {
|
|
40
|
+
const allowedTags = options.tags ?? [];
|
|
41
|
+
const hasTagFilter = allowedTags.length > 0;
|
|
42
|
+
walk(dom, (node) => {
|
|
43
|
+
const el = node;
|
|
44
|
+
if (!el.attribs?.style) return;
|
|
45
|
+
if (hasTagFilter && !allowedTags.includes(el.name)) return;
|
|
46
|
+
const styleValue = el.attribs.style;
|
|
47
|
+
try {
|
|
48
|
+
const { css } = postcss().use(mergeLonghand).process(`div { ${styleValue} }`, { parser: safeParser });
|
|
49
|
+
const match = css.match(/div\s*\{\s*([^}]+)\s*\}/);
|
|
50
|
+
if (match && match[1]) {
|
|
51
|
+
const newStyle = match[1].trim();
|
|
52
|
+
if (newStyle !== styleValue) el.attribs.style = newStyle;
|
|
53
|
+
}
|
|
54
|
+
} catch {}
|
|
55
|
+
});
|
|
56
|
+
return dom;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { shorthandCss, shorthandCssDom };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=shorthandCss.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shorthandCss.js","names":[],"sources":["../../src/transformers/shorthandCss.ts"],"sourcesContent":["import postcss from 'postcss'\nimport safeParser from 'postcss-safe-parser'\nimport mergeLonghand from 'postcss-merge-longhand'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\n/**\n * Options for the `shorthandCss` transformer.\n */\nexport interface ShorthandCssOptions {\n /**\n * Restrict the transform to a list of HTML tag names. Omit to apply to\n * every element with a `style` attribute.\n *\n * @example ['td', 'div']\n */\n tags?: string[]\n}\n\n/**\n * Rewrite longhand CSS inside inline `style` attributes with shorthand\n * syntax. Works with margin, padding, and border when all sides are\n * specified.\n *\n * For example:\n * `margin-left: 2px; margin-right: 2px; margin-top: 4px; margin-bottom: 4px`\n * becomes:\n * `margin: 4px 2px`\n *\n * @param html HTML string to transform.\n * @param options Optional Maizzle options (`tags`).\n * @returns The transformed HTML string.\n *\n * @example\n * import { shorthandCss } from '@maizzle/framework'\n *\n * const out = shorthandCss(\n * '<p style=\"margin-top: 4px; margin-right: 2px; margin-bottom: 4px; margin-left: 2px;\">x</p>',\n * { tags: ['p'] },\n * )\n */\nexport function shorthandCss(html: string, options: ShorthandCssOptions = {}): string {\n return serialize(shorthandCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link shorthandCss} used by the internal transformer\n * pipeline. Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function shorthandCssDom(dom: ChildNode[], options: ShorthandCssOptions = {}): ChildNode[] {\n const allowedTags = options.tags ?? []\n const hasTagFilter = allowedTags.length > 0\n\n walk(dom, (node) => {\n const el = node as Element\n\n // Skip if no attribs or no style\n if (!el.attribs?.style) {\n return\n }\n\n // Skip if tag filter is active and this tag is not allowed\n if (hasTagFilter && !allowedTags.includes(el.name)) {\n return\n }\n\n const styleValue = el.attribs.style\n\n try {\n // Process the style with postcss-merge-longhand\n // Wrap in a dummy selector since postcss needs a rule\n const { css } = postcss()\n .use(mergeLonghand)\n .process(`div { ${styleValue} }`, { parser: safeParser })\n\n // Extract the content between the braces\n const match = css.match(/div\\s*\\{\\s*([^}]+)\\s*\\}/)\n if (match && match[1]) {\n const newStyle = match[1].trim()\n if (newStyle !== styleValue) {\n el.attribs.style = newStyle\n }\n }\n } catch {\n // If processing fails, keep the original style\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,aAAa,MAAc,UAA+B,EAAE,EAAU;CACpF,OAAO,UAAU,gBAAgB,MAAM,KAAK,EAAE,QAAQ,CAAC;;;;;;;AAQzD,SAAgB,gBAAgB,KAAkB,UAA+B,EAAE,EAAe;CAChG,MAAM,cAAc,QAAQ,QAAQ,EAAE;CACtC,MAAM,eAAe,YAAY,SAAS;CAE1C,KAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EAGX,IAAI,CAAC,GAAG,SAAS,OACf;EAIF,IAAI,gBAAgB,CAAC,YAAY,SAAS,GAAG,KAAK,EAChD;EAGF,MAAM,aAAa,GAAG,QAAQ;EAE9B,IAAI;GAGF,MAAM,EAAE,QAAQ,SAAS,CACtB,IAAI,cAAc,CAClB,QAAQ,SAAS,WAAW,KAAK,EAAE,QAAQ,YAAY,CAAC;GAG3D,MAAM,QAAQ,IAAI,MAAM,0BAA0B;GAClD,IAAI,SAAS,MAAM,IAAI;IACrB,MAAM,WAAW,MAAM,GAAG,MAAM;IAChC,IAAI,aAAa,YACf,GAAG,QAAQ,QAAQ;;UAGjB;GAGR;CAEF,OAAO"}
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
import { CssConfig } from "../types/config.js";
|
|
2
1
|
import { ChildNode } from "domhandler";
|
|
3
2
|
|
|
4
3
|
//#region src/transformers/sixHex.d.ts
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Converts 3-digit HEX color codes to 6-digit in `bgcolor` and `color`
|
|
5
|
+
* Convert 3-digit HEX color codes to 6-digit in `bgcolor` and `color`
|
|
9
6
|
* attributes, for better email client compatibility.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
8
|
+
* @param html HTML string to transform.
|
|
9
|
+
* @returns The transformed HTML string.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* import { sixHex } from '@maizzle/framework'
|
|
13
|
+
*
|
|
14
|
+
* const out = sixHex('<font color="#abc">x</font>')
|
|
15
|
+
*/
|
|
16
|
+
declare function sixHex(html: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* DOM-form of {@link sixHex} used by the internal transformer pipeline.
|
|
19
|
+
* Takes a parsed DOM, returns a parsed DOM — avoids redundant
|
|
20
|
+
* serialize/parse round-trips when chained with other transformers.
|
|
12
21
|
*/
|
|
13
|
-
declare function
|
|
22
|
+
declare function sixHexDom(dom: ChildNode[]): ChildNode[];
|
|
14
23
|
//#endregion
|
|
15
|
-
export { sixHex };
|
|
24
|
+
export { sixHex, sixHexDom };
|
|
16
25
|
//# sourceMappingURL=sixHex.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sixHex.d.ts","names":[],"sources":["../../src/transformers/sixHex.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"sixHex.d.ts","names":[],"sources":["../../src/transformers/sixHex.ts"],"mappings":";;;;;AAkBA;;;;;AASA;;;;;iBATgB,MAAA,CAAO,IAAA;;;;;;iBASP,SAAA,CAAU,GAAA,EAAK,SAAA,KAAc,SAAA"}
|