Merge pull request #5032 from h3poteto/iss-4754/follow
refs #4754 Add follow/unfollow tag button
This commit is contained in:
commit
6e8790ef3c
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue