@lyhue1991/wxgzh 0.2.0 → 0.2.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 CHANGED
@@ -6,7 +6,13 @@
6
6
 
7
7
  <mark>安装此SKILL</mark>
8
8
 
9
+ ```bash
10
+ #for openclaw
11
+ npx clawhub@latest install wxgzh
9
12
  ```
13
+
14
+ ```bash
15
+ #for opencode/claudecode/cursor/openclaw/...
10
16
  npx skills add lyhue1991/wxgzh
11
17
  ```
12
18
 
@@ -40,7 +46,7 @@ npm install -g @lyhue1991/wxgzh
40
46
  * step2: 一键配置 (提前准备好微信公众号AppID和 AppSecret , 并配置好IP白名单)
41
47
 
42
48
  ```bash
43
- wxgzh config --appid 你的AppID --appsecret 你的AppSecret
49
+ wxgzh config --account 公众号名称1 --appid 你的AppID --appsecret 你的AppSecret
44
50
 
45
51
  ```
46
52
 
@@ -67,24 +73,93 @@ wxgzh article.md --author 文章作者姓名
67
73
  前往微信公众号公众平台,使用已认证的公众号管理员账号登录:
68
74
 
69
75
  1. 打开公众号后台
70
- 2. 进入“设置与开发”
71
- 3. 找到“开发接口管理”
76
+ 2. 进入"设置与开发"
77
+ 3. 找到"开发接口管理"
72
78
  4. 查看并复制 `AppID` 和 `AppSecret`
73
79
 
74
- 拿到后,执行:
80
+ 拿到后,执行(**必须同时指定公众号账号名**):
75
81
 
76
82
  ```bash
77
- wxgzh config --appid 你的AppID --appsecret 你的AppSecret
83
+ wxgzh config --account 公众号名称1 --appid 你的AppID --appsecret 你的AppSecret
78
84
  ```
79
85
 
80
86
  也可以同时设置默认作者、默认主题:
81
87
 
82
88
  ```bash
83
- wxgzh config --appid 你的AppID --appsecret 你的AppSecret --author "wxgzh" --default-theme blue
89
+ wxgzh config --account 公众号名称1 --appid 你的AppID --appsecret 你的AppSecret --author "wxgzh" --default-theme blue
90
+ ```
91
+
92
+ ### 2. 如何管理多个公众号账号
93
+
94
+ `wxgzh` 支持同时管理多个公众号账号,适合有多个公众号或需要在不同账号间切换的场景。
95
+
96
+ **添加多个账号:**
97
+
98
+ ```bash
99
+ # 主账号
100
+ wxgzh config --account 公众号名称1 --appid 你的AppID --appsecret 你的AppSecret
101
+
102
+ # 备用账号
103
+ wxgzh config --account 公众号名称2 --appid 另一个AppID --appsecret 另一个AppSecret
104
+ ```
105
+
106
+ **设置默认账号:**
107
+
108
+ ```bash
109
+ wxgzh config --set-default-account 公众号名称1
110
+ ```
111
+
112
+ **查看已配置的账号:**
113
+
114
+ ```bash
115
+ wxgzh config --list-accounts
116
+ ```
117
+
118
+ **发布时指定账号:**
119
+
120
+ ```bash
121
+ # 使用公众号名称1发布
122
+ wxgzh article.md --account 公众号名称1
123
+
124
+ # 使用公众号名称2发布
125
+ wxgzh article.md --account 公众号名称2
126
+
127
+ # 不指定则使用默认账号
128
+ wxgzh article.md
129
+ ```
130
+
131
+ **账号选择优先级:**
132
+
133
+ 命令行 `--account` > front matter 中的 `account` 字段 > 环境变量 `WX_ACCOUNT` > 用户配置的默认账号
134
+
135
+ **使用 front matter 指定账号:**
136
+
137
+ ```yaml
138
+ ---
139
+ title: 我的文章
140
+ author: wxgzh
141
+ account: 公众号名称2
142
+ ---
143
+
144
+ # 文章正文
84
145
  ```
85
146
 
147
+ **使用环境变量(适用于 CI/CD 场景):**
148
+
149
+ ```bash
150
+ export WX_ACCOUNT=公众号名称2
151
+ export WX_APPID=你的AppID
152
+ export WX_APPSECRET=你的AppSecret
153
+ wxgzh article.md
154
+ ```
86
155
 
