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" + /> +