feat: hide notifications from untrusted users
This commit is contained in:
@@ -5,6 +5,7 @@ import { usePrimaryPage } from '@/PageManager'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useNoteStats } from '@/providers/NoteStatsProvider'
|
||||
import { useNotification } from '@/providers/NotificationProvider'
|
||||
import { useUserTrust } from '@/providers/UserTrustProvider'
|
||||
import client from '@/services/client.service'
|
||||
import { TNotificationType } from '@/types'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -22,6 +23,7 @@ const NotificationList = forwardRef((_, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const { current } = usePrimaryPage()
|
||||
const { pubkey } = useNostr()
|
||||
const { enabled: hideUntrustedEvents, isUserTrusted } = useUserTrust()
|
||||
const { clearNewNotifications, getNotificationsSeenAt } = useNotification()
|
||||
const { updateNoteStatsByEvents } = useNoteStats()
|
||||
const [notificationType, setNotificationType] = useState<TNotificationType>('all')
|
||||
@@ -122,7 +124,9 @@ const NotificationList = forwardRef((_, ref) => {
|
||||
}, [pubkey, refreshCount, filterKinds, current])
|
||||
|
||||
useEffect(() => {
|
||||
const visibleNotifications = notifications.slice(0, showCount)
|
||||
const visibleNotifications = notifications
|
||||
.slice(0, showCount)
|
||||
.filter((event) => isUserTrusted(event.pubkey))
|
||||
const index = visibleNotifications.findIndex((event) => event.created_at <= lastReadTime)
|
||||
if (index === -1) {
|
||||
setNewNotifications(visibleNotifications)
|
||||
@@ -131,7 +135,7 @@ const NotificationList = forwardRef((_, ref) => {
|
||||
setNewNotifications(visibleNotifications.slice(0, index))
|
||||
setOldNotifications(visibleNotifications.slice(index))
|
||||
}
|
||||
}, [notifications, lastReadTime, showCount])
|
||||
}, [notifications, lastReadTime, showCount, hideUntrustedEvents])
|
||||
|
||||
useEffect(() => {
|
||||
const options = {
|
||||
|
||||
@@ -14,7 +14,7 @@ export const StorageKey = {
|
||||
ACCOUNT_FEED_INFO_MAP: 'accountFeedInfoMap',
|
||||
MEDIA_UPLOAD_SERVICE: 'mediaUploadService',
|
||||
AUTOPLAY: 'autoplay',
|
||||
HIDE_UNTRUSTED_REPLIES: 'hideUntrustedReplies',
|
||||
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents',
|
||||
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated
|
||||
ACCOUNT_FOLLOW_LIST_EVENT_MAP: 'accountFollowListEventMap', // deprecated
|
||||
ACCOUNT_MUTE_LIST_EVENT_MAP: 'accountMuteListEventMap', // deprecated
|
||||
|
||||
@@ -233,6 +233,9 @@ export default {
|
||||
'Platinum Sponsors': 'الرعاة البلاتينيون',
|
||||
From: 'من',
|
||||
'Comment on': 'تعليق على',
|
||||
'View on njump.me': 'عرض على njump.me'
|
||||
'View on njump.me': 'عرض على njump.me',
|
||||
'Hide content from untrusted users': 'إخفاء المحتوى من المستخدمين غير الموثوقين',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'فقط عرض المحتوى من المستخدمين الذين تتابعهم والمستخدمين الذين يتابعونهم'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +239,10 @@ export default {
|
||||
'Platinum Sponsors': 'Platin-Sponsoren',
|
||||
From: 'Von',
|
||||
'Comment on': 'Kommentar zu',
|
||||
'View on njump.me': 'Auf njump.me ansehen'
|
||||
'View on njump.me': 'Auf njump.me ansehen',
|
||||
'Hide content from untrusted users':
|
||||
'Inhalte von nicht vertrauenswürdigen Benutzern ausblenden',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'Nur Inhalte von Benutzern anzeigen, denen du folgst und die sie folgen'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +233,9 @@ export default {
|
||||
'Platinum Sponsors': 'Platinum Sponsors',
|
||||
From: 'From',
|
||||
'Comment on': 'Comment on',
|
||||
'View on njump.me': 'View on njump.me'
|
||||
'View on njump.me': 'View on njump.me',
|
||||
'Hide content from untrusted users': 'Hide content from untrusted users',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'Only show content from your followed users and the users they follow'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +238,9 @@ export default {
|
||||
'Platinum Sponsors': 'Patrocinadores Platino',
|
||||
From: 'De',
|
||||
'Comment on': 'Comentar en',
|
||||
'View on njump.me': 'Ver en njump.me'
|
||||
'View on njump.me': 'Ver en njump.me',
|
||||
'Hide content from untrusted users': 'Ocultar contenido de usuarios no confiables',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'Solo mostrar contenido de tus usuarios seguidos y los usuarios que ellos siguen'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +238,9 @@ export default {
|
||||
'Platinum Sponsors': 'Sponsors Platine',
|
||||
From: 'De',
|
||||
'Comment on': 'Commenter sur',
|
||||
'View on njump.me': 'Voir sur njump.me'
|
||||
'View on njump.me': 'Voir sur njump.me',
|
||||
'Hide content from untrusted users': 'Hider le contenu des utilisateurs non fiables',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'Afficher uniquement le contenu de vos utilisateurs suivis et des utilisateurs qu’ils suivent'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +237,9 @@ export default {
|
||||
'Platinum Sponsors': 'Sponsor Platino',
|
||||
From: 'Da',
|
||||
'Comment on': 'Commenta su',
|
||||
'View on njump.me': 'Visualizza su njump.me'
|
||||
'View on njump.me': 'Visualizza su njump.me',
|
||||
'Hide content from untrusted users': 'Nascondi contenuti da utenti non fidati',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'Mostra solo contenuti dai tuoi utenti seguiti e dagli utenti che seguono'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +234,9 @@ export default {
|
||||
'Platinum Sponsors': 'プラチナスポンサー',
|
||||
From: 'から',
|
||||
'Comment on': 'にコメント',
|
||||
'View on njump.me': 'njump.meで表示'
|
||||
'View on njump.me': 'njump.meで表示',
|
||||
'Hide content from untrusted users': '信頼できないユーザーのコンテンツを非表示',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'フォローしているユーザーとそのユーザーがフォローしているユーザーのコンテンツのみを表示'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +236,9 @@ export default {
|
||||
'Platinum Sponsors': 'Sponsorzy Platynowi',
|
||||
From: 'Od',
|
||||
'Comment on': 'Komentarz do',
|
||||
'View on njump.me': 'Zobacz na njump.me'
|
||||
'View on njump.me': 'Zobacz na njump.me',
|
||||
'Hide content from untrusted users': 'Ukryj treści od nieznanych użytkowników',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'Pokaż tylko treści od użytkowników, których obserwujesz i ich obserwowanych'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +236,9 @@ export default {
|
||||
'Platinum Sponsors': 'Patrocinadores Platinum',
|
||||
From: 'Fonte',
|
||||
'Comment on': 'Comentando',
|
||||
'View on njump.me': 'Ver em njump.me'
|
||||
'View on njump.me': 'Ver em njump.me',
|
||||
'Hide content from untrusted users': 'Ocultar conteúdo de usuários não confiáveis',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'Mostrar apenas conteúdo dos usuários que você segue e dos usuários que eles seguem'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +237,9 @@ export default {
|
||||
'Platinum Sponsors': 'Patrocinadores Platinum',
|
||||
From: 'De',
|
||||
'Comment on': 'Comentar em',
|
||||
'View on njump.me': 'Ver em njump.me'
|
||||
'View on njump.me': 'Ver em njump.me',
|
||||
'Hide content from untrusted users': 'Esconder conteúdo de usuários não confiáveis',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'Mostrar apenas conteúdo dos usuários que você segue e dos usuários que eles seguem'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +237,9 @@ export default {
|
||||
'Platinum Sponsors': 'Платиновые спонсоры',
|
||||
From: 'От',
|
||||
'Comment on': 'Прокомментировать',
|
||||
'View on njump.me': 'Посмотреть на njump.me'
|
||||
'View on njump.me': 'Посмотреть на njump.me',
|
||||
'Hide content from untrusted users': 'Скрыть контент от недоверенных пользователей',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'Показывать только контент от пользователей, на которых вы подписаны, и от пользователей, на которых они подписаны'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +234,9 @@ export default {
|
||||
'Platinum Sponsors': '白金赞助商',
|
||||
From: '来自',
|
||||
'Comment on': '评论于',
|
||||
'View on njump.me': '在 njump.me 上查看'
|
||||
'View on njump.me': '在 njump.me 上查看',
|
||||
'Hide content from untrusted users': '隐藏不受信任用户的内容',
|
||||
'Only show content from your followed users and the users they follow':
|
||||
'仅显示您关注的用户及其关注的用户的内容'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||
const [language, setLanguage] = useState<TLanguage>(i18n.language as TLanguage)
|
||||
const { themeSetting, setThemeSetting } = useTheme()
|
||||
const { autoplay, setAutoplay } = useAutoplay()
|
||||
const { enabled: hideUntrustedRepliesEnabled, updateEnabled: updateHideUntrustedRepliesEnabled } =
|
||||
const { enabled: hideUntrustedEventsEnabled, updateEnabled: updateHideUntrustedEventsEnabled } =
|
||||
useUserTrust()
|
||||
|
||||
const handleLanguageChange = (value: TLanguage) => {
|
||||
@@ -67,16 +67,16 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||
<Switch id="autoplay" checked={autoplay} onCheckedChange={setAutoplay} />
|
||||
</SettingItem>
|
||||
<SettingItem>
|
||||
<Label htmlFor="hide-untrusted-replies" className="text-base font-normal">
|
||||
{t('Hide replies from untrusted users')}
|
||||
<Label htmlFor="hide-untrusted-events" className="text-base font-normal">
|
||||
{t('Hide content from untrusted users')}
|
||||
<div className="text-muted-foreground">
|
||||
{t('Only show replies from your followed users and the users they follow')}
|
||||
{t('Only show content from your followed users and the users they follow')}
|
||||
</div>
|
||||
</Label>
|
||||
<Switch
|
||||
id="hide-untrusted-replies"
|
||||
checked={hideUntrustedRepliesEnabled}
|
||||
onCheckedChange={updateHideUntrustedRepliesEnabled}
|
||||
id="hide-untrusted-events"
|
||||
checked={hideUntrustedEventsEnabled}
|
||||
onCheckedChange={updateHideUntrustedEventsEnabled}
|
||||
/>
|
||||
</SettingItem>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SubCloser } from 'nostr-tools/abstract-pool'
|
||||
import { createContext, useContext, useEffect, useRef, useState } from 'react'
|
||||
import { useMuteList } from './MuteListProvider'
|
||||
import { useNostr } from './NostrProvider'
|
||||
import { useUserTrust } from './UserTrustProvider'
|
||||
|
||||
type TNotificationContext = {
|
||||
hasNewNotification: boolean
|
||||
@@ -24,6 +25,7 @@ export const useNotification = () => {
|
||||
|
||||
export function NotificationProvider({ children }: { children: React.ReactNode }) {
|
||||
const { pubkey, notificationsSeenAt, updateNotificationsSeenAt } = useNostr()
|
||||
const { isUserTrusted } = useUserTrust()
|
||||
const { mutePubkeys } = useMuteList()
|
||||
const [newNotificationIds, setNewNotificationIds] = useState(new Set<string>())
|
||||
const subCloserRef = useRef<SubCloser | null>(null)
|
||||
@@ -61,7 +63,11 @@ export function NotificationProvider({ children }: { children: React.ReactNode }
|
||||
{
|
||||
onevent: (evt) => {
|
||||
// Only show notification if not from self and not muted
|
||||
if (evt.pubkey !== pubkey && !mutePubkeys.includes(evt.pubkey)) {
|
||||
if (
|
||||
evt.pubkey !== pubkey &&
|
||||
!mutePubkeys.includes(evt.pubkey) &&
|
||||
isUserTrusted(evt.pubkey)
|
||||
) {
|
||||
setNewNotificationIds((prev) => new Set([...prev, evt.id]))
|
||||
}
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ const wotSet = new Set<string>()
|
||||
|
||||
export function UserTrustProvider({ children }: { children: React.ReactNode }) {
|
||||
const { pubkey: currentPubkey } = useNostr()
|
||||
const [enabled, setEnabled] = useState(storage.getHideUntrustedReplies())
|
||||
const [enabled, setEnabled] = useState(storage.getHideUntrustedEvents())
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentPubkey) return
|
||||
@@ -43,7 +43,7 @@ export function UserTrustProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const updateEnabled = (enabled: boolean) => {
|
||||
setEnabled(enabled)
|
||||
storage.setHideUntrustedReplies(enabled)
|
||||
storage.setHideUntrustedEvents(enabled)
|
||||
}
|
||||
|
||||
const isUserTrusted = (pubkey: string) => {
|
||||
|
||||
@@ -25,7 +25,7 @@ class LocalStorageService {
|
||||
private accountFeedInfoMap: Record<string, TFeedInfo | undefined> = {}
|
||||
private mediaUploadService: string = DEFAULT_NIP_96_SERVICE
|
||||
private autoplay: boolean = true
|
||||
private hideUntrustedReplies: boolean = true
|
||||
private hideUntrustedEvents: boolean = true
|
||||
|
||||
constructor() {
|
||||
if (!LocalStorageService.instance) {
|
||||
@@ -93,8 +93,8 @@ class LocalStorageService {
|
||||
|
||||
this.autoplay = window.localStorage.getItem(StorageKey.AUTOPLAY) !== 'false'
|
||||
|
||||
this.hideUntrustedReplies =
|
||||
window.localStorage.getItem(StorageKey.HIDE_UNTRUSTED_REPLIES) !== 'false'
|
||||
this.hideUntrustedEvents =
|
||||
window.localStorage.getItem(StorageKey.HIDE_UNTRUSTED_EVENTS) !== 'false'
|
||||
|
||||
// Clean up deprecated data
|
||||
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
|
||||
@@ -252,12 +252,13 @@ class LocalStorageService {
|
||||
window.localStorage.setItem(StorageKey.AUTOPLAY, autoplay.toString())
|
||||
}
|
||||
|
||||
getHideUntrustedReplies() {
|
||||
return this.hideUntrustedReplies
|
||||
getHideUntrustedEvents() {
|
||||
return this.hideUntrustedEvents
|
||||
}
|
||||
setHideUntrustedReplies(hide: boolean) {
|
||||
this.hideUntrustedReplies = hide
|
||||
window.localStorage.setItem(StorageKey.HIDE_UNTRUSTED_REPLIES, hide.toString())
|
||||
|
||||
setHideUntrustedEvents(hide: boolean) {
|
||||
this.hideUntrustedEvents = hide
|
||||
window.localStorage.setItem(StorageKey.HIDE_UNTRUSTED_EVENTS, hide.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user