Browse Source

#49 ちょろちょろ

#23
みてるぞ 3 weeks ago
parent
commit
f49640727f
12 changed files with 138 additions and 38 deletions
  1. +60
    -2
      frontend/package-lock.json
  2. +1
    -0
      frontend/package.json
  3. +25
    -0
      frontend/src/components/common/TabControl.tsx
  4. +12
    -20
      frontend/src/pages/PostDetailPage.tsx
  5. +4
    -2
      frontend/src/pages/PostNewPage.tsx
  6. +8
    -4
      frontend/src/pages/PostPage.tsx
  7. +5
    -1
      frontend/src/pages/WikiDetailPage.tsx
  8. +5
    -1
      frontend/src/pages/WikiDiffPage.tsx
  9. +4
    -2
      frontend/src/pages/WikiEditPage.tsx
  10. +5
    -1
      frontend/src/pages/WikiHistoryPage.tsx
  11. +4
    -4
      frontend/src/pages/WikiNewPage.tsx
  12. +5
    -1
      frontend/src/pages/WikiPage.tsx

+ 60
- 2
frontend/package-lock.json View File

@@ -20,6 +20,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-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",
@@ -3974,7 +3975,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
@@ -4120,6 +4120,18 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -4909,7 +4921,6 @@
"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"
@@ -5284,6 +5295,17 @@
"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",
@@ -5361,6 +5383,33 @@
"react": "^19.1.0" "react": "^19.1.0"
} }
}, },
"node_modules/react-fast-compare": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
"license": "MIT"
},
"node_modules/react-helmet": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
"integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
"license": "MIT",
"dependencies": {
"object-assign": "^4.1.1",
"prop-types": "^15.7.2",
"react-fast-compare": "^3.1.1",
"react-side-effect": "^2.1.0"
},
"peerDependencies": {
"react": ">=16.3.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",
@@ -5492,6 +5541,15 @@
"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",


+ 1
- 0
frontend/package.json View File

@@ -21,6 +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-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",


+ 25
- 0
frontend/src/components/common/TabControl.tsx View File

@@ -0,0 +1,25 @@
import React, { useEffect, useState } from 'react'

type Props = { tabs: { [key: string]: React.ReactNode }
init?: string }


export default ({ tabs, init }: Props) => {
const [current, setCurrent] = useState<string> (init
?? Object.keys (tabs)?.[0]
?? '')

return (
<div className="mt-4">
{Object.entries (tabs).map (([name, element]) => (
<a href="#"
className="text-blue-400 hover:underline cursor-pointer"
onClick={(event) => {
event.preventDefault ()
setCurrent (name)
}}>
{name}
</a>))}
{current && tabs[current]}
</div>)
}

+ 12
- 20
frontend/src/pages/PostDetailPage.tsx View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import { Link, useLocation, useParams } from 'react-router-dom' import { Link, useLocation, useParams } from 'react-router-dom'
import axios from 'axios' import axios from 'axios'
import { API_BASE_URL, SITE_TITLE } from '@/config' import { API_BASE_URL, SITE_TITLE } from '@/config'
@@ -9,6 +10,7 @@ import { cn } from '@/lib/utils'
import MainArea from '@/components/layout/MainArea' import MainArea from '@/components/layout/MainArea'
import TagDetailSidebar from '@/components/TagDetailSidebar' import TagDetailSidebar from '@/components/TagDetailSidebar'
import PostEditForm from '@/components/PostEditForm' import PostEditForm from '@/components/PostEditForm'
import TabControl from '@/components/common/TabControl'


import type { Post, Tag, User } from '@/types' import type { Post, Tag, User } from '@/types'


@@ -69,20 +71,13 @@ export default ({ user }: Props) => {
setEditing (true) setEditing (true)
}, [editing]) }, [editing])


if (!(post))
return (
<>
<TagDetailSidebar post={null} />
<MainArea>Loading...</MainArea>
</>)

if (post)
document.title = `${ post.title || post.url } | ${ SITE_TITLE }`

const url = post ? new URL (post.url) : undefined const url = post ? new URL (post.url) : undefined