87
- ### 2. 如何把本机IP加入公众号 IP 白名单
156
+ **删除账号配置:**
157
+
158
+ ```bash
159
+ wxgzh config --remove-account 公众号名称2
160
+ ```
161
+
162
+ ### 3. 如何把本机IP加入公众号 IP 白名单
88
163
 
89
164
  > [!IMPORTANT]
90
165
  > 调用微信公众号接口前,除了配置 `appid` 和 `appsecret`,还要把当前机器的公网 IP 加到公众号后台的 IP 白名单。
@@ -100,7 +175,7 @@ wxgzh config --appid 你的AppID --appsecret 你的AppSecret --author "wxgzh" --
100
175
  查看本机公网 IP 的方法:浏览器访问 https://ip.sb/ 查看 Address
101
176
 
102
177
 
103
- ### 3. 如何查看当前配置
178
+ ### 4. 如何查看当前配置
104
179
 
105
180
  ```bash
106
181
  wxgzh config --list
@@ -391,6 +466,7 @@ enableComment: true
391
466
  - `author`:作者
392
467
  - `digest`:摘要
393
468
  - `theme`:主题名
469
+ - `account`:目标公众号账号名
394
470
  - `cover`:封面图路径
395
471
  - `enableComment`:是否开启评论
396
472
 
@@ -424,4 +500,3 @@ wxgzh fix .wxgzh/article.html --no-upload
424
500
  wxgzh cover --title "我的文章" --to .wxgzh/cover.jpg
425
501
  wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg
426
502
  ```
427
-
@@ -6,57 +6,109 @@ const config_1 = require("../utils/config");
6
6
  const logger_1 = require("../utils/logger");
7
7
  function listConfig(config) {
8
8
  const payload = {
9
- ...config,
10
- appsecret: (0, config_1.maskSecret)(config.appsecret)
9
+ account: config.account,
10
+ currentAccount: config.currentAccount,
11
+ availableAccounts: Object.keys(config.accounts),
12
+ appid: config.appid,
13
+ appsecret: (0, config_1.maskSecret)(config.appsecret),
14
+ author: config.author,
15
+ defaultTheme: config.defaultTheme,
16
+ enableComment: config.enableComment
11
17
  };
12
18
  console.log(JSON.stringify(payload, null, 2));
13
19
  (0, logger_1.info)(`用户级配置路径: ${(0, config_1.getUserConfigPath)()}`);
14
20
  }
