import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import type { StyleConfig, HtmlDocumentMeta } from "./types.js"; import { DEFAULT_STYLE } from "./constants.js"; const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); const CODE_THEMES_DIR = path.resolve(SCRIPT_DIR, "code-themes"); export function buildCss(baseCss: string, themeCss: string, style: StyleConfig = DEFAULT_STYLE): string { const variables = ` :root { --md-primary-color: ${style.primaryColor}; --md-font-family: ${style.fontFamily}; --md-font-size: ${style.fontSize}; --foreground: ${style.foreground}; --blockquote-background: ${style.blockquoteBackground}; --md-accent-color: ${style.accentColor}; --md-container-bg: ${style.containerBg}; } body { margin: 0; padding: 24px; background: #ffffff; } #output { max-width: 860px; margin: 0 auto; } `.trim(); return [variables, baseCss, themeCss].join("\n\n"); } export function loadCodeThemeCss(themeName: string): string { const filePath = path.join(CODE_THEMES_DIR, `${themeName}.min.css`); try { return fs.readFileSync(filePath, "utf-8"); } catch { console.error(`Code theme CSS not found: ${filePath}`); return ""; } } export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string { const lines = [ "", "", "", ' ', ' ', ` ${meta.title}`, ]; if (meta.author) { lines.push(` `); } if (meta.description) { lines.push(` `); } lines.push(` `); if (codeThemeCss) { lines.push(` `); } lines.push( "", "", '
', html, "
", "", "" ); return lines.join("\n"); } export async function inlineCss(html: string): Promise { try { const { default: juice } = await import("juice"); return juice(html, { inlinePseudoElements: true, preserveImportant: true, resolveCSSVariables: false, }); } catch (error) { const detail = error instanceof Error ? error.message : String(error); throw new Error( `Missing dependency "juice" for CSS inlining. Install it first (e.g. "bun add juice" or "npm add juice"). Original error: ${detail}` ); } } export function normalizeCssText(cssText: string, style: StyleConfig = DEFAULT_STYLE): string { return cssText .replace(/var\(--md-primary-color\)/g, style.primaryColor) .replace(/var\(--md-font-family\)/g, style.fontFamily) .replace(/var\(--md-font-size\)/g, style.fontSize) .replace(/var\(--blockquote-background\)/g, style.blockquoteBackground) .replace(/var\(--md-accent-color\)/g, style.accentColor) .replace(/var\(--md-container-bg\)/g, style.containerBg) .replace(/hsl\(var\(--foreground\)\)/g, "#3f3f3f") .replace(/--md-primary-color:\s*[^;"']+;?/g, "") .replace(/--md-font-family:\s*[^;"']+;?/g, "") .replace(/--md-font-size:\s*[^;"']+;?/g, "") .replace(/--blockquote-background:\s*[^;"']+;?/g, "") .replace(/--md-accent-color:\s*[^;"']+;?/g, "") .replace(/--md-container-bg:\s*[^;"']+;?/g, "") .replace(/--foreground:\s*[^;"']+;?/g, ""); } export function normalizeInlineCss(html: string, style: StyleConfig = DEFAULT_STYLE): string { let output = html; output = output.replace( /]*)>([\s\S]*?)<\/style>/gi, (_match, attrs: string, cssText: string) => `${normalizeCssText(cssText, style)}` ); output = output.replace( /style="([^"]*)"/gi, (_match, cssText: string) => `style="${normalizeCssText(cssText, style)}"` ); output = output.replace( /style='([^']*)'/gi, (_match, cssText: string) => `style='${normalizeCssText(cssText, style)}'` ); return output; } export function modifyHtmlStructure(htmlString: string): string { let output = htmlString; const pattern = /]*)>([\s\S]*?)(|)<\/li>/i; while (pattern.test(output)) { output = output.replace(pattern, "$2$3"); } return output; } export function removeFirstHeading(html: string): string { return html.replace(/]*>[\s\S]*?<\/h[12]>/, ""); }