@wenyan-md/core 3.0.10 → 3.0.11

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
@@ -202,7 +202,19 @@ async function handleFrontMatter(markdown) {
202
202
  const { attributes, body } = fm(markdown);
203
203
  const result = { content: body || "" };
204
204
  let head = "";
205
- const { title, description, cover, author, source_url, need_open_comment, only_fans_can_comment, image_list, type } = attributes;
205
+ const {
206
+ title,
207
+ description,
208
+ cover,
209
+ author,
210
+ source_url,
211
+ pic_crop_235_1,
212
+ pic_crop_1_1,
213
+ need_open_comment,
214
+ only_fans_can_comment,
215
+ image_list,
216
+ type
217
+ } = attributes;
206
218
  if (title) {
207
219
  result.title = title;
208
220
  }
@@ -219,6 +231,12 @@ async function handleFrontMatter(markdown) {
219
231
  if (source_url) {
220
232
  result.source_url = source_url;
221
233
  }
234
+ if (pic_crop_235_1) {
235
+ result.pic_crop_235_1 = String(pic_crop_235_1);
236
+ }
237
+ if (pic_crop_1_1) {
238
+ result.pic_crop_1_1 = String(pic_crop_1_1);
239
+ }
222
240
  if (need_open_comment !== void 0) {
223
241
  result.need_open_comment = need_open_comment;
224
242
  }
@@ -996,15 +1014,34 @@ function wechatPostRender(element) {
996
1014
  }
997
1015
  li.appendChild(section);
998
1016
  });
999
- const nestedLists = element.querySelectorAll(
1000
- "li > section > ul, li > section > ol"
1001
- );
1002
- nestedLists.forEach((nestedList) => {
1003
- const li = nestedList.closest("li");
1004
- if (li) {
1005
- li.insertAdjacentElement("afterend", nestedList);
1017
+ const flattenNestedLists = () => {
1018
+ let nestedLists = element.querySelectorAll(
1019
+ "li > section > ul, li > section > ol"
1020
+ );
1021
+ while (nestedLists.length > 0) {
1022
+ Array.from(nestedLists).reverse().forEach((nestedList) => {
1023
+ const isOrdered = nestedList.tagName === "OL";
1024
+ const doc = element.ownerDocument;
1025
+ const items = Array.from(nestedList.children).filter(
1026
+ (child) => child.tagName === "LI"
1027
+ );
1028
+ const wrapper = doc.createElement("section");
1029
+ wrapper.style.marginLeft = "1em";
1030
+ items.forEach((item, index) => {
1031
+ const content = item.querySelector("section");
1032
+ const sectionEl = doc.createElement("section");
1033
+ const marker = isOrdered ? `${index + 1}. ` : "• ";
1034
+ sectionEl.innerHTML = marker + (content?.innerHTML ?? item.innerHTML);
1035
+ wrapper.appendChild(sectionEl);
1036
+ });
1037
+ nestedList.replaceWith(wrapper);
1038
+ });
1039
+ nestedLists = element.querySelectorAll(
1040
+ "li > section > ul, li > section > ol"
1041
+ );
1006
1042
  }
1007
- });
1043
+ };
1044
+ flattenNestedLists();
1008
1045
  element.style.color = "rgb(0, 0, 0)";
1009
1046
  element.style.caretColor = "rgb(0, 0, 0)";
1010
1047
  }
