import { useEffect, useState } from 'react' import PostFormTagsArea from '@/components/PostFormTagsArea' import PostOriginalCreatedTimeField from '@/components/PostOriginalCreatedTimeField' import FieldError from '@/components/common/FieldError' import FormField from '@/components/common/FormField' import { useDialogue } from '@/components/dialogues/DialogueProvider' import { Button } from '@/components/ui/button' import { toast } from '@/components/ui/use-toast' import { isApiError } from '@/lib/api' import { updatePost } from '@/lib/posts' import { inputClass } from '@/lib/utils' import { useValidationErrors } from '@/lib/useValidationErrors' import type { FC, FormEvent } from 'react' import type { Post, Tag } from '@/types' type PostFormField = 'parentPostIds' | 'tags' | 'originalCreatedAt' const tagsToStr = (tags: Tag[]): string => { const result: Tag[] = [] const walk = (tag: Tag) => { const { children, ...rest } = tag result.push (rest) children?.forEach (walk) } tags.filter (t => t.category !== 'nico').forEach (walk) return [...(new Set (result.map (t => t.name)))].join (' ') } type Props = { post: Post onSave: (newPost: Post) => void } const PostEditForm: FC = ({ post, onSave }) => { const [disabled, setDisabled] = useState (false) const { baseErrors, fieldErrors, clearValidationErrors, applyValidationError } = useValidationErrors () const [originalCreatedBefore, setOriginalCreatedBefore] = useState (post.originalCreatedBefore) const [originalCreatedFrom, setOriginalCreatedFrom] = useState (post.originalCreatedFrom) const [parentPostIds, setParentPostIds] = useState ((post.parentPosts ?? []).map (p => p.id).join (' ')) const [tags, setTags] = useState ('') const [title, setTitle] = useState (post.title) const dialogue = useDialogue () const update = async (...args: Parameters) => { clearValidationErrors () try { const data = await updatePost (...args) onSave ({ ...post, versionNo: data.versionNo, title: data.title, tags: data.tags, parentPosts: data.parentPosts, childPosts: data.childPosts, siblingPosts: data.siblingPosts, originalCreatedFrom: data.originalCreatedFrom, originalCreatedBefore: data.originalCreatedBefore } as Post) toast ({ description: '更新しました.' }) } catch (e) { const response = isApiError<{ mergeable?: boolean }> (e) ? e.response : undefined if (response?.status !== 409) { if (applyValidationError (e)) { toast ({ description: '更新はできなかったよ……' }) return } toast ({ title: '失敗……', description: '入力を確認してください.' }) return } const action = await dialogue.choice ({ title: '競合が発生しました.', description: (

ほかの耕作員が先に更新してゐます.

現在の変更をどう扱ひますか?

), choices: [...(response?.data?.mergeable ? [{ value: 'merge', label: '差分をマージ' }] : []), { value: 'overwrite', label: '強制上書き', variant: 'danger' }] }) if (action === 'merge') { // TODO: 差分 UI await update ({ id: post.id, title, tags, parentPostIds, originalCreatedFrom, originalCreatedBefore }, { baseVersionNo: post.versionNo, merge: true }) return } if (action === 'overwrite') { await update ({ id: post.id, title, tags, parentPostIds, originalCreatedFrom, originalCreatedBefore }, { baseVersionNo: post.versionNo, force: true }) return } } } const handleSubmit = async (e: FormEvent) => { e.preventDefault () setDisabled (true) try { await update ({ id: post.id, title, tags, parentPostIds, originalCreatedFrom, originalCreatedBefore }, { baseVersionNo: post.versionNo }) } finally { setDisabled (false) } } useEffect (() => { setTags(tagsToStr (post.tags)) }, [post]) return (
{/* タイトル */} {({ invalid }) => ( setTitle (ev.target.value)}/>)} {/* 親投稿 */} {({ describedBy, invalid }) => ( setParentPostIds (e.target.value)} aria-describedby={describedBy} aria-invalid={invalid} className={inputClass (invalid)}/>)} {/* タグ */} {/* オリジナルの作成日時 */} {/* 送信 */} ) } export default PostEditForm