@wenyan-md/core 3.0.9 → 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,6 +1014,34 @@ function wechatPostRender(element) {
996
1014
  }
997
1015
  li.appendChild(section);
998
1016
  });
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
+ );
1042
+ }
1043
+ };
1044
+ flattenNestedLists();
999
1045
  element.style.color = "rgb(0, 0, 0)";
1000
1046
  element.style.caretColor = "rgb(0, 0, 0)";
1001
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,12 +40,49 @@ 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
  }
79
+ const WECHAT_ERROR_HINTS = {
80
+ 45166: "内容超长。小绿书模式有内容长度限制,请精简正文后重试。"
81
+ };
43
82
  function assertWechatSuccess(data) {
44
- if ("errcode" in data) {
45
- throw new Error(`${data.errcode}: ${data.errmsg}`);
83
+ if ("errcode" in data && data.errcode !== 0) {
84
+ const hint = WECHAT_ERROR_HINTS[data.errcode];
85
+ throw new Error(hint ? `${data.errcode}: ${hint} (${data.errmsg})` : `${data.errcode}: ${data.errmsg}`);
46
86
  }
47
87
  }
48
88
  export {
package/dist/wrapper.js CHANGED
@@ -269,7 +269,8 @@ function needUpload(url) {
269
269
  return !/^(https?:\/\/|data:|asset:\/\/)/i.test(url);
270
270
  }
271
271
  async function uploadLocalImage(originalUrl, serverUrl, headers, relativePath) {
272
- const imagePath = RuntimeEnv.resolveLocalPath(originalUrl, relativePath);
272
+ const decodedUrl = decodeURIComponent(originalUrl);
273
+ const imagePath = RuntimeEnv.resolveLocalPath(decodedUrl, relativePath);
273
274
  let fileBuffer;
274
275
  try {
275
276
  fileBuffer = await readBinaryFile(imagePath);
@@ -435,7 +436,8 @@ async function uploadImage(imageUrl, accessToken, fileName, relativePath, appId)
435
436
  const contentType = response.headers.get("content-type") || "image/jpeg";
436
437
  fileData = new Blob([arrayBuffer], { type: contentType });
437
438
  } else {
438
- const resolvedPath = RuntimeEnv.resolveLocalPath(imageUrl, relativePath);
439
+ const decodedUrl = decodeURIComponent(imageUrl);
440
+ const resolvedPath = RuntimeEnv.resolveLocalPath(decodedUrl, relativePath);
439
441
  const stats = await stat(resolvedPath);
440
442
  if (stats.size === 0) {
441
443
  throw new Error(`本地图片大小为0,无法上传: ${resolvedPath}`);
@@ -476,7 +478,17 @@ async function uploadImages(content, accessToken, relativePath, appId) {
476
478
  return { html: updatedHtml, firstImageId };
477
479
  }
478
480
  async function publishToWechatDraft(articleOptions, publishOptions = {}) {
479
- 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;
480
492
  const { appId, appSecret, relativePath } = publishOptions;
481
493
  const { appId: appIdFinal, appSecret: appSecretFinal } = await getAppIdAndSecret(appId, appSecret);
482
494
  const accessToken = await wechatPublisher.getAccessTokenWithCache(appIdFinal, appSecretFinal);
@@ -512,6 +524,8 @@ async function publishToWechatDraft(articleOptions, publishOptions = {}) {
512
524
  thumb_media_id: thumbMediaId,
513
525
  author,
514
526
  content_source_url: source_url,
527
+ pic_crop_235_1,
528
+ pic_crop_1_1,
515
529
  need_open_comment: need_open_comment ? 1 : 0,
516
530
  only_fans_can_comment: only_fans_can_comment ? 1 : 0
517
531
  });
@@ -569,11 +583,30 @@ async function publishImageTextToWechatDraft(articleOptions, publishOptions = {}
569
583
  if (!thumbMediaId) {
570
584
  throw new Error("未能获取封面图的 media_id");
571
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
+ }
572
605
  const data = await wechatPublisher.publishToDraft(
573
606
  accessToken,
574
607
  {
575
608
  title,
576
- content,
609
+ content: plainContent,
577
610
  thumb_media_id: thumbMediaId,
578
611
  author,
579
612
  article_type: "newspic",
@@ -862,7 +895,21 @@ async function extractAndCleanImages(body) {
862
895
  parent.remove();
863
896
  }
864
897
  }
865
- 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() };
866
913
  }
867
914
  const nodeMermaidRenderer = createNodeMermaidRenderer();
868
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.9",
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",