feat: 💨

This commit is contained in:
codytseng
2025-08-31 00:08:30 +08:00
parent 9589095dc5
commit b3b7176bcd
19 changed files with 108 additions and 83 deletions

View File

@@ -45,24 +45,24 @@ export function useMenuActions({
isSmallScreen
}: UseMenuActionsProps) {
const { t } = useTranslation()
const { pubkey, relayList, attemptDelete } = useNostr()
const { pubkey, attemptDelete } = useNostr()
const { relaySets, favoriteRelays } = useFavoriteRelays()
const { mutePubkeyPublicly, mutePubkeyPrivately, unmutePubkey, mutePubkeys } = useMuteList()
const isMuted = useMemo(() => mutePubkeys.includes(event.pubkey), [mutePubkeys, event])
const broadcastSubMenu: SubMenuAction[] = useMemo(() => {
const items = []
if (pubkey) {
if (pubkey && event.pubkey === pubkey) {
items.push({
label: (
<div className="flex items-center gap-2 w-full pl-1">
<Mail />
<div className="flex-1 truncate text-left">{t('Write relays')}</div>
<div className="flex-1 truncate text-left">{t('Suitable Relays')}</div>
</div>
),
onClick: async () => {
closeDrawer()
const relays = relayList?.write.slice(0, 10)
const relays = await client.determineTargetRelays(event)
if (relays?.length) {
await client
.publishEvent(relays, event)

View File

@@ -369,6 +369,7 @@ export default {
Reset: 'إعادة تعيين',
'Share something on this Relay': 'شارك شيئاً على هذا الريلاي',
'Try deleting this note': 'حاول حذف هذه الملاحظة',
'Deletion request sent to {{count}} relays': 'تم إرسال طلب الحذف إلى {{count}} ريلايات'
'Deletion request sent to {{count}} relays': 'تم إرسال طلب الحذف إلى {{count}} ريلايات',
'Suitable Relays': 'الريلايات المناسبة'
}
}

View File

@@ -377,6 +377,7 @@ export default {
Reset: 'Zurücksetzen',
'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'
'Deletion request sent to {{count}} relays': 'Löschanfrage an {{count}} Relays gesendet',
'Suitable Relays': 'Geeignete Relays'
}
}

View File

@@ -368,6 +368,7 @@ export default {
Reset: 'Reset',
'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'
'Deletion request sent to {{count}} relays': 'Deletion request sent to {{count}} relays',
'Suitable Relays': 'Suitable Relays'
}
}

View File

@@ -374,6 +374,7 @@ export default {
'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'
'Solicitud de eliminación enviada a {{count}} relés',
'Suitable Relays': 'Relés adecuados'
}
}

View File

@@ -370,6 +370,7 @@ export default {
Reset: 'بازنشانی',
'Share something on this Relay': 'در این رله چیزی به اشتراک بگذارید',
'Try deleting this note': 'سعی کنید این یادداشت را حذف کنید',
'Deletion request sent to {{count}} relays': 'درخواست حذف به {{count}} رله ارسال شد'
'Deletion request sent to {{count}} relays': 'درخواست حذف به {{count}} رله ارسال شد',
'Suitable Relays': 'رله‌های مناسب'
}
}

View File

@@ -375,6 +375,8 @@ export default {
Reset: 'Réinitialiser',
'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'
'Deletion request sent to {{count}} relays':
'Demande de suppression envoyée à {{count}} relais',
'Suitable Relays': 'Relais adaptés'
}
}

View File

@@ -374,6 +374,7 @@ export default {
'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'
'Richiesta di eliminazione inviata a {{count}} relays',
'Suitable Relays': 'Relays adatti'
}
}

View File

@@ -371,6 +371,7 @@ export default {
'Share something on this Relay': 'このリレーで何かを共有する',
'Try deleting this note': 'このノートを削除してみてください',
'Deletion request sent to {{count}} relays':
'削除リクエストが{{count}}個のリレーに送信されました'
'削除リクエストが{{count}}個のリレーに送信されました',
'Suitable Relays': '適切なリレー'
}
}

View File

@@ -370,6 +370,8 @@ export default {
Reset: '초기화',
'Share something on this Relay': '이 릴레이에서 무언가를 공유하세요',
'Try deleting this note': '이 노트를 삭제해 보세요',
'Deletion request sent to {{count}} relays': '삭제 요청이 {{count}}개의 릴레이로 전송되었습니다'
'Deletion request sent to {{count}} relays':
'삭제 요청이 {{count}}개의 릴레이로 전송되었습니다',
'Suitable Relays': '적합한 릴레이'
}
}

View File

@@ -375,6 +375,7 @@ export default {
'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'
'Żądanie usunięcia wysłane do {{count}} przekaźników',
'Suitable Relays': 'Odpowiednie przekaźniki'
}
}

View File

@@ -371,6 +371,7 @@ export default {
Reset: 'Redefinir',
'Share something on this Relay': 'Compartilhe algo neste Relay',
'Try deleting this note': 'Solicitar exclusão desta nota',
'Deletion request sent to {{count}} relays': 'Pedido de exclusão enviado para {{count}} relays'
'Deletion request sent to {{count}} relays': 'Pedido de exclusão enviado para {{count}} relays',
'Suitable Relays': 'Relays adequados'
}
}

View File

@@ -374,6 +374,7 @@ export default {
'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'
'Pedido de eliminação enviado para {{count}} relays',
'Suitable Relays': 'Relays adequados'
}
}

View File

