Merge pull request #5032 from h3poteto/iss-4754/follow

refs #4754 Add follow/unfollow tag button
This commit is contained in:
AkiraFukushima 2024-09-04 22:44:32 +09:00 committed by GitHub
commit 6e8790ef3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 82 additions and 5 deletions

View File

@ -150,7 +150,9 @@
}, },
"detail": { "detail": {
"back": "Back", "back": "Back",
"close": "Close" "close": "Close",
"follow_tag": "Follow hashtag",
"unfollow_tag": "Unfollow hashtag"
}, },
"profile": { "profile": {
"follow": "Follow", "follow": "Follow",

View File

@ -1,6 +1,6 @@
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { HTMLAttributes, useEffect, useState } from 'react' import { HTMLAttributes, useEffect, useState } from 'react'
import { FaChevronLeft, FaX } from 'react-icons/fa6' import { FaChevronLeft, FaUserPlus, FaUserXmark, FaX } from 'react-icons/fa6'
import Thread from './Thread' import Thread from './Thread'
import { Entity, MegalodonInterface } from 'megalodon' import { Entity, MegalodonInterface } from 'megalodon'
import Reply from './Reply' import Reply from './Reply'
@ -8,6 +8,7 @@ import Profile from './Profile'
import Tag from './Tag' import Tag from './Tag'
import { Account } from '@/db' import { Account } from '@/db'
import { useIntl } from 'react-intl' import { useIntl } from 'react-intl'
import { useTimelines } from '../layouts/timelines'
type Props = { type Props = {
client: MegalodonInterface client: MegalodonInterface
@ -19,6 +20,8 @@ export default function Detail(props: Props) {
const [target, setTarget] = useState<'status' | 'reply' | 'profile' | 'tag' | null>(null) const [target, setTarget] = useState<'status' | 'reply' | 'profile' | 'tag' | null>(null)
const router = useRouter() const router = useRouter()
const { formatMessage } = useIntl() const { formatMessage } = useIntl()
const [tagFollowing, setTagFollowing] = useState(false)
const { reloadMenu } = useTimelines()
useEffect(() => { useEffect(() => {
if (router.query.status_id) { if (router.query.status_id) {
@ -34,6 +37,23 @@ export default function Detail(props: Props) {
} }
}, [router.query]) }, [router.query])
useEffect(() => {
if (router.query.tag) {
refreshFollowing(router.query.tag as string)
} else {
setTagFollowing(false)
}
}, [router.query.tag])
const refreshFollowing = async (tag: string) => {
const res = await props.client.getTag(tag)
if (res.data.following) {
setTagFollowing(true)
} else {
setTagFollowing(false)
}
}
const back = () => { const back = () => {
router.back() router.back()
} }
@ -42,6 +62,18 @@ export default function Detail(props: Props) {
router.push({ query: { id: router.query.id, timeline: router.query.timeline } }) router.push({ query: { id: router.query.id, timeline: router.query.timeline } })
} }
const followTag = async (tag: string) => {
await props.client.followTag(tag)
await refreshFollowing(tag)
await reloadMenu()
}
const unfollowTag = async (tag: string) => {
await props.client.unfollowTag(tag)
await refreshFollowing(tag)
await reloadMenu()
}
return ( return (
<> <>
{target && ( {target && (
@ -54,6 +86,28 @@ export default function Detail(props: Props) {
{target === 'tag' && `#${router.query.tag}`} {target === 'tag' && `#${router.query.tag}`}
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
{target === 'tag' && (
<>
{tagFollowing ? (
<button
className="text-lg mr-4"
title={formatMessage({ id: 'detail.unfollow_tag' })}
onClick={() => unfollowTag(router.query.tag as string)}
>
<FaUserXmark />
</button>
) : (
<button
className="text-lg mr-4"
title={formatMessage({ id: 'detail.follow_tag' })}
onClick={() => followTag(router.query.tag as string)}
>
<FaUserPlus />
</button>
)}
</>
)}
<button className="text-lg" title={formatMessage({ id: 'detail.close' })}> <button className="text-lg" title={formatMessage({ id: 'detail.close' })}>
<FaX onClick={close} /> <FaX onClick={close} />
</button> </button>

View File

@ -1,14 +1,28 @@
import { Account, db } from '@/db' import { Account, db } from '@/db'
import { Card, Chip, List, ListItem, ListItemPrefix, ListItemSuffix } from '@material-tailwind/react' import { Card, Chip, List, ListItem, ListItemPrefix, ListItemSuffix } from '@material-tailwind/react'
import generator, { Entity } from 'megalodon' import generator, { Entity, MegalodonInterface } from 'megalodon'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useEffect, useState } from 'react' import { createContext, useContext, useEffect, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { FaBell, FaBookmark, FaGlobe, FaHouse, FaList, FaStar, FaUsers, FaHashtag } from 'react-icons/fa6' import { FaBell, FaBookmark, FaGlobe, FaHouse, FaList, FaStar, FaUsers, FaHashtag } from 'react-icons/fa6'
import { useIntl } from 'react-intl' import { useIntl } from 'react-intl'
import Jump from '../Jump' import Jump from '../Jump'
import { useUnreads } from '@/provider/unreads' import { useUnreads } from '@/provider/unreads'
type Context = {
reloadMenu: () => Promise<void>
}
const TimelinesContext = createContext<Context>({
reloadMenu: async () => {}
})
TimelinesContext.displayName = 'TimelinesContext'
export const useTimelines = () => {
return useContext(TimelinesContext)
}
type LayoutProps = { type LayoutProps = {
children: React.ReactNode children: React.ReactNode
} }
@ -29,6 +43,7 @@ export default function Layout({ children }: LayoutProps) {
const [lists, setLists] = useState<Array<Entity.List>>([]) const [lists, setLists] = useState<Array<Entity.List>>([])
const [followedTags, setFollowedTags] = useState<Array<Entity.Tag>>([]) const [followedTags, setFollowedTags] = useState<Array<Entity.Tag>>([])
const [openJump, setOpenJump] = useState(false) const [openJump, setOpenJump] = useState(false)
const [client, setClient] = useState<MegalodonInterface>()
useHotkeys('mod+k', () => setOpenJump(current => !current)) useHotkeys('mod+k', () => setOpenJump(current => !current))
@ -46,6 +61,7 @@ export default function Layout({ children }: LayoutProps) {
useEffect(() => { useEffect(() => {
if (!account) return if (!account) return
const c = generator(account.sns, account.url, account.access_token, 'Whalebird') const c = generator(account.sns, account.url, account.access_token, 'Whalebird')
setClient(c)
const f = async () => { const f = async () => {
const res = await c.getLists() const res = await c.getLists()
setLists(res.data) setLists(res.data)
@ -97,6 +113,11 @@ export default function Layout({ children }: LayoutProps) {
} }
] ]
const reloadMenu = async () => {
const res = await client.getFollowedTags()
setFollowedTags(res.data)
}
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)} timelines={pages} /> <Jump opened={openJump} close={() => setOpenJump(false)} timelines={pages} />
@ -159,7 +180,7 @@ export default function Layout({ children }: LayoutProps) {
))} ))}
</List> </List>
</Card> </Card>
{children} <TimelinesContext.Provider value={{ reloadMenu }}>{children}</TimelinesContext.Provider>
</section> </section>
) )
} }