From f1487f509e1d1517d36c33d9614db257374f3b77 Mon Sep 17 00:00:00 2001 From: cc Date: Sat, 27 Jun 2026 16:52:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(blog):=20PostEditor=20=E5=86=99/=E6=94=B9?= =?UTF-8?q?=E6=96=87=E7=AB=A0=EF=BC=88=E5=90=AB=20Markdown=20=E9=A2=84?= =?UTF-8?q?=E8=A7=88=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- Client/src/components/PostEditor.tsx | 107 +++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 Client/src/components/PostEditor.tsx diff --git a/Client/src/components/PostEditor.tsx b/Client/src/components/PostEditor.tsx new file mode 100644 index 0000000..63f5ceb --- /dev/null +++ b/Client/src/components/PostEditor.tsx @@ -0,0 +1,107 @@ +import { useState } from 'react' +import { createPost, updatePost, type Post, type PostError } from '../api/posts' +import { Markdown } from './Markdown' + +interface PostEditorProps { + token: string + post?: Post + onSaved: () => void + onCancel: () => void +} + +const ERROR_TEXT: Record = { + full: '文章数已达上限(200 篇)', + 'too-large': '正文太长了(上限 64KB)', + 'bad-input': '标题不能为空,标签也别太长', + failed: '保存失败,稍后再试', +} + +export function PostEditor({ token, post, onSaved, onCancel }: PostEditorProps) { + const [title, setTitle] = useState(post?.title ?? '') + const [tag, setTag] = useState(post?.tag ?? '') + const [body, setBody] = useState(post?.body ?? '') + const [preview, setPreview] = useState(false) + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + + const save = async () => { + setSaving(true) + setError(null) + try { + const input = { title, tag, body } + if (post) await updatePost(token, post.id, input) + else await createPost(token, input) + onSaved() + } catch (e) { + const reason = (e as Error).message as PostError + setError(ERROR_TEXT[reason] ?? ERROR_TEXT.failed) + setSaving(false) + } + } + + return ( +
+
+

+ {post ? 'Edit post' : 'New post'} +

+ +
+ + {preview ? ( +
+

+ {title || '(无标题)'} +

+ {body || '_正文为空_'} +
+ ) : ( +
+ setTitle(e.target.value)} + placeholder="标题" + className="w-full rounded-lg border border-line bg-surface px-4 py-3 font-display text-2xl text-text outline-none focus:border-accent" + /> + setTag(e.target.value)} + placeholder="标签(如 Rust、随笔)" + className="w-full rounded-lg border border-line bg-surface px-4 py-2 text-sm text-text outline-none focus:border-accent" + /> +