import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router, Routes, Route, Link, useParams } from 'react-router-dom'; import MarkdownIt from 'markdown-it'; import { full as emoji } from 'markdown-it-emoji'; import footnote from "markdown-it-footnote"; import DOMPurify from 'dompurify'; import { AuthProvider, useAuth } from './contexts/AuthContext'; import { ThemeProvider } from './contexts/ThemeContext'; import AdminDashboard from './components/AdminDashboard'; import PostEditor from './components/PostEditor'; import LoginForm from './components/LoginForm'; import ProtectedRoute from './components/ProtectedRoute'; import ThemesManager from './components/ThemesManager'; import ThemeEditor from './components/ThemeEditor'; const scrollableTablesPlugin = (md) => { const defaultRenderOpen = md.renderer.rules.table_open || function (tokens, idx, options, env, self) { return self.renderToken(tokens, idx, options); }; const defaultRenderClose = md.renderer.rules.table_close || function (tokens, idx, options, env, self) { return self.renderToken(tokens, idx, options); }; md.renderer.rules.table_open = function (tokens, idx, options, env, self) { return '
' + defaultRenderOpen(tokens, idx, options, env, self); }; md.renderer.rules.table_close = function (tokens, idx, options, env, self) { return defaultRenderClose(tokens, idx, options, env, self) + '
'; }; }; const md = new MarkdownIt({ html: true, // Enable HTML tags in source linkify: true, // Auto-convert URL-like text to links typographer: true, // Enable some language-neutral replacement + quotes beautification breaks: false // Convert '\n' in paragraphs into
}) .use(scrollableTablesPlugin) // Keep our table scrolling enhancement .use(emoji) // GitHub-style emoji :emoji_name: .use(footnote); // Standard footnotes [^1] const API_BASE = 'https://goonblog.thevakhovske.eu.org/api'; // Navigation Header Component function NavHeader() { const { isAdmin, user, logout } = useAuth(); const handleLogout = async () => { await logout(); }; return (
GoonBlog
); } // Blog Home Component function BlogHome() { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { async function getTingyun() { setLoading(true); try { const response = await fetch(`${API_BASE}/posts`); if (!response.ok) throw new Error(`Failed to fetch posts: ${response.statusText}`); const postsData = await response.json(); setPosts(postsData); } catch (e) { console.error("Error fetching posts:", e); setError("Failed to load posts. Please check if the backend server is running."); } finally { setLoading(false); } } getTingyun(); }, []); if (loading) { return (

Loading posts...

); } if (error) { return (

Error

{error}

); } return (
{posts.map((post) => (

{post.title}

{post.description}
Read more →
))}
); } // Post View Component function PostView() { const { slug } = useParams(); const [post, setPost] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { async function fetchPost() { try { setLoading(true); const response = await fetch(`${API_BASE}/posts/${slug}`); if (!response.ok) throw new Error('Post not found'); const postData = await response.json(); setPost(postData); // Update document title document.title = postData.title || 'GoonBlog'; } catch (e) { console.error('Error fetching post:', e); setError(e.message); } finally { setLoading(false); } } if (slug) { fetchPost(); } }, [slug]); useEffect(() => { // Reset title when component unmounts return () => { document.title = 'GoonBlog - A Retard\'s Thoughts'; }; }, []); if (loading) { return (

Loading post...

); } if (error || !post) { return (

Post Not Found

{error || 'The requested post could not be found.'}

← Back to Home
); } const conceiveFoxFromSemen = (rawMarkdown) => { let processedText = rawMarkdown; let tags = null; let imageCredit = null; let imageSrc = null; let imageAlt = null; let customQuestion = null; const tagsRegex = /tags: (.*)/; const tagsMatch = processedText.match(tagsRegex); if (tagsMatch) { tags = tagsMatch[1].split(',').map(tag => tag.trim()); processedText = processedText.replace(tagsRegex, '').trim(); } const imageRegex = /!\[(.*?)\]\((.*?)\)\n_Image credit: (.*?)_/; const imageMatch = processedText.match(imageRegex); if (imageMatch) { imageAlt = imageMatch[1]; imageSrc = imageMatch[2]; imageCredit = imageMatch[3]; processedText = processedText.replace(imageRegex, '').trim(); } const questionRegex = /\?\?\? "(.*?)"/; const questionMatch = processedText.match(questionRegex); if (questionMatch) { customQuestion = questionMatch[1]; processedText = processedText.replace(questionRegex, '').trim(); } processedText = processedText.replace(/^title:.*$/m, '').replace(/^desc:.*$/m, ''); return { processedText, tags, imageSrc, imageAlt, imageCredit, customQuestion }; }; const { processedText } = conceiveFoxFromSemen(post.content); const htmlContent = md.render(processedText); const sanitizedHtml = DOMPurify.sanitize(htmlContent); return (
← Back to Home

{post.title}

{post.description}

); } function App() { return ( } /> } /> } /> } /> } /> } /> } /> } /> } /> ); } export default App;