Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nostr bookmark hook #68

Merged
merged 13 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/mobile/src/assets/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ export const RepostIcon: React.FC<SvgProps> = (props) => {
);
};


export const LikeIcon: React.FC<SvgProps> = (props) => {
return (
<Svg width="26" height="26" viewBox="0 0 20 20" fill="none" {...props}>
Expand Down
22 changes: 13 additions & 9 deletions apps/mobile/src/modules/Post/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {NDKEvent, NDKKind} from '@nostr-dev-kit/ndk';
import {useNavigation} from '@react-navigation/native';
import {useQueryClient} from '@tanstack/react-query';
import {useProfile, useReact, useReactions, useReplyNotes, useRepost} from 'afk_nostr_sdk';
import {useProfile, useReact, useReactions, useReplyNotes, useRepost, useBookmark} from 'afk_nostr_sdk';
// import { useAuth } from '../../store/auth';
import {useAuth} from 'afk_nostr_sdk';
import {useMemo, useState} from 'react';
Expand Down Expand Up @@ -31,7 +31,6 @@ export type PostProps = {
isRepost?:boolean
};


export const Post: React.FC<PostProps> = ({asComment, event, repostedEventProps, isRepost}) => {
const repostedEvent = repostedEventProps ?? undefined;

Expand All @@ -51,6 +50,7 @@ export const Post: React.FC<PostProps> = ({asComment, event, repostedEventProps,
const react = useReact();
const queryClient = useQueryClient();
const repostMutation = useRepost({ event });
const { bookmarkNote } = useBookmark(publicKey);

const [menuOpen, setMenuOpen] = useState(false);

Expand Down Expand Up @@ -134,8 +134,15 @@ export const Post: React.FC<PostProps> = ({asComment, event, repostedEventProps,
}
};

const handleBookmarkList = async () => {
showToast({title: 'Bookmark and List coming soon', type: 'info'});
const handleBookmark = async () => {
if (!event) return;
try {
await bookmarkNote({ event });
showToast({title: 'Post bookmarked successfully', type: 'success'});
} catch (error) {
console.error('Bookmark error:', error);
showToast({title: 'Failed to bookmark', type: 'error'});
}
};

const content = event?.content || '';
Expand Down Expand Up @@ -287,10 +294,7 @@ export const Post: React.FC<PostProps> = ({asComment, event, repostedEventProps,

<Pressable
style={{marginHorizontal: 3}}
onPress={() => {
if (!event) return;
handleBookmarkList();
}}
onPress={handleBookmark}
>
<Icon name="BookmarkIcon" size={20} title="Bookmark" />
</Pressable>
Expand Down Expand Up @@ -319,4 +323,4 @@ export const Post: React.FC<PostProps> = ({asComment, event, repostedEventProps,
)}
</View>
);
};
};
1 change: 1 addition & 0 deletions packages/afk_nostr_sdk/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ export {useRepost} from './useRepost';
export {useSendPrivateMessage} from './messages/useSendPrivateMessage';
export {useMyGiftWrapMessages} from './messages/useMyGiftWrapMessages';
export {useMyMessagesSent} from './messages/useMyMessagesSent';
export {useBookmark} from './useBookmark';

106 changes: 106 additions & 0 deletions packages/afk_nostr_sdk/src/hooks/useBookmark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useNostrContext } from '../context/NostrContext';

interface BookmarkParams {
event: NDKEvent;
category?: string;
}

interface RemoveBookmarkParams {
eventId: string;
category?: string;
}

export const useBookmark = (userPublicKey: string) => {
const { ndk } = useNostrContext();
const queryClient = useQueryClient();

const bookmarkNote = useMutation({
mutationKey: ["bookmark", ndk],
mutationFn: async ({ event, category }: BookmarkParams) => {
if (!event) {
throw new Error('No event provided for bookmark');
}

const bookmarkEvent = new NDKEvent(ndk);

if (category) {
bookmarkEvent.kind = NDKKind.BookmarkSet;
bookmarkEvent.tags = [
MSghais marked this conversation as resolved.
Show resolved Hide resolved
['d', category],
['e', event.id, event.relay?.url || ''],
['p', event.pubkey],
];
} else {
bookmarkEvent.kind = 10003;
bookmarkEvent.tags = [
['e', event.id, event.relay?.url || ''],
['p', event.pubkey],
];
}

await bookmarkEvent.publish();
return bookmarkEvent;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['bookmarks'] });
},
onError: (error) => {
console.error('Failed to bookmark note:', error);
},
});

const getBookmarks = useQuery({
queryKey: ['bookmarks', userPublicKey],
queryFn: async () => {
if (!ndk.signer) {
throw new Error('No signer available');
}
const filter = { kinds: [10003, 30003], authors: [userPublicKey] };
const events = await ndk.fetchEvents(filter);
return Array.from(events);
},
});

const removeBookmark = useMutation({
mutationKey: ["bookmark", ndk],
mutationFn: async ({ eventId, category }: RemoveBookmarkParams) => {
const existingBookmarks = getBookmarks.data;

if (!existingBookmarks) {
throw new Error('No existing bookmarks found');
}

const bookmarkEvent = existingBookmarks.find((event) => {
const isMatchingCategory = category
? event.tags.some(tag => tag[0] === 'd' && tag[1] === category)
: true;

return isMatchingCategory && event.tags.some(tag => tag[0] === 'e' && tag[1] === eventId);
});

if (bookmarkEvent) {
bookmarkEvent.tags = bookmarkEvent.tags.filter(tag => !(tag[0] === 'e' && tag[1] === eventId));
if (bookmarkEvent.tags.length > 0) {
await bookmarkEvent.publish();
}
} else {
throw new Error('Bookmark not found');
}
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['bookmarks', userPublicKey] });
},
onError: (error) => {
console.error('Failed to remove bookmark:', error);
},
});

return {
bookmarkNote: bookmarkNote.mutateAsync,
removeBookmark: removeBookmark.mutateAsync,
getBookmarks: getBookmarks.data,
isFetchingBookmarks: getBookmarks.isFetching,
};
};
Loading