/** * Generate Blog Card Sections * * Converts blog post content into card-based sections for better UI presentation. * Similar to document card sections but optimized for blog posts. */ const { getCollection, connect, close } = require('../src/utils/db.util'); const { extractTOC, markdownToHtml } = require('../src/utils/markdown.util'); const SLUG = 'tractatus-research-working-paper-v01'; /** * Parse HTML content into sections based on H2 headings */ function parseContentIntoSections(htmlContent) { const sections = []; // Split by H2 headings const h2Regex = /]*id="([^"]*)"[^>]*>(.*?)<\/h2>/g; const matches = [...htmlContent.matchAll(h2Regex)]; if (matches.length === 0) { // No H2 headings - treat entire content as one section return [{ number: 1, title: 'Introduction', slug: 'introduction', content_html: htmlContent, excerpt: extractExcerpt(htmlContent), readingTime: calculateReadingTime(htmlContent), technicalLevel: 'intermediate', category: 'conceptual' }]; } // Process each section for (let i = 0; i < matches.length; i++) { const match = matches[i]; const slug = match[1]; const title = stripHtmlTags(match[2]); // Find content between this H2 and the next one (or end of document) const startIndex = match.index; const endIndex = i < matches.length - 1 ? matches[i + 1].index : htmlContent.length; const sectionHtml = htmlContent.substring(startIndex, endIndex); sections.push({ number: i + 1, title, slug, content_html: sectionHtml, excerpt: extractExcerpt(sectionHtml), readingTime: calculateReadingTime(sectionHtml), technicalLevel: determineTechnicalLevel(title, sectionHtml), category: determineCategory(title, sectionHtml) }); } return sections; } /** * Strip HTML tags from text */ function stripHtmlTags(html) { return html.replace(/<[^>]*>/g, '').trim(); } /** * Extract excerpt from HTML content */ function extractExcerpt(html, maxLength = 200) { const text = stripHtmlTags(html); if (text.length <= maxLength) return text; // Find last complete sentence within maxLength const truncated = text.substring(0, maxLength); const lastPeriod = truncated.lastIndexOf('.'); if (lastPeriod > maxLength * 0.7) { return truncated.substring(0, lastPeriod + 1); } return truncated + '...'; } /** * Calculate reading time in minutes */ function calculateReadingTime(html) { const text = stripHtmlTags(html); const wordsPerMinute = 200; const words = text.split(/\s+/).length; const minutes = Math.ceil(words / wordsPerMinute); return Math.max(1, minutes); } /** * Determine technical level based on content */ function determineTechnicalLevel(title, content) { const text = (title + ' ' + stripHtmlTags(content)).toLowerCase(); // Advanced indicators if (text.match(/\b(architecture|implementation|validation|methodology|hook|interceptor|database schema)\b/)) { return 'advanced'; } // Beginner indicators if (text.match(/\b(what this is|introduction|getting started|overview|background)\b/)) { return 'beginner'; } return 'intermediate'; } /** * Determine category based on content */ function determineCategory(title, content) { const text = (title + ' ' + stripHtmlTags(content)).toLowerCase(); if (text.match(/\b(critical|limitation|warning|cannot claim|disclaimer)\b/)) { return 'critical'; } if (text.match(/\b(example|pattern|code|implementation)\b/)) { return 'practical'; } if (text.match(/\b(research|study|methodology|validation|citation)\b/)) { return 'research'; } return 'conceptual'; } async function generateBlogCardSections() { try { console.log('šŸŽØ Generating blog card sections...\n'); await connect(); const collection = await getCollection('blog_posts'); // Find the blog post const post = await collection.findOne({ slug: SLUG }); if (!post) { console.error('āŒ Blog post not found:', SLUG); return; } console.log('šŸ“ Found blog post:', post.title); console.log(' Content type:', post.content.startsWith('<') ? 'HTML' : 'Markdown'); console.log(' Content length:', post.content.length, 'characters\n'); // Parse content into sections const sections = parseContentIntoSections(post.content); console.log(`āœ… Generated ${sections.length} card sections:\n`); sections.forEach(section => { console.log(` ${section.number}. ${section.title}`); console.log(` Slug: ${section.slug}`); console.log(` Reading time: ${section.readingTime} min`); console.log(` Level: ${section.technicalLevel}`); console.log(` Category: ${section.category}`); console.log(` Excerpt: ${section.excerpt.substring(0, 80)}...`); console.log(''); }); // Update the blog post with sections await collection.updateOne( { slug: SLUG }, { $set: { sections } } ); console.log('āœ… Blog post updated with card sections'); console.log(' Total sections:', sections.length); console.log(' Total reading time:', sections.reduce((sum, s) => sum + s.readingTime, 0), 'minutes'); console.log(''); console.log('šŸ“ Preview at: http://localhost:9000/blog-post.html?slug=' + SLUG); } catch (error) { console.error('āŒ Error generating blog card sections:', error); throw error; } finally { await close(); } } // Run if called directly if (require.main === module) { generateBlogCardSections() .then(() => { console.log('\n✨ Card section generation complete'); process.exit(0); }) .catch(error => { console.error('\nšŸ’„ Card section generation failed:', error); process.exit(1); }); } module.exports = { generateBlogCardSections };