0 && 'outline-4 outline-green-500',
+ (post.parentPosts ?? []).length > 0 && 'ring-4 ring-yellow-500')}
whileHover={{ scale: 1.02 }}
onLayoutAnimationStart={() => {
if (!(cardRef.current))
diff --git a/frontend/src/components/TagLink.tsx b/frontend/src/components/TagLink.tsx
index 884c851..a68f8a9 100644
--- a/frontend/src/components/TagLink.tsx
+++ b/frontend/src/components/TagLink.tsx
@@ -45,9 +45,9 @@ export default (({ tag,
<>
{(linkFlg && withWiki) && (
- {(tag.materialId != null || tag.hasWiki)
+ {(tag.materialId != null || tag.hasWiki || tag.hasDeerjikists)
? (
- tag.materialId == null
+ tag.materialId == null && !(tag.hasDeerjikists)
? (
)
: (
-
- ?
- ))
+ tag.materialId != null
+ ? (
+
+ ?
+ )
+ : (
+
+ ?
+ )))
: (
['character', 'material'].includes (tag.category)
? (
@@ -71,13 +79,23 @@ export default (({ tag,
!
)
: (
-
- !
- ))}
+ tag.category === 'deerjikist'
+ ? (
+
+ !
+ )
+ : (
+
+ !
+ )))}
)}
{nestLevel > 0 && (
=
+ { nico: 'ニコニコ', youtube: 'YouTube' } as const
+
export const TAG_COLOUR = {
deerjikist: 'rose',
meme: 'purple',
diff --git a/frontend/src/lib/queryKeys.ts b/frontend/src/lib/queryKeys.ts
index 97bae56..6ac3f21 100644
--- a/frontend/src/lib/queryKeys.ts
+++ b/frontend/src/lib/queryKeys.ts
@@ -9,11 +9,12 @@ export const postsKeys = {
['posts', 'changes', p] as const }
export const tagsKeys = {
- root: ['tags'] as const,
- index: (p: FetchTagsParams) => ['tags', 'index', p] as const,
- show: (name: string) => ['tags', name] as const,
- changes: (p: { id?: string; page: number; limit: number }) =>
- ['tags', 'changes', p] as const }
+ root: ['tags'] as const,
+ index: (p: FetchTagsParams) => ['tags', 'index', p] as const,
+ show: (name: string) => ['tags', name] as const,
+ changes: (p: { id?: string; page: number; limit: number }) =>
+ ['tags', 'changes', p] as const,
+ deerjikists: (id: string) => ['tags', 'deerjikists', id] as const }
export const wikiKeys = {
root: ['wiki'] as const,
diff --git a/frontend/src/lib/tags.ts b/frontend/src/lib/tags.ts
index e2c95c3..74eba45 100644
--- a/frontend/src/lib/tags.ts
+++ b/frontend/src/lib/tags.ts
@@ -1,6 +1,6 @@
import { apiGet } from '@/lib/api'
-import type { FetchTagsParams, Tag, TagVersion } from '@/types'
+import type { Deerjikist, FetchTagsParams, Tag, TagVersion } from '@/types'
export const fetchTags = async (
@@ -56,3 +56,9 @@ export const fetchTagChanges = async (
versions: TagVersion[]
count: number }> =>
await apiGet ('/tags/versions', { params: { ...(id && { id }), page, limit } })
+
+
+export const fetchDeerjikistsByTag = async (
+ id: string,
+): Promise<{ tag: Tag; deerjikists: Deerjikist[]}> =>
+ await apiGet (`/tags/${ id }/deerjikists`)
diff --git a/frontend/src/pages/deerjikists/DeerjikistDetailPage.tsx b/frontend/src/pages/deerjikists/DeerjikistDetailPage.tsx
new file mode 100644
index 0000000..ee3651f
--- /dev/null
+++ b/frontend/src/pages/deerjikists/DeerjikistDetailPage.tsx
@@ -0,0 +1,155 @@
+import { useQuery, useQueryClient } from '@tanstack/react-query'
+import { useEffect, useState } from 'react'
+import { useParams } from 'react-router-dom'
+
+import TagLink from '@/components/TagLink'
+import Label from '@/components/common/Label'
+import PageTitle from '@/components/common/PageTitle'
+import MainArea from '@/components/layout/MainArea'
+import { toast } from '@/components/ui/use-toast'
+import { PLATFORM_NAMES, PLATFORMS } from '@/consts'
+import { apiPut } from '@/lib/api'
+import { tagsKeys } from '@/lib/queryKeys'
+import { fetchDeerjikistsByTag } from '@/lib/tags'
+import { cn } from '@/lib/utils'
+
+import type { FC, FormEvent } from 'react'
+
+import type { Deerjikist, Platform } from '@/types'
+
+
+export default (() => {
+ const { id } = useParams ()
+ const tagId = String (id ?? '')
+ const tagKey = tagsKeys.deerjikists (tagId)
+
+ const { data: qData, isLoading: loading } =
+ useQuery ({ queryKey: tagKey, queryFn: () => fetchDeerjikistsByTag (tagId) })
+ const tag = qData?.tag
+ const deerjikists = qData?.deerjikists ?? []
+
+ const [data, setData] =
+ useState<(Omit & { platform: Platform | null })[]> ([])
+ const [disabled, setDisabled] = useState (true)
+
+ const qc = useQueryClient ()
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault ()
+
+ try
+ {
+ setDisabled (true)
+
+ setData (await apiPut (`/tags/${ id }/deerjikists`, data))
+ qc.invalidateQueries ({ queryKey: tagsKeys.root })
+
+ toast ({ description: '更新しました.' })
+ }
+ catch
+ {
+ toast ({ title: '更新失敗', description: '入力内容を確認してください.' })
+ }
+ finally
+ {
+ setDisabled (false)
+ }
+ }
+
+ useEffect (() => {
+ if (!(tag))
+ {
+ setDisabled (true)
+ return
+ }
+
+ setData (deerjikists)
+ setDisabled (false)
+ }, [tag, deerjikists])
+
+ return (
+
+ {(loading || !(tag)) ? 'Loading...' : (
+
+ )}
+ )
+}) satisfies FC
diff --git a/frontend/src/pages/posts/PostDetailPage.tsx b/frontend/src/pages/posts/PostDetailPage.tsx
index 50a19d9..5cefce7 100644
--- a/frontend/src/pages/posts/PostDetailPage.tsx
+++ b/frontend/src/pages/posts/PostDetailPage.tsx
@@ -21,7 +21,7 @@ import ServiceUnavailable from '@/pages/ServiceUnavailable'
import type { FC } from 'react'
-import type { NiconicoViewerHandle, User } from '@/types'
+import type { NiconicoViewerHandle, Post, User } from '@/types'
type Props = { user: User | null }
@@ -108,6 +108,34 @@ export default (({ user }: Props) => {
{post
? (
<>
+ {(post.childPosts ?? []).length > 0 && (
+
+
この投稿には {post.childPosts!.length} 件の子投稿があります.
+
({
+ ...p, parentPosts: [{ } as Post] }))]}/>
+
+ )}
+ {(post.parentPosts ?? []).map (pp => {
+ const siblings = post.siblingPosts?.[String (pp.id) as `${ number }`]
+ if (!(siblings))
+ return
+
+ return (
+
+
+ この投稿には 1 件の親投稿{
+ siblings.length > 1
+ && `と ${ siblings.length - 1 } 件の姉妹投稿`}があります.
+
+
({
+ ...p, parentPosts: [{ } as Post] }))]}/>
+ )
+ })}
+
{(post.thumbnail || post.thumbnailBase) && (
{
(prev: any) => newPost ?? prev)
qc.invalidateQueries ({ queryKey: postsKeys.root })
qc.invalidateQueries ({ queryKey: tagsKeys.root })
- toast ({ description: '更新しました.' })
}}/>
)}
diff --git a/frontend/src/pages/posts/PostHistoryPage.tsx b/frontend/src/pages/posts/PostHistoryPage.tsx
index fb6b27e..2977527 100644
--- a/frontend/src/pages/posts/PostHistoryPage.tsx
+++ b/frontend/src/pages/posts/PostHistoryPage.tsx
@@ -95,6 +95,8 @@ export default (() => {
{/* タグ */}
+ {/* TODO: 親投稿 */}
+ {/* */}
{/* オリジナルの投稿日時 */}
{/* 更新日時 */}
@@ -110,6 +112,8 @@ export default (() => {
タイトル |
URL |
タグ |
+ {/* TODO: 親投稿の履歴 */}
+ {/* 親投稿 | */}
オリジナルの投稿日時 |
更新日時 |
|
@@ -180,6 +184,29 @@ export default (() => {
{tag.name}
))))}
+ {/* TODO: 親投稿の履歴 */}
+ {/*
+ {change.parentPosts.map ((pp, i) => (
+ pp.type === 'added'
+ ? (
+
+ {pp.title}
+ )
+ : (
+ pp.type === 'removed'
+ ? (
+
+ {pp.title}
+ )
+ : (
+
+ {pp.title}
+ ))))}
+ | */}
{change.versionNo === 1
? originalCreatedAtString (change.originalCreatedFrom.current,
@@ -225,6 +252,11 @@ export default (() => {
.map (t => t.name)
.filter (t => t.slice (0, 5) !== 'nico:')
.join (' '),
+ parent_post_ids:
+ (change.parentPosts ?? [])
+ .filter (p => p.type !== 'removed')
+ .map (p => p.id)
+ .join (' '),
original_created_from:
change.originalCreatedFrom.current,
original_created_before:
diff --git a/frontend/src/pages/posts/PostNewPage.tsx b/frontend/src/pages/posts/PostNewPage.tsx
index 5a2f77b..a5bcbf3 100644
--- a/frontend/src/pages/posts/PostNewPage.tsx
+++ b/frontend/src/pages/posts/PostNewPage.tsx
@@ -29,6 +29,7 @@ export default (({ user }: Props) => {
const [originalCreatedBefore, setOriginalCreatedBefore] = useState (null)
const [originalCreatedFrom, setOriginalCreatedFrom] = useState (null)
+ const [parentPostIds, setParentPostIds] = useState ('')
const [tags, setTags] = useState ('')
const [thumbnailAutoFlg, setThumbnailAutoFlg] = useState (true)
const [thumbnailFile, setThumbnailFile] = useState (null)
@@ -46,6 +47,7 @@ export default (({ user }: Props) => {
formData.append ('title', title)
formData.append ('url', url)
formData.append ('tags', tags)
+ formData.append ('parent_post_ids', parentPostIds)
if (thumbnailFile)
formData.append ('thumbnail', thumbnailFile)
if (originalCreatedFrom)
@@ -177,6 +179,16 @@ export default (({ user }: Props) => {
className="mt-2 max-h-48 rounded border"/>)}
+ {/* 親投稿 */}
+
+
+ setParentPostIds (e.target.value)}
+ className="w-full border p-2 rounded"/>
+
+
{/* タグ */}
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index d5eb53e..5fb8078 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -1,5 +1,6 @@
import { CATEGORIES,
FETCH_POSTS_ORDER_FIELDS,
+ PLATFORMS,
USER_ROLES,
ViewFlagBehavior } from '@/consts'
@@ -7,6 +8,8 @@ import type { ReactNode } from 'react'
export type Category = typeof CATEGORIES[number]
+export type Deerjikist = { platform: Platform; code: string }
+
export type FetchPostsOrder = `${ FetchPostsOrderField }:${ 'asc' | 'desc' }`
export type FetchPostsOrderField = typeof FETCH_POSTS_ORDER_FIELDS[number]
@@ -114,6 +117,8 @@ export type NiconicoViewerHandle = {
showComments: () => void
hideComments: () => void }
+export type Platform = typeof PLATFORMS[number]
+
export type Post = {
id: number
url: string
@@ -121,6 +126,9 @@ export type Post = {
thumbnail: string | null
thumbnailBase: string | null
tags: Tag[]
+ parentPosts?: Post[]
+ childPosts?: Post[]
+ siblingPosts?: Record<`${ number }`, Post[]>
viewed: boolean
related: Post[]
originalCreatedFrom: string | null
@@ -144,7 +152,11 @@ export type PostVersion = {
url: { current: string; prev: string | null }
thumbnail: { current: string | null; prev: string | null }
thumbnailBase: { current: string | null; prev: string | null }
- tags: { name: string; type: 'context' | 'added' | 'removed' }[]
+ tags: { name: string
+ type: 'context' | 'added' | 'removed' }[]
+ parentPosts: { id: number
+ title: string
+ type: 'context' | 'added' | 'removed' }[]
originalCreatedFrom: { current: string | null; prev: string | null }
originalCreatedBefore: { current: string | null; prev: string | null }
createdAt: string
@@ -171,7 +183,8 @@ export type Tag = {
createdAt: string
updatedAt: string
hasWiki: boolean
- materialId: number
+ materialId: number | null
+ hasDeerjikists: boolean
children?: Tag[]
matchedAlias?: string | null }
|