@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,280 @@
1
+ /**
2
+ * 预置主题。
3
+ * 每个主题通过 tokens 定义配色/字体/间距,blocks 尽量使用 `{{token}}` 引用以保持一致性。
4
+ * description 面向「选主题的 Agent」,务必写清楚风格与适用场景。
5
+ */
6
+ import { WEMD_THEMES } from './wemd-presets.js';
7
+ /** 生成一组默认 block,可传入覆盖项做定制。 */
8
+ function defaultBlocks(p, overrides) {
9
+ const base = {
10
+ h1: {
11
+ color: '{{primaryColor}}',
12
+ 'font-size': '1.6em',
13
+ 'font-weight': '600',
14
+ 'line-height': '1.35',
15
+ 'margin': `0 0 {{spacing.block}}`,
16
+ },
17
+ h2: {
18
+ color: '{{primaryColor}}',
19
+ 'font-size': '1.35em',
20
+ 'font-weight': '600',
21
+ 'line-height': '1.4',
22
+ 'margin': `0 0 {{spacing.block}}`,
23
+ },
24
+ h3: {
25
+ color: '{{primaryColor}}',
26
+ 'font-size': '1.15em',
27
+ 'font-weight': '600',
28
+ 'line-height': '1.4',
29
+ 'margin': `0 0 {{spacing.block}}`,
30
+ },
31
+ p: {
32
+ color: '{{textColor}}',
33
+ 'font-size': '{{fontSize}}',
34
+ 'line-height': '{{lineHeight}}',
35
+ 'margin': `0 0 {{spacing.block}}`,
36
+ },
37
+ blockquote: {
38
+ 'border-left': `4px solid {{primaryColor}}`,
39
+ 'background-color': p.blockquoteBg,
40
+ color: p.blockquoteColor,
41
+ padding: '12px 16px',
42
+ margin: `0 0 {{spacing.block}}`,
43
+ 'border-radius': '4px',
44
+ },
45
+ ul: {
46
+ 'padding-left': '1.4em',
47
+ margin: `0 0 {{spacing.block}}`,
48
+ 'list-style': 'disc',
49
+ },
50
+ ol: {
51
+ 'padding-left': '1.4em',
52
+ margin: `0 0 {{spacing.block}}`,
53
+ },
54
+ li: {
55
+ 'line-height': '{{lineHeight}}',
56
+ margin: '0 0 6px',
57
+ },
58
+ code: {
59
+ 'font-family': 'Menlo, Consolas, "Courier New", monospace',
60
+ 'font-size': '0.9em',
61
+ 'background-color': p.codeBg,
62
+ color: p.codeColor,
63
+ padding: '2px 5px',
64
+ 'border-radius': '4px',
65
+ },
66
+ pre: {
67
+ 'background-color': p.codeBg,
68
+ padding: '16px',
69
+ 'border-radius': '6px',
70
+ 'overflow-x': 'auto',
71
+ 'font-size': '0.88em',
72
+ 'line-height': '1.6',
73
+ 'font-family': 'Menlo, Consolas, "Courier New", monospace',
74
+ color: p.codeColor,
75
+ margin: `0 0 {{spacing.block}}`,
76
+ },
77
+ img: {
78
+ 'max-width': '100%',
79
+ height: 'auto',
80
+ display: 'block',
81
+ 'border-radius': '4px',
82
+ margin: `0 auto {{spacing.block}}`,
83
+ },
84
+ figcaption: {
85
+ 'font-size': '0.85em',
86
+ color: p.weakText,
87
+ 'text-align': 'center',
88
+ 'margin-top': '8px',
89
+ },
90
+ hr: {
91
+ border: 'none',
92
+ 'border-top': `1px solid ${p.borderColor}`,
93
+ margin: '24px 0',
94
+ },
95
+ a: {
96
+ color: '{{primaryColor}}',
97
+ 'text-decoration': 'none',
98
+ 'border-bottom': `1px solid {{primaryColor}}`,
99
+ },
100
+ strong: {
101
+ color: '{{primaryColor}}',
102
+ 'font-weight': '600',
103
+ },
104
+ };
105
+ return mergeBlocks(base, overrides);
106
+ }
107
+ function mergeBlocks(base, overrides) {
108
+ const result = { ...base };
109
+ if (!overrides)
110
+ return result;
111
+ for (const [key, value] of Object.entries(overrides)) {
112
+ if (!value)
113
+ continue;
114
+ const k = key;
115
+ result[k] = { ...(result[k] ?? {}), ...value };
116
+ }
117
+ return result;
118
+ }
119
+ function build(name, description, p, tokens, blockOverrides) {
120
+ return {
121
+ name,
122
+ description,
123
+ tokens,
124
+ blocks: defaultBlocks(p, blockOverrides),
125
+ };
126
+ }
127
+ const safeFont = '-apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif';
128
+ /** 科技极简 —— 冷冽的蓝/青主色,留白多,适合技术、效率、产品类文章。 */
129
+ const techMinimal = build('tech-minimal', '科技极简:以冷峻的青蓝色为主,大量留白、细线条与克制的高对比,强调「清晰易读」。适合技术博客、效率工具、SaaS 产品介绍、测试与工程类文章。', {
130
+ primaryColor: '#0b6bff',
131
+ textColor: '#1f2329',
132
+ codeBg: '#f0f4ff',
133
+ codeColor: '#3b5b9a',
134
+ blockquoteBg: '#f5f9ff',
135
+ blockquoteColor: '#3a4a5a',
136
+ weakText: '#8a94a6',
137
+ borderColor: '#e2e8f0',
138
+ }, {
139
+ primaryColor: '#0b6bff',
140
+ textColor: '#1f2329',
141
+ fontSize: '15px',
142
+ lineHeight: 1.75,
143
+ fontFamily: safeFont,
144
+ spacing: { block: '16px' },
145
+ }, {
146
+ h1: { 'border-left': '5px solid #0b6bff', 'padding-left': '12px' },
147
+ h2: { 'border-left': '3px solid #0b6bff', 'padding-left': '10px' },
148
+ });
149
+ /** 商务 —— 稳重藏蓝 + 金色点缀,适合企业、财经、公司动态。 */
150
+ const business = build('business', '商务稳重:以藏蓝为主色、金色作点缀,字重稍高、行距适中,传达专业与信任感。适合企业宣传、行业报告、财经分析、公司动态类内容。', {
151
+ primaryColor: '#1f3864',
152
+ textColor: '#2b2b2b',
153
+ codeBg: '#f2f4f8',
154
+ codeColor: '#3f4a63',
155
+ blockquoteBg: '#f4f6fa',
156
+ blockquoteColor: '#59627a',
157
+ weakText: '#8a8f99',
158
+ borderColor: '#d9dee8',
159
+ }, {
160
+ primaryColor: '#1f3864',
161
+ textColor: '#2b2b2b',
162
+ fontSize: '15px',
163
+ lineHeight: 1.8,
164
+ fontFamily: safeFont,
165
+ spacing: { block: '16px' },
166
+ }, {
167
+ h1: { 'background-color': '#1f3864', color: '#ffffff', padding: '10px 14px', 'border-radius': '4px' },
168
+ h2: { 'border-bottom': '2px solid #1f3864', 'padding-bottom': '8px' },
169
+ });
170
+ /** 文艺杂志 —— 衬线感字体、暖棕/玫红,适合文学、生活、人文。 */
171
+ const magazine = build('magazine', '文艺杂志:暖棕与玫红调、衬线字体、宽松行距,如同杂志内页。适合文学、散文、生活感悟、文化评论、旅行笔记类内容。', {
172
+ primaryColor: '#b4546a',
173
+ textColor: '#3a3232',
174
+ codeBg: '#f6f0ee',
175
+ codeColor: '#7a5a5f',
176
+ blockquoteBg: '#faf3f1',
177
+ blockquoteColor: '#7d6a6a',
178
+ weakText: '#a89a9a',
179
+ borderColor: '#e8dbd7',
180
+ }, {
181
+ primaryColor: '#b4546a',
182
+ textColor: '#3a3232',
183
+ fontSize: '16px',
184
+ lineHeight: 2,
185
+ fontFamily: 'Georgia, "STZhongsong", "Songti SC", "SimSun", serif',
186
+ spacing: { block: '18px' },
187
+ }, {
188
+ h1: { 'text-align': 'center', 'letter-spacing': '2px' },
189
+ h2: { 'text-align': 'center', 'font-style': 'normal' },
190
+ p: { 'text-align': 'justify', 'text-indent': '2em' },
191
+ blockquote: { 'font-style': 'italic' },
192
+ });
193
+ /** 政务红 —— 正式、庄重的中国红,适合政务、党建、官方通知。 */
194
+ const govRed = build('gov-red', '政务庄重:以中国红为主色,字重较重、结构清晰,传递正式与权威感。适合政务发布、党建文章、官方通知、政策解读。', {
195
+ primaryColor: '#c8142c',
196
+ textColor: '#2e2e2e',
197
+ codeBg: '#f7f0f0',
198
+ codeColor: '#8a3a3a',
199
+ blockquoteBg: '#fdf2f3',
200
+ blockquoteColor: '#8a4a4a',
201
+ weakText: '#9a8a8a',
202
+ borderColor: '#e7d2d4',
203
+ }, {
204
+ primaryColor: '#c8142c',
205
+ textColor: '#2e2e2e',
206
+ fontSize: '16px',
207
+ lineHeight: 1.9,
208
+ fontFamily: safeFont,
209
+ spacing: { block: '16px' },
210
+ }, {
211
+ h1: { 'text-align': 'center', 'font-weight': '700' },
212
+ h2: { 'border-left': '5px solid #c8142c', 'padding-left': '12px' },
213
+ p: { 'text-indent': '2em' },
214
+ });
215
+ /** 学术 —— 中性、克制、多注释的论文风。 */
216
+ const academic = build('academic', '学术严谨:中性灰黑、极小装饰、高可读性,适合论文导读、研究报告、技术文档、方法论类内容。', {
217
+ primaryColor: '#005a8c',
218
+ textColor: '#1c1c1c',
219
+ codeBg: '#f4f4f4',
220
+ codeColor: '#333333',
221
+ blockquoteBg: '#f2f5f7',
222
+ blockquoteColor: '#4a5a63',
223
+ weakText: '#888888',
224
+ borderColor: '#d8d8d8',
225
+ }, {
226
+ primaryColor: '#005a8c',
227
+ textColor: '#1c1c1c',
228
+ fontSize: '14px',
229
+ lineHeight: 1.85,
230
+ fontFamily: 'Georgia, "Times New Roman", "Songti SC", serif',
231
+ spacing: { block: '14px' },
232
+ }, {
233
+ h1: { 'font-weight': '700', 'border-bottom': '2px solid #005a8c', 'padding-bottom': '8px' },
234
+ h2: { 'font-weight': '700', 'border-bottom': '1px solid #cccccc', 'padding-bottom': '6px' },
235
+ p: { 'text-align': 'justify' },
236
+ blockquote: { 'font-style': 'italic' },
237
+ });
238
+ /** 深色代码风 —— 深色底 + 高亮语法色,适合代码示例、极客向内容。 */
239
+ const darkCode = build('dark-code', '深色代码风:深色底、荧光高亮、等宽字体,沉浸感强,适合技术教程、代码赏析、极客与开发者向内容。注意:微信内不支持动态高亮,颜色为静态内联。', {
240
+ primaryColor: '#3ee0a4',
241
+ textColor: '#e7e7e7',
242
+ codeBg: '#1e1e1e',
243
+ codeColor: '#c9d1d9',
244
+ blockquoteBg: '#26262a',
245
+ blockquoteColor: '#b7b7c0',
246
+ weakText: '#9a9aa3',
247
+ borderColor: '#3a3a40',
248
+ }, {
249
+ primaryColor: '#3ee0a4',
250
+ textColor: '#e7e7e7',
251
+ fontSize: '15px',
252
+ lineHeight: 1.7,
253
+ fontFamily: safeFont,
254
+ spacing: { block: '16px' },
255
+ }, {
256
+ h1: { color: '#3ee0a4', 'border-bottom': '1px solid #2a2a30', 'padding-bottom': '8px' },
257
+ h2: { color: '#7cd1ff', 'border-bottom': '1px solid #2a2a30', 'padding-bottom': '6px' },
258
+ p: { color: '#e7e7e7' },
259
+ blockquote: { color: '#b7b7c0', 'border-left': '4px solid #3ee0a4' },
260
+ code: { color: '#f4b25b', 'background-color': '#2d2d33' },
261
+ pre: { color: '#c9d1d9', 'background-color': '#1e1e1e' },
262
+ a: { color: '#7cd1ff' },
263
+ strong: { color: '#3ee0a4' },
264
+ hr: { 'border-top': '1px solid #3a3a40' },
265
+ img: { 'border-radius': '6px' },
266
+ });
267
+ export const PRESET_THEMES = [
268
+ techMinimal,
269
+ business,
270
+ magazine,
271
+ govRed,
272
+ academic,
273
+ darkCode,
274
+ ...WEMD_THEMES,
275
+ ];
276
+ /** 按名字查找预置主题。 */
277
+ export function getPresetTheme(name) {
278
+ return PRESET_THEMES.find((t) => t.name === name);
279
+ }
280
+ //# sourceMappingURL=presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.js","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod';
2
+ /** 主题 block 允许出现的元素名(Markdown/rehype 输出中对应的标签)。 */
3
+ export declare const BLOCK_NAMES: readonly ["h1", "h2", "h3", "p", "blockquote", "ul", "ol", "li", "code", "pre", "img", "figcaption", "hr", "a", "strong"];
4
+ export type BlockName = (typeof BLOCK_NAMES)[number];
5
+ export declare const colorSchema: z.ZodString;
6
+ export declare const lengthSchema: z.ZodString;
7
+ export declare const themeBlocksSchema: z.ZodObject<{
8
+ h1: z.ZodRecord<z.ZodString, z.ZodString>;
9
+ h2: z.ZodRecord<z.ZodString, z.ZodString>;
10
+ h3: z.ZodRecord<z.ZodString, z.ZodString>;
11
+ p: z.ZodRecord<z.ZodString, z.ZodString>;
12
+ blockquote: z.ZodRecord<z.ZodString, z.ZodString>;
13
+ ul: z.ZodRecord<z.ZodString, z.ZodString>;
14
+ ol: z.ZodRecord<z.ZodString, z.ZodString>;
15
+ li: z.ZodRecord<z.ZodString, z.ZodString>;
16
+ code: z.ZodRecord<z.ZodString, z.ZodString>;
17
+ pre: z.ZodRecord<z.ZodString, z.ZodString>;
18
+ img: z.ZodRecord<z.ZodString, z.ZodString>;
19
+ figcaption: z.ZodRecord<z.ZodString, z.ZodString>;
20
+ hr: z.ZodRecord<z.ZodString, z.ZodString>;
21
+ a: z.ZodRecord<z.ZodString, z.ZodString>;
22
+ strong: z.ZodRecord<z.ZodString, z.ZodString>;
23
+ }, z.core.$strip>;
24
+ /** 主题 tokens —— 全局可复用的「设计变量」,block 声明里可用 `{{tokenName}}` 引用。 */
25
+ export declare const themeTokensSchema: z.ZodObject<{
26
+ primaryColor: z.ZodString;
27
+ textColor: z.ZodString;
28
+ fontSize: z.ZodString;
29
+ lineHeight: z.ZodNumber;
30
+ fontFamily: z.ZodString;
31
+ spacing: z.ZodObject<{
32
+ block: z.ZodString;
33
+ }, z.core.$strip>;
34
+ }, z.core.$strip>;
35
+ /**
36
+ * 主题 Schema。
37
+ * blocks 内每个声明键(CSS 属性)必须落在微信白名单内;值可通过 `{{token}}` 引用 tokens。
38
+ */
39
+ export declare const themeSchema: z.ZodObject<{
40
+ name: z.ZodString;
41
+ description: z.ZodString;
42
+ tokens: z.ZodObject<{
43
+ primaryColor: z.ZodString;
44
+ textColor: z.ZodString;
45
+ fontSize: z.ZodString;
46
+ lineHeight: z.ZodNumber;
47
+ fontFamily: z.ZodString;
48
+ spacing: z.ZodObject<{
49
+ block: z.ZodString;
50
+ }, z.core.$strip>;
51
+ }, z.core.$strip>;
52
+ blocks: z.ZodObject<{
53
+ h1: z.ZodRecord<z.ZodString, z.ZodString>;
54
+ h2: z.ZodRecord<z.ZodString, z.ZodString>;
55
+ h3: z.ZodRecord<z.ZodString, z.ZodString>;
56
+ p: z.ZodRecord<z.ZodString, z.ZodString>;
57
+ blockquote: z.ZodRecord<z.ZodString, z.ZodString>;
58
+ ul: z.ZodRecord<z.ZodString, z.ZodString>;
59
+ ol: z.ZodRecord<z.ZodString, z.ZodString>;
60
+ li: z.ZodRecord<z.ZodString, z.ZodString>;
61
+ code: z.ZodRecord<z.ZodString, z.ZodString>;
62
+ pre: z.ZodRecord<z.ZodString, z.ZodString>;
63
+ img: z.ZodRecord<z.ZodString, z.ZodString>;
64
+ figcaption: z.ZodRecord<z.ZodString, z.ZodString>;
65
+ hr: z.ZodRecord<z.ZodString, z.ZodString>;
66
+ a: z.ZodRecord<z.ZodString, z.ZodString>;
67
+ strong: z.ZodRecord<z.ZodString, z.ZodString>;
68
+ }, z.core.$strip>;
69
+ }, z.core.$strip>;
70
+ export type Theme = z.infer<typeof themeSchema>;
71
+ export type ThemeTokens = z.infer<typeof themeTokensSchema>;
72
+ export type ThemeBlock = Record<string, string>;
73
+ /** 导出 JSON Schema(供 LLM 结构化输出约束 / MCP tool 描述使用)。 */
74
+ export declare function themeToJsonSchema(): Record<string, unknown>;
75
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,mDAAmD;AACnD,eAAO,MAAM,WAAW,2HAgBd,CAAA;AACV,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAEpD,eAAO,MAAM,WAAW,aASrB,CAAA;AAEH,eAAO,MAAM,YAAY,aAE4C,CAAA;AAYrE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;iBAA2B,CAAA;AAEzD,gEAAgE;AAChE,eAAO,MAAM,iBAAiB;;;;;;;;;iBAS5B,CAAA;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkCpB,CAAA;AAEJ,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA;AAC/C,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAC3D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAE/C,qDAAqD;AACrD,wBAAgB,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE3D"}
package/dist/schema.js ADDED
@@ -0,0 +1,93 @@
1
+ import { z } from 'zod';
2
+ import { isCssPropertyAllowed } from './css-whitelist.js';
3
+ import { resolveTokenReferences } from './tokens.js';
4
+ /** 主题 block 允许出现的元素名(Markdown/rehype 输出中对应的标签)。 */
5
+ export const BLOCK_NAMES = [
6
+ 'h1',
7
+ 'h2',
8
+ 'h3',
9
+ 'p',
10
+ 'blockquote',
11
+ 'ul',
12
+ 'ol',
13
+ 'li',
14
+ 'code',
15
+ 'pre',
16
+ 'img',
17
+ 'figcaption',
18
+ 'hr',
19
+ 'a',
20
+ 'strong',
21
+ ];
22
+ export const colorSchema = z
23
+ .string()
24
+ .refine((v) => /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(v) ||
25
+ /^rgba?\([^)]+\)$/.test(v) ||
26
+ /^hsla?\([^)]+\)$/.test(v) ||
27
+ /^oklch\([^)]+\)$/.test(v), { message: '颜色值必须是十六进制、rgb/rgba、hsl/hsla 或 oklch 格式。' });
28
+ export const lengthSchema = z
29
+ .string()
30
+ .regex(/^\d+(\.\d+)?(px|em|rem|pt|%)$/, '尺寸必须带合法单位,例如 16px、1.5em。');
31
+ /** 各 block 的「声明值」必须是字符串字面量,或 `{{token}}` 引用。 */
32
+ const declarationValueSchema = z.string().min(1);
33
+ const blockSchema = z.record(declarationValueSchema, declarationValueSchema);
34
+ function buildBlockObjectSchema() {
35
+ const record = {};
36
+ for (const name of BLOCK_NAMES)
37
+ record[name] = blockSchema;
38
+ return z.object(record);
39
+ }
40
+ export const themeBlocksSchema = buildBlockObjectSchema();
41
+ /** 主题 tokens —— 全局可复用的「设计变量」,block 声明里可用 `{{tokenName}}` 引用。 */
42
+ export const themeTokensSchema = z.object({
43
+ primaryColor: colorSchema,
44
+ textColor: colorSchema,
45
+ fontSize: lengthSchema,
46
+ lineHeight: z.number().min(0.8).max(4),
47
+ fontFamily: z.string().min(1).max(200),
48
+ spacing: z.object({
49
+ block: lengthSchema,
50
+ }),
51
+ });
52
+ /**
53
+ * 主题 Schema。
54
+ * blocks 内每个声明键(CSS 属性)必须落在微信白名单内;值可通过 `{{token}}` 引用 tokens。
55
+ */
56
+ export const themeSchema = z
57
+ .object({
58
+ name: z.string().min(1).max(60),
59
+ description: z.string().min(1).max(500),
60
+ tokens: themeTokensSchema,
61
+ blocks: themeBlocksSchema,
62
+ })
63
+ .superRefine((theme, ctx) => {
64
+ for (const blockName of BLOCK_NAMES) {
65
+ const block = theme.blocks[blockName];
66
+ for (const [property, rawValue] of Object.entries(block)) {
67
+ const propPath = `blocks.${blockName}.${property}`;
68
+ // 仅对「微信真实会过滤」的属性硬禁止(position/filter 等);transform/animation/float
69
+ // 等为灰色属性(微信草稿 API 实测保留),不在主题层面判非法,交由 validator 提示。
70
+ if (!isCssPropertyAllowed(property)) {
71
+ ctx.addIssue({
72
+ code: 'custom',
73
+ path: [propPath],
74
+ message: `CSS 属性「${property}」已被真实微信实测会过滤(如 position / filter 等),请移除或改用微信保留的属性。`,
75
+ });
76
+ continue;
77
+ }
78
+ const resolved = resolveTokenReferences(rawValue, theme.tokens);
79
+ if (resolved.trim() === '') {
80
+ ctx.addIssue({
81
+ code: 'custom',
82
+ path: [propPath],
83
+ message: `属性「${property}」的值经过 token 引用解析后为空,请检查是否引用了非法 token。`,
84
+ });
85
+ }
86
+ }
87
+ }
88
+ });
89
+ /** 导出 JSON Schema(供 LLM 结构化输出约束 / MCP tool 描述使用)。 */
90
+ export function themeToJsonSchema() {
91
+ return z.toJSONSchema(themeSchema);
92
+ }
93
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=theme.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.test.d.ts","sourceRoot":"","sources":["../src/theme.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,163 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { compileThemeToCss, compileRootBaseStyle, getPresetTheme, PRESET_THEMES, resolveTokenReferences, validateTheme, themeToJsonSchema, themeSchema, isCssPropertyAllowed, classifyCssProperty, findUnsafeCssValue, hasTokenReferences, } from './index.js';
3
+ const validTheme = {
4
+ name: 'test-theme',
5
+ description: '用于测试的主题',
6
+ tokens: {
7
+ primaryColor: '#0b6bff',
8
+ textColor: '#1f2329',
9
+ fontSize: '15px',
10
+ lineHeight: 1.75,
11
+ fontFamily: 'sans-serif',
12
+ spacing: { block: '16px' },
13
+ },
14
+ blocks: {
15
+ h1: { color: '{{primaryColor}}', 'font-size': '1.6em', margin: '0 0 {{spacing.block}}' },
16
+ h2: {},
17
+ h3: {},
18
+ p: { color: '{{textColor}}', 'font-size': '{{fontSize}}', 'line-height': '{{lineHeight}}' },
19
+ blockquote: { 'border-left': '4px solid {{primaryColor}}' },
20
+ ul: {},
21
+ ol: {},
22
+ li: {},
23
+ code: { 'background-color': '#f2f2f2' },
24
+ pre: { 'background-color': '#f6f8fa' },
25
+ img: { 'max-width': '100%' },
26
+ figcaption: {},
27
+ hr: { 'border-top': '1px solid #ccc' },
28
+ a: { color: '{{primaryColor}}' },
29
+ strong: { 'font-weight': '600' },
30
+ },
31
+ };
32
+ describe('preset themes', () => {
33
+ it('至少提供 6 套预置主题,且每套都通过 schema 校验', () => {
34
+ expect(PRESET_THEMES.length).toBeGreaterThanOrEqual(6);
35
+ for (const theme of PRESET_THEMES) {
36
+ const result = validateTheme(theme);
37
+ expect(result.ok, `主题 ${theme.name} 应通过校验:${JSON.stringify(result.issues)}`).toBe(true);
38
+ expect(theme.description.length).toBeGreaterThan(10);
39
+ }
40
+ });
41
+ it('每套主题应包含全部 16 个 block', () => {
42
+ const names = [
43
+ 'h1', 'h2', 'h3', 'p', 'blockquote', 'ul', 'ol', 'li',
44
+ 'code', 'pre', 'img', 'figcaption', 'hr', 'a', 'strong',
45
+ ];
46
+ for (const theme of PRESET_THEMES) {
47
+ for (const n of names)
48
+ expect(theme.blocks[n], `${theme.name} 缺 block ${n}`).toBeDefined();
49
+ }
50
+ });
51
+ it('可通过名字查找预置主题', () => {
52
+ expect(getPresetTheme('tech-minimal')?.name).toBe('tech-minimal');
53
+ expect(getPresetTheme('not-exist')).toBeUndefined();
54
+ });
55
+ });
56
+ describe('validateTheme', () => {
57
+ it('合法主题通过', () => {
58
+ const r = validateTheme(validTheme);
59
+ expect(r.ok).toBe(true);
60
+ expect(r.issues).toEqual([]);
61
+ });
62
+ it('包含真实微信会过滤的属性(position)时失败并给出可读提示', () => {
63
+ const bad = structuredClone(validTheme);
64
+ bad.blocks.p['position'] = 'absolute';
65
+ const r = validateTheme(bad);
66
+ expect(r.ok).toBe(false);
67
+ const issue = r.issues.find((i) => i.path.includes('position'));
68
+ expect(issue).toBeDefined();
69
+ expect(issue?.message).toContain('过滤');
70
+ });
71
+ it('灰色属性(transform)不判主题非法,但归类为 gray', () => {
72
+ const ok = structuredClone(validTheme);
73
+ ok.blocks.h1['transform'] = 'rotate(3deg)';
74
+ const r = validateTheme(ok);
75
+ expect(r.ok).toBe(true); // 不再硬禁止
76
+ expect(classifyCssProperty('transform')).toBe('gray');
77
+ });
78
+ it('!important 不再让主题非法(降级为 validator 层 warning)', () => {
79
+ const bad = structuredClone(validTheme);
80
+ bad.blocks.h1['color'] = '{{primaryColor}} !important';
81
+ const r = validateTheme(bad);
82
+ expect(r.ok).toBe(true);
83
+ });
84
+ });
85
+ describe('themeToJsonSchema', () => {
86
+ it('导出合法 JSON Schema,包含 name/description/tokens/blocks', () => {
87
+ const schema = themeToJsonSchema();
88
+ expect(schema.type).toBe('object');
89
+ const props = schema.properties;
90
+ expect(props).toHaveProperty('name');
91
+ expect(props).toHaveProperty('description');
92
+ expect(props).toHaveProperty('tokens');
93
+ expect(props).toHaveProperty('blocks');
94
+ });
95
+ });
96
+ describe('compileThemeToCss', () => {
97
+ it('能解析 token 引用,输出中不残留 {{ }}', () => {
98
+ const css = compileThemeToCss(validTheme);
99
+ expect(css).not.toContain('{{');
100
+ expect(css).toContain('h1');
101
+ expect(css).toContain('#0b6bff');
102
+ expect(css).toContain('line-height: 1.75');
103
+ });
104
+ it('root base style 包含继承默认值', () => {
105
+ const base = compileRootBaseStyle(validTheme);
106
+ expect(base).toContain('font-family: sans-serif');
107
+ expect(base).toContain('font-size: 15px');
108
+ expect(base).toContain('color: #1f2329');
109
+ expect(base).toContain('line-height: 1.75');
110
+ });
111
+ });
112
+ describe('token 系统', () => {
113
+ it('解析 token 引用', () => {
114
+ expect(resolveTokenReferences('0 0 {{spacing.block}}', validTheme.tokens)).toBe('0 0 16px');
115
+ expect(resolveTokenReferences('{{rowColor}}', validTheme.tokens)).toBe('');
116
+ });
117
+ it('检测 token 引用存在', () => {
118
+ expect(hasTokenReferences('{{primaryColor}}')).toBe(true);
119
+ expect(hasTokenReferences('#0b6bff')).toBe(false);
120
+ });
121
+ });
122
+ describe('CSS 白名单', () => {
123
+ it('白名单允许安全属性', () => {
124
+ expect(isCssPropertyAllowed('color')).toBe(true);
125
+ expect(isCssPropertyAllowed('font-size')).toBe(true);
126
+ expect(isCssPropertyAllowed('margin')).toBe(true);
127
+ expect(isCssPropertyAllowed('border-radius')).toBe(true);
128
+ });
129
+ it('真实微信会过滤的属性(banned)被拒绝', () => {
130
+ for (const prop of ['position', 'filter', 'backdrop-filter']) {
131
+ expect(isCssPropertyAllowed(prop), `${prop} 应被禁止`).toBe(false);
132
+ expect(classifyCssProperty(prop)).toBe('banned');
133
+ }
134
+ });
135
+ it('灰色属性(allow + warning)被放行', () => {
136
+ for (const prop of ['transform', 'animation', 'float', 'box-shadow', 'transition', 'opacity', 'top', 'z-index']) {
137
+ expect(isCssPropertyAllowed(prop), `${prop} 应可放行`).toBe(true);
138
+ expect(classifyCssProperty(prop)).toBe('gray');
139
+ }
140
+ });
141
+ it('拒绝通配符与 CSS 变量定义', () => {
142
+ expect(isCssPropertyAllowed('*')).toBe(false);
143
+ expect(isCssPropertyAllowed('--bg')).toBe(false);
144
+ });
145
+ it('findUnsafeCssValue 识别外部 url()', () => {
146
+ expect(findUnsafeCssValue('url(https://evil.com/a.png)')).not.toBeNull();
147
+ expect(findUnsafeCssValue('url(data:image/png;base64,xxx)')).toBeNull();
148
+ expect(findUnsafeCssValue('red')).toBeNull();
149
+ });
150
+ });
151
+ describe('themeSchema 类型解析', () => {
152
+ it('lineHeight 越界被拒绝', () => {
153
+ const bad = structuredClone(validTheme);
154
+ bad.tokens.lineHeight = 5;
155
+ expect(themeSchema.safeParse(bad).success).toBe(false);
156
+ });
157
+ it('无效颜色被拒绝', () => {
158
+ const bad = structuredClone(validTheme);
159
+ bad.tokens.primaryColor = 'not-a-color';
160
+ expect(themeSchema.safeParse(bad).success).toBe(false);
161
+ });
162
+ });
163
+ //# sourceMappingURL=theme.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.test.js","sourceRoot":"","sources":["../src/theme.test.ts"],"names":[],"mappings":"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"}