@@ -374,6 +374,7 @@ export default {
Reset: 'Сбросить',
'Share something on this Relay': 'Поделиться чем-то на этом релее',
'Try deleting this note': 'Попробуйте удалить эту заметку',
'Deletion request sent to {{count}} relays': 'Запрос на удаление отправлен на {{count}} релеев'
'Deletion request sent to {{count}} relays': 'Запрос на удаление отправлен на {{count}} релеев',
'Suitable Relays': 'Подходящие релея'
}
}

View File

@@ -367,6 +367,7 @@ export default {
Reset: 'รีเซ็ต',
'Share something on this Relay': 'แชร์บางอย่างบนรีเลย์นี้',
'Try deleting this note': 'ลองลบโน้ตนี้ดู',
'Deletion request sent to {{count}} relays': 'คำขอลบถูกส่งไปยังรีเลย์ {{count}} รายการ'
'Deletion request sent to {{count}} relays': 'คำขอลบถูกส่งไปยังรีเลย์ {{count}} รายการ',
'Suitable Relays': 'รีเลย์ที่เหมาะสม'
}
}

View File

@@ -365,6 +365,7 @@ export default {
Reset: '重置',
'Share something on this Relay': '在此服务器上分享点什么',
'Try deleting this note': '尝试删除此笔记',
'Deletion request sent to {{count}} relays': '删除请求已发送到 {{count}} 个服务器'
'Deletion request sent to {{count}} relays': '删除请求已发送到 {{count}} 个服务器',
'Suitable Relays': '适合的服务器'
}
}

View File

@@ -9,13 +9,21 @@ import {
} from '@/lib/draft-event'
import { getLatestEvent, getReplaceableEventIdentifier, isProtectedEvent } from '@/lib/event'
import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata'
import { formatPubkey, isValidPubkey, pubkeyToNpub } from '@/lib/pubkey'
import { formatPubkey, pubkeyToNpub } from '@/lib/pubkey'
import client from '@/services/client.service'
import customEmojiService from '@/services/custom-emoji.service'
import indexedDb from '@/services/indexed-db.service'
import storage from '@/services/local-storage.service'
import noteStatsService from '@/services/note-stats.service'
import { ISigner, TAccount, TAccountPointer, TDraftEvent, TProfile, TRelayList } from '@/types'
import {
ISigner,
TAccount,
TAccountPointer,
TDraftEvent,
TProfile,
TPublishOptions,
TRelayList
} from '@/types'
import { hexToBytes } from '@noble/hashes/utils'
import dayjs from 'dayjs'
import { Event, kinds, VerifiedEvent } from 'nostr-tools'
@@ -24,17 +32,12 @@ import * as nip49 from 'nostr-tools/nip49'
import { createContext, useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { useDeletedEvent } from '../DeletedEventProvider'
import { BunkerSigner } from './bunker.signer'
import { Nip07Signer } from './nip-07.signer'
import { NostrConnectionSigner } from './nostrConnection.signer'
import { NpubSigner } from './npub.signer'
import { NsecSigner } from './nsec.signer'
import { useDeletedEvent } from '../DeletedEventProvider'
type TPublishOptions = {
specifiedRelayUrls?: string[]
additionalRelayUrls?: string[]
}
type TNostrContext = {
isInitialized: boolean
@@ -611,7 +614,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
}
}
const relays = await determineTargetRelays(event, options)
const relays = await client.determineTargetRelays(event, options)
await client.publishEvent(relays, event)
return event
@@ -628,7 +631,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
const deletionRequest = await signEvent(createDeletionRequestDraftEvent(targetEvent))
const seenOn = client.getSeenEventRelayUrls(targetEvent.id)
const relays = await determineTargetRelays(targetEvent, {
const relays = await client.determineTargetRelays(targetEvent, {
specifiedRelayUrls: isProtectedEvent(targetEvent) ? seenOn : undefined,
additionalRelayUrls: seenOn
})
@@ -777,55 +780,3 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
</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
}

View File

@@ -6,11 +6,11 @@ import {
isReplaceableEvent
} from '@/lib/event'
import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata'
import { formatPubkey, pubkeyToNpub, userIdToPubkey } from '@/lib/pubkey'
import { formatPubkey, isValidPubkey, pubkeyToNpub, userIdToPubkey } from '@/lib/pubkey'
import { getPubkeysFromPTags, getServersFromServerTags } from '@/lib/tag'
import { isLocalNetworkUrl, isWebsocketUrl, normalizeUrl } from '@/lib/url'
import { isSafari } from '@/lib/utils'
import { ISigner, TProfile, TRelayList, TSubRequestFilter } from '@/types'
import { ISigner, TProfile, TPublishOptions, TRelayList, TSubRequestFilter } from '@/types'
import { sha256 } from '@noble/hashes/sha2'
import DataLoader from 'dataloader'
import dayjs from 'dayjs'
@@ -80,6 +80,58 @@ class ClientService extends EventTarget {
await indexedDb.iterateProfileEvents((profileEvent) => this.addUsernameToIndex(profileEvent))
}
async determineTargetRelays(
event: NEvent,
{ 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 this.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 this.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
}
async publishEvent(relayUrls: string[], event: NEvent) {
try {
const uniqueRelayUrls = Array.from(new Set(relayUrls))

View File

@@ -116,6 +116,11 @@ export type TImetaInfo = {
pubkey?: string
}
export type TPublishOptions = {
specifiedRelayUrls?: string[]
additionalRelayUrls?: string[]
}
export type TNoteListMode = 'posts' | 'postsAndReplies' | 'you'
export type TNotificationType = 'all' | 'mentions' | 'reactions' | 'zaps'