@wenyan-md/core 3.0.5 → 3.0.6

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/dist/core.js CHANGED
@@ -194,9 +194,9 @@ function renderListStyleFootnotes(footnotes) {
194
194
  }
195
195
  async function handleFrontMatter(markdown) {
196
196
  const { attributes, body } = fm(markdown);
197
- const result = { body: body || "" };
197
+ const result = { content: body || "" };
198
198
  let head = "";
199
- const { title, description, cover, author, source_url } = attributes;
199
+ const { title, description, cover, author, source_url, need_open_comment, only_fans_can_comment, image_list } = attributes;
200
200
  if (title) {
201
201
  result.title = title;
202
202
  }
@@ -208,7 +208,7 @@ async function handleFrontMatter(markdown) {
208
208
  result.cover = cover;
209
209
  }
210
210
  if (head) {
211
- result.body = head + result.body;
211
+ result.content = head + result.content;
212
212
  }
213
213
  if (author) {
214
214
  result.author = author;
@@ -216,6 +216,15 @@ async function handleFrontMatter(markdown) {
216
216
  if (source_url) {
217
217
  result.source_url = source_url;
218
218
  }
219
+ if (need_open_comment !== void 0) {
220
+ result.need_open_comment = need_open_comment;
221
+ }
222
+ if (only_fans_can_comment !== void 0) {
223
+ result.only_fans_can_comment = only_fans_can_comment;
224
+ }
225
+ if (image_list) {
226
+ result.image_list = image_list;
227
+ }
219
228
  return result;
220
229
  }
221
230
  const parseOptions = {
@@ -1,4 +1,4 @@
1
- import { FrontMatterResult } from "./parser/frontMatterParser.js";
1
+ import { StyledContent } from "../node/types.js";
2
2
  export interface WenyanOptions {
3
3
  isConvertMathJax?: boolean;
4
4
  isWechat?: boolean;
@@ -12,7 +12,7 @@ export interface ApplyStylesOptions {
12
12
  isAddFootnote?: boolean;
13
13
  }
14
14
  export declare function createWenyanCore(options?: WenyanOptions): Promise<{
15
- handleFrontMatter(markdown: string): Promise<FrontMatterResult>;
15
+ handleFrontMatter(markdown: string): Promise<StyledContent>;
16
16
  renderMarkdown(markdown: string): Promise<string>;
17
17
  applyStylesWithTheme(wenyanElement: HTMLElement, options?: ApplyStylesOptions): Promise<string>;
18
18
  applyStylesWithResolvedCss(wenyanElement: HTMLElement, options: {
@@ -1,9 +1,12 @@
1
1
  export interface FrontMatterResult {
2
- body: string;
2
+ content: string;
3
3
  title?: string;
4
- cover?: string;
5
4
  description?: string;
5
+ cover?: string;
6
6
  author?: string;
7
7
  source_url?: string;
8
+ need_open_comment?: boolean;
9
+ only_fans_can_comment?: boolean;
10
+ image_list?: string[];
8
11
  }
9
12
  export declare function handleFrontMatter(markdown: string): Promise<FrontMatterResult>;
@@ -13,3 +13,4 @@ export declare function uploadStyledContent(gzhContent: StyledContent, serverUrl
13
13
  export declare function requestServerPublish(mdFileId: string, serverUrl: string, headers: Record<string, string>, options: ClientPublishOptions): Promise<string>;
14
14
  export declare function uploadLocalImages(content: string, serverUrl: string, headers: Record<string, string>, relativePath?: string): Promise<string>;
15
15
  export declare function uploadCover(serverUrl: string, headers: Record<string, string>, cover?: string, relativePath?: string): Promise<string | undefined>;
16
+ export declare function uploadImageList(serverUrl: string, headers: Record<string, string>, imageList?: string[], relativePath?: string): Promise<string[]>;
@@ -1,5 +1,5 @@
1
- import { WechatPublishResponse } from "../wechat.js";
2
- import { ArticleOptions, WechatPublisher } from "../publish.js";
1
+ import type { WechatPublishResponse } from "../wechat.js";
2
+ import { ArticleOptions, ImageTextArticleOptions, WechatPublisher } from "../publish.js";
3
3
  import { CredentialStore } from "../credentialStore.js";
4
4
  export declare const wechatPublisher: WechatPublisher;
5
5
  export declare const credentialStore: CredentialStore;
@@ -9,5 +9,9 @@ interface PublishOptions {
9
9
  relativePath?: string;
10
10
  }
11
11
  export declare function publishToWechatDraft(articleOptions: ArticleOptions, publishOptions?: PublishOptions): Promise<WechatPublishResponse>;
12
+ /**
13
+ * @deprecated use publishToWechatDraft instead
14
+ */
12
15
  export declare function publishToDraft(title: string, content: string, cover?: string, options?: PublishOptions): Promise<WechatPublishResponse>;
16
+ export declare function publishImageTextToWechatDraft(articleOptions: ImageTextArticleOptions, publishOptions?: PublishOptions): Promise<WechatPublishResponse>;
13
17
  export {};
@@ -1,5 +1,4 @@
1
1
  import { ApplyStylesOptions } from "../core/index.js";
2
- import { GetInputContentFn, RenderContext, RenderOptions, StyledContent } from "./types.js";
3
- export declare function renderWithTheme(markdownContent: string, options: RenderOptions): Promise<StyledContent>;
4
- export declare function renderStyledContent(content: string, options?: ApplyStylesOptions): Promise<StyledContent>;
2
+ import { GetInputContentFn, RenderContext, RenderOptions } from "./types.js";
3
+ export declare function renderStyledContent(content: string, options?: ApplyStylesOptions): Promise<string>;
5
4
  export declare function prepareRenderContext(inputContent: string | undefined, options: RenderOptions, getInputContent: GetInputContentFn): Promise<RenderContext>;
@@ -21,10 +21,13 @@ export interface RenderContext {
21
21
  export interface StyledContent {
22
22
  content: string;
23
23
  title?: string;
24
- cover?: string;
25
24
  description?: string;
25
+ cover?: string;
26
26
  author?: string;
27
27
  source_url?: string;
28
+ need_open_comment?: boolean;
29
+ only_fans_can_comment?: boolean;
30
+ image_list?: string[];
28
31
  }
29
32
  export type GetInputContentFn = (inputContent?: string, filePath?: string) => Promise<{
30
33
  content: string;
@@ -1,5 +1,4 @@
1
- import { ClientPublishOptions, GetInputContentFn, PublishOptions, StyledContent } from "./types.js";
2
- export declare function getGzhContent(content: string, themeId: string, hlThemeId: string, isMacStyle?: boolean, isAddFootnote?: boolean): Promise<StyledContent>;
1
+ import { ClientPublishOptions, GetInputContentFn, PublishOptions } from "./types.js";
3
2
  export declare function renderAndPublish(inputContent: string | undefined, options: PublishOptions, getInputContent: GetInputContentFn): Promise<string>;
4
3
  export declare function renderAndPublishToServer(inputContent: string | undefined, options: ClientPublishOptions, getInputContent: GetInputContentFn): Promise<string>;
5
4
  export * from "./configStore.js";
@@ -8,6 +8,11 @@ export interface ArticleOptions {
8
8
  cover?: string;
9
9
  author?: string;
10
10
  source_url?: string;
11
+ need_open_comment?: boolean;
12
+ only_fans_can_comment?: boolean;
13
+ }
14
+ export interface ImageTextArticleOptions extends ArticleOptions {
15
+ images: string[];
11
16
  }
12
17
  export declare class WechatPublisher {
13
18
  private tokenStore;
@@ -1,10 +1,28 @@
1
1
  import type { HttpAdapter } from "./http.js";
2
+ export interface ImageCropPercent {
3
+ ratio: string;
4
+ x1: number;
5
+ y1: number;
6
+ x2: number;
7
+ y2: number;
8
+ }
9
+ export interface ImageListItem {
10
+ image_media_id: string;
11
+ crop_percent_list?: ImageCropPercent[];
12
+ }
13
+ export interface ImageInfo {
14
+ image_list: ImageListItem[];
15
+ }
2
16
  export interface WechatPublishOptions {
3
17
  title: string;
4
18
  author?: string;
5
19
  content: string;
6
20
  thumb_media_id: string;
7
21
  content_source_url?: string;
22
+ article_type?: "news" | "newspic";
23
+ image_info?: ImageInfo;
24
+ need_open_comment?: 0 | 1;
25
+ only_fans_can_comment?: 0 | 1;
8
26
  }
9
27
  export interface WechatErrorResponse {
10
28
  errcode: number;
package/dist/wrapper.js CHANGED
@@ -327,6 +327,19 @@ async function uploadCover(serverUrl, headers, cover, relativePath) {
327
327
  }
328
328
  return cover;
329
329
  }
330
+ async function uploadImageList(serverUrl, headers, imageList, relativePath) {
331
+ if (imageList && imageList.length > 0) {
332
+ const uploadPromises = imageList.map(async (image) => {
333
+ if (needUpload(image)) {
334
+ const newImageUrl = await uploadLocalImage(image, serverUrl, headers, relativePath);
335
+ return newImageUrl || image;
336
+ }
337
+ return image;
338
+ });
339
+ return await Promise.all(uploadPromises);
340
+ }
341
+ return imageList || [];
342
+ }
330
343
  const nodeHttpAdapter = {
331
344
  fetch,
332
345
  createMultipart(field, file, filename) {
@@ -463,7 +476,7 @@ async function uploadImages(content, accessToken, relativePath, appId) {
463
476
  return { html: updatedHtml, firstImageId };
464
477
  }
465
478
  async function publishToWechatDraft(articleOptions, publishOptions = {}) {
466
- const { title, content, cover, author, source_url } = articleOptions;
479
+ const { title, content, cover, author, source_url, need_open_comment, only_fans_can_comment } = articleOptions;
467
480
  const { appId, appSecret, relativePath } = publishOptions;
468
481
  const { appId: appIdFinal, appSecret: appSecretFinal } = await getAppIdAndSecret(appId, appSecret);
469
482
  const accessToken = await wechatPublisher.getAccessTokenWithCache(appIdFinal, appSecretFinal);
@@ -498,7 +511,9 @@ async function publishToWechatDraft(articleOptions, publishOptions = {}) {
498
511
  content: html,
499
512
  thumb_media_id: thumbMediaId,
500
513
  author,
501
- content_source_url: source_url
514
+ content_source_url: source_url,
515
+ need_open_comment: need_open_comment ? 1 : 0,
516
+ only_fans_can_comment: only_fans_can_comment ? 1 : 0
502
517
  });
503
518
  if (data.media_id) {
504
519
  return data;
@@ -526,6 +541,52 @@ async function getAppIdAndSecret(appId, appSecret) {
526
541
  }
527
542
  throw new Error(`未能找到 AppID 为 "${appId}" 的公众号凭据,请检查配置文件。`);
528
543
  }
544
+ async function publishImageTextToWechatDraft(articleOptions, publishOptions = {}) {
545
+ const { title, content, images, cover, author, need_open_comment, only_fans_can_comment } = articleOptions;
546
+ const { appId, appSecret, relativePath } = publishOptions;
547
+ const { appId: appIdFinal, appSecret: appSecretFinal } = await getAppIdAndSecret(appId, appSecret);
548
+ if (!images || images.length === 0) {
549
+ throw new Error("图片消息至少需要一张图片");
550
+ }
551
+ const accessToken = await wechatPublisher.getAccessTokenWithCache(appIdFinal, appSecretFinal);
552
+ const imageInfoList = [];
553
+ for (const img of images) {
554
+ const resp = await uploadImage(img, accessToken, void 0, relativePath);
555
+ imageInfoList.push({ image_media_id: resp.media_id });
556
+ }
557
+ let thumbMediaId = "";
558
+ if (cover) {
559
+ const cachedThumbMediaId = mediaIdMapping.get(cover);
560
+ if (cachedThumbMediaId) {
561
+ thumbMediaId = cachedThumbMediaId;
562
+ } else {
563
+ const resp = await uploadImage(cover, accessToken, "cover.jpg", relativePath);
564
+ thumbMediaId = resp.media_id;
565
+ }
566
+ } else {
567
+ thumbMediaId = imageInfoList[0].image_media_id;
568
+ }
569
+ if (!thumbMediaId) {
570
+ throw new Error("未能获取封面图的 media_id");
571
+ }
572
+ const data = await wechatPublisher.publishToDraft(
573
+ accessToken,
574
+ {
575
+ title,
576
+ content,
577
+ thumb_media_id: thumbMediaId,
578
+ author,
579
+ article_type: "newspic",
580
+ image_info: { image_list: imageInfoList },
581
+ need_open_comment: need_open_comment ? 1 : 0,
582
+ only_fans_can_comment: only_fans_can_comment ? 1 : 0
583
+ }
584
+ );
585
+ if (data.media_id) {
586
+ return data;
587
+ }
588
+ throw new Error(`上传图片消息到公众号草稿失败: ${JSON.stringify(data)}`);
589
+ }
529
590
  const defaultConfig = {};
530
591
  const configPath = path.join(configDir, "config.json");
531
592
  class ConfigStore {
@@ -625,25 +686,22 @@ async function renderWithTheme(markdownContent, options) {
625
686
  return gzhContent;
626
687
  }
627
688
  async function renderStyledContent(content, options = {}) {
628
- const preHandlerContent = await wenyanCoreInstance.handleFrontMatter(content);
629
- const html = await wenyanCoreInstance.renderMarkdown(preHandlerContent.body);
689
+ const html = await wenyanCoreInstance.renderMarkdown(content);
630
690
  const dom = new JSDOM(`<body><section id="wenyan">${html}</section></body>`);
631
691
  const document = dom.window.document;
632
692
  const wenyan = document.getElementById("wenyan");
633
693
  const result = await wenyanCoreInstance.applyStylesWithTheme(wenyan, options);
634
- return {
635
- content: result,
636
- title: preHandlerContent.title,
637
- cover: preHandlerContent.cover,
638
- description: preHandlerContent.description,
639
- author: preHandlerContent.author,
640
- source_url: preHandlerContent.source_url
641
- };
694
+ return result;
642
695
  }
643
696
  async function prepareRenderContext(inputContent, options, getInputContent) {
644
697
  const { content, absoluteDirPath } = await getInputContent(inputContent, options.file);
645
- const gzhContent = await renderWithTheme(content, options);
646
- return { gzhContent, absoluteDirPath };
698
+ const preHandlerContent = await wenyanCoreInstance.handleFrontMatter(content);
699
+ if (preHandlerContent.image_list && preHandlerContent.image_list.length > 0) {
700
+ return { gzhContent: preHandlerContent, absoluteDirPath };
701
+ }
702
+ const styledContent = await renderWithTheme(preHandlerContent.content, options);
703
+ preHandlerContent.content = styledContent;
704
+ return { gzhContent: preHandlerContent, absoluteDirPath };
647
705
  }
648
706
  async function listThemes() {
649
707
  const themes = getAllGzhThemes();
@@ -705,30 +763,34 @@ async function checkCustomThemeExists(themeId) {
705
763
  const customThemes = await configStore.getThemes();
706
764
  return customThemes.some((theme) => theme.id === themeId);
707
765
  }
708
- async function getGzhContent(content, themeId, hlThemeId, isMacStyle = true, isAddFootnote = true) {
709
- return await renderStyledContent(content, {
710
- themeId,
711
- hlThemeId,
712
- isMacStyle,
713
- isAddFootnote
714
- });
715
- }
716
766
  async function renderAndPublish(inputContent, options, getInputContent) {
717
767
  const { gzhContent, absoluteDirPath } = await prepareRenderContext(inputContent, options, getInputContent);
718
768
  if (!gzhContent.title) throw new Error("未能找到文章标题");
719
- const data = await publishToWechatDraft(
720
- {
721
- title: gzhContent.title,
722
- content: gzhContent.content,
723
- cover: gzhContent.cover,
724
- author: gzhContent.author,
725
- source_url: gzhContent.source_url
726
- },
727
- {
728
- appId: options.appId,
729
- relativePath: absoluteDirPath
730
- }
731
- );
769
+ let data;
770
+ if (gzhContent.image_list && gzhContent.image_list.length > 0) {
771
+ data = await publishImageTextToWechatDraft(
772
+ {
773
+ ...gzhContent,
774
+ title: gzhContent.title,
775
+ images: gzhContent.image_list
776
+ },
777
+ {
778
+ appId: options.appId,
779
+ relativePath: absoluteDirPath
780
+ }
781
+ );
782
+ } else {
783
+ data = await publishToWechatDraft(
784
+ {
785
+ ...gzhContent,
786
+ title: gzhContent.title
787
+ },
788
+ {
789
+ appId: options.appId,
790
+ relativePath: absoluteDirPath
791
+ }
792
+ );
793
+ }
732
794
  if (data.media_id) {
733
795
  return data.media_id;
734
796
  } else {
@@ -743,7 +805,11 @@ async function renderAndPublishToServer(inputContent, options, getInputContent)
743
805
  await verifyAuth(serverUrl, headers);
744
806
  const { gzhContent, absoluteDirPath } = await prepareRenderContext(inputContent, options, getInputContent);
745
807
  if (!gzhContent.title) throw new Error("未能找到文章标题");
746
- gzhContent.content = await uploadLocalImages(gzhContent.content, serverUrl, headers, absoluteDirPath);
808
+ if (gzhContent.image_list && gzhContent.image_list.length > 0) {
809
+ gzhContent.image_list = await uploadImageList(serverUrl, headers, gzhContent.image_list, absoluteDirPath);
810
+ } else {
811
+ gzhContent.content = await uploadLocalImages(gzhContent.content, serverUrl, headers, absoluteDirPath);
812
+ }
747
813
  gzhContent.cover = await uploadCover(serverUrl, headers, gzhContent.cover, absoluteDirPath);
748
814
  const mdFileId = await uploadStyledContent(gzhContent, serverUrl, headers);
749
815
  return await requestServerPublish(mdFileId, serverUrl, headers, options);
@@ -755,7 +821,6 @@ export {
755
821
  configStore,
756
822
  credentialStore,
757
823
  ensureDir,
758
- getGzhContent,
759
824
  getNormalizeFilePath,
760
825
  isAbsolutePath,
761
826
  listThemes,
@@ -763,6 +828,7 @@ export {
763
828
  md5FromFile,
764
829
  normalizePath,
765
830
  prepareRenderContext,
831
+ publishImageTextToWechatDraft,
766
832
  publishToDraft,
767
833
  publishToWechatDraft,
768
834
  readBinaryFile,
@@ -771,7 +837,6 @@ export {
771
837
  renderAndPublish,
772
838
  renderAndPublishToServer,
773
839
  renderStyledContent,
774
- renderWithTheme,
775
840
  safeReadJson,
776
841
  safeWriteJson,
777
842
  wechatPublisher
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenyan-md/core",
3
- "version": "3.0.5",
3
+ "version": "3.0.6",
4
4
  "description": "Core library for Wenyan markdown rendering & publishing",
5
5
  "author": "Lei <caol64@gmail.com> (https://github.com/caol64)",
6
6
  "license": "Apache-2.0",