Enhance Admin page with post editing functionality; implement PUT API for saving post edits, including frontmatter parsing and error handling. Update welcome post metadata and improve UI for editing posts.
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
---
|
||||
title: "Read Me . Markdown!"
|
||||
date: "2025-06-17"
|
||||
tags: ["welcome", "introduction"]
|
||||
summary: "Read Me Please"
|
||||
title: Read Me . Markdown!
|
||||
date: '2025-06-19'
|
||||
tags:
|
||||
- welcome
|
||||
- introduction
|
||||
summary: Read Me Please
|
||||
author: Rattatwinko's
|
||||
---
|
||||
|
||||
# Welcome to the Blog
|
||||
@@ -97,9 +100,7 @@ You can pin a post both in the UI and in the backend of the server.
|
||||
|
||||
| Status | Task |
|
||||
|:---------------------------------------------:|:-------------------------------------------------:|
|
||||
|<span style="color:red;">NOT DONE!</span> | GitHub's Caution/Error Stuff |
|
||||
|<span style="color:orange;">IN WORK</span> | Docker Building ; broke during recent update |
|
||||
|<span style="color:pink;">Maybe Future</span> | Easy deployment of this shit |
|
||||
|<span style="color:green;text-align:center;">DONE</span>|Code Editor in Admin Panel with saving!
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { marked } from 'marked';
|
||||
import hljs from 'highlight.js';
|
||||
import matter from 'gray-matter';
|
||||
|
||||
interface Post {
|
||||
slug: string;
|
||||
@@ -70,6 +71,7 @@ export default function AdminPage() {
|
||||
const [changePwConfirm, setChangePwConfirm] = useState('');
|
||||
const [changePwFeedback, setChangePwFeedback] = useState<string | null>(null);
|
||||
const [previewHtml, setPreviewHtml] = useState('');
|
||||
const [editingPost, setEditingPost] = useState<{ slug: string, path: string } | null>(null);
|
||||
const router = useRouter();
|
||||
const usernameRef = useRef<HTMLInputElement>(null);
|
||||
const passwordRef = useRef<HTMLInputElement>(null);
|
||||
@@ -416,6 +418,62 @@ export default function AdminPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Function to load a post's raw markdown
|
||||
const loadPostRaw = async (slug: string, folderPath: string) => {
|
||||
const params = new URLSearchParams({ slug, path: folderPath });
|
||||
const res = await fetch(`/api/admin/posts/raw?${params.toString()}`);
|
||||
if (!res.ok) {
|
||||
alert('Error loading post');
|
||||
return;
|
||||
}
|
||||
const text = await res.text();
|
||||
const parsed = matter(text);
|
||||
setNewPost({
|
||||
title: parsed.data.title || '',
|
||||
date: parsed.data.date || new Date().toISOString().split('T')[0],
|
||||
tags: Array.isArray(parsed.data.tags) ? parsed.data.tags.join(', ') : (parsed.data.tags || ''),
|
||||
summary: parsed.data.summary || '',
|
||||
content: parsed.content || '',
|
||||
});
|
||||
setEditingPost({ slug, path: folderPath });
|
||||
};
|
||||
|
||||
// Function to save edits
|
||||
const handleEditPost = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!editingPost) return;
|
||||
try {
|
||||
// Always update date to today if changed
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const newDate = newPost.date !== today ? today : newPost.date;
|
||||
const newFrontmatter = matter.stringify(newPost.content, {
|
||||
title: newPost.title,
|
||||
date: newDate,
|
||||
tags: newPost.tags.split(',').map(tag => tag.trim()),
|
||||
summary: newPost.summary,
|
||||
author: process.env.NEXT_PUBLIC_BLOG_OWNER + "'s" || 'Anonymous',
|
||||
});
|
||||
const response = await fetch('/api/admin/posts', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
slug: editingPost.slug,
|
||||
path: editingPost.path,
|
||||
content: newFrontmatter,
|
||||
}),
|
||||
});
|
||||
if (response.ok) {
|
||||
setEditingPost(null);
|
||||
setNewPost({ title: '', date: today, tags: '', summary: '', content: '' });
|
||||
loadContent();
|
||||
} else {
|
||||
alert('Error saving post');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error saving post');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 p-8">
|
||||
{pinFeedback && (
|
||||
@@ -633,7 +691,7 @@ export default function AdminPage() {
|
||||
{/* Create Post Form */}
|
||||
<div className="bg-white rounded-lg shadow p-6 mb-8">
|
||||
<h2 className="text-2xl font-bold mb-4">Create New Post</h2>
|
||||
<form onSubmit={handleCreatePost} className="space-y-4">
|
||||
<form onSubmit={editingPost ? handleEditPost : handleCreatePost} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Title</label>
|
||||
<input
|
||||
@@ -696,7 +754,7 @@ export default function AdminPage() {
|
||||
type="submit"
|
||||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
Create Post
|
||||
{editingPost ? 'Save Changes' : 'Create Post'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -727,11 +785,19 @@ export default function AdminPage() {
|
||||
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">
|
||||
<div key={post.slug} className="border rounded-lg p-4 relative flex flex-col gap-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<h3 className="text-xl font-semibold flex-1">{post.title}</h3>
|
||||
<button
|
||||
onClick={() => loadPostRaw(post.slug, currentPath.join('/'))}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 text-lg font-bold shadow focus:outline-none focus:ring-2 focus:ring-blue-400"
|
||||
>
|
||||
✏️ Edit
|
||||
</button>
|
||||
{post.pinned && (
|
||||
<span title="Angeheftet" className="absolute top-2 right-2 text-2xl">📌</span>
|
||||
<span title="Angeheftet" className="text-2xl ml-2">📌</span>
|
||||
)}
|
||||
<h3 className="text-xl font-semibold">{post.title}</h3>
|
||||
</div>
|
||||
<p className="text-gray-600">{post.date}</p>
|
||||
<p className="text-sm text-gray-500">{post.summary}</p>
|
||||
<div className="mt-2 flex gap-2">
|
||||
|
||||
22
src/app/api/admin/posts/raw/route.ts
Normal file
22
src/app/api/admin/posts/raw/route.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const postsDirectory = path.join(process.cwd(), 'posts');
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const slug = searchParams.get('slug');
|
||||
const folderPath = searchParams.get('path') || '';
|
||||
if (!slug) {
|
||||
return NextResponse.json({ error: 'Missing slug' }, { status: 400 });
|
||||
}
|
||||
const filePath = folderPath && folderPath.trim() !== ''
|
||||
? path.join(postsDirectory, folderPath, `${slug}.md`)
|
||||
: path.join(postsDirectory, `${slug}.md`);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return NextResponse.json({ error: 'File does not exist' }, { status: 404 });
|
||||
}
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
return new NextResponse(content, { status: 200 });
|
||||
}
|
||||
@@ -64,3 +64,25 @@ export async function PATCH(request: Request) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { slug, path: folderPath, content } = body;
|
||||
if (!slug || typeof content !== 'string') {
|
||||
return NextResponse.json({ error: 'Missing slug or content' }, { status: 400 });
|
||||
}
|
||||
// Compute file path
|
||||
const filePath = folderPath && folderPath.trim() !== ''
|
||||
? path.join(postsDirectory, folderPath, `${slug}.md`)
|
||||
: path.join(postsDirectory, `${slug}.md`);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return NextResponse.json({ error: 'File does not exist' }, { status: 404 });
|
||||
}
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error editing post:', error);
|
||||
return NextResponse.json({ error: 'Error editing post' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user