// 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)}

`; } renderSection(block) { return `
${block.heading ? `

${this.decodeText(block.heading)}

` : ''}

${this.renderMarkdown(block.content)}

`; } renderQuote(block) { return `

${this.decodeText(block.text)}

${block.author ? `— ${this.decodeText(block.author)}` : ''}
`; } renderList(block) { const listClass = block.style === 'checklist' ? 'checklist' : 'bullet-list'; const icon = block.style === 'checklist' ? '' : ''; const items = block.items.map(item => `
  • ${icon} ${this.renderMarkdown(item)}
  • `).join(''); return `
    ${block.title ? `

    ${this.decodeText(block.title)}

    ` : ''}
    `; } renderImage(block) { return `
    ${block.alt || ''} ${block.caption ? `
    ${this.decodeText(block.caption)}
    ` : ''}
    `; } renderCallout(block) { const styles = { info: 'border-blue-500/50 bg-blue-500/10', warning: 'border-orange-500/50 bg-orange-500/10', success: 'border-neon/50 bg-neon/10', error: 'border-red-500/50 bg-red-500/10', cta: 'border-electric/50 bg-electric/10' }; const icons = { info: '', warning: '', success: '', error: '', cta: '' }; const styleClass = styles[block.style] || styles.info; const icon = icons[block.style] || icons.info; return `
    ${icon}
    ${block.title ? `

    ${this.decodeText(block.title)}

    ` : ''}
    ${this.renderMarkdown(block.content)}
    ${block.link ? ` ${this.decodeText(block.linkText || 'Mehr erfahren')} ` : ''}
    `; } renderCode(block) { return `
    ${this.escapeHtml(block.content)}
    `; } renderVideo(block) { // Support for YouTube and Vimeo embeds let embedHtml = ''; if (block.platform === 'youtube') { embedHtml = ``; } else if (block.platform === 'vimeo') { embedHtml = ``; } return `
    ${embedHtml} ${block.caption ? `

    ${block.caption}

    ` : ''}
    `; } // Helper: Escape HTML escapeHtml(text) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.replace(/[&<>"']/g, m => map[m]); } // Calculate reading time calculateReadingTime(content) { const wordsPerMinute = 200; let wordCount = 0; content.forEach(block => { if (block.text) wordCount += block.text.split(/\s+/).length; if (block.content) wordCount += block.content.split(/\s+/).length; if (block.items) { block.items.forEach(item => { wordCount += item.split(/\s+/).length; }); } }); return Math.ceil(wordCount / wordsPerMinute); } // Format date formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString('de-DE', { year: 'numeric', month: 'long', day: 'numeric' }); } // Generate Schema.org JSON-LD generateSchemaMarkup(post) { const schema = post.schema || {}; return ` `; } } // Export for use in other scripts if (typeof module !== 'undefined' && module.exports) { module.exports = BlogEngine; }