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();
 |