コミットを比較

..

4 コミット

作成者 SHA1 メッセージ 日付
みてるぞ 9963050546 Merge remote-tracking branch 'origin/main' into feature/095 2026-04-11 23:13:40 +09:00
みてるぞ 09ff99309f #95 2026-03-29 03:34:32 +09:00
みてるぞ e09964818f #95 2026-03-28 19:58:47 +09:00
みてるぞ 39036d1189 #95 2026-03-28 16:07:36 +09:00
21個のファイルの変更259行の追加450行の削除
-1
ファイルの表示
@@ -13,4 +13,3 @@ r2:
secret_access_key: <%= ENV['R2_SECRET_ACCESS_KEY'] %> secret_access_key: <%= ENV['R2_SECRET_ACCESS_KEY'] %>
bucket: <%= ENV['R2_BUCKET'] %> bucket: <%= ENV['R2_BUCKET'] %>
region: auto region: auto
request_checksum_calculation: when_required
生成ファイル
+9 -37
ファイルの表示
@@ -42,7 +42,6 @@
}, },
"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",
@@ -2208,33 +2207,6 @@
"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",
@@ -2891,9 +2863,9 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.15.0", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz",
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.11", "follow-redirects": "^1.15.11",
@@ -2932,9 +2904,9 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.14", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -7784,9 +7756,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.4.2", "version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
-1
ファイルの表示
@@ -44,7 +44,6 @@
}, },
"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",
+6 -11
ファイルの表示
@@ -1,4 +1,4 @@
import { AnimatePresence, LayoutGroup, motion } from 'framer-motion' import { AnimatePresence, LayoutGroup } from 'framer-motion'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { BrowserRouter, import { BrowserRouter,
Navigate, Navigate,
@@ -15,7 +15,6 @@ 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'
@@ -46,6 +45,7 @@ const RouteTransitionWrapper = ({ user, setUser }: {
const location = useLocation () const location = useLocation ()
return ( return (
<LayoutGroup id="gallery-shared">
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
<Routes location={location}> <Routes location={location}>
<Route path="/" element={<Navigate to="/posts" replace/>}/> <Route path="/" element={<Navigate to="/posts" replace/>}/>
@@ -72,10 +72,10 @@ 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>
</LayoutGroup>)
} }
@@ -133,15 +133,10 @@ export default (() => {
<> <>
<RouteBlockerOverlay/> <RouteBlockerOverlay/>
<BrowserRouter> <BrowserRouter>
<LayoutGroup> <div className="flex flex-col h-dvh w-full">
<motion.div
layout="position"
transition={{ layout: { duration: .2, ease: 'easeOut' } }}
className="flex flex-col h-dvh w-full overflow-y-hidden">
<TopNav user={user}/> <TopNav user={user}/>
<RouteTransitionWrapper user={user} setUser={setUser}/> <RouteTransitionWrapper user={user} setUser={setUser}/>
</motion.div> </div>
</LayoutGroup>
<Toaster/> <Toaster/>
</BrowserRouter> </BrowserRouter>
</>) </>)
+1 -1
ファイルの表示
@@ -56,7 +56,7 @@ export default (({ posts, onClick }: Props) => {
cardRef.current.style.zIndex = '' cardRef.current.style.zIndex = ''
cardRef.current.style.position = '' cardRef.current.style.position = ''
}} }}
transition={{ layout: { duration: .2, ease: 'easeOut' } }}> transition={{ type: 'spring', stiffness: 500, damping: 40, mass: .5 }}>
<img src={post.thumbnail || post.thumbnailBase || undefined} <img src={post.thumbnail || post.thumbnailBase || undefined}
alt={post.title || post.url} alt={post.title || post.url}
title={post.title || post.url || undefined} title={post.title || post.url || undefined}
+1 -3
ファイルの表示
@@ -65,9 +65,7 @@ export default (({ posts, onClick }: Props) => {
{CATEGORIES.flatMap (cat => cat in tags ? ( {CATEGORIES.flatMap (cat => cat in tags ? (
tags[cat].map (tag => ( tags[cat].map (tag => (
<li key={tag.id} className="mb-1"> <li key={tag.id} className="mb-1">
<motion.div <motion.div layoutId={`tag-${ tag.id }`}>
transition={{ layout: { duration: .2, ease: 'easeOut' } }}
layoutId={`tag-${ tag.id }`}>
<TagLink tag={tag} onClick={onClick}/> <TagLink tag={tag} onClick={onClick}/>
</motion.div> </motion.div>
</li>))) : [])} </li>))) : [])}
+72 -176
ファイルの表示
@@ -14,65 +14,11 @@ import { fetchWikiPage } from '@/lib/wiki'
import type { FC, MouseEvent } from 'react' import type { FC, MouseEvent } from 'react'
import type { Menu, MenuVisibleItem, Tag, User } from '@/types' import type { Menu, User } from '@/types'
type Props = { user: User | null } type Props = { user: User | null }
export const menuOutline = ({ tag, wikiId, user, pathName }: {
tag?: Tag | null
wikiId: number | null
user: User | null,
pathName: string }): Menu => {
const postCount = tag?.postCount ?? 0
const wikiPageFlg = Boolean (/^\/wiki\/(?!new|changes)[^\/]+/.test (pathName) && wikiId)
const wikiTitle = 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) => { export default (({ user }: Props) => {
const location = useLocation () const location = useLocation ()
@@ -80,9 +26,9 @@ export default (({ user }: Props) => {
const itemsRef = useRef<(HTMLAnchorElement | null)[]> ([]) const itemsRef = useRef<(HTMLAnchorElement | null)[]> ([])
const navRef = useRef<HTMLDivElement | null> (null) const navRef = useRef<HTMLDivElement | null> (null)
const measure = (idx: number) => { const measure = () => {
const nav = navRef.current const nav = navRef.current
const el = itemsRef.current[idx < 0 ? visibleMenu.length : idx] const el = itemsRef.current[activeIdx < 0 ? menu.length : activeIdx]
if (!(nav) || !(el)) if (!(nav) || !(el))
{ {
@@ -103,7 +49,6 @@ export default (({ user }: Props) => {
width: 0, width: 0,
visible: false }) visible: false })
const [menuOpen, setMenuOpen] = useState (false) const [menuOpen, setMenuOpen] = useState (false)
const [moreVsbl, setMoreVsbl] = useState (false)
const [openItemIdx, setOpenItemIdx] = useState (-1) const [openItemIdx, setOpenItemIdx] = useState (-1)
const [wikiId, setWikiId] = useState<number | null> (WikiIdBus.get ()) const [wikiId, setWikiId] = useState<number | null> (WikiIdBus.get ())
@@ -121,10 +66,51 @@ export default (({ user }: Props) => {
queryKey: tagsKeys.show (effectiveTitle), queryKey: tagsKeys.show (effectiveTitle),
queryFn: () => fetchTagByName (effectiveTitle) }) queryFn: () => fetchTagByName (effectiveTitle) })
const menu = menuOutline ({ tag, wikiId, user, pathName: location.pathname }) const postCount = tag?.postCount ?? 0
const visibleMenu = menu.filter ((item): item is MenuVisibleItem => item.visible ?? true)
const activeIdx = const wikiPageFlg = Boolean (/^\/wiki\/(?!new|changes)[^\/]+/.test (location.pathname) && wikiId)
visibleMenu.findIndex (item => location.pathname.startsWith (item.base || item.to)) 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 prevActiveIdxRef = useRef<number> (activeIdx) const prevActiveIdxRef = useRef<number> (activeIdx)
@@ -137,24 +123,25 @@ export default (({ user }: Props) => {
const dir = dirRef.current const dir = dirRef.current
useLayoutEffect (() => { useLayoutEffect (() => {
const raf = requestAnimationFrame (() => measure (moreVsbl ? -1 : activeIdx)) const raf = requestAnimationFrame (measure)
const onResize = () => requestAnimationFrame (() => measure (moreVsbl ? -1 : activeIdx)) const onResize = () => requestAnimationFrame (measure)
addEventListener ('resize', onResize) addEventListener ('resize', onResize)
return () => { return () => {
cancelAnimationFrame (raf) cancelAnimationFrame (raf)
removeEventListener ('resize', onResize) removeEventListener ('resize', onResize)
} }
}) }, [activeIdx])
useEffect (() => { useEffect (() => {
const unsubscribe = WikiIdBus.subscribe (setWikiId) const unsubscribe = WikiIdBus.subscribe (setWikiId)
return () => unsubscribe () return () => unsubscribe ()
}, [activeIdx]) }, [])
useEffect (() => { useEffect (() => {
setMenuOpen (false) setMenuOpen (false)
setOpenItemIdx (activeIdx) setOpenItemIdx (menu.findIndex (item => (
location.pathname.startsWith (item.base || item.to))))
}, [location]) }, [location])
return ( return (
@@ -181,16 +168,9 @@ export default (({ user }: Props) => {
transform: `translateX(${ hl.left }px)`, transform: `translateX(${ hl.left }px)`,
opacity: hl.visible ? 1 : 0 }}/> opacity: hl.visible ? 1 : 0 }}/>
{visibleMenu.map ((item, i) => ( {menu.map ((item, i) => (
<motion.div
key={item.to}
layoutId={`menu-${ item.name }`}
animate={{ opacity: moreVsbl ? 0 : 1 }}
transition={{ opacity: { duration: .12 },
layout: { duration: .2, ease: 'easeOut' } }}
style={{ pointerEvents: moreVsbl ? 'none' : 'auto' }}
onMouseEnter={() => setMoreVsbl (false)}>
<PrefetchLink <PrefetchLink
key={i}
to={item.to} to={item.to}
ref={(el: (HTMLAnchorElement | null)) => { ref={(el: (HTMLAnchorElement | null)) => {
itemsRef.current[i] = el itemsRef.current[i] = el
@@ -198,20 +178,14 @@ export default (({ user }: Props) => {
className={cn ('relative z-10 flex h-full items-center px-5', className={cn ('relative z-10 flex h-full items-center px-5',
(i === openItemIdx) && 'font-bold')}> (i === openItemIdx) && 'font-bold')}>
{item.name} {item.name}
</PrefetchLink> </PrefetchLink>))}
</motion.div>))}
<PrefetchLink <PrefetchLink
to="/more" to="#"
ref={(el: (HTMLAnchorElement | null)) => { ref={(el: (HTMLAnchorElement | null)) => {
itemsRef.current[visibleMenu.length] = el itemsRef.current[menu.length] = el
}}
onClick={() => setMoreVsbl (false)}
onMouseEnter={() => {
setMoreVsbl (true)
measure (-1)
}} }}
className={cn ('relative z-10 flex h-full items-center px-5', className={cn ('relative z-10 flex h-full items-center px-5',
(openItemIdx < 0 || moreVsbl) && 'font-bold')}> (openItemIdx < 0) && 'font-bold')}>
&raquo; &raquo;
</PrefetchLink> </PrefetchLink>
</div> </div>
@@ -232,73 +206,15 @@ export default (({ user }: Props) => {
</nav> </nav>
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
{(menu[activeIdx]?.subMenu ?? []).length > 0 && (
<motion.div <motion.div
key="submenu-shell" key="submenu-shell"
layout
className="relative hidden md:block overflow-hidden className="relative hidden md:block overflow-hidden
bg-yellow-200 dark:bg-red-950" bg-yellow-200 dark:bg-red-950"
style={{ height: moreVsbl ? 40 * menu.length : (activeIdx < 0 ? 0 : 40) }} initial={{ height: 0 }}
onMouseLeave={() => { animate={{ height: 40 }}
if (moreVsbl) exit={{ height: 0 }}
setMoreVsbl (false) transition={{ duration: .2, ease: 'easeOut' }}>
}}
transition={{ layout: { duration: .2, ease: 'easeOut' } }}
onAnimationComplete={() => {
measure (moreVsbl ? -1 : activeIdx)
}}>
{moreVsbl
? (
menu.map ((item, i) => (
<div key={i} className="relative h-[40px]">
<div className="absolute inset-0 flex items-center px-3">
<motion.div
transition={{ duration: .2, ease: 'easeOut' }}
{...((item.visible ?? true)
? { layoutId: `menu-${ item.name }` }
: { initial: { x: 40, y: -40, opacity: 0 },
animate: { x: 0, y: 0, opacity: 1 },
exit: { x: 40, y: -40, opacity: 0 } })}
className="z-10 h-full flex items-center px-3 font-bold w-24">
<h2>{item.name}</h2>
</motion.div>
{item.subMenu
.filter (subItem => subItem.visible ?? true)
.map ((subItem, j) => (
'component' in subItem
? (
<motion.div
key={`c-${ i }-${ j }`}
transition={{ duration: .2, ease: 'easeOut' }}
{...((visibleMenu[activeIdx]?.name
=== item.name)
? { layoutId: `submenu-${ item.name }-${ j }` }
: { initial: { y: -40, opacity: 0 },
animate: { y: 0, opacity: 1 },
exit: { y: -40, opacity: 0 } })}>
{subItem.component}
</motion.div>)
: (
<motion.div
key={`l-${ i }-${ j }`}
transition={{ duration: .2, ease: 'easeOut' }}
{...((visibleMenu[activeIdx]?.name
=== item.name)
? { layoutId: `submenu-${ item.name }-${ j }` }
: { initial: { y: -40, opacity: 0 },
animate: { y: 0, opacity: 1 },
exit: { y: -40, opacity: 0 } })}>
<PrefetchLink
to={subItem.to}
target={subItem.to.slice (0, 2) === '//' ? '_blank' : undefined}
onClick={() => setMoreVsbl (false)}
className="h-full flex items-center px-3">
{subItem.name}
</PrefetchLink>
</motion.div>)))}
</div>
</div>)))
: ((visibleMenu[activeIdx]?.subMenu ?? []).length > 0
&& (
<div className="relative h-[40px]"> <div className="relative h-[40px]">
<AnimatePresence initial={false} custom={dir}> <AnimatePresence initial={false} custom={dir}>
<motion.div <motion.div
@@ -312,33 +228,23 @@ export default (({ user }: Props) => {
animate="centre" animate="centre"
exit="exit" exit="exit"
transition={{ duration: .2, ease: 'easeOut' }}> transition={{ duration: .2, ease: 'easeOut' }}>
{(visibleMenu[activeIdx]?.subMenu ?? []) {(menu[activeIdx]?.subMenu ?? [])
.filter (item => item.visible ?? true) .filter (item => item.visible ?? true)
.map ((item, i) => ( .map ((item, i) => (
'component' in item 'component' in item
? ( ? <Fragment key={`c-${ i }`}>{item.component}</Fragment>
<motion.div
key={`c-${ i }`}
transition={{ layout: { duration: .2, ease: 'easeOut' } }}
layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
{item.component}
</motion.div>)
: ( : (
<motion.div
key={`l-${ i }`}
transition={{ layout: { duration: .2, ease: 'easeOut' } }}
layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
<PrefetchLink <PrefetchLink
key={`l-${ i }`}
to={item.to} to={item.to}
target={item.to.slice (0, 2) === '//' ? '_blank' : undefined} target={item.to.slice (0, 2) === '//' ? '_blank' : undefined}
className="h-full flex items-center px-3"> className="h-full flex items-center px-3">
{item.name} {item.name}
</PrefetchLink> </PrefetchLink>)))}
</motion.div>)))}
</motion.div> </motion.div>
</AnimatePresence> </AnimatePresence>
</div>))} </div>
</motion.div> </motion.div>)}
</AnimatePresence> </AnimatePresence>
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
@@ -356,7 +262,7 @@ export default (({ user }: Props) => {
exit="closed" exit="closed"
transition={{ duration: .2, ease: 'easeOut' }}> transition={{ duration: .2, ease: 'easeOut' }}>
<Separator/> <Separator/>
{visibleMenu.map ((item, i) => ( {menu.map ((item, i) => (
<Fragment key={i}> <Fragment key={i}>
<PrefetchLink <PrefetchLink
to={i === openItemIdx ? item.to : '#'} to={i === openItemIdx ? item.to : '#'}
@@ -409,16 +315,6 @@ export default (({ user }: Props) => {
</motion.div>)} </motion.div>)}
</AnimatePresence> </AnimatePresence>
</Fragment>))} </Fragment>))}
<PrefetchLink
to="/more"
ref={(el: (HTMLAnchorElement | null)) => {
itemsRef.current[visibleMenu.length] = el
}}
className={cn ('w-full min-h-[40px] flex items-center pl-8',
((openItemIdx < 0)
&& 'font-bold bg-yellow-50 dark:bg-red-950'))}>
&raquo;
</PrefetchLink>
<TopNavUser user={user} sp/> <TopNavUser user={user} sp/>
<Separator/> <Separator/>
</motion.div>)} </motion.div>)}
+7 -1
ファイルの表示
@@ -4,6 +4,8 @@ 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'
@@ -14,7 +16,11 @@ import type { Components } from 'react-markdown'
type Props = { title: string type Props = { title: string
body?: string } body?: string }
const mdComponents = { a: (({ href, children }) => ( 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)) ['/', '.'].some (e => href?.startsWith (e))
? <PrefetchLink to={href!}>{children}</PrefetchLink> ? <PrefetchLink to={href!}>{children}</PrefetchLink>
: ( : (
+5 -7
ファイルの表示
@@ -1,11 +1,9 @@
import { cn } from '@/lib/utils' import React from 'react'
import type { ComponentPropsWithoutRef, FC } from 'react' type Props = { children: React.ReactNode }
type Props = ComponentPropsWithoutRef<'h2'>
export default (({ children, className, ...rest }: Props) => ( export default ({ children }: Props) => (
<h2 {...rest} className={cn ('text-xl my-4', className)}> <h2 className="text-xl my-4">
{children} {children}
</h2>)) satisfies FC<Props> </h2>)
+3 -7
ファイルの表示
@@ -1,5 +1,3 @@
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import type { FC, ReactNode } from 'react' import type { FC, ReactNode } from 'react'
@@ -10,9 +8,7 @@ type Props = {
export default (({ children, className }: Props) => ( export default (({ children, className }: Props) => (
<motion.main <main className={cn ('flex-1 overflow-y-auto p-4 md:h-[calc(100dvh-88px)]',
transition={{ layout: { duration: .2, ease: 'easeOut' } }} className)}>
className={cn ('flex-1 overflow-y-auto p-4', className)}
layout="position">
{children} {children}
</motion.main>)) satisfies FC<Props> </main>)) satisfies FC<Props>
+5 -6
ファイルの表示
@@ -1,4 +1,3 @@
import { motion } from 'framer-motion'
import { Helmet } from 'react-helmet-async' import { Helmet } from 'react-helmet-async'
import type { FC, ReactNode } from 'react' import type { FC, ReactNode } from 'react'
@@ -7,10 +6,10 @@ type Props = { children: ReactNode }
export default (({ children }: Props) => ( export default (({ children }: Props) => (
<motion.div <div
layout="position" className="p-4 w-full md:w-64 md:h-full
transition={{ layout: { duration: .2, ease: 'easeOut' } }} md:h-[calc(100dvh-88px)] md:overflow-y-auto
className="p-4 w-full md:w-64 md:h-full md:overflow-y-auto sidebar"> sidebar">
<Helmet> <Helmet>
<style> <style>
{` {`
@@ -27,4 +26,4 @@ export default (({ children }: Props) => (
</Helmet> </Helmet>
{children} {children}
</motion.div>)) satisfies FC<Props> </div>)) satisfies FC<Props>
+6 -1
ファイルの表示
@@ -1,4 +1,9 @@
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 => ({ })
export const useMDXComponents = (): MDXComponents => ({
h1: props => <PageTitle {...props}/>,
h2: props => <SectionTitle {...props}/> })
-46
ファイルの表示
@@ -1,46 +0,0 @@
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, pathName: location.pathname })
return (
<MainArea className="md:flex">
<Helmet>
<title>{`メニュー | ${ SITE_TITLE }`}</title>
</Helmet>
{[...Array (Math.ceil (menu.length / 4)).keys ()].map (i => (
<div key={i} className="flex-1 mx-16">
{menu.slice (4 * i, 4 * (i + 1)).map ((item, j) => (
<section key={j}>
<SectionTitle className="font-bold">{item.name}</SectionTitle>
<ul>
{item.subMenu
.filter (subItem => (subItem.visible ?? true))
.map ((subItem, k) => ('name' in subItem && (
<li key={k}>
<PrefetchLink
to={subItem.to}
target={subItem.to.slice (0, 2) === '//'
? '_blank'
: undefined}>
{subItem.name}
</PrefetchLink>
</li>)))}
</ul>
</section>))}
</div>))}
</MainArea>)
}) satisfies FC
+41 -39
ファイルの表示
@@ -3,37 +3,35 @@ import MainArea from '@/components/layout/MainArea'
import { SITE_TITLE } from '@/config' import { SITE_TITLE } from '@/config'
import { dateString } from '@/lib/utils' import { dateString } from '@/lib/utils'
export const lastUpdatedAt = dateString ('2026-04-12', 'hour')
<MainArea> <MainArea>
<Helmet> <Helmet>
<title>{`利用規約 | ${ SITE_TITLE }`}</title> <title>{`利用規約 | ${ SITE_TITLE }`}</title>
</Helmet> </Helmet>
<article className="prose dark:prose-invert mx-auto p-4"> <article className="prose mx-auto p-4">
# 利用規約 # 利用規約
最終更新日: {lastUpdatedAt} 最終更新日: {dateString ('2026-03-27', 'hour')}
この利用規約(以下「本規約」)は、ぼざクリ タグ広場(以下「本サービス」)の利用条件を定めるものです。利用者は、本サービスを利用した時点で、本規約に同意したものとみなされます。 この利用規約(以下「本規約」)は、ぼざクリ タグ広場(以下「本サービス」)の利用条件を定めるものです。利用者は、本サービスを利用した時点で、本規約に同意したものとみなされます。
## 第 1 条 本サービスの位置づけ ## 第 1 条 本サービスの位置づけ
1. 本サービスは、タグ・Wiki・外部リンクの整理を中心とする知識共有基盤です。 1. 本サービスは、タグ・Wiki・外部リンクの整理を中心とする知識共有基盤です。
2. 本サービスの中心価値は、コンテンツそのものの再配布ではなく、タグを軸にした整理、検索、再発見、および周辺知識の蓄積にあります。 2. 本サービスの中心価値は、コンテンツそのものの再配布ではなく、タグを軸にした整理、検索、再発見、および周辺知識の蓄積にあります。
3. 本サービスは、運営上の必要に応じて、機能、公開範囲、名称、URL、表示内容その他の仕様を変更することがあります。 3. 本サービスは、運営上の必要に応じて、機能、公開範囲、名称、URL、表示内容その他の仕様を変更することがあります。
## 第 2 条 公開方針と利用者区分 ## 第 2 条 公開方針と利用者区分
1. 本サービスは、初回一般公開時点では、**誰でも閲覧できる一方で、投稿・編輯は申請制** とします。 1. 本サービスは、初回一般公開時点では、**誰でも閲覧できる一方で、投稿・編輯は申請制** とします。
2. 初回一般公開時点では、通常の農奴は閲覧のみを行えます。 2. 初回一般公開時点では、通常の農奴は閲覧のみを行えます。
3. 投稿、タグ編輯、Wiki 編輯その他の耕作行為は、運営が承認した利用者(以下「耕作員」)に限って認めます。 3. 投稿、タグ編輯、Wiki 編輯その他の耕作行為は、運営が承認した利用者(以下「耕作員」)に限って認めます。
4. 独裁者は、耕作員に加えて、差、削除、利用制限、その他の管理操作を行えます。 4. 独裁者は、耕作員に加えて、差し戻し、削除、利用制限、その他の管理操作を行えます。
5. 運営は、履歴管理、差、BAN 運用、監査導線その他の運営装備がじゅうぶんに整ったと判断した場合、農奴に一部の編輯権限を開放することがあります。 5. 運営は、履歴管理、差し戻し、BAN 運用、監査導線その他の運営装備がじゅうぶんに整ったと判断した場合、農奴に一部の編輯権限を開放することがあります。
6. 利用者区分、権限範囲、申請条件、承認基準、承認後の取扱いは、運営が必要に応じて定め、変更できます。 6. 利用者区分、権限範囲、申請条件、承認基準、承認後の取扱いは、運営が必要に応じて定め、変更できます。
## 第 3 条 利用開始と引継ぎコード ## 第 3 条 利用開始と引継ぎコード
1. 本サービスでは、一般的な Id. / パスワード方式ではなく、運営が別途定める認証情報または引継ぎコードを用いる場合があります。 1. 本サービスでは、一般的な Id. / パスワード方式ではなく、運営が別途定める認証情報または引継ぎコードを用いる場合があります。
2. 利用者は、自身に割り当てられた引継ぎコード、認証情報、端末上の保存情報を自己の責任で管理するものとします。 2. 利用者は、自身に割り当てられた引継ぎコード、認証情報、端末上の保存情報を自己の責任で管理するものとします。
@@ -41,15 +39,15 @@ export const lastUpdatedAt = dateString ('2026-04-12', 'hour')
4. 引継ぎコードの漏洩、第 3 者利用、紛失、盗用その他の事故によって利用者または第 3 者に生じた損害について、運営は責任を負いません。 4. 引継ぎコードの漏洩、第 3 者利用、紛失、盗用その他の事故によって利用者または第 3 者に生じた損害について、運営は責任を負いません。
5. 運営は、本人確認、濫用対策、監査対応または保守のため、利用情報とアクセス元情報を関聯づけて扱うことがあります。 5. 運営は、本人確認、濫用対策、監査対応または保守のため、利用情報とアクセス元情報を関聯づけて扱うことがあります。
## 第 4 条 申請制編輯の基本ルール ## 第 4 条 申請制編輯の基本ルール
1. 耕作員は、タグ整理基盤の品質維持を最優先し、個人的な所有主張ではなく、検索性、再利用性、可読性、整合性を重視して編輯しなければなりません。 1. 耕作員は、タグ整理基盤の品質維持を最優先し、個人的な所有主張ではなく、検索性、再利用性、可読性、整合性を重視して編輯しなければなりません。
2. 耕作員は、主観的な好悪、内輪ネタ、報復、私怨、対立誘導のためにタグや Wiki を操作してはなりません。 2. 耕作員は、主観的な好悪、内輪ネタ、報復、私怨、対立誘導のためにタグや Wiki を操作してはなりません。
3. 耕作員は、誤りの修正、体系の整理、リンクの保守、知識の補足を目的として編輯を行うものとします。 3. 耕作員は、誤りの修正、体系の整理、リンクの保守、知識の補足を目的として編輯を行うものとします。
4. 運営は、申請内容、過去の行動、編輯品質、聯絡可能性、運営負荷その他の事情を考慮して、承認、保留、拒否、取消を行えます。 4. 運営は、申請内容、過去の行動、編輯品質、聯絡可能性、運営負荷その他の事情を考慮して、承認、保留、拒否、取消を行えます。
5. 耕作員資格は権利ではなく、運営が本サービスの維持のために付与する可撤回の権限です。 5. 耕作員資格は権利ではなく、運営が本サービスの維持のために付与する可撤回の権限です。
## 第 5 条 禁止事項 ## 第 5 条 禁止事項
利用者は、以下の行為をしてはなりません。 利用者は、以下の行為をしてはなりません。
@@ -60,75 +58,79 @@ export const lastUpdatedAt = dateString ('2026-04-12', 'hour')
5. 実在人物に関する名誉毀損、侮辱、差別、脅迫、晒し、つきまとい、嫌がらせ、私刑の扇動その他の加害行為。 5. 実在人物に関する名誉毀損、侮辱、差別、脅迫、晒し、つきまとい、嫌がらせ、私刑の扇動その他の加害行為。
6. 個人情報、非公開情報、秘匿されるべき情報を本人の承諾なく掲載、送信、共有、推測可能な形で開示する行為。 6. 個人情報、非公開情報、秘匿されるべき情報を本人の承諾なく掲載、送信、共有、推測可能な形で開示する行為。
7. 虚偽の情報、誤解を招く情報、出典を偽装した情報、意図的なミスリード、荒らし目的のタグづけ、関係のないタグの大量付与、分類妨碍、検索妨碍その他の品質破壊行為。 7. 虚偽の情報、誤解を招く情報、出典を偽装した情報、意図的なミスリード、荒らし目的のタグづけ、関係のないタグの大量付与、分類妨碍、検索妨碍その他の品質破壊行為。
8. マルウェア、フィッシング、詐欺、誘導広告、悪質なリダイレクト、危険な外部リンクその他利用者または運営に危害を与える行為。 {/* 8. 自動化ツール、スクリプト、Bot その他の手段を用いて、運営の許可なく大量投稿、大量編集、大量アクセス、過剰なスクレイピング、過負荷送信を行う行為。 */}
9. 本サービスの趣旨に照らして不相当な政治的扇動、宗教勧誘、商業宣伝、連鎖的勧誘、スパム、同一内容の反復送信。 {/* 9. 脆弱性の探索、過度な負荷試験、リバースエンジニアリング、認可回避、BAN 回避、なりすまし、セッション奪取その他の不正アクセスに類する行為。 */}
10. 未成年の安全に反する行為、児童性的搾取、違法または著しく不適切な性的表現、過度に露骨な性表現や残虐表現を、一般公開導線に無警告で流し込む行為。 10. マルウェア、フィッシング、詐欺、誘導広告、悪質なリダイレクト、危険な外部リンクその他利用者または運営に危害を与える行為。
11. 運営、他の利用者、外部サービスまたは第 3 者に著しい負担、不利益、混乱を生じさせる行為 11. 本サービスの趣旨に照らして不相当な政治的扇動、宗教勧誘、商業宣伝、連鎖的勧誘、スパム、同一内容の反復送信
12. 前各号のいずれかを試みる行為、教唆する行為、容易にする行為。 12. 未成年の安全に反する行為、児童性的搾取、違法または著しく不適切な性的表現、過度に露骨な性表現や残虐表現を、一般公開導線に無警告で流し込む行為。
13. その他、運営が本サービスの目的または安全な運営に照らして不適切と判断する行為。 13. 運営、他の利用者、外部サービスまたは第 3 者に著しい負担、不利益、混乱を生じさせる行為。
14. 前各号のいずれかを試みる行為、教唆する行為、容易にする行為。
15. その他、運営が本サービスの目的または安全な運営に照らして不適切と判断する行為。
## 第 6 条 投稿、タグ、Wiki 等の取扱い ## 第 6 条 投稿、タグ、Wiki 等の取扱い
1. 利用者は、自らが投稿、編輯、登録、送信または変更する情報について、必要な権利を有し、または適法に利用できる状態でなければなりません。 1. 利用者は、自らが投稿、編輯、登録、送信または変更する情報について、必要な権利を有し、または適法に利用できる状態でなければなりません。
2. 利用者は、自らが行った投稿、タグづけ、Wiki 編輯、説明文、コメント、関聯づけその他の行為について責任を負います。 2. 利用者は、自らが行った投稿、タグづけ、Wiki 編輯、説明文、コメント、関聯づけその他の行為について責任を負います。
3. 利用者は、運営に対し、本サービスの運営、表示、複製、保存、配信、整形、引用、履歴表示、差、バックアップ、障碍対応および弘報のために必要な範囲で、当該利用者生成情報を無償で利用する非独占的な権利を許諾するものとします。 3. 利用者は、運営に対し、本サービスの運営、表示、複製、保存、配信、整形、引用、履歴表示、差し戻し、バックアップ、障碍対応および弘報のために必要な範囲で、当該利用者生成情報を無償で利用する非独占的な権利を許諾するものとします。
4. 前項の許諾は、本サービスの運営上必要な範囲に限られ、利用者の権利帰属自体を運営へ移転するものではありません。 4. 前項の許諾は、本サービスの運営上必要な範囲に限られ、利用者の権利帰属自体を運営へ移転するものではありません。
5. 運営は、分類整合性、表記統一、誤記修正、別名統合、差その他の理由により、投稿、タグ、Wiki その他の内容を編輯、非表示化、削除、統合、分割または凍結できます。 5. 運営は、分類整合性、表記統一、誤記修正、別名統合、差し戻しその他の理由により、投稿、タグ、Wiki その他の内容を編輯、非表示化、削除、統合、分割または凍結できます。
## 第 7 条 外部リンクと埋め込み ## 第 7 条 外部リンクと埋め込み
1. 本サービスは、外部サイトへのリンク、外部コンテンツの埋め込みまたはそれらに関するメタデータを表示する場合があります。 1. 本サービスは、外部サイトへのリンク、外部コンテンツの埋め込みまたはそれらに関するメタデータを表示する場合があります。
2. 外部リンク先または埋め込み先の権利、利用条件、公開範囲、削除方針、広告、追跡、Cookie その他の取扱いは、当該外部サービスの定めに従います。 2. 外部リンク先または埋め込み先の権利、利用条件、公開範囲、削除方針、広告、追跡、Cookie その他の取扱いは、当該外部サービスの定めに従います。
3. 運営は、外部リンク先の適法性、安全性、継続性、正確性、品質、可用性、または内容の完全性を保証しません。 3. 運営は、外部リンク先の適法性、安全性、継続性、正確性、品質、可用性、または内容の完全性を保証しません。
4. 外部権利者からの申立て、運営判断、法令対応または安全性確保のため、運営は外部リンク、埋め込み、サムネイル、説明文その他の表示を制限、差替え、非表示または削除できます。 4. 外部権利者からの申立て、運営判断、法令対応または安全性確保のため、運営は外部リンク、埋め込み、サムネイル、説明文その他の表示を制限、差替え、非表示または削除できます。
## 第 8 条 履歴、差、削除 ## 第 8 条 履歴、差し戻し、削除
1. 本サービスでは、保守、監査、荒らし対策、説明責任その他の目的で、投稿、タグ、Wiki その他の変更履歴を保持し、表示し、または内部的に参照することがあります。 1. 本サービスでは、保守、監査、荒らし対策、説明責任その他の目的で、投稿、タグ、Wiki その他の変更履歴を保持し、表示し、または内部的に参照することがあります。
2. 利用者は、一度行った編輯が、後に差、修正、非表示化または削除されることがあることをあらかじめ承諾するものとします。 2. 利用者は、一度行った編輯が、後に差し戻し、修正、非表示化または削除されることがあることをあらかじめ承諾するものとします。
3. 利用者が削除を希望した場合でも、法令上、保守上、監査上、紛争対応上またはバックアップ上の必要により、直ちに完全消去できないことがあります。 3. 利用者が削除を希望した場合でも、法令上、保守上、監査上、紛争対応上またはバックアップ上の必要により、直ちに完全消去できないことがあります。
4. 運営は、本サービス全体の健全性を維持するため、説明の有無を問わず、履歴の表示範囲、保存期間、差方針、削除方針を定め、変更できます。 4. 運営は、本サービス全体の健全性を維持するため、説明の有無を問わず、履歴の表示範囲、保存期間、差し戻し方針、削除方針を定め、変更できます。
## 第 9 条 利用制限、資格取消、BAN ## 第 9 条 利用制限、資格取消、BAN
1. 運営は、利用者が次のいずれかに該当すると判断した場合、事前の通知なく、または通知後に、投稿・編輯の制限、耕作員資格の取消、コンテンツの非表示または削除、引継ぎコードの失効、ユーザ BAN、IP BAN その他必要な措置を行えます。 1. 運営は、利用者が次のいずれかに該当すると判断した場合、事前の通知なく、または通知後に、投稿・編輯の制限、耕作員資格の取消、コンテンツの非表示または削除、引継ぎコードの失効、ユーザ BAN、IP BAN その他必要な措置を行えます。
- 本規約に違反した場合 - 本規約に違反した場合
- 本サービスの趣旨に反する運用妨、荒らし、品質破壊行為を行った場合 - 本サービスの趣旨に反する運用妨、荒らし、品質破壊行為を行った場合
- 運営からの確認、修正要請、停止要請に合理的理由なく応じない場合 - 運営からの確認、修正要請、停止要請に合理的理由なく応じない場合
- 登録情報、申請内容または説明に虚偽がある場合 - 登録情報、申請内容または説明に虚偽がある場合
- 安全性、法令順守、運営継続の観点から措置が必要と判断された場合 - 安全性、法令順守、運営継続の観点から措置が必要と判断された場合
2. 運営は、前項の措置について、その理由、基準、証拠または内部判断過程を常に開示する義務を負いません。 2. 運営は、前項の措置について、その理由、基準、証拠または内部判断過程を常に開示する義務を負いません。
3. 利用制限または資格取消後も、運営は、必要に応じて履歴、ログ、申請記録、通報記録その他のデータを保持できます。 3. 利用制限または資格取消後も、運営は、必要に応じて履歴、ログ、申請記録、通報記録その他のデータを保持できます。
## 第 10 条 未成年の利用 ## 第 10 条 未成年の利用
1. 運営は、未成年の安全確保の観点から、年齢に応じた表示制限、導線制御、非表示化、削除、申請拒否その他の措置を行えます。 {/* 1. 未成年者は、法定代理人の同意を得たうえで本サービスを利用しなければなりません。 */}
2. 利用者は、未成年が閲覧しうる一般公開面において、未成年に不適切な内容を無警告で流し込まないものとします。 2. 運営は、未成年の安全確保の観点から、年齢に応じた表示制限、導線制御、非表示化、削除、申請拒否その他の措置を行えます。
3. 利用者は、未成年が閲覧しうる一般公開面において、未成年に不適切な内容を無警告で流し込まないものとします。
## 第 11 条 お問い合わせ、通報、御意見番 ## 第 11 条 お問い合わせ、通報、御意見番
1. 利用者は、本サービスが別途案内する問い合わせ、通報または御意見板の導線を通じて、バグ報告、問題報告、削除要請その他の聯絡を行えます。 1. 利用者は、本サービスが別途案内する問い合わせ、通報または御意見板の導線を通じて、バグ報告、問題報告、削除要請その他の聯絡を行えます。
2. 運営は、すべての問い合わせに回答する義務を負わず、回答期限、対応結果または対応方法を保証しません。 {/* 2. 運営は、再現、調査、保守または安全確保のため、操作ログ、画面情報、環境情報その他の関連情報の提出または自動添付を求めることがあります。 */}
3. 運営は、すべての問い合わせに回答する義務を負わず、回答期限、対応結果または対応方法を保証しません。
## 第 12 条 免責 ## 第 12 条 免責
1. 運営は、本サービスについて、特定目的適合性、完全性、正確性、継続性、安全性、無瑕疵性、または利用者の期待への適合を保証しません。 1. 運営は、本サービスについて、特定目的適合性、完全性、正確性、継続性、安全性、無瑕疵性、または利用者の期待への適合を保証しません。
2. 運営は、外部リンク先、外部埋め込み先、第 3 者投稿、利用者同士の紛争、通信障碍、データ消失、誤分類、誤リンク、誤記、差、機能停止または仕様変更によって生じた損害について、責任を負いません。 2. 運営は、外部リンク先、外部埋め込み先、第 3 者投稿、利用者同士の紛争、通信障碍、データ消失、誤分類、誤リンク、誤記、差し戻し、機能停止または仕様変更によって生じた損害について、責任を負いません。
3. 本サービスは、予告なく停止、終了、変更または縮小されることがあります。 3. 本サービスは、予告なく停止、終了、変更または縮小されることがあります。
## 第 13 条 規約の変更 ## 第 13 条 規約の変更
1. 運営は、法令改正、機能追加、運用方針の変更、安全対策、表現調整その他の理由により、本規約を変更できます。 1. 運営は、法令改正、機能追加、運用方針の変更、安全対策、表現調整その他の理由により、本規約を変更できます。
2. 変更後の本規約は、本サービス上に掲載された時点または運営が別途定める時点から効力を生じます。 2. 変更後の本規約は、本サービス上に掲載された時点または運営が別途定める時点から効力を生じます。
3. 変更後に利用を継続した利用者は、変更後の本規約に同意したものとみなされます。 3. 変更後に利用を継続した利用者は、変更後の本規約に同意したものとみなされます。
## 第 14 条 準拠法および管轄 ## 第 14 条 準拠法および管轄
1. 本規約および本サービスの利用には、日本法を準拠法とします。 1. 本規約および本サービスの利用には、日本法を準拠法とします。
2. 本規約または本サービスに関して生じた一切の紛争については、運営の所在地を管轄する裁判所を第 1 審の専属的合意管轄裁判所とします。ただし、法令に別段の定めがある場合はこの限りではありません。 2. 本規約または本サービスに関して生じた一切の紛争については、運営の所在地を管轄する裁判所を第 1 審の専属的合意管轄裁判所とします。ただし、法令に別段の定めがある場合はこの限りではありません。
## 附則 ## 附則
本規約は、{lastUpdatedAt} から適用します。 本規約は、{dateString ('2026-03-27', 'hour')} から適用します。
</article> </article>
</MainArea> </MainArea>
+2 -1
ファイルの表示
@@ -6,7 +6,8 @@ import type { FC } from 'react'
export default (() => ( export default (() => (
<div className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden"> <div className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden
md:h-[calc(100dvh-88px)]">
<MaterialSidebar/> <MaterialSidebar/>
<Outlet/> <Outlet/>
</div>)) satisfies FC </div>)) satisfies FC
+2 -1
ファイルの表示
@@ -93,7 +93,8 @@ export default (({ user }: Props) => {
: 'bg-gray-500 hover:bg-gray-600') : 'bg-gray-500 hover:bg-gray-600')
return ( return (
<div className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden"> <div className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden
md:h-[calc(100dvh-88px)]">
<Helmet> <Helmet>
{(post?.thumbnail || post?.thumbnailBase) && ( {(post?.thumbnail || post?.thumbnailBase) && (
<meta name="thumbnail" content={post.thumbnail || post.thumbnailBase}/>)} <meta name="thumbnail" content={post.thumbnail || post.thumbnailBase}/>)}
+2 -1
ファイルの表示
@@ -70,7 +70,8 @@ export default (() => {
return ( return (
<div <div
className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden" className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden
md:h-[calc(100dvh-88px)]"
ref={containerRef}> ref={containerRef}>
<Helmet> <Helmet>
<title> <title>
+1 -1
ファイルの表示
@@ -232,7 +232,7 @@ export default (() => {
return <ErrorScreen status={status}/> return <ErrorScreen status={status}/>
return ( return (
<div className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden"> <div className="md:flex md:flex-1">
<Helmet> <Helmet>
{theatre && ( {theatre && (
<title> <title>
+6 -5
ファイルの表示
@@ -7,6 +7,7 @@ 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'
@@ -106,15 +107,15 @@ export default () => {
</PrefetchLink>) : '(最新)'} </PrefetchLink>) : '(最新)'}
</div>)} </div>)}
<article className="prose dark:prose-invert mx-auto p-4"> <PageTitle>
<h1 className="prose-a:no-underline">
<TagLink tag={tag ?? defaultTag} <TagLink tag={tag ?? defaultTag}
withWiki={false} withWiki={false}
withCount={false} withCount={false}
{...(version && { to: `/wiki/${ encodeURIComponent (title) }` })}/> {...(version && { to: `/wiki/${ encodeURIComponent (title) }` })}/>
</h1> </PageTitle>
{loading ? <div>Loading...</div> : <WikiBody title={title} body={wikiPage?.body}/>} <div className="prose mx-auto p-4">
</article> {loading ? 'Loading...' : <WikiBody title={title} body={wikiPage?.body}/>}
</div>
{(!(version) && posts.length > 0) && ( {(!(version) && posts.length > 0) && (
<TabGroup> <TabGroup>
+4 -18
ファイルの表示
@@ -63,20 +63,10 @@ export type Material = {
export type Menu = MenuItem[] export type Menu = MenuItem[]
export type MenuInvisibleItem = { export type MenuItem = {
name: ReactNode
to?: string
base?: string
visible: false
subMenu: SubMenuItem[] }
export type MenuItem = MenuVisibleItem | MenuInvisibleItem
export type MenuVisibleItem = {
name: ReactNode name: ReactNode
to: string to: string
base?: string base?: string
visible?: true
subMenu: SubMenuItem[] } subMenu: SubMenuItem[] }
export type NicoTag = Tag & { export type NicoTag = Tag & {
@@ -136,14 +126,10 @@ export type PostTagChange = {
changeType: 'add' | 'remove' changeType: 'add' | 'remove'
timestamp: string } timestamp: string }
export type SubMenuComponentItem = { export type SubMenuItem =
component: ReactNode | { component: ReactNode
visible: boolean } visible: boolean }
| { name: ReactNode
export type SubMenuItem = SubMenuComponentItem | SubMenuStringItem
export type SubMenuStringItem = {
name: ReactNode
to: string to: string
visible?: boolean } visible?: boolean }
+2 -2
ファイルの表示
@@ -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,mdx}'], content: ['./src/**/*.{html,js,ts,jsx,tsx}'],
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: [require ('@tailwindcss/typography')] } satisfies Config plugins: [] } satisfies Config