@wenyan-md/core 1.0.17 → 2.0.0

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.
Files changed (39) hide show
  1. package/README.md +19 -169
  2. package/dist/core.js +1060 -416
  3. package/dist/publish.js +64 -66
  4. package/dist/types/core/hlThemeRegistry.d.ts +8 -0
  5. package/dist/types/core/index.d.ts +29 -0
  6. package/dist/types/core/parser/cssParser.d.ts +11 -0
  7. package/dist/types/core/parser/frontMatterParser.d.ts +7 -0
  8. package/dist/types/core/parser/markedParser.d.ts +6 -0
  9. package/dist/types/core/parser/mathjaxParser.d.ts +8 -0
  10. package/dist/types/core/renderer/footnotesRender.d.ts +1 -0
  11. package/dist/types/core/renderer/highlightApplyRender.d.ts +1 -0
  12. package/dist/types/core/renderer/macStyleRender.d.ts +3 -0
  13. package/dist/types/core/renderer/pseudoApplyRender.d.ts +1 -0
  14. package/dist/types/core/renderer/themeApplyRender.d.ts +1 -0
  15. package/dist/types/core/renderer/wechatPostRender.d.ts +1 -0
  16. package/dist/types/core/themeRegistry.d.ts +16 -0
  17. package/dist/types/core/types.d.ts +10 -0
  18. package/dist/types/core/utils.d.ts +6 -0
  19. package/dist/types/node/wrapper.d.ts +9 -0
  20. package/dist/types/wechat/adapters/browser.d.ts +2 -0
  21. package/dist/types/wechat/adapters/node.d.ts +2 -0
  22. package/dist/types/wechat/core.d.ts +6 -0
  23. package/dist/types/wechat/http.d.ts +12 -0
  24. package/dist/wrapper.js +17 -8
  25. package/package.json +30 -23
  26. package/dist/browser/wenyan-core.js +0 -94
  27. package/dist/hltheme.js +0 -70
  28. package/dist/math/wenyan-math.js +0 -19
  29. package/dist/styles/wenyan-styles.js +0 -2232
  30. package/dist/theme.js +0 -461
  31. package/dist/types/core.d.ts +0 -17
  32. package/dist/types/hltheme.d.ts +0 -6
  33. package/dist/types/theme.d.ts +0 -11
  34. package/dist/types/utils.d.ts +0 -1
  35. package/dist/types/wechatApi.d.ts +0 -8
  36. package/dist/types/wrapper.d.ts +0 -7
  37. package/dist/utils-YieK94fG.js +0 -9
  38. /package/dist/types/{runtimeEnv.d.ts → node/runtimeEnv.d.ts} +0 -0
  39. /package/dist/types/{publish.d.ts → wechat/publish.d.ts} +0 -0
package/dist/publish.js CHANGED
@@ -1,68 +1,10 @@
1
1
  import { JSDOM } from "jsdom";
2
2
  import { fileFromPath } from "formdata-node/file-from-path";
3
- import { FormData, Blob } from "formdata-node";
4
- import path$1 from "node:path";
3
+ import path from "node:path";
5
4
  import { stat } from "node:fs/promises";
6
5
  import { FormDataEncoder } from "form-data-encoder";
6
+ import { FormData } from "formdata-node";
7
7
  import { Readable } from "node:stream";
