From f49640727fe0452d152b30cf1e949713b192183a Mon Sep 17 00:00:00 2001 From: miteruzo Date: Thu, 26 Jun 2025 07:59:08 +0900 Subject: [PATCH] =?UTF-8?q?#49=20=E3=81=A1=E3=82=87=E3=82=8D=E3=81=A1?= =?UTF-8?q?=E3=82=87=E3=82=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 62 ++++++++++++++++++- frontend/package.json | 1 + frontend/src/components/common/TabControl.tsx | 25 ++++++++ frontend/src/pages/PostDetailPage.tsx | 32 ++++------ frontend/src/pages/PostNewPage.tsx | 6 +- frontend/src/pages/PostPage.tsx | 12 ++-- frontend/src/pages/WikiDetailPage.tsx | 6 +- frontend/src/pages/WikiDiffPage.tsx | 6 +- frontend/src/pages/WikiEditPage.tsx | 6 +- frontend/src/pages/WikiHistoryPage.tsx | 6 +- frontend/src/pages/WikiNewPage.tsx | 8 +-- frontend/src/pages/WikiPage.tsx | 6 +- 12 files changed, 138 insertions(+), 38 deletions(-) create mode 100644 frontend/src/components/common/TabControl.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 550a659..99b75d6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,6 +20,7 @@ "markdown-it": "^14.1.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-helmet": "^6.1.0", "react-markdown": "^10.1.0", "react-markdown-editor-lite": "^1.3.4", "react-router-dom": "^6.30.0", @@ -3974,7 +3975,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -4120,6 +4120,18 @@ "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": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4909,7 +4921,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5284,6 +5295,17 @@ "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": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -5361,6 +5383,33 @@ "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": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", @@ -5492,6 +5541,15 @@ "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": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9ab3f97..ebeed51 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "markdown-it": "^14.1.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-helmet": "^6.1.0", "react-markdown": "^10.1.0", "react-markdown-editor-lite": "^1.3.4", "react-router-dom": "^6.30.0", diff --git a/frontend/src/components/common/TabControl.tsx b/frontend/src/components/common/TabControl.tsx new file mode 100644 index 0000000..f805bd2 --- /dev/null +++ b/frontend/src/components/common/TabControl.tsx @@ -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 (init + ?? Object.keys (tabs)?.[0] + ?? '') + + return ( +
+ {Object.entries (tabs).map (([name, element]) => ( + { + event.preventDefault () + setCurrent (name) + }}> + {name} + ))} + {current && tabs[current]} +
) +} diff --git a/frontend/src/pages/PostDetailPage.tsx b/frontend/src/pages/PostDetailPage.tsx index 7e87b90..062fa1e 100644 --- a/frontend/src/pages/PostDetailPage.tsx +++ b/frontend/src/pages/PostDetailPage.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react' +import { Helmet } from 'react-helmet' import { Link, useLocation, useParams } from 'react-router-dom' import axios from 'axios' import { API_BASE_URL, SITE_TITLE } from '@/config' @@ -9,6 +10,7 @@ import { cn } from '@/lib/utils' import MainArea from '@/components/layout/MainArea' import TagDetailSidebar from '@/components/TagDetailSidebar' import PostEditForm from '@/components/PostEditForm' +import TabControl from '@/components/common/TabControl' import type { Post, Tag, User } from '@/types' @@ -69,20 +71,13 @@ export default ({ user }: Props) => { setEditing (true) }, [editing]) - if (!(post)) - return ( - <> - - Loading... - ) - - if (post) - document.title = `${ post.title || post.url } | ${ SITE_TITLE }` - const url = post ? new URL (post.url) : undefined return ( <> + + {post && {`${ post.title || post.url } | ${ SITE_TITLE }`}} + {post @@ -107,16 +102,13 @@ export default ({ user }: Props) => { {post.viewed ? '閲覧済' : '未閲覧'} {(['admin', 'member'].includes (user.role) && editing) && -
- - 編輯 - - { - setPost (newPost) - toast ({ description: '更新しました.' }) - }} /> -
} + { + setPost (newPost) + toast ({ description: '更新しました.' }) + }} />) }} />} ) : 'Loading...'}
diff --git a/frontend/src/pages/PostNewPage.tsx b/frontend/src/pages/PostNewPage.tsx index d9ca452..ad619a4 100644 --- a/frontend/src/pages/PostNewPage.tsx +++ b/frontend/src/pages/PostNewPage.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useRef } from 'react' +import { Helmet } from 'react-helmet' import { Link, useLocation, useParams, useNavigate } from 'react-router-dom' import axios from 'axios' import { API_BASE_URL, SITE_TITLE } from '@/config' @@ -48,8 +49,6 @@ export default () => { description: '入力を確認してください。' }))) } - document.title = `広場に投稿を追加 | ${ SITE_TITLE }` - useEffect (() => { void (axios.get ('/api/tags') .then (res => setTags (res.data)) @@ -113,6 +112,9 @@ export default () => { return ( + + {`広場に投稿を追加 | ${ SITE_TITLE }`} +

広場に投稿を追加する

diff --git a/frontend/src/pages/PostPage.tsx b/frontend/src/pages/PostPage.tsx index 80859fc..d531008 100644 --- a/frontend/src/pages/PostPage.tsx +++ b/frontend/src/pages/PostPage.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react' +import { Helmet } from 'react-helmet' import { Link, useLocation } from 'react-router-dom' import axios from 'axios' import { API_BASE_URL, SITE_TITLE } from '@/config' @@ -17,10 +18,6 @@ export default () => { // const anyFlg = query.get ('match') === 'any' const anyFlg = false 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(() => { const fetchPosts = async () => { @@ -42,6 +39,13 @@ export default () => { return ( <> + + + {tags.length + ? `${ tags.join (anyFlg ? ' or ' : ' and ') } | ${ SITE_TITLE }` + : `${ SITE_TITLE } 〜 ぼざクリも、ぼざろ外も、外伝もあるんだよ`} + +
diff --git a/frontend/src/pages/WikiDetailPage.tsx b/frontend/src/pages/WikiDetailPage.tsx index b58a9ac..e642f19 100644 --- a/frontend/src/pages/WikiDetailPage.tsx +++ b/frontend/src/pages/WikiDetailPage.tsx @@ -1,8 +1,9 @@ import { useEffect, useState } from 'react' +import { Helmet } from 'react-helmet' import { Link, useLocation, useParams, useNavigate } from 'react-router-dom' import ReactMarkdown from 'react-markdown' 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 { WikiIdBus } from '@/lib/eventBus/WikiIdBus' @@ -38,6 +39,9 @@ export default () => { return ( + + {`${ title } Wiki | ${ SITE_TITLE }`} + {(wikiPage && version) && (
{wikiPage.pred ? ( diff --git a/frontend/src/pages/WikiDiffPage.tsx b/frontend/src/pages/WikiDiffPage.tsx index 5e13c44..5241a20 100644 --- a/frontend/src/pages/WikiDiffPage.tsx +++ b/frontend/src/pages/WikiDiffPage.tsx @@ -1,8 +1,9 @@ import { useEffect, useState } from 'react' +import { Helmet } from 'react-helmet' import { Link, useLocation, useParams } from 'react-router-dom' import axios from 'axios' 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' @@ -25,6 +26,9 @@ export default () => { return ( + + {`Wiki 差分: ${ diff?.title } | ${ SITE_TITLE }`} +

{diff?.title}

{diff ? ( diff --git a/frontend/src/pages/WikiEditPage.tsx b/frontend/src/pages/WikiEditPage.tsx index 926d235..ddb1836 100644 --- a/frontend/src/pages/WikiEditPage.tsx +++ b/frontend/src/pages/WikiEditPage.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useRef } from 'react' +import { Helmet } from 'react-helmet' import { Link, useLocation, useParams, useNavigate } from 'react-router-dom' import axios from 'axios' import { API_BASE_URL, SITE_TITLE } from '@/config' @@ -47,12 +48,13 @@ export default () => { setTitle (res.data.title) setBody (res.data.body) })) - - document.title = `Wiki ページを編輯 | ${ SITE_TITLE }` }, [id]) return ( + + {`Wiki ページを編輯 | ${ SITE_TITLE }`} +

Wiki ページを編輯

diff --git a/frontend/src/pages/WikiHistoryPage.tsx b/frontend/src/pages/WikiHistoryPage.tsx index c645fd2..372a69f 100644 --- a/frontend/src/pages/WikiHistoryPage.tsx +++ b/frontend/src/pages/WikiHistoryPage.tsx @@ -1,8 +1,9 @@ import { useEffect, useState } from 'react' +import { Helmet } from 'react-helmet' import { Link, useLocation, useParams } from 'react-router-dom' import MainArea from '@/components/layout/MainArea' import axios from 'axios' -import { API_BASE_URL } from '@/config' +import { API_BASE_URL, SITE_TITLE } from '@/config' import type { WikiPageChange } from '@/types' @@ -21,6 +22,9 @@ export default () => { return ( + + {`Wiki 変更履歴 | ${ SITE_TITLE }`} + diff --git a/frontend/src/pages/WikiNewPage.tsx b/frontend/src/pages/WikiNewPage.tsx index a32fec9..b581f15 100644 --- a/frontend/src/pages/WikiNewPage.tsx +++ b/frontend/src/pages/WikiNewPage.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useRef } from 'react' +import { Helmet } from 'react-helmet' import { Link, useLocation, useParams, useNavigate } from 'react-router-dom' import axios from 'axios' import { API_BASE_URL, SITE_TITLE } from '@/config' @@ -42,12 +43,11 @@ export default () => { description: '入力を確認してください。' }))) } - useEffect (() => { - document.title = `新規 Wiki ページ | ${ SITE_TITLE }` - }, []) - return ( + + {`新規 Wiki ページ | ${ SITE_TITLE }`} +

新規 Wiki ページ

diff --git a/frontend/src/pages/WikiPage.tsx b/frontend/src/pages/WikiPage.tsx index f76af57..9a52752 100644 --- a/frontend/src/pages/WikiPage.tsx +++ b/frontend/src/pages/WikiPage.tsx @@ -1,8 +1,9 @@ import React, { useEffect, useState } from 'react' +import { Helmet } from 'react-helmet' import { Link } from 'react-router-dom' import axios from 'axios' 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' @@ -29,6 +30,9 @@ export default () => { return ( + + {`Wiki | ${ SITE_TITLE }`} +

Wiki