Merge pull request #4766 from h3poteto/iss-4653/search
refs #4653 Add jump modal
This commit is contained in:
commit
44e80b0265
|
@ -134,5 +134,8 @@
|
||||||
"title": "Report {user}",
|
"title": "Report {user}",
|
||||||
"detail": "Detail",
|
"detail": "Detail",
|
||||||
"submit": "Submit"
|
"submit": "Submit"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Search timeline"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -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}`)
|
||||||
|
|
Loading…
Reference in New Issue