add cvs
All checks were successful
Deploy valere.dev / deploy (push) Successful in 10s

This commit is contained in:
valere
2025-10-29 20:56:34 +01:00
parent 973c1bedb4
commit 7355b1a9b4
30 changed files with 19384 additions and 64 deletions

256
scripts/convert.mjs Normal file
View 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&amp;display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&amp;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();