21
+ function listAccounts(config) {
22
+ const accountNames = Object.keys(config.accounts);
23
+ if (accountNames.length === 0) {
24
+ (0, logger_1.info)('当前还没有配置任何公众号账号');
25
+ return;
26
+ }
27
+ for (const accountName of accountNames) {
28
+ console.log(accountName === config.currentAccount ? `${accountName} (default)` : accountName);
29
+ }
30
+ (0, logger_1.info)(`用户级配置路径: ${(0, config_1.getUserConfigPath)()}`);
31
+ }
15
32
  function registerConfigCommand(program) {
16
33
  program
17
34
  .command('config')
18
35
  .description('查看或更新 wxgzh 的用户级默认配置')
19
36
  .usage('[options]')
20
37
  .helpOption('-h, --help', '查看帮助')
21
- .option('--appid <appid>', '设置微信公众号 appid')
22
- .option('--appsecret <appsecret>', '设置微信公众号 appsecret')
23
- .option('--author <author>', '设置默认作者名')
24
- .option('--default-theme <theme>', `设置默认主题,可用值:${(0, themes_1.listAvailableThemes)().join(', ')}`)
25
- .option('--enable-comment', '把默认评论开关设为开启')
26
- .option('--disable-comment', '把默认评论开关设为关闭')
38
+ .option('--account <name>', '指定要查看或更新的公众号账号名;设置 appid/appsecret 时必填')
39
+ .option('--appid <appid>', '为目标账号设置微信公众号 appid')
40
+ .option('--appsecret <appsecret>', '为目标账号设置微信公众号 appsecret')
41
+ .option('--author <author>', '为目标账号设置默认作者名')
42
+ .option('--default-theme <theme>', `为目标账号设置默认主题,可用值:${(0, themes_1.listAvailableThemes)().join(', ')}`)
43
+ .option('--enable-comment', '把目标账号的默认评论开关设为开启')
44
+ .option('--disable-comment', '把目标账号的默认评论开关设为关闭')
45
+ .option('--set-default-account <name>', '设置默认公众号账号')
46
+ .option('--remove-account <name>', '删除指定公众号账号配置')
27
47
  .option('--list', '查看当前生效配置')
48
+ .option('--list-accounts', '查看全部已配置账号')
28
49
  .option('--list-themes', '查看全部可用主题名')
29
50
  .option('--clear', '清空用户级配置文件')
30
- .addHelpText('after', '\n示例:\n $ wxgzh config --list\n $ wxgzh config --list-themes\n $ wxgzh config --author "wxgzh" --default-theme blue\n $ wxgzh config --enable-comment\n')
51
+ .addHelpText('after', '\n示例:\n $ wxgzh config --list\n $ wxgzh config --list-accounts\n $ wxgzh config --account 公众号名称1 --appid wx123 --appsecret abc123\n $ wxgzh config --account 公众号名称1 --author "wxgzh" --default-theme blue\n $ wxgzh config --set-default-account 公众号名称1\n\n说明:\n 设置 appid 或 appsecret 时,必须同时通过 --account 指定公众号账号名\n')
31
52
  .action(async (options, command) => {
32
53
  const mergedOptions = command.optsWithGlobals();
54
+ const patch = {
55
+ ...(mergedOptions.appid ? { appid: mergedOptions.appid } : {}),
56
+ ...(mergedOptions.appsecret ? { appsecret: mergedOptions.appsecret } : {}),
57
+ ...(mergedOptions.author ? { author: mergedOptions.author } : {}),
58
+ ...(mergedOptions.defaultTheme ? { defaultTheme: (0, themes_1.assertThemeExists)(mergedOptions.defaultTheme) } : {}),
59
+ ...(mergedOptions.enableComment ? { enableComment: true } : {}),
60
+ ...(mergedOptions.disableComment ? { enableComment: false } : {})
61
+ };
33
62
  if (mergedOptions.clear) {
34
63
  await (0, config_1.clearUserConfig)();
35
64
  (0, logger_1.success)('已清空用户级配置');
36
65
  return;
37
66
  }
38
67
  if (mergedOptions.list) {
39
- listConfig(await (0, config_1.loadConfig)());
68
+ listConfig(await (0, config_1.loadConfig)({ account: mergedOptions.account }));
40
69
  return;
41
70
  }
42
71
  if (mergedOptions.listThemes) {
43
72
  console.log((0, themes_1.listAvailableThemes)().join('\n'));
44
73
  return;
45
74
  }
46
- const patch = {
47
- ...(mergedOptions.appid ? { appid: mergedOptions.appid } : {}),
48
- ...(mergedOptions.appsecret ? { appsecret: mergedOptions.appsecret } : {}),
49
- ...(mergedOptions.author ? { author: mergedOptions.author } : {}),
50
- ...(mergedOptions.defaultTheme ? { defaultTheme: (0, themes_1.assertThemeExists)(mergedOptions.defaultTheme) } : {}),
51
- ...(mergedOptions.enableComment ? { enableComment: true } : {}),
52
- ...(mergedOptions.disableComment ? { enableComment: false } : {})
53
- };
54
- if (Object.keys(patch).length === 0) {
55
- listConfig(await (0, config_1.loadConfig)());
75
+ if (mergedOptions.listAccounts) {
76
+ listAccounts(await (0, config_1.loadUserConfig)());
77
+ return;
78
+ }
79
+ if (mergedOptions.removeAccount) {
80
+ if (Object.keys(patch).length > 0 || mergedOptions.setDefaultAccount) {
81
+ throw new Error('--remove-account 不能与其他写入类参数同时使用');
82
+ }
83
+ const next = await (0, config_1.removeAccountConfig)(mergedOptions.removeAccount);
84
+ (0, logger_1.success)(`已删除公众号账号 ${mergedOptions.removeAccount}`);
85
+ listAccounts(next);
86
+ return;
87
+ }
88
+ let selectedAccount = mergedOptions.account;
89
+ const hasCredentialsToSave = mergedOptions.appid || mergedOptions.appsecret;
90
+ if (hasCredentialsToSave && !selectedAccount) {
91
+ throw new Error('配置 appid 或 appsecret 时,必须通过 --account <name> 指定公众号账号名');
92
+ }
93
+ if (Object.keys(patch).length > 0) {
94
+ if (!selectedAccount) {
95
+ const userConfig = await (0, config_1.loadUserConfig)();
96
+ selectedAccount = userConfig.currentAccount;
97
+ }
98
+ if (!selectedAccount) {
99
+ throw new Error('当前没有默认公众号账号,请通过 --account <name> 指定要写入的账号');
100
+ }
101
+ await (0, config_1.saveAccountConfig)(selectedAccount, patch);
102
+ }
103
+ if (mergedOptions.setDefaultAccount) {
104
+ await (0, config_1.setDefaultAccount)(mergedOptions.setDefaultAccount);
105
+ selectedAccount = mergedOptions.setDefaultAccount;
106
+ }
107
+ if (Object.keys(patch).length === 0 && !mergedOptions.setDefaultAccount) {
108
+ listConfig(await (0, config_1.loadConfig)({ account: mergedOptions.account }));
56
109
  return;
57
110
  }
58
- const next = await (0, config_1.saveUserConfig)(patch);
59
111
  (0, logger_1.success)('用户级配置已更新');
60
- console.log(JSON.stringify({ ...next, appsecret: (0, config_1.maskSecret)(next.appsecret) }, null, 2));
112
+ listConfig(await (0, config_1.loadConfig)({ account: selectedAccount }));
61
113
  });
62
114
  }
package/dist/cli/fix.js CHANGED
@@ -5,9 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.registerFixCommand = registerFixCommand;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
+ const converter_1 = require("../core/converter");
8
9
  const fixer_1 = require("../core/fixer");
9
10
  const wechat_1 = require("../core/wechat");
10
11
  const config_1 = require("../utils/config");
12
+ const fs_1 = require("../utils/fs");
11
13
  const logger_1 = require("../utils/logger");
12
14
  function registerFixCommand(program) {
13
15
  program
@@ -16,19 +18,20 @@ function registerFixCommand(program) {
16
18
  .usage('<article.html> [options]')
17
19
  .helpOption('-h, --help', '查看帮助')
18
20
  .argument('<article.html>', '要修复的 HTML 文件路径')
21
+ .option('--account <name>', '指定上传正文图片时使用的公众号账号')
19
22
  .option('--no-upload', '只修复 HTML 结构,不上传图片到微信')
20
23
  .option('--cdn <url>', '把本地图片路径替换为指定 CDN 地址')
21
- .addHelpText('after', '\n示例:\n $ wxgzh fix .wxgzh/article.html\n $ wxgzh fix .wxgzh/article.html --no-upload\n $ wxgzh fix .wxgzh/article.html --cdn https://cdn.example.com\n')
24
+ .addHelpText('after', '\n示例:\n $ wxgzh fix .wxgzh/article.html\n $ wxgzh fix .wxgzh/article.html --account 公众号名称1\n $ wxgzh fix .wxgzh/article.html --no-upload\n $ wxgzh fix .wxgzh/article.html --cdn https://cdn.example.com\n')
22
25
  .action(async (articlePath, options) => {
26
+ const absoluteArticlePath = node_path_1.default.resolve(articlePath);
23
27
  let wechat;
24
28
  if (options.upload !== false) {
25
- const config = await (0, config_1.loadConfig)();
26
- if (!config.appid || !config.appsecret) {
27
- (0, logger_1.error)('未配置微信认证信息,请先运行: wxgzh config --appid xxx --appsecret yyy');
28
- }
29
- wechat = new wechat_1.WechatClient({ appid: config.appid, appsecret: config.appsecret });
29
+ const html = await (0, fs_1.readTextFile)(absoluteArticlePath);
30
+ const metadata = (0, converter_1.readHtmlMetadata)(html);
31
+ const config = await (0, config_1.loadConfig)({ account: options.account ?? metadata.account });
32
+ wechat = new wechat_1.WechatClient((0, config_1.resolveWechatCredentials)(config));
30
33
  }
31
- const result = await (0, fixer_1.fixHtmlFile)(node_path_1.default.resolve(articlePath), {
34
+ const result = await (0, fixer_1.fixHtmlFile)(absoluteArticlePath, {
32
35
  upload: options.upload !== false,
33
36
  cdn: options.cdn,
34
37
  wechat
package/dist/cli/index.js CHANGED
@@ -56,6 +56,7 @@ dotenv_1.default.config();
56
56
  const EXAMPLES_TEXT = [
57
57
  '常用示例:',
58
58
  ' $ wxgzh article.md',
59
+ ' $ wxgzh article.md --account 公众号名称1',
59
60
  ' $ wxgzh article.md --theme blue',
60
61
  ' $ wxgzh article.md --author "wxgzh" --digest "这是一篇摘要"',
61
62
  ' $ wxgzh article.md --cover ./cover.jpg',
@@ -83,25 +84,23 @@ async function runDefaultPipeline(articlePath, options) {
83
84
  const articleBaseName = node_path_1.default.basename(absoluteArticle, node_path_1.default.extname(absoluteArticle));
84
85
  const htmlPath = node_path_1.default.join(outputDir, `${articleBaseName}.html`);
85
86
  const coverPath = node_path_1.default.join(outputDir, `${articleBaseName}.cover.jpg`);
86
- const config = await (0, config_1.loadConfig)();
87
87
  const rawMarkdown = await (0, promises_1.readFile)(absoluteArticle, 'utf8');
88
88
  const parsed = (0, parser_1.parseMarkdown)(rawMarkdown);
89
+ const config = await (0, config_1.loadConfig)({ account: options.account ?? parsed.metadata.account });
89
90
  const title = parsed.metadata.title ?? articleBaseName;
90
91
  const author = options.author ?? parsed.metadata.author ?? config.author ?? 'wxgzh';
91
92
  const digest = options.digest ?? parsed.metadata.digest ?? '由 wxgzh 自动生成的公众号草稿';
92
93
  const autoCoverDisabled = options.cover === false;
93
94
  const resolvedTheme = options.theme ?? parsed.metadata.theme ?? config.defaultTheme;
94
95
  await (0, md2html_1.convertMarkdownFile)(absoluteArticle, htmlPath, {
96
+ account: config.account,
95
97
  theme: resolvedTheme ? (0, themes_1.assertThemeExists)(resolvedTheme) : undefined,
96
98
  author
97
99
  });
98
100
  (0, logger_1.info)(`已生成 HTML: ${htmlPath}`);
99
101
  const { fixHtmlFile } = await Promise.resolve().then(() => __importStar(require('../core/fixer')));
100
102
  const { WechatClient } = await Promise.resolve().then(() => __importStar(require('../core/wechat')));
101
- if (!config.appid || !config.appsecret) {
102
- throw new Error('未配置微信认证信息,请先运行: wxgzh config --appid xxx --appsecret yyy');
103
- }
104
- const wechat = new WechatClient({ appid: config.appid, appsecret: config.appsecret });
103
+ const wechat = new WechatClient((0, config_1.resolveWechatCredentials)(config));
105
104
  await fixHtmlFile(htmlPath, { upload: true, wechat });
106
105
  (0, logger_1.info)(`已修复并上传正文图片: ${htmlPath}`);
107
106
  let finalCoverPath = resolveCoverValue(options.cover) ?? parsed.metadata.cover;
@@ -118,6 +117,7 @@ async function runDefaultPipeline(articlePath, options) {
118
117
  throw new Error('当前未提供封面图。请通过 --cover 指定,或去掉 --no-cover 以自动生成简易封面。');
119
118
  }
120
119
  const result = await (0, publish_1.publishDraft)(htmlPath, finalCoverPath, {
120
+ account: config.account,
121
121
  title,
122
122
  author,
123
123
  digest,
@@ -137,6 +137,7 @@ async function main() {
137
137
  .argument('[article]', 'Markdown 文件路径;传入后会自动执行 md2html -> fix -> cover -> publish')
138
138
  .option('--theme <theme>', `指定主题名,可用值:${(0, themes_1.listAvailableThemes)().join(', ')}`)
139
139
  .option('--author <author>', '覆盖作者名;未传时优先使用 front matter 或 config 中的 author')
140
+ .option('--account <name>', '指定本次发布使用的公众号账号')
140
141
  .option('--cover <cover.jpg>', '指定现成封面图;未传时默认自动生成')
141
142
  .option('--no-cover', '禁用自动封面生成;适合 front matter 已配置 cover 的场景')
142
143
  .option('--digest <digest>', '覆盖文章摘要;未传时自动从正文提取')
@@ -14,13 +14,14 @@ const config_1 = require("../utils/config");
14
14
  const fs_1 = require("../utils/fs");
15
15
  const logger_1 = require("../utils/logger");
16
16
  async function convertMarkdownFile(inputPath, outputPath, options) {
17
- const config = await (0, config_1.loadConfig)();
18
17
  const absoluteInputPath = node_path_1.default.resolve(inputPath);
19
18
  const raw = await (0, promises_1.readFile)(absoluteInputPath, 'utf8');
20
19
  const parsed = (0, parser_1.parseMarkdown)(raw);
20
+ const config = await (0, config_1.loadConfig)({ account: options?.account ?? parsed.metadata.account });
21
21
  const resolvedTheme = options?.theme ?? parsed.metadata.theme ?? config.defaultTheme ?? 'default';
22
22
  const html = await (0, converter_1.renderMarkdownToHtml)(parsed.body, {
23
23
  ...parsed.metadata,
24
+ account: options?.account ?? parsed.metadata.account ?? config.account,
24
25
  theme: (0, themes_1.assertThemeExists)(resolvedTheme),
25
26
  author: options?.author ?? parsed.metadata.author ?? config.author,
26
27
  sourceDir: node_path_1.default.dirname(absoluteInputPath)
@@ -12,14 +12,12 @@ const config_1 = require("../utils/config");
12
12
  const fs_1 = require("../utils/fs");
13
13
  const logger_1 = require("../utils/logger");
14
14
  async function publishDraft(articlePath, coverPath, options) {
15
- const config = await (0, config_1.loadConfig)();
16
- if (!config.appid || !config.appsecret) {
17
- (0, logger_1.error)('未配置微信认证信息,请先运行: wxgzh config --appid xxx --appsecret yyy');
18
- }
19
15
  const html = await (0, fs_1.readTextFile)(node_path_1.default.resolve(articlePath));
20
16
  const metadata = (0, converter_1.readHtmlMetadata)(html);
17
+ const config = await (0, config_1.loadConfig)({ account: options?.account ?? metadata.account });
18
+ const credentials = (0, config_1.resolveWechatCredentials)(config);
21
19
  const publishableContent = (0, converter_1.extractPublishableContent)(html);
22
- const client = new wechat_1.WechatClient({ appid: config.appid, appsecret: config.appsecret });
20
+ const client = new wechat_1.WechatClient(credentials);
23
21
  const cover = await client.uploadCoverImage(node_path_1.default.resolve(coverPath));
24
22
  return client.createDraft({
25
23
  title: options?.title ?? metadata.title ?? node_path_1.default.basename(articlePath, node_path_1.default.extname(articlePath)),
@@ -38,13 +36,15 @@ function registerPublishCommand(program) {
38
36
  .helpOption('-h, --help', '查看帮助')
39
37
  .requiredOption('--article <article.html>', '已处理好的 HTML 正文路径')
40
38
  .requiredOption('--cover <cover.jpg>', '封面图路径')
39
+ .option('--account <name>', '指定本次发布使用的公众号账号')
41
40
  .option('--title <title>', '覆盖文章标题')
42
41
  .option('--author <author>', '覆盖作者名')
43
42
  .option('--digest <digest>', '覆盖文章摘要')
44
43
  .option('--enable-comment', '为这篇草稿开启评论')
45
- .addHelpText('after', '\n示例:\n $ wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg\n $ wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg --title "新的标题"\n')
44
+ .addHelpText('after', '\n示例:\n $ wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg\n $ wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg --account 公众号名称1\n $ wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg --title "新的标题"\n')
46
45
  .action(async (options) => {
47
46
  const result = await publishDraft(options.article, options.cover, {
47
+ account: options.account,
48
48
  title: options.title,
49
49
  author: options.author,
50
50
  digest: options.digest,
@@ -478,12 +478,30 @@ function normalizeListItems($) {
478
478
  });
479
479
  const firstChild = childElements[0];
480
480
  if (!hasBlockChild && isTagNode(firstChild, 'span') && childElements.length === 1) {
481
+ $(firstChild).attr('data-wxgzh', 'li-inline');
481
482
  return;
482
483
  }
483
484
  if (hasBlockChild) {
485
+ const inlineNodes = [];
486
+ for (const node of childElements) {
487
+ if (node.type === 'tag') {
488
+ const tagName = node.tagName.toLowerCase();
489
+ if (['p', 'ul', 'ol', 'section', 'blockquote', 'pre', 'table'].includes(tagName)) {
490
+ break;
491
+ }
492
+ }
493
+ inlineNodes.push(node);
494
+ }
495
+ if (inlineNodes.length > 0) {
496
+ const inlineHtml = inlineNodes.map((node) => $.html(node)).join('');
497
+ for (const node of inlineNodes) {
498
+ $(node).remove();
499
+ }
500
+ $item.prepend(`<span data-wxgzh="li-inline">${inlineHtml}</span>`);
501
+ }
484
502
  return;
485
503
  }
486
- $item.html(`<span>${$item.html() ?? ''}</span>`);
504
+ $item.html(`<span data-wxgzh="li-inline">${$item.html() ?? ''}</span>`);
487
505
  });
488
506
  }
489
507
  function normalizeImages($) {
@@ -700,6 +718,38 @@ function normalizeInlinedListStyles($) {
700
718
  $item.removeAttr('style');
701
719
  });
702
720
  }
721
+ function decorateUnorderedListMarkers($) {
722
+ $('ul').each((_, element) => {
723
+ const $list = $(element);
724
+ const depth = $list.parents('ul').length;
725
+ const paddingLeft = depth === 0 ? '0 !important' : '1.2em !important';
726
+ $list.attr('style', setInlineStyleProperty($list.attr('style'), 'list-style', 'none !important'));
727
+ $list.attr('style', setInlineStyleProperty($list.attr('style'), 'margin-left', '0 !important'));
728
+ $list.attr('style', setInlineStyleProperty($list.attr('style'), 'padding-left', paddingLeft));
729
+ });
730
+ $('ul > li').each((_, element) => {
731
+ const $item = $(element);
732
+ const hasCheckbox = $item.children('input[type="checkbox"]').length > 0;
733
+ if (hasCheckbox) {
734
+ return;
735
+ }
736
+ $item.children('span[data-wxgzh="ul-marker"]').remove();
737
+ $item.attr('style', setInlineStyleProperty($item.attr('style'), 'list-style', 'none !important'));
738
+ $item.attr('style', setInlineStyleProperty($item.attr('style'), 'margin-left', '0 !important'));
739
+ const $content = $item.children('span[data-wxgzh="li-inline"]').first();
740
+ if ($content.length === 0) {
741
+ return;
742
+ }
743
+ $content.children('span[data-wxgzh="ul-marker"]').remove();
744
+ $content.prepend('<span data-wxgzh="ul-marker">●&nbsp;</span>');
745
+ const $marker = $content.children('span[data-wxgzh="ul-marker"]').first();
746
+ $marker.attr('style', setInlineStyleProperty($marker.attr('style'), 'display', 'inline !important'));
747
+ $marker.attr('style', setInlineStyleProperty($marker.attr('style'), 'line-height', 'inherit !important'));
748
+ $marker.attr('style', setInlineStyleProperty($marker.attr('style'), 'font-size', '1.1em !important'));
749
+ $marker.attr('style', setInlineStyleProperty($marker.attr('style'), 'font-weight', '700 !important'));
750
+ $marker.attr('style', setInlineStyleProperty($marker.attr('style'), 'color', '#000000 !important'));
751
+ });
752
+ }
703
753
  function removeWechatUnsafeListWhitespace($) {
704
754
  $('ol, ul').each((_, element) => {
705
755
  $(element)
@@ -807,6 +857,7 @@ function cleanInlinedHtml(html) {
807
857
  $element.html(renderWechatFriendlyCodeHtml($element.contents().toArray()));
808
858
  });
809
859
  normalizeInlinedListStyles($);
860
+ decorateUnorderedListMarkers($);
810
861
  removeWechatUnsafeListWhitespace($);
811
862
  normalizeWechatCodeBlocks($);
812
863
  const $article = $('body > #write > article').first();
@@ -80,6 +80,9 @@ function normalizeMetadata(data) {
80
80
  if (typeof data.theme === 'string' && data.theme.trim()) {
81
81
  metadata.theme = data.theme.trim();
82
82
  }
83
+ if (typeof data.account === 'string' && data.account.trim()) {
84
+ metadata.account = data.account.trim();
85
+ }
83
86
  if (typeof data.cover === 'string' && data.cover.trim()) {
84
87
  metadata.cover = data.cover.trim();
85
88
  }