| @@ -42,6 +42,7 @@ | |||||
| }, | }, | ||||
| "devDependencies": { | "devDependencies": { | ||||
| "@eslint/js": "^9.25.0", | "@eslint/js": "^9.25.0", | ||||
| "@tailwindcss/typography": "^0.5.19", | |||||
| "@types/axios": "^0.14.4", | "@types/axios": "^0.14.4", | ||||
| "@types/markdown-it": "^14.1.2", | "@types/markdown-it": "^14.1.2", | ||||
| "@types/mdx": "^2.0.13", | "@types/mdx": "^2.0.13", | ||||
| @@ -2207,6 +2208,33 @@ | |||||
| "win32" | "win32" | ||||
| ] | ] | ||||
| }, | }, | ||||
| "node_modules/@tailwindcss/typography": { | |||||
| "version": "0.5.19", | |||||
| "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", | |||||
| "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", | |||||
| "dev": true, | |||||
| "license": "MIT", | |||||
| "dependencies": { | |||||
| "postcss-selector-parser": "6.0.10" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" | |||||
| } | |||||
| }, | |||||
| "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { | |||||
| "version": "6.0.10", | |||||
| "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", | |||||
| "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", | |||||
| "dev": true, | |||||
| "license": "MIT", | |||||
| "dependencies": { | |||||
| "cssesc": "^3.0.0", | |||||
| "util-deprecate": "^1.0.2" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">=4" | |||||
| } | |||||
| }, | |||||
| "node_modules/@tanstack/query-core": { | "node_modules/@tanstack/query-core": { | ||||
| "version": "5.90.2", | "version": "5.90.2", | ||||
| "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.2.tgz", | "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.2.tgz", | ||||
| @@ -2863,9 +2891,9 @@ | |||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/axios": { | "node_modules/axios": { | ||||
| "version": "1.14.0", | |||||
| "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", | |||||
| "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", | |||||
| "version": "1.15.0", | |||||
| "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", | |||||
| "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", | |||||
| "license": "MIT", | "license": "MIT", | ||||
| "dependencies": { | "dependencies": { | ||||
| "follow-redirects": "^1.15.11", | "follow-redirects": "^1.15.11", | ||||
| @@ -2904,9 +2932,9 @@ | |||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/brace-expansion": { | "node_modules/brace-expansion": { | ||||
| "version": "1.1.11", | |||||
| "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", | |||||
| "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", | |||||
| "version": "1.1.14", | |||||
| "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", | |||||
| "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", | |||||
| "dev": true, | "dev": true, | ||||
| "license": "MIT", | "license": "MIT", | ||||
| "dependencies": { | "dependencies": { | ||||
| @@ -7756,9 +7784,9 @@ | |||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/vite": { | "node_modules/vite": { | ||||
| "version": "6.4.1", | |||||
| "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", | |||||
| "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", | |||||
| "version": "6.4.2", | |||||
| "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", | |||||
| "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", | |||||
| "dev": true, | "dev": true, | ||||
| "license": "MIT", | "license": "MIT", | ||||
| "dependencies": { | "dependencies": { | ||||
| @@ -44,6 +44,7 @@ | |||||
| }, | }, | ||||
| "devDependencies": { | "devDependencies": { | ||||
| "@eslint/js": "^9.25.0", | "@eslint/js": "^9.25.0", | ||||
| "@tailwindcss/typography": "^0.5.19", | |||||
| "@types/axios": "^0.14.4", | "@types/axios": "^0.14.4", | ||||
| "@types/markdown-it": "^14.1.2", | "@types/markdown-it": "^14.1.2", | ||||
| "@types/mdx": "^2.0.13", | "@types/mdx": "^2.0.13", | ||||
| @@ -15,6 +15,7 @@ import MaterialDetailPage from '@/pages/materials/MaterialDetailPage' | |||||
| import MaterialListPage from '@/pages/materials/MaterialListPage' | import MaterialListPage from '@/pages/materials/MaterialListPage' | ||||
| import MaterialNewPage from '@/pages/materials/MaterialNewPage' | import MaterialNewPage from '@/pages/materials/MaterialNewPage' | ||||
| // import MaterialSearchPage from '@/pages/materials/MaterialSearchPage' | // import MaterialSearchPage from '@/pages/materials/MaterialSearchPage' | ||||
| import MorePage from '@/pages/MorePage' | |||||
| import NicoTagListPage from '@/pages/tags/NicoTagListPage' | import NicoTagListPage from '@/pages/tags/NicoTagListPage' | ||||
| import NotFound from '@/pages/NotFound' | import NotFound from '@/pages/NotFound' | ||||
| import TOSPage from '@/pages/TOSPage.mdx' | 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="/users/settings" element={<SettingPage user={user} setUser={setUser}/>}/> | ||||
| <Route path="/settings" element={<Navigate to="/users/settings" replace/>}/> | <Route path="/settings" element={<Navigate to="/users/settings" replace/>}/> | ||||
| <Route path="/tos" element={<TOSPage/>}/> | <Route path="/tos" element={<TOSPage/>}/> | ||||
| <Route path="/more" element={<MorePage/>}/> | |||||
| <Route path="*" element={<NotFound/>}/> | <Route path="*" element={<NotFound/>}/> | ||||
| </Routes> | </Routes> | ||||
| </AnimatePresence> | </AnimatePresence> | ||||
| @@ -14,11 +14,63 @@ import { fetchWikiPage } from '@/lib/wiki' | |||||
| import type { FC, MouseEvent } from 'react' | import type { FC, MouseEvent } from 'react' | ||||
| import type { Menu, User } from '@/types' | |||||
| import type { Menu, Tag, User } from '@/types' | |||||
| type Props = { user: User | null } | 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: <>第 1 会場</>, to: '/theatres/1' }, | |||||
| { name: 'CyTube', to: '//cytube.mm428.net/r/deernijika' }, | |||||
| { name: <>ニジカ放送局第 1 チャンネル</>, | |||||
| 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) => { | export default (({ user }: Props) => { | ||||
| const location = useLocation () | const location = useLocation () | ||||
| @@ -66,51 +118,10 @@ export default (({ user }: Props) => { | |||||
| queryKey: tagsKeys.show (effectiveTitle), | queryKey: tagsKeys.show (effectiveTitle), | ||||
| queryFn: () => fetchTagByName (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: <>第 1 会場</>, to: '/theatres/1' }, | |||||
| { name: 'CyTube', to: '//cytube.mm428.net/r/deernijika' }, | |||||
| { name: <>ニジカ放送局第 1 チャンネル</>, | |||||
| 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) | const prevActiveIdxRef = useRef<number> (activeIdx) | ||||
| @@ -140,8 +151,8 @@ export default (({ user }: Props) => { | |||||
| useEffect (() => { | useEffect (() => { | ||||
| setMenuOpen (false) | 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]) | }, [location]) | ||||
| return ( | return ( | ||||
| @@ -168,10 +179,10 @@ export default (({ user }: Props) => { | |||||
| transform: `translateX(${ hl.left }px)`, | transform: `translateX(${ hl.left }px)`, | ||||
| opacity: hl.visible ? 1 : 0 }}/> | opacity: hl.visible ? 1 : 0 }}/> | ||||
| {menu.map ((item, i) => ( | |||||
| {menu.filter (item => item.visible ?? true).map ((item, i) => ( | |||||
| <PrefetchLink | <PrefetchLink | ||||
| key={i} | key={i} | ||||
| to={item.to} | |||||
| to={item.to!} | |||||
| ref={(el: (HTMLAnchorElement | null)) => { | ref={(el: (HTMLAnchorElement | null)) => { | ||||
| itemsRef.current[i] = el | itemsRef.current[i] = el | ||||
| }} | }} | ||||
| @@ -180,7 +191,7 @@ export default (({ user }: Props) => { | |||||
| {item.name} | {item.name} | ||||
| </PrefetchLink>))} | </PrefetchLink>))} | ||||
| <PrefetchLink | <PrefetchLink | ||||
| to="#" | |||||
| to="/more" | |||||
| ref={(el: (HTMLAnchorElement | null)) => { | ref={(el: (HTMLAnchorElement | null)) => { | ||||
| itemsRef.current[menu.length] = el | itemsRef.current[menu.length] = el | ||||
| }} | }} | ||||
| @@ -262,10 +273,10 @@ export default (({ user }: Props) => { | |||||
| exit="closed" | exit="closed" | ||||
| transition={{ duration: .2, ease: 'easeOut' }}> | transition={{ duration: .2, ease: 'easeOut' }}> | ||||
| <Separator/> | <Separator/> | ||||
| {menu.map ((item, i) => ( | |||||
| {menu.filter (item => item.visible ?? true).map ((item, i) => ( | |||||
| <Fragment key={i}> | <Fragment key={i}> | ||||
| <PrefetchLink | <PrefetchLink | ||||
| to={i === openItemIdx ? item.to : '#'} | |||||
| to={i === openItemIdx ? item.to! : '#'} | |||||
| className={cn ('w-full min-h-[40px] flex items-center pl-8', | className={cn ('w-full min-h-[40px] flex items-center pl-8', | ||||
| ((i === openItemIdx) | ((i === openItemIdx) | ||||
| && 'font-bold bg-yellow-50 dark:bg-red-950'))} | && 'font-bold bg-yellow-50 dark:bg-red-950'))} | ||||
| @@ -315,6 +326,14 @@ export default (({ user }: Props) => { | |||||
| </motion.div>)} | </motion.div>)} | ||||
| </AnimatePresence> | </AnimatePresence> | ||||
| </Fragment>))} | </Fragment>))} | ||||
| <PrefetchLink | |||||
| to="/more" | |||||
| ref={(el: (HTMLAnchorElement | null)) => { | |||||
| itemsRef.current[menu.length] = el | |||||
| }} | |||||
| className="w-full min-h-[40px] flex items-center pl-8"> | |||||
| その他 » | |||||
| </PrefetchLink> | |||||
| <TopNavUser user={user} sp/> | <TopNavUser user={user} sp/> | ||||
| <Separator/> | <Separator/> | ||||
| </motion.div>)} | </motion.div>)} | ||||
| @@ -4,8 +4,6 @@ import ReactMarkdown from 'react-markdown' | |||||
| import remarkGFM from 'remark-gfm' | import remarkGFM from 'remark-gfm' | ||||
| import PrefetchLink from '@/components/PrefetchLink' | import PrefetchLink from '@/components/PrefetchLink' | ||||
| import SectionTitle from '@/components/common/SectionTitle' | |||||
| import SubsectionTitle from '@/components/common/SubsectionTitle' | |||||
| import { wikiKeys } from '@/lib/queryKeys' | import { wikiKeys } from '@/lib/queryKeys' | ||||
| import remarkWikiAutoLink from '@/lib/remark-wiki-autolink' | import remarkWikiAutoLink from '@/lib/remark-wiki-autolink' | ||||
| import { fetchWikiPages } from '@/lib/wiki' | import { fetchWikiPages } from '@/lib/wiki' | ||||
| @@ -16,19 +14,15 @@ import type { Components } from 'react-markdown' | |||||
| type Props = { title: string | type Props = { title: string | ||||
| body?: 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) => { | export default (({ title, body }: Props) => { | ||||
| @@ -1,9 +1,4 @@ | |||||
| import type { MDXComponents } from 'mdx/types' | 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 => ({ }) | |||||
| @@ -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 | |||||
| @@ -9,7 +9,7 @@ import { dateString } from '@/lib/utils' | |||||
| <title>{`利用規約 | ${ SITE_TITLE }`}</title> | <title>{`利用規約 | ${ SITE_TITLE }`}</title> | ||||
| </Helmet> | </Helmet> | ||||
| <article className="prose mx-auto p-4"> | |||||
| <article className="prose dark:prose-invert mx-auto p-4"> | |||||
| # 利用規約 | # 利用規約 | ||||
| 最終更新日: {dateString ('2026-03-27', 'hour')} | 最終更新日: {dateString ('2026-03-27', 'hour')} | ||||
| @@ -60,12 +60,12 @@ import { dateString } from '@/lib/utils' | |||||
| 7. 虚偽の情報、誤解を招く情報、出典を偽装した情報、意図的なミスリード、荒らし目的のタグづけ、関係のないタグの大量付与、分類妨碍、検索妨碍その他の品質破壊行為。 | 7. 虚偽の情報、誤解を招く情報、出典を偽装した情報、意図的なミスリード、荒らし目的のタグづけ、関係のないタグの大量付与、分類妨碍、検索妨碍その他の品質破壊行為。 | ||||
| {/* 8. 自動化ツール、スクリプト、Bot その他の手段を用いて、運営の許可なく大量投稿、大量編集、大量アクセス、過剰なスクレイピング、過負荷送信を行う行為。 */} | {/* 8. 自動化ツール、スクリプト、Bot その他の手段を用いて、運営の許可なく大量投稿、大量編集、大量アクセス、過剰なスクレイピング、過負荷送信を行う行為。 */} | ||||
| {/* 9. 脆弱性の探索、過度な負荷試験、リバースエンジニアリング、認可回避、BAN 回避、なりすまし、セッション奪取その他の不正アクセスに類する行為。 */} | {/* 9. 脆弱性の探索、過度な負荷試験、リバースエンジニアリング、認可回避、BAN 回避、なりすまし、セッション奪取その他の不正アクセスに類する行為。 */} | ||||
| 10. マルウェア、フィッシング、詐欺、誘導広告、悪質なリダイレクト、危険な外部リンクその他利用者または運営に危害を与える行為。 | |||||
| 11. 本サービスの趣旨に照らして不相当な政治的扇動、宗教勧誘、商業宣伝、連鎖的勧誘、スパム、同一内容の反復送信。 | |||||
| 12. 未成年の安全に反する行為、児童性的搾取、違法または著しく不適切な性的表現、過度に露骨な性表現や残虐表現を、一般公開導線に無警告で流し込む行為。 | |||||
| 13. 運営、他の利用者、外部サービスまたは第 3 者に著しい負担、不利益、混乱を生じさせる行為。 | |||||
| 14. 前各号のいずれかを試みる行為、教唆する行為、容易にする行為。 | |||||
| 15. その他、運営が本サービスの目的または安全な運営に照らして不適切と判断する行為。 | |||||
| 8. マルウェア、フィッシング、詐欺、誘導広告、悪質なリダイレクト、危険な外部リンクその他利用者または運営に危害を与える行為。 | |||||
| 9. 本サービスの趣旨に照らして不相当な政治的扇動、宗教勧誘、商業宣伝、連鎖的勧誘、スパム、同一内容の反復送信。 | |||||
| 10. 未成年の安全に反する行為、児童性的搾取、違法または著しく不適切な性的表現、過度に露骨な性表現や残虐表現を、一般公開導線に無警告で流し込む行為。 | |||||
| 11. 運営、他の利用者、外部サービスまたは第 3 者に著しい負担、不利益、混乱を生じさせる行為。 | |||||
| 12. 前各号のいずれかを試みる行為、教唆する行為、容易にする行為。 | |||||
| 13. その他、運営が本サービスの目的または安全な運営に照らして不適切と判断する行為。 | |||||
| ## 第 6 条 投稿、タグ、Wiki 等の取扱い | ## 第 6 条 投稿、タグ、Wiki 等の取扱い | ||||
| @@ -103,14 +103,14 @@ import { dateString } from '@/lib/utils' | |||||
| ## 第 10 条 未成年の利用 | ## 第 10 条 未成年の利用 | ||||
| {/* 1. 未成年者は、法定代理人の同意を得たうえで本サービスを利用しなければなりません。 */} | {/* 1. 未成年者は、法定代理人の同意を得たうえで本サービスを利用しなければなりません。 */} | ||||
| 2. 運営は、未成年の安全確保の観点から、年齢に応じた表示制限、導線制御、非表示化、削除、申請拒否その他の措置を行えます。 | |||||
| 3. 利用者は、未成年が閲覧しうる一般公開面において、未成年に不適切な内容を無警告で流し込まないものとします。 | |||||
| 1. 運営は、未成年の安全確保の観点から、年齢に応じた表示制限、導線制御、非表示化、削除、申請拒否その他の措置を行えます。 | |||||
| 2. 利用者は、未成年が閲覧しうる一般公開面において、未成年に不適切な内容を無警告で流し込まないものとします。 | |||||
| ## 第 11 条 お問い合わせ、通報、御意見番 | ## 第 11 条 お問い合わせ、通報、御意見番 | ||||
| 1. 利用者は、本サービスが別途案内する問い合わせ、通報または御意見板の導線を通じて、バグ報告、問題報告、削除要請その他の聯絡を行えます。 | 1. 利用者は、本サービスが別途案内する問い合わせ、通報または御意見板の導線を通じて、バグ報告、問題報告、削除要請その他の聯絡を行えます。 | ||||
| {/* 2. 運営は、再現、調査、保守または安全確保のため、操作ログ、画面情報、環境情報その他の関連情報の提出または自動添付を求めることがあります。 */} | {/* 2. 運営は、再現、調査、保守または安全確保のため、操作ログ、画面情報、環境情報その他の関連情報の提出または自動添付を求めることがあります。 */} | ||||
| 3. 運営は、すべての問い合わせに回答する義務を負わず、回答期限、対応結果または対応方法を保証しません。 | |||||
| 2. 運営は、すべての問い合わせに回答する義務を負わず、回答期限、対応結果または対応方法を保証しません。 | |||||
| ## 第 12 条 免責 | ## 第 12 条 免責 | ||||
| @@ -7,7 +7,6 @@ import PostList from '@/components/PostList' | |||||
| import PrefetchLink from '@/components/PrefetchLink' | import PrefetchLink from '@/components/PrefetchLink' | ||||
| import TagLink from '@/components/TagLink' | import TagLink from '@/components/TagLink' | ||||
| import WikiBody from '@/components/WikiBody' | import WikiBody from '@/components/WikiBody' | ||||
| import PageTitle from '@/components/common/PageTitle' | |||||
| import TabGroup, { Tab } from '@/components/common/TabGroup' | import TabGroup, { Tab } from '@/components/common/TabGroup' | ||||
| import MainArea from '@/components/layout/MainArea' | import MainArea from '@/components/layout/MainArea' | ||||
| import { SITE_TITLE } from '@/config' | import { SITE_TITLE } from '@/config' | ||||
| @@ -107,15 +106,15 @@ export default () => { | |||||
| </PrefetchLink>) : '(最新)'} | </PrefetchLink>) : '(最新)'} | ||||
| </div>)} | </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) && ( | {(!(version) && posts.length > 0) && ( | ||||
| <TabGroup> | <TabGroup> | ||||
| @@ -63,11 +63,17 @@ export type Material = { | |||||
| export type Menu = MenuItem[] | 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 & { | export type NicoTag = Tag & { | ||||
| category: 'nico' | category: 'nico' | ||||
| @@ -8,7 +8,7 @@ import { DARK_COLOUR_SHADE, | |||||
| const colours = Object.values (TAG_COLOUR) | const colours = Object.values (TAG_COLOUR) | ||||
| export default { | export default { | ||||
| content: ['./src/**/*.{html,js,ts,jsx,tsx}'], | |||||
| content: ['./src/**/*.{html,js,ts,jsx,tsx,mdx}'], | |||||
| safelist: [...colours.map (c => `text-${ c }-${ LIGHT_COLOUR_SHADE }`), | safelist: [...colours.map (c => `text-${ c }-${ LIGHT_COLOUR_SHADE }`), | ||||
| ...colours.map (c => `hover:text-${ c }-${ LIGHT_COLOUR_SHADE - 200 }`), | ...colours.map (c => `hover:text-${ c }-${ LIGHT_COLOUR_SHADE - 200 }`), | ||||
| ...colours.map (c => `dark:text-${ c }-${ DARK_COLOUR_SHADE }`), | ...colours.map (c => `dark:text-${ c }-${ DARK_COLOUR_SHADE }`), | ||||
| @@ -24,4 +24,4 @@ export default { | |||||
| 'rainbow-scroll': { | 'rainbow-scroll': { | ||||
| '0%': { backgroundPosition: '0% 50%' }, | '0%': { backgroundPosition: '0% 50%' }, | ||||
| '100%': { backgroundPosition: '200% 50%' } } } } }, | '100%': { backgroundPosition: '200% 50%' } } } } }, | ||||
| plugins: [] } satisfies Config | |||||
| plugins: [require ('@tailwindcss/typography')] } satisfies Config | |||||