// LLMO Ready - Blog Engine
// Renders blog posts from JSON data with full LLM optimization
class BlogEngine {
constructor() {
this.posts = [];
this.currentPost = null;
}
// Load all posts from JSON
async loadPosts() {
try {
const pathParts = window.location.pathname.split('/').filter(Boolean);
const maybeLocale = pathParts[0];
const availableLocales = ['en', 'de'];
const locale = availableLocales.includes(maybeLocale) ? maybeLocale : null;
let response = null;
if (locale) {
response = await fetch(`/${locale}/blog/data/posts.json`);
}
// Fallbacks (legacy)
if (!response || !response.ok) {
response = await fetch('data/posts.json');
}
if (!response.ok) {
console.log('[Blog] Trying absolute path...');
response = await fetch('/blog/data/posts.json');
}
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
this.posts = data.posts;
console.log('[Blog] Loaded', this.posts.length, 'posts');
return this.posts;
} catch (error) {
console.error('[Blog] Error loading posts:', error);
return [];
}
}
// Get post by slug
getPostBySlug(slug) {
return this.posts.find(post => post.slug === slug);
}
// Decode text (handles both real and escaped characters)
decodeText(text) {
if (!text) return '';
// Handle escaped newlines from JSON
text = text.replace(/\\n/g, '\n');
// Decode Unicode escapes (e.g., \u201e for „)
try {
text = text.replace(/\\u([0-9a-fA-F]{4})/g, (match, hex) => {
return String.fromCharCode(parseInt(hex, 16));
});
} catch (e) {
console.warn('[Blog] Error decoding unicode:', e);
}
return text;
}
// Simple markdown renderer
renderMarkdown(text) {
if (!text) return '';
// First decode any escaped characters
text = this.decodeText(text);
return text
// Bold
.replace(/\*\*(.*?)\*\*/g, '$1')
// Italic
.replace(/\*(.*?)\*/g, '$1')
// Links
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1')
// Line breaks
.replace(/ \n/g, '
')
.replace(/\n\n/g, '
')
.replace(/\n/g, '
');
}
// Render content blocks
renderBlock(block) {
switch (block.type) {
case 'intro':
return this.renderIntro(block);
case 'section':
return this.renderSection(block);
case 'quote':
return this.renderQuote(block);
case 'list':
return this.renderList(block);
case 'image':
return this.renderImage(block);
case 'callout':
return this.renderCallout(block);
case 'code':
return this.renderCode(block);
case 'video':
return this.renderVideo(block);
default:
console.warn('[Blog] Unknown block type:', block.type);
return '';
}
}
renderIntro(block) {
return `
${this.renderMarkdown(block.text)}
${this.renderMarkdown(block.content)}
`; } renderList(block) { const listClass = block.style === 'checklist' ? 'checklist' : 'bullet-list'; const icon = block.style === 'checklist' ? '' : '•'; const items = block.items.map(item => `${this.decodeText(block.text)}
${block.author ? `— ${this.decodeText(block.author)}` : ''}
${this.escapeHtml(block.content)}
${block.caption}
` : ''}