feat: 💨

This commit is contained in:
codytseng 2025-11-29 12:17:52 +08:00
parent a9f115d202
commit f48f7d9edd
3 changed files with 56 additions and 27 deletions

View file

@ -1,8 +1,8 @@
import { FormattedTimestamp } from '@/components/FormattedTimestamp' import { FormattedTimestamp } from '@/components/FormattedTimestamp'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import UserAvatar from '@/components/UserAvatar' import UserAvatar, { SimpleUserAvatar } from '@/components/UserAvatar'
import Username from '@/components/Username' import Username, { SimpleUsername } from '@/components/Username'
import { isMentioningMutedUsers } from '@/lib/event' import { isMentioningMutedUsers } from '@/lib/event'
import { toNote, toUserAggregationDetail } from '@/lib/link' import { toNote, toUserAggregationDetail } from '@/lib/link'
import { cn, isTouchDevice } from '@/lib/utils' import { cn, isTouchDevice } from '@/lib/utils'
@ -371,6 +371,7 @@ function UserAggregationItem({
onClick: () => void onClick: () => void
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
const supportTouch = useMemo(() => isTouchDevice(), [])
const [hasNewEvents, setHasNewEvents] = useState(true) const [hasNewEvents, setHasNewEvents] = useState(true)
const [isPinned, setIsPinned] = useState(userAggregationService.isPinned(aggregation.pubkey)) const [isPinned, setIsPinned] = useState(userAggregationService.isPinned(aggregation.pubkey))
@ -418,14 +419,26 @@ function UserAggregationItem({
className="group relative flex items-center gap-4 px-4 py-3 border-b hover:bg-accent/30 cursor-pointer transition-all duration-200" className="group relative flex items-center gap-4 px-4 py-3 border-b hover:bg-accent/30 cursor-pointer transition-all duration-200"
onClick={onClick} onClick={onClick}
> >
<UserAvatar userId={aggregation.pubkey} /> {supportTouch ? (
<SimpleUserAvatar userId={aggregation.pubkey} />
) : (
<UserAvatar userId={aggregation.pubkey} />
)}
<div className="flex-1 min-w-0 flex flex-col"> <div className="flex-1 min-w-0 flex flex-col">
<Username {supportTouch ? (
userId={aggregation.pubkey} <SimpleUsername
className="font-semibold text-base truncate max-w-fit" userId={aggregation.pubkey}
skeletonClassName="h-4" className="font-semibold text-base truncate max-w-fit"
/> skeletonClassName="h-4"
/>
) : (
<Username
userId={aggregation.pubkey}
className="font-semibold text-base truncate max-w-fit"
skeletonClassName="h-4"
/>
)}
<FormattedTimestamp <FormattedTimestamp
timestamp={aggregation.lastEventTime} timestamp={aggregation.lastEventTime}
className="text-sm text-muted-foreground" className="text-sm text-muted-foreground"

View file

@ -3,7 +3,7 @@ import { Skeleton } from '@/components/ui/skeleton'
import { useFetchProfile } from '@/hooks' import { useFetchProfile } from '@/hooks'
import { toProfile } from '@/lib/link' import { toProfile } from '@/lib/link'
import { generateImageByPubkey } from '@/lib/pubkey' import { generateImageByPubkey } from '@/lib/pubkey'
import { cn } from '@/lib/utils' import { cn, isTouchDevice } from '@/lib/utils'
import { SecondaryPageLink } from '@/PageManager' import { SecondaryPageLink } from '@/PageManager'
import { useMemo } from 'react' import { useMemo } from 'react'
import Image from '../Image' import Image from '../Image'
@ -29,13 +29,21 @@ export default function UserAvatar({
className?: string className?: string
size?: 'large' | 'big' | 'semiBig' | 'normal' | 'medium' | 'small' | 'xSmall' | 'tiny' size?: 'large' | 'big' | 'semiBig' | 'normal' | 'medium' | 'small' | 'xSmall' | 'tiny'
}) { }) {
const supportTouch = useMemo(() => isTouchDevice(), [])
const trigger = (
<SecondaryPageLink to={toProfile(userId)} onClick={(e) => e.stopPropagation()}>
<SimpleUserAvatar userId={userId} size={size} className={className} />
</SecondaryPageLink>
)
if (supportTouch) {
return trigger
}
return ( return (
<HoverCard> <HoverCard>
<HoverCardTrigger> <HoverCardTrigger>{trigger}</HoverCardTrigger>
<SecondaryPageLink to={toProfile(userId)} onClick={(e) => e.stopPropagation()}>
<SimpleUserAvatar userId={userId} size={size} className={className} />
</SecondaryPageLink>
</HoverCardTrigger>
<HoverCardContent className="w-72"> <HoverCardContent className="w-72">
<ProfileCard userId={userId} /> <ProfileCard userId={userId} />
</HoverCardContent> </HoverCardContent>

View file

@ -2,10 +2,11 @@ import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/h
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { useFetchProfile } from '@/hooks' import { useFetchProfile } from '@/hooks'
import { toProfile } from '@/lib/link' import { toProfile } from '@/lib/link'
import { cn } from '@/lib/utils' import { cn, isTouchDevice } from '@/lib/utils'
import { SecondaryPageLink } from '@/PageManager' import { SecondaryPageLink } from '@/PageManager'
import ProfileCard from '../ProfileCard' import ProfileCard from '../ProfileCard'
import TextWithEmojis from '../TextWithEmojis' import TextWithEmojis from '../TextWithEmojis'
import { useMemo } from 'react'
export default function Username({ export default function Username({
userId, userId,
@ -21,6 +22,7 @@ export default function Username({
withoutSkeleton?: boolean withoutSkeleton?: boolean
}) { }) {
const { profile, isFetching } = useFetchProfile(userId) const { profile, isFetching } = useFetchProfile(userId)
const supportTouch = useMemo(() => isTouchDevice(), [])
if (!profile && isFetching && !withoutSkeleton) { if (!profile && isFetching && !withoutSkeleton) {
return ( return (
<div className="py-1"> <div className="py-1">
@ -30,20 +32,26 @@ export default function Username({
} }
if (!profile) return null if (!profile) return null
const trigger = (
<div className={className}>
<SecondaryPageLink
to={toProfile(userId)}
className="truncate hover:underline"
onClick={(e) => e.stopPropagation()}
>
{showAt && '@'}
<TextWithEmojis text={profile.username} emojis={profile.emojis} emojiClassName="mb-1" />
</SecondaryPageLink>
</div>
)
if (supportTouch) {
return trigger
}
return ( return (
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>{trigger}</HoverCardTrigger>
<div className={className}>
<SecondaryPageLink
to={toProfile(userId)}
className="truncate hover:underline"
onClick={(e) => e.stopPropagation()}
>
{showAt && '@'}
<TextWithEmojis text={profile.username} emojis={profile.emojis} emojiClassName="mb-1" />
</SecondaryPageLink>
</div>
</HoverCardTrigger>
<HoverCardContent className="w-80"> <HoverCardContent className="w-80">
<ProfileCard userId={userId} /> <ProfileCard userId={userId} />
</HoverCardContent> </HoverCardContent>