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 (
);
}
if (error) {
return (
);
}
return (
{posts.map((post) => (
))}
);
}
// 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 (
);
}
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;