mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-12 22:09:48 +08:00
856e8e5205
Split monolithic render.ts into cli, constants, extend-config, html-builder, renderer, themes, and types modules. Bundle highlight.js code theme CSS files locally instead of CDN loading.
120 lines
3.8 KiB
TypeScript
120 lines
3.8 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import type { StyleConfig, HtmlDocumentMeta } from "./types.js";
|
|
import { DEFAULT_STYLE, THEME_STYLE_DEFAULTS } from "./constants.js";
|
|
import { loadThemeCss, normalizeThemeCss } from "./themes.js";
|
|
import { parseArgs, printUsage } from "./cli.js";
|
|
import { initRenderer, renderMarkdown, postProcessHtml } from "./renderer.js";
|
|
import {
|
|
buildCss,
|
|
loadCodeThemeCss,
|
|
buildHtmlDocument,
|
|
inlineCss,
|
|
normalizeInlineCss,
|
|
modifyHtmlStructure,
|
|
removeFirstHeading,
|
|
} from "./html-builder.js";
|
|
|
|
function formatTimestamp(date = new Date()): string {
|
|
const pad = (value: number) => String(value).padStart(2, "0");
|
|
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(
|
|
date.getDate()
|
|
)}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const options = parseArgs(process.argv.slice(2));
|
|
if (!options) {
|
|
printUsage();
|
|
process.exit(1);
|
|
}
|
|
|
|
const inputPath = path.resolve(process.cwd(), options.inputPath);
|
|
if (!inputPath.toLowerCase().endsWith(".md")) {
|
|
console.error("Input file must end with .md");
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!fs.existsSync(inputPath)) {
|
|
console.error(`File not found: ${inputPath}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const outputPath = path.resolve(
|
|
process.cwd(),
|
|
options.inputPath.replace(/\.md$/i, ".html")
|
|
);
|
|
|
|
const themeDefaults = THEME_STYLE_DEFAULTS[options.theme] ?? {};
|
|
const style: StyleConfig = {
|
|
...DEFAULT_STYLE,
|
|
...themeDefaults,
|
|
...(options.primaryColor !== undefined ? { primaryColor: options.primaryColor } : {}),
|
|
...(options.fontFamily !== undefined ? { fontFamily: options.fontFamily } : {}),
|
|
...(options.fontSize !== undefined ? { fontSize: options.fontSize } : {}),
|
|
};
|
|
|
|
const { baseCss, themeCss } = loadThemeCss(options.theme);
|
|
const css = normalizeThemeCss(buildCss(baseCss, themeCss, style));
|
|
const codeThemeCss = loadCodeThemeCss(options.codeTheme);
|
|
|
|
const markdown = fs.readFileSync(inputPath, "utf-8");
|
|
|
|
const renderer = initRenderer({
|
|
legend: options.legend,
|
|
citeStatus: options.citeStatus,
|
|
countStatus: options.countStatus,
|
|
isMacCodeBlock: options.isMacCodeBlock,
|
|
isShowLineNumber: options.isShowLineNumber,
|
|
});
|
|
const { yamlData } = renderer.parseFrontMatterAndContent(markdown);
|
|
const { html: baseHtml, readingTime: readingTimeResult } = renderMarkdown(
|
|
markdown,
|
|
renderer
|
|
);
|
|
let content = postProcessHtml(baseHtml, readingTimeResult, renderer);
|
|
if (!options.keepTitle) {
|
|
content = removeFirstHeading(content);
|
|
}
|
|
|
|
const stripQuotes = (s?: string): string | undefined => {
|
|
if (!s) return s;
|
|
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
|
return s.slice(1, -1);
|
|
}
|
|
if ((s.startsWith('\u201c') && s.endsWith('\u201d')) || (s.startsWith('\u2018') && s.endsWith('\u2019'))) {
|
|
return s.slice(1, -1);
|
|
}
|
|
return s;
|
|
};
|
|
|
|
const meta: HtmlDocumentMeta = {
|
|
title: stripQuotes(yamlData.title) || path.basename(outputPath, ".html"),
|
|
author: stripQuotes(yamlData.author),
|
|
description: stripQuotes(yamlData.description) || stripQuotes(yamlData.summary),
|
|
};
|
|
const html = buildHtmlDocument(meta, css, content, codeThemeCss);
|
|
const inlinedHtml = normalizeInlineCss(await inlineCss(html), style);
|
|
const finalHtml = modifyHtmlStructure(inlinedHtml);
|
|
|
|
let backupPath = "";
|
|
if (fs.existsSync(outputPath)) {
|
|
backupPath = `${outputPath}.bak-${formatTimestamp()}`;
|
|
fs.renameSync(outputPath, backupPath);
|
|
}
|
|
|
|
fs.writeFileSync(outputPath, finalHtml, "utf-8");
|
|
|
|
if (backupPath) {
|
|
console.log(`Backup created: ${backupPath}`);
|
|
}
|
|
console.log(`HTML written: ${outputPath}`);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|