257 lines
5.9 KiB
JavaScript
257 lines
5.9 KiB
JavaScript
#!/usr/bin/env node
|
||
import { promises as fs } from "fs";
|
||
import path from "path";
|
||
import { glob } from "glob";
|
||
import MarkdownIt from "markdown-it";
|
||
import { mdToPdf } from "md-to-pdf";
|
||
|
||
const PUBLIC_DIR = path.resolve(process.cwd(), "public/cv");
|
||
|
||
async function ensureDir(dir) {
|
||
await fs.mkdir(dir, { recursive: true }).catch(() => {});
|
||
}
|
||
|
||
const STYLE_CSS = `
|
||
/* Default styles */
|
||
pre {
|
||
background: #2d2d2d;
|
||
border-radius: 4px;
|
||
margin: 0.5em 0;
|
||
}
|
||
code {
|
||
font-family: 'Fira Code', Consolas, Monaco, monospace;
|
||
}
|
||
/* Custom CSS */
|
||
/* ====== Markdown PDF Pro Theme ====== */
|
||
|
||
@page {
|
||
margin: 25mm 20mm;
|
||
margin-top: 0mm;
|
||
}
|
||
|
||
body {
|
||
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||
font-size: 11pt;
|
||
line-height: 1.6;
|
||
color: #333;
|
||
background: white;
|
||
max-width: 800px;
|
||
margin: auto;
|
||
padding: 60px;
|
||
}
|
||
|
||
/* Headings */
|
||
h1,
|
||
h2,
|
||
h3,
|
||
h4,
|
||
h5,
|
||
h6 {
|
||
font-family: "Segoe UI Semibold", "Helvetica Neue", Arial, sans-serif;
|
||
font-weight: 600;
|
||
margin-top: 2em;
|
||
margin-bottom: 0.6em;
|
||
line-height: 1.3;
|
||
color: #222;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 24pt;
|
||
border-bottom: 2px solid #000;
|
||
/* accent color */
|
||
padding-bottom: 0.3em;
|
||
margin-top: 0;
|
||
}
|
||
|
||
h2 {
|
||
font-size: 18pt;
|
||
border-left: 4px solid #000;
|
||
padding-left: 0.5em;
|
||
}
|
||
|
||
h3 {
|
||
font-size: 14pt;
|
||
color: #444;
|
||
}
|
||
|
||
h4,
|
||
h5,
|
||
h6 {
|
||
font-size: 12pt;
|
||
color: #555;
|
||
}
|
||
|
||
/* Paragraphs */
|
||
p {
|
||
margin: 0.5em 0;
|
||
text-align: justify;
|
||
}
|
||
|
||
/* Links */
|
||
a {
|
||
color: #1e7fce;
|
||
text-decoration: none;
|
||
}
|
||
|
||
a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
/* Lists */
|
||
ul,
|
||
ol {
|
||
margin: 0.5em 0 0.5em 2em;
|
||
}
|
||
|
||
li {
|
||
margin-bottom: 0.3em;
|
||
}
|
||
|
||
/* Blockquote */
|
||
blockquote {
|
||
border-left: 4px solid #0078D7;
|
||
margin: 1em 0;
|
||
padding: 0.5em 1em;
|
||
color: #555;
|
||
background: #f9f9f9;
|
||
font-style: italic;
|
||
}
|
||
|
||
/* Code */
|
||
code {
|
||
font-family: "Fira Code", "Consolas", monospace;
|
||
background: #f4f4f4;
|
||
padding: 0.2em 0.4em;
|
||
border-radius: 4px;
|
||
font-size: 0.95em;
|
||
}
|
||
|
||
pre {
|
||
background: #1e1e1e;
|
||
color: #dcdcdc;
|
||
padding: 1em;
|
||
border-radius: 6px;
|
||
overflow-x: auto;
|
||
font-size: 0.9em;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* Tables */
|
||
table {
|
||
border-collapse: collapse;
|
||
margin: 1em 0;
|
||
width: 100%;
|
||
font-size: 0.95em;
|
||
}
|
||
|
||
th,
|
||
td {
|
||
border: 1px solid #ccc;
|
||
padding: 0.6em 0.8em;
|
||
text-align: left;
|
||
}
|
||
|
||
th {
|
||
background: #f0f0f0;
|
||
font-weight: 600;
|
||
}
|
||
|
||
tr:nth-child(even) td {
|
||
background: #fafafa;
|
||
}
|
||
|
||
/* Horizontal rule */
|
||
hr {
|
||
border: none;
|
||
border-top: 2px solid #eee;
|
||
margin: 2em 0;
|
||
}`;
|
||
|
||
function wrapHtml(title, body) {
|
||
return `<html><head>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
|
||
<link href="https://fonts.googleapis.com/css2?family=Barlow:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
|
||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet">
|
||
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
|
||
<style>${STYLE_CSS}
|
||
</style>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-sql.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-go.min.js"></script>
|
||
</head>
|
||
<body>
|
||
${body}
|
||
</body>
|
||
</html>`;
|
||
}
|
||
|
||
async function convertFile(mdPath) {
|
||
const base = path.basename(mdPath, ".md");
|
||
const dir = path.dirname(mdPath);
|
||
const rel = path.relative(PUBLIC_DIR, mdPath);
|
||
|
||
// Créer les dossiers de destination s'ils n'existent pas
|
||
const htmlDir = path.join(PUBLIC_DIR, 'html');
|
||
const pdfDir = path.join(PUBLIC_DIR, 'pdf');
|
||
await ensureDir(htmlDir);
|
||
await ensureDir(pdfDir);
|
||
|
||
let mdContent = await fs.readFile(mdPath, "utf8");
|
||
|
||
// Remplacer les guillemets courbes par des guillemets droits
|
||
mdContent = mdContent
|
||
.replace(/’/g, "'"); // Remplace les apostrophes courbes par des droites
|
||
|
||
// HTML
|
||
const md = new MarkdownIt({ html: true, linkify: true, typographer: true });
|
||
const htmlBody = md.render(mdContent);
|
||
const html = wrapHtml(base, htmlBody);
|
||
const htmlPath = path.join(htmlDir, `${base}.html`);
|
||
await fs.writeFile(htmlPath, html, "utf8");
|
||
|
||
// PDF
|
||
const pdfPath = path.join(pdfDir, `${base}.pdf`);
|
||
const pdf = await mdToPdf(
|
||
{ content: mdContent, path: mdPath },
|
||
{
|
||
basedir: dir,
|
||
launch_options: { args: ["--no-sandbox", "--disable-setuid-sandbox"] },
|
||
pdf_options: { format: "A4", printBackground: true },
|
||
css: STYLE_CSS,
|
||
}
|
||
);
|
||
if (pdf && pdf.content) {
|
||
await fs.writeFile(pdfPath, pdf.content);
|
||
}
|
||
|
||
console.log(
|
||
`Converted ${rel} -> ${path.relative(
|
||
PUBLIC_DIR,
|
||
htmlPath
|
||
)}, ${path.relative(PUBLIC_DIR, pdfPath)}`
|
||
);
|
||
}
|
||
|
||
async function main() {
|
||
await ensureDir(PUBLIC_DIR);
|
||
const mdFiles = await glob("public/**/*.md", { nodir: true });
|
||
if (!mdFiles.length) {
|
||
console.log("No Markdown files found in public/.");
|
||
return;
|
||
}
|
||
for (const file of mdFiles) {
|
||
try {
|
||
await convertFile(path.resolve(process.cwd(), file));
|
||
} catch (err) {
|
||
console.error(`Failed to convert ${file}:`, err.message || err);
|
||
process.exitCode = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
main();
|