Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refs #23 Search accounts #1122

Merged
merged 3 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@
"submit": "Apply"
}
},
"search": {
"title": "Search",
"placeholder": "Search or paste URL",
"results": {
"accounts": "People",
"more": "Load more"
}
},
"timeline": {
"reload": "Reload",
"settings": {
Expand Down
6 changes: 5 additions & 1 deletion src/components/Navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { invoke } from '@tauri-apps/api/tauri'
import { Dispatch, ReactElement, SetStateAction, useEffect, useState } from 'react'
import { Icon } from '@rsuite/icons'
import { Popover, Dropdown, Sidebar, Sidenav, Whisper, Button, Avatar, Badge, FlexboxGrid, useToaster } from 'rsuite'
import { BsPlus, BsGear, BsPencilSquare } from 'react-icons/bs'
import { BsPlus, BsGear, BsPencilSquare, BsSearch } from 'react-icons/bs'
import { Server, ServerSet } from 'src/entities/server'
import { Account } from 'src/entities/account'
import { Timeline } from 'src/entities/timeline'
Expand All @@ -23,6 +23,7 @@ type NavigatorProps = {
openAuthorize: (server: Server) => void
openAnnouncements: (server: Server, account: Account) => void
toggleCompose: () => void
toggleSearch: () => void
openThirdparty: () => void
openSettings: () => void
setHighlighted: Dispatch<SetStateAction<Timeline>>
Expand Down Expand Up @@ -132,6 +133,9 @@ const Navigator: React.FC<NavigatorProps> = (props): ReactElement => {
<Button appearance="link" size="lg" onClick={props.toggleCompose}>
<Icon as={BsPencilSquare} style={{ fontSize: '1.4em' }} />
</Button>
<Button appearance="link" size="lg" onClick={props.toggleSearch}>
<Icon as={BsSearch} style={{ fontSize: '1.4em' }} />
</Button>
</Sidenav.Body>
</Sidenav>
<Sidenav expanded={false}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/compose/Compose.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import failoverImg from 'src/utils/failoverImg'
import Status from './Status'
import { FormattedMessage } from 'react-intl'

const renderAccountIcon = (props: any, ref: any, account: [Account, Server] | undefined) => {
export const renderAccountIcon = (props: any, ref: any, account: [Account, Server] | undefined) => {
if (account && account.length > 0) {
return (
<FlexboxGrid {...props} ref={ref} align="middle">
Expand Down
2 changes: 1 addition & 1 deletion src/components/detail/profile/Followers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const Followers: React.ForwardRefRenderFunction<FuncProps, ArgProps> = (props, r
) : (
<List>
{followers.map(account => (
<List.Item key={account.id} style={{ padding: '4px 0', backgroundColor: 'var(rs-gary-800)' }}>
<List.Item key={account.id} style={{ padding: '4px 0' }}>
<User user={account} relationship={targetRelationship(account)} follow={follow} unfollow={unfollow} />
</List.Item>
))}
Expand Down
2 changes: 1 addition & 1 deletion src/components/detail/profile/Following.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const Following: React.ForwardRefRenderFunction<FuncProps, ArgProps> = (props, r
) : (
<List>
{following.map(account => (
<List.Item key={account.id} style={{ padding: '4px 0', backgroundColor: 'var(rs-gary-800)' }}>
<List.Item key={account.id} style={{ padding: '4px 0' }}>
<User user={account} relationship={targetRelationship(account)} follow={follow} unfollow={unfollow} />
</List.Item>
))}
Expand Down
101 changes: 101 additions & 0 deletions src/components/search/Results.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { Icon } from '@rsuite/icons'
import { Entity, MegalodonInterface } from 'megalodon'
import { useRouter } from 'next/router'
import { useCallback, useState } from 'react'
import { BsSearch, BsPeople } from 'react-icons/bs'
import { FormattedMessage, useIntl } from 'react-intl'
import { Input, InputGroup, List, Avatar } from 'rsuite'
import { Server } from 'src/entities/server'
import emojify from 'src/utils/emojify'

type Props = {
server: Server
client: MegalodonInterface
}

export default function Results(props: Props) {
const { formatMessage } = useIntl()
const router = useRouter()

const [word, setWord] = useState<string>('')
const [accounts, setAccounts] = useState<Array<Entity.Account>>([])

const search = async (word: string) => {
const res = await props.client.search(word, { limit: 5 })
setAccounts(res.data.accounts)
}

const loadMoreAccount = useCallback(async () => {
const res = await props.client.search(word, { type: 'accounts', limit: 5, offset: accounts.length })
setAccounts(prev => prev.concat(res.data.accounts))
}, [word, accounts])

const open = (user: Entity.Account) => {
router.push({ query: { user_id: user.id, server_id: props.server.id, account_id: props.server.account_id } })
}

return (
<>
<div style={{ margin: '12px 0' }}>
<InputGroup inside>
<Input placeholder={formatMessage({ id: 'search.placeholder' })} value={word} onChange={value => setWord(value)} />
<InputGroup.Button onClick={() => search(word)}>
<Icon as={BsSearch} />
</InputGroup.Button>
</InputGroup>
</div>
{/* accounts */}
{accounts.length > 0 && (
<div style={{ width: '100%' }}>
<div style={{ fontSize: '1.2em', marginBottom: '0.4em' }}>
<Icon as={BsPeople} style={{ fontSize: '1.2em', marginRight: '0.2em' }} />
<FormattedMessage id="search.results.accounts" />
</div>
<List>
{accounts.map((account, index) => (
<List.Item key={index} style={{ backgroundColor: 'var(--rs-border-primary)', padding: '4px 0' }}>
<User user={account} open={open} />
</List.Item>
))}
<List.Item
key="more"
style={{ backgroundColor: 'var(--rs-border-primary)', padding: '1em 0', textAlign: 'center', cursor: 'pointer' }}
onClick={() => loadMoreAccount()}
>
<FormattedMessage id="search.results.more" />
</List.Item>
</List>
</div>
)}
</>
)
}

type UserProps = {
user: Entity.Account
open: (user: Entity.Account) => void
}

const User: React.FC<UserProps> = props => {
const { user, open } = props

return (
<div style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }} onClick={() => open(user)}>
{/** icon **/}
<div style={{ width: '56px' }}>
<div style={{ margin: '6px' }}>
<Avatar src={user.avatar} />
</div>
</div>
{/** name **/}
<div style={{ paddingRight: '8px', width: 'cac(100% - 56px)', overflow: 'hidden' }}>
<div style={{ width: '100%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
<span dangerouslySetInnerHTML={{ __html: emojify(user.display_name, user.emojis) }} />
</div>
<div style={{ width: '100%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
<span style={{ color: 'var(--rs-text-tertiary)' }}>@{user.acct}</span>
</div>
</div>
</div>
)
}
83 changes: 83 additions & 0 deletions src/components/search/Search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Icon } from '@rsuite/icons'
import { BsX } from 'react-icons/bs'
import { FormattedMessage } from 'react-intl'
import { Button, Container, Content, FlexboxGrid, Header, Dropdown } from 'rsuite'
import { Server, ServerSet } from 'src/entities/server'
import { renderAccountIcon } from '../compose/Compose'
import { useState, useEffect } from 'react'
import { Account } from 'src/entities/account'
import { invoke } from '@tauri-apps/api/tauri'
import generator, { MegalodonInterface } from 'megalodon'
import { USER_AGENT } from 'src/defaults'
import Results from './Results'

type Props = {
setOpened: (value: boolean) => void
servers: Array<ServerSet>
}

export default function Search(props: Props) {
const [accounts, setAccounts] = useState<Array<[Account, Server]>>([])
const [fromAccount, setFromAccount] = useState<[Account, Server]>()
const [client, setClient] = useState<MegalodonInterface>()
h3poteto marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
const f = async () => {
const accounts = await invoke<Array<[Account, Server]>>('list_accounts')
setAccounts(accounts)

const usual = accounts.find(([a, _]) => a.usual)
if (usual) {
setFromAccount(usual)
} else {
setFromAccount(accounts[0])
}
}
f()
}, [props.servers])

useEffect(() => {
if (!fromAccount || fromAccount.length < 2) {
return
}
const client = generator(fromAccount[1].sns, fromAccount[1].base_url, fromAccount[0].access_token, USER_AGENT)
setClient(client)
}, [fromAccount])

const selectAccount = async (eventKey: string) => {
const account = accounts[parseInt(eventKey)]
setFromAccount(account)
await invoke('set_usual_account', { id: account[0].id })
}

return (
<Container style={{ backgroundColor: 'var(--rs-border-secondary)', height: '100%' }}>
<Header style={{ borderBottom: '1px solid var(--rs-divider-border)', backgroundColor: 'var(--rs-state-hover-bg)' }}>
<FlexboxGrid justify="space-between" align="middle">
<FlexboxGrid.Item style={{ lineHeight: '53px', paddingLeft: '12px', fontSize: '18px' }}>
<FormattedMessage id="search.title" />
</FlexboxGrid.Item>
<FlexboxGrid.Item>
<Button appearance="link" onClick={() => props.setOpened(false)}>
<Icon as={BsX} style={{ fontSize: '1.4em' }} />
</Button>
</FlexboxGrid.Item>
</FlexboxGrid>
</Header>
<Content style={{ height: '100%', margin: '12px', backgroundColor: 'var(--rs-border-secondary)' }}>
<FlexboxGrid>
<FlexboxGrid.Item>
<Dropdown renderToggle={(props, ref) => renderAccountIcon(props, ref, fromAccount)} onSelect={selectAccount}>
{accounts.map((account, index) => (
<Dropdown.Item eventKey={index} key={index}>
@{account[0].username}@{account[1].domain}
</Dropdown.Item>
))}
</Dropdown>
</FlexboxGrid.Item>
</FlexboxGrid>
{fromAccount && <Results client={client} server={fromAccount[1]} />}
</Content>
</Container>
)
}
26 changes: 26 additions & 0 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import ListMemberships from 'src/components/listMemberships/ListMemberships'
import AddListMember from 'src/components/addListMember/AddListMember'
import { useIntl } from 'react-intl'
import { Context } from 'src/i18n'
import Search from 'src/components/search/Search'

const { scrollLeft } = DOMHelper

Expand All @@ -40,6 +41,7 @@ function App() {
const [timelines, setTimelines] = useState<Array<[Timeline, Server]>>([])
const [unreads, setUnreads] = useState<Array<Unread>>([])
const [composeOpened, setComposeOpened] = useState<boolean>(false)
const [searchOpened, setSearchOpened] = useState<boolean>(false)
const [style, setStyle] = useState<CSSProperties>({})
const [highlighted, setHighlighted] = useState<Timeline | null>(null)

Expand Down Expand Up @@ -149,12 +151,22 @@ function App() {

const toggleCompose = () => {
if (servers.find(s => s.account !== null)) {
setSearchOpened(false)
setComposeOpened(previous => !previous)
} else {
toaster.push(alert('info', formatMessage({ id: 'alert.need_auth' })), { placement: 'topStart' })
}
}

const toggleSearch = () => {
if (servers.find(s => s.account !== null)) {
setComposeOpened(false)
setSearchOpened(previous => !previous)
} else {
toaster.push(alert('info', formatMessage({ id: 'alert.need_auth' })), { placement: 'topStart' })
}
}

return (
<div
className="container index"
Expand Down Expand Up @@ -223,6 +235,7 @@ function App() {
openThirdparty={() => dispatch({ target: 'thirdparty', value: true })}
openSettings={() => dispatch({ target: 'settings', value: true })}
toggleCompose={toggleCompose}
toggleSearch={toggleSearch}
setHighlighted={setHighlighted}
setUnreads={setUnreads}
/>
Expand All @@ -239,6 +252,19 @@ function App() {
</div>
)}
</Animation.Transition>
<Animation.Transition
in={searchOpened}
exitedClassName="compose-exited"
exitingClassName="compose-exiting"
enteredClassName="compose-entered"
enteringClassName="compose-entering"
>
{(props, ref) => (
<div {...props} ref={ref} style={{ overflow: 'hidden' }}>
<Search setOpened={setSearchOpened} servers={servers} />
</div>
)}
</Animation.Transition>
<Content className="timeline-space" style={{ display: 'flex', position: 'relative' }} ref={spaceRef}>
{timelines.map(timeline => (
<ShowTimeline
Expand Down
Loading