package/dist/publish.js CHANGED
@@ -196,12 +196,18 @@ class WechatPublisher {
196
196
  uploadCacheStore;
197
197
  uploadMaterial;
198
198
  publishArticle;
199
+ _listDraftsFn;
200
+ _getDraftFn;
201
+ _updateDraftFn;
199
202
  fetchAccessToken;
200
203
  constructor(httpAdapter, tokenStoreAdapter, uploadCacheStoreAdapter) {
201
- const { uploadMaterial, publishArticle, fetchAccessToken } = createWechatClient(httpAdapter);
204
+ const { uploadMaterial, publishArticle, fetchAccessToken, listDrafts, getDraft, updateDraft } = createWechatClient(httpAdapter);
202
205
  this.uploadMaterial = uploadMaterial;
203
206
  this.publishArticle = publishArticle;
204
207
  this.fetchAccessToken = fetchAccessToken;
208
+ this._listDraftsFn = listDrafts;
209
+ this._getDraftFn = getDraft;
210
+ this._updateDraftFn = updateDraft;
205
211
  this.tokenStore = tokenStoreAdapter ? new TokenStore(tokenStoreAdapter) : void 0;
206
212
  this.uploadCacheStore = uploadCacheStoreAdapter ? new UploadCacheStore(uploadCacheStoreAdapter) : void 0;
207
213
  }
@@ -242,6 +248,15 @@ class WechatPublisher {
242
248
  async publishToDraft(accessToken, options) {
243
249
  return await this.publishArticle(accessToken, options);
244
250
  }
251
+ async listDrafts(accessToken, offset = 0, count = 20, noContent = 0) {
252
+ return await this._listDraftsFn(accessToken, offset, count, noContent);
253
+ }
254
+ async getDraft(accessToken, mediaId) {
255
+ return await this._getDraftFn(accessToken, mediaId);
256
+ }
257
+ async updateDraft(accessToken, mediaId, articleIndex, options) {
258
+ await this._updateDraftFn(accessToken, mediaId, articleIndex, options);
259
+ }
245
260
  async clearCache() {
246
261
  if (this.tokenStore) {
247
262
  await this.tokenStore.clear();
@@ -5,6 +5,8 @@ export interface FrontMatterResult {
5
5
  cover?: string;
6
6
  author?: string;
7
7
  source_url?: string;
8
+ pic_crop_235_1?: string;
9
+ pic_crop_1_1?: string;
8
10
  need_open_comment?: boolean;
9
11
  only_fans_can_comment?: boolean;
10
12
  image_list?: string[];
@@ -1,13 +1,15 @@
1
1
  import { HttpAdapter } from "./http.js";
2
2
  import { TokenStorageAdapter } from "./tokenStore.js";
3
3
  import { UploadCacheStorageAdapter } from "./uploadCacheStore.js";
4
- import { WechatPublishOptions, WechatPublishResponse, WechatUploadResponse } from "./wechat.js";
4
+ import { WechatPublishOptions, WechatPublishResponse, WechatUploadResponse, WechatDraftListResponse, WechatDraftGetResponse } from "./wechat.js";
5
5
  export interface ArticleOptions {
6
6
  title: string;
7
7
  content: string;
8
8
  cover?: string;
9
9
  author?: string;
10
10
  source_url?: string;
11
+ pic_crop_235_1?: string;
12
+ pic_crop_1_1?: string;
11
13
  need_open_comment?: boolean;
12
14
  only_fans_can_comment?: boolean;
13
15
  }
@@ -19,11 +21,17 @@ export declare class WechatPublisher {
19
21
  private uploadCacheStore;
20
22
  private uploadMaterial;
21
23
  private publishArticle;
24
+ private _listDraftsFn;
25
+ private _getDraftFn;
26
+ private _updateDraftFn;
22
27
  private fetchAccessToken;
23
28
  constructor(httpAdapter: HttpAdapter, tokenStoreAdapter?: TokenStorageAdapter, uploadCacheStoreAdapter?: UploadCacheStorageAdapter);
24
29
  getAccessTokenWithCache(appId: string, appSecret: string): Promise<string>;
25
30
  uploadImage(file: Blob, filename: string, accessToken: string, appId?: string): Promise<WechatUploadResponse>;
26
31
  publishToDraft(accessToken: string, options: WechatPublishOptions): Promise<WechatPublishResponse>;
32
+ listDrafts(accessToken: string, offset?: number, count?: number, noContent?: number): Promise<WechatDraftListResponse>;
33
+ getDraft(accessToken: string, mediaId: string): Promise<WechatDraftGetResponse>;
34
+ updateDraft(accessToken: string, mediaId: string, articleIndex: number, options: Partial<WechatPublishOptions>): Promise<void>;
27
35
  clearCache(): Promise<void>;
28
36
  setExternalToken(appid: string, accessToken: string): Promise<void>;
29
37
  }
@@ -19,6 +19,8 @@ export interface WechatPublishOptions {
19
19
  content: string;
20
20
  thumb_media_id: string;
21
21
  content_source_url?: string;
22
+ pic_crop_235_1?: string;
23
+ pic_crop_1_1?: string;
22
24
  article_type?: "news" | "newspic";
23
25
  image_info?: ImageInfo;
24
26
  need_open_comment?: 0 | 1;
@@ -39,9 +41,31 @@ export interface WechatTokenResponse {
39
41
  export interface WechatPublishResponse {
40
42
  media_id: string;
41
43
  }
44
+ export interface WechatDraftListItem {
45
+ media_id: string;
46
+ content: {
47
+ news_item: WechatPublishOptions[];
48
+ };
49
+ update_time: number;
50
+ }
51
+ export interface WechatDraftListResponse {
52
+ total_count: number;
53
+ item_count: number;
54
+ item: WechatDraftListItem[];
55
+ }
56
+ export interface WechatDraftGetResponse {
57
+ news_item: WechatPublishOptions[];
58
+ }
59
+ export interface WechatDraftUpdateResponse {
60
+ errcode: number;
61
+ errmsg: string;
62
+ }
42
63
  export declare function createWechatClient(httpAdapter: HttpAdapter): {
43
64
  fetchAccessToken(appId: string, appSecret: string): Promise<WechatTokenResponse>;
44
65
  uploadMaterial(type: string, file: Blob, filename: string, accessToken: string): Promise<WechatUploadResponse>;
45
66
  publishArticle(accessToken: string, options: WechatPublishOptions): Promise<WechatPublishResponse>;
67
+ listDrafts(accessToken: string, offset?: number, count?: number, noContent?: number): Promise<WechatDraftListResponse>;
68
+ getDraft(accessToken: string, mediaId: string): Promise<WechatDraftGetResponse>;
69
+ updateDraft(accessToken: string, mediaId: string, articleIndex: number, options: Partial<WechatPublishOptions>): Promise<void>;
46
70
  };
47
71
  export type WechatClient = ReturnType<typeof createWechatClient>;
package/dist/wechat.js CHANGED
@@ -1,6 +1,9 @@
1
1
  const tokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
2
2
  const publishUrl = "https://api.weixin.qq.com/cgi-bin/draft/add";
3
3
  const uploadUrl = "https://api.weixin.qq.com/cgi-bin/material/add_material";
4
+ const draftListUrl = "https://api.weixin.qq.com/cgi-bin/draft/batchget";
5
+ const draftGetUrl = "https://api.weixin.qq.com/cgi-bin/draft/get";
6
+ const draftUpdateUrl = "https://api.weixin.qq.com/cgi-bin/draft/update";
4
7
  function createWechatClient(httpAdapter) {
5
8
  return {
6
9
  async fetchAccessToken(appId, appSecret) {
@@ -37,6 +40,39 @@ function createWechatClient(httpAdapter) {
37
40
  const data = await res.json();
38
41
  assertWechatSuccess(data);
39
42
  return data;
43
+ },
44
+ async listDrafts(accessToken, offset = 0, count = 20, noContent = 0) {
45
+ const res = await httpAdapter.fetch(`${draftListUrl}?access_token=${accessToken}`, {
46
+ method: "POST",
47
+ body: JSON.stringify({ offset, count, no_content: noContent })
48
+ });
49
+ if (!res.ok) throw new Error(await res.text());
50
+ const data = await res.json();
51
+ assertWechatSuccess(data);
52
+ return data;
53
+ },
54
+ async getDraft(accessToken, mediaId) {
55
+ const res = await httpAdapter.fetch(`${draftGetUrl}?access_token=${accessToken}`, {
56
+ method: "POST",
57
+ body: JSON.stringify({ media_id: mediaId })
58
+ });
59
+ if (!res.ok) throw new Error(await res.text());
60
+ const data = await res.json();
61
+ assertWechatSuccess(data);
62
+ return data;
63
+ },
64
+ async updateDraft(accessToken, mediaId, articleIndex, options) {
65
+ const res = await httpAdapter.fetch(`${draftUpdateUrl}?access_token=${accessToken}`, {
66
+ method: "POST",
67
+ body: JSON.stringify({
68
+ media_id: mediaId,
69
+ index: articleIndex,
70
+ articles: options
71
+ })
72
+ });
73
+ if (!res.ok) throw new Error(await res.text());
74
+ const data = await res.json();
75
+ assertWechatSuccess(data);
40
76
  }
41
77
  };
42
78
  }
@@ -44,7 +80,7 @@ const WECHAT_ERROR_HINTS = {
44
80
  45166: "内容超长。小绿书模式有内容长度限制,请精简正文后重试。"
45
81
  };
46
82
  function assertWechatSuccess(data) {
47
- if ("errcode" in data) {
83
+ if ("errcode" in data && data.errcode !== 0) {
48
84
  const hint = WECHAT_ERROR_HINTS[data.errcode];
49
85
  throw new Error(hint ? `${data.errcode}: ${hint} (${data.errmsg})` : `${data.errcode}: ${data.errmsg}`);
50
86
  }
package/dist/wrapper.js CHANGED
@@ -478,7 +478,17 @@ async function uploadImages(content, accessToken, relativePath, appId) {
478
478
  return { html: updatedHtml, firstImageId };
479
479
  }
480
480
  async function publishToWechatDraft(articleOptions, publishOptions = {}) {
481
- const { title, content, cover, author, source_url, need_open_comment, only_fans_can_comment } = articleOptions;
481
+ const {
482
+ title,
483
+ content,
484
+ cover,
485
+ author,
486
+ source_url,
487
+ pic_crop_235_1,
488
+ pic_crop_1_1,
489
+ need_open_comment,
490
+ only_fans_can_comment
491
+ } = articleOptions;
482
492
  const { appId, appSecret, relativePath } = publishOptions;
483
493
  const { appId: appIdFinal, appSecret: appSecretFinal } = await getAppIdAndSecret(appId, appSecret);
484
494
  const accessToken = await wechatPublisher.getAccessTokenWithCache(appIdFinal, appSecretFinal);
@@ -514,6 +524,8 @@ async function publishToWechatDraft(articleOptions, publishOptions = {}) {
514
524
  thumb_media_id: thumbMediaId,
515
525
  author,
516
526
  content_source_url: source_url,
527
+ pic_crop_235_1,
528
+ pic_crop_1_1,
517
529
  need_open_comment: need_open_comment ? 1 : 0,
518
530
  only_fans_can_comment: only_fans_can_comment ? 1 : 0
519
531
  });
@@ -571,11 +583,30 @@ async function publishImageTextToWechatDraft(articleOptions, publishOptions = {}
571
583
  if (!thumbMediaId) {
572
584
  throw new Error("未能获取封面图的 media_id");
573
585
  }
586
+ let plainContent = content || "";
587
+ if (plainContent) {
588
+ if (plainContent.includes("<br") || plainContent.includes("<p")) {
589
+ plainContent = plainContent.replace(/<br\s*\/?>\n/g, "\n");
590
+ const dom = new JSDOM(`<body>${plainContent}</body>`);
591
+ const document = dom.window.document;
592
+ const brs = document.querySelectorAll("br");
593
+ for (const br of brs) {
594
+ br.replaceWith(document.createTextNode("\n"));
595
+ }
596
+ const paragraphs = document.querySelectorAll("p");
597
+ for (const p of paragraphs) {
598
+ const text = document.createTextNode("\n" + p.textContent + "\n");
599
+ p.replaceWith(text);
600
+ }
601
+ plainContent = document.body.textContent?.trim() || "";
602
+ }
603
+ plainContent = plainContent.replace(/^#{1,6}\s+/gm, "").replace(/\*\*(.*?)\*\*/g, "$1").replace(/\*(.*?)\*/g, "$1").replace(/__(.*?)__/g, "$1").replace(/_(.*?)_/g, "$1").replace(/`{1,3}[^`]*`{1,3}/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "").replace(/^\s*[-*+]\s+/gm, "").replace(/^\s*\d+\.\s+/gm, "").replace(/^\s*>\s+/gm, "").replace(/\n{3,}/g, "\n\n").trim();
604
+ }
574
605
  const data = await wechatPublisher.publishToDraft(
575
606
  accessToken,
576
607
  {
577
608
  title,
578
- content,
609
+ content: plainContent,
579
610
  thumb_media_id: thumbMediaId,
580
611
  author,
581
612
  article_type: "newspic",
@@ -864,7 +895,21 @@ async function extractAndCleanImages(body) {
864
895
  parent.remove();
865
896
  }
866
897
  }
867
- return { imagePaths, cleanedHtml: wenyan.outerHTML };
898
+ for (const child of Array.from(wenyan.children)) {
899
+ if (!child.textContent?.trim()) {
900
+ child.remove();
901
+ }
902
+ }
903
+ const paragraphs = Array.from(wenyan.querySelectorAll("p"));
904
+ for (const p of paragraphs) {
905
+ const frag = document.createDocumentFragment();
906
+ while (p.firstChild) {
907
+ frag.appendChild(p.firstChild);
908
+ }
909
+ frag.appendChild(document.createElement("br"));
910
+ p.parentNode?.replaceChild(frag, p);
911
+ }
912
+ return { imagePaths, cleanedHtml: wenyan.innerHTML.trim() };
868
913
  }
869
914
  const nodeMermaidRenderer = createNodeMermaidRenderer();
870
915
  const wenyanCoreInstance = await createWenyanCore({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenyan-md/core",
3
- "version": "3.0.10",
3
+ "version": "3.0.11",
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",