@wenyan-md/core 2.0.1 → 2.0.3

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
@@ -1293,6 +1293,7 @@ const DEFAULT_CSS_UPDATES = {
1293
1293
  ]
1294
1294
  };
1295
1295
  export {
1296
+ addFootnotes,
1296
1297
  createCssModifier,
1297
1298
  createWenyanCore,
1298
1299
  getAllGzhThemes,
package/dist/publish.js CHANGED
@@ -2,100 +2,11 @@ import { JSDOM } from "jsdom";
2
2
  import { fileFromPath } from "formdata-node/file-from-path";
3
3
  import path from "node:path";
4
4
  import { stat } from "node:fs/promises";
5
+ import { R as RuntimeEnv } from "./runtimeEnv-pU2mTDLR.js";
6
+ import { createWechatClient } from "./wechat.js";
5
7
  import { FormDataEncoder } from "form-data-encoder";
6
8
  import { FormData } from "formdata-node";
7
9
  import { Readable } from "node:stream";
8
- function normalizePath(p) {
9
- return p.replace(/\\/g, "/").replace(/\/+$/, "");
10
- }
11
- function isAbsolutePath(path2) {
12
- if (!path2) return false;
13
- const winAbsPattern = /^[a-zA-Z]:\//;
14
- const linuxAbsPattern = /^\//;
15
- return winAbsPattern.test(path2) || linuxAbsPattern.test(path2);
16
- }
17
- const RuntimeEnv = {
18
- isContainer: !!process.env.CONTAINERIZED,
19
- hostFilePath: normalizePath(process.env.HOST_FILE_PATH || ""),
20
- containerFilePath: normalizePath(process.env.CONTAINER_FILE_PATH || "/mnt/host-downloads"),
21
- resolveLocalPath(inputPath, relativeBase) {
22
- if (!this.isContainer) {
23
- if (relativeBase) {
24
- return path.resolve(relativeBase, inputPath);
25
- } else {
26
- if (!path.isAbsolute(inputPath)) {
27
- throw new Error(
28
- `Invalid input: '${inputPath}'. When relativeBase is not provided, inputPath must be an absolute path.`
29
- );
30
- }
31
- return path.normalize(inputPath);
32
- }
33
- }
34
- let normalizedInput = normalizePath(inputPath);
35
- relativeBase = normalizePath(relativeBase || "");
36
- if (relativeBase) {
37
- if (!isAbsolutePath(normalizedInput)) {
38
- normalizedInput = relativeBase + (normalizedInput.startsWith("/") ? "" : "/") + normalizedInput;
39
- }
40
- } else {
41
- if (!isAbsolutePath(normalizedInput)) {
42
- throw new Error(
43
- `Invalid input: '${inputPath}'. When relativeBase is not provided, inputPath must be an absolute path.`
44
- );
45
- }
46
- }
47
- if (normalizedInput.startsWith(this.hostFilePath)) {
48
- let relativePart = normalizedInput.slice(this.hostFilePath.length);
49
- if (relativePart && !relativePart.startsWith("/")) {
50
- return normalizedInput;
51
- }
52
- if (!relativePart.startsWith("/")) {
53
- relativePart = "/" + relativePart;
54
- }
55
- return this.containerFilePath + relativePart;
56
- }
57
- return normalizedInput;
58
- }
59
- };
60
- const tokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
61
- const publishUrl = "https://api.weixin.qq.com/cgi-bin/draft/add";
62
- const uploadUrl = "https://api.weixin.qq.com/cgi-bin/material/add_material";
63
- function createWechatClient(adapter) {
64
- return {
65
- async fetchAccessToken(appId, appSecret) {
66
- const res = await adapter.fetch(
67
- `${tokenUrl}?grant_type=client_credential&appid=${appId}&secret=${appSecret}`
68
- );
69
- if (!res.ok) throw new Error(await res.text());
70
- return res.json();
71
- },
72
- async uploadMaterial(type, file, filename, accessToken) {
73
- const multipart = adapter.createMultipart("media", file, filename);
74
- const res = await adapter.fetch(`${uploadUrl}?access_token=${accessToken}&type=${type}`, {
75
- ...multipart,
76
- method: "POST"
77
- });
78
- const data = await res.json();
79
- if (data.errcode && data.errcode !== 0) {
80
- throw new Error(`${data.errcode}: ${data.errmsg}`);
81
- }
82
- if (data.url?.startsWith("http://")) {
83
- data.url = data.url.replace(/^http:\/\//i, "https://");
84
- }
85
- return data;
86
- },
87
- async publishArticle(title, content, thumbMediaId, accessToken) {
88
- const res = await adapter.fetch(`${publishUrl}?access_token=${accessToken}`, {
89
- method: "POST",
90
- body: JSON.stringify({
91
- articles: [{ title, content, thumb_media_id: thumbMediaId }]
92
- })
93
- });
94
- if (!res.ok) throw new Error(await res.text());
95
- return res.json();
96
- }
97
- };
98
- }
99
10
  const nodeHttpAdapter = {
100
11
  fetch,
101
12
  createMultipart(field, file, filename) {
@@ -0,0 +1,56 @@
1
+ import path from "node:path";
2
+ function normalizePath(p) {
3
+ return p.replace(/\\/g, "/").replace(/\/+$/, "");
4
+ }
5
+ function isAbsolutePath(path2) {
6
+ if (!path2) return false;
7
+ const winAbsPattern = /^[a-zA-Z]:\//;
8
+ const linuxAbsPattern = /^\//;
9
+ return winAbsPattern.test(path2) || linuxAbsPattern.test(path2);
10
+ }
11
+ const RuntimeEnv = {
12
+ isContainer: !!process.env.CONTAINERIZED,
13
+ hostFilePath: normalizePath(process.env.HOST_FILE_PATH || ""),
14
+ containerFilePath: normalizePath(process.env.CONTAINER_FILE_PATH || "/mnt/host-downloads"),
15
+ resolveLocalPath(inputPath, relativeBase) {
16
+ if (!this.isContainer) {
17
+ if (relativeBase) {
18
+ return path.resolve(relativeBase, inputPath);
19
+ } else {
20
+ if (!path.isAbsolute(inputPath)) {
21
+ throw new Error(
22
+ `Invalid input: '${inputPath}'. When relativeBase is not provided, inputPath must be an absolute path.`
23
+ );
24
+ }
25
+ return path.normalize(inputPath);
26
+ }
27
+ }
28
+ let normalizedInput = normalizePath(inputPath);
29
+ relativeBase = normalizePath(relativeBase || "");
30
+ if (relativeBase) {
31
+ if (!isAbsolutePath(normalizedInput)) {
32
+ normalizedInput = relativeBase + (normalizedInput.startsWith("/") ? "" : "/") + normalizedInput;
33
+ }
34
+ } else {
35
+ if (!isAbsolutePath(normalizedInput)) {
36
+ throw new Error(
37
+ `Invalid input: '${inputPath}'. When relativeBase is not provided, inputPath must be an absolute path.`
38
+ );
39
+ }
40
+ }
41
+ if (normalizedInput.startsWith(this.hostFilePath)) {
42
+ let relativePart = normalizedInput.slice(this.hostFilePath.length);
43
+ if (relativePart && !relativePart.startsWith("/")) {
44
+ return normalizedInput;
45
+ }
46
+ if (!relativePart.startsWith("/")) {
47
+ relativePart = "/" + relativePart;
48
+ }
49
+ return this.containerFilePath + relativePart;
50
+ }
51
+ return normalizedInput;
52
+ }
53
+ };
54
+ export {
55
+ RuntimeEnv as R
56
+ };
@@ -30,3 +30,4 @@ export { getMacStyleCss, registerMacStyle } from "./theme/macStyleRegistry.js";
30
30
  export * from "./platform/medium.js";
31
31
  export * from "./platform/zhihu.js";
32
32
  export * from "./platform/toutiao.js";
33
+ export { addFootnotes } from "./renderer/footnotesRender.js";
@@ -0,0 +1,27 @@
1
+ export interface WenyanConfig {
2
+ themes?: Record<string, ThemeConfigOptions>;
3
+ }
4
+ export interface ThemeConfigOptions {
5
+ id: string;
6
+ name?: string;
7
+ description?: string;
8
+ path: string;
9
+ }
10
+ export declare const configDir: string;
11
+ export declare const configPath: string;
12
+ declare class ConfigStore {
13
+ private config;
14
+ constructor();
15
+ private load;
16
+ private save;
17
+ private mkdirIfNotExists;
18
+ getConfig(): WenyanConfig;
19
+ addThemeToConfig(name: string, content: string): void;
20
+ getThemes(): ThemeConfigOptions[];
21
+ getThemeById(themeId: string): string | undefined;
22
+ addThemeFile(themeId: string, themeContent: string): string;
23
+ deleteThemeFromConfig(themeId: string): void;
24
+ deleteThemeFile(filePath: string): void;
25
+ }
26
+ export declare const configStore: ConfigStore;
27
+ export {};
@@ -7,3 +7,5 @@ export interface StyledContent {
7
7
  }
8
8
  export declare function renderStyledContent(content: string, options?: ApplyStylesOptions): Promise<StyledContent>;
9
9
  export declare function getGzhContent(content: string, themeId: string, hlThemeId: string, isMacStyle?: boolean, isAddFootnote?: boolean): Promise<StyledContent>;
10
+ export * from "./configStore.js";
11
+ export * from "./runtimeEnv.js";
@@ -4,3 +4,6 @@ export declare function createWechatClient(adapter: HttpAdapter): {
4
4
  uploadMaterial(type: string, file: Blob, filename: string, accessToken: string): Promise<any>;
5
5
  publishArticle(title: string, content: string, thumbMediaId: string, accessToken: string): Promise<any>;
6
6
  };
7
+ export type WechatClient = ReturnType<typeof createWechatClient>;
8
+ export * from "./http.js";
9
+ export * from "./adapters/browser.js";
package/dist/wechat.js ADDED
@@ -0,0 +1,53 @@
1
+ const browserHttpAdapter = {
2
+ fetch: window.fetch.bind(window),
3
+ createMultipart(field, file, filename) {
4
+ const form = new FormData();
5
+ form.append(field, file, filename);
6
+ return {
7
+ body: form
8
+ };
9
+ }
10
+ };
11
+ const tokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
12
+ const publishUrl = "https://api.weixin.qq.com/cgi-bin/draft/add";
13
+ const uploadUrl = "https://api.weixin.qq.com/cgi-bin/material/add_material";
14
+ function createWechatClient(adapter) {
15
+ return {
16
+ async fetchAccessToken(appId, appSecret) {
17
+ const res = await adapter.fetch(
18
+ `${tokenUrl}?grant_type=client_credential&appid=${appId}&secret=${appSecret}`
19
+ );
20
+ if (!res.ok) throw new Error(await res.text());
21
+ return res.json();
22
+ },
23
+ async uploadMaterial(type, file, filename, accessToken) {
24
+ const multipart = adapter.createMultipart("media", file, filename);
25
+ const res = await adapter.fetch(`${uploadUrl}?access_token=${accessToken}&type=${type}`, {
26
+ ...multipart,
27
+ method: "POST"
28
+ });
29
+ const data = await res.json();
30
+ if (data.errcode && data.errcode !== 0) {
31
+ throw new Error(`${data.errcode}: ${data.errmsg}`);
32
+ }
33
+ if (data.url?.startsWith("http://")) {
34
+ data.url = data.url.replace(/^http:\/\//i, "https://");
35
+ }
36
+ return data;
37
+ },
38
+ async publishArticle(title, content, thumbMediaId, accessToken) {
39
+ const res = await adapter.fetch(`${publishUrl}?access_token=${accessToken}`, {
40
+ method: "POST",
41
+ body: JSON.stringify({
42
+ articles: [{ title, content, thumb_media_id: thumbMediaId }]
43
+ })
44
+ });
45
+ if (!res.ok) throw new Error(await res.text());
46
+ return res.json();
47
+ }
48
+ };
49
+ }
50
+ export {
51
+ browserHttpAdapter,
52
+ createWechatClient
53
+ };
package/dist/wrapper.js CHANGED
@@ -1,5 +1,93 @@
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
+ import { R } from "./runtimeEnv-pU2mTDLR.js";
7
+ const defaultConfig = {};
8
+ const configDir = process.env.APPDATA ? path.join(process.env.APPDATA, "wenyan-md") : path.join(os.homedir(), ".config", "wenyan-md");
9
+ const configPath = path.join(configDir, "config.json");
10
+ class ConfigStore {
11
+ config = { ...defaultConfig };
12
+ constructor() {
13
+ this.load();
14
+ }
15
+ load() {
16
+ if (fs.existsSync(configPath)) {
17
+ try {
18
+ const fileContent = fs.readFileSync(configPath, "utf-8");
19
+ this.config = { ...defaultConfig, ...JSON.parse(fileContent) };
20
+ } catch (error) {
21
+ console.warn("⚠️ 配置文件解析失败,将使用默认配置");
22
+ this.config = { ...defaultConfig };
23
+ }
24
+ }
25
+ }
26
+ save() {
27
+ this.mkdirIfNotExists();
28
+ try {
29
+ fs.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
30
+ } catch (error) {
31
+ console.error("❌ 无法保存配置文件:", error);
32
+ }
33
+ }
34
+ mkdirIfNotExists(dir = configDir) {
35
+ try {
36
+ if (!fs.existsSync(dir)) {
37
+ fs.mkdirSync(dir, { recursive: true });
38
+ }
39
+ } catch (error) {
40
+ console.error("❌ 无法创建配置目录:", error);
41
+ }
42
+ }
43
+ getConfig() {
44
+ return this.config;
45
+ }
46
+ addThemeToConfig(name, content) {
47
+ const savedPath = this.addThemeFile(name, content);
48
+ this.config.themes = this.config.themes || {};
49
+ this.config.themes[name] = {
50
+ id: name,
51
+ name,
52
+ path: savedPath
53
+ };
54
+ this.save();
55
+ }
56
+ getThemes() {
57
+ return this.config.themes ? Object.values(this.config.themes) : [];
58
+ }
59
+ getThemeById(themeId) {
60
+ const themeOption = this.config.themes ? this.config.themes[themeId] : void 0;
61
+ if (themeOption) {
62
+ const absoluteFilePath = path.join(configDir, themeOption.path);
63
+ if (fs.existsSync(absoluteFilePath)) {
64
+ return fs.readFileSync(absoluteFilePath, "utf-8");
65
+ }
66
+ }
67
+ return void 0;
68
+ }
69
+ addThemeFile(themeId, themeContent) {
70
+ const filePath = `themes/${themeId}.css`;
71
+ const absoluteFilePath = path.join(configDir, filePath);
72
+ this.mkdirIfNotExists(path.dirname(absoluteFilePath));
73
+ fs.writeFileSync(absoluteFilePath, themeContent, "utf-8");
74
+ return filePath;
75
+ }
76
+ deleteThemeFromConfig(themeId) {
77
+ if (this.config.themes && this.config.themes[themeId]) {
78
+ this.deleteThemeFile(this.config.themes[themeId].path);
79
+ delete this.config.themes[themeId];
80
+ this.save();
81
+ }
82
+ }
83
+ deleteThemeFile(filePath) {
84
+ const absoluteFilePath = path.join(configDir, filePath);
85
+ if (fs.existsSync(absoluteFilePath)) {
86
+ fs.unlinkSync(absoluteFilePath);
87
+ }
88
+ }
89
+ }
90
+ const configStore = new ConfigStore();
3
91
  const wenyanCoreInstance = await createWenyanCore();
4
92
  async function renderStyledContent(content, options = {}) {
5
93
  const preHandlerContent = await wenyanCoreInstance.handleFrontMatter(content);
@@ -24,6 +112,10 @@ async function getGzhContent(content, themeId, hlThemeId, isMacStyle = true, isA
24
112
  });
25
113
  }
26
114
  export {
115
+ R as RuntimeEnv,
116
+ configDir,
117
+ configPath,
118
+ configStore,
27
119
  getGzhContent,
28
120
  renderStyledContent
29
121
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenyan-md/core",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
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",
@@ -38,13 +38,16 @@
38
38
  "./wrapper": {
39
39
  "import": "./dist/wrapper.js",
40
40
  "types": "./dist/types/node/wrapper.d.ts"
41
+ },
42
+ "./wechat": {
43
+ "import": "./dist/wechat.js",
44
+ "types": "./dist/types/wechat/core.d.ts"
41
45
  }
42
46
  },
43
47
  "devDependencies": {
44
48
  "@types/css-tree": "^2.3.11",
45
49
  "@types/jsdom": "^27.0.0",
46
50
  "@types/node": "^24.3.0",
47
- "ts-lib": "^0.0.5",
48
51
  "typescript": "^5.9.2",
49
52
  "vite": "^7.1.4",
50
53
  "vitest": "^3.2.4"
@@ -58,9 +61,9 @@
58
61
  "mathjax-full": "3.2.2"
59
62
  },
60
63
  "peerDependencies": {
61
- "jsdom": "^27.4.0",
62
64
  "form-data-encoder": "^4.1.0",
63
- "formdata-node": "^6.0.3"
65
+ "formdata-node": "^6.0.3",
66
+ "jsdom": "^27.4.0"
64
67
  },
65
68
  "peerDependenciesMeta": {
66
69
  "jsdom": {
@@ -76,7 +79,7 @@
76
79
  "scripts": {
77
80
  "dev": "vite build --watch",
78
81
  "check": "tsc --noEmit",
79
- "build": "pnpm check && vite build && tsc",
82
+ "build": "vite build && tsc",
80
83
  "build:browser": "vite build --config vite.config.browser.ts",
81
84
  "build:styles": "vite build --config vite.config.styles.ts",
82
85
  "build:math": "vite build --config vite.config.math.ts",