This commit is contained in:
		
							
								
								
									
										256
									
								
								scripts/convert.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								scripts/convert.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,256 @@ | ||||
| #!/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(); | ||||
		Reference in New Issue
	
	Block a user