feat: add support for displaying kind 7 and kind 17 reaction events
Reactions now render with a large emoji (matching emoji-only note sizing) and a "reacted to" preview pill linking to the target event, following the same pattern as comment parent previews. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5596e5eb7b
commit
234010c385
26 changed files with 209 additions and 29 deletions
50
src/components/ContentPreview/ReactionPreview.tsx
Normal file
50
src/components/ContentPreview/ReactionPreview.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import Image from '@/components/Image'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Heart } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function ReactionPreview({
|
||||
event,
|
||||
className
|
||||
}: {
|
||||
event: Event
|
||||
className?: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const reaction = useMemo(() => {
|
||||
if (!event.content || event.content === '+') {
|
||||
return <Heart size={14} className="inline text-red-400" />
|
||||
}
|
||||
|
||||
const emojiName = /^:([^:]+):$/.exec(event.content)?.[1]
|
||||
if (emojiName) {
|
||||
const emojiTag = event.tags.find((tag) => tag[0] === 'emoji' && tag[1] === emojiName)
|
||||
const emojiUrl = emojiTag?.[2]
|
||||
if (emojiUrl) {
|
||||
return (
|
||||
<Image
|
||||
image={{ url: emojiUrl, pubkey: event.pubkey }}
|
||||
alt={emojiName}
|
||||
className="inline-block h-4 w-4"
|
||||
classNames={{ errorPlaceholder: 'bg-transparent', wrapper: 'inline-block rounded-md' }}
|
||||
errorPlaceholder={<Heart size={14} className="inline text-red-400" />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
if (event.content.length > 4) {
|
||||
return <Heart size={14} className="inline text-red-400" />
|
||||
}
|
||||
return <span>{event.content}</span>
|
||||
}, [event])
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center gap-1 truncate', className)}>
|
||||
<span className="truncate">[{t('Reaction')}]</span>
|
||||
{reaction}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import LongFormArticlePreview from './LongFormArticlePreview'
|
|||
import NormalContentPreview from './NormalContentPreview'
|
||||
import PictureNotePreview from './PictureNotePreview'
|
||||
import PollPreview from './PollPreview'
|
||||
import ReactionPreview from './ReactionPreview'
|
||||
import VideoNotePreview from './VideoNotePreview'
|
||||
|
||||
export default function ContentPreview({
|
||||
|
|
@ -110,6 +111,10 @@ export default function ContentPreview({
|
|||
return <FollowPackPreview event={event} className={className} />
|
||||
}
|
||||
|
||||
if (event.kind === kinds.Reaction || event.kind === ExtendedKind.EXTERNAL_CONTENT_REACTION) {
|
||||
return <ReactionPreview event={event} className={className} />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
[
|
||||
|
|
|
|||
33
src/components/Note/Reaction.tsx
Normal file
33
src/components/Note/Reaction.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import Emoji from '@/components/Emoji'
|
||||
import { getEmojiInfosFromEmojiTags } from '@/lib/tag'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function Reaction({
|
||||
event,
|
||||
className
|
||||
}: {
|
||||
event: Event
|
||||
className?: string
|
||||
}) {
|
||||
const emoji = useMemo(() => {
|
||||
const content = event.content
|
||||
if (!content || content === '+') return '+'
|
||||
|
||||
const emojiName = /^:([^:]+):$/.exec(content)?.[1]
|
||||
if (emojiName) {
|
||||
const emojiInfos = getEmojiInfosFromEmojiTags(event.tags)
|
||||
const emojiInfo = emojiInfos.find((e) => e.shortcode === emojiName)
|
||||
if (emojiInfo) return emojiInfo
|
||||
}
|
||||
|
||||
if (content.length <= 4) return content
|
||||
return '+'
|
||||
}, [event])
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Emoji emoji={emoji} classNames={{ text: 'text-7xl leading-none', img: 'size-20' }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -2,11 +2,13 @@ import { useSecondaryPage } from '@/PageManager'
|
|||
import { ExtendedKind, NSFW_DISPLAY_POLICY, SUPPORTED_KINDS } from '@/constants'
|
||||
import { getParentStuff, isNsfwEvent } from '@/lib/event'
|
||||
import { toExternalContent, toNote } from '@/lib/link'
|
||||
import { generateBech32IdFromATag, generateBech32IdFromETag, tagNameEquals } from '@/lib/tag'
|
||||
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
||||
import { useMuteList } from '@/providers/MuteListProvider'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { Event, kinds } from 'nostr-tools'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AudioPlayer from '../AudioPlayer'
|
||||
import ClientTag from '../ClientTag'
|
||||
import Content from '../Content'
|
||||
|
|
@ -32,6 +34,7 @@ import MutedNote from './MutedNote'
|
|||
import NsfwNote from './NsfwNote'
|
||||
import PictureNote from './PictureNote'
|
||||
import Poll from './Poll'
|
||||
import Reaction from './Reaction'
|
||||
import RelayReview from './RelayReview'
|
||||
import UnknownNote from './UnknownNote'
|
||||
import VideoNote from './VideoNote'
|
||||
|
|
@ -51,11 +54,21 @@ export default function Note({
|
|||
hideParentNotePreview?: boolean
|
||||
showFull?: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { push } = useSecondaryPage()
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const { parentEventId, parentExternalContent } = useMemo(() => {
|
||||
return getParentStuff(event)
|
||||
}, [event])
|
||||
const reactionTargetEventId = useMemo(() => {
|
||||
if (event.kind !== kinds.Reaction && event.kind !== ExtendedKind.EXTERNAL_CONTENT_REACTION) {
|
||||
return undefined
|
||||
}
|
||||
const aTag = event.tags.findLast(tagNameEquals('a'))
|
||||
if (aTag) return generateBech32IdFromATag(aTag)
|
||||
const eTag = event.tags.findLast(tagNameEquals('e'))
|
||||
return eTag ? generateBech32IdFromETag(eTag) : undefined
|
||||
}, [event])
|
||||
const { nsfwDisplayPolicy } = useContentPolicy()
|
||||
const [showNsfw, setShowNsfw] = useState(false)
|
||||
const { mutePubkeySet } = useMuteList()
|
||||
|
|
@ -117,6 +130,8 @@ export default function Note({
|
|||
content = <EmojiPack className="mt-2" event={event} />
|
||||
} else if (event.kind === ExtendedKind.FOLLOW_PACK) {
|
||||
content = <FollowPack className="mt-2" event={event} />
|
||||
} else if (event.kind === kinds.Reaction) {
|
||||
content = <Reaction className="mt-2" event={event} />
|
||||
} else {
|
||||
content = <Content className="mt-2" event={event} enableHighlight />
|
||||
}
|
||||
|
|
@ -170,6 +185,17 @@ export default function Note({
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
{reactionTargetEventId && (
|
||||
<ParentNotePreview
|
||||
eventId={reactionTargetEventId}
|
||||
label={t('reacted to')}
|
||||
className="mt-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
push(toNote(reactionTargetEventId))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{content}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,15 +9,18 @@ export default function ParentNotePreview({
|
|||
eventId,
|
||||
externalContent,
|
||||
className,
|
||||
onClick
|
||||
onClick,
|
||||
label
|
||||
}: {
|
||||
eventId?: string
|
||||
externalContent?: string
|
||||
className?: string
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement> | undefined
|
||||
label?: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { event, isFetching } = useFetchEvent(eventId)
|
||||
const displayLabel = label ?? t('reply to')
|
||||
|
||||
if (externalContent) {
|
||||
return (
|
||||
|
|
@ -28,7 +31,7 @@ export default function ParentNotePreview({
|
|||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="shrink-0">{t('reply to')}</div>
|
||||
<div className="shrink-0">{displayLabel}</div>
|
||||
<div className="truncate">{externalContent}</div>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -46,7 +49,7 @@ export default function ParentNotePreview({
|
|||
className
|
||||
)}
|
||||
>
|
||||
<div className="shrink-0">{t('reply to')}</div>
|
||||
<div className="shrink-0">{displayLabel}</div>
|
||||
<Skeleton className="h-4 w-4 rounded-full" />
|
||||
<div className="flex-1 py-1">
|
||||
<Skeleton className="h-3" />
|
||||
|
|
@ -64,7 +67,7 @@ export default function ParentNotePreview({
|
|||
)}
|
||||
onClick={event ? onClick : undefined}
|
||||
>
|
||||
<div className="shrink-0">{t('reply to')}</div>
|
||||
<div className="shrink-0">{displayLabel}</div>
|
||||
{event && <UserAvatar className="shrink-0" userId={event.pubkey} size="tiny" />}
|
||||
<ContentPreview className="truncate" event={event} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useSecondaryPage } from '@/PageManager'
|
|||
import { SPECIAL_TRUST_SCORE_FILTER_ID } from '@/constants'
|
||||
import { useStuff } from '@/hooks/useStuff'
|
||||
import { useStuffStatsById } from '@/hooks/useStuffStatsById'
|
||||
import { toProfile } from '@/lib/link'
|
||||
import { toNote } from '@/lib/link'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { useUserTrust } from '@/providers/UserTrustProvider'
|
||||
import { TEmoji } from '@/types'
|
||||
|
|
@ -27,6 +27,7 @@ export default function ReactionList({ stuff }: { stuff: Event | string }) {
|
|||
const [filteredLikes, setFilteredLikes] = useState<
|
||||
Array<{
|
||||
id: string
|
||||
eventId: string
|
||||
pubkey: string
|
||||
emoji: string | TEmoji
|
||||
created_at: number
|
||||
|
|
@ -38,6 +39,7 @@ export default function ReactionList({ stuff }: { stuff: Event | string }) {
|
|||
const likes = noteStats?.likes ?? []
|
||||
const filtered: {
|
||||
id: string
|
||||
eventId: string
|
||||
pubkey: string
|
||||
created_at: number
|
||||
emoji: string | TEmoji
|
||||
|
|
@ -81,7 +83,7 @@ export default function ReactionList({ stuff }: { stuff: Event | string }) {
|
|||
<div
|
||||
key={like.id}
|
||||
className="clickable flex items-center gap-3 border-b px-4 py-3 transition-colors"
|
||||
onClick={() => push(toProfile(like.pubkey))}
|
||||
onClick={() => push(toNote(like.eventId))}
|
||||
>
|
||||
<div className="flex w-6 flex-col items-center">
|
||||
<Emoji
|
||||
|
|
|
|||
|
|
@ -125,7 +125,9 @@ export const SUPPORTED_KINDS = [
|
|||
...ALLOWED_FILTER_KINDS,
|
||||
ExtendedKind.RELAY_REVIEW,
|
||||
kinds.Emojisets,
|
||||
ExtendedKind.FOLLOW_PACK
|
||||
ExtendedKind.FOLLOW_PACK,
|
||||
kinds.Reaction,
|
||||
ExtendedKind.EXTERNAL_CONTENT_REACTION
|
||||
]
|
||||
|
||||
export const URL_REGEX =
|
||||
|
|
|
|||
|
|
@ -677,6 +677,8 @@ export default {
|
|||
'Protected event hint': 'الأحداث المحمية (NIP-70) لا يمكن نشرها إلا من قبل المؤلف. سترفض الخوادم هذه الأحداث من أطراف ثالثة، مما يمنع الآخرين من إعادة بث محتواك. ملاحظة: لا تدعم جميع الخوادم الأحداث المحمية.',
|
||||
'Allow insecure connections': 'السماح بالاتصالات غير الآمنة',
|
||||
'Allow insecure connections description':
|
||||
'السماح بتحميل موارد http:// والاتصال بمرحلات ws://. قد يؤدي إلى تحذيرات المحتوى المختلط في المتصفح.'
|
||||
'السماح بتحميل موارد http:// والاتصال بمرحلات ws://. قد يؤدي إلى تحذيرات المحتوى المختلط في المتصفح.',
|
||||
'reacted to': 'تفاعل مع',
|
||||
Reaction: 'تفاعل'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -701,6 +701,8 @@ export default {
|
|||
'Protected event hint': 'Geschützte Ereignisse (NIP-70) können nur vom Autor veröffentlicht werden. Relays lehnen diese Ereignisse von Dritten ab und verhindern so, dass andere Ihre Inhalte weiterverbreiten. Hinweis: Nicht alle Relays unterstützen geschützte Ereignisse.',
|
||||
'Allow insecure connections': 'Unsichere Verbindungen zulassen',
|
||||
'Allow insecure connections description':
|
||||
'Laden von http://-Ressourcen und Verbindung zu ws://-Relays erlauben. Kann Browser-Warnungen zu gemischten Inhalten auslösen.'
|
||||
'Laden von http://-Ressourcen und Verbindung zu ws://-Relays erlauben. Kann Browser-Warnungen zu gemischten Inhalten auslösen.',
|
||||
'reacted to': 'reagierte auf',
|
||||
Reaction: 'Reaktion'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -684,6 +684,8 @@ export default {
|
|||
'Protected events (NIP-70) can only be published by the author. Relays will reject these events from third parties, preventing others from rebroadcasting your content. Note: not all relays support protected events.',
|
||||
'Allow insecure connections': 'Allow insecure connections',
|
||||
'Allow insecure connections description':
|
||||
'Allow loading http:// resources and connecting to ws:// relays. May trigger browser mixed content warnings.'
|
||||
'Allow loading http:// resources and connecting to ws:// relays. May trigger browser mixed content warnings.',
|
||||
'reacted to': 'reacted to',
|
||||
Reaction: 'Reaction'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -694,6 +694,8 @@ export default {
|
|||
'Protected event hint': 'Los eventos protegidos (NIP-70) solo pueden ser publicados por el autor. Los relés rechazarán estos eventos de terceros, evitando que otros redistribuyan tu contenido. Nota: no todos los relés admiten eventos protegidos.',
|
||||
'Allow insecure connections': 'Permitir conexiones inseguras',
|
||||
'Allow insecure connections description':
|
||||
'Permitir cargar recursos http:// y conectar a relays ws://. Puede activar advertencias de contenido mixto del navegador.'
|
||||
'Permitir cargar recursos http:// y conectar a relays ws://. Puede activar advertencias de contenido mixto del navegador.',
|
||||
'reacted to': 'reaccionó a',
|
||||
Reaction: 'Reacción'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -689,6 +689,8 @@ export default {
|
|||
'Protected event hint': 'رویدادهای محافظتشده (NIP-70) فقط توسط نویسنده قابل انتشار هستند. رلهها این رویدادها را از اشخاص ثالث رد میکنند و از بازنشر محتوای شما توسط دیگران جلوگیری میکنند. توجه: همه رلهها از رویدادهای محافظتشده پشتیبانی نمیکنند.',
|
||||
'Allow insecure connections': 'اجازه اتصالات ناامن',
|
||||
'Allow insecure connections description':
|
||||
'اجازه بارگذاری منابع http:// و اتصال به رلههای ws://. ممکن است هشدارهای محتوای مختلط مرورگر را فعال کند.'
|
||||
'اجازه بارگذاری منابع http:// و اتصال به رلههای ws://. ممکن است هشدارهای محتوای مختلط مرورگر را فعال کند.',
|
||||
'reacted to': 'واکنش نشان داد به',
|
||||
Reaction: 'واکنش'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -698,6 +698,8 @@ export default {
|
|||
'Protected event hint': 'Les événements protégés (NIP-70) ne peuvent être publiés que par l\'auteur. Les relais rejetteront ces événements provenant de tiers, empêchant les autres de rediffuser votre contenu. Remarque : tous les relais ne prennent pas en charge les événements protégés.',
|
||||
'Allow insecure connections': 'Autoriser les connexions non sécurisées',
|
||||
'Allow insecure connections description':
|
||||
'Autoriser le chargement des ressources http:// et la connexion aux relais ws://. Peut déclencher des avertissements de contenu mixte du navigateur.'
|
||||
'Autoriser le chargement des ressources http:// et la connexion aux relais ws://. Peut déclencher des avertissements de contenu mixte du navigateur.',
|
||||
'reacted to': 'a réagi à',
|
||||
Reaction: 'Réaction'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -689,6 +689,8 @@ export default {
|
|||
'Protected event hint': 'संरक्षित इवेंट (NIP-70) केवल लेखक द्वारा प्रकाशित किए जा सकते हैं। रिले तीसरे पक्ष से इन इवेंट को अस्वीकार कर देंगे, जिससे दूसरों को आपकी सामग्री को पुनः प्रसारित करने से रोका जा सके। नोट: सभी रिले संरक्षित इवेंट का समर्थन नहीं करते।',
|
||||
'Allow insecure connections': 'असुरक्षित कनेक्शन की अनुमति दें',
|
||||
'Allow insecure connections description':
|
||||
'http:// संसाधन लोड करने और ws:// रिले से कनेक्ट करने की अनुमति दें। ब्राउज़र मिश्रित सामग्री चेतावनियाँ ट्रिगर हो सकती हैं।'
|
||||
'http:// संसाधन लोड करने और ws:// रिले से कनेक्ट करने की अनुमति दें। ब्राउज़र मिश्रित सामग्री चेतावनियाँ ट्रिगर हो सकती हैं।',
|
||||
'reacted to': 'पर प्रतिक्रिया दी',
|
||||
Reaction: 'प्रतिक्रिया'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -683,6 +683,8 @@ export default {
|
|||
'Protected event hint': 'A védett eseményeket (NIP-70) csak a szerző teheti közzé. A csomópontok elutasítják ezeket az eseményeket harmadik felektől, megakadályozva, hogy mások újraközvetítsék a tartalmadat. Megjegyzés: nem minden csomópont támogatja a védett eseményeket.',
|
||||
'Allow insecure connections': 'Nem biztonságos kapcsolatok engedélyezése',
|
||||
'Allow insecure connections description':
|
||||
'http:// erőforrások betöltésének és ws:// relékhez való csatlakozás engedélyezése. Böngésző vegyes tartalom figyelmeztetéseket válthat ki.'
|
||||
'http:// erőforrások betöltésének és ws:// relékhez való csatlakozás engedélyezése. Böngésző vegyes tartalom figyelmeztetéseket válthat ki.',
|
||||
'reacted to': 'reagált erre',
|
||||
Reaction: 'Reakció'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -694,6 +694,8 @@ export default {
|
|||
'Protected event hint': 'Gli eventi protetti (NIP-70) possono essere pubblicati solo dall\'autore. I relay rifiuteranno questi eventi da terze parti, impedendo ad altri di ridiffondere i tuoi contenuti. Nota: non tutti i relay supportano gli eventi protetti.',
|
||||
'Allow insecure connections': 'Consenti connessioni non sicure',
|
||||
'Allow insecure connections description':
|
||||
'Consenti il caricamento di risorse http:// e la connessione a relay ws://. Potrebbe attivare avvisi di contenuto misto del browser.'
|
||||
'Consenti il caricamento di risorse http:// e la connessione a relay ws://. Potrebbe attivare avvisi di contenuto misto del browser.',
|
||||
'reacted to': 'ha reagito a',
|
||||
Reaction: 'Reazione'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -689,6 +689,8 @@ export default {
|
|||
'Protected event hint': '保護されたイベント(NIP-70)は作成者のみが公開できます。リレーは第三者からのこれらのイベントを拒否し、他者によるコンテンツの再配信を防ぎます。 注意:すべてのリレーが保護されたイベントに対応しているわけではありません。',
|
||||
'Allow insecure connections': '安全でない接続を許可',
|
||||
'Allow insecure connections description':
|
||||
'http:// リソースの読み込みと ws:// リレーへの接続を許可します。ブラウザの混合コンテンツ警告が表示される場合があります。'
|
||||
'http:// リソースの読み込みと ws:// リレーへの接続を許可します。ブラウザの混合コンテンツ警告が表示される場合があります。',
|
||||
'reacted to': 'にリアクションしました',
|
||||
Reaction: 'リアクション'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -683,6 +683,8 @@ export default {
|
|||
'Protected event hint': '보호된 이벤트(NIP-70)는 작성자만 게시할 수 있습니다. 릴레이는 제3자의 이벤트를 거부하여 다른 사람이 콘텐츠를 재배포하는 것을 방지합니다. 참고: 모든 릴레이가 보호된 이벤트를 지원하는 것은 아닙니다.',
|
||||
'Allow insecure connections': '안전하지 않은 연결 허용',
|
||||
'Allow insecure connections description':
|
||||
'http:// 리소스 로드 및 ws:// 릴레이 연결을 허용합니다. 브라우저 혼합 콘텐츠 경고가 발생할 수 있습니다.'
|
||||
'http:// 리소스 로드 및 ws:// 릴레이 연결을 허용합니다. 브라우저 혼합 콘텐츠 경고가 발생할 수 있습니다.',
|
||||
'reacted to': '에 반응했습니다',
|
||||
Reaction: '반응'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -695,6 +695,8 @@ export default {
|
|||
'Protected event hint': 'Chronione zdarzenia (NIP-70) mogą być publikowane tylko przez autora. Transmitery odrzucą publikację tych zdarzeń przez osoby trzecie, uniemożliwiając innym retransmisję Twoich treści. Uwaga: nie wszystkie transmitery obsługują ochronę zdarzeń.',
|
||||
'Allow insecure connections': 'Zezwól na niezabezpieczone połączenia',
|
||||
'Allow insecure connections description':
|
||||
'Zezwól na ładowanie zasobów http:// i łączenie z transmiterami ws://. Może to wywołać ostrzeżenia przeglądarki o mieszanej zawartości.'
|
||||
'Zezwól na ładowanie zasobów http:// i łączenie z transmiterami ws://. Może to wywołać ostrzeżenia przeglądarki o mieszanej zawartości.',
|
||||
'reacted to': 'zareagował na',
|
||||
Reaction: 'Reakcja'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -692,6 +692,8 @@ export default {
|
|||
'Protected event hint': 'Eventos protegidos (NIP-70) só podem ser publicados pelo autor. Os relays rejeitarão esses eventos de terceiros, impedindo que outros retransmitam seu conteúdo. Nota: nem todos os relays suportam eventos protegidos.',
|
||||
'Allow insecure connections': 'Permitir conexões inseguras',
|
||||
'Allow insecure connections description':
|
||||
'Permitir carregar recursos http:// e conectar a relays ws://. Pode acionar avisos de conteúdo misto do navegador.'
|
||||
'Permitir carregar recursos http:// e conectar a relays ws://. Pode acionar avisos de conteúdo misto do navegador.',
|
||||
'reacted to': 'reagiu a',
|
||||
Reaction: 'Reação'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -695,6 +695,8 @@ export default {
|
|||
'Protected event hint': 'Eventos protegidos (NIP-70) só podem ser publicados pelo autor. Os relés rejeitarão estes eventos de terceiros, impedindo que outros retransmitam o seu conteúdo. Nota: nem todos os relés suportam eventos protegidos.',
|
||||
'Allow insecure connections': 'Permitir ligações inseguras',
|
||||
'Allow insecure connections description':
|
||||
'Permitir carregar recursos http:// e ligar a relays ws://. Pode acionar avisos de conteúdo misto do navegador.'
|
||||
'Permitir carregar recursos http:// e ligar a relays ws://. Pode acionar avisos de conteúdo misto do navegador.',
|
||||
'reacted to': 'reagiu a',
|
||||
Reaction: 'Reação'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -694,6 +694,8 @@ export default {
|
|||
'Protected event hint': 'Защищённые события (NIP-70) могут быть опубликованы только автором. Ретрансляторы отклонят эти события от третьих лиц, предотвращая повторную трансляцию вашего контента. Примечание: не все ретрансляторы поддерживают защищённые события.',
|
||||
'Allow insecure connections': 'Разрешить небезопасные соединения',
|
||||
'Allow insecure connections description':
|
||||
'Разрешить загрузку ресурсов http:// и подключение к реле ws://. Может вызвать предупреждения браузера о смешанном содержимом.'
|
||||
'Разрешить загрузку ресурсов http:// и подключение к реле ws://. Может вызвать предупреждения браузера о смешанном содержимом.',
|
||||
'reacted to': 'отреагировал на',
|
||||
Reaction: 'Реакция'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -679,6 +679,8 @@ export default {
|
|||
'Protected event hint': 'เหตุการณ์ที่ได้รับการป้องกัน (NIP-70) สามารถเผยแพร่ได้โดยผู้เขียนเท่านั้น รีเลย์จะปฏิเสธเหตุการณ์เหล่านี้จากบุคคลที่สาม ป้องกันไม่ให้ผู้อื่นเผยแพร่เนื้อหาของคุณซ้ำ หมายเหตุ: รีเลย์บางแห่งไม่รองรับเหตุการณ์ที่ได้รับการป้องกัน',
|
||||
'Allow insecure connections': 'อนุญาตการเชื่อมต่อที่ไม่ปลอดภัย',
|
||||
'Allow insecure connections description':
|
||||
'อนุญาตให้โหลดทรัพยากร http:// และเชื่อมต่อกับรีเลย์ ws:// อาจทำให้เบราว์เซอร์แสดงคำเตือนเนื้อหาแบบผสม'
|
||||
'อนุญาตให้โหลดทรัพยากร http:// และเชื่อมต่อกับรีเลย์ ws:// อาจทำให้เบราว์เซอร์แสดงคำเตือนเนื้อหาแบบผสม',
|
||||
'reacted to': 'รีแอคชันไปที่',
|
||||
Reaction: 'รีแอคชัน'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -661,6 +661,8 @@ export default {
|
|||
'Protected event hint': '受保護的事件(NIP-70)只能由作者發布。伺服器將拒絕來自第三方的這些事件,防止他人轉播你的內容。 注意:並非所有伺服器都支持受保護的事件。',
|
||||
'Allow insecure connections': '允許不安全的連線',
|
||||
'Allow insecure connections description':
|
||||
'允許載入 http:// 資源和連線 ws:// relay。可能會觸發瀏覽器混合內容警告。'
|
||||
'允許載入 http:// 資源和連線 ws:// relay。可能會觸發瀏覽器混合內容警告。',
|
||||
'reacted to': '回應了',
|
||||
Reaction: '回應'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -666,6 +666,8 @@ export default {
|
|||
'Protected event hint': '受保护的事件(NIP-70)只能由作者发布。服务器将拒绝来自第三方的这些事件,防止他人转播你的内容。 注意:并非所有服务器都支持受保护的事件。',
|
||||
'Allow insecure connections': '允许不安全的连接',
|
||||
'Allow insecure connections description':
|
||||
'允许加载 http:// 资源和连接 ws:// relay。可能会触发浏览器混合内容警告。'
|
||||
'允许加载 http:// 资源和连接 ws:// relay。可能会触发浏览器混合内容警告。',
|
||||
'reacted to': '回应了',
|
||||
Reaction: '回应'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { ExtendedKind } from '@/constants'
|
||||
import { getEventKey, getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event'
|
||||
import {
|
||||
getEventKey,
|
||||
getNoteBech32Id,
|
||||
getReplaceableCoordinateFromEvent,
|
||||
isReplaceableEvent
|
||||
} from '@/lib/event'
|
||||
import { getZapInfoFromEvent } from '@/lib/event-metadata'
|
||||
import { getDefaultRelayUrls } from '@/lib/relay'
|
||||
import { getEmojiInfosFromEmojiTags, tagNameEquals } from '@/lib/tag'
|
||||
|
|
@ -10,7 +15,13 @@ import { Event, Filter, kinds } from 'nostr-tools'
|
|||
|
||||
export type TStuffStats = {
|
||||
likeIdSet: Set<string>
|
||||
likes: { id: string; pubkey: string; created_at: number; emoji: TEmoji | string }[]
|
||||
likes: {
|
||||
id: string
|
||||
eventId: string
|
||||
pubkey: string
|
||||
created_at: number
|
||||
emoji: TEmoji | string
|
||||
}[]
|
||||
repostPubkeySet: Set<string>
|
||||
reposts: { id: string; pubkey: string; created_at: number }[]
|
||||
zapPrSet: Set<string>
|
||||
|
|
@ -269,7 +280,13 @@ class StuffStatsService {
|
|||
}
|
||||
|
||||
likeIdSet.add(evt.id)
|
||||
likes.push({ id: evt.id, pubkey: evt.pubkey, created_at: evt.created_at, emoji })
|
||||
likes.push({
|
||||
id: evt.id,
|
||||
eventId: getNoteBech32Id(evt),
|
||||
pubkey: evt.pubkey,
|
||||
created_at: evt.created_at,
|
||||
emoji
|
||||
})
|
||||
this.stuffStatsMap.set(targetEventKey, { ...old, likeIdSet, likes })
|
||||
return targetEventKey
|
||||
}
|
||||
|
|
@ -298,7 +315,13 @@ class StuffStatsService {
|
|||
}
|
||||
|
||||
likeIdSet.add(evt.id)
|
||||
likes.push({ id: evt.id, pubkey: evt.pubkey, created_at: evt.created_at, emoji })
|
||||
likes.push({
|
||||
id: evt.id,
|
||||
eventId: getNoteBech32Id(evt),
|
||||
pubkey: evt.pubkey,
|
||||
created_at: evt.created_at,
|
||||
emoji
|
||||
})
|
||||
this.stuffStatsMap.set(target, { ...old, likeIdSet, likes })
|
||||
return target
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue