Add profile in detail
This commit is contained in:
parent
6ec9ce3536
commit
e1d5a17675
|
@ -97,5 +97,10 @@
|
|||
"attachment_type": "You can attach only images or videos"
|
||||
},
|
||||
"upload_error": "Failed to upload the file"
|
||||
},
|
||||
"profile": {
|
||||
"follow": "Follow",
|
||||
"unfollow": "Unfollow",
|
||||
"open_original": "Open original page"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,3 +31,7 @@
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.raw-html .invisible {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -4,13 +4,14 @@ import { FaChevronLeft, FaX } from 'react-icons/fa6'
|
|||
import Thread from './Thread'
|
||||
import { MegalodonInterface } from 'megalodon'
|
||||
import Reply from './Reply'
|
||||
import Profile from './Profile'
|
||||
|
||||
type Props = {
|
||||
client: MegalodonInterface
|
||||
} & HTMLAttributes<HTMLElement>
|
||||
|
||||
export default function Detail(props: Props) {
|
||||
const [target, setTarget] = useState<'status' | 'reply' | null>(null)
|
||||
const [target, setTarget] = useState<'status' | 'reply' | 'profile' | null>(null)
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -18,6 +19,8 @@ export default function Detail(props: Props) {
|
|||
setTarget('status')
|
||||
} else if (router.query.reply_target_id) {
|
||||
setTarget('reply')
|
||||
} else if (router.query.user_id) {
|
||||
setTarget('profile')
|
||||
} else {
|
||||
setTarget(null)
|
||||
}
|
||||
|
@ -41,6 +44,7 @@ export default function Detail(props: Props) {
|
|||
</div>
|
||||
{target === 'status' && <Thread client={props.client} status_id={router.query.status_id as string} />}
|
||||
{target === 'reply' && <Reply client={props.client} status_id={router.query.reply_target_id as string} />}
|
||||
{target === 'profile' && <Profile client={props.client} user_id={router.query.user_id as string} />}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
import emojify from '@/utils/emojify'
|
||||
import { Avatar, Button, Dropdown } from 'flowbite-react'
|
||||
import { Entity, MegalodonInterface } from 'megalodon'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { FaEllipsisVertical } from 'react-icons/fa6'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
|
||||
type Props = {
|
||||
client: MegalodonInterface
|
||||
user_id: string
|
||||
}
|
||||
|
||||
export default function Profile(props: Props) {
|
||||
const [user, setUser] = useState<Entity.Account | null>(null)
|
||||
const [relationship, setRelationship] = useState<Entity.Relationship | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const f = async () => {
|
||||
if (props.user_id) {
|
||||
const res = await props.client.getAccount(props.user_id)
|
||||
setUser(res.data)
|
||||
const rel = await props.client.getRelationship(props.user_id)
|
||||
setRelationship(rel.data)
|
||||
}
|
||||
}
|
||||
f()
|
||||
}, [props.user_id, props.client])
|
||||
|
||||
const follow = async (id: string) => {
|
||||
const rel = await props.client.followAccount(id)
|
||||
setRelationship(rel.data)
|
||||
}
|
||||
|
||||
const unfollow = async (id: string) => {
|
||||
const rel = await props.client.unfollowAccount(id)
|
||||
setRelationship(rel.data)
|
||||
}
|
||||
|
||||
const openOriginal = async (url: string) => {
|
||||
global.ipc.invoke('open-browser', url)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ height: 'calc(100% - 50px)' }} className="overflow-y-auto">
|
||||
{user && relationship && (
|
||||
<>
|
||||
<div className="header-image w-full bg-gray-100">
|
||||
<img src={user.header} alt="header image" className="w-full object-cover h-40" />
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<div className="flex items-end justify-between" style={{ marginTop: '-50px' }}>
|
||||
<Avatar img={user.avatar} size="lg" stacked />
|
||||
<div className="flex gap-2">
|
||||
{relationship.following ? (
|
||||
<Button onClick={() => unfollow(user.id)}>
|
||||
<FormattedMessage id="profile.unfollow" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={() => follow(user.id)}>
|
||||
<FormattedMessage id="profile.follow" />
|
||||
</Button>
|
||||
)}
|
||||
<Dropdown
|
||||
label=""
|
||||
renderTrigger={() => (
|
||||
<Button color="gray">
|
||||
<FaEllipsisVertical />
|
||||
</Button>
|
||||
)}
|
||||
>
|
||||
<Dropdown.Item onClick={() => openOriginal(user.url)}>
|
||||
<FormattedMessage id="profile.open_original" />
|
||||
</Dropdown.Item>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-4">
|
||||
<div className="font-bold">{user.display_name}</div>
|
||||
<div className="text-gray-500">@{user.acct}</div>
|
||||
<div className="mt-4">
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: emojify(user.note, user.emojis) }}
|
||||
className="overflow-hidden break-all text-gray-800"
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-gray-100 overflow-hidden break-all raw-html mt-2">
|
||||
{user.fields.map((data, index) => (
|
||||
<dl key={index} className="px-4 py-2 border-gray-200 border-b">
|
||||
<dt className="text-gray-500">{data.name}</dt>
|
||||
<dd className="text-gray-700" dangerouslySetInnerHTML={{ __html: emojify(data.value, user.emojis) }} />
|
||||
</dl>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -52,7 +52,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||
streaming.stop()
|
||||
})
|
||||
streamings.current = []
|
||||
console.log('close user streaming')
|
||||
console.log('close user streamings')
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
|
|
@ -31,16 +31,20 @@ export default function Status(props: Props) {
|
|||
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 } })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border-b mr-2 py-1">
|
||||
{rebloggedHeader(props.status)}
|
||||
<div className="flex">
|
||||
<div className="p-2" style={{ width: '56px' }}>
|
||||
<Avatar img={status.account.avatar} />
|
||||
<div className="p-2 cursor-pointer" style={{ width: '56px' }}>
|
||||
<Avatar img={status.account.avatar} onClick={() => openUser(status.account.id)} />
|
||||
</div>
|
||||
<div className="text-gray-950 break-all overflow-hidden" style={{ width: 'calc(100% - 56px)' }}>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex">
|
||||
<div className="flex cursor-pointer" onClick={() => openUser(status.account.id)}>
|
||||
<span
|
||||
className="text-gray-950 text-ellipsis break-all overflow-hidden"
|
||||
dangerouslySetInnerHTML={{ __html: emojify(status.account.display_name, status.account.emojis) }}
|
||||
|
|
|
@ -9,54 +9,54 @@ const generateNotification = (
|
|||
switch (notification.type) {
|
||||
case 'follow':
|
||||
return [
|
||||
formatMessage({ id: 'timeline.notification.follow.title' }),
|
||||
formatMessage({ id: 'timeline.notification.follow.body' }, { user: notification.account.acct })
|
||||
formatMessage({ id: 'notification.follow.title' }),
|
||||
formatMessage({ id: 'notification.follow.body' }, { user: notification.account.acct })
|
||||
]
|
||||
case 'follow_request':
|
||||
return [
|
||||
formatMessage({ id: 'timeline.notification.follow_request.title' }),
|
||||
formatMessage({ id: 'timeline.notification.follow_requested.body' }, { user: notification.account.acct })
|
||||
formatMessage({ id: 'notification.follow_request.title' }),
|
||||
formatMessage({ id: 'notification.follow_requested.body' }, { user: notification.account.acct })
|
||||
]
|
||||
case 'favourite':
|
||||
return [
|
||||
formatMessage({ id: 'timeline.notification.favourite.title' }),
|
||||
formatMessage({ id: 'timeline.notification.favourite.body' }, { user: notification.account.acct })
|
||||
formatMessage({ id: 'notification.favourite.title' }),
|
||||
formatMessage({ id: 'notification.favourite.body' }, { user: notification.account.acct })
|
||||
]
|
||||
case 'reblog':
|
||||
return [
|
||||
formatMessage({ id: 'timeline.notification.reblog.title' }),
|
||||
formatMessage({ id: 'timeline.notification.reblog.body' }, { user: notification.account.acct })
|
||||
formatMessage({ id: 'notification.reblog.title' }),
|
||||
formatMessage({ id: 'notification.reblog.body' }, { user: notification.account.acct })
|
||||
]
|
||||
case 'poll_expired':
|
||||
return [
|
||||
formatMessage({ id: 'timeline.notification.poll_expired.title' }),
|
||||
formatMessage({ id: 'timeline.notification.poll_expired.body' }, { user: notification.account.acct })
|
||||
formatMessage({ id: 'notification.poll_expired.title' }),
|
||||
formatMessage({ id: 'notification.poll_expired.body' }, { user: notification.account.acct })
|
||||
]
|
||||
case 'poll_vote':
|
||||
return [
|
||||
formatMessage({ id: 'timeline.notification.poll_vote.title' }),
|
||||
formatMessage({ id: 'timeline.notification.poll_vote.body' }, { user: notification.account.acct })
|
||||
formatMessage({ id: 'notification.poll_vote.title' }),
|
||||
formatMessage({ id: 'notification.poll_vote.body' }, { user: notification.account.acct })
|
||||
]
|
||||
case 'quote':
|
||||
return [
|
||||
formatMessage({ id: 'timeline.notification.quote.title' }),
|
||||
formatMessage({ id: 'timeline.notification.quote.body' }, { user: notification.account.acct })
|
||||
formatMessage({ id: 'notification.quote.title' }),
|
||||
formatMessage({ id: 'notification.quote.body' }, { user: notification.account.acct })
|
||||
]
|
||||
case 'status':
|
||||
return [
|
||||
formatMessage({ id: 'timeline.notification.status.title' }),
|
||||
formatMessage({ id: 'timeline.notification.status.body' }, { user: notification.account.acct })
|
||||
formatMessage({ id: 'notification.status.title' }),
|
||||
formatMessage({ id: 'notification.status.body' }, { user: notification.account.acct })
|
||||
]
|
||||
case 'update':
|
||||
return [
|
||||
formatMessage({ id: 'timeline.notification.update.title' }),
|
||||
formatMessage({ id: 'timeline.notification.update.body' }, { user: notification.account.acct })
|
||||
formatMessage({ id: 'notification.update.title' }),
|
||||
formatMessage({ id: 'notification.update.body' }, { user: notification.account.acct })
|
||||
]
|
||||
case 'emoji_reaction':
|
||||
case 'reaction':
|
||||
return [
|
||||
formatMessage({ id: 'timeline.notification.emoji_reaction.title' }),
|
||||
formatMessage({ id: 'timeline.notification.emoji_reaction.body' }, { user: notification.account.acct })
|
||||
formatMessage({ id: 'notification.emoji_reaction.title' }),
|
||||
formatMessage({ id: 'notification.emoji_reaction.body' }, { user: notification.account.acct })
|
||||
]
|
||||
case 'mention':
|
||||
return [
|
||||
|
|
Loading…
Reference in New Issue