import { useEffect, useMemo, useState } from 'react' import { useLocale } from '@/lib/useLocale' import PostOriginalCreatedTimeField from '@/components/PostOriginalCreatedTimeField' import FieldError from '@/components/common/FieldError' import PostDurationField from '@/components/posts/PostDurationField' import PostTagsField from '@/components/posts/PostTagsField' import PostTextField from '@/components/posts/PostTextField' import { Button } from '@/components/ui/button' import { toast } from '@/components/ui/use-toast' import { isApiError } from '@/lib/api' import useDialogue from '@/lib/dialogues/useDialogue' import { updatePost } from '@/lib/posts' import { msToTime } from '@/lib/utils' import { useValidationErrors } from '@/lib/useValidationErrors' import { useUITranslation } from '@/lib/useUITranslation' import type { FC, FormEvent } from 'react' import type { Post, TagWithSections } from '@/types' type PostFormField = 'parentPostIds' | 'tags' | 'videoMs' | 'originalCreatedAt' | 'originalCreatedFrom' | 'originalCreatedBefore' const groupedMessages = (...values: (string[] | undefined)[]): string[] => [...new Set (values.flatMap (value => value ?? []))] const videoMsToDurationValue = (videoMs: number | null): string => videoMs == null ? '' : String (videoMs / 1_000) const tagsToStr = (tags: TagWithSections[]): string => { const result: Omit[] = [] const walk = (tag: TagWithSections) => { 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 }${ t.sections .map (s => `[${ msToTime (s.beginMs) }-${ s.endMs == null ? '' : msToTime (s.endMs) }]`) .join ('') }`)))].join (' ') } type Props = { post: Post onSave: (newPost: Post) => void } const PostEditForm: FC = ({ post, onSave }) => { const locale = useLocale () const { label } = useUITranslation ('posts') const [disabled, setDisabled] = useState (false) const [duration, setDuration] = useState (videoMsToDurationValue (post.videoMs)) 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 videoFlg = useMemo (() => tags.split (/\s+/).some (tag => tag.replace (/\[.*\]$/, '') === '動画'), [tags]) const dialogue = useDialogue () const update = async ( postData: Parameters[0], options: Parameters[1], ) => { clearValidationErrors () try { const data = await updatePost (postData, options, locale) onSave ({ ...post, versionNo: data.versionNo, title: data.title, videoMs: data.videoMs, tags: data.tags, parentPosts: data.parentPosts, childPosts: data.childPosts, siblingPosts: data.siblingPosts, originalCreatedFrom: data.originalCreatedFrom, originalCreatedBefore: data.originalCreatedBefore } as Post) toast ({ description: label ('shared:messages.updated') }) } catch (e) { const response = isApiError<{ mergeable?: boolean }> (e) ? e.response : undefined if (response?.status !== 409) { if (applyValidationError (e)) { toast ({ description: label ('edit.validationFailed') }) return } toast ({ title: label ('shared:messages.failed'), description: label ('shared:messages.checkInput') }) return } const action = await dialogue.choice ({ title: label ('edit.conflictTitle'), description: (

{label ('edit.conflictDescription')}

{label ('edit.conflictQuestion')}

), cancelText: label ('shared:actions.cancel'), closeLabel: label ('settings:actions.close'), choices: [...(response?.data?.mergeable ? [{ value: 'merge', label: label ('edit.merge') }] : []), { value: 'overwrite', label: label ('edit.overwrite'), variant: 'danger' }] }) if (action === 'merge') { // TODO: 差分 UI await update ({ id: post.id, title, tags, parentPostIds, duration: videoFlg ? duration : null, originalCreatedFrom, originalCreatedBefore }, { baseVersionNo: post.versionNo, merge: true }) return } if (action === 'overwrite') { await update ({ id: post.id, title, tags, parentPostIds, duration: videoFlg ? duration : null, 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, duration: videoFlg ? duration : null, originalCreatedFrom, originalCreatedBefore }, { baseVersionNo: post.versionNo }) } finally { setDisabled (false) } } useEffect (() => { setTags (tagsToStr (post.tags)) setDuration (videoMsToDurationValue (post.videoMs)) }, [post]) return (
{/* オリジナルの作成日時 */} {videoFlg && ( )} {/* 送信 */} ) } export default PostEditForm