/**
* 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 };