Browse Source

more refactoring

imwald
Silberengel 1 month ago
parent
commit
9e549521bf
  1. 4
      src/components/NoteBoostBadges/index.tsx
  2. 9
      src/components/NoteInteractions/Tabs.tsx
  3. 14
      src/components/NoteInteractions/index.tsx
  4. 49
      src/components/NoteStats/Likes.tsx
  5. 98
      src/components/ReactionList/index.tsx
  6. 56
      src/components/ReplyNoteList/ZapReplyFeedRow.tsx
  7. 15
      src/components/ReplyNoteList/index.tsx
  8. 81
      src/components/RepostList/index.tsx
  9. 91
      src/components/ZapList/index.tsx
  10. 1
      src/i18n/locales/de.ts
  11. 1
      src/i18n/locales/en.ts
  12. 1
      src/pages/secondary/NotePage/index.tsx

4
src/components/NoteBoostBadges/index.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { ExtendedKind } from '@/constants'
import { useNoteStatsById } from '@/hooks/useNoteStatsById'
import { shouldHideInteractions } from '@/lib/event-filtering'
import { cn } from '@/lib/utils'
import { ExtendedKind } from '@/constants'
import { useUserTrust } from '@/providers/UserTrustProvider'
import { Event } from 'nostr-tools'
import { useMemo } from 'react'
@ -51,7 +51,7 @@ export default function NoteBoostBadges({ event, className }: { event: Event; cl @@ -51,7 +51,7 @@ export default function NoteBoostBadges({ event, className }: { event: Event; cl
{overflow > 0 ? (
<span
className="-ml-1 rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground ring-2 ring-background"
title={t('No more boosts')}
title={t('n more boosts', { count: overflow })}
>
+{overflow}
</span>

9
src/components/NoteInteractions/Tabs.tsx

@ -2,11 +2,9 @@ import { cn } from '@/lib/utils' @@ -2,11 +2,9 @@ import { cn } from '@/lib/utils'
import { useTranslation } from 'react-i18next'
import { useRef, useEffect, useState } from 'react'
export type TTabValue = 'replies' | 'quotes' | 'reactions' | 'zaps'
export type TTabValue = 'replies' | 'quotes'
const TABS = [
{ value: 'replies', label: 'Replies' },
{ value: 'zaps', label: 'Zaps' },
{ value: 'reactions', label: 'Reactions' },
{ value: 'quotes', label: 'Quotes' }
] as { value: TTabValue; label: string }[]
@ -25,10 +23,7 @@ export function Tabs({ @@ -25,10 +23,7 @@ export function Tabs({
const containerRef = useRef<HTMLDivElement | null>(null)
const [indicatorStyle, setIndicatorStyle] = useState({ width: 0, left: 0, top: 0 })
// Filter tabs based on hideBoostsAndQuotes
const visibleTabs = hideBoostsAndQuotes
? TABS.filter((tab) => tab.value !== 'boosts' && tab.value !== 'quotes')
: TABS
const visibleTabs = hideQuotesForDiscussion ? TABS.filter((tab) => tab.value !== 'quotes') : TABS
useEffect(() => {
setTimeout(() => {

14
src/components/NoteInteractions/index.tsx

@ -5,9 +5,7 @@ import { Event } from 'nostr-tools' @@ -5,9 +5,7 @@ import { Event } from 'nostr-tools'
import { useState } from 'react'
import HideUntrustedContentButton from '../HideUntrustedContentButton'
import QuoteList from '../QuoteList'
import ReactionList from '../ReactionList'
import ReplyNoteList from '../ReplyNoteList'
import ZapList from '../ZapList'
import { Tabs, TTabValue } from './Tabs'
import ReplySort, { ReplySortOption } from './ReplySort'
@ -36,16 +34,6 @@ export default function NoteInteractions({ @@ -36,16 +34,6 @@ export default function NoteInteractions({
if (isDiscussion) return null // Hide quotes for discussions
list = <QuoteList event={event} />
break
case 'reactions':
list = <ReactionList event={event} />
break
case 'boosts':
if (isDiscussion) return null // Hide boosts for discussions
list = <RepostList event={event} />
break
case 'zaps':
list = <ZapList event={event} />
break
default:
break
}
@ -54,7 +42,7 @@ export default function NoteInteractions({ @@ -54,7 +42,7 @@ export default function NoteInteractions({
<>
<div className="flex items-center justify-between">
<div className="flex-1 w-0">
<Tabs selectedTab={type} onTabChange={setType} hideBoostsAndQuotes={isDiscussion} />
<Tabs selectedTab={type} onTabChange={setType} hideQuotesForDiscussion={isDiscussion} />
</div>
<Separator orientation="vertical" className="h-6" />
{type === 'replies' && isDiscussion && (

49
src/components/NoteStats/Likes.tsx

@ -1,20 +1,25 @@ @@ -1,20 +1,25 @@
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
import { ExtendedKind } from '@/constants'
import { useNoteStatsById } from '@/hooks/useNoteStatsById'
import { shouldHideInteractions } from '@/lib/event-filtering'
import { createReactionDraftEvent } from '@/lib/draft-event'
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
import { useUserTrust } from '@/providers/UserTrustProvider'
import noteStatsService from '@/services/note-stats.service'
import { TEmoji } from '@/types'
import { Loader } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useMemo, useRef, useState } from 'react'
import Emoji from '../Emoji'
import Username from '../Username'
import logger from '@/lib/logger'
export default function Likes({ event }: { event: Event }) {
const inQuietMode = shouldHideInteractions(event)
const { pubkey, checkLogin, publish } = useNostr()
const { hideUntrustedInteractions, isUserTrusted } = useUserTrust()
const noteStats = useNoteStatsById(event.id)
const [liking, setLiking] = useState<string | null>(null)
const longPressTimerRef = useRef<NodeJS.Timeout | null>(null)
@ -22,9 +27,16 @@ export default function Likes({ event }: { event: Event }) { @@ -22,9 +27,16 @@ export default function Likes({ event }: { event: Event }) {
const [isCompleted, setIsCompleted] = useState<string | null>(null)
const likes = useMemo(() => {
const _likes = noteStats?.likes
let _likes = noteStats?.likes
if (!_likes) return []
if (event.kind === ExtendedKind.DISCUSSION) {
_likes = _likes.filter((item) => item.emoji === '⬆' || item.emoji === '⬇')
}
if (hideUntrustedInteractions) {
_likes = _likes.filter((item) => isUserTrusted(item.pubkey))
}
const stats = new Map<string, { key: string; emoji: TEmoji | string; pubkeys: Set<string> }>()
_likes.forEach((item) => {
// In quiet mode, normalize all emojis to "+" to prevent trolling with funny emojis
@ -42,8 +54,10 @@ export default function Likes({ event }: { event: Event }) { @@ -42,8 +54,10 @@ export default function Likes({ event }: { event: Event }) {
stats.get(key)?.pubkeys.add(item.pubkey)
}
})
return Array.from(stats.values()).sort((a, b) => b.pubkeys.size - a.pubkeys.size)
}, [noteStats, event, inQuietMode])
return Array.from(stats.values())
.filter((g) => g.pubkeys.size > 0)
.sort((a, b) => b.pubkeys.size - a.pubkeys.size)
}, [noteStats, event, inQuietMode, hideUntrustedInteractions, isUserTrusted])
if (!likes.length) return null
@ -123,9 +137,12 @@ export default function Likes({ event }: { event: Event }) { @@ -123,9 +137,12 @@ export default function Likes({ event }: { event: Event }) {
return (
<ScrollArea className="pb-2 mb-1">
<div className="flex gap-1">
{likes.map(({ key, emoji, pubkeys }) => (
{likes.map(({ key, emoji, pubkeys }) => {
const contributorIds = Array.from(pubkeys).sort()
return (
<HoverCard key={key} openDelay={250} closeDelay={50}>
<HoverCardTrigger asChild>
<div
key={key}
className={cn(
'flex h-7 w-fit gap-2 px-2 rounded-full items-center border shrink-0 select-none relative overflow-hidden transition-all duration-200',
pubkey && pubkeys.has(pubkey)
@ -169,8 +186,30 @@ export default function Likes({ event }: { event: Event }) { @@ -169,8 +186,30 @@ export default function Likes({ event }: { event: Event }) {
<div className="text-sm">{pubkeys.size}</div>
</div>
</div>
</HoverCardTrigger>
<HoverCardContent
className="w-72 max-h-64 p-0 overflow-hidden"
side="top"
align="center"
onClick={(e) => e.stopPropagation()}
>
<ScrollArea className="max-h-60">
<div className="flex flex-col gap-1.5 p-3 pr-4">
{contributorIds.map((userId) => (
<Username
key={userId}
userId={userId}
className="text-sm truncate text-foreground"
skeletonClassName="h-4"
/>
))}
</div>
</ScrollArea>
</HoverCardContent>
</HoverCard>
)
})}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
)

98
src/components/ReactionList/index.tsx

@ -1,98 +0,0 @@ @@ -1,98 +0,0 @@
import { useSecondaryPage } from '@/PageManager'
import { ExtendedKind } from '@/constants'
import { useNoteStatsById } from '@/hooks/useNoteStatsById'
import { shouldHideInteractions } from '@/lib/event-filtering'
import { toProfile } from '@/lib/link'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { useUserTrust } from '@/providers/UserTrustProvider'
import { Event } from 'nostr-tools'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Emoji from '../Emoji'
import { FormattedTimestamp } from '../FormattedTimestamp'
import Nip05 from '../Nip05'
import UserAvatar from '../UserAvatar'
import Username from '../Username'
const SHOW_COUNT = 20
export default function ReactionList({ event }: { event: Event }) {
const inQuietMode = shouldHideInteractions(event)
const { t } = useTranslation()
const { push } = useSecondaryPage()
const { isSmallScreen } = useScreenSize()
const { hideUntrustedInteractions, isUserTrusted } = useUserTrust()
const noteStats = useNoteStatsById(event.id)
const filteredLikes = useMemo(() => {
let likes = noteStats?.likes ?? []
// For discussion events (kind 11), only show up/down arrow reactions
if (event.kind === ExtendedKind.DISCUSSION) {
likes = likes.filter(like => like.emoji === '⬆' || like.emoji === '⬇')
}
return likes
.filter((like) => !hideUntrustedInteractions || isUserTrusted(like.pubkey))
.sort((a, b) => b.created_at - a.created_at)
}, [noteStats, event.id, hideUntrustedInteractions, isUserTrusted, event.kind])
const [showCount, setShowCount] = useState(SHOW_COUNT)
const bottomRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
if (!bottomRef.current || filteredLikes.length <= showCount) return
const obs = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) setShowCount((c) => c + SHOW_COUNT)
},
{ rootMargin: '10px', threshold: 0.1 }
)
obs.observe(bottomRef.current)
return () => obs.disconnect()
}, [filteredLikes.length, showCount])
return (
<div className="min-h-[80vh]">
{filteredLikes.slice(0, showCount).map((like) => (
<div
key={like.id}
className="px-4 py-3 border-b transition-colors clickable flex items-center gap-3"
onClick={() => push(toProfile(like.pubkey))}
>
<div className="w-6 flex flex-col items-center">
<Emoji
emoji={inQuietMode ? '+' : like.emoji}
classNames={{
text: 'text-xl'
}}
/>
</div>
<UserAvatar userId={like.pubkey} size="medium" className="shrink-0" />
<div className="flex-1 w-0">
<Username
userId={like.pubkey}
className="text-sm font-semibold text-muted-foreground hover:text-foreground max-w-fit truncate"
skeletonClassName="h-3"
/>
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Nip05 pubkey={like.pubkey} append="·" />
<FormattedTimestamp
timestamp={like.created_at}
className="shrink-0"
short={isSmallScreen}
/>
</div>
</div>
</div>
))}
<div ref={bottomRef} />
<div className="text-sm mt-2 text-center text-muted-foreground">
{filteredLikes.length > 0 ? t('No more reactions') : t('No reactions yet')}
</div>
</div>
)
}

56
src/components/ReplyNoteList/ZapReplyFeedRow.tsx

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
import Content from '@/components/Content'
import { FormattedTimestamp } from '@/components/FormattedTimestamp'
import Nip05 from '@/components/Nip05'
import UserAvatar from '@/components/UserAvatar'
import Username from '@/components/Username'
import { formatAmount } from '@/lib/lightning'
import { toProfile } from '@/lib/link'
import { useSecondaryPage } from '@/PageManager'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import type { TNoteStats } from '@/services/note-stats.service'
import { Zap } from 'lucide-react'
import { useTranslation } from 'react-i18next'
export type TZapFeedEntry = TNoteStats['zaps'][number]
export default function ZapReplyFeedRow({ zap }: { zap: TZapFeedEntry }) {
const { t } = useTranslation()
const { push } = useSecondaryPage()
const { isSmallScreen } = useScreenSize()
return (
<div
className="clickable pb-3 border-b transition-colors duration-500"
onClick={() => push(toProfile(zap.pubkey))}
>
<div className="flex items-start space-x-2 px-4 pt-3">
<UserAvatar userId={zap.pubkey} size="medium" className="mt-0.5 shrink-0" />
<div className="min-w-0 w-full overflow-hidden">
<div className="flex items-start justify-between gap-2">
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-1.5">
<Zap className="size-4 shrink-0 text-primary" strokeWidth={2.5} aria-hidden />
<Username
userId={zap.pubkey}
className="truncate text-sm font-semibold text-muted-foreground hover:text-foreground"
skeletonClassName="h-3"
/>
</div>
<div className="mt-0.5 flex flex-wrap items-center gap-1 text-sm text-muted-foreground">
<span className="font-semibold tabular-nums text-foreground">
{formatAmount(zap.amount)} {t('sats')}
</span>
<span className="text-muted-foreground/80" aria-hidden>
·
</span>
<Nip05 pubkey={zap.pubkey} append="·" />
<FormattedTimestamp timestamp={zap.created_at} className="shrink-0" short={isSmallScreen} />
</div>
</div>
</div>
{zap.comment ? <Content className="mt-2 text-sm" content={zap.comment} /> : null}
</div>
</div>
</div>
)
}

15
src/components/ReplyNoteList/index.tsx

@ -9,6 +9,7 @@ import { @@ -9,6 +9,7 @@ import {
isReplaceableEvent,
isReplyNoteEvent
} from '@/lib/event'
import { shouldHideInteractions } from '@/lib/event-filtering'
import logger from '@/lib/logger'
import { toNote } from '@/lib/link'
import { generateBech32IdFromETag, tagNameEquals } from '@/lib/tag'
@ -25,10 +26,12 @@ import noteStatsService from '@/services/note-stats.service' @@ -25,10 +26,12 @@ import noteStatsService from '@/services/note-stats.service'
import discussionFeedCache from '@/services/discussion-feed-cache.service'
import { buildReplyReadRelayList } from '@/lib/relay-list-builder'
import { Filter, Event as NEvent, kinds } from 'nostr-tools'
import { useNoteStatsById } from '@/hooks/useNoteStatsById'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { LoadingBar } from '../LoadingBar'
import ReplyNote, { ReplyNoteSkeleton } from '../ReplyNote'
import ZapReplyFeedRow from './ZapReplyFeedRow'
type TRootInfo =
| { type: 'E'; id: string; pubkey: string }
@ -43,6 +46,7 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even @@ -43,6 +46,7 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even
const { navigateToNote } = useSmartNoteNavigation()
const { currentIndex } = useSecondaryPage()
const { hideUntrustedInteractions, isUserTrusted } = useUserTrust()
const noteStats = useNoteStatsById(event.id)
const { mutePubkeySet } = useMuteList()
const { hideContentMentioningMutedUsers } = useContentPolicy()
const { relayList: userRelayList, pubkey: userPubkey } = useNostr()
@ -179,6 +183,14 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even @@ -179,6 +183,14 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even
return replyEvents.sort((a, b) => b.created_at - a.created_at)
}
}, [event.id, repliesMap, mutePubkeySet, hideContentMentioningMutedUsers, sort])
const zapsForFeed = useMemo(() => {
if (shouldHideInteractions(event)) return []
const raw = noteStats?.zaps ?? []
const filtered = hideUntrustedInteractions ? raw.filter((z) => isUserTrusted(z.pubkey)) : raw
return [...filtered].sort((a, b) => b.amount - a.amount)
}, [event, noteStats, hideUntrustedInteractions, isUserTrusted])
const [timelineKey] = useState<string | undefined>(undefined)
const [until, setUntil] = useState<number | undefined>(undefined)
const [loading, setLoading] = useState<boolean>(false)
@ -470,6 +482,9 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even @@ -470,6 +482,9 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even
return (
<div className="min-h-[80vh]">
{loading && <LoadingBar />}
{zapsForFeed.map((zap) => (
<ZapReplyFeedRow key={zap.pr} zap={zap} />
))}
{!loading && until && (
<div
className={`text-sm text-center text-muted-foreground border-b py-2 ${!loading ? 'hover:text-foreground cursor-pointer' : ''}`}

81
src/components/RepostList/index.tsx

@ -1,81 +0,0 @@ @@ -1,81 +0,0 @@
import { useSecondaryPage } from '@/PageManager'
import { useNoteStatsById } from '@/hooks/useNoteStatsById'
import { toProfile } from '@/lib/link'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { useUserTrust } from '@/providers/UserTrustProvider'
import { Repeat } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FormattedTimestamp } from '../FormattedTimestamp'
import Nip05 from '../Nip05'
import UserAvatar from '../UserAvatar'
import Username from '../Username'
const SHOW_COUNT = 20
export default function RepostList({ event }: { event: Event }) {
const { t } = useTranslation()
const { push } = useSecondaryPage()
const { isSmallScreen } = useScreenSize()
const { hideUntrustedInteractions, isUserTrusted } = useUserTrust()
const noteStats = useNoteStatsById(event.id)
const filteredReposts = useMemo(() => {
return (noteStats?.reposts ?? [])
.filter((repost) => !hideUntrustedInteractions || isUserTrusted(repost.pubkey))
.sort((a, b) => b.created_at - a.created_at)
}, [noteStats, event.id, hideUntrustedInteractions, isUserTrusted])
const [showCount, setShowCount] = useState(SHOW_COUNT)
const bottomRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
if (!bottomRef.current || filteredReposts.length <= showCount) return
const obs = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) setShowCount((c) => c + SHOW_COUNT)
},
{ rootMargin: '10px', threshold: 0.1 }
)
obs.observe(bottomRef.current)
return () => obs.disconnect()
}, [filteredReposts.length, showCount])
return (
<div className="min-h-[80vh]">
{filteredReposts.slice(0, showCount).map((repost) => (
<div
key={repost.id}
className="px-4 py-3 border-b transition-colors clickable flex items-center gap-3"
onClick={() => push(toProfile(repost.pubkey))}
>
<Repeat className="text-green-400 size-5" />
<UserAvatar userId={repost.pubkey} size="medium" className="shrink-0" />
<div className="flex-1 w-0">
<Username
userId={repost.pubkey}
className="text-sm font-semibold text-muted-foreground hover:text-foreground max-w-fit truncate"
skeletonClassName="h-3"
/>
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Nip05 pubkey={repost.pubkey} append="·" />
<FormattedTimestamp
timestamp={repost.created_at}
className="shrink-0"
short={isSmallScreen}
/>
</div>
</div>
</div>
))}
<div ref={bottomRef} />
<div className="text-sm mt-2 text-center text-muted-foreground">
{filteredReposts.length > 0 ? t('No more boosts') : t('No boosts yet')}
</div>
</div>
)
}

91
src/components/ZapList/index.tsx

@ -1,91 +0,0 @@ @@ -1,91 +0,0 @@
import { useSecondaryPage } from '@/PageManager'
import { useNoteStatsById } from '@/hooks/useNoteStatsById'
import { shouldHideInteractions } from '@/lib/event-filtering'
import { formatAmount } from '@/lib/lightning'
import { toProfile } from '@/lib/link'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { Zap } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Content from '../Content'
import { FormattedTimestamp } from '../FormattedTimestamp'
import Nip05 from '../Nip05'
import UserAvatar from '../UserAvatar'
import Username from '../Username'
const SHOW_COUNT = 20
export default function ZapList({ event }: { event: Event }) {
const inQuietMode = shouldHideInteractions(event)
// Hide zap receipts in quiet mode as they contain emojis and text
if (inQuietMode) {
return null
}
const { t } = useTranslation()
const { push } = useSecondaryPage()
const { isSmallScreen } = useScreenSize()
const noteStats = useNoteStatsById(event.id)
const filteredZaps = useMemo(() => {
return (noteStats?.zaps ?? []).sort((a, b) => b.amount - a.amount)
}, [noteStats, event.id])
const [showCount, setShowCount] = useState(SHOW_COUNT)
const bottomRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
if (!bottomRef.current || filteredZaps.length <= showCount) return
const obs = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) setShowCount((c) => c + SHOW_COUNT)
},
{ rootMargin: '10px', threshold: 0.1 }
)
obs.observe(bottomRef.current)
return () => obs.disconnect()
}, [filteredZaps.length, showCount])
return (
<div className="min-h-[80vh]">
{filteredZaps.slice(0, showCount).map((zap) => (
<div
key={zap.pr}
className="px-4 py-3 border-b transition-colors clickable flex gap-2"
onClick={() => push(toProfile(zap.pubkey))}
>
<div className="w-8 flex flex-col items-center mt-0.5">
<Zap className="text-yellow-400 size-5" />
<div className="text-sm font-semibold text-yellow-400">{formatAmount(zap.amount)}</div>
</div>
<div className="flex space-x-2 items-start">
<UserAvatar userId={zap.pubkey} size="medium" className="shrink-0 mt-0.5" />
<div className="flex-1">
<Username
userId={zap.pubkey}
className="text-sm font-semibold text-muted-foreground hover:text-foreground max-w-fit truncate"
skeletonClassName="h-3"
/>
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Nip05 pubkey={zap.pubkey} append="·" />
<FormattedTimestamp
timestamp={zap.created_at}
className="shrink-0"
short={isSmallScreen}
/>
</div>
<Content className="mt-2" content={zap.comment} />
</div>
</div>
</div>
))}
<div ref={bottomRef} />
<div className="text-sm mt-2 text-center text-muted-foreground">
{filteredZaps.length > 0 ? t('No more zaps') : t('No zaps yet')}
</div>
</div>
)
}

1
src/i18n/locales/de.ts

@ -498,6 +498,7 @@ export default { @@ -498,6 +498,7 @@ export default {
'No zaps yet': 'Noch keine Zaps',
'No more boosts': 'Keine weiteren Boosts',
'No boosts yet': 'Noch keine Boosts',
'n more boosts': '{{count}} weitere Boosts',
Boosts: 'Boosts',
FollowListNotFoundConfirmation:
'Folgeliste nicht gefunden. Möchten Sie eine neue erstellen? Wenn Sie zuvor Benutzer gefolgt haben, bestätigen Sie bitte NICHT, da diese Operation dazu führt, dass Sie Ihre vorherige Folgeliste verlieren.',

1
src/i18n/locales/en.ts

@ -568,6 +568,7 @@ export default { @@ -568,6 +568,7 @@ export default {
'No zaps yet': 'No zaps yet',
'No more boosts': 'No more boosts',
'No boosts yet': 'No boosts yet',
'n more boosts': '{{count}} more boosts',
Boosts: 'Boosts',
FollowListNotFoundConfirmation:
'Follow list not found. Do you want to create a new one? If you have followed users before, please DO NOT confirm as this operation will cause you to lose your previous follow list.',

1
src/pages/secondary/NotePage/index.tsx

@ -493,6 +493,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }: @@ -493,6 +493,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false, initialEvent }:
: undefined
}
/>
<NoteBoostBadges event={finalEvent} className="mt-2" />
<NoteStats className="mt-3" event={finalEvent} fetchIfNotExisting displayTopZapsAndLikes />
</div>
<Separator className="mt-4" />

Loading…
Cancel
Save