This commit is contained in:
2025-06-29 22:27:28 +09:00
parent 281c85f2f6
commit 068d0aed14
4 changed files with 94 additions and 32 deletions
@@ -66,14 +66,14 @@ class WikiPagesController < ApplicationController
end end
def search def search
title = params[:title]&.strip
q = WikiPage.all q = WikiPage.all
q = q.where('title LIKE ?', "%#{ WikiPage.sanitize_sql_like(title) }%") if title.present?
if params[:title].present? render json: q.limit(20).map { |page|
title = params[:title].to_s.strip page.sha = nil
q = q.where('title LIKE ?', "%#{ WikiPage.sanitize_sql_like(title) }%") page }
end
render json: q.limit(20)
end end
def changes def changes
+68 -8
View File
@@ -12,7 +12,7 @@ type Props = { user: User
const enum Menu { None, const enum Menu { None,
Post, Post,
Deerjikist, User,
Tag, Tag,
Wiki } Wiki }
@@ -28,6 +28,8 @@ const TopNav: React.FC = ({ user, setUser }: Props) => {
const [activeIndex, setActiveIndex] = useState (-1) const [activeIndex, setActiveIndex] = useState (-1)
const [suggestions, setSuggestions] = useState<WikiPage[]> ([]) const [suggestions, setSuggestions] = useState<WikiPage[]> ([])
const [suggestionsVsbl, setSuggestionsVsbl] = useState (false) const [suggestionsVsbl, setSuggestionsVsbl] = useState (false)
const [tagSearch, setTagSearch] = useState ('')
const [userSearch, setUserSearch] = useState ('')
const MyLink = ({ to, title, menu, base }: { to: string const MyLink = ({ to, title, menu, base }: { to: string
title: string title: string
@@ -40,16 +42,43 @@ const TopNav: React.FC = ({ user, setUser }: Props) => {
{title} {title}
</Link>) </Link>)
const whenWikiSearchChanged = e => { const whenTagSearchChanged = ev => {
setWikiSearch (e.target.value) // TODO: 実装
const q: string = e.target.value.split (' ').at (-1) setTagSearch (ev.target.value)
const q: string = ev.target.value.split (' ').at (-1)
if (!(q))
{
setSuggestions ([])
return
}
}
const whenWikiSearchChanged = ev => {
// TODO: 実装
setWikiSearch (ev.target.value)
const q: string = ev.target.value.split (' ').at (-1)
if (!(q))
{
setSuggestions ([])
return
}
}
const whenUserSearchChanged = ev => {
// TODO: 実装
setUserSearch (ev.target.value)
const q: string = ev.target.value.split (' ').at (-1)
if (!(q)) if (!(q))
{ {
setSuggestions ([]) setSuggestions ([])
return return
} }
// void (axios.get(`${ API_BASE_URL }/`))
} }
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
@@ -70,8 +99,8 @@ const TopNav: React.FC = ({ user, setUser }: Props) => {
useEffect (() => { useEffect (() => {
if (location.pathname.startsWith ('/posts')) if (location.pathname.startsWith ('/posts'))
setSelectedMenu (Menu.Post) setSelectedMenu (Menu.Post)
else if (location.pathname.startsWith ('/deerjikists')) else if (location.pathname.startsWith ('/users'))
setSelectedMenu (Menu.Deerjikist) setSelectedMenu (Menu.User)
else if (location.pathname.startsWith ('/tags')) else if (location.pathname.startsWith ('/tags'))
setSelectedMenu (Menu.Tag) setSelectedMenu (Menu.Tag)
else if (location.pathname.startsWith ('/wiki')) else if (location.pathname.startsWith ('/wiki'))
@@ -86,9 +115,9 @@ const TopNav: React.FC = ({ user, setUser }: Props) => {
<div className="flex items-center gap-2 h-full"> <div className="flex items-center gap-2 h-full">
<Link to="/posts" className="mx-4 text-xl font-bold text-orange-500"> </Link> <Link to="/posts" className="mx-4 text-xl font-bold text-orange-500"> </Link>
<MyLink to="/posts" title="広場" /> <MyLink to="/posts" title="広場" />
<MyLink to="/deerjikists" title="ニジラー" />
<MyLink to="/tags" title="タグ" /> <MyLink to="/tags" title="タグ" />
<MyLink to="/wiki/ヘルプ:ホーム" base="/wiki" title="Wiki" /> <MyLink to="/wiki/ヘルプ:ホーム" base="/wiki" title="Wiki" />
<MyLink to="/users" title="ニジラー" />
</div> </div>
<div className="ml-auto pr-4"> <div className="ml-auto pr-4">
<Button onClick={() => setSettingsVsbl (true)}>{user?.name || '名もなきニジラー'}</Button> <Button onClick={() => setSettingsVsbl (true)}>{user?.name || '名もなきニジラー'}</Button>
@@ -112,6 +141,23 @@ const TopNav: React.FC = ({ user, setUser }: Props) => {
<Link to="/posts/new" className={subClass}>稿</Link> <Link to="/posts/new" className={subClass}>稿</Link>
<Link to="/wiki/ヘルプ:広場" className={subClass}></Link> <Link to="/wiki/ヘルプ:広場" className={subClass}></Link>
</div>) </div>)
case Menu.Tag:
return (
<div className={className}>
<input type="text"
className={inputBox}
placeholder="タグ検索"
value={tagSearch}
onChange={whenTagSearchChanged}
onFocus={() => setSuggestionsVsbl (true)}
onBlur={() => setSuggestionsVsbl (false)}
onKeyDown={handleKeyDown} />
<Link to="/tags" className={subClass}></Link>
<Link to="/tags/aliases" className={subClass}></Link>
<Link to="/tags/implications" className={subClass}></Link>
<Link to="/wiki/ヘルプ:タグのつけ方" className={subClass}></Link>
<Link to="/wiki/ヘルプ:タグ" className={subClass}></Link>
</div>)
case Menu.Wiki: case Menu.Wiki:
return ( return (
<div className={className}> <div className={className}>
@@ -135,6 +181,20 @@ const TopNav: React.FC = ({ user, setUser }: Props) => {
<Link to={`/wiki/${ wikiId || location.pathname.split ('/')[2] }/edit`} className={subClass}></Link> <Link to={`/wiki/${ wikiId || location.pathname.split ('/')[2] }/edit`} className={subClass}></Link>
</>} </>}
</div>) </div>)
case Menu.User:
return (
<div className={className}>
<input type="text"
className={inputBox}
placeholder="ニジラー検索"
value={userSearch}
onChange={whenUserSearchChanged}
onFocus={() => setSuggestionsVsbl (true)}
onBlur={() => setSuggestionsVsbl (false)}
onKeyDown={handleKeyDown} />
<Link to="/users" className={subClass}></Link>
{user && <Link to={`/users/${ user.id }`} className={subClass}></Link>}
</div>)
} }
}) ()} }) ()}
</>) </>)
+7 -6
View File
@@ -14,7 +14,7 @@ import type { Post, Tag, WikiPage } from '@/types'
export default () => { export default () => {
const [posts, setPosts] = useState<Post[]> ([]) const [posts, setPosts] = useState<Post[] | null> (null)
const [wikiPage, setWikiPage] = useState<WikiPage | null> (null) const [wikiPage, setWikiPage] = useState<WikiPage | null> (null)
const location = useLocation () const location = useLocation ()
@@ -30,7 +30,7 @@ export default () => {
.then (res => setPosts (toCamel (res.data, { deep: true }))) .then (res => setPosts (toCamel (res.data, { deep: true })))
.catch (err => { .catch (err => {
console.error ('Failed to fetch posts:', err) console.error ('Failed to fetch posts:', err)
setPosts ([]) setPosts (null)
})) }))
setWikiPage (null) setWikiPage (null)
@@ -53,11 +53,12 @@ export default () => {
: `${ SITE_TITLE } 〜 ぼざクリも、ぼざろ外も、外伝もあるんだよ`} : `${ SITE_TITLE } 〜 ぼざクリも、ぼざろ外も、外伝もあるんだよ`}
</title> </title>
</Helmet> </Helmet>
<TagSidebar posts={posts} /> <TagSidebar posts={posts || []} />
<MainArea> <MainArea>
<TabGroup key={wikiPage}> <TabGroup key={wikiPage}>
<Tab name="広場"> <Tab name="広場">
{posts.length {posts == null ? 'Loading...' : (
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 => (
@@ -68,10 +69,10 @@ export default () => {
className="object-none w-full h-full" /> className="object-none w-full h-full" />
</Link>))} </Link>))}
</div>) </div>)
: '広場には何もありませんよ.'} : '広場には何もありませんよ.')}
</Tab> </Tab>
{(wikiPage && wikiPage.body) && ( {(wikiPage && wikiPage.body) && (
<Tab name="Wiki" init={!(posts.length)}> <Tab name="Wiki" init={!(posts?.length)}>
<WikiBody body={wikiPage.body} /> <WikiBody body={wikiPage.body} />
<div className="my-2"> <div className="my-2">
<Link to={`/wiki/${ wikiPage.title }`}>Wiki </Link> <Link to={`/wiki/${ wikiPage.title }`}>Wiki </Link>
+3 -2
View File
@@ -1,4 +1,5 @@
import axios from 'axios' import axios from 'axios'
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'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
@@ -17,7 +18,7 @@ export default () => {
const search = () => { const search = () => {
void (axios.get (`${ API_BASE_URL }/wiki/search`, { params: { title } }) void (axios.get (`${ API_BASE_URL }/wiki/search`, { params: { title } })
.then (res => setResults (res.data))) .then (res => setResults (toCamel (res.data, { deep: true }))))
} }
const handleSearch = (e: React.FormEvent) => { const handleSearch = (e: React.FormEvent) => {
@@ -82,7 +83,7 @@ export default () => {
</Link> </Link>
</td> </td>
<td className="p-2 text-gray-100 text-sm"> <td className="p-2 text-gray-100 text-sm">
{page.updated_at} {page.updatedAt}
</td> </td>
</tr>))} </tr>))}
</tbody> </tbody>