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 { useNostr } from '@/providers/NostrProvider'
|
||||||
import { useNoteStats } from '@/providers/NoteStatsProvider'
|
import { useNoteStats } from '@/providers/NoteStatsProvider'
|
||||||
import { useNotification } from '@/providers/NotificationProvider'
|
import { useNotification } from '@/providers/NotificationProvider'
|
||||||
|
import { useUserTrust } from '@/providers/UserTrustProvider'
|
||||||
import client from '@/services/client.service'
|
import client from '@/services/client.service'
|
||||||
import { TNotificationType } from '@/types'
|
import { TNotificationType } from '@/types'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -22,6 +23,7 @@ const NotificationList = forwardRef((_, ref) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { current } = usePrimaryPage()
|
const { current } = usePrimaryPage()
|
||||||
const { pubkey } = useNostr()
|
const { pubkey } = useNostr()
|
||||||
|
const { enabled: hideUntrustedEvents, isUserTrusted } = useUserTrust()
|
||||||
const { clearNewNotifications, getNotificationsSeenAt } = useNotification()
|
const { clearNewNotifications, getNotificationsSeenAt } = useNotification()
|
||||||
const { updateNoteStatsByEvents } = useNoteStats()
|
const { updateNoteStatsByEvents } = useNoteStats()
|
||||||
const [notificationType, setNotificationType] = useState<TNotificationType>('all')
|
const [notificationType, setNotificationType] = useState<TNotificationType>('all')
|
||||||
@@ -122,7 +124,9 @@ const NotificationList = forwardRef((_, ref) => {
|
|||||||
}, [pubkey, refreshCount, filterKinds, current])
|
}, [pubkey, refreshCount, filterKinds, current])
|
||||||
|
|
||||||
useEffect(() => {
|
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)
|
const index = visibleNotifications.findIndex((event) => event.created_at <= lastReadTime)
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
setNewNotifications(visibleNotifications)
|
setNewNotifications(visibleNotifications)
|
||||||
@@ -131,7 +135,7 @@ const NotificationList = forwardRef((_, ref) => {
|
|||||||
setNewNotifications(visibleNotifications.slice(0, index))
|
setNewNotifications(visibleNotifications.slice(0, index))
|
||||||
setOldNotifications(visibleNotifications.slice(index))
|
setOldNotifications(visibleNotifications.slice(index))
|
||||||
}
|
}
|
||||||
}, [notifications, lastReadTime, showCount])
|
}, [notifications, lastReadTime, showCount, hideUntrustedEvents])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const options = {
|
const options = {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const StorageKey = {
|
|||||||
ACCOUNT_FEED_INFO_MAP: 'accountFeedInfoMap',
|
ACCOUNT_FEED_INFO_MAP: 'accountFeedInfoMap',
|
||||||
MEDIA_UPLOAD_SERVICE: 'mediaUploadService',
|
MEDIA_UPLOAD_SERVICE: 'mediaUploadService',
|
||||||
AUTOPLAY: 'autoplay',
|
AUTOPLAY: 'autoplay',
|
||||||
HIDE_UNTRUSTED_REPLIES: 'hideUntrustedReplies',
|
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents',
|
||||||
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated
|
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated
|
||||||
ACCOUNT_FOLLOW_LIST_EVENT_MAP: 'accountFollowListEventMap', // deprecated
|
ACCOUNT_FOLLOW_LIST_EVENT_MAP: 'accountFollowListEventMap', // deprecated
|
||||||
ACCOUNT_MUTE_LIST_EVENT_MAP: 'accountMuteListEventMap', // deprecated
|
ACCOUNT_MUTE_LIST_EVENT_MAP: 'accountMuteListEventMap', // deprecated
|
||||||
|
|||||||
@@ -233,6 +233,9 @@ export default {
|
|||||||
'Platinum Sponsors': 'الرعاة البلاتينيون',
|
'Platinum Sponsors': 'الرعاة البلاتينيون',
|
||||||
From: 'من',
|
From: 'من',
|
||||||
'Comment on': 'تعليق على',
|
'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',
|
'Platinum Sponsors': 'Platin-Sponsoren',
|
||||||
From: 'Von',
|
From: 'Von',
|
||||||
'Comment on': 'Kommentar zu',
|
'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',
|
'Platinum Sponsors': 'Platinum Sponsors',
|
||||||
From: 'From',
|
From: 'From',
|
||||||
'Comment on': 'Comment on',
|
'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',
|
'Platinum Sponsors': 'Patrocinadores Platino',
|
||||||
From: 'De',
|
From: 'De',
|
||||||
'Comment on': 'Comentar en',
|
'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',
|
'Platinum Sponsors': 'Sponsors Platine',
|
||||||
From: 'De',
|
From: 'De',
|
||||||
'Comment on': 'Commenter sur',
|
'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',
|
'Platinum Sponsors': 'Sponsor Platino',
|
||||||
From: 'Da',
|
From: 'Da',
|
||||||
'Comment on': 'Commenta su',
|
'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': 'プラチナスポンサー',
|
'Platinum Sponsors': 'プラチナスポンサー',
|
||||||
From: 'から',
|
From: 'から',
|
||||||
'Comment on': 'にコメント',
|
'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',
|
'Platinum Sponsors': 'Sponsorzy Platynowi',
|
||||||
From: 'Od',
|
From: 'Od',
|
||||||
'Comment on': 'Komentarz do',
|
'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',
|
'Platinum Sponsors': 'Patrocinadores Platinum',
|
||||||
From: 'Fonte',
|
From: 'Fonte',
|
||||||
'Comment on': 'Comentando',
|
'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',
|
'Platinum Sponsors': 'Patrocinadores Platinum',
|
||||||
From: 'De',
|
From: 'De',
|
||||||
'Comment on': 'Comentar em',
|
'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': 'Платиновые спонсоры',
|
'Platinum Sponsors': 'Платиновые спонсоры',
|
||||||
From: 'От',
|
From: 'От',
|
||||||
'Comment on': 'Прокомментировать',
|
'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': '白金赞助商',
|
'Platinum Sponsors': '白金赞助商',
|
||||||
From: '来自',
|
From: '来自',
|
||||||
'Comment on': '评论于',
|
'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 [language, setLanguage] = useState<TLanguage>(i18n.language as TLanguage)
|
||||||
const { themeSetting, setThemeSetting } = useTheme()
|
const { themeSetting, setThemeSetting } = useTheme()
|
||||||
const { autoplay, setAutoplay } = useAutoplay()
|
const { autoplay, setAutoplay } = useAutoplay()
|
||||||
const { enabled: hideUntrustedRepliesEnabled, updateEnabled: updateHideUntrustedRepliesEnabled } =
|
const { enabled: hideUntrustedEventsEnabled, updateEnabled: updateHideUntrustedEventsEnabled } =
|
||||||
useUserTrust()
|
useUserTrust()
|
||||||
|
|
||||||
const handleLanguageChange = (value: TLanguage) => {
|
const handleLanguageChange = (value: TLanguage) => {
|
||||||
@@ -67,16 +67,16 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
|||||||
<Switch id="autoplay" checked={autoplay} onCheckedChange={setAutoplay} />
|
<Switch id="autoplay" checked={autoplay} onCheckedChange={setAutoplay} />
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<Label htmlFor="hide-untrusted-replies" className="text-base font-normal">
|
<Label htmlFor="hide-untrusted-events" className="text-base font-normal">
|
||||||
{t('Hide replies from untrusted users')}
|
{t('Hide content from untrusted users')}
|
||||||
<div className="text-muted-foreground">
|
<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>
|
</div>
|
||||||
</Label>
|
</Label>
|
||||||
<Switch
|
<Switch
|
||||||
id="hide-untrusted-replies"
|
id="hide-untrusted-events"
|
||||||
checked={hideUntrustedRepliesEnabled}
|
checked={hideUntrustedEventsEnabled}
|
||||||
onCheckedChange={updateHideUntrustedRepliesEnabled}
|
onCheckedChange={updateHideUntrustedEventsEnabled}
|
||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { SubCloser } from 'nostr-tools/abstract-pool'
|
|||||||
import { createContext, useContext, useEffect, useRef, useState } from 'react'
|
import { createContext, useContext, useEffect, useRef, useState } from 'react'
|
||||||
import { useMuteList } from './MuteListProvider'
|
import { useMuteList } from './MuteListProvider'
|
||||||
import { useNostr } from './NostrProvider'
|
import { useNostr } from './NostrProvider'
|
||||||
|
import { useUserTrust } from './UserTrustProvider'
|
||||||
|
|
||||||
type TNotificationContext = {
|
type TNotificationContext = {
|
||||||
hasNewNotification: boolean
|
hasNewNotification: boolean
|
||||||
@@ -24,6 +25,7 @@ export const useNotification = () => {
|
|||||||
|
|
||||||
export function NotificationProvider({ children }: { children: React.ReactNode }) {
|
export function NotificationProvider({ children }: { children: React.ReactNode }) {
|
||||||
const { pubkey, notificationsSeenAt, updateNotificationsSeenAt } = useNostr()
|
const { pubkey, notificationsSeenAt, updateNotificationsSeenAt } = useNostr()
|
||||||
|
const { isUserTrusted } = useUserTrust()
|
||||||
const { mutePubkeys } = useMuteList()
|
const { mutePubkeys } = useMuteList()
|
||||||
const [newNotificationIds, setNewNotificationIds] = useState(new Set<string>())
|
const [newNotificationIds, setNewNotificationIds] = useState(new Set<string>())
|
||||||
const subCloserRef = useRef<SubCloser | null>(null)
|
const subCloserRef = useRef<SubCloser | null>(null)
|
||||||
@@ -61,7 +63,11 @@ export function NotificationProvider({ children }: { children: React.ReactNode }
|
|||||||
{
|
{
|
||||||
onevent: (evt) => {
|
onevent: (evt) => {
|
||||||
// Only show notification if not from self and not muted
|
// 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]))
|
setNewNotificationIds((prev) => new Set([...prev, evt.id]))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const wotSet = new Set<string>()
|
|||||||
|
|
||||||
export function UserTrustProvider({ children }: { children: React.ReactNode }) {
|
export function UserTrustProvider({ children }: { children: React.ReactNode }) {
|
||||||
const { pubkey: currentPubkey } = useNostr()
|
const { pubkey: currentPubkey } = useNostr()
|
||||||
const [enabled, setEnabled] = useState(storage.getHideUntrustedReplies())
|
const [enabled, setEnabled] = useState(storage.getHideUntrustedEvents())
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentPubkey) return
|
if (!currentPubkey) return
|
||||||
@@ -43,7 +43,7 @@ export function UserTrustProvider({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const updateEnabled = (enabled: boolean) => {
|
const updateEnabled = (enabled: boolean) => {
|
||||||
setEnabled(enabled)
|
setEnabled(enabled)
|
||||||
storage.setHideUntrustedReplies(enabled)
|
storage.setHideUntrustedEvents(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isUserTrusted = (pubkey: string) => {
|
const isUserTrusted = (pubkey: string) => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class LocalStorageService {
|
|||||||
private accountFeedInfoMap: Record<string, TFeedInfo | undefined> = {}
|
private accountFeedInfoMap: Record<string, TFeedInfo | undefined> = {}
|
||||||
private mediaUploadService: string = DEFAULT_NIP_96_SERVICE
|
private mediaUploadService: string = DEFAULT_NIP_96_SERVICE
|
||||||
private autoplay: boolean = true
|
private autoplay: boolean = true
|
||||||
private hideUntrustedReplies: boolean = true
|
private hideUntrustedEvents: boolean = true
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (!LocalStorageService.instance) {
|
if (!LocalStorageService.instance) {
|
||||||
@@ -93,8 +93,8 @@ class LocalStorageService {
|
|||||||
|
|
||||||
this.autoplay = window.localStorage.getItem(StorageKey.AUTOPLAY) !== 'false'
|
this.autoplay = window.localStorage.getItem(StorageKey.AUTOPLAY) !== 'false'
|
||||||
|
|
||||||
this.hideUntrustedReplies =
|
this.hideUntrustedEvents =
|
||||||
window.localStorage.getItem(StorageKey.HIDE_UNTRUSTED_REPLIES) !== 'false'
|
window.localStorage.getItem(StorageKey.HIDE_UNTRUSTED_EVENTS) !== 'false'
|
||||||
|
|
||||||
// Clean up deprecated data
|
// Clean up deprecated data
|
||||||
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
|
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
|
||||||
@@ -252,12 +252,13 @@ class LocalStorageService {
|
|||||||
window.localStorage.setItem(StorageKey.AUTOPLAY, autoplay.toString())
|
window.localStorage.setItem(StorageKey.AUTOPLAY, autoplay.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
getHideUntrustedReplies() {
|
getHideUntrustedEvents() {
|
||||||
return this.hideUntrustedReplies
|
return this.hideUntrustedEvents
|
||||||
}
|
}
|
||||||
setHideUntrustedReplies(hide: boolean) {
|
|
||||||
this.hideUntrustedReplies = hide
|
setHideUntrustedEvents(hide: boolean) {
|
||||||
window.localStorage.setItem(StorageKey.HIDE_UNTRUSTED_REPLIES, hide.toString())
|
this.hideUntrustedEvents = hide
|
||||||
|
window.localStorage.setItem(StorageKey.HIDE_UNTRUSTED_EVENTS, hide.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user