ぼざクリ タグ広場 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.
 
 
 
 
 
 

189 lines
6.1 KiB

  1. import axios from 'axios'
  2. import toCamel from 'camelcase-keys'
  3. import { useState, useEffect } from 'react'
  4. import { Link, useLocation, useNavigate } from 'react-router-dom'
  5. import Separator from '@/components/MenuSeparator'
  6. import { API_BASE_URL } from '@/config'
  7. import { WikiIdBus } from '@/lib/eventBus/WikiIdBus'
  8. import { cn } from '@/lib/utils'
  9. import type { Menu, Tag, User, WikiPage } from '@/types'
  10. type Props = { user: User | null }
  11. export default ({ user }: Props) => {
  12. const location = useLocation ()
  13. const navigate = useNavigate ()
  14. const [menuOpen, setMenuOpen] = useState (false)
  15. const [openItemIdx, setOpenItemIdx] = useState (-1)
  16. const [postCount, setPostCount] = useState<number | null> (null)
  17. const [wikiId, setWikiId] = useState<number | null> (WikiIdBus.get ())
  18. const wikiPageFlg = Boolean (/^\/wiki\/(?!new|changes)[^\/]+/.test (location.pathname) && wikiId)
  19. const wikiTitle = location.pathname.split ('/')[2]
  20. const menu: Menu = [
  21. { name: '広場', to: '/posts', subMenu: [
  22. { name: '一覧', to: '/posts' },
  23. { name: '投稿追加', to: '/posts/new' },
  24. { name: 'ヘルプ', to: '/wiki/ヘルプ:広場' }] },
  25. { name: 'タグ', to: '/tags', subMenu: [
  26. { name: 'タグ一覧', to: '/tags', visible: false },
  27. { name: '別名タグ', to: '/tags/aliases', visible: false },
  28. { name: '上位タグ', to: '/tags/implications', visible: false },
  29. { name: 'ニコニコ連携', to: '/tags/nico' },
  30. { name: 'タグのつけ方', to: '/wiki/ヘルプ:タグのつけ方' },
  31. { name: 'ヘルプ', to: '/wiki/ヘルプ:タグ' }] },
  32. { name: 'Wiki', to: '/wiki/ヘルプ:ホーム', base: '/wiki', subMenu: [
  33. { name: '検索', to: '/wiki' },
  34. { name: '新規', to: '/wiki/new' },
  35. { name: '全体履歴', to: '/wiki/changes' },
  36. { name: 'ヘルプ', to: '/wiki/ヘルプ:Wiki' },
  37. { component: <Separator />, visible: wikiPageFlg },
  38. { name: `広場 (${ postCount || 0 })`, to: `/posts?tags=${ wikiTitle }`,
  39. visible: wikiPageFlg },
  40. { name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg },
  41. { name: '編輯', to: `/wiki/${ wikiId || wikiTitle }/edit`, visible: wikiPageFlg }] },
  42. { name: 'ユーザ', to: '/users', subMenu: [
  43. { name: '一覧', to: '/users', visible: false },
  44. { name: 'お前', to: `/users/${ user?.id }`, visible: false },
  45. { name: '設定', to: '/users/settings', visible: Boolean (user) }] }]
  46. useEffect (() => {
  47. const unsubscribe = WikiIdBus.subscribe (setWikiId)
  48. return () => unsubscribe ()
  49. }, [])
  50. useEffect (() => {
  51. setOpenItemIdx (menu.findIndex (item => (
  52. location.pathname.startsWith (item.base || item.to))))
  53. }, [location])
  54. useEffect (() => {
  55. if (!(wikiId))
  56. return
  57. const fetchPostCount = async () => {
  58. try
  59. {
  60. const pageRes = await axios.get (`${ API_BASE_URL }/wiki/${ wikiId }`)
  61. const wikiPage = toCamel (pageRes.data as any, { deep: true }) as WikiPage
  62. const tagRes = await axios.get (`${ API_BASE_URL }/tags/name/${ wikiPage.title }`)
  63. const tag = toCamel (tagRes.data as any, { deep: true }) as Tag
  64. setPostCount (tag.postCount)
  65. }
  66. catch
  67. {
  68. setPostCount (0)
  69. }
  70. }
  71. fetchPostCount ()
  72. }, [wikiId])
  73. return (
  74. <>
  75. <nav className="px-3 flex justify-between items-center w-full min-h-[48px]
  76. bg-yellow-200 dark:bg-red-975 md:bg-yellow-50">
  77. <div className="flex items-center gap-2 h-full">
  78. <Link to="/"
  79. className="mx-4 text-xl font-bold text-pink-600 hover:text-pink-400
  80. dark:text-pink-300 dark:hover:text-pink-100">
  81. ぼざクリ タグ広場
  82. </Link>
  83. {menu.map ((item, i) => (
  84. <Link key={i}
  85. to={item.to}
  86. className={cn ('hidden md:flex h-full items-center',
  87. (location.pathname.startsWith (item.base || item.to)
  88. ? 'bg-yellow-200 dark:bg-red-950 px-4 font-bold'
  89. : 'px-2'))}>
  90. {item.name}
  91. </Link>
  92. ))}
  93. </div>
  94. {user && (
  95. <div className="hidden md:block ml-auto pr-4">
  96. <Link to="/users/settings"
  97. className="font-bold text-red-600 hover:text-red-400
  98. dark:text-yellow-400 dark:hover:text-yellow-200">
  99. {user.name || '名もなきニジラー'}
  100. </Link>
  101. </div>)}
  102. <a href="#"
  103. className="md:hidden ml-auto pr-4
  104. text-pink-600 hover:text-pink-400
  105. dark:text-pink-300 dark:hover:text-pink-100"
  106. onClick={ev => {
  107. ev.preventDefault ()
  108. setMenuOpen (!(menuOpen))
  109. }}>
  110. {menuOpen ? '×' : 'Menu'}
  111. </a>
  112. </nav>
  113. <div className="hidden md:flex bg-yellow-200 dark:bg-red-950
  114. items-center w-full min-h-[40px] px-3">
  115. {menu.find (item => location.pathname.startsWith (item.base || item.to))?.subMenu
  116. .filter (item => item.visible ?? true)
  117. .map ((item, i) => 'component' in item ? item.component : (
  118. <Link key={i}
  119. to={item.to}
  120. className="h-full flex items-center px-3">
  121. {item.name}
  122. </Link>))}
  123. </div>
  124. <div className={cn (menuOpen ? 'flex flex-col md:hidden' : 'hidden',
  125. 'bg-yellow-200 dark:bg-red-975 items-start')}>
  126. <Separator />
  127. {menu.map ((item, i) => (
  128. <>
  129. <a key={i}
  130. href="#"
  131. className={cn ('w-full min-h-[40px] flex items-center pl-8',
  132. i === openItemIdx && 'font-bold bg-yellow-50 dark:bg-red-950')}
  133. onClick={ev => {
  134. ev.preventDefault ()
  135. if (i === openItemIdx)
  136. {
  137. setMenuOpen (false)
  138. navigate (item.to)
  139. }
  140. else
  141. setOpenItemIdx (i)
  142. }}>
  143. {item.name}
  144. </a>
  145. {i === openItemIdx && (
  146. item.subMenu
  147. .filter (subItem => subItem.visible ?? true)
  148. .map ((subItem, j) => 'component' in subItem ? subItem.component : (
  149. <Link key={j}
  150. to={subItem.to}
  151. className="w-full min-h-[36px] flex items-center pl-12
  152. bg-yellow-50 dark:bg-red-950">
  153. {subItem.name}
  154. </Link>)))}
  155. </>))}
  156. {user && (
  157. <>
  158. <Separator />
  159. <Link to="/users/settings"
  160. className="w-full min-h-[40px] flex items-center pl-8
  161. font-bold text-red-600 hover:text-red-400
  162. dark:text-yellow-400 dark:hover:text-yellow-200">
  163. {user.name || '名もなきニジラー'}
  164. </Link>
  165. </>)}
  166. <Separator />
  167. </div>
  168. </>)
  169. }