Browse Source

#140

pull/254/head
みてるぞ 1 week ago
parent
commit
914dc43889
7 changed files with 120 additions and 85 deletions
  1. +1
    -1
      frontend/src/components/TopNav.tsx
  2. +14
    -3
      frontend/src/lib/posts.ts
  3. +27
    -6
      frontend/src/lib/prefetchers.ts
  4. +6
    -3
      frontend/src/lib/queryKeys.ts
  5. +11
    -3
      frontend/src/lib/wiki.ts
  6. +60
    -68
      frontend/src/pages/posts/PostHistoryPage.tsx
  7. +1
    -1
      frontend/src/pages/wiki/WikiDetailPage.tsx

+ 1
- 1
frontend/src/components/TopNav.tsx View File

@@ -120,7 +120,7 @@ export default (({ user }: Props) => {
const fetchPostCount = async () => { const fetchPostCount = async () => {
try try
{ {
const wikiPage = await fetchWikiPage (String (wikiId ?? ''))
const wikiPage = await fetchWikiPage (String (wikiId ?? ''), { })
const tag = await fetchTagByName (wikiPage.title) const tag = await fetchTagByName (wikiPage.title)


setPostCount (tag.postCount) setPostCount (tag.postCount)


+ 14
- 3
frontend/src/lib/posts.ts View File

@@ -1,6 +1,6 @@
import { apiDelete, apiGet, apiPost } from '@/lib/api' import { apiDelete, apiGet, apiPost } from '@/lib/api'


import type { Post } from '@/types'
import type { Post, PostTagChange } from '@/types'




export const fetchPosts = async ( export const fetchPosts = async (
@@ -13,8 +13,8 @@ export const fetchPosts = async (
): Promise<{ ): Promise<{
posts: Post[] posts: Post[]
count: number count: number
nextCursor: string }> => await apiGet ('/posts', {
params: {
nextCursor: string }> =>
await apiGet ('/posts', { params: {
tags, tags,
match, match,
...(page && { page }), ...(page && { page }),
@@ -25,6 +25,17 @@ export const fetchPosts = async (
export const fetchPost = async (id: string): Promise<Post> => await apiGet (`/posts/${ id }`) export const fetchPost = async (id: string): Promise<Post> => await apiGet (`/posts/${ id }`)




export const fetchPostChanges = async (
{ id, page, limit }: {
id?: string
page: number
limit: number },
): Promise<{
changes: PostTagChange[]
count: number }> =>
await apiGet ('/posts/changes', { params: { ...(id && { id }), page, limit } })


export const toggleViewedFlg = async (id: string, viewed: boolean): Promise<void> => { export const toggleViewedFlg = async (id: string, viewed: boolean): Promise<void> => {
await (viewed ? apiPost : apiDelete) (`/posts/${ id }/viewed`) await (viewed ? apiPost : apiDelete) (`/posts/${ id }/viewed`)
} }

+ 27
- 6
frontend/src/lib/prefetchers.ts View File

@@ -1,14 +1,23 @@
import { QueryClient } from '@tanstack/react-query' import { QueryClient } from '@tanstack/react-query'
import { match } from 'path-to-regexp' import { match } from 'path-to-regexp'


import { fetchPost, fetchPosts } from '@/lib/posts'
import { postsKeys } from '@/lib/queryKeys'
import { fetchPost, fetchPosts, fetchPostChanges } from '@/lib/posts'
import { postsKeys, wikiKeys } from '@/lib/queryKeys'
import { fetchWikiPages } from '@/lib/wiki'


type Prefetcher = (qc: QueryClient, url: URL) => Promise<void> type Prefetcher = (qc: QueryClient, url: URL) => Promise<void>


const mPost = match<{ id: string }> ('/posts/:id') const mPost = match<{ id: string }> ('/posts/:id')




const prefetchWikiPagesIndex: Prefetcher = async (qc, url) => {
const title = url.searchParams.get ('title') ?? ''
await qc.prefetchQuery ({
queryKey: wikiKeys.index ({ title }),
queryFn: () => fetchWikiPages ({ title }) })
}


const prefetchPostsIndex: Prefetcher = async (qc, url) => { const prefetchPostsIndex: Prefetcher = async (qc, url) => {
const tags = url.searchParams.get ('tags') ?? '' const tags = url.searchParams.get ('tags') ?? ''
const m = url.searchParams.get ('match') === 'any' ? 'any' : 'all' const m = url.searchParams.get ('match') === 'any' ? 'any' : 'all'
@@ -32,11 +41,23 @@ const prefetchPostShow: Prefetcher = async (qc, url) => {
} }




export const routePrefetchers: {
test: (u: URL) => boolean
run: Prefetcher }[] = [
const prefetchPostChanges: Prefetcher = async (qc, url) => {
const id = url.searchParams.get ('id')
const page = Number (url.searchParams.get ('page') || 1)
const limit = Number (url.searchParams.get ('limit') || 20)
await qc.prefetchQuery ({
queryKey: postsKeys.changes ({ ...(id && { id }), page, limit }),
queryFn: () => fetchPostChanges ({ ...(id && { id }), page, limit }) })
}


export const routePrefetchers: { test: (u: URL) => boolean; run: Prefetcher }[] = [
{ test: u => u.pathname === '/' || u.pathname === '/posts', run: prefetchPostsIndex }, { test: u => u.pathname === '/' || u.pathname === '/posts', run: prefetchPostsIndex },
{ test: u => Boolean (mPost (u.pathname)), run: prefetchPostShow }]
{ test: u => (['/posts/new', '/posts/changes'].includes(u.pathname)
&& Boolean (mPost (u.pathname))),
run: prefetchPostShow },
{ test: u => u.pathname === '/posts/changes', run: prefetchPostChanges },
{ test: u => u.pathname === '/wiki', run: prefetchWikiPagesIndex }]




export const prefetchForURL = async (qc: QueryClient, urlLike: string): Promise<void> => { export const prefetchForURL = async (qc: QueryClient, urlLike: string): Promise<void> => {


+ 6
- 3
frontend/src/lib/queryKeys.ts View File

@@ -3,8 +3,11 @@ export const postsKeys = {
index: (p: { tags: string; match: 'any' | 'all'; page: number; limit: number }) => index: (p: { tags: string; match: 'any' | 'all'; page: number; limit: number }) =>
['posts', 'index', p] as const, ['posts', 'index', p] as const,
show: (id: string) => ['posts', id] as const, show: (id: string) => ['posts', id] as const,
related: (id: string) => ['related', id] as const }
related: (id: string) => ['related', id] as const,
changes: (p: { id?: string; page: number; limit: number }) =>
['posts', 'changes', p] as const }


export const wikiKeys = { export const wikiKeys = {
root: ['wiki'] as const,
show: (title: string, p: { version: string }) => ['wiki', title, p] as const }
root: ['wiki'] as const,
index: (p: { title: string }) => ['wiki', 'index', p] as const,
show: (title: string, p: { version: string }) => ['wiki', title, p] as const }

+ 11
- 3
frontend/src/lib/wiki.ts View File

@@ -3,12 +3,20 @@ import { apiGet } from '@/lib/api'
import type { WikiPage } from '@/types' import type { WikiPage } from '@/types'




export const fetchWikiPage = async (id: string): Promise<WikiPage> =>
await apiGet (`/wiki/${ id }`)
export const fetchWikiPages = async ({ title }: { title: string }) =>
await apiGet ('/wiki', { params: { title } })


export const fetchWikiPage = async (
id: string,
{ version }: { version?: string },
): Promise<WikiPage> =>
await apiGet (`/wiki/${ id }`, { params: version ? { version } : { } })




export const fetchWikiPageByTitle = async ( export const fetchWikiPageByTitle = async (
title: string, title: string,
{ version }: { version?: string }, { version }: { version?: string },
): Promise<WikiPage> => ): Promise<WikiPage> =>
await apiGet (`/wiki/title/${ title }`, { params: version ? { version } : { } })
await apiGet (`/wiki/title/${ title }`,
{ params: version ? { version } : { } })

+ 60
- 68
frontend/src/pages/posts/PostHistoryPage.tsx View File

@@ -1,6 +1,4 @@
import axios from 'axios'
import toCamel from 'camelcase-keys'
import { useEffect, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Helmet } from 'react-helmet-async' import { Helmet } from 'react-helmet-async'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'


@@ -9,17 +7,14 @@ import PrefetchLink from '@/components/PrefetchLink'
import PageTitle from '@/components/common/PageTitle' import PageTitle from '@/components/common/PageTitle'
import Pagination from '@/components/common/Pagination' import Pagination from '@/components/common/Pagination'
import MainArea from '@/components/layout/MainArea' import MainArea from '@/components/layout/MainArea'
import { API_BASE_URL, SITE_TITLE } from '@/config'
import { SITE_TITLE } from '@/config'
import { fetchPostChanges } from '@/lib/posts'
import { postsKeys } from '@/lib/queryKeys'


import type { FC } from 'react' import type { FC } from 'react'


import type { PostTagChange } from '@/types'



export default (() => { export default (() => {
const [changes, setChanges] = useState<PostTagChange[]> ([])
const [totalPages, setTotalPages] = useState<number> (0)

const location = useLocation () const location = useLocation ()
const query = new URLSearchParams (location.search) const query = new URLSearchParams (location.search)
const id = query.get ('id') const id = query.get ('id')
@@ -29,17 +24,11 @@ export default (() => {
// 投稿列の結合で使用 // 投稿列の結合で使用
let rowsCnt: number let rowsCnt: number


useEffect (() => {
void (async () => {
const res = await axios.get (`${ API_BASE_URL }/posts/changes`,
{ params: { ...(id && { id }), page, limit } })
const data = toCamel (res.data as any, { deep: true }) as {
changes: PostTagChange[]
count: number }
setChanges (data.changes)
setTotalPages (Math.ceil (data.count / limit))
}) ()
}, [id, page, limit])
const { data, isLoading: loading } = useQuery ({
queryKey: postsKeys.changes ({ ...(id && { id }), page, limit }),
queryFn: () => fetchPostChanges ({ ...(id && { id }), page, limit }) })
const changes = data?.changes ?? []
const totalPages = data ? Math.ceil (data.count / limit) : 0


return ( return (
<MainArea> <MainArea>
@@ -52,54 +41,57 @@ export default (() => {
{id && <>: 投稿 {<PrefetchLink to={`/posts/${ id }`}>#{id}</PrefetchLink>}</>} {id && <>: 投稿 {<PrefetchLink to={`/posts/${ id }`}>#{id}</PrefetchLink>}</>}
</PageTitle> </PageTitle>


<table className="table-auto w-full border-collapse">
<thead>
<tr>
<th className="p-2 text-left">投稿</th>
<th className="p-2 text-left">変更</th>
<th className="p-2 text-left">日時</th>
</tr>
</thead>
<tbody>
{changes.map ((change, i) => {
let withPost = i === 0 || change.post.id !== changes[i - 1].post.id
if (withPost)
{
rowsCnt = 1
for (let j = i + 1;
(j < changes.length
&& change.post.id === changes[j].post.id);
++j)
++rowsCnt
}
return (
<tr key={`${ change.timestamp }-${ change.post.id }-${ change.tag.id }`}>
{withPost && (
<td className="align-top" rowSpan={rowsCnt}>
<PrefetchLink to={`/posts/${ change.post.id }`}>
<img src={change.post.thumbnail || change.post.thumbnailBase || undefined}
alt={change.post.title || change.post.url}
title={change.post.title || change.post.url || undefined}
className="w-40"/>
</PrefetchLink>
</td>)}
<td>
<TagLink tag={change.tag} withWiki={false} withCount={false}/>
{`を${ change.changeType === 'add' ? '追加' : '削除' }`}
</td>
<td>
{change.user ? (
<PrefetchLink to={`/users/${ change.user.id }`}>
{change.user.name}
</PrefetchLink>) : 'bot 操作'}
<br/>
{change.timestamp}
</td>
</tr>)
})}
</tbody>
</table>
{loading ? 'Loading...' : (
<>
<table className="table-auto w-full border-collapse">
<thead>
<tr>
<th className="p-2 text-left">投稿</th>
<th className="p-2 text-left">変更</th>
<th className="p-2 text-left">日時</th>
</tr>
</thead>
<tbody>
{changes.map ((change, i) => {
let withPost = i === 0 || change.post.id !== changes[i - 1].post.id
if (withPost)
{
rowsCnt = 1
for (let j = i + 1;
(j < changes.length
&& change.post.id === changes[j].post.id);
++j)
++rowsCnt
}
return (
<tr key={`${ change.timestamp }-${ change.post.id }-${ change.tag.id }`}>
{withPost && (
<td className="align-top" rowSpan={rowsCnt}>
<PrefetchLink to={`/posts/${ change.post.id }`}>
<img src={change.post.thumbnail || change.post.thumbnailBase || undefined}
alt={change.post.title || change.post.url}
title={change.post.title || change.post.url || undefined}
className="w-40"/>
</PrefetchLink>
</td>)}
<td>
<TagLink tag={change.tag} withWiki={false} withCount={false}/>
{`を${ change.changeType === 'add' ? '記載' : '消除' }`}
</td>
<td>
{change.user ? (
<PrefetchLink to={`/users/${ change.user.id }`}>
{change.user.name}
</PrefetchLink>) : 'bot 操作'}
<br/>
{change.timestamp}
</td>
</tr>)
})}
</tbody>
</table>


<Pagination page={page} totalPages={totalPages}/>
<Pagination page={page} totalPages={totalPages}/>
</>)}
</MainArea>) </MainArea>)
}) satisfies FC }) satisfies FC

+ 1
- 1
frontend/src/pages/wiki/WikiDetailPage.tsx View File

@@ -41,7 +41,7 @@ export default () => {
setWikiPage (undefined) setWikiPage (undefined)
try try
{ {
const data = await fetchWikiPage (title)
const data = await fetchWikiPage (title, { })
navigate (`/wiki/${ encodeURIComponent(data.title) }`, { replace: true }) navigate (`/wiki/${ encodeURIComponent(data.title) }`, { replace: true })
} }
catch catch


Loading…
Cancel
Save