@stylewx/theme 0.1.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.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * theme → CSS 编译器。
3
+ * 把主题对象编译成一段仅含「微信兼容属性、无 class 依赖」的 CSS 字符串,
4
+ * 供 core 的 juice 内联管线使用。纯函数、无 DOM / Node 依赖。
5
+ */
6
+ import type { Theme } from './schema.js';
7
+ import type { ThemeTokens } from './schema.js';
8
+ /**
9
+ * 解析 block 声明值中的 token 引用。
10
+ * 与 resolveTokenReferences 不同的是:此处内置了 lineHeight 数字→无单位、block 间距等特例,
11
+ * 保证编译出的 CSS 值总是合法字符串。
12
+ */
13
+ export declare function resolveDeclValue(value: string, tokens: ThemeTokens): string;
14
+ /**
15
+ * 编译主题为 CSS 字符串。
16
+ * @param theme 已通过 themeSchema 校验的主题
17
+ * @returns 可直接交给 juice 的 CSS(不含 <style> 包裹)
18
+ */
19
+ export declare function compileThemeToCss(theme: Theme): string;
20
+ /**
21
+ * 生成「根容器」的基础内联样式,作为后代元素的继承默认值。
22
+ * 这些值直接内联到渲染的根节点上(不依赖 class / <style>)。
23
+ */
24
+ export declare function compileRootBaseStyle(theme: Theme): string;
25
+ //# sourceMappingURL=compiler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAW9C;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAK3E;AAgBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAGtD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CASzD"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * theme → CSS 编译器。
3
+ * 把主题对象编译成一段仅含「微信兼容属性、无 class 依赖」的 CSS 字符串,
4
+ * 供 core 的 juice 内联管线使用。纯函数、无 DOM / Node 依赖。
5
+ */
6
+ import { BLOCK_NAMES } from './schema.js';
7
+ import { resolveTokenReferences } from './tokens.js';
8
+ /** 把 token 值转为 CSS 片段(lineHeight 是数字,需追加单位;其余直接输出)。 */
9
+ function cssValueForToken(path, tokens) {
10
+ if (path === 'spacing.block' && tokens.spacing.block)
11
+ return tokens.spacing.block;
12
+ const value = tokens[path];
13
+ if (typeof value === 'number')
14
+ return String(value);
15
+ if (typeof value === 'string')
16
+ return value;
17
+ return '';
18
+ }
19
+ /**
20
+ * 解析 block 声明值中的 token 引用。
21
+ * 与 resolveTokenReferences 不同的是:此处内置了 lineHeight 数字→无单位、block 间距等特例,
22
+ * 保证编译出的 CSS 值总是合法字符串。
23
+ */
24
+ export function resolveDeclValue(value, tokens) {
25
+ return value.replace(/\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}/g, (_full, path) => cssValueForToken(path, tokens));
26
+ }
27
+ function buildBlockRules(theme) {
28
+ const rules = [];
29
+ for (const blockName of BLOCK_NAMES) {
30
+ const declarations = theme.blocks[blockName];
31
+ const entries = Object.entries(declarations);
32
+ if (entries.length === 0)
33
+ continue;
34
+ const css = entries
35
+ .map(([prop, value]) => `${prop}: ${resolveDeclValue(value, theme.tokens)};`)
36
+ .join(' ');
37
+ rules.push(`${blockName} { ${css} }`);
38
+ }
39
+ return rules;
40
+ }
41
+ /**
42
+ * 编译主题为 CSS 字符串。
43
+ * @param theme 已通过 themeSchema 校验的主题
44
+ * @returns 可直接交给 juice 的 CSS(不含 <style> 包裹)
45
+ */
46
+ export function compileThemeToCss(theme) {
47
+ const blockRules = buildBlockRules(theme);
48
+ return blockRules.join('\n');
49
+ }
50
+ /**
51
+ * 生成「根容器」的基础内联样式,作为后代元素的继承默认值。
52
+ * 这些值直接内联到渲染的根节点上(不依赖 class / <style>)。
53
+ */
54
+ export function compileRootBaseStyle(theme) {
55
+ const t = theme.tokens;
56
+ const parts = [];
57
+ parts.push(`font-family: ${t.fontFamily};`);
58
+ parts.push(`font-size: ${t.fontSize};`);
59
+ parts.push(`color: ${t.textColor};`);
60
+ parts.push(`line-height: ${t.lineHeight};`);
61
+ // 注意:不要添加 -webkit-text-size-adjust 等不在微信白名单内的属性,否则会被 validator 拦截。
62
+ return parts.join(' ');
63
+ }
64
+ //# sourceMappingURL=compiler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compiler.js","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAGpD,uDAAuD;AACvD,SAAS,gBAAgB,CAAC,IAAY,EAAE,MAAmB;IACzD,IAAI,IAAI,KAAK,eAAe,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAA;IACjF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAyB,CAAC,CAAA;IAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAA;IACnD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3C,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa,EAAE,MAAmB;IACjE,OAAO,KAAK,CAAC,OAAO,CAClB,iCAAiC,EACjC,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,CACxD,CAAA;AACH,CAAC;AAED,SAAS,eAAe,CAAC,KAAY;IACnC,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,MAAM,SAAS,IAAI,WAAW,EAAE,CAAC;QACpC,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;QAC5C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QAClC,MAAM,GAAG,GAAG,OAAO;aAChB,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;aAC5E,IAAI,CAAC,GAAG,CAAC,CAAA;QACZ,KAAK,CAAC,IAAI,CAAC,GAAG,SAAS,MAAM,GAAG,IAAI,CAAC,CAAA;IACvC,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAY;IAC5C,MAAM,UAAU,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;IACzC,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAY;IAC/C,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAA;IACtB,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,UAAU,GAAG,CAAC,CAAA;IAC3C,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAA;IACvC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,SAAS,GAAG,CAAC,CAAA;IACpC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,UAAU,GAAG,CAAC,CAAA;IAC3C,mEAAmE;IACnE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * 微信兼容 CSS 白名单(三档,基于真实微信实测校准)。
3
+ *
4
+ * 我们先前的「黑名单」是社区经验;这次改用真实公众号做了三层实测,结论清晰:
5
+ *
6
+ * 1) 草稿 API(draft/add → draft/get)——这是「持久化/净化」层:
7
+ * 保留 float/transform/animation/box-shadow/display:flex/opacity/top/z-index/gap/!important 等;
8
+ * **裁掉 position / filter**,以及结构层危险内容(<style>、<script>、on* 事件、javascript: 链接)。
9
+ * 2) 微信图文编辑器(ProseMirror,真实浏览器实测)——这是「显示/编辑」层:
10
+ * **非常宽容**,手动把含 position:absolute/filter/transform/box-shadow/… 的 HTML 粘进去,
11
+ * 编辑器 DOM 全部保留(含 position),且计算样式真实渲染(position=absolute、transform 生效……)。
12
+ * 即:编辑器本身**不裁剪**内联样式,裁剪发生在**草稿 API** 层。
13
+ * 3) Chromium 移动端(390px 视口 getComputedStyle 实测)——gray 属性全部能渲染。
14
+ *
15
+ * 因此对「无头排版 → 直接写草稿 API」的管线而言:position/filter 在 API 层就会被裁,不会到读者端;
16
+ * 编辑器虽然显示它们,但保存后仍会被 API 裁掉。故把 position/filter 列为 BANNED 是证据驱动的、正确且安全。
17
+ *
18
+ * 三档定义:
19
+ * - SAFE 放行,无提示:微信内联时稳定保留的基础排版属性。
20
+ * - GRAY 放行但提示 warning:微信草稿 API 实测**保留**、Chromium 能渲染;但读者端最终显示
21
+ * 仍需真机核对,故不硬禁止,只提示。
22
+ * - BANNED 硬禁止(error):实测微信 API 会过滤 / 存在明显风险的属性(position、filter 等)以及
23
+ * 结构层危险内容(style 标签、script 标签、on* 事件属性、javascript: 链接,见 validator)。
24
+ *
25
+ * 其余不在三个集合内的属性视为「unknown」,也**放行但提示**(证据表明微信很宽容,故不硬失败;
26
+ * 具体是否可用交由 validator 提示)。这比「一刀切禁止」更诚实、更贴近实测。
27
+ *
28
+ * 注意:本模块必须保持同构 —— 不 import 任何 DOM / Node 独有 API。
29
+ */
30
+ /** 基础安全属性(内联时微信稳定保留)。 */
31
+ export declare const CSS_PROPERTY_SAFE: ReadonlySet<string>;
32
+ /**
33
+ * 灰色属性(警告):微信草稿 API 实测**保留**,但编辑器粘贴 / 读者渲染不确定。
34
+ * 不再硬禁止,评估为 warning。
35
+ */
36
+ export declare const CSS_PROPERTY_GRAY: ReadonlySet<string>;
37
+ /**
38
+ * 硬禁止(error):实测微信会过滤,或存在明显风险。
39
+ * position / filter 已被真实微信草稿 API 实测为「过滤」;其余为同类风险属性。
40
+ */
41
+ export declare const CSS_PROPERTY_BANNED: ReadonlySet<string>;
42
+ /** 允许(放行)的属性集合 = SAFE ∪ GRAY。 */
43
+ export declare const CSS_PROPERTY_WHITELIST: ReadonlySet<string>;
44
+ export type CssPropertyTier = 'safe' | 'gray' | 'banned' | 'unknown';
45
+ /**
46
+ * 判断单个 CSS 属性的档位:
47
+ * - banned:微信实测会过滤/有风险(error)
48
+ * - gray:微信保留但编辑器/渲染不确定(warning)
49
+ * - safe:稳定保留(无提示)
50
+ * - unknown:未纳入已知白名单(放行 + 提示)
51
+ */
52
+ export declare function classifyCssProperty(property: string): CssPropertyTier;
53
+ /**
54
+ * 是否允许出现在主题中:仅硬禁止 banned(及通配符 / CSS 变量定义)。
55
+ * safe / gray / unknown 均放行(unknown 也在 validator 侧提示)。
56
+ */
57
+ export declare function isCssPropertyAllowed(property: string): boolean;
58
+ /**
59
+ * 校验某个 CSS 值里的「不安全/不可靠」内容,返回可读提示(warning 级),没有问题返回 null。
60
+ * 关注外部 url() 与 !important(微信对二者行为不稳定)。
61
+ */
62
+ export declare function findUnsafeCssValue(value: string): string | null;
63
+ /**
64
+ * 解析一段 `prop: value;` 形式的声明文本(用于内联 style 属性校验)。
65
+ * 返回属性→值 的映射;解析错误的条目会被跳过并可通过回调收集。
66
+ */
67
+ export declare function parseStyleDeclarations(style: string, onRaw?: (raw: string) => void): CssDeclaration[];
68
+ /** 供校验器/其它模块使用的声明结构。 */
69
+ export interface CssDeclaration {
70
+ property: string;
71
+ value: string;
72
+ }
73
+ //# sourceMappingURL=css-whitelist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css-whitelist.d.ts","sourceRoot":"","sources":["../src/css-whitelist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,yBAAyB;AACzB,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAkFhD,CAAA;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CA6DhD,CAAA;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAOlD,CAAA;AAEF,iCAAiC;AACjC,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAGrD,CAAA;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEpE;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAOrE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAI9D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc/D;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC5B,cAAc,EAAE,CAkBlB;AAED,wBAAwB;AACxB,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd"}
@@ -0,0 +1,269 @@
1
+ /**
2
+ * 微信兼容 CSS 白名单(三档,基于真实微信实测校准)。
3
+ *
4
+ * 我们先前的「黑名单」是社区经验;这次改用真实公众号做了三层实测,结论清晰:
5
+ *
6
+ * 1) 草稿 API(draft/add → draft/get)——这是「持久化/净化」层:
7
+ * 保留 float/transform/animation/box-shadow/display:flex/opacity/top/z-index/gap/!important 等;
8
+ * **裁掉 position / filter**,以及结构层危险内容(<style>、<script>、on* 事件、javascript: 链接)。
9
+ * 2) 微信图文编辑器(ProseMirror,真实浏览器实测)——这是「显示/编辑」层:
10
+ * **非常宽容**,手动把含 position:absolute/filter/transform/box-shadow/… 的 HTML 粘进去,
11
+ * 编辑器 DOM 全部保留(含 position),且计算样式真实渲染(position=absolute、transform 生效……)。
12
+ * 即:编辑器本身**不裁剪**内联样式,裁剪发生在**草稿 API** 层。
13
+ * 3) Chromium 移动端(390px 视口 getComputedStyle 实测)——gray 属性全部能渲染。
14
+ *
15
+ * 因此对「无头排版 → 直接写草稿 API」的管线而言:position/filter 在 API 层就会被裁,不会到读者端;
16
+ * 编辑器虽然显示它们,但保存后仍会被 API 裁掉。故把 position/filter 列为 BANNED 是证据驱动的、正确且安全。
17
+ *
18
+ * 三档定义:
19
+ * - SAFE 放行,无提示:微信内联时稳定保留的基础排版属性。
20
+ * - GRAY 放行但提示 warning:微信草稿 API 实测**保留**、Chromium 能渲染;但读者端最终显示
21
+ * 仍需真机核对,故不硬禁止,只提示。
22
+ * - BANNED 硬禁止(error):实测微信 API 会过滤 / 存在明显风险的属性(position、filter 等)以及
23
+ * 结构层危险内容(style 标签、script 标签、on* 事件属性、javascript: 链接,见 validator)。
24
+ *
25
+ * 其余不在三个集合内的属性视为「unknown」,也**放行但提示**(证据表明微信很宽容,故不硬失败;
26
+ * 具体是否可用交由 validator 提示)。这比「一刀切禁止」更诚实、更贴近实测。
27
+ *
28
+ * 注意:本模块必须保持同构 —— 不 import 任何 DOM / Node 独有 API。
29
+ */
30
+ /** 基础安全属性(内联时微信稳定保留)。 */
31
+ export const CSS_PROPERTY_SAFE = new Set([
32
+ // ---- 字体 & 文本 ----
33
+ 'color',
34
+ 'font',
35
+ 'font-family',
36
+ 'font-size',
37
+ 'font-style',
38
+ 'font-variant',
39
+ 'font-weight',
40
+ 'font-feature-settings',
41
+ 'letter-spacing',
42
+ 'line-height',
43
+ 'text-align',
44
+ 'text-align-last',
45
+ 'text-decoration',
46
+ 'text-decoration-color',
47
+ 'text-decoration-line',
48
+ 'text-decoration-style',
49
+ 'text-decoration-thickness',
50
+ 'text-indent',
51
+ 'text-transform',
52
+ 'text-shadow',
53
+ 'text-overflow',
54
+ 'text-rendering',
55
+ 'word-break',
56
+ 'word-spacing',
57
+ 'word-wrap',
58
+ 'overflow-wrap',
59
+ 'white-space',
60
+ 'vertical-align',
61
+ // ---- 盒模型 ----
62
+ 'box-sizing',
63
+ 'width',
64
+ 'min-width',
65
+ 'max-width',
66
+ 'height',
67
+ 'min-height',
68
+ 'max-height',
69
+ 'margin',
70
+ 'margin-top',
71
+ 'margin-right',
72
+ 'margin-bottom',
73
+ 'margin-left',
74
+ 'padding',
75
+ 'padding-top',
76
+ 'padding-right',
77
+ 'padding-bottom',
78
+ 'padding-left',
79
+ // ---- 背景(纯色)----
80
+ 'background-color',
81
+ // ---- 边框 & 圆角 ----
82
+ 'border',
83
+ 'border-top',
84
+ 'border-right',
85
+ 'border-bottom',
86
+ 'border-left',
87
+ 'border-color',
88
+ 'border-width',
89
+ 'border-style',
90
+ 'border-radius',
91
+ 'border-collapse',
92
+ 'border-spacing',
93
+ 'outline',
94
+ // ---- 布局 / 列表 / 其它 ----
95
+ 'display',
96
+ 'visibility',
97
+ 'overflow',
98
+ 'overflow-x',
99
+ 'overflow-y',
100
+ 'list-style',
101
+ 'list-style-type',
102
+ 'list-style-position',
103
+ 'list-style-image',
104
+ 'object-fit',
105
+ 'object-position',
106
+ 'table-layout',
107
+ 'direction',
108
+ 'writing-mode',
109
+ ]);
110
+ /**
111
+ * 灰色属性(警告):微信草稿 API 实测**保留**,但编辑器粘贴 / 读者渲染不确定。
112
+ * 不再硬禁止,评估为 warning。
113
+ */
114
+ export const CSS_PROPERTY_GRAY = new Set([
115
+ // 浮动
116
+ 'float',
117
+ 'clear',
118
+ // 变换 / 动画 / 过渡
119
+ 'transform',
120
+ 'transform-origin',
121
+ 'transform-style',
122
+ 'perspective',
123
+ 'perspective-origin',
124
+ 'backface-visibility',
125
+ 'animation',
126
+ 'animation-name',
127
+ 'animation-duration',
128
+ 'animation-delay',
129
+ 'animation-iteration-count',
130
+ 'animation-direction',
131
+ 'animation-timing-function',
132
+ 'animation-fill-mode',
133
+ 'transition',
134
+ 'transition-property',
135
+ 'transition-duration',
136
+ 'transition-delay',
137
+ 'transition-timing-function',
138
+ // 阴影 / 指针交互
139
+ 'box-shadow',
140
+ 'cursor',
141
+ 'user-select',
142
+ 'pointer-events',
143
+ 'touch-action',
144
+ // 定位(实测 top/left/z-index 保留)
145
+ 'top',
146
+ 'right',
147
+ 'bottom',
148
+ 'left',
149
+ 'z-index',
150
+ // flex / gap / 透明度 / 渐变背景
151
+ 'flex',
152
+ 'flex-direction',
153
+ 'flex-wrap',
154
+ 'flex-flow',
155
+ 'justify-content',
156
+ 'align-items',
157
+ 'align-content',
158
+ 'align-self',
159
+ 'flex-grow',
160
+ 'flex-shrink',
161
+ 'flex-basis',
162
+ 'order',
163
+ 'opacity',
164
+ 'gap',
165
+ 'row-gap',
166
+ 'column-gap',
167
+ 'background',
168
+ 'background-image',
169
+ 'background-position',
170
+ 'background-size',
171
+ 'background-repeat',
172
+ 'background-origin',
173
+ 'background-clip',
174
+ 'background-attachment',
175
+ ]);
176
+ /**
177
+ * 硬禁止(error):实测微信会过滤,或存在明显风险。
178
+ * position / filter 已被真实微信草稿 API 实测为「过滤」;其余为同类风险属性。
179
+ */
180
+ export const CSS_PROPERTY_BANNED = new Set([
181
+ 'position',
182
+ 'filter',
183
+ 'backdrop-filter',
184
+ 'mix-blend-mode',
185
+ 'scroll-snap-type',
186
+ 'scroll-behavior',
187
+ ]);
188
+ /** 允许(放行)的属性集合 = SAFE ∪ GRAY。 */
189
+ export const CSS_PROPERTY_WHITELIST = new Set([
190
+ ...CSS_PROPERTY_SAFE,
191
+ ...CSS_PROPERTY_GRAY,
192
+ ]);
193
+ /**
194
+ * 判断单个 CSS 属性的档位:
195
+ * - banned:微信实测会过滤/有风险(error)
196
+ * - gray:微信保留但编辑器/渲染不确定(warning)
197
+ * - safe:稳定保留(无提示)
198
+ * - unknown:未纳入已知白名单(放行 + 提示)
199
+ */
200
+ export function classifyCssProperty(property) {
201
+ const p = property.trim().toLowerCase();
202
+ if (!p || p === '*' || p.startsWith('--'))
203
+ return 'unknown';
204
+ if (CSS_PROPERTY_BANNED.has(p))
205
+ return 'banned';
206
+ if (CSS_PROPERTY_GRAY.has(p))
207
+ return 'gray';
208
+ if (CSS_PROPERTY_SAFE.has(p))
209
+ return 'safe';
210
+ return 'unknown';
211
+ }
212
+ /**
213
+ * 是否允许出现在主题中:仅硬禁止 banned(及通配符 / CSS 变量定义)。
214
+ * safe / gray / unknown 均放行(unknown 也在 validator 侧提示)。
215
+ */
216
+ export function isCssPropertyAllowed(property) {
217
+ const p = property.trim().toLowerCase();
218
+ if (!p || p === '*' || p.startsWith('--'))
219
+ return false;
220
+ return classifyCssProperty(property) !== 'banned';
221
+ }
222
+ /**
223
+ * 校验某个 CSS 值里的「不安全/不可靠」内容,返回可读提示(warning 级),没有问题返回 null。
224
+ * 关注外部 url() 与 !important(微信对二者行为不稳定)。
225
+ */
226
+ export function findUnsafeCssValue(value) {
227
+ const v = value.trim().toLowerCase();
228
+ if (v.includes('!important')) {
229
+ return '包含 !important,微信编辑器/渲染端行为不稳定,建议移除改用更具体的属性覆盖。';
230
+ }
231
+ const urlMatch = value.match(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/);
232
+ if (urlMatch) {
233
+ const raw = urlMatch[1] ?? '';
234
+ if (raw.startsWith('data:') || raw.startsWith('mmbiz.qpic.cn') || raw.startsWith('https://mmbiz.qpic.cn')) {
235
+ return null;
236
+ }
237
+ return 'CSS 值中引用了外部资源 URL,微信端可能无法加载或被清洗,请改用内联 data URI 或上传到微信素材库。';
238
+ }
239
+ return null;
240
+ }
241
+ /**
242
+ * 解析一段 `prop: value;` 形式的声明文本(用于内联 style 属性校验)。
243
+ * 返回属性→值 的映射;解析错误的条目会被跳过并可通过回调收集。
244
+ */
245
+ export function parseStyleDeclarations(style, onRaw) {
246
+ const declarations = [];
247
+ if (!style)
248
+ return declarations;
249
+ const segments = style.split(';');
250
+ for (const seg of segments) {
251
+ const trimmed = seg.trim();
252
+ if (!trimmed)
253
+ continue;
254
+ if (onRaw)
255
+ onRaw(trimmed);
256
+ const colon = trimmed.indexOf(':');
257
+ if (colon === -1) {
258
+ if (onRaw)
259
+ onRaw(trimmed);
260
+ continue;
261
+ }
262
+ const property = trimmed.slice(0, colon).trim();
263
+ const value = trimmed.slice(colon + 1).trim();
264
+ if (property && value)
265
+ declarations.push({ property, value });
266
+ }
267
+ return declarations;
268
+ }
269
+ //# sourceMappingURL=css-whitelist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css-whitelist.js","sourceRoot":"","sources":["../src/css-whitelist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export * from './schema.js';
2
+ export * from './tokens.js';
3
+ export * from './compiler.js';
4
+ export * from './css-whitelist.js';
5
+ export * from './presets.js';
6
+ export * from './wemd-presets.js';
7
+ export * from './validate.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,oBAAoB,CAAA;AAClC,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from './schema.js';
2
+ export * from './tokens.js';
3
+ export * from './compiler.js';
4
+ export * from './css-whitelist.js';
5
+ export * from './presets.js';
6
+ export * from './wemd-presets.js';
7
+ export * from './validate.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,oBAAoB,CAAA;AAClC,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 预置主题。
3
+ * 每个主题通过 tokens 定义配色/字体/间距,blocks 尽量使用 `{{token}}` 引用以保持一致性。
4
+ * description 面向「选主题的 Agent」,务必写清楚风格与适用场景。
5
+ */
6
+ import type { Theme } from './schema.js';
7
+ export declare const PRESET_THEMES: Theme[];
8
+ /** 按名字查找预置主题。 */
9
+ export declare function getPresetTheme(name: string): Theme | undefined;
10
+ //# sourceMappingURL=presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAc,MAAM,aAAa,CAAA;AAyUpD,eAAO,MAAM,aAAa,EAAE,KAAK,EAQhC,CAAA;AAED,iBAAiB;AACjB,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAE9D"}