diff --git a/src/lib/bluesky/hooks/usePost.ts b/src/lib/bluesky/hooks/usePost.ts new file mode 100644 index 0000000..4b0714c --- /dev/null +++ b/src/lib/bluesky/hooks/usePost.ts @@ -0,0 +1,15 @@ +import { useQuery } from '@tanstack/react-query'; +import { useBlueskyStore } from '../store'; + +export function usePost({ handle, rkey }: { handle: string; rkey: string }) { + const agent = useBlueskyStore((store) => store.agent); + + return useQuery({ + queryKey: ['post', { handle, rkey }], + queryFn: async () => { + const response = await agent.getPost({ repo: handle, rkey }); + return response.value; + }, + enabled: !!agent && !!handle, + }); +} diff --git a/src/lib/bluesky/types/BSkyNotification.ts b/src/lib/bluesky/types/BSkyNotification.ts index 5a5774a..feff1f7 100644 --- a/src/lib/bluesky/types/BSkyNotification.ts +++ b/src/lib/bluesky/types/BSkyNotification.ts @@ -119,7 +119,7 @@ export const BSkyReplyNotification = Type.Object({ author: BSkyAuthor, reason: Type.Literal('reply'), reasonSubject: Type.Optional(Type.String()), - record: BSkyPost['record'], + record: BSkyPost.properties.record, isRead: Type.Boolean(), indexedAt: Type.String(), labels: Type.Optional(Type.Array(Type.Any())), diff --git a/src/lib/bluesky/types/BSkyPost.ts b/src/lib/bluesky/types/BSkyPost.ts index cb54d36..c789f77 100644 --- a/src/lib/bluesky/types/BSkyPost.ts +++ b/src/lib/bluesky/types/BSkyPost.ts @@ -13,119 +13,7 @@ export const BSkyPost = Type.Object({ record: Type.Object({ $type: Type.Literal('app.bsky.feed.post'), createdAt: Type.String(), - embed: Type.Optional( - Type.Union([ - Type.Object({ - $type: Type.Literal('app.bsky.embed.record'), - record: Type.Object({ - cid: Type.String(), - uri: Type.String(), - }), - }), - Type.Object({ - $type: Type.Literal('app.bsky.embed.recordWithMedia'), - media: Type.Union([ - Type.Object({ - $type: Type.Literal('app.bsky.embed.external'), - external: Type.Object({ - description: Type.String(), - thumb: Type.Object({ - $type: Type.Literal('blob'), - ref: Type.Object({ - $link: Type.String(), - }), - mimeType: Type.String(), - size: Type.Number(), - }), - title: Type.String(), - uri: Type.String(), - }), - }), - Type.Object({ - $type: Type.Literal('app.bsky.embed.images'), - images: Type.Array( - Type.Object({ - alt: Type.String(), - aspectRatio: Type.Object({ - height: Type.Number(), - width: Type.Number(), - }), - image: Type.Object({ - $type: Type.Literal('blob'), - ref: Type.Object({ - $link: Type.String(), - }), - mimeType: Type.String(), - size: Type.Number(), - }), - }), - ), - }), - ]), - - record: Type.Object({ - $type: Type.Literal('app.bsky.embed.record'), - record: Type.Object({ - cid: Type.String(), - uri: Type.String(), - }), - }), - }), - Type.Object({ - $type: Type.Literal('app.bsky.embed.images'), - images: Type.Array( - Type.Object({ - alt: Type.String(), - aspectRatio: Type.Object({ - height: Type.Number(), - width: Type.Number(), - }), - image: Type.Object({ - $type: Type.Literal('blob'), - ref: Type.Object({ - $link: Type.String(), - }), - mimeType: Type.String(), - size: Type.Number(), - }), - }), - ), - }), - Type.Object({ - $type: Type.Literal('app.bsky.embed.video'), - alt: Type.Optional(Type.String()), - aspectRatio: Type.Object({ - height: Type.Number(), - width: Type.Number(), - }), - video: Type.Object({ - $type: Type.Literal('blob'), - ref: Type.Object({ - $link: Type.String(), - }), - mimeType: Type.String(), - size: Type.Number(), - }), - }), - Type.Object({ - $type: Type.Literal('app.bsky.embed.external'), - external: Type.Object({ - description: Type.String(), - thumb: Type.Object({ - $type: Type.Literal('blob'), - ref: Type.Object({ - $link: Type.String(), - }), - mimeType: Type.String(), - size: Type.Number(), - }), - - title: Type.String(), - uri: Type.String(), - }), - }), - ]), - ), + embed: Type.Optional(BSkyPostEmbed), facets: Type.Optional(Type.Array(BSkyFacet)), langs: Type.Optional(Type.Array(Type.String())), reply: Type.Optional( diff --git a/src/lib/bluesky/types/BSkyPostEmbed.ts b/src/lib/bluesky/types/BSkyPostEmbed.ts index 52e90c7..d862076 100644 --- a/src/lib/bluesky/types/BSkyPostEmbed.ts +++ b/src/lib/bluesky/types/BSkyPostEmbed.ts @@ -279,4 +279,116 @@ export const BSkyPostEmbed = Type.Recursive((Self) => { ]); }); +// Type.Union([ +// Type.Object({ +// $type: Type.Literal('app.bsky.embed.record'), +// record: Type.Object({ +// cid: Type.String(), +// uri: Type.String(), +// }), +// }), +// Type.Object({ +// $type: Type.Literal('app.bsky.embed.recordWithMedia'), +// media: Type.Union([ +// Type.Object({ +// $type: Type.Literal('app.bsky.embed.external'), +// external: Type.Object({ +// description: Type.String(), +// thumb: Type.Object({ +// $type: Type.Literal('blob'), +// ref: Type.Object({ +// $link: Type.String(), +// }), +// mimeType: Type.String(), +// size: Type.Number(), +// }), +// title: Type.String(), +// uri: Type.String(), +// }), +// }), +// Type.Object({ +// $type: Type.Literal('app.bsky.embed.images'), +// images: Type.Array( +// Type.Object({ +// alt: Type.String(), +// aspectRatio: Type.Object({ +// height: Type.Number(), +// width: Type.Number(), +// }), +// image: Type.Object({ +// $type: Type.Literal('blob'), +// ref: Type.Object({ +// $link: Type.String(), +// }), +// mimeType: Type.String(), +// size: Type.Number(), +// }), +// }), +// ), +// }), +// ]), + +// record: Type.Object({ +// $type: Type.Literal('app.bsky.embed.record'), +// record: Type.Object({ +// cid: Type.String(), +// uri: Type.String(), +// }), +// }), +// }), +// Type.Object({ +// $type: Type.Literal('app.bsky.embed.images'), +// images: Type.Array( +// Type.Object({ +// alt: Type.String(), +// aspectRatio: Type.Object({ +// height: Type.Number(), +// width: Type.Number(), +// }), +// image: Type.Object({ +// $type: Type.Literal('blob'), +// ref: Type.Object({ +// $link: Type.String(), +// }), +// mimeType: Type.String(), +// size: Type.Number(), +// }), +// }), +// ), +// }), +// Type.Object({ +// $type: Type.Literal('app.bsky.embed.video'), +// alt: Type.Optional(Type.String()), +// aspectRatio: Type.Object({ +// height: Type.Number(), +// width: Type.Number(), +// }), +// video: Type.Object({ +// $type: Type.Literal('blob'), +// ref: Type.Object({ +// $link: Type.String(), +// }), +// mimeType: Type.String(), +// size: Type.Number(), +// }), +// }), +// Type.Object({ +// $type: Type.Literal('app.bsky.embed.external'), +// external: Type.Object({ +// description: Type.String(), +// thumb: Type.Object({ +// $type: Type.Literal('blob'), +// ref: Type.Object({ +// $link: Type.String(), +// }), +// mimeType: Type.String(), +// size: Type.Number(), +// }), + +// title: Type.String(), +// uri: Type.String(), +// }), +// }), +// ]), + export type BSkyPostEmbed = Static; diff --git a/src/routes/notifications/components/FollowNotification.tsx b/src/routes/notifications/components/FollowNotification.tsx index 1991434..aef7a0c 100644 --- a/src/routes/notifications/components/FollowNotification.tsx +++ b/src/routes/notifications/components/FollowNotification.tsx @@ -1,26 +1,36 @@ import { Avatar } from '@/components/ui/avatar'; import { Link } from '@/components/ui/Link'; import { BSkyFollowNotification } from '@/lib/bluesky/types/BSkyNotification'; +import { UserPlus2 } from 'lucide-react'; import { useTranslation } from 'react-i18next'; export function FollowNotification({ notification }: { notification: BSkyFollowNotification }) { const { t } = useTranslation('notifications'); return ( - -
-
- +
+
+
+
- {notification.author.displayName} {t('followedYou')} + +
+
+ +
+
+ {notification.author.displayName} {t('followedYou')} +
+
+
- +
); } diff --git a/src/routes/notifications/components/GroupedNotifications.tsx b/src/routes/notifications/components/GroupedNotifications.tsx index ebfb4d9..d633c43 100644 --- a/src/routes/notifications/components/GroupedNotifications.tsx +++ b/src/routes/notifications/components/GroupedNotifications.tsx @@ -6,6 +6,7 @@ import { forwardRef, HtmlHTMLAttributes, Ref } from 'react'; import { useTranslation } from 'react-i18next'; import { Virtuoso } from 'react-virtuoso'; import { GroupNotification } from './GroupNotification'; +import { HeartIcon } from 'lucide-react'; // group notifications by uri export function GroupedNotifications() { diff --git a/src/routes/notifications/components/LikeNotification.tsx b/src/routes/notifications/components/LikeNotification.tsx index 83283fa..5da30cc 100644 --- a/src/routes/notifications/components/LikeNotification.tsx +++ b/src/routes/notifications/components/LikeNotification.tsx @@ -1,14 +1,19 @@ import { Avatar } from '@/components/ui/avatar'; import { Debug } from '@/components/ui/Debug'; +import { FormattedText } from '@/components/ui/FormattedText'; import { Link } from '@/components/ui/Link'; +import { Loading } from '@/components/ui/loading'; +import { usePost } from '@/lib/bluesky/hooks/usePost'; import { useBlueskyStore } from '@/lib/bluesky/store'; import { BSkyLikeNotification, isBSkyLikeNotification } from '@/lib/bluesky/types/BSkyNotification'; +import { HeartIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; export function LikeNotification({ notifications }: { notifications: BSkyLikeNotification[] }) { const { t } = useTranslation('notifications'); const { session } = useBlueskyStore(); const notification = notifications[0]; + if (!notification) throw new Error('Notification is missing'); if (!session) throw new Error('Session is missing'); const othersCount = notifications.length - 1; @@ -16,31 +21,54 @@ export function LikeNotification({ notifications }: { notifications: BSkyLikeNot if (!isBSkyLikeNotification(notification)) throw new Error('Notification is not a like notification'); return ( - -
+
+
+ +
+
-
- {notifications.map((notification) => ( - - ))} -
- {notifications.map((notification) => notification.author.displayName).slice(-1)} - {notifications.length - 1 >= 1 && - `${t('and')} ${othersCount} ${othersCount >= 1 && (othersCount === 1 ? t('other') : t('others'))} `}{' '} - {t('likedYourPost')} + +
+
+ {notifications.map((notification) => ( + + ))} +
+
+ {notifications.map((notification) => notification.author.displayName).slice(-1)} + {notifications.length - 1 >= 1 && + `${'and'} ${othersCount} ${othersCount >= 1 && (othersCount === 1 ? 'other' : 'others')} `}{' '} + {t('likedYourPost')} +
+
+ +
+
+
- +
); } + +function LikePost({ notification }: { notification: BSkyLikeNotification }) { + const session = useBlueskyStore((state) => state.session); + const rkey = notification.record.subject.uri.split('/')[notification.record.subject.uri.split('/').length - 1] as string; + const { data: post, isLoading } = usePost({ handle: session!.did, rkey }); + + if (isLoading) ; + if (!post) return null; + + return ; +} diff --git a/src/routes/notifications/components/MentionNotification.tsx b/src/routes/notifications/components/MentionNotification.tsx index 5f70853..f3a7189 100644 --- a/src/routes/notifications/components/MentionNotification.tsx +++ b/src/routes/notifications/components/MentionNotification.tsx @@ -7,7 +7,7 @@ export function MentionNotification({ notification }: { notification: BSkyMentio return (
- +
{notification.author.displayName} {t('mentionedYou')} diff --git a/src/routes/notifications/components/QuoteNotification.tsx b/src/routes/notifications/components/QuoteNotification.tsx index b62b57f..e6f2689 100644 --- a/src/routes/notifications/components/QuoteNotification.tsx +++ b/src/routes/notifications/components/QuoteNotification.tsx @@ -6,11 +6,18 @@ export function QuoteNotification({ notification }: { notification: BSkyQuoteNot const { t } = useTranslation('notifications'); return (
-
- -
-
- {notification.author.displayName} {t('quotedYourPost')} +
+
+ +
+
+
+ +
+
+ {notification.author.displayName} {t('quotedYourPost')} +
+
); diff --git a/src/routes/notifications/components/ReplyNotification.tsx b/src/routes/notifications/components/ReplyNotification.tsx index 3c9106c..449a9c3 100644 --- a/src/routes/notifications/components/ReplyNotification.tsx +++ b/src/routes/notifications/components/ReplyNotification.tsx @@ -1,19 +1,22 @@ +import { PostEmbed } from '@/components/PostEmbed'; import { Avatar } from '@/components/ui/avatar'; import { FormattedText } from '@/components/ui/FormattedText'; +import { Handle } from '@/components/ui/Handle'; import { BSkyReplyNotification } from '@/lib/bluesky/types/BSkyNotification'; -import { useTranslation } from 'react-i18next'; export function ReplyNotification({ notification }: { notification: BSkyReplyNotification }) { - const { t } = useTranslation('notifications'); return ( -
-
- +
+
+
-
- {notification.author.displayName} {t('repliedToYourPost')} +
+
+ {notification.author.displayName} +
+ +
-
); } diff --git a/src/routes/notifications/components/RepostNotification.tsx b/src/routes/notifications/components/RepostNotification.tsx index 36405fb..39074b7 100644 --- a/src/routes/notifications/components/RepostNotification.tsx +++ b/src/routes/notifications/components/RepostNotification.tsx @@ -1,16 +1,24 @@ import { Avatar } from '@/components/ui/avatar'; import { BSkyRepostNotification } from '@/lib/bluesky/types/BSkyNotification'; +import { Repeat } from 'lucide-react'; import { useTranslation } from 'react-i18next'; export function RepostNotification({ notification }: { notification: BSkyRepostNotification }) { const { t } = useTranslation('notifications'); return (
-
- -
-
- {notification.author.displayName} {t('repostedYourPost')} +
+
+ +
+
+
+ +
+
+ {notification.author.displayName} {t('repostedYourPost')} +
+
); diff --git a/src/routes/notifications/components/StarterpackJoinedNotification.tsx b/src/routes/notifications/components/StarterpackJoinedNotification.tsx index 234c6d1..09a997f 100644 --- a/src/routes/notifications/components/StarterpackJoinedNotification.tsx +++ b/src/routes/notifications/components/StarterpackJoinedNotification.tsx @@ -7,7 +7,7 @@ export function StarterpackJoinedNotification({ notification }: { notification: return (
- +
{notification.author.displayName} {t('joinedYourStarterpack')}