このコミットが含まれているのは:
2026-04-12 04:43:53 +09:00
コミット 2a4def667c
11個のファイルの変更196行の追加110行の削除
+2
ファイルの表示
@@ -15,6 +15,7 @@ import MaterialDetailPage from '@/pages/materials/MaterialDetailPage'
import MaterialListPage from '@/pages/materials/MaterialListPage'
import MaterialNewPage from '@/pages/materials/MaterialNewPage'
// import MaterialSearchPage from '@/pages/materials/MaterialSearchPage'
import MorePage from '@/pages/MorePage'
import NicoTagListPage from '@/pages/tags/NicoTagListPage'
import NotFound from '@/pages/NotFound'
import TOSPage from '@/pages/TOSPage.mdx'
@@ -72,6 +73,7 @@ const RouteTransitionWrapper = ({ user, setUser }: {
<Route path="/users/settings" element={<SettingPage user={user} setUser={setUser}/>}/>
<Route path="/settings" element={<Navigate to="/users/settings" replace/>}/>
<Route path="/tos" element={<TOSPage/>}/>
<Route path="/more" element={<MorePage/>}/>
<Route path="*" element={<NotFound/>}/>
</Routes>
</AnimatePresence>
+72 -53
ファイルの表示
@@ -14,11 +14,63 @@ import { fetchWikiPage } from '@/lib/wiki'
import type { FC, MouseEvent } from 'react'
import type { Menu, User } from '@/types'
import type { Menu, Tag, User } from '@/types'
type Props = { user: User | null }
export const menuOutline = ({ tag, wikiId, user }: { tag?: Tag | null
wikiId: number | null
user: User | null }): Menu => {
const postCount = tag?.postCount ?? 0
const wikiPageFlg = Boolean (/^\/wiki\/(?!new|changes)[^\/]+/.test (location.pathname) && wikiId)
const wikiTitle = location.pathname.split ('/')[2] ?? ''
return [
{ name: '広場', to: '/posts', subMenu: [
{ name: '一覧', to: '/posts' },
{ name: '検索', to: '/posts/search' },
{ name: '追加', to: '/posts/new' },
{ name: '履歴', to: '/posts/changes' },
{ name: 'ヘルプ', to: '/wiki/ヘルプ:広場' }] },
{ name: 'タグ', to: '/tags', subMenu: [
{ name: 'マスタ', to: '/tags' },
{ name: '別名タグ', to: '/tags/aliases', visible: false },
{ name: '上位タグ', to: '/tags/implications', visible: false },
{ name: 'ニコニコ連携', to: '/tags/nico' },
{ name: 'ヘルプ', to: '/wiki/ヘルプ:タグ' }] },
{ name: '素材', to: '/materials', visible: false, subMenu: [
{ name: '一覧', to: '/materials' },
{ name: '検索', to: '/materials/search', visible: false },
{ name: '追加', to: '/materials/new' },
{ name: '履歴', to: '/materials/changes', visible: false },
{ name: 'ヘルプ', to: '/wiki/ヘルプ:素材集' }] },
{ name: '上映会', to: '/theatres/1', base: '/theatres', subMenu: [
{ name: <>&thinsp;1&thinsp;</>, to: '/theatres/1' },
{ name: 'CyTube', to: '//cytube.mm428.net/r/deernijika' },
{ name: <>&thinsp;1&thinsp;</>,
to: '//www.youtube.com/watch?v=DCU3hL4Uu6A' },
{ name: 'ヘルプ', to: '/wiki/ヘルプ:上映会' }] },
{ name: 'Wiki', to: '/wiki/ヘルプ:ホーム', base: '/wiki', subMenu: [
{ name: '検索', to: '/wiki' },
{ name: '新規', to: '/wiki/new' },
{ name: '全体履歴', to: '/wiki/changes' },
{ name: 'ヘルプ', to: '/wiki/ヘルプ:Wiki' },
{ component: <Separator/>, visible: wikiPageFlg },
{ name: `広場 (${ postCount || 0 })`, to: `/posts?tags=${ wikiTitle }`,
visible: wikiPageFlg },
{ name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg },
{ name: '編輯', to: `/wiki/${ wikiId || wikiTitle }/edit`, visible: wikiPageFlg }] },
{ name: 'ユーザ', to: '/users/settings', visible: false, subMenu: [
{ name: '一覧', to: '/users', visible: false },
{ name: 'お前', to: `/users/${ user?.id }`, visible: false },
{ name: '設定', to: '/users/settings', visible: Boolean (user) }] },
{ name: '法規', visible: false, subMenu: [
{ name: '利用規約', to: '/tos' }] }]
}
export default (({ user }: Props) => {
const location = useLocation ()
@@ -66,51 +118,10 @@ export default (({ user }: Props) => {
queryKey: tagsKeys.show (effectiveTitle),
queryFn: () => fetchTagByName (effectiveTitle) })
const postCount = tag?.postCount ?? 0
const wikiPageFlg = Boolean (/^\/wiki\/(?!new|changes)[^\/]+/.test (location.pathname) && wikiId)
const wikiTitle = location.pathname.split ('/')[2] ?? ''
const menu: Menu = [
{ name: '広場', to: '/posts', subMenu: [
{ name: '一覧', to: '/posts' },
{ name: '検索', to: '/posts/search' },
{ name: '追加', to: '/posts/new' },
{ name: '履歴', to: '/posts/changes' },
{ name: 'ヘルプ', to: '/wiki/ヘルプ:広場' }] },
{ name: 'タグ', to: '/tags', subMenu: [
{ name: 'マスタ', to: '/tags' },
{ name: '別名タグ', to: '/tags/aliases', visible: false },
{ name: '上位タグ', to: '/tags/implications', visible: false },
{ name: 'ニコニコ連携', to: '/tags/nico' },
{ name: 'ヘルプ', to: '/wiki/ヘルプ:タグ' }] },
// { name: '素材', to: '/materials', subMenu: [
// { name: '一覧', to: '/materials' },
// { name: '検索', to: '/materials/search', visible: false },
// { name: '追加', to: '/materials/new' },
// { name: '履歴', to: '/materials/changes', visible: false },
// { name: 'ヘルプ', to: '/wiki/ヘルプ:素材集' }] },
{ name: '上映会', to: '/theatres/1', base: '/theatres', subMenu: [
{ name: <>&thinsp;1&thinsp;</>, to: '/theatres/1' },
{ name: 'CyTube', to: '//cytube.mm428.net/r/deernijika' },
{ name: <>&thinsp;1&thinsp;</>,
to: '//www.youtube.com/watch?v=DCU3hL4Uu6A' },
{ name: 'ヘルプ', to: '/wiki/ヘルプ:上映会' }] },
{ name: 'Wiki', to: '/wiki/ヘルプ:ホーム', base: '/wiki', subMenu: [
{ name: '検索', to: '/wiki' },
{ name: '新規', to: '/wiki/new' },
{ name: '全体履歴', to: '/wiki/changes' },
{ name: 'ヘルプ', to: '/wiki/ヘルプ:Wiki' },
{ component: <Separator/>, visible: wikiPageFlg },
{ name: `広場 (${ postCount || 0 })`, to: `/posts?tags=${ wikiTitle }`,
visible: wikiPageFlg },
{ name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg },
{ name: '編輯', to: `/wiki/${ wikiId || wikiTitle }/edit`, visible: wikiPageFlg }] },
{ name: 'ユーザ', to: '/users/settings', subMenu: [
{ name: '一覧', to: '/users', visible: false },
{ name: 'お前', to: `/users/${ user?.id }`, visible: false },
{ name: '設定', to: '/users/settings', visible: Boolean (user) }] }]
const activeIdx = menu.findIndex (item => location.pathname.startsWith (item.base || item.to))
const menu = menuOutline ({ tag, wikiId, user })
const activeIdx = (menu
.filter (item => item.visible ?? true)
.findIndex (item => location.pathname.startsWith (item.base || item.to!)))
const prevActiveIdxRef = useRef<number> (activeIdx)
@@ -140,8 +151,8 @@ export default (({ user }: Props) => {
useEffect (() => {
setMenuOpen (false)
setOpenItemIdx (menu.findIndex (item => (
location.pathname.startsWith (item.base || item.to))))
setOpenItemIdx (menu.filter (item => item.visible ?? true).findIndex (item => (
location.pathname.startsWith (item.base || item.to!))))
}, [location])
return (
@@ -168,10 +179,10 @@ export default (({ user }: Props) => {
transform: `translateX(${ hl.left }px)`,
opacity: hl.visible ? 1 : 0 }}/>
{menu.map ((item, i) => (
{menu.filter (item => item.visible ?? true).map ((item, i) => (
<PrefetchLink
key={i}
to={item.to}
to={item.to!}
ref={(el: (HTMLAnchorElement | null)) => {
itemsRef.current[i] = el
}}
@@ -180,7 +191,7 @@ export default (({ user }: Props) => {
{item.name}
</PrefetchLink>))}
<PrefetchLink
to="#"
to="/more"
ref={(el: (HTMLAnchorElement | null)) => {
itemsRef.current[menu.length] = el
}}
@@ -262,10 +273,10 @@ export default (({ user }: Props) => {
exit="closed"
transition={{ duration: .2, ease: 'easeOut' }}>
<Separator/>
{menu.map ((item, i) => (
{menu.filter (item => item.visible ?? true).map ((item, i) => (
<Fragment key={i}>
<PrefetchLink
to={i === openItemIdx ? item.to : '#'}
to={i === openItemIdx ? item.to! : '#'}
className={cn ('w-full min-h-[40px] flex items-center pl-8',
((i === openItemIdx)
&& 'font-bold bg-yellow-50 dark:bg-red-950'))}
@@ -315,6 +326,14 @@ export default (({ user }: Props) => {
</motion.div>)}
</AnimatePresence>
</Fragment>))}
<PrefetchLink
to="/more"
ref={(el: (HTMLAnchorElement | null)) => {
itemsRef.current[menu.length] = el
}}
className="w-full min-h-[40px] flex items-center pl-8">
&raquo;
</PrefetchLink>
<TopNavUser user={user} sp/>
<Separator/>
</motion.div>)}
+9 -15
ファイルの表示
@@ -4,8 +4,6 @@ import ReactMarkdown from 'react-markdown'
import remarkGFM from 'remark-gfm'
import PrefetchLink from '@/components/PrefetchLink'
import SectionTitle from '@/components/common/SectionTitle'
import SubsectionTitle from '@/components/common/SubsectionTitle'
import { wikiKeys } from '@/lib/queryKeys'
import remarkWikiAutoLink from '@/lib/remark-wiki-autolink'
import { fetchWikiPages } from '@/lib/wiki'
@@ -16,19 +14,15 @@ import type { Components } from 'react-markdown'
type Props = { title: string
body?: string }
const mdComponents = { h1: ({ children }) => <SectionTitle>{children}</SectionTitle>,
h2: ({ children }) => <SubsectionTitle>{children}</SubsectionTitle>,
ol: ({ children }) => <ol className="list-decimal pl-6">{children}</ol>,
ul: ({ children }) => <ul className="list-disc pl-6">{children}</ul>,
a: (({ href, children }) => (
['/', '.'].some (e => href?.startsWith (e))
? <PrefetchLink to={href!}>{children}</PrefetchLink>
: (
<a href={href}
target="_blank"
rel="noopener noreferrer">
{children}
</a>))) } as const satisfies Components
const mdComponents = { a: (({ href, children }) => (
['/', '.'].some (e => href?.startsWith (e))
? <PrefetchLink to={href!}>{children}</PrefetchLink>
: (
<a href={href}
target="_blank"
rel="noopener noreferrer">
{children}
</a>))) } as const satisfies Components
export default (({ title, body }: Props) => {
+1 -6
ファイルの表示
@@ -1,9 +1,4 @@
import type { MDXComponents } from 'mdx/types'
import PageTitle from '@/components/common/PageTitle'
import SectionTitle from '@/components/common/SectionTitle'
export const useMDXComponents = (): MDXComponents => ({
h1: props => <PageTitle {...props}/>,
h2: props => <SectionTitle {...props}/> })
export const useMDXComponents = (): MDXComponents => ({ })
+42
ファイルの表示
@@ -0,0 +1,42 @@
import { Helmet } from 'react-helmet-async'
import PrefetchLink from '@/components/PrefetchLink'
import { menuOutline } from '@/components/TopNav'
import SectionTitle from '@/components/common/SectionTitle'
import MainArea from '@/components/layout/MainArea'
import { SITE_TITLE } from '@/config'
import type { FC } from 'react'
import type { User } from '@/types'
export default (() => {
const menu = menuOutline ({ tag: null, wikiId: null, user: { } as User })
return (
<MainArea>
<Helmet>
<title>{`メニュー | ${ SITE_TITLE }`}</title>
</Helmet>
{menu.map ((item, i) => (
<section key={i}>
<SectionTitle>{item.name}</SectionTitle>
<ul>
{item.subMenu
.filter (subItem => (subItem.visible ?? true))
.map ((subItem, j) => ('name' in subItem && (
<li key={j}>
<PrefetchLink
to={subItem.to}
target={subItem.to.slice (0, 2) === '//'
? '_blank'
: undefined}>
{subItem.name}
</PrefetchLink>
</li>)))}
</ul>
</section>))}
</MainArea>)
}) satisfies FC
+10 -10
ファイルの表示
@@ -9,7 +9,7 @@ import { dateString } from '@/lib/utils'
<title>{`利用規約 | ${ SITE_TITLE }`}</title>
</Helmet>
<article className="prose mx-auto p-4">
<article className="prose dark:prose-invert mx-auto p-4">
# 利用規約
最終更新日: {dateString ('2026-03-27', 'hour')}
@@ -60,12 +60,12 @@ import { dateString } from '@/lib/utils'
7. 虚偽の情報、誤解を招く情報、出典を偽装した情報、意図的なミスリード、荒らし目的のタグづけ、関係のないタグの大量付与、分類妨碍、検索妨碍その他の品質破壊行為。
{/* 8. 自動化ツール、スクリプト、Bot その他の手段を用いて、運営の許可なく大量投稿、大量編集、大量アクセス、過剰なスクレイピング、過負荷送信を行う行為。 */}
{/* 9. 脆弱性の探索、過度な負荷試験、リバースエンジニアリング、認可回避、BAN 回避、なりすまし、セッション奪取その他の不正アクセスに類する行為。 */}
10. マルウェア、フィッシング、詐欺、誘導広告、悪質なリダイレクト、危険な外部リンクその他利用者または運営に危害を与える行為。
11. 本サービスの趣旨に照らして不相当な政治的扇動、宗教勧誘、商業宣伝、連鎖的勧誘、スパム、同一内容の反復送信。
12. 未成年の安全に反する行為、児童性的搾取、違法または著しく不適切な性的表現、過度に露骨な性表現や残虐表現を、一般公開導線に無警告で流し込む行為。
13. 運営、他の利用者、外部サービスまたは第 3 者に著しい負担、不利益、混乱を生じさせる行為。
14. 前各号のいずれかを試みる行為、教唆する行為、容易にする行為。
15. その他、運営が本サービスの目的または安全な運営に照らして不適切と判断する行為。
8. マルウェア、フィッシング、詐欺、誘導広告、悪質なリダイレクト、危険な外部リンクその他利用者または運営に危害を与える行為。
9. 本サービスの趣旨に照らして不相当な政治的扇動、宗教勧誘、商業宣伝、連鎖的勧誘、スパム、同一内容の反復送信。
10. 未成年の安全に反する行為、児童性的搾取、違法または著しく不適切な性的表現、過度に露骨な性表現や残虐表現を、一般公開導線に無警告で流し込む行為。
11. 運営、他の利用者、外部サービスまたは第 3 者に著しい負担、不利益、混乱を生じさせる行為。
12. 前各号のいずれかを試みる行為、教唆する行為、容易にする行為。
13. その他、運営が本サービスの目的または安全な運営に照らして不適切と判断する行為。
## 第 6 条 投稿、タグ、Wiki 等の取扱い
@@ -103,14 +103,14 @@ import { dateString } from '@/lib/utils'
## 第 10 条 未成年の利用
{/* 1. 未成年者は、法定代理人の同意を得たうえで本サービスを利用しなければなりません。 */}
2. 運営は、未成年の安全確保の観点から、年齢に応じた表示制限、導線制御、非表示化、削除、申請拒否その他の措置を行えます。
3. 利用者は、未成年が閲覧しうる一般公開面において、未成年に不適切な内容を無警告で流し込まないものとします。
1. 運営は、未成年の安全確保の観点から、年齢に応じた表示制限、導線制御、非表示化、削除、申請拒否その他の措置を行えます。
2. 利用者は、未成年が閲覧しうる一般公開面において、未成年に不適切な内容を無警告で流し込まないものとします。
## 第 11 条 お問い合わせ、通報、御意見番
1. 利用者は、本サービスが別途案内する問い合わせ、通報または御意見板の導線を通じて、バグ報告、問題報告、削除要請その他の聯絡を行えます。
{/* 2. 運営は、再現、調査、保守または安全確保のため、操作ログ、画面情報、環境情報その他の関連情報の提出または自動添付を求めることがあります。 */}
3. 運営は、すべての問い合わせに回答する義務を負わず、回答期限、対応結果または対応方法を保証しません。
2. 運営は、すべての問い合わせに回答する義務を負わず、回答期限、対応結果または対応方法を保証しません。
## 第 12 条 免責
+9 -10
ファイルの表示
@@ -7,7 +7,6 @@ import PostList from '@/components/PostList'
import PrefetchLink from '@/components/PrefetchLink'
import TagLink from '@/components/TagLink'
import WikiBody from '@/components/WikiBody'
import PageTitle from '@/components/common/PageTitle'
import TabGroup, { Tab } from '@/components/common/TabGroup'
import MainArea from '@/components/layout/MainArea'
import { SITE_TITLE } from '@/config'
@@ -107,15 +106,15 @@ export default () => {
</PrefetchLink>) : '(最新)'}
</div>)}
<PageTitle>
<TagLink tag={tag ?? defaultTag}
withWiki={false}
withCount={false}
{...(version && { to: `/wiki/${ encodeURIComponent (title) }` })}/>
</PageTitle>
<div className="prose mx-auto p-4">
{loading ? 'Loading...' : <WikiBody title={title} body={wikiPage?.body}/>}
</div>
<article className="prose dark:prose-invert mx-auto p-4">
<h1 className="prose-a:no-underline">
<TagLink tag={tag ?? defaultTag}
withWiki={false}
withCount={false}
{...(version && { to: `/wiki/${ encodeURIComponent (title) }` })}/>
</h1>
{loading ? <div>Loading...</div> : <WikiBody title={title} body={wikiPage?.body}/>}
</article>
{(!(version) && posts.length > 0) && (
<TabGroup>
+11 -5
ファイルの表示
@@ -63,11 +63,17 @@ export type Material = {
export type Menu = MenuItem[]
export type MenuItem = {
name: ReactNode
to: string
base?: string
subMenu: SubMenuItem[] }
export type MenuItem =
| { name: ReactNode
to: string
base?: string
visible?: true
subMenu: SubMenuItem[] }
| { name: ReactNode
to?: string
base?: string
visible: false
subMenu: SubMenuItem[] }
export type NicoTag = Tag & {
category: 'nico'