return ( return (
<> <>
<Helmet>
{post && <title>{`${ post.title || post.url } | ${ SITE_TITLE }`}</title>}
</Helmet>
<TagDetailSidebar post={post} /> <TagDetailSidebar post={post} />
<MainArea> <MainArea>
{post {post
@@ -107,16 +102,13 @@ export default ({ user }: Props) => {
{post.viewed ? '閲覧済' : '未閲覧'} {post.viewed ? '閲覧済' : '未閲覧'}
</Button> </Button>
{(['admin', 'member'].includes (user.role) && editing) && {(['admin', 'member'].includes (user.role) && editing) &&
<div className="mt-4">
<span className="text-blue-400 hover:underline cursor-pointer">
編輯
</span>
<PostEditForm post={post}
onSave={newPost => {
setPost (newPost)
toast ({ description: '更新しました.' })
}} />
</div>}
<TabControl tabs={{
['編輯']: (
<PostEditForm post={post}
onSave={newPost => {
setPost (newPost)
toast ({ description: '更新しました.' })
}} />) }} />}
</div>) </div>)
: 'Loading...'} : 'Loading...'}
</MainArea> </MainArea>


+ 4
- 2
frontend/src/pages/PostNewPage.tsx View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState, useRef } from 'react' import React, { useEffect, useState, useRef } from 'react'
import { Helmet } from 'react-helmet'
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom' import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
import axios from 'axios' import axios from 'axios'
import { API_BASE_URL, SITE_TITLE } from '@/config' import { API_BASE_URL, SITE_TITLE } from '@/config'
@@ -48,8 +49,6 @@ export default () => {
description: '入力を確認してください。' }))) description: '入力を確認してください。' })))
} }


document.title = `広場に投稿を追加 | ${ SITE_TITLE }`

