ぼざクリタグ広場 https://hub.nizika.monster
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

98 lines
2.4 KiB

  1. import { Fragment, useEffect, useState } from 'react'
  2. import TagLink from '@/components/TagLink'
  3. import SidebarComponent from '@/components/layout/SidebarComponent'
  4. import { apiGet } from '@/lib/api'
  5. import type { FC, ReactNode } from 'react'
  6. import type { Tag } from '@/types'
  7. type TagWithDepth = Tag & {
  8. hasChildren: boolean
  9. children: TagWithDepth[] }
  10. const setChildrenById = (
  11. tags: TagWithDepth[],
  12. targetId: number,
  13. children: TagWithDepth[],
  14. ): TagWithDepth[] => (
  15. tags.map (tag => {
  16. if (tag.id === targetId)
  17. return { ...tag, children }
  18. if (tag.children.length === 0)
  19. return tag
  20. return { ...tag,
  21. children: (setChildrenById (tag.children, targetId, children)
  22. .filter (t => t.category !== 'meme' || t.hasChildren)) }
  23. }))
  24. export default (() => {
  25. const [tags, setTags] = useState<TagWithDepth[]> ([])
  26. const [openTags, setOpenTags] = useState<Record<number, boolean>> ({ })
  27. const [tagFetchedFlags, setTagFetchedFlags] = useState<Record<number, boolean>> ({ })
  28. useEffect (() => {
  29. void (async () => {
  30. setTags ((await apiGet<TagWithDepth[]> ('/tags/with-depth'))
  31. .filter (t => t.category !== 'meme' || t.hasChildren))
  32. }) ()
  33. }, [])
  34. const renderTags = (ts: TagWithDepth[], nestLevel = 0): ReactNode => (
  35. ts.map (t => (
  36. <Fragment key={t.id}>
  37. <li>
  38. <div className="flex">
  39. <div className="flex-none w-4">
  40. {t.hasChildren && (
  41. <a
  42. href="#"
  43. onClick={async e => {
  44. e.preventDefault ()
  45. if (!(tagFetchedFlags[t.id]))
  46. {
  47. try
  48. {
  49. const data =
  50. await apiGet<TagWithDepth[]> (
  51. '/tags/with-depth', { params: { parent: String (t.id) } })
  52. setTags (prev => setChildrenById (prev, t.id, data))
  53. setTagFetchedFlags (prev => ({ ...prev, [t.id]: true }))
  54. }
  55. catch
  56. {
  57. ;
  58. }
  59. }
  60. setOpenTags (prev => ({ ...prev, [t.id]: !(prev[t.id]) }))
  61. }}>
  62. {openTags[t.id] ? <>&minus;</> : '+'}
  63. </a>)}
  64. </div>
  65. <div className="flex-1 truncate">
  66. <TagLink
  67. tag={t}
  68. nestLevel={nestLevel}
  69. title={t.name}
  70. withCount={false}
  71. withWiki={false}
  72. to={`/materials?tag=${ encodeURIComponent (t.name) }`}/>
  73. </div>
  74. </div>
  75. </li>
  76. {openTags[t.id] && renderTags (t.children, nestLevel + 1)}
  77. </Fragment>)))
  78. return (
  79. <SidebarComponent>
  80. <ul>
  81. {renderTags (tags)}
  82. </ul>
  83. </SidebarComponent>)
  84. }) satisfies FC