@wenyan-md/mcp 1.0.1 → 1.0.2
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/README.md +34 -2
- package/package.json +1 -1
- package/dist/highlight/highlight/styles/atom-one-dark.min.css +0 -1
- package/dist/highlight/highlight/styles/atom-one-light.min.css +0 -1
- package/dist/highlight/highlight/styles/dracula.min.css +0 -8
- package/dist/highlight/highlight/styles/github-dark.min.css +0 -10
- package/dist/highlight/highlight/styles/github.min.css +0 -10
- package/dist/highlight/highlight/styles/monokai.min.css +0 -1
- package/dist/highlight/highlight/styles/solarized-dark.min.css +0 -8
- package/dist/highlight/highlight/styles/solarized-light.min.css +0 -8
- package/dist/highlight/highlight/styles/xcode.min.css +0 -1
- package/dist/highlight/styles/atom-one-dark.min.css +0 -1
- package/dist/highlight/styles/atom-one-light.min.css +0 -1
- package/dist/highlight/styles/dracula.min.css +0 -8
- package/dist/highlight/styles/github-dark.min.css +0 -10
- package/dist/highlight/styles/github.min.css +0 -10
- package/dist/highlight/styles/monokai.min.css +0 -1
- package/dist/highlight/styles/solarized-dark.min.css +0 -8
- package/dist/highlight/styles/solarized-light.min.css +0 -8
- package/dist/highlight/styles/xcode.min.css +0 -1
- package/dist/index.d.ts +0 -11
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -118
- package/dist/mac_style.css +0 -8
- package/dist/main.js +0 -495
- package/dist/publish.d.ts +0 -2
- package/dist/publish.d.ts.map +0 -1
- package/dist/publish.js +0 -142
- package/dist/resources.d.ts +0 -3
- package/dist/resources.d.ts.map +0 -1
- package/dist/resources.js +0 -42
- package/dist/theme.d.ts +0 -7
- package/dist/theme.d.ts.map +0 -1
- package/dist/theme.js +0 -42
- package/dist/themes/default.css +0 -180
- package/dist/themes/lapis.css +0 -190
- package/dist/themes/maize.css +0 -190
- package/dist/themes/orangeheart.css +0 -180
- package/dist/themes/phycat.css +0 -331
- package/dist/themes/pie.css +0 -236
- package/dist/themes/purple.css +0 -179
- package/dist/themes/rainbow.css +0 -163
- package/dist/themes/themes/default.css +0 -180
- package/dist/themes/themes/lapis.css +0 -190
- package/dist/themes/themes/maize.css +0 -190
- package/dist/themes/themes/orangeheart.css +0 -180
- package/dist/themes/themes/phycat.css +0 -331
- package/dist/themes/themes/pie.css +0 -236
- package/dist/themes/themes/purple.css +0 -179
- package/dist/themes/themes/rainbow.css +0 -163
- package/dist/types.d.ts +0 -10
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
package/dist/main.js
DELETED
|
@@ -1,495 +0,0 @@
|
|
|
1
|
-
import { marked } from "marked";
|
|
2
|
-
import fm from "front-matter";
|
|
3
|
-
import hljs from "highlight.js";
|
|
4
|
-
import { markedHighlight } from "marked-highlight";
|
|
5
|
-
import * as csstree from "css-tree";
|
|
6
|
-
import { readFile } from "fs/promises";
|
|
7
|
-
import { dirname, join } from "path";
|
|
8
|
-
import { fileURLToPath } from "url";
|
|
9
|
-
import { JSDOM } from "jsdom";
|
|
10
|
-
import { mathjax } from 'mathjax-full/js/mathjax.js';
|
|
11
|
-
import { TeX } from 'mathjax-full/js/input/tex.js';
|
|
12
|
-
import { SVG } from 'mathjax-full/js/output/svg.js';
|
|
13
|
-
import { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor.js';
|
|
14
|
-
import { RegisterHTMLHandler } from 'mathjax-full/js/handlers/html.js';
|
|
15
|
-
import { AllPackages } from 'mathjax-full/js/input/tex/AllPackages.js';
|
|
16
|
-
|
|
17
|
-
const serif =
|
|
18
|
-
"ui-serif, Georgia, Cambria, 'Noto Serif', 'Times New Roman', serif";
|
|
19
|
-
const sansSerif =
|
|
20
|
-
"ui-sans-serif, system-ui, 'Apple Color Emoji', 'Segoe UI', 'Segoe UI Symbol', 'Noto Sans', 'Roboto', sans-serif";
|
|
21
|
-
const monospace =
|
|
22
|
-
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Roboto Mono', 'Courier New', 'Microsoft YaHei', monospace";
|
|
23
|
-
|
|
24
|
-
const texConfig = {
|
|
25
|
-
inlineMath: [['$', '$'], ['\\(', '\\)']],
|
|
26
|
-
displayMath: [['$$', '$$'], ['\\[', '\\]']],
|
|
27
|
-
processEscapes: true,
|
|
28
|
-
packages: AllPackages.sort().join(', ').split(/\s*,\s*/)
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const svgConfig = {
|
|
32
|
-
fontCache: 'none'
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const adaptor = liteAdaptor();
|
|
36
|
-
RegisterHTMLHandler(adaptor);
|
|
37
|
-
const tex = new TeX(texConfig);
|
|
38
|
-
const svg = new SVG(svgConfig);
|
|
39
|
-
|
|
40
|
-
function addContainer(math, doc) {
|
|
41
|
-
// console.log(math)
|
|
42
|
-
const tag = math.display ? 'section' : 'span';
|
|
43
|
-
const cls = math.display ? 'block-equation' : 'inline-equation';
|
|
44
|
-
// math.typesetRoot.setAttribute("math", math.math);
|
|
45
|
-
math.typesetRoot = doc.adaptor.node(tag, {class: cls}, [math.typesetRoot]);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function renderMathInHtml(htmlString) {
|
|
49
|
-
try {
|
|
50
|
-
const html = mathjax.document(htmlString, {
|
|
51
|
-
InputJax: tex,
|
|
52
|
-
OutputJax: svg,
|
|
53
|
-
renderActions: {
|
|
54
|
-
addContainer: [190, (doc) => {for (const math of doc.math) {addContainer(math, doc)}}, addContainer]
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
html.render();
|
|
58
|
-
return adaptor.outerHTML(adaptor.root(html.document))
|
|
59
|
-
} catch (error) {
|
|
60
|
-
console.error("Error rendering MathJax:", error);
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function initMarkdownRenderer() {
|
|
66
|
-
// ----------- 代码高亮 -----------
|
|
67
|
-
marked.use(
|
|
68
|
-
markedHighlight({
|
|
69
|
-
langPrefix: "hljs language-",
|
|
70
|
-
highlight: function(code, language) {
|
|
71
|
-
language = hljs.getLanguage(language) ? language : "plaintext";
|
|
72
|
-
return hljs.highlight(code, { language: language }).value;
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
// ----------- 自定义图片语法扩展 ![](){...} -----------
|
|
78
|
-
const attributeImageExtension = {
|
|
79
|
-
name: "attributeImage",
|
|
80
|
-
level: "inline",
|
|
81
|
-
start(src) {
|
|
82
|
-
return src.indexOf("![");
|
|
83
|
-
},
|
|
84
|
-
tokenizer(src) {
|
|
85
|
-
const rule = /^!\[([^\]]*)\]\(([^)]+)\)\{(.*?)\}/;
|
|
86
|
-
const match = rule.exec(src);
|
|
87
|
-
if (match) {
|
|
88
|
-
return {
|
|
89
|
-
type: "attributeImage",
|
|
90
|
-
raw: match[0],
|
|
91
|
-
alt: match[1],
|
|
92
|
-
href: match[2],
|
|
93
|
-
attrs: match[3],
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
renderer(token) {
|
|
98
|
-
const attrs = stringToMap(token.attrs);
|
|
99
|
-
const attrStr = Array.from(attrs)
|
|
100
|
-
.map(([k, v]) =>
|
|
101
|
-
/^\d+$/.test(v) ? `${k}:${v}px` : `${k}:${v}`
|
|
102
|
-
)
|
|
103
|
-
.join("; ");
|
|
104
|
-
return `<img src="${token.href}" alt="${token.alt}" style="${attrStr}">`;
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
marked.use({ extensions: [attributeImageExtension] });
|
|
109
|
-
|
|
110
|
-
// ----------- 自定义渲染器 -----------
|
|
111
|
-
const renderer = marked.Renderer;
|
|
112
|
-
const parser = marked.Parser;
|
|
113
|
-
|
|
114
|
-
renderer.heading = function (heading) {
|
|
115
|
-
const text = parser.parseInline(heading.tokens);
|
|
116
|
-
const level = heading.depth;
|
|
117
|
-
return `<h${level}><span>${text}</span></h${level}>\n`;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
renderer.paragraph = function (paragraph) {
|
|
121
|
-
const text = paragraph.text;
|
|
122
|
-
if (
|
|
123
|
-
text.length > 4 &&
|
|
124
|
-
(/\$\$[\s\S]*?\$\$/g.test(text) || /\\\[[\s\S]*?\\\]/g.test(text))
|
|
125
|
-
) {
|
|
126
|
-
return `${text}\n`;
|
|
127
|
-
} else {
|
|
128
|
-
return `<p>${parser.parseInline(paragraph.tokens)}</p>\n`;
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
renderer.image = function (img, title, text) {
|
|
133
|
-
const href = img.href;
|
|
134
|
-
return `<img src="${href}" alt="${text || ""}" title="${
|
|
135
|
-
title || text || ""
|
|
136
|
-
}">`;
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
marked.use({ renderer });
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 辅助函数:把 "{width=100 height=200}" 字符串转成 Map
|
|
143
|
-
function stringToMap(str) {
|
|
144
|
-
const map = new Map();
|
|
145
|
-
str.split(/\s+/).forEach((pair) => {
|
|
146
|
-
const [key, value] = pair.split("=");
|
|
147
|
-
if (key && value) {
|
|
148
|
-
map.set(key, value.replace(/^["']|["']$/g, "")); // 去掉引号
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
return map;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function handleFrontMatter(markdown) {
|
|
155
|
-
const { attributes, body } = fm(markdown);
|
|
156
|
-
const result = {};
|
|
157
|
-
let head = "";
|
|
158
|
-
const { title, description, cover } = attributes;
|
|
159
|
-
if (title) {
|
|
160
|
-
result.title = title;
|
|
161
|
-
}
|
|
162
|
-
if (description) {
|
|
163
|
-
head += "> " + description + "\n\n";
|
|
164
|
-
result.description = description;
|
|
165
|
-
}
|
|
166
|
-
if (cover) {
|
|
167
|
-
result.cover = cover;
|
|
168
|
-
}
|
|
169
|
-
result.body = head + body;
|
|
170
|
-
return result;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export async function renderMarkdown(content, themeId) {
|
|
174
|
-
const html = marked.parse(content);
|
|
175
|
-
const htmlWithMath = await renderMathInHtml(html);
|
|
176
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
177
|
-
const themeCssPath = join(__dirname, `themes/${themeId}.css`);
|
|
178
|
-
const themeCss = await readFile(themeCssPath, "utf8");
|
|
179
|
-
let customCss = replaceCSSVariables(themeCss);
|
|
180
|
-
customCss = modifyCss(customCss, {
|
|
181
|
-
'#wenyan pre code': [
|
|
182
|
-
{
|
|
183
|
-
property: 'font-family',
|
|
184
|
-
value: monospace,
|
|
185
|
-
append: true
|
|
186
|
-
}
|
|
187
|
-
],
|
|
188
|
-
'#wenyan pre': [
|
|
189
|
-
{
|
|
190
|
-
property: 'font-size',
|
|
191
|
-
value: "12px",
|
|
192
|
-
append: true
|
|
193
|
-
}
|
|
194
|
-
]
|
|
195
|
-
});
|
|
196
|
-
const highlightCssPath = join(
|
|
197
|
-
__dirname,
|
|
198
|
-
"highlight/styles/solarized-light.min.css"
|
|
199
|
-
);
|
|
200
|
-
const highlightCss = await readFile(highlightCssPath, "utf8");
|
|
201
|
-
const macStyleCssPath = join(__dirname, "mac_style.css");
|
|
202
|
-
const macStyleCss = await readFile(macStyleCssPath, "utf8");
|
|
203
|
-
return await getContentForGzh(htmlWithMath, customCss, highlightCss, macStyleCss);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
async function getContentForGzh(html, customCss, highlightCss, macStyleCss) {
|
|
207
|
-
const ast = csstree.parse(customCss, {
|
|
208
|
-
context: "stylesheet",
|
|
209
|
-
positions: false,
|
|
210
|
-
parseAtrulePrelude: false,
|
|
211
|
-
parseCustomProperty: false,
|
|
212
|
-
parseValue: false,
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
const ast1 = csstree.parse(highlightCss, {
|
|
216
|
-
context: "stylesheet",
|
|
217
|
-
positions: false,
|
|
218
|
-
parseAtrulePrelude: false,
|
|
219
|
-
parseCustomProperty: false,
|
|
220
|
-
parseValue: false,
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
ast.children.appendList(ast1.children);
|
|
224
|
-
|
|
225
|
-
const ast2 = csstree.parse(macStyleCss, {
|
|
226
|
-
context: 'stylesheet',
|
|
227
|
-
positions: false,
|
|
228
|
-
parseAtrulePrelude: false,
|
|
229
|
-
parseCustomProperty: false,
|
|
230
|
-
parseValue: false
|
|
231
|
-
});
|
|
232
|
-
ast.children.appendList(ast2.children);
|
|
233
|
-
|
|
234
|
-
const dom = new JSDOM(`<body><section id="wenyan">${html}</section></body>`);
|
|
235
|
-
const document = dom.window.document;
|
|
236
|
-
const wenyan = document.getElementById("wenyan");
|
|
237
|
-
|
|
238
|
-
csstree.walk(ast, {
|
|
239
|
-
visit: "Rule",
|
|
240
|
-
enter(node, item, list) {
|
|
241
|
-
const selectorList = node.prelude.children;
|
|
242
|
-
if (selectorList) {
|
|
243
|
-
selectorList.forEach((selectorNode) => {
|
|
244
|
-
const selector = csstree.generate(selectorNode);
|
|
245
|
-
// console.log(selector);
|
|
246
|
-
|
|
247
|
-
const declarations = node.block.children.toArray();
|
|
248
|
-
if (selector === "#wenyan") {
|
|
249
|
-
declarations.forEach((decl) => {
|
|
250
|
-
const value = csstree.generate(decl.value);
|
|
251
|
-
wenyan.style[decl.property] = value;
|
|
252
|
-
});
|
|
253
|
-
} else {
|
|
254
|
-
const elements =
|
|
255
|
-
wenyan.querySelectorAll(selector);
|
|
256
|
-
elements.forEach((element) => {
|
|
257
|
-
declarations.forEach((decl) => {
|
|
258
|
-
const value = csstree.generate(decl.value);
|
|
259
|
-
element.style[decl.property] = value;
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
},
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// 处理公式
|
|
269
|
-
let elements = wenyan.querySelectorAll("mjx-container");
|
|
270
|
-
elements.forEach((element) => {
|
|
271
|
-
const svg = element.querySelector("svg");
|
|
272
|
-
svg.style.width = svg.getAttribute("width");
|
|
273
|
-
svg.style.height = svg.getAttribute("height");
|
|
274
|
-
svg.removeAttribute("width");
|
|
275
|
-
svg.removeAttribute("height");
|
|
276
|
-
const parent = element.parentElement;
|
|
277
|
-
element.remove();
|
|
278
|
-
parent.appendChild(svg);
|
|
279
|
-
if (parent.classList.contains("block-equation")) {
|
|
280
|
-
parent.setAttribute(
|
|
281
|
-
"style",
|
|
282
|
-
"text-align: center; margin-bottom: 1rem;"
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
// 处理代码块
|
|
287
|
-
elements = wenyan.querySelectorAll("pre code");
|
|
288
|
-
elements.forEach((element) => {
|
|
289
|
-
element.innerHTML = element.innerHTML
|
|
290
|
-
.replace(/\n/g, "<br>")
|
|
291
|
-
.replace(/(>[^<]+)|(^[^<]+)/g, (str) =>
|
|
292
|
-
str.replace(/\s/g, " ")
|
|
293
|
-
);
|
|
294
|
-
});
|
|
295
|
-
// 公众号不支持css伪元素,将伪元素样式提取出来拼接成一个span
|
|
296
|
-
elements = wenyan.querySelectorAll(
|
|
297
|
-
"h1, h2, h3, h4, h5, h6, blockquote, pre"
|
|
298
|
-
);
|
|
299
|
-
elements.forEach((element) => {
|
|
300
|
-
const afterResults = new Map();
|
|
301
|
-
const beforeResults = new Map();
|
|
302
|
-
csstree.walk(ast, {
|
|
303
|
-
visit: "Rule",
|
|
304
|
-
enter(node) {
|
|
305
|
-
const selector = csstree.generate(node.prelude); // 生成选择器字符串
|
|
306
|
-
const tagName = element.tagName.toLowerCase();
|
|
307
|
-
|
|
308
|
-
// 检查是否匹配 ::after 或 ::before
|
|
309
|
-
if (selector.includes(`${tagName}::after`)) {
|
|
310
|
-
extractDeclarations(node, afterResults);
|
|
311
|
-
} else if (selector.includes(`${tagName}::before`)) {
|
|
312
|
-
extractDeclarations(node, beforeResults);
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
});
|
|
316
|
-
if (afterResults.size > 0) {
|
|
317
|
-
element.appendChild(buildPseudoSpan(afterResults, document));
|
|
318
|
-
}
|
|
319
|
-
if (beforeResults.size > 0) {
|
|
320
|
-
element.insertBefore(
|
|
321
|
-
buildPseudoSpan(beforeResults, document),
|
|
322
|
-
element.firstChild
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
wenyan.setAttribute("data-provider", "WenYan");
|
|
327
|
-
return `${wenyan.outerHTML.replace(
|
|
328
|
-
/class="mjx-solid"/g,
|
|
329
|
-
'fill="none" stroke-width="70"'
|
|
330
|
-
)}`;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function replaceCSSVariables(css) {
|
|
334
|
-
// 正则表达式用于匹配变量定义,例如 --sans-serif-font: ...
|
|
335
|
-
const variablePattern =
|
|
336
|
-
/--([a-zA-Z0-9\-]+):\s*([^;()]*\((?:[^()]*|\([^()]*\))*\)[^;()]*|[^;]+);/g;
|
|
337
|
-
// 正则表达式用于匹配使用 var() 的地方
|
|
338
|
-
const varPattern = /var\(--([a-zA-Z0-9\-]+)\)/g;
|
|
339
|
-
const cssVariables = {};
|
|
340
|
-
|
|
341
|
-
// 1. 提取变量定义并存入字典
|
|
342
|
-
let match;
|
|
343
|
-
while ((match = variablePattern.exec(css)) !== null) {
|
|
344
|
-
const variableName = match[1];
|
|
345
|
-
const variableValue = match[2].trim().replaceAll("\n", "");
|
|
346
|
-
|
|
347
|
-
// 将变量存入字典
|
|
348
|
-
cssVariables[variableName] = variableValue;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (!cssVariables["sans-serif-font"]) {
|
|
352
|
-
cssVariables["sans-serif-font"] = sansSerif;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (!cssVariables["monospace-font"]) {
|
|
356
|
-
cssVariables["monospace-font"] = monospace;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// 2. 递归解析 var() 引用为字典中对应的值
|
|
360
|
-
function resolveVariable(value, variables, resolved = new Set()) {
|
|
361
|
-
// 如果已经解析过这个值,则返回原始值以避免死循环
|
|
362
|
-
if (resolved.has(value)) return value;
|
|
363
|
-
|
|
364
|
-
resolved.add(value);
|
|
365
|
-
let resolvedValue = value;
|
|
366
|
-
|
|
367
|
-
// 解析变量
|
|
368
|
-
let match;
|
|
369
|
-
while ((match = varPattern.exec(resolvedValue)) !== null) {
|
|
370
|
-
const varName = match[1];
|
|
371
|
-
|
|
372
|
-
// 查找对应的变量值,如果变量引用另一个变量,递归解析
|
|
373
|
-
if (variables[varName]) {
|
|
374
|
-
const resolvedVar = resolveVariable(
|
|
375
|
-
variables[varName],
|
|
376
|
-
variables,
|
|
377
|
-
resolved
|
|
378
|
-
);
|
|
379
|
-
resolvedValue = resolvedValue.replace(match[0], resolvedVar);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
return resolvedValue;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// 3. 替换所有变量引用
|
|
386
|
-
for (const key in cssVariables) {
|
|
387
|
-
const resolvedValue = resolveVariable(cssVariables[key], cssVariables);
|
|
388
|
-
cssVariables[key] = resolvedValue;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// 4. 替换 CSS 中的 var() 引用
|
|
392
|
-
let modifiedCSS = css;
|
|
393
|
-
while ((match = varPattern.exec(css)) !== null) {
|
|
394
|
-
const varName = match[1];
|
|
395
|
-
|
|
396
|
-
// 查找对应的变量值
|
|
397
|
-
if (cssVariables[varName]) {
|
|
398
|
-
modifiedCSS = modifiedCSS.replace(match[0], cssVariables[varName]);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return modifiedCSS.replace(/:root\s*\{[^}]*\}/g, "");
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function extractDeclarations(ruleNode, resultMap) {
|
|
406
|
-
csstree.walk(ruleNode.block, {
|
|
407
|
-
visit: 'Declaration',
|
|
408
|
-
enter(declNode) {
|
|
409
|
-
const property = declNode.property;
|
|
410
|
-
const value = csstree.generate(declNode.value);
|
|
411
|
-
resultMap.set(property, value);
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function buildPseudoSpan(beforeRresults, document) {
|
|
417
|
-
// 创建一个新的 <span> 元素
|
|
418
|
-
const span = document.createElement('section');
|
|
419
|
-
// 将伪类的内容和样式应用到 <span> 标签
|
|
420
|
-
if (beforeRresults.get("content")) {
|
|
421
|
-
span.textContent = beforeRresults.get("content").replace(/['"]/g, '');
|
|
422
|
-
beforeRresults.delete("content");
|
|
423
|
-
}
|
|
424
|
-
for (const [k, v] of beforeRresults) {
|
|
425
|
-
if (v.includes("url(")) {
|
|
426
|
-
const svgMatch = v.match(/data:image\/svg\+xml;utf8,(.*<\/svg>)/);
|
|
427
|
-
const base64SvgMatch = v.match(/data:image\/svg\+xml;base64,([^"'\)]*)["']?\)/);
|
|
428
|
-
const httpMatch = v.match(/(?:"|')?(https?[^"'\)]*)(?:"|')?\)/);
|
|
429
|
-
if (svgMatch) {
|
|
430
|
-
const svgCode = decodeURIComponent(svgMatch[1]);
|
|
431
|
-
span.innerHTML = svgCode;
|
|
432
|
-
} else if (base64SvgMatch) {
|
|
433
|
-
const decodedString = atob(base64SvgMatch[1]);
|
|
434
|
-
span.innerHTML = decodedString;
|
|
435
|
-
} else if (httpMatch) {
|
|
436
|
-
const img = document.createElement('img');
|
|
437
|
-
img.src = httpMatch[1];
|
|
438
|
-
img.setAttribute("style", "vertical-align: top;");
|
|
439
|
-
span.appendChild(img);
|
|
440
|
-
}
|
|
441
|
-
beforeRresults.delete(k);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
const entries = Array.from(beforeRresults.entries());
|
|
445
|
-
const cssString = entries.map(([key, value]) => `${key}: ${value}`).join('; ');
|
|
446
|
-
span.style.cssText = cssString;
|
|
447
|
-
return span;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function modifyCss(customCss, updates) {
|
|
451
|
-
const ast = csstree.parse(customCss, {
|
|
452
|
-
context: 'stylesheet',
|
|
453
|
-
positions: false,
|
|
454
|
-
parseAtrulePrelude: false,
|
|
455
|
-
parseCustomProperty: false,
|
|
456
|
-
parseValue: false
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
csstree.walk(ast, {
|
|
460
|
-
visit: 'Rule',
|
|
461
|
-
leave: (node, item, list) => {
|
|
462
|
-
if (node.prelude.type !== 'SelectorList') return;
|
|
463
|
-
|
|
464
|
-
const selectors = node.prelude.children.toArray().map(sel => csstree.generate(sel));
|
|
465
|
-
if (selectors) {
|
|
466
|
-
const selector = selectors[0];
|
|
467
|
-
const update = updates[selector];
|
|
468
|
-
if (!update) return;
|
|
469
|
-
|
|
470
|
-
for (const { property, value, append } of update) {
|
|
471
|
-
if (value) {
|
|
472
|
-
let found = false;
|
|
473
|
-
csstree.walk(node.block, decl => {
|
|
474
|
-
if (decl.type === 'Declaration' && decl.property === property) {
|
|
475
|
-
decl.value = csstree.parse(value, { context: 'value' });
|
|
476
|
-
found = true;
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
if (!found && append) {
|
|
480
|
-
node.block.children.prepend(
|
|
481
|
-
list.createItem({
|
|
482
|
-
type: 'Declaration',
|
|
483
|
-
property,
|
|
484
|
-
value: csstree.parse(value, { context: 'value' })
|
|
485
|
-
})
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
return csstree.generate(ast);
|
|
495
|
-
}
|
package/dist/publish.d.ts
DELETED
package/dist/publish.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"AAyGA,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,gBAyCjF"}
|
package/dist/publish.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { JSDOM } from "jsdom";
|
|
2
|
-
import { FormData } from 'formdata-node';
|
|
3
|
-
import { fileFromPath } from 'formdata-node/file-from-path';
|
|
4
|
-
import path from "path";
|
|
5
|
-
const tokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
|
|
6
|
-
const publishUrl = "https://api.weixin.qq.com/cgi-bin/draft/add";
|
|
7
|
-
const uploadUrl = `https://api.weixin.qq.com/cgi-bin/material/add_material`;
|
|
8
|
-
const appId = process.env.WECHAT_APP_ID || "";
|
|
9
|
-
const appSecret = process.env.WECHAT_APP_SECRET || "";
|
|
10
|
-
const hostImagePath = process.env.HOST_IMAGE_PATH || "";
|
|
11
|
-
const dockerImagePath = "/mnt/host-downloads";
|
|
12
|
-
async function fetchAccessToken() {
|
|
13
|
-
try {
|
|
14
|
-
const response = await fetch(`${tokenUrl}?grant_type=client_credential&appid=${appId}&secret=${appSecret}`);
|
|
15
|
-
const data = await response.json();
|
|
16
|
-
if (data.access_token) {
|
|
17
|
-
return data;
|
|
18
|
-
}
|
|
19
|
-
else if (data.errcode) {
|
|
20
|
-
throw new Error(`获取 Access Token 失败,错误码:${data.errcode},${data.errmsg}`);
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
throw new Error(`获取 Access Token 失败: ${data}`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
async function uploadMaterial(type, fileData, fileName, accessToken) {
|
|
31
|
-
const form = new FormData();
|
|
32
|
-
form.append("media", fileData, fileName);
|
|
33
|
-
const response = await fetch(`${uploadUrl}?access_token=${accessToken}&type=${type}`, {
|
|
34
|
-
method: 'POST',
|
|
35
|
-
body: form,
|
|
36
|
-
});
|
|
37
|
-
if (!response.ok) {
|
|
38
|
-
const errorText = await response.text();
|
|
39
|
-
throw new Error(`上传失败: ${response.status} ${errorText}`);
|
|
40
|
-
}
|
|
41
|
-
const data = await response.json();
|
|
42
|
-
if (data.errcode) {
|
|
43
|
-
throw new Error(`上传失败,错误码:${data.errcode},错误信息:${data.errmsg}`);
|
|
44
|
-
}
|
|
45
|
-
const result = data.url.replace("http://", "https://");
|
|
46
|
-
data.url = result;
|
|
47
|
-
return data;
|
|
48
|
-
}
|
|
49
|
-
async function uploadImage(imageUrl, accessToken, fileName) {
|
|
50
|
-
if (imageUrl.startsWith("http")) {
|
|
51
|
-
const response = await fetch(imageUrl);
|
|
52
|
-
if (!response.ok || !response.body) {
|
|
53
|
-
throw new Error(`Failed to download image from URL: ${imageUrl}`);
|
|
54
|
-
}
|
|
55
|
-
const fileNameFromUrl = path.basename(imageUrl.split("?")[0]);
|
|
56
|
-
const ext = path.extname(fileNameFromUrl);
|
|
57
|
-
const imageName = fileName ?? (ext === "" ? `${fileNameFromUrl}.jpg` : fileNameFromUrl);
|
|
58
|
-
const buffer = await response.arrayBuffer();
|
|
59
|
-
return await uploadMaterial('image', new Blob([buffer]), imageName, accessToken);
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
const localImagePath = hostImagePath ? imageUrl.replace(hostImagePath, dockerImagePath) : imageUrl;
|
|
63
|
-
const fileNameFromLocal = path.basename(localImagePath);
|
|
64
|
-
const ext = path.extname(fileNameFromLocal);
|
|
65
|
-
const imageName = fileName ?? (ext === "" ? `${fileNameFromLocal}.jpg` : fileNameFromLocal);
|
|
66
|
-
const file = await fileFromPath(localImagePath);
|
|
67
|
-
return await uploadMaterial('image', file, imageName, accessToken);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async function uploadImages(content, accessToken) {
|
|
71
|
-
if (!content.includes('<img')) {
|
|
72
|
-
return { html: content, firstImageId: "" };
|
|
73
|
-
}
|
|
74
|
-
const dom = new JSDOM(content);
|
|
75
|
-
const document = dom.window.document;
|
|
76
|
-
const images = Array.from(document.querySelectorAll('img'));
|
|
77
|
-
const uploadPromises = images.map(async (element) => {
|
|
78
|
-
const dataSrc = element.getAttribute('src');
|
|
79
|
-
if (dataSrc) {
|
|
80
|
-
if (!dataSrc.startsWith('https://mmbiz.qpic.cn')) {
|
|
81
|
-
const resp = await uploadImage(dataSrc, accessToken);
|
|
82
|
-
element.setAttribute('src', resp.url);
|
|
83
|
-
return resp.media_id;
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
return dataSrc;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return null;
|
|
90
|
-
});
|
|
91
|
-
const mediaIds = (await Promise.all(uploadPromises)).filter(Boolean);
|
|
92
|
-
const firstImageId = mediaIds[0] || "";
|
|
93
|
-
const updatedHtml = dom.serialize();
|
|
94
|
-
return { html: updatedHtml, firstImageId };
|
|
95
|
-
}
|
|
96
|
-
export async function publishToDraft(title, content, cover) {
|
|
97
|
-
try {
|
|
98
|
-
const accessToken = await fetchAccessToken();
|
|
99
|
-
const handledContent = content.replace(/\n<li/g, "<li").replace(/<\/li>\n/g, "<\/li>");
|
|
100
|
-
const { html, firstImageId } = await uploadImages(handledContent, accessToken.access_token);
|
|
101
|
-
let thumbMediaId = "";
|
|
102
|
-
if (cover) {
|
|
103
|
-
const resp = await uploadImage(cover, accessToken.access_token, "cover.jpg");
|
|
104
|
-
thumbMediaId = resp.media_id;
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
if (firstImageId.startsWith("https://mmbiz.qpic.cn")) {
|
|
108
|
-
const resp = await uploadImage(firstImageId, accessToken.access_token, "cover.jpg");
|
|
109
|
-
thumbMediaId = resp.media_id;
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
thumbMediaId = firstImageId;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (!thumbMediaId) {
|
|
116
|
-
throw new Error("你必须指定一张封面图或者在正文中至少出现一张图片。");
|
|
117
|
-
}
|
|
118
|
-
const response = await fetch(`${publishUrl}?access_token=${accessToken.access_token}`, {
|
|
119
|
-
method: 'POST',
|
|
120
|
-
body: JSON.stringify({
|
|
121
|
-
articles: [{
|
|
122
|
-
title: title,
|
|
123
|
-
content: html,
|
|
124
|
-
thumb_media_id: thumbMediaId,
|
|
125
|
-
}]
|
|
126
|
-
})
|
|
127
|
-
});
|
|
128
|
-
const data = await response.json();
|
|
129
|
-
if (data.media_id) {
|
|
130
|
-
return data;
|
|
131
|
-
}
|
|
132
|
-
else if (data.errcode) {
|
|
133
|
-
throw new Error(`上传到公众号草稿失败,错误码:${data.errcode},${data.errmsg}`);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
throw new Error(`上传到公众号草稿失败: ${data}`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
|
-
throw error;
|
|
141
|
-
}
|
|
142
|
-
}
|
package/dist/resources.d.ts
DELETED
package/dist/resources.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAyCxC,CAAC"}
|
package/dist/resources.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export const themes = {
|
|
2
|
-
default: {
|
|
3
|
-
id: "default",
|
|
4
|
-
name: "Default",
|
|
5
|
-
description: "A clean and traditional layout for articles.",
|
|
6
|
-
},
|
|
7
|
-
orangeHeart: {
|
|
8
|
-
id: "orangeHeart",
|
|
9
|
-
name: "Orange Heart",
|
|
10
|
-
description: "Modern look with bold headers and minimalistic styling.",
|
|
11
|
-
},
|
|
12
|
-
rainbow: {
|
|
13
|
-
id: "rainbow",
|
|
14
|
-
name: "Rainbow",
|
|
15
|
-
description: "Typewriter-style layout for retro aesthetics.",
|
|
16
|
-
},
|
|
17
|
-
lapis: {
|
|
18
|
-
id: "lapis",
|
|
19
|
-
name: "Lapis",
|
|
20
|
-
description: "Typewriter-style layout for retro aesthetics.",
|
|
21
|
-
},
|
|
22
|
-
pie: {
|
|
23
|
-
id: "pie",
|
|
24
|
-
name: "Pie",
|
|
25
|
-
description: "Typewriter-style layout for retro aesthetics.",
|
|
26
|
-
},
|
|
27
|
-
maize: {
|
|
28
|
-
id: "maize",
|
|
29
|
-
name: "Maize",
|
|
30
|
-
description: "Typewriter-style layout for retro aesthetics.",
|
|
31
|
-
},
|
|
32
|
-
purple: {
|
|
33
|
-
id: "purple",
|
|
34
|
-
name: "Purple",
|
|
35
|
-
description: "Typewriter-style layout for retro aesthetics.",
|
|
36
|
-
},
|
|
37
|
-
phycat: {
|
|
38
|
-
id: "phycat",
|
|
39
|
-
name: "物理猫-薄荷",
|
|
40
|
-
description: "A well-structured, hierarchical mint green theme.",
|
|
41
|
-
},
|
|
42
|
-
};
|