8
- import path from "path";
9
- const tokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
10
- const publishUrl = "https://api.weixin.qq.com/cgi-bin/draft/add";
11
- const uploadUrl = `https://api.weixin.qq.com/cgi-bin/material/add_material`;
12
- const appIdEnv = process.env.WECHAT_APP_ID || "";
13
- const appSecretEnv = process.env.WECHAT_APP_SECRET || "";
14
- async function fetchAccessToken(appId, appSecret) {
15
- appId = appId ?? appIdEnv;
16
- appSecret = appSecret ?? appSecretEnv;
17
- const response = await fetch(`${tokenUrl}?grant_type=client_credential&appid=${appId}&secret=${appSecret}`);
18
- if (!response.ok) {
19
- const errorText = await response.text();
20
- throw new Error(`获取AccessToken失败: ${response.status} ${errorText}`);
21
- }
22
- return await response.json();
23
- }
24
- async function uploadMaterial(type, fileData, fileName, accessToken) {
25
- const form = new FormData();
26
- form.append("media", fileData, fileName);
27
- const encoder = new FormDataEncoder(form);
28
- const response = await fetch(`${uploadUrl}?access_token=${accessToken}&type=${type}`, {
29
- method: "POST",
30
- headers: encoder.headers,
31
- body: Readable.from(encoder),
32
- duplex: "half"
33
- });
34
- if (!response.ok) {
35
- const errorText = await response.text();
36
- throw new Error(`上传素材失败: ${response.status} ${errorText}`);
37
- }
38
- const data = await response.json();
39
- if (data.errcode && data.errcode !== 0) {
40
- throw new Error(`上传素材失败: ${data.errcode} ${data.errmsg}`);
41
- }
42
- if (data.url && data.url.startsWith("http://")) {
43
- data.url = data.url.replace(/^http:\/\//i, "https://");
44
- }
45
- return data;
46
- }
47
- async function publishArticle(title, content, thumbMediaId, accessToken) {
48
- const response = await fetch(`${publishUrl}?access_token=${accessToken}`, {
49
- method: "POST",
50
- body: JSON.stringify({
51
- articles: [
52
- {
53
- title,
54
- content,
55
- thumb_media_id: thumbMediaId
56
- }
57
- ]
58
- })
59
- });
60
- if (!response.ok) {
61
- const errorText = await response.text();
62
- throw new Error(`发布失败: ${response.status} ${errorText}`);
63
- }
64
- return await response.json();
65
- }
66
8
  function normalizePath(p) {
67
9
  return p.replace(/\\/g, "/").replace(/\/+$/, "");
68
10
  }
@@ -115,6 +57,59 @@ const RuntimeEnv = {
115
57
  return normalizedInput;
116
58
  }
117
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
+ const nodeHttpAdapter = {
100
+ fetch,
101
+ createMultipart(field, file, filename) {
102
+ const form = new FormData();
103
+ form.append(field, file, filename);
104
+ const encoder = new FormDataEncoder(form);
105
+ return {
106
+ body: Readable.from(encoder),
107
+ headers: encoder.headers,
108
+ duplex: "half"
109
+ };
110
+ }
111
+ };
112
+ const { uploadMaterial, publishArticle, fetchAccessToken } = createWechatClient(nodeHttpAdapter);
118
113
  async function uploadImage(imageUrl, accessToken, fileName, relativePath) {
119
114
  let fileData;
120
115
  let finalName;
@@ -123,8 +118,8 @@ async function uploadImage(imageUrl, accessToken, fileName, relativePath) {
123
118
  if (!response.ok || !response.body) {
124
119
  throw new Error(`Failed to download image from URL: ${imageUrl}`);
125
120
  }
126
- const fileNameFromUrl = path$1.basename(imageUrl.split("?")[0]);
127
- const ext = path$1.extname(fileNameFromUrl);
121
+ const fileNameFromUrl = path.basename(imageUrl.split("?")[0]);
122
+ const ext = path.extname(fileNameFromUrl);
128
123
  finalName = fileName ?? (ext === "" ? `${fileNameFromUrl}.jpg` : fileNameFromUrl);
129
124
  const buffer = await response.arrayBuffer();
130
125
  if (buffer.byteLength === 0) {
@@ -138,10 +133,11 @@ async function uploadImage(imageUrl, accessToken, fileName, relativePath) {
138
133
  if (stats.size === 0) {
139
134
  throw new Error(`本地图片大小为0,无法上传: ${resolvedPath}`);
140
135
  }
141
- const fileNameFromLocal = path$1.basename(resolvedPath);
142
- const ext = path$1.extname(fileNameFromLocal);
136
+ const fileNameFromLocal = path.basename(resolvedPath);
137
+ const ext = path.extname(fileNameFromLocal);
143
138
  finalName = fileName ?? (ext === "" ? `${fileNameFromLocal}.jpg` : fileNameFromLocal);
144
- fileData = await fileFromPath(resolvedPath);
139
+ const fileFromPathResult = await fileFromPath(resolvedPath);
140
+ fileData = new Blob([await fileFromPathResult.arrayBuffer()], { type: fileFromPathResult.type });
145
141
  }
146
142
  const data = await uploadMaterial("image", fileData, finalName, accessToken);
147
143
  if (data.errcode) {
@@ -176,7 +172,9 @@ async function uploadImages(content, accessToken, relativePath) {
176
172
  }
177
173
  async function publishToDraft(title, content, cover = "", options = {}) {
178
174
  const { appId, appSecret, relativePath } = options;
179
- const accessToken = await fetchAccessToken(appId, appSecret);
175
+ const appIdEnv = process.env.WECHAT_APP_ID || "";
176
+ const appSecretEnv = process.env.WECHAT_APP_SECRET || "";
177
+ const accessToken = await fetchAccessToken(appId ?? appIdEnv, appSecret ?? appSecretEnv);
180
178
  if (!accessToken.access_token) {
181
179
  if (accessToken.errcode) {
182
180
  throw new Error(`获取 Access Token 失败,错误码:${accessToken.errcode},${accessToken.errmsg}`);
@@ -0,0 +1,8 @@
1
+ export interface HlTheme {
2
+ id: string;
3
+ getCss(): Promise<string>;
4
+ }
5
+ export declare function registerHlTheme(theme: HlTheme): void;
6
+ export declare function getHlTheme(id: string): HlTheme | undefined;
7
+ export declare function getAllHlThemes(): HlTheme[];
8
+ export declare function registerBuiltInHlThemes(): void;
@@ -0,0 +1,29 @@
1
+ import { FrontMatterResult } from "./parser/frontMatterParser.js";
2
+ export interface WenyanOptions {
3
+ isConvertMathJax?: boolean;
4
+ isWechat?: boolean;
5
+ }
6
+ export interface ApplyStylesOptions {
7
+ themeId?: string;
8
+ hlThemeId?: string;
9
+ themeCss?: string;
10
+ hlThemeCss?: string;
11
+ isMacStyle?: boolean;
12
+ isAddFootnote?: boolean;
13
+ }
14
+ export declare function createWenyanCore(options?: WenyanOptions): Promise<{
15
+ handleFrontMatter(markdown: string): Promise<FrontMatterResult>;
16
+ renderMarkdown(markdown: string): Promise<string>;
17
+ applyStylesWithTheme(wenyanElement: HTMLElement, options?: ApplyStylesOptions): Promise<string>;
18
+ applyStylesWithResolvedCss(wenyanElement: HTMLElement, options: {
19
+ themeCss: string;
20
+ hlThemeCss: string;
21
+ isMacStyle: boolean;
22
+ isAddFootnote: boolean;
23
+ }): Promise<string>;
24
+ }>;
25
+ export * from "./themeRegistry.js";
26
+ export * from "./hlThemeRegistry.js";
27
+ export { serif, sansSerif, monospace } from "./utils.js";
28
+ export { createCssModifier } from "./parser/cssParser.js";
29
+ export { macStyleCss } from "./renderer/macStyleRender.js";
@@ -0,0 +1,11 @@
1
+ import * as csstree from "css-tree";
2
+ type CssUpdate = {
3
+ property: string;
4
+ value?: string;
5
+ append?: boolean;
6
+ };
7
+ type CssUpdateMap = Record<string, CssUpdate[]>;
8
+ export declare const parseOptions: csstree.ParseOptions;
9
+ export declare function createCssModifier(updates: CssUpdateMap): (customCss: string) => string;
10
+ export declare function createCssApplier(css: string): (element: HTMLElement) => void;
11
+ export {};
@@ -0,0 +1,7 @@
1
+ export interface FrontMatterResult {
2
+ body: string;
3
+ title?: string;
4
+ description?: string;
5
+ cover?: string;
6
+ }
7
+ export declare function handleFrontMatter(markdown: string): Promise<FrontMatterResult>;
@@ -0,0 +1,6 @@
1
+ export declare function createMarkedClient(): {
2
+ /**
3
+ * 解析 Markdown 为 HTML
4
+ */
5
+ parse(markdown: string): Promise<string>;
6
+ };
@@ -0,0 +1,8 @@
1
+ export interface MathJaxParserOptions {
2
+ inlineMath?: [string, string][];
3
+ displayMath?: [string, string][];
4
+ fontCache?: "none" | "local" | "global";
5
+ }
6
+ export declare function createMathJaxParser(options?: MathJaxParserOptions): {
7
+ parser(htmlString: string): string;
8
+ };
@@ -0,0 +1 @@
1
+ export declare function addFootnotes(element: HTMLElement, listStyle?: boolean): void;
@@ -0,0 +1 @@
1
+ export declare function renderHighlightTheme(wenyanElement: HTMLElement, highlightCss: string): void;
@@ -0,0 +1,3 @@
1
+ import macStyleCss from "../../assets/mac_style.css?raw";
2
+ export declare function renderMacStyle(wenyanElement: HTMLElement): void;
3
+ export { macStyleCss };
@@ -0,0 +1 @@
1
+ export declare function applyPseudoElements(element: HTMLElement, themeCss: string): void;
@@ -0,0 +1 @@
1
+ export declare function renderTheme(wenyanElement: HTMLElement, themeCss: string): void;
@@ -0,0 +1 @@
1
+ export declare function wechatPostRender(element: HTMLElement): void;
@@ -0,0 +1,16 @@
1
+ export interface ThemeMeta {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ appName: string;
6
+ author: string;
7
+ }
8
+ export interface Theme {
9
+ meta: ThemeMeta;
10
+ getCss(): Promise<string>;
11
+ }
12
+ export declare function registerTheme(theme: Theme): void;
13
+ export declare function getTheme(id: string): Theme | undefined;
14
+ export declare function getAllThemes(): Theme[];
15
+ export declare function registerAllBuiltInThemes(): void;
16
+ export declare function getAllGzhThemes(): Theme[];
@@ -0,0 +1,10 @@
1
+ export type CssSource = {
2
+ type: "inline";
3
+ css: string;
4
+ } | {
5
+ type: "asset";
6
+ loader: () => Promise<string>;
7
+ } | {
8
+ type: "url";
9
+ url: string;
10
+ };
@@ -0,0 +1,6 @@
1
+ export declare const serif = "Georgia, Cambria, 'Noto Serif', 'Times New Roman', serif";
2
+ export declare const sansSerif = "system-ui, 'Apple Color Emoji', 'Segoe UI', 'Segoe UI Symbol', 'Noto Sans', 'Roboto', sans-serif";
3
+ export declare const monospace = "Menlo, Monaco, Consolas, 'Liberation Mono', 'Roboto Mono', 'Courier New', 'Microsoft YaHei', monospace";
4
+ export declare function normalizeCssLoader(loaderOrContent: any): () => Promise<string>;
5
+ export declare function stringToMap(str: string): Map<string, string>;
6
+ export declare function replaceCSSVariables(css: string): string;
@@ -0,0 +1,9 @@
1
+ import { ApplyStylesOptions } from "../core/index.js";
2
+ export interface StyledContent {
3
+ content: string;
4
+ title?: string;
5
+ cover?: string;
6
+ description?: string;
7
+ }
8
+ export declare function renderStyledContent(content: string, options?: ApplyStylesOptions): Promise<StyledContent>;
9
+ export declare function getGzhContent(content: string, themeId: string, hlThemeId: string, isMacStyle?: boolean, isAddFootnote?: boolean): Promise<StyledContent>;
@@ -0,0 +1,2 @@
1
+ import { HttpAdapter } from "../http.js";
2
+ export declare const browserHttpAdapter: HttpAdapter;
@@ -0,0 +1,2 @@
1
+ import { HttpAdapter } from "../http.js";
2
+ export declare const nodeHttpAdapter: HttpAdapter;
@@ -0,0 +1,6 @@
1
+ import type { HttpAdapter } from "./http.js";
2
+ 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>;
6
+ };
@@ -0,0 +1,12 @@
1
+ export interface MultipartBody {
2
+ body: BodyInit;
3
+ headers?: Record<string, string>;
4
+ }
5
+ export interface HttpAdapter {
6
+ fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
7
+ createMultipart(field: string, file: Blob, filename: string): MultipartBody;
8
+ }
9
+ export interface UploadResponse {
10
+ media_id: string;
11
+ url: string;
12
+ }
package/dist/wrapper.js CHANGED
@@ -1,20 +1,29 @@
1
1
  import { JSDOM } from "jsdom";
2
- import { configureMarked, handleFrontMatter, renderMarkdown, getContentForGzhBuiltinTheme } from "./core.js";
3
- async function getGzhContent(content, themeId, hlThemeId, isMacStyle = true, isAddFootnote = true) {
4
- await configureMarked();
5
- const preHandlerContent = await handleFrontMatter(content);
6
- const html = await renderMarkdown(preHandlerContent.body);
2
+ import { createWenyanCore } from "./core.js";
3
+ const wenyanCoreInstance = await createWenyanCore();
4
+ async function renderStyledContent(content, options = {}) {
5
+ const preHandlerContent = await wenyanCoreInstance.handleFrontMatter(content);
6
+ const html = await wenyanCoreInstance.renderMarkdown(preHandlerContent.body);
7
7
  const dom = new JSDOM(`<body><section id="wenyan">${html}</section></body>`);
8
8
  const document = dom.window.document;
9
9
  const wenyan = document.getElementById("wenyan");
10
- const result = await getContentForGzhBuiltinTheme(wenyan, themeId, hlThemeId, isMacStyle, isAddFootnote);
10
+ const result = await wenyanCoreInstance.applyStylesWithTheme(wenyan, options);
11
11
  return {
12
+ content: result,
12
13
  title: preHandlerContent.title,
13
14
  cover: preHandlerContent.cover,
14
- content: result,
15
15
  description: preHandlerContent.description
16
16
  };
17
17
  }
18
+ async function getGzhContent(content, themeId, hlThemeId, isMacStyle = true, isAddFootnote = true) {
19
+ return await renderStyledContent(content, {
20
+ themeId,
21
+ hlThemeId,
22
+ isMacStyle,
23
+ isAddFootnote
24
+ });
25
+ }
18
26
  export {
19
- getGzhContent
27
+ getGzhContent,
28
+ renderStyledContent
20
29
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenyan-md/core",
3
- "version": "1.0.17",
3
+ "version": "2.0.0",
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",
@@ -28,28 +28,21 @@
28
28
  "exports": {
29
29
  ".": {
30
30
  "import": "./dist/core.js",
31
- "types": "./dist/types/core.d.ts",
31
+ "types": "./dist/types/core/index.d.ts",
32
32
  "browser": "./dist/browser/wenyan-core.js"
33
33
  },
34
34
  "./publish": {
35
35
  "import": "./dist/publish.js",
36
- "types": "./dist/types/publish.d.ts"
36
+ "types": "./dist/types/wechat/publish.d.ts"
37
37
  },
38
38
  "./wrapper": {
39
39
  "import": "./dist/wrapper.js",
40
- "types": "./dist/types/wrapper.d.ts"
41
- },
42
- "./theme": {
43
- "import": "./dist/theme.js",
44
- "types": "./dist/types/theme.d.ts"
45
- },
46
- "./hltheme": {
47
- "import": "./dist/hltheme.js",
48
- "types": "./dist/types/hltheme.d.ts"
40
+ "types": "./dist/types/node/wrapper.d.ts"
49
41
  }
50
42
  },
51
43
  "devDependencies": {
52
- "@types/jsdom": "^21.1.7",
44
+ "@types/css-tree": "^2.3.11",
45
+ "@types/jsdom": "^27.0.0",
53
46
  "@types/node": "^24.3.0",
54
47
  "ts-lib": "^0.0.5",
55
48
  "typescript": "^5.9.2",
@@ -57,27 +50,41 @@
57
50
  "vitest": "^3.2.4"
58
51
  },
59
52
  "dependencies": {
60
- "css-tree": "3.0.1",
61
- "form-data-encoder": "^4.1.0",
62
- "formdata-node": "^6.0.3",
53
+ "css-tree": "^3.1.0",
63
54
  "front-matter": "^4.0.2",
64
55
  "highlight.js": "11.10.0",
65
- "jsdom": "^26.1.0",
66
56
  "marked": "^15.0.12",
67
57
  "marked-highlight": "^2.2.1",
68
58
  "mathjax-full": "3.2.2"
69
59
  },
60
+ "peerDependencies": {
61
+ "jsdom": "^27.4.0",
62
+ "form-data-encoder": "^4.1.0",
63
+ "formdata-node": "^6.0.3"
64
+ },
65
+ "peerDependenciesMeta": {
66
+ "jsdom": {
67
+ "optional": true
68
+ },
69
+ "form-data-encoder": {
70
+ "optional": true
71
+ },
72
+ "formdata-node": {
73
+ "optional": true
74
+ }
75
+ },
70
76
  "scripts": {
71
77
  "dev": "vite build --watch",
72
- "build": "vite build && tsc && node -e \"const fs = require('fs'); fs.copyFileSync('src/main.d.ts', 'dist/types/core.d.ts');\"",
78
+ "check": "tsc --noEmit",
79
+ "build": "vite build && tsc",
73
80
  "build:browser": "vite build --config vite.config.browser.ts",
74
81
  "build:styles": "vite build --config vite.config.styles.ts",
75
82
  "build:math": "vite build --config vite.config.math.ts",
76
83
  "build:all": "pnpm build && pnpm build:styles && pnpm build:math && pnpm build:browser",
77
- "test:main": "vitest run main.test.js",
78
- "test:wrapper": "pnpm build && vitest run wrapper.test.js",
79
- "test:publish": "vitest run test/publish.test.js",
80
- "test:realPublish": "vitest run test/realPublish.test.js",
81
- "test:runtimeEnv": "vitest run test/runtimeEnv.test.js"
84
+ "test": "pnpm build && vitest",
85
+ "test:wrapper": "pnpm build && vitest run test/wrapper.test.ts",
86
+ "test:publish": "vitest run test/publish.test.ts",
87
+ "test:realPublish": "vitest run test/realPublish.test.ts",
88
+ "test:runtimeEnv": "vitest run test/runtimeEnv.test.ts"
82
89
  }
83
90
  }