fuck my butthole pls

This commit is contained in:
rattatwinko
2025-06-16 20:10:14 +02:00
parent 500695d471
commit 1303078c2e
4 changed files with 163 additions and 40 deletions

View File

@@ -11,6 +11,7 @@ interface Post {
tags: string[];
summary: string;
content: string;
pinned: boolean;
}
interface Folder {
@@ -28,6 +29,7 @@ interface Post {
tags: string[];
summary: string;
content: string;
pinned: boolean;
}
type Node = Post | Folder;
@@ -52,6 +54,13 @@ export default function AdminPage() {
item: null,
});
const [showManageContent, setShowManageContent] = useState(false);
const [managePath, setManagePath] = useState<string[]>([]);
const [pinned, setPinned] = useState<string[]>(() => {
if (typeof window !== 'undefined') {
return JSON.parse(localStorage.getItem('pinnedPosts') || '[]');
}
return [];
});
const router = useRouter();
useEffect(() => {
@@ -63,6 +72,10 @@ export default function AdminPage() {
}
}, []);
useEffect(() => {
localStorage.setItem('pinnedPosts', JSON.stringify(pinned));
}, [pinned]);
const loadContent = async () => {
try {
const response = await fetch('/api/posts');
@@ -180,6 +193,23 @@ export default function AdminPage() {
})),
];
// Get nodes for manage content
const getManageNodes = (): Node[] => {
let currentNodes: Node[] = nodes;
for (const segment of managePath) {
const folder = currentNodes.find(
(n) => n.type === 'folder' && n.name === segment
) as Folder | undefined;
if (folder) {
currentNodes = folder.children;
} else {
break;
}
}
return currentNodes;
};
const manageNodes = getManageNodes();
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault();
setIsDragging(true);
@@ -270,6 +300,12 @@ export default function AdminPage() {
}
};
const handlePin = (slug: string) => {
setPinned((prev) =>
prev.includes(slug) ? prev.filter((s) => s !== slug) : [slug, ...prev]
);
};
return (
<div className="min-h-screen bg-gray-100 p-8">
{!isAuthenticated ? (
@@ -483,11 +519,16 @@ export default function AdminPage() {
</div>
))}
{/* Posts */}
{currentNodes
.filter((node): node is Post => node.type === 'post')
.map((post) => (
<div key={post.slug} className="border rounded-lg p-4">
{/* Posts: pinned first, then unpinned */}
{(() => {
const posts = currentNodes.filter((node): node is Post => node.type === 'post');
const pinnedPosts = posts.filter(post => post.pinned);
const unpinnedPosts = posts.filter(post => !post.pinned);
return [...pinnedPosts, ...unpinnedPosts].map((post) => (
<div key={post.slug} className="border rounded-lg p-4 relative">
{post.pinned && (
<span title="Pinned" className="absolute top-2 right-2 text-2xl">📌</span>
)}
<h3 className="text-xl font-semibold">{post.title}</h3>
<p className="text-gray-600">{post.date}</p>
<p className="text-sm text-gray-500">{post.summary}</p>
@@ -499,7 +540,8 @@ export default function AdminPage() {
))}
</div>
</div>
))}
));
})()}
</div>
</div>
@@ -516,7 +558,62 @@ export default function AdminPage() {
<p className="text-gray-600 mb-2">
Delete posts and folders, manage your content structure
</p>
<a href="/admin/manage" className="text-blue-600 hover:underline">Go to Content Manager</a>
{/* Folder navigation breadcrumbs */}
<div className="flex flex-wrap justify-center gap-2 mb-4">
<button
onClick={() => setManagePath([])}
className={`px-2 py-1 rounded ${managePath.length === 0 ? 'bg-blue-100 text-blue-800' : 'hover:bg-gray-200'}`}
>
Root
</button>
{managePath.map((name, idx) => (
<button
key={idx}
onClick={() => setManagePath(managePath.slice(0, idx + 1))}
className={`px-2 py-1 rounded ${idx === managePath.length - 1 ? 'bg-blue-100 text-blue-800' : 'hover:bg-gray-200'}`}
>
{name}
</button>
))}
</div>
{/* Folders */}
<div className="space-y-2 mb-4">
{manageNodes.filter((n) => n.type === 'folder').map((folder: any) => (
<div
key={folder.path}
className="border rounded-lg p-3 cursor-pointer hover:bg-gray-50 flex items-center gap-2 justify-center"
onClick={() => setManagePath([...managePath, folder.name])}
>
<span className="text-2xl">📁</span>
<span className="font-semibold text-lg">{folder.name}</span>
</div>
))}
</div>
{/* Posts (pinned first) */}
<div className="space-y-2">
{[...manageNodes.filter((n) => n.type === 'post' && pinned.includes(n.slug)),
...manageNodes.filter((n) => n.type === 'post' && !pinned.includes(n.slug))
].map((post: any) => (
<div
key={post.slug}
className={`border rounded-lg p-3 flex items-center gap-3 justify-between ${pinned.includes(post.slug) ? 'bg-yellow-100 border-yellow-400' : ''}`}
>
<div className="flex-1 text-left">
<div className="font-semibold">{post.title}</div>
<div className="text-xs text-gray-500">{post.date}</div>
<div className="text-xs text-gray-400">{post.summary}</div>
</div>
<button
onClick={() => handlePin(post.slug)}
className={`text-2xl focus:outline-none ${pinned.includes(post.slug) ? 'text-yellow-500' : 'text-gray-400 hover:text-yellow-500'}`}
title={pinned.includes(post.slug) ? 'Unpin' : 'Pin'}
>
{pinned.includes(post.slug) ? '★' : '☆'}
</button>
</div>
))}
</div>
<a href="/admin/manage" className="block mt-6 text-blue-600 hover:underline">Go to Content Manager</a>
</div>
)}
</div>

