This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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>)
|
||||||
}
|
}
|
||||||
}) ()}
|
}) ()}
|
||||||
</>)
|
</>)
|
||||||
|
|||||||
@@ -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,25 +53,26 @@ 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">
|
? (
|
||||||
{posts.map (post => (
|
<div className="flex flex-wrap gap-4 p-4">
|
||||||
<Link to={`/posts/${ post.id }`}
|
{posts.map (post => (
|
||||||
key={post.id}
|
<Link to={`/posts/${ post.id }`}
|
||||||
className="w-40 h-40 overflow-hidden rounded-lg shadow-md hover:shadow-lg">
|
key={post.id}
|
||||||
<img src={post.thumbnail ?? post.thumbnailBase}
|
className="w-40 h-40 overflow-hidden rounded-lg shadow-md hover:shadow-lg">
|
||||||
className="object-none w-full h-full" />
|
<img src={post.thumbnail ?? post.thumbnailBase}
|
||||||
</Link>))}
|
className="object-none w-full h-full" />
|
||||||
</div>)
|
</Link>))}
|
||||||
: '広場には何もありませんよ.'}
|
</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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user