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

170 行
6.5 KiB
TypeScript

import { useQuery } from '@tanstack/react-query'
import { useEffect, useMemo } from 'react'
import { useLocation, useParams } from 'react-router-dom'
import { useLocale, useLocaleNavigate } from '@/lib/useLocale'
import { useUITranslation } from '@/lib/useUITranslation'
import PostList from '@/components/PostList'
import PrefetchLink from '@/components/PrefetchLink'
import TagLink from '@/components/TagLink'
import WikiBody from '@/components/WikiBody'
import TabGroup, { Tab } from '@/components/common/TabGroup'
import MainArea from '@/components/layout/MainArea'
import PageHead from '@/components/common/PageHead'
import { WikiIdBus } from '@/lib/eventBus/WikiIdBus'
import { getHelpTitleIndex } from '@/lib/help'
import { localeLanguage } from '@/lib/locales'
import { fetchPosts } from '@/lib/posts'
import { helpKeys, postsKeys, wikiKeys } from '@/lib/queryKeys'
import { fetchWikiPageByReference, fetchWikiTag } from '@/lib/wiki'
import NotFound from '@/pages/NotFound'
import type { FC } from 'react'
import type { Tag, WikiTagReference } from '@/types'
const WikiDetailPage: FC = () => {
const { message, label } = useUITranslation ('shared')
const locale = useLocale ()
const params = useParams ()
const title = params.title ?? ''
const numericReference = /^\d+$/.test (title)
const location = useLocation ()
const navigate = useLocaleNavigate ()
const defaultTag = useMemo (() => ({ name: title, category: 'general' } as Tag), [title])
const query = new URLSearchParams (location.search)
const version = query.get ('version') || undefined
const { data: wikiPage, isLoading: loading, isSuccess, isFetching, error } = useQuery ({
enabled: Boolean (title),
queryKey: wikiKeys.show (title, { version }, locale),
queryFn: () => fetchWikiPageByReference (title, { version }, locale) })
const missingWiki = isSuccess && !(isFetching) && wikiPage == null
const { data: titleIndex, isPending: lookupPending,
isFetching: lookupFetching, error: lookupError } = useQuery ({
enabled: missingWiki && !numericReference,
queryKey: helpKeys.titles ('ja'),
queryFn: () => getHelpTitleIndex ('ja'),
staleTime: Infinity })
const lookupTitle = title.trim ().normalize ('NFKC').toLowerCase ().replace (/^ヘルプ:/, '')
const identifiers = useMemo (() => (
(titleIndex != null && Object.hasOwn (titleIndex, lookupTitle))
? titleIndex[lookupTitle] : []), [titleIndex, lookupTitle])
useEffect (() => {
if (missingWiki && !numericReference
&& !(lookupFetching) && lookupError == null && identifiers.length === 1)
navigate (`/help/${ encodeURIComponent (identifiers[0]) }`, { replace: true })
}, [missingWiki, numericReference, lookupFetching, lookupError, identifiers, navigate])
const effectiveTitle = wikiPage?.title ?? title
const deprecated = wikiPage?.deprecatedAt != null
const tagReference: WikiTagReference = (wikiPage != null || !numericReference)
? { name: effectiveTitle } : { wikiId: title }
const { data: tag, isPending: tagPending,
isFetching: tagFetching, error: tagError } = useQuery ({
enabled: Boolean (title) && (missingWiki || wikiPage != null),
queryKey: wikiKeys.tag (tagReference, locale),
queryFn: () => fetchWikiTag (tagReference, locale) })
const displayTitle = wikiPage?.title ?? tag?.name ?? title
const keys = {
tags: displayTitle, match: 'all', page: 1, limit: 8, url: '', title: '',
originalCreatedFrom: '', originalCreatedTo: '',
createdFrom: '', createdTo: '', updatedFrom: '', updatedTo: '',
order: 'original_created_at:desc' } as const
const { data } = useQuery ({
enabled: Boolean (effectiveTitle) && !(version)
&& (!numericReference || wikiPage != null || tag != null),
queryKey: postsKeys.index (keys, locale),
queryFn: () => fetchPosts (keys, locale) })
const posts = data?.posts || []
useEffect (() => {
if (!(wikiPage))
return
WikiIdBus.set (wikiPage.id)
if (!numericReference && wikiPage.title !== title)
{
navigate ({ pathname: `/wiki/${ encodeURIComponent (wikiPage.title) }`,
search: location.search, hash: location.hash }, { replace: true })
}
return () => WikiIdBus.set (null)
}, [wikiPage, title, numericReference, navigate, location.search, location.hash])
const displayError = error ?? (missingWiki ? lookupError ?? tagError : null)
if (displayError != null)
return <MainArea><p role="alert">{displayError.message}</p></MainArea>
const waiting = loading || (isFetching && wikiPage == null)
|| (missingWiki && ((!numericReference
&& (lookupPending || lookupFetching || identifiers.length === 1))
|| tagPending || (tagFetching && tag == null)))
if (missingWiki && !(waiting) && (tag == null || version != null))
return <NotFound/>
return (
<MainArea>
<PageHead title={{
text: `${displayTitle}${deprecated
? message ('labels.deprecatedSuffix').text : ''} Wiki`,
language: deprecated ? message ('labels.deprecatedSuffix').language : locale }}>
{!(wikiPage?.body) && <meta name="robots" content="noindex"/>}
</PageHead>
{(wikiPage && version) && (
<div className="text-sm flex gap-3 items-center justify-center
border border-gray-700 rounded px-2 py-1 mb-4">
{wikiPage.pred ? (
<PrefetchLink to={`/wiki/${ encodeURIComponent (title) }?version=${ wikiPage.pred }`}>
{label ('actions.old')}
</PrefetchLink>) : label ('actions.oldest')}
<span>{wikiPage.updatedAt}</span>
{wikiPage.succ ? (
<PrefetchLink to={`/wiki/${ encodeURIComponent (title) }?version=${ wikiPage.succ }`}>
{label ('actions.new')}
</PrefetchLink>) : label ('actions.latest')}
</div>)}
<article className="prose dark:prose-invert mx-auto p-4">
<h1 className="prose-a:no-underline">
<TagLink tag={tag ?? { ...defaultTag,
name: displayTitle,
deprecatedAt: wikiPage?.deprecatedAt ?? null }}
withWiki={false}
withCount={false}
{...(version && { to: `/wiki/${ encodeURIComponent (title) }` })}/>
{deprecated && label ('labels.deprecatedSuffix')}
</h1>
{waiting
? label ('common:loading')
: <WikiBody title={displayTitle} body={wikiPage?.body}
language={wikiPage?.languageCode ?? (wikiPage?.resourceType === 'tag_description'
? localeLanguage (locale) : 'und')}/>}
{(!(version) && posts.length > 0) && (
<div className="not-prose">
<TabGroup>
<Tab name={message ('navigation:posts').text}>
<PostList posts={posts}/>
</Tab>
</TabGroup>
</div>)}
</article>
</MainArea>)
}
export default WikiDetailPage