import { Entity, MegalodonInterface } from 'megalodon' import dayjs from 'dayjs' import emojify from '@/utils/emojify' import Body from '../status/Body' import Poll from '../status/Poll' import Card from '../status/Card' import Media from '../status/Media' import { findAccount, findLink, ParsedAccount, accountMatch, findTag } from '@/utils/statusParser' import { FaBarsProgress, FaHouse, FaPenToSquare, FaRetweet, FaStar } from 'react-icons/fa6' import { FormattedMessage, useIntl } from 'react-intl' import { MouseEventHandler, useState } from 'react' import { Avatar, List, ListItem, Card as MaterialCard } from '@material-tailwind/react' import { useRouter } from 'next/router' import { invoke } from '@/utils/invoke' import { Account } from '@/db' type Props = { notification: Entity.Notification client: MegalodonInterface account: Account onRefresh: (status: Entity.Status) => void openMedia: (media: Array, index: number) => void } export default function Reaction(props: Props) { const status = props.notification.status const [spoilered, setSpoilered] = useState(status.spoiler_text.length > 0) const { formatMessage } = useIntl() const router = useRouter() const refresh = async () => { const res = await props.client.getStatus(status.id) props.onRefresh(res.data) } const openStatus = () => { router.push({ query: { id: router.query.id, timeline: router.query.timeline, status_id: status.id, detail: true } }) } const openUser = (id: string) => { router.push({ query: { id: router.query.id, timeline: router.query.timeline, user_id: id, detail: true } }) } const statusClicked: MouseEventHandler = async e => { const parsedAccount = findAccount(e.target as HTMLElement, 'status-body') if (parsedAccount) { e.preventDefault() e.stopPropagation() const account = await searchAccount(parsedAccount, status, props.client, props.account.domain) if (account) { router.push({ query: { id: router.query.id, timeline: router.query.timeline, user_id: account.id, detail: true } }) } else { console.warn('account not found', parsedAccount) } return } const parsedTag = findTag(e.target as HTMLElement, 'status-body') if (parsedTag) { e.preventDefault() e.stopPropagation() router.push({ query: { id: router.query.id, timeline: router.query.timeline, tag: parsedTag, detail: true } }) return } const url = findLink(e.target as HTMLElement, 'status-body') if (url) { invoke('open-browser', url) e.preventDefault() e.stopPropagation() } } const onContextMenu: MouseEventHandler = e => { e.preventDefault() hideOthers() const context = document.getElementById(`context-${status.id}`) if (context) { context.style.left = `${e.clientX}px` context.style.top = `${e.clientY}px` context.style.display = 'block' } } const onClick: MouseEventHandler = e => { e.preventDefault() hideOthers() } const hideOthers = () => { const menu = document.getElementsByClassName('context-menu') for (let i = 0; i < menu.length; i++) { ;(menu[i] as HTMLElement).style.display = 'none' } } const copyLink = () => { navigator.clipboard.writeText(status.url) } return (
{actionIcon(props.notification)}
openUser(props.notification.account.id)} >
openUser(status.account.id)} variant="rounded" style={{ width: '40px', height: '40px' }} alt={formatMessage({ id: 'timeline.status.avatar' }, { user: status.account.username })} />
openUser(status.account.id)}> @{status.account.acct}
{!spoilered && ( <> {status.poll && } {status.card && } )}
) } const actionIcon = (notification: Entity.Notification) => { switch (notification.type) { case 'favourite': { return } case 'reblog': case 'quote': { return } case 'poll_expired': case 'poll_vote': { return } case 'status': { return } case 'update': { return } case 'reaction': case 'emoji_reaction': { if (notification.reaction) { if (notification.reaction.url) { return (
) } else { return (
) } } else { return null } } default: return null } } const actionId = (notification: Entity.Notification) => { switch (notification.type) { case 'favourite': return 'notification.favourite.body' case 'reblog': return 'notification.reblog.body' case 'quote': return 'notification.quote.body' case 'poll_expired': return 'notification.poll_expired.body' case 'poll_vote': return 'notification.poll.vote' case 'status': return 'notification.status.body' case 'update': return 'notification.update.body' case 'reaction': case 'emoji_reaction': return 'notification.emoji_reaction.body' default: return '' } } async function searchAccount(account: ParsedAccount, status: Entity.Status, client: MegalodonInterface, domain: string) { if (status.in_reply_to_account_id) { const res = await client.getAccount(status.in_reply_to_account_id) if (res.status === 200) { const user = accountMatch([res.data], account, domain) if (user) return user } } if (status.in_reply_to_id) { const res = await client.getStatusContext(status.id) if (res.status === 200) { const accounts: Array = res.data.ancestors.map(s => s.account).concat(res.data.descendants.map(s => s.account)) const user = accountMatch(accounts, account, domain) if (user) return user } } const res = await client.searchAccount(account.url, { resolve: true }) if (res.data.length === 0) return null const user = accountMatch(res.data, account, domain) if (user) return user return null }