useEffect (() => { useEffect (() => {
void (axios.get ('/api/tags') void (axios.get ('/api/tags')
.then (res => setTags (res.data)) .then (res => setTags (res.data))
@@ -113,6 +112,9 @@ export default () => {


return ( return (
<MainArea> <MainArea>
<Helmet>
<title>{`広場に投稿を追加 | ${ SITE_TITLE }`}</title>
</Helmet>
<div className="max-w-xl mx-auto p-4 space-y-4"> <div className="max-w-xl mx-auto p-4 space-y-4">
<h1 className="text-2xl font-bold mb-2">広場に投稿を追加する</h1> <h1 className="text-2xl font-bold mb-2">広場に投稿を追加する</h1>




+ 8
- 4
frontend/src/pages/PostPage.tsx View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import { Link, useLocation } from 'react-router-dom' import { Link, useLocation } from 'react-router-dom'
import axios from 'axios' import axios from 'axios'
import { API_BASE_URL, SITE_TITLE } from '@/config' import { API_BASE_URL, SITE_TITLE } from '@/config'
@@ -17,10 +18,6 @@ export default () => {
// const anyFlg = query.get ('match') === 'any' // const anyFlg = query.get ('match') === 'any'
const anyFlg = false const anyFlg = false
const tags = tagsQuery.split (' ').filter (e => e !== '') const tags = tagsQuery.split (' ').filter (e => e !== '')
if (tags.length)
document.title = `${ tags.join (anyFlg ? ' or ' : ' and ') } | ${ SITE_TITLE }`
else
document.title = `${ SITE_TITLE } 〜 ぼざクリも、ぼざろ外も、外伝もあるんだよ`


useEffect(() => { useEffect(() => {
const fetchPosts = async () => { const fetchPosts = async () => {
@@ -42,6 +39,13 @@ export default () => {


return ( return (
<> <>
<Helmet>
<title>
{tags.length
? `${ tags.join (anyFlg ? ' or ' : ' and ') } | ${ SITE_TITLE }`
: `${ SITE_TITLE } 〜 ぼざクリも、ぼざろ外も、外伝もあるんだよ`}
</title>
</Helmet>
<TagSidebar posts={posts} /> <TagSidebar posts={posts} />
<MainArea> <MainArea>
<div className="flex flex-wrap gap-4 p-4"> <div className="flex flex-wrap gap-4 p-4">


+ 5
- 1
frontend/src/pages/WikiDetailPage.tsx View File

@@ -1,8 +1,9 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom' import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import axios from 'axios' import axios from 'axios'
import { API_BASE_URL } from '@/config'
import { API_BASE_URL, SITE_TITLE } from '@/config'
import MainArea from '@/components/layout/MainArea' import MainArea from '@/components/layout/MainArea'
import { WikiIdBus } from '@/lib/eventBus/WikiIdBus' import { WikiIdBus } from '@/lib/eventBus/WikiIdBus'


@@ -38,6 +39,9 @@ export default () => {


return ( return (
<MainArea> <MainArea>
<Helmet>
<title>{`${ title } Wiki | ${ SITE_TITLE }`}</title>
</Helmet>
{(wikiPage && version) && ( {(wikiPage && version) && (
<div className="text-sm flex gap-3 items-center justify-center border border-gray-700 rounded px-2 py-1 mb-4"> <div className="text-sm flex gap-3 items-center justify-center border border-gray-700 rounded px-2 py-1 mb-4">
{wikiPage.pred ? ( {wikiPage.pred ? (


+ 5
- 1
frontend/src/pages/WikiDiffPage.tsx View File

@@ -1,8 +1,9 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import { Link, useLocation, useParams } from 'react-router-dom' import { Link, useLocation, useParams } from 'react-router-dom'
import axios from 'axios' import axios from 'axios'
import MainArea from '@/components/layout/MainArea' import MainArea from '@/components/layout/MainArea'
import { API_BASE_URL } from '@/config'
import { API_BASE_URL, SITE_TITLE } from '@/config'


import type { WikiPageDiff } from '@/types' import type { WikiPageDiff } from '@/types'


@@ -25,6 +26,9 @@ export default () => {


return ( return (
<MainArea> <MainArea>
<Helmet>
<title>{`Wiki 差分: ${ diff?.title } | ${ SITE_TITLE }`}</title>
</Helmet>
<h1>{diff?.title}</h1> <h1>{diff?.title}</h1>
<div className="prose mx-auto p-4"> <div className="prose mx-auto p-4">
{diff ? ( {diff ? (


+ 4
- 2
frontend/src/pages/WikiEditPage.tsx View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState, useRef } from 'react' import React, { useEffect, useState, useRef } from 'react'
import { Helmet } from 'react-helmet'
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom' import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
import axios from 'axios' import axios from 'axios'
import { API_BASE_URL, SITE_TITLE } from '@/config' import { API_BASE_URL, SITE_TITLE } from '@/config'
@@ -47,12 +48,13 @@ export default () => {
setTitle (res.data.title) setTitle (res.data.title)
setBody (res.data.body) setBody (res.data.body)
})) }))

document.title = `Wiki ページを編輯 | ${ SITE_TITLE }`
}, [id]) }, [id])


return ( return (
<MainArea> <MainArea>
<Helmet>
<title>{`Wiki ページを編輯 | ${ SITE_TITLE }`}</title>
</Helmet>
<div className="max-w-xl mx-auto p-4 space-y-4"> <div className="max-w-xl mx-auto p-4 space-y-4">
<h1 className="text-2xl font-bold mb-2">Wiki ページを編輯</h1> <h1 className="text-2xl font-bold mb-2">Wiki ページを編輯</h1>




+ 5
- 1
frontend/src/pages/WikiHistoryPage.tsx View File

@@ -1,8 +1,9 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
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'
import axios from 'axios' import axios from 'axios'
import { API_BASE_URL } from '@/config'
import { API_BASE_URL, SITE_TITLE } from '@/config'


import type { WikiPageChange } from '@/types' import type { WikiPageChange } from '@/types'


@@ -21,6 +22,9 @@ export default () => {


return ( return (
<MainArea> <MainArea>
<Helmet>
<title>{`Wiki 変更履歴 | ${ SITE_TITLE }`}</title>
</Helmet>
<table className="table-auto w-full border-collapse"> <table className="table-auto w-full border-collapse">
<thead> <thead>
<tr> <tr>


+ 4
- 4
frontend/src/pages/WikiNewPage.tsx View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState, useRef } from 'react' import React, { useEffect, useState, useRef } from 'react'
import { Helmet } from 'react-helmet'
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom' import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
import axios from 'axios' import axios from 'axios'
import { API_BASE_URL, SITE_TITLE } from '@/config' import { API_BASE_URL, SITE_TITLE } from '@/config'
@@ -42,12 +43,11 @@ export default () => {
description: '入力を確認してください。' }))) description: '入力を確認してください。' })))
} }


useEffect (() => {
document.title = `新規 Wiki ページ | ${ SITE_TITLE }`
}, [])

return ( return (
<MainArea> <MainArea>
<Helmet>
<title>{`新規 Wiki ページ | ${ SITE_TITLE }`}</title>
</Helmet>
<div className="max-w-xl mx-auto p-4 space-y-4"> <div className="max-w-xl mx-auto p-4 space-y-4">
<h1 className="text-2xl font-bold mb-2">新規 Wiki ページ</h1> <h1 className="text-2xl font-bold mb-2">新規 Wiki ページ</h1>




+ 5
- 1
frontend/src/pages/WikiPage.tsx View File

@@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import axios from 'axios' import axios from 'axios'
import MainArea from '@/components/layout/MainArea' import MainArea from '@/components/layout/MainArea'
import { API_BASE_URL } from '@/config'
import { API_BASE_URL, SITE_TITLE } from '@/config'


import type { Category, WikiPage } from '@/types' import type { Category, WikiPage } from '@/types'


@@ -29,6 +30,9 @@ export default () => {


return ( return (
<MainArea> <MainArea>
<Helmet>
<title>{`Wiki | ${ SITE_TITLE }`}</title>
</Helmet>
<div className="max-w-xl"> <div className="max-w-xl">
<h2 className="text-xl mb-4">Wiki</h2> <h2 className="text-xl mb-4">Wiki</h2>
<form onSubmit={handleSearch} className="space-y-2"> <form onSubmit={handleSearch} className="space-y-2">


Loading…
Cancel
Save