diff --git a/src/components/Notes/Note.tsx b/src/components/Notes/Note.tsx index 042d3f5..4995736 100644 --- a/src/components/Notes/Note.tsx +++ b/src/components/Notes/Note.tsx @@ -21,7 +21,7 @@ import { useState } from 'react' import { Link } from 'react-router-dom' import { appRoutes, getProfilePageRoute } from 'routes' import { FeedPostsFilter, NSFWFilter, UserProfile } from 'types' -import { DEFAULT_FILTER_OPTIONS, hexToNpub } from 'utils' +import { DEFAULT_FILTER_OPTIONS, hexToNpub, log, LogType } from 'utils' import { NoteRepostPopup } from './NoteRepostPopup' import { NoteQuoteRepostPopup } from './NoteQuoteRepostPopup' import { NsfwCommentWrapper } from 'components/NsfwCommentWrapper' @@ -62,10 +62,26 @@ export const Note = ({ ndkEvent }: NoteProps) => { ndkEvent.author.fetchProfile().then((res) => setEventProfile(res)) if (isRepost) { - const parsedEvent = JSON.parse(ndkEvent.content) - const ndkRepostEvent = new NDKEvent(ndk, parsedEvent) - setRepostEvent(ndkRepostEvent) - ndkRepostEvent.author.fetchProfile().then((res) => setRepostProfile(res)) + try { + const parsedEvent = JSON.parse(ndkEvent.content) + const ndkRepostEvent = new NDKEvent(ndk, parsedEvent) + setRepostEvent(ndkRepostEvent) + ndkRepostEvent.author + .fetchProfile() + .then((res) => setRepostProfile(res)) + } catch (error) { + if (error instanceof SyntaxError) { + log( + true, + LogType.Error, + 'Event content malformed', + error, + ndkEvent.content + ) + } else { + log(true, LogType.Error, error) + } + } } const repostFilter: NDKFilter = { diff --git a/src/components/Notes/NoteRepostPopup.tsx b/src/components/Notes/NoteRepostPopup.tsx index 82c743f..8091046 100644 --- a/src/components/Notes/NoteRepostPopup.tsx +++ b/src/components/Notes/NoteRepostPopup.tsx @@ -7,7 +7,7 @@ import { CommentContent } from 'components/comment/CommentContent' import { getProfilePageRoute } from 'routes' import { nip19 } from 'nostr-tools' import { UserProfile } from 'types' -import { hexToNpub } from 'utils' +import { hexToNpub, log, LogType } from 'utils' import { formatDate } from 'date-fns' interface NoteRepostProps { @@ -28,8 +28,16 @@ export const NoteRepostPopup = ({ useDidMount(async () => { const repost = await ndkEvent.repost(false) - setContent(JSON.parse(repost.content).content) ndkEvent.author.fetchProfile().then((res) => setProfile(res)) + try { + setContent(JSON.parse(repost.content).content) + } catch (error) { + if (error instanceof SyntaxError) { + log(true, LogType.Error, 'Repost event content malformed', error) + } else { + log(true, LogType.Error, error) + } + } }) const profileRoute = getProfilePageRoute( diff --git a/src/components/comment/CommentContent.tsx b/src/components/comment/CommentContent.tsx index 49e7f1b..e1d9b88 100644 --- a/src/components/comment/CommentContent.tsx +++ b/src/components/comment/CommentContent.tsx @@ -22,9 +22,9 @@ export const CommentContent = ({

Hide full post

)} -

+

-

+
{isTextOverflowing && !isExpanded && (

View full post

diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index 478257c..50f9acf 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -1,4 +1,9 @@ -import { NDKFilter, NDKKind } from '@nostr-dev-kit/ndk' +import { + NDKEvent, + NDKFilter, + NDKKind, + NDKSubscriptionCacheUsage +} from '@nostr-dev-kit/ndk' import { LoadingSpinner } from 'components/LoadingSpinner' import { ModCard } from 'components/ModCard' import { ModFilter } from 'components/Filters/ModsFilter' @@ -19,10 +24,12 @@ import { toast } from 'react-toastify' import { appRoutes } from 'routes' import { BlogCardDetails, + FeedPostsFilter, FilterOptions, ModDetails, ModeratedFilter, NSFWFilter, + RepostFilter, SortBy, UserRelaysType } from 'types' @@ -42,6 +49,8 @@ import { CheckboxField } from 'components/Inputs' import { ProfilePageLoaderResult } from './loader' import { BlogCard } from 'components/BlogCard' import { BlogsFilter } from 'components/Filters/BlogsFilter' +import { FeedFilter } from 'components/Filters/FeedFilter' +import { Note } from 'components/Notes/Note' export const ProfilePage = () => { const { @@ -451,7 +460,7 @@ export const ProfilePage = () => { )} {tab === 1 && } - {tab === 2 && <>WIP} + {tab === 2 && }
@@ -870,3 +879,133 @@ const ProfileTabBlogs = () => { ) } + +const ProfileTabPosts = () => { + const SHOWING_STEP = 20 + const { profilePubkey } = useLoaderData() as ProfilePageLoaderResult + const { ndk } = useNDKContext() + const [notes, setNotes] = useState([]) + const [isFetching, setIsFetching] = useState(false) + const [isLoadMoreVisible, setIsLoadMoreVisible] = useState(true) + const [showing, setShowing] = useState(SHOWING_STEP) + + const filterKey = 'filter-feed-2' + const [filterOptions] = useLocalStorage( + filterKey, + DEFAULT_FILTER_OPTIONS + ) + + useEffect(() => { + setIsFetching(true) + setIsLoadMoreVisible(true) + + const filter: NDKFilter = { + authors: [profilePubkey], + kinds: [NDKKind.Text, NDKKind.Repost], + limit: 50 + } + + ndk + .fetchEvents(filter, { + closeOnEose: true, + cacheUsage: NDKSubscriptionCacheUsage.PARALLEL + }) + .then((ndkEventSet) => { + const ndkEvents = Array.from(ndkEventSet) + setNotes(ndkEvents) + }) + .finally(() => { + setIsFetching(false) + }) + }, [ndk, profilePubkey]) + + const filteredNotes = useMemo(() => { + let _notes = notes || [] + _notes = _notes.filter((n) => { + if (n.kind === NDKKind.Text) { + // Filter out the replies (Kind 1 events with e tags are replies to other kind 1 events) + return n.getMatchingTags('e').length === 0 + } + // Filter repost events if the option is set to hide reposts + return !( + n.kind === NDKKind.Repost && + filterOptions.repost === RepostFilter.Hide_Repost + ) + }) + + _notes = _notes.sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0)) + + showing > 0 && _notes.splice(showing) + return _notes + }, [filterOptions.repost, notes, showing]) + + const handleLoadMore = () => { + const LOAD_MORE_STEP = SHOWING_STEP * 2 + setShowing((prev) => prev + SHOWING_STEP) + const lastNote = filteredNotes[filteredNotes.length - 1] + const filter: NDKFilter = { + authors: [profilePubkey], + kinds: [NDKKind.Text, NDKKind.Repost], + limit: LOAD_MORE_STEP + } + + filter.until = lastNote.created_at + + setIsFetching(true) + ndk + .fetchEvents(filter, { + closeOnEose: true, + cacheUsage: NDKSubscriptionCacheUsage.PARALLEL + }) + .then((ndkEventSet) => { + setNotes((prevNotes) => { + const newNotes = Array.from(ndkEventSet) + const combinedNotes = [...prevNotes, ...newNotes] + const uniqueBlogs = Array.from( + new Set(combinedNotes.map((b) => b.id)) + ) + .map((id) => combinedNotes.find((b) => b.id === id)) + .filter((b) => b !== undefined) + + if (newNotes.length < LOAD_MORE_STEP) { + setIsLoadMoreVisible(false) + } + + return uniqueBlogs + }) + }) + .finally(() => { + setIsFetching(false) + }) + } + + return ( + <> + + {isFetching && } + {filteredNotes.length === 0 && !isFetching && ( +
+

There are no posts to show

+
+ )} +
+
+ {filteredNotes.map((note) => ( + + ))} +
+
+ {!isFetching && isLoadMoreVisible && filteredNotes.length > 0 && ( +
+ +
+ )} + + ) +}