diff --git a/src/components/ReplyNote/index.tsx b/src/components/ReplyNote/index.tsx
index c0b82d1..7851575 100644
--- a/src/components/ReplyNote/index.tsx
+++ b/src/components/ReplyNote/index.tsx
@@ -3,6 +3,7 @@ import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
import { isMentioningMutedUsers } from '@/lib/event'
import { toNote } from '@/lib/link'
+import { cn } from '@/lib/utils'
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
import { useMuteList } from '@/providers/MuteListProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
@@ -15,9 +16,10 @@ import Content from '../Content'
import { FormattedTimestamp } from '../FormattedTimestamp'
import Nip05 from '../Nip05'
import NoteOptions from '../NoteOptions'
-import StuffStats from '../StuffStats'
import ParentNotePreview from '../ParentNotePreview'
+import StuffStats from '../StuffStats'
import TranslateButton from '../TranslateButton'
+import TrustScoreBadge from '../TrustScoreBadge'
import UserAvatar from '../UserAvatar'
import Username from '../Username'
@@ -25,12 +27,14 @@ export default function ReplyNote({
event,
parentEventId,
onClickParent = () => {},
- highlight = false
+ highlight = false,
+ className = ''
}: {
event: Event
parentEventId?: string
onClickParent?: () => void
highlight?: boolean
+ className?: string
}) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
@@ -53,7 +57,11 @@ export default function ReplyNote({
return (
push(toNote(event))}
>
@@ -68,6 +76,7 @@ export default function ReplyNote({
className="text-sm font-semibold text-muted-foreground hover:text-foreground truncate"
skeletonClassName="h-3"
/>
+
diff --git a/src/components/ReplyNoteList/SubReplies.tsx b/src/components/ReplyNoteList/SubReplies.tsx
new file mode 100644
index 0000000..8cbb785
--- /dev/null
+++ b/src/components/ReplyNoteList/SubReplies.tsx
@@ -0,0 +1,152 @@
+import { useSecondaryPage } from '@/PageManager'
+import { Separator } from '@/components/ui/separator'
+import { getEventKey, getKeyFromTag, getParentTag, isMentioningMutedUsers } from '@/lib/event'
+import { toNote } from '@/lib/link'
+import { generateBech32IdFromETag } from '@/lib/tag'
+import { useContentPolicy } from '@/providers/ContentPolicyProvider'
+import { useMuteList } from '@/providers/MuteListProvider'
+import { useReply } from '@/providers/ReplyProvider'
+import { useUserTrust } from '@/providers/UserTrustProvider'
+import { ChevronDown, ChevronUp } from 'lucide-react'
+import { NostrEvent } from 'nostr-tools'
+import { useCallback, useMemo, useRef, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import ReplyNote from '../ReplyNote'
+import { cn } from '@/lib/utils'
+
+export default function SubReplies({ parentKey }: { parentKey: string }) {
+ const { t } = useTranslation()
+ const { push } = useSecondaryPage()
+ const { repliesMap } = useReply()
+ const { hideUntrustedInteractions, isUserTrusted } = useUserTrust()
+ const { mutePubkeySet } = useMuteList()
+ const { hideContentMentioningMutedUsers } = useContentPolicy()
+ const [isExpanded, setIsExpanded] = useState(false)
+ const replies = useMemo(() => {
+ const replyKeySet = new Set
()
+ const replyEvents: NostrEvent[] = []
+
+ let parentKeys = [parentKey]
+ while (parentKeys.length > 0) {
+ const events = parentKeys.flatMap((key) => repliesMap.get(key)?.events || [])
+ events.forEach((evt) => {
+ const key = getEventKey(evt)
+ if (replyKeySet.has(key)) return
+ if (mutePubkeySet.has(evt.pubkey)) return
+ if (hideContentMentioningMutedUsers && isMentioningMutedUsers(evt, mutePubkeySet)) return
+ if (hideUntrustedInteractions && !isUserTrusted(evt.pubkey)) {
+ const replyKey = getEventKey(evt)
+ const repliesForThisReply = repliesMap.get(replyKey)
+ // If the reply is not trusted and there are no trusted replies for this reply, skip rendering
+ if (
+ !repliesForThisReply ||
+ repliesForThisReply.events.every((evt) => !isUserTrusted(evt.pubkey))
+ ) {
+ return
+ }
+ }
+
+ replyKeySet.add(key)
+ replyEvents.push(evt)
+ })
+ parentKeys = events.map((evt) => getEventKey(evt))
+ }
+ return replyEvents.sort((a, b) => a.created_at - b.created_at)
+ }, [
+ parentKey,
+ repliesMap,
+ mutePubkeySet,
+ hideContentMentioningMutedUsers,
+ hideUntrustedInteractions
+ ])
+ const [highlightReplyKey, setHighlightReplyKey] = useState(undefined)
+ const replyRefs = useRef>({})
+
+ const highlightReply = useCallback((key: string, eventId?: string, scrollTo = true) => {
+ let found = false
+ if (scrollTo) {
+ const ref = replyRefs.current[key]
+ if (ref) {
+ found = true
+ ref.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
+ }
+ }
+ if (!found) {
+ if (eventId) push(toNote(eventId))
+ return
+ }
+
+ setHighlightReplyKey(key)
+ setTimeout(() => {
+ setHighlightReplyKey((pre) => (pre === key ? undefined : pre))
+ }, 1500)
+ }, [])
+
+ if (replies.length === 0) return
+
+ return (
+
+ {replies.length > 1 && (
+
+ )}
+ {(isExpanded || replies.length === 1) && (
+
+ {replies.map((reply, index) => {
+ const currentReplyKey = getEventKey(reply)
+ const _parentTag = getParentTag(reply)
+ if (_parentTag?.type !== 'e') return null
+ const _parentKey = _parentTag ? getKeyFromTag(_parentTag.tag) : undefined
+ const _parentEventId = generateBech32IdFromETag(_parentTag.tag)
+ return (
+
(replyRefs.current[currentReplyKey] = el)}
+ key={currentReplyKey}
+ className="scroll-mt-12 flex"
+ >
+
+
{
+ if (!_parentKey) return
+ highlightReply(_parentKey, _parentEventId)
+ }}
+ highlight={highlightReplyKey === currentReplyKey}
+ />
+
+ )
+ })}
+
+ )}
+
+ )
+}
diff --git a/src/components/ReplyNoteList/index.tsx b/src/components/ReplyNoteList/index.tsx
index 2659cb7..90386d8 100644
--- a/src/components/ReplyNoteList/index.tsx
+++ b/src/components/ReplyNoteList/index.tsx
@@ -2,16 +2,13 @@ import { BIG_RELAY_URLS, ExtendedKind } from '@/constants'
import { useStuff } from '@/hooks/useStuff'
import {
getEventKey,
- getKeyFromTag,
- getParentTag,
getReplaceableCoordinateFromEvent,
getRootTag,
isMentioningMutedUsers,
isProtectedEvent,
isReplaceableEvent
} from '@/lib/event'
-import { toNote } from '@/lib/link'
-import { generateBech32IdFromATag, generateBech32IdFromETag } from '@/lib/tag'
+import { generateBech32IdFromETag } from '@/lib/tag'
import { useSecondaryPage } from '@/PageManager'
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
import { useMuteList } from '@/providers/MuteListProvider'
@@ -23,6 +20,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { LoadingBar } from '../LoadingBar'
import ReplyNote, { ReplyNoteSkeleton } from '../ReplyNote'
+import SubReplies from './SubReplies'
type TRootInfo =
| { type: 'E'; id: string; pubkey: string }
@@ -40,7 +38,7 @@ export default function ReplyNoteList({
index?: number
}) {
const { t } = useTranslation()
- const { push, currentIndex } = useSecondaryPage()
+ const { currentIndex } = useSecondaryPage()
const { hideUntrustedInteractions, isUserTrusted } = useUserTrust()
const { mutePubkeySet } = useMuteList()
const { hideContentMentioningMutedUsers } = useContentPolicy()
@@ -49,30 +47,40 @@ export default function ReplyNoteList({
const { event, externalContent, stuffKey } = useStuff(stuff)
const replies = useMemo(() => {
const replyKeySet = new Set()
- const replyEvents: NEvent[] = []
+ const replyEvents = (repliesMap.get(stuffKey)?.events || []).filter((evt) => {
+ const key = getEventKey(evt)
+ if (replyKeySet.has(key)) return false
+ if (mutePubkeySet.has(evt.pubkey)) return false
+ if (hideContentMentioningMutedUsers && isMentioningMutedUsers(evt, mutePubkeySet)) {
+ return false
+ }
+ if (hideUntrustedInteractions && !isUserTrusted(evt.pubkey)) {
+ const replyKey = getEventKey(evt)
+ const repliesForThisReply = repliesMap.get(replyKey)
+ // If the reply is not trusted and there are no trusted replies for this reply, skip rendering
+ if (
+ !repliesForThisReply ||
+ repliesForThisReply.events.every((evt) => !isUserTrusted(evt.pubkey))
+ ) {
+ return false
+ }
+ }
- let parentKeys = [stuffKey]
- while (parentKeys.length > 0) {
- const events = parentKeys.flatMap((key) => repliesMap.get(key)?.events || [])
- events.forEach((evt) => {
- const key = getEventKey(evt)
- if (replyKeySet.has(key)) return
- if (mutePubkeySet.has(evt.pubkey)) return
- if (hideContentMentioningMutedUsers && isMentioningMutedUsers(evt, mutePubkeySet)) return
-
- replyKeySet.add(key)
- replyEvents.push(evt)
- })
- parentKeys = events.map((evt) => getEventKey(evt))
- }
+ replyKeySet.add(key)
+ return true
+ })
return replyEvents.sort((a, b) => a.created_at - b.created_at)
- }, [stuffKey, repliesMap])
+ }, [
+ stuffKey,
+ repliesMap,
+ mutePubkeySet,
+ hideContentMentioningMutedUsers,
+ hideUntrustedInteractions
+ ])
const [timelineKey, setTimelineKey] = useState(undefined)
const [until, setUntil] = useState(undefined)
const [loading, setLoading] = useState(false)
const [showCount, setShowCount] = useState(SHOW_COUNT)
- const [highlightReplyKey, setHighlightReplyKey] = useState(undefined)
- const replyRefs = useRef>({})
const bottomRef = useRef(null)
useEffect(() => {
@@ -252,26 +260,6 @@ export default function ReplyNoteList({
setLoading(false)
}, [loading, until, timelineKey])
- const highlightReply = useCallback((key: string, eventId?: string, scrollTo = true) => {
- let found = false
- if (scrollTo) {
- const ref = replyRefs.current[key]
- if (ref) {
- found = true
- ref.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
- }
- }
- if (!found) {
- if (eventId) push(toNote(eventId))
- return
- }
-
- setHighlightReplyKey(key)
- setTimeout(() => {
- setHighlightReplyKey((pre) => (pre === key ? undefined : pre))
- }, 1500)
- }, [])
-
return (
{loading &&
}
@@ -285,44 +273,11 @@ export default function ReplyNoteList({
)}
{replies.slice(0, showCount).map((reply) => {
- if (hideUntrustedInteractions && !isUserTrusted(reply.pubkey)) {
- const replyKey = getEventKey(reply)
- const repliesForThisReply = repliesMap.get(replyKey)
- // If the reply is not trusted and there are no trusted replies for this reply, skip rendering
- if (
- !repliesForThisReply ||
- repliesForThisReply.events.every((evt) => !isUserTrusted(evt.pubkey))
- ) {
- return null
- }
- }
-
- const rootKey = event ? getEventKey(event) : externalContent!
- const currentReplyKey = getEventKey(reply)
- const parentTag = getParentTag(reply)
- const parentKey = parentTag ? getKeyFromTag(parentTag.tag) : undefined
- const parentEventId = parentTag
- ? parentTag.type === 'e'
- ? generateBech32IdFromETag(parentTag.tag)
- : parentTag.type === 'a'
- ? generateBech32IdFromATag(parentTag.tag)
- : undefined
- : undefined
+ const key = getEventKey(reply)
return (
-
(replyRefs.current[currentReplyKey] = el)}
- key={currentReplyKey}
- className="scroll-mt-12"
- >
-
{
- if (!parentKey) return
- highlightReply(parentKey, parentEventId)
- }}
- highlight={highlightReplyKey === currentReplyKey}
- />
+
+
+
)
})}
diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts
index d09616e..e945fd5 100644
--- a/src/i18n/locales/ar.ts
+++ b/src/i18n/locales/ar.ts
@@ -587,6 +587,8 @@ export default {
'Relay Feeds': 'تدفقات الترحيل',
'Create Highlight': 'إنشاء تمييز',
'Write your thoughts about this highlight...': 'اكتب أفكارك حول هذا التمييز...',
- 'Publish Highlight': 'نشر التمييز'
+ 'Publish Highlight': 'نشر التمييز',
+ 'Show replies': 'إظهار الردود',
+ 'Hide replies': 'إخفاء الردود'
}
}
diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts
index 8d4abed..da185bf 100644
--- a/src/i18n/locales/de.ts
+++ b/src/i18n/locales/de.ts
@@ -604,6 +604,8 @@ export default {
'Create Highlight': 'Markierung Erstellen',
'Write your thoughts about this highlight...':
'Schreiben Sie Ihre Gedanken zu dieser Markierung...',
- 'Publish Highlight': 'Markierung Veröffentlichen'
+ 'Publish Highlight': 'Markierung Veröffentlichen',
+ 'Show replies': 'Antworten anzeigen',
+ 'Hide replies': 'Antworten ausblenden'
}
}
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts
index 7270639..92bd32e 100644
--- a/src/i18n/locales/en.ts
+++ b/src/i18n/locales/en.ts
@@ -590,6 +590,8 @@ export default {
'Relay Feeds': 'Relay Feeds',
'Create Highlight': 'Create Highlight',
'Write your thoughts about this highlight...': 'Write your thoughts about this highlight...',
- 'Publish Highlight': 'Publish Highlight'
+ 'Publish Highlight': 'Publish Highlight',
+ 'Show replies': 'Show replies',
+ 'Hide replies': 'Hide replies'
}
}
diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts
index 095cb87..996b11f 100644
--- a/src/i18n/locales/es.ts
+++ b/src/i18n/locales/es.ts
@@ -600,6 +600,8 @@ export default {
'Create Highlight': 'Crear Resaltado',
'Write your thoughts about this highlight...':
'Escribe tus pensamientos sobre este resaltado...',
- 'Publish Highlight': 'Publicar Resaltado'
+ 'Publish Highlight': 'Publicar Resaltado',
+ 'Show replies': 'Mostrar respuestas',
+ 'Hide replies': 'Ocultar respuestas'
}
}
diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts
index 78a8d84..7722315 100644
--- a/src/i18n/locales/fa.ts
+++ b/src/i18n/locales/fa.ts
@@ -593,6 +593,8 @@ export default {
'Relay Feeds': 'فیدهای رله',
'Create Highlight': 'ایجاد برجستهسازی',
'Write your thoughts about this highlight...': 'نظرات خود را درباره این برجستهسازی بنویسید...',
- 'Publish Highlight': 'انتشار برجستهسازی'
+ 'Publish Highlight': 'انتشار برجستهسازی',
+ 'Show replies': 'نمایش پاسخها',
+ 'Hide replies': 'پنهان کردن پاسخها'
}
}
diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts
index 9d5031b..ea87bb6 100644
--- a/src/i18n/locales/fr.ts
+++ b/src/i18n/locales/fr.ts
@@ -602,6 +602,8 @@ export default {
'Relay Feeds': 'Flux de Relais',
'Create Highlight': 'Créer un Surlignage',
'Write your thoughts about this highlight...': 'Écrivez vos pensées sur ce surlignage...',
- 'Publish Highlight': 'Publier le Surlignage'
+ 'Publish Highlight': 'Publier le Surlignage',
+ 'Show replies': 'Afficher les réponses',
+ 'Hide replies': 'Masquer les réponses'
}
}
diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts
index a3a2933..4c05d3f 100644
--- a/src/i18n/locales/hi.ts
+++ b/src/i18n/locales/hi.ts
@@ -594,6 +594,8 @@ export default {
'Relay Feeds': 'रिले फ़ीड',
'Create Highlight': 'हाइलाइट बनाएं',
'Write your thoughts about this highlight...': 'इस हाइलाइट के बारे में अपने विचार लिखें...',
- 'Publish Highlight': 'हाइलाइट प्रकाशित करें'
+ 'Publish Highlight': 'हाइलाइट प्रकाशित करें',
+ 'Show replies': 'जवाब दिखाएं',
+ 'Hide replies': 'जवाब छुपाएं'
}
}
diff --git a/src/i18n/locales/hu.ts b/src/i18n/locales/hu.ts
index b531180..f6cae2a 100644
--- a/src/i18n/locales/hu.ts
+++ b/src/i18n/locales/hu.ts
@@ -588,6 +588,8 @@ export default {
'Relay Feeds': 'Relay Feedek',
'Create Highlight': 'Kiemelés Létrehozása',
'Write your thoughts about this highlight...': 'Írd le a gondolataidat erről a kiemelésről...',
- 'Publish Highlight': 'Kiemelés Közzététele'
+ 'Publish Highlight': 'Kiemelés Közzététele',
+ 'Show replies': 'Válaszok megjelenítése',
+ 'Hide replies': 'Válaszok elrejtése'
}
}
diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts
index 84e2310..50494d5 100644
--- a/src/i18n/locales/it.ts
+++ b/src/i18n/locales/it.ts
@@ -599,6 +599,8 @@ export default {
'Create Highlight': 'Crea Evidenziazione',
'Write your thoughts about this highlight...':
'Scrivi i tuoi pensieri su questa evidenziazione...',
- 'Publish Highlight': 'Pubblica Evidenziazione'
+ 'Publish Highlight': 'Pubblica Evidenziazione',
+ 'Show replies': 'Mostra risposte',
+ 'Hide replies': 'Nascondi risposte'
}
}
diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts
index 8bd6419..586c36a 100644
--- a/src/i18n/locales/ja.ts
+++ b/src/i18n/locales/ja.ts
@@ -594,6 +594,8 @@ export default {
'Create Highlight': 'ハイライトを作成',
'Write your thoughts about this highlight...':
'このハイライトについての考えを書いてください...',
- 'Publish Highlight': 'ハイライトを公開'
+ 'Publish Highlight': 'ハイライトを公開',
+ 'Show replies': '返信を表示',
+ 'Hide replies': '返信を非表示'
}
}
diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts
index 9ac6fa9..d4f0ab5 100644
--- a/src/i18n/locales/ko.ts
+++ b/src/i18n/locales/ko.ts
@@ -592,6 +592,8 @@ export default {
'Relay Feeds': '릴레이 피드',
'Create Highlight': '하이라이트 만들기',
'Write your thoughts about this highlight...': '이 하이라이트에 대한 생각을 작성하세요...',
- 'Publish Highlight': '하이라이트 게시'
+ 'Publish Highlight': '하이라이트 게시',
+ 'Show replies': '답글 표시',
+ 'Hide replies': '답글 숨기기'
}
}
diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts
index 575909d..1056c6a 100644
--- a/src/i18n/locales/pl.ts
+++ b/src/i18n/locales/pl.ts
@@ -600,6 +600,8 @@ export default {
'Create Highlight': 'Utwórz Podświetlenie',
'Write your thoughts about this highlight...':
'Napisz swoje przemyślenia na temat tego podświetlenia...',
- 'Publish Highlight': 'Opublikuj Podświetlenie'
+ 'Publish Highlight': 'Opublikuj Podświetlenie',
+ 'Show replies': 'Pokaż odpowiedzi',
+ 'Hide replies': 'Ukryj odpowiedzi'
}
}
diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts
index 7144e12..030d497 100644
--- a/src/i18n/locales/pt-BR.ts
+++ b/src/i18n/locales/pt-BR.ts
@@ -595,6 +595,8 @@ export default {
'Create Highlight': 'Criar Destaque',
'Write your thoughts about this highlight...':
'Escreva seus pensamentos sobre este destaque...',
- 'Publish Highlight': 'Publicar Destaque'
+ 'Publish Highlight': 'Publicar Destaque',
+ 'Show replies': 'Mostrar respostas',
+ 'Hide replies': 'Ocultar respostas'
}
}
diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts
index aacfb04..902eed4 100644
--- a/src/i18n/locales/pt-PT.ts
+++ b/src/i18n/locales/pt-PT.ts
@@ -598,6 +598,8 @@ export default {
'Create Highlight': 'Criar Destaque',
'Write your thoughts about this highlight...':
'Escreva os seus pensamentos sobre este destaque...',
- 'Publish Highlight': 'Publicar Destaque'
+ 'Publish Highlight': 'Publicar Destaque',
+ 'Show replies': 'Mostrar respostas',
+ 'Hide replies': 'Ocultar respostas'
}
}
diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts
index b2f8cc7..022d13c 100644
--- a/src/i18n/locales/ru.ts
+++ b/src/i18n/locales/ru.ts
@@ -599,6 +599,8 @@ export default {
'Relay Feeds': 'Ленты Релеев',
'Create Highlight': 'Создать Выделение',
'Write your thoughts about this highlight...': 'Напишите свои мысли об этом выделении...',
- 'Publish Highlight': 'Опубликовать Выделение'
+ 'Publish Highlight': 'Опубликовать Выделение',
+ 'Show replies': 'Показать ответы',
+ 'Hide replies': 'Скрыть ответы'
}
}
diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts
index 2a50c0d..71db2df 100644
--- a/src/i18n/locales/th.ts
+++ b/src/i18n/locales/th.ts
@@ -586,6 +586,8 @@ export default {
'Relay Feeds': 'ฟีดรีเลย์',
'Create Highlight': 'สร้างไฮไลท์',
'Write your thoughts about this highlight...': 'เขียนความคิดของคุณเกี่ยวกับไฮไลท์นี้...',
- 'Publish Highlight': 'เผยแพร่ไฮไลท์'
+ 'Publish Highlight': 'เผยแพร่ไฮไลท์',
+ 'Show replies': 'แสดงการตอบกลับ',
+ 'Hide replies': 'ซ่อนการตอบกลับ'
}
}
diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts
index 9359112..a62d05e 100644
--- a/src/i18n/locales/zh.ts
+++ b/src/i18n/locales/zh.ts
@@ -579,6 +579,8 @@ export default {
'Relay Feeds': '中继订阅',
'Create Highlight': '创建高亮',
'Write your thoughts about this highlight...': '写下你对这段高亮的想法...',
- 'Publish Highlight': '发布高亮'
+ 'Publish Highlight': '发布高亮',
+ 'Show replies': '显示回复',
+ 'Hide replies': '隐藏回复'
}
}