Merge pull request #4766 from h3poteto/iss-4653/search

refs #4653 Add jump modal
This commit is contained in:
AkiraFukushima 2024-01-12 22:22:44 +09:00 committed by GitHub
commit 44e80b0265
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 118 additions and 5 deletions

View File

@ -134,5 +134,8 @@
"title": "Report {user}", "title": "Report {user}",
"detail": "Detail", "detail": "Detail",
"submit": "Submit" "submit": "Submit"
},
"search": {
"placeholder": "Search timeline"
} }
} }

View File

@ -0,0 +1,104 @@
import { Dialog, DialogBody, Input, List, ListItem } from '@material-tailwind/react'
import { useRouter } from 'next/router'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useIntl } from 'react-intl'
type Props = {
opened: boolean
close: () => void
}
type Timeline = {
title: string
path: string
}
export default function Jump(props: Props) {
const router = useRouter()
const { formatMessage } = useIntl()
const [keyword, setKeyword] = useState('')
const [match, setMatch] = useState<Array<Timeline>>([])
const [selected, setSelected] = useState(0)
const [timelines, setTimelines] = useState<Array<Timeline>>([])
const inputRef = useRef<HTMLInputElement>()
const handleKeyPress = useCallback(
(event: KeyboardEvent) => {
if (event.key === 'Enter') {
if (match[selected]) {
jump(match[selected].path)
}
}
if (event.key === 'ArrowUp') {
setSelected(current => (current > 0 ? current - 1 : current))
} else if (event.key === 'ArrowDown') {
setSelected(current => (match[current + 1] ? current + 1 : current))
}
},
[match, selected, setSelected]
)
useEffect(() => {
inputRef.current?.addEventListener('keydown', handleKeyPress)
return () => {
inputRef.current?.removeEventListener('keydown', handleKeyPress)
}
}, [handleKeyPress, props.opened])
useEffect(() => {
if (keyword.length > 0) {
const m = timelines
.map(v => {
if (v.title.toLowerCase().includes(keyword.toLowerCase())) {
return v
} else {
return null
}
})
.filter(v => v !== null)
setMatch(m)
} else {
setMatch(timelines)
}
}, [keyword, timelines])
useEffect(() => {
setTimelines([
{ title: formatMessage({ id: 'timeline.home' }), path: `/accounts/${router.query.id}/home` },
{ title: formatMessage({ id: 'timeline.notifications' }), path: `/accounts/${router.query.id}/notifications` },
{ title: formatMessage({ id: 'timeline.local' }), path: `/accounts/${router.query.id}/local` },
{ title: formatMessage({ id: 'timeline.public' }), path: `/accounts/${router.query.id}/public` }
])
}, [router.query.id])
const jump = (path: string) => {
props.close()
setKeyword('')
setSelected(0)
console.log(path)
router.push(path)
}
return (
<Dialog open={props.opened} handler={props.close} size="sm">
<DialogBody>
<Input
value={keyword}
onChange={e => setKeyword(e.target.value)}
size="lg"
label={formatMessage({ id: 'search.placeholder' })}
ref={inputRef}
/>
<List>
{match.map((m, index) => (
<ListItem key={index} selected={index === selected} onClick={() => jump(m.path)}>
{m.title}
</ListItem>
))}
</List>
</DialogBody>
</Dialog>
)
}

View File

@ -3,8 +3,10 @@ import { Card, List, ListItem, ListItemPrefix } from '@material-tailwind/react'
import generator, { Entity } from 'megalodon' import generator, { Entity } from 'megalodon'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { FaBell, FaGlobe, FaHouse, FaList, FaUsers } from 'react-icons/fa6' import { FaBell, FaGlobe, FaHouse, FaList, FaUsers } from 'react-icons/fa6'
import { useIntl } from 'react-intl' import { useIntl } from 'react-intl'
import Jump from '../Jump'
type LayoutProps = { type LayoutProps = {
children: React.ReactNode children: React.ReactNode
@ -16,6 +18,9 @@ export default function Layout({ children }: LayoutProps) {
const [account, setAccount] = useState<Account | null>(null) const [account, setAccount] = useState<Account | null>(null)
const [lists, setLists] = useState<Array<Entity.List>>([]) const [lists, setLists] = useState<Array<Entity.List>>([])
const [openJump, setOpenJump] = useState(false)
useHotkeys('ctrl+k', () => setOpenJump(current => !current))
useEffect(() => { useEffect(() => {
if (router.query.id) { if (router.query.id) {
@ -67,6 +72,7 @@ export default function Layout({ children }: LayoutProps) {
return ( return (
<section className="flex h-screen w-full overflow-hidden"> <section className="flex h-screen w-full overflow-hidden">
<Jump opened={openJump} close={() => setOpenJump(false)} />
<Card className="text-blue-100 sidebar w-64 bg-blue-950 rounded-none"> <Card className="text-blue-100 sidebar w-64 bg-blue-950 rounded-none">
<div className="max-w-full pl-4 mt-2 mb-4 my-profile"> <div className="max-w-full pl-4 mt-2 mb-4 my-profile">
<p>{account?.username}</p> <p>{account?.username}</p>

View File

@ -135,8 +135,8 @@ export default function Notifications(props: Props) {
)} )}
/> />
) : ( ) : (
<div className="w-full" style={{ height: '100%' }}> <div className="w-full pt-6" style={{ height: '100%' }}>
<Spinner className="m-auto mt-6" /> <Spinner className="m-auto" />
</div> </div>
)} )}
</div> </div>

View File

@ -224,8 +224,8 @@ export default function Timeline(props: Props) {
)} )}
/> />
) : ( ) : (
<div className="w-full" style={{ height: `calc(100% - ${composeHeight}px)` }}> <div className="w-full pt-6" style={{ height: `calc(100% - ${composeHeight}px)` }}>
<Spinner className="m-auto mt-6" /> <Spinner className="m-auto" />
</div> </div>
)} )}

View File

@ -12,7 +12,7 @@ export default function Index() {
if (accounts.length > 0) { if (accounts.length > 0) {
if (typeof localStorage !== 'undefined') { if (typeof localStorage !== 'undefined') {
const lastAccount = localStorage.getItem(`lastAccount`) const lastAccount = localStorage.getItem(`lastAccount`)
if (lastAccount) { if (parseInt(lastAccount) >= 0) {
router.push(`/accounts/${lastAccount}`) router.push(`/accounts/${lastAccount}`)
} else { } else {
router.push(`/accounts/${accounts[0].id}`) router.push(`/accounts/${accounts[0].id}`)