@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 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
+ ![](wxgzh_shot1.png)
16
+
17
+ ![](wxgzh_shot2.png)
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
 
@@ -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 childElements = $item
414
- .contents()
415
- .toArray()
416
- .filter((node) => !isTextNodeEmpty(node));
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?.type === 'tag' && firstChild.tagName.toLowerCase() === 'span' && childElements.length === 1) {
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
- for (const declaration of existingStyle.split(';')) {
567
- const trimmed = declaration.trim();
568
- if (!trimmed) {
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
- const separatorIndex = trimmed.indexOf(':');
572
- if (separatorIndex === -1) {
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
- const name = trimmed.slice(0, separatorIndex).trim();
576
- const declarationValue = trimmed.slice(separatorIndex + 1).trim();
577
- if (!name || !declarationValue) {
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
- entries.set(name, declarationValue);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyhue1991/wxgzh",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Markdown to WeChat Official Account draft CLI",
5
5
  "license": "MIT",
6
6
  "main": "dist/cli/index.js",