11 changed files with 324 additions and 41 deletions
@ -0,0 +1,63 @@ |
|||||||
|
import { buildAccountListRelayUrlsForMerge } from '@/lib/account-list-relay-urls' |
||||||
|
import { |
||||||
|
buildPinListTagsAfterRemovingRef, |
||||||
|
buildPinListTagsAfterToggle, |
||||||
|
fetchNewestPinListForPubkey, |
||||||
|
isEventInPinList |
||||||
|
} from '@/lib/replaceable-list-latest' |
||||||
|
import { decodePersonalListBech32Ref } from '@/lib/personal-list-mutations' |
||||||
|
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' |
||||||
|
import { useNostr } from '@/providers/NostrProvider' |
||||||
|
import indexedDb from '@/services/indexed-db.service' |
||||||
|
import { useCallback } from 'react' |
||||||
|
import type { Event } from 'nostr-tools' |
||||||
|
|
||||||
|
/** |
||||||
|
* Publish an updated kind 10001 pin list without the given entry (by loaded event or NIP-19 ref). |
||||||
|
*/ |
||||||
|
export function useRemovePinListEntry(onSuccess?: () => void) { |
||||||
|
const { publish, pubkey } = useNostr() |
||||||
|
const { favoriteRelays, blockedRelays } = useFavoriteRelays() |
||||||
|
|
||||||
|
const removePinEntry = useCallback( |
||||||
|
async (bech32Id: string, loadedEvent: Event | null): Promise<boolean> => { |
||||||
|
if (!pubkey) return false |
||||||
|
const comprehensiveRelays = await buildAccountListRelayUrlsForMerge({ |
||||||
|
accountPubkey: pubkey, |
||||||
|
favoriteRelays: favoriteRelays ?? [], |
||||||
|
blockedRelays |
||||||
|
}) |
||||||
|
if (!comprehensiveRelays.length) return false |
||||||
|
|
||||||
|
const latest = await fetchNewestPinListForPubkey(pubkey, comprehensiveRelays) |
||||||
|
if (!latest) return false |
||||||
|
|
||||||
|
let newTags: string[][] | null = null |
||||||
|
if (loadedEvent) { |
||||||
|
if (!isEventInPinList(latest, loadedEvent)) return false |
||||||
|
newTags = buildPinListTagsAfterToggle(latest, loadedEvent, false) |
||||||
|
} else { |
||||||
|
const ref = decodePersonalListBech32Ref(bech32Id) |
||||||
|
if (!ref) return false |
||||||
|
newTags = buildPinListTagsAfterRemovingRef(latest.tags, ref) |
||||||
|
} |
||||||
|
if (!newTags) return false |
||||||
|
|
||||||
|
const published = await publish( |
||||||
|
{ |
||||||
|
kind: 10001, |
||||||
|
tags: newTags, |
||||||
|
content: '', |
||||||
|
created_at: Math.floor(Date.now() / 1000) |
||||||
|
}, |
||||||
|
{ specifiedRelayUrls: comprehensiveRelays } |
||||||
|
) |
||||||
|
await indexedDb.putReplaceableEvent(published as Event) |
||||||
|
onSuccess?.() |
||||||
|
return true |
||||||
|
}, |
||||||
|
[blockedRelays, favoriteRelays, onSuccess, publish, pubkey] |
||||||
|
) |
||||||
|
|
||||||
|
return removePinEntry |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
import { nip19 } from 'nostr-tools' |
||||||
|
|
||||||
|
/** Decoded target for one bookmark/pin list entry (NIP-19 nevent/note or naddr). */ |
||||||
|
export type TPersonalListBech32Ref = { eIdLower?: string; aCoordLower?: string } |
||||||
|
|
||||||
|
export function decodePersonalListBech32Ref(bech32Id: string): TPersonalListBech32Ref | null { |
||||||
|
try { |
||||||
|
const dec = nip19.decode(bech32Id.trim()) |
||||||
|
if (dec.type === 'nevent') { |
||||||
|
return { eIdLower: dec.data.id.toLowerCase() } |
||||||
|
} |
||||||
|
if (dec.type === 'note') { |
||||||
|
return { eIdLower: dec.data.toLowerCase() } |
||||||
|
} |
||||||
|
if (dec.type === 'naddr') { |
||||||
|
const { kind, pubkey, identifier } = dec.data |
||||||
|
return { aCoordLower: `${kind}:${pubkey}:${identifier}`.toLowerCase() } |
||||||
|
} |
||||||
|
} catch { |
||||||
|
return null |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Next bookmark list (kind 10003) tags after dropping one `e` or `a` ref. |
||||||
|
* Returns null if nothing matched (list unchanged). |
||||||
|
*/ |
||||||
|
export function bookmarkListTagsAfterRemovingRef( |
||||||
|
tags: string[][], |
||||||
|
ref: TPersonalListBech32Ref |
||||||
|
): string[][] | null { |
||||||
|
if (!ref.eIdLower && !ref.aCoordLower) return null |
||||||
|
const next = tags.filter((tag) => { |
||||||
|
if (ref.eIdLower && tag[0] === 'e' && tag[1]?.toLowerCase() === ref.eIdLower) return false |
||||||
|
if (ref.aCoordLower && tag[0] === 'a' && tag[1]?.toLowerCase() === ref.aCoordLower) return false |
||||||
|
return true |
||||||
|
}) |
||||||
|
return next.length === tags.length ? null : next |
||||||
|
} |
||||||
Loading…
Reference in new issue