From 51913a5163978cedd6bfd1468fda4de36c30be6b Mon Sep 17 00:00:00 2001 From: codytseng Date: Thu, 24 Apr 2025 22:23:22 +0800 Subject: [PATCH] =?UTF-8?q?style:=20=F0=9F=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/BookmarkButton/index.tsx | 156 +++++++++---------- src/components/BookmarkList/index.tsx | 194 ++++++++++++------------ src/components/NewNotesButton/index.tsx | 136 ++++++++--------- src/components/NoteStats/Likes.tsx | 2 +- src/providers/BookmarksProvider.tsx | 130 ++++++++-------- tailwind.config.js | 2 +- 6 files changed, 310 insertions(+), 310 deletions(-) diff --git a/src/components/BookmarkButton/index.tsx b/src/components/BookmarkButton/index.tsx index 3120f6e..9353bd1 100644 --- a/src/components/BookmarkButton/index.tsx +++ b/src/components/BookmarkButton/index.tsx @@ -1,78 +1,78 @@ -import { useToast } from '@/hooks' -import { useBookmarks } from '@/providers/BookmarksProvider' -import { useNostr } from '@/providers/NostrProvider' -import { BookmarkIcon, Loader } from 'lucide-react' -import { useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { Event } from 'nostr-tools' - -export default function BookmarkButton({ event }: { event: Event }) { - const { t } = useTranslation() - const { toast } = useToast() - const { pubkey: accountPubkey, bookmarkListEvent, checkLogin } = useNostr() - const { addBookmark, removeBookmark } = useBookmarks() - const [updating, setUpdating] = useState(false) - const isBookmarked = useMemo( - () => bookmarkListEvent?.tags.some((tag) => tag[0] === 'e' && tag[1] === event.id), - [bookmarkListEvent, event] - ) - - if (!accountPubkey) return null - - const handleBookmark = async (e: React.MouseEvent) => { - e.stopPropagation() - checkLogin(async () => { - if (isBookmarked) return - - setUpdating(true) - try { - await addBookmark(event) - } catch (error) { - toast({ - title: t('Bookmark failed'), - description: (error as Error).message, - variant: 'destructive' - }) - } finally { - setUpdating(false) - } - }) - } - - const handleRemoveBookmark = async (e: React.MouseEvent) => { - e.stopPropagation() - checkLogin(async () => { - if (!isBookmarked) return - - setUpdating(true) - try { - await removeBookmark(event) - } catch (error) { - toast({ - title: t('Remove bookmark failed'), - description: (error as Error).message, - variant: 'destructive' - }) - } finally { - setUpdating(false) - } - }) - } - - return ( - - ) -} +import { useToast } from '@/hooks' +import { useBookmarks } from '@/providers/BookmarksProvider' +import { useNostr } from '@/providers/NostrProvider' +import { BookmarkIcon, Loader } from 'lucide-react' +import { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Event } from 'nostr-tools' + +export default function BookmarkButton({ event }: { event: Event }) { + const { t } = useTranslation() + const { toast } = useToast() + const { pubkey: accountPubkey, bookmarkListEvent, checkLogin } = useNostr() + const { addBookmark, removeBookmark } = useBookmarks() + const [updating, setUpdating] = useState(false) + const isBookmarked = useMemo( + () => bookmarkListEvent?.tags.some((tag) => tag[0] === 'e' && tag[1] === event.id), + [bookmarkListEvent, event] + ) + + if (!accountPubkey) return null + + const handleBookmark = async (e: React.MouseEvent) => { + e.stopPropagation() + checkLogin(async () => { + if (isBookmarked) return + + setUpdating(true) + try { + await addBookmark(event) + } catch (error) { + toast({ + title: t('Bookmark failed'), + description: (error as Error).message, + variant: 'destructive' + }) + } finally { + setUpdating(false) + } + }) + } + + const handleRemoveBookmark = async (e: React.MouseEvent) => { + e.stopPropagation() + checkLogin(async () => { + if (!isBookmarked) return + + setUpdating(true) + try { + await removeBookmark(event) + } catch (error) { + toast({ + title: t('Remove bookmark failed'), + description: (error as Error).message, + variant: 'destructive' + }) + } finally { + setUpdating(false) + } + }) + } + + return ( + + ) +} diff --git a/src/components/BookmarkList/index.tsx b/src/components/BookmarkList/index.tsx index ecd1341..c63ed35 100644 --- a/src/components/BookmarkList/index.tsx +++ b/src/components/BookmarkList/index.tsx @@ -1,97 +1,97 @@ -import { useFetchEvent } from '@/hooks' -import { generateEventIdFromETag } from '@/lib/tag' -import { useNostr } from '@/providers/NostrProvider' -import { kinds } from 'nostr-tools' -import { useEffect, useMemo, useRef, useState } from 'react' -import { useTranslation } from 'react-i18next' -import NoteCard, { NoteCardLoadingSkeleton } from '../NoteCard' - -const SHOW_COUNT = 10 - -export default function BookmarkList() { - const { t } = useTranslation() - const { bookmarkListEvent } = useNostr() - const eventIds = useMemo(() => { - if (!bookmarkListEvent) return [] - - return ( - bookmarkListEvent.tags - .map((tag) => (tag[0] === 'e' ? generateEventIdFromETag(tag) : undefined)) - .filter(Boolean) as `nevent1${string}`[] - ).reverse() - }, [bookmarkListEvent]) - const [showCount, setShowCount] = useState(SHOW_COUNT) - const bottomRef = useRef(null) - - useEffect(() => { - const options = { - root: null, - rootMargin: '10px', - threshold: 0.1 - } - - const loadMore = () => { - if (showCount < eventIds.length) { - setShowCount((prev) => prev + SHOW_COUNT) - } - } - - const observerInstance = new IntersectionObserver((entries) => { - if (entries[0].isIntersecting) { - loadMore() - } - }, options) - - const currentBottomRef = bottomRef.current - - if (currentBottomRef) { - observerInstance.observe(currentBottomRef) - } - - return () => { - if (observerInstance && currentBottomRef) { - observerInstance.unobserve(currentBottomRef) - } - } - }, [showCount, eventIds]) - - if (eventIds.length === 0) { - return ( -
- {t('no bookmarks found')} -
- ) - } - - return ( -
- {eventIds.slice(0, showCount).map((eventId) => ( - - ))} - - {showCount < eventIds.length ? ( -
- -
- ) : ( -
- {t('no more bookmarks')} -
- )} -
- ) -} - -function BookmarkedNote({ eventId }: { eventId: string }) { - const { event, isFetching } = useFetchEvent(eventId) - - if (isFetching) { - return - } - - if (!event || event.kind !== kinds.ShortTextNote) { - return null - } - - return -} +import { useFetchEvent } from '@/hooks' +import { generateEventIdFromETag } from '@/lib/tag' +import { useNostr } from '@/providers/NostrProvider' +import { kinds } from 'nostr-tools' +import { useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import NoteCard, { NoteCardLoadingSkeleton } from '../NoteCard' + +const SHOW_COUNT = 10 + +export default function BookmarkList() { + const { t } = useTranslation() + const { bookmarkListEvent } = useNostr() + const eventIds = useMemo(() => { + if (!bookmarkListEvent) return [] + + return ( + bookmarkListEvent.tags + .map((tag) => (tag[0] === 'e' ? generateEventIdFromETag(tag) : undefined)) + .filter(Boolean) as `nevent1${string}`[] + ).reverse() + }, [bookmarkListEvent]) + const [showCount, setShowCount] = useState(SHOW_COUNT) + const bottomRef = useRef(null) + + useEffect(() => { + const options = { + root: null, + rootMargin: '10px', + threshold: 0.1 + } + + const loadMore = () => { + if (showCount < eventIds.length) { + setShowCount((prev) => prev + SHOW_COUNT) + } + } + + const observerInstance = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting) { + loadMore() + } + }, options) + + const currentBottomRef = bottomRef.current + + if (currentBottomRef) { + observerInstance.observe(currentBottomRef) + } + + return () => { + if (observerInstance && currentBottomRef) { + observerInstance.unobserve(currentBottomRef) + } + } + }, [showCount, eventIds]) + + if (eventIds.length === 0) { + return ( +
+ {t('no bookmarks found')} +
+ ) + } + + return ( +
+ {eventIds.slice(0, showCount).map((eventId) => ( + + ))} + + {showCount < eventIds.length ? ( +
+ +
+ ) : ( +
+ {t('no more bookmarks')} +
+ )} +
+ ) +} + +function BookmarkedNote({ eventId }: { eventId: string }) { + const { event, isFetching } = useFetchEvent(eventId) + + if (isFetching) { + return + } + + if (!event || event.kind !== kinds.ShortTextNote) { + return null + } + + return +} diff --git a/src/components/NewNotesButton/index.tsx b/src/components/NewNotesButton/index.tsx index 181beb2..359c1a0 100644 --- a/src/components/NewNotesButton/index.tsx +++ b/src/components/NewNotesButton/index.tsx @@ -1,68 +1,68 @@ -import { Button } from '@/components/ui/button' -import { SimpleUserAvatar } from '@/components/UserAvatar' -import { cn } from '@/lib/utils' -import { useScreenSize } from '@/providers/ScreenSizeProvider' -import { Event } from 'nostr-tools' -import { useMemo } from 'react' -import { useTranslation } from 'react-i18next' - -export default function NewNotesButton({ - newEvents = [], - onClick -}: { - newEvents?: Event[] - onClick?: () => void -}) { - const { t } = useTranslation() - const { isSmallScreen } = useScreenSize() - const pubkeys = useMemo(() => { - const arr: string[] = [] - for (const event of newEvents) { - if (!arr.includes(event.pubkey)) { - arr.push(event.pubkey) - } - if (arr.length >= 3) break - } - return arr - }, [newEvents]) - - return ( - <> - {newEvents.length > 0 && ( -
- -
- )} - - ) -} +import { Button } from '@/components/ui/button' +import { SimpleUserAvatar } from '@/components/UserAvatar' +import { cn } from '@/lib/utils' +import { useScreenSize } from '@/providers/ScreenSizeProvider' +import { Event } from 'nostr-tools' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' + +export default function NewNotesButton({ + newEvents = [], + onClick +}: { + newEvents?: Event[] + onClick?: () => void +}) { + const { t } = useTranslation() + const { isSmallScreen } = useScreenSize() + const pubkeys = useMemo(() => { + const arr: string[] = [] + for (const event of newEvents) { + if (!arr.includes(event.pubkey)) { + arr.push(event.pubkey) + } + if (arr.length >= 3) break + } + return arr + }, [newEvents]) + + return ( + <> + {newEvents.length > 0 && ( +
+ +
+ )} + + ) +} diff --git a/src/components/NoteStats/Likes.tsx b/src/components/NoteStats/Likes.tsx index b24ade7..621ce3b 100644 --- a/src/components/NoteStats/Likes.tsx +++ b/src/components/NoteStats/Likes.tsx @@ -67,7 +67,7 @@ export default function Likes({ event }: { event: Event }) { like(key, emoji) }} > - {liking === key ? : } + {liking === key ? : }
{pubkeys.size}
))} diff --git a/src/providers/BookmarksProvider.tsx b/src/providers/BookmarksProvider.tsx index 276f33f..70c03dc 100644 --- a/src/providers/BookmarksProvider.tsx +++ b/src/providers/BookmarksProvider.tsx @@ -1,65 +1,65 @@ -import { createBookmarkDraftEvent } from '@/lib/draft-event' -import client from '@/services/client.service' -import { createContext, useContext } from 'react' -import { useNostr } from './NostrProvider' -import { Event } from 'nostr-tools' - -type TBookmarksContext = { - addBookmark: (event: Event) => Promise - removeBookmark: (event: Event) => Promise -} - -const BookmarksContext = createContext(undefined) - -export const useBookmarks = () => { - const context = useContext(BookmarksContext) - if (!context) { - throw new Error('useBookmarks must be used within a BookmarksProvider') - } - return context -} - -export function BookmarksProvider({ children }: { children: React.ReactNode }) { - const { pubkey: accountPubkey, publish, updateBookmarkListEvent } = useNostr() - - const addBookmark = async (event: Event) => { - if (!accountPubkey) return - - const bookmarkListEvent = await client.fetchBookmarkListEvent(accountPubkey) - const currentTags = bookmarkListEvent?.tags || [] - - if (currentTags.some((tag) => tag[0] === 'e' && tag[1] === event.id)) return - - const newBookmarkDraftEvent = createBookmarkDraftEvent( - [...currentTags, ['e', event.id, client.getEventHint(event.id), '', event.pubkey]], - bookmarkListEvent?.content - ) - const newBookmarkEvent = await publish(newBookmarkDraftEvent) - await updateBookmarkListEvent(newBookmarkEvent) - } - - const removeBookmark = async (event: Event) => { - if (!accountPubkey) return - - const bookmarkListEvent = await client.fetchBookmarkListEvent(accountPubkey) - if (!bookmarkListEvent) return - - const newTags = bookmarkListEvent.tags.filter((tag) => !(tag[0] === 'e' && tag[1] === event.id)) - if (newTags.length === bookmarkListEvent.tags.length) return - - const newBookmarkDraftEvent = createBookmarkDraftEvent(newTags, bookmarkListEvent.content) - const newBookmarkEvent = await publish(newBookmarkDraftEvent) - await updateBookmarkListEvent(newBookmarkEvent) - } - - return ( - - {children} - - ) -} +import { createBookmarkDraftEvent } from '@/lib/draft-event' +import client from '@/services/client.service' +import { createContext, useContext } from 'react' +import { useNostr } from './NostrProvider' +import { Event } from 'nostr-tools' + +type TBookmarksContext = { + addBookmark: (event: Event) => Promise + removeBookmark: (event: Event) => Promise +} + +const BookmarksContext = createContext(undefined) + +export const useBookmarks = () => { + const context = useContext(BookmarksContext) + if (!context) { + throw new Error('useBookmarks must be used within a BookmarksProvider') + } + return context +} + +export function BookmarksProvider({ children }: { children: React.ReactNode }) { + const { pubkey: accountPubkey, publish, updateBookmarkListEvent } = useNostr() + + const addBookmark = async (event: Event) => { + if (!accountPubkey) return + + const bookmarkListEvent = await client.fetchBookmarkListEvent(accountPubkey) + const currentTags = bookmarkListEvent?.tags || [] + + if (currentTags.some((tag) => tag[0] === 'e' && tag[1] === event.id)) return + + const newBookmarkDraftEvent = createBookmarkDraftEvent( + [...currentTags, ['e', event.id, client.getEventHint(event.id), '', event.pubkey]], + bookmarkListEvent?.content + ) + const newBookmarkEvent = await publish(newBookmarkDraftEvent) + await updateBookmarkListEvent(newBookmarkEvent) + } + + const removeBookmark = async (event: Event) => { + if (!accountPubkey) return + + const bookmarkListEvent = await client.fetchBookmarkListEvent(accountPubkey) + if (!bookmarkListEvent) return + + const newTags = bookmarkListEvent.tags.filter((tag) => !(tag[0] === 'e' && tag[1] === event.id)) + if (newTags.length === bookmarkListEvent.tags.length) return + + const newBookmarkDraftEvent = createBookmarkDraftEvent(newTags, bookmarkListEvent.content) + const newBookmarkEvent = await publish(newBookmarkDraftEvent) + await updateBookmarkListEvent(newBookmarkEvent) + } + + return ( + + {children} + + ) +} diff --git a/tailwind.config.js b/tailwind.config.js index 44ea9f7..dfdd055 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -23,7 +23,7 @@ export default { primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))', - hover: 'hsl(var(--primary-hover))', + hover: 'hsl(var(--primary-hover))' }, secondary: { DEFAULT: 'hsl(var(--secondary))',