@lyhue1991/wxgzh 0.2.2 → 0.2.4

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 CHANGED
@@ -294,6 +294,7 @@ wxgzh fix .wxgzh/article.html
294
294
  - 移除不适合公众号的结构(如 `script`、`iframe`)
295
295
  - 处理图片样式
296
296
  - 将本地图片上传到微信,替换为微信返回的图片地址
297
+ - 兼容懒加载结构:图片真实地址在 `data-src`、`src` 为空白占位图时,会自动以 `data-src` 为准回写 `src`;无法处理的图片(如纯占位图)会逐张告警,不再静默跳过
297
298
 
298
299
  如果你只想修结构,不上传图片:
299
300
 
package/dist/cli/fix.js CHANGED
@@ -37,5 +37,8 @@ function registerFixCommand(program) {
37
37
  wechat
38
38
  });
39
39
  (0, logger_1.success)(`HTML 修复完成,共处理 ${result.imageCount} 张图片`);
40
+ for (const message of result.skippedImages) {
41
+ (0, logger_1.warn)(message);
42
+ }
40
43
  });
41
44
  }
package/dist/cli/index.js CHANGED
@@ -101,8 +101,11 @@ async function runDefaultPipeline(articlePath, options) {
101
101
  const { fixHtmlFile } = await Promise.resolve().then(() => __importStar(require('../core/fixer')));
102
102
  const { WechatClient } = await Promise.resolve().then(() => __importStar(require('../core/wechat')));
103
103
  const wechat = new WechatClient((0, config_1.resolveWechatCredentials)(config));
104
- await fixHtmlFile(htmlPath, { upload: true, wechat });
104
+ const fixResult = await fixHtmlFile(htmlPath, { upload: true, wechat });
105
105
  (0, logger_1.info)(`已修复并上传正文图片: ${htmlPath}`);
106
+ for (const message of fixResult.skippedImages) {
107
+ (0, logger_1.warn)(message);
108
+ }
106
109
  let finalCoverPath = resolveCoverValue(options.cover) ?? parsed.metadata.cover;
107
110
  if (finalCoverPath && !node_path_1.default.isAbsolute(finalCoverPath)) {
108
111
  finalCoverPath = node_path_1.default.resolve(node_path_1.default.dirname(absoluteArticle), finalCoverPath);
@@ -127,6 +130,9 @@ async function runDefaultPipeline(articlePath, options) {
127
130
  }
128
131
  async function main() {
129
132
  const program = new commander_1.Command();
133
+ // 外层命令与子命令存在同名选项(如 publish 的 --cover),必须启用 positional options:
134
+ // 子命令名之后出现的选项只归子命令所有,避免外层 --cover/--no-cover 吞掉 publish 的 --cover 参数
135
+ program.enablePositionalOptions();
130
136
  program
131
137
  .name('wxgzh')
132
138
  .description('把 Markdown 文章处理成微信公众号草稿,支持主题、图片修复、封面生成与一键发布')
@@ -18,7 +18,7 @@ async function convertMarkdownFile(inputPath, outputPath, options) {
18
18
  const raw = await (0, promises_1.readFile)(absoluteInputPath, 'utf8');
19
19
  const parsed = (0, parser_1.parseMarkdown)(raw);
20
20
  const config = await (0, config_1.loadConfig)({ account: options?.account ?? parsed.metadata.account });
21
- const resolvedTheme = options?.theme ?? parsed.metadata.theme ?? config.defaultTheme ?? 'default';
21
+ const resolvedTheme = options?.theme ?? parsed.metadata.theme ?? config.defaultTheme ?? (0, themes_1.getDefaultThemeName)();
22
22
  const html = await (0, converter_1.renderMarkdownToHtml)(parsed.body, {
23
23
  ...parsed.metadata,
24
24
  account: options?.account ?? parsed.metadata.account ?? config.account,
@@ -41,9 +41,27 @@ const node_path_1 = __importDefault(require("node:path"));
41
41
  const cheerio = __importStar(require("cheerio"));
42
42
  const converter_1 = require("./converter");
43
43
  const fs_1 = require("../utils/fs");
44
+ const LAZY_SOURCE_ATTRIBUTE = 'data-src';
44
45
  function isRemoteUrl(value) {
45
46
  return /^https?:\/\//i.test(value);
46
47
  }
48
+ function isPlaceholderValue(value) {
49
+ const trimmed = value.trim();
50
+ return trimmed === '' || trimmed.startsWith('data:') || trimmed === 'about:blank';
51
+ }
52
+ function isUsableSource(value) {
53
+ return value !== undefined && !isPlaceholderValue(value);
54
+ }
55
+ function pickImageSource(lazySource, directSource) {
56
+ // 微信保存页等懒加载结构:真实地址在 data-src,src 可能是空白占位图
57
+ if (isUsableSource(lazySource)) {
58
+ return lazySource.trim();
59
+ }
60
+ if (isUsableSource(directSource)) {
61
+ return directSource.trim();
62
+ }
63
+ return undefined;
64
+ }
47
65
  function toCdnUrl(cdn, source) {
48
66
  const cleanBase = cdn.replace(/\/+$/, '');
49
67
  const fileName = node_path_1.default.basename(source);
@@ -70,20 +88,26 @@ async function fixHtmlFile(articlePath, options) {
70
88
  const sourceBaseDir = metadata.sourceDir ? node_path_1.default.resolve(metadata.sourceDir) : node_path_1.default.dirname(articlePath);
71
89
  $('script,iframe').remove();
72
90
  const images = $('img').toArray();
73
- for (const element of images) {
91
+ const skippedImages = [];
92
+ let imageCount = 0;
93
+ for (const [index, element] of images.entries()) {
74
94
  const image = $(element);
75
- const source = image.attr('src');
76
- if (!source) {
95
+ const source = pickImageSource(image.attr(LAZY_SOURCE_ATTRIBUTE), image.attr('src'));
96
+ if (source === undefined) {
97
+ skippedImages.push(`第 ${index + 1} 张图片缺少可用地址(src 与 data-src 均为空或占位图),已跳过`);
77
98
  continue;
78
99
  }
100
+ let finalSource = source;
79
101
  if (options.upload && options.wechat && !source.includes('mmbiz.qpic.cn')) {
80
- const uploadedUrl = await options.wechat.uploadArticleImage(resolveImageSource(sourceBaseDir, source));
81
- image.attr('src', uploadedUrl);
102
+ finalSource = await options.wechat.uploadArticleImage(resolveImageSource(sourceBaseDir, source));
82
103
  image.attr('data-original-src', source);
83
104
  }
84
105
  else if (options.cdn && !isRemoteUrl(source)) {
85
- image.attr('src', toCdnUrl(options.cdn, source));
106
+ finalSource = toCdnUrl(options.cdn, source);
86
107
  }
108
+ // 真实地址统一回写 src,并移除懒加载占位属性,避免渲染端读到占位图
109
+ image.attr('src', finalSource);
110
+ image.removeAttr(LAZY_SOURCE_ATTRIBUTE);
87
111
  image.attr('style', [
88
112
  'display:block',
89
113
  'max-width:100%',
@@ -91,7 +115,8 @@ async function fixHtmlFile(articlePath, options) {
91
115
  'margin:0 auto',
92
116
  'border-radius:6px'
93
117
  ].join(';'));
118
+ imageCount += 1;
94
119
  }
95
120
  await (0, fs_1.writeTextFile)(articlePath, $.html());
96
- return { imageCount: images.length };
121
+ return { imageCount, skippedImages };
97
122
  }
@@ -9,7 +9,7 @@ exports.assertThemeExists = assertThemeExists;
9
9
  exports.getDefaultThemeName = getDefaultThemeName;
10
10
  const node_fs_1 = require("node:fs");
11
11
  const node_path_1 = __importDefault(require("node:path"));
12
- const DEFAULT_THEME = 'default';
12
+ const DEFAULT_THEME = 'black';
13
13
  function getStylesDir() {
14
14
  return node_path_1.default.resolve(__dirname, '../../styles');
15
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyhue1991/wxgzh",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Markdown to WeChat Official Account draft CLI",
5
5
  "license": "MIT",
6
6
  "main": "dist/cli/index.js",
package/styles/black.css CHANGED
@@ -70,17 +70,23 @@ h1 {
70
70
  }
71
71
 
72
72
  h2{
73
- font-size: 25px;
74
- padding: 10px 10px 10px 10px;
75
- line-height: 1.15;
73
+ display: block;
74
+ width: 100%;
75
+ font-size: 18px;
76
+ font-weight: 800;
77
+ padding: 8px 14px;
78
+ line-height: 1.4;
76
79
  margin-top: 1.85em;
77
80
  margin-bottom: 1.15em;
78
- background:#000;
79
- color:#fff;
81
+ margin-left: 0;
82
+ border-bottom: none;
83
+ box-sizing: border-box;
84
+ background: #000;
85
+ color: #fff;
80
86
  }
81
87
 
82
88
  h3 {
83
- font-size: 20px;
89
+ font-size: 16px;
84
90
  line-height: 1.15;
85
91
  margin-top: 2.285714em;
86
92
  margin-bottom: 1.15em;
package/styles/blue.css CHANGED
@@ -71,20 +71,26 @@ h1 {
71
71
  }
72
72
 
73
73
  h2{
74
- font-size: 25px;
75
- padding: 10px 10px 10px 10px; /* 上、右、下、左的内边距分别为10px、20px、15px、25px */
76
- line-height: 1.15;
74
+ display: block;
75
+ width: 100%;
76
+ font-size: 18px;
77
+ font-weight: 800;
78
+ padding: 8px 14px;
79
+ line-height: 1.4;
77
80
  /*24 / 21*/
78
81
  margin-top: 1.85em;
79
82
  /*48 / 21*/
80
83
  margin-bottom: 1.15em;
81
84
  /*24 / 21*/
82
- background:#2891DB;
83
- color:#fff;
85
+ margin-left: 0;
86
+ border-bottom: none;
87
+ box-sizing: border-box;
88
+ background:#2891DB;
89
+ color:#fff;
84
90
  }
85
91
 
86
92
  h3 {
87
- font-size: 20px;
93
+ font-size: 16px;
88
94
  /*21 / 16*/
89
95
  line-height: 1.15;
90
96
  /*24 / 21*/
package/styles/brown.css CHANGED
@@ -70,17 +70,23 @@ h1 {
70
70
  }
71
71
 
72
72
  h2{
73
- font-size: 25px;
74
- padding: 10px 10px 10px 10px;
75
- line-height: 1.15;
73
+ display: block;
74
+ width: 100%;
75
+ font-size: 18px;
76
+ font-weight: 800;
77
+ padding: 8px 14px;
78
+ line-height: 1.4;
76
79
  margin-top: 1.85em;
77
80
  margin-bottom: 1.15em;
78
- background:#D27D2D;
79
- color:#fff;
81
+ margin-left: 0;
82
+ border-bottom: none;
83
+ box-sizing: border-box;
84
+ background:#D27D2D;
85
+ color:#fff;
80
86
  }
81
87
 
82
88
  h3 {
83
- font-size: 20px;
89
+ font-size: 16px;
84
90
  line-height: 1.15;
85
91
  margin-top: 2.285714em;
86
92
  margin-bottom: 1.15em;
@@ -69,17 +69,23 @@ h1 {
69
69
  }
70
70
 
71
71
  h2{
72
- font-size: 25px;
73
- padding: 10px 10px 10px 10px;
74
- line-height: 1.15;
72
+ display: block;
73
+ width: 100%;
74
+ font-size: 18px;
75
+ font-weight: 800;
76
+ padding: 8px 14px;
77
+ line-height: 1.4;
75
78
  margin-top: 1.85em;
76
79
  margin-bottom: 1.15em;
77
- background:#349971;
78
- color:#fff;
80
+ margin-left: 0;
81
+ border-bottom: none;
82
+ box-sizing: border-box;
83
+ background:#349971;
84
+ color:#fff;
79
85
  }
80
86
 
81
87
  h3 {
82
- font-size: 20px;
88
+ font-size: 16px;
83
89
  line-height: 1.15;
84
90
  margin-top: 2.285714em;
85
91
  margin-bottom: 1.15em;
package/styles/green.css CHANGED
@@ -72,20 +72,26 @@ h1 {
72
72
  }
73
73
 
74
74
  h2{
75
- font-size: 25px;
76
- padding: 10px 10px 10px 10px; /* 上、右、下、左的内边距分别为10px、20px、15px、25px */
77
- line-height: 1.15;
75
+ display: block;
76
+ width: 100%;
77
+ font-size: 18px;
78
+ font-weight: 800;
79
+ padding: 8px 14px;
80
+ line-height: 1.4;
78
81
  /*24 / 21*/
79
82
  margin-top: 1.85em;
80
83
  /*48 / 21*/
81
84
  margin-bottom: 1.15em;
82
85
  /*24 / 21*/
83
- background:#7FFF06;
84
- color:#fff;
86
+ margin-left: 0;
87
+ border-bottom: none;
88
+ box-sizing: border-box;
89
+ background:#7FFF06;
90
+ color:#fff;
85
91
  }
86
92
 
87
93
  h3 {
88
- font-size: 20px;
94
+ font-size: 16px;
89
95
  /*21 / 16*/
90
96
  line-height: 1.15;
91
97
  /*24 / 21*/
package/styles/orange.css CHANGED
@@ -72,20 +72,26 @@ h1 {
72
72
  }
73
73
 
74
74
  h2{
75
- font-size: 25px;
76
- padding: 10px 10px 10px 10px; /* 上、右、下、左的内边距分别为10px、20px、15px、25px */
77
- line-height: 1.15;
75
+ display: block;
76
+ width: 100%;
77
+ font-size: 18px;
78
+ font-weight: 800;
79
+ padding: 8px 14px;
80
+ line-height: 1.4;
78
81
  /*24 / 21*/
79
82
  margin-top: 1.85em;
80
83
  /*48 / 21*/
81
84
  margin-bottom: 1.15em;
82
85
  /*24 / 21*/
83
- background:#F7BC0A;
84
- color:#fff;
86
+ margin-left: 0;
87
+ border-bottom: none;
88
+ box-sizing: border-box;
89
+ background:#F7BC0A;
90
+ color:#fff;
85
91
  }
86
92
 
87
93
  h3 {
88
- font-size: 20px;
94
+ font-size: 16px;
89
95
  /*21 / 16*/
90
96
  line-height: 1.15;
91
97
  /*24 / 21*/
package/styles/red.css CHANGED
@@ -72,20 +72,26 @@ h1 {
72
72
  }
73
73
 
74
74
  h2{
75
- font-size: 25px;
76
- padding: 10px 10px 10px 10px; /* 上、右、下、左的内边距分别为10px、20px、15px、25px */
77
- line-height: 1.15;
75
+ display: block;
76
+ width: 100%;
77
+ font-size: 18px;
78
+ font-weight: 800;
79
+ padding: 8px 14px;
80
+ line-height: 1.4;
78
81
  /*24 / 21*/
79
82
  margin-top: 1.85em;
80
83
  /*48 / 21*/
81
84
  margin-bottom: 1.15em;
82
85
  /*24 / 21*/
83
- background:#DC143C;
84
- color:#fff;
86
+ margin-left: 0;
87
+ border-bottom: none;
88
+ box-sizing: border-box;
89
+ background:#DC143C;
90
+ color:#fff;
85
91
  }
86
92
 
87
93
  h3 {
88
- font-size: 20px;
94
+ font-size: 16px;
89
95
  /*21 / 16*/
90
96
  line-height: 1.15;
91
97
  /*24 / 21*/
package/styles/yellow.css CHANGED
@@ -72,20 +72,26 @@ h1 {
72
72
  }
73
73
 
74
74
  h2{
75
- font-size: 25px;
76
- padding: 10px 10px 10px 10px; /* 上、右、下、左的内边距分别为10px、20px、15px、25px */
77
- line-height: 1.15;
75
+ display: block;
76
+ width: 100%;
77
+ font-size: 18px;
78
+ font-weight: 800;
79
+ padding: 8px 14px;
80
+ line-height: 1.4;
78
81
  /*24 / 21*/
79
82
  margin-top: 1.85em;
80
83
  /*48 / 21*/
81
84
  margin-bottom: 1.15em;
82
85
  /*24 / 21*/
83
- background:#FFD139;
84
- color:#fff;
86
+ margin-left: 0;
87
+ border-bottom: none;
88
+ box-sizing: border-box;
89
+ background:#FFD139;
90
+ color:#fff;
85
91
  }
86
92
 
87
93
  h3 {
88
- font-size: 20px;
94
+ font-size: 16px;
89
95
  /*21 / 16*/
90
96
  line-height: 1.15;
91
97
  /*24 / 21*/