// Blog Post Viewer (function() { 'use strict'; function getPostId() { const params = new URLSearchParams(window.location.search); return params.get('id'); } async function fetchPost(postId) { try { const response = await fetch(`/api/blog/post/${postId}`, { method: 'GET', headers: { 'Accept': 'application/json' } }); if (!response.ok) { console.warn(`Post API request failed: ${response.status}`); return null; } const post = await response.json(); console.log(`✓ Fetched post: ${post.name}`); return post; } catch (error) { console.error('Post API fetch error:', error.message); return null; } } /** * Format date to readable string */ function formatDate(dateString) { const date = new Date(dateString); const options = { year: 'numeric', month: 'long', day: 'numeric' }; return date.toLocaleDateString('en-US', options); } function parseMarkdown(markdown) { if (!markdown) return '

No content available.

'; if (typeof marked !== 'undefined' && typeof hljs !== 'undefined') { marked.setOptions({ highlight: function(code, lang) { if (lang && hljs.getLanguage(lang)) { return hljs.highlight(code, { language: lang }).value; } return hljs.highlightAuto(code).value; }, breaks: true, gfm: true }); return marked.parse(markdown); } let html = markdown .replace(/```(\w+)?\n([\s\S]*?)```/g, '
$2
') .replace(/^#### (.*$)/gim, '

$1

') .replace(/^### (.*$)/gim, '

$1

') .replace(/^## (.*$)/gim, '

$1

') .replace(/^# (.*$)/gim, '

$1

') .replace(/^---$/gm, '
') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/`([^`]+)`/g, '$1') .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1') .replace(/^\* (.+)$/gm, '
  • $1
  • ') .replace(/(
  • .*<\/li>)/s, '') .replace(/^\d+\. (.+)$/gm, '
  • $1
  • ') .replace(/^> (.+)$/gm, '
    $1
    ') .split('\n\n') .map(para => { para = para.trim(); if (para.match(/^<(h[1-6]|ul|ol|pre|blockquote|hr)/)) { return para; } return para ? `

    ${para}

    ` : ''; }) .join('\n'); return html; } /** * Render post to DOM */ function renderPost(post) { const loading = document.getElementById('post-loading'); const content = document.getElementById('post-content'); const error = document.getElementById('post-error'); if (!post) { loading.style.display = 'none'; error.style.display = 'block'; return; } loading.style.display = 'none'; content.style.display = 'block'; // Update page title document.getElementById('page-title').textContent = `${post.name} - pyserve`; document.getElementById('breadcrumb-title').textContent = post.name; // Update meta const date = formatDate(post.published_at || post.created_at); document.getElementById('post-date').textContent = date; document.getElementById('post-tag').textContent = post.tag_name || 'post'; // Update title document.getElementById('post-title').textContent = post.name; // Parse and render body const bodyHtml = parseMarkdown(post.body); document.getElementById('post-body').innerHTML = bodyHtml; // Update author info if (post.author) { const authorSection = document.getElementById('post-author-full'); const avatarImg = document.getElementById('author-avatar'); const authorName = document.getElementById('author-name'); const authorLogin = document.getElementById('author-login'); if (avatarImg) { avatarImg.src = post.author.avatar_url || 'https://via.placeholder.com/80'; avatarImg.alt = post.author.full_name || post.author.login; } if (authorName) authorName.textContent = post.author.full_name || post.author.login; if (authorLogin) authorLogin.textContent = `@${post.author.login}`; authorSection.style.display = 'flex'; } } /** * Show error state */ function showError() { const loading = document.getElementById('post-loading'); const error = document.getElementById('post-error'); loading.style.display = 'none'; error.style.display = 'block'; } /** * Main initialization */ async function init() { const postId = getPostId(); if (!postId) { showError(); return; } const post = await fetchPost(postId); if (post) { renderPost(post); } else { showError(); } } // Start when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();