ファイル
btrc-hub/frontend/src/pages/wiki/WikiEditPage.tsx
T
2026-10-20 20:44:14 +09:00

144 行
4.6 KiB
TypeScript

import { useQuery, useQueryClient } from '@tanstack/react-query'
import MarkdownIt from 'markdown-it'
import { useEffect, useState } from 'react'
import MdEditor from 'react-markdown-editor-lite'
import { useParams } from 'react-router-dom'
import { useLocale, useLocaleNavigate } from '@/lib/useLocale'
import { useUITranslation } from '@/lib/useUITranslation'
import FieldError from '@/components/common/FieldError'
import FormField from '@/components/common/FormField'
import MainArea from '@/components/layout/MainArea'
import { toast } from '@/components/ui/use-toast'
import PageHead from '@/components/common/PageHead'
import { apiGet, apiPut } from '@/lib/api'
import { wikiKeys } from '@/lib/queryKeys'
import { canEditContent } from '@/lib/users'
import { inputClass } from '@/lib/utils'
import { useValidationErrors } from '@/lib/useValidationErrors'
import Forbidden from '@/pages/Forbidden'
import 'react-markdown-editor-lite/lib/index.css'
import type { FC } from 'react'
import type { User, WikiPage } from '@/types'
const mdParser = new MarkdownIt
const FIXED_WIKI_EDITOR_VIEW = { menu: true, md: true, html: true } as const
// `wiki_editor_mode` is still kept in backend settings, but the common
// settings screen does not expose it at present. Until that UI returns, the
// editor stays on the fixed split view here.
type Props = { user: User | null }
type WikiFormField = 'title' | 'body'
const WikiEditPage: FC<Props> = ({ user }) => {
const { message, label } = useUITranslation ('wiki')
const locale = useLocale ()
const editable = canEditContent (user)
const { id } = useParams ()
const navigate = useLocaleNavigate ()
const qc = useQueryClient ()
const source = `${locale}:${id}`
const { data: wiki, isPending: loading } = useQuery ({
queryKey: wikiKeys.show (id ?? '', { }, locale),
queryFn: () => apiGet<WikiPage> (`/wiki/${ encodeURIComponent (id ?? '') }`, { locale }),
enabled: editable })
const [body, setBody] = useState (wiki?.body ?? '')
const [loadedSource, setLoadedSource] = useState (wiki == null ? '' : source)
const pending = loading || loadedSource !== source
const [title, setTitle] = useState (wiki?.title ?? '')
const { baseErrors, fieldErrors, clearValidationErrors, applyValidationError } =
useValidationErrors<WikiFormField> ()
const handleSubmit = async () => {
if (pending || !editable)
return
clearValidationErrors ()
const formData = new FormData ()
formData.append ('title', title)
formData.append ('body', body)
try
{
await apiPut (`/wiki/${ id }`, formData,
{ locale, headers: { 'Content-Type': 'multipart/form-data' } })
qc.setQueryData (wikiKeys.show (title, { }, locale),
(prev: WikiPage | undefined) =>
prev == null ? prev : { ...prev, title, body })
qc.invalidateQueries ({ queryKey: wikiKeys.root })
toast ({ title: label ('shared:messages.postSuccess') })
navigate (`/wiki/${ encodeURIComponent (title) }`)
}
catch (e)
{
applyValidationError (e)
toast ({ title: label ('shared:messages.postFailed'),
description: label ('shared:messages.checkInputModern') })
}
}
useEffect (() => {
if (!(editable) || wiki == null)
return
setTitle (wiki.title)
setBody (wiki.body)
setLoadedSource (source)
}, [editable, source, wiki])
if (!(editable))
return <Forbidden/>
return (
<MainArea>
<PageHead title={message ('editPage')}/>
<div className="max-w-xl mx-auto p-4 space-y-4">
<h1 className="text-2xl font-bold mb-2">{label ('editPage')}</h1>
{pending ? label ('common:loading') : (
<>
<FieldError messages={baseErrors}/>
{/* タイトル */}
{/* TODO: タグ補完 */}
<FormField label={label ('shared:labels.title')} messages={fieldErrors.title}>
{({ describedBy, invalid }) => (
<input type="text"
value={title}
onChange={e => setTitle (e.target.value)}
aria-describedby={describedBy}
aria-invalid={invalid}
className={inputClass (invalid)}/>)}
</FormField>
{/* 本文 */}
<FormField label={label ('shared:labels.body')} messages={fieldErrors.body}>
{() => (
<MdEditor value={body}
style={{ height: '500px' }}
config={{ view: FIXED_WIKI_EDITOR_VIEW }}
renderHTML={text => mdParser.render (text)}
onChange={({ text }) => setBody (text)}/>)}
</FormField>
{/* 送信 */}
<button onClick={handleSubmit}
className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400">
{label ('shared:actions.edit')}
</button>
</>)}
</div>
</MainArea>)
}
export default WikiEditPage