@lyhue1991/wxgzh 0.1.2 → 0.1.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 +18 -3
- package/dist/core/converter.js +239 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
# wxgzh
|
|
1
|
+
# wxgzh,这个SKILL打通了AI写公众号文章的最后一公里
|
|
2
|
+
|
|
3
|
+
这是一个面向微信公众号的 SKILL,基于 NodeJS CLI.
|
|
4
|
+
|
|
5
|
+
把 Markdown 文章处理成适合公众号发布的 HTML,上传图片,生成封面,并提交到公众号草稿箱。
|
|
6
|
+
|
|
7
|
+
<mark>安装此SKILL</mark>
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npx skills add lyhue1991/wxgzh
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
效果演示
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+

|
|
2
18
|
|
|
3
|
-
一个面向微信公众号的 Node.js CLI 工具:把 Markdown 文章处理成适合公众号发布的 HTML,修复图片,并提交到公众号草稿箱。
|
|
4
19
|
|
|
5
20
|
## 一、功能概述
|
|
6
21
|
|
|
@@ -12,7 +27,7 @@
|
|
|
12
27
|
- 支持主题样式、自定义 CSS、自动摘要、封面图生成。
|
|
13
28
|
- 也支持分步执行,便于你单独检查 HTML、封面或发布结果。
|
|
14
29
|
|
|
15
|
-
|
|
30
|
+
**手动操作方法**
|
|
16
31
|
|
|
17
32
|
* step1: 一键安装(要求:Node.js >= 18)
|
|
18
33
|
|
package/dist/core/converter.js
CHANGED
|
@@ -260,6 +260,32 @@ section.tbl-wrapper a {
|
|
|
260
260
|
text-decoration: none !important;
|
|
261
261
|
}
|
|
262
262
|
`;
|
|
263
|
+
const WECHAT_CODE_BLOCK_INLINE_CSS = `
|
|
264
|
+
pre {
|
|
265
|
+
overflow-x: auto !important;
|
|
266
|
+
overflow-y: hidden !important;
|
|
267
|
+
-webkit-overflow-scrolling: touch;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
pre code.hljs,
|
|
271
|
+
pre code[class*="language-"] {
|
|
272
|
+
display: block !important;
|
|
273
|
+
width: max-content !important;
|
|
274
|
+
min-width: 100% !important;
|
|
275
|
+
box-sizing: border-box !important;
|
|
276
|
+
white-space: nowrap !important;
|
|
277
|
+
word-break: normal !important;
|
|
278
|
+
overflow-wrap: normal !important;
|
|
279
|
+
overflow: visible !important;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
pre code.hljs *,
|
|
283
|
+
pre code[class*="language-"] * {
|
|
284
|
+
white-space: inherit !important;
|
|
285
|
+
word-break: inherit !important;
|
|
286
|
+
overflow-wrap: inherit !important;
|
|
287
|
+
}
|
|
288
|
+
`;
|
|
263
289
|
const WECHAT_MATH_INLINE_CSS = `
|
|
264
290
|
span.math-inline {
|
|
265
291
|
display: inline-block !important;
|
|
@@ -371,6 +397,9 @@ function escapeHtmlAttribute(value) {
|
|
|
371
397
|
function isTextNodeEmpty(node) {
|
|
372
398
|
return node.type === 'text' && !node.data.trim();
|
|
373
399
|
}
|
|
400
|
+
function isTagNode(node, tagName) {
|
|
401
|
+
return node.type === 'tag' && node.tagName.toLowerCase() === tagName;
|
|
402
|
+
}
|
|
374
403
|
function isStandaloneImageParagraph($paragraph) {
|
|
375
404
|
const nodes = $paragraph.contents().toArray().filter((node) => !isTextNodeEmpty(node));
|
|
376
405
|
return nodes.every((node) => node.type === 'tag' && ['img', 'br'].includes(node.tagName.toLowerCase()));
|
|
@@ -410,10 +439,33 @@ async function loadCustomCss() {
|
|
|
410
439
|
function normalizeListItems($) {
|
|
411
440
|
$('li').each((_, element) => {
|
|
412
441
|
const $item = $(element);
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
442
|
+
const initialChildren = $item.contents().toArray();
|
|
443
|
+
const hasParagraphChild = initialChildren.some((node) => isTagNode(node, 'p'));
|
|
444
|
+
if (hasParagraphChild) {
|
|
445
|
+
const fragments = [];
|
|
446
|
+
let previousWasParagraph = false;
|
|
447
|
+
for (const node of initialChildren) {
|
|
448
|
+
if (isTextNodeEmpty(node)) {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (isTagNode(node, 'p')) {
|
|
452
|
+
const paragraphHtml = renderSoftBreakFriendlyHtml(node.children ?? []);
|
|
453
|
+
if (!paragraphHtml.trim()) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (previousWasParagraph) {
|
|
457
|
+
fragments.push('<br>');
|
|
458
|
+
}
|
|
459
|
+
fragments.push(paragraphHtml);
|
|
460
|
+
previousWasParagraph = true;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
fragments.push(renderSoftBreakFriendlyNode(node, true));
|
|
464
|
+
previousWasParagraph = false;
|
|
465
|
+
}
|
|
466
|
+
$item.html(fragments.join(''));
|
|
467
|
+
}
|
|
468
|
+
const childElements = $item.contents().toArray().filter((node) => !isTextNodeEmpty(node));
|
|
417
469
|
if (childElements.length === 0) {
|
|
418
470
|
return;
|
|
419
471
|
}
|
|
@@ -425,7 +477,7 @@ function normalizeListItems($) {
|
|
|
425
477
|
return ['p', 'ul', 'ol', 'section', 'blockquote', 'pre', 'table'].includes(tagName);
|
|
426
478
|
});
|
|
427
479
|
const firstChild = childElements[0];
|
|
428
|
-
if (!hasBlockChild && firstChild
|
|
480
|
+
if (!hasBlockChild && isTagNode(firstChild, 'span') && childElements.length === 1) {
|
|
429
481
|
return;
|
|
430
482
|
}
|
|
431
483
|
if (hasBlockChild) {
|
|
@@ -561,29 +613,192 @@ function createDocumentHtml(content, metadata) {
|
|
|
561
613
|
].join('\n');
|
|
562
614
|
}
|
|
563
615
|
function setInlineStyleProperty(existingStyle, property, value) {
|
|
616
|
+
const entries = parseInlineStyle(existingStyle);
|
|
617
|
+
entries.set(property, value);
|
|
618
|
+
return stringifyInlineStyle(entries) ?? '';
|
|
619
|
+
}
|
|
620
|
+
function removeInlineStyleProperties(existingStyle, properties) {
|
|
621
|
+
const entries = parseInlineStyle(existingStyle);
|
|
622
|
+
const propertiesToRemove = new Set(properties.map((property) => property.trim().toLowerCase()).filter(Boolean));
|
|
623
|
+
for (const name of Array.from(entries.keys())) {
|
|
624
|
+
if (propertiesToRemove.has(name.toLowerCase())) {
|
|
625
|
+
entries.delete(name);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return stringifyInlineStyle(entries);
|
|
629
|
+
}
|
|
630
|
+
function parseInlineStyle(existingStyle) {
|
|
564
631
|
const entries = new Map();
|
|
565
|
-
if (existingStyle) {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
632
|
+
if (!existingStyle) {
|
|
633
|
+
return entries;
|
|
634
|
+
}
|
|
635
|
+
for (const declaration of existingStyle.split(';')) {
|
|
636
|
+
const trimmed = declaration.trim();
|
|
637
|
+
if (!trimmed) {
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
const separatorIndex = trimmed.indexOf(':');
|
|
641
|
+
if (separatorIndex === -1) {
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
const name = trimmed.slice(0, separatorIndex).trim();
|
|
645
|
+
const declarationValue = trimmed.slice(separatorIndex + 1).trim();
|
|
646
|
+
if (!name || !declarationValue) {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
entries.set(name, declarationValue);
|
|
650
|
+
}
|
|
651
|
+
return entries;
|
|
652
|
+
}
|
|
653
|
+
function stringifyInlineStyle(entries) {
|
|
654
|
+
if (entries.size === 0) {
|
|
655
|
+
return undefined;
|
|
656
|
+
}
|
|
657
|
+
return Array.from(entries.entries())
|
|
658
|
+
.map(([name, declarationValue]) => `${name}: ${declarationValue}`)
|
|
659
|
+
.join('; ');
|
|
660
|
+
}
|
|
661
|
+
function getInlineStyleProperty(existingStyle, property) {
|
|
662
|
+
const entries = parseInlineStyle(existingStyle);
|
|
663
|
+
for (const [name, declarationValue] of entries.entries()) {
|
|
664
|
+
if (name.trim().toLowerCase() === property.trim().toLowerCase()) {
|
|
665
|
+
return declarationValue;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
670
|
+
function normalizeInlinedListStyles($) {
|
|
671
|
+
$('ol').each((_, element) => {
|
|
672
|
+
const $list = $(element);
|
|
673
|
+
const depth = $list.parents('ol').length;
|
|
674
|
+
const cleanedStyle = removeInlineStyleProperties($list.attr('style'), ['list-style', 'list-style-type', 'list-style-position']);
|
|
675
|
+
const listType = depth === 0 ? '1' : depth === 1 ? 'a' : 'i';
|
|
676
|
+
if (cleanedStyle) {
|
|
677
|
+
$list.attr('style', cleanedStyle);
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
$list.removeAttr('style');
|
|
681
|
+
}
|
|
682
|
+
$list.attr('type', listType);
|
|
683
|
+
});
|
|
684
|
+
$('ul').each((_, element) => {
|
|
685
|
+
const $list = $(element);
|
|
686
|
+
const cleanedStyle = removeInlineStyleProperties($list.attr('style'), ['list-style', 'list-style-type', 'list-style-position']);
|
|
687
|
+
if (cleanedStyle) {
|
|
688
|
+
$list.attr('style', cleanedStyle);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
$list.removeAttr('style');
|
|
692
|
+
});
|
|
693
|
+
$('ol > li, ul > li').each((_, element) => {
|
|
694
|
+
const $item = $(element);
|
|
695
|
+
const cleanedStyle = removeInlineStyleProperties($item.attr('style'), ['list-style', 'list-style-type', 'list-style-position']);
|
|
696
|
+
if (cleanedStyle) {
|
|
697
|
+
$item.attr('style', cleanedStyle);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
$item.removeAttr('style');
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
function removeWechatUnsafeListWhitespace($) {
|
|
704
|
+
$('ol, ul').each((_, element) => {
|
|
705
|
+
$(element)
|
|
706
|
+
.contents()
|
|
707
|
+
.toArray()
|
|
708
|
+
.filter((node) => isTextNodeEmpty(node))
|
|
709
|
+
.forEach((node) => {
|
|
710
|
+
$(node).remove();
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
function normalizeWechatCodeBlocks($) {
|
|
715
|
+
const backgroundStyleProperties = [
|
|
716
|
+
'background',
|
|
717
|
+
'background-color',
|
|
718
|
+
'background-image',
|
|
719
|
+
'background-repeat',
|
|
720
|
+
'background-position',
|
|
721
|
+
'background-size'
|
|
722
|
+
];
|
|
723
|
+
const codeStyleProperties = [
|
|
724
|
+
...backgroundStyleProperties,
|
|
725
|
+
'border',
|
|
726
|
+
'border-top',
|
|
727
|
+
'border-right',
|
|
728
|
+
'border-bottom',
|
|
729
|
+
'border-left',
|
|
730
|
+
'border-color',
|
|
731
|
+
'border-style',
|
|
732
|
+
'border-width',
|
|
733
|
+
'border-radius',
|
|
734
|
+
'box-shadow',
|
|
735
|
+
'padding',
|
|
736
|
+
'padding-top',
|
|
737
|
+
'padding-right',
|
|
738
|
+
'padding-bottom',
|
|
739
|
+
'padding-left'
|
|
740
|
+
];
|
|
741
|
+
const wrapperStyleProperties = ['margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left'];
|
|
742
|
+
$('pre').each((_, element) => {
|
|
743
|
+
const $pre = $(element);
|
|
744
|
+
const $code = $pre.children('code').first();
|
|
745
|
+
if ($code.length === 0) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (!$pre.parent().is('section.code-block-wrapper')) {
|
|
749
|
+
$pre.wrap('<section class="code-block-wrapper"></section>');
|
|
750
|
+
}
|
|
751
|
+
const $wrapper = $pre.parent();
|
|
752
|
+
const codeStyle = $code.attr('style');
|
|
753
|
+
const preStyle = $pre.attr('style');
|
|
754
|
+
for (const property of wrapperStyleProperties) {
|
|
755
|
+
const value = getInlineStyleProperty(codeStyle, property) ?? getInlineStyleProperty(preStyle, property);
|
|
756
|
+
if (!value) {
|
|
569
757
|
continue;
|
|
570
758
|
}
|
|
571
|
-
|
|
572
|
-
|
|
759
|
+
$wrapper.attr('style', setInlineStyleProperty($wrapper.attr('style'), property, value));
|
|
760
|
+
}
|
|
761
|
+
$wrapper.attr('style', setInlineStyleProperty($wrapper.attr('style'), 'display', 'block !important'));
|
|
762
|
+
$wrapper.attr('style', setInlineStyleProperty($wrapper.attr('style'), 'overflow-x', 'auto !important'));
|
|
763
|
+
$wrapper.attr('style', setInlineStyleProperty($wrapper.attr('style'), 'overflow-y', 'hidden !important'));
|
|
764
|
+
$wrapper.attr('style', setInlineStyleProperty($wrapper.attr('style'), '-webkit-overflow-scrolling', 'touch'));
|
|
765
|
+
for (const property of codeStyleProperties) {
|
|
766
|
+
const value = getInlineStyleProperty(codeStyle, property);
|
|
767
|
+
if (!value) {
|
|
573
768
|
continue;
|
|
574
769
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
770
|
+
$pre.attr('style', setInlineStyleProperty($pre.attr('style'), property, value));
|
|
771
|
+
}
|
|
772
|
+
$pre.attr('style', removeInlineStyleProperties($pre.attr('style'), wrapperStyleProperties));
|
|
773
|
+
$pre.attr('style', setInlineStyleProperty($pre.attr('style'), 'display', 'inline-block !important'));
|
|
774
|
+
$pre.attr('style', setInlineStyleProperty($pre.attr('style'), 'width', 'max-content !important'));
|
|
775
|
+
$pre.attr('style', setInlineStyleProperty($pre.attr('style'), 'min-width', '100% !important'));
|
|
776
|
+
$pre.attr('style', setInlineStyleProperty($pre.attr('style'), 'max-width', 'none !important'));
|
|
777
|
+
$pre.attr('style', setInlineStyleProperty($pre.attr('style'), 'box-sizing', 'border-box !important'));
|
|
778
|
+
$pre.attr('style', setInlineStyleProperty($pre.attr('style'), 'white-space', 'normal !important'));
|
|
779
|
+
$pre.attr('style', setInlineStyleProperty($pre.attr('style'), 'word-break', 'normal !important'));
|
|
780
|
+
$pre.attr('style', setInlineStyleProperty($pre.attr('style'), 'overflow', 'visible !important'));
|
|
781
|
+
$pre.attr('style', setInlineStyleProperty($pre.attr('style'), 'vertical-align', 'top !important'));
|
|
782
|
+
$code.attr('style', removeInlineStyleProperties($code.attr('style'), [...codeStyleProperties, ...wrapperStyleProperties]));
|
|
783
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), 'display', 'inline-block !important'));
|
|
784
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), 'width', 'max-content !important'));
|
|
785
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), 'min-width', '100% !important'));
|
|
786
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), 'max-width', 'none !important'));
|
|
787
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), 'white-space', 'nowrap !important'));
|
|
788
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), 'word-break', 'normal !important'));
|
|
789
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), 'overflow-wrap', 'normal !important'));
|
|
790
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), 'border', '0 !important'));
|
|
791
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), 'border-radius', '0 !important'));
|
|
792
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), 'padding', '0 !important'));
|
|
793
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), 'margin', '0 !important'));
|
|
794
|
+
for (const property of backgroundStyleProperties) {
|
|
795
|
+
const value = getInlineStyleProperty(codeStyle, property) ?? getInlineStyleProperty(preStyle, property);
|
|
796
|
+
if (!value) {
|
|
578
797
|
continue;
|
|
579
798
|
}
|
|
580
|
-
|
|
799
|
+
$code.attr('style', setInlineStyleProperty($code.attr('style'), property, value));
|
|
581
800
|
}
|
|
582
|
-
}
|
|
583
|
-
entries.set(property, value);
|
|
584
|
-
return Array.from(entries.entries())
|
|
585
|
-
.map(([name, declarationValue]) => `${name}: ${declarationValue}`)
|
|
586
|
-
.join('; ');
|
|
801
|
+
});
|
|
587
802
|
}
|
|
588
803
|
function cleanInlinedHtml(html) {
|
|
589
804
|
const $ = cheerio.load(html);
|
|
@@ -591,6 +806,9 @@ function cleanInlinedHtml(html) {
|
|
|
591
806
|
const $element = $(element);
|
|
592
807
|
$element.html(renderWechatFriendlyCodeHtml($element.contents().toArray()));
|
|
593
808
|
});
|
|
809
|
+
normalizeInlinedListStyles($);
|
|
810
|
+
removeWechatUnsafeListWhitespace($);
|
|
811
|
+
normalizeWechatCodeBlocks($);
|
|
594
812
|
const $article = $('body > #write > article').first();
|
|
595
813
|
const $firstChild = $article.children().first();
|
|
596
814
|
if ($firstChild.length > 0) {
|
|
@@ -645,7 +863,7 @@ async function renderMarkdownToHtml(body, metadata) {
|
|
|
645
863
|
const articleHtml = $('article').html() ?? '';
|
|
646
864
|
const baseHtml = createDocumentHtml(articleHtml, metadata);
|
|
647
865
|
const inlinedHtml = (0, css_inline_1.inline)(baseHtml, {
|
|
648
|
-
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}`,
|
|
866
|
+
extraCss: `${themeCss}\n${HIGHLIGHT_INLINE_CSS}\n${WECHAT_BLOCKQUOTE_INLINE_CSS}\n${WECHAT_TABLE_INLINE_CSS}\n${WECHAT_CODE_BLOCK_INLINE_CSS}\n${WECHAT_MATH_INLINE_CSS}\n${WECHAT_LAYOUT_INLINE_CSS}\n${customCss}`,
|
|
649
867
|
keepAtRules: true,
|
|
650
868
|
applyWidthAttributes: false,
|
|
651
869
|
applyHeightAttributes: false
|