Build Your Kit page and full Micromelon website
Complete website build including: - Build Your Kit store page with cart system, sectioned layout (Hardware, Software, Attachments, Spare Parts), inline quote request form, and sticky sidebar summary - 16+ pages: Education, Platform, Resources, News, About Us, Download, Contact, Rover, Code Editor, Robot Simulator, etc. - 89+ MDX resource articles and 18 news posts - Store product images scraped from micromelon.com.au - Quote request API route with Airtable integration - Dynamic back links and cover photos on resource pages - Redesigned downloads page - Fixed corrupted MDX code blocks
This commit is contained in:
111
scripts/fix-all-mdx.mjs
Normal file
111
scripts/fix-all-mdx.mjs
Normal file
@@ -0,0 +1,111 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const dir = path.join(__dirname, '..', 'content', 'resources');
|
||||
|
||||
let fixed = 0;
|
||||
for (const f of fs.readdirSync(dir).filter(f => f.endsWith('.mdx'))) {
|
||||
const filepath = path.join(dir, f);
|
||||
const original = fs.readFileSync(filepath, 'utf8');
|
||||
|
||||
// Split frontmatter from content
|
||||
const secondDash = original.indexOf('---', 4);
|
||||
if (secondDash === -1) continue;
|
||||
let frontmatter = original.substring(0, secondDash + 3);
|
||||
let content = original.substring(secondDash + 3);
|
||||
|
||||
// === Fix body content ===
|
||||
|
||||
// Remove BACK TO POSTS and OPEN PAGE AS PDF
|
||||
content = content.replace(/\[\s*\*?\*?\s*BACK TO POSTS\s*\*?\*?\s*\]\([^)]*\)/gi, '');
|
||||
content = content.replace(/\*?\*?\s*BACK TO POSTS\s*\*?\*?/gi, '');
|
||||
content = content.replace(/\|?\s*\[?\s*\*?\*?\s*OPEN PAGE AS PDF\s*\*?\*?\s*\]?\s*\([^)]*\)/gi, '');
|
||||
|
||||
// Remove CSS block definitions
|
||||
content = content.replace(/#block-[a-zA-Z0-9_-]+ \{[^}]*?\}/g, '');
|
||||
|
||||
// Remove --> artifacts
|
||||
content = content.replace(/^-->\s*$/gm, '');
|
||||
|
||||
// Escape ALL < that aren't part of:
|
||||
// 1. Markdown images:  - these don't have <
|
||||
// 2. iframe tags: <iframe ... > or </iframe>
|
||||
// 3. Already escaped: \<
|
||||
// Strategy: protect safe tags, escape everything else, restore safe tags
|
||||
|
||||
// Temporarily replace safe tags with placeholders
|
||||
const safeTags = [];
|
||||
content = content.replace(/<(\/?)iframe([^>]*)>/gi, (match) => {
|
||||
safeTags.push(match);
|
||||
return `__SAFE_TAG_${safeTags.length - 1}__`;
|
||||
});
|
||||
content = content.replace(/<(\/?)video([^>]*)>/gi, (match) => {
|
||||
safeTags.push(match);
|
||||
return `__SAFE_TAG_${safeTags.length - 1}__`;
|
||||
});
|
||||
content = content.replace(/<source([^>]*)>/gi, (match) => {
|
||||
safeTags.push(match);
|
||||
return `__SAFE_TAG_${safeTags.length - 1}__`;
|
||||
});
|
||||
|
||||
// Now escape all remaining < and > that aren't in image markdown
|
||||
// Split by lines to handle image markdown
|
||||
const lines = content.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
// Skip lines that are image markdown
|
||||
if (line.trim().startsWith('![')) continue;
|
||||
// Skip lines that only contain safe tag placeholders
|
||||
if (line.trim().match(/^__SAFE_TAG_\d+__$/)) continue;
|
||||
|
||||
// Escape < and > that aren't already escaped or part of placeholders
|
||||
lines[i] = line.replace(/<(?!_SAFE_TAG)/g, (match, offset) => {
|
||||
// Check if already escaped
|
||||
if (offset > 0 && line[offset - 1] === '\\') return match;
|
||||
return '<';
|
||||
});
|
||||
lines[i] = lines[i].replace(/>(?!_)/g, (match, offset) => {
|
||||
const before = lines[i].substring(0, offset);
|
||||
// Don't escape if it's after a safe tag placeholder
|
||||
if (before.match(/__SAFE_TAG_\d+$/)) return match;
|
||||
// Don't escape if already escaped
|
||||
if (offset > 0 && lines[i][offset - 1] === '\\') return match;
|
||||
// Don't escape markdown blockquotes at start of line
|
||||
if (before.match(/^\s*$/)) return match;
|
||||
return '>';
|
||||
});
|
||||
}
|
||||
content = lines.join('\n');
|
||||
|
||||
// Restore safe tags
|
||||
for (let i = 0; i < safeTags.length; i++) {
|
||||
content = content.replace(`__SAFE_TAG_${i}__`, safeTags[i]);
|
||||
}
|
||||
|
||||
// Clean excessive blank lines
|
||||
content = content.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
// === Fix frontmatter excerpt ===
|
||||
const excerptMatch = frontmatter.match(/^excerpt: "(.*)"$/m);
|
||||
if (excerptMatch) {
|
||||
let excerpt = excerptMatch[1];
|
||||
excerpt = excerpt.replace(/\*?\*?BACK TO POSTS\*?\*?/gi, '');
|
||||
excerpt = excerpt.replace(/!\[[^\]]*\]\([^)]*\)/g, '');
|
||||
excerpt = excerpt.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1');
|
||||
excerpt = excerpt.replace(/\*\*/g, '');
|
||||
excerpt = excerpt.replace(/#block-[^\s]+ \{[^}]*?\}/g, '');
|
||||
excerpt = excerpt.replace(/\s*\|\s*/g, ' ');
|
||||
excerpt = excerpt.replace(/\s+/g, ' ').trim();
|
||||
excerpt = excerpt.substring(0, 250);
|
||||
frontmatter = frontmatter.replace(excerptMatch[0], `excerpt: "${excerpt}"`);
|
||||
}
|
||||
|
||||
const result = frontmatter + content;
|
||||
if (result !== original) {
|
||||
fs.writeFileSync(filepath, result);
|
||||
fixed++;
|
||||
}
|
||||
}
|
||||
console.log(`Fixed ${fixed} files`);
|
||||
Reference in New Issue
Block a user