@qcplay/cli 1.0.9 → 1.0.10
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/bin/qcplay.js +12 -2
- package/lib/wechat-article.js +77 -10
- package/package.json +1 -1
package/bin/qcplay.js
CHANGED
|
@@ -9,7 +9,7 @@ import path from "path";
|
|
|
9
9
|
import readline from "readline";
|
|
10
10
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
11
11
|
|
|
12
|
-
import { buildOfficialArticleMarkdown, importWechatArticle } from "../lib/wechat-article.js";
|
|
12
|
+
import { buildOfficialArticleMarkdown, importWechatArticle, normalizeArticleColor } from "../lib/wechat-article.js";
|
|
13
13
|
|
|
14
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
15
|
const __dirname = path.dirname(__filename);
|
|
@@ -1150,6 +1150,15 @@ function applyInlineMarkdown(text) {
|
|
|
1150
1150
|
return storeToken(`<code>${escapeHtml(code)}</code>`);
|
|
1151
1151
|
});
|
|
1152
1152
|
|
|
1153
|
+
output = output.replace(/\{\{qc-color:([^}]+)\}\}([\s\S]*?)\{\{\/qc-color\}\}/g, (_, rawColor, content) => {
|
|
1154
|
+
const color = normalizeArticleColor(rawColor);
|
|
1155
|
+
if (!color) {
|
|
1156
|
+
return content;
|
|
1157
|
+
}
|
|
1158
|
+
const plainText = content.replace(/\\([\\`*_\[\]])/g, "$1");
|
|
1159
|
+
return storeToken(`<span style="color:${escapeHtml(color)};">${escapeHtml(plainText)}</span>`);
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1153
1162
|
output = output.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, url) => {
|
|
1154
1163
|
return storeToken(
|
|
1155
1164
|
`<img src="${escapeHtml(url.trim())}" alt="${escapeHtml(alt.trim())}" ` +
|
|
@@ -1171,7 +1180,7 @@ function applyInlineMarkdown(text) {
|
|
|
1171
1180
|
output = output.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "<em>$1</em>");
|
|
1172
1181
|
output = output.replace(/(?<!_)_([^_]+)_(?!_)/g, "<em>$1</em>");
|
|
1173
1182
|
|
|
1174
|
-
for (let index =
|
|
1183
|
+
for (let index = tokens.length - 1; index >= 0; index -= 1) {
|
|
1175
1184
|
output = output.replace(`@@MDTOKEN${index}@@`, tokens[index]);
|
|
1176
1185
|
}
|
|
1177
1186
|
|
|
@@ -1478,6 +1487,7 @@ async function importWechatArticleCommand(sourceUrl, file = "article.md") {
|
|
|
1478
1487
|
console.log(targetFile);
|
|
1479
1488
|
console.log(chalk.gray(`标题: ${article.title}`));
|
|
1480
1489
|
console.log(chalk.gray(`已转存图片: ${article.imageCount} 张`));
|
|
1490
|
+
console.log(chalk.gray(`已保留文字颜色: ${article.colorCount} 处`));
|
|
1481
1491
|
console.log("");
|
|
1482
1492
|
console.log("请补充 cate_id 等业务字段,确认内容后执行:");
|
|
1483
1493
|
console.log("");
|
package/lib/wechat-article.js
CHANGED
|
@@ -234,9 +234,68 @@ function escapeMarkdownText(value) {
|
|
|
234
234
|
return normalizeTextContent(value).replace(/([\\`*_\[\]])/g, "\\$1");
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
|
|
237
|
+
const NAMED_TEXT_COLORS = new Set([
|
|
238
|
+
"aqua",
|
|
239
|
+
"black",
|
|
240
|
+
"blue",
|
|
241
|
+
"cyan",
|
|
242
|
+
"fuchsia",
|
|
243
|
+
"gray",
|
|
244
|
+
"green",
|
|
245
|
+
"grey",
|
|
246
|
+
"lime",
|
|
247
|
+
"magenta",
|
|
248
|
+
"maroon",
|
|
249
|
+
"navy",
|
|
250
|
+
"olive",
|
|
251
|
+
"orange",
|
|
252
|
+
"pink",
|
|
253
|
+
"purple",
|
|
254
|
+
"red",
|
|
255
|
+
"silver",
|
|
256
|
+
"teal",
|
|
257
|
+
"transparent",
|
|
258
|
+
"white",
|
|
259
|
+
"yellow"
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
export function normalizeArticleColor(value) {
|
|
263
|
+
const color = String(value || "")
|
|
264
|
+
.trim()
|
|
265
|
+
.replace(/\s*!important\s*$/i, "")
|
|
266
|
+
.trim()
|
|
267
|
+
.toLowerCase();
|
|
268
|
+
if (/^#[0-9a-f]{3,4}(?:[0-9a-f]{3,4})?$/.test(color)) {
|
|
269
|
+
return color;
|
|
270
|
+
}
|
|
271
|
+
if (/^rgba?\(\s*(?:\d{1,3}%?\s*,\s*){2}\d{1,3}%?(?:\s*,\s*(?:0|1|0?\.\d+|\d{1,3}%))?\s*\)$/.test(color)) {
|
|
272
|
+
return color.replace(/\s+/g, " ");
|
|
273
|
+
}
|
|
274
|
+
return NAMED_TEXT_COLORS.has(color) ? color : "";
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function elementTextColor(element) {
|
|
278
|
+
const style = [element.attr("style"), element.attr("data-style")].filter(Boolean).join(";");
|
|
279
|
+
let color = "";
|
|
280
|
+
for (const match of style.matchAll(/(?:^|;)\s*(?:color|-webkit-text-fill-color)\s*:\s*([^;]+)/gi)) {
|
|
281
|
+
color = normalizeArticleColor(match[1]) || color;
|
|
282
|
+
}
|
|
283
|
+
return (
|
|
284
|
+
color ||
|
|
285
|
+
normalizeArticleColor(element.attr("color")) ||
|
|
286
|
+
normalizeArticleColor(element.attr("data-color"))
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function markdownForNode($, node, listDepth = 0, inheritedColor = "", context = { colorCount: 0, colors: new Set() }) {
|
|
238
291
|
if (node.type === "text") {
|
|
239
|
-
|
|
292
|
+
const value = escapeMarkdownText(node.data);
|
|
293
|
+
if (!inheritedColor || !value.trim()) {
|
|
294
|
+
return value;
|
|
295
|
+
}
|
|
296
|
+
context.colorCount += 1;
|
|
297
|
+
context.colors.add(inheritedColor);
|
|
298
|
+
return `{{qc-color:${inheritedColor}}}${value}{{/qc-color}}`;
|
|
240
299
|
}
|
|
241
300
|
if (node.type !== "tag") {
|
|
242
301
|
return "";
|
|
@@ -244,7 +303,13 @@ function markdownForNode($, node, listDepth = 0) {
|
|
|
244
303
|
|
|
245
304
|
const element = $(node);
|
|
246
305
|
const tag = String(node.tagName || node.name || "").toLowerCase();
|
|
247
|
-
const
|
|
306
|
+
const textColor = elementTextColor(element) || inheritedColor;
|
|
307
|
+
const children = () =>
|
|
308
|
+
element
|
|
309
|
+
.contents()
|
|
310
|
+
.toArray()
|
|
311
|
+
.map(child => markdownForNode($, child, listDepth, textColor, context))
|
|
312
|
+
.join("");
|
|
248
313
|
|
|
249
314
|
if (["script", "style", "noscript", "iframe", "video", "audio", "canvas", "svg", "form", "button", "input"].includes(tag)) {
|
|
250
315
|
return "";
|
|
@@ -297,12 +362,7 @@ function markdownForNode($, node, listDepth = 0) {
|
|
|
297
362
|
if (tag === "ul" || tag === "ol") {
|
|
298
363
|
const ordered = tag === "ol";
|
|
299
364
|
const items = element.children("li").toArray().map((item, index) => {
|
|
300
|
-
const value =
|
|
301
|
-
.contents()
|
|
302
|
-
.toArray()
|
|
303
|
-
.map(child => markdownForNode($, child, listDepth + 1))
|
|
304
|
-
.join("")
|
|
305
|
-
.trim();
|
|
365
|
+
const value = markdownForNode($, item, listDepth + 1, textColor, context).trim();
|
|
306
366
|
const prefix = ordered ? `${index + 1}.` : "-";
|
|
307
367
|
return `${" ".repeat(listDepth)}${prefix} ${value}`;
|
|
308
368
|
});
|
|
@@ -411,8 +471,13 @@ export async function convertWechatHtml(html, pageUrl, options = {}) {
|
|
|
411
471
|
$(element).attr("data-qcplay-src", uploaded.get(key));
|
|
412
472
|
});
|
|
413
473
|
|
|
474
|
+
const colorContext = { colorCount: 0, colors: new Set() };
|
|
414
475
|
const markdown = normalizeMarkdown(
|
|
415
|
-
content
|
|
476
|
+
content
|
|
477
|
+
.contents()
|
|
478
|
+
.toArray()
|
|
479
|
+
.map(node => markdownForNode($, node, 0, "", colorContext))
|
|
480
|
+
.join("")
|
|
416
481
|
);
|
|
417
482
|
if (!markdown) {
|
|
418
483
|
throw new Error("微信文章正文转换后为空");
|
|
@@ -426,6 +491,8 @@ export async function convertWechatHtml(html, pageUrl, options = {}) {
|
|
|
426
491
|
thumbnail: coverSource ? uploaded.get(assertWechatImageUrl(coverSource, sourceUrl).toString()) || "" : uploaded.values().next().value || "",
|
|
427
492
|
markdown,
|
|
428
493
|
imageCount: uploaded.size,
|
|
494
|
+
colorCount: colorContext.colorCount,
|
|
495
|
+
colors: [...colorContext.colors],
|
|
429
496
|
sourceUrl: sourceUrl.toString()
|
|
430
497
|
};
|
|
431
498
|
}
|