@wenyan-md/core 2.0.4 → 2.0.5

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.
@@ -0,0 +1,111 @@
1
+ import path from "node:path";
2
+ import os from "node:os";
3
+ import fs from "node:fs";
4
+ import crypto from "node:crypto";
5
+ function safeReadJson(file, fallback) {
6
+ try {
7
+ return JSON.parse(fs.readFileSync(file, "utf-8"));
8
+ } catch {
9
+ return fallback;
10
+ }
11
+ }
12
+ function safeWriteJson(file, data) {
13
+ const tmp = file + ".tmp";
14
+ fs.writeFileSync(tmp, JSON.stringify(data ?? {}, null, 2), "utf-8");
15
+ fs.renameSync(tmp, file);
16
+ }
17
+ function ensureDir(dir) {
18
+ if (!fs.existsSync(dir)) {
19
+ fs.mkdirSync(dir, { recursive: true });
20
+ }
21
+ }
22
+ function md5FromBuffer(buf) {
23
+ return crypto.createHash("md5").update(buf).digest("hex");
24
+ }
25
+ function md5FromFile(filePath) {
26
+ const buf = fs.readFileSync(filePath);
27
+ return md5FromBuffer(buf);
28
+ }
29
+ const defaultConfig = {};
30
+ const configDir = process.env.APPDATA ? path.join(process.env.APPDATA, "wenyan-md") : path.join(os.homedir(), ".config", "wenyan-md");
31
+ const configPath = path.join(configDir, "config.json");
32
+ class ConfigStore {
33
+ config = { ...defaultConfig };
34
+ constructor() {
35
+ this.load();
36
+ }
37
+ load() {
38
+ ensureDir(configDir);
39
+ if (fs.existsSync(configPath)) {
40
+ this.config = {
41
+ ...defaultConfig,
42
+ ...safeReadJson(configPath, defaultConfig)
43
+ };
44
+ }
45
+ }
46
+ save() {
47
+ try {
48
+ ensureDir(configDir);
49
+ safeWriteJson(configPath, this.config);
50
+ } catch (error) {
51
+ console.error("❌ 无法保存配置文件:", error);
52
+ }
53
+ }
54
+ getConfig() {
55
+ return this.config;
56
+ }
57
+ getThemes() {
58
+ return Object.values(this.config.themes ?? {});
59
+ }
60
+ getThemeById(themeId) {
61
+ const themeOption = this.config.themes?.[themeId];
62
+ if (!themeOption) return;
63
+ const absoluteFilePath = path.join(configDir, themeOption.path);
64
+ try {
65
+ return fs.readFileSync(absoluteFilePath, "utf-8");
66
+ } catch {
67
+ return void 0;
68
+ }
69
+ }
70
+ addThemeToConfig(name, content) {
71
+ const savedPath = this.addThemeFile(name, content);
72
+ this.config.themes ??= {};
73
+ this.config.themes[name] = {
74
+ id: name,
75
+ name,
76
+ path: savedPath
77
+ };
78
+ this.save();
79
+ }
80
+ addThemeFile(themeId, themeContent) {
81
+ const filePath = `themes/${themeId}.css`;
82
+ const absoluteFilePath = path.join(configDir, filePath);
83
+ ensureDir(path.dirname(absoluteFilePath));
84
+ fs.writeFileSync(absoluteFilePath, themeContent, "utf-8");
85
+ return filePath;
86
+ }
87
+ deleteThemeFromConfig(themeId) {
88
+ const theme = this.config.themes?.[themeId];
89
+ if (!theme) return;
90
+ this.deleteThemeFile(theme.path);
91
+ delete this.config.themes[themeId];
92
+ this.save();
93
+ }
94
+ deleteThemeFile(filePath) {
95
+ try {
96
+ fs.unlinkSync(path.join(configDir, filePath));
97
+ } catch {
98
+ }
99
+ }
100
+ }
101
+ const configStore = new ConfigStore();
102
+ export {
103
+ configPath as a,
104
+ configStore as b,
105
+ configDir as c,
106
+ safeWriteJson as d,
107
+ ensureDir as e,
108
+ md5FromFile as f,
109
+ md5FromBuffer as m,
110
+ safeReadJson as s
111
+ };
package/dist/core.js CHANGED
@@ -196,7 +196,7 @@ async function handleFrontMatter(markdown) {
196
196
  const { attributes, body } = fm(markdown);
197
197
  const result = { body: body || "" };
198
198
  let head = "";
199
- const { title, description, cover } = attributes;
199
+ const { title, description, cover, author, source_url } = attributes;
200
200
  if (title) {
201
201
  result.title = title;
202
202
  }
@@ -210,6 +210,12 @@ async function handleFrontMatter(markdown) {
210
210
  if (head) {
211
211
  result.body = head + result.body;
212
212
  }
213
+ if (author) {
214
+ result.author = author;
215
+ }
216
+ if (source_url) {
217
+ result.source_url = source_url;
218
+ }
213
219
  return result;
214
220
  }
215
221
  const parseOptions = {
package/dist/publish.js CHANGED
@@ -6,6 +6,8 @@ import { createWechatClient } from "./wechat.js";
6
6
  import { FormDataEncoder } from "form-data-encoder";
7
7
  import { FormData } from "formdata-node";
8
8
  import { Readable } from "node:stream";
9
+ import fs from "node:fs";
10
+ import { c as configDir, e as ensureDir, s as safeReadJson, d as safeWriteJson, m as md5FromBuffer, f as md5FromFile } from "./configStore-lZ5bhrcC.js";
9
11
  function normalizePath(p) {
10
12
  return p.replace(/\\/g, "/").replace(/\/+$/, "");
11
13
  }
@@ -71,22 +73,114 @@ const nodeHttpAdapter = {
71
73
  };
72
74
  }
73
75
  };
