From 5f785e5553396b8d3ce47a8150dd1e1d613006ed Mon Sep 17 00:00:00 2001 From: codytseng Date: Wed, 31 Dec 2025 18:22:23 +0800 Subject: [PATCH] feat: trust score filter --- AGENTS.md | 2 + .../ExternalContentInteractions/index.tsx | 4 +- .../HideUntrustedContentButton/index.tsx | 77 -------- src/components/NormalFeed/index.tsx | 5 +- src/components/NoteInteractions/index.tsx | 7 +- src/components/NoteList/index.tsx | 24 ++- .../NotificationItem/index.tsx | 56 ++++-- src/components/ReactionList/index.tsx | 42 ++++- .../RelayInfo/RelayReviewsPreview.tsx | 4 +- src/components/ReplyNote/index.tsx | 46 +++-- src/components/ReplyNoteList/SubReplies.tsx | 52 +----- src/components/ReplyNoteList/index.tsx | 47 +---- src/components/RepostList/index.tsx | 34 +++- src/components/StuffStats/LikeButton.tsx | 29 ++- src/components/StuffStats/ReplyButton.tsx | 48 +---- src/components/StuffStats/RepostButton.tsx | 36 ++-- src/components/TrustScoreFilter/index.tsx | 171 ++++++++++++++++++ src/components/UserAggregationList/index.tsx | 105 ++++++----- src/constants.ts | 9 +- src/hooks/index.tsx | 1 + src/hooks/useFilteredReplies.tsx | 160 ++++++++++++++++ src/i18n/locales/ar.ts | 13 ++ src/i18n/locales/de.ts | 14 ++ src/i18n/locales/en.ts | 13 ++ src/i18n/locales/es.ts | 14 ++ src/i18n/locales/fa.ts | 15 ++ src/i18n/locales/fr.ts | 15 ++ src/i18n/locales/hi.ts | 14 ++ src/i18n/locales/hu.ts | 15 ++ src/i18n/locales/it.ts | 14 ++ src/i18n/locales/ja.ts | 15 ++ src/i18n/locales/ko.ts | 12 ++ src/i18n/locales/pl.ts | 14 ++ src/i18n/locales/pt-BR.ts | 15 ++ src/i18n/locales/pt-PT.ts | 15 ++ src/i18n/locales/ru.ts | 14 ++ src/i18n/locales/th.ts | 15 ++ src/i18n/locales/zh-TW.ts | 12 ++ src/i18n/locales/zh.ts | 12 ++ src/lib/notification.ts | 12 +- src/pages/primary/ExplorePage/index.tsx | 4 +- .../primary/NotificationListPage/index.tsx | 4 +- .../secondary/EmojiPackSettingsPage/index.tsx | 3 - .../secondary/GeneralSettingsPage/index.tsx | 14 +- src/providers/NotificationProvider.tsx | 51 +++--- src/providers/UserTrustProvider.tsx | 59 +++--- src/services/fayan.service.ts | 8 +- src/services/local-storage.service.ts | 89 ++++----- 48 files changed, 974 insertions(+), 480 deletions(-) delete mode 100644 src/components/HideUntrustedContentButton/index.tsx create mode 100644 src/components/TrustScoreFilter/index.tsx create mode 100644 src/hooks/useFilteredReplies.tsx diff --git a/AGENTS.md b/AGENTS.md index 2767e39..8837f3e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -149,6 +149,8 @@ And some Providers are placed in `PageManager.tsx` because they need to use the Jumble is a multi-language application. When you add new text content, please ensure to add translations for all supported languages as much as possible. Append new translations to the end of each translation file without modifying or removing existing keys. +At the trial stage, you can skip translation first. After the feature is completed and confirmed satisfactory, you can add translation content later. + - Translation files located in `src/i18n/locales/` - Using `react-i18next` for internationalization - Supported languages: ar, de, en, es, fa, fr, hi, hu, it, ja, ko, pl, pt-BR, pt-PT, ru, th, zh, zh-TW diff --git a/src/components/ExternalContentInteractions/index.tsx b/src/components/ExternalContentInteractions/index.tsx index a5cc483..1214fa1 100644 --- a/src/components/ExternalContentInteractions/index.tsx +++ b/src/components/ExternalContentInteractions/index.tsx @@ -1,10 +1,10 @@ import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area' import { Separator } from '@/components/ui/separator' import { useState } from 'react' -import HideUntrustedContentButton from '../HideUntrustedContentButton' import QuoteList from '../QuoteList' import ReactionList from '../ReactionList' import ReplyNoteList from '../ReplyNoteList' +import TrustScoreFilter from '../TrustScoreFilter' import { Tabs, TTabValue } from './Tabs' export default function ExternalContentInteractions({ @@ -37,7 +37,7 @@ export default function ExternalContentInteractions({
- +
diff --git a/src/components/HideUntrustedContentButton/index.tsx b/src/components/HideUntrustedContentButton/index.tsx deleted file mode 100644 index 99e45cb..0000000 --- a/src/components/HideUntrustedContentButton/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger -} from '@/components/ui/alert-dialog' -import { Button, buttonVariants } from '@/components/ui/button' -import { useUserTrust } from '@/providers/UserTrustProvider' -import { VariantProps } from 'class-variance-authority' -import { Shield, ShieldCheck } from 'lucide-react' -import { useTranslation } from 'react-i18next' - -export default function HideUntrustedContentButton({ - type, - size = 'icon' -}: { - type: 'interactions' | 'notifications' - size?: VariantProps['size'] -}) { - const { t } = useTranslation() - const { - hideUntrustedInteractions, - hideUntrustedNotifications, - updateHideUntrustedInteractions, - updateHideUntrustedNotifications - } = useUserTrust() - - const enabled = type === 'interactions' ? hideUntrustedInteractions : hideUntrustedNotifications - - const updateEnabled = - type === 'interactions' ? updateHideUntrustedInteractions : updateHideUntrustedNotifications - - const typeText = t(type) - - return ( - - - - - - - - {enabled - ? t('Show untrusted {type}', { type: typeText }) - : t('Hide untrusted {type}', { type: typeText })} - - - {enabled - ? t('Currently hiding {type} from untrusted users.', { type: typeText }) - : t('Currently showing all {type}.', { type: typeText })}{' '} - {t('Trusted users include people you follow and people they follow.')}{' '} - {enabled - ? t('Click continue to show all {type}.', { type: typeText }) - : t('Click continue to hide {type} from untrusted users.', { type: typeText })} - - - - {t('Cancel')} - updateEnabled(!enabled)}> - {t('Continue')} - - - - - ) -} diff --git a/src/components/NormalFeed/index.tsx b/src/components/NormalFeed/index.tsx index c25cc4f..5af6d32 100644 --- a/src/components/NormalFeed/index.tsx +++ b/src/components/NormalFeed/index.tsx @@ -1,9 +1,9 @@ import NoteList, { TNoteListRef } from '@/components/NoteList' import Tabs from '@/components/Tabs' +import TrustScoreFilter from '@/components/TrustScoreFilter' import UserAggregationList, { TUserAggregationListRef } from '@/components/UserAggregationList' import { isTouchDevice } from '@/lib/utils' import { useKindFilter } from '@/providers/KindFilterProvider' -import { useUserTrust } from '@/providers/UserTrustProvider' import storage from '@/services/local-storage.service' import { TFeedSubRequest, TNoteListMode } from '@/types' import { useMemo, useRef, useState } from 'react' @@ -25,7 +25,6 @@ export default function NormalFeed({ disable24hMode?: boolean onRefresh?: () => void }) { - const { hideUntrustedNotes } = useUserTrust() const { showKinds } = useKindFilter() const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds) const [listMode, setListMode] = useState(() => storage.getNoteListMode()) @@ -79,6 +78,7 @@ export default function NormalFeed({ }} /> )} + {showKindsFilter && ( diff --git a/src/components/NoteInteractions/index.tsx b/src/components/NoteInteractions/index.tsx index 8fd123d..769a188 100644 --- a/src/components/NoteInteractions/index.tsx +++ b/src/components/NoteInteractions/index.tsx @@ -2,16 +2,17 @@ import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area' import { Separator } from '@/components/ui/separator' 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 RepostList from '../RepostList' +import TrustScoreFilter from '../TrustScoreFilter' import ZapList from '../ZapList' import { Tabs, TTabValue } from './Tabs' export default function NoteInteractions({ event }: { event: Event }) { const [type, setType] = useState('replies') + let list switch (type) { case 'replies': @@ -41,9 +42,7 @@ export default function NoteInteractions({ event }: { event: Event }) { -
- -
+ {list} diff --git a/src/components/NoteList/index.tsx b/src/components/NoteList/index.tsx index 79c04a2..e0f98e0 100644 --- a/src/components/NoteList/index.tsx +++ b/src/components/NoteList/index.tsx @@ -1,5 +1,6 @@ import NewNotesButton from '@/components/NewNotesButton' import { Button } from '@/components/ui/button' +import { SPAMMER_PERCENTILE_THRESHOLD } from '@/constants' import { useInfiniteScroll } from '@/hooks/useInfiniteScroll' import { getEventKey, getKeyFromTag, isMentioningMutedUsers, isReplyNoteEvent } from '@/lib/event' import { tagNameEquals } from '@/lib/tag' @@ -46,7 +47,6 @@ const NoteList = forwardRef< showKinds?: number[] filterMutedNotes?: boolean hideReplies?: boolean - hideUntrustedNotes?: boolean hideSpam?: boolean areAlgoRelays?: boolean showRelayCloseReason?: boolean @@ -61,7 +61,6 @@ const NoteList = forwardRef< showKinds, filterMutedNotes = true, hideReplies = false, - hideUntrustedNotes = false, hideSpam = false, areAlgoRelays = false, showRelayCloseReason = false, @@ -73,7 +72,7 @@ const NoteList = forwardRef< ) => { const { t } = useTranslation() const { startLogin } = useNostr() - const { isUserTrusted, isSpammer } = useUserTrust() + const { isSpammer, meetsMinTrustScore } = useUserTrust() const { mutePubkeySet } = useMuteList() const { hideContentMentioningMutedUsers } = useContentPolicy() const { isEventDeleted } = useDeletedEvent() @@ -106,7 +105,6 @@ const NoteList = forwardRef< if (pinnedEventHexIdSet.has(evt.id)) return true if (isEventDeleted(evt)) return true - if (hideUntrustedNotes && !isUserTrusted(evt.pubkey)) return true if (filterMutedNotes && mutePubkeySet.has(evt.pubkey)) return true if ( filterMutedNotes && @@ -121,7 +119,7 @@ const NoteList = forwardRef< return false }, - [hideUntrustedNotes, mutePubkeySet, JSON.stringify(pinnedEventIds), isEventDeleted, filterFn] + [mutePubkeySet, JSON.stringify(pinnedEventIds), isEventDeleted, filterFn] ) useEffect(() => { @@ -195,7 +193,13 @@ const NoteList = forwardRef< const _filteredNotes = ( await Promise.all( filteredEvents.map(async (evt, i) => { - if (hideSpam && (await isSpammer(evt.pubkey))) { + // Check trust score filter + if ( + !(await meetsMinTrustScore( + evt.pubkey, + hideSpam ? SPAMMER_PERCENTILE_THRESHOLD : undefined + )) + ) { return null } const key = keys[i] @@ -213,7 +217,7 @@ const NoteList = forwardRef< setFiltering(true) processEvents().finally(() => setFiltering(false)) - }, [events, shouldHideEvent, hideReplies, isSpammer, hideSpam]) + }, [events, shouldHideEvent, hideReplies, hideSpam, meetsMinTrustScore]) useEffect(() => { const processNewEvents = async () => { @@ -238,6 +242,10 @@ const NoteList = forwardRef< if (hideSpam && (await isSpammer(evt.pubkey))) { return null } + // Check trust score filter + if (!(await meetsMinTrustScore(evt.pubkey))) { + return null + } return evt }) ) @@ -245,7 +253,7 @@ const NoteList = forwardRef< setFilteredNewEvents(_filteredNotes) } processNewEvents() - }, [newEvents, shouldHideEvent, isSpammer, hideSpam]) + }, [newEvents, shouldHideEvent, isSpammer, hideSpam, meetsMinTrustScore]) const scrollToTop = (behavior: ScrollBehavior = 'instant') => { setTimeout(() => { diff --git a/src/components/NotificationList/NotificationItem/index.tsx b/src/components/NotificationList/NotificationItem/index.tsx index cd68df9..9908640 100644 --- a/src/components/NotificationList/NotificationItem/index.tsx +++ b/src/components/NotificationList/NotificationItem/index.tsx @@ -1,11 +1,12 @@ import { ExtendedKind } from '@/constants' -import { notificationFilter } from '@/lib/notification' +import { isMentioningMutedUsers } from '@/lib/event' +import { tagNameEquals } from '@/lib/tag' import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useMuteList } from '@/providers/MuteListProvider' import { useNostr } from '@/providers/NostrProvider' import { useUserTrust } from '@/providers/UserTrustProvider' import { Event, kinds } from 'nostr-tools' -import { useMemo } from 'react' +import { useEffect, useState } from 'react' import { HighlightNotification } from './HighlightNotification' import { MentionNotification } from './MentionNotification' import { PollResponseNotification } from './PollResponseNotification' @@ -23,22 +24,51 @@ export function NotificationItem({ const { pubkey } = useNostr() const { mutePubkeySet } = useMuteList() const { hideContentMentioningMutedUsers } = useContentPolicy() - const { hideUntrustedNotifications, isUserTrusted } = useUserTrust() - const canShow = useMemo(() => { - return notificationFilter(notification, { - pubkey, - mutePubkeySet, - hideContentMentioningMutedUsers, - hideUntrustedNotifications, - isUserTrusted - }) + const { minTrustScore, meetsMinTrustScore } = useUserTrust() + const [canShow, setCanShow] = useState(false) + + useEffect(() => { + const checkCanShow = async () => { + // Check muted users + if (mutePubkeySet.has(notification.pubkey)) { + setCanShow(false) + return + } + + // Check content mentioning muted users + if (hideContentMentioningMutedUsers && isMentioningMutedUsers(notification, mutePubkeySet)) { + setCanShow(false) + return + } + + // Check trust score + if (!(await meetsMinTrustScore(notification.pubkey))) { + setCanShow(false) + return + } + + // Check reaction target for kind 7 + if (pubkey && notification.kind === kinds.Reaction) { + const targetPubkey = notification.tags.findLast(tagNameEquals('p'))?.[1] + if (targetPubkey !== pubkey) { + setCanShow(false) + return + } + } + + setCanShow(true) + } + + checkCanShow() }, [ notification, + pubkey, mutePubkeySet, hideContentMentioningMutedUsers, - hideUntrustedNotifications, - isUserTrusted + minTrustScore, + meetsMinTrustScore ]) + if (!canShow) return null if (notification.kind === kinds.Reaction) { diff --git a/src/components/ReactionList/index.tsx b/src/components/ReactionList/index.tsx index e506d2a..31483a9 100644 --- a/src/components/ReactionList/index.tsx +++ b/src/components/ReactionList/index.tsx @@ -1,17 +1,18 @@ import { useSecondaryPage } from '@/PageManager' +import { useStuff } from '@/hooks/useStuff' import { useStuffStatsById } from '@/hooks/useStuffStatsById' import { toProfile } from '@/lib/link' import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useUserTrust } from '@/providers/UserTrustProvider' +import { TEmoji } from '@/types' import { Event } from 'nostr-tools' -import { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, 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' -import { useStuff } from '@/hooks/useStuff' const SHOW_COUNT = 20 @@ -19,14 +20,39 @@ export default function ReactionList({ stuff }: { stuff: Event | string }) { const { t } = useTranslation() const { push } = useSecondaryPage() const { isSmallScreen } = useScreenSize() - const { hideUntrustedInteractions, isUserTrusted } = useUserTrust() + const { minTrustScore, meetsMinTrustScore } = useUserTrust() const { stuffKey } = useStuff(stuff) const noteStats = useStuffStatsById(stuffKey) - const filteredLikes = useMemo(() => { - return (noteStats?.likes ?? []) - .filter((like) => !hideUntrustedInteractions || isUserTrusted(like.pubkey)) - .sort((a, b) => b.created_at - a.created_at) - }, [noteStats, stuffKey, hideUntrustedInteractions, isUserTrusted]) + const [filteredLikes, setFilteredLikes] = useState< + Array<{ + id: string + pubkey: string + emoji: string | TEmoji + created_at: number + }> + >([]) + + useEffect(() => { + const filterLikes = async () => { + const likes = noteStats?.likes ?? [] + const filtered: { + id: string + pubkey: string + created_at: number + emoji: string | TEmoji + }[] = [] + await Promise.all( + likes.map(async (like) => { + if (await meetsMinTrustScore(like.pubkey)) { + filtered.push(like) + } + }) + ) + filtered.sort((a, b) => b.created_at - a.created_at) + setFilteredLikes(filtered) + } + filterLikes() + }, [noteStats, stuffKey, minTrustScore, meetsMinTrustScore]) const [showCount, setShowCount] = useState(SHOW_COUNT) const bottomRef = useRef(null) diff --git a/src/components/RelayInfo/RelayReviewsPreview.tsx b/src/components/RelayInfo/RelayReviewsPreview.tsx index b43f7a0..1b016b4 100644 --- a/src/components/RelayInfo/RelayReviewsPreview.tsx +++ b/src/components/RelayInfo/RelayReviewsPreview.tsx @@ -29,7 +29,7 @@ export default function RelayReviewsPreview({ relayUrl }: { relayUrl: string }) const { t } = useTranslation() const { push } = useSecondaryPage() const { pubkey, checkLogin } = useNostr() - const { hideUntrustedNotes, isUserTrusted, isSpammer } = useUserTrust() + const { isSpammer } = useUserTrust() const { mutePubkeySet } = useMuteList() const [showEditor, setShowEditor] = useState(false) const [myReview, setMyReview] = useState(null) @@ -103,7 +103,7 @@ export default function RelayReviewsPreview({ relayUrl }: { relayUrl: string }) setInitialized(true) } init() - }, [relayUrl, pubkey, mutePubkeySet, hideUntrustedNotes, isUserTrusted]) + }, [relayUrl, pubkey, mutePubkeySet]) const handleReviewed = (evt: NostrEvent) => { setMyReview(evt) diff --git a/src/components/ReplyNote/index.tsx b/src/components/ReplyNote/index.tsx index b1ed253..e6946dd 100644 --- a/src/components/ReplyNote/index.tsx +++ b/src/components/ReplyNote/index.tsx @@ -10,7 +10,7 @@ import { useMuteList } from '@/providers/MuteListProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useUserTrust } from '@/providers/UserTrustProvider' import { Event } from 'nostr-tools' -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import ClientTag from '../ClientTag' import Collapsible from '../Collapsible' @@ -42,11 +42,13 @@ export default function ReplyNote({ const { isSmallScreen } = useScreenSize() const { push } = useSecondaryPage() const { mutePubkeySet } = useMuteList() - const { hideUntrustedInteractions, isUserTrusted } = useUserTrust() + const { minTrustScore, meetsMinTrustScore } = useUserTrust() const { hideContentMentioningMutedUsers } = useContentPolicy() const eventKey = useMemo(() => getEventKey(event), [event]) const replies = useThread(eventKey) const [showMuted, setShowMuted] = useState(false) + const [hasReplies, setHasReplies] = useState(false) + const show = useMemo(() => { if (showMuted) { return true @@ -59,24 +61,32 @@ export default function ReplyNote({ } return true }, [showMuted, mutePubkeySet, event, hideContentMentioningMutedUsers]) - const hasReplies = useMemo(() => { - if (!replies || replies.length === 0) { - return false + + useEffect(() => { + const checkHasReplies = async () => { + if (!replies || replies.length === 0) { + setHasReplies(false) + return + } + + for (const reply of replies) { + if (mutePubkeySet.has(reply.pubkey)) { + continue + } + if (hideContentMentioningMutedUsers && isMentioningMutedUsers(reply, mutePubkeySet)) { + continue + } + if (!(await meetsMinTrustScore(reply.pubkey))) { + continue + } + setHasReplies(true) + return + } + setHasReplies(false) } - for (const reply of replies) { - if (hideUntrustedInteractions && !isUserTrusted(reply.pubkey)) { - continue - } - if (mutePubkeySet.has(reply.pubkey)) { - continue - } - if (hideContentMentioningMutedUsers && isMentioningMutedUsers(reply, mutePubkeySet)) { - continue - } - return true - } - }, [replies]) + checkHasReplies() + }, [replies, minTrustScore, meetsMinTrustScore, mutePubkeySet, hideContentMentioningMutedUsers]) return (
{ - const replyKeySet = new Set() - const replyEvents: NostrEvent[] = [] - - let parentKeys = [parentKey] - while (parentKeys.length > 0) { - const events = parentKeys.flatMap((key) => allThreads.get(key) ?? []) - 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 = allThreads.get(replyKey) - // If the reply is not trusted and there are no trusted replies for this reply, skip rendering - if ( - !repliesForThisReply || - repliesForThisReply.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, - allThreads, - mutePubkeySet, - hideContentMentioningMutedUsers, - hideUntrustedInteractions - ]) + const { replies } = useFilteredAllReplies(parentKey) const [highlightReplyKey, setHighlightReplyKey] = useState(undefined) const replyRefs = useRef>({}) diff --git a/src/components/ReplyNoteList/index.tsx b/src/components/ReplyNoteList/index.tsx index 6cb19b7..f231eb6 100644 --- a/src/components/ReplyNoteList/index.tsx +++ b/src/components/ReplyNoteList/index.tsx @@ -1,10 +1,7 @@ +import { useFilteredReplies } from '@/hooks' import { useInfiniteScroll } from '@/hooks/useInfiniteScroll' import { useStuff } from '@/hooks/useStuff' -import { useAllDescendantThreads } from '@/hooks/useThread' -import { getEventKey, isMentioningMutedUsers } from '@/lib/event' -import { useContentPolicy } from '@/providers/ContentPolicyProvider' -import { useMuteList } from '@/providers/MuteListProvider' -import { useUserTrust } from '@/providers/UserTrustProvider' +import { getEventKey } from '@/lib/event' import threadService from '@/services/thread.service' import { Event as NEvent } from 'nostr-tools' import { useCallback, useEffect, useMemo, useState } from 'react' @@ -18,47 +15,9 @@ const SHOW_COUNT = 10 export default function ReplyNoteList({ stuff }: { stuff: NEvent | string }) { const { t } = useTranslation() - const { hideUntrustedInteractions, isUserTrusted } = useUserTrust() - const { mutePubkeySet } = useMuteList() - const { hideContentMentioningMutedUsers } = useContentPolicy() const { stuffKey } = useStuff(stuff) - const allThreads = useAllDescendantThreads(stuffKey) const [initialLoading, setInitialLoading] = useState(false) - - const replies = useMemo(() => { - const replyKeySet = new Set() - const thread = allThreads.get(stuffKey) || [] - const replyEvents = thread.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 = allThreads.get(replyKey) - // If the reply is not trusted and there are no trusted replies for this reply, skip rendering - if ( - !repliesForThisReply || - repliesForThisReply.every((evt) => !isUserTrusted(evt.pubkey)) - ) { - return false - } - } - - replyKeySet.add(key) - return true - }) - return replyEvents.sort((a, b) => b.created_at - a.created_at) - }, [ - stuffKey, - allThreads, - mutePubkeySet, - hideContentMentioningMutedUsers, - hideUntrustedInteractions, - isUserTrusted - ]) + const { replies } = useFilteredReplies(stuffKey) // Initial subscription useEffect(() => { diff --git a/src/components/RepostList/index.tsx b/src/components/RepostList/index.tsx index e1c5170..b71b983 100644 --- a/src/components/RepostList/index.tsx +++ b/src/components/RepostList/index.tsx @@ -6,7 +6,7 @@ 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 { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { FormattedTimestamp } from '../FormattedTimestamp' import Nip05 from '../Nip05' @@ -19,13 +19,33 @@ export default function RepostList({ event }: { event: Event }) { const { t } = useTranslation() const { push } = useSecondaryPage() const { isSmallScreen } = useScreenSize() - const { hideUntrustedInteractions, isUserTrusted } = useUserTrust() + const { minTrustScore, meetsMinTrustScore } = useUserTrust() const noteStats = useStuffStatsById(getEventKey(event)) - 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 [filteredReposts, setFilteredReposts] = useState< + Array<{ id: string; pubkey: string; created_at: number }> + >([]) + + useEffect(() => { + const filterReposts = async () => { + const reposts = noteStats?.reposts ?? [] + const filtered = ( + await Promise.all( + reposts.map(async (repost) => { + if (await meetsMinTrustScore(repost.pubkey)) { + return repost + } + }) + ) + ).filter(Boolean) as { + id: string + pubkey: string + created_at: number + }[] + filtered.sort((a, b) => b.created_at - a.created_at) + setFilteredReposts(filtered) + } + filterReposts() + }, [noteStats, event.id, minTrustScore, meetsMinTrustScore]) const [showCount, setShowCount] = useState(SHOW_COUNT) const bottomRef = useRef(null) diff --git a/src/components/StuffStats/LikeButton.tsx b/src/components/StuffStats/LikeButton.tsx index 8848f84..f7f5376 100644 --- a/src/components/StuffStats/LikeButton.tsx +++ b/src/components/StuffStats/LikeButton.tsx @@ -27,23 +27,38 @@ export default function LikeButton({ stuff }: { stuff: Event | string }) { const { t } = useTranslation() const { isSmallScreen } = useScreenSize() const { pubkey, publish, checkLogin } = useNostr() - const { hideUntrustedInteractions, isUserTrusted } = useUserTrust() + const { meetsMinTrustScore } = useUserTrust() const { quickReaction, quickReactionEmoji } = useUserPreferences() const { event, externalContent, stuffKey } = useStuff(stuff) const [liking, setLiking] = useState(false) const [isEmojiReactionsOpen, setIsEmojiReactionsOpen] = useState(false) const [isPickerOpen, setIsPickerOpen] = useState(false) + const [likeCount, setLikeCount] = useState(0) const longPressTimerRef = useRef(null) const isLongPressRef = useRef(false) const noteStats = useStuffStatsById(stuffKey) - const { myLastEmoji, likeCount } = useMemo(() => { + const myLastEmoji = useMemo(() => { const stats = noteStats || {} const myLike = stats.likes?.find((like) => like.pubkey === pubkey) - const likes = hideUntrustedInteractions - ? stats.likes?.filter((like) => isUserTrusted(like.pubkey)) - : stats.likes - return { myLastEmoji: myLike?.emoji, likeCount: likes?.length } - }, [noteStats, pubkey, hideUntrustedInteractions]) + return myLike?.emoji + }, [noteStats, pubkey]) + + useEffect(() => { + const filterLikes = async () => { + const stats = noteStats || {} + const likes = stats.likes || [] + let count = 0 + await Promise.all( + likes.map(async (like) => { + if (await meetsMinTrustScore(like.pubkey)) { + count++ + } + }) + ) + setLikeCount(count) + } + filterLikes() + }, [noteStats, meetsMinTrustScore]) useEffect(() => { setTimeout(() => setIsPickerOpen(false), 100) diff --git a/src/components/StuffStats/ReplyButton.tsx b/src/components/StuffStats/ReplyButton.tsx index 53e8556..d41b777 100644 --- a/src/components/StuffStats/ReplyButton.tsx +++ b/src/components/StuffStats/ReplyButton.tsx @@ -1,55 +1,19 @@ +import { useFilteredAllReplies } from '@/hooks' import { useStuff } from '@/hooks/useStuff' -import { useAllDescendantThreads } from '@/hooks/useThread' -import { getEventKey, isMentioningMutedUsers } from '@/lib/event' import { cn } from '@/lib/utils' -import { useContentPolicy } from '@/providers/ContentPolicyProvider' -import { useMuteList } from '@/providers/MuteListProvider' import { useNostr } from '@/providers/NostrProvider' -import { useUserTrust } from '@/providers/UserTrustProvider' import { MessageCircle } from 'lucide-react' import { Event } from 'nostr-tools' -import { useMemo, useState } from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import PostEditor from '../PostEditor' import { formatCount } from './utils' export default function ReplyButton({ stuff }: { stuff: Event | string }) { const { t } = useTranslation() - const { pubkey, checkLogin } = useNostr() - const { event, stuffKey } = useStuff(stuff) - const allThreads = useAllDescendantThreads(stuffKey) - const { hideUntrustedInteractions, isUserTrusted } = useUserTrust() - const { mutePubkeySet } = useMuteList() - const { hideContentMentioningMutedUsers } = useContentPolicy() - const { replyCount, hasReplied } = useMemo(() => { - const hasReplied = pubkey - ? allThreads.get(stuffKey)?.some((evt) => evt.pubkey === pubkey) - : false - - let replyCount = 0 - const replies = [...(allThreads.get(stuffKey) ?? [])] - while (replies.length > 0) { - const reply = replies.pop() - if (!reply) break - - const replyKey = getEventKey(reply) - const nestedReplies = allThreads.get(replyKey) ?? [] - replies.push(...nestedReplies) - - if (hideUntrustedInteractions && !isUserTrusted(reply.pubkey)) { - continue - } - if (mutePubkeySet.has(reply.pubkey)) { - continue - } - if (hideContentMentioningMutedUsers && isMentioningMutedUsers(reply, mutePubkeySet)) { - continue - } - replyCount++ - } - - return { replyCount, hasReplied } - }, [allThreads, event, stuffKey, hideUntrustedInteractions]) + const { checkLogin } = useNostr() + const { stuffKey } = useStuff(stuff) + const { replies, hasReplied } = useFilteredAllReplies(stuffKey) const [open, setOpen] = useState(false) return ( @@ -68,7 +32,7 @@ export default function ReplyButton({ stuff }: { stuff: Event | string }) { title={t('Reply')} > - {!!replyCount &&
{formatCount(replyCount)}
} + {!!replies.length &&
{formatCount(replies.length)}
} diff --git a/src/components/StuffStats/RepostButton.tsx b/src/components/StuffStats/RepostButton.tsx index 0ca3be4..7983a33 100644 --- a/src/components/StuffStats/RepostButton.tsx +++ b/src/components/StuffStats/RepostButton.tsx @@ -17,7 +17,7 @@ import { useUserTrust } from '@/providers/UserTrustProvider' import stuffStatsService from '@/services/stuff-stats.service' import { Loader, PencilLine, Repeat } from 'lucide-react' import { Event } from 'nostr-tools' -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import PostEditor from '../PostEditor' import { formatCount } from './utils' @@ -25,24 +25,38 @@ import { formatCount } from './utils' export default function RepostButton({ stuff }: { stuff: Event | string }) { const { t } = useTranslation() const { isSmallScreen } = useScreenSize() - const { hideUntrustedInteractions, isUserTrusted } = useUserTrust() + const { meetsMinTrustScore } = useUserTrust() const { publish, checkLogin, pubkey } = useNostr() const { event, stuffKey } = useStuff(stuff) const noteStats = useStuffStatsById(stuffKey) const [reposting, setReposting] = useState(false) const [isPostDialogOpen, setIsPostDialogOpen] = useState(false) const [isDrawerOpen, setIsDrawerOpen] = useState(false) - const { repostCount, hasReposted } = useMemo(() => { - // external content - if (!event) return { repostCount: 0, hasReposted: false } + const [repostCount, setRepostCount] = useState(0) + const hasReposted = useMemo(() => { + return pubkey ? noteStats?.repostPubkeySet?.has(pubkey) : false + }, [noteStats, pubkey]) - return { - repostCount: hideUntrustedInteractions - ? noteStats?.reposts?.filter((repost) => isUserTrusted(repost.pubkey)).length - : noteStats?.reposts?.length, - hasReposted: pubkey ? noteStats?.repostPubkeySet?.has(pubkey) : false + useEffect(() => { + const filterReposts = async () => { + if (!event) { + setRepostCount(0) + return + } + + const reposts = noteStats?.reposts || [] + let count = 0 + await Promise.all( + reposts.map(async (repost) => { + if (await meetsMinTrustScore(repost.pubkey)) { + count++ + } + }) + ) + setRepostCount(count) } - }, [noteStats, event, hideUntrustedInteractions]) + filterReposts() + }, [noteStats, event, meetsMinTrustScore]) const canRepost = !hasReposted && !reposting && !!event const repost = async () => { diff --git a/src/components/TrustScoreFilter/index.tsx b/src/components/TrustScoreFilter/index.tsx new file mode 100644 index 0000000..b6de47e --- /dev/null +++ b/src/components/TrustScoreFilter/index.tsx @@ -0,0 +1,171 @@ +import { Button } from '@/components/ui/button' +import { Drawer, DrawerContent, DrawerHeader, DrawerTrigger } from '@/components/ui/drawer' +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { Slider } from '@/components/ui/slider' +import { cn } from '@/lib/utils' +import { useScreenSize } from '@/providers/ScreenSizeProvider' +import { useUserTrust } from '@/providers/UserTrustProvider' +import { Shield, ShieldCheck } from 'lucide-react' +import { useEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' + +const TRUST_LEVELS = [ + { value: 0, label: 'trust-filter.off' }, + { value: 60, label: 'trust-filter.low' }, + { value: 80, label: 'trust-filter.medium' }, + { value: 90, label: 'trust-filter.high' }, + { value: 100, label: 'trust-filter.wot' } +] + +function getDescription(score: number, t: (key: string, options?: any) => string) { + if (score === 0) { + return t('trust-filter.show-all-content') + } else if (score === 100) { + return t('trust-filter.only-show-wot') + } else { + return t('trust-filter.hide-bottom-percent', { score }) + } +} + +export default function TrustScoreFilter() { + const { t } = useTranslation() + const { isSmallScreen } = useScreenSize() + const { minTrustScore, updateMinTrustScore } = useUserTrust() + const [open, setOpen] = useState(false) + const [temporaryScore, setTemporaryScore] = useState(minTrustScore) + const debounceTimerRef = useRef(null) + + useEffect(() => { + setTemporaryScore(minTrustScore) + }, [minTrustScore]) + + // Debounced update function + const handleScoreChange = (newScore: number) => { + setTemporaryScore(newScore) + + // Clear existing timer + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current) + } + + // Set new timer for debounced update + debounceTimerRef.current = setTimeout(() => { + updateMinTrustScore(newScore) + }, 300) // 300ms debounce delay + } + + // Cleanup timer on unmount + useEffect(() => { + return () => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current) + } + } + }, []) + + const description = getDescription(temporaryScore, t) + + const trigger = ( + + ) + + const content = ( + <> + {/* Slider */} +
+
+ + {t('trust-filter.filter-threshold')} + + + {temporaryScore === 0 ? t('trust-filter.off') : `${temporaryScore}%`} + +
+ handleScoreChange(value)} + min={0} + max={100} + step={5} + className="w-full" + /> +
+ + {/* Quick Presets */} +
+
{t('trust-filter.quick-presets')}
+
+ {TRUST_LEVELS.map((level) => ( + + ))} +
+
+ + {/* Description */} +
+
{description}
+
+ {t('trust-filter.trust-score-description')} +
+
+ + ) + + if (isSmallScreen) { + return ( + <> + {trigger} + + + + + {t('trust-filter.title')} + +
{content}
+
+
+ + ) + } + + return ( + + {trigger} + + {content} + + + ) +} diff --git a/src/components/UserAggregationList/index.tsx b/src/components/UserAggregationList/index.tsx index 50d053b..c0954b3 100644 --- a/src/components/UserAggregationList/index.tsx +++ b/src/components/UserAggregationList/index.tsx @@ -34,6 +34,7 @@ import PullToRefresh from 'react-simple-pull-to-refresh' import { toast } from 'sonner' import { LoadingBar } from '../LoadingBar' import NewNotesButton from '../NewNotesButton' +import TrustScoreBadge from '../TrustScoreBadge' const LIMIT = 500 const SHOW_COUNT = 20 @@ -66,14 +67,16 @@ const UserAggregationList = forwardRef< const { t } = useTranslation() const { pubkey: currentPubkey, startLogin } = useNostr() const { push } = useSecondaryPage() - const { hideUntrustedNotes, isUserTrusted } = useUserTrust() const { mutePubkeySet } = useMuteList() const { pinnedPubkeySet } = usePinnedUsers() + const { meetsMinTrustScore } = useUserTrust() const { hideContentMentioningMutedUsers } = useContentPolicy() const { isEventDeleted } = useDeletedEvent() const [since, setSince] = useState(() => dayjs().subtract(1, 'day').unix()) const [events, setEvents] = useState([]) + const [filteredEvents, setFilteredEvents] = useState([]) const [newEvents, setNewEvents] = useState([]) + const [filteredNewEvents, setFilteredNewEvents] = useState([]) const [newEventPubkeys, setNewEventPubkeys] = useState>(new Set()) const [timelineKey, setTimelineKey] = useState(undefined) const [loading, setLoading] = useState(true) @@ -233,31 +236,40 @@ const UserAggregationList = forwardRef< return () => clearTimeout(timeout) }, [loading]) - const shouldHideEvent = useCallback( - (evt: Event) => { - if (evt.pubkey === currentPubkey) return true - if (isEventDeleted(evt)) return true - if (hideUntrustedNotes && !isUserTrusted(evt.pubkey)) return true - if (filterMutedNotes && mutePubkeySet.has(evt.pubkey)) return true - if ( - filterMutedNotes && - hideContentMentioningMutedUsers && - isMentioningMutedUsers(evt, mutePubkeySet) - ) { - return true - } + const filterEvents = useCallback( + async (events: Event[]) => { + const results = await Promise.allSettled( + events.map(async (evt) => { + if (evt.pubkey === currentPubkey) return null + if (evt.created_at < since) return null + if (isEventDeleted(evt)) return null + if (filterMutedNotes && mutePubkeySet.has(evt.pubkey)) return null + if ( + filterMutedNotes && + hideContentMentioningMutedUsers && + isMentioningMutedUsers(evt, mutePubkeySet) + ) { + return null + } + if (!(await meetsMinTrustScore(evt.pubkey))) { + return null + } - return false + return evt + }) + ) + return results + .filter((res) => res.status === 'fulfilled' && res.value !== null) + .map((res) => (res as PromiseFulfilledResult).value) }, [ - hideUntrustedNotes, mutePubkeySet, isEventDeleted, currentPubkey, filterMutedNotes, - isUserTrusted, hideContentMentioningMutedUsers, - isMentioningMutedUsers + isMentioningMutedUsers, + meetsMinTrustScore ] ) @@ -265,13 +277,17 @@ const UserAggregationList = forwardRef< return dayjs().diff(dayjs.unix(since), 'day') }, [since]) - const filteredEvents = useMemo(() => { - return events.filter((evt) => evt.created_at >= since && !shouldHideEvent(evt)) - }, [events, since, shouldHideEvent]) + useEffect(() => { + filterEvents(events).then((filtered) => { + setFilteredEvents(filtered) + }) + }, [events, filterEvents]) - const filteredNewEvents = useMemo(() => { - return newEvents.filter((evt) => evt.created_at >= since && !shouldHideEvent(evt)) - }, [newEvents, since, shouldHideEvent]) + useEffect(() => { + filterEvents(newEvents).then((filtered) => { + setFilteredNewEvents(filtered) + }) + }, [newEvents, filterEvents]) const aggregations = useMemo(() => { const aggs = userAggregationService.aggregateByUser(filteredEvents) @@ -528,25 +544,28 @@ function UserAggregationItem({ )}
- {supportTouch ? ( - - ) : ( - - )} +
+ {supportTouch ? ( + + ) : ( + + )} + +
([]) + const [hasReplied, setHasReplied] = useState(false) + + useEffect(() => { + const filterReplies = async () => { + const replyKeySet = new Set() + const thread = allThreads.get(stuffKey) || [] + const filtered: NostrEvent[] = [] + + await Promise.all( + thread.map(async (evt) => { + const key = getEventKey(evt) + if (replyKeySet.has(key)) return + replyKeySet.add(key) + + if (mutePubkeySet.has(evt.pubkey)) return + if (hideContentMentioningMutedUsers && isMentioningMutedUsers(evt, mutePubkeySet)) return + + const meetsTrust = await meetsMinTrustScore(evt.pubkey) + if (!meetsTrust) { + const replyKey = getEventKey(evt) + const repliesForThisReply = allThreads.get(replyKey) + // If the reply is not trusted, check if there are any trusted replies for this reply + if (repliesForThisReply && repliesForThisReply.length > 0) { + let hasTrustedReply = false + for (const reply of repliesForThisReply) { + if (await meetsMinTrustScore(reply.pubkey)) { + hasTrustedReply = true + break + } + } + if (!hasTrustedReply) return + } else { + return + } + } + filtered.push(evt) + }) + ) + + filtered.sort((a, b) => b.created_at - a.created_at) + setReplies(filtered) + } + + filterReplies() + }, [ + stuffKey, + allThreads, + mutePubkeySet, + hideContentMentioningMutedUsers, + minTrustScore, + meetsMinTrustScore + ]) + + useEffect(() => { + let replied = false + for (const reply of replies) { + if (reply.pubkey === pubkey) { + replied = true + break + } + } + setHasReplied(replied) + }, [replies, pubkey]) + + return { replies, hasReplied } +} + +export function useFilteredAllReplies(stuffKey: string) { + const { pubkey } = useNostr() + const allThreads = useAllDescendantThreads(stuffKey) + const { minTrustScore, meetsMinTrustScore } = useUserTrust() + const { mutePubkeySet } = useMuteList() + const { hideContentMentioningMutedUsers } = useContentPolicy() + const [replies, setReplies] = useState([]) + const [hasReplied, setHasReplied] = useState(false) + + useEffect(() => { + const filterReplies = async () => { + const replyKeySet = new Set() + const replyEvents: NostrEvent[] = [] + + let parentKeys = [stuffKey] + while (parentKeys.length > 0) { + const events = parentKeys.flatMap((key) => allThreads.get(key) ?? []) + await Promise.all( + events.map(async (evt) => { + const key = getEventKey(evt) + if (replyKeySet.has(key)) return + replyKeySet.add(key) + + if (mutePubkeySet.has(evt.pubkey)) return + if (hideContentMentioningMutedUsers && isMentioningMutedUsers(evt, mutePubkeySet)) + return + + const meetsTrust = await meetsMinTrustScore(evt.pubkey) + if (!meetsTrust) { + const replyKey = getEventKey(evt) + const repliesForThisReply = allThreads.get(replyKey) + // If the reply is not trusted, check if there are any trusted replies for this reply + if (repliesForThisReply && repliesForThisReply.length > 0) { + let hasTrustedReply = false + for (const reply of repliesForThisReply) { + if (await meetsMinTrustScore(reply.pubkey)) { + hasTrustedReply = true + break + } + } + if (!hasTrustedReply) return + } else { + return + } + } + + replyEvents.push(evt) + }) + ) + parentKeys = events.map((evt) => getEventKey(evt)) + } + setReplies(replyEvents.sort((a, b) => a.created_at - b.created_at)) + } + + filterReplies() + }, [ + stuffKey, + allThreads, + mutePubkeySet, + hideContentMentioningMutedUsers, + minTrustScore, + meetsMinTrustScore + ]) + + useEffect(() => { + let replied = false + for (const reply of replies) { + if (reply.pubkey === pubkey) { + replied = true + break + } + } + setHasReplied(replied) + }, [replies, pubkey]) + + return { replies, hasReplied } +} diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index 6486ac3..2e654dd 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -638,6 +638,19 @@ export default { 'Enter Password': 'أدخل كلمة المرور', Password: 'كلمة المرور', Confirm: 'تأكيد', + 'trust-filter.title': 'مرشح درجة الثقة', + 'trust-filter.off': 'إيقاف', + 'trust-filter.low': 'منخفض', + 'trust-filter.medium': 'متوسط', + 'trust-filter.high': 'عالي', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'عتبة التصفية', + 'trust-filter.quick-presets': 'إعدادات سريعة', + 'trust-filter.show-all-content': 'إظهار جميع المحتويات', + 'trust-filter.only-show-wot': 'إظهار شبكة الثقة الخاصة بك فقط (المتابَعون + متابَعوهم)', + 'trust-filter.hide-bottom-percent': + 'تصفية أدنى {{score}}٪ من المستخدمين حسب تصنيف الثقة', + 'trust-filter.trust-score-description': 'تصنف درجة الثقة المستخدمين حسب النسبة المئوية للسمعة', 'Auto-load profile pictures': 'تحميل صور الملف الشخصي تلقائيًا' } } diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index 05c118f..5b3da2b 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -659,6 +659,20 @@ export default { 'Enter Password': 'Passwort eingeben', Password: 'Passwort', Confirm: 'Bestätigen', + 'trust-filter.title': 'Vertrauenswert-Filter', + 'trust-filter.off': 'Aus', + 'trust-filter.low': 'Niedrig', + 'trust-filter.medium': 'Mittel', + 'trust-filter.high': 'Hoch', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'Filterschwelle', + 'trust-filter.quick-presets': 'Schnellvoreinstellungen', + 'trust-filter.show-all-content': 'Alle Inhalte anzeigen', + 'trust-filter.only-show-wot': 'Nur Ihr Vertrauensnetzwerk anzeigen (Folgende + deren Folgende)', + 'trust-filter.hide-bottom-percent': + 'Untere {{score}}% der Benutzer nach Vertrauensrang filtern', + 'trust-filter.trust-score-description': + 'Der Vertrauenswert ordnet Benutzer nach Reputationsperzentil', 'Auto-load profile pictures': 'Profilbilder automatisch laden' } } diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index c05add5..7cba2d9 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -643,6 +643,19 @@ export default { 'Enter Password': 'Enter Password', Password: 'Password', Confirm: 'Confirm', + 'trust-filter.title': 'Trust Score Filter', + 'trust-filter.off': 'Off', + 'trust-filter.low': 'Low', + 'trust-filter.medium': 'Medium', + 'trust-filter.high': 'High', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'Filter Threshold', + 'trust-filter.quick-presets': 'Quick presets', + 'trust-filter.show-all-content': 'Show all content', + 'trust-filter.only-show-wot': 'Only show your Web of Trust (follows + their follows)', + 'trust-filter.hide-bottom-percent': + 'Filter out bottom {{score}}% of users by trust rank', + 'trust-filter.trust-score-description': 'Trust score ranks users by reputation percentile', 'Auto-load profile pictures': 'Auto-load profile pictures' } } diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index f668d40..dcf4e8d 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -653,6 +653,20 @@ export default { 'Enter Password': 'Ingresar contraseña', Password: 'Contraseña', Confirm: 'Confirmar', + 'trust-filter.title': 'Filtro de puntuación de confianza', + 'trust-filter.off': 'Desactivado', + 'trust-filter.low': 'Bajo', + 'trust-filter.medium': 'Medio', + 'trust-filter.high': 'Alto', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'Umbral de filtro', + 'trust-filter.quick-presets': 'Ajustes rápidos', + 'trust-filter.show-all-content': 'Mostrar todo el contenido', + 'trust-filter.only-show-wot': 'Mostrar solo tu red de confianza (seguidos + sus seguidos)', + 'trust-filter.hide-bottom-percent': + 'Filtrar el {{score}}% inferior de usuarios por clasificación de confianza', + 'trust-filter.trust-score-description': + 'La puntuación de confianza clasifica a los usuarios por percentil de reputación', 'Auto-load profile pictures': 'Cargar imágenes de perfil automáticamente' } } diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts index fffd33c..7d27b2e 100644 --- a/src/i18n/locales/fa.ts +++ b/src/i18n/locales/fa.ts @@ -648,6 +648,21 @@ export default { 'Enter Password': 'رمز عبور را وارد کنید', Password: 'رمز عبور', Confirm: 'تأیید', + 'trust-filter.title': 'فیلتر امتیاز اعتماد', + 'trust-filter.off': 'خاموش', + 'trust-filter.low': 'پایین', + 'trust-filter.medium': 'متوسط', + 'trust-filter.high': 'بالا', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'آستانه فیلتر', + 'trust-filter.quick-presets': 'تنظیمات سریع', + 'trust-filter.show-all-content': 'نمایش همه محتوا', + 'trust-filter.only-show-wot': + 'فقط شبکه اعتماد شما را نشان دهید (دنبال‌شوندگان + دنبال‌شوندگان آنها)', + 'trust-filter.hide-bottom-percent': + 'فیلتر کردن {{score}}٪ پایین‌ترین کاربران بر اساس رتبه اعتماد', + 'trust-filter.trust-score-description': + 'امتیاز اعتماد کاربران را بر اساس صدک شهرت رتبه‌بندی می‌کند', 'Auto-load profile pictures': 'بارگذاری خودکار تصاویر پروفایل' } } diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index 64d69df..61c40f9 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -656,6 +656,21 @@ export default { 'Enter Password': 'Entrer le mot de passe', Password: 'Mot de passe', Confirm: 'Confirmer', + 'trust-filter.title': 'Filtre de score de confiance', + 'trust-filter.off': 'Désactivé', + 'trust-filter.low': 'Faible', + 'trust-filter.medium': 'Moyen', + 'trust-filter.high': 'Élevé', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'Seuil de filtrage', + 'trust-filter.quick-presets': 'Préréglages rapides', + 'trust-filter.show-all-content': 'Afficher tout le contenu', + 'trust-filter.only-show-wot': + 'Afficher uniquement votre réseau de confiance (abonnements + leurs abonnements)', + 'trust-filter.hide-bottom-percent': + 'Filtrer les {{score}}% inférieurs des utilisateurs par classement de confiance', + 'trust-filter.trust-score-description': + 'Le score de confiance classe les utilisateurs par percentile de réputation', 'Auto-load profile pictures': 'Charger les images de profil automatiquement' } } diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index 0a4db43..31cc1dd 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -649,6 +649,20 @@ export default { 'Enter Password': 'पासवर्ड दर्ज करें', Password: 'पासवर्ड', Confirm: 'पुष्टि करें', + 'trust-filter.title': 'विश्वास स्कोर फ़िल्टर', + 'trust-filter.off': 'बंद', + 'trust-filter.low': 'कम', + 'trust-filter.medium': 'मध्यम', + 'trust-filter.high': 'उच्च', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'फ़िल्टर सीमा', + 'trust-filter.quick-presets': 'त्वरित प्रीसेट', + 'trust-filter.show-all-content': 'सभी सामग्री दिखाएं', + 'trust-filter.only-show-wot': 'केवल अपना विश्वास नेटवर्क दिखाएं (फ़ॉलो + उनके फ़ॉलो)', + 'trust-filter.hide-bottom-percent': + 'विश्वास रैंक द्वारा निचले {{score}}% उपयोगकर्ताओं को फ़िल्टर करें', + 'trust-filter.trust-score-description': + 'विश्वास स्कोर प्रतिष्ठा प्रतिशतक द्वारा उपयोगकर्ताओं को रैंक करता है', 'Auto-load profile pictures': 'प्रोफ़ाइल चित्र स्वतः लोड करें' } } diff --git a/src/i18n/locales/hu.ts b/src/i18n/locales/hu.ts index 53f72b6..67c6b4f 100644 --- a/src/i18n/locales/hu.ts +++ b/src/i18n/locales/hu.ts @@ -641,6 +641,21 @@ export default { 'Enter Password': 'Jelszó megadása', Password: 'Jelszó', Confirm: 'Megerősítés', + 'trust-filter.title': 'Bizalmi pontszám szűrő', + 'trust-filter.off': 'Ki', + 'trust-filter.low': 'Alacsony', + 'trust-filter.medium': 'Közepes', + 'trust-filter.high': 'Magas', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'Szűrési küszöb', + 'trust-filter.quick-presets': 'Gyors beállítások', + 'trust-filter.show-all-content': 'Minden tartalom megjelenítése', + 'trust-filter.only-show-wot': + 'Csak a bizalmi hálózatod megjelenítése (követettek + követetteik)', + 'trust-filter.hide-bottom-percent': + 'Alsó {{score}}% felhasználók szűrése bizalmi rangsor szerint', + 'trust-filter.trust-score-description': + 'A bizalmi pontszám a felhasználókat hírnév percentilis szerint rangsorolja', 'Auto-load profile pictures': 'Profilképek automatikus betöltése' } } diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts index 691122a..589dd6c 100644 --- a/src/i18n/locales/it.ts +++ b/src/i18n/locales/it.ts @@ -653,6 +653,20 @@ export default { 'Enter Password': 'Inserisci password', Password: 'Password', Confirm: 'Conferma', + 'trust-filter.title': 'Filtro punteggio di fiducia', + 'trust-filter.off': 'Disattivato', + 'trust-filter.low': 'Basso', + 'trust-filter.medium': 'Medio', + 'trust-filter.high': 'Alto', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'Soglia di filtro', + 'trust-filter.quick-presets': 'Preimpostazioni rapide', + 'trust-filter.show-all-content': 'Mostra tutti i contenuti', + 'trust-filter.only-show-wot': 'Mostra solo la tua rete di fiducia (seguiti + i loro seguiti)', + 'trust-filter.hide-bottom-percent': + 'Filtra il {{score}}% inferiore degli utenti per classifica di fiducia', + 'trust-filter.trust-score-description': + 'Il punteggio di fiducia classifica gli utenti per percentile di reputazione', 'Auto-load profile pictures': 'Caricamento automatico immagini di profilo' } } diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index 95e810f..c78a442 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -647,6 +647,21 @@ export default { 'Enter Password': 'パスワードを入力', Password: 'パスワード', Confirm: '確認', + 'trust-filter.title': '信頼スコアフィルター', + 'trust-filter.off': 'オフ', + 'trust-filter.low': '低', + 'trust-filter.medium': '中', + 'trust-filter.high': '高', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'フィルター閾値', + 'trust-filter.quick-presets': 'クイックプリセット', + 'trust-filter.show-all-content': 'すべてのコンテンツを表示', + 'trust-filter.only-show-wot': + 'あなたの信頼ネットワークのみを表示(フォロー + フォローのフォロー)', + 'trust-filter.hide-bottom-percent': + '信頼ランク下位 {{score}}% のユーザーをフィルタリング', + 'trust-filter.trust-score-description': + '信頼スコアは評判パーセンタイルでユーザーをランク付けします', 'Auto-load profile pictures': 'プロフィール画像を自動読み込み' } } diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index 3adf98e..3ad0259 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -644,6 +644,18 @@ export default { 'Enter Password': '비밀번호 입력', Password: '비밀번호', Confirm: '확인', + 'trust-filter.title': '신뢰 점수 필터', + 'trust-filter.off': '끄기', + 'trust-filter.low': '낮음', + 'trust-filter.medium': '중간', + 'trust-filter.high': '높음', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': '필터 임계값', + 'trust-filter.quick-presets': '빠른 프리셋', + 'trust-filter.show-all-content': '모든 콘텐츠 표시', + 'trust-filter.only-show-wot': '신뢰 네트워크만 표시 (팔로우 + 팔로우의 팔로우)', + 'trust-filter.hide-bottom-percent': '신뢰도 하위 {{score}}% 사용자 필터링', + 'trust-filter.trust-score-description': '신뢰 점수는 평판 백분위수로 사용자를 순위 매깁니다', 'Auto-load profile pictures': '프로필 사진 자동 로드' } } diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index d29282b..2f851d7 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -654,6 +654,20 @@ export default { 'Enter Password': 'Wprowadź hasło', Password: 'Hasło', Confirm: 'Potwierdź', + 'trust-filter.title': 'Filtr wyniku zaufania', + 'trust-filter.off': 'Wyłączony', + 'trust-filter.low': 'Niski', + 'trust-filter.medium': 'Średni', + 'trust-filter.high': 'Wysoki', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'Próg filtrowania', + 'trust-filter.quick-presets': 'Szybkie ustawienia', + 'trust-filter.show-all-content': 'Pokaż całą zawartość', + 'trust-filter.only-show-wot': 'Pokaż tylko swoją sieć zaufania (obserwowani + ich obserwowani)', + 'trust-filter.hide-bottom-percent': + 'Filtruj dolne {{score}}% użytkowników według rankingu zaufania', + 'trust-filter.trust-score-description': + 'Wynik zaufania klasyfikuje użytkowników według percentyla reputacji', 'Auto-load profile pictures': 'Automatyczne ładowanie zdjęć profilowych' } } diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts index c4b4d73..5d6fb0f 100644 --- a/src/i18n/locales/pt-BR.ts +++ b/src/i18n/locales/pt-BR.ts @@ -649,6 +649,21 @@ export default { 'Enter Password': 'Digite a senha', Password: 'Senha', Confirm: 'Confirmar', + 'trust-filter.title': 'Filtro de pontuação de confiança', + 'trust-filter.off': 'Desativado', + 'trust-filter.low': 'Baixo', + 'trust-filter.medium': 'Médio', + 'trust-filter.high': 'Alto', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'Limite de filtro', + 'trust-filter.quick-presets': 'Predefinições rápidas', + 'trust-filter.show-all-content': 'Mostrar todo o conteúdo', + 'trust-filter.only-show-wot': + 'Mostrar apenas sua rede de confiança (seguidos + seguidos deles)', + 'trust-filter.hide-bottom-percent': + 'Filtrar os {{score}}% inferiores de usuários por classificação de confiança', + 'trust-filter.trust-score-description': + 'A pontuação de confiança classifica os usuários por percentil de reputação', 'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente' } } diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts index 6551793..43f63ff 100644 --- a/src/i18n/locales/pt-PT.ts +++ b/src/i18n/locales/pt-PT.ts @@ -652,6 +652,21 @@ export default { 'Enter Password': 'Introduza a palavra-passe', Password: 'Palavra-passe', Confirm: 'Confirmar', + 'trust-filter.title': 'Filtro de pontuação de confiança', + 'trust-filter.off': 'Desativado', + 'trust-filter.low': 'Baixo', + 'trust-filter.medium': 'Médio', + 'trust-filter.high': 'Alto', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'Limite de filtro', + 'trust-filter.quick-presets': 'Predefinições rápidas', + 'trust-filter.show-all-content': 'Mostrar todo o conteúdo', + 'trust-filter.only-show-wot': + 'Mostrar apenas a sua rede de confiança (seguidos + seguidos deles)', + 'trust-filter.hide-bottom-percent': + 'Filtrar os {{score}}% inferiores de utilizadores por classificação de confiança', + 'trust-filter.trust-score-description': + 'A pontuação de confiança classifica os utilizadores por percentil de reputação', 'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente' } } diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index 5fcb2da..7bc7f63 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -653,6 +653,20 @@ export default { 'Enter Password': 'Введите пароль', Password: 'Пароль', Confirm: 'Подтвердить', + 'trust-filter.title': 'Фильтр доверия', + 'trust-filter.off': 'Выкл', + 'trust-filter.low': 'Низкий', + 'trust-filter.medium': 'Средний', + 'trust-filter.high': 'Высокий', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'Порог фильтрации', + 'trust-filter.quick-presets': 'Быстрые предустановки', + 'trust-filter.show-all-content': 'Показать весь контент', + 'trust-filter.only-show-wot': 'Показывать только вашу сеть доверия (подписки + их подписки)', + 'trust-filter.hide-bottom-percent': + 'Отфильтровать нижние {{score}}% пользователей по рейтингу доверия', + 'trust-filter.trust-score-description': + 'Оценка доверия ранжирует пользователей по процентилю репутации', 'Auto-load profile pictures': 'Автозагрузка аватаров' } } diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts index c67a2c2..3d9e0ef 100644 --- a/src/i18n/locales/th.ts +++ b/src/i18n/locales/th.ts @@ -638,6 +638,21 @@ export default { 'Enter Password': 'ป้อนรหัสผ่าน', Password: 'รหัสผ่าน', Confirm: 'ยืนยัน', + 'trust-filter.title': 'ตัวกรองคะแนนความไว้วางใจ', + 'trust-filter.off': 'ปิด', + 'trust-filter.low': 'ต่ำ', + 'trust-filter.medium': 'ปานกลาง', + 'trust-filter.high': 'สูง', + 'trust-filter.wot': 'WoT', + 'trust-filter.filter-threshold': 'เกณฑ์การกรอง', + 'trust-filter.quick-presets': 'ค่าที่ตั้งไว้ล่วงหน้าแบบเร็ว', + 'trust-filter.show-all-content': 'แสดงเนื้อหาทั้งหมด', + 'trust-filter.only-show-wot': + 'แสดงเฉพาะเครือข่ายความไว้วางใจของคุณ (ผู้ติดตาม + ผู้ติดตามของพวกเขา)', + 'trust-filter.hide-bottom-percent': + 'กรอง {{score}}% ล่างสุดของผู้ใช้ตามอันดับความไว้วางใจ', + 'trust-filter.trust-score-description': + 'คะแนนความไว้วางใจจัดอันดับผู้ใช้ตามเปอร์เซ็นไทล์ชื่อเสียง', 'Auto-load profile pictures': 'โหลดรูปโปรไฟล์อัตโนมัติ' } } diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index ae05086..e78c896 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -624,6 +624,18 @@ export default { 'Enter Password': '輸入密碼', Password: '密碼', Confirm: '確認', + 'trust-filter.title': '信任分數過濾器', + 'trust-filter.off': '關閉', + 'trust-filter.low': '低', + 'trust-filter.medium': '中', + 'trust-filter.high': '高', + 'trust-filter.wot': '信任網路', + 'trust-filter.filter-threshold': '過濾閾值', + 'trust-filter.quick-presets': '快速預設', + 'trust-filter.show-all-content': '顯示所有內容', + 'trust-filter.only-show-wot': '僅顯示你的信任網路(關注的人 + 他們關注的人)', + 'trust-filter.hide-bottom-percent': '過濾掉信任度排名後 {{score}}% 的使用者', + 'trust-filter.trust-score-description': '信任分數按聲譽百分位對使用者進行排名', 'Auto-load profile pictures': '自動載入大頭照' } } diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts index 4db3eb8..5c9123d 100644 --- a/src/i18n/locales/zh.ts +++ b/src/i18n/locales/zh.ts @@ -629,6 +629,18 @@ export default { 'Enter Password': '输入密码', Password: '密码', Confirm: '确认', + 'trust-filter.title': '信任分数过滤器', + 'trust-filter.off': '关闭', + 'trust-filter.low': '低', + 'trust-filter.medium': '中', + 'trust-filter.high': '高', + 'trust-filter.wot': '信任网络', + 'trust-filter.filter-threshold': '过滤阈值', + 'trust-filter.quick-presets': '快速预设', + 'trust-filter.show-all-content': '显示所有内容', + 'trust-filter.only-show-wot': '仅显示你的信任网络(关注的人 + 他们关注的人)', + 'trust-filter.hide-bottom-percent': '过滤掉信任度排名后 {{score}}% 的用户', + 'trust-filter.trust-score-description': '信任分数按声誉百分位对用户进行排名', 'Auto-load profile pictures': '自动加载头像' } } diff --git a/src/lib/notification.ts b/src/lib/notification.ts index 024d5e1..5bceced 100644 --- a/src/lib/notification.ts +++ b/src/lib/notification.ts @@ -2,26 +2,24 @@ import { kinds, NostrEvent } from 'nostr-tools' import { isMentioningMutedUsers } from './event' import { tagNameEquals } from './tag' -export function notificationFilter( +export async function notificationFilter( event: NostrEvent, { pubkey, mutePubkeySet, hideContentMentioningMutedUsers, - hideUntrustedNotifications, - isUserTrusted + meetsMinTrustScore }: { pubkey?: string | null mutePubkeySet: Set hideContentMentioningMutedUsers?: boolean - hideUntrustedNotifications?: boolean - isUserTrusted: (pubkey: string) => boolean + meetsMinTrustScore: (pubkey: string, minScore?: number) => Promise } -): boolean { +): Promise { if ( mutePubkeySet.has(event.pubkey) || (hideContentMentioningMutedUsers && isMentioningMutedUsers(event, mutePubkeySet)) || - (hideUntrustedNotifications && !isUserTrusted(event.pubkey)) + !(await meetsMinTrustScore(event.pubkey)) ) { return false } diff --git a/src/pages/primary/ExplorePage/index.tsx b/src/pages/primary/ExplorePage/index.tsx index a43dffa..eaf9587 100644 --- a/src/pages/primary/ExplorePage/index.tsx +++ b/src/pages/primary/ExplorePage/index.tsx @@ -7,7 +7,6 @@ import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' import PrimaryPageLayout from '@/layouts/PrimaryPageLayout' import { getReplaceableEventIdentifier } from '@/lib/event' import { isLocalNetworkUrl, isOnionUrl, isWebsocketUrl } from '@/lib/url' -import { useUserTrust } from '@/providers/UserTrustProvider' import storage from '@/services/local-storage.service' import { TPageRef } from '@/types' import { Compass, Plus } from 'lucide-react' @@ -18,7 +17,6 @@ import { useTranslation } from 'react-i18next' type TExploreTabs = 'following' | 'explore' | 'reviews' const ExplorePage = forwardRef((_, ref) => { - const { hideUntrustedNotes } = useUserTrust() const [tab, setTab] = useState('explore') const topRef = useRef(null) @@ -52,7 +50,7 @@ const ExplorePage = forwardRef((_, ref) => { ) : ( ) - }, [tab, relayReviewFilterFn, hideUntrustedNotes]) + }, [tab, relayReviewFilterFn]) return (
{t('Notifications')}
- +
) } diff --git a/src/pages/secondary/EmojiPackSettingsPage/index.tsx b/src/pages/secondary/EmojiPackSettingsPage/index.tsx index 2f3a251..1d585d0 100644 --- a/src/pages/secondary/EmojiPackSettingsPage/index.tsx +++ b/src/pages/secondary/EmojiPackSettingsPage/index.tsx @@ -3,7 +3,6 @@ import NoteList from '@/components/NoteList' import Tabs from '@/components/Tabs' import { BIG_RELAY_URLS } from '@/constants' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' -import { useUserTrust } from '@/providers/UserTrustProvider' import { kinds } from 'nostr-tools' import { forwardRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -12,7 +11,6 @@ type TTab = 'my-packs' | 'explore' const EmojiPackSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { const { t } = useTranslation() - const { hideUntrustedNotes } = useUserTrust() const [tab, setTab] = useState('my-packs') return ( @@ -33,7 +31,6 @@ const EmojiPackSettingsPage = forwardRef(({ index }: { index?: number }, ref) => )} diff --git a/src/pages/secondary/GeneralSettingsPage/index.tsx b/src/pages/secondary/GeneralSettingsPage/index.tsx index 866e73d..06ed4e0 100644 --- a/src/pages/secondary/GeneralSettingsPage/index.tsx +++ b/src/pages/secondary/GeneralSettingsPage/index.tsx @@ -14,8 +14,7 @@ import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { cn, isSupportCheckConnectionType } from '@/lib/utils' import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useUserPreferences } from '@/providers/UserPreferencesProvider' -import { useUserTrust } from '@/providers/UserTrustProvider' -import { TMediaAutoLoadPolicy, TProfilePictureAutoLoadPolicy, TNsfwDisplayPolicy } from '@/types' +import { TMediaAutoLoadPolicy, TNsfwDisplayPolicy, TProfilePictureAutoLoadPolicy } from '@/types' import { SelectValue } from '@radix-ui/react-select' import { RotateCcw } from 'lucide-react' import { forwardRef, HTMLProps, useState } from 'react' @@ -36,7 +35,6 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { profilePictureAutoLoadPolicy, setProfilePictureAutoLoadPolicy } = useContentPolicy() - const { hideUntrustedNotes, updateHideUntrustedNotes } = useUserTrust() const { quickReaction, updateQuickReaction, quickReactionEmoji, updateQuickReactionEmoji } = useUserPreferences() @@ -120,16 +118,6 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { - - - -