|
- import { useQuery, useQueryClient } from '@tanstack/react-query'
- import { motion } from 'framer-motion'
- import { useEffect } from 'react'
- import { Helmet } from 'react-helmet-async'
- import { useLocation } from 'react-router-dom'
-
- import TagLink from '@/components/TagLink'
- import PrefetchLink from '@/components/PrefetchLink'
- import PageTitle from '@/components/common/PageTitle'
- import Pagination from '@/components/common/Pagination'
- import MainArea from '@/components/layout/MainArea'
- import { toast } from '@/components/ui/use-toast'
- import { SITE_TITLE } from '@/config'
- import { apiPut } from '@/lib/api'
- import { fetchPostChanges } from '@/lib/posts'
- import { postsKeys, tagsKeys } from '@/lib/queryKeys'
- import { fetchTag } from '@/lib/tags'
- import { cn, dateString, originalCreatedAtString } from '@/lib/utils'
-
- import type { FC } from 'react'
-
-
- const renderDiff = (diff: { current: string | null; prev: string | null }) => (
- <>
- {(diff.prev && diff.prev !== diff.current) && (
- <>
- <del className="text-red-600 dark:text-red-400">
- {diff.prev}
- </del>
- {diff.current && <br/>}
- </>)}
- {diff.current}
- </>)
-
-
- export default (() => {
- const location = useLocation ()
- const query = new URLSearchParams (location.search)
- const id = query.get ('id')
- const tagId = query.get ('tag')
- const page = Number (query.get ('page') ?? 1)
- const limit = Number (query.get ('limit') ?? 20)
-
- // 投稿列の結合で使用
- let rowsCnt: number
-
- const { data: tag } =
- tagId
- ? useQuery ({ queryKey: tagsKeys.show (tagId),
- queryFn: () => fetchTag (tagId) })
- : { data: null }
-
- const { data, isLoading: loading } = useQuery ({
- queryKey: postsKeys.changes ({ ...(id && { post: id }),
- ...(tagId && { tag: tagId }),
- page, limit }),
- queryFn: () => fetchPostChanges ({ ...(id && { post: id }),
- ...(tagId && { tag: tagId }),
- page, limit }) })
- const changes = data?.versions ?? []
- const totalPages = data ? Math.ceil (data.count / limit) : 0
-
- const qc = useQueryClient ()
-
- useEffect (() => {
- document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
- }, [location.search])
-
- const layoutIds: string[] = []
-
- return (
- <MainArea>
- <Helmet>
- <title>{`耕作履歴 | ${ SITE_TITLE }`}</title>
- </Helmet>
-
- <PageTitle>
- 耕作履歴
- {id && <>: 投稿 {<PrefetchLink to={`/posts/${ id }`}>#{id}</PrefetchLink>}</>}
- {tag && <>(<TagLink tag={tag} withWiki={false} withCount={false}/>)</>}
- </PageTitle>
-
- {loading ? 'Loading...' : (
- <>
- <div className="overflow-x-auto">
- <table className="w-full min-w-[1200px] table-fixed border-collapse">
- <colgroup>
- {/* 投稿 */}
- <col className="w-64"/>
- {/* 版 */}
- <col className="w-40"/>
- {/* タイトル */}
- <col className="w-96"/>
- {/* URL */}
- <col className="w-96"/>
- {/* タグ */}
- <col className="w-[48rem]"/>
- {/* オリジナルの投稿日時 */}
- <col className="w-96"/>
- {/* 更新日時 */}
- <col className="w-64"/>
- {/* (差戻ボタン) */}
- <col className="w-20"/>
- </colgroup>
-
- <thead className="border-b-2 border-black dark:border-white">
- <tr>
- <th className="p-2 text-left">投稿</th>
- <th className="p-2 text-left">版</th>
- <th className="p-2 text-left">タイトル</th>
- <th className="p-2 text-left">URL</th>
- <th className="p-2 text-left">タグ</th>
- <th className="p-2 text-left">オリジナルの投稿日時</th>
- <th className="p-2 text-left">更新日時</th>
- <th className="p-2"/>
- </tr>
- </thead>
-
- <tbody>
- {changes.map ((change, i) => {
- const withPost = i === 0 || change.postId !== changes[i - 1].postId
- if (withPost)
- {
- rowsCnt = 1
- for (let j = i + 1;
- (j < changes.length
- && change.postId === changes[j].postId);
- ++j)
- ++rowsCnt
- }
-
- let layoutId: string | undefined = `page-${ change.postId }`
- if (layoutIds.includes (layoutId))
- layoutId = undefined
- else
- layoutIds.push (layoutId)
-
- return (
- <tr key={`${ change.postId }.${ change.versionNo }`}
- className={cn ('even:bg-gray-100 dark:even:bg-gray-700',
- withPost && 'border-t')}>
- {withPost && (
- <td className="align-top p-2 bg-white dark:bg-[#242424] border-r"
- rowSpan={rowsCnt}>
- <PrefetchLink to={`/posts/${ change.postId }`}>
- <motion.div
- layoutId={layoutId}
- transition={{ layout: { duration: .2, ease: 'easeOut' } }}>
- <img src={change.thumbnail.current
- || change.thumbnailBase.current
- || undefined}
- alt={change.title.current || change.url.current}
- title={change.title.current || change.url.current || undefined}
- className="w-40"/>
- </motion.div>
- </PrefetchLink>
- </td>)}
- <td className="p-2">{change.postId}.{change.versionNo}</td>
- <td className="p-2 break-all">{renderDiff (change.title)}</td>
- <td className="p-2 break-all">{renderDiff (change.url)}</td>
- <td className="p-2">
- {change.tags.map ((tag, i) => (
- tag.type === 'added'
- ? (
- <ins
- key={i}
- className="mr-2 text-green-600 dark:text-green-400">
- {tag.name}
- </ins>)
- : (
- tag.type === 'removed'
- ? (
- <del
- key={i}
- className="mr-2 text-red-600 dark:text-red-400">
- {tag.name}
- </del>)
- : (
- <span key={i} className="mr-2">
- {tag.name}
- </span>))))}
- </td>
- <td className="p-2">
- {change.versionNo === 1
- ? originalCreatedAtString (change.originalCreatedFrom.current,
- change.originalCreatedBefore.current)
- : renderDiff ({
- current: originalCreatedAtString (
- change.originalCreatedFrom.current,
- change.originalCreatedBefore.current),
- prev: originalCreatedAtString (
- change.originalCreatedFrom.prev,
- change.originalCreatedBefore.prev) })}
- </td>
- <td className="p-2">
- {change.createdByUser
- ? (
- <PrefetchLink to={`/users/${ change.createdByUser.id }`}>
- {change.createdByUser.name
- || `名もなきニジラー(#${ change.createdByUser.id })`}
- </PrefetchLink>)
- : 'bot 操作'}
- <br/>
- {dateString (change.createdAt)}
- </td>
- <td className="p-2">
- <a
- href="#"
- onClick={async e => {
- e.preventDefault ()
-
- if (!(confirm (
- `『${ change.title.current
- || change.url.current }』を版 ${
- change.versionNo } に差戻します.\nよろしいですか?`)))
- return
-
- await apiPut (
- `/posts/${ change.postId }`,
- { title: change.title.current,
- tags: change.tags
- .filter (t => t.type !== 'removed')
- .map (t => t.name)
- .filter (t => t.slice (0, 5) !== 'nico:')
- .join (' '),
- original_created_from:
- change.originalCreatedFrom.current,
- original_created_before:
- change.originalCreatedBefore.current })
-
- qc.invalidateQueries ({ queryKey: postsKeys.root })
- qc.invalidateQueries ({ queryKey: tagsKeys.root })
- toast ({ description: '更新しました.' })
- }}>
- 復元
- </a>
- </td>
- </tr>)
- })}
- </tbody>
- </table>
- </div>
-
- <Pagination page={page} totalPages={totalPages}/>
- </>)}
- </MainArea>)
- }) satisfies FC
|