@wenyan-md/mcp 1.0.1
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/LICENSE +201 -0
- package/README.md +216 -0
- package/dist/highlight/highlight/styles/atom-one-dark.min.css +1 -0
- package/dist/highlight/highlight/styles/atom-one-light.min.css +1 -0
- package/dist/highlight/highlight/styles/dracula.min.css +8 -0
- package/dist/highlight/highlight/styles/github-dark.min.css +10 -0
- package/dist/highlight/highlight/styles/github.min.css +10 -0
- package/dist/highlight/highlight/styles/monokai.min.css +1 -0
- package/dist/highlight/highlight/styles/solarized-dark.min.css +8 -0
- package/dist/highlight/highlight/styles/solarized-light.min.css +8 -0
- package/dist/highlight/highlight/styles/xcode.min.css +1 -0
- package/dist/highlight/styles/atom-one-dark.min.css +1 -0
- package/dist/highlight/styles/atom-one-light.min.css +1 -0
- package/dist/highlight/styles/dracula.min.css +8 -0
- package/dist/highlight/styles/github-dark.min.css +10 -0
- package/dist/highlight/styles/github.min.css +10 -0
- package/dist/highlight/styles/monokai.min.css +1 -0
- package/dist/highlight/styles/solarized-dark.min.css +8 -0
- package/dist/highlight/styles/solarized-light.min.css +8 -0
- package/dist/highlight/styles/xcode.min.css +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +118 -0
- package/dist/mac_style.css +8 -0
- package/dist/main.js +495 -0
- package/dist/publish.d.ts +2 -0
- package/dist/publish.d.ts.map +1 -0
- package/dist/publish.js +142 -0
- package/dist/resources.d.ts +3 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +42 -0
- package/dist/theme.d.ts +7 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +42 -0
- package/dist/themes/default.css +180 -0
- package/dist/themes/lapis.css +190 -0
- package/dist/themes/maize.css +190 -0
- package/dist/themes/orangeheart.css +180 -0
- package/dist/themes/phycat.css +331 -0
- package/dist/themes/pie.css +236 -0
- package/dist/themes/purple.css +179 -0
- package/dist/themes/rainbow.css +163 -0
- package/dist/themes/themes/default.css +180 -0
- package/dist/themes/themes/lapis.css +190 -0
- package/dist/themes/themes/maize.css +190 -0
- package/dist/themes/themes/orangeheart.css +180 -0
- package/dist/themes/themes/phycat.css +331 -0
- package/dist/themes/themes/pie.css +236 -0
- package/dist/themes/themes/purple.css +179 -0
- package/dist/themes/themes/rainbow.css +163 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +31 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* This is a template MCP server that implements a simple notes system.
|
|
4
|
+
* It demonstrates core MCP concepts like resources and tools by allowing:
|
|
5
|
+
* - Listing notes as resources
|
|
6
|
+
* - Reading individual notes
|
|
7
|
+
* - Creating new notes via a tool
|
|
8
|
+
* - Summarizing all notes via a prompt
|
|
9
|
+
*/
|
|
10
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
import { getGzhContent } from "@wenyan-md/core/wrapper";
|
|
14
|
+
import { publishToDraft } from "@wenyan-md/core/publish";
|
|
15
|
+
import { themes } from "@wenyan-md/core/theme";
|
|
16
|
+
/**
|
|
17
|
+
* Create an MCP server with capabilities for resources (to list/read notes),
|
|
18
|
+
* tools (to create new notes), and prompts (to summarize notes).
|
|
19
|
+
*/
|
|
20
|
+
const server = new Server({
|
|
21
|
+
name: "wenyan-mcp",
|
|
22
|
+
version: "0.1.0",
|
|
23
|
+
}, {
|
|
24
|
+
capabilities: {
|
|
25
|
+
resources: {},
|
|
26
|
+
tools: {},
|
|
27
|
+
prompts: {},
|
|
28
|
+
// logging: {},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Handler that lists available tools.
|
|
33
|
+
* Exposes a single "publish_article" tool that lets clients publish new article.
|
|
34
|
+
*/
|
|
35
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
36
|
+
return {
|
|
37
|
+
tools: [
|
|
38
|
+
{
|
|
39
|
+
name: "publish_article",
|
|
40
|
+
description: "Format a Markdown article using a selected theme and publish it to '微信公众号'.",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
content: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "The original Markdown content to publish, preserving its frontmatter (if present).",
|
|
47
|
+
},
|
|
48
|
+
theme_id: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "ID of the theme to use (e.g., default, orangeheart, rainbow, lapis, pie, maize, purple, phycat).",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
required: ["content"],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "list_themes",
|
|
58
|
+
description: "List the themes compatible with the 'publish_article' tool to publish an article to '微信公众号'.",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Handler for the publish_article tool.
|
|
69
|
+
* Publish a new article with the provided title and content, and returns success message.
|
|
70
|
+
*/
|
|
71
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
72
|
+
if (request.params.name === "publish_article") {
|
|
73
|
+
// server.sendLoggingMessage({
|
|
74
|
+
// level: "debug",
|
|
75
|
+
// data: JSON.stringify(request.params.arguments),
|
|
76
|
+
// });
|
|
77
|
+
const content = String(request.params.arguments?.content || "");
|
|
78
|
+
const themeId = String(request.params.arguments?.theme_id || "");
|
|
79
|
+
const gzhContent = await getGzhContent(content, themeId, "solarized-light", true);
|
|
80
|
+
const title = gzhContent.title ?? "this is title";
|
|
81
|
+
const cover = gzhContent.cover ?? "";
|
|
82
|
+
const response = await publishToDraft(title, gzhContent.content, cover);
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: `Your article was successfully published to '公众号草稿箱'. The media ID is ${response.media_id}.`,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
else if (request.params.name === "list_themes") {
|
|
93
|
+
const themeResources = Object.entries(themes).map(([id, theme]) => ({
|
|
94
|
+
type: "text",
|
|
95
|
+
text: JSON.stringify({
|
|
96
|
+
id: theme.id,
|
|
97
|
+
name: theme.name,
|
|
98
|
+
description: theme.description
|
|
99
|
+
}),
|
|
100
|
+
}));
|
|
101
|
+
return {
|
|
102
|
+
content: themeResources,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
throw new Error("Unknown tool");
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* Start the server using stdio transport.
|
|
109
|
+
* This allows the server to communicate via standard input/output streams.
|
|
110
|
+
*/
|
|
111
|
+
async function main() {
|
|
112
|
+
const transport = new StdioServerTransport();
|
|
113
|
+
await server.connect(transport);
|
|
114
|
+
}
|
|
115
|
+
main().catch((error) => {
|
|
116
|
+
console.error("Server error:", error);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#wenyan pre::before {
|
|
2
|
+
display: block;
|
|
3
|
+
content: "";
|
|
4
|
+
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="12" viewBox="0 0 450 130"><ellipse cx="65" cy="65" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2" fill="rgb(237,108,96)"/><ellipse cx="225" cy="65" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2" fill="rgb(247,193,81)"/><ellipse cx="385" cy="65" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2" fill="rgb(100,200,86)"/></svg>');
|
|
5
|
+
background-repeat: no-repeat;
|
|
6
|
+
width: 100%;
|
|
7
|
+
height: 16px;
|
|
8
|
+
}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|