// Blog System - Powered by Gitea Releases (function() { 'use strict'; const API_URL = '/api/blog/posts'; const CACHE_KEY = 'pyserve_blog_cache'; const CACHE_DURATION = 1800000; async function fetchBlogPosts() { try { const response = await fetch(API_URL, { method: 'GET', headers: { 'Accept': 'application/json' } }); if (!response.ok) { console.warn(`Blog API request failed: ${response.status}`); return null; } const posts = await response.json(); console.log(`✓ Fetched ${posts.length} blog posts`); return posts; } catch (error) { console.error('Blog API fetch error:', error.message); return null; } } function getCachedPosts() { try { const cached = localStorage.getItem(CACHE_KEY); if (!cached) return null; const data = JSON.parse(cached); const now = Date.now(); if (now - data.timestamp < CACHE_DURATION) { return data.posts; } localStorage.removeItem(CACHE_KEY); return null; } catch (error) { console.error('Cache read error:', error); return null; } } function cachePosts(posts) { try { const data = { posts: posts, timestamp: Date.now() }; localStorage.setItem(CACHE_KEY, JSON.stringify(data)); } catch (error) { console.error('Cache write error:', error); } } 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 ''; 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(/^### (.*$)/gim, '
$2')
.replace(/`([^`]+)`/g, '$1')
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1')
.replace(/\n\n/g, '')
.replace(/\n/g, '
');
return '
' + html + '
'; } function extractExcerpt(markdown, maxLength = 200) { if (!markdown) return 'No description available.'; // Remove markdown formatting and get first paragraph const plain = markdown .replace(/^#+\s+.*/gm, '') // Remove headers .replace(/\*\*(.+?)\*\*/g, '$1') // Remove bold .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Remove links .replace(/```[\s\S]*?```/g, '') // Remove code blocks .replace(/`([^`]+)`/g, '$1') // Remove inline code .trim(); const firstParagraph = plain.split('\n\n')[0] || plain.split('\n')[0]; if (firstParagraph.length > maxLength) { return firstParagraph.substring(0, maxLength) + '...'; } return firstParagraph || 'No description available.'; } function createPostCard(post) { const excerpt = extractExcerpt(post.body); const date = formatDate(post.published_at || post.created_at); const tag = post.tag_name || 'post'; const author = post.author || {}; const authorName = author.full_name || author.login || 'Unknown'; const authorLogin = author.login || ''; const authorAvatar = author.avatar_url || ''; return `Check back soon for updates and articles!