Browse Source

Merge branch 'main' into #59

#92
みてるぞ 1 week ago
parent
commit
cecd9fe593
7 changed files with 154 additions and 47 deletions
  1. +25
    -13
      frontend/index.html
  2. +2
    -0
      frontend/public/.htaccess
  3. +98
    -6
      frontend/scripts/generate-sitemap.js
  4. +7
    -6
      frontend/src/components/WikiBody.tsx
  5. +17
    -17
      frontend/src/components/common/TabGroup.tsx
  6. +4
    -4
      frontend/src/pages/posts/PostListPage.tsx
  7. +1
    -1
      frontend/src/pages/wiki/WikiDetailPage.tsx

+ 25
- 13
frontend/index.html View File

@@ -6,21 +6,33 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="ぼざクリ ぼざろクリーチャーシリーズ 伊地知ニジカ リンク集 Wiki ニコニコ" />
<title>ぼざクリ タグ広場 〜 ぼざろクリーチャーシリーズ綜合リンク集</title>
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "WebSite",
"url": "https://hub.nizika.monster",
"potentialAction": {
"@type": "SearchAction",
"target": "https://hub.nizika.monster/posts?tags={search_term}",
"query-input": "required name=search_term"
}
}
</script>
<!-- helmet -->
</head>
<body>
<div id="root"></div>
<div id="root">
<div class="flex flex-col h-screen w-screen">
<nav class="px-3 flex justify-between items-center w-full min-h-[48px]
bg-yellow-200 dark:bg-red-975 md:bg-yellow-50">
<div class="flex items-center gap-2 h-full">
<a class="mx-4 text-xl font-bold text-pink-600 hover:text-pink-400
dark:text-pink-300 dark:hover:text-pink-100"
href="/">
ぼざクリ タグ広場
</a>
<a class="hidden md:flex h-full items-center px-2" href="/posts">広場</a>
<a class="hidden md:flex h-full items-center px-2" href="/tags">タグ</a>
<a class="hidden md:flex h-full items-center px-2" href="/wiki/ヘルプ:ホーム">Wiki</a>
<a class="hidden md:flex h-full items-center px-2" href="/users">ユーザ</a>
</div>
<a href="#" class="md:hidden ml-auto pr-4
text-pink-600 hover:text-pink-400
dark:text-pink-300 dark:hover:text-pink-100">
Menu
</a>
</nav>
</div>
<!-- outlet -->
</div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

+ 2
- 0
frontend/public/.htaccess View File

@@ -8,6 +8,8 @@
RewriteEngine On
RewriteBase /

# routes #

RewriteCond %{REQUEST_URI} !^/api
RewriteCond %{REQUEST_URI} !^/sitemap\.xml
RewriteCond %{REQUEST_FILENAME} !-f


+ 98
- 6
frontend/scripts/generate-sitemap.js View File

@@ -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 => `
<main class="flex-1 overflow-y-auto p-4">
<div class="mt-4">
<div class="flex gap-4"><a href="#" class="font-bold">広場</a></div>
<div class="mt-2">
<div class="flex flex-wrap gap-6 p-4">
${ (await fetchPosts (tagName)).map (post => `
<a class="w-40 h-40 overflow-hidden rounded-lg shadow-md hover:shadow-lg"
href="/posts/${ post.id }">
<img alt="${ post.title }"
title="${ post.title }"
loading="eager"
fetchpriority="high"
decoding="async"
class="object-none w-full h-full"
src="${ post.url }" />
</a>`).join ('') }
</div>
</div>
</div>
</main>`

const createPostDetailOutlet = post => `
<div class="md:flex md:flex-1">
<div>
<div>
${ ['deerjikist', 'meme', 'character', 'general', 'material', 'meta', 'nico']
.map (cat => '<ul>' + post.tags.filter (tag => tag.category === cat).map (tag => `
<li>
<span>
<a href="/wiki/${ encodeURIComponent (tag.name) }">?</a>
</span>
<a href="/posts?${ new URLSearchParams ({ tags: tag.name }) }">
${ tag.name }
</a>
<span>${ tag['post_count'] }</span>
</li>`).join ('') + '</ul>').join ('') }
</div>
</div>
<main class="flex-1 overflow-y-auto p-4">
<img src="${ post.thumbnail }" alt="${ post.url }" />
</main>
</div>`

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 (), `
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "WebSite",
"url": "https://hub.nizika.monster",
"potentialAction": {
"@type": "SearchAction",
"target": "https://hub.nizika.monster/posts?tags={search_term}",
"query-input": "required name=search_term"
}
}
</script>`],
...tags,
...posts,
['/wiki', `Wiki | ${ SITE_TITLE }`],
@@ -70,3 +136,29 @@ ${ routes.map (([route, title]) => ` <item>

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>)/, 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 #', ''))

+ 7
- 6
frontend/src/components/WikiBody.tsx View File

@@ -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>)

+ 17
- 17
frontend/src/components/common/TabGroup.tsx View File

@@ -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>)
}

+ 4
- 4
frontend/src/pages/posts/PostListPage.tsx View File

@@ -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>


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

@@ -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) && (


Loading…
Cancel
Save