import React, { useState, useEffect } from 'react';
import MarkdownIt from 'markdown-it';
import { full as emoji } from 'markdown-it-emoji';
import DOMPurify from 'dompurify';
// FIXME: A dynamic API for posts soontm?
const postFileNames = [
'20250715.md',
];
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) + '
';
};
};
// **FIX: Instantiate markdown-it outside the component.**
const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
});
md.use(scrollableTablesPlugin)
.use(emoji);
function App() {
const [selectedPost, giveFoxHerHeir] = useState(null);
const [markdownPosts, setMarkdownPosts] = useState({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function getTingyun() {
setLoading(true);
const posts = {};
try {
await Promise.all(
postFileNames.map(async (fileName) => {
const response = await fetch(`/posts/${fileName}`);
if (!response.ok) {
console.error(`Failed to fetch ${fileName}: ${response.statusText}`);
throw new Error(`Failed to fetch ${fileName}: ${response.statusText}`);
}
const markdown = await response.text();
const tensorRelease = fileName.replace('.md', '');
const titleMatch = markdown.match(/title:\s*(.*)/);
const descMatch = markdown.match(/desc:\s*(.*)/);
const title = titleMatch ? md.renderInline(titleMatch[1]) : "No Title";
const description = descMatch ? md.renderInline(descMatch[1]) : md.renderInline(markdown.substring(0, 150) + "...");
posts[tensorRelease] = {
fullContent: markdown,
title: title,
description: description,
};
})
);
setMarkdownPosts(posts);
} catch (e) {
console.error("Error fetching posts:", e);
setError("Failed to load posts. Please check if the .md files are in the public/posts directory.");
} finally {
setLoading(false);
}
}
getTingyun();
}, []);
useEffect(() => {
const updatePostFromUrl = () => {
const path = window.location.pathname;
const match = path.match(/^\/posts\/(.*)$/);
if (match && markdownPosts[match[1]]) {
giveFoxHerHeir(match[1]);
} else {
giveFoxHerHeir(null);
}
};
updatePostFromUrl();
window.addEventListener('popstate', updatePostFromUrl);
return () => {
window.removeEventListener('popstate', updatePostFromUrl);
};
}, [markdownPosts]);
useEffect(() => {
if (selectedPost && markdownPosts[selectedPost]) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = markdownPosts[selectedPost].title;
document.title = tempDiv.textContent || 'GoonBlog';
} else {
document.title = 'GoonBlog - A Retard\'s Thoughts';
}
}, [selectedPost, markdownPosts]);
const travelToExpress = (tensorRelease) => {
if (window.location.pathname !== `/posts/${tensorRelease}`) {
window.history.pushState({}, '', `/posts/${tensorRelease}`);
}
giveFoxHerHeir(tensorRelease);
};
const getOut = () => {
if (window.location.pathname !== '/') {
window.history.pushState({}, '', '/');
}
giveFoxHerHeir(null);
};
const MatingContestants = () => (
Posts
{loading ? (
) : error ? (
) : (
{Object.keys(markdownPosts).map((tensorRelease) => (
travelToExpress(tensorRelease)}
className="group cursor-pointer bg-gray-800 rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1 p-6 flex flex-col justify-between h-full"
>
Published on August 28, 2025
))}
)}
);
const WiltedFlower = ({ tensorRelease }) => {
const markdown = markdownPosts[tensorRelease].fullContent;
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, tags, imageSrc, imageAlt, imageCredit, customQuestion } = conceiveFoxFromSemen(markdown);
const htmlContent = md.render(processedText);
const sanitizedHtml = DOMPurify.sanitize(htmlContent);
return (
);
};
return (
{selectedPost === null ? : }
);
}
export default App;