コミットを比較
4 コミット
d5ff17074f
...
714f00e278
| 作成者 | SHA1 | 日付 | |
|---|---|---|---|
| 714f00e278 | |||
| 5446f4795d | |||
| 7e2d94fdc0 | |||
| e79688783e |
+21
-19
@@ -20,10 +20,12 @@ import WikiHistoryPage from '@/pages/wiki/WikiHistoryPage'
|
|||||||
import WikiNewPage from '@/pages/wiki/WikiNewPage'
|
import WikiNewPage from '@/pages/wiki/WikiNewPage'
|
||||||
import WikiSearchPage from '@/pages/wiki/WikiSearchPage'
|
import WikiSearchPage from '@/pages/wiki/WikiSearchPage'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
|
||||||
import type { User } from '@/types'
|
import type { User } from '@/types'
|
||||||
|
|
||||||
|
|
||||||
export default () => {
|
export default (() => {
|
||||||
const [user, setUser] = useState<User | null> (null)
|
const [user, setUser] = useState<User | null> (null)
|
||||||
const [status, setStatus] = useState (200)
|
const [status, setStatus] = useState (200)
|
||||||
|
|
||||||
@@ -65,30 +67,30 @@ export default () => {
|
|||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
case 503:
|
case 503:
|
||||||
return <ServiceUnavailable />
|
return <ServiceUnavailable/>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<div className="flex flex-col h-screen w-screen">
|
<div className="flex flex-col h-screen w-screen">
|
||||||
<TopNav user={user} />
|
<TopNav user={user}/>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Navigate to="/posts" replace />} />
|
<Route path="/" element={<Navigate to="/posts" replace/>}/>
|
||||||
<Route path="/posts" element={<PostListPage />} />
|
<Route path="/posts" element={<PostListPage/>}/>
|
||||||
<Route path="/posts/new" element={<PostNewPage user={user} />} />
|
<Route path="/posts/new" element={<PostNewPage user={user}/>}/>
|
||||||
<Route path="/posts/:id" element={<PostDetailPage user={user} />} />
|
<Route path="/posts/:id" element={<PostDetailPage user={user}/>}/>
|
||||||
<Route path="/tags/nico" element={<NicoTagListPage user={user} />} />
|
<Route path="/tags/nico" element={<NicoTagListPage user={user}/>}/>
|
||||||
<Route path="/wiki" element={<WikiSearchPage />} />
|
<Route path="/wiki" element={<WikiSearchPage/>}/>
|
||||||
<Route path="/wiki/:title" element={<WikiDetailPage />} />
|
<Route path="/wiki/:title" element={<WikiDetailPage/>}/>
|
||||||
<Route path="/wiki/new" element={<WikiNewPage user={user} />} />
|
<Route path="/wiki/new" element={<WikiNewPage user={user}/>}/>
|
||||||
<Route path="/wiki/:id/edit" element={<WikiEditPage user={user} />} />
|
<Route path="/wiki/:id/edit" element={<WikiEditPage user={user}/>}/>
|
||||||
<Route path="/wiki/:id/diff" element={<WikiDiffPage />} />
|
<Route path="/wiki/:id/diff" element={<WikiDiffPage/>}/>
|
||||||
<Route path="/wiki/changes" element={<WikiHistoryPage />} />
|
<Route path="/wiki/changes" element={<WikiHistoryPage/>}/>
|
||||||
<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="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
<Toaster />
|
<Toaster/>
|
||||||
</BrowserRouter>)
|
</BrowserRouter>)
|
||||||
}
|
}) satisfies FC
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import errorImg from '@/assets/images/not-found.gif'
|
|||||||
import MainArea from '@/components/layout/MainArea'
|
import MainArea from '@/components/layout/MainArea'
|
||||||
import { SITE_TITLE } from '@/config'
|
import { SITE_TITLE } from '@/config'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
|
||||||
type Props = { status: number }
|
type Props = { status: number }
|
||||||
|
|
||||||
|
|
||||||
export default ({ status }: Props) => {
|
export default (({ status }: Props) => {
|
||||||
const [message, rightMsg, leftMsg]: [string, string, string] = (() => {
|
const [message, rightMsg, leftMsg]: [string, string, string] = (() => {
|
||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
@@ -39,7 +41,7 @@ export default ({ status }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<MainArea>
|
<MainArea>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<meta name="robots" content="noindex" />
|
<meta name="robots" content="noindex"/>
|
||||||
<title>{title} | {SITE_TITLE}</title>
|
<title>{title} | {SITE_TITLE}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="text-6xl font-bold text-transparent
|
<div className="text-6xl font-bold text-transparent
|
||||||
@@ -50,10 +52,10 @@ export default ({ status }: Props) => {
|
|||||||
<p>{status}</p>
|
<p>{status}</p>
|
||||||
<div className="flex flex-row space-x-1 sm:space-x-2 md:space-x-4">
|
<div className="flex flex-row space-x-1 sm:space-x-2 md:space-x-4">
|
||||||
<p style={{ writingMode: 'vertical-rl' }}>{leftMsg}</p>
|
<p style={{ writingMode: 'vertical-rl' }}>{leftMsg}</p>
|
||||||
<img className="max-w-[70vw]" src={errorImg} alt="逃げたギター" />
|
<img className="max-w-[70vw]" src={errorImg} alt="逃げたギター"/>
|
||||||
<p style={{ writingMode: 'vertical-rl' }}>{rightMsg}</p>
|
<p style={{ writingMode: 'vertical-rl' }}>{rightMsg}</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="mr-[-.5em]">{message}</p>
|
<p className="mr-[-.5em]">{message}</p>
|
||||||
</div>
|
</div>
|
||||||
</MainArea>)
|
</MainArea>)
|
||||||
}
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
export default () => (
|
import type { FC } from 'react'
|
||||||
|
|
||||||
|
|
||||||
|
export default (() => (
|
||||||
<>
|
<>
|
||||||
<span className="hidden md:inline flex items-center px-2">|</span>
|
<span className="hidden md:inline flex items-center px-2">|</span>
|
||||||
<hr className="block md:hidden w-full opacity-25
|
<hr className="block md:hidden w-full opacity-25
|
||||||
border-t border-black dark:border-white" />
|
border-t border-black dark:border-white"/>
|
||||||
</>)
|
</>)) satisfies FC
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ type Props = { id: string,
|
|||||||
height: number,
|
height: number,
|
||||||
style?: CSSProperties }
|
style?: CSSProperties }
|
||||||
|
|
||||||
import type { CSSProperties } from 'react'
|
import type { CSSProperties, FC } from 'react'
|
||||||
|
|
||||||
|
|
||||||
export default (props: Props) => {
|
export default ((props: Props) => {
|
||||||
const { id, width, height, style = { } } = props
|
const { id, width, height, style = { } } = props
|
||||||
|
|
||||||
const iframeRef = useRef<HTMLIFrameElement> (null)
|
const iframeRef = useRef<HTMLIFrameElement> (null)
|
||||||
@@ -107,5 +107,5 @@ export default (props: Props) => {
|
|||||||
height={height}
|
height={height}
|
||||||
style={margedStyle}
|
style={margedStyle}
|
||||||
allowFullScreen
|
allowFullScreen
|
||||||
allow="autoplay" />)
|
allow="autoplay"/>)
|
||||||
}
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ import TextArea from '@/components/common/TextArea'
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { API_BASE_URL } from '@/config'
|
import { API_BASE_URL } from '@/config'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
|
||||||
import type { Post } from '@/types'
|
import type { Post } from '@/types'
|
||||||
|
|
||||||
type Props = { post: Post
|
type Props = { post: Post
|
||||||
onSave: (newPost: Post) => void }
|
onSave: (newPost: Post) => void }
|
||||||
|
|
||||||
|
|
||||||
export default ({ post, onSave }: Props) => {
|
export default (({ post, onSave }: Props) => {
|
||||||
const [title, setTitle] = useState (post.title)
|
const [title, setTitle] = useState (post.title)
|
||||||
const [tags, setTags] = useState<string> (post.tags
|
const [tags, setTags] = useState<string> (post.tags
|
||||||
.filter (t => t.category !== 'nico')
|
.filter (t => t.category !== 'nico')
|
||||||
@@ -39,14 +41,14 @@ export default ({ post, onSave }: Props) => {
|
|||||||
<input type="text"
|
<input type="text"
|
||||||
className="w-full border rounded p-2"
|
className="w-full border rounded p-2"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={e => setTitle (e.target.value)} />
|
onChange={e => setTitle (e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* タグ */}
|
{/* タグ */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block font-semibold">タグ</label>
|
<label className="block font-semibold">タグ</label>
|
||||||
<TextArea value={tags}
|
<TextArea value={tags}
|
||||||
onChange={ev => setTags (ev.target.value)} />
|
onChange={ev => setTags (ev.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 送信 */}
|
{/* 送信 */}
|
||||||
@@ -55,4 +57,4 @@ export default ({ post, onSave }: Props) => {
|
|||||||
更新
|
更新
|
||||||
</Button>
|
</Button>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
import type { MouseEvent } from 'react'
|
import type { FC, MouseEvent } from 'react'
|
||||||
|
|
||||||
import type { Post } from '@/types'
|
import type { Post } from '@/types'
|
||||||
|
|
||||||
type Props = { posts: Post[]
|
type Props = { posts: Post[]
|
||||||
onClick?: (event: MouseEvent<HTMLElement>) => void }
|
onClick?: (event: MouseEvent<HTMLElement>) => void }
|
||||||
|
|
||||||
|
|
||||||
export default ({ posts, onClick }: Props) => (
|
export default (({ posts, onClick }: Props) => (
|
||||||
<div className="flex flex-wrap gap-6 p-4">
|
<div className="flex flex-wrap gap-6 p-4">
|
||||||
{posts.map ((post, i) => (
|
{posts.map ((post, i) => (
|
||||||
<Link to={`/posts/${ post.id }`}
|
<Link to={`/posts/${ post.id }`}
|
||||||
@@ -22,4 +23,4 @@ export default ({ posts, onClick }: Props) => (
|
|||||||
decoding="async"
|
decoding="async"
|
||||||
className="object-none w-full h-full" />
|
className="object-none w-full h-full" />
|
||||||
</Link>))}
|
</Link>))}
|
||||||
</div>)
|
</div>)) satisfies FC<Props>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import SubsectionTitle from '@/components/common/SubsectionTitle'
|
|||||||
import SidebarComponent from '@/components/layout/SidebarComponent'
|
import SidebarComponent from '@/components/layout/SidebarComponent'
|
||||||
import { CATEGORIES } from '@/consts'
|
import { CATEGORIES } from '@/consts'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
|
||||||
import type { Category, Post, Tag } from '@/types'
|
import type { Category, Post, Tag } from '@/types'
|
||||||
|
|
||||||
type TagByCategory = { [key in Category]: Tag[] }
|
type TagByCategory = { [key in Category]: Tag[] }
|
||||||
@@ -13,7 +15,7 @@ type TagByCategory = { [key in Category]: Tag[] }
|
|||||||
type Props = { post: Post | null }
|
type Props = { post: Post | null }
|
||||||
|
|
||||||
|
|
||||||
export default ({ post }: Props) => {
|
export default (({ post }: Props) => {
|
||||||
const [tags, setTags] = useState ({ } as TagByCategory)
|
const [tags, setTags] = useState ({ } as TagByCategory)
|
||||||
|
|
||||||
const categoryNames: Record<Category, string> = {
|
const categoryNames: Record<Category, string> = {
|
||||||
@@ -46,16 +48,16 @@ export default ({ post }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarComponent>
|
<SidebarComponent>
|
||||||
<TagSearch />
|
<TagSearch/>
|
||||||
{CATEGORIES.map ((cat: Category) => cat in tags && (
|
{CATEGORIES.map ((cat: Category) => cat in tags && (
|
||||||
<div className="my-3" key={cat}>
|
<div className="my-3" key={cat}>
|
||||||
<SubsectionTitle>{categoryNames[cat]}</SubsectionTitle>
|
<SubsectionTitle>{categoryNames[cat]}</SubsectionTitle>
|
||||||
<ul>
|
<ul>
|
||||||
{tags[cat].map ((tag, i) => (
|
{tags[cat].map ((tag, i) => (
|
||||||
<li key={i} className="mb-1">
|
<li key={i} className="mb-1">
|
||||||
<TagLink tag={tag} />
|
<TagLink tag={tag}/>
|
||||||
</li>))}
|
</li>))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>))}
|
</div>))}
|
||||||
</SidebarComponent>)
|
</SidebarComponent>)
|
||||||
}
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'
|
|||||||
import { LIGHT_COLOUR_SHADE, DARK_COLOUR_SHADE, TAG_COLOUR } from '@/consts'
|
import { LIGHT_COLOUR_SHADE, DARK_COLOUR_SHADE, TAG_COLOUR } from '@/consts'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
import type { ComponentProps, HTMLAttributes } from 'react'
|
import type { ComponentProps, FC, HTMLAttributes } from 'react'
|
||||||
|
|
||||||
import type { Tag } from '@/types'
|
import type { Tag } from '@/types'
|
||||||
|
|
||||||
@@ -20,11 +20,11 @@ type PropsWithoutLink =
|
|||||||
type Props = PropsWithLink | PropsWithoutLink
|
type Props = PropsWithLink | PropsWithoutLink
|
||||||
|
|
||||||
|
|
||||||
export default ({ tag,
|
export default (({ tag,
|
||||||
linkFlg = true,
|
linkFlg = true,
|
||||||
withWiki = true,
|
withWiki = true,
|
||||||
withCount = true,
|
withCount = true,
|
||||||
...props }: Props) => {
|
...props }: Props) => {
|
||||||
const spanClass = cn (
|
const spanClass = cn (
|
||||||
`text-${ TAG_COLOUR[tag.category] }-${ LIGHT_COLOUR_SHADE }`,
|
`text-${ TAG_COLOUR[tag.category] }-${ LIGHT_COLOUR_SHADE }`,
|
||||||
`dark:text-${ TAG_COLOUR[tag.category] }-${ DARK_COLOUR_SHADE }`)
|
`dark:text-${ TAG_COLOUR[tag.category] }-${ DARK_COLOUR_SHADE }`)
|
||||||
@@ -57,4 +57,4 @@ export default ({ tag,
|
|||||||
{withCount && (
|
{withCount && (
|
||||||
<span className="ml-1">{tag.postCount}</span>)}
|
<span className="ml-1">{tag.postCount}</span>)}
|
||||||
</>)
|
</>)
|
||||||
}
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import { API_BASE_URL } from '@/config'
|
|||||||
|
|
||||||
import TagSearchBox from './TagSearchBox'
|
import TagSearchBox from './TagSearchBox'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
|
||||||
import type { Tag } from '@/types'
|
import type { Tag } from '@/types'
|
||||||
|
|
||||||
|
|
||||||
const TagSearch: React.FC = () => {
|
export default (() => {
|
||||||
const location = useLocation ()
|
const location = useLocation ()
|
||||||
const navigate = useNavigate ()
|
const navigate = useNavigate ()
|
||||||
|
|
||||||
@@ -24,8 +26,8 @@ const TagSearch: React.FC = () => {
|
|||||||
const q = ev.target.value.trim ().split (' ').at (-1)
|
const q = ev.target.value.trim ().split (' ').at (-1)
|
||||||
if (!(q))
|
if (!(q))
|
||||||
{
|
{
|
||||||
setSuggestions ([])
|
setSuggestions ([])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await axios.get (`${ API_BASE_URL }/tags/autocomplete`, { params: { q } })
|
const res = await axios.get (`${ API_BASE_URL }/tags/autocomplete`, { params: { q } })
|
||||||
@@ -52,7 +54,7 @@ const TagSearch: React.FC = () => {
|
|||||||
|
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
if (activeIndex < 0)
|
if (activeIndex < 0)
|
||||||
break
|
break
|
||||||
ev.preventDefault ()
|
ev.preventDefault ()
|
||||||
const selected = suggestions[activeIndex]
|
const selected = suggestions[activeIndex]
|
||||||
selected && handleTagSelect (selected)
|
selected && handleTagSelect (selected)
|
||||||
@@ -65,8 +67,8 @@ const TagSearch: React.FC = () => {
|
|||||||
}
|
}
|
||||||
if (ev.key === 'Enter' && (!(suggestionsVsbl) || activeIndex < 0))
|
if (ev.key === 'Enter' && (!(suggestionsVsbl) || activeIndex < 0))
|
||||||
{
|
{
|
||||||
navigate (`/posts?${ (new URLSearchParams ({ tags: search })).toString () }`)
|
navigate (`/posts?${ (new URLSearchParams ({ tags: search })).toString () }`)
|
||||||
setSuggestionsVsbl (false)
|
setSuggestionsVsbl (false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,18 +88,16 @@ const TagSearch: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
placeholder="タグ検索..."
|
placeholder="タグ検索..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={whenChanged}
|
onChange={whenChanged}
|
||||||
onFocus={() => setSuggestionsVsbl (true)}
|
onFocus={() => setSuggestionsVsbl (true)}
|
||||||
onBlur={() => setSuggestionsVsbl (false)}
|
onBlur={() => setSuggestionsVsbl (false)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
className="w-full px-3 py-2 border rounded dark:border-gray-600 dark:bg-gray-800 dark:text-white" />
|
className="w-full px-3 py-2 border rounded dark:border-gray-600 dark:bg-gray-800 dark:text-white"/>
|
||||||
<TagSearchBox suggestions={suggestionsVsbl && suggestions.length ? suggestions : [] as Tag[]}
|
<TagSearchBox suggestions={suggestionsVsbl && suggestions.length ? suggestions : [] as Tag[]}
|
||||||
activeIndex={activeIndex}
|
activeIndex={activeIndex}
|
||||||
onSelect={handleTagSelect} />
|
onSelect={handleTagSelect}/>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}) satisfies FC
|
||||||
|
|
||||||
export default TagSearch
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
|
||||||
import type { Tag } from '@/types'
|
import type { Tag } from '@/types'
|
||||||
|
|
||||||
type Props = { suggestions: Tag[]
|
type Props = { suggestions: Tag[]
|
||||||
@@ -7,7 +9,7 @@ type Props = { suggestions: Tag[]
|
|||||||
onSelect: (tag: Tag) => void }
|
onSelect: (tag: Tag) => void }
|
||||||
|
|
||||||
|
|
||||||
export default ({ suggestions, activeIndex, onSelect }: Props) => {
|
export default (({ suggestions, activeIndex, onSelect }: Props) => {
|
||||||
if (!(suggestions.length))
|
if (!(suggestions.length))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -25,4 +27,4 @@ export default ({ suggestions, activeIndex, onSelect }: Props) => {
|
|||||||
{<span className="ml-2 text-sm text-gray-400">{tag.postCount}</span>}
|
{<span className="ml-2 text-sm text-gray-400">{tag.postCount}</span>}
|
||||||
</li>))}
|
</li>))}
|
||||||
</ul>)
|
</ul>)
|
||||||
}
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { API_BASE_URL } from '@/config'
|
|||||||
import { CATEGORIES } from '@/consts'
|
import { CATEGORIES } from '@/consts'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
|
||||||
import type { Post, Tag } from '@/types'
|
import type { Post, Tag } from '@/types'
|
||||||
|
|
||||||
type TagByCategory = Record<string, Tag[]>
|
type TagByCategory = Record<string, Tag[]>
|
||||||
@@ -17,7 +19,7 @@ type TagByCategory = Record<string, Tag[]>
|
|||||||
type Props = { posts: Post[] }
|
type Props = { posts: Post[] }
|
||||||
|
|
||||||
|
|
||||||
export default ({ posts }: Props) => {
|
export default (({ posts }: Props) => {
|
||||||
const navigate = useNavigate ()
|
const navigate = useNavigate ()
|
||||||
|
|
||||||
const [tagsVsbl, setTagsVsbl] = useState (false)
|
const [tagsVsbl, setTagsVsbl] = useState (false)
|
||||||
@@ -58,14 +60,14 @@ export default ({ posts }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarComponent>
|
<SidebarComponent>
|
||||||
<TagSearch />
|
<TagSearch/>
|
||||||
<div className={cn (!(tagsVsbl) && 'hidden', 'md:block mt-4')}>
|
<div className={cn (!(tagsVsbl) && 'hidden', 'md:block mt-4')}>
|
||||||
<SectionTitle>タグ</SectionTitle>
|
<SectionTitle>タグ</SectionTitle>
|
||||||
<ul>
|
<ul>
|
||||||
{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">
|
||||||
<TagLink tag={tag} />
|
<TagLink tag={tag}/>
|
||||||
</li>))) : [])}
|
</li>))) : [])}
|
||||||
</ul>
|
</ul>
|
||||||
<SectionTitle>関聯</SectionTitle>
|
<SectionTitle>関聯</SectionTitle>
|
||||||
@@ -101,4 +103,4 @@ export default ({ posts }: Props) => {
|
|||||||
{tagsVsbl ? '▲▲▲ タグ一覧を閉じる ▲▲▲' : '▼▼▼ タグ一覧を表示 ▼▼▼'}
|
{tagsVsbl ? '▲▲▲ タグ一覧を閉じる ▲▲▲' : '▼▼▼ タグ一覧を表示 ▼▼▼'}
|
||||||
</a>
|
</a>
|
||||||
</SidebarComponent>)
|
</SidebarComponent>)
|
||||||
}
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ import { API_BASE_URL } from '@/config'
|
|||||||
import { WikiIdBus } from '@/lib/eventBus/WikiIdBus'
|
import { WikiIdBus } from '@/lib/eventBus/WikiIdBus'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
|
||||||
import type { Menu, Tag, User, WikiPage } from '@/types'
|
import type { Menu, Tag, User, WikiPage } from '@/types'
|
||||||
|
|
||||||
type Props = { user: User | null }
|
type Props = { user: User | null }
|
||||||
|
|
||||||
|
|
||||||
export default ({ user }: Props) => {
|
export default (({ user }: Props) => {
|
||||||
const location = useLocation ()
|
const location = useLocation ()
|
||||||
|
|
||||||
const [menuOpen, setMenuOpen] = useState (false)
|
const [menuOpen, setMenuOpen] = useState (false)
|
||||||
@@ -41,7 +43,7 @@ export default ({ user }: Props) => {
|
|||||||
{ name: '新規', to: '/wiki/new' },
|
{ name: '新規', to: '/wiki/new' },
|
||||||
{ name: '全体履歴', to: '/wiki/changes' },
|
{ name: '全体履歴', to: '/wiki/changes' },
|
||||||
{ name: 'ヘルプ', to: '/wiki/ヘルプ:Wiki' },
|
{ name: 'ヘルプ', to: '/wiki/ヘルプ:Wiki' },
|
||||||
{ component: <Separator />, visible: wikiPageFlg },
|
{ component: <Separator/>, visible: wikiPageFlg },
|
||||||
{ name: `広場 (${ postCount || 0 })`, to: `/posts?tags=${ wikiTitle }`,
|
{ name: `広場 (${ postCount || 0 })`, to: `/posts?tags=${ wikiTitle }`,
|
||||||
visible: wikiPageFlg },
|
visible: wikiPageFlg },
|
||||||
{ name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg },
|
{ name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg },
|
||||||
@@ -108,7 +110,7 @@ export default ({ user }: Props) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TopNavUser user={user} />
|
<TopNavUser user={user}/>
|
||||||
|
|
||||||
<a href="#"
|
<a href="#"
|
||||||
className="md:hidden ml-auto pr-4
|
className="md:hidden ml-auto pr-4
|
||||||
@@ -136,7 +138,7 @@ export default ({ user }: Props) => {
|
|||||||
|
|
||||||
<div className={cn (menuOpen ? 'flex flex-col md:hidden' : 'hidden',
|
<div className={cn (menuOpen ? 'flex flex-col md:hidden' : 'hidden',
|
||||||
'bg-yellow-200 dark:bg-red-975 items-start')}>
|
'bg-yellow-200 dark:bg-red-975 items-start')}>
|
||||||
<Separator />
|
<Separator/>
|
||||||
{menu.map ((item, i) => (
|
{menu.map ((item, i) => (
|
||||||
<Fragment key={i}>
|
<Fragment key={i}>
|
||||||
<Link to={i === openItemIdx ? item.to : '#'}
|
<Link to={i === openItemIdx ? item.to : '#'}
|
||||||
@@ -163,8 +165,8 @@ export default ({ user }: Props) => {
|
|||||||
{subItem.name}
|
{subItem.name}
|
||||||
</Link>)))}
|
</Link>)))}
|
||||||
</Fragment>))}
|
</Fragment>))}
|
||||||
<TopNavUser user={user} sp />
|
<TopNavUser user={user} sp/>
|
||||||
<Separator />
|
<Separator/>
|
||||||
</div>
|
</div>
|
||||||
</>)
|
</>)
|
||||||
}
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import { Link } from 'react-router-dom'
|
|||||||
import Separator from '@/components/MenuSeparator'
|
import Separator from '@/components/MenuSeparator'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
|
||||||
import type { User } from '@/types'
|
import type { User } from '@/types'
|
||||||
|
|
||||||
type Props = { user: User | null,
|
type Props = { user: User | null,
|
||||||
sp?: boolean }
|
sp?: boolean }
|
||||||
|
|
||||||
|
|
||||||
export default ({ user, sp }: Props) => {
|
export default (({ user, sp }: Props) => {
|
||||||
if (!(user))
|
if (!(user))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -21,10 +23,10 @@ export default ({ user, sp }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{sp && <Separator />}
|
{sp && <Separator/>}
|
||||||
<Link to="/users/settings"
|
<Link to="/users/settings"
|
||||||
className={className}>
|
className={className}>
|
||||||
{user.name || '名もなきニジラー'}
|
{user.name || '名もなきニジラー'}
|
||||||
</Link>
|
</Link>
|
||||||
</>)
|
</>)
|
||||||
}
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import SectionTitle from '@/components/common/SectionTitle'
|
|||||||
import SubsectionTitle from '@/components/common/SubsectionTitle'
|
import SubsectionTitle from '@/components/common/SubsectionTitle'
|
||||||
import { API_BASE_URL } from '@/config'
|
import { API_BASE_URL } from '@/config'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
import type { Components } from 'react-markdown'
|
import type { Components } from 'react-markdown'
|
||||||
|
|
||||||
import type { WikiPage } from '@/types'
|
import type { WikiPage } from '@/types'
|
||||||
@@ -31,7 +32,7 @@ const mdComponents = { h1: ({ children }) => <SectionTitle>{children}</SectionT
|
|||||||
</a>))) } as const satisfies Components
|
</a>))) } as const satisfies Components
|
||||||
|
|
||||||
|
|
||||||
export default ({ title, body }: Props) => {
|
export default (({ title, body }: Props) => {
|
||||||
const [pageNames, setPageNames] = useState<string[]> ([])
|
const [pageNames, setPageNames] = useState<string[]> ([])
|
||||||
const [realBody, setRealBody] = useState<string> ('')
|
const [realBody, setRealBody] = useState<string> ('')
|
||||||
|
|
||||||
@@ -101,4 +102,4 @@ export default ({ title, body }: Props) => {
|
|||||||
<ReactMarkdown components={mdComponents} remarkPlugins={[remarkGFM]}>
|
<ReactMarkdown components={mdComponents} remarkPlugins={[remarkGFM]}>
|
||||||
{realBody || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`}
|
{realBody || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`}
|
||||||
</ReactMarkdown>)
|
</ReactMarkdown>)
|
||||||
}
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react'
|
import type { FC, ReactNode } from 'react'
|
||||||
|
|
||||||
type Props = { children: React.ReactNode }
|
type Props = { children: ReactNode }
|
||||||
|
|
||||||
|
|
||||||
export default ({ children }: Props) => (
|
export default (({ children }: Props) => (
|
||||||
<div className="max-w-xl mx-auto p-4 space-y-4">
|
<div className="max-w-xl mx-auto p-4 space-y-4">
|
||||||
{children}
|
{children}
|
||||||
</div>)
|
</div>)) satisfies FC<Props>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default ({ children, checkBox }: Props) => {
|
|||||||
<label className="flex items-center block gap-1">
|
<label className="flex items-center block gap-1">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
checked={checkBox.checked}
|
checked={checkBox.checked}
|
||||||
onChange={checkBox.onChange} />
|
onChange={checkBox.onChange}/>
|
||||||
{checkBox.label}
|
{checkBox.label}
|
||||||
</label>
|
</label>
|
||||||
</div>)
|
</div>)
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ type Props = { value?: string
|
|||||||
export default ({ value, onChange }: Props) => (
|
export default ({ value, onChange }: Props) => (
|
||||||
<textarea className="rounded border w-full p-2 h-32"
|
<textarea className="rounded border w-full p-2 h-32"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange} />)
|
onChange={onChange}/>)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default ({ visible, onVisibleChange, setUser }: Props) => {
|
|||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input placeholder="引継ぎコードを入力"
|
<Input placeholder="引継ぎコードを入力"
|
||||||
value={inputCode}
|
value={inputCode}
|
||||||
onChange={ev => setInputCode (ev.target.value)} />
|
onChange={ev => setInputCode (ev.target.value)}/>
|
||||||
<Button onClick={handleTransfer}>引継ぐ</Button>
|
<Button onClick={handleTransfer}>引継ぐ</Button>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
+1
-1
@@ -8,5 +8,5 @@ const helmetContext = { }
|
|||||||
|
|
||||||
createRoot (document.getElementById ('root')!).render (
|
createRoot (document.getElementById ('root')!).render (
|
||||||
<HelmetProvider context={helmetContext}>
|
<HelmetProvider context={helmetContext}>
|
||||||
<App />
|
<App/>
|
||||||
</HelmetProvider>)
|
</HelmetProvider>)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ErrorScreen from '@/components/ErrorScreen'
|
import ErrorScreen from '@/components/ErrorScreen'
|
||||||
|
|
||||||
|
|
||||||
export default () => <ErrorScreen status={403} />
|
export default () => <ErrorScreen status={403}/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ErrorScreen from '@/components/ErrorScreen'
|
import ErrorScreen from '@/components/ErrorScreen'
|
||||||
|
|
||||||
|
|
||||||
export default () => <ErrorScreen status={404} />
|
export default () => <ErrorScreen status={404}/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ErrorScreen from '@/components/ErrorScreen'
|
import ErrorScreen from '@/components/ErrorScreen'
|
||||||
|
|
||||||
|
|
||||||
export default () => <ErrorScreen status={503} />
|
export default () => <ErrorScreen status={503}/>
|
||||||
|
|||||||
@@ -72,9 +72,9 @@ export default ({ user }: Props) => {
|
|||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
case 404:
|
case 404:
|
||||||
return <NotFound />
|
return <NotFound/>
|
||||||
case 503:
|
case 503:
|
||||||
return <ServiceUnavailable />
|
return <ServiceUnavailable/>
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = post ? new URL (post.url) : null
|
const url = post ? new URL (post.url) : null
|
||||||
@@ -89,11 +89,11 @@ export default ({ user }: Props) => {
|
|||||||
<div className="md:flex md:flex-1">
|
<div className="md:flex md:flex-1">
|
||||||
<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}/>)}
|
||||||
{post && <title>{`${ post.title || post.url } | ${ SITE_TITLE }`}</title>}
|
{post && <title>{`${ post.title || post.url } | ${ SITE_TITLE }`}</title>}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
<TagDetailSidebar post={post} />
|
<TagDetailSidebar post={post}/>
|
||||||
</div>
|
</div>
|
||||||
<MainArea>
|
<MainArea>
|
||||||
{post
|
{post
|
||||||
@@ -103,8 +103,8 @@ export default ({ user }: Props) => {
|
|||||||
? (
|
? (
|
||||||
<NicoViewer id={videoId}
|
<NicoViewer id={videoId}
|
||||||
width={640}
|
width={640}
|
||||||
height={360} />)
|
height={360}/>)
|
||||||
: <img src={post.thumbnail} alt={post.url} className="mb-4 w-full" />}
|
: <img src={post.thumbnail} alt={post.url} className="mb-4 w-full"/>}
|
||||||
<Button onClick={changeViewedFlg}
|
<Button onClick={changeViewedFlg}
|
||||||
className={cn ('text-white', viewedClass)}>
|
className={cn ('text-white', viewedClass)}>
|
||||||
{post.viewed ? '閲覧済' : '未閲覧'}
|
{post.viewed ? '閲覧済' : '未閲覧'}
|
||||||
@@ -121,14 +121,14 @@ export default ({ user }: Props) => {
|
|||||||
onSave={newPost => {
|
onSave={newPost => {
|
||||||
setPost (newPost)
|
setPost (newPost)
|
||||||
toast ({ description: '更新しました.' })
|
toast ({ description: '更新しました.' })
|
||||||
}} />
|
}}/>
|
||||||
</Tab>)}
|
</Tab>)}
|
||||||
</TabGroup>
|
</TabGroup>
|
||||||
</>)
|
</>)
|
||||||
: 'Loading...'}
|
: 'Loading...'}
|
||||||
</MainArea>
|
</MainArea>
|
||||||
<div className="md:hidden">
|
<div className="md:hidden">
|
||||||
<TagDetailSidebar post={post} />
|
<TagDetailSidebar post={post}/>
|
||||||
</div>
|
</div>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export default () => {
|
|||||||
</title>
|
</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<TagSidebar posts={posts.slice (0, 20)} />
|
<TagSidebar posts={posts.slice (0, 20)}/>
|
||||||
|
|
||||||
<MainArea>
|
<MainArea>
|
||||||
<TabGroup>
|
<TabGroup>
|
||||||
@@ -124,14 +124,14 @@ export default () => {
|
|||||||
scroll: containerRef.current?.scrollTop ?? 0 }
|
scroll: containerRef.current?.scrollTop ?? 0 }
|
||||||
sessionStorage.setItem (`posts:${ tagsQuery }`,
|
sessionStorage.setItem (`posts:${ tagsQuery }`,
|
||||||
JSON.stringify (statesToSave))
|
JSON.stringify (statesToSave))
|
||||||
}} />)
|
}}/>)
|
||||||
: !(loading) && '広場には何もありませんよ.'}
|
: !(loading) && '広場には何もありませんよ.'}
|
||||||
{loading && 'Loading...'}
|
{loading && 'Loading...'}
|
||||||
<div ref={loaderRef} className="h-12"></div>
|
<div ref={loaderRef} className="h-12"></div>
|
||||||
</Tab>
|
</Tab>
|
||||||
{tags.length === 1 && (
|
{tags.length === 1 && (
|
||||||
<Tab name="Wiki">
|
<Tab name="Wiki">
|
||||||
<WikiBody title={tags[0]} body={wikiPage?.body} />
|
<WikiBody title={tags[0]} body={wikiPage?.body}/>
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
<Link to={`/wiki/${ encodeURIComponent (tags[0]) }`}>
|
<Link to={`/wiki/${ encodeURIComponent (tags[0]) }`}>
|
||||||
Wiki を見る
|
Wiki を見る
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type Props = { user: User | null }
|
|||||||
|
|
||||||
export default ({ user }: Props) => {
|
export default ({ user }: Props) => {
|
||||||
if (!(['admin', 'member'].some (r => user?.role === r)))
|
if (!(['admin', 'member'].some (r => user?.role === r)))
|
||||||
return <Forbidden />
|
return <Forbidden/>
|
||||||
|
|
||||||
const navigate = useNavigate ()
|
const navigate = useNavigate ()
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ export default ({ user }: Props) => {
|
|||||||
value={url}
|
value={url}
|
||||||
onChange={e => setURL (e.target.value)}
|
onChange={e => setURL (e.target.value)}
|
||||||
className="w-full border p-2 rounded"
|
className="w-full border p-2 rounded"
|
||||||
onBlur={handleURLBlur} />
|
onBlur={handleURLBlur}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* タイトル */}
|
{/* タイトル */}
|
||||||
@@ -141,7 +141,7 @@ export default ({ user }: Props) => {
|
|||||||
value={title}
|
value={title}
|
||||||
placeholder={titleLoading ? 'Loading...' : ''}
|
placeholder={titleLoading ? 'Loading...' : ''}
|
||||||
onChange={ev => setTitle (ev.target.value)}
|
onChange={ev => setTitle (ev.target.value)}
|
||||||
disabled={titleAutoFlg} />
|
disabled={titleAutoFlg}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* サムネール */}
|
{/* サムネール */}
|
||||||
@@ -169,11 +169,11 @@ export default ({ user }: Props) => {
|
|||||||
setThumbnailFile (file)
|
setThumbnailFile (file)
|
||||||
setThumbnailPreview (URL.createObjectURL (file))
|
setThumbnailPreview (URL.createObjectURL (file))
|
||||||
}
|
}
|
||||||
}} />)}
|
}}/>)}
|
||||||
{thumbnailPreview && (
|
{thumbnailPreview && (
|
||||||
<img src={thumbnailPreview}
|
<img src={thumbnailPreview}
|
||||||
alt="preview"
|
alt="preview"
|
||||||
className="mt-2 max-h-48 rounded border" />)}
|
className="mt-2 max-h-48 rounded border"/>)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* タグ */}
|
{/* タグ */}
|
||||||
@@ -181,7 +181,7 @@ export default ({ user }: Props) => {
|
|||||||
<div>
|
<div>
|
||||||
<Label>タグ</Label>
|
<Label>タグ</Label>
|
||||||
<TextArea value={tags}
|
<TextArea value={tags}
|
||||||
onChange={ev => setTags (ev.target.value)} />
|
onChange={ev => setTags (ev.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 送信 */}
|
{/* 送信 */}
|
||||||
|
|||||||
@@ -114,19 +114,19 @@ export default ({ user }: Props) => {
|
|||||||
{nicoTags.map ((tag, i) => (
|
{nicoTags.map ((tag, i) => (
|
||||||
<tr key={i}>
|
<tr key={i}>
|
||||||
<td className="p-2">
|
<td className="p-2">
|
||||||
<TagLink tag={tag} withWiki={false} withCount={false} />
|
<TagLink tag={tag} withWiki={false} withCount={false}/>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2">
|
<td className="p-2">
|
||||||
{editing[tag.id]
|
{editing[tag.id]
|
||||||
? (
|
? (
|
||||||
<TextArea value={rawTags[tag.id]} onChange={ev => {
|
<TextArea value={rawTags[tag.id]} onChange={ev => {
|
||||||
setRawTags (rawTags => ({ ...rawTags, [tag.id]: ev.target.value }))
|
setRawTags (rawTags => ({ ...rawTags, [tag.id]: ev.target.value }))
|
||||||
}} />)
|
}}/>)
|
||||||
: tag.linkedTags.map((lt, j) => (
|
: tag.linkedTags.map((lt, j) => (
|
||||||
<span key={j} className="mr-2">
|
<span key={j} className="mr-2">
|
||||||
<TagLink tag={lt}
|
<TagLink tag={lt}
|
||||||
linkFlg={false}
|
linkFlg={false}
|
||||||
withCount={false} />
|
withCount={false}/>
|
||||||
</span>))}
|
</span>))}
|
||||||
</td>
|
</td>
|
||||||
{memberFlg && (
|
{memberFlg && (
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default ({ user, setUser }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<MainArea>
|
<MainArea>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<meta name="robots" content="noindex" />
|
<meta name="robots" content="noindex"/>
|
||||||
<title>設定 | {SITE_TITLE}</title>
|
<title>設定 | {SITE_TITLE}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ export default ({ user, setUser }: Props) => {
|
|||||||
className="w-full border rounded p-2"
|
className="w-full border rounded p-2"
|
||||||
value={name}
|
value={name}
|
||||||
placeholder="名もなきニジラー"
|
placeholder="名もなきニジラー"
|
||||||
onChange={ev => setName (ev.target.value)} />
|
onChange={ev => setName (ev.target.value)}/>
|
||||||
{(user && !(user.name)) && (
|
{(user && !(user.name)) && (
|
||||||
<p className="mt-1 text-sm text-red-500">
|
<p className="mt-1 text-sm text-red-500">
|
||||||
名前が未設定のアカウントは 30 日間アクセスしないと削除されます!!!!
|
名前が未設定のアカウントは 30 日間アクセスしないと削除されます!!!!
|
||||||
@@ -104,10 +104,10 @@ export default ({ user, setUser }: Props) => {
|
|||||||
<UserCodeDialogue visible={userCodeVsbl}
|
<UserCodeDialogue visible={userCodeVsbl}
|
||||||
onVisibleChange={setUserCodeVsbl}
|
onVisibleChange={setUserCodeVsbl}
|
||||||
user={user}
|
user={user}
|
||||||
setUser={setUser} />
|
setUser={setUser}/>
|
||||||
|
|
||||||
<InheritDialogue visible={inheritVsbl}
|
<InheritDialogue visible={inheritVsbl}
|
||||||
onVisibleChange={setInheritVsbl}
|
onVisibleChange={setInheritVsbl}
|
||||||
setUser={setUser} />
|
setUser={setUser}/>
|
||||||
</MainArea>)
|
</MainArea>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export default () => {
|
|||||||
<MainArea>
|
<MainArea>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{`${ title } Wiki | ${ SITE_TITLE }`}</title>
|
<title>{`${ title } Wiki | ${ SITE_TITLE }`}</title>
|
||||||
{!(wikiPage?.body) && <meta name="robots" content="noindex" />}
|
{!(wikiPage?.body) && <meta name="robots" content="noindex"/>}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
{(wikiPage && version) && (
|
{(wikiPage && version) && (
|
||||||
@@ -120,18 +120,18 @@ export default () => {
|
|||||||
<TagLink tag={tag}
|
<TagLink tag={tag}
|
||||||
withWiki={false}
|
withWiki={false}
|
||||||
withCount={false}
|
withCount={false}
|
||||||
{...(version && { to: `/wiki/${ encodeURIComponent (title) }` })} />
|
{...(version && { to: `/wiki/${ encodeURIComponent (title) }` })}/>
|
||||||
</PageTitle>
|
</PageTitle>
|
||||||
<div className="prose mx-auto p-4">
|
<div className="prose mx-auto p-4">
|
||||||
{wikiPage === undefined
|
{wikiPage === undefined
|
||||||
? 'Loading...'
|
? 'Loading...'
|
||||||
: <WikiBody title={title} body={wikiPage?.body} />}
|
: <WikiBody title={title} body={wikiPage?.body}/>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(!(version) && posts.length > 0) && (
|
{(!(version) && posts.length > 0) && (
|
||||||
<TabGroup>
|
<TabGroup>
|
||||||
<Tab name="広場">
|
<Tab name="広場">
|
||||||
<PostList posts={posts} />
|
<PostList posts={posts}/>
|
||||||
</Tab>
|
</Tab>
|
||||||
</TabGroup>)}
|
</TabGroup>)}
|
||||||
</MainArea>)
|
</MainArea>)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default () => {
|
|||||||
diff.diff.map (d => (
|
diff.diff.map (d => (
|
||||||
<span className={cn (d.type === 'added' && 'bg-green-200 dark:bg-green-800',
|
<span className={cn (d.type === 'added' && 'bg-green-200 dark:bg-green-800',
|
||||||
d.type === 'removed' && 'bg-red-200 dark:bg-red-800')}>
|
d.type === 'removed' && 'bg-red-200 dark:bg-red-800')}>
|
||||||
{d.content == '\n' ? <br /> : d.content}
|
{d.content == '\n' ? <br/> : d.content}
|
||||||
</span>)))
|
</span>)))
|
||||||
: 'Loading...'}
|
: 'Loading...'}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type Props = { user: User | null }
|
|||||||
|
|
||||||
export default ({ user }: Props) => {
|
export default ({ user }: Props) => {
|
||||||
if (!(['admin', 'member'].some (r => user?.role === r)))
|
if (!(['admin', 'member'].some (r => user?.role === r)))
|
||||||
return <Forbidden />
|
return <Forbidden/>
|
||||||
|
|
||||||
const { id } = useParams ()
|
const { id } = useParams ()
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ export default ({ user }: Props) => {
|
|||||||
<input type="text"
|
<input type="text"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={e => setTitle (e.target.value)}
|
onChange={e => setTitle (e.target.value)}
|
||||||
className="w-full border p-2 rounded" />
|
className="w-full border p-2 rounded"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 本文 */}
|
{/* 本文 */}
|
||||||
@@ -82,7 +82,7 @@ export default ({ user }: Props) => {
|
|||||||
<MdEditor value={body}
|
<MdEditor value={body}
|
||||||
style={{ height: '500px' }}
|
style={{ height: '500px' }}
|
||||||
renderHTML={text => mdParser.render (text)}
|
renderHTML={text => mdParser.render (text)}
|
||||||
onChange={({ text }) => setBody (text)} />
|
onChange={({ text }) => setBody (text)}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 送信 */}
|
{/* 送信 */}
|
||||||
|
|||||||
@@ -27,54 +27,54 @@ export default () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MainArea>
|
<MainArea>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{`Wiki 変更履歴 | ${ SITE_TITLE }`}</title>
|
<title>{`Wiki 変更履歴 | ${ SITE_TITLE }`}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<table className="table-auto w-full border-collapse">
|
<table className="table-auto w-full border-collapse">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th className="p-2 text-left">タイトル</th>
|
<th className="p-2 text-left">タイトル</th>
|
||||||
<th className="p-2 text-left">変更</th>
|
<th className="p-2 text-left">変更</th>
|
||||||
<th className="p-2 text-left">日時</th>
|
<th className="p-2 text-left">日時</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{changes.map (change => (
|
{changes.map (change => (
|
||||||
<tr key={change.sha}>
|
<tr key={change.sha}>
|
||||||
<td>
|
<td>
|
||||||
{change.changeType === 'update' && (
|
{change.changeType === 'update' && (
|
||||||
<Link to={`/wiki/${ change.wikiPage.id }/diff?from=${ change.pred }&to=${ change.sha }`}>
|
<Link to={`/wiki/${ change.wikiPage.id }/diff?from=${ change.pred }&to=${ change.sha }`}>
|
||||||
差分
|
差分
|
||||||
</Link>)}
|
</Link>)}
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2">
|
<td className="p-2">
|
||||||
<Link to={`/wiki/${ encodeURIComponent (change.wikiPage.title) }?version=${ change.sha }`}>
|
<Link to={`/wiki/${ encodeURIComponent (change.wikiPage.title) }?version=${ change.sha }`}>
|
||||||
{change.wikiPage.title}
|
{change.wikiPage.title}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2">
|
<td className="p-2">
|
||||||
{(() => {
|
{(() => {
|
||||||
switch (change.changeType)
|
switch (change.changeType)
|
||||||
{
|
{
|
||||||
case 'create':
|
case 'create':
|
||||||
return '新規'
|
return '新規'
|
||||||
case 'update':
|
case 'update':
|
||||||
return '更新'
|
return '更新'
|
||||||
case 'delete':
|
case 'delete':
|
||||||
return '削除'
|
return '削除'
|
||||||
}
|
}
|
||||||
}) ()}
|
}) ()}
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2">
|
<td className="p-2">
|
||||||
<Link to={`/users/${ change.user.id }`}>
|
<Link to={`/users/${ change.user.id }`}>
|
||||||
{change.user.name}
|
{change.user.name}
|
||||||
</Link>
|
</Link>
|
||||||
<br />
|
<br/>
|
||||||
{change.timestamp}
|
{change.timestamp}
|
||||||
</td>
|
</td>
|
||||||
</tr>))}
|
</tr>))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</MainArea>)
|
</MainArea>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type Props = { user: User | null }
|
|||||||
|
|
||||||
export default ({ user }: Props) => {
|
export default ({ user }: Props) => {
|
||||||
if (!(['admin', 'member'].some (r => user?.role === r)))
|
if (!(['admin', 'member'].some (r => user?.role === r)))
|
||||||
return <Forbidden />
|
return <Forbidden/>
|
||||||
|
|
||||||
const location = useLocation ()
|
const location = useLocation ()
|
||||||
const navigate = useNavigate ()
|
const navigate = useNavigate ()
|
||||||
@@ -67,7 +67,7 @@ export default ({ user }: Props) => {
|
|||||||
<input type="text"
|
<input type="text"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={e => setTitle (e.target.value)}
|
onChange={e => setTitle (e.target.value)}
|
||||||
className="w-full border p-2 rounded" />
|
className="w-full border p-2 rounded"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 本文 */}
|
{/* 本文 */}
|
||||||
@@ -76,7 +76,7 @@ export default ({ user }: Props) => {
|
|||||||
<MdEditor value={body}
|
<MdEditor value={body}
|
||||||
style={{ height: '500px' }}
|
style={{ height: '500px' }}
|
||||||
renderHTML={text => mdParser.render (text)}
|
renderHTML={text => mdParser.render (text)}
|
||||||
onChange={({ text }) => setBody (text)} />
|
onChange={({ text }) => setBody (text)}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 送信 */}
|
{/* 送信 */}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
// import { Route,
|
|
||||||
// createBrowserRouter,
|
|
||||||
// createRoutesFromElements } from 'react-router-dom'
|
|
||||||
//
|
|
||||||
// import App from '@/App'
|
|
||||||
新しい課題から参照
ユーザをブロックする