From b2737bf8bdb15d8619aa3ba42ae34533ede95918 Mon Sep 17 00:00:00 2001 From: AkiraFukushima Date: Mon, 24 Jun 2024 01:05:55 +0900 Subject: [PATCH 1/4] refs #4792 Implement search for statuses --- locales/en/translation.json | 5 +- renderer/components/compose/Compose.tsx | 1 + renderer/components/detail/Profile.tsx | 7 +- renderer/components/timelines/Search.tsx | 104 ++++++++++++++++++ renderer/components/timelines/Timeline.tsx | 24 +++- .../components/timelines/search/Statuses.tsx | 43 ++++++++ renderer/pages/_app.tsx | 10 +- renderer/pages/accounts/[id]/[timeline].tsx | 29 +++-- 8 files changed, 198 insertions(+), 25 deletions(-) create mode 100644 renderer/components/timelines/Search.tsx create mode 100644 renderer/components/timelines/search/Statuses.tsx 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..26bcfcde --- /dev/null +++ b/renderer/components/timelines/Search.tsx @@ -0,0 +1,104 @@ +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' + +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: [] + }) + + useEffect(() => { + if (router.query.q) { + setQuery(router.query.q as string) + search(router.query.q as string) + } + }, [router.query.q]) + + const search = (query: string) => { + setResults({ + accounts: [], + hashtags: [], + statuses: [] + }) + props.client.search(query).then(res => { + setResults(res.data) + }) + } + + 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 + }) + }) + } + } + + return ( + <> +
+
+
+
submit(ev)}> + setQuery(e.target.value)} + icon={} + /> + +
+ + + setActiveTab('statuses')}> + + + setActiveTab('accounts')}> + + + setActiveTab('hashtags')}> + + + + + + + + accounts + 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} : }
-
+ search(ev)}>
diff --git a/renderer/components/timelines/search/Statuses.tsx b/renderer/components/timelines/search/Statuses.tsx new file mode 100644 index 00000000..23c7bed0 --- /dev/null +++ b/renderer/components/timelines/search/Statuses.tsx @@ -0,0 +1,43 @@ +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 + openMedia: (media: Array, index: number) => void +} + +export default function Statuses(props: Props) { + return ( + <> +
+ {props.statuses.length > 0 ? ( + ( + {}} + filters={[]} + /> + )} + /> + ) : ( +
+ +
+ )} +
+ + ) +} 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 }) + } + /> + )} + )} Date: Mon, 24 Jun 2024 01:29:28 +0900 Subject: [PATCH 2/4] refs #4792 Implement search for accounts --- renderer/components/timelines/Search.tsx | 26 ++++++-- .../components/timelines/search/Accounts.tsx | 66 +++++++++++++++++++ .../components/timelines/search/Statuses.tsx | 11 +++- 3 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 renderer/components/timelines/search/Accounts.tsx diff --git a/renderer/components/timelines/Search.tsx b/renderer/components/timelines/Search.tsx index 26bcfcde..017a7a21 100644 --- a/renderer/components/timelines/Search.tsx +++ b/renderer/components/timelines/Search.tsx @@ -6,6 +6,7 @@ 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' type Props = { client: MegalodonInterface @@ -24,6 +25,7 @@ export default function Search(props: Props) { hashtags: [], statuses: [] }) + const [loading, setLoading] = useState(false) useEffect(() => { if (router.query.q) { @@ -33,14 +35,20 @@ export default function Search(props: Props) { }, [router.query.q]) const search = (query: string) => { + setLoading(true) setResults({ accounts: [], hashtags: [], statuses: [] }) - props.client.search(query).then(res => { - setResults(res.data) - }) + props.client + .search(query) + .then(res => { + setResults(res.data) + }) + .finally(() => { + setLoading(false) + }) } const submit = (ev: FormEvent) => { @@ -91,9 +99,17 @@ export default function Search(props: Props) {
- + + + + - accounts hashtags
diff --git a/renderer/components/timelines/search/Accounts.tsx b/renderer/components/timelines/search/Accounts.tsx new file mode 100644 index 00000000..6215db05 --- /dev/null +++ b/renderer/components/timelines/search/Accounts.tsx @@ -0,0 +1,66 @@ +import { Avatar, Spinner } from '@material-tailwind/react' +import { Entity, MegalodonInterface } from 'megalodon' +import emojify from '@/utils/emojify' +import { Virtuoso } from 'react-virtuoso' +import { useRouter } from 'next/router' + +type Props = { + client: MegalodonInterface + 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.loading && ( +
+ +
+ )} + + )} +
+ + ) +} + +type UserProps = { + user: Entity.Account + openUser: (id: string) => void +} + +function User(props: UserProps) { + return ( +
+
props.openUser(props.user.id)}> +
+ +
+
+

+

@{props.user.acct}

+
+
+
+ ) +} diff --git a/renderer/components/timelines/search/Statuses.tsx b/renderer/components/timelines/search/Statuses.tsx index 23c7bed0..4f214dc5 100644 --- a/renderer/components/timelines/search/Statuses.tsx +++ b/renderer/components/timelines/search/Statuses.tsx @@ -8,6 +8,7 @@ type Props = { client: MegalodonInterface account: Account statuses: Array + loading: boolean openMedia: (media: Array, index: number) => void } @@ -33,9 +34,13 @@ export default function Statuses(props: Props) { )} /> ) : ( -
- -
+ <> + {props.loading && ( +
+ +
+ )} + )}
From 247116ebcbfe7d04b212afa95a30a2b2507d18b4 Mon Sep 17 00:00:00 2001 From: AkiraFukushima Date: Wed, 26 Jun 2024 01:03:15 +0900 Subject: [PATCH 3/4] refs #4792 Implement search for tags --- renderer/components/timelines/Search.tsx | 7 ++- .../components/timelines/search/Accounts.tsx | 15 ++--- .../components/timelines/search/Hashtags.tsx | 60 +++++++++++++++++++ 3 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 renderer/components/timelines/search/Hashtags.tsx diff --git a/renderer/components/timelines/Search.tsx b/renderer/components/timelines/Search.tsx index 017a7a21..f290e72b 100644 --- a/renderer/components/timelines/Search.tsx +++ b/renderer/components/timelines/Search.tsx @@ -7,6 +7,7 @@ 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' type Props = { client: MegalodonInterface @@ -108,9 +109,11 @@ export default function Search(props: Props) { /> - + + + + - hashtags diff --git a/renderer/components/timelines/search/Accounts.tsx b/renderer/components/timelines/search/Accounts.tsx index 6215db05..073accb1 100644 --- a/renderer/components/timelines/search/Accounts.tsx +++ b/renderer/components/timelines/search/Accounts.tsx @@ -1,11 +1,9 @@ import { Avatar, Spinner } from '@material-tailwind/react' -import { Entity, MegalodonInterface } from 'megalodon' +import { Entity } from 'megalodon' import emojify from '@/utils/emojify' -import { Virtuoso } from 'react-virtuoso' import { useRouter } from 'next/router' type Props = { - client: MegalodonInterface users: Array loading: boolean } @@ -21,12 +19,11 @@ export default function Accounts(props: Props) { <>
{props.users.length > 0 ? ( - } - /> + <> + {props.users.map((user, index) => ( + + ))} + ) : ( <> {props.loading && ( diff --git a/renderer/components/timelines/search/Hashtags.tsx b/renderer/components/timelines/search/Hashtags.tsx new file mode 100644 index 00000000..b1d87ab6 --- /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, hashtag: 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} +
+
+
+ ) +} From 620ba5f76913159a8afd568904f21ce3c3165ceb Mon Sep 17 00:00:00 2001 From: AkiraFukushima Date: Tue, 2 Jul 2024 22:32:04 +0900 Subject: [PATCH 4/4] refs #4792 Show detail in search results --- renderer/components/timelines/Search.tsx | 13 +++++++++++-- renderer/components/timelines/search/Hashtags.tsx | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/renderer/components/timelines/Search.tsx b/renderer/components/timelines/Search.tsx index f290e72b..968bea95 100644 --- a/renderer/components/timelines/Search.tsx +++ b/renderer/components/timelines/Search.tsx @@ -8,6 +8,7 @@ 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 @@ -65,10 +66,17 @@ export default function Search(props: Props) { } } + const timelineClass = () => { + if (router.query.detail) { + return 'timeline-with-drawer' + } + return 'timeline' + } + return ( <> -
-
+
+
submit(ev)}>
+
) diff --git a/renderer/components/timelines/search/Hashtags.tsx b/renderer/components/timelines/search/Hashtags.tsx index b1d87ab6..c8e57f6c 100644 --- a/renderer/components/timelines/search/Hashtags.tsx +++ b/renderer/components/timelines/search/Hashtags.tsx @@ -12,7 +12,7 @@ export default function Hashtags(props: Props) { const router = useRouter() const openTag = (tag: string) => { - router.push({ query: { id: router.query.id, timeline: router.query.timeline, hashtag: tag, detail: true } }) + router.push({ query: { id: router.query.id, timeline: router.query.timeline, tag: tag, detail: true } }) } return (