From 432736e19d1410f34565731ca8bc1056c005225e Mon Sep 17 00:00:00 2001 From: miteruzo Date: Thu, 31 Jul 2025 23:15:42 +0900 Subject: [PATCH 1/2] #80 --- frontend/index.html | 38 ++++++---- frontend/public/.htaccess | 2 + frontend/scripts/generate-sitemap.js | 104 +++++++++++++++++++++++++-- 3 files changed, 125 insertions(+), 19 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 4ddfde7..e134ffd 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -6,21 +6,33 @@ ぼざクリ タグ広場 〜 ぼざろクリーチャーシリーズ綜合リンク集 - + -
+
+ + +
diff --git a/frontend/public/.htaccess b/frontend/public/.htaccess index 51ba3ad..91a8667 100644 --- a/frontend/public/.htaccess +++ b/frontend/public/.htaccess @@ -8,6 +8,8 @@ RewriteEngine On RewriteBase / + # routes # + RewriteCond %{REQUEST_URI} !^/api RewriteCond %{REQUEST_URI} !^/sitemap\.xml RewriteCond %{REQUEST_FILENAME} !-f diff --git a/frontend/scripts/generate-sitemap.js b/frontend/scripts/generate-sitemap.js index ef03c9d..f30786c 100644 --- a/frontend/scripts/generate-sitemap.js +++ b/frontend/scripts/generate-sitemap.js @@ -1,4 +1,5 @@ import axios from 'axios' +import crypto from 'crypto' import fs from 'fs' import path from 'path' @@ -14,7 +15,10 @@ const SITE_TITLE = 'ぼざクリ タグ広場' const DOMAIN = 'https://hub.nizika.monster' const API_BASE_URL = 'https://hub.nizika.monster/api' -const fetchPosts = async () => (await axios.get (`${ API_BASE_URL }/posts`)).data.posts +const fetchPosts = async tagName => (await axios.get (`${ API_BASE_URL }/posts`, + { params: { ...(tagName && { tags: tagName, + match: 'all', + limit: '20' }) } })).data.posts const fetchPostIds = async () => (await fetchPosts ()).map (post => post.id) const fetchTags = async () => (await axios.get (`${ API_BASE_URL }/tags`)).data @@ -23,20 +27,82 @@ const fetchTagNames = async () => (await fetchTags ()).map (tag => tag.name) const fetchWikiPages = async () => (await axios.get (`${ API_BASE_URL }/wiki`)).data const fetchWikiTitles = async () => (await fetchWikiPages ()).map (page => page.title) +const createPostListOutlet = async tagName => ` +
+
+ +
+
+ ${ (await fetchPosts (tagName)).map (post => ` + + ${ post.title } + `).join ('') } +
+
+
+
` + +const createPostDetailOutlet = post => ` +
+
+
+ ${ ['deerjikist', 'meme', 'character', 'general', 'material', 'meta', 'nico'] + .map (cat => '
    ' + post.tags.filter (tag => tag.category === cat).map (tag => ` +
  • + + ? + + + ${ tag.name } + + ${ tag['post_count'] } +
  • `).join ('') + '
').join ('') } +
+
+
+ ${ post.url } +
+
` + const posts = (await fetchPosts ()).map (post => [ `/posts/${ post.id }`, - `${ post.title || post.url } | ${ SITE_TITLE }` ]) + `${ post.title || post.url } | ${ SITE_TITLE }`, + createPostDetailOutlet (post)]) -const tags = (await fetchTags ()).map (tag => [ - `/posts?${ new URLSearchParams ({ tags: tag.name }) }`, - `${ tag.name } | ${ SITE_TITLE }`]) +const tags = [] +for (const tag of await fetchTags ()) + { + tags.push ([`/posts?${ new URLSearchParams ({ tags: tag.name }) }`, + `${ tag.name } | ${ SITE_TITLE }`, + await createPostListOutlet (tag.name)]) + } const wikiPages = (await fetchWikiPages ()).map (page => [ `/wiki/${ encodeURIComponent (page.title) }`, `${ page.title } Wiki | ${ SITE_TITLE }`]) const routes = [ - ['/', `${ SITE_TITLE } 〜 ぼざろクリーチャーシリーズ綜合リンク集サイト`], + ['/', `${ SITE_TITLE } 〜 ぼざろクリーチャーシリーズ綜合リンク集`, + await createPostListOutlet (), ` + `], ...tags, ...posts, ['/wiki', `Wiki | ${ SITE_TITLE }`], @@ -70,3 +136,29 @@ ${ routes.map (([route, title]) => ` fs.writeFileSync (path.resolve ('dist/sitemap.xml'), xml) fs.writeFileSync (path.resolve ('dist/rss.xml'), rss) + +const baseHTML = fs.readFileSync (path.resolve ('dist/index.html'), 'utf8') +let htaccess = fs.readFileSync (path.resolve ('dist/.htaccess'), 'utf8') +routes.forEach (([route, title, outlet, helmet]) => { + const fileName = ( + route === '/' + ? 'index.html' + : `${ crypto.createHash ('sha256').update (encodeURIComponent (route)).digest ('hex') }.html`) + + let html = baseHTML.replace (/(?<=).*?(?=<\/title>)/, title) + html = html.replace ('<!-- outlet -->', outlet || '') + html = html.replace ('<!-- helmet -->', helmet || '') + fs.writeFileSync (path.resolve (`dist/${ fileName }`), html) + + const [routeBase, q] = route.split ('?') + if (route !== '/') + { + htaccess = htaccess.replace ('# routes #', ` + RewriteCond %{REQUEST_URI} ^${ routeBase }$ + ${ q ? `RewriteCond %{QUERY_STRING} (^|&)${ q }(&|$)` : '' } + RewriteRule ^ /${ fileName } [L] + # routes # + `) + } +}) +fs.writeFileSync (path.resolve ('dist/.htaccess'), htaccess.replace ('# routes #', '')) From 732ad0ef21237fe1c9b2f0794706bfa95ff3d4ff Mon Sep 17 00:00:00 2001 From: miteruzo <miteruzo@naver.com> Date: Thu, 31 Jul 2025 23:40:46 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E7=B4=B0=E9=83=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/WikiBody.tsx | 13 ++++---- frontend/src/components/common/TabGroup.tsx | 34 ++++++++++----------- frontend/src/pages/posts/PostListPage.tsx | 8 ++--- frontend/src/pages/wiki/WikiDetailPage.tsx | 2 +- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/WikiBody.tsx b/frontend/src/components/WikiBody.tsx index d3d707f..30727c7 100644 --- a/frontend/src/components/WikiBody.tsx +++ b/frontend/src/components/WikiBody.tsx @@ -1,13 +1,14 @@ import ReactMarkdown from 'react-markdown' import { Link } from 'react-router-dom' -type Props = { body: string } +type Props = { title: string + body?: string } -export default ({ body }: Props) => ( +export default ({ title, body }: Props) => ( <ReactMarkdown components={{ a: ( - ({ href, children }) => (['/', '.'].some (e => href?.startsWith (e)) - ? <Link to={href!}>{children}</Link> - : <a href={href} target="_blank" rel="noopener noreferrer">{children}</a>)) }}> - {body} + ({ href, children }) => (['/', '.'].some (e => href?.startsWith (e)) + ? <Link to={href!}>{children}</Link> + : <a href={href} target="_blank" rel="noopener noreferrer">{children}</a>)) }}> + {body || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`} </ReactMarkdown>) diff --git a/frontend/src/components/common/TabGroup.tsx b/frontend/src/components/common/TabGroup.tsx index 4444b23..fd15549 100644 --- a/frontend/src/components/common/TabGroup.tsx +++ b/frontend/src/components/common/TabGroup.tsx @@ -2,8 +2,8 @@ import React, { useState } from 'react' import { cn } from '@/lib/utils' type TabProps = { name: string - init?: boolean - children: React.ReactNode } + init?: boolean + children: React.ReactNode } type Props = { children: React.ReactNode } @@ -20,20 +20,20 @@ export default ({ children }: Props) => { return ( <div className="mt-4"> - <div className="flex gap-4"> - {tabs.map ((tab, i) => ( - <a key={i} - href="#" - className={cn (i === current && 'font-bold')} - onClick={ev => { - ev.preventDefault () - setCurrent (i) - }}> - {tab.props.name} - </a>))} - </div> - <div className="mt-2"> - {tabs[current]} - </div> + <div className="flex gap-4"> + {tabs.map ((tab, i) => ( + <a key={i} + href="#" + className={cn (i === current && 'font-bold')} + onClick={ev => { + ev.preventDefault () + setCurrent (i) + }}> + {tab.props.name} + </a>))} + </div> + <div className="mt-2"> + {tabs[current]} + </div> </div>) } diff --git a/frontend/src/pages/posts/PostListPage.tsx b/frontend/src/pages/posts/PostListPage.tsx index e535138..95003b5 100644 --- a/frontend/src/pages/posts/PostListPage.tsx +++ b/frontend/src/pages/posts/PostListPage.tsx @@ -129,11 +129,11 @@ export default () => { {loading && 'Loading...'} <div ref={loaderRef} className="h-12"></div> </Tab> - {(wikiPage && wikiPage.body) && ( - <Tab name="Wiki" init={!(posts.length)}> - <WikiBody body={wikiPage.body} /> + {tags.length === 1 && ( + <Tab name="Wiki"> + <WikiBody body={wikiPage?.body} title={tags[0]} /> <div className="my-2"> - <Link to={`/wiki/${ encodeURIComponent (wikiPage.title) }`}> + <Link to={`/wiki/${ encodeURIComponent (tags[0]) }`}> Wiki を見る </Link> </div> diff --git a/frontend/src/pages/wiki/WikiDetailPage.tsx b/frontend/src/pages/wiki/WikiDetailPage.tsx index 39b781d..be69f59 100644 --- a/frontend/src/pages/wiki/WikiDetailPage.tsx +++ b/frontend/src/pages/wiki/WikiDetailPage.tsx @@ -123,7 +123,7 @@ export default () => { <div className="prose mx-auto p-4"> {wikiPage === undefined ? 'Loading...' - : <WikiBody body={wikiPage?.body || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`} />} + : <WikiBody body={wikiPage?.body} title={title} />} </div> {(!(version) && posts.length > 0) && (