This commit is contained in:
@@ -8,6 +8,7 @@ import TagSidebar from '@/components/TagSidebar'
|
||||
import TopNav from '@/components/TopNav'
|
||||
import { Toaster } from '@/components/ui/toaster'
|
||||
import { API_BASE_URL } from '@/config'
|
||||
import NicoTagListPage from '@/pages/tags/NicoTagListPage'
|
||||
import NotFound from '@/pages/NotFound'
|
||||
import PostDetailPage from '@/pages/posts/PostDetailPage'
|
||||
import PostListPage from '@/pages/posts/PostListPage'
|
||||
@@ -62,6 +63,7 @@ export default () => {
|
||||
<Route path="/posts" element={<PostListPage />} />
|
||||
<Route path="/posts/new" element={<PostNewPage />} />
|
||||
<Route path="/posts/:id" element={<PostDetailPage user={user} />} />
|
||||
<Route path="/tags/nico" element={<NicoTagListPage />} />
|
||||
<Route path="/wiki" element={<WikiSearchPage />} />
|
||||
<Route path="/wiki/:title" element={<WikiDetailPage />} />
|
||||
<Route path="/wiki/new" element={<WikiNewPage />} />
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import axios from 'axios'
|
||||
import toCamel from 'camelcase-keys'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import SectionTitle from '@/components/common/SectionTitle'
|
||||
import TextArea from '@/components/common/TextArea'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
import { API_BASE_URL, SITE_TITLE } from '@/config'
|
||||
|
||||
import type { NicoTag } from '@/types'
|
||||
|
||||
|
||||
export default () => {
|
||||
const [nicoTags, setNicoTags] = useState<NicoTag[]> ([])
|
||||
const [editing, setEditing] = useState<{ [key: number]: boolean }> ({ })
|
||||
const [rawTags, setRawTags] = useState<{ [key: number]: string }> ({ })
|
||||
|
||||
useEffect (() => {
|
||||
void (async () => {
|
||||
const res = await axios.get (`${ API_BASE_URL }/tags/nico`)
|
||||
const data = toCamel (res.data, { deep: true })
|
||||
|
||||
setNicoTags (data.tags)
|
||||
|
||||
const newEditing = Object.fromEntries (data.tags.map (t => [t.id, false]))
|
||||
setEditing (editing => ({ ...editing, ...newEditing }))
|
||||
|
||||
const newRawTags = Object.fromEntries (
|
||||
data.tags.map (t => [t.id, t.linkedTags.map (lt => lt.name).join (' ')]))
|
||||
setRawTags (rawTags => ({ ...rawTags, ...newRawTags }))
|
||||
}) ()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<MainArea>
|
||||
<Helmet>
|
||||
<title>ニコニコ連携 | {SITE_TITLE}</title>
|
||||
</Helmet>
|
||||
|
||||
<div className="max-w-xl">
|
||||
<SectionTitle>ニコニコ連携</SectionTitle>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<table className="table-auto w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="p-2 text-left">ニコニコタグ</th>
|
||||
<th className="p-2 text-left">連携タグ</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{nicoTags.map (tag => (
|
||||
<tr key={tag.id}>
|
||||
<td className="p-2">
|
||||
{tag.name}
|
||||
</td>
|
||||
<td className="p-2">
|
||||
{editing[tag.id]
|
||||
? (
|
||||
<TextArea value={rawTags[tag.id]} onChange={ev => {
|
||||
setRawTags (rawTags => ({ ...rawTags, [tag.id]: ev.target.value }))
|
||||
}} />)
|
||||
: rawTags[tag.id]}
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" onClick={ev => {
|
||||
setEditing (editing => ({ ...editing, [tag.id]: !(editing[tag.id]) }))
|
||||
}}>
|
||||
{editing[tag.id] ? <span className="text-red-400">更新</span> : '編輯'}
|
||||
</a>
|
||||
</td>
|
||||
</tr>))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</MainArea>)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import toCamel from 'camelcase-keys'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import SectionTitle from '@/components/common/SectionTitle'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
import { API_BASE_URL, SITE_TITLE } from '@/config'
|
||||
@@ -18,7 +19,7 @@ export default () => {
|
||||
|
||||
const search = () => {
|
||||
void (axios.get (`${ API_BASE_URL }/wiki/search`, { params: { title } })
|
||||
.then (res => setResults (toCamel (res.data, { deep: true }))))
|
||||
.then (res => setResults (toCamel (res.data, { deep: true }))))
|
||||
}
|
||||
|
||||
const handleSearch = (e: React.FormEvent) => {
|
||||
@@ -32,62 +33,62 @@ export default () => {
|
||||
|
||||
return (
|
||||
<MainArea>
|
||||
<Helmet>
|
||||
<title>{`Wiki | ${ SITE_TITLE }`}</title>
|
||||
</Helmet>
|
||||
<div className="max-w-xl">
|
||||
<SectionTitle className="text-xl mb-4">Wiki</SectionTitle>
|
||||
<form onSubmit={handleSearch} className="space-y-2">
|
||||
{/* タイトル */}
|
||||
<div>
|
||||
<label>タイトル:</label><br />
|
||||
<input type="text"
|
||||
value={title}
|
||||
onChange={e => setTitle (e.target.value)}
|
||||
className="border p-1 w-full" />
|
||||
</div>
|
||||
<Helmet>
|
||||
<title>Wiki | {SITE_TITLE}</title>
|
||||
</Helmet>
|
||||
<div className="max-w-xl">
|
||||
<SectionTitle>Wiki</SectionTitle>
|
||||
<form onSubmit={handleSearch} className="space-y-2">
|
||||
{/* タイトル */}
|
||||
<div>
|
||||
<label>タイトル:</label><br />
|
||||
<input type="text"
|
||||
value={title}
|
||||
onChange={e => setTitle (e.target.value)}
|
||||
className="border p-1 w-full" />
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
<div>
|
||||
<label>内容:</label><br />
|
||||
<input type="text"
|
||||
value={text}
|
||||
onChange={e => setText (e.target.value)}
|
||||
className="border p-1 w-full" />
|
||||
</div>
|
||||
{/* 内容 */}
|
||||
<div>
|
||||
<label>内容:</label><br />
|
||||
<input type="text"
|
||||
value={text}
|
||||
onChange={e => setText (e.target.value)}
|
||||
className="border p-1 w-full" />
|
||||
</div>
|
||||
|
||||
{/* 検索 */}
|
||||
<div className="py-3">
|
||||
<button type="submit"
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded">
|
||||
検索
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/* 検索 */}
|
||||
<div className="py-3">
|
||||
<button type="submit"
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded">
|
||||
検索
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<table className="table-auto w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="p-2 text-left">タイトル</th>
|
||||
<th className="p-2 text-left">最終更新</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{results.map (page => (
|
||||
<tr key={page.id}>
|
||||
<td className="p-2">
|
||||
<Link to={`/wiki/${ encodeURIComponent (page.title) }`}>
|
||||
{page.title}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="p-2 text-gray-100 text-sm">
|
||||
{page.updatedAt}
|
||||
</td>
|
||||
</tr>))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<table className="table-auto w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="p-2 text-left">タイトル</th>
|
||||
<th className="p-2 text-left">最終更新</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{results.map (page => (
|
||||
<tr key={page.id}>
|
||||
<td className="p-2">
|
||||
<Link to={`/wiki/${ encodeURIComponent (page.title) }`}>
|
||||
{page.title}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="p-2 text-gray-100 text-sm">
|
||||
{page.updatedAt}
|
||||
</td>
|
||||
</tr>))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</MainArea>)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,11 @@ import { CATEGORIES, USER_ROLES } from '@/consts'
|
||||
|
||||
export type Category = typeof CATEGORIES[number]
|
||||
|
||||
export type NicoTag = {
|
||||
id: number
|
||||
name: string
|
||||
linkedTags: Tag[] }
|
||||
|
||||
export type Post = {
|
||||
id: number
|
||||
url: string
|
||||
|
||||
Reference in New Issue
Block a user