diff --git a/locales/en/translation.json b/locales/en/translation.json
index ee1850cb..3a6d8ed4 100644
--- a/locales/en/translation.json
+++ b/locales/en/translation.json
@@ -183,6 +183,9 @@
"submit": "Submit"
},
"search": {
- "placeholder": "Search timeline"
+ "placeholder": "Search timeline",
+ "statuses": "Statuses",
+ "accounts": "Accounts",
+ "hashtags": "Hashtags"
}
}
diff --git a/renderer/components/compose/Compose.tsx b/renderer/components/compose/Compose.tsx
index 0f5b936c..2ef8d75f 100644
--- a/renderer/components/compose/Compose.tsx
+++ b/renderer/components/compose/Compose.tsx
@@ -239,6 +239,7 @@ export default function Compose(props: Props) {
color="blue-gray"
containerProps={{ className: 'mb-2' }}
value={spoiler}
+ className="placeholder:opacity-100"
onChange={ev => setSpoiler(ev.target.value)}
placeholder={formatMessage({ id: 'compose.spoiler.placeholder' })}
/>
diff --git a/renderer/components/detail/Profile.tsx b/renderer/components/detail/Profile.tsx
index 4a08449d..e4114a27 100644
--- a/renderer/components/detail/Profile.tsx
+++ b/renderer/components/detail/Profile.tsx
@@ -256,7 +256,12 @@ export default function Profile(props: Props) {
-
+
diff --git a/renderer/components/timelines/Search.tsx b/renderer/components/timelines/Search.tsx
new file mode 100644
index 00000000..968bea95
--- /dev/null
+++ b/renderer/components/timelines/Search.tsx
@@ -0,0 +1,132 @@
+import { Input, Tab, TabPanel, Tabs, TabsBody, TabsHeader } from '@material-tailwind/react'
+import { Entity, MegalodonInterface } from 'megalodon'
+import { useRouter } from 'next/router'
+import { FormEvent, useEffect, useState } from 'react'
+import { FaMagnifyingGlass } from 'react-icons/fa6'
+import { FormattedMessage, useIntl } from 'react-intl'
+import Statuses from './search/Statuses'
+import { Account } from '@/db'
+import Accounts from './search/Accounts'
+import Hashtags from './search/Hashtags'
+import Detail from '../detail/Detail'
+
+type Props = {
+ client: MegalodonInterface
+ account: Account
+ openMedia: (media: Array, index: number) => void
+}
+
+export default function Search(props: Props) {
+ const router = useRouter()
+ const { formatMessage } = useIntl()
+
+ const [query, setQuery] = useState('')
+ const [activeTab, setActiveTab] = useState('statuses')
+ const [results, setResults] = useState({
+ accounts: [],
+ hashtags: [],
+ statuses: []
+ })
+ const [loading, setLoading] = useState(false)
+
+ useEffect(() => {
+ if (router.query.q) {
+ setQuery(router.query.q as string)
+ search(router.query.q as string)
+ }
+ }, [router.query.q])
+
+ const search = (query: string) => {
+ setLoading(true)
+ setResults({
+ accounts: [],
+ hashtags: [],
+ statuses: []
+ })
+ props.client
+ .search(query)
+ .then(res => {
+ setResults(res.data)
+ })
+ .finally(() => {
+ setLoading(false)
+ })
+ }
+
+ const submit = (ev: FormEvent) => {
+ ev.preventDefault()
+ const word = ((ev.target as HTMLFormElement).elements[0] as HTMLInputElement).value
+ if (word.length > 0) {
+ router.push({
+ query: Object.assign({}, router.query, {
+ timeline: 'search',
+ q: word
+ })
+ })
+ }
+ }
+
+ const timelineClass = () => {
+ if (router.query.detail) {
+ return 'timeline-with-drawer'
+ }
+ return 'timeline'
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+ setActiveTab('statuses')}>
+
+
+ setActiveTab('accounts')}>
+
+
+ setActiveTab('hashtags')}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/renderer/components/timelines/Timeline.tsx b/renderer/components/timelines/Timeline.tsx
index 65a305b9..b4e5bb04 100644
--- a/renderer/components/timelines/Timeline.tsx
+++ b/renderer/components/timelines/Timeline.tsx
@@ -1,6 +1,6 @@
import { Account } from '@/db'
import { Entity, MegalodonInterface, WebSocketInterface } from 'megalodon'
-import { useEffect, useState, useCallback, useRef } from 'react'
+import { useEffect, useState, useCallback, useRef, FormEvent } from 'react'
import { Virtuoso } from 'react-virtuoso'
import Status from './status/Status'
import { FormattedMessage, useIntl } from 'react-intl'
@@ -244,6 +244,19 @@ export default function Timeline(props: Props) {
})
}
+ const search = (ev: FormEvent) => {
+ ev.preventDefault()
+ const word = ((ev.target as HTMLFormElement).elements[0] as HTMLInputElement).value
+ if (word.length > 0) {
+ router.push({
+ query: Object.assign({}, router.query, {
+ timeline: 'search',
+ q: word
+ })
+ })
+ }
+ }
+
return (
@@ -252,14 +265,13 @@ export default function Timeline(props: Props) {
{props.timeline.match(/list_(\d+)/) ? <>{list && list.title}> : }
-
diff --git a/renderer/components/timelines/search/Accounts.tsx b/renderer/components/timelines/search/Accounts.tsx
new file mode 100644
index 00000000..073accb1
--- /dev/null
+++ b/renderer/components/timelines/search/Accounts.tsx
@@ -0,0 +1,63 @@
+import { Avatar, Spinner } from '@material-tailwind/react'
+import { Entity } from 'megalodon'
+import emojify from '@/utils/emojify'
+import { useRouter } from 'next/router'
+
+type Props = {
+ users: Array
+ loading: boolean
+}
+
+export default function Accounts(props: Props) {
+ const router = useRouter()
+
+ const openUser = (id: string) => {
+ router.push({ query: { id: router.query.id, timeline: router.query.timeline, user_id: id, detail: true } })
+ }
+
+ return (
+ <>
+
+ {props.users.length > 0 ? (
+ <>
+ {props.users.map((user, index) => (
+
+ ))}
+ >
+ ) : (
+ <>
+ {props.loading && (
+
+
+
+ )}
+ >
+ )}
+
+ >
+ )
+}
+
+type UserProps = {
+ user: Entity.Account
+ openUser: (id: string) => void
+}
+
+function User(props: UserProps) {
+ return (
+
+
props.openUser(props.user.id)}>
+
+
+
+
+ )
+}
diff --git a/renderer/components/timelines/search/Hashtags.tsx b/renderer/components/timelines/search/Hashtags.tsx
new file mode 100644
index 00000000..c8e57f6c
--- /dev/null
+++ b/renderer/components/timelines/search/Hashtags.tsx
@@ -0,0 +1,60 @@
+import { Spinner } from '@material-tailwind/react'
+import { Entity } from 'megalodon'
+import { useRouter } from 'next/router'
+import { FaHashtag } from 'react-icons/fa6'
+
+type Props = {
+ hashtags: Array
+ loading: boolean
+}
+
+export default function Hashtags(props: Props) {
+ const router = useRouter()
+
+ const openTag = (tag: string) => {
+ router.push({ query: { id: router.query.id, timeline: router.query.timeline, tag: tag, detail: true } })
+ }
+
+ return (
+ <>
+
+ {props.hashtags.length > 0 ? (
+ <>
+ {props.hashtags.map(hashtag => (
+
+ ))}
+ >
+ ) : (
+ <>
+ {props.loading && (
+
+
+
+ )}
+ >
+ )}
+
+ >
+ )
+}
+
+type HashtagProps = {
+ hashtag: Entity.Tag
+ openTag: (tag: string) => void
+}
+
+function Hashtag(props: HashtagProps) {
+ return (
+
+
+
props.openTag(props.hashtag.name)}
+ >
+
+ {props.hashtag.name}
+
+
+
+ )
+}
diff --git a/renderer/components/timelines/search/Statuses.tsx b/renderer/components/timelines/search/Statuses.tsx
new file mode 100644
index 00000000..4f214dc5
--- /dev/null
+++ b/renderer/components/timelines/search/Statuses.tsx
@@ -0,0 +1,48 @@
+import { Entity, MegalodonInterface } from 'megalodon'
+import { Virtuoso } from 'react-virtuoso'
+import Status from '../status/Status'
+import { Account } from '@/db'
+import { Spinner } from '@material-tailwind/react'
+
+type Props = {
+ client: MegalodonInterface
+ account: Account
+ statuses: Array
+ loading: boolean
+ openMedia: (media: Array, index: number) => void
+}
+
+export default function Statuses(props: Props) {
+ return (
+ <>
+
+ {props.statuses.length > 0 ? (
+
(
+ {}}
+ filters={[]}
+ />
+ )}
+ />
+ ) : (
+ <>
+ {props.loading && (
+
+
+
+ )}
+ >
+ )}
+
+ >
+ )
+}
diff --git a/renderer/pages/_app.tsx b/renderer/pages/_app.tsx
index 9579946c..053930a7 100644
--- a/renderer/pages/_app.tsx
+++ b/renderer/pages/_app.tsx
@@ -340,12 +340,7 @@ export default function MyApp({ Component, pageProps }: AppProps) {
},
tabsHeader: {
defaultProps: {
- className: ''
- },
- styles: {
- base: {
- bg: 'bg-blue-gray-50 dark:bg-blue-gray-800'
- }
+ className: 'rounded-none border-b border-blue-gray-50 dark:border-blue-gray-900 bg-transparent p-0'
}
},
tab: {
@@ -360,9 +355,6 @@ export default function MyApp({ Component, pageProps }: AppProps) {
initial: {
color: 'text-blue-gray-900 dark:text-blue-gray-200'
}
- },
- indicator: {
- bg: 'bg-white dark:bg-gray-700'
}
}
}
diff --git a/renderer/pages/accounts/[id]/[timeline].tsx b/renderer/pages/accounts/[id]/[timeline].tsx
index 0f72773d..8b3eaab3 100644
--- a/renderer/pages/accounts/[id]/[timeline].tsx
+++ b/renderer/pages/accounts/[id]/[timeline].tsx
@@ -4,6 +4,7 @@ import { useEffect, useReducer, useState } from 'react'
import { Account, db } from '@/db'
import generator, { Entity, MegalodonInterface } from 'megalodon'
import Notifications from '@/components/timelines/Notifications'
+import Search from '@/components/timelines/Search'
import Media from '@/components/Media'
import Report from '@/components/report/Report'
@@ -69,14 +70,26 @@ export default function Page() {
}
/>
) : (
- , index: number) =>
- dispatch({ target: 'media', value: true, object: media, index: index })
- }
- />
+ <>
+ {(router.query.timeline as string) === 'search' ? (
+ , index: number) =>
+ dispatch({ target: 'media', value: true, object: media, index: index })
+ }
+ />
+ ) : (
+ , index: number) =>
+ dispatch({ target: 'media', value: true, object: media, index: index })
+ }
+ />
+ )}
+ >
)}