feat: support for follow packs
This commit is contained in:
22
src/components/ContentPreview/FollowPackPreview.tsx
Normal file
22
src/components/ContentPreview/FollowPackPreview.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { getFollowPackInfoFromEvent } from '@/lib/event-metadata'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function FollowPackPreview({
|
||||
event,
|
||||
className
|
||||
}: {
|
||||
event: Event
|
||||
className?: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { title } = useMemo(() => getFollowPackInfoFromEvent(event), [event])
|
||||
|
||||
return (
|
||||
<div className={cn('truncate', className)}>
|
||||
[{t('Follow Pack')}] <span className="italic pr-0.5">{title}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CommunityDefinitionPreview from './CommunityDefinitionPreview'
|
||||
import EmojiPackPreview from './EmojiPackPreview'
|
||||
import FollowPackPreview from './FollowPackPreview'
|
||||
import GroupMetadataPreview from './GroupMetadataPreview'
|
||||
import HighlightPreview from './HighlightPreview'
|
||||
import LiveEventPreview from './LiveEventPreview'
|
||||
@@ -105,5 +106,9 @@ export default function ContentPreview({
|
||||
return <EmojiPackPreview event={event} className={className} />
|
||||
}
|
||||
|
||||
if (event.kind === ExtendedKind.FOLLOW_PACK) {
|
||||
return <FollowPackPreview event={event} className={className} />
|
||||
}
|
||||
|
||||
return <div className={className}>[{t('Cannot handle event of kind k', { k: event.kind })}]</div>
|
||||
}
|
||||
|
||||
55
src/components/Note/FollowPack.tsx
Normal file
55
src/components/Note/FollowPack.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { getFollowPackInfoFromEvent } from '@/lib/event-metadata'
|
||||
import { toFollowPack } from '@/lib/link'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Image from '../Image'
|
||||
|
||||
export default function FollowPack({ event, className }: { event: Event; className?: string }) {
|
||||
const { t } = useTranslation()
|
||||
const { push } = useSecondaryPage()
|
||||
const { title, description, image, pubkeys } = useMemo(
|
||||
() => getFollowPackInfoFromEvent(event),
|
||||
[event]
|
||||
)
|
||||
|
||||
const handleViewDetails = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
push(toFollowPack(event))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex items-start gap-2 mb-2">
|
||||
{image && (
|
||||
<Image
|
||||
image={{ url: image, pubkey: event.pubkey }}
|
||||
className="w-24 h-20 object-cover rounded-lg"
|
||||
classNames={{
|
||||
wrapper: 'w-24 h-20 flex-shrink-0',
|
||||
errorPlaceholder: 'w-24 h-20'
|
||||
}}
|
||||
hideIfError
|
||||
/>
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-xl font-semibold mb-1 truncate">{title}</h3>
|
||||
<span className="text-xs text-muted-foreground shrink-0">
|
||||
{t('n users', { count: pubkeys.length })}
|
||||
</span>
|
||||
</div>
|
||||
{description && (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button onClick={handleViewDetails} variant="outline" className="w-full">
|
||||
{t('View Details')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
import CommunityDefinition from './CommunityDefinition'
|
||||
import EmojiPack from './EmojiPack'
|
||||
import FollowPack from './FollowPack'
|
||||
import GroupMetadata from './GroupMetadata'
|
||||
import Highlight from './Highlight'
|
||||
import LiveEvent from './LiveEvent'
|
||||
@@ -109,6 +110,8 @@ export default function Note({
|
||||
content = <RelayReview className="mt-2" event={event} />
|
||||
} else if (event.kind === kinds.Emojisets) {
|
||||
content = <EmojiPack className="mt-2" event={event} />
|
||||
} else if (event.kind === ExtendedKind.FOLLOW_PACK) {
|
||||
content = <FollowPack className="mt-2" event={event} />
|
||||
} else {
|
||||
content = <Content className="mt-2" event={event} />
|
||||
}
|
||||
|
||||
@@ -81,13 +81,14 @@ export const ExtendedKind = {
|
||||
VOICE_COMMENT: 1244,
|
||||
FAVORITE_RELAYS: 10012,
|
||||
BLOSSOM_SERVER_LIST: 10063,
|
||||
FOLLOW_PACK: 39089,
|
||||
RELAY_REVIEW: 31987,
|
||||
GROUP_METADATA: 39000,
|
||||
ADDRESSABLE_NORMAL_VIDEO: 34235,
|
||||
ADDRESSABLE_SHORT_VIDEO: 34236
|
||||
}
|
||||
|
||||
export const SUPPORTED_KINDS = [
|
||||
export const ALLOWED_FILTER_KINDS = [
|
||||
kinds.ShortTextNote,
|
||||
kinds.Repost,
|
||||
kinds.GenericRepost,
|
||||
@@ -100,12 +101,17 @@ export const SUPPORTED_KINDS = [
|
||||
ExtendedKind.VOICE_COMMENT,
|
||||
kinds.Highlights,
|
||||
kinds.LongFormArticle,
|
||||
ExtendedKind.RELAY_REVIEW,
|
||||
kinds.Emojisets,
|
||||
ExtendedKind.ADDRESSABLE_NORMAL_VIDEO,
|
||||
ExtendedKind.ADDRESSABLE_SHORT_VIDEO
|
||||
]
|
||||
|
||||
export const SUPPORTED_KINDS = [
|
||||
...ALLOWED_FILTER_KINDS,
|
||||
ExtendedKind.RELAY_REVIEW,
|
||||
kinds.Emojisets,
|
||||
ExtendedKind.FOLLOW_PACK
|
||||
]
|
||||
|
||||
export const URL_REGEX =
|
||||
/https?:\/\/[\w\p{L}\p{N}\p{M}&.\-/?=#@%+_:!~*]+[^\s.,;:'")\]}!?,。;:"'!?】)]/giu
|
||||
export const WS_URL_REGEX =
|
||||
|
||||
@@ -552,6 +552,13 @@ export default {
|
||||
Highlight: 'تسليط الضوء',
|
||||
'Optimal relays and {{count}} other relays': 'المرحلات المثلى و {{count}} مرحلات أخرى',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'حساب مشبوه للغاية (درجة الثقة: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'حساب مشبوه (درجة الثقة: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'حساب مشبوه (درجة الثقة: {{percentile}}%)',
|
||||
'n users': '{{count}} مستخدمين',
|
||||
'View Details': 'عرض التفاصيل',
|
||||
'Follow Pack Not Found': 'لم يتم العثور على حزمة المتابعة',
|
||||
'Follow pack not found': 'لم يتم العثور على حزمة المتابعة',
|
||||
Users: 'المستخدمون',
|
||||
Feed: 'التغذية',
|
||||
'Follow Pack': 'حزمة المتابعة'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,6 +568,13 @@ export default {
|
||||
Highlight: 'Hervorheben',
|
||||
'Optimal relays and {{count}} other relays': 'Optimale Relays und {{count}} andere Relays',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'Wahrscheinlich Spam-Konto (Vertrauenswert: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Verdächtiges Konto (Vertrauenswert: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Verdächtiges Konto (Vertrauenswert: {{percentile}}%)',
|
||||
'n users': '{{count}} Benutzer',
|
||||
'View Details': 'Details anzeigen',
|
||||
'Follow Pack Not Found': 'Follow-Pack nicht gefunden',
|
||||
'Follow pack not found': 'Follow-Pack nicht gefunden',
|
||||
Users: 'Benutzer',
|
||||
Feed: 'Feed',
|
||||
'Follow Pack': 'Follow-Pack'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,6 +555,15 @@ export default {
|
||||
'Likely spam account (Trust score: {{percentile}}%)':
|
||||
'Likely spam account (Trust score: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)':
|
||||
'Suspicious account (Trust score: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)',
|
||||
'n users': '{{count}} users',
|
||||
'n users_one': '{{count}} user',
|
||||
'n users_other': '{{count}} users',
|
||||
'View Details': 'View Details',
|
||||
'Follow Pack Not Found': 'Follow Pack Not Found',
|
||||
'Follow pack not found': 'Follow pack not found',
|
||||
Users: 'Users',
|
||||
Feed: 'Feed',
|
||||
'Follow Pack': 'Follow Pack'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,6 +563,13 @@ export default {
|
||||
Highlight: 'Destacado',
|
||||
'Optimal relays and {{count}} other relays': 'Relays óptimos y {{count}} otros relays',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'Probablemente cuenta spam (Puntuación de confianza: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Cuenta sospechosa (Puntuación de confianza: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Cuenta sospechosa (Puntuación de confianza: {{percentile}}%)',
|
||||
'n users': '{{count}} usuarios',
|
||||
'View Details': 'Ver detalles',
|
||||
'Follow Pack Not Found': 'Paquete de seguimiento no encontrado',
|
||||
'Follow pack not found': 'Paquete de seguimiento no encontrado',
|
||||
Users: 'Usuarios',
|
||||
Feed: 'Feed',
|
||||
'Follow Pack': 'Paquete de Seguimiento'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,6 +557,13 @@ export default {
|
||||
Highlight: 'برجستهسازی',
|
||||
'Optimal relays and {{count}} other relays': 'رلههای بهینه و {{count}} رله دیگر',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'احتمالاً حساب هرزنامه (امتیاز اعتماد: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'حساب مشکوک (امتیاز اعتماد: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'حساب مشکوک (امتیاز اعتماد: {{percentile}}%)',
|
||||
'n users': '{{count}} کاربر',
|
||||
'View Details': 'مشاهده جزئیات',
|
||||
'Follow Pack Not Found': 'بسته دنبالکننده یافت نشد',
|
||||
'Follow pack not found': 'بسته دنبالکننده یافت نشد',
|
||||
Users: 'کاربران',
|
||||
Feed: 'فید',
|
||||
'Follow Pack': 'بسته دنبالکننده'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,6 +566,13 @@ export default {
|
||||
Highlight: 'Surligner',
|
||||
'Optimal relays and {{count}} other relays': 'Relais optimaux et {{count}} autres relais',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'Compte probablement spam (Score de confiance: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Compte suspect (Score de confiance: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Compte suspect (Score de confiance: {{percentile}}%)',
|
||||
'n users': '{{count}} utilisateurs',
|
||||
'View Details': 'Voir les détails',
|
||||
'Follow Pack Not Found': 'Pack de suivi introuvable',
|
||||
'Follow pack not found': 'Pack de suivi introuvable',
|
||||
Users: 'Utilisateurs',
|
||||
Feed: 'Flux',
|
||||
'Follow Pack': 'Pack de Suivi'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,6 +558,13 @@ export default {
|
||||
Highlight: 'हाइलाइट',
|
||||
'Optimal relays and {{count}} other relays': 'इष्टतम रिले और {{count}} अन्य रिले',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'संभावित स्पैम खाता (विश्वास स्कोर: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'संदिग्ध खाता (विश्वास स्कोर: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'संदिग्ध खाता (विश्वास स्कोर: {{percentile}}%)',
|
||||
'n users': '{{count}} उपयोगकर्ता',
|
||||
'View Details': 'विवरण देखें',
|
||||
'Follow Pack Not Found': 'फॉलो पैक नहीं मिला',
|
||||
'Follow pack not found': 'फॉलो पैक नहीं मिला',
|
||||
Users: 'उपयोगकर्ता',
|
||||
Feed: 'फ़ीड',
|
||||
'Follow Pack': 'फॉलो पैक'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,6 +553,13 @@ export default {
|
||||
Highlight: 'Kiemelés',
|
||||
'Optimal relays and {{count}} other relays': 'Optimális relay-ek és {{count}} másik relay',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'Valószínűleg spam fiók (Megbízhatósági pontszám: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Gyanús fiók (Megbízhatósági pontszám: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Gyanús fiók (Megbízhatósági pontszám: {{percentile}}%)',
|
||||
'n users': '{{count}} felhasználó',
|
||||
'View Details': 'Részletek megtekintése',
|
||||
'Follow Pack Not Found': 'Követési csomag nem található',
|
||||
'Follow pack not found': 'Követési csomag nem található',
|
||||
Users: 'Felhasználók',
|
||||
Feed: 'Hírfolyam',
|
||||
'Follow Pack': 'Követési Csomag'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,6 +562,13 @@ export default {
|
||||
Highlight: 'Evidenzia',
|
||||
'Optimal relays and {{count}} other relays': 'Relay ottimali e {{count}} altri relay',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'Probabile account spam (Punteggio di fiducia: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Account sospetto (Punteggio di fiducia: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Account sospetto (Punteggio di fiducia: {{percentile}}%)',
|
||||
'n users': '{{count}} utenti',
|
||||
'View Details': 'Visualizza dettagli',
|
||||
'Follow Pack Not Found': 'Pacchetto di follow non trovato',
|
||||
'Follow pack not found': 'Pacchetto di follow non trovato',
|
||||
Users: 'Utenti',
|
||||
Feed: 'Feed',
|
||||
'Follow Pack': 'Pacchetto di Follow'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,6 +557,13 @@ export default {
|
||||
Highlight: 'ハイライト',
|
||||
'Optimal relays and {{count}} other relays': '最適なリレーと他の{{count}}個のリレー',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'スパムの可能性が高いアカウント(信頼スコア:{{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': '疑わしいアカウント(信頼スコア:{{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': '疑わしいアカウント(信頼スコア:{{percentile}}%)',
|
||||
'n users': '{{count}}人のユーザー',
|
||||
'View Details': '詳細を表示',
|
||||
'Follow Pack Not Found': 'フォローパックが見つかりません',
|
||||
'Follow pack not found': 'フォローパックが見つかりません',
|
||||
Users: 'ユーザー',
|
||||
Feed: 'フィード',
|
||||
'Follow Pack': 'フォローパック'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,6 +557,13 @@ export default {
|
||||
Highlight: '하이라이트',
|
||||
'Optimal relays and {{count}} other relays': '최적 릴레이 및 기타 {{count}}개 릴레이',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': '스팸 계정 가능성 높음 (신뢰 점수: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': '의심스러운 계정 (신뢰 점수: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': '의심스러운 계정 (신뢰 점수: {{percentile}}%)',
|
||||
'n users': '{{count}}명의 사용자',
|
||||
'View Details': '세부 정보 보기',
|
||||
'Follow Pack Not Found': '팔로우 팩을 찾을 수 없음',
|
||||
'Follow pack not found': '팔로우 팩을 찾을 수 없습니다',
|
||||
Users: '사용자',
|
||||
Feed: '피드',
|
||||
'Follow Pack': '팔로우 팩'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,6 +563,13 @@ export default {
|
||||
Highlight: 'Podświetl',
|
||||
'Optimal relays and {{count}} other relays': 'Optymalne przekaźniki i {{count}} innych przekaźników',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'Prawdopodobnie konto spamowe (Wynik zaufania: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Podejrzane konto (Wynik zaufania: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Podejrzane konto (Wynik zaufania: {{percentile}}%)',
|
||||
'n users': '{{count}} użytkowników',
|
||||
'View Details': 'Zobacz szczegóły',
|
||||
'Follow Pack Not Found': 'Nie znaleziono pakietu obserwowanych',
|
||||
'Follow pack not found': 'Nie znaleziono pakietu obserwowanych',
|
||||
Users: 'Użytkownicy',
|
||||
Feed: 'Kanał',
|
||||
'Follow Pack': 'Pakiet Obserwowanych'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,6 +558,13 @@ export default {
|
||||
Highlight: 'Marcação',
|
||||
'Optimal relays and {{count}} other relays': 'Relays ideais e {{count}} outros relays',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'Provável conta de spam (Pontuação de confiança: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Conta suspeita (Pontuação de confiança: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Conta suspeita (Pontuação de confiança: {{percentile}}%)',
|
||||
'n users': '{{count}} usuários',
|
||||
'View Details': 'Ver detalhes',
|
||||
'Follow Pack Not Found': 'Pacote de seguir não encontrado',
|
||||
'Follow pack not found': 'Pacote de seguir não encontrado',
|
||||
Users: 'Usuários',
|
||||
Feed: 'Feed',
|
||||
'Follow Pack': 'Pacote de Seguir'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,6 +561,13 @@ export default {
|
||||
Highlight: 'Destacar',
|
||||
'Optimal relays and {{count}} other relays': 'Relays ideais e {{count}} outros relays',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'Provável conta de spam (Pontuação de confiança: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Conta suspeita (Pontuação de confiança: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Conta suspeita (Pontuação de confiança: {{percentile}}%)',
|
||||
'n users': '{{count}} utilizadores',
|
||||
'View Details': 'Ver detalhes',
|
||||
'Follow Pack Not Found': 'Pacote de seguir não encontrado',
|
||||
'Follow pack not found': 'Pacote de seguir não encontrado',
|
||||
Users: 'Utilizadores',
|
||||
Feed: 'Feed',
|
||||
'Follow Pack': 'Pacote de Seguir'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,6 +563,13 @@ export default {
|
||||
Highlight: 'Выделить',
|
||||
'Optimal relays and {{count}} other relays': 'Оптимальные релеи и {{count}} других релеев',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'Вероятно спам-аккаунт (Оценка доверия: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Подозрительный аккаунт (Оценка доверия: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'Подозрительный аккаунт (Оценка доверия: {{percentile}}%)',
|
||||
'n users': '{{count}} пользователей',
|
||||
'View Details': 'Посмотреть детали',
|
||||
'Follow Pack Not Found': 'Пакет подписок не найден',
|
||||
'Follow pack not found': 'Пакет подписок не найден',
|
||||
Users: 'Пользователи',
|
||||
Feed: 'Лента',
|
||||
'Follow Pack': 'Пакет Подписок'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,6 +550,13 @@ export default {
|
||||
Highlight: 'ไฮไลต์',
|
||||
'Optimal relays and {{count}} other relays': 'รีเลย์ที่เหมาะสมและรีเลย์อื่น {{count}} รายการ',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': 'น่าจะเป็นบัญชีสแปม (คะแนนความน่าเชื่อถือ: {{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'บัญชีที่น่าสงสัย (คะแนนความน่าเชื่อถือ: {{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': 'บัญชีที่น่าสงสัย (คะแนนความน่าเชื่อถือ: {{percentile}}%)',
|
||||
'n users': '{{count}} ผู้ใช้',
|
||||
'View Details': 'ดูรายละเอียด',
|
||||
'Follow Pack Not Found': 'ไม่พบแพ็คการติดตาม',
|
||||
'Follow pack not found': 'ไม่พบแพ็คการติดตาม',
|
||||
Users: 'ผู้ใช้',
|
||||
Feed: 'ฟีด',
|
||||
'Follow Pack': 'แพ็คการติดตาม'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,6 +545,13 @@ export default {
|
||||
Highlight: '高亮',
|
||||
'Optimal relays and {{count}} other relays': '最佳中继器和其他 {{count}} 个中继器',
|
||||
'Likely spam account (Trust score: {{percentile}}%)': '疑似垃圾账号(信任分数:{{percentile}}%)',
|
||||
'Suspicious account (Trust score: {{percentile}}%)': '可疑账号(信任分数:{{percentile}}%)'
|
||||
'Suspicious account (Trust score: {{percentile}}%)': '可疑账号(信任分数:{{percentile}}%)',
|
||||
'n users': '{{count}} 位用户',
|
||||
'View Details': '查看详情',
|
||||
'Follow Pack Not Found': '未找到关注包',
|
||||
'Follow pack not found': '未找到关注包',
|
||||
Users: '用户',
|
||||
Feed: '动态',
|
||||
'Follow Pack': '关注包'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Event, kinds } from 'nostr-tools'
|
||||
import { buildATag } from './draft-event'
|
||||
import { getReplaceableEventIdentifier } from './event'
|
||||
import { getAmountFromInvoice, getLightningAddressFromProfile } from './lightning'
|
||||
import { formatPubkey, pubkeyToNpub } from './pubkey'
|
||||
import { formatPubkey, isValidPubkey, pubkeyToNpub } from './pubkey'
|
||||
import { generateBech32IdFromETag, getEmojiInfosFromEmojiTags, tagNameEquals } from './tag'
|
||||
import { isOnionUrl, isWebsocketUrl, normalizeHttpUrl, normalizeUrl } from './url'
|
||||
|
||||
@@ -403,3 +403,28 @@ export function getPinnedEventHexIdSetFromPinListEvent(event?: Event | null): Se
|
||||
.slice(0, MAX_PINNED_NOTES) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
export function getFollowPackInfoFromEvent(event: Event) {
|
||||
let title: string | undefined
|
||||
let description: string | undefined
|
||||
let image: string | undefined
|
||||
const pubkeys: string[] = []
|
||||
|
||||
event.tags.forEach(([tagName, tagValue]) => {
|
||||
if (tagName === 'title') {
|
||||
title = tagValue
|
||||
} else if (tagName === 'description') {
|
||||
description = tagValue
|
||||
} else if (tagName === 'image') {
|
||||
image = tagValue
|
||||
} else if (tagName === 'p' && isValidPubkey(tagValue)) {
|
||||
pubkeys.push(tagValue)
|
||||
}
|
||||
})
|
||||
|
||||
if (!title) {
|
||||
title = event.tags.find(tagNameEquals('d'))?.[1] ?? 'Untitled Follow Pack'
|
||||
}
|
||||
|
||||
return { title, description, image, pubkeys }
|
||||
}
|
||||
|
||||
@@ -77,6 +77,11 @@ export const toRelayReviews = (url: string) => `/relays/${encodeURIComponent(url
|
||||
export const toMuteList = () => '/mutes'
|
||||
export const toRizful = () => '/rizful'
|
||||
export const toBookmarks = () => '/bookmarks'
|
||||
export const toFollowPack = (eventOrId: Event | string) => {
|
||||
if (typeof eventOrId === 'string') return `/follow-packs/${eventOrId}`
|
||||
const naddr = getNoteBech32Id(eventOrId)
|
||||
return `/follow-packs/${naddr}`
|
||||
}
|
||||
|
||||
export const toChachiChat = (relay: string, d: string) => {
|
||||
return `https://chachi.chat/${relay.replace(/^wss?:\/\//, '').replace(/\/$/, '')}/${d}`
|
||||
|
||||
116
src/pages/secondary/FollowPackPage/index.tsx
Normal file
116
src/pages/secondary/FollowPackPage/index.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import ImageWithLightbox from '@/components/ImageWithLightbox'
|
||||
import NormalFeed from '@/components/NormalFeed'
|
||||
import ProfileList from '@/components/ProfileList'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { useFetchEvent } from '@/hooks/useFetchEvent'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { getFollowPackInfoFromEvent } from '@/lib/event-metadata'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import client from '@/services/client.service'
|
||||
import { TFeedSubRequest } from '@/types'
|
||||
import { forwardRef, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const FollowPackPage = forwardRef(({ id, index }: { id?: string; index?: number }, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const [tab, setTab] = useState<'users' | 'feed'>('users')
|
||||
|
||||
const { event, isFetching } = useFetchEvent(id)
|
||||
|
||||
const { title, description, image, pubkeys } = useMemo(() => {
|
||||
if (!event) return { title: '', description: '', image: '', pubkeys: [] }
|
||||
return getFollowPackInfoFromEvent(event)
|
||||
}, [event])
|
||||
|
||||
if (isFetching) {
|
||||
return (
|
||||
<SecondaryPageLayout ref={ref} index={index} title={t('Follow Pack')}>
|
||||
<div className="px-4 py-3 space-y-2">
|
||||
<Skeleton className="h-48 w-full" />
|
||||
<Skeleton className="h-7 py-1 w-full" />
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
return (
|
||||
<SecondaryPageLayout ref={ref} index={index} title={t('Follow Pack')}>
|
||||
<div className="p-4 text-center text-muted-foreground">{t('Follow pack not found')}</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SecondaryPageLayout ref={ref} index={index} title={t('Follow Pack')} displayScrollToTopButton>
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="px-4 pt-3 space-y-2">
|
||||
{image && (
|
||||
<ImageWithLightbox
|
||||
image={{ url: image, pubkey: event.pubkey }}
|
||||
className="w-full h-48 object-cover rounded-lg"
|
||||
classNames={{
|
||||
wrapper: 'w-full h-48 border-none'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-2xl font-semibold mb-1 truncate">{title}</h3>
|
||||
<span className="text-xs text-muted-foreground shrink-0">
|
||||
{t('n users', { count: pubkeys.length })}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{description && (
|
||||
<p className="text-sm text-muted-foreground whitespace-pre-wrap">{description}</p>
|
||||
)}
|
||||
|
||||
<div className="inline-flex items-center rounded-lg border bg-muted/50">
|
||||
<button
|
||||
onClick={() => setTab('users')}
|
||||
className={cn(
|
||||
'px-3 py-1.5 text-sm font-medium rounded-l-lg transition-colors',
|
||||
tab === 'users'
|
||||
? 'bg-background text-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
{t('Users')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTab('feed')}
|
||||
className={cn(
|
||||
'px-3 py-1.5 text-sm font-medium rounded-r-lg transition-colors',
|
||||
tab === 'feed'
|
||||
? 'bg-background text-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
{t('Feed')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
{tab === 'users' && <ProfileList pubkeys={pubkeys} />}
|
||||
{tab === 'feed' && pubkeys.length > 0 && <Feed pubkeys={pubkeys} />}
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
})
|
||||
FollowPackPage.displayName = 'FollowPackPage'
|
||||
export default FollowPackPage
|
||||
|
||||
function Feed({ pubkeys }: { pubkeys: string[] }) {
|
||||
const { pubkey: myPubkey } = useNostr()
|
||||
const [subRequests, setSubRequests] = useState<TFeedSubRequest[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
client.generateSubRequestsForPubkeys(pubkeys, myPubkey).then(setSubRequests)
|
||||
}, [pubkeys, myPubkey])
|
||||
|
||||
return <NormalFeed subRequests={subRequests} />
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import BookmarkPage from '@/pages/secondary/BookmarkPage'
|
||||
import EmojiPackSettingsPage from '@/pages/secondary/EmojiPackSettingsPage'
|
||||
import ExternalContentPage from '@/pages/secondary/ExternalContentPage'
|
||||
import FollowingListPage from '@/pages/secondary/FollowingListPage'
|
||||
import FollowPackPage from '@/pages/secondary/FollowPackPage'
|
||||
import GeneralSettingsPage from '@/pages/secondary/GeneralSettingsPage'
|
||||
import MuteListPage from '@/pages/secondary/MuteListPage'
|
||||
import NoteListPage from '@/pages/secondary/NoteListPage'
|
||||
@@ -48,7 +49,8 @@ const SECONDARY_ROUTE_CONFIGS = [
|
||||
{ path: '/profile-editor', element: <ProfileEditorPage /> },
|
||||
{ path: '/mutes', element: <MuteListPage /> },
|
||||
{ path: '/rizful', element: <RizfulPage /> },
|
||||
{ path: '/bookmarks', element: <BookmarkPage /> }
|
||||
{ path: '/bookmarks', element: <BookmarkPage /> },
|
||||
{ path: '/follow-packs/:id', element: <FollowPackPage /> }
|
||||
]
|
||||
|
||||
export const SECONDARY_ROUTES = SECONDARY_ROUTE_CONFIGS.map(({ path, element }) => ({
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
ALLOWED_FILTER_KINDS,
|
||||
DEFAULT_FAVICON_URL_TEMPLATE,
|
||||
DEFAULT_NIP_96_SERVICE,
|
||||
ExtendedKind,
|
||||
MEDIA_AUTO_LOAD_POLICY,
|
||||
NOTIFICATION_LIST_STYLE,
|
||||
SUPPORTED_KINDS,
|
||||
StorageKey,
|
||||
TPrimaryColor
|
||||
} from '@/constants'
|
||||
@@ -165,7 +165,7 @@ class LocalStorageService {
|
||||
|
||||
const showKindsStr = window.localStorage.getItem(StorageKey.SHOW_KINDS)
|
||||
if (!showKindsStr) {
|
||||
this.showKinds = SUPPORTED_KINDS
|
||||
this.showKinds = ALLOWED_FILTER_KINDS
|
||||
} else {
|
||||
const showKindsVersionStr = window.localStorage.getItem(StorageKey.SHOW_KINDS_VERSION)
|
||||
const showKindsVersion = showKindsVersionStr ? parseInt(showKindsVersionStr) : 0
|
||||
|
||||
Reference in New Issue
Block a user