@lyhue1991/wxgzh 0.1.1 → 0.1.3
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 +37 -47
- package/dist/core/converter.js +129 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# wxgzh
|
|
2
2
|
|
|
3
|
-
一个面向微信公众号的
|
|
3
|
+
一个面向微信公众号的 SKILL,基于 NodeJS CLI.
|
|
4
|
+
|
|
5
|
+
把 Markdown 文章处理成适合公众号发布的 HTML,上传图片,生成封面,并提交到公众号草稿箱。
|
|
6
|
+
|
|
7
|
+
<mark>安装此SKILL</mark>
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npx skills add lyhue1991/wxgzh
|
|
11
|
+
```
|
|
12
|
+
|
|
4
13
|
|
|
5
14
|
## 一、功能概述
|
|
6
15
|
|
|
@@ -12,31 +21,40 @@
|
|
|
12
21
|
- 支持主题样式、自定义 CSS、自动摘要、封面图生成。
|
|
13
22
|
- 也支持分步执行,便于你单独检查 HTML、封面或发布结果。
|
|
14
23
|
|
|
15
|
-
|
|
24
|
+
**手动操作方法**
|
|
25
|
+
|
|
26
|
+
* step1: 一键安装(要求:Node.js >= 18)
|
|
16
27
|
|
|
17
28
|
```bash
|
|
18
|
-
wxgzh
|
|
29
|
+
npm install -g @lyhue1991/wxgzh
|
|
30
|
+
```
|
|
31
|
+
安装完成后可直接使用 `wxgzh` 命令。
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
* step2: 一键配置 (提前准备好微信公众号AppID和 AppSecret , 并配置好IP白名单)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
wxgzh config --appid 你的AppID --appsecret 你的AppSecret
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
* step3: 一键投稿 (将本地markdown文章直接投递到公众号草稿箱, 人工确认后即可发布)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
wxgzh article.md --author 文章作者姓名
|
|
19
45
|
```
|
|
20
46
|
|
|
21
47
|
这条命令会自动执行:
|
|
22
48
|
|
|
23
49
|
1. 读取 `article.md`
|
|
24
50
|
2. 转换成公众号可用 HTML
|
|
25
|
-
3.
|
|
51
|
+
3. 上传正文中的本地图片和非微信图床图片到微信公众号图床
|
|
26
52
|
4. 自动生成封面图(如果你没有提供)
|
|
27
53
|
5. 提交到微信公众号草稿箱
|
|
28
54
|
|
|
29
|
-
## 安装
|
|
30
55
|
|
|
31
|
-
要求:Node.js >= 18
|
|
32
56
|
|
|
33
|
-
|
|
34
|
-
npm install -g @lyhue1991/wxgzh
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
安装完成后可直接使用 `wxgzh` 命令。
|
|
38
|
-
|
|
39
|
-
## 二、基本配置
|
|
57
|
+
## 二、配置说明
|
|
40
58
|
|
|
41
59
|
### 1. 如何获取公众号 `appid` 和 `appsecret`
|
|
42
60
|
|
|
@@ -99,8 +117,6 @@ wxgzh config --list-themes
|
|
|
99
117
|
当前内置主题来自 `styles/` 目录,例如:`default`、`blue`、`green`、`red`、`yellow`、`brown`、`black`、`orange`。
|
|
100
118
|
|
|
101
119
|
|
|
102
|
-
|
|
103
|
-
|
|
104
120
|
## 三、快速流程
|
|
105
121
|
|
|
106
122
|
最常见用法:
|
|
@@ -374,7 +390,10 @@ enableComment: true
|
|
|
374
390
|
|
|
375
391
|
命令行参数优先级高于 Front Matter,Front Matter 又高于默认配置。
|
|
376
392
|
|
|
377
|
-
|
|
393
|
+
|
|
394
|
+
## 六,备忘清单
|
|
395
|
+
|
|
396
|
+
### 1. 支持的 Markdown 内容
|
|
378
397
|
|
|
379
398
|
本项目当前重点支持这些常见内容:
|
|
380
399
|
|
|
@@ -386,9 +405,10 @@ enableComment: true
|
|
|
386
405
|
- 表格
|
|
387
406
|
- 数学公式(行内与块级)
|
|
388
407
|
|
|
389
|
-
|
|
408
|
+
### 2. 常用命令速查
|
|
390
409
|
|
|
391
410
|
```bash
|
|
411
|
+
wxgzh --help
|
|
392
412
|
wxgzh article.md
|
|
393
413
|
wxgzh config --list
|
|
394
414
|
wxgzh config --list-themes
|
|
@@ -398,34 +418,4 @@ wxgzh cover --title "我的文章" --to .wxgzh/cover.jpg
|
|
|
398
418
|
wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg
|
|
399
419
|
```
|
|
400
420
|
|
|
401
|
-
## 开发与构建
|
|
402
|
-
|
|
403
|
-
开发环境安装依赖:
|
|
404
|
-
|
|
405
|
-
```bash
|
|
406
|
-
npm install
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
类型检查:
|
|
410
|
-
|
|
411
|
-
```bash
|
|
412
|
-
npm run typecheck
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
构建:
|
|
416
|
-
|
|
417
|
-
```bash
|
|
418
|
-
npm run build
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
查看帮助:
|
|
422
|
-
|
|
423
|
-
```bash
|
|
424
|
-
node dist/cli/index.js --help
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
## 说明
|
|
428
421
|
|
|
429
|
-
- 配置文件保存在用户目录下,不会写进项目源码目录。
|
|
430
|
-
- `dist/` 是编译输出目录,日常修改应以 `src/` 为准。
|
|
431
|
-
- 如果一键发布失败,建议改用“分步流程”逐步检查 `HTML`、图片上传和封面图。
|
package/dist/core/converter.js
CHANGED
|
@@ -178,6 +178,35 @@ q table:last-child {
|
|
|
178
178
|
margin-bottom: 0 !important;
|
|
179
179
|
}
|
|
180
180
|
`;
|
|
181
|
+
const WECHAT_LAYOUT_INLINE_CSS = `
|
|
182
|
+
p,
|
|
183
|
+
h1,
|
|
184
|
+
h2,
|
|
185
|
+
h3,
|
|
186
|
+
h4,
|
|
187
|
+
h5,
|
|
188
|
+
h6,
|
|
189
|
+
ul,
|
|
190
|
+
ol,
|
|
191
|
+
dl,
|
|
192
|
+
blockquote,
|
|
193
|
+
q,
|
|
194
|
+
pre,
|
|
195
|
+
hr,
|
|
196
|
+
section.img-wrapper,
|
|
197
|
+
section.tbl-wrapper,
|
|
198
|
+
section.math-block {
|
|
199
|
+
margin-left: 0 !important;
|
|
200
|
+
margin-right: 0 !important;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
section.img-wrapper,
|
|
204
|
+
section.tbl-wrapper,
|
|
205
|
+
section.math-block {
|
|
206
|
+
width: 100% !important;
|
|
207
|
+
box-sizing: border-box !important;
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
181
210
|
const WECHAT_TABLE_INLINE_CSS = `
|
|
182
211
|
section.tbl-wrapper {
|
|
183
212
|
margin: 1.4em 3% !important;
|
|
@@ -342,6 +371,9 @@ function escapeHtmlAttribute(value) {
|
|
|
342
371
|
function isTextNodeEmpty(node) {
|
|
343
372
|
return node.type === 'text' && !node.data.trim();
|
|
344
373
|
}
|
|
374
|
+
function isTagNode(node, tagName) {
|
|
375
|
+
return node.type === 'tag' && node.tagName.toLowerCase() === tagName;
|
|
376
|
+
}
|
|
345
377
|
function isStandaloneImageParagraph($paragraph) {
|
|
346
378
|
const nodes = $paragraph.contents().toArray().filter((node) => !isTextNodeEmpty(node));
|
|
347
379
|
return nodes.every((node) => node.type === 'tag' && ['img', 'br'].includes(node.tagName.toLowerCase()));
|
|
@@ -381,10 +413,33 @@ async function loadCustomCss() {
|
|
|
381
413
|
function normalizeListItems($) {
|
|
382
414
|
$('li').each((_, element) => {
|
|
383
415
|
const $item = $(element);
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
416
|
+
const initialChildren = $item.contents().toArray();
|
|
417
|
+
const hasParagraphChild = initialChildren.some((node) => isTagNode(node, 'p'));
|
|
418
|
+
if (hasParagraphChild) {
|
|
419
|
+
const fragments = [];
|
|
420
|
+
let previousWasParagraph = false;
|
|
421
|
+
for (const node of initialChildren) {
|
|
422
|
+
if (isTextNodeEmpty(node)) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (isTagNode(node, 'p')) {
|
|
426
|
+
const paragraphHtml = renderSoftBreakFriendlyHtml(node.children ?? []);
|
|
427
|
+
if (!paragraphHtml.trim()) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (previousWasParagraph) {
|
|
431
|
+
fragments.push('<br>');
|
|
432
|
+
}
|
|
433
|
+
fragments.push(paragraphHtml);
|
|
434
|
+
previousWasParagraph = true;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
fragments.push(renderSoftBreakFriendlyNode(node, true));
|
|
438
|
+
previousWasParagraph = false;
|
|
439
|
+
}
|
|
440
|
+
$item.html(fragments.join(''));
|
|
441
|
+
}
|
|
442
|
+
const childElements = $item.contents().toArray().filter((node) => !isTextNodeEmpty(node));
|
|
388
443
|
if (childElements.length === 0) {
|
|
389
444
|
return;
|
|
390
445
|
}
|
|
@@ -396,7 +451,7 @@ function normalizeListItems($) {
|
|
|
396
451
|
return ['p', 'ul', 'ol', 'section', 'blockquote', 'pre', 'table'].includes(tagName);
|
|
397
452
|
});
|
|
398
453
|
const firstChild = childElements[0];
|
|
399
|
-
if (!hasBlockChild && firstChild
|
|
454
|
+
if (!hasBlockChild && isTagNode(firstChild, 'span') && childElements.length === 1) {
|
|
400
455
|
return;
|
|
401
456
|
}
|
|
402
457
|
if (hasBlockChild) {
|
|
@@ -556,12 +611,80 @@ function setInlineStyleProperty(existingStyle, property, value) {
|
|
|
556
611
|
.map(([name, declarationValue]) => `${name}: ${declarationValue}`)
|
|
557
612
|
.join('; ');
|
|
558
613
|
}
|
|
614
|
+
function removeInlineStyleProperties(existingStyle, properties) {
|
|
615
|
+
if (!existingStyle) {
|
|
616
|
+
return undefined;
|
|
617
|
+
}
|
|
618
|
+
const propertiesToRemove = new Set(properties.map((property) => property.trim().toLowerCase()).filter(Boolean));
|
|
619
|
+
const declarations = existingStyle
|
|
620
|
+
.split(';')
|
|
621
|
+
.map((declaration) => declaration.trim())
|
|
622
|
+
.filter(Boolean)
|
|
623
|
+
.filter((declaration) => {
|
|
624
|
+
const separatorIndex = declaration.indexOf(':');
|
|
625
|
+
if (separatorIndex === -1) {
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
const name = declaration.slice(0, separatorIndex).trim().toLowerCase();
|
|
629
|
+
return !propertiesToRemove.has(name);
|
|
630
|
+
});
|
|
631
|
+
if (declarations.length === 0) {
|
|
632
|
+
return undefined;
|
|
633
|
+
}
|
|
634
|
+
return declarations.join('; ');
|
|
635
|
+
}
|
|
636
|
+
function normalizeInlinedListStyles($) {
|
|
637
|
+
$('ol').each((_, element) => {
|
|
638
|
+
const $list = $(element);
|
|
639
|
+
const depth = $list.parents('ol').length;
|
|
640
|
+
const cleanedStyle = removeInlineStyleProperties($list.attr('style'), ['list-style', 'list-style-type', 'list-style-position']);
|
|
641
|
+
const listType = depth === 0 ? '1' : depth === 1 ? 'a' : 'i';
|
|
642
|
+
if (cleanedStyle) {
|
|
643
|
+
$list.attr('style', cleanedStyle);
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
$list.removeAttr('style');
|
|
647
|
+
}
|
|
648
|
+
$list.attr('type', listType);
|
|
649
|
+
});
|
|
650
|
+
$('ul').each((_, element) => {
|
|
651
|
+
const $list = $(element);
|
|
652
|
+
const cleanedStyle = removeInlineStyleProperties($list.attr('style'), ['list-style', 'list-style-type', 'list-style-position']);
|
|
653
|
+
if (cleanedStyle) {
|
|
654
|
+
$list.attr('style', cleanedStyle);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
$list.removeAttr('style');
|
|
658
|
+
});
|
|
659
|
+
$('ol > li, ul > li').each((_, element) => {
|
|
660
|
+
const $item = $(element);
|
|
661
|
+
const cleanedStyle = removeInlineStyleProperties($item.attr('style'), ['list-style', 'list-style-type', 'list-style-position']);
|
|
662
|
+
if (cleanedStyle) {
|
|
663
|
+
$item.attr('style', cleanedStyle);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
$item.removeAttr('style');
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
function removeWechatUnsafeListWhitespace($) {
|
|
670
|
+
$('ol, ul').each((_, element) => {
|
|
671
|
+
$(element)
|
|
672
|
+
.contents()
|
|
673
|
+
.toArray()
|
|
674
|
+
.filter((node) => isTextNodeEmpty(node))
|
|
675
|
+
.forEach((node) => {
|
|
676
|
+
$(node).remove();
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
}
|
|
559
680
|
function cleanInlinedHtml(html) {
|
|
560
681
|
const $ = cheerio.load(html);
|
|
561
682
|
$('pre > code').each((_, element) => {
|
|
562
683
|
const $element = $(element);
|
|
563
684
|
$element.html(renderWechatFriendlyCodeHtml($element.contents().toArray()));
|
|
564
685
|
});
|
|
686
|
+
normalizeInlinedListStyles($);
|
|
687
|
+
removeWechatUnsafeListWhitespace($);
|
|
565
688
|
const $article = $('body > #write > article').first();
|
|
566
689
|
const $firstChild = $article.children().first();
|
|
567
690
|
if ($firstChild.length > 0) {
|
|
@@ -616,7 +739,7 @@ async function renderMarkdownToHtml(body, metadata) {
|
|
|
616
739
|
const articleHtml = $('article').html() ?? '';
|
|
617
740
|
const baseHtml = createDocumentHtml(articleHtml, metadata);
|
|
618
741
|
const inlinedHtml = (0, css_inline_1.inline)(baseHtml, {
|
|
619
|
-
extraCss: `${themeCss}\n${HIGHLIGHT_INLINE_CSS}\n${WECHAT_BLOCKQUOTE_INLINE_CSS}\n${WECHAT_TABLE_INLINE_CSS}\n${WECHAT_MATH_INLINE_CSS}\n${customCss}`,
|
|
742
|
+
extraCss: `${themeCss}\n${HIGHLIGHT_INLINE_CSS}\n${WECHAT_BLOCKQUOTE_INLINE_CSS}\n${WECHAT_TABLE_INLINE_CSS}\n${WECHAT_MATH_INLINE_CSS}\n${WECHAT_LAYOUT_INLINE_CSS}\n${customCss}`,
|
|
620
743
|
keepAtRules: true,
|
|
621
744
|
applyWidthAttributes: false,
|
|
622
745
|
applyHeightAttributes: false
|