feat: molzi3d.de v1.0.0 — Komplettes Redesign mit Next.js 16
- WordPress durch Next.js 16 + Tailwind CSS v4 + Framer Motion ersetzt - 44 Guides + 15 Seiten aus WordPress migriert (HTML -> Markdown) - Emerald Design-System mit Light/Dark Mode Toggle - Sidebar-First Navigation (Dokumentations-Stil) - Difficulty-Badges, Lesezeit, verwandte Guides - Statischer Export fuer Plesk-Hosting - WordPress-DB Backup gesichert (6.2 MB) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
137
scripts/convert-content.mjs
Normal file
137
scripts/convert-content.mjs
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Konvertiert WordPress HTML-Exports in saubere Markdown-Dateien
|
||||
* mit Frontmatter fuer das Next.js-Projekt.
|
||||
*/
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { NodeHtmlMarkdown } from "node-html-markdown";
|
||||
|
||||
const BACKUP_POSTS = path.resolve("backup/content/posts");
|
||||
const BACKUP_PAGES = path.resolve("backup/content/pages");
|
||||
const OUT_GUIDES = path.resolve("app/src/content/guides");
|
||||
const OUT_PAGES = path.resolve("app/src/content/pages");
|
||||
|
||||
// Duplikate/veraltete Slugs (die mit Datum-Suffix)
|
||||
const SKIP_SLUGS = [
|
||||
"guide-1-pla-perfekt-einstellen-2026-03-25",
|
||||
"guide-2-stringing-reduzieren-2026-03-25",
|
||||
"guide-1-warping-vermeiden-2026-03-26",
|
||||
"guide-2-petg-ohne-frust-2026-03-26",
|
||||
];
|
||||
|
||||
// Kategorie-Zuordnung basierend auf Slug-Patterns
|
||||
const categorize = (slug, title) => {
|
||||
const s = slug.toLowerCase();
|
||||
const t = title.toLowerCase();
|
||||
|
||||
if (s.includes("guide-orcaslicer") || s.includes("guide-cura") || s.includes("guide-bambu") || s.includes("guide-prusaslicer") || s.includes("slicer"))
|
||||
return "Slicer";
|
||||
if (s.includes("pla") || s.includes("petg") || s.includes("tpu") || s.includes("asa") || s.includes("abs") || s.includes("nylon") || s.includes("carbon") || s.includes("resin") || s.includes("filament") || s.includes("bed-adhesion"))
|
||||
return "Materialien";
|
||||
if (s.includes("stringing") || s.includes("warping") || s.includes("unterextrusion") || s.includes("layer-separation") || s.includes("elefantenfuss") || s.includes("verstopfte"))
|
||||
return "Fehlerbehebung";
|
||||
if (s.includes("retraction") || s.includes("flow-rate") || s.includes("pressure-advance") || s.includes("input-shaping") || s.includes("temperaturturm") || s.includes("speed-tower") || s.includes("erste-schicht") || s.includes("druckbett-leveln"))
|
||||
return "Kalibrierung";
|
||||
if (s.includes("adaptive") || s.includes("modifier") || s.includes("ironing") || s.includes("fuzzy") || s.includes("multi-material") || s.includes("klipper"))
|
||||
return "Fortgeschritten";
|
||||
if (s.includes("erstes-modell") || s.includes("support") || s.includes("infill") || s.includes("duesenwechsel") || s.includes("druckzeit") || s.includes("masshaltigkeit") || s.includes("bruecken") || s.includes("nachbearbeiten") || s.includes("gridfinity") || s.includes("naht"))
|
||||
return "Grundlagen";
|
||||
|
||||
return "Allgemein";
|
||||
};
|
||||
|
||||
// Schwierigkeitsgrad
|
||||
const difficulty = (slug, category) => {
|
||||
if (slug.includes("erstes-modell") || slug.includes("erste-schicht") || slug.includes("druckbett-leveln"))
|
||||
return "einsteiger";
|
||||
if (category === "Fortgeschritten" || slug.includes("klipper") || slug.includes("pressure-advance") || slug.includes("input-shaping") || slug.includes("carbon") || slug.includes("nylon-pa"))
|
||||
return "experte";
|
||||
return "fortgeschritten";
|
||||
};
|
||||
|
||||
const nhm = new NodeHtmlMarkdown({
|
||||
keepDataImages: false,
|
||||
useLinkReferenceDefinitions: false,
|
||||
});
|
||||
|
||||
const processFile = (filePath, outDir) => {
|
||||
const raw = fs.readFileSync(filePath, "utf-8");
|
||||
|
||||
// Frontmatter extrahieren
|
||||
const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
||||
if (!fmMatch) return null;
|
||||
|
||||
const fmBlock = fmMatch[1];
|
||||
const htmlContent = fmMatch[2].trim();
|
||||
|
||||
// Felder parsen
|
||||
const titleMatch = fmBlock.match(/title:\s*"(.+?)"/);
|
||||
const slugMatch = fmBlock.match(/slug:\s*"(.+?)"/);
|
||||
const excerptMatch = fmBlock.match(/excerpt:\s*"(.*)"/);
|
||||
|
||||
const title = titleMatch?.[1] ?? path.basename(filePath, ".html");
|
||||
const slug = slugMatch?.[1] ?? path.basename(filePath, ".html");
|
||||
const excerpt = excerptMatch?.[1] ?? "";
|
||||
|
||||
if (SKIP_SLUGS.includes(slug)) {
|
||||
console.log(` SKIP (Duplikat): ${slug}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// HTML -> Markdown
|
||||
let markdown = nhm.translate(htmlContent);
|
||||
|
||||
// Cleanup: Elementor-Artefakte, leere Divs, etc.
|
||||
markdown = markdown
|
||||
.replace(/\[vc_[^\]]*\]/g, "")
|
||||
.replace(/\[\/vc_[^\]]*\]/g, "")
|
||||
.replace(/\[elementor[^\]]*\]/g, "")
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.trim();
|
||||
|
||||
const cat = categorize(slug, title);
|
||||
const diff = difficulty(slug, cat);
|
||||
|
||||
const frontmatter = [
|
||||
"---",
|
||||
`title: "${title}"`,
|
||||
`slug: "${slug}"`,
|
||||
`category: "${cat}"`,
|
||||
`difficulty: "${diff}"`,
|
||||
`excerpt: "${excerpt}"`,
|
||||
"---",
|
||||
].join("\n");
|
||||
|
||||
const output = `${frontmatter}\n\n${markdown}\n`;
|
||||
const outPath = path.join(outDir, `${slug}.md`);
|
||||
fs.writeFileSync(outPath, output, "utf-8");
|
||||
console.log(` OK: ${slug} (${cat}, ${diff})`);
|
||||
return slug;
|
||||
};
|
||||
|
||||
// Main
|
||||
console.log("=== WordPress -> Markdown Konvertierung ===\n");
|
||||
|
||||
fs.mkdirSync(OUT_GUIDES, { recursive: true });
|
||||
fs.mkdirSync(OUT_PAGES, { recursive: true });
|
||||
|
||||
// Posts -> Guides
|
||||
console.log("Posts -> Guides:");
|
||||
const postFiles = fs.readdirSync(BACKUP_POSTS).filter((f) => f.endsWith(".html"));
|
||||
let postCount = 0;
|
||||
for (const file of postFiles) {
|
||||
const result = processFile(path.join(BACKUP_POSTS, file), OUT_GUIDES);
|
||||
if (result) postCount++;
|
||||
}
|
||||
console.log(`\n${postCount} Guides konvertiert.\n`);
|
||||
|
||||
// Pages
|
||||
console.log("Pages:");
|
||||
const pageFiles = fs.readdirSync(BACKUP_PAGES).filter((f) => f.endsWith(".html"));
|
||||
let pageCount = 0;
|
||||
for (const file of pageFiles) {
|
||||
const result = processFile(path.join(BACKUP_PAGES, file), OUT_PAGES);
|
||||
if (result) pageCount++;
|
||||
}
|
||||
console.log(`\n${pageCount} Pages konvertiert.`);
|
||||
console.log(`\nFertig! Output: ${OUT_GUIDES} und ${OUT_PAGES}`);
|
||||
Reference in New Issue
Block a user