diff --git a/src/components/NotificationList/NotificationItem/MentionNotification.tsx b/src/components/NotificationList/NotificationItem/MentionNotification.tsx index 69a0f6e..147aca8 100644 --- a/src/components/NotificationList/NotificationItem/MentionNotification.tsx +++ b/src/components/NotificationList/NotificationItem/MentionNotification.tsx @@ -1,13 +1,16 @@ import ParentNotePreview from '@/components/ParentNotePreview' -import { NOTIFICATION_LIST_STYLE } from '@/constants' -import { getEmbeddedPubkeys, getParentStuff } from '@/lib/event' +import { ExtendedKind, NOTIFICATION_LIST_STYLE } from '@/constants' +import { getEmbeddedPubkeys, getParentStuff, getParentTag } from '@/lib/event' import { toExternalContent, toNote } from '@/lib/link' +import { generateBech32IdFromETag, tagNameEquals } from '@/lib/tag' import { useSecondaryPage } from '@/PageManager' import { useNostr } from '@/providers/NostrProvider' +import { useNotificationUserPreference } from '@/providers/NotificationUserPreferenceProvider' import { useUserPreferences } from '@/providers/UserPreferencesProvider' +import client from '@/services/client.service' import { AtSign, MessageCircle, Quote } from 'lucide-react' import { Event } from 'nostr-tools' -import { useMemo } from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Notification from './Notification' @@ -22,6 +25,7 @@ export function MentionNotification({ const { push } = useSecondaryPage() const { pubkey } = useNostr() const { notificationListStyle } = useUserPreferences() + const { hideIndirect } = useNotificationUserPreference() const isMention = useMemo(() => { if (!pubkey) return false const mentions = getEmbeddedPubkeys(notification) @@ -30,6 +34,49 @@ export function MentionNotification({ const { parentEventId, parentExternalContent } = useMemo(() => { return getParentStuff(notification) }, [notification]) + const [isDirectMention, setIsDirectMention] = useState(false) + useEffect(() => { + const checkIsDirectMention = async () => { + if (!pubkey) return false + if (isMention) return true + if (notification.kind === ExtendedKind.POLL) return true + + if ( + notification.kind === ExtendedKind.VOICE_COMMENT || + notification.kind === ExtendedKind.COMMENT + ) { + const parentPTag = notification.tags.findLast(tagNameEquals('p')) + const parentPubkey = parentPTag?.[1] + return parentPubkey === pubkey + } + + const parentTag = getParentTag(notification) + if (parentTag?.type === 'e') { + const [, , , , parentPubkey] = parentTag.tag + if (parentPubkey) { + return parentPubkey === pubkey + } + const parentEventId = generateBech32IdFromETag(parentTag.tag) + if (!parentEventId) return false + const parentEvent = await client.fetchEvent(parentEventId) + if (parentEvent) { + return parentEvent.pubkey === pubkey + } + return false + } + if (parentTag?.type === 'a') { + const coordinate = parentTag.tag[1] + const [, parentPubkey] = coordinate.split(':') + return parentPubkey === pubkey + } + return false + } + checkIsDirectMention().then(setIsDirectMention) + }, [pubkey, notification, isMention]) + + if (hideIndirect && !isDirectMention) { + return null + } return ( { const aTag = notification.tags.findLast(tagNameEquals('a')) if (aTag) { @@ -56,6 +58,9 @@ export function ReactionNotification({ if (!event || !eventId || !reaction) { return null } + if (hideIndirect && event.pubkey !== pubkey) { + return null + } return ( { try { const event = JSON.parse(notification.content) as Event @@ -25,6 +29,9 @@ export function RepostNotification({ } }, [notification.content]) if (!event) return null + if (hideIndirect && event.pubkey !== pubkey) { + return null + } return ( { setShowCount(SHOW_COUNT) setNotificationType(type as TNotificationType) }} - options={!supportTouch ? refresh()} /> : null} + options={ + <> + {!supportTouch ? refresh()} /> : null} + + + } />
{supportTouch ? ( diff --git a/src/constants.ts b/src/constants.ts index 321d10e..1f48158 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -45,6 +45,7 @@ export const StorageKey = { MUTED_WORDS: 'mutedWords', MIN_TRUST_SCORE: 'minTrustScore', MIN_TRUST_SCORE_MAP: 'minTrustScoreMap', + HIDE_INDIRECT_NOTIFICATIONS: 'hideIndirectNotifications', ENABLE_LIVE_FEED: 'enableLiveFeed', // deprecated HIDE_UNTRUSTED_NOTES: 'hideUntrustedNotes', // deprecated HIDE_UNTRUSTED_INTERACTIONS: 'hideUntrustedInteractions', // deprecated diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index e24c978..d3f50a4 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -665,6 +665,7 @@ export default { 'Zap Details': 'تفاصيل Zap', 'Default trust score filter threshold ({{n}}%)': 'عتبة مرشح درجة الثقة الافتراضية ({{n}}%)', 'No notes found': 'لم يتم العثور على ملاحظات', - 'Try again later or check your connection': 'حاول مرة أخرى لاحقًا أو تحقق من اتصالك' + 'Try again later or check your connection': 'حاول مرة أخرى لاحقًا أو تحقق من اتصالك', + 'Hide indirect': 'إخفاء غير المباشرة' } } diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index 0e5fbc6..22bc1c7 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -688,6 +688,8 @@ export default { 'Default trust score filter threshold ({{n}}%)': 'Standard-Vertrauenswert-Filter-Schwelle ({{n}}%)', 'No notes found': 'Keine Notizen gefunden', - 'Try again later or check your connection': 'Versuchen Sie es später erneut oder überprüfen Sie Ihre Verbindung' + 'Try again later or check your connection': + 'Versuchen Sie es später erneut oder überprüfen Sie Ihre Verbindung', + 'Hide indirect': 'Indirekte ausblenden' } } diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index d2b3e9a..4777610 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -668,8 +668,10 @@ export default { 'Muted words': 'Muted words', 'Add muted word': 'Add muted word', 'Zap Details': 'Zap Details', - 'Default trust score filter threshold ({{n}}%)': 'Default trust score filter threshold ({{n}}%)', + 'Default trust score filter threshold ({{n}}%)': + 'Default trust score filter threshold ({{n}}%)', 'No notes found': 'No notes found', - 'Try again later or check your connection': 'Try again later or check your connection' + 'Try again later or check your connection': 'Try again later or check your connection', + 'Hide indirect': 'Hide indirect' } } diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index f5edb43..91be964 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -682,6 +682,7 @@ export default { 'Default trust score filter threshold ({{n}}%)': 'Umbral predeterminado del filtro de puntuación de confianza ({{n}}%)', 'No notes found': 'No se encontraron notas', - 'Try again later or check your connection': 'Inténtalo más tarde o verifica tu conexión' + 'Try again later or check your connection': 'Inténtalo más tarde o verifica tu conexión', + 'Hide indirect': 'Ocultar indirectas' } } diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts index 582a685..cb96fa0 100644 --- a/src/i18n/locales/fa.ts +++ b/src/i18n/locales/fa.ts @@ -676,6 +676,8 @@ export default { 'Zap Details': 'جزئیات زپ', 'Default trust score filter threshold ({{n}}%)': 'آستانه فیلتر امتیاز اعتماد پیش‌فرض ({{n}}%)', 'No notes found': 'یادداشتی یافت نشد', - 'Try again later or check your connection': 'بعداً دوباره امتحان کنید یا اتصال خود را بررسی کنید' + 'Try again later or check your connection': + 'بعداً دوباره امتحان کنید یا اتصال خود را بررسی کنید', + 'Hide indirect': 'پنهان کردن غیرمستقیم' } } diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index ca925a1..f018c3a 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -686,6 +686,7 @@ export default { 'Default trust score filter threshold ({{n}}%)': 'Seuil par défaut du filtre de score de confiance ({{n}}%)', 'No notes found': 'Aucune note trouvée', - 'Try again later or check your connection': 'Réessayez plus tard ou vérifiez votre connexion' + 'Try again later or check your connection': 'Réessayez plus tard ou vérifiez votre connexion', + 'Hide indirect': 'Masquer indirects' } } diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index 0d07bd3..7531958 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -677,6 +677,7 @@ export default { 'Zap Details': 'जैप विवरण', 'Default trust score filter threshold ({{n}}%)': 'डिफ़ॉल्ट विश्वास स्कोर फ़िल्टर सीमा ({{n}}%)', 'No notes found': 'कोई नोट्स नहीं मिले', - 'Try again later or check your connection': 'बाद में पुनः प्रयास करें या अपना कनेक्शन जाँचें' + 'Try again later or check your connection': 'बाद में पुनः प्रयास करें या अपना कनेक्शन जाँचें', + 'Hide indirect': 'अप्रत्यक्ष छुपाएं' } } diff --git a/src/i18n/locales/hu.ts b/src/i18n/locales/hu.ts index 66616e8..0153a1f 100644 --- a/src/i18n/locales/hu.ts +++ b/src/i18n/locales/hu.ts @@ -671,6 +671,7 @@ export default { 'Default trust score filter threshold ({{n}}%)': 'Alapértelmezett bizalmi pontszám szűrő küszöbérték ({{n}}%)', 'No notes found': 'Nem található jegyzet', - 'Try again later or check your connection': 'Próbáld újra később vagy ellenőrizd a kapcsolatot' + 'Try again later or check your connection': 'Próbáld újra később vagy ellenőrizd a kapcsolatot', + 'Hide indirect': 'Közvetettek elrejtése' } } diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts index 6b05d22..bec7d84 100644 --- a/src/i18n/locales/it.ts +++ b/src/i18n/locales/it.ts @@ -682,6 +682,7 @@ export default { 'Default trust score filter threshold ({{n}}%)': 'Soglia predefinita del filtro del punteggio di fiducia ({{n}}%)', 'No notes found': 'Nessuna nota trovata', - 'Try again later or check your connection': 'Riprova più tardi o controlla la connessione' + 'Try again later or check your connection': 'Riprova più tardi o controlla la connessione', + 'Hide indirect': 'Nascondi indirette' } } diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index 7956136..750ac80 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -673,8 +673,11 @@ export default { 'Muted words': 'ミュートワード', 'Add muted word': 'ミュートワードを追加', 'Zap Details': 'Zapの詳細', - 'Default trust score filter threshold ({{n}}%)': 'デフォルトの信頼スコアフィルター閾値 ({{n}}%)', + 'Default trust score filter threshold ({{n}}%)': + 'デフォルトの信頼スコアフィルター閾値 ({{n}}%)', 'No notes found': 'ノートが見つかりません', - 'Try again later or check your connection': '後でもう一度お試しいただくか、接続を確認してください' + 'Try again later or check your connection': + '後でもう一度お試しいただくか、接続を確認してください', + 'Hide indirect': '間接通知を非表示' } } diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index 5fbe6c1..ea9eec2 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -671,6 +671,7 @@ export default { 'Zap Details': '잽 세부 정보', 'Default trust score filter threshold ({{n}}%)': '기본 신뢰 점수 필터 임계값 ({{n}}%)', 'No notes found': '노트를 찾을 수 없습니다', - 'Try again later or check your connection': '나중에 다시 시도하거나 연결을 확인하세요' + 'Try again later or check your connection': '나중에 다시 시도하거나 연결을 확인하세요', + 'Hide indirect': '간접 숨기기' } } diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index 74a493a..d89b59e 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -680,8 +680,10 @@ export default { 'Muted words': 'Wyciszone słowa', 'Add muted word': 'Dodaj wyciszone słowo', 'Zap Details': 'Szczegóły zapu', - 'Default trust score filter threshold ({{n}}%)': 'Domyślny próg filtra wyniku zaufania ({{n}}%)', + 'Default trust score filter threshold ({{n}}%)': + 'Domyślny próg filtra wyniku zaufania ({{n}}%)', 'No notes found': 'Nie znaleziono notatek', - 'Try again later or check your connection': 'Spróbuj ponownie później lub sprawdź połączenie' + 'Try again later or check your connection': 'Spróbuj ponownie później lub sprawdź połączenie', + 'Hide indirect': 'Ukryj pośrednie' } } diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts index 4254c3a..3c134f6 100644 --- a/src/i18n/locales/pt-BR.ts +++ b/src/i18n/locales/pt-BR.ts @@ -679,6 +679,8 @@ export default { 'Default trust score filter threshold ({{n}}%)': 'Limite padrão do filtro de pontuação de confiança ({{n}}%)', 'No notes found': 'Nenhuma nota encontrada', - 'Try again later or check your connection': 'Tente novamente mais tarde ou verifique sua conexão' + 'Try again later or check your connection': + 'Tente novamente mais tarde ou verifique sua conexão', + 'Hide indirect': 'Ocultar indiretas' } } diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts index 351acbc..349e80a 100644 --- a/src/i18n/locales/pt-PT.ts +++ b/src/i18n/locales/pt-PT.ts @@ -682,6 +682,8 @@ export default { 'Default trust score filter threshold ({{n}}%)': 'Limite predefinido do filtro de pontuação de confiança ({{n}}%)', 'No notes found': 'Nenhuma nota encontrada', - 'Try again later or check your connection': 'Tente novamente mais tarde ou verifique a sua ligação' + 'Try again later or check your connection': + 'Tente novamente mais tarde ou verifique a sua ligação', + 'Hide indirect': 'Ocultar indiretas' } } diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index fbd7d33..925c74b 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -682,6 +682,7 @@ export default { 'Default trust score filter threshold ({{n}}%)': 'Порог фильтра рейтинга доверия по умолчанию ({{n}}%)', 'No notes found': 'Заметки не найдены', - 'Try again later or check your connection': 'Попробуйте позже или проверьте подключение' + 'Try again later or check your connection': 'Попробуйте позже или проверьте подключение', + 'Hide indirect': 'Скрыть косвенные' } } diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts index 2522792..2fdadd1 100644 --- a/src/i18n/locales/th.ts +++ b/src/i18n/locales/th.ts @@ -667,6 +667,7 @@ export default { 'Default trust score filter threshold ({{n}}%)': 'เกณฑ์ตัวกรองคะแนนความไว้วางใจเริ่มต้น ({{n}}%)', 'No notes found': 'ไม่พบโน้ต', - 'Try again later or check your connection': 'ลองใหม่ภายหลังหรือตรวจสอบการเชื่อมต่อของคุณ' + 'Try again later or check your connection': 'ลองใหม่ภายหลังหรือตรวจสอบการเชื่อมต่อของคุณ', + 'Hide indirect': 'ซ่อนทางอ้อม' } } diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index d99db55..956616d 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -649,6 +649,7 @@ export default { 'Zap Details': '打閃詳情', 'Default trust score filter threshold ({{n}}%)': '預設信任分數過濾閾值 ({{n}}%)', 'No notes found': '沒有找到筆記', - 'Try again later or check your connection': '請稍後重試或檢查網路連接' + 'Try again later or check your connection': '請稍後重試或檢查網路連接', + 'Hide indirect': '隱藏間接通知' } } diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts index 7be7169..b625ff8 100644 --- a/src/i18n/locales/zh.ts +++ b/src/i18n/locales/zh.ts @@ -654,6 +654,7 @@ export default { 'Zap Details': '打闪详情', 'Default trust score filter threshold ({{n}}%)': '默认信任分数过滤阈值 ({{n}}%)', 'No notes found': '没有找到笔记', - 'Try again later or check your connection': '请稍后重试或检查网络连接' + 'Try again later or check your connection': '请稍后重试或检查网络连接', + 'Hide indirect': '隐藏间接通知' } } diff --git a/src/pages/primary/NoteListPage/index.tsx b/src/pages/primary/NoteListPage/index.tsx index 5d567d0..fd4751e 100644 --- a/src/pages/primary/NoteListPage/index.tsx +++ b/src/pages/primary/NoteListPage/index.tsx @@ -127,7 +127,7 @@ function NoteListPageTitlebar({ layoutRef?.current?.scrollToTop('smooth') } }} - className={showRelayDetails ? 'bg-accent/50' : ''} + className={showRelayDetails ? 'bg-muted/40' : ''} > diff --git a/src/pages/primary/NotificationListPage/index.tsx b/src/pages/primary/NotificationListPage/index.tsx index 2cdca7a..ca52749 100644 --- a/src/pages/primary/NotificationListPage/index.tsx +++ b/src/pages/primary/NotificationListPage/index.tsx @@ -1,15 +1,21 @@ import NotificationList from '@/components/NotificationList' -import TrustScoreFilter from '@/components/TrustScoreFilter' -import { SPECIAL_TRUST_SCORE_FILTER_ID } from '@/constants' +import { Button } from '@/components/ui/button' import PrimaryPageLayout from '@/layouts/PrimaryPageLayout' +import { cn } from '@/lib/utils' import { usePrimaryPage } from '@/PageManager' +import { + NotificationUserPreferenceContext, + useNotificationUserPreference +} from '@/providers/NotificationUserPreferenceProvider' +import localStorage from '@/services/local-storage.service' import { TPageRef } from '@/types' import { Bell } from 'lucide-react' -import { forwardRef, useEffect, useRef } from 'react' +import { forwardRef, useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' const NotificationListPage = forwardRef((_, ref) => { const { current } = usePrimaryPage() + const [hideIndirect, setHideIndirect] = useState(localStorage.getHideIndirectNotifications()) const firstRenderRef = useRef(true) const notificationListRef = useRef<{ refresh: () => void }>(null) @@ -20,15 +26,30 @@ const NotificationListPage = forwardRef((_, ref) => { firstRenderRef.current = false }, [current]) + const updateHideIndirect = useCallback( + (enable: boolean) => { + setHideIndirect(enable) + localStorage.setHideIndirectNotifications(enable) + }, + [setHideIndirect] + ) + return ( - } - displayScrollToTopButton + - - + } + displayScrollToTopButton + > + + + ) }) NotificationListPage.displayName = 'NotificationListPage' @@ -43,7 +64,25 @@ function NotificationListPageTitlebar() {
{t('Notifications')}
- + ) } + +function HideUnrelatedNotificationsToggle() { + const { t } = useTranslation() + const { hideIndirect, updateHideIndirect } = useNotificationUserPreference() + + return ( + + ) +} diff --git a/src/providers/NotificationUserPreferenceProvider.tsx b/src/providers/NotificationUserPreferenceProvider.tsx new file mode 100644 index 0000000..4b2e460 --- /dev/null +++ b/src/providers/NotificationUserPreferenceProvider.tsx @@ -0,0 +1,14 @@ +import { createContext, useContext } from 'react' + +type TNotificationUserPreferenceContext = { + hideIndirect: boolean + updateHideIndirect: (enable: boolean) => void +} + +export const NotificationUserPreferenceContext = + createContext(null) + +export function useNotificationUserPreference() { + const ctx = useContext(NotificationUserPreferenceContext) + return ctx ?? { hideIndirect: false, updateHideIndirect: () => {} } +} diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts index a3a7655..d5221c3 100644 --- a/src/services/local-storage.service.ts +++ b/src/services/local-storage.service.ts @@ -68,6 +68,7 @@ class LocalStorageService { private mutedWords: string[] = [] private minTrustScore: number = 0 private minTrustScoreMap: Record = {} + private hideIndirectNotifications: boolean = false constructor() { if (!LocalStorageService.instance) { @@ -319,6 +320,9 @@ class LocalStorageService { } } + this.hideIndirectNotifications = + window.localStorage.getItem(StorageKey.HIDE_INDIRECT_NOTIFICATIONS) === 'true' + // Clean up deprecated data window.localStorage.removeItem(StorageKey.PINNED_PUBKEYS) window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP) @@ -684,6 +688,15 @@ class LocalStorageService { this.mutedWords = words window.localStorage.setItem(StorageKey.MUTED_WORDS, JSON.stringify(this.mutedWords)) } + + getHideIndirectNotifications() { + return this.hideIndirectNotifications + } + + setHideIndirectNotifications(onlyShow: boolean) { + this.hideIndirectNotifications = onlyShow + window.localStorage.setItem(StorageKey.HIDE_INDIRECT_NOTIFICATIONS, onlyShow.toString()) + } } const instance = new LocalStorageService()