7b15cb2c5a
#99 #99 #99 #99 #99 #99 #99 #99 #99 #99 Co-authored-by: miteruzo <miteruzo@naver.com> Reviewed-on: #303
98 lines
2.4 KiB
TypeScript
98 lines
2.4 KiB
TypeScript
import { Fragment, useEffect, useState } from 'react'
|
|
|
|
import TagLink from '@/components/TagLink'
|
|
import SidebarComponent from '@/components/layout/SidebarComponent'
|
|
import { apiGet } from '@/lib/api'
|
|
|
|
import type { FC, ReactNode } from 'react'
|
|
|
|
import type { Tag } from '@/types'
|
|
|
|
type TagWithDepth = Tag & {
|
|
hasChildren: boolean
|
|
children: TagWithDepth[] }
|
|
|
|
|
|
const setChildrenById = (
|
|
tags: TagWithDepth[],
|
|
targetId: number,
|
|
children: TagWithDepth[],
|
|
): TagWithDepth[] => (
|
|
tags.map (tag => {
|
|
if (tag.id === targetId)
|
|
return { ...tag, children }
|
|
|
|
if (tag.children.length === 0)
|
|
return tag
|
|
|
|
return { ...tag,
|
|
children: (setChildrenById (tag.children, targetId, children)
|
|
.filter (t => t.category !== 'meme' || t.hasChildren)) }
|
|
}))
|
|
|
|
|
|
export default (() => {
|
|
const [tags, setTags] = useState<TagWithDepth[]> ([])
|
|
const [openTags, setOpenTags] = useState<Record<number, boolean>> ({ })
|
|
const [tagFetchedFlags, setTagFetchedFlags] = useState<Record<number, boolean>> ({ })
|
|
|
|
useEffect (() => {
|
|
void (async () => {
|
|
setTags ((await apiGet<TagWithDepth[]> ('/tags/with-depth'))
|
|
.filter (t => t.category !== 'meme' || t.hasChildren))
|
|
}) ()
|
|
}, [])
|
|
|
|
const renderTags = (ts: TagWithDepth[], nestLevel = 0): ReactNode => (
|
|
ts.map (t => (
|
|
<Fragment key={t.id}>
|
|
<li>
|
|
<div className="flex">
|
|
<div className="flex-none w-4">
|
|
{t.hasChildren && (
|
|
<a
|
|
href="#"
|
|
onClick={async e => {
|
|
e.preventDefault ()
|
|
if (!(tagFetchedFlags[t.id]))
|
|
{
|
|
try
|
|
{
|
|
const data =
|
|
await apiGet<TagWithDepth[]> (
|
|
'/tags/with-depth', { params: { parent: String (t.id) } })
|
|
setTags (prev => setChildrenById (prev, t.id, data))
|
|
setTagFetchedFlags (prev => ({ ...prev, [t.id]: true }))
|
|
}
|
|
catch
|
|
{
|
|
;
|
|
}
|
|
}
|
|
setOpenTags (prev => ({ ...prev, [t.id]: !(prev[t.id]) }))
|
|
}}>
|
|
{openTags[t.id] ? <>−</> : '+'}
|
|
</a>)}
|
|
</div>
|
|
<div className="flex-1 truncate">
|
|
<TagLink
|
|
tag={t}
|
|
nestLevel={nestLevel}
|
|
title={t.name}
|
|
withCount={false}
|
|
withWiki={false}
|
|
to={`/materials?tag=${ encodeURIComponent (t.name) }`}/>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
{openTags[t.id] && renderTags (t.children, nestLevel + 1)}
|
|
</Fragment>)))
|
|
|
|
return (
|
|
<SidebarComponent>
|
|
<ul>
|
|
{renderTags (tags)}
|
|
</ul>
|
|
</SidebarComponent>)
|
|
}) satisfies FC
|