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]
import { API_BASE } from "./config";
// 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(() => {
if (post) {
const setMeta = (name, content) => {
let element = document.querySelector(`meta[name='${name}']`);
if (!element) {
element = document.createElement("meta");
element.setAttribute("name", name);
document.head.appendChild(element);
}
element.setAttribute("content", content);
};
setMeta("og:title", post.title);
setMeta("og:description", post.description);
setMeta("og:type", "article");
setMeta("og:url", window.location.href);
}
return () => {
const metaTags = [
"og:title",
"og:description",
"og:type",
"og:url",
"og:image",
];
metaTags.forEach((name) => {
const element = document.querySelector(`meta[name='${name}']`);
if (element) {
element.remove();
}
});
};
}, [post]);
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;