@wenyan-md/core 3.0.1 → 3.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 +9 -7
- package/dist/publish.js +15 -5
- package/dist/types/core/index.d.ts +1 -0
- package/dist/types/node/publish.d.ts +2 -1
- package/dist/types/publish.d.ts +1 -0
- package/dist/types/uploadCacheStore.d.ts +1 -0
- package/dist/wrapper.js +80 -31
- package/package.json +9 -7
package/dist/core.js
CHANGED
|
@@ -29,8 +29,8 @@ function stringToMap(str) {
|
|
|
29
29
|
return map;
|
|
30
30
|
}
|
|
31
31
|
function replaceCSSVariables(css) {
|
|
32
|
-
const variablePattern = /--([a-zA-Z0-9
|
|
33
|
-
const varPattern = /var\(--([a-zA-Z0-9
|
|
32
|
+
const variablePattern = /--([a-zA-Z0-9-]+):\s*([^;()]*\((?:[^()]*|\([^()]*\))*\)[^;()]*|[^;]+);/g;
|
|
33
|
+
const varPattern = /var\(--([a-zA-Z0-9-]+)\)/g;
|
|
34
34
|
const cssVariables = {};
|
|
35
35
|
let match;
|
|
36
36
|
while ((match = variablePattern.exec(css)) !== null) {
|
|
@@ -48,7 +48,7 @@ function replaceCSSVariables(css) {
|
|
|
48
48
|
if (resolved.has(value)) return value;
|
|
49
49
|
resolved.add(value);
|
|
50
50
|
let resolvedValue = value;
|
|
51
|
-
const innerVarPattern = /var\(--([a-zA-Z0-9
|
|
51
|
+
const innerVarPattern = /var\(--([a-zA-Z0-9-]+)\)/g;
|
|
52
52
|
resolvedValue = resolvedValue.replace(innerVarPattern, (match2, varName) => {
|
|
53
53
|
if (variables[varName]) {
|
|
54
54
|
return resolveVariable(variables[varName], variables, resolved);
|
|
@@ -61,7 +61,7 @@ function replaceCSSVariables(css) {
|
|
|
61
61
|
const resolvedValue = resolveVariable(cssVariables[key], cssVariables);
|
|
62
62
|
cssVariables[key] = resolvedValue;
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
const modifiedCSS = css.replace(varPattern, (match2, varName) => {
|
|
65
65
|
if (cssVariables[varName]) {
|
|
66
66
|
return cssVariables[varName];
|
|
67
67
|
}
|
|
@@ -285,7 +285,7 @@ function createCssApplier(css) {
|
|
|
285
285
|
targets.forEach((el) => {
|
|
286
286
|
declarations.forEach((decl) => {
|
|
287
287
|
if (decl.type !== "Declaration") return;
|
|
288
|
-
|
|
288
|
+
const value = csstree.generate(decl.value);
|
|
289
289
|
const property = decl.property;
|
|
290
290
|
const priority = decl.important ? "important" : "";
|
|
291
291
|
el.style.setProperty(property, value, priority);
|
|
@@ -347,8 +347,8 @@ function buildPseudoElement(originalResults, document) {
|
|
|
347
347
|
for (const [k, v] of beforeResults) {
|
|
348
348
|
if (v.includes("url(")) {
|
|
349
349
|
const svgMatch = v.match(/data:image\/svg\+xml;utf8,(.*<\/svg>)/);
|
|
350
|
-
const base64SvgMatch = v.match(/data:image\/svg\+xml;base64,([^"'
|
|
351
|
-
const httpMatch = v.match(/(?:"|')?(https?[^"'
|
|
350
|
+
const base64SvgMatch = v.match(/data:image\/svg\+xml;base64,([^"')]*)["']?\)/);
|
|
351
|
+
const httpMatch = v.match(/(?:"|')?(https?[^"')]*)(?:"|')?\)/);
|
|
352
352
|
if (svgMatch) {
|
|
353
353
|
const svgCode = decodeURIComponent(svgMatch[1]);
|
|
354
354
|
section.innerHTML = svgCode;
|
|
@@ -587,6 +587,8 @@ function wechatPostRender(element) {
|
|
|
587
587
|
}
|
|
588
588
|
li.appendChild(section);
|
|
589
589
|
});
|
|
590
|
+
element.style.color = "rgb(0, 0, 0)";
|
|
591
|
+
element.style.caretColor = "rgb(0, 0, 0)";
|
|
590
592
|
}
|
|
591
593
|
const themeModifier = createCssModifier({});
|
|
592
594
|
function renderTheme(wenyanElement, themeCss) {
|
package/dist/publish.js
CHANGED
|
@@ -62,6 +62,7 @@ class UploadCacheStore {
|
|
|
62
62
|
cache = {};
|
|
63
63
|
adapter;
|
|
64
64
|
initPromise;
|
|
65
|
+
_saveQueue = Promise.resolve();
|
|
65
66
|
constructor(adapter) {
|
|
66
67
|
this.adapter = adapter;
|
|
67
68
|
this.initPromise = this.load();
|
|
@@ -73,12 +74,13 @@ class UploadCacheStore {
|
|
|
73
74
|
throw new Error(`无法加载上传缓存: ${error instanceof Error ? error.message : String(error)}`);
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
save() {
|
|
78
|
+
this._saveQueue = this._saveQueue.then(async () => {
|
|
78
79
|
await this.adapter.saveCache(this.cache);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
}
|
|
80
|
+
}).catch((err) => {
|
|
81
|
+
console.error(err);
|
|
82
|
+
});
|
|
83
|
+
return this._saveQueue;
|
|
82
84
|
}
|
|
83
85
|
async waitForInit() {
|
|
84
86
|
await this.initPromise;
|
|
@@ -154,6 +156,14 @@ class WechatPublisher {
|
|
|
154
156
|
async publishToDraft(accessToken, options) {
|
|
155
157
|
return await this.publishArticle(accessToken, options);
|
|
156
158
|
}
|
|
159
|
+
async clearCache() {
|
|
160
|
+
if (this.tokenStore) {
|
|
161
|
+
await this.tokenStore.clear();
|
|
162
|
+
}
|
|
163
|
+
if (this.uploadCacheStore) {
|
|
164
|
+
await this.uploadCacheStore.clear();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
157
167
|
}
|
|
158
168
|
export {
|
|
159
169
|
TokenStore,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WechatPublishResponse } from "../wechat.js";
|
|
2
|
-
import { ArticleOptions } from "../publish.js";
|
|
2
|
+
import { ArticleOptions, WechatPublisher } from "../publish.js";
|
|
3
|
+
export declare const wechatPublisher: WechatPublisher;
|
|
3
4
|
interface PublishOptions {
|
|
4
5
|
appId?: string;
|
|
5
6
|
appSecret?: string;
|
package/dist/types/publish.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare class WechatPublisher {
|
|
|
19
19
|
getAccessTokenWithCache(appId: string, appSecret: string): Promise<string>;
|
|
20
20
|
uploadImage(file: Blob, filename: string, accessToken: string): Promise<WechatUploadResponse>;
|
|
21
21
|
publishToDraft(accessToken: string, options: WechatPublishOptions): Promise<WechatPublishResponse>;
|
|
22
|
+
clearCache(): Promise<void>;
|
|
22
23
|
}
|
|
23
24
|
export * from "./tokenStore.js";
|
|
24
25
|
export * from "./uploadCacheStore.js";
|
package/dist/wrapper.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { FormDataEncoder } from "form-data-encoder";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import https from "node:https";
|
|
5
4
|
import { JSDOM } from "jsdom";
|
|
6
5
|
import fs, { stat } from "node:fs/promises";
|
|
7
6
|
import crypto from "node:crypto";
|
|
8
7
|
import { fileFromPath } from "formdata-node/file-from-path";
|
|
8
|
+
import { FormDataEncoder } from "form-data-encoder";
|
|
9
|
+
import { FormData } from "formdata-node";
|
|
10
|
+
import { Readable } from "node:stream";
|
|
9
11
|
import os from "node:os";
|
|
10
12
|
import { defaultTokenCache, WechatPublisher } from "./publish.js";
|
|
11
13
|
import { createWenyanCore, getAllGzhThemes } from "./core.js";
|
|
@@ -107,6 +109,72 @@ const RuntimeEnv = {
|
|
|
107
109
|
return normalizedInput;
|
|
108
110
|
}
|
|
109
111
|
};
|
|
112
|
+
async function chunkedUpload(serverUrl, headers, fileBuffer, filename, mimeType) {
|
|
113
|
+
const url = new URL(`${serverUrl}/upload`);
|
|
114
|
+
const boundary = "----FormBoundary" + Math.random().toString(36).substring(2);
|
|
115
|
+
const headerPart = `--${boundary}\r
|
|
116
|
+
Content-Disposition: form-data; name="file"; filename="${filename}"\r
|
|
117
|
+
Content-Type: ${mimeType}\r
|
|
118
|
+
\r
|
|
119
|
+
`;
|
|
120
|
+
const footerPart = `\r
|
|
121
|
+
--${boundary}--\r
|
|
122
|
+
`;
|
|
123
|
+
const headerBuf = Buffer.from(headerPart, "utf-8");
|
|
124
|
+
const footerBuf = Buffer.from(footerPart, "utf-8");
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
const requestModule = url.protocol === "https:" ? https : http;
|
|
127
|
+
const defaultPort = url.protocol === "https:" ? 443 : 80;
|
|
128
|
+
const req = requestModule.request(
|
|
129
|
+
{
|
|
130
|
+
hostname: url.hostname,
|
|
131
|
+
port: url.port || defaultPort,
|
|
132
|
+
path: url.pathname,
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: {
|
|
135
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
136
|
+
"Transfer-Encoding": "chunked",
|
|
137
|
+
...headers
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
(res) => {
|
|
141
|
+
let body = "";
|
|
142
|
+
res.on("data", (chunk) => body += chunk);
|
|
143
|
+
res.on("end", () => {
|
|
144
|
+
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
|
145
|
+
reject(new Error(`Server returned status ${res.statusCode}: ${body}`));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
resolve(JSON.parse(body));
|
|
150
|
+
} catch (_e) {
|
|
151
|
+
reject(new Error(`Invalid server response: ${body}`));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
req.on("error", reject);
|
|
157
|
+
req.write(headerBuf);
|
|
158
|
+
const CHUNK = 65536;
|
|
159
|
+
let offset = 0;
|
|
160
|
+
function writeNext() {
|
|
161
|
+
if (offset >= fileBuffer.length) {
|
|
162
|
+
req.write(footerBuf);
|
|
163
|
+
req.end();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const end = Math.min(offset + CHUNK, fileBuffer.length);
|
|
167
|
+
if (req.write(fileBuffer.subarray(offset, end))) {
|
|
168
|
+
offset = end;
|
|
169
|
+
writeNext();
|
|
170
|
+
} else {
|
|
171
|
+
offset = end;
|
|
172
|
+
req.once("drain", writeNext);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
writeNext();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
110
178
|
function getServerUrl(options) {
|
|
111
179
|
let serverUrl = options.server || "http://localhost:3000";
|
|
112
180
|
serverUrl = serverUrl.replace(/\/$/, "");
|
|
@@ -156,21 +224,10 @@ async function verifyAuth(serverUrl, headers) {
|
|
|
156
224
|
}
|
|
157
225
|
async function uploadStyledContent(gzhContent, serverUrl, headers) {
|
|
158
226
|
const mdFilename = "publish_target.json";
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
new
|
|
163
|
-
);
|
|
164
|
-
const mdEncoder = new FormDataEncoder(mdForm);
|
|
165
|
-
const mdUploadRes = await fetch(`${serverUrl}/upload`, {
|
|
166
|
-
method: "POST",
|
|
167
|
-
headers: { ...headers, ...mdEncoder.headers },
|
|
168
|
-
body: Readable.from(mdEncoder),
|
|
169
|
-
duplex: "half"
|
|
170
|
-
});
|
|
171
|
-
const mdUploadData = await mdUploadRes.json();
|
|
172
|
-
if (!mdUploadRes.ok || !mdUploadData.success) {
|
|
173
|
-
throw new Error(`Upload Document Failed: ${mdUploadData.error || mdUploadData.desc || mdUploadRes.statusText}`);
|
|
227
|
+
const fileBuffer = Buffer.from(JSON.stringify(gzhContent), "utf-8");
|
|
228
|
+
const mdUploadData = await chunkedUpload(serverUrl, headers, fileBuffer, mdFilename, "application/json");
|
|
229
|
+
if (!mdUploadData.success) {
|
|
230
|
+
throw new Error(`Upload Document Failed: ${mdUploadData.error || mdUploadData.desc}`);
|
|
174
231
|
}
|
|
175
232
|
const mdFileId = mdUploadData.data.fileId;
|
|
176
233
|
return mdFileId;
|
|
@@ -224,17 +281,8 @@ async function uploadLocalImage(originalUrl, serverUrl, headers, relativePath) {
|
|
|
224
281
|
".svg": "image/svg+xml"
|
|
225
282
|
};
|
|
226
283
|
const type = mimeTypes[ext] || "application/octet-stream";
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
const encoder = new FormDataEncoder(form);
|
|
230
|
-
const uploadRes = await fetch(`${serverUrl}/upload`, {
|
|
231
|
-
method: "POST",
|
|
232
|
-
headers: { ...headers, ...encoder.headers },
|
|
233
|
-
body: Readable.from(encoder),
|
|
234
|
-
duplex: "half"
|
|
235
|
-
});
|
|
236
|
-
const uploadData = await uploadRes.json();
|
|
237
|
-
if (uploadRes.ok && uploadData.success) {
|
|
284
|
+
const uploadData = await chunkedUpload(serverUrl, headers, fileBuffer, filename, type);
|
|
285
|
+
if (uploadData.success) {
|
|
238
286
|
return `asset://${uploadData.data.fileId}`;
|
|
239
287
|
} else {
|
|
240
288
|
console.error(`[Client] Warning: Failed to upload ${filename}: ${uploadData.error || uploadData.desc}`);
|
|
@@ -469,7 +517,7 @@ async function publishToWechatDraft(articleOptions, publishOptions = {}) {
|
|
|
469
517
|
}
|
|
470
518
|
const accessToken = await wechatPublisher.getAccessTokenWithCache(appIdFinal, appSecretFinal);
|
|
471
519
|
const { html, firstImageId } = await uploadImages(content, accessToken, relativePath);
|
|
472
|
-
let thumbMediaId
|
|
520
|
+
let thumbMediaId;
|
|
473
521
|
if (cover) {
|
|
474
522
|
const cachedThumbMediaId = mediaIdMapping.get(cover);
|
|
475
523
|
if (cachedThumbMediaId) {
|
|
@@ -681,5 +729,6 @@ export {
|
|
|
681
729
|
renderStyledContent,
|
|
682
730
|
renderWithTheme,
|
|
683
731
|
safeReadJson,
|
|
684
|
-
safeWriteJson
|
|
732
|
+
safeWriteJson,
|
|
733
|
+
wechatPublisher
|
|
685
734
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenyan-md/core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.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",
|
|
@@ -48,10 +48,14 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
+
"@eslint/js": "^10.0.1",
|
|
51
52
|
"@types/css-tree": "^2.3.11",
|
|
52
53
|
"@types/jsdom": "^27.0.0",
|
|
53
54
|
"@types/node": "^24.3.0",
|
|
55
|
+
"eslint": "^10.1.0",
|
|
56
|
+
"globals": "^17.4.0",
|
|
54
57
|
"typescript": "^5.9.2",
|
|
58
|
+
"typescript-eslint": "^8.58.0",
|
|
55
59
|
"vite": "^7.1.4",
|
|
56
60
|
"vitest": "^3.2.4"
|
|
57
61
|
},
|
|
@@ -80,12 +84,10 @@
|
|
|
80
84
|
}
|
|
81
85
|
},
|
|
82
86
|
"scripts": {
|
|
83
|
-
"
|
|
87
|
+
"typecheck": "tsc --noEmit",
|
|
84
88
|
"build": "vite build && tsc",
|
|
85
|
-
"
|
|
86
|
-
"test
|
|
87
|
-
"test:
|
|
88
|
-
"test:realPublish": "vitest run tests/realPublish.test.ts",
|
|
89
|
-
"test:runtimeEnv": "vitest run tests/runtimeEnv.test.ts"
|
|
89
|
+
"lint": "eslint . --fix",
|
|
90
|
+
"test": "vitest run",
|
|
91
|
+
"test:watch": "vitest"
|
|
90
92
|
}
|
|
91
93
|
}
|