feat: support hiding indirect notifications
This commit is contained in:
parent
331811f683
commit
2cd1ae481b
27 changed files with 196 additions and 38 deletions
|
|
@ -1,13 +1,16 @@
|
||||||
import ParentNotePreview from '@/components/ParentNotePreview'
|
import ParentNotePreview from '@/components/ParentNotePreview'
|
||||||
import { NOTIFICATION_LIST_STYLE } from '@/constants'
|
import { ExtendedKind, NOTIFICATION_LIST_STYLE } from '@/constants'
|
||||||
import { getEmbeddedPubkeys, getParentStuff } from '@/lib/event'
|
import { getEmbeddedPubkeys, getParentStuff, getParentTag } from '@/lib/event'
|
||||||
import { toExternalContent, toNote } from '@/lib/link'
|
import { toExternalContent, toNote } from '@/lib/link'
|
||||||
|
import { generateBech32IdFromETag, tagNameEquals } from '@/lib/tag'
|
||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
|
import { useNotificationUserPreference } from '@/providers/NotificationUserPreferenceProvider'
|
||||||
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
|
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
|
||||||
|
import client from '@/services/client.service'
|
||||||
import { AtSign, MessageCircle, Quote } from 'lucide-react'
|
import { AtSign, MessageCircle, Quote } from 'lucide-react'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useMemo } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Notification from './Notification'
|
import Notification from './Notification'
|
||||||
|
|
||||||
|
|
@ -22,6 +25,7 @@ export function MentionNotification({
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
const { pubkey } = useNostr()
|
const { pubkey } = useNostr()
|
||||||
const { notificationListStyle } = useUserPreferences()
|
const { notificationListStyle } = useUserPreferences()
|
||||||
|
const { hideIndirect } = useNotificationUserPreference()
|
||||||
const isMention = useMemo(() => {
|
const isMention = useMemo(() => {
|
||||||
if (!pubkey) return false
|
if (!pubkey) return false
|
||||||
const mentions = getEmbeddedPubkeys(notification)
|
const mentions = getEmbeddedPubkeys(notification)
|
||||||
|
|
@ -30,6 +34,49 @@ export function MentionNotification({
|
||||||
const { parentEventId, parentExternalContent } = useMemo(() => {
|
const { parentEventId, parentExternalContent } = useMemo(() => {
|
||||||
return getParentStuff(notification)
|
return getParentStuff(notification)
|
||||||
}, [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 (
|
return (
|
||||||
<Notification
|
<Notification
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import Image from '@/components/Image'
|
||||||
import { useFetchEvent } from '@/hooks'
|
import { useFetchEvent } from '@/hooks'
|
||||||
import { generateBech32IdFromATag, generateBech32IdFromETag, tagNameEquals } from '@/lib/tag'
|
import { generateBech32IdFromATag, generateBech32IdFromETag, tagNameEquals } from '@/lib/tag'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
|
import { useNotificationUserPreference } from '@/providers/NotificationUserPreferenceProvider'
|
||||||
import { Heart } from 'lucide-react'
|
import { Heart } from 'lucide-react'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
@ -17,6 +18,7 @@ export function ReactionNotification({
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { pubkey } = useNostr()
|
const { pubkey } = useNostr()
|
||||||
|
const { hideIndirect } = useNotificationUserPreference()
|
||||||
const eventId = useMemo(() => {
|
const eventId = useMemo(() => {
|
||||||
const aTag = notification.tags.findLast(tagNameEquals('a'))
|
const aTag = notification.tags.findLast(tagNameEquals('a'))
|
||||||
if (aTag) {
|
if (aTag) {
|
||||||
|
|
@ -56,6 +58,9 @@ export function ReactionNotification({
|
||||||
if (!event || !eventId || !reaction) {
|
if (!event || !eventId || !reaction) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
if (hideIndirect && event.pubkey !== pubkey) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Notification
|
<Notification
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
|
import { useNotificationUserPreference } from '@/providers/NotificationUserPreferenceProvider'
|
||||||
import client from '@/services/client.service'
|
import client from '@/services/client.service'
|
||||||
import { Repeat } from 'lucide-react'
|
import { Repeat } from 'lucide-react'
|
||||||
import { Event, validateEvent } from 'nostr-tools'
|
import { Event, validateEvent } from 'nostr-tools'
|
||||||
|
|
@ -13,6 +15,8 @@ export function RepostNotification({
|
||||||
isNew?: boolean
|
isNew?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { pubkey } = useNostr()
|
||||||
|
const { hideIndirect } = useNotificationUserPreference()
|
||||||
const event = useMemo(() => {
|
const event = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
const event = JSON.parse(notification.content) as Event
|
const event = JSON.parse(notification.content) as Event
|
||||||
|
|
@ -25,6 +29,9 @@ export function RepostNotification({
|
||||||
}
|
}
|
||||||
}, [notification.content])
|
}, [notification.content])
|
||||||
if (!event) return null
|
if (!event) return null
|
||||||
|
if (hideIndirect && event.pubkey !== pubkey) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Notification
|
<Notification
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ExtendedKind, NOTIFICATION_LIST_STYLE } from '@/constants'
|
import { ExtendedKind, NOTIFICATION_LIST_STYLE, SPECIAL_TRUST_SCORE_FILTER_ID } from '@/constants'
|
||||||
import { useInfiniteScroll } from '@/hooks'
|
import { useInfiniteScroll } from '@/hooks'
|
||||||
import { compareEvents } from '@/lib/event'
|
import { compareEvents } from '@/lib/event'
|
||||||
import { getDefaultRelayUrls } from '@/lib/relay'
|
import { getDefaultRelayUrls } from '@/lib/relay'
|
||||||
|
|
@ -28,6 +28,7 @@ import PullToRefresh from 'react-simple-pull-to-refresh'
|
||||||
import { LoadingBar } from '../LoadingBar'
|
import { LoadingBar } from '../LoadingBar'
|
||||||
import { RefreshButton } from '../RefreshButton'
|
import { RefreshButton } from '../RefreshButton'
|
||||||
import Tabs from '../Tabs'
|
import Tabs from '../Tabs'
|
||||||
|
import TrustScoreFilter from '../TrustScoreFilter'
|
||||||
import { NotificationItem } from './NotificationItem'
|
import { NotificationItem } from './NotificationItem'
|
||||||
import { NotificationSkeleton } from './NotificationItem/Notification'
|
import { NotificationSkeleton } from './NotificationItem/Notification'
|
||||||
|
|
||||||
|
|
@ -280,7 +281,12 @@ const NotificationList = forwardRef((_, ref) => {
|
||||||
setShowCount(SHOW_COUNT)
|
setShowCount(SHOW_COUNT)
|
||||||
setNotificationType(type as TNotificationType)
|
setNotificationType(type as TNotificationType)
|
||||||
}}
|
}}
|
||||||
options={!supportTouch ? <RefreshButton onClick={() => refresh()} /> : null}
|
options={
|
||||||
|
<>
|
||||||
|
{!supportTouch ? <RefreshButton onClick={() => refresh()} /> : null}
|
||||||
|
<TrustScoreFilter filterId={SPECIAL_TRUST_SCORE_FILTER_ID.NOTIFICATIONS} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div ref={topRef} className="scroll-mt-[calc(6rem+1px)]" />
|
<div ref={topRef} className="scroll-mt-[calc(6rem+1px)]" />
|
||||||
{supportTouch ? (
|
{supportTouch ? (
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ export const StorageKey = {
|
||||||
MUTED_WORDS: 'mutedWords',
|
MUTED_WORDS: 'mutedWords',
|
||||||
MIN_TRUST_SCORE: 'minTrustScore',
|
MIN_TRUST_SCORE: 'minTrustScore',
|
||||||
MIN_TRUST_SCORE_MAP: 'minTrustScoreMap',
|
MIN_TRUST_SCORE_MAP: 'minTrustScoreMap',
|
||||||
|
HIDE_INDIRECT_NOTIFICATIONS: 'hideIndirectNotifications',
|
||||||
ENABLE_LIVE_FEED: 'enableLiveFeed', // deprecated
|
ENABLE_LIVE_FEED: 'enableLiveFeed', // deprecated
|
||||||
HIDE_UNTRUSTED_NOTES: 'hideUntrustedNotes', // deprecated
|
HIDE_UNTRUSTED_NOTES: 'hideUntrustedNotes', // deprecated
|
||||||
HIDE_UNTRUSTED_INTERACTIONS: 'hideUntrustedInteractions', // deprecated
|
HIDE_UNTRUSTED_INTERACTIONS: 'hideUntrustedInteractions', // deprecated
|
||||||
|
|
|
||||||
|
|
@ -665,6 +665,7 @@ export default {
|
||||||
'Zap Details': 'تفاصيل Zap',
|
'Zap Details': 'تفاصيل Zap',
|
||||||
'Default trust score filter threshold ({{n}}%)': 'عتبة مرشح درجة الثقة الافتراضية ({{n}}%)',
|
'Default trust score filter threshold ({{n}}%)': 'عتبة مرشح درجة الثقة الافتراضية ({{n}}%)',
|
||||||
'No notes found': 'لم يتم العثور على ملاحظات',
|
'No notes found': 'لم يتم العثور على ملاحظات',
|
||||||
'Try again later or check your connection': 'حاول مرة أخرى لاحقًا أو تحقق من اتصالك'
|
'Try again later or check your connection': 'حاول مرة أخرى لاحقًا أو تحقق من اتصالك',
|
||||||
|
'Hide indirect': 'إخفاء غير المباشرة'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -688,6 +688,8 @@ export default {
|
||||||
'Default trust score filter threshold ({{n}}%)':
|
'Default trust score filter threshold ({{n}}%)':
|
||||||
'Standard-Vertrauenswert-Filter-Schwelle ({{n}}%)',
|
'Standard-Vertrauenswert-Filter-Schwelle ({{n}}%)',
|
||||||
'No notes found': 'Keine Notizen gefunden',
|
'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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -668,8 +668,10 @@ export default {
|
||||||
'Muted words': 'Muted words',
|
'Muted words': 'Muted words',
|
||||||
'Add muted word': 'Add muted word',
|
'Add muted word': 'Add muted word',
|
||||||
'Zap Details': 'Zap Details',
|
'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',
|
'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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -682,6 +682,7 @@ export default {
|
||||||
'Default trust score filter threshold ({{n}}%)':
|
'Default trust score filter threshold ({{n}}%)':
|
||||||
'Umbral predeterminado del filtro de puntuación de confianza ({{n}}%)',
|
'Umbral predeterminado del filtro de puntuación de confianza ({{n}}%)',
|
||||||
'No notes found': 'No se encontraron notas',
|
'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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -676,6 +676,8 @@ export default {
|
||||||
'Zap Details': 'جزئیات زپ',
|
'Zap Details': 'جزئیات زپ',
|
||||||
'Default trust score filter threshold ({{n}}%)': 'آستانه فیلتر امتیاز اعتماد پیشفرض ({{n}}%)',
|
'Default trust score filter threshold ({{n}}%)': 'آستانه فیلتر امتیاز اعتماد پیشفرض ({{n}}%)',
|
||||||
'No notes found': 'یادداشتی یافت نشد',
|
'No notes found': 'یادداشتی یافت نشد',
|
||||||
'Try again later or check your connection': 'بعداً دوباره امتحان کنید یا اتصال خود را بررسی کنید'
|
'Try again later or check your connection':
|
||||||
|
'بعداً دوباره امتحان کنید یا اتصال خود را بررسی کنید',
|
||||||
|
'Hide indirect': 'پنهان کردن غیرمستقیم'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -686,6 +686,7 @@ export default {
|
||||||
'Default trust score filter threshold ({{n}}%)':
|
'Default trust score filter threshold ({{n}}%)':
|
||||||
'Seuil par défaut du filtre de score de confiance ({{n}}%)',
|
'Seuil par défaut du filtre de score de confiance ({{n}}%)',
|
||||||
'No notes found': 'Aucune note trouvée',
|
'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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -677,6 +677,7 @@ export default {
|
||||||
'Zap Details': 'जैप विवरण',
|
'Zap Details': 'जैप विवरण',
|
||||||
'Default trust score filter threshold ({{n}}%)': 'डिफ़ॉल्ट विश्वास स्कोर फ़िल्टर सीमा ({{n}}%)',
|
'Default trust score filter threshold ({{n}}%)': 'डिफ़ॉल्ट विश्वास स्कोर फ़िल्टर सीमा ({{n}}%)',
|
||||||
'No notes found': 'कोई नोट्स नहीं मिले',
|
'No notes found': 'कोई नोट्स नहीं मिले',
|
||||||
'Try again later or check your connection': 'बाद में पुनः प्रयास करें या अपना कनेक्शन जाँचें'
|
'Try again later or check your connection': 'बाद में पुनः प्रयास करें या अपना कनेक्शन जाँचें',
|
||||||
|
'Hide indirect': 'अप्रत्यक्ष छुपाएं'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -671,6 +671,7 @@ export default {
|
||||||
'Default trust score filter threshold ({{n}}%)':
|
'Default trust score filter threshold ({{n}}%)':
|
||||||
'Alapértelmezett bizalmi pontszám szűrő küszöbérték ({{n}}%)',
|
'Alapértelmezett bizalmi pontszám szűrő küszöbérték ({{n}}%)',
|
||||||
'No notes found': 'Nem található jegyzet',
|
'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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -682,6 +682,7 @@ export default {
|
||||||
'Default trust score filter threshold ({{n}}%)':
|
'Default trust score filter threshold ({{n}}%)':
|
||||||
'Soglia predefinita del filtro del punteggio di fiducia ({{n}}%)',
|
'Soglia predefinita del filtro del punteggio di fiducia ({{n}}%)',
|
||||||
'No notes found': 'Nessuna nota trovata',
|
'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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -673,8 +673,11 @@ export default {
|
||||||
'Muted words': 'ミュートワード',
|
'Muted words': 'ミュートワード',
|
||||||
'Add muted word': 'ミュートワードを追加',
|
'Add muted word': 'ミュートワードを追加',
|
||||||
'Zap Details': 'Zapの詳細',
|
'Zap Details': 'Zapの詳細',
|
||||||
'Default trust score filter threshold ({{n}}%)': 'デフォルトの信頼スコアフィルター閾値 ({{n}}%)',
|
'Default trust score filter threshold ({{n}}%)':
|
||||||
|
'デフォルトの信頼スコアフィルター閾値 ({{n}}%)',
|
||||||
'No notes found': 'ノートが見つかりません',
|
'No notes found': 'ノートが見つかりません',
|
||||||
'Try again later or check your connection': '後でもう一度お試しいただくか、接続を確認してください'
|
'Try again later or check your connection':
|
||||||
|
'後でもう一度お試しいただくか、接続を確認してください',
|
||||||
|
'Hide indirect': '間接通知を非表示'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -671,6 +671,7 @@ export default {
|
||||||
'Zap Details': '잽 세부 정보',
|
'Zap Details': '잽 세부 정보',
|
||||||
'Default trust score filter threshold ({{n}}%)': '기본 신뢰 점수 필터 임계값 ({{n}}%)',
|
'Default trust score filter threshold ({{n}}%)': '기본 신뢰 점수 필터 임계값 ({{n}}%)',
|
||||||
'No notes found': '노트를 찾을 수 없습니다',
|
'No notes found': '노트를 찾을 수 없습니다',
|
||||||
'Try again later or check your connection': '나중에 다시 시도하거나 연결을 확인하세요'
|
'Try again later or check your connection': '나중에 다시 시도하거나 연결을 확인하세요',
|
||||||
|
'Hide indirect': '간접 숨기기'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -680,8 +680,10 @@ export default {
|
||||||
'Muted words': 'Wyciszone słowa',
|
'Muted words': 'Wyciszone słowa',
|
||||||
'Add muted word': 'Dodaj wyciszone słowo',
|
'Add muted word': 'Dodaj wyciszone słowo',
|
||||||
'Zap Details': 'Szczegóły zapu',
|
'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',
|
'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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -679,6 +679,8 @@ export default {
|
||||||
'Default trust score filter threshold ({{n}}%)':
|
'Default trust score filter threshold ({{n}}%)':
|
||||||
'Limite padrão do filtro de pontuação de confiança ({{n}}%)',
|
'Limite padrão do filtro de pontuação de confiança ({{n}}%)',
|
||||||
'No notes found': 'Nenhuma nota encontrada',
|
'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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -682,6 +682,8 @@ export default {
|
||||||
'Default trust score filter threshold ({{n}}%)':
|
'Default trust score filter threshold ({{n}}%)':
|
||||||
'Limite predefinido do filtro de pontuação de confiança ({{n}}%)',
|
'Limite predefinido do filtro de pontuação de confiança ({{n}}%)',
|
||||||
'No notes found': 'Nenhuma nota encontrada',
|
'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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -682,6 +682,7 @@ export default {
|
||||||
'Default trust score filter threshold ({{n}}%)':
|
'Default trust score filter threshold ({{n}}%)':
|
||||||
'Порог фильтра рейтинга доверия по умолчанию ({{n}}%)',
|
'Порог фильтра рейтинга доверия по умолчанию ({{n}}%)',
|
||||||
'No notes found': 'Заметки не найдены',
|
'No notes found': 'Заметки не найдены',
|
||||||
'Try again later or check your connection': 'Попробуйте позже или проверьте подключение'
|
'Try again later or check your connection': 'Попробуйте позже или проверьте подключение',
|
||||||
|
'Hide indirect': 'Скрыть косвенные'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -667,6 +667,7 @@ export default {
|
||||||
'Default trust score filter threshold ({{n}}%)':
|
'Default trust score filter threshold ({{n}}%)':
|
||||||
'เกณฑ์ตัวกรองคะแนนความไว้วางใจเริ่มต้น ({{n}}%)',
|
'เกณฑ์ตัวกรองคะแนนความไว้วางใจเริ่มต้น ({{n}}%)',
|
||||||
'No notes found': 'ไม่พบโน้ต',
|
'No notes found': 'ไม่พบโน้ต',
|
||||||
'Try again later or check your connection': 'ลองใหม่ภายหลังหรือตรวจสอบการเชื่อมต่อของคุณ'
|
'Try again later or check your connection': 'ลองใหม่ภายหลังหรือตรวจสอบการเชื่อมต่อของคุณ',
|
||||||
|
'Hide indirect': 'ซ่อนทางอ้อม'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -649,6 +649,7 @@ export default {
|
||||||
'Zap Details': '打閃詳情',
|
'Zap Details': '打閃詳情',
|
||||||
'Default trust score filter threshold ({{n}}%)': '預設信任分數過濾閾值 ({{n}}%)',
|
'Default trust score filter threshold ({{n}}%)': '預設信任分數過濾閾值 ({{n}}%)',
|
||||||
'No notes found': '沒有找到筆記',
|
'No notes found': '沒有找到筆記',
|
||||||
'Try again later or check your connection': '請稍後重試或檢查網路連接'
|
'Try again later or check your connection': '請稍後重試或檢查網路連接',
|
||||||
|
'Hide indirect': '隱藏間接通知'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -654,6 +654,7 @@ export default {
|
||||||
'Zap Details': '打闪详情',
|
'Zap Details': '打闪详情',
|
||||||
'Default trust score filter threshold ({{n}}%)': '默认信任分数过滤阈值 ({{n}}%)',
|
'Default trust score filter threshold ({{n}}%)': '默认信任分数过滤阈值 ({{n}}%)',
|
||||||
'No notes found': '没有找到笔记',
|
'No notes found': '没有找到笔记',
|
||||||
'Try again later or check your connection': '请稍后重试或检查网络连接'
|
'Try again later or check your connection': '请稍后重试或检查网络连接',
|
||||||
|
'Hide indirect': '隐藏间接通知'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ function NoteListPageTitlebar({
|
||||||
layoutRef?.current?.scrollToTop('smooth')
|
layoutRef?.current?.scrollToTop('smooth')
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={showRelayDetails ? 'bg-accent/50' : ''}
|
className={showRelayDetails ? 'bg-muted/40' : ''}
|
||||||
>
|
>
|
||||||
<Info />
|
<Info />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
import NotificationList from '@/components/NotificationList'
|
import NotificationList from '@/components/NotificationList'
|
||||||
import TrustScoreFilter from '@/components/TrustScoreFilter'
|
import { Button } from '@/components/ui/button'
|
||||||
import { SPECIAL_TRUST_SCORE_FILTER_ID } from '@/constants'
|
|
||||||
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
import { usePrimaryPage } from '@/PageManager'
|
import { usePrimaryPage } from '@/PageManager'
|
||||||
|
import {
|
||||||
|
NotificationUserPreferenceContext,
|
||||||
|
useNotificationUserPreference
|
||||||
|
} from '@/providers/NotificationUserPreferenceProvider'
|
||||||
|
import localStorage from '@/services/local-storage.service'
|
||||||
import { TPageRef } from '@/types'
|
import { TPageRef } from '@/types'
|
||||||
import { Bell } from 'lucide-react'
|
import { Bell } from 'lucide-react'
|
||||||
import { forwardRef, useEffect, useRef } from 'react'
|
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const NotificationListPage = forwardRef<TPageRef>((_, ref) => {
|
const NotificationListPage = forwardRef<TPageRef>((_, ref) => {
|
||||||
const { current } = usePrimaryPage()
|
const { current } = usePrimaryPage()
|
||||||
|
const [hideIndirect, setHideIndirect] = useState(localStorage.getHideIndirectNotifications())
|
||||||
const firstRenderRef = useRef(true)
|
const firstRenderRef = useRef(true)
|
||||||
const notificationListRef = useRef<{ refresh: () => void }>(null)
|
const notificationListRef = useRef<{ refresh: () => void }>(null)
|
||||||
|
|
||||||
|
|
@ -20,15 +26,30 @@ const NotificationListPage = forwardRef<TPageRef>((_, ref) => {
|
||||||
firstRenderRef.current = false
|
firstRenderRef.current = false
|
||||||
}, [current])
|
}, [current])
|
||||||
|
|
||||||
|
const updateHideIndirect = useCallback(
|
||||||
|
(enable: boolean) => {
|
||||||
|
setHideIndirect(enable)
|
||||||
|
localStorage.setHideIndirectNotifications(enable)
|
||||||
|
},
|
||||||
|
[setHideIndirect]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PrimaryPageLayout
|
<NotificationUserPreferenceContext.Provider
|
||||||
ref={ref}
|
value={{
|
||||||
pageName="notifications"
|
hideIndirect,
|
||||||
titlebar={<NotificationListPageTitlebar />}
|
updateHideIndirect
|
||||||
displayScrollToTopButton
|
}}
|
||||||
>
|
>
|
||||||
<NotificationList ref={notificationListRef} />
|
<PrimaryPageLayout
|
||||||
</PrimaryPageLayout>
|
ref={ref}
|
||||||
|
pageName="notifications"
|
||||||
|
titlebar={<NotificationListPageTitlebar />}
|
||||||
|
displayScrollToTopButton
|
||||||
|
>
|
||||||
|
<NotificationList ref={notificationListRef} />
|
||||||
|
</PrimaryPageLayout>
|
||||||
|
</NotificationUserPreferenceContext.Provider>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
NotificationListPage.displayName = 'NotificationListPage'
|
NotificationListPage.displayName = 'NotificationListPage'
|
||||||
|
|
@ -43,7 +64,25 @@ function NotificationListPageTitlebar() {
|
||||||
<Bell />
|
<Bell />
|
||||||
<div className="text-lg font-semibold">{t('Notifications')}</div>
|
<div className="text-lg font-semibold">{t('Notifications')}</div>
|
||||||
</div>
|
</div>
|
||||||
<TrustScoreFilter filterId={SPECIAL_TRUST_SCORE_FILTER_ID.NOTIFICATIONS} />
|
<HideUnrelatedNotificationsToggle />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function HideUnrelatedNotificationsToggle() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { hideIndirect, updateHideIndirect } = useNotificationUserPreference()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className={cn(
|
||||||
|
'h-10 px-3 shrink-0 rounded-xl [&_svg]:size-5',
|
||||||
|
hideIndirect ? 'text-foreground bg-muted/40' : 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
onClick={() => updateHideIndirect(!hideIndirect)}
|
||||||
|
>
|
||||||
|
{t('Hide indirect')}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
14
src/providers/NotificationUserPreferenceProvider.tsx
Normal file
14
src/providers/NotificationUserPreferenceProvider.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { createContext, useContext } from 'react'
|
||||||
|
|
||||||
|
type TNotificationUserPreferenceContext = {
|
||||||
|
hideIndirect: boolean
|
||||||
|
updateHideIndirect: (enable: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NotificationUserPreferenceContext =
|
||||||
|
createContext<TNotificationUserPreferenceContext | null>(null)
|
||||||
|
|
||||||
|
export function useNotificationUserPreference() {
|
||||||
|
const ctx = useContext(NotificationUserPreferenceContext)
|
||||||
|
return ctx ?? { hideIndirect: false, updateHideIndirect: () => {} }
|
||||||
|
}
|
||||||
|
|
@ -68,6 +68,7 @@ class LocalStorageService {
|
||||||
private mutedWords: string[] = []
|
private mutedWords: string[] = []
|
||||||
private minTrustScore: number = 0
|
private minTrustScore: number = 0
|
||||||
private minTrustScoreMap: Record<string, number> = {}
|
private minTrustScoreMap: Record<string, number> = {}
|
||||||
|
private hideIndirectNotifications: boolean = false
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (!LocalStorageService.instance) {
|
if (!LocalStorageService.instance) {
|
||||||
|
|
@ -319,6 +320,9 @@ class LocalStorageService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.hideIndirectNotifications =
|
||||||
|
window.localStorage.getItem(StorageKey.HIDE_INDIRECT_NOTIFICATIONS) === 'true'
|
||||||
|
|
||||||
// Clean up deprecated data
|
// Clean up deprecated data
|
||||||
window.localStorage.removeItem(StorageKey.PINNED_PUBKEYS)
|
window.localStorage.removeItem(StorageKey.PINNED_PUBKEYS)
|
||||||
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
|
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
|
||||||
|
|
@ -684,6 +688,15 @@ class LocalStorageService {
|
||||||
this.mutedWords = words
|
this.mutedWords = words
|
||||||
window.localStorage.setItem(StorageKey.MUTED_WORDS, JSON.stringify(this.mutedWords))
|
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()
|
const instance = new LocalStorageService()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue