This commit is contained in:
Generated
+26
-37
@@ -21,7 +21,7 @@
|
|||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet-async": "^2.0.5",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-markdown-editor-lite": "^1.3.4",
|
"react-markdown-editor-lite": "^1.3.4",
|
||||||
"react-router-dom": "^6.30.0",
|
"react-router-dom": "^6.30.0",
|
||||||
@@ -3841,6 +3841,15 @@
|
|||||||
"integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==",
|
"integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/invariant": {
|
||||||
|
"version": "2.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||||
|
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-alphabetical": {
|
"node_modules/is-alphabetical": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
|
||||||
@@ -4964,6 +4973,7 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -5338,17 +5348,6 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prop-types": {
|
|
||||||
"version": "15.8.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"loose-envify": "^1.4.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"react-is": "^16.13.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/property-information": {
|
"node_modules/property-information": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
|
||||||
@@ -5444,27 +5443,20 @@
|
|||||||
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
|
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/react-helmet": {
|
"node_modules/react-helmet-async": {
|
||||||
"version": "6.1.0",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-2.0.5.tgz",
|
||||||
"integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
|
"integrity": "sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==",
|
||||||
"license": "MIT",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"object-assign": "^4.1.1",
|
"invariant": "^2.2.4",
|
||||||
"prop-types": "^15.7.2",
|
"react-fast-compare": "^3.2.2",
|
||||||
"react-fast-compare": "^3.1.1",
|
"shallowequal": "^1.1.0"
|
||||||
"react-side-effect": "^2.1.0"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=16.3.0"
|
"react": "^16.6.0 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-is": {
|
|
||||||
"version": "16.13.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/react-markdown": {
|
"node_modules/react-markdown": {
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
|
||||||
@@ -5596,15 +5588,6 @@
|
|||||||
"react-dom": ">=16.8"
|
"react-dom": ">=16.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-side-effect": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-style-singleton": {
|
"node_modules/react-style-singleton": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||||
@@ -5805,6 +5788,12 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/shallowequal": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet-async": "^2.0.5",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-markdown-editor-lite": "^1.3.4",
|
"react-markdown-editor-lite": "^1.3.4",
|
||||||
"react-router-dom": "^6.30.0",
|
"react-router-dom": "^6.30.0",
|
||||||
|
|||||||
+38
-41
@@ -27,57 +27,54 @@ export default () => {
|
|||||||
const [user, setUser] = useState<User | null> (null)
|
const [user, setUser] = useState<User | null> (null)
|
||||||
|
|
||||||
useEffect (() => {
|
useEffect (() => {
|
||||||
const createUser = () => (
|
const createUser = async () => {
|
||||||
axios.post (`${ API_BASE_URL }/users`)
|
const { data } = await axios.post (`${ API_BASE_URL }/users`)
|
||||||
.then (res => {
|
if (data.code)
|
||||||
if (res.data.code)
|
{
|
||||||
{
|
localStorage.setItem ('user_code', data.code)
|
||||||
localStorage.setItem ('user_code', res.data.code)
|
setUser (toCamel (data.user, { deep: true }))
|
||||||
setUser (toCamel (res.data.user, { deep: true }))
|
}
|
||||||
}
|
}
|
||||||
}))
|
|
||||||
|
|
||||||
const code = localStorage.getItem ('user_code')
|
const code = localStorage.getItem ('user_code')
|
||||||
if (code)
|
if (code)
|
||||||
{
|
{
|
||||||
void (axios.post (`${ API_BASE_URL }/users/verify`, { code })
|
void (async () => {
|
||||||
.then (res => {
|
const { data } = await axios.post (`${ API_BASE_URL }/users/verify`, { code })
|
||||||
if (res.data.valid)
|
if (data.valid)
|
||||||
setUser (toCamel (res.data.user, { deep: true }))
|
setUser (toCamel (data.user, { deep: true }))
|
||||||
else
|
else
|
||||||
createUser ()
|
createUser ()
|
||||||
}))
|
}) ()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
createUser ()
|
createUser ()
|
||||||
|
|
||||||
alert ('このサイトはまだ作りかけです!!!!\n出てけ!!!!!!!!!!!!!!!!!!!!')
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Router>
|
<Router>
|
||||||
<div className="flex flex-col h-screen w-screen">
|
<div className="flex flex-col h-screen w-screen">
|
||||||
<TopNav user={user} setUser={setUser} />
|
<TopNav user={user} setUser={setUser} />
|
||||||
<div className="flex flex-1">
|
<div className="flex flex-1">
|
||||||
<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 />} />
|
<Route path="/posts/new" element={<PostNewPage />} />
|
||||||
<Route path="/posts/:id" element={<PostDetailPage user={user} />} />
|
<Route path="/posts/:id" element={<PostDetailPage 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 />} />
|
<Route path="/wiki/new" element={<WikiNewPage />} />
|
||||||
<Route path="/wiki/:id/edit" element={<WikiEditPage />} />
|
<Route path="/wiki/:id/edit" element={<WikiEditPage />} />
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</>)
|
</>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export default ({ post }: Props) => {
|
|||||||
<SidebarComponent>
|
<SidebarComponent>
|
||||||
<TagSearch />
|
<TagSearch />
|
||||||
{CATEGORIES.map ((cat: Category) => cat in tags && (
|
{CATEGORIES.map ((cat: Category) => cat in tags && (
|
||||||
<div className="my-3">
|
<div className="my-3" key={cat}>
|
||||||
<SubsectionTitle>{categoryNames[cat]}</SubsectionTitle>
|
<SubsectionTitle>{categoryNames[cat]}</SubsectionTitle>
|
||||||
<ul>
|
<ul>
|
||||||
{tags[cat].map (tag => (
|
{tags[cat].map (tag => (
|
||||||
|
|||||||
@@ -32,18 +32,18 @@ export default ({ posts }: Props) => {
|
|||||||
loop:
|
loop:
|
||||||
for (const post of posts)
|
for (const post of posts)
|
||||||
{
|
{
|
||||||
for (const tag of post.tags)
|
for (const tag of post.tags)
|
||||||
{
|
{
|
||||||
if (!(tag.category in tagsTmp))
|
if (!(tag.category in tagsTmp))
|
||||||
tagsTmp[tag.category] = []
|
tagsTmp[tag.category] = []
|
||||||
if (!(tagsTmp[tag.category].map (t => t.id).includes (tag.id)))
|
if (!(tagsTmp[tag.category].map (t => t.id).includes (tag.id)))
|
||||||
{
|
{
|
||||||
tagsTmp[tag.category].push (tag)
|
tagsTmp[tag.category].push (tag)
|
||||||
++cnt
|
++cnt
|
||||||
if (cnt >= 25)
|
if (cnt >= 25)
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const cat of Object.keys (tagsTmp))
|
for (const cat of Object.keys (tagsTmp))
|
||||||
tagsTmp[cat].sort ((tagA, tagB) => tagA.name < tagB.name ? -1 : 1)
|
tagsTmp[cat].sort ((tagA, tagB) => tagA.name < tagB.name ? -1 : 1)
|
||||||
@@ -52,40 +52,38 @@ export default ({ posts }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarComponent>
|
<SidebarComponent>
|
||||||
<TagSearch />
|
<TagSearch />
|
||||||
<SectionTitle>タグ</SectionTitle>
|
<SectionTitle>タグ</SectionTitle>
|
||||||
<ul>
|
<ul>
|
||||||
{CATEGORIES.map (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">
|
<Link to={`/posts?${ (new URLSearchParams ({ tags: tag.name })).toString () }`}>
|
||||||
<Link to={`/posts?${ (new URLSearchParams ({ tags: tag.name })).toString () }`}>
|
{tag.name}
|
||||||
{tag.name}
|
</Link>
|
||||||
</Link>
|
<span className="ml-1">{tag.postCount}</span>
|
||||||
<span className="ml-1">{tag.postCount}</span>
|
</li>))) : [])}
|
||||||
</li>))}
|
</ul>
|
||||||
</>))}
|
<SectionTitle>関聯</SectionTitle>
|
||||||
</ul>
|
{posts.length && (
|
||||||
<SectionTitle>関聯</SectionTitle>
|
<a href="#"
|
||||||
{posts.length && (
|
onClick={ev => {
|
||||||
<a href="#"
|
ev.preventDefault ()
|
||||||
onClick={ev => {
|
void ((async () => {
|
||||||
ev.preventDefault ()
|
try
|
||||||
void ((async () => {
|
{
|
||||||
try
|
const { data } = await axios.get (`${ API_BASE_URL }/posts/random`,
|
||||||
{
|
{ params: { tags: tagsQuery.split (' ').filter (e => e !== '').join (','),
|
||||||
const { data } = await axios.get (`${ API_BASE_URL }/posts/random`,
|
match: (anyFlg ? 'any' : 'all') } })
|
||||||
{ params: { tags: tagsQuery.split (' ').filter (e => e !== '').join (','),
|
navigate (`/posts/${ (data as Post).id }`)
|
||||||
match: (anyFlg ? 'any' : 'all') } })
|
}
|
||||||
navigate (`/posts/${ (data as Post).id }`)
|
catch
|
||||||
}
|
{
|
||||||
catch
|
;
|
||||||
{
|
}
|
||||||
;
|
}) ())
|
||||||
}
|
}}>
|
||||||
}) ())
|
ランダム
|
||||||
}}>
|
</a>)}
|
||||||
ランダム
|
|
||||||
</a>)}
|
|
||||||
</SidebarComponent>)
|
</SidebarComponent>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger } from '@/components/ui/dialog'
|
||||||
|
|
||||||
|
type Props = { visible: boolean
|
||||||
|
onVisibleChange: (visible: boolean) => void
|
||||||
|
user: User | null,
|
||||||
|
setUser: (user: User) => void }
|
||||||
|
|
||||||
|
|
||||||
|
export default ({ visible, onVisibleChange, user, setUser }: Props) => {
|
||||||
|
return (
|
||||||
|
<Dialog open={visible} onOpenChange={onVisibleChange}>
|
||||||
|
<DialogContent className="space-y-6">
|
||||||
|
<DialogTitle>ほかのブラウザから引継ぐ</DialogTitle>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger } from '@/components/ui/dialog'
|
||||||
|
|
||||||
|
type Props = { visible: boolean
|
||||||
|
onVisibleChange: (visible: boolean) => void
|
||||||
|
user: User | null }
|
||||||
|
|
||||||
|
|
||||||
|
export default ({ visible, onVisibleChange, user }: Props) => {
|
||||||
|
return (
|
||||||
|
<Dialog open={visible} onOpenChange={onVisibleChange}>
|
||||||
|
<DialogContent className="space-y-6">
|
||||||
|
<DialogTitle>引継ぎコード</DialogTitle>
|
||||||
|
<div>
|
||||||
|
<p>あなたの引継ぎコードはこちらです:</p>
|
||||||
|
<div className="m-2">{user?.inheritanceCode}</div>
|
||||||
|
<p className="mt-1 text-sm text-red-500">
|
||||||
|
このコードはほかの人には教えないでください!
|
||||||
|
</p>
|
||||||
|
<div className="my-4">
|
||||||
|
<Button className="px-4 py-2 bg-red-600 text-white rounded disabled:bg-gray-400">
|
||||||
|
引継ぎコードを変更する
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>)
|
||||||
|
}
|
||||||
@@ -7,3 +7,7 @@ export const CATEGORIES = ['general',
|
|||||||
'meta'] as const
|
'meta'] as const
|
||||||
|
|
||||||
export const USER_ROLES = ['admin', 'member', 'guest'] as const
|
export const USER_ROLES = ['admin', 'member', 'guest'] as const
|
||||||
|
|
||||||
|
export const enum ViewFlagBehavior { OnShowedDetail = 1,
|
||||||
|
OnClickedLink = 2,
|
||||||
|
NotAuto = 3 }
|
||||||
|
|||||||
+12
-7
@@ -1,10 +1,15 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import './index.css'
|
import { HelmetProvider } from 'react-helmet-async'
|
||||||
import App from './App.tsx'
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
import '@/index.css'
|
||||||
<StrictMode>
|
import App from '@/App'
|
||||||
<App />
|
|
||||||
</StrictMode>,
|
const helmetContext = { }
|
||||||
)
|
|
||||||
|
createRoot (document.getElementById ('root')!).render (
|
||||||
|
<StrictMode>
|
||||||
|
<HelmetProvider context={helmetContext}>
|
||||||
|
<App />
|
||||||
|
</HelmetProvider>
|
||||||
|
</StrictMode>)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet-async'
|
||||||
|
|
||||||
import MainArea from '@/components/layout/MainArea'
|
import MainArea from '@/components/layout/MainArea'
|
||||||
import { SITE_TITLE } from '@/config'
|
import { SITE_TITLE } from '@/config'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import toCamel from 'camelcase-keys'
|
import toCamel from 'camelcase-keys'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet-async'
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom'
|
import { Link, useLocation, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import TagDetailSidebar from '@/components/TagDetailSidebar'
|
import TagDetailSidebar from '@/components/TagDetailSidebar'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import toCamel from 'camelcase-keys'
|
import toCamel from 'camelcase-keys'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet-async'
|
||||||
import { Link, useLocation } from 'react-router-dom'
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import TagSidebar from '@/components/TagSidebar'
|
import TagSidebar from '@/components/TagSidebar'
|
||||||
@@ -14,7 +14,7 @@ import type { Post, Tag, WikiPage } from '@/types'
|
|||||||
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [posts, setPosts] = useState<Post[] | null> (null)
|
const [posts, setPosts] = useState<Post[]> ([])
|
||||||
const [wikiPage, setWikiPage] = useState<WikiPage | null> (null)
|
const [wikiPage, setWikiPage] = useState<WikiPage | null> (null)
|
||||||
const [cursor, setCursor] = useState ('')
|
const [cursor, setCursor] = useState ('')
|
||||||
const [loading, setLoading] = useState (false)
|
const [loading, setLoading] = useState (false)
|
||||||
@@ -24,11 +24,11 @@ export default () => {
|
|||||||
const loadMore = async withCursor => {
|
const loadMore = async withCursor => {
|
||||||
setLoading (true)
|
setLoading (true)
|
||||||
const res = await axios.get (`${ API_BASE_URL }/posts`, {
|
const res = await axios.get (`${ API_BASE_URL }/posts`, {
|
||||||
params: { tags: tags.join (' '),
|
params: { tags: tags.join (' '),
|
||||||
match: (anyFlg ? 'any' : 'all'),
|
match: (anyFlg ? 'any' : 'all'),
|
||||||
...(withCursor ? { cursor } : { }) } })
|
...(withCursor ? { cursor } : { }) } })
|
||||||
const data = toCamel (res.data, { deep: true })
|
const data = toCamel (res.data, { deep: true })
|
||||||
setPosts (posts => [...(posts || []), ...data.posts])
|
setPosts (posts => [...(withCursor ? posts : []), ...data.posts])
|
||||||
setCursor (data.nextCursor)
|
setCursor (data.nextCursor)
|
||||||
setLoading (false)
|
setLoading (false)
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ export default () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const observer = new IntersectionObserver (entries => {
|
const observer = new IntersectionObserver (entries => {
|
||||||
if (entries[0].isIntersecting && !(loading) && cursor)
|
if (entries[0].isIntersecting && !(loading) && cursor)
|
||||||
loadMore (true)
|
loadMore (true)
|
||||||
}, { threshold: 1 })
|
}, { threshold: 1 })
|
||||||
|
|
||||||
const target = loaderRef.current
|
const target = loaderRef.current
|
||||||
@@ -52,58 +52,65 @@ export default () => {
|
|||||||
}, [loaderRef, loading])
|
}, [loaderRef, loading])
|
||||||
|
|
||||||
useEffect (() => {
|
useEffect (() => {
|
||||||
setPosts (null)
|
setPosts ([])
|
||||||
setCursor ('')
|
|
||||||
loadMore (false)
|
loadMore (false)
|
||||||
|
|
||||||
setWikiPage (null)
|
setWikiPage (null)
|
||||||
if (tags.length === 1)
|
if (tags.length === 1)
|
||||||
{
|
{
|
||||||
void (axios.get (`${ API_BASE_URL }/wiki/title/${ encodeURIComponent (tags[0]) }`)
|
void (async () => {
|
||||||
.then (res => setWikiPage (toCamel (res.data, { deep: true })))
|
try
|
||||||
.catch (() => {
|
{
|
||||||
;
|
const tagName = tags[0]
|
||||||
}))
|
const { data } = await axios.get (`${ API_BASE_URL }/wiki/title/${ tagName }`)
|
||||||
|
setWikiPage (toCamel (data, { deep: true }))
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}) ()
|
||||||
}
|
}
|
||||||
}, [location.search])
|
}, [location.search])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>
|
<title>
|
||||||
{tags.length
|
{tags.length
|
||||||
? `${ tags.join (anyFlg ? ' or ' : ' and ') } | ${ SITE_TITLE }`
|
? `${ tags.join (anyFlg ? ' or ' : ' and ') } | ${ SITE_TITLE }`
|
||||||
: `${ SITE_TITLE } 〜 ぼざクリ関聯綜合リンク集サイト`}
|
: `${ SITE_TITLE } 〜 ぼざクリ関聯綜合リンク集サイト`}
|
||||||
</title>
|
</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<TagSidebar posts={posts?.slice (0, 20) || []} />
|
<TagSidebar posts={posts.slice (0, 20)} />
|
||||||
<MainArea>
|
<MainArea>
|
||||||
<TabGroup key={wikiPage}>
|
<TabGroup key={wikiPage}>
|
||||||
<Tab name="広場">
|
<Tab name="広場">
|
||||||
{posts == null ? 'Loading...' : (
|
{posts.length
|
||||||
posts.length
|
? (
|
||||||
? (
|
<div className="flex flex-wrap gap-4 p-4">
|
||||||
<div className="flex flex-wrap gap-4 p-4">
|
{posts.map (post => (
|
||||||
{posts.map (post => (
|
<Link to={`/posts/${ post.id }`}
|
||||||
<Link to={`/posts/${ post.id }`}
|
key={post.id}
|
||||||
key={post.id}
|
className="w-40 h-40 overflow-hidden rounded-lg shadow-md hover:shadow-lg">
|
||||||
className="w-40 h-40 overflow-hidden rounded-lg shadow-md hover:shadow-lg">
|
<img src={post.thumbnail || post.thumbnailBase || null}
|
||||||
<img src={post.thumbnail ?? post.thumbnailBase}
|
alt={post.title || post.url}
|
||||||
alt={post.title || post.url}
|
title={post.title || post.url || null}
|
||||||
className="object-none w-full h-full" />
|
className="object-none w-full h-full" />
|
||||||
</Link>))}
|
</Link>))}
|
||||||
</div>)
|
</div>)
|
||||||
: '広場には何もありませんよ.')}
|
: !(loading) && '広場には何もありませんよ.'}
|
||||||
<div ref={loaderRef} className="h-12"></div>
|
{loading && 'Loading...'}
|
||||||
</Tab>
|
<div ref={loaderRef} className="h-12"></div>
|
||||||
{(wikiPage && wikiPage.body) && (
|
</Tab>
|
||||||
<Tab name="Wiki" init={posts && !(posts.length)}>
|
{(wikiPage && wikiPage.body) && (
|
||||||
<WikiBody body={wikiPage.body} />
|
<Tab name="Wiki" init={!(posts.length)}>
|
||||||
<div className="my-2">
|
<WikiBody body={wikiPage.body} />
|
||||||
<Link to={`/wiki/${ wikiPage.title }`}>Wiki を見る</Link>
|
<div className="my-2">
|
||||||
</div>
|
<Link to={`/wiki/${ wikiPage.title }`}>Wiki を見る</Link>
|
||||||
</Tab>)}
|
</div>
|
||||||
</TabGroup>
|
</Tab>)}
|
||||||
</MainArea>
|
</TabGroup>
|
||||||
|
</MainArea>
|
||||||
</>)
|
</>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import React, { useEffect, useState, useRef } from 'react'
|
import React, { useEffect, useState, useRef } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet-async'
|
||||||
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
|
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
import NicoViewer from '@/components/NicoViewer'
|
import NicoViewer from '@/components/NicoViewer'
|
||||||
@@ -49,8 +49,8 @@ export default () => {
|
|||||||
toast ({ title: '投稿成功!' })
|
toast ({ title: '投稿成功!' })
|
||||||
navigate ('/posts')
|
navigate ('/posts')
|
||||||
})
|
})
|
||||||
.catch (e => toast ({ title: '投稿失敗',
|
.catch (() => toast ({ title: '投稿失敗',
|
||||||
description: '入力を確認してください。' })))
|
description: '入力を確認してください。' })))
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect (() => {
|
useEffect (() => {
|
||||||
|
|||||||
@@ -1,50 +1,103 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet-async'
|
||||||
|
|
||||||
import Form from '@/components/common/Form'
|
import Form from '@/components/common/Form'
|
||||||
import Label from '@/components/common/Label'
|
import Label from '@/components/common/Label'
|
||||||
import PageTitle from '@/components/common/PageTitle'
|
import PageTitle from '@/components/common/PageTitle'
|
||||||
import MainArea from '@/components/layout/MainArea'
|
import MainArea from '@/components/layout/MainArea'
|
||||||
import { SITE_TITLE } from '@/config'
|
import InheritDialogue from '@/components/users/InheritDialogue'
|
||||||
|
import UserCodeDialogue from '@/components/users/UserCodeDialogue'
|
||||||
|
import { API_BASE_URL, SITE_TITLE } from '@/config'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
import type { User } from '@/types'
|
import type { User } from '@/types'
|
||||||
|
|
||||||
type Props = { user: User
|
type Props = { user: User | null
|
||||||
setUser: (user: User) => void }
|
setUser: (user: User) => void }
|
||||||
|
|
||||||
|
|
||||||
export default ({ user, setUser }: Props) => {
|
export default ({ user, setUser }: Props) => {
|
||||||
const [name, setName] = useState ('')
|
const [name, setName] = useState ('')
|
||||||
|
const [userCodeVsbl, setUserCodeVsbl] = useState (false)
|
||||||
|
const [inheritVsbl, setInheritVsbl] = useState (false)
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const formData = new FormData ()
|
||||||
|
formData.append ('name', name)
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await axios.post (`${ API_BASE_URL }/users`, formData, { headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
'X-Transfer-Code': localStorage.getItem ('user_code') || '' } })
|
||||||
|
toast ({ title: '設定を更新しました.' })
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
toast ({ title: 'しっぱい……' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect (() => {
|
useEffect (() => {
|
||||||
if (!user)
|
if (!user)
|
||||||
return
|
return
|
||||||
|
|
||||||
setName (user?.name)
|
setName (user.name)
|
||||||
}, [user])
|
}, [user])
|
||||||
|
|
||||||
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>
|
||||||
<Form>
|
|
||||||
<PageTitle>設定</PageTitle>
|
|
||||||
|
|
||||||
{/* 名前 */}
|
<Form>
|
||||||
<div>
|
<PageTitle>設定</PageTitle>
|
||||||
<Label>表示名</Label>
|
|
||||||
<input type="text"
|
{/* 名前 */}
|
||||||
className="w-full border rounded p-2"
|
<div>
|
||||||
value={name}
|
<Label>表示名</Label>
|
||||||
placeholder="名もなきニジラー"
|
<input type="text"
|
||||||
onChange={ev => setName (ev.target.value)} />
|
className="w-full border rounded p-2"
|
||||||
{(user && !(user.name)) && (
|
value={name}
|
||||||
<p class="mt-1 text-sm text-red-500">
|
placeholder="名もなきニジラー"
|
||||||
名前が未設定のアカウントは 30 日間アクセスしないと削除されます!!!!
|
onChange={ev => setName (ev.target.value)} />
|
||||||
</p>)}
|
{(user && !(user.name)) && (
|
||||||
</div>
|
<p className="mt-1 text-sm text-red-500">
|
||||||
</Form>
|
名前が未設定のアカウントは 30 日間アクセスしないと削除されます!!!!
|
||||||
|
</p>)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 送信 */}
|
||||||
|
<Button onClick={handleSubmit}
|
||||||
|
className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400">
|
||||||
|
更新
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* 引継ぎ */}
|
||||||
|
<div>
|
||||||
|
<Label>引継ぎ</Label>
|
||||||
|
<Button onClick={() => setUserCodeVsbl (true)}
|
||||||
|
className="px-4 py-2 bg-gray-600 text-white rounded disabled:bg-gray-400"
|
||||||
|
disabled={!(user)}>
|
||||||
|
引継ぎコードを表示
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setInheritVsbl (true)}
|
||||||
|
className="ml-2 px-4 py-2 bg-red-600 text-white rounded disabled:bg-gray-400"
|
||||||
|
disabled={!(user)}>
|
||||||
|
ほかのブラウザから引継ぐ
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<UserCodeDialogue visible={userCodeVsbl}
|
||||||
|
onVisibleChange={setUserCodeVsbl}
|
||||||
|
user={user} />
|
||||||
|
|
||||||
|
<InheritDialogue visible={inheritVsbl}
|
||||||
|
onVisibleChange={setInheritVsbl}
|
||||||
|
user={user}
|
||||||
|
setUser={setUser} />
|
||||||
</MainArea>)
|
</MainArea>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import toCamel from 'camelcase-keys'
|
import toCamel from 'camelcase-keys'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet-async'
|
||||||
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'
|
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import WikiBody from '@/components/WikiBody'
|
import WikiBody from '@/components/WikiBody'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import toCamel from 'camelcase-keys'
|
import toCamel from 'camelcase-keys'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet-async'
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom'
|
import { Link, useLocation, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import PageTitle from '@/components/common/PageTitle'
|
import PageTitle from '@/components/common/PageTitle'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
import React, { useEffect, useState, useRef } from 'react'
|
import React, { useEffect, useState, useRef } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet-async'
|
||||||
import MdEditor from 'react-markdown-editor-lite'
|
import MdEditor from 'react-markdown-editor-lite'
|
||||||
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
|
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import toCamel from 'camelcase-keys'
|
import toCamel from 'camelcase-keys'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet-async'
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom'
|
import { Link, useLocation, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import MainArea from '@/components/layout/MainArea'
|
import MainArea from '@/components/layout/MainArea'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
import React, { useEffect, useState, useRef } from 'react'
|
import React, { useEffect, useState, useRef } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet-async'
|
||||||
import MdEditor from 'react-markdown-editor-lite'
|
import MdEditor from 'react-markdown-editor-lite'
|
||||||
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
|
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import toCamel from 'camelcase-keys'
|
import toCamel from 'camelcase-keys'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet-async'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import SectionTitle from '@/components/common/SectionTitle'
|
import SectionTitle from '@/components/common/SectionTitle'
|
||||||
import MainArea from '@/components/layout/MainArea'
|
import MainArea from '@/components/layout/MainArea'
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { Route,
|
||||||
|
createBrowserRouter,
|
||||||
|
createRoutesFromElements } from 'react-router-dom'
|
||||||
|
|
||||||
|
import App from '@/App'
|
||||||
+13
-11
@@ -2,15 +2,17 @@ import { defineConfig } from 'vite'
|
|||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig ({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
resolve: { alias: { '@': path.resolve (__dirname, './src') } },
|
resolve: { alias: { '@': path.resolve (__dirname, './src') } },
|
||||||
server: { host: true,
|
server: { host: true,
|
||||||
port: 5173,
|
port: 5173,
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
allowedHosts: ['hub.nizika.monster', 'localhost'],
|
allowedHosts: ['hub.nizika.monster', 'localhost'],
|
||||||
proxy: { '/api': { target: 'http://localhost:3002',
|
proxy: { '/api': { target: 'http://localhost:3002',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false } } }
|
secure: false } },
|
||||||
})
|
watch: { usePolling: true,
|
||||||
|
interval: 100 } } })
|
||||||
|
|||||||
Reference in New Issue
Block a user