プリフェッチ実装(#140) (#256)

Merge branch 'main' into feature/140

#140

Merge remote-tracking branch 'origin/main' into feature/140

#140

#140

#140

#140

#140

Merge remote-tracking branch 'origin/main' into feature/140

#140

#140

#140

#140

#140

#140

#140

#140

#140

#140

#140

Merge remote-tracking branch 'origin/main' into feature/140

Merge remote-tracking branch 'origin/main' into feature/140

#140 ぼちぼち

Merge remote-tracking branch 'origin/main' into feature/140

#140

#140

#140

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #256
This commit was merged in pull request #256.
This commit is contained in:
2026-02-11 13:27:28 +09:00
parent 1a776e348a
commit eb975e5301
30 changed files with 517 additions and 488 deletions
+43 -61
View File
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { useEffect, useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
@@ -12,10 +13,11 @@ import MainArea from '@/components/layout/MainArea'
import { SITE_TITLE } from '@/config'
import { WikiIdBus } from '@/lib/eventBus/WikiIdBus'
import { fetchPosts } from '@/lib/posts'
import { postsKeys, tagsKeys, wikiKeys } from '@/lib/queryKeys'
import { fetchTagByName } from '@/lib/tags'
import { fetchWikiPage, fetchWikiPageByTitle } from '@/lib/wiki'
import type { Post, Tag, WikiPage } from '@/types'
import type { Tag } from '@/types'
export default () => {
@@ -25,76 +27,57 @@ export default () => {
const location = useLocation ()
const navigate = useNavigate ()
const defaultTag = { name: title, category: 'general' } as Tag
const [posts, setPosts] = useState<Post[]> ([])
const [tag, setTag] = useState (defaultTag)
const [wikiPage, setWikiPage] = useState<WikiPage | null | undefined> (undefined)
const defaultTag = useMemo (() => ({ name: title, category: 'general' } as Tag), [title])
const query = new URLSearchParams (location.search)
const version = query.get ('version')
const version = query.get ('version') || undefined
const { data: wikiPage, isLoading: loading } = useQuery ({
enabled: Boolean (title) && !(/^\d+$/.test (title)),
queryKey: wikiKeys.show (title, { version }),
queryFn: () => fetchWikiPageByTitle (title, { version }) })
const effectiveTitle = wikiPage?.title ?? title
const { data: tag } = useQuery ({
enabled: Boolean (effectiveTitle),
queryKey: tagsKeys.show (effectiveTitle),
queryFn: () => fetchTagByName (effectiveTitle) })
const { data } = useQuery ({
enabled: Boolean (effectiveTitle) && !(version),
queryKey: postsKeys.index ({ tags: effectiveTitle, match: 'all', page: 1, limit: 8 }),
queryFn: () => fetchPosts ({ tags: effectiveTitle, match: 'all', page: 1, limit: 8 }) })
const posts = data?.posts || []
useEffect (() => {
if (/^\d+$/.test (title))
{
void (async () => {
setWikiPage (undefined)
try
{
const data = await fetchWikiPage (title)
navigate (`/wiki/${ encodeURIComponent(data.title) }`, { replace: true })
}
catch
{
;
}
}) ()
if (!(wikiPage))
return
return
}
WikiIdBus.set (wikiPage.id)
void (async () => {
setWikiPage (undefined)
try
{
const data = await fetchWikiPageByTitle (title, version ? { version } : { })
if (data.title !== title)
navigate (`/wiki/${ encodeURIComponent(data.title) }`, { replace: true })
setWikiPage (data)
WikiIdBus.set (data.id)
}
catch
{
setWikiPage (null)
}
}) ()
if (wikiPage.title !== title)
navigate (`/wiki/${ encodeURIComponent(wikiPage.title) }`, { replace: true })
return () => WikiIdBus.set (null)
}, [wikiPage, title, navigate])
useEffect (() => {
if (!(/^\d+$/.test (title)))
return
setPosts ([])
void (async () => {
try
{
const data = await fetchPosts ({ tags: title, match: 'all', limit: 8 })
setPosts (data.posts)
const data = await fetchWikiPage (title, { })
navigate (`/wiki/${ encodeURIComponent(data.title) }`, { replace: true })
}
catch
{
;
}
}) ()
void (async () => {
try
{
setTag (await fetchTagByName (title))
}
catch
{
setTag (defaultTag)
}
}) ()
return () => WikiIdBus.set (null)
}, [title, location.search])
}, [title, navigate])
return (
<MainArea>
@@ -104,7 +87,8 @@ export default () => {
</Helmet>
{(wikiPage && version) && (
<div className="text-sm flex gap-3 items-center justify-center border border-gray-700 rounded px-2 py-1 mb-4">
<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 }`}>
&lt;
@@ -119,15 +103,13 @@ export default () => {
</div>)}
<PageTitle>
<TagLink tag={tag}
<TagLink tag={tag ?? defaultTag}
withWiki={false}
withCount={false}
{...(version && { to: `/wiki/${ encodeURIComponent (title) }` })}/>
</PageTitle>
<div className="prose mx-auto p-4">
{wikiPage === undefined
? 'Loading...'
: <WikiBody title={title} body={wikiPage?.body}/>}
{loading ? 'Loading...' : <WikiBody title={title} body={wikiPage?.body}/>}
</div>
{(!(version) && posts.length > 0) && (
+3 -5
View File
@@ -1,12 +1,11 @@
import axios from 'axios'
import toCamel from 'camelcase-keys'
import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import { useLocation, useParams } from 'react-router-dom'
import PageTitle from '@/components/common/PageTitle'
import MainArea from '@/components/layout/MainArea'
import { API_BASE_URL, SITE_TITLE } from '@/config'
import { SITE_TITLE } from '@/config'
import { apiGet } from '@/lib/api'
import { cn } from '@/lib/utils'
import type { WikiPageDiff } from '@/types'
@@ -25,8 +24,7 @@ export default () => {
useEffect (() => {
void (async () => {
const res = await axios.get (`${ API_BASE_URL }/wiki/${ id }/diff`, { params: { from, to } })
setDiff (toCamel (res.data as any, { deep: true }) as WikiPageDiff)
setDiff (await apiGet<WikiPageDiff> (`/wiki/${ id }/diff`, { params: { from, to } }))
}) ()
}, [])
+12 -7
View File
@@ -1,4 +1,4 @@
import axios from 'axios'
import { useQueryClient } from '@tanstack/react-query'
import MarkdownIt from 'markdown-it'
import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet-async'
@@ -7,7 +7,9 @@ import { useParams, useNavigate } from 'react-router-dom'
import MainArea from '@/components/layout/MainArea'
import { toast } from '@/components/ui/use-toast'
import { API_BASE_URL, SITE_TITLE } from '@/config'
import { SITE_TITLE } from '@/config'
import { apiGet, apiPut } from '@/lib/api'
import { wikiKeys } from '@/lib/queryKeys'
import Forbidden from '@/pages/Forbidden'
import 'react-markdown-editor-lite/lib/index.css'
@@ -29,6 +31,8 @@ export default (({ user }: Props) => {
const navigate = useNavigate ()
const qc = useQueryClient ()
const [body, setBody] = useState ('')
const [loading, setLoading] = useState (true)
const [title, setTitle] = useState ('')
@@ -40,9 +44,11 @@ export default (({ user }: Props) => {
try
{
await axios.put (`${ API_BASE_URL }/wiki/${ id }`, formData, { headers: {
'Content-Type': 'multipart/form-data',
'X-Transfer-Code': localStorage.getItem ('user_code') ?? '' } })
await apiPut (`/wiki/${ id }`, formData,
{ headers: { 'Content-Type': 'multipart/form-data' } })
qc.setQueryData (wikiKeys.show (title, { }),
(prev: WikiPage) => ({ ...prev, title, body }))
qc.invalidateQueries ({ queryKey: wikiKeys.root })
toast ({ title: '投稿成功!' })
navigate (`/wiki/${ title }`)
}
@@ -55,8 +61,7 @@ export default (({ user }: Props) => {
useEffect (() => {
void (async () => {
setLoading (true)
const res = await axios.get (`${ API_BASE_URL }/wiki/${ id }`)
const data = res.data as WikiPage
const data = await apiGet<WikiPage> (`/wiki/${ id }`)
setTitle (data.title)
setBody (data.body)
setLoading (false)
+13 -13
View File
@@ -1,11 +1,11 @@
import axios from 'axios'
import toCamel from 'camelcase-keys'
import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import { Link, useLocation } from 'react-router-dom'
import { useLocation } from 'react-router-dom'
import PrefetchLink from '@/components/PrefetchLink'
import MainArea from '@/components/layout/MainArea'
import { API_BASE_URL, SITE_TITLE } from '@/config'
import { SITE_TITLE } from '@/config'
import { apiGet } from '@/lib/api'
import type { WikiPageChange } from '@/types'
@@ -19,9 +19,7 @@ export default () => {
useEffect (() => {
void (async () => {
const res = await axios.get (`${ API_BASE_URL }/wiki/changes`,
{ params: { ...(id ? { id } : { }) } })
setChanges (toCamel (res.data as any, { deep: true }) as WikiPageChange[])
setChanges (await apiGet<WikiPageChange[]> ('/wiki/changes', { params: id ? { id } : { } }))
}) ()
}, [location.search])
@@ -44,22 +42,24 @@ export default () => {
<tr key={change.revisionId}>
<td>
{change.pred != null && (
<Link to={`/wiki/${ change.wikiPage.id }/diff?from=${ change.pred }&to=${ change.revisionId }`}>
<PrefetchLink
to={`/wiki/${ change.wikiPage.id }/diff?from=${ change.pred }&to=${ change.revisionId }`}>
</Link>)}
</PrefetchLink>)}
</td>
<td className="p-2">
<Link to={`/wiki/${ encodeURIComponent (change.wikiPage.title) }?version=${ change.revisionId }`}>
<PrefetchLink
to={`/wiki/${ encodeURIComponent (change.wikiPage.title) }?version=${ change.revisionId }`}>
{change.wikiPage.title}
</Link>
</PrefetchLink>
</td>
<td className="p-2">
{change.pred == null ? '新規' : '更新'}
</td>
<td className="p-2">
<Link to={`/users/${ change.user.id }`}>
<PrefetchLink to={`/users/${ change.user.id }`}>
{change.user.name}
</Link>
</PrefetchLink>
<br/>
{change.timestamp}
</td>
+4 -6
View File
@@ -1,4 +1,3 @@
import axios from 'axios'
import MarkdownIt from 'markdown-it'
import { useState } from 'react'
import { Helmet } from 'react-helmet-async'
@@ -7,7 +6,8 @@ import { useLocation, useNavigate } from 'react-router-dom'
import MainArea from '@/components/layout/MainArea'
import { toast } from '@/components/ui/use-toast'
import { API_BASE_URL, SITE_TITLE } from '@/config'
import { SITE_TITLE } from '@/config'
import { apiPost } from '@/lib/api'
import Forbidden from '@/pages/Forbidden'
import 'react-markdown-editor-lite/lib/index.css'
@@ -39,10 +39,8 @@ export default ({ user }: Props) => {
try
{
const res = await axios.post (`${ API_BASE_URL }/wiki`, formData, { headers: {
'Content-Type': 'multipart/form-data',
'X-Transfer-Code': localStorage.getItem ('user_code') || '' } })
const data = res.data as WikiPage
const data = await apiPost<WikiPage> ('/wiki', formData,
{ headers: { 'Content-Type': 'multipart/form-data' } })
toast ({ title: '投稿成功!' })
navigate (`/wiki/${ data.title }`)
}
+10 -10
View File
@@ -1,12 +1,13 @@
import axios from 'axios'
import toCamel from 'camelcase-keys'
import React, { useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import { Link } from 'react-router-dom'
import PrefetchLink from '@/components/PrefetchLink'
import SectionTitle from '@/components/common/SectionTitle'
import MainArea from '@/components/layout/MainArea'
import { API_BASE_URL, SITE_TITLE } from '@/config'
import { SITE_TITLE } from '@/config'
import { apiGet } from '@/lib/api'
import type { FormEvent } from 'react'
import type { WikiPage } from '@/types'
@@ -17,11 +18,10 @@ export default () => {
const [results, setResults] = useState<WikiPage[]> ([])
const search = async () => {
const res = await axios.get (`${ API_BASE_URL }/wiki/search`, { params: { title } })
setResults (toCamel (res.data as any, { deep: true }) as WikiPage[])
setResults (await apiGet ('/wiki', { params: { title } }))
}
const handleSearch = (ev: React.FormEvent) => {
const handleSearch = (ev: FormEvent) => {
ev.preventDefault ()
search ()
}
@@ -78,9 +78,9 @@ export default () => {
{results.map (page => (
<tr key={page.id}>
<td className="p-2">
<Link to={`/wiki/${ encodeURIComponent (page.title) }`}>
<PrefetchLink to={`/wiki/${ encodeURIComponent (page.title) }`}>
{page.title}
</Link>
</PrefetchLink>
</td>
<td className="p-2 text-gray-100 text-sm">
{page.updatedAt}