feat: add try-delete post option
This commit is contained in:
@@ -4,6 +4,7 @@ import './index.css'
|
|||||||
import { Toaster } from '@/components/ui/sonner'
|
import { Toaster } from '@/components/ui/sonner'
|
||||||
import { BookmarksProvider } from '@/providers/BookmarksProvider'
|
import { BookmarksProvider } from '@/providers/BookmarksProvider'
|
||||||
import { ContentPolicyProvider } from '@/providers/ContentPolicyProvider'
|
import { ContentPolicyProvider } from '@/providers/ContentPolicyProvider'
|
||||||
|
import { DeletedEventProvider } from '@/providers/DeletedEventProvider'
|
||||||
import { FavoriteRelaysProvider } from '@/providers/FavoriteRelaysProvider'
|
import { FavoriteRelaysProvider } from '@/providers/FavoriteRelaysProvider'
|
||||||
import { FeedProvider } from '@/providers/FeedProvider'
|
import { FeedProvider } from '@/providers/FeedProvider'
|
||||||
import { FollowListProvider } from '@/providers/FollowListProvider'
|
import { FollowListProvider } from '@/providers/FollowListProvider'
|
||||||
@@ -24,6 +25,7 @@ export default function App(): JSX.Element {
|
|||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<ContentPolicyProvider>
|
<ContentPolicyProvider>
|
||||||
<ScreenSizeProvider>
|
<ScreenSizeProvider>
|
||||||
|
<DeletedEventProvider>
|
||||||
<NostrProvider>
|
<NostrProvider>
|
||||||
<ZapProvider>
|
<ZapProvider>
|
||||||
<TranslationServiceProvider>
|
<TranslationServiceProvider>
|
||||||
@@ -50,6 +52,7 @@ export default function App(): JSX.Element {
|
|||||||
</TranslationServiceProvider>
|
</TranslationServiceProvider>
|
||||||
</ZapProvider>
|
</ZapProvider>
|
||||||
</NostrProvider>
|
</NostrProvider>
|
||||||
|
</DeletedEventProvider>
|
||||||
</ScreenSizeProvider>
|
</ScreenSizeProvider>
|
||||||
</ContentPolicyProvider>
|
</ContentPolicyProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
isReplaceableEvent,
|
isReplaceableEvent,
|
||||||
isReplyNoteEvent
|
isReplyNoteEvent
|
||||||
} from '@/lib/event'
|
} from '@/lib/event'
|
||||||
|
import { useDeletedEvent } from '@/providers/DeletedEventProvider'
|
||||||
import { useMuteList } from '@/providers/MuteListProvider'
|
import { useMuteList } from '@/providers/MuteListProvider'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { useUserTrust } from '@/providers/UserTrustProvider'
|
import { useUserTrust } from '@/providers/UserTrustProvider'
|
||||||
@@ -44,6 +45,7 @@ const NoteList = forwardRef(
|
|||||||
const { startLogin } = useNostr()
|
const { startLogin } = useNostr()
|
||||||
const { isUserTrusted } = useUserTrust()
|
const { isUserTrusted } = useUserTrust()
|
||||||
const { mutePubkeys } = useMuteList()
|
const { mutePubkeys } = useMuteList()
|
||||||
|
const { isEventDeleted } = useDeletedEvent()
|
||||||
const [events, setEvents] = useState<Event[]>([])
|
const [events, setEvents] = useState<Event[]>([])
|
||||||
const [newEvents, setNewEvents] = useState<Event[]>([])
|
const [newEvents, setNewEvents] = useState<Event[]>([])
|
||||||
const [hasMore, setHasMore] = useState<boolean>(true)
|
const [hasMore, setHasMore] = useState<boolean>(true)
|
||||||
@@ -58,6 +60,7 @@ const NoteList = forwardRef(
|
|||||||
const idSet = new Set<string>()
|
const idSet = new Set<string>()
|
||||||
|
|
||||||
return events.slice(0, showCount).filter((evt) => {
|
return events.slice(0, showCount).filter((evt) => {
|
||||||
|
if (isEventDeleted(evt)) return false
|
||||||
if (hideReplies && isReplyNoteEvent(evt)) return false
|
if (hideReplies && isReplyNoteEvent(evt)) return false
|
||||||
if (hideUntrustedNotes && !isUserTrusted(evt.pubkey)) return false
|
if (hideUntrustedNotes && !isUserTrusted(evt.pubkey)) return false
|
||||||
|
|
||||||
@@ -68,12 +71,13 @@ const NoteList = forwardRef(
|
|||||||
idSet.add(id)
|
idSet.add(id)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}, [events, hideReplies, hideUntrustedNotes, showCount])
|
}, [events, hideReplies, hideUntrustedNotes, showCount, isEventDeleted])
|
||||||
|
|
||||||
const filteredNewEvents = useMemo(() => {
|
const filteredNewEvents = useMemo(() => {
|
||||||
const idSet = new Set<string>()
|
const idSet = new Set<string>()
|
||||||
|
|
||||||
return newEvents.filter((event: Event) => {
|
return newEvents.filter((event: Event) => {
|
||||||
|
if (isEventDeleted(event)) return false
|
||||||
if (hideReplies && isReplyNoteEvent(event)) return false
|
if (hideReplies && isReplyNoteEvent(event)) return false
|
||||||
if (hideUntrustedNotes && !isUserTrusted(event.pubkey)) return false
|
if (hideUntrustedNotes && !isUserTrusted(event.pubkey)) return false
|
||||||
if (filterMutedNotes && mutePubkeys.includes(event.pubkey)) return false
|
if (filterMutedNotes && mutePubkeys.includes(event.pubkey)) return false
|
||||||
@@ -87,7 +91,7 @@ const NoteList = forwardRef(
|
|||||||
idSet.add(id)
|
idSet.add(id)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}, [newEvents, hideReplies, hideUntrustedNotes, filterMutedNotes, mutePubkeys])
|
}, [newEvents, hideReplies, hideUntrustedNotes, filterMutedNotes, mutePubkeys, isEventDeleted])
|
||||||
|
|
||||||
const scrollToTop = (behavior: ScrollBehavior = 'instant') => {
|
const scrollToTop = (behavior: ScrollBehavior = 'instant') => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
|
|||||||
import { useMuteList } from '@/providers/MuteListProvider'
|
import { useMuteList } from '@/providers/MuteListProvider'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import client from '@/services/client.service'
|
import client from '@/services/client.service'
|
||||||
import { Bell, BellOff, Code, Copy, Link, Mail, SatelliteDish, Server } from 'lucide-react'
|
import { Bell, BellOff, Code, Copy, Link, Mail, SatelliteDish, Server, Trash2 } from 'lucide-react'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -45,7 +45,7 @@ export function useMenuActions({
|
|||||||
isSmallScreen
|
isSmallScreen
|
||||||
}: UseMenuActionsProps) {
|
}: UseMenuActionsProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { pubkey, relayList } = useNostr()
|
const { pubkey, relayList, attemptDelete } = useNostr()
|
||||||
const { relaySets, favoriteRelays } = useFavoriteRelays()
|
const { relaySets, favoriteRelays } = useFavoriteRelays()
|
||||||
const { mutePubkeyPublicly, mutePubkeyPrivately, unmutePubkey, mutePubkeys } = useMuteList()
|
const { mutePubkeyPublicly, mutePubkeyPrivately, unmutePubkey, mutePubkeys } = useMuteList()
|
||||||
const isMuted = useMemo(() => mutePubkeys.includes(event.pubkey), [mutePubkeys, event])
|
const isMuted = useMemo(() => mutePubkeys.includes(event.pubkey), [mutePubkeys, event])
|
||||||
@@ -235,6 +235,19 @@ export function useMenuActions({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pubkey && event.pubkey === pubkey) {
|
||||||
|
actions.push({
|
||||||
|
icon: Trash2,
|
||||||
|
label: t('Try deleting this note'),
|
||||||
|
onClick: () => {
|
||||||
|
closeDrawer()
|
||||||
|
attemptDelete(event)
|
||||||
|
},
|
||||||
|
className: 'text-destructive focus:text-destructive',
|
||||||
|
separator: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return actions
|
return actions
|
||||||
}, [
|
}, [
|
||||||
t,
|
t,
|
||||||
|
|||||||
@@ -367,6 +367,8 @@ export default {
|
|||||||
'Remember my choice': 'تذكر اختياري',
|
'Remember my choice': 'تذكر اختياري',
|
||||||
Apply: 'تطبيق',
|
Apply: 'تطبيق',
|
||||||
Reset: 'إعادة تعيين',
|
Reset: 'إعادة تعيين',
|
||||||
'Share something on this Relay': 'شارك شيئاً على هذا الريلاي'
|
'Share something on this Relay': 'شارك شيئاً على هذا الريلاي',
|
||||||
|
'Try deleting this note': 'حاول حذف هذه الملاحظة',
|
||||||
|
'Deletion request sent to {{count}} relays': 'تم إرسال طلب الحذف إلى {{count}} ريلايات'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -375,6 +375,8 @@ export default {
|
|||||||
'Remember my choice': 'Meine Auswahl merken',
|
'Remember my choice': 'Meine Auswahl merken',
|
||||||
Apply: 'Anwenden',
|
Apply: 'Anwenden',
|
||||||
Reset: 'Zurücksetzen',
|
Reset: 'Zurücksetzen',
|
||||||
'Share something on this Relay': 'Teile etwas auf diesem Relay'
|
'Share something on this Relay': 'Teile etwas auf diesem Relay',
|
||||||
|
'Try deleting this note': 'Versuche, diese Notiz zu löschen',
|
||||||
|
'Deletion request sent to {{count}} relays': 'Löschanfrage an {{count}} Relays gesendet'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -366,6 +366,8 @@ export default {
|
|||||||
'Remember my choice': 'Remember my choice',
|
'Remember my choice': 'Remember my choice',
|
||||||
Apply: 'Apply',
|
Apply: 'Apply',
|
||||||
Reset: 'Reset',
|
Reset: 'Reset',
|
||||||
'Share something on this Relay': 'Share something on this Relay'
|
'Share something on this Relay': 'Share something on this Relay',
|
||||||
|
'Try deleting this note': 'Try deleting this note',
|
||||||
|
'Deletion request sent to {{count}} relays': 'Deletion request sent to {{count}} relays'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -371,6 +371,9 @@ export default {
|
|||||||
'Remember my choice': 'Recordar mi elección',
|
'Remember my choice': 'Recordar mi elección',
|
||||||
Apply: 'Aplicar',
|
Apply: 'Aplicar',
|
||||||
Reset: 'Restablecer',
|
Reset: 'Restablecer',
|
||||||
'Share something on this Relay': 'Comparte algo en este relé'
|
'Share something on this Relay': 'Comparte algo en este relé',
|
||||||
|
'Try deleting this note': 'Intenta eliminar esta nota',
|
||||||
|
'Deletion request sent to {{count}} relays':
|
||||||
|
'Solicitud de eliminación enviada a {{count}} relés'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -368,6 +368,8 @@ export default {
|
|||||||
'Remember my choice': 'انتخاب من را به خاطر بسپار',
|
'Remember my choice': 'انتخاب من را به خاطر بسپار',
|
||||||
Apply: 'اعمال',
|
Apply: 'اعمال',
|
||||||
Reset: 'بازنشانی',
|
Reset: 'بازنشانی',
|
||||||
'Share something on this Relay': 'در این رله چیزی به اشتراک بگذارید'
|
'Share something on this Relay': 'در این رله چیزی به اشتراک بگذارید',
|
||||||
|
'Try deleting this note': 'سعی کنید این یادداشت را حذف کنید',
|
||||||
|
'Deletion request sent to {{count}} relays': 'درخواست حذف به {{count}} رله ارسال شد'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -373,6 +373,8 @@ export default {
|
|||||||
'Remember my choice': 'Se souvenir de mon choix',
|
'Remember my choice': 'Se souvenir de mon choix',
|
||||||
Apply: 'Appliquer',
|
Apply: 'Appliquer',
|
||||||
Reset: 'Réinitialiser',
|
Reset: 'Réinitialiser',
|
||||||
'Share something on this Relay': 'Partager quelque chose sur ce relais'
|
'Share something on this Relay': 'Partager quelque chose sur ce relais',
|
||||||
|
'Try deleting this note': 'Essayez de supprimer cette note',
|
||||||
|
'Deletion request sent to {{count}} relays': 'Demande de suppression envoyée à {{count}} relais'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -371,6 +371,9 @@ export default {
|
|||||||
'Remember my choice': 'Ricorda la mia scelta',
|
'Remember my choice': 'Ricorda la mia scelta',
|
||||||
Apply: 'Applica',
|
Apply: 'Applica',
|
||||||
Reset: 'Reimposta',
|
Reset: 'Reimposta',
|
||||||
'Share something on this Relay': 'Condividi qualcosa su questo Relay'
|
'Share something on this Relay': 'Condividi qualcosa su questo Relay',
|
||||||
|
'Try deleting this note': 'Prova a eliminare questa nota',
|
||||||
|
'Deletion request sent to {{count}} relays':
|
||||||
|
'Richiesta di eliminazione inviata a {{count}} relays'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -368,6 +368,9 @@ export default {
|
|||||||
'Remember my choice': '選択を記憶',
|
'Remember my choice': '選択を記憶',
|
||||||
Apply: '適用',
|
Apply: '適用',
|
||||||
Reset: 'リセット',
|
Reset: 'リセット',
|
||||||
'Share something on this Relay': 'このリレーで何かを共有する'
|
'Share something on this Relay': 'このリレーで何かを共有する',
|
||||||
|
'Try deleting this note': 'このノートを削除してみてください',
|
||||||
|
'Deletion request sent to {{count}} relays':
|
||||||
|
'削除リクエストが{{count}}個のリレーに送信されました'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -368,6 +368,8 @@ export default {
|
|||||||
'Remember my choice': '내 선택 기억하기',
|
'Remember my choice': '내 선택 기억하기',
|
||||||
Apply: '적용',
|
Apply: '적용',
|
||||||
Reset: '초기화',
|
Reset: '초기화',
|
||||||
'Share something on this Relay': '이 릴레이에서 무언가를 공유하세요'
|
'Share something on this Relay': '이 릴레이에서 무언가를 공유하세요',
|
||||||
|
'Try deleting this note': '이 노트를 삭제해 보세요',
|
||||||
|
'Deletion request sent to {{count}} relays': '삭제 요청이 {{count}}개의 릴레이로 전송되었습니다'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -372,6 +372,9 @@ export default {
|
|||||||
'Remember my choice': 'Zapamiętaj mój wybór',
|
'Remember my choice': 'Zapamiętaj mój wybór',
|
||||||
Apply: 'Zastosuj',
|
Apply: 'Zastosuj',
|
||||||
Reset: 'Resetuj',
|
Reset: 'Resetuj',
|
||||||
'Share something on this Relay': 'Udostępnij coś na tym przekaźniku'
|
'Share something on this Relay': 'Udostępnij coś na tym przekaźniku',
|
||||||
|
'Try deleting this note': 'Spróbuj usunąć ten wpis',
|
||||||
|
'Deletion request sent to {{count}} relays':
|
||||||
|
'Żądanie usunięcia wysłane do {{count}} przekaźników'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,6 +369,8 @@ export default {
|
|||||||
'Remember my choice': 'Lembrar minha escolha',
|
'Remember my choice': 'Lembrar minha escolha',
|
||||||
Apply: 'Aplicar',
|
Apply: 'Aplicar',
|
||||||
Reset: 'Redefinir',
|
Reset: 'Redefinir',
|
||||||
'Share something on this Relay': 'Compartilhe algo neste Relay'
|
'Share something on this Relay': 'Compartilhe algo neste Relay',
|
||||||
|
'Try deleting this note': 'Tente excluir esta nota',
|
||||||
|
'Deletion request sent to {{count}} relays': 'Pedido de exclusão enviado para {{count}} relays'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -371,6 +371,9 @@ export default {
|
|||||||
'Remember my choice': 'Lembrar a minha escolha',
|
'Remember my choice': 'Lembrar a minha escolha',
|
||||||
Apply: 'Aplicar',
|
Apply: 'Aplicar',
|
||||||
Reset: 'Repor',
|
Reset: 'Repor',
|
||||||
'Share something on this Relay': 'Partilhe algo neste Relay'
|
'Share something on this Relay': 'Partilhe algo neste Relay',
|
||||||
|
'Try deleting this note': 'Tente eliminar esta nota',
|
||||||
|
'Deletion request sent to {{count}} relays':
|
||||||
|
'Pedido de eliminação enviado para {{count}} relays'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -372,6 +372,8 @@ export default {
|
|||||||
'Remember my choice': 'Запомнить мой выбор',
|
'Remember my choice': 'Запомнить мой выбор',
|
||||||
Apply: 'Применить',
|
Apply: 'Применить',
|
||||||
Reset: 'Сбросить',
|
Reset: 'Сбросить',
|
||||||
'Share something on this Relay': 'Поделиться чем-то на этом релее'
|
'Share something on this Relay': 'Поделиться чем-то на этом релее',
|
||||||
|
'Try deleting this note': 'Попробуйте удалить эту заметку',
|
||||||
|
'Deletion request sent to {{count}} relays': 'Запрос на удаление отправлен на {{count}} релеев'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -365,6 +365,8 @@ export default {
|
|||||||
'Remember my choice': 'จำการเลือกของฉัน',
|
'Remember my choice': 'จำการเลือกของฉัน',
|
||||||
Apply: 'ใช้',
|
Apply: 'ใช้',
|
||||||
Reset: 'รีเซ็ต',
|
Reset: 'รีเซ็ต',
|
||||||
'Share something on this Relay': 'แชร์บางอย่างบนรีเลย์นี้'
|
'Share something on this Relay': 'แชร์บางอย่างบนรีเลย์นี้',
|
||||||
|
'Try deleting this note': 'ลองลบโน้ตนี้ดู',
|
||||||
|
'Deletion request sent to {{count}} relays': 'คำขอลบถูกส่งไปยังรีเลย์ {{count}} รายการ'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -363,6 +363,8 @@ export default {
|
|||||||
'Remember my choice': '记住我的选择',
|
'Remember my choice': '记住我的选择',
|
||||||
Apply: '应用',
|
Apply: '应用',
|
||||||
Reset: '重置',
|
Reset: '重置',
|
||||||
'Share something on this Relay': '在此服务器上分享点什么'
|
'Share something on this Relay': '在此服务器上分享点什么',
|
||||||
|
'Try deleting this note': '尝试删除此笔记',
|
||||||
|
'Deletion request sent to {{count}} relays': '删除请求已发送到 {{count}} 个服务器'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -416,6 +416,22 @@ export function createPollResponseDraftEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createDeletionRequestDraftEvent(event: Event): TDraftEvent {
|
||||||
|
const tags: string[][] = [buildKTag(event.kind)]
|
||||||
|
if (isReplaceableEvent(event.kind)) {
|
||||||
|
tags.push(['a', getReplaceableCoordinateFromEvent(event)])
|
||||||
|
} else {
|
||||||
|
tags.push(['e', event.id])
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: kinds.EventDeletion,
|
||||||
|
content: 'Request for deletion of the event.',
|
||||||
|
tags,
|
||||||
|
created_at: dayjs().unix()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function generateImetaTags(imageUrls: string[]) {
|
function generateImetaTags(imageUrls: string[]) {
|
||||||
return imageUrls
|
return imageUrls
|
||||||
.map((imageUrl) => {
|
.map((imageUrl) => {
|
||||||
|
|||||||
43
src/providers/DeletedEventProvider.tsx
Normal file
43
src/providers/DeletedEventProvider.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event'
|
||||||
|
import { NostrEvent } from 'nostr-tools'
|
||||||
|
import { createContext, useCallback, useContext, useState } from 'react'
|
||||||
|
|
||||||
|
type TDeletedEventContext = {
|
||||||
|
addDeletedEvent: (event: NostrEvent) => void
|
||||||
|
isEventDeleted: (event: NostrEvent) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeletedEventContext = createContext<TDeletedEventContext | undefined>(undefined)
|
||||||
|
|
||||||
|
export const useDeletedEvent = () => {
|
||||||
|
const context = useContext(DeletedEventContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useDeletedEvent must be used within a DeletedEventProvider')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeletedEventProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [deletedEventKeys, setDeletedEventKeys] = useState<Set<string>>(new Set())
|
||||||
|
|
||||||
|
const isEventDeleted = useCallback(
|
||||||
|
(event: NostrEvent) => {
|
||||||
|
return deletedEventKeys.has(getKey(event))
|
||||||
|
},
|
||||||
|
[deletedEventKeys]
|
||||||
|
)
|
||||||
|
|
||||||
|
const addDeletedEvent = (event: NostrEvent) => {
|
||||||
|
setDeletedEventKeys((prev) => new Set(prev).add(getKey(event)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DeletedEventContext.Provider value={{ addDeletedEvent, isEventDeleted }}>
|
||||||
|
{children}
|
||||||
|
</DeletedEventContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKey(event: NostrEvent) {
|
||||||
|
return isReplaceableEvent(event.kind) ? getReplaceableCoordinateFromEvent(event) : event.id
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import LoginDialog from '@/components/LoginDialog'
|
import LoginDialog from '@/components/LoginDialog'
|
||||||
import { ApplicationDataKey, BIG_RELAY_URLS, ExtendedKind } from '@/constants'
|
import { ApplicationDataKey, BIG_RELAY_URLS, ExtendedKind } from '@/constants'
|
||||||
import {
|
import {
|
||||||
|
createDeletionRequestDraftEvent,
|
||||||
createFollowListDraftEvent,
|
createFollowListDraftEvent,
|
||||||
createMuteListDraftEvent,
|
createMuteListDraftEvent,
|
||||||
createRelayListDraftEvent,
|
createRelayListDraftEvent,
|
||||||
createSeenNotificationsAtDraftEvent
|
createSeenNotificationsAtDraftEvent
|
||||||
} from '@/lib/draft-event'
|
} from '@/lib/draft-event'
|
||||||
import { getLatestEvent, getReplaceableEventIdentifier } from '@/lib/event'
|
import { getLatestEvent, getReplaceableEventIdentifier, isProtectedEvent } from '@/lib/event'
|
||||||
import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata'
|
import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata'
|
||||||
import { formatPubkey, isValidPubkey, pubkeyToNpub } from '@/lib/pubkey'
|
import { formatPubkey, isValidPubkey, pubkeyToNpub } from '@/lib/pubkey'
|
||||||
import client from '@/services/client.service'
|
import client from '@/services/client.service'
|
||||||
@@ -28,6 +29,7 @@ import { Nip07Signer } from './nip-07.signer'
|
|||||||
import { NostrConnectionSigner } from './nostrConnection.signer'
|
import { NostrConnectionSigner } from './nostrConnection.signer'
|
||||||
import { NpubSigner } from './npub.signer'
|
import { NpubSigner } from './npub.signer'
|
||||||
import { NsecSigner } from './nsec.signer'
|
import { NsecSigner } from './nsec.signer'
|
||||||
|
import { useDeletedEvent } from '../DeletedEventProvider'
|
||||||
|
|
||||||
type TPublishOptions = {
|
type TPublishOptions = {
|
||||||
specifiedRelayUrls?: string[]
|
specifiedRelayUrls?: string[]
|
||||||
@@ -62,6 +64,7 @@ type TNostrContext = {
|
|||||||
* Default publish the event to current relays, user's write relays and additional relays
|
* Default publish the event to current relays, user's write relays and additional relays
|
||||||
*/
|
*/
|
||||||
publish: (draftEvent: TDraftEvent, options?: TPublishOptions) => Promise<Event>
|
publish: (draftEvent: TDraftEvent, options?: TPublishOptions) => Promise<Event>
|
||||||
|
attemptDelete: (targetEvent: Event) => Promise<void>
|
||||||
signHttpAuth: (url: string, method: string) => Promise<string>
|
signHttpAuth: (url: string, method: string) => Promise<string>
|
||||||
signEvent: (draftEvent: TDraftEvent) => Promise<VerifiedEvent>
|
signEvent: (draftEvent: TDraftEvent) => Promise<VerifiedEvent>
|
||||||
nip04Encrypt: (pubkey: string, plainText: string) => Promise<string>
|
nip04Encrypt: (pubkey: string, plainText: string) => Promise<string>
|
||||||
@@ -91,6 +94,7 @@ export const useNostr = () => {
|
|||||||
|
|
||||||
export function NostrProvider({ children }: { children: React.ReactNode }) {
|
export function NostrProvider({ children }: { children: React.ReactNode }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { addDeletedEvent } = useDeletedEvent()
|
||||||
const [accounts, setAccounts] = useState<TAccountPointer[]>(
|
const [accounts, setAccounts] = useState<TAccountPointer[]>(
|
||||||
storage.getAccounts().map((act) => ({ pubkey: act.pubkey, signerType: act.signerType }))
|
storage.getAccounts().map((act) => ({ pubkey: act.pubkey, signerType: act.signerType }))
|
||||||
)
|
)
|
||||||
@@ -587,10 +591,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||||||
return event as VerifiedEvent
|
return event as VerifiedEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
const publish = async (
|
const publish = async (draftEvent: TDraftEvent, options: TPublishOptions = {}) => {
|
||||||
draftEvent: TDraftEvent,
|
|
||||||
{ specifiedRelayUrls, additionalRelayUrls }: TPublishOptions = {}
|
|
||||||
) => {
|
|
||||||
if (!account || !signer || account.signerType === 'npub') {
|
if (!account || !signer || account.signerType === 'npub') {
|
||||||
throw new Error('You need to login first')
|
throw new Error('You need to login first')
|
||||||
}
|
}
|
||||||
@@ -610,58 +611,34 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const _additionalRelayUrls: string[] = additionalRelayUrls ?? []
|
const relays = await determineTargetRelays(event, options)
|
||||||
if (
|
|
||||||
!specifiedRelayUrls?.length &&
|
|
||||||
![kinds.Contacts, kinds.Mutelist].includes(draftEvent.kind)
|
|
||||||
) {
|
|
||||||
const mentions: string[] = []
|
|
||||||
draftEvent.tags.forEach(([tagName, tagValue]) => {
|
|
||||||
if (
|
|
||||||
['p', 'P'].includes(tagName) &&
|
|
||||||
!!tagValue &&
|
|
||||||
isValidPubkey(tagValue) &&
|
|
||||||
!mentions.includes(tagValue)
|
|
||||||
) {
|
|
||||||
mentions.push(tagValue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (mentions.length > 0) {
|
|
||||||
const relayLists = await client.fetchRelayLists(mentions)
|
|
||||||
relayLists.forEach((relayList) => {
|
|
||||||
_additionalRelayUrls.push(...relayList.read.slice(0, 4))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
kinds.RelayList,
|
|
||||||
kinds.Contacts,
|
|
||||||
ExtendedKind.FAVORITE_RELAYS,
|
|
||||||
ExtendedKind.BLOSSOM_SERVER_LIST
|
|
||||||
].includes(draftEvent.kind)
|
|
||||||
) {
|
|
||||||
_additionalRelayUrls.push(...BIG_RELAY_URLS)
|
|
||||||
}
|
|
||||||
|
|
||||||
let relays: string[]
|
|
||||||
if (specifiedRelayUrls?.length) {
|
|
||||||
relays = specifiedRelayUrls
|
|
||||||
} else {
|
|
||||||
const relayList = await client.fetchRelayList(event.pubkey)
|
|
||||||
relays = (relayList?.write.slice(0, 10) ?? []).concat(
|
|
||||||
Array.from(new Set(_additionalRelayUrls)) ?? []
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!relays.length) {
|
|
||||||
relays.push(...BIG_RELAY_URLS)
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.publishEvent(relays, event)
|
await client.publishEvent(relays, event)
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const attemptDelete = async (targetEvent: Event) => {
|
||||||
|
if (!signer) {
|
||||||
|
throw new Error(t('You need to login first'))
|
||||||
|
}
|
||||||
|
if (account?.pubkey !== targetEvent.pubkey) {
|
||||||
|
throw new Error(t('You can only delete your own notes'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletionRequest = await signEvent(createDeletionRequestDraftEvent(targetEvent))
|
||||||
|
|
||||||
|
const seenOn = client.getSeenEventRelayUrls(targetEvent.id)
|
||||||
|
const relays = await determineTargetRelays(targetEvent, {
|
||||||
|
specifiedRelayUrls: isProtectedEvent(targetEvent) ? seenOn : undefined,
|
||||||
|
additionalRelayUrls: seenOn
|
||||||
|
})
|
||||||
|
|
||||||
|
await client.publishEvent(relays, deletionRequest)
|
||||||
|
|
||||||
|
addDeletedEvent(targetEvent)
|
||||||
|
toast.success(t('Deletion request sent to {{count}} relays', { count: relays.length }))
|
||||||
|
}
|
||||||
|
|
||||||
const signHttpAuth = async (url: string, method: string, content = '') => {
|
const signHttpAuth = async (url: string, method: string, content = '') => {
|
||||||
const event = await signEvent({
|
const event = await signEvent({
|
||||||
content,
|
content,
|
||||||
@@ -779,6 +756,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||||||
npubLogin,
|
npubLogin,
|
||||||
removeAccount,
|
removeAccount,
|
||||||
publish,
|
publish,
|
||||||
|
attemptDelete,
|
||||||
signHttpAuth,
|
signHttpAuth,
|
||||||
nip04Encrypt,
|
nip04Encrypt,
|
||||||
nip04Decrypt,
|
nip04Decrypt,
|
||||||
@@ -799,3 +777,55 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||||||
</NostrContext.Provider>
|
</NostrContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function determineTargetRelays(
|
||||||
|
event: Event,
|
||||||
|
{ specifiedRelayUrls, additionalRelayUrls }: TPublishOptions = {}
|
||||||
|
) {
|
||||||
|
const _additionalRelayUrls: string[] = additionalRelayUrls ?? []
|
||||||
|
if (!specifiedRelayUrls?.length && ![kinds.Contacts, kinds.Mutelist].includes(event.kind)) {
|
||||||
|
const mentions: string[] = []
|
||||||
|
event.tags.forEach(([tagName, tagValue]) => {
|
||||||
|
if (
|
||||||
|
['p', 'P'].includes(tagName) &&
|
||||||
|
!!tagValue &&
|
||||||
|
isValidPubkey(tagValue) &&
|
||||||
|
!mentions.includes(tagValue)
|
||||||
|
) {
|
||||||
|
mentions.push(tagValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (mentions.length > 0) {
|
||||||
|
const relayLists = await client.fetchRelayLists(mentions)
|
||||||
|
relayLists.forEach((relayList) => {
|
||||||
|
_additionalRelayUrls.push(...relayList.read.slice(0, 4))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
kinds.RelayList,
|
||||||
|
kinds.Contacts,
|
||||||
|
ExtendedKind.FAVORITE_RELAYS,
|
||||||
|
ExtendedKind.BLOSSOM_SERVER_LIST
|
||||||
|
].includes(event.kind)
|
||||||
|
) {
|
||||||
|
_additionalRelayUrls.push(...BIG_RELAY_URLS)
|
||||||
|
}
|
||||||
|
|
||||||
|
let relays: string[]
|
||||||
|
if (specifiedRelayUrls?.length) {
|
||||||
|
relays = specifiedRelayUrls
|
||||||
|
} else {
|
||||||
|
const relayList = await client.fetchRelayList(event.pubkey)
|
||||||
|
relays = (relayList?.write.slice(0, 10) ?? []).concat(
|
||||||
|
Array.from(new Set(_additionalRelayUrls)) ?? []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relays.length) {
|
||||||
|
relays.push(...BIG_RELAY_URLS)
|
||||||
|
}
|
||||||
|
|
||||||
|
return relays
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user