76
+ const tokenPath = path.join(configDir, "token.json");
77
+ const defaultCache = {
78
+ appid: "",
79
+ accessToken: "",
80
+ expireAt: 0
81
+ };
82
+ class TokenStore {
83
+ cache = defaultCache;
84
+ constructor() {
85
+ this.load();
86
+ }
87
+ load() {
88
+ ensureDir(configDir);
89
+ if (fs.existsSync(tokenPath)) {
90
+ this.cache = safeReadJson(tokenPath, defaultCache);
91
+ }
92
+ }
93
+ save() {
94
+ try {
95
+ ensureDir(configDir);
96
+ safeWriteJson(tokenPath, this.cache);
97
+ } catch (error) {
98
+ console.error("❌ 无法保存 token:", error);
99
+ }
100
+ }
101
+ isValid(appid) {
102
+ if (!this.cache) return false;
103
+ return this.cache.appid === appid && this.cache.expireAt > Date.now() / 1e3 + 600;
104
+ }
105
+ getToken(appid) {
106
+ return this.isValid(appid) ? this.cache.accessToken : null;
107
+ }
108
+ setToken(appid, accessToken, expiresIn) {
109
+ this.cache = {
110
+ appid,
111
+ accessToken,
112
+ expireAt: Math.floor(Date.now() / 1e3) + expiresIn
113
+ };
114
+ this.save();
115
+ }
116
+ clear() {
117
+ this.cache = defaultCache;
118
+ try {
119
+ if (fs.existsSync(tokenPath)) {
120
+ fs.unlinkSync(tokenPath);
121
+ }
122
+ } catch {
123
+ }
124
+ }
125
+ }
126
+ const tokenStore = new TokenStore();
127
+ const cachePath = path.join(configDir, "upload-cache.json");
128
+ class UploadCacheStore {
129
+ cache = {};
130
+ constructor() {
131
+ this.load();
132
+ }
133
+ load() {
134
+ ensureDir(configDir);
135
+ if (fs.existsSync(cachePath)) {
136
+ this.cache = safeReadJson(cachePath, {});
137
+ }
138
+ }
139
+ save() {
140
+ try {
141
+ ensureDir(configDir);
142
+ safeWriteJson(cachePath, this.cache);
143
+ } catch (error) {
144
+ console.error("❌ 无法保存上传缓存:", error);
145
+ }
146
+ }
147
+ get(md5) {
148
+ return this.cache[md5];
149
+ }
150
+ set(md5, mediaId, url) {
151
+ this.cache[md5] = { media_id: mediaId, url, updated_at: Date.now() };
152
+ this.save();
153
+ }
154
+ }
155
+ const uploadCacheStore = new UploadCacheStore();
74
156
  const { uploadMaterial, publishArticle, fetchAccessToken } = createWechatClient(nodeHttpAdapter);
