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