@zzclub/pipeline 0.10.0 → 0.10.2
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/README.md +113 -8
- package/package.json +4 -4
- package/src/adapter-types.ts +21 -0
- package/src/adapters/builtin-markdown-renderer.ts +7 -0
- package/src/cli.ts +5 -1
- package/src/commands/ingest-handoff.ts +1 -21
- package/src/commands/prepare-finalize.ts +2 -1
- package/src/commands/prepare.ts +30 -19
- package/src/commands/publish.ts +2 -3
- package/src/commands/reconcile.ts +3 -5
- package/src/commands/render.ts +2 -1
- package/src/commands/republish.ts +2 -3
- package/src/commands/reset.ts +9 -1
- package/src/commands/review.ts +3 -2
- package/src/commands/sync-blog.ts +2 -1
- package/src/commands/wechat-export.test.ts +41 -0
- package/src/commands/wechat-export.ts +156 -20
- package/src/commands/wechat-preview.ts +160 -0
- package/src/config.ts +12 -0
- package/src/imgx/runtime.ts +31 -3
- package/src/logger.test.ts +56 -0
- package/src/logger.ts +178 -0
- package/src/plugins.ts +2 -0
- package/src/providers/blog.ts +1 -2
- package/src/providers/index.ts +35 -3
- package/src/publish-targets.ts +4 -5
- package/src/routes.ts +9 -1
- package/src/schema/config.ts +2 -0
- package/src/state.ts +24 -0
- package/src/task-manager.test.ts +57 -0
- package/src/task-manager.ts +23 -5
- package/src/wechat-preview/assets/browser-dist/.vite/manifest.json +1 -302
- package/src/wechat-preview/assets/browser-dist/editor-export.js +500 -958
- package/src/wechat-preview/browser/assets.d.ts +9 -0
- package/src/wechat-preview/browser/editor-export.css +5 -326
- package/src/wechat-preview/browser/editor-export.ts +30 -65
- package/src/wechat-preview/bundle-stale.test.ts +52 -0
- package/src/wechat-preview/fixtures/all-nodes.md +68 -0
- package/src/wechat-preview/index.test.ts +17 -0
- package/src/wechat-preview/index.ts +424 -180
- package/src/wechat-preview/server/client.ts +164 -0
- package/src/wechat-preview/server/http.ts +229 -0
- package/src/wechat-preview/server/index.ts +33 -0
- package/src/wechat-preview/server/local-file.test.ts +52 -0
- package/src/wechat-preview/server/local-file.ts +116 -0
- package/src/wechat-preview/server/paths.ts +80 -0
- package/src/wechat-preview/server/registry.test.ts +60 -0
- package/src/wechat-preview/server/registry.ts +118 -0
- package/src/wechat-preview/server/singleton.ts +118 -0
- package/src/wechat-preview/server/types.ts +61 -0
- package/src/wechat-preview/server/ui.ts +324 -0
- package/src/wechat-preview/themes.ts +58 -45
- package/src/wechat-preview/wechat-export.integration.test.ts +94 -0
- package/src/wechat-preview/wechat-renderer.links.test.ts +44 -0
- package/src/wechat-preview/wechat-renderer.test.ts +50 -0
- package/src/wechat-preview/wechat-renderer.ts +698 -0
- package/src/wechat-preview.test.ts +25 -41
- package/src/workflow-materials.test.ts +52 -1
- package/src/workflow-materials.ts +12 -0
- package/src/workflow.test.ts +105 -5
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/style-vhHYSP8T.css +0 -1
- package/src/wechat-preview/assets/fonts/SweiCurveLegCJKsc-Regular.woff +0 -0
- package/src/wechat-preview/wechat-formatter.test.ts +0 -287
- package/src/wechat-preview/wechat-formatter.ts +0 -1531
|
@@ -1,330 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Thin re-export for tooling that still points at browser/editor-export.css.
|
|
3
|
+
* Canonical source: package @zzclub/milkdown-article-style (article.css).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* --font-base = 16px (body)
|
|
8
|
-
* --leading = 2.0 (body line-height ratio)
|
|
9
|
-
* --brand = #ca6093
|
|
10
|
-
* --text = #2c2c2c (warm black)
|
|
11
|
-
*
|
|
12
|
-
* When converting to WeChat inline styles, resolve each var() to its
|
|
13
|
-
* computed value. The token layer makes future rebranding a one-line edit.
|
|
5
|
+
* The Vite export entry imports the package directly (?raw does not resolve
|
|
6
|
+
* @import). Do not add rules here.
|
|
14
7
|
*/
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
/* ── Core tokens ── */
|
|
18
|
-
--font-base: 16px;
|
|
19
|
-
--font-sans: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
|
20
|
-
-apple-system, "SF Pro Text", "Noto Sans SC", sans-serif;
|
|
21
|
-
--font-mono: "JetBrains Mono", "Fira Code", "SF Mono", "Consolas",
|
|
22
|
-
"Monaco", "Courier New", monospace;
|
|
23
|
-
--font-serif: "Noto Serif SC", "Source Han Serif SC", "Songti SC",
|
|
24
|
-
"STSong", "Georgia", serif;
|
|
25
|
-
|
|
26
|
-
--leading: 2.0;
|
|
27
|
-
|
|
28
|
-
--brand: #ca6093;
|
|
29
|
-
--brand-soft: #d4789e; /* hover / secondary accent */
|
|
30
|
-
--brand-bg: rgba(202, 96, 147, 0.08);
|
|
31
|
-
--brand-bg-deep: rgba(202, 96, 147, 0.14);
|
|
32
|
-
|
|
33
|
-
--text: #2c2c2c; /* warm black, never pure #000 */
|
|
34
|
-
--text-soft: #8c8c8c; /* captions, meta */
|
|
35
|
-
--text-mid: #5c5c5c; /* blockquote, secondary text */
|
|
36
|
-
|
|
37
|
-
--border: #e8e4e5; /* warm gray */
|
|
38
|
-
--divider: #d8d0d3; /* slightly warmer than border */
|
|
39
|
-
--bg-page: #fefefe;
|
|
40
|
-
--bg-warm: #faf8f9; /* subtle warm tint for blockquote */
|
|
41
|
-
|
|
42
|
-
--radius-sm: 3px;
|
|
43
|
-
--radius-md: 6px;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
47
|
-
Container
|
|
48
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
49
|
-
|
|
50
|
-
.milkdown .editor {
|
|
51
|
-
max-width: 680px;
|
|
52
|
-
margin: 0 auto;
|
|
53
|
-
padding: 48px 24px 80px;
|
|
54
|
-
font-family: var(--font-sans);
|
|
55
|
-
font-size: var(--font-base);
|
|
56
|
-
line-height: var(--leading);
|
|
57
|
-
color: var(--text);
|
|
58
|
-
text-rendering: optimizeLegibility;
|
|
59
|
-
-webkit-font-smoothing: antialiased;
|
|
60
|
-
-moz-osx-font-smoothing: grayscale;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
64
|
-
Paragraph
|
|
65
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
66
|
-
|
|
67
|
-
.milkdown .editor p {
|
|
68
|
-
margin: 0 0 1.4em 0;
|
|
69
|
-
line-height: var(--leading);
|
|
70
|
-
text-align: justify;
|
|
71
|
-
hyphens: auto;
|
|
72
|
-
word-break: break-all;
|
|
73
|
-
overflow-wrap: break-word;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.milkdown .editor p:first-child {
|
|
77
|
-
margin-top: 0;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.milkdown .editor p:last-child {
|
|
81
|
-
margin-bottom: 0;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
85
|
-
Headings
|
|
86
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
87
|
-
|
|
88
|
-
.milkdown .editor h1 {
|
|
89
|
-
font-size: 1.625rem; /* 26px */
|
|
90
|
-
font-weight: 700;
|
|
91
|
-
line-height: 1.3;
|
|
92
|
-
margin: 3em 0 1em 0;
|
|
93
|
-
color: var(--text);
|
|
94
|
-
letter-spacing: -0.01em;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/* ── h2: brand color, clean style ── */
|
|
98
|
-
|
|
99
|
-
.milkdown .editor h2 {
|
|
100
|
-
font-size: 1.25rem; /* 20px */
|
|
101
|
-
font-weight: 700;
|
|
102
|
-
line-height: 1.35;
|
|
103
|
-
margin: 2.4em 0 0.9em 0;
|
|
104
|
-
color: var(--brand);
|
|
105
|
-
letter-spacing: 0.02em;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/* ── h3: warm black, semi-bold, underlined by a thin rule ── */
|
|
109
|
-
|
|
110
|
-
.milkdown .editor h3 {
|
|
111
|
-
font-size: 1.125rem; /* 18px */
|
|
112
|
-
font-weight: 600;
|
|
113
|
-
line-height: 1.45;
|
|
114
|
-
margin: 2em 0 0.65em 0;
|
|
115
|
-
padding-bottom: 0.5em;
|
|
116
|
-
color: var(--text);
|
|
117
|
-
letter-spacing: 0.015em;
|
|
118
|
-
border-bottom: 1px solid var(--border);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
122
|
-
Bold — brand-colored emphasis
|
|
123
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
124
|
-
|
|
125
|
-
.milkdown .editor strong,
|
|
126
|
-
.milkdown .editor b {
|
|
127
|
-
font-weight: 700;
|
|
128
|
-
color: var(--text);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
132
|
-
Links
|
|
133
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
134
|
-
|
|
135
|
-
.milkdown .editor a {
|
|
136
|
-
color: var(--brand);
|
|
137
|
-
text-decoration: none;
|
|
138
|
-
border-bottom: 1px solid var(--brand-soft);
|
|
139
|
-
transition: border-color 0.15s;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.milkdown .editor a:hover {
|
|
143
|
-
border-bottom-color: var(--brand);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
147
|
-
Inline code
|
|
148
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
149
|
-
|
|
150
|
-
.milkdown .editor code {
|
|
151
|
-
font-family: var(--font-mono);
|
|
152
|
-
font-size: 0.875em;
|
|
153
|
-
color: var(--brand);
|
|
154
|
-
background: var(--brand-bg);
|
|
155
|
-
padding: 2px 6px;
|
|
156
|
-
border-radius: var(--radius-sm);
|
|
157
|
-
word-break: break-all;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.milkdown .editor pre {
|
|
161
|
-
background: #f5f2f3;
|
|
162
|
-
border: 1px solid var(--border);
|
|
163
|
-
border-radius: var(--radius-md);
|
|
164
|
-
padding: 1.2em 1.3em;
|
|
165
|
-
margin: 1.5em 0;
|
|
166
|
-
overflow-x: auto;
|
|
167
|
-
line-height: 1.65;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.milkdown .editor pre code {
|
|
171
|
-
background: none;
|
|
172
|
-
padding: 0;
|
|
173
|
-
font-size: 0.875em;
|
|
174
|
-
color: var(--text);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
178
|
-
Image caption
|
|
179
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
180
|
-
|
|
181
|
-
.milkdown .editor figure {
|
|
182
|
-
margin: 1.6em auto;
|
|
183
|
-
text-align: center;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.milkdown .editor figure img {
|
|
187
|
-
display: block;
|
|
188
|
-
max-width: 100%;
|
|
189
|
-
margin: 0 auto;
|
|
190
|
-
border-radius: var(--radius-sm);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.milkdown .editor figure figcaption,
|
|
194
|
-
.milkdown .editor .img-caption {
|
|
195
|
-
display: block;
|
|
196
|
-
margin-top: 10px;
|
|
197
|
-
font-size: 0.875rem; /* 14px */
|
|
198
|
-
line-height: 1.6;
|
|
199
|
-
color: var(--text-soft);
|
|
200
|
-
text-align: center;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/* For standalone images (not wrapped in figure) */
|
|
204
|
-
.milkdown .editor p:has(img) + p.img-caption,
|
|
205
|
-
.milkdown .editor img + .img-caption {
|
|
206
|
-
display: block;
|
|
207
|
-
margin-top: 8px;
|
|
208
|
-
font-size: 0.875rem;
|
|
209
|
-
color: var(--text-soft);
|
|
210
|
-
text-align: center;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/* Standalone image between paragraphs */
|
|
214
|
-
.milkdown .editor p img {
|
|
215
|
-
display: block;
|
|
216
|
-
max-width: 100%;
|
|
217
|
-
margin: 0 auto;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
221
|
-
Divider
|
|
222
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
223
|
-
|
|
224
|
-
.milkdown .editor hr {
|
|
225
|
-
display: block;
|
|
226
|
-
height: 0;
|
|
227
|
-
border: none;
|
|
228
|
-
margin: 2.2em 0;
|
|
229
|
-
text-align: center;
|
|
230
|
-
/* thin warm line */
|
|
231
|
-
border-top: 1px solid var(--divider);
|
|
232
|
-
padding: 0;
|
|
233
|
-
background: transparent;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/*
|
|
237
|
-
* Decorative centered-dot divider variant.
|
|
238
|
-
* Override by adding class="hr-dots" on the <hr>.
|
|
239
|
-
*/
|
|
240
|
-
.milkdown .editor hr.hr-dots {
|
|
241
|
-
border-top: none;
|
|
242
|
-
line-height: 1;
|
|
243
|
-
letter-spacing: 0.6em;
|
|
244
|
-
color: var(--text-soft);
|
|
245
|
-
font-size: 0.75rem;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.milkdown .editor hr.hr-dots::after {
|
|
249
|
-
content: "· · ·";
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
253
|
-
Blockquote
|
|
254
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
255
|
-
|
|
256
|
-
.milkdown .editor blockquote {
|
|
257
|
-
margin: 1.5em 0;
|
|
258
|
-
padding: 0.8em 1em 0.8em 1.15em;
|
|
259
|
-
border-left: 3px solid var(--brand-soft);
|
|
260
|
-
background: var(--bg-warm);
|
|
261
|
-
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
.milkdown .editor blockquote p {
|
|
265
|
-
font-size: 0.9375rem; /* 15px */
|
|
266
|
-
line-height: 1.85;
|
|
267
|
-
color: var(--text-mid);
|
|
268
|
-
margin: 0.45em 0;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
272
|
-
Lists
|
|
273
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
274
|
-
|
|
275
|
-
.milkdown .editor ul,
|
|
276
|
-
.milkdown .editor ol {
|
|
277
|
-
margin: 1.1em 0;
|
|
278
|
-
padding-left: 1.5em;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.milkdown .editor li {
|
|
282
|
-
margin: 0.5em 0;
|
|
283
|
-
line-height: 1.85;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
.milkdown .editor li > p {
|
|
287
|
-
margin: 0.2em 0;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
291
|
-
Tables
|
|
292
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
293
|
-
|
|
294
|
-
.milkdown .editor table {
|
|
295
|
-
width: 100%;
|
|
296
|
-
margin: 1.4em 0;
|
|
297
|
-
border-collapse: collapse;
|
|
298
|
-
font-size: 0.9375rem;
|
|
299
|
-
line-height: 1.75;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.milkdown .editor th,
|
|
303
|
-
.milkdown .editor td {
|
|
304
|
-
padding: 10px 14px;
|
|
305
|
-
border: 1px solid var(--border);
|
|
306
|
-
text-align: left;
|
|
307
|
-
vertical-align: top;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
.milkdown .editor th {
|
|
311
|
-
color: var(--text);
|
|
312
|
-
background: var(--brand-bg);
|
|
313
|
-
font-weight: 700;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/* ══════════════════════════════════════════════════════════════════════
|
|
317
|
-
Responsive
|
|
318
|
-
══════════════════════════════════════════════════════════════════════ */
|
|
319
|
-
|
|
320
|
-
@media (max-width: 768px) {
|
|
321
|
-
.milkdown .editor {
|
|
322
|
-
max-width: 100%;
|
|
323
|
-
padding: 28px 20px 64px;
|
|
324
|
-
font-size: 15px;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
.milkdown .editor h1 { font-size: 1.4rem; }
|
|
328
|
-
.milkdown .editor h2 { font-size: 1.15rem; }
|
|
329
|
-
.milkdown .editor h3 { font-size: 1.05rem; }
|
|
330
|
-
}
|
|
9
|
+
@import "@zzclub/milkdown-article-style/article.css";
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import "@milkdown/
|
|
4
|
-
import "@milkdown/
|
|
5
|
-
import "
|
|
6
|
-
import {
|
|
1
|
+
import { defaultValueCtx, Editor, rootCtx } from "@milkdown/kit/core";
|
|
2
|
+
import { commonmark } from "@milkdown/kit/preset/commonmark";
|
|
3
|
+
import { gfm } from "@milkdown/kit/preset/gfm";
|
|
4
|
+
import { getHTML } from "@milkdown/kit/utils";
|
|
5
|
+
import articleCss from "@zzclub/milkdown-article-style/article.css?raw";
|
|
6
|
+
import { renderWechatHtml } from "../wechat-renderer";
|
|
7
|
+
import type { WechatExportTheme } from "../themes";
|
|
7
8
|
|
|
8
9
|
interface BrowserPayload {
|
|
9
10
|
markdown: string;
|
|
10
11
|
editorVars: Record<string, string>;
|
|
11
12
|
exportTheme: WechatExportTheme;
|
|
13
|
+
customCss?: string;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
declare global {
|
|
@@ -17,11 +19,11 @@ declare global {
|
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
function setResult(
|
|
23
|
+
result:
|
|
24
|
+
| { status: "success"; html: string; semanticHtml?: string }
|
|
25
|
+
| { status: "error"; error: string },
|
|
26
|
+
): void {
|
|
25
27
|
const el = document.getElementById("zzhub-wechat-export-result");
|
|
26
28
|
if (!el) {
|
|
27
29
|
return;
|
|
@@ -29,72 +31,35 @@ function setResult(result: { status: "success"; html: string } | { status: "erro
|
|
|
29
31
|
el.textContent = JSON.stringify(result);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
function doubleRaf(timeoutMs = 1500): Promise<void> {
|
|
33
|
-
return new Promise((resolve) => {
|
|
34
|
-
let done = false;
|
|
35
|
-
const timer = setTimeout(() => {
|
|
36
|
-
if (done) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
done = true;
|
|
40
|
-
resolve();
|
|
41
|
-
}, timeoutMs);
|
|
42
|
-
|
|
43
|
-
requestAnimationFrame(() => {
|
|
44
|
-
requestAnimationFrame(() => {
|
|
45
|
-
if (done) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
done = true;
|
|
49
|
-
clearTimeout(timer);
|
|
50
|
-
resolve();
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function applyEditorVars(vars: Record<string, string>): void {
|
|
57
|
-
const root = document.documentElement;
|
|
58
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
59
|
-
root.style.setProperty(key, value);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
34
|
async function main(): Promise<void> {
|
|
64
35
|
const payload = window.__ZZHUB_WECHAT_PAYLOAD__;
|
|
65
36
|
if (!payload) {
|
|
66
37
|
throw new Error("Missing __ZZHUB_WECHAT_PAYLOAD__");
|
|
67
38
|
}
|
|
68
39
|
|
|
69
|
-
applyEditorVars(payload.editorVars);
|
|
70
|
-
|
|
71
40
|
const host = document.getElementById("editor-host");
|
|
72
41
|
if (!host) {
|
|
73
42
|
throw new Error("Missing editor host");
|
|
74
43
|
}
|
|
75
44
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
45
|
+
const editor = Editor.make()
|
|
46
|
+
.config((ctx) => {
|
|
47
|
+
ctx.set(rootCtx, host);
|
|
48
|
+
ctx.set(defaultValueCtx, payload.markdown);
|
|
49
|
+
})
|
|
50
|
+
.use(commonmark)
|
|
51
|
+
.use(gfm);
|
|
52
|
+
await editor.create();
|
|
53
|
+
const semanticHtml = editor.action(getHTML());
|
|
54
|
+
const html = renderWechatHtml({
|
|
55
|
+
semanticHtml,
|
|
56
|
+
baseCss: articleCss,
|
|
57
|
+
customCss: payload.customCss,
|
|
58
|
+
editorVars: payload.editorVars,
|
|
59
|
+
theme: payload.exportTheme,
|
|
80
60
|
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
await crepe.editor.use(listener).create();
|
|
84
|
-
crepe.setReadonly(true);
|
|
85
|
-
|
|
86
|
-
await sleep(80);
|
|
87
|
-
await doubleRaf();
|
|
88
|
-
await sleep(160);
|
|
89
|
-
await doubleRaf();
|
|
90
|
-
|
|
91
|
-
const editorDom = host.querySelector(".milkdown .editor") as HTMLElement | null;
|
|
92
|
-
if (!editorDom) {
|
|
93
|
-
throw new Error("Milkdown editor DOM not found");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const html = getWeChatMinimalHTML(editorDom, payload.exportTheme);
|
|
97
|
-
setResult({ status: "success", html });
|
|
61
|
+
await editor.destroy();
|
|
62
|
+
setResult({ status: "success", html, semanticHtml });
|
|
98
63
|
}
|
|
99
64
|
|
|
100
65
|
void main().catch((error: unknown) => {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { isWechatPreviewBundleStale } from "./index";
|
|
6
|
+
|
|
7
|
+
describe("isWechatPreviewBundleStale", () => {
|
|
8
|
+
test("returns true when manifest is missing", () => {
|
|
9
|
+
const dir = mkdtempSync(join(tmpdir(), "zzhub-bundle-stale-"));
|
|
10
|
+
try {
|
|
11
|
+
expect(isWechatPreviewBundleStale([], join(dir, "missing.json"))).toBe(true);
|
|
12
|
+
} finally {
|
|
13
|
+
rmSync(dir, { recursive: true, force: true });
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("returns true when a source is newer than manifest", () => {
|
|
18
|
+
const dir = mkdtempSync(join(tmpdir(), "zzhub-bundle-stale-"));
|
|
19
|
+
try {
|
|
20
|
+
const src = join(dir, "src.css");
|
|
21
|
+
const manifest = join(dir, "manifest.json");
|
|
22
|
+
writeFileSync(manifest, "{}");
|
|
23
|
+
// ensure source is newer
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
writeFileSync(src, "body{}");
|
|
26
|
+
// touch source mtime into the future relative to manifest by rewriting after delay is flaky;
|
|
27
|
+
// instead set source after manifest with a small sleep via Bun
|
|
28
|
+
Bun.sleepSync(20);
|
|
29
|
+
writeFileSync(src, "body{color:red}");
|
|
30
|
+
void now;
|
|
31
|
+
expect(isWechatPreviewBundleStale([src], manifest)).toBe(true);
|
|
32
|
+
} finally {
|
|
33
|
+
rmSync(dir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("returns false when sources are older than manifest", () => {
|
|
38
|
+
const dir = mkdtempSync(join(tmpdir(), "zzhub-bundle-stale-"));
|
|
39
|
+
try {
|
|
40
|
+
const srcDir = join(dir, "src");
|
|
41
|
+
mkdirSync(srcDir);
|
|
42
|
+
const src = join(srcDir, "src.css");
|
|
43
|
+
writeFileSync(src, "body{}");
|
|
44
|
+
Bun.sleepSync(20);
|
|
45
|
+
const manifest = join(dir, "manifest.json");
|
|
46
|
+
writeFileSync(manifest, "{}");
|
|
47
|
+
expect(isWechatPreviewBundleStale([src], manifest)).toBe(false);
|
|
48
|
+
} finally {
|
|
49
|
+
rmSync(dir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 微信排版语义节点验收
|
|
3
|
+
slug: wechat-renderer-all-nodes
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 微信排版语义节点验收
|
|
7
|
+
|
|
8
|
+
# 一级标题:克制的中文编辑部
|
|
9
|
+
|
|
10
|
+
这是一段用于检查正文密度的中文。它包含 **加粗文字**、*强调文字*、~~删除文字~~、`inlineCode()`,以及一个[外部链接](https://example.com/reading-guide?from=wechat)。
|
|
11
|
+
|
|
12
|
+
这一行之后保留硬换行。\
|
|
13
|
+
下一行仍属于同一段,并包含[文内链接](#section-two)。
|
|
14
|
+
|
|
15
|
+
## 二级标题:清晰但不过度装饰
|
|
16
|
+
|
|
17
|
+
### 三级标题:细分信息层级
|
|
18
|
+
|
|
19
|
+
#### 四级标题:低频但仍然可辨
|
|
20
|
+
|
|
21
|
+
> 好的排版不是抢夺注意力,而是让内容更轻松地被读完。
|
|
22
|
+
>
|
|
23
|
+
> 引用可以有第二段,并保留 **重点信息**。
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
- 无序列表第一项
|
|
28
|
+
- 嵌套列表包含 `nested-code`
|
|
29
|
+
- 嵌套列表第二项
|
|
30
|
+
- 无序列表第二项
|
|
31
|
+
|
|
32
|
+
1. 有序列表第一步
|
|
33
|
+
2. 有序列表第二步
|
|
34
|
+
1. 嵌套步骤
|
|
35
|
+
2. 另一个嵌套步骤
|
|
36
|
+
|
|
37
|
+
- [x] 已完成的任务
|
|
38
|
+
- [ ] 尚未完成的任务
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
type Article = {
|
|
42
|
+
title: string;
|
|
43
|
+
published: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const article: Article = {
|
|
47
|
+
title: "保留代码缩进",
|
|
48
|
+
published: false,
|
|
49
|
+
};
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
| 节点 | 微信标签 | 设计原则 |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| 正文 | `p` | 16px 与舒展行高 |
|
|
55
|
+
| 代码 | `pre/code` | 单色、保留空白 |
|
|
56
|
+
| 图片 | `img` | 宽度不溢出 |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 危险 HTML 清理
|
|
61
|
+
|
|
62
|
+
<div onclick="alert('x')" style="display: grid; position: fixed">
|
|
63
|
+
<script>alert('never')</script>
|
|
64
|
+
<p onmouseover="alert('x')" style="color: var(--missing-color)">危险容器里的安全文本</p>
|
|
65
|
+
<img src="javascript:alert('x')" onerror="alert('x')" alt="危险图片">
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
文末再放一个[资料链接](https://example.org/reference),用于检查编号脚注与“相关链接”。
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
resolveMarkdownAsset,
|
|
7
7
|
rewriteRelativeImagePaths,
|
|
8
8
|
extractRenderResult,
|
|
9
|
+
buildWechatPreviewShell,
|
|
9
10
|
} from "./index";
|
|
10
11
|
|
|
11
12
|
describe("escapeInlineJson", () => {
|
|
@@ -51,6 +52,10 @@ describe("isExternalUrl", () => {
|
|
|
51
52
|
expect(isExternalUrl("//cdn.example.com/img.png")).toBe(true);
|
|
52
53
|
});
|
|
53
54
|
|
|
55
|
+
test("preserves unknown schemes for the final sanitizer", () => {
|
|
56
|
+
expect(isExternalUrl("javascript:alert(1)")).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
54
59
|
test("rejects relative paths", () => {
|
|
55
60
|
expect(isExternalUrl("./img.png")).toBe(false);
|
|
56
61
|
expect(isExternalUrl("../img.png")).toBe(false);
|
|
@@ -152,3 +157,15 @@ describe("extractRenderResult", () => {
|
|
|
152
157
|
expect(() => extractRenderResult(dom)).toThrow();
|
|
153
158
|
});
|
|
154
159
|
});
|
|
160
|
+
|
|
161
|
+
describe("buildWechatPreviewShell", () => {
|
|
162
|
+
test("embeds the final article fragment verbatim without a renderer bundle", () => {
|
|
163
|
+
const article = '<section style="color: red;"><p>正文</p></section>';
|
|
164
|
+
const shell = buildWechatPreviewShell(article, "标题 <安全>");
|
|
165
|
+
|
|
166
|
+
expect(shell).toContain(`<main id="wechat-preview-content">${article}</main>`);
|
|
167
|
+
expect(shell).toContain("标题 <安全>");
|
|
168
|
+
expect(shell).not.toContain("editor-export.js");
|
|
169
|
+
expect(shell).not.toContain("browser-dist");
|
|
170
|
+
});
|
|
171
|
+
});
|