Add profile in detail

This commit is contained in:
AkiraFukushima 2023-12-02 11:50:42 +09:00
parent 6ec9ce3536
commit e1d5a17675
No known key found for this signature in database
GPG Key ID: B6E51BAC4DE1A957
7 changed files with 142 additions and 25 deletions

View File

@ -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"
}
}

View File

@ -31,3 +31,7 @@
width: 100%;
}
}
.raw-html .invisible {
display: none;
}

View File

@ -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>
)}
</>

View File

@ -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>
)
}

View File

@ -52,7 +52,7 @@ export default function Layout({ children }: LayoutProps) {
streaming.stop()
})
streamings.current = []
console.log('close user streaming')
console.log('close user streamings')
}
}, [])

View File

@ -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) }}

View File

@ -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 [