const { marked } = require('marked'); const fs = require('fs'); const path = require('path'); const cheerio = require('cheerio'); // Configure marked options const options = { headerIds: true, gfm: true }; // Footer template const footer = ` `; // Function to convert markdown to HTML async function convertMarkdownToHtml(mdFilePath, outputPath) { try { // Read markdown file const markdown = await fs.promises.readFile(mdFilePath, 'utf8'); // Extract metadata from markdown (assuming front matter) const metadata = {}; const content = markdown.replace(/^---\n([\s\S]*?)\n---\n/, (_, frontMatter) => { frontMatter.split('\n').forEach(line => { const [key, ...valueParts] = line.split(':'); if (key && valueParts.length > 0) { metadata[key.trim()] = valueParts.join(':').trim(); } }); return ''; }); // Configure marked options for proper heading rendering const markedOptions = { headerIds: true, gfm: true, breaks: true, pedantic: false, smartLists: true, smartypants: true }; // Convert markdown to HTML const articleContent = marked.parse(content, markedOptions); // Calculate read time (rough estimate: 200 words per minute) const wordCount = content.trim().split(/\s+/).length; const readTime = Math.ceil(wordCount / 200); // Create full HTML document const html = ` ${metadata.title || 'Blog Post'} - Glenn Thompson

${metadata.title || 'Blog Post'}

${readTime} min read By ${metadata.author || 'Glenn Thompson'}
${metadata.tags ? `
${metadata.tags.split(',').map(tag => `${tag.trim()}` ).join('')}
` : ''}
${articleContent}
${footer}`; // Write HTML file const htmlPath = outputPath || mdFilePath.replace('.md', '.html'); await fs.promises.writeFile(htmlPath, html); console.log(`Converted ${mdFilePath} to ${htmlPath}`); } catch (error) { console.error('Error converting markdown to HTML:', error); process.exit(1); } } // Function to extract summary from markdown content function extractSummary(content, maxLength = 200) { // Remove frontmatter const contentWithoutFrontmatter = content.replace(/^---\n[\s\S]*?\n---\n/, ''); // Parse the markdown to HTML const html = marked.parse(contentWithoutFrontmatter); // Use cheerio to extract text from the first paragraph const $ = cheerio.load(html); const firstParagraph = $('p').first().text(); // Truncate to maxLength and add ellipsis if needed if (firstParagraph.length <= maxLength) { return firstParagraph; } return firstParagraph.substring(0, maxLength).trim() + '...'; } // Function to update index.html with blog post summaries async function updateIndexWithSummaries() { try { const postsDir = path.join(process.cwd(), 'content', 'posts'); const indexPath = path.join(process.cwd(), 'index.html'); // Read all markdown files const files = await fs.promises.readdir(postsDir); const posts = []; for (const file of files) { if (file.endsWith('.md')) { const filePath = path.join(postsDir, file); const content = await fs.promises.readFile(filePath, 'utf8'); // Extract metadata const metadata = {}; content.replace(/^---\n([\s\S]*?)\n---\n/, (_, frontMatter) => { frontMatter.split('\n').forEach(line => { const [key, ...valueParts] = line.split(':'); if (key && valueParts.length > 0) { metadata[key.trim()] = valueParts.join(':').trim(); } }); return ''; }); // Extract summary const summary = extractSummary(content); // Parse and format the date let formattedDate = ''; let isoDate = ''; try { // Handle date formats like "2024-04-08 16:50" or "2024-04-08" const dateStr = metadata.date.split(' ')[0]; const date = new Date(dateStr); formattedDate = date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); isoDate = dateStr; } catch (e) { console.error(`Error parsing date for ${file}:`, e); } // Parse tags let tags = []; if (metadata.tags) { // Remove square brackets and split by commas tags = metadata.tags.replace(/[\[\]]/g, '').split(',').map(t => t.trim().replace(/"/g, '')); } posts.push({ title: metadata.title?.replace(/"/g, '') || 'Untitled', date: formattedDate, isoDate, summary, tags, url: `/content/posts/${path.basename(file, '.md')}.html` }); } } // Sort posts by date (newest first) posts.sort((a, b) => new Date(b.isoDate) - new Date(a.isoDate)); // Read index.html let indexHtml = await fs.promises.readFile(indexPath, 'utf8'); // Create the HTML for blog posts const postsHtml = posts.map(post => `
Tech ${post.tags.map(tag => `${tag}`).join('')}

${post.title}

${post.summary}

${post.tags.map(tag => `${tag}`).join('')}
`).join('\n'); // Find the blog posts section and replace its content const blogSectionStart = indexHtml.indexOf('
'); const blogSectionEnd = indexHtml.indexOf('
') + ''.length; if (blogSectionStart === -1 || blogSectionEnd === -1) { console.error('Could not find blog posts section in index.html'); return; } // Replace the content between the markers indexHtml = indexHtml.substring(0, blogSectionStart) + '
\n' + postsHtml + '\n' + '
' + indexHtml.substring(blogSectionEnd); // Write the updated index.html await fs.promises.writeFile(indexPath, indexHtml); console.log('Successfully updated index.html with blog post summaries'); } catch (error) { console.error('Error updating index.html:', error); throw error; } } // Export functions module.exports = { convertMarkdownToHtml, updateIndexWithSummaries }; // If running from command line if (require.main === module) { const mdFilePath = process.argv[2]; const outputPath = process.argv[3]; if (!mdFilePath) { console.error('Please provide a markdown file path'); process.exit(1); } convertMarkdownToHtml(mdFilePath, outputPath); }