157
+ const mediaIdMapping = /* @__PURE__ */ new Map();
75
158
  async function uploadImage(imageUrl, accessToken, fileName, relativePath) {
76
159
  let fileData;
77
160
  let finalName;
161
+ let md5;
78
162
  if (imageUrl.startsWith("http")) {
79
163
  const response = await fetch(imageUrl);
80
164
  if (!response.ok || !response.body) {
81
165
  throw new Error(`Failed to download image from URL: ${imageUrl}`);
82
166
  }
167
+ const arrayBuffer = await response.arrayBuffer();
168
+ if (arrayBuffer.byteLength === 0) {
169
+ throw new Error(`远程图片大小为0,无法上传: ${imageUrl}`);
170
+ }
171
+ const buffer = Buffer.from(arrayBuffer);
172
+ md5 = md5FromBuffer(buffer);
173
+ const cached = uploadCacheStore.get(md5);
174
+ if (cached) {
175
+ mediaIdMapping.set(cached.url, cached.media_id);
176
+ return {
177
+ media_id: cached.media_id,
178
+ url: cached.url
179
+ };
180
+ }
83
181
  const fileNameFromUrl = path.basename(imageUrl.split("?")[0]);
84
182
  const ext = path.extname(fileNameFromUrl);
85
183
  finalName = fileName ?? (ext === "" ? `${fileNameFromUrl}.jpg` : fileNameFromUrl);
86
- const buffer = await response.arrayBuffer();
87
- if (buffer.byteLength === 0) {
88
- throw new Error(`远程图片大小为0,无法上传: ${imageUrl}`);
89
- }
90
184
  const contentType = response.headers.get("content-type") || "image/jpeg";
91
185
  fileData = new Blob([buffer], { type: contentType });
92
186
  } else {
@@ -95,6 +189,15 @@ async function uploadImage(imageUrl, accessToken, fileName, relativePath) {
95
189
  if (stats.size === 0) {
96
190
  throw new Error(`本地图片大小为0,无法上传: ${resolvedPath}`);
97
191
  }
192
+ md5 = md5FromFile(resolvedPath);
193
+ const cached = uploadCacheStore.get(md5);
194
+ if (cached) {
195
+ mediaIdMapping.set(cached.url, cached.media_id);
196
+ return {
197
+ media_id: cached.media_id,
198
+ url: cached.url
199
+ };
200
+ }
98
201
  const fileNameFromLocal = path.basename(resolvedPath);
99
202
  const ext = path.extname(fileNameFromLocal);
100
203
  finalName = fileName ?? (ext === "" ? `${fileNameFromLocal}.jpg` : fileNameFromLocal);
@@ -102,9 +205,8 @@ async function uploadImage(imageUrl, accessToken, fileName, relativePath) {
102
205
  fileData = new Blob([await fileFromPathResult.arrayBuffer()], { type: fileFromPathResult.type });
103
206
  }
104
207
  const data = await uploadMaterial("image", fileData, finalName, accessToken);
105
- if (data.errcode) {
106
- throw new Error(`上传失败,错误码:${data.errcode},错误信息:${data.errmsg}`);
107
- }
208
+ uploadCacheStore.set(md5, data.media_id, data.url);
209
+ mediaIdMapping.set(data.url, data.media_id);
108
210
  return data;
109
211
  }
110
212
  async function uploadImages(content, accessToken, relativePath) {
@@ -132,27 +234,36 @@ async function uploadImages(content, accessToken, relativePath) {
132
234
  const updatedHtml = dom.serialize();
133
235
  return { html: updatedHtml, firstImageId };
134
236
  }
135
- async function publishToDraft(title, content, cover = "", options = {}) {
136
- const { appId, appSecret, relativePath } = options;
137
- const appIdEnv = process.env.WECHAT_APP_ID || "";
138
- const appSecretEnv = process.env.WECHAT_APP_SECRET || "";
139
- const accessToken = await fetchAccessToken(appId ?? appIdEnv, appSecret ?? appSecretEnv);
140
- if (!accessToken.access_token) {
141
- if (accessToken.errcode) {
142
- throw new Error(`获取 Access Token 失败,错误码:${accessToken.errcode},${accessToken.errmsg}`);
143
- } else {
144
- throw new Error(`获取 Access Token 失败: ${accessToken}`);
145
- }
237
+ async function publishToWechatDraft(articleOptions, publishOptions = {}) {
238
+ const { title, content, cover, author, source_url } = articleOptions;
239
+ const { appId, appSecret, relativePath } = publishOptions;
240
+ const appIdFinal = appId ?? process.env.WECHAT_APP_ID;
241
+ const appSecretFinal = appSecret ?? process.env.WECHAT_APP_SECRET;
242
+ if (!appIdFinal || !appSecretFinal) {
243
+ throw new Error("请通过参数或环境变量 WECHAT_APP_ID / WECHAT_APP_SECRET 提供公众号凭据");
146
244
  }
147
- const { html, firstImageId } = await uploadImages(content, accessToken.access_token, relativePath);
245
+ const accessToken = await getAccessTokenWithCache(appIdFinal, appSecretFinal);
246
+ const { html, firstImageId } = await uploadImages(content, accessToken, relativePath);
148
247
  let thumbMediaId = "";
149
248
  if (cover) {
150
- const resp = await uploadImage(cover, accessToken.access_token, "cover.jpg", relativePath);
151
- thumbMediaId = resp.media_id;
249
+ const cachedThumbMediaId = mediaIdMapping.get(cover);
250
+ if (cachedThumbMediaId) {
251
+ thumbMediaId = cachedThumbMediaId;
252
+ } else {
253
+ const resp = await uploadImage(cover, accessToken, "cover.jpg", relativePath);
254
+ thumbMediaId = resp.media_id;
255
+ mediaIdMapping.set(resp.url, resp.media_id);
256
+ }
152
257
  } else {
153
258
  if (firstImageId.startsWith("https://mmbiz.qpic.cn")) {
154
- const resp = await uploadImage(firstImageId, accessToken.access_token, "cover.jpg", relativePath);
155
- thumbMediaId = resp.media_id;
259
+ const cachedThumbMediaId = mediaIdMapping.get(firstImageId);
260
+ if (cachedThumbMediaId) {
261
+ thumbMediaId = cachedThumbMediaId;
262
+ } else {
263
+ const resp = await uploadImage(firstImageId, accessToken, "cover.jpg", relativePath);
264
+ thumbMediaId = resp.media_id;
265
+ mediaIdMapping.set(resp.url, resp.media_id);
266
+ }
156
267
  } else {
157
268
  thumbMediaId = firstImageId;
158
269
  }
@@ -160,15 +271,31 @@ async function publishToDraft(title, content, cover = "", options = {}) {
160
271
  if (!thumbMediaId) {
161
272
  throw new Error("你必须指定一张封面图或者在正文中至少出现一张图片。");
162
273
  }
163
- const data = await publishArticle(title, html, thumbMediaId, accessToken.access_token);
274
+ const data = await publishArticle(accessToken, {
275
+ title,
276
+ content: html,
277
+ thumb_media_id: thumbMediaId,
278
+ author,
279
+ content_source_url: source_url
280
+ });
164
281
  if (data.media_id) {
165
282
  return data;
166
- } else if (data.errcode) {
167
- throw new Error(`上传到公众号草稿失败,错误码:${data.errcode},${data.errmsg}`);
168
- } else {
169
- throw new Error(`上传到公众号草稿失败: ${data}`);
170
283
  }
284
+ throw new Error(`上传到公众号草稿失败: ${JSON.stringify(data)}`);
285
+ }
286
+ async function publishToDraft(title, content, cover = "", options = {}) {
287
+ return publishToWechatDraft({ title, content, cover }, options);
288
+ }
289
+ async function getAccessTokenWithCache(appId, appSecret) {
290
+ const cached = tokenStore.getToken(appId);
291
+ if (cached) {
292
+ return cached;
293
+ }
294
+ const result = await fetchAccessToken(appId, appSecret);
295
+ tokenStore.setToken(appId, result.access_token, result.expires_in);
296
+ return result.access_token;
171
297
  }
172
298
  export {
173
- publishToDraft
299
+ publishToDraft,
300
+ publishToWechatDraft
174
301
  };
@@ -1,7 +1,9 @@
1
1
  export interface FrontMatterResult {
2
2
  body: string;
3
3
  title?: string;
4
- description?: string;
5
4
  cover?: string;
5
+ description?: string;
6
+ author?: string;
7
+ source_url?: string;
6
8
  }
7
9
  export declare function handleFrontMatter(markdown: string): Promise<FrontMatterResult>;
@@ -6,7 +6,3 @@ export interface HttpAdapter {
6
6
  fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
7
7
  createMultipart(field: string, file: Blob, filename: string): MultipartBody;
8
8
  }
9
- export interface UploadResponse {
10
- media_id: string;
11
- url: string;
12
- }
@@ -14,11 +14,10 @@ declare class ConfigStore {
14
14
  constructor();
15
15
  private load;
16
16
  private save;
17
- private mkdirIfNotExists;
18
17
  getConfig(): WenyanConfig;
19
- addThemeToConfig(name: string, content: string): void;
20
18
  getThemes(): ThemeConfigOptions[];
21
19
  getThemeById(themeId: string): string | undefined;
20
+ addThemeToConfig(name: string, content: string): void;
22
21
  addThemeFile(themeId: string, themeContent: string): string;
23
22
  deleteThemeFromConfig(themeId: string): void;
24
23
  deleteThemeFile(filePath: string): void;
@@ -3,4 +3,12 @@ export interface PublishOptions {
3
3
  appSecret?: string;
4
4
  relativePath?: string;
5
5
  }
6
- export declare function publishToDraft(title: string, content: string, cover?: string, options?: PublishOptions): Promise<any>;
6
+ export interface ArticleOptions {
7
+ title: string;
8
+ content: string;
9
+ cover?: string;
10
+ author?: string;
11
+ source_url?: string;
12
+ }
13
+ export declare function publishToWechatDraft(articleOptions: ArticleOptions, publishOptions?: PublishOptions): Promise<import("../wechat.js").WechatPublishResponse>;
14
+ export declare function publishToDraft(title: string, content: string, cover?: string, options?: PublishOptions): Promise<import("../wechat.js").WechatPublishResponse>;
@@ -0,0 +1,18 @@
1
+ export declare const tokenPath: string;
2
+ export interface TokenCache {
3
+ appid: string;
4
+ accessToken: string;
5
+ expireAt: number;
6
+ }
7
+ declare class TokenStore {
8
+ private cache;
9
+ constructor();
10
+ private load;
11
+ private save;
12
+ isValid(appid: string): boolean;
13
+ getToken(appid: string): string | null;
14
+ setToken(appid: string, accessToken: string, expiresIn: number): void;
15
+ clear(): void;
16
+ }
17
+ export declare const tokenStore: TokenStore;
18
+ export {};
@@ -0,0 +1,15 @@
1
+ export interface MediaInfo {
2
+ media_id: string;
3
+ url: string;
4
+ updated_at?: number;
5
+ }
6
+ declare class UploadCacheStore {
7
+ private cache;
8
+ constructor();
9
+ private load;
10
+ private save;
11
+ get(md5: string): MediaInfo;
12
+ set(md5: string, mediaId: string, url: string): void;
13
+ }
14
+ export declare const uploadCacheStore: UploadCacheStore;
15
+ export {};
@@ -0,0 +1,6 @@
1
+ import crypto from "node:crypto";
2
+ export declare function safeReadJson<T>(file: string, fallback: T): T;
3
+ export declare function safeWriteJson(file: string, data: unknown): void;
4
+ export declare function ensureDir(dir: string): void;
5
+ export declare function md5FromBuffer(buf: crypto.BinaryLike): string;
6
+ export declare function md5FromFile(filePath: string): string;
@@ -4,6 +4,8 @@ export interface StyledContent {
4
4
  title?: string;
5
5
  cover?: string;
6
6
  description?: string;
7
+ author?: string;
8
+ source_url?: string;
7
9
  }
8
10
  export declare function renderStyledContent(content: string, options?: ApplyStylesOptions): Promise<StyledContent>;
9
11
  export declare function getGzhContent(content: string, themeId: string, hlThemeId: string, isMacStyle?: boolean, isAddFootnote?: boolean): Promise<StyledContent>;
@@ -1,7 +1,29 @@
1
1
  import type { HttpAdapter } from "./http.js";
2
+ export interface WechatPublishOptions {
3
+ title: string;
4
+ author?: string;
5
+ content: string;
6
+ thumb_media_id: string;
7
+ content_source_url?: string;
8
+ }
9
+ export interface WechatErrorResponse {
10
+ errcode: number;
11
+ errmsg: string;
12
+ }
13
+ export interface WechatUploadResponse {
14
+ media_id: string;
15
+ url: string;
16
+ }
17
+ export interface WechatTokenResponse {
18
+ access_token: string;
19
+ expires_in: number;
20
+ }
21
+ export interface WechatPublishResponse {
22
+ media_id: string;
23
+ }
2
24
  export declare function createWechatClient(adapter: HttpAdapter): {
3
- fetchAccessToken(appId: string, appSecret: string): Promise<any>;
4
- uploadMaterial(type: string, file: Blob, filename: string, accessToken: string): Promise<any>;
5
- publishArticle(title: string, content: string, thumbMediaId: string, accessToken: string): Promise<any>;
25
+ fetchAccessToken(appId: string, appSecret: string): Promise<WechatTokenResponse>;
26
+ uploadMaterial(type: string, file: Blob, filename: string, accessToken: string): Promise<WechatUploadResponse>;
27
+ publishArticle(accessToken: string, options: WechatPublishOptions): Promise<WechatPublishResponse>;
6
28
  };
7
29
  export type WechatClient = ReturnType<typeof createWechatClient>;
package/dist/wechat.js CHANGED
@@ -8,7 +8,9 @@ function createWechatClient(adapter) {
8
8
  `${tokenUrl}?grant_type=client_credential&appid=${appId}&secret=${appSecret}`
9
9
  );
10
10
  if (!res.ok) throw new Error(await res.text());
11
- return res.json();
11
+ const data = await res.json();
12
+ assertWechatSuccess(data);
13
+ return data;
12
14
  },
13
15
  async uploadMaterial(type, file, filename, accessToken) {
14
16
  const multipart = adapter.createMultipart("media", file, filename);
@@ -16,27 +18,33 @@ function createWechatClient(adapter) {
16
18
  ...multipart,
17
19
  method: "POST"
18
20
  });
21
+ if (!res.ok) throw new Error(await res.text());
19
22
  const data = await res.json();
20
- if (data.errcode && data.errcode !== 0) {
21
- throw new Error(`${data.errcode}: ${data.errmsg}`);
22
- }
23
- if (data.url?.startsWith("http://")) {
23
+ assertWechatSuccess(data);
24
+ if (data.url.startsWith("http://")) {
24
25
  data.url = data.url.replace(/^http:\/\//i, "https://");
25
26
  }
26
27
  return data;
27
28
  },
28
- async publishArticle(title, content, thumbMediaId, accessToken) {
29
+ async publishArticle(accessToken, options) {
29
30
  const res = await adapter.fetch(`${publishUrl}?access_token=${accessToken}`, {
30
31
  method: "POST",
31
32
  body: JSON.stringify({
32
- articles: [{ title, content, thumb_media_id: thumbMediaId }]
33
+ articles: [options]
33
34
  })
34
35
  });
35
36
  if (!res.ok) throw new Error(await res.text());
36
- return res.json();
37
+ const data = await res.json();
38
+ assertWechatSuccess(data);
39
+ return data;
37
40
  }
38
41
  };
39
42
  }
43
+ function assertWechatSuccess(data) {
44
+ if ("errcode" in data) {
45
+ throw new Error(`${data.errcode}: ${data.errmsg}`);
46
+ }
47
+ }
40
48
  export {
41
49
  createWechatClient
42
50
  };
package/dist/wrapper.js CHANGED
@@ -1,92 +1,6 @@
1
1
  import { JSDOM } from "jsdom";
2
2
  import { createWenyanCore } from "./core.js";
3
- import path from "node:path";
4
- import os from "node:os";
5
- import fs from "node:fs";
6
- const defaultConfig = {};
7
- const configDir = process.env.APPDATA ? path.join(process.env.APPDATA, "wenyan-md") : path.join(os.homedir(), ".config", "wenyan-md");
8
- const configPath = path.join(configDir, "config.json");
9
- class ConfigStore {
10
- config = { ...defaultConfig };
11
- constructor() {
12
- this.load();
13
- }
14
- load() {
15
- if (fs.existsSync(configPath)) {
16
- try {
17
- const fileContent = fs.readFileSync(configPath, "utf-8");
18
- this.config = { ...defaultConfig, ...JSON.parse(fileContent) };
19
- } catch (error) {
20
- console.warn("⚠️ 配置文件解析失败,将使用默认配置");
21
- this.config = { ...defaultConfig };
22
- }
23
- }
24
- }
25
- save() {
26
- this.mkdirIfNotExists();
27
- try {
28
- fs.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
29
- } catch (error) {
30
- console.error("❌ 无法保存配置文件:", error);
31
- }
32
- }
33
- mkdirIfNotExists(dir = configDir) {
34
- try {
35
- if (!fs.existsSync(dir)) {
36
- fs.mkdirSync(dir, { recursive: true });
37
- }
38
- } catch (error) {
39
- console.error("❌ 无法创建配置目录:", error);
40
- }
41
- }
42
- getConfig() {
43
- return this.config;
44
- }
45
- addThemeToConfig(name, content) {
46
- const savedPath = this.addThemeFile(name, content);
47
- this.config.themes = this.config.themes || {};
48
- this.config.themes[name] = {
49
- id: name,
50
- name,
51
- path: savedPath
52
- };
53
- this.save();
54
- }
55
- getThemes() {
56
- return this.config.themes ? Object.values(this.config.themes) : [];
57
- }
58
- getThemeById(themeId) {
59
- const themeOption = this.config.themes ? this.config.themes[themeId] : void 0;
60
- if (themeOption) {
61
- const absoluteFilePath = path.join(configDir, themeOption.path);
62
- if (fs.existsSync(absoluteFilePath)) {
63
- return fs.readFileSync(absoluteFilePath, "utf-8");
64
- }
65
- }
66
- return void 0;
67
- }
68
- addThemeFile(themeId, themeContent) {
69
- const filePath = `themes/${themeId}.css`;
70
- const absoluteFilePath = path.join(configDir, filePath);
71
- this.mkdirIfNotExists(path.dirname(absoluteFilePath));
72
- fs.writeFileSync(absoluteFilePath, themeContent, "utf-8");
73
- return filePath;
74
- }
75
- deleteThemeFromConfig(themeId) {
76
- if (this.config.themes && this.config.themes[themeId]) {
77
- this.deleteThemeFile(this.config.themes[themeId].path);
78
- delete this.config.themes[themeId];
79
- this.save();
80
- }
81
- }
82
- deleteThemeFile(filePath) {
83
- const absoluteFilePath = path.join(configDir, filePath);
84
- if (fs.existsSync(absoluteFilePath)) {
85
- fs.unlinkSync(absoluteFilePath);
86
- }
87
- }
88
- }
89
- const configStore = new ConfigStore();
3
+ import { c, a, b } from "./configStore-lZ5bhrcC.js";
90
4
  const wenyanCoreInstance = await createWenyanCore();
91
5
  async function renderStyledContent(content, options = {}) {
92
6
  const preHandlerContent = await wenyanCoreInstance.handleFrontMatter(content);
@@ -99,7 +13,9 @@ async function renderStyledContent(content, options = {}) {
99
13
  content: result,
100
14
  title: preHandlerContent.title,
101
15
  cover: preHandlerContent.cover,
102
- description: preHandlerContent.description
16
+ description: preHandlerContent.description,
17
+ author: preHandlerContent.author,
18
+ source_url: preHandlerContent.source_url
103
19
  };
104
20
  }
105
21
  async function getGzhContent(content, themeId, hlThemeId, isMacStyle = true, isAddFootnote = true) {
@@ -111,9 +27,9 @@ async function getGzhContent(content, themeId, hlThemeId, isMacStyle = true, isA
111
27
  });
112
28
  }
113
29
  export {
114
- configDir,
115
- configPath,
116
- configStore,
30
+ c as configDir,
31
+ a as configPath,
32
+ b as configStore,
117
33
  getGzhContent,
118
34
  renderStyledContent
119
35
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenyan-md/core",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
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",