feat: bookmarks (#279)
This commit is contained in:
72
src/providers/BookmarksProvider.tsx
Normal file
72
src/providers/BookmarksProvider.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { createBookmarkDraftEvent } from '@/lib/draft-event'
|
||||
import { createContext, useContext, useMemo } from 'react'
|
||||
import { useNostr } from './NostrProvider'
|
||||
import client from '@/services/client.service'
|
||||
|
||||
type TBookmarksContext = {
|
||||
bookmarks: string[][]
|
||||
addBookmark: (eventId: string, eventPubkey: string, relayHint?: string) => Promise<void>
|
||||
removeBookmark: (eventId: string) => Promise<void>
|
||||
}
|
||||
|
||||
const BookmarksContext = createContext<TBookmarksContext | undefined>(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, bookmarkListEvent, publish, updateBookmarkListEvent } = useNostr()
|
||||
const bookmarks = useMemo(
|
||||
() => (bookmarkListEvent ? bookmarkListEvent.tags : []),
|
||||
[bookmarkListEvent]
|
||||
)
|
||||
|
||||
const addBookmark = async (eventId: string, eventPubkey: string, relayHint?: string) => {
|
||||
if (!accountPubkey) return
|
||||
|
||||
const relayHintToUse = relayHint || client.getEventHint(eventId)
|
||||
|
||||
const newTag = ['e', eventId, relayHintToUse, eventPubkey]
|
||||
|
||||
const currentTags = bookmarkListEvent?.tags || []
|
||||
|
||||
const isDuplicate = currentTags.some((tag) => tag[0] === 'e' && tag[1] === eventId)
|
||||
|
||||
if (isDuplicate) return
|
||||
|
||||
const newTags = [...currentTags, newTag]
|
||||
|
||||
const newBookmarkDraftEvent = createBookmarkDraftEvent(newTags)
|
||||
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
|
||||
await updateBookmarkListEvent(newBookmarkEvent)
|
||||
}
|
||||
|
||||
const removeBookmark = async (eventId: string) => {
|
||||
if (!accountPubkey || !bookmarkListEvent) return
|
||||
|
||||
const newTags = bookmarkListEvent.tags.filter((tag) => !(tag[0] === 'e' && tag[1] === eventId))
|
||||
|
||||
if (newTags.length === bookmarkListEvent.tags.length) return
|
||||
|
||||
const newBookmarkDraftEvent = createBookmarkDraftEvent(newTags)
|
||||
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
|
||||
await updateBookmarkListEvent(newBookmarkEvent)
|
||||
}
|
||||
|
||||
return (
|
||||
<BookmarksContext.Provider
|
||||
value={{
|
||||
bookmarks,
|
||||
addBookmark,
|
||||
removeBookmark
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</BookmarksContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -170,6 +170,20 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
|
||||
})
|
||||
return setIsReady(true)
|
||||
}
|
||||
if (feedType === 'bookmarks') {
|
||||
if (!options.pubkey) {
|
||||
return setIsReady(true)
|
||||
}
|
||||
|
||||
const newFeedInfo = { feedType }
|
||||
setFeedInfo(newFeedInfo)
|
||||
feedInfoRef.current = newFeedInfo
|
||||
storage.setFeedInfo(newFeedInfo, pubkey)
|
||||
|
||||
setRelayUrls([])
|
||||
setFilter({})
|
||||
return setIsReady(true)
|
||||
}
|
||||
if (feedType === 'temporary') {
|
||||
const urls = options.temporaryRelayUrls ?? temporaryRelayUrls
|
||||
if (!urls.length) {
|
||||
|
||||
@@ -33,6 +33,7 @@ type TNostrContext = {
|
||||
relayList: TRelayList | null
|
||||
followListEvent?: Event
|
||||
muteListEvent?: Event
|
||||
bookmarkListEvent?: Event
|
||||
favoriteRelaysEvent: Event | null
|
||||
notificationsSeenAt: number
|
||||
account: TAccountPointer | null
|
||||
@@ -60,6 +61,7 @@ type TNostrContext = {
|
||||
updateProfileEvent: (profileEvent: Event) => Promise<void>
|
||||
updateFollowListEvent: (followListEvent: Event) => Promise<void>
|
||||
updateMuteListEvent: (muteListEvent: Event, tags: string[][]) => Promise<void>
|
||||
updateBookmarkListEvent: (bookmarkListEvent: Event) => Promise<void>
|
||||
updateFavoriteRelaysEvent: (favoriteRelaysEvent: Event) => Promise<void>
|
||||
updateNotificationsSeenAt: () => Promise<void>
|
||||
}
|
||||
@@ -87,6 +89,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
||||
const [relayList, setRelayList] = useState<TRelayList | null>(null)
|
||||
const [followListEvent, setFollowListEvent] = useState<Event | undefined>(undefined)
|
||||
const [muteListEvent, setMuteListEvent] = useState<Event | undefined>(undefined)
|
||||
const [bookmarkListEvent, setBookmarkListEvent] = useState<Event | undefined>(undefined)
|
||||
const [favoriteRelaysEvent, setFavoriteRelaysEvent] = useState<Event | null>(null)
|
||||
const [notificationsSeenAt, setNotificationsSeenAt] = useState(-1)
|
||||
const [isInitialized, setIsInitialized] = useState(false)
|
||||
@@ -149,12 +152,14 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
||||
storedProfileEvent,
|
||||
storedFollowListEvent,
|
||||
storedMuteListEvent,
|
||||
storedBookmarkListEvent,
|
||||
storedFavoriteRelaysEvent
|
||||
] = await Promise.all([
|
||||
indexedDb.getReplaceableEvent(account.pubkey, kinds.RelayList),
|
||||
indexedDb.getReplaceableEvent(account.pubkey, kinds.Metadata),
|
||||
indexedDb.getReplaceableEvent(account.pubkey, kinds.Contacts),
|
||||
indexedDb.getReplaceableEvent(account.pubkey, kinds.Mutelist),
|
||||
indexedDb.getReplaceableEvent(account.pubkey, kinds.BookmarkList),
|
||||
indexedDb.getReplaceableEvent(account.pubkey, ExtendedKind.FAVORITE_RELAYS)
|
||||
])
|
||||
if (storedRelayListEvent) {
|
||||
@@ -172,6 +177,9 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
||||
if (storedMuteListEvent) {
|
||||
setMuteListEvent(storedMuteListEvent)
|
||||
}
|
||||
if (storedBookmarkListEvent) {
|
||||
setBookmarkListEvent(storedBookmarkListEvent)
|
||||
}
|
||||
if (storedFavoriteRelaysEvent) {
|
||||
setFavoriteRelaysEvent(storedFavoriteRelaysEvent)
|
||||
}
|
||||
@@ -190,7 +198,13 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const events = await client.fetchEvents(relayList.write.concat(BIG_RELAY_URLS).slice(0, 4), [
|
||||
{
|
||||
kinds: [kinds.Metadata, kinds.Contacts, kinds.Mutelist, ExtendedKind.FAVORITE_RELAYS],
|
||||
kinds: [
|
||||
kinds.Metadata,
|
||||
kinds.Contacts,
|
||||
kinds.Mutelist,
|
||||
kinds.BookmarkList,
|
||||
ExtendedKind.FAVORITE_RELAYS
|
||||
],
|
||||
authors: [account.pubkey]
|
||||
},
|
||||
{
|
||||
@@ -203,6 +217,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
||||
const profileEvent = sortedEvents.find((e) => e.kind === kinds.Metadata)
|
||||
const followListEvent = sortedEvents.find((e) => e.kind === kinds.Contacts)
|
||||
const muteListEvent = sortedEvents.find((e) => e.kind === kinds.Mutelist)
|
||||
const bookmarkListEvent = sortedEvents.find((e) => e.kind === kinds.BookmarkList)
|
||||
const favoriteRelaysEvent = sortedEvents.find((e) => e.kind === ExtendedKind.FAVORITE_RELAYS)
|
||||
const notificationsSeenAtEvent = sortedEvents.find(
|
||||
(e) =>
|
||||
@@ -227,6 +242,10 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
||||
setMuteListEvent(muteListEvent)
|
||||
await indexedDb.putReplaceableEvent(muteListEvent)
|
||||
}
|
||||
if (bookmarkListEvent) {
|
||||
setBookmarkListEvent(bookmarkListEvent)
|
||||
await indexedDb.putReplaceableEvent(bookmarkListEvent)
|
||||
}
|
||||
if (favoriteRelaysEvent) {
|
||||
setFavoriteRelaysEvent(favoriteRelaysEvent)
|
||||
await indexedDb.putReplaceableEvent(favoriteRelaysEvent)
|
||||
@@ -563,6 +582,13 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
||||
setMuteListEvent(muteListEvent)
|
||||
}
|
||||
|
||||
const updateBookmarkListEvent = async (bookmarkListEvent: Event) => {
|
||||
const newBookmarkListEvent = await indexedDb.putReplaceableEvent(bookmarkListEvent)
|
||||
if (newBookmarkListEvent.id !== bookmarkListEvent.id) return
|
||||
|
||||
setBookmarkListEvent(newBookmarkListEvent)
|
||||
}
|
||||
|
||||
const updateFavoriteRelaysEvent = async (favoriteRelaysEvent: Event) => {
|
||||
const newFavoriteRelaysEvent = await indexedDb.putReplaceableEvent(favoriteRelaysEvent)
|
||||
if (newFavoriteRelaysEvent.id !== favoriteRelaysEvent.id) return
|
||||
@@ -591,6 +617,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
||||
relayList,
|
||||
followListEvent,
|
||||
muteListEvent,
|
||||
bookmarkListEvent,
|
||||
favoriteRelaysEvent,
|
||||
notificationsSeenAt,
|
||||
account,
|
||||
@@ -617,6 +644,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
||||
updateProfileEvent,
|
||||
updateFollowListEvent,
|
||||
updateMuteListEvent,
|
||||
updateBookmarkListEvent,
|
||||
updateFavoriteRelaysEvent,
|
||||
updateNotificationsSeenAt
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user