View File

@@ -7,6 +7,14 @@ import html from 'remark-html';
const postsDirectory = path.join(process.cwd(), 'posts');
const pinnedPath = path.join(postsDirectory, 'pinned.json');
let pinnedSlugs: string[] = [];
if (fs.existsSync(pinnedPath)) {
try {
pinnedSlugs = JSON.parse(fs.readFileSync(pinnedPath, 'utf8'));
} catch {}
}
// Function to get file creation date
function getFileCreationDate(filePath: string): Date {
const stats = fs.statSync(filePath);
@@ -27,6 +35,7 @@ async function getPostByPath(filePath: string, relPath: string) {
summary: data.summary,
content: processedContent.toString(),
createdAt: createdAt.toISOString(),
pinned: pinnedSlugs.includes(relPath.replace(/\.md$/, '')),
};
}

View File

@@ -27,13 +27,21 @@ export default function RootLayout({
</Head>
<body className={inter.className}>
<header className="bg-gray-100 p-4">
<div className="container mx-auto flex justify-between items-center">
<div className="flex gap-2">
<div className="container mx-auto flex flex-col md:flex-row justify-between items-center">
<div className="flex gap-2 justify-center md:justify-start w-full md:w-auto mb-2 md:mb-0">
<img src="https://img.shields.io/badge/markdown-%23000000.svg?style=for-the-badge&logo=markdown&logoColor=white" alt="Markdown" />
<img src="https://img.shields.io/badge/Next-black?style=for-the-badge&logo=next.js&logoColor=white" alt="Next.js" />
<img src="https://img.shields.io/badge/tailwindcss-%2338BDF8.svg?style=for-the-badge&logo=tailwind-css&logoColor=white" alt="Tailwind CSS" />
<img src="https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
</div>
<div className="flex gap-2 justify-center w-full md:w-auto mt-2 md:mt-0">
<a href="https://instagram.com/rattatwinko" target="_blank" rel="noopener noreferrer">
<img src="https://img.shields.io/badge/Instagram-%23E4405F.svg?style=for-the-badge&logo=Instagram&logoColor=white" alt="Instagram" />
</a>
<a href="https://github.com/ZockerKatze" target="_blank" rel="noopener noreferrer">
<img src="https://img.shields.io/badge/GitHub-%23121011.svg?style=for-the-badge&logo=GitHub&logoColor=white" alt="GitHub" />
</a>
</div>
<Link href="/admin" className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-full">
Admin Login
</Link>

View File

@@ -13,6 +13,7 @@ interface Post {
summary: string;
content: string;
createdAt: string;
pinned: boolean;
}
interface Folder {
@@ -100,38 +101,46 @@ export default function Home() {
</div>
))}
{/* Posts */}
{nodes.filter((n) => n.type === 'post').map((post: any) => (
<article key={post.slug} className="border rounded-lg p-6 hover:shadow-lg transition-shadow">
<Link href={`/posts/${post.slug}`}>
<h2 className="text-2xl font-semibold mb-2">{post.title}</h2>
<div className="text-gray-600 mb-4">
{post.date ? (
<div>Veröffentlicht: {format(new Date(post.date), 'd. MMMM yyyy')}</div>
) : (
<div className="flex flex-col items-center">
<div className="flex">
<span className="text-2xl animate-spin mr-2"></span>
<span className="text-2xl animate-spin-reverse"></span>
{(() => {
const posts = nodes.filter((n) => n.type === 'post');
const pinnedPosts = posts.filter((post: any) => post.pinned);
const unpinnedPosts = posts.filter((post: any) => !post.pinned);
return [...pinnedPosts, ...unpinnedPosts].map((post: any) => (
<article key={post.slug} className="border rounded-lg p-6 hover:shadow-lg transition-shadow relative">
{post.pinned && (
<span className="absolute top-4 right-4 text-2xl" title="Pinned">📌</span>
)}
<Link href={`/posts/${post.slug}`}>
<h2 className="text-2xl font-semibold mb-2">{post.title}</h2>
<div className="text-gray-600 mb-4">
{post.date ? (
<div>Veröffentlicht: {format(new Date(post.date), 'd. MMMM yyyy')}</div>
) : (
<div className="flex flex-col items-center">
<div className="flex">
<span className="text-2xl animate-spin mr-2"></span>
<span className="text-2xl animate-spin-reverse"></span>
</div>
<div className="text-xl font-bold mt-2">In Bearbeitung</div>
</div>
<div className="text-xl font-bold mt-2">In Bearbeitung</div>
</div>
)}
<div>Erstellt: {format(new Date(post.createdAt), 'd. MMMM yyyy HH:mm')}</div>
</div>
<p className="text-gray-700 mb-4">{post.summary}</p>
<div className="flex gap-2">
{post.tags.map((tag: string) => (
<span
key={tag}
className="bg-gray-100 text-gray-800 px-3 py-1 rounded-full text-sm"
>
{tag}
</span>
))}
</div>
</Link>
</article>
))}
)}
<div>Erstellt: {format(new Date(post.createdAt), 'd. MMMM yyyy HH:mm')}</div>
</div>
<p className="text-gray-700 mb-4">{post.summary}</p>
<div className="flex gap-2">
{post.tags.map((tag: string) => (
<span
key={tag}
className="bg-gray-100 text-gray-800 px-3 py-1 rounded-full text-sm"
>
{tag}
</span>
))}
</div>
</Link>
</article>
));
})()}
